commit 4fb101c5db8862fb69943159b3802c99df9de507 Author: Viktoria Polyakova Date: Sun Jan 25 08:57:38 2026 +0000 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/backend/.eslintrc.js.old b/backend/.eslintrc.js.old new file mode 100644 index 0000000..4e5bb3b --- /dev/null +++ b/backend/.eslintrc.js.old @@ -0,0 +1,30 @@ +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + plugins: ['@typescript-eslint/eslint-plugin'], + extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'], + root: true, + env: { + node: true, + jest: true, + }, + ignorePatterns: ['.eslintrc.js'], + rules: { + '@typescript-eslint/consistent-type-imports': [ + 'warn', + { + prefer: 'type-imports', + fixStyle: 'inline-type-imports', + }, + ], + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'max-len': ['error', 120, 2], + }, +}; diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..9bd728e --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,45 @@ +# compiled output +/dist +/node_modules + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.run +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode + +# Http request env +artifacts/_environment-config/http-client.env.json + +# Environment configuration +.env.local + +# Yarn +.yarn/cache/ +.yarn/unplugged/ +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* \ No newline at end of file diff --git a/backend/.gitlab-ci.yml b/backend/.gitlab-ci.yml new file mode 100644 index 0000000..ea0a9a6 --- /dev/null +++ b/backend/.gitlab-ci.yml @@ -0,0 +1,145 @@ +stages: + - prepare + - build + - deploy + +default: + image: kroniak/ssh-client + interruptible: true + before_script: + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config + - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa + - chmod 400 ~/.ssh/id_rsa + +.prepare_script: &prepare_script + - tar -czf sources.tar.gz . + - scp -r sources.tar.gz $USER@$SERVER:$FOLDER + +.build_script: &build_script + - ssh $USER@$SERVER "cd $FOLDER && ./build.sh" + +.deploy_script: &deploy_script + - ssh $USER@$SERVER "cd $FOLDER && ./deploy.sh" + +.staging_job: + rules: + - if: $CI_COMMIT_BRANCH == "develop" + variables: + SSH_PRIVATE_KEY: $STAGING_SSH_PRIVATE_KEY + USER: $STAGING_USER + SERVER: $STAGING_SERVER + FOLDER: /amwork/backend + environment: + name: staging + url: https://amwork.dev + +.amwork_job: + rules: + - if: $CI_COMMIT_BRANCH == "main" + variables: + SSH_PRIVATE_KEY: $AMWORK_PROD_SSH_PRIVATE_KEY + USER: $AMWORK_PROD_USER + SERVER: $AMWORK_PROD_SERVER + FOLDER: /amwork/backend + environment: + name: production/amwork + url: https://amwork.com + +.mywork_job: + rules: + - if: $CI_COMMIT_BRANCH == "main" + variables: + SSH_PRIVATE_KEY: $MYWORK_PROD_SSH_PRIVATE_KEY + USER: $MYWORK_PROD_USER + SERVER: $MYWORK_PROD_SERVER + FOLDER: /mywork/backend + environment: + name: production/mywork + url: https://mywork.app + +# Staging +staging_prepare: + extends: .staging_job + stage: prepare + script: *prepare_script +staging_build: + extends: .staging_job + stage: build + variables: + GIT_STRATEGY: none + script: *build_script +staging_deploy: + extends: .staging_job + stage: deploy + variables: + GIT_STRATEGY: none + script: *deploy_script + +# Amwork Production +amwork_prepare: + extends: .amwork_job + stage: prepare + script: *prepare_script +amwork_build: + extends: .amwork_job + stage: build + variables: + GIT_STRATEGY: none + script: *build_script +amwork_deploy: + extends: .amwork_job + stage: deploy + when: manual + variables: + GIT_STRATEGY: none + script: *deploy_script + +# Mywork Production +mywork_prepare: + extends: .mywork_job + stage: prepare + variables: + FOLDER: /apps/mywork/backend + script: *prepare_script +mywork_build: + extends: .mywork_job + stage: build + variables: + GIT_STRATEGY: none + FOLDER: /apps/mywork/backend + script: *build_script +mywork_deploy: + extends: .mywork_job + stage: deploy + when: manual + variables: + GIT_STRATEGY: none + FOLDER: /apps/mywork/backend + script: *deploy_script + +# Platforma500 Production +platforma500_prepare: + extends: .mywork_job + stage: prepare + needs: [mywork_prepare] + variables: + FOLDER: /apps/platforma500/backend + script: *prepare_script +platforma500_build: + extends: .mywork_job + stage: build + needs: [mywork_build] + variables: + GIT_STRATEGY: none + FOLDER: /apps/platforma500/backend + script: *build_script +platforma500_deploy: + extends: .mywork_job + stage: deploy + when: manual + variables: + GIT_STRATEGY: none + FOLDER: /apps/platforma500/backend + script: *deploy_script \ No newline at end of file diff --git a/backend/.prettierrc b/backend/.prettierrc new file mode 100644 index 0000000..f65aabc --- /dev/null +++ b/backend/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "printWidth": 120 +} \ No newline at end of file diff --git a/backend/.yarn/releases/yarn-4.9.1.cjs b/backend/.yarn/releases/yarn-4.9.1.cjs new file mode 100644 index 0000000..657026d --- /dev/null +++ b/backend/.yarn/releases/yarn-4.9.1.cjs @@ -0,0 +1,948 @@ +#!/usr/bin/env node +/* eslint-disable */ +//prettier-ignore +(()=>{var u7e=Object.create;var D_=Object.defineProperty;var f7e=Object.getOwnPropertyDescriptor;var A7e=Object.getOwnPropertyNames;var p7e=Object.getPrototypeOf,h7e=Object.prototype.hasOwnProperty;var ye=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var It=(t,e)=>()=>(t&&(e=t(t=0)),e);var L=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),Vt=(t,e)=>{for(var r in e)D_(t,r,{get:e[r],enumerable:!0})},g7e=(t,e,r,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of A7e(e))!h7e.call(t,a)&&a!==r&&D_(t,a,{get:()=>e[a],enumerable:!(s=f7e(e,a))||s.enumerable});return t};var et=(t,e,r)=>(r=t!=null?u7e(p7e(t)):{},g7e(e||!t||!t.__esModule?D_(r,"default",{value:t,enumerable:!0}):r,t));var ui={};Vt(ui,{SAFE_TIME:()=>P$,S_IFDIR:()=>lx,S_IFLNK:()=>cx,S_IFMT:()=>_f,S_IFREG:()=>M2});var _f,lx,M2,cx,P$,x$=It(()=>{_f=61440,lx=16384,M2=32768,cx=40960,P$=456789e3});var or={};Vt(or,{EBADF:()=>Uo,EBUSY:()=>d7e,EEXIST:()=>w7e,EINVAL:()=>y7e,EISDIR:()=>C7e,ENOENT:()=>E7e,ENOSYS:()=>m7e,ENOTDIR:()=>I7e,ENOTEMPTY:()=>v7e,EOPNOTSUPP:()=>S7e,EROFS:()=>B7e,ERR_DIR_CLOSED:()=>b_});function wc(t,e){return Object.assign(new Error(`${t}: ${e}`),{code:t})}function d7e(t){return wc("EBUSY",t)}function m7e(t,e){return wc("ENOSYS",`${t}, ${e}`)}function y7e(t){return wc("EINVAL",`invalid argument, ${t}`)}function Uo(t){return wc("EBADF",`bad file descriptor, ${t}`)}function E7e(t){return wc("ENOENT",`no such file or directory, ${t}`)}function I7e(t){return wc("ENOTDIR",`not a directory, ${t}`)}function C7e(t){return wc("EISDIR",`illegal operation on a directory, ${t}`)}function w7e(t){return wc("EEXIST",`file already exists, ${t}`)}function B7e(t){return wc("EROFS",`read-only filesystem, ${t}`)}function v7e(t){return wc("ENOTEMPTY",`directory not empty, ${t}`)}function S7e(t){return wc("EOPNOTSUPP",`operation not supported, ${t}`)}function b_(){return wc("ERR_DIR_CLOSED","Directory handle was closed")}var ux=It(()=>{});var el={};Vt(el,{BigIntStatsEntry:()=>iE,DEFAULT_MODE:()=>k_,DirEntry:()=>P_,StatEntry:()=>nE,areStatsEqual:()=>Q_,clearStats:()=>fx,convertToBigIntStats:()=>b7e,makeDefaultStats:()=>k$,makeEmptyStats:()=>D7e});function k$(){return new nE}function D7e(){return fx(k$())}function fx(t){for(let e in t)if(Object.hasOwn(t,e)){let r=t[e];typeof r=="number"?t[e]=0:typeof r=="bigint"?t[e]=BigInt(0):x_.types.isDate(r)&&(t[e]=new Date(0))}return t}function b7e(t){let e=new iE;for(let r in t)if(Object.hasOwn(t,r)){let s=t[r];typeof s=="number"?e[r]=BigInt(s):x_.types.isDate(s)&&(e[r]=new Date(s))}return e.atimeNs=e.atimeMs*BigInt(1e6),e.mtimeNs=e.mtimeMs*BigInt(1e6),e.ctimeNs=e.ctimeMs*BigInt(1e6),e.birthtimeNs=e.birthtimeMs*BigInt(1e6),e}function Q_(t,e){if(t.atimeMs!==e.atimeMs||t.birthtimeMs!==e.birthtimeMs||t.blksize!==e.blksize||t.blocks!==e.blocks||t.ctimeMs!==e.ctimeMs||t.dev!==e.dev||t.gid!==e.gid||t.ino!==e.ino||t.isBlockDevice()!==e.isBlockDevice()||t.isCharacterDevice()!==e.isCharacterDevice()||t.isDirectory()!==e.isDirectory()||t.isFIFO()!==e.isFIFO()||t.isFile()!==e.isFile()||t.isSocket()!==e.isSocket()||t.isSymbolicLink()!==e.isSymbolicLink()||t.mode!==e.mode||t.mtimeMs!==e.mtimeMs||t.nlink!==e.nlink||t.rdev!==e.rdev||t.size!==e.size||t.uid!==e.uid)return!1;let r=t,s=e;return!(r.atimeNs!==s.atimeNs||r.mtimeNs!==s.mtimeNs||r.ctimeNs!==s.ctimeNs||r.birthtimeNs!==s.birthtimeNs)}var x_,k_,P_,nE,iE,T_=It(()=>{x_=et(ye("util")),k_=33188,P_=class{constructor(){this.name="";this.path="";this.mode=0}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},nE=class{constructor(){this.uid=0;this.gid=0;this.size=0;this.blksize=0;this.atimeMs=0;this.mtimeMs=0;this.ctimeMs=0;this.birthtimeMs=0;this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=0;this.ino=0;this.mode=k_;this.nlink=1;this.rdev=0;this.blocks=1}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&61440)===16384}isFIFO(){return!1}isFile(){return(this.mode&61440)===32768}isSocket(){return!1}isSymbolicLink(){return(this.mode&61440)===40960}},iE=class{constructor(){this.uid=BigInt(0);this.gid=BigInt(0);this.size=BigInt(0);this.blksize=BigInt(0);this.atimeMs=BigInt(0);this.mtimeMs=BigInt(0);this.ctimeMs=BigInt(0);this.birthtimeMs=BigInt(0);this.atimeNs=BigInt(0);this.mtimeNs=BigInt(0);this.ctimeNs=BigInt(0);this.birthtimeNs=BigInt(0);this.atime=new Date(0);this.mtime=new Date(0);this.ctime=new Date(0);this.birthtime=new Date(0);this.dev=BigInt(0);this.ino=BigInt(0);this.mode=BigInt(k_);this.nlink=BigInt(1);this.rdev=BigInt(0);this.blocks=BigInt(1)}isBlockDevice(){return!1}isCharacterDevice(){return!1}isDirectory(){return(this.mode&BigInt(61440))===BigInt(16384)}isFIFO(){return!1}isFile(){return(this.mode&BigInt(61440))===BigInt(32768)}isSocket(){return!1}isSymbolicLink(){return(this.mode&BigInt(61440))===BigInt(40960)}}});function T7e(t){let e,r;if(e=t.match(k7e))t=e[1];else if(r=t.match(Q7e))t=`\\\\${r[1]?".\\":""}${r[2]}`;else return t;return t.replace(/\//g,"\\")}function R7e(t){t=t.replace(/\\/g,"/");let e,r;return(e=t.match(P7e))?t=`/${e[1]}`:(r=t.match(x7e))&&(t=`/unc/${r[1]?".dot/":""}${r[2]}`),t}function Ax(t,e){return t===ue?T$(e):R_(e)}var _2,vt,Er,ue,K,Q$,P7e,x7e,k7e,Q7e,R_,T$,tl=It(()=>{_2=et(ye("path")),vt={root:"/",dot:".",parent:".."},Er={home:"~",nodeModules:"node_modules",manifest:"package.json",lockfile:"yarn.lock",virtual:"__virtual__",pnpJs:".pnp.js",pnpCjs:".pnp.cjs",pnpData:".pnp.data.json",pnpEsmLoader:".pnp.loader.mjs",rc:".yarnrc.yml",env:".env"},ue=Object.create(_2.default),K=Object.create(_2.default.posix);ue.cwd=()=>process.cwd();K.cwd=process.platform==="win32"?()=>R_(process.cwd()):process.cwd;process.platform==="win32"&&(K.resolve=(...t)=>t.length>0&&K.isAbsolute(t[0])?_2.default.posix.resolve(...t):_2.default.posix.resolve(K.cwd(),...t));Q$=function(t,e,r){return e=t.normalize(e),r=t.normalize(r),e===r?".":(e.endsWith(t.sep)||(e=e+t.sep),r.startsWith(e)?r.slice(e.length):null)};ue.contains=(t,e)=>Q$(ue,t,e);K.contains=(t,e)=>Q$(K,t,e);P7e=/^([a-zA-Z]:.*)$/,x7e=/^\/\/(\.\/)?(.*)$/,k7e=/^\/([a-zA-Z]:.*)$/,Q7e=/^\/unc\/(\.dot\/)?(.*)$/;R_=process.platform==="win32"?R7e:t=>t,T$=process.platform==="win32"?T7e:t=>t;ue.fromPortablePath=T$;ue.toPortablePath=R_});async function px(t,e){let r="0123456789abcdef";await t.mkdirPromise(e.indexPath,{recursive:!0});let s=[];for(let a of r)for(let n of r)s.push(t.mkdirPromise(t.pathUtils.join(e.indexPath,`${a}${n}`),{recursive:!0}));return await Promise.all(s),e.indexPath}async function R$(t,e,r,s,a){let n=t.pathUtils.normalize(e),c=r.pathUtils.normalize(s),f=[],p=[],{atime:h,mtime:E}=a.stableTime?{atime:md,mtime:md}:await r.lstatPromise(c);await t.mkdirpPromise(t.pathUtils.dirname(e),{utimes:[h,E]}),await F_(f,p,t,n,r,c,{...a,didParentExist:!0});for(let C of f)await C();await Promise.all(p.map(C=>C()))}async function F_(t,e,r,s,a,n,c){let f=c.didParentExist?await F$(r,s):null,p=await a.lstatPromise(n),{atime:h,mtime:E}=c.stableTime?{atime:md,mtime:md}:p,C;switch(!0){case p.isDirectory():C=await N7e(t,e,r,s,f,a,n,p,c);break;case p.isFile():C=await M7e(t,e,r,s,f,a,n,p,c);break;case p.isSymbolicLink():C=await _7e(t,e,r,s,f,a,n,p,c);break;default:throw new Error(`Unsupported file type (${p.mode})`)}return(c.linkStrategy?.type!=="HardlinkFromIndex"||!p.isFile())&&((C||f?.mtime?.getTime()!==E.getTime()||f?.atime?.getTime()!==h.getTime())&&(e.push(()=>r.lutimesPromise(s,h,E)),C=!0),(f===null||(f.mode&511)!==(p.mode&511))&&(e.push(()=>r.chmodPromise(s,p.mode&511)),C=!0)),C}async function F$(t,e){try{return await t.lstatPromise(e)}catch{return null}}async function N7e(t,e,r,s,a,n,c,f,p){if(a!==null&&!a.isDirectory())if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;let h=!1;a===null&&(t.push(async()=>{try{await r.mkdirPromise(s,{mode:f.mode})}catch(S){if(S.code!=="EEXIST")throw S}}),h=!0);let E=await n.readdirPromise(c),C=p.didParentExist&&!a?{...p,didParentExist:!1}:p;if(p.stableSort)for(let S of E.sort())await F_(t,e,r,r.pathUtils.join(s,S),n,n.pathUtils.join(c,S),C)&&(h=!0);else(await Promise.all(E.map(async P=>{await F_(t,e,r,r.pathUtils.join(s,P),n,n.pathUtils.join(c,P),C)}))).some(P=>P)&&(h=!0);return h}async function O7e(t,e,r,s,a,n,c,f,p,h){let E=await n.checksumFilePromise(c,{algorithm:"sha1"}),C=420,S=f.mode&511,P=`${E}${S!==C?S.toString(8):""}`,I=r.pathUtils.join(h.indexPath,E.slice(0,2),`${P}.dat`),R;(ce=>(ce[ce.Lock=0]="Lock",ce[ce.Rename=1]="Rename"))(R||={});let N=1,U=await F$(r,I);if(a){let ie=U&&a.dev===U.dev&&a.ino===U.ino,Ae=U?.mtimeMs!==F7e;if(ie&&Ae&&h.autoRepair&&(N=0,U=null),!ie)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1}let W=!U&&N===1?`${I}.${Math.floor(Math.random()*4294967296).toString(16).padStart(8,"0")}`:null,te=!1;return t.push(async()=>{if(!U&&(N===0&&await r.lockPromise(I,async()=>{let ie=await n.readFilePromise(c);await r.writeFilePromise(I,ie)}),N===1&&W)){let ie=await n.readFilePromise(c);await r.writeFilePromise(W,ie);try{await r.linkPromise(W,I)}catch(Ae){if(Ae.code==="EEXIST")te=!0,await r.unlinkPromise(W);else throw Ae}}a||await r.linkPromise(I,s)}),e.push(async()=>{U||(await r.lutimesPromise(I,md,md),S!==C&&await r.chmodPromise(I,S)),W&&!te&&await r.unlinkPromise(W)}),!1}async function L7e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{let h=await n.readFilePromise(c);await r.writeFilePromise(s,h)}),!0}async function M7e(t,e,r,s,a,n,c,f,p){return p.linkStrategy?.type==="HardlinkFromIndex"?O7e(t,e,r,s,a,n,c,f,p,p.linkStrategy):L7e(t,e,r,s,a,n,c,f,p)}async function _7e(t,e,r,s,a,n,c,f,p){if(a!==null)if(p.overwrite)t.push(async()=>r.removePromise(s)),a=null;else return!1;return t.push(async()=>{await r.symlinkPromise(Ax(r.pathUtils,await n.readlinkPromise(c)),s)}),!0}var md,F7e,N_=It(()=>{tl();md=new Date(456789e3*1e3),F7e=md.getTime()});function hx(t,e,r,s){let a=()=>{let n=r.shift();if(typeof n>"u")return null;let c=t.pathUtils.join(e,n);return Object.assign(t.statSync(c),{name:n,path:void 0})};return new U2(e,a,s)}var U2,N$=It(()=>{ux();U2=class{constructor(e,r,s={}){this.path=e;this.nextDirent=r;this.opts=s;this.closed=!1}throwIfClosed(){if(this.closed)throw b_()}async*[Symbol.asyncIterator](){try{let e;for(;(e=await this.read())!==null;)yield e}finally{await this.close()}}read(e){let r=this.readSync();return typeof e<"u"?e(null,r):Promise.resolve(r)}readSync(){return this.throwIfClosed(),this.nextDirent()}close(e){return this.closeSync(),typeof e<"u"?e(null):Promise.resolve()}closeSync(){this.throwIfClosed(),this.opts.onClose?.(),this.closed=!0}}});function O$(t,e){if(t!==e)throw new Error(`Invalid StatWatcher status: expected '${e}', got '${t}'`)}var L$,gx,M$=It(()=>{L$=ye("events");T_();gx=class t extends L$.EventEmitter{constructor(r,s,{bigint:a=!1}={}){super();this.status="ready";this.changeListeners=new Map;this.startTimeout=null;this.fakeFs=r,this.path=s,this.bigint=a,this.lastStats=this.stat()}static create(r,s,a){let n=new t(r,s,a);return n.start(),n}start(){O$(this.status,"ready"),this.status="running",this.startTimeout=setTimeout(()=>{this.startTimeout=null,this.fakeFs.existsSync(this.path)||this.emit("change",this.lastStats,this.lastStats)},3)}stop(){O$(this.status,"running"),this.status="stopped",this.startTimeout!==null&&(clearTimeout(this.startTimeout),this.startTimeout=null),this.emit("stop")}stat(){try{return this.fakeFs.statSync(this.path,{bigint:this.bigint})}catch{let r=this.bigint?new iE:new nE;return fx(r)}}makeInterval(r){let s=setInterval(()=>{let a=this.stat(),n=this.lastStats;Q_(a,n)||(this.lastStats=a,this.emit("change",a,n))},r.interval);return r.persistent?s:s.unref()}registerChangeListener(r,s){this.addListener("change",r),this.changeListeners.set(r,this.makeInterval(s))}unregisterChangeListener(r){this.removeListener("change",r);let s=this.changeListeners.get(r);typeof s<"u"&&clearInterval(s),this.changeListeners.delete(r)}unregisterAllChangeListeners(){for(let r of this.changeListeners.keys())this.unregisterChangeListener(r)}hasChangeListeners(){return this.changeListeners.size>0}ref(){for(let r of this.changeListeners.values())r.ref();return this}unref(){for(let r of this.changeListeners.values())r.unref();return this}}});function sE(t,e,r,s){let a,n,c,f;switch(typeof r){case"function":a=!1,n=!0,c=5007,f=r;break;default:({bigint:a=!1,persistent:n=!0,interval:c=5007}=r),f=s;break}let p=dx.get(t);typeof p>"u"&&dx.set(t,p=new Map);let h=p.get(e);return typeof h>"u"&&(h=gx.create(t,e,{bigint:a}),p.set(e,h)),h.registerChangeListener(f,{persistent:n,interval:c}),h}function yd(t,e,r){let s=dx.get(t);if(typeof s>"u")return;let a=s.get(e);typeof a>"u"||(typeof r>"u"?a.unregisterAllChangeListeners():a.unregisterChangeListener(r),a.hasChangeListeners()||(a.stop(),s.delete(e)))}function Ed(t){let e=dx.get(t);if(!(typeof e>"u"))for(let r of e.keys())yd(t,r)}var dx,O_=It(()=>{M$();dx=new WeakMap});function U7e(t){let e=t.match(/\r?\n/g);if(e===null)return U$.EOL;let r=e.filter(a=>a===`\r +`).length,s=e.length-r;return r>s?`\r +`:` +`}function Id(t,e){return e.replace(/\r?\n/g,U7e(t))}var _$,U$,Ep,Uf,Cd=It(()=>{_$=ye("crypto"),U$=ye("os");N_();tl();Ep=class{constructor(e){this.pathUtils=e}async*genTraversePromise(e,{stableSort:r=!1}={}){let s=[e];for(;s.length>0;){let a=s.shift();if((await this.lstatPromise(a)).isDirectory()){let c=await this.readdirPromise(a);if(r)for(let f of c.sort())s.push(this.pathUtils.join(a,f));else throw new Error("Not supported")}else yield a}}async checksumFilePromise(e,{algorithm:r="sha512"}={}){let s=await this.openPromise(e,"r");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,_$.createHash)(r),f=0;for(;(f=await this.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest("hex")}finally{await this.closePromise(s)}}async removePromise(e,{recursive:r=!0,maxRetries:s=5}={}){let a;try{a=await this.lstatPromise(e)}catch(n){if(n.code==="ENOENT")return;throw n}if(a.isDirectory()){if(r){let n=await this.readdirPromise(e);await Promise.all(n.map(c=>this.removePromise(this.pathUtils.resolve(e,c))))}for(let n=0;n<=s;n++)try{await this.rmdirPromise(e);break}catch(c){if(c.code!=="EBUSY"&&c.code!=="ENOTEMPTY")throw c;nsetTimeout(f,n*100))}}else await this.unlinkPromise(e)}removeSync(e,{recursive:r=!0}={}){let s;try{s=this.lstatSync(e)}catch(a){if(a.code==="ENOENT")return;throw a}if(s.isDirectory()){if(r)for(let a of this.readdirSync(e))this.removeSync(this.pathUtils.resolve(e,a));this.rmdirSync(e)}else this.unlinkSync(e)}async mkdirpPromise(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{await this.mkdirPromise(f)}catch(p){if(p.code==="EEXIST")continue;throw p}if(n??=f,r!=null&&await this.chmodPromise(f,r),s!=null)await this.utimesPromise(f,s[0],s[1]);else{let p=await this.statPromise(this.pathUtils.dirname(f));await this.utimesPromise(f,p.atime,p.mtime)}}}return n}mkdirpSync(e,{chmod:r,utimes:s}={}){if(e=this.resolve(e),e===this.pathUtils.dirname(e))return;let a=e.split(this.pathUtils.sep),n;for(let c=2;c<=a.length;++c){let f=a.slice(0,c).join(this.pathUtils.sep);if(!this.existsSync(f)){try{this.mkdirSync(f)}catch(p){if(p.code==="EEXIST")continue;throw p}if(n??=f,r!=null&&this.chmodSync(f,r),s!=null)this.utimesSync(f,s[0],s[1]);else{let p=this.statSync(this.pathUtils.dirname(f));this.utimesSync(f,p.atime,p.mtime)}}}return n}async copyPromise(e,r,{baseFs:s=this,overwrite:a=!0,stableSort:n=!1,stableTime:c=!1,linkStrategy:f=null}={}){return await R$(this,e,s,r,{overwrite:a,stableSort:n,stableTime:c,linkStrategy:f})}copySync(e,r,{baseFs:s=this,overwrite:a=!0}={}){let n=s.lstatSync(r),c=this.existsSync(e);if(n.isDirectory()){this.mkdirpSync(e);let p=s.readdirSync(r);for(let h of p)this.copySync(this.pathUtils.join(e,h),s.pathUtils.join(r,h),{baseFs:s,overwrite:a})}else if(n.isFile()){if(!c||a){c&&this.removeSync(e);let p=s.readFileSync(r);this.writeFileSync(e,p)}}else if(n.isSymbolicLink()){if(!c||a){c&&this.removeSync(e);let p=s.readlinkSync(r);this.symlinkSync(Ax(this.pathUtils,p),e)}}else throw new Error(`Unsupported file type (file: ${r}, mode: 0o${n.mode.toString(8).padStart(6,"0")})`);let f=n.mode&511;this.chmodSync(e,f)}async changeFilePromise(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferPromise(e,r,s):this.changeFileTextPromise(e,r,s)}async changeFileBufferPromise(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=await this.readFilePromise(e)}catch{}Buffer.compare(a,r)!==0&&await this.writeFilePromise(e,r,{mode:s})}async changeFileTextPromise(e,r,{automaticNewlines:s,mode:a}={}){let n="";try{n=await this.readFilePromise(e,"utf8")}catch{}let c=s?Id(n,r):r;n!==c&&await this.writeFilePromise(e,c,{mode:a})}changeFileSync(e,r,s={}){return Buffer.isBuffer(r)?this.changeFileBufferSync(e,r,s):this.changeFileTextSync(e,r,s)}changeFileBufferSync(e,r,{mode:s}={}){let a=Buffer.alloc(0);try{a=this.readFileSync(e)}catch{}Buffer.compare(a,r)!==0&&this.writeFileSync(e,r,{mode:s})}changeFileTextSync(e,r,{automaticNewlines:s=!1,mode:a}={}){let n="";try{n=this.readFileSync(e,"utf8")}catch{}let c=s?Id(n,r):r;n!==c&&this.writeFileSync(e,c,{mode:a})}async movePromise(e,r){try{await this.renamePromise(e,r)}catch(s){if(s.code==="EXDEV")await this.copyPromise(r,e),await this.removePromise(e);else throw s}}moveSync(e,r){try{this.renameSync(e,r)}catch(s){if(s.code==="EXDEV")this.copySync(r,e),this.removeSync(e);else throw s}}async lockPromise(e,r){let s=`${e}.flock`,a=1e3/60,n=Date.now(),c=null,f=async()=>{let p;try{[p]=await this.readJsonPromise(s)}catch{return Date.now()-n<500}try{return process.kill(p,0),!0}catch{return!1}};for(;c===null;)try{c=await this.openPromise(s,"wx")}catch(p){if(p.code==="EEXIST"){if(!await f())try{await this.unlinkPromise(s);continue}catch{}if(Date.now()-n<60*1e3)await new Promise(h=>setTimeout(h,a));else throw new Error(`Couldn't acquire a lock in a reasonable time (via ${s})`)}else throw p}await this.writePromise(c,JSON.stringify([process.pid]));try{return await r()}finally{try{await this.closePromise(c),await this.unlinkPromise(s)}catch{}}}async readJsonPromise(e){let r=await this.readFilePromise(e,"utf8");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}readJsonSync(e){let r=this.readFileSync(e,"utf8");try{return JSON.parse(r)}catch(s){throw s.message+=` (in ${e})`,s}}async writeJsonPromise(e,r,{compact:s=!1}={}){let a=s?0:2;return await this.writeFilePromise(e,`${JSON.stringify(r,null,a)} +`)}writeJsonSync(e,r,{compact:s=!1}={}){let a=s?0:2;return this.writeFileSync(e,`${JSON.stringify(r,null,a)} +`)}async preserveTimePromise(e,r){let s=await this.lstatPromise(e),a=await r();typeof a<"u"&&(e=a),await this.lutimesPromise(e,s.atime,s.mtime)}async preserveTimeSync(e,r){let s=this.lstatSync(e),a=r();typeof a<"u"&&(e=a),this.lutimesSync(e,s.atime,s.mtime)}},Uf=class extends Ep{constructor(){super(K)}}});var Hs,Ip=It(()=>{Cd();Hs=class extends Ep{getExtractHint(e){return this.baseFs.getExtractHint(e)}resolve(e){return this.mapFromBase(this.baseFs.resolve(this.mapToBase(e)))}getRealPath(){return this.mapFromBase(this.baseFs.getRealPath())}async openPromise(e,r,s){return this.baseFs.openPromise(this.mapToBase(e),r,s)}openSync(e,r,s){return this.baseFs.openSync(this.mapToBase(e),r,s)}async opendirPromise(e,r){return Object.assign(await this.baseFs.opendirPromise(this.mapToBase(e),r),{path:e})}opendirSync(e,r){return Object.assign(this.baseFs.opendirSync(this.mapToBase(e),r),{path:e})}async readPromise(e,r,s,a,n){return await this.baseFs.readPromise(e,r,s,a,n)}readSync(e,r,s,a,n){return this.baseFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return typeof r=="string"?await this.baseFs.writePromise(e,r,s):await this.baseFs.writePromise(e,r,s,a,n)}writeSync(e,r,s,a,n){return typeof r=="string"?this.baseFs.writeSync(e,r,s):this.baseFs.writeSync(e,r,s,a,n)}async closePromise(e){return this.baseFs.closePromise(e)}closeSync(e){this.baseFs.closeSync(e)}createReadStream(e,r){return this.baseFs.createReadStream(e!==null?this.mapToBase(e):e,r)}createWriteStream(e,r){return this.baseFs.createWriteStream(e!==null?this.mapToBase(e):e,r)}async realpathPromise(e){return this.mapFromBase(await this.baseFs.realpathPromise(this.mapToBase(e)))}realpathSync(e){return this.mapFromBase(this.baseFs.realpathSync(this.mapToBase(e)))}async existsPromise(e){return this.baseFs.existsPromise(this.mapToBase(e))}existsSync(e){return this.baseFs.existsSync(this.mapToBase(e))}accessSync(e,r){return this.baseFs.accessSync(this.mapToBase(e),r)}async accessPromise(e,r){return this.baseFs.accessPromise(this.mapToBase(e),r)}async statPromise(e,r){return this.baseFs.statPromise(this.mapToBase(e),r)}statSync(e,r){return this.baseFs.statSync(this.mapToBase(e),r)}async fstatPromise(e,r){return this.baseFs.fstatPromise(e,r)}fstatSync(e,r){return this.baseFs.fstatSync(e,r)}lstatPromise(e,r){return this.baseFs.lstatPromise(this.mapToBase(e),r)}lstatSync(e,r){return this.baseFs.lstatSync(this.mapToBase(e),r)}async fchmodPromise(e,r){return this.baseFs.fchmodPromise(e,r)}fchmodSync(e,r){return this.baseFs.fchmodSync(e,r)}async chmodPromise(e,r){return this.baseFs.chmodPromise(this.mapToBase(e),r)}chmodSync(e,r){return this.baseFs.chmodSync(this.mapToBase(e),r)}async fchownPromise(e,r,s){return this.baseFs.fchownPromise(e,r,s)}fchownSync(e,r,s){return this.baseFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return this.baseFs.chownPromise(this.mapToBase(e),r,s)}chownSync(e,r,s){return this.baseFs.chownSync(this.mapToBase(e),r,s)}async renamePromise(e,r){return this.baseFs.renamePromise(this.mapToBase(e),this.mapToBase(r))}renameSync(e,r){return this.baseFs.renameSync(this.mapToBase(e),this.mapToBase(r))}async copyFilePromise(e,r,s=0){return this.baseFs.copyFilePromise(this.mapToBase(e),this.mapToBase(r),s)}copyFileSync(e,r,s=0){return this.baseFs.copyFileSync(this.mapToBase(e),this.mapToBase(r),s)}async appendFilePromise(e,r,s){return this.baseFs.appendFilePromise(this.fsMapToBase(e),r,s)}appendFileSync(e,r,s){return this.baseFs.appendFileSync(this.fsMapToBase(e),r,s)}async writeFilePromise(e,r,s){return this.baseFs.writeFilePromise(this.fsMapToBase(e),r,s)}writeFileSync(e,r,s){return this.baseFs.writeFileSync(this.fsMapToBase(e),r,s)}async unlinkPromise(e){return this.baseFs.unlinkPromise(this.mapToBase(e))}unlinkSync(e){return this.baseFs.unlinkSync(this.mapToBase(e))}async utimesPromise(e,r,s){return this.baseFs.utimesPromise(this.mapToBase(e),r,s)}utimesSync(e,r,s){return this.baseFs.utimesSync(this.mapToBase(e),r,s)}async lutimesPromise(e,r,s){return this.baseFs.lutimesPromise(this.mapToBase(e),r,s)}lutimesSync(e,r,s){return this.baseFs.lutimesSync(this.mapToBase(e),r,s)}async mkdirPromise(e,r){return this.baseFs.mkdirPromise(this.mapToBase(e),r)}mkdirSync(e,r){return this.baseFs.mkdirSync(this.mapToBase(e),r)}async rmdirPromise(e,r){return this.baseFs.rmdirPromise(this.mapToBase(e),r)}rmdirSync(e,r){return this.baseFs.rmdirSync(this.mapToBase(e),r)}async rmPromise(e,r){return this.baseFs.rmPromise(this.mapToBase(e),r)}rmSync(e,r){return this.baseFs.rmSync(this.mapToBase(e),r)}async linkPromise(e,r){return this.baseFs.linkPromise(this.mapToBase(e),this.mapToBase(r))}linkSync(e,r){return this.baseFs.linkSync(this.mapToBase(e),this.mapToBase(r))}async symlinkPromise(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkPromise(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkPromise(c,a,s)}symlinkSync(e,r,s){let a=this.mapToBase(r);if(this.pathUtils.isAbsolute(e))return this.baseFs.symlinkSync(this.mapToBase(e),a,s);let n=this.mapToBase(this.pathUtils.join(this.pathUtils.dirname(r),e)),c=this.baseFs.pathUtils.relative(this.baseFs.pathUtils.dirname(a),n);return this.baseFs.symlinkSync(c,a,s)}async readFilePromise(e,r){return this.baseFs.readFilePromise(this.fsMapToBase(e),r)}readFileSync(e,r){return this.baseFs.readFileSync(this.fsMapToBase(e),r)}readdirPromise(e,r){return this.baseFs.readdirPromise(this.mapToBase(e),r)}readdirSync(e,r){return this.baseFs.readdirSync(this.mapToBase(e),r)}async readlinkPromise(e){return this.mapFromBase(await this.baseFs.readlinkPromise(this.mapToBase(e)))}readlinkSync(e){return this.mapFromBase(this.baseFs.readlinkSync(this.mapToBase(e)))}async truncatePromise(e,r){return this.baseFs.truncatePromise(this.mapToBase(e),r)}truncateSync(e,r){return this.baseFs.truncateSync(this.mapToBase(e),r)}async ftruncatePromise(e,r){return this.baseFs.ftruncatePromise(e,r)}ftruncateSync(e,r){return this.baseFs.ftruncateSync(e,r)}watch(e,r,s){return this.baseFs.watch(this.mapToBase(e),r,s)}watchFile(e,r,s){return this.baseFs.watchFile(this.mapToBase(e),r,s)}unwatchFile(e,r){return this.baseFs.unwatchFile(this.mapToBase(e),r)}fsMapToBase(e){return typeof e=="number"?e:this.mapToBase(e)}}});var Hf,H$=It(()=>{Ip();Hf=class extends Hs{constructor(e,{baseFs:r,pathUtils:s}){super(s),this.target=e,this.baseFs=r}getRealPath(){return this.target}getBaseFs(){return this.baseFs}mapFromBase(e){return e}mapToBase(e){return e}}});function j$(t){let e=t;return typeof t.path=="string"&&(e.path=ue.toPortablePath(t.path)),e}var q$,Yn,wd=It(()=>{q$=et(ye("fs"));Cd();tl();Yn=class extends Uf{constructor(e=q$.default){super(),this.realFs=e}getExtractHint(){return!1}getRealPath(){return vt.root}resolve(e){return K.resolve(e)}async openPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.open(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}openSync(e,r,s){return this.realFs.openSync(ue.fromPortablePath(e),r,s)}async opendirPromise(e,r){return await new Promise((s,a)=>{typeof r<"u"?this.realFs.opendir(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.opendir(ue.fromPortablePath(e),this.makeCallback(s,a))}).then(s=>{let a=s;return Object.defineProperty(a,"path",{value:e,configurable:!0,writable:!0}),a})}opendirSync(e,r){let a=typeof r<"u"?this.realFs.opendirSync(ue.fromPortablePath(e),r):this.realFs.opendirSync(ue.fromPortablePath(e));return Object.defineProperty(a,"path",{value:e,configurable:!0,writable:!0}),a}async readPromise(e,r,s=0,a=0,n=-1){return await new Promise((c,f)=>{this.realFs.read(e,r,s,a,n,(p,h)=>{p?f(p):c(h)})})}readSync(e,r,s,a,n){return this.realFs.readSync(e,r,s,a,n)}async writePromise(e,r,s,a,n){return await new Promise((c,f)=>typeof r=="string"?this.realFs.write(e,r,s,this.makeCallback(c,f)):this.realFs.write(e,r,s,a,n,this.makeCallback(c,f)))}writeSync(e,r,s,a,n){return typeof r=="string"?this.realFs.writeSync(e,r,s):this.realFs.writeSync(e,r,s,a,n)}async closePromise(e){await new Promise((r,s)=>{this.realFs.close(e,this.makeCallback(r,s))})}closeSync(e){this.realFs.closeSync(e)}createReadStream(e,r){let s=e!==null?ue.fromPortablePath(e):e;return this.realFs.createReadStream(s,r)}createWriteStream(e,r){let s=e!==null?ue.fromPortablePath(e):e;return this.realFs.createWriteStream(s,r)}async realpathPromise(e){return await new Promise((r,s)=>{this.realFs.realpath(ue.fromPortablePath(e),{},this.makeCallback(r,s))}).then(r=>ue.toPortablePath(r))}realpathSync(e){return ue.toPortablePath(this.realFs.realpathSync(ue.fromPortablePath(e),{}))}async existsPromise(e){return await new Promise(r=>{this.realFs.exists(ue.fromPortablePath(e),r)})}accessSync(e,r){return this.realFs.accessSync(ue.fromPortablePath(e),r)}async accessPromise(e,r){return await new Promise((s,a)=>{this.realFs.access(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}existsSync(e){return this.realFs.existsSync(ue.fromPortablePath(e))}async statPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.stat(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.stat(ue.fromPortablePath(e),this.makeCallback(s,a))})}statSync(e,r){return r?this.realFs.statSync(ue.fromPortablePath(e),r):this.realFs.statSync(ue.fromPortablePath(e))}async fstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.fstat(e,r,this.makeCallback(s,a)):this.realFs.fstat(e,this.makeCallback(s,a))})}fstatSync(e,r){return r?this.realFs.fstatSync(e,r):this.realFs.fstatSync(e)}async lstatPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.lstat(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.lstat(ue.fromPortablePath(e),this.makeCallback(s,a))})}lstatSync(e,r){return r?this.realFs.lstatSync(ue.fromPortablePath(e),r):this.realFs.lstatSync(ue.fromPortablePath(e))}async fchmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.fchmod(e,r,this.makeCallback(s,a))})}fchmodSync(e,r){return this.realFs.fchmodSync(e,r)}async chmodPromise(e,r){return await new Promise((s,a)=>{this.realFs.chmod(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}chmodSync(e,r){return this.realFs.chmodSync(ue.fromPortablePath(e),r)}async fchownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.fchown(e,r,s,this.makeCallback(a,n))})}fchownSync(e,r,s){return this.realFs.fchownSync(e,r,s)}async chownPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.chown(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}chownSync(e,r,s){return this.realFs.chownSync(ue.fromPortablePath(e),r,s)}async renamePromise(e,r){return await new Promise((s,a)=>{this.realFs.rename(ue.fromPortablePath(e),ue.fromPortablePath(r),this.makeCallback(s,a))})}renameSync(e,r){return this.realFs.renameSync(ue.fromPortablePath(e),ue.fromPortablePath(r))}async copyFilePromise(e,r,s=0){return await new Promise((a,n)=>{this.realFs.copyFile(ue.fromPortablePath(e),ue.fromPortablePath(r),s,this.makeCallback(a,n))})}copyFileSync(e,r,s=0){return this.realFs.copyFileSync(ue.fromPortablePath(e),ue.fromPortablePath(r),s)}async appendFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e=="string"?ue.fromPortablePath(e):e;s?this.realFs.appendFile(c,r,s,this.makeCallback(a,n)):this.realFs.appendFile(c,r,this.makeCallback(a,n))})}appendFileSync(e,r,s){let a=typeof e=="string"?ue.fromPortablePath(e):e;s?this.realFs.appendFileSync(a,r,s):this.realFs.appendFileSync(a,r)}async writeFilePromise(e,r,s){return await new Promise((a,n)=>{let c=typeof e=="string"?ue.fromPortablePath(e):e;s?this.realFs.writeFile(c,r,s,this.makeCallback(a,n)):this.realFs.writeFile(c,r,this.makeCallback(a,n))})}writeFileSync(e,r,s){let a=typeof e=="string"?ue.fromPortablePath(e):e;s?this.realFs.writeFileSync(a,r,s):this.realFs.writeFileSync(a,r)}async unlinkPromise(e){return await new Promise((r,s)=>{this.realFs.unlink(ue.fromPortablePath(e),this.makeCallback(r,s))})}unlinkSync(e){return this.realFs.unlinkSync(ue.fromPortablePath(e))}async utimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.utimes(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}utimesSync(e,r,s){this.realFs.utimesSync(ue.fromPortablePath(e),r,s)}async lutimesPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.lutimes(ue.fromPortablePath(e),r,s,this.makeCallback(a,n))})}lutimesSync(e,r,s){this.realFs.lutimesSync(ue.fromPortablePath(e),r,s)}async mkdirPromise(e,r){return await new Promise((s,a)=>{this.realFs.mkdir(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}mkdirSync(e,r){return this.realFs.mkdirSync(ue.fromPortablePath(e),r)}async rmdirPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rmdir(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rmdir(ue.fromPortablePath(e),this.makeCallback(s,a))})}rmdirSync(e,r){return this.realFs.rmdirSync(ue.fromPortablePath(e),r)}async rmPromise(e,r){return await new Promise((s,a)=>{r?this.realFs.rm(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.rm(ue.fromPortablePath(e),this.makeCallback(s,a))})}rmSync(e,r){return this.realFs.rmSync(ue.fromPortablePath(e),r)}async linkPromise(e,r){return await new Promise((s,a)=>{this.realFs.link(ue.fromPortablePath(e),ue.fromPortablePath(r),this.makeCallback(s,a))})}linkSync(e,r){return this.realFs.linkSync(ue.fromPortablePath(e),ue.fromPortablePath(r))}async symlinkPromise(e,r,s){return await new Promise((a,n)=>{this.realFs.symlink(ue.fromPortablePath(e.replace(/\/+$/,"")),ue.fromPortablePath(r),s,this.makeCallback(a,n))})}symlinkSync(e,r,s){return this.realFs.symlinkSync(ue.fromPortablePath(e.replace(/\/+$/,"")),ue.fromPortablePath(r),s)}async readFilePromise(e,r){return await new Promise((s,a)=>{let n=typeof e=="string"?ue.fromPortablePath(e):e;this.realFs.readFile(n,r,this.makeCallback(s,a))})}readFileSync(e,r){let s=typeof e=="string"?ue.fromPortablePath(e):e;return this.realFs.readFileSync(s,r)}async readdirPromise(e,r){return await new Promise((s,a)=>{r?r.recursive&&process.platform==="win32"?r.withFileTypes?this.realFs.readdir(ue.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(j$)),a)):this.realFs.readdir(ue.fromPortablePath(e),r,this.makeCallback(n=>s(n.map(ue.toPortablePath)),a)):this.realFs.readdir(ue.fromPortablePath(e),r,this.makeCallback(s,a)):this.realFs.readdir(ue.fromPortablePath(e),this.makeCallback(s,a))})}readdirSync(e,r){return r?r.recursive&&process.platform==="win32"?r.withFileTypes?this.realFs.readdirSync(ue.fromPortablePath(e),r).map(j$):this.realFs.readdirSync(ue.fromPortablePath(e),r).map(ue.toPortablePath):this.realFs.readdirSync(ue.fromPortablePath(e),r):this.realFs.readdirSync(ue.fromPortablePath(e))}async readlinkPromise(e){return await new Promise((r,s)=>{this.realFs.readlink(ue.fromPortablePath(e),this.makeCallback(r,s))}).then(r=>ue.toPortablePath(r))}readlinkSync(e){return ue.toPortablePath(this.realFs.readlinkSync(ue.fromPortablePath(e)))}async truncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.truncate(ue.fromPortablePath(e),r,this.makeCallback(s,a))})}truncateSync(e,r){return this.realFs.truncateSync(ue.fromPortablePath(e),r)}async ftruncatePromise(e,r){return await new Promise((s,a)=>{this.realFs.ftruncate(e,r,this.makeCallback(s,a))})}ftruncateSync(e,r){return this.realFs.ftruncateSync(e,r)}watch(e,r,s){return this.realFs.watch(ue.fromPortablePath(e),r,s)}watchFile(e,r,s){return this.realFs.watchFile(ue.fromPortablePath(e),r,s)}unwatchFile(e,r){return this.realFs.unwatchFile(ue.fromPortablePath(e),r)}makeCallback(e,r){return(s,a)=>{s?r(s):e(a)}}}});var Sn,G$=It(()=>{wd();Ip();tl();Sn=class extends Hs{constructor(e,{baseFs:r=new Yn}={}){super(K),this.target=this.pathUtils.normalize(e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.target)}resolve(e){return this.pathUtils.isAbsolute(e)?K.normalize(e):this.baseFs.resolve(K.join(this.target,e))}mapFromBase(e){return e}mapToBase(e){return this.pathUtils.isAbsolute(e)?e:this.pathUtils.join(this.target,e)}}});var W$,jf,Y$=It(()=>{wd();Ip();tl();W$=vt.root,jf=class extends Hs{constructor(e,{baseFs:r=new Yn}={}){super(K),this.target=this.pathUtils.resolve(vt.root,e),this.baseFs=r}getRealPath(){return this.pathUtils.resolve(this.baseFs.getRealPath(),this.pathUtils.relative(vt.root,this.target))}getTarget(){return this.target}getBaseFs(){return this.baseFs}mapToBase(e){let r=this.pathUtils.normalize(e);if(this.pathUtils.isAbsolute(e))return this.pathUtils.resolve(this.target,this.pathUtils.relative(W$,e));if(r.match(/^\.\.\/?/))throw new Error(`Resolving this path (${e}) would escape the jail`);return this.pathUtils.resolve(this.target,e)}mapFromBase(e){return this.pathUtils.resolve(W$,this.pathUtils.relative(this.target,e))}}});var oE,V$=It(()=>{Ip();oE=class extends Hs{constructor(r,s){super(s);this.instance=null;this.factory=r}get baseFs(){return this.instance||(this.instance=this.factory()),this.instance}set baseFs(r){this.instance=r}mapFromBase(r){return r}mapToBase(r){return r}}});var Bd,rl,n0,K$=It(()=>{Bd=ye("fs");Cd();wd();O_();ux();tl();rl=4278190080,n0=class extends Uf{constructor({baseFs:r=new Yn,filter:s=null,magicByte:a=42,maxOpenFiles:n=1/0,useCache:c=!0,maxAge:f=5e3,typeCheck:p=Bd.constants.S_IFREG,getMountPoint:h,factoryPromise:E,factorySync:C}){if(Math.floor(a)!==a||!(a>1&&a<=127))throw new Error("The magic byte must be set to a round value between 1 and 127 included");super();this.fdMap=new Map;this.nextFd=3;this.isMount=new Set;this.notMount=new Set;this.realPaths=new Map;this.limitOpenFilesTimeout=null;this.baseFs=r,this.mountInstances=c?new Map:null,this.factoryPromise=E,this.factorySync=C,this.filter=s,this.getMountPoint=h,this.magic=a<<24,this.maxAge=f,this.maxOpenFiles=n,this.typeCheck=p}getExtractHint(r){return this.baseFs.getExtractHint(r)}getRealPath(){return this.baseFs.getRealPath()}saveAndClose(){if(Ed(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.saveAndClose?.(),this.mountInstances.delete(r)}discardAndClose(){if(Ed(this),this.mountInstances)for(let[r,{childFs:s}]of this.mountInstances.entries())s.discardAndClose?.(),this.mountInstances.delete(r)}resolve(r){return this.baseFs.resolve(r)}remapFd(r,s){let a=this.nextFd++|this.magic;return this.fdMap.set(a,[r,s]),a}async openPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.openPromise(r,s,a),async(n,{subPath:c})=>this.remapFd(n,await n.openPromise(c,s,a)))}openSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.openSync(r,s,a),(n,{subPath:c})=>this.remapFd(n,n.openSync(c,s,a)))}async opendirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.opendirPromise(r,s),async(a,{subPath:n})=>await a.opendirPromise(n,s),{requireSubpath:!1})}opendirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.opendirSync(r,s),(a,{subPath:n})=>a.opendirSync(n,s),{requireSubpath:!1})}async readPromise(r,s,a,n,c){if((r&rl)!==this.magic)return await this.baseFs.readPromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Uo("read");let[p,h]=f;return await p.readPromise(h,s,a,n,c)}readSync(r,s,a,n,c){if((r&rl)!==this.magic)return this.baseFs.readSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Uo("readSync");let[p,h]=f;return p.readSync(h,s,a,n,c)}async writePromise(r,s,a,n,c){if((r&rl)!==this.magic)return typeof s=="string"?await this.baseFs.writePromise(r,s,a):await this.baseFs.writePromise(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Uo("write");let[p,h]=f;return typeof s=="string"?await p.writePromise(h,s,a):await p.writePromise(h,s,a,n,c)}writeSync(r,s,a,n,c){if((r&rl)!==this.magic)return typeof s=="string"?this.baseFs.writeSync(r,s,a):this.baseFs.writeSync(r,s,a,n,c);let f=this.fdMap.get(r);if(typeof f>"u")throw Uo("writeSync");let[p,h]=f;return typeof s=="string"?p.writeSync(h,s,a):p.writeSync(h,s,a,n,c)}async closePromise(r){if((r&rl)!==this.magic)return await this.baseFs.closePromise(r);let s=this.fdMap.get(r);if(typeof s>"u")throw Uo("close");this.fdMap.delete(r);let[a,n]=s;return await a.closePromise(n)}closeSync(r){if((r&rl)!==this.magic)return this.baseFs.closeSync(r);let s=this.fdMap.get(r);if(typeof s>"u")throw Uo("closeSync");this.fdMap.delete(r);let[a,n]=s;return a.closeSync(n)}createReadStream(r,s){return r===null?this.baseFs.createReadStream(r,s):this.makeCallSync(r,()=>this.baseFs.createReadStream(r,s),(a,{archivePath:n,subPath:c})=>{let f=a.createReadStream(c,s);return f.path=ue.fromPortablePath(this.pathUtils.join(n,c)),f})}createWriteStream(r,s){return r===null?this.baseFs.createWriteStream(r,s):this.makeCallSync(r,()=>this.baseFs.createWriteStream(r,s),(a,{subPath:n})=>a.createWriteStream(n,s))}async realpathPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.realpathPromise(r),async(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>"u"&&(c=await this.baseFs.realpathPromise(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,await s.realpathPromise(n)))})}realpathSync(r){return this.makeCallSync(r,()=>this.baseFs.realpathSync(r),(s,{archivePath:a,subPath:n})=>{let c=this.realPaths.get(a);return typeof c>"u"&&(c=this.baseFs.realpathSync(a),this.realPaths.set(a,c)),this.pathUtils.join(c,this.pathUtils.relative(vt.root,s.realpathSync(n)))})}async existsPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.existsPromise(r),async(s,{subPath:a})=>await s.existsPromise(a))}existsSync(r){return this.makeCallSync(r,()=>this.baseFs.existsSync(r),(s,{subPath:a})=>s.existsSync(a))}async accessPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.accessPromise(r,s),async(a,{subPath:n})=>await a.accessPromise(n,s))}accessSync(r,s){return this.makeCallSync(r,()=>this.baseFs.accessSync(r,s),(a,{subPath:n})=>a.accessSync(n,s))}async statPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.statPromise(r,s),async(a,{subPath:n})=>await a.statPromise(n,s))}statSync(r,s){return this.makeCallSync(r,()=>this.baseFs.statSync(r,s),(a,{subPath:n})=>a.statSync(n,s))}async fstatPromise(r,s){if((r&rl)!==this.magic)return this.baseFs.fstatPromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Uo("fstat");let[n,c]=a;return n.fstatPromise(c,s)}fstatSync(r,s){if((r&rl)!==this.magic)return this.baseFs.fstatSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Uo("fstatSync");let[n,c]=a;return n.fstatSync(c,s)}async lstatPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.lstatPromise(r,s),async(a,{subPath:n})=>await a.lstatPromise(n,s))}lstatSync(r,s){return this.makeCallSync(r,()=>this.baseFs.lstatSync(r,s),(a,{subPath:n})=>a.lstatSync(n,s))}async fchmodPromise(r,s){if((r&rl)!==this.magic)return this.baseFs.fchmodPromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Uo("fchmod");let[n,c]=a;return n.fchmodPromise(c,s)}fchmodSync(r,s){if((r&rl)!==this.magic)return this.baseFs.fchmodSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Uo("fchmodSync");let[n,c]=a;return n.fchmodSync(c,s)}async chmodPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.chmodPromise(r,s),async(a,{subPath:n})=>await a.chmodPromise(n,s))}chmodSync(r,s){return this.makeCallSync(r,()=>this.baseFs.chmodSync(r,s),(a,{subPath:n})=>a.chmodSync(n,s))}async fchownPromise(r,s,a){if((r&rl)!==this.magic)return this.baseFs.fchownPromise(r,s,a);let n=this.fdMap.get(r);if(typeof n>"u")throw Uo("fchown");let[c,f]=n;return c.fchownPromise(f,s,a)}fchownSync(r,s,a){if((r&rl)!==this.magic)return this.baseFs.fchownSync(r,s,a);let n=this.fdMap.get(r);if(typeof n>"u")throw Uo("fchownSync");let[c,f]=n;return c.fchownSync(f,s,a)}async chownPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.chownPromise(r,s,a),async(n,{subPath:c})=>await n.chownPromise(c,s,a))}chownSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.chownSync(r,s,a),(n,{subPath:c})=>n.chownSync(c,s,a))}async renamePromise(r,s){return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.renamePromise(r,s),async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),async(a,{subPath:n})=>await this.makeCallPromise(s,async()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},async(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return await a.renamePromise(n,f)}))}renameSync(r,s){return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.renameSync(r,s),()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})}),(a,{subPath:n})=>this.makeCallSync(s,()=>{throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"})},(c,{subPath:f})=>{if(a!==c)throw Object.assign(new Error("EEXDEV: cross-device link not permitted"),{code:"EEXDEV"});return a.renameSync(n,f)}))}async copyFilePromise(r,s,a=0){let n=async(c,f,p,h)=>{if(a&Bd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:"EXDEV"});if(a&Bd.constants.COPYFILE_EXCL&&await this.existsPromise(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:"EEXIST"});let E;try{E=await c.readFilePromise(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:"EINVAL"})}await p.writeFilePromise(h,E)};return await this.makeCallPromise(r,async()=>await this.makeCallPromise(s,async()=>await this.baseFs.copyFilePromise(r,s,a),async(c,{subPath:f})=>await n(this.baseFs,r,c,f)),async(c,{subPath:f})=>await this.makeCallPromise(s,async()=>await n(c,f,this.baseFs,s),async(p,{subPath:h})=>c!==p?await n(c,f,p,h):await c.copyFilePromise(f,h,a)))}copyFileSync(r,s,a=0){let n=(c,f,p,h)=>{if(a&Bd.constants.COPYFILE_FICLONE_FORCE)throw Object.assign(new Error(`EXDEV: cross-device clone not permitted, copyfile '${f}' -> ${h}'`),{code:"EXDEV"});if(a&Bd.constants.COPYFILE_EXCL&&this.existsSync(f))throw Object.assign(new Error(`EEXIST: file already exists, copyfile '${f}' -> '${h}'`),{code:"EEXIST"});let E;try{E=c.readFileSync(f)}catch{throw Object.assign(new Error(`EINVAL: invalid argument, copyfile '${f}' -> '${h}'`),{code:"EINVAL"})}p.writeFileSync(h,E)};return this.makeCallSync(r,()=>this.makeCallSync(s,()=>this.baseFs.copyFileSync(r,s,a),(c,{subPath:f})=>n(this.baseFs,r,c,f)),(c,{subPath:f})=>this.makeCallSync(s,()=>n(c,f,this.baseFs,s),(p,{subPath:h})=>c!==p?n(c,f,p,h):c.copyFileSync(f,h,a)))}async appendFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.appendFilePromise(r,s,a),async(n,{subPath:c})=>await n.appendFilePromise(c,s,a))}appendFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.appendFileSync(r,s,a),(n,{subPath:c})=>n.appendFileSync(c,s,a))}async writeFilePromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.writeFilePromise(r,s,a),async(n,{subPath:c})=>await n.writeFilePromise(c,s,a))}writeFileSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.writeFileSync(r,s,a),(n,{subPath:c})=>n.writeFileSync(c,s,a))}async unlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.unlinkPromise(r),async(s,{subPath:a})=>await s.unlinkPromise(a))}unlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.unlinkSync(r),(s,{subPath:a})=>s.unlinkSync(a))}async utimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.utimesPromise(r,s,a),async(n,{subPath:c})=>await n.utimesPromise(c,s,a))}utimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.utimesSync(r,s,a),(n,{subPath:c})=>n.utimesSync(c,s,a))}async lutimesPromise(r,s,a){return await this.makeCallPromise(r,async()=>await this.baseFs.lutimesPromise(r,s,a),async(n,{subPath:c})=>await n.lutimesPromise(c,s,a))}lutimesSync(r,s,a){return this.makeCallSync(r,()=>this.baseFs.lutimesSync(r,s,a),(n,{subPath:c})=>n.lutimesSync(c,s,a))}async mkdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.mkdirPromise(r,s),async(a,{subPath:n})=>await a.mkdirPromise(n,s))}mkdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.mkdirSync(r,s),(a,{subPath:n})=>a.mkdirSync(n,s))}async rmdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmdirPromise(r,s),async(a,{subPath:n})=>await a.rmdirPromise(n,s))}rmdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmdirSync(r,s),(a,{subPath:n})=>a.rmdirSync(n,s))}async rmPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.rmPromise(r,s),async(a,{subPath:n})=>await a.rmPromise(n,s))}rmSync(r,s){return this.makeCallSync(r,()=>this.baseFs.rmSync(r,s),(a,{subPath:n})=>a.rmSync(n,s))}async linkPromise(r,s){return await this.makeCallPromise(s,async()=>await this.baseFs.linkPromise(r,s),async(a,{subPath:n})=>await a.linkPromise(r,n))}linkSync(r,s){return this.makeCallSync(s,()=>this.baseFs.linkSync(r,s),(a,{subPath:n})=>a.linkSync(r,n))}async symlinkPromise(r,s,a){return await this.makeCallPromise(s,async()=>await this.baseFs.symlinkPromise(r,s,a),async(n,{subPath:c})=>await n.symlinkPromise(r,c))}symlinkSync(r,s,a){return this.makeCallSync(s,()=>this.baseFs.symlinkSync(r,s,a),(n,{subPath:c})=>n.symlinkSync(r,c))}async readFilePromise(r,s){return this.makeCallPromise(r,async()=>await this.baseFs.readFilePromise(r,s),async(a,{subPath:n})=>await a.readFilePromise(n,s))}readFileSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readFileSync(r,s),(a,{subPath:n})=>a.readFileSync(n,s))}async readdirPromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.readdirPromise(r,s),async(a,{subPath:n})=>await a.readdirPromise(n,s),{requireSubpath:!1})}readdirSync(r,s){return this.makeCallSync(r,()=>this.baseFs.readdirSync(r,s),(a,{subPath:n})=>a.readdirSync(n,s),{requireSubpath:!1})}async readlinkPromise(r){return await this.makeCallPromise(r,async()=>await this.baseFs.readlinkPromise(r),async(s,{subPath:a})=>await s.readlinkPromise(a))}readlinkSync(r){return this.makeCallSync(r,()=>this.baseFs.readlinkSync(r),(s,{subPath:a})=>s.readlinkSync(a))}async truncatePromise(r,s){return await this.makeCallPromise(r,async()=>await this.baseFs.truncatePromise(r,s),async(a,{subPath:n})=>await a.truncatePromise(n,s))}truncateSync(r,s){return this.makeCallSync(r,()=>this.baseFs.truncateSync(r,s),(a,{subPath:n})=>a.truncateSync(n,s))}async ftruncatePromise(r,s){if((r&rl)!==this.magic)return this.baseFs.ftruncatePromise(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Uo("ftruncate");let[n,c]=a;return n.ftruncatePromise(c,s)}ftruncateSync(r,s){if((r&rl)!==this.magic)return this.baseFs.ftruncateSync(r,s);let a=this.fdMap.get(r);if(typeof a>"u")throw Uo("ftruncateSync");let[n,c]=a;return n.ftruncateSync(c,s)}watch(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watch(r,s,a),(n,{subPath:c})=>n.watch(c,s,a))}watchFile(r,s,a){return this.makeCallSync(r,()=>this.baseFs.watchFile(r,s,a),()=>sE(this,r,s,a))}unwatchFile(r,s){return this.makeCallSync(r,()=>this.baseFs.unwatchFile(r,s),()=>yd(this,r,s))}async makeCallPromise(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!="string")return await s();let c=this.resolve(r),f=this.findMount(c);return f?n&&f.subPath==="/"?await s():await this.getMountPromise(f.archivePath,async p=>await a(p,f)):await s()}makeCallSync(r,s,a,{requireSubpath:n=!0}={}){if(typeof r!="string")return s();let c=this.resolve(r),f=this.findMount(c);return!f||n&&f.subPath==="/"?s():this.getMountSync(f.archivePath,p=>a(p,f))}findMount(r){if(this.filter&&!this.filter.test(r))return null;let s="";for(;;){let a=r.substring(s.length),n=this.getMountPoint(a,s);if(!n)return null;if(s=this.pathUtils.join(s,n),!this.isMount.has(s)){if(this.notMount.has(s))continue;try{if(this.typeCheck!==null&&(this.baseFs.statSync(s).mode&Bd.constants.S_IFMT)!==this.typeCheck){this.notMount.add(s);continue}}catch{return null}this.isMount.add(s)}return{archivePath:s,subPath:this.pathUtils.join(vt.root,r.substring(s.length))}}}limitOpenFiles(r){if(this.mountInstances===null)return;let s=Date.now(),a=s+this.maxAge,n=r===null?0:this.mountInstances.size-r;for(let[c,{childFs:f,expiresAt:p,refCount:h}]of this.mountInstances.entries())if(!(h!==0||f.hasOpenFileHandles?.())){if(s>=p){f.saveAndClose?.(),this.mountInstances.delete(c),n-=1;continue}else if(r===null||n<=0){a=p;break}f.saveAndClose?.(),this.mountInstances.delete(c),n-=1}this.limitOpenFilesTimeout===null&&(r===null&&this.mountInstances.size>0||r!==null)&&isFinite(a)&&(this.limitOpenFilesTimeout=setTimeout(()=>{this.limitOpenFilesTimeout=null,this.limitOpenFiles(null)},a-s).unref())}async getMountPromise(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);if(!a){let n=await this.factoryPromise(this.baseFs,r);a=this.mountInstances.get(r),a||(a={childFs:n(),expiresAt:0,refCount:0})}this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,a.refCount+=1;try{return await s(a.childFs)}finally{a.refCount-=1}}else{let a=(await this.factoryPromise(this.baseFs,r))();try{return await s(a)}finally{a.saveAndClose?.()}}}getMountSync(r,s){if(this.mountInstances){let a=this.mountInstances.get(r);return a||(a={childFs:this.factorySync(this.baseFs,r),expiresAt:0,refCount:0}),this.mountInstances.delete(r),this.limitOpenFiles(this.maxOpenFiles-1),this.mountInstances.set(r,a),a.expiresAt=Date.now()+this.maxAge,s(a.childFs)}else{let a=this.factorySync(this.baseFs,r);try{return s(a)}finally{a.saveAndClose?.()}}}}});var er,mx,J$=It(()=>{Cd();tl();er=()=>Object.assign(new Error("ENOSYS: unsupported filesystem access"),{code:"ENOSYS"}),mx=class t extends Ep{static{this.instance=new t}constructor(){super(K)}getExtractHint(){throw er()}getRealPath(){throw er()}resolve(){throw er()}async openPromise(){throw er()}openSync(){throw er()}async opendirPromise(){throw er()}opendirSync(){throw er()}async readPromise(){throw er()}readSync(){throw er()}async writePromise(){throw er()}writeSync(){throw er()}async closePromise(){throw er()}closeSync(){throw er()}createWriteStream(){throw er()}createReadStream(){throw er()}async realpathPromise(){throw er()}realpathSync(){throw er()}async readdirPromise(){throw er()}readdirSync(){throw er()}async existsPromise(e){throw er()}existsSync(e){throw er()}async accessPromise(){throw er()}accessSync(){throw er()}async statPromise(){throw er()}statSync(){throw er()}async fstatPromise(e){throw er()}fstatSync(e){throw er()}async lstatPromise(e){throw er()}lstatSync(e){throw er()}async fchmodPromise(){throw er()}fchmodSync(){throw er()}async chmodPromise(){throw er()}chmodSync(){throw er()}async fchownPromise(){throw er()}fchownSync(){throw er()}async chownPromise(){throw er()}chownSync(){throw er()}async mkdirPromise(){throw er()}mkdirSync(){throw er()}async rmdirPromise(){throw er()}rmdirSync(){throw er()}async rmPromise(){throw er()}rmSync(){throw er()}async linkPromise(){throw er()}linkSync(){throw er()}async symlinkPromise(){throw er()}symlinkSync(){throw er()}async renamePromise(){throw er()}renameSync(){throw er()}async copyFilePromise(){throw er()}copyFileSync(){throw er()}async appendFilePromise(){throw er()}appendFileSync(){throw er()}async writeFilePromise(){throw er()}writeFileSync(){throw er()}async unlinkPromise(){throw er()}unlinkSync(){throw er()}async utimesPromise(){throw er()}utimesSync(){throw er()}async lutimesPromise(){throw er()}lutimesSync(){throw er()}async readFilePromise(){throw er()}readFileSync(){throw er()}async readlinkPromise(){throw er()}readlinkSync(){throw er()}async truncatePromise(){throw er()}truncateSync(){throw er()}async ftruncatePromise(e,r){throw er()}ftruncateSync(e,r){throw er()}watch(){throw er()}watchFile(){throw er()}unwatchFile(){throw er()}}});var i0,z$=It(()=>{Ip();tl();i0=class extends Hs{constructor(e){super(ue),this.baseFs=e}mapFromBase(e){return ue.fromPortablePath(e)}mapToBase(e){return ue.toPortablePath(e)}}});var H7e,L_,j7e,fo,Z$=It(()=>{wd();Ip();tl();H7e=/^[0-9]+$/,L_=/^(\/(?:[^/]+\/)*?(?:\$\$virtual|__virtual__))((?:\/((?:[^/]+-)?[a-f0-9]+)(?:\/([^/]+))?)?((?:\/.*)?))$/,j7e=/^([^/]+-)?[a-f0-9]+$/,fo=class t extends Hs{static makeVirtualPath(e,r,s){if(K.basename(e)!=="__virtual__")throw new Error('Assertion failed: Virtual folders must be named "__virtual__"');if(!K.basename(r).match(j7e))throw new Error("Assertion failed: Virtual components must be ended by an hexadecimal hash");let n=K.relative(K.dirname(e),s).split("/"),c=0;for(;c{M_=et(ye("buffer")),X$=ye("url"),$$=ye("util");Ip();tl();yx=class extends Hs{constructor(e){super(ue),this.baseFs=e}mapFromBase(e){return e}mapToBase(e){if(typeof e=="string")return e;if(e instanceof URL)return(0,X$.fileURLToPath)(e);if(Buffer.isBuffer(e)){let r=e.toString();if(!q7e(e,r))throw new Error("Non-utf8 buffers are not supported at the moment. Please upvote the following issue if you encounter this error: https://github.com/yarnpkg/berry/issues/4942");return r}throw new Error(`Unsupported path type: ${(0,$$.inspect)(e)}`)}}});var see,Ho,Cp,s0,Ex,Ix,aE,Nu,Ou,tee,ree,nee,iee,H2,oee=It(()=>{see=ye("readline"),Ho=Symbol("kBaseFs"),Cp=Symbol("kFd"),s0=Symbol("kClosePromise"),Ex=Symbol("kCloseResolve"),Ix=Symbol("kCloseReject"),aE=Symbol("kRefs"),Nu=Symbol("kRef"),Ou=Symbol("kUnref"),H2=class{constructor(e,r){this[iee]=1;this[nee]=void 0;this[ree]=void 0;this[tee]=void 0;this[Ho]=r,this[Cp]=e}get fd(){return this[Cp]}async appendFile(e,r){try{this[Nu](this.appendFile);let s=(typeof r=="string"?r:r?.encoding)??void 0;return await this[Ho].appendFilePromise(this.fd,e,s?{encoding:s}:void 0)}finally{this[Ou]()}}async chown(e,r){try{return this[Nu](this.chown),await this[Ho].fchownPromise(this.fd,e,r)}finally{this[Ou]()}}async chmod(e){try{return this[Nu](this.chmod),await this[Ho].fchmodPromise(this.fd,e)}finally{this[Ou]()}}createReadStream(e){return this[Ho].createReadStream(null,{...e,fd:this.fd})}createWriteStream(e){return this[Ho].createWriteStream(null,{...e,fd:this.fd})}datasync(){throw new Error("Method not implemented.")}sync(){throw new Error("Method not implemented.")}async read(e,r,s,a){try{this[Nu](this.read);let n;return Buffer.isBuffer(e)?n=e:(e??={},n=e.buffer??Buffer.alloc(16384),r=e.offset||0,s=e.length??n.byteLength,a=e.position??null),r??=0,s??=0,s===0?{bytesRead:s,buffer:n}:{bytesRead:await this[Ho].readPromise(this.fd,n,r,s,a),buffer:n}}finally{this[Ou]()}}async readFile(e){try{this[Nu](this.readFile);let r=(typeof e=="string"?e:e?.encoding)??void 0;return await this[Ho].readFilePromise(this.fd,r)}finally{this[Ou]()}}readLines(e){return(0,see.createInterface)({input:this.createReadStream(e),crlfDelay:1/0})}async stat(e){try{return this[Nu](this.stat),await this[Ho].fstatPromise(this.fd,e)}finally{this[Ou]()}}async truncate(e){try{return this[Nu](this.truncate),await this[Ho].ftruncatePromise(this.fd,e)}finally{this[Ou]()}}utimes(e,r){throw new Error("Method not implemented.")}async writeFile(e,r){try{this[Nu](this.writeFile);let s=(typeof r=="string"?r:r?.encoding)??void 0;await this[Ho].writeFilePromise(this.fd,e,s)}finally{this[Ou]()}}async write(...e){try{if(this[Nu](this.write),ArrayBuffer.isView(e[0])){let[r,s,a,n]=e;return{bytesWritten:await this[Ho].writePromise(this.fd,r,s??void 0,a??void 0,n??void 0),buffer:r}}else{let[r,s,a]=e;return{bytesWritten:await this[Ho].writePromise(this.fd,r,s,a),buffer:r}}}finally{this[Ou]()}}async writev(e,r){try{this[Nu](this.writev);let s=0;if(typeof r<"u")for(let a of e){let n=await this.write(a,void 0,void 0,r);s+=n.bytesWritten,r+=n.bytesWritten}else for(let a of e){let n=await this.write(a);s+=n.bytesWritten}return{buffers:e,bytesWritten:s}}finally{this[Ou]()}}readv(e,r){throw new Error("Method not implemented.")}close(){if(this[Cp]===-1)return Promise.resolve();if(this[s0])return this[s0];if(this[aE]--,this[aE]===0){let e=this[Cp];this[Cp]=-1,this[s0]=this[Ho].closePromise(e).finally(()=>{this[s0]=void 0})}else this[s0]=new Promise((e,r)=>{this[Ex]=e,this[Ix]=r}).finally(()=>{this[s0]=void 0,this[Ix]=void 0,this[Ex]=void 0});return this[s0]}[(Ho,Cp,iee=aE,nee=s0,ree=Ex,tee=Ix,Nu)](e){if(this[Cp]===-1){let r=new Error("file closed");throw r.code="EBADF",r.syscall=e.name,r}this[aE]++}[Ou](){if(this[aE]--,this[aE]===0){let e=this[Cp];this[Cp]=-1,this[Ho].closePromise(e).then(this[Ex],this[Ix])}}}});function j2(t,e){e=new yx(e);let r=(s,a,n)=>{let c=s[a];s[a]=n,typeof c?.[lE.promisify.custom]<"u"&&(n[lE.promisify.custom]=c[lE.promisify.custom])};{r(t,"exists",(s,...a)=>{let c=typeof a[a.length-1]=="function"?a.pop():()=>{};process.nextTick(()=>{e.existsPromise(s).then(f=>{c(f)},()=>{c(!1)})})}),r(t,"read",(...s)=>{let[a,n,c,f,p,h]=s;if(s.length<=3){let E={};s.length<3?h=s[1]:(E=s[1],h=s[2]),{buffer:n=Buffer.alloc(16384),offset:c=0,length:f=n.byteLength,position:p}=E}if(c==null&&(c=0),f|=0,f===0){process.nextTick(()=>{h(null,0,n)});return}p==null&&(p=-1),process.nextTick(()=>{e.readPromise(a,n,c,f,p).then(E=>{h(null,E,n)},E=>{h(E,0,n)})})});for(let s of aee){let a=s.replace(/Promise$/,"");if(typeof t[a]>"u")continue;let n=e[s];if(typeof n>"u")continue;r(t,a,(...f)=>{let h=typeof f[f.length-1]=="function"?f.pop():()=>{};process.nextTick(()=>{n.apply(e,f).then(E=>{h(null,E)},E=>{h(E)})})})}t.realpath.native=t.realpath}{r(t,"existsSync",s=>{try{return e.existsSync(s)}catch{return!1}}),r(t,"readSync",(...s)=>{let[a,n,c,f,p]=s;return s.length<=3&&({offset:c=0,length:f=n.byteLength,position:p}=s[2]||{}),c==null&&(c=0),f|=0,f===0?0:(p==null&&(p=-1),e.readSync(a,n,c,f,p))});for(let s of G7e){let a=s;if(typeof t[a]>"u")continue;let n=e[s];typeof n>"u"||r(t,a,n.bind(e))}t.realpathSync.native=t.realpathSync}{let s=t.promises;for(let a of aee){let n=a.replace(/Promise$/,"");if(typeof s[n]>"u")continue;let c=e[a];typeof c>"u"||a!=="open"&&r(s,n,(f,...p)=>f instanceof H2?f[n].apply(f,p):c.call(e,f,...p))}r(s,"open",async(...a)=>{let n=await e.openPromise(...a);return new H2(n,e)})}t.read[lE.promisify.custom]=async(s,a,...n)=>({bytesRead:await e.readPromise(s,a,...n),buffer:a}),t.write[lE.promisify.custom]=async(s,a,...n)=>({bytesWritten:await e.writePromise(s,a,...n),buffer:a})}function Cx(t,e){let r=Object.create(t);return j2(r,e),r}var lE,G7e,aee,lee=It(()=>{lE=ye("util");eee();oee();G7e=new Set(["accessSync","appendFileSync","createReadStream","createWriteStream","chmodSync","fchmodSync","chownSync","fchownSync","closeSync","copyFileSync","linkSync","lstatSync","fstatSync","lutimesSync","mkdirSync","openSync","opendirSync","readlinkSync","readFileSync","readdirSync","readlinkSync","realpathSync","renameSync","rmdirSync","rmSync","statSync","symlinkSync","truncateSync","ftruncateSync","unlinkSync","unwatchFile","utimesSync","watch","watchFile","writeFileSync","writeSync"]),aee=new Set(["accessPromise","appendFilePromise","fchmodPromise","chmodPromise","fchownPromise","chownPromise","closePromise","copyFilePromise","linkPromise","fstatPromise","lstatPromise","lutimesPromise","mkdirPromise","openPromise","opendirPromise","readdirPromise","realpathPromise","readFilePromise","readdirPromise","readlinkPromise","renamePromise","rmdirPromise","rmPromise","statPromise","symlinkPromise","truncatePromise","ftruncatePromise","unlinkPromise","utimesPromise","writeFilePromise","writeSync"])});function cee(t){let e=Math.ceil(Math.random()*4294967296).toString(16).padStart(8,"0");return`${t}${e}`}function uee(){if(__)return __;let t=ue.toPortablePath(fee.default.tmpdir()),e=le.realpathSync(t);return process.once("exit",()=>{le.rmtempSync()}),__={tmpdir:t,realTmpdir:e}}var fee,Lu,__,le,Aee=It(()=>{fee=et(ye("os"));wd();tl();Lu=new Set,__=null;le=Object.assign(new Yn,{detachTemp(t){Lu.delete(t)},mktempSync(t){let{tmpdir:e,realTmpdir:r}=uee();for(;;){let s=cee("xfs-");try{this.mkdirSync(K.join(e,s))}catch(n){if(n.code==="EEXIST")continue;throw n}let a=K.join(r,s);if(Lu.add(a),typeof t>"u")return a;try{return t(a)}finally{if(Lu.has(a)){Lu.delete(a);try{this.removeSync(a)}catch{}}}}},async mktempPromise(t){let{tmpdir:e,realTmpdir:r}=uee();for(;;){let s=cee("xfs-");try{await this.mkdirPromise(K.join(e,s))}catch(n){if(n.code==="EEXIST")continue;throw n}let a=K.join(r,s);if(Lu.add(a),typeof t>"u")return a;try{return await t(a)}finally{if(Lu.has(a)){Lu.delete(a);try{await this.removePromise(a)}catch{}}}}},async rmtempPromise(){await Promise.all(Array.from(Lu.values()).map(async t=>{try{await le.removePromise(t,{maxRetries:0}),Lu.delete(t)}catch{}}))},rmtempSync(){for(let t of Lu)try{le.removeSync(t),Lu.delete(t)}catch{}}})});var q2={};Vt(q2,{AliasFS:()=>Hf,BasePortableFakeFS:()=>Uf,CustomDir:()=>U2,CwdFS:()=>Sn,FakeFS:()=>Ep,Filename:()=>Er,JailFS:()=>jf,LazyFS:()=>oE,MountFS:()=>n0,NoFS:()=>mx,NodeFS:()=>Yn,PortablePath:()=>vt,PosixFS:()=>i0,ProxiedFS:()=>Hs,VirtualFS:()=>fo,constants:()=>ui,errors:()=>or,extendFs:()=>Cx,normalizeLineEndings:()=>Id,npath:()=>ue,opendir:()=>hx,patchFs:()=>j2,ppath:()=>K,setupCopyIndex:()=>px,statUtils:()=>el,unwatchAllFiles:()=>Ed,unwatchFile:()=>yd,watchFile:()=>sE,xfs:()=>le});var bt=It(()=>{x$();ux();T_();N_();N$();O_();Cd();tl();tl();H$();Cd();G$();Y$();V$();K$();J$();wd();z$();Ip();Z$();lee();Aee()});var mee=L((A5t,dee)=>{dee.exports=gee;gee.sync=Y7e;var pee=ye("fs");function W7e(t,e){var r=e.pathExt!==void 0?e.pathExt:process.env.PATHEXT;if(!r||(r=r.split(";"),r.indexOf("")!==-1))return!0;for(var s=0;s{Cee.exports=Eee;Eee.sync=V7e;var yee=ye("fs");function Eee(t,e,r){yee.stat(t,function(s,a){r(s,s?!1:Iee(a,e))})}function V7e(t,e){return Iee(yee.statSync(t),e)}function Iee(t,e){return t.isFile()&&K7e(t,e)}function K7e(t,e){var r=t.mode,s=t.uid,a=t.gid,n=e.uid!==void 0?e.uid:process.getuid&&process.getuid(),c=e.gid!==void 0?e.gid:process.getgid&&process.getgid(),f=parseInt("100",8),p=parseInt("010",8),h=parseInt("001",8),E=f|p,C=r&h||r&p&&a===c||r&f&&s===n||r&E&&n===0;return C}});var vee=L((g5t,Bee)=>{var h5t=ye("fs"),wx;process.platform==="win32"||global.TESTING_WINDOWS?wx=mee():wx=wee();Bee.exports=U_;U_.sync=J7e;function U_(t,e,r){if(typeof e=="function"&&(r=e,e={}),!r){if(typeof Promise!="function")throw new TypeError("callback not provided");return new Promise(function(s,a){U_(t,e||{},function(n,c){n?a(n):s(c)})})}wx(t,e||{},function(s,a){s&&(s.code==="EACCES"||e&&e.ignoreErrors)&&(s=null,a=!1),r(s,a)})}function J7e(t,e){try{return wx.sync(t,e||{})}catch(r){if(e&&e.ignoreErrors||r.code==="EACCES")return!1;throw r}}});var Qee=L((d5t,kee)=>{var cE=process.platform==="win32"||process.env.OSTYPE==="cygwin"||process.env.OSTYPE==="msys",See=ye("path"),z7e=cE?";":":",Dee=vee(),bee=t=>Object.assign(new Error(`not found: ${t}`),{code:"ENOENT"}),Pee=(t,e)=>{let r=e.colon||z7e,s=t.match(/\//)||cE&&t.match(/\\/)?[""]:[...cE?[process.cwd()]:[],...(e.path||process.env.PATH||"").split(r)],a=cE?e.pathExt||process.env.PATHEXT||".EXE;.CMD;.BAT;.COM":"",n=cE?a.split(r):[""];return cE&&t.indexOf(".")!==-1&&n[0]!==""&&n.unshift(""),{pathEnv:s,pathExt:n,pathExtExe:a}},xee=(t,e,r)=>{typeof e=="function"&&(r=e,e={}),e||(e={});let{pathEnv:s,pathExt:a,pathExtExe:n}=Pee(t,e),c=[],f=h=>new Promise((E,C)=>{if(h===s.length)return e.all&&c.length?E(c):C(bee(t));let S=s[h],P=/^".*"$/.test(S)?S.slice(1,-1):S,I=See.join(P,t),R=!P&&/^\.[\\\/]/.test(t)?t.slice(0,2)+I:I;E(p(R,h,0))}),p=(h,E,C)=>new Promise((S,P)=>{if(C===a.length)return S(f(E+1));let I=a[C];Dee(h+I,{pathExt:n},(R,N)=>{if(!R&&N)if(e.all)c.push(h+I);else return S(h+I);return S(p(h,E,C+1))})});return r?f(0).then(h=>r(null,h),r):f(0)},Z7e=(t,e)=>{e=e||{};let{pathEnv:r,pathExt:s,pathExtExe:a}=Pee(t,e),n=[];for(let c=0;c{"use strict";var Tee=(t={})=>{let e=t.env||process.env;return(t.platform||process.platform)!=="win32"?"PATH":Object.keys(e).reverse().find(s=>s.toUpperCase()==="PATH")||"Path"};H_.exports=Tee;H_.exports.default=Tee});var Lee=L((y5t,Oee)=>{"use strict";var Fee=ye("path"),X7e=Qee(),$7e=Ree();function Nee(t,e){let r=t.options.env||process.env,s=process.cwd(),a=t.options.cwd!=null,n=a&&process.chdir!==void 0&&!process.chdir.disabled;if(n)try{process.chdir(t.options.cwd)}catch{}let c;try{c=X7e.sync(t.command,{path:r[$7e({env:r})],pathExt:e?Fee.delimiter:void 0})}catch{}finally{n&&process.chdir(s)}return c&&(c=Fee.resolve(a?t.options.cwd:"",c)),c}function eKe(t){return Nee(t)||Nee(t,!0)}Oee.exports=eKe});var Mee=L((E5t,q_)=>{"use strict";var j_=/([()\][%!^"`<>&|;, *?])/g;function tKe(t){return t=t.replace(j_,"^$1"),t}function rKe(t,e){return t=`${t}`,t=t.replace(/(?=(\\+?)?)\1"/g,'$1$1\\"'),t=t.replace(/(?=(\\+?)?)\1$/,"$1$1"),t=`"${t}"`,t=t.replace(j_,"^$1"),e&&(t=t.replace(j_,"^$1")),t}q_.exports.command=tKe;q_.exports.argument=rKe});var Uee=L((I5t,_ee)=>{"use strict";_ee.exports=/^#!(.*)/});var jee=L((C5t,Hee)=>{"use strict";var nKe=Uee();Hee.exports=(t="")=>{let e=t.match(nKe);if(!e)return null;let[r,s]=e[0].replace(/#! ?/,"").split(" "),a=r.split("/").pop();return a==="env"?s:s?`${a} ${s}`:a}});var Gee=L((w5t,qee)=>{"use strict";var G_=ye("fs"),iKe=jee();function sKe(t){let r=Buffer.alloc(150),s;try{s=G_.openSync(t,"r"),G_.readSync(s,r,0,150,0),G_.closeSync(s)}catch{}return iKe(r.toString())}qee.exports=sKe});var Kee=L((B5t,Vee)=>{"use strict";var oKe=ye("path"),Wee=Lee(),Yee=Mee(),aKe=Gee(),lKe=process.platform==="win32",cKe=/\.(?:com|exe)$/i,uKe=/node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;function fKe(t){t.file=Wee(t);let e=t.file&&aKe(t.file);return e?(t.args.unshift(t.file),t.command=e,Wee(t)):t.file}function AKe(t){if(!lKe)return t;let e=fKe(t),r=!cKe.test(e);if(t.options.forceShell||r){let s=uKe.test(e);t.command=oKe.normalize(t.command),t.command=Yee.command(t.command),t.args=t.args.map(n=>Yee.argument(n,s));let a=[t.command].concat(t.args).join(" ");t.args=["/d","/s","/c",`"${a}"`],t.command=process.env.comspec||"cmd.exe",t.options.windowsVerbatimArguments=!0}return t}function pKe(t,e,r){e&&!Array.isArray(e)&&(r=e,e=null),e=e?e.slice(0):[],r=Object.assign({},r);let s={command:t,args:e,options:r,file:void 0,original:{command:t,args:e}};return r.shell?s:AKe(s)}Vee.exports=pKe});var Zee=L((v5t,zee)=>{"use strict";var W_=process.platform==="win32";function Y_(t,e){return Object.assign(new Error(`${e} ${t.command} ENOENT`),{code:"ENOENT",errno:"ENOENT",syscall:`${e} ${t.command}`,path:t.command,spawnargs:t.args})}function hKe(t,e){if(!W_)return;let r=t.emit;t.emit=function(s,a){if(s==="exit"){let n=Jee(a,e);if(n)return r.call(t,"error",n)}return r.apply(t,arguments)}}function Jee(t,e){return W_&&t===1&&!e.file?Y_(e.original,"spawn"):null}function gKe(t,e){return W_&&t===1&&!e.file?Y_(e.original,"spawnSync"):null}zee.exports={hookChildProcess:hKe,verifyENOENT:Jee,verifyENOENTSync:gKe,notFoundError:Y_}});var J_=L((S5t,uE)=>{"use strict";var Xee=ye("child_process"),V_=Kee(),K_=Zee();function $ee(t,e,r){let s=V_(t,e,r),a=Xee.spawn(s.command,s.args,s.options);return K_.hookChildProcess(a,s),a}function dKe(t,e,r){let s=V_(t,e,r),a=Xee.spawnSync(s.command,s.args,s.options);return a.error=a.error||K_.verifyENOENTSync(a.status,s),a}uE.exports=$ee;uE.exports.spawn=$ee;uE.exports.sync=dKe;uE.exports._parse=V_;uE.exports._enoent=K_});var tte=L((D5t,ete)=>{"use strict";function mKe(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function vd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,vd)}mKe(vd,Error);vd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C>",b=ur(">>",!1),y=">&",F=ur(">&",!1),z=">",Z=ur(">",!1),$="<<<",oe=ur("<<<",!1),xe="<&",Te=ur("<&",!1),lt="<",Et=ur("<",!1),qt=function(O){return{type:"argument",segments:[].concat(...O)}},ir=function(O){return O},Pt="$'",gn=ur("$'",!1),Pr="'",Ir=ur("'",!1),Nr=function(O){return[{type:"text",text:O}]},nn='""',oi=ur('""',!1),wo=function(){return{type:"text",text:""}},rs='"',eo=ur('"',!1),Bo=function(O){return O},Hi=function(O){return{type:"arithmetic",arithmetic:O,quoted:!0}},to=function(O){return{type:"shell",shell:O,quoted:!0}},vo=function(O){return{type:"variable",...O,quoted:!0}},RA=function(O){return{type:"text",text:O}},pf=function(O){return{type:"arithmetic",arithmetic:O,quoted:!1}},Eh=function(O){return{type:"shell",shell:O,quoted:!1}},Ih=function(O){return{type:"variable",...O,quoted:!1}},ro=function(O){return{type:"glob",pattern:O}},jn=/^[^']/,Rs=zi(["'"],!0,!1),no=function(O){return O.join("")},lu=/^[^$"]/,cu=zi(["$",'"'],!0,!1),uu=`\\ +`,FA=ur(`\\ +`,!1),NA=function(){return""},aa="\\",la=ur("\\",!1),OA=/^[\\$"`]/,gr=zi(["\\","$",'"',"`"],!1,!1),So=function(O){return O},Me="\\a",fu=ur("\\a",!1),Cr=function(){return"a"},hf="\\b",LA=ur("\\b",!1),MA=function(){return"\b"},Au=/^[Ee]/,pu=zi(["E","e"],!1,!1),ac=function(){return"\x1B"},ve="\\f",Nt=ur("\\f",!1),lc=function(){return"\f"},Ni="\\n",io=ur("\\n",!1),Rt=function(){return` +`},xn="\\r",ca=ur("\\r",!1),ji=function(){return"\r"},Oi="\\t",Oa=ur("\\t",!1),dn=function(){return" "},Jn="\\v",hu=ur("\\v",!1),Ch=function(){return"\v"},La=/^[\\'"?]/,Ma=zi(["\\","'",'"',"?"],!1,!1),Ua=function(O){return String.fromCharCode(parseInt(O,16))},Xe="\\x",Ha=ur("\\x",!1),gf="\\u",cc=ur("\\u",!1),wn="\\U",ua=ur("\\U",!1),_A=function(O){return String.fromCodePoint(parseInt(O,16))},UA=/^[0-7]/,fa=zi([["0","7"]],!1,!1),vl=/^[0-9a-fA-f]/,Mt=zi([["0","9"],["a","f"],["A","f"]],!1,!1),kn=Ef(),Aa="{}",ja=ur("{}",!1),ns=function(){return"{}"},uc="-",gu=ur("-",!1),fc="+",qa=ur("+",!1),Li=".",Cs=ur(".",!1),Sl=function(O,J,re){return{type:"number",value:(O==="-"?-1:1)*parseFloat(J.join("")+"."+re.join(""))}},df=function(O,J){return{type:"number",value:(O==="-"?-1:1)*parseInt(J.join(""))}},Ac=function(O){return{type:"variable",...O}},wi=function(O){return{type:"variable",name:O}},Qn=function(O){return O},pc="*",Je=ur("*",!1),st="/",St=ur("/",!1),lr=function(O,J,re){return{type:J==="*"?"multiplication":"division",right:re}},ee=function(O,J){return J.reduce((re,de)=>({left:re,...de}),O)},Ie=function(O,J,re){return{type:J==="+"?"addition":"subtraction",right:re}},Oe="$((",ht=ur("$((",!1),mt="))",Dt=ur("))",!1),tr=function(O){return O},fn="$(",ai=ur("$(",!1),qi=function(O){return O},Tn="${",Ga=ur("${",!1),my=":-",t2=ur(":-",!1),Do=function(O,J){return{name:O,defaultValue:J}},yy=":-}",wh=ur(":-}",!1),r2=function(O){return{name:O,defaultValue:[]}},bo=":+",Bh=ur(":+",!1),vh=function(O,J){return{name:O,alternativeValue:J}},du=":+}",Sh=ur(":+}",!1),Ng=function(O){return{name:O,alternativeValue:[]}},Og=function(O){return{name:O}},Lg="$",Ey=ur("$",!1),mf=function(O){return e.isGlobPattern(O)},Po=function(O){return O},Dl=/^[a-zA-Z0-9_]/,Dh=zi([["a","z"],["A","Z"],["0","9"],"_"],!1,!1),Mg=function(){return By()},bl=/^[$@*?#a-zA-Z0-9_\-]/,Pl=zi(["$","@","*","?","#",["a","z"],["A","Z"],["0","9"],"_","-"],!1,!1),Iy=/^[()}<>$|&; \t"']/,HA=zi(["(",")","}","<",">","$","|","&",";"," "," ",'"',"'"],!1,!1),Cy=/^[<>&; \t"']/,wy=zi(["<",">","&",";"," "," ",'"',"'"],!1,!1),jA=/^[ \t]/,qA=zi([" "," "],!1,!1),Y=0,xt=0,GA=[{line:1,column:1}],xo=0,yf=[],dt=0,mu;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function By(){return t.substring(xt,Y)}function _g(){return If(xt,Y)}function n2(O,J){throw J=J!==void 0?J:If(xt,Y),WA([Ug(O)],t.substring(xt,Y),J)}function bh(O,J){throw J=J!==void 0?J:If(xt,Y),gi(O,J)}function ur(O,J){return{type:"literal",text:O,ignoreCase:J}}function zi(O,J,re){return{type:"class",parts:O,inverted:J,ignoreCase:re}}function Ef(){return{type:"any"}}function Wa(){return{type:"end"}}function Ug(O){return{type:"other",description:O}}function yu(O){var J=GA[O],re;if(J)return J;for(re=O-1;!GA[re];)re--;for(J=GA[re],J={line:J.line,column:J.column};rexo&&(xo=Y,yf=[]),yf.push(O))}function gi(O,J){return new vd(O,null,null,J)}function WA(O,J,re){return new vd(vd.buildMessage(O,J),O,J,re)}function Ya(){var O,J,re;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();return J!==r?(re=pa(),re===r&&(re=null),re!==r?(xt=O,J=n(re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function pa(){var O,J,re,de,Ke;if(O=Y,J=Ph(),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=Hg(),de!==r?(Ke=Va(),Ke===r&&(Ke=null),Ke!==r?(xt=O,J=c(J,de,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;if(O===r)if(O=Y,J=Ph(),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();re!==r?(de=Hg(),de===r&&(de=null),de!==r?(xt=O,J=f(J,de),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function Va(){var O,J,re,de,Ke;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(re=pa(),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=p(re),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function Hg(){var O;return t.charCodeAt(Y)===59?(O=h,Y++):(O=r,dt===0&&wt(E)),O===r&&(t.charCodeAt(Y)===38?(O=C,Y++):(O=r,dt===0&&wt(S))),O}function Ph(){var O,J,re;return O=Y,J=YA(),J!==r?(re=jg(),re===r&&(re=null),re!==r?(xt=O,J=P(J,re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function jg(){var O,J,re,de,Ke,ft,dr;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(re=vy(),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=Ph(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();ft!==r?(xt=O,J=I(re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function vy(){var O;return t.substr(Y,2)===R?(O=R,Y+=2):(O=r,dt===0&&wt(N)),O===r&&(t.substr(Y,2)===U?(O=U,Y+=2):(O=r,dt===0&&wt(W))),O}function YA(){var O,J,re;return O=Y,J=Cf(),J!==r?(re=qg(),re===r&&(re=null),re!==r?(xt=O,J=te(J,re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function qg(){var O,J,re,de,Ke,ft,dr;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(re=Eu(),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=YA(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();ft!==r?(xt=O,J=ie(re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;return O}function Eu(){var O;return t.substr(Y,2)===Ae?(O=Ae,Y+=2):(O=r,dt===0&&wt(ce)),O===r&&(t.charCodeAt(Y)===124?(O=me,Y++):(O=r,dt===0&&wt(pe))),O}function Iu(){var O,J,re,de,Ke,ft;if(O=Y,J=Qh(),J!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,dt===0&&wt(Ce)),re!==r)if(de=VA(),de!==r){for(Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();Ke!==r?(xt=O,J=g(J,de),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;else Y=O,O=r;if(O===r)if(O=Y,J=Qh(),J!==r)if(t.charCodeAt(Y)===61?(re=Be,Y++):(re=r,dt===0&&wt(Ce)),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=we(J),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r;return O}function Cf(){var O,J,re,de,Ke,ft,dr,Br,_n,di,ws;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(t.charCodeAt(Y)===40?(re=Ee,Y++):(re=r,dt===0&&wt(fe)),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=pa(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();if(ft!==r)if(t.charCodeAt(Y)===41?(dr=se,Y++):(dr=r,dt===0&&wt(X)),dr!==r){for(Br=[],_n=kt();_n!==r;)Br.push(_n),_n=kt();if(Br!==r){for(_n=[],di=qn();di!==r;)_n.push(di),di=qn();if(_n!==r){for(di=[],ws=kt();ws!==r;)di.push(ws),ws=kt();di!==r?(xt=O,J=De(Ke,_n),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r)if(t.charCodeAt(Y)===123?(re=Re,Y++):(re=r,dt===0&&wt(gt)),re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r)if(Ke=pa(),Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();if(ft!==r)if(t.charCodeAt(Y)===125?(dr=j,Y++):(dr=r,dt===0&&wt(rt)),dr!==r){for(Br=[],_n=kt();_n!==r;)Br.push(_n),_n=kt();if(Br!==r){for(_n=[],di=qn();di!==r;)_n.push(di),di=qn();if(_n!==r){for(di=[],ws=kt();ws!==r;)di.push(ws),ws=kt();di!==r?(xt=O,J=Fe(Ke,_n),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;else Y=O,O=r;if(O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r){for(re=[],de=Iu();de!==r;)re.push(de),de=Iu();if(re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();if(de!==r){if(Ke=[],ft=Cu(),ft!==r)for(;ft!==r;)Ke.push(ft),ft=Cu();else Ke=r;if(Ke!==r){for(ft=[],dr=kt();dr!==r;)ft.push(dr),dr=kt();ft!==r?(xt=O,J=Ne(re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r}else Y=O,O=r;if(O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r){if(re=[],de=Iu(),de!==r)for(;de!==r;)re.push(de),de=Iu();else re=r;if(re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=Pe(re),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r}}}return O}function Fs(){var O,J,re,de,Ke;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r){if(re=[],de=xi(),de!==r)for(;de!==r;)re.push(de),de=xi();else re=r;if(re!==r){for(de=[],Ke=kt();Ke!==r;)de.push(Ke),Ke=kt();de!==r?(xt=O,J=Ye(re),O=J):(Y=O,O=r)}else Y=O,O=r}else Y=O,O=r;return O}function Cu(){var O,J,re;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();if(J!==r?(re=qn(),re!==r?(xt=O,J=ke(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r){for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();J!==r?(re=xi(),re!==r?(xt=O,J=ke(re),O=J):(Y=O,O=r)):(Y=O,O=r)}return O}function qn(){var O,J,re,de,Ke;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();return J!==r?(it.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(_e)),re===r&&(re=null),re!==r?(de=is(),de!==r?(Ke=xi(),Ke!==r?(xt=O,J=x(re,de,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function is(){var O;return t.substr(Y,2)===w?(O=w,Y+=2):(O=r,dt===0&&wt(b)),O===r&&(t.substr(Y,2)===y?(O=y,Y+=2):(O=r,dt===0&&wt(F)),O===r&&(t.charCodeAt(Y)===62?(O=z,Y++):(O=r,dt===0&&wt(Z)),O===r&&(t.substr(Y,3)===$?(O=$,Y+=3):(O=r,dt===0&&wt(oe)),O===r&&(t.substr(Y,2)===xe?(O=xe,Y+=2):(O=r,dt===0&&wt(Te)),O===r&&(t.charCodeAt(Y)===60?(O=lt,Y++):(O=r,dt===0&&wt(Et))))))),O}function xi(){var O,J,re;for(O=Y,J=[],re=kt();re!==r;)J.push(re),re=kt();return J!==r?(re=VA(),re!==r?(xt=O,J=ke(re),O=J):(Y=O,O=r)):(Y=O,O=r),O}function VA(){var O,J,re;if(O=Y,J=[],re=wf(),re!==r)for(;re!==r;)J.push(re),re=wf();else J=r;return J!==r&&(xt=O,J=qt(J)),O=J,O}function wf(){var O,J;return O=Y,J=mn(),J!==r&&(xt=O,J=ir(J)),O=J,O===r&&(O=Y,J=Gg(),J!==r&&(xt=O,J=ir(J)),O=J,O===r&&(O=Y,J=Wg(),J!==r&&(xt=O,J=ir(J)),O=J,O===r&&(O=Y,J=ss(),J!==r&&(xt=O,J=ir(J)),O=J))),O}function mn(){var O,J,re,de;return O=Y,t.substr(Y,2)===Pt?(J=Pt,Y+=2):(J=r,dt===0&&wt(gn)),J!==r?(re=yn(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,dt===0&&wt(Ir)),de!==r?(xt=O,J=Nr(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function Gg(){var O,J,re,de;return O=Y,t.charCodeAt(Y)===39?(J=Pr,Y++):(J=r,dt===0&&wt(Ir)),J!==r?(re=Bf(),re!==r?(t.charCodeAt(Y)===39?(de=Pr,Y++):(de=r,dt===0&&wt(Ir)),de!==r?(xt=O,J=Nr(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function Wg(){var O,J,re,de;if(O=Y,t.substr(Y,2)===nn?(J=nn,Y+=2):(J=r,dt===0&&wt(oi)),J!==r&&(xt=O,J=wo()),O=J,O===r)if(O=Y,t.charCodeAt(Y)===34?(J=rs,Y++):(J=r,dt===0&&wt(eo)),J!==r){for(re=[],de=xl();de!==r;)re.push(de),de=xl();re!==r?(t.charCodeAt(Y)===34?(de=rs,Y++):(de=r,dt===0&&wt(eo)),de!==r?(xt=O,J=Bo(re),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;return O}function ss(){var O,J,re;if(O=Y,J=[],re=ko(),re!==r)for(;re!==r;)J.push(re),re=ko();else J=r;return J!==r&&(xt=O,J=Bo(J)),O=J,O}function xl(){var O,J;return O=Y,J=Xr(),J!==r&&(xt=O,J=Hi(J)),O=J,O===r&&(O=Y,J=kh(),J!==r&&(xt=O,J=to(J)),O=J,O===r&&(O=Y,J=JA(),J!==r&&(xt=O,J=vo(J)),O=J,O===r&&(O=Y,J=vf(),J!==r&&(xt=O,J=RA(J)),O=J))),O}function ko(){var O,J;return O=Y,J=Xr(),J!==r&&(xt=O,J=pf(J)),O=J,O===r&&(O=Y,J=kh(),J!==r&&(xt=O,J=Eh(J)),O=J,O===r&&(O=Y,J=JA(),J!==r&&(xt=O,J=Ih(J)),O=J,O===r&&(O=Y,J=Sy(),J!==r&&(xt=O,J=ro(J)),O=J,O===r&&(O=Y,J=xh(),J!==r&&(xt=O,J=RA(J)),O=J)))),O}function Bf(){var O,J,re;for(O=Y,J=[],jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Rs));re!==r;)J.push(re),jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Rs));return J!==r&&(xt=O,J=no(J)),O=J,O}function vf(){var O,J,re;if(O=Y,J=[],re=kl(),re===r&&(lu.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(cu))),re!==r)for(;re!==r;)J.push(re),re=kl(),re===r&&(lu.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(cu)));else J=r;return J!==r&&(xt=O,J=no(J)),O=J,O}function kl(){var O,J,re;return O=Y,t.substr(Y,2)===uu?(J=uu,Y+=2):(J=r,dt===0&&wt(FA)),J!==r&&(xt=O,J=NA()),O=J,O===r&&(O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,dt===0&&wt(la)),J!==r?(OA.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(gr)),re!==r?(xt=O,J=So(re),O=J):(Y=O,O=r)):(Y=O,O=r)),O}function yn(){var O,J,re;for(O=Y,J=[],re=Qo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Rs)));re!==r;)J.push(re),re=Qo(),re===r&&(jn.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Rs)));return J!==r&&(xt=O,J=no(J)),O=J,O}function Qo(){var O,J,re;return O=Y,t.substr(Y,2)===Me?(J=Me,Y+=2):(J=r,dt===0&&wt(fu)),J!==r&&(xt=O,J=Cr()),O=J,O===r&&(O=Y,t.substr(Y,2)===hf?(J=hf,Y+=2):(J=r,dt===0&&wt(LA)),J!==r&&(xt=O,J=MA()),O=J,O===r&&(O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,dt===0&&wt(la)),J!==r?(Au.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(pu)),re!==r?(xt=O,J=ac(),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===ve?(J=ve,Y+=2):(J=r,dt===0&&wt(Nt)),J!==r&&(xt=O,J=lc()),O=J,O===r&&(O=Y,t.substr(Y,2)===Ni?(J=Ni,Y+=2):(J=r,dt===0&&wt(io)),J!==r&&(xt=O,J=Rt()),O=J,O===r&&(O=Y,t.substr(Y,2)===xn?(J=xn,Y+=2):(J=r,dt===0&&wt(ca)),J!==r&&(xt=O,J=ji()),O=J,O===r&&(O=Y,t.substr(Y,2)===Oi?(J=Oi,Y+=2):(J=r,dt===0&&wt(Oa)),J!==r&&(xt=O,J=dn()),O=J,O===r&&(O=Y,t.substr(Y,2)===Jn?(J=Jn,Y+=2):(J=r,dt===0&&wt(hu)),J!==r&&(xt=O,J=Ch()),O=J,O===r&&(O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,dt===0&&wt(la)),J!==r?(La.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Ma)),re!==r?(xt=O,J=So(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=wu()))))))))),O}function wu(){var O,J,re,de,Ke,ft,dr,Br,_n,di,ws,zA;return O=Y,t.charCodeAt(Y)===92?(J=aa,Y++):(J=r,dt===0&&wt(la)),J!==r?(re=ha(),re!==r?(xt=O,J=Ua(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Xe?(J=Xe,Y+=2):(J=r,dt===0&&wt(Ha)),J!==r?(re=Y,de=Y,Ke=ha(),Ke!==r?(ft=Ns(),ft!==r?(Ke=[Ke,ft],de=Ke):(Y=de,de=r)):(Y=de,de=r),de===r&&(de=ha()),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,J=Ua(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===gf?(J=gf,Y+=2):(J=r,dt===0&&wt(cc)),J!==r?(re=Y,de=Y,Ke=Ns(),Ke!==r?(ft=Ns(),ft!==r?(dr=Ns(),dr!==r?(Br=Ns(),Br!==r?(Ke=[Ke,ft,dr,Br],de=Ke):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,J=Ua(re),O=J):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===wn?(J=wn,Y+=2):(J=r,dt===0&&wt(ua)),J!==r?(re=Y,de=Y,Ke=Ns(),Ke!==r?(ft=Ns(),ft!==r?(dr=Ns(),dr!==r?(Br=Ns(),Br!==r?(_n=Ns(),_n!==r?(di=Ns(),di!==r?(ws=Ns(),ws!==r?(zA=Ns(),zA!==r?(Ke=[Ke,ft,dr,Br,_n,di,ws,zA],de=Ke):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r)):(Y=de,de=r),de!==r?re=t.substring(re,Y):re=de,re!==r?(xt=O,J=_A(re),O=J):(Y=O,O=r)):(Y=O,O=r)))),O}function ha(){var O;return UA.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,dt===0&&wt(fa)),O}function Ns(){var O;return vl.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,dt===0&&wt(Mt)),O}function xh(){var O,J,re,de,Ke;if(O=Y,J=[],re=Y,t.charCodeAt(Y)===92?(de=aa,Y++):(de=r,dt===0&&wt(la)),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,dt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===Aa?(de=Aa,Y+=2):(de=r,dt===0&&wt(ja)),de!==r&&(xt=re,de=ns()),re=de,re===r&&(re=Y,de=Y,dt++,Ke=Dy(),dt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,dt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r))),re!==r)for(;re!==r;)J.push(re),re=Y,t.charCodeAt(Y)===92?(de=aa,Y++):(de=r,dt===0&&wt(la)),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,dt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r),re===r&&(re=Y,t.substr(Y,2)===Aa?(de=Aa,Y+=2):(de=r,dt===0&&wt(ja)),de!==r&&(xt=re,de=ns()),re=de,re===r&&(re=Y,de=Y,dt++,Ke=Dy(),dt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,dt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r)));else J=r;return J!==r&&(xt=O,J=no(J)),O=J,O}function KA(){var O,J,re,de,Ke,ft;if(O=Y,t.charCodeAt(Y)===45?(J=uc,Y++):(J=r,dt===0&&wt(gu)),J===r&&(t.charCodeAt(Y)===43?(J=fc,Y++):(J=r,dt===0&&wt(qa))),J===r&&(J=null),J!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,dt===0&&wt(_e)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,dt===0&&wt(_e));else re=r;if(re!==r)if(t.charCodeAt(Y)===46?(de=Li,Y++):(de=r,dt===0&&wt(Cs)),de!==r){if(Ke=[],it.test(t.charAt(Y))?(ft=t.charAt(Y),Y++):(ft=r,dt===0&&wt(_e)),ft!==r)for(;ft!==r;)Ke.push(ft),it.test(t.charAt(Y))?(ft=t.charAt(Y),Y++):(ft=r,dt===0&&wt(_e));else Ke=r;Ke!==r?(xt=O,J=Sl(J,re,Ke),O=J):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;if(O===r){if(O=Y,t.charCodeAt(Y)===45?(J=uc,Y++):(J=r,dt===0&&wt(gu)),J===r&&(t.charCodeAt(Y)===43?(J=fc,Y++):(J=r,dt===0&&wt(qa))),J===r&&(J=null),J!==r){if(re=[],it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,dt===0&&wt(_e)),de!==r)for(;de!==r;)re.push(de),it.test(t.charAt(Y))?(de=t.charAt(Y),Y++):(de=r,dt===0&&wt(_e));else re=r;re!==r?(xt=O,J=df(J,re),O=J):(Y=O,O=r)}else Y=O,O=r;if(O===r&&(O=Y,J=JA(),J!==r&&(xt=O,J=Ac(J)),O=J,O===r&&(O=Y,J=hc(),J!==r&&(xt=O,J=wi(J)),O=J,O===r)))if(O=Y,t.charCodeAt(Y)===40?(J=Ee,Y++):(J=r,dt===0&&wt(fe)),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=so(),de!==r){for(Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();Ke!==r?(t.charCodeAt(Y)===41?(ft=se,Y++):(ft=r,dt===0&&wt(X)),ft!==r?(xt=O,J=Qn(de),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r}return O}function Sf(){var O,J,re,de,Ke,ft,dr,Br;if(O=Y,J=KA(),J!==r){for(re=[],de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===42?(ft=pc,Y++):(ft=r,dt===0&&wt(Je)),ft===r&&(t.charCodeAt(Y)===47?(ft=st,Y++):(ft=r,dt===0&&wt(St))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=KA(),Br!==r?(xt=de,Ke=lr(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===42?(ft=pc,Y++):(ft=r,dt===0&&wt(Je)),ft===r&&(t.charCodeAt(Y)===47?(ft=st,Y++):(ft=r,dt===0&&wt(St))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=KA(),Br!==r?(xt=de,Ke=lr(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,J=ee(J,re),O=J):(Y=O,O=r)}else Y=O,O=r;return O}function so(){var O,J,re,de,Ke,ft,dr,Br;if(O=Y,J=Sf(),J!==r){for(re=[],de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===43?(ft=fc,Y++):(ft=r,dt===0&&wt(qa)),ft===r&&(t.charCodeAt(Y)===45?(ft=uc,Y++):(ft=r,dt===0&&wt(gu))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=Sf(),Br!==r?(xt=de,Ke=Ie(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r;for(;de!==r;){for(re.push(de),de=Y,Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();if(Ke!==r)if(t.charCodeAt(Y)===43?(ft=fc,Y++):(ft=r,dt===0&&wt(qa)),ft===r&&(t.charCodeAt(Y)===45?(ft=uc,Y++):(ft=r,dt===0&&wt(gu))),ft!==r){for(dr=[],Br=kt();Br!==r;)dr.push(Br),Br=kt();dr!==r?(Br=Sf(),Br!==r?(xt=de,Ke=Ie(J,ft,Br),de=Ke):(Y=de,de=r)):(Y=de,de=r)}else Y=de,de=r;else Y=de,de=r}re!==r?(xt=O,J=ee(J,re),O=J):(Y=O,O=r)}else Y=O,O=r;return O}function Xr(){var O,J,re,de,Ke,ft;if(O=Y,t.substr(Y,3)===Oe?(J=Oe,Y+=3):(J=r,dt===0&&wt(ht)),J!==r){for(re=[],de=kt();de!==r;)re.push(de),de=kt();if(re!==r)if(de=so(),de!==r){for(Ke=[],ft=kt();ft!==r;)Ke.push(ft),ft=kt();Ke!==r?(t.substr(Y,2)===mt?(ft=mt,Y+=2):(ft=r,dt===0&&wt(Dt)),ft!==r?(xt=O,J=tr(de),O=J):(Y=O,O=r)):(Y=O,O=r)}else Y=O,O=r;else Y=O,O=r}else Y=O,O=r;return O}function kh(){var O,J,re,de;return O=Y,t.substr(Y,2)===fn?(J=fn,Y+=2):(J=r,dt===0&&wt(ai)),J!==r?(re=pa(),re!==r?(t.charCodeAt(Y)===41?(de=se,Y++):(de=r,dt===0&&wt(X)),de!==r?(xt=O,J=qi(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O}function JA(){var O,J,re,de,Ke,ft;return O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,dt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,2)===my?(de=my,Y+=2):(de=r,dt===0&&wt(t2)),de!==r?(Ke=Fs(),Ke!==r?(t.charCodeAt(Y)===125?(ft=j,Y++):(ft=r,dt===0&&wt(rt)),ft!==r?(xt=O,J=Do(re,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,dt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,3)===yy?(de=yy,Y+=3):(de=r,dt===0&&wt(wh)),de!==r?(xt=O,J=r2(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,dt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,2)===bo?(de=bo,Y+=2):(de=r,dt===0&&wt(Bh)),de!==r?(Ke=Fs(),Ke!==r?(t.charCodeAt(Y)===125?(ft=j,Y++):(ft=r,dt===0&&wt(rt)),ft!==r?(xt=O,J=vh(re,Ke),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,dt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.substr(Y,3)===du?(de=du,Y+=3):(de=r,dt===0&&wt(Sh)),de!==r?(xt=O,J=Ng(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.substr(Y,2)===Tn?(J=Tn,Y+=2):(J=r,dt===0&&wt(Ga)),J!==r?(re=hc(),re!==r?(t.charCodeAt(Y)===125?(de=j,Y++):(de=r,dt===0&&wt(rt)),de!==r?(xt=O,J=Og(re),O=J):(Y=O,O=r)):(Y=O,O=r)):(Y=O,O=r),O===r&&(O=Y,t.charCodeAt(Y)===36?(J=Lg,Y++):(J=r,dt===0&&wt(Ey)),J!==r?(re=hc(),re!==r?(xt=O,J=Og(re),O=J):(Y=O,O=r)):(Y=O,O=r)))))),O}function Sy(){var O,J,re;return O=Y,J=Yg(),J!==r?(xt=Y,re=mf(J),re?re=void 0:re=r,re!==r?(xt=O,J=Po(J),O=J):(Y=O,O=r)):(Y=O,O=r),O}function Yg(){var O,J,re,de,Ke;if(O=Y,J=[],re=Y,de=Y,dt++,Ke=Th(),dt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,dt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r),re!==r)for(;re!==r;)J.push(re),re=Y,de=Y,dt++,Ke=Th(),dt--,Ke===r?de=void 0:(Y=de,de=r),de!==r?(t.length>Y?(Ke=t.charAt(Y),Y++):(Ke=r,dt===0&&wt(kn)),Ke!==r?(xt=re,de=So(Ke),re=de):(Y=re,re=r)):(Y=re,re=r);else J=r;return J!==r&&(xt=O,J=no(J)),O=J,O}function Qh(){var O,J,re;if(O=Y,J=[],Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Dh)),re!==r)for(;re!==r;)J.push(re),Dl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Dh));else J=r;return J!==r&&(xt=O,J=Mg()),O=J,O}function hc(){var O,J,re;if(O=Y,J=[],bl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Pl)),re!==r)for(;re!==r;)J.push(re),bl.test(t.charAt(Y))?(re=t.charAt(Y),Y++):(re=r,dt===0&&wt(Pl));else J=r;return J!==r&&(xt=O,J=Mg()),O=J,O}function Dy(){var O;return Iy.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,dt===0&&wt(HA)),O}function Th(){var O;return Cy.test(t.charAt(Y))?(O=t.charAt(Y),Y++):(O=r,dt===0&&wt(wy)),O}function kt(){var O,J;if(O=[],jA.test(t.charAt(Y))?(J=t.charAt(Y),Y++):(J=r,dt===0&&wt(qA)),J!==r)for(;J!==r;)O.push(J),jA.test(t.charAt(Y))?(J=t.charAt(Y),Y++):(J=r,dt===0&&wt(qA));else O=r;return O}if(mu=a(),mu!==r&&Y===t.length)return mu;throw mu!==r&&Y!1}){try{return(0,rte.parse)(t,e)}catch(r){throw r.location&&(r.message=r.message.replace(/(\.)?$/,` (line ${r.location.start.line}, column ${r.location.start.column})$1`)),r}}function fE(t,{endSemicolon:e=!1}={}){return t.map(({command:r,type:s},a)=>`${Sx(r)}${s===";"?a!==t.length-1||e?";":"":" &"}`).join(" ")}function Sx(t){return`${AE(t.chain)}${t.then?` ${z_(t.then)}`:""}`}function z_(t){return`${t.type} ${Sx(t.line)}`}function AE(t){return`${X_(t)}${t.then?` ${Z_(t.then)}`:""}`}function Z_(t){return`${t.type} ${AE(t.chain)}`}function X_(t){switch(t.type){case"command":return`${t.envs.length>0?`${t.envs.map(e=>Bx(e)).join(" ")} `:""}${t.args.map(e=>$_(e)).join(" ")}`;case"subshell":return`(${fE(t.subshell)})${t.args.length>0?` ${t.args.map(e=>G2(e)).join(" ")}`:""}`;case"group":return`{ ${fE(t.group,{endSemicolon:!0})} }${t.args.length>0?` ${t.args.map(e=>G2(e)).join(" ")}`:""}`;case"envs":return t.envs.map(e=>Bx(e)).join(" ");default:throw new Error(`Unsupported command type: "${t.type}"`)}}function Bx(t){return`${t.name}=${t.args[0]?Sd(t.args[0]):""}`}function $_(t){switch(t.type){case"redirection":return G2(t);case"argument":return Sd(t);default:throw new Error(`Unsupported argument type: "${t.type}"`)}}function G2(t){return`${t.subtype} ${t.args.map(e=>Sd(e)).join(" ")}`}function Sd(t){return t.segments.map(e=>eU(e)).join("")}function eU(t){let e=(s,a)=>a?`"${s}"`:s,r=s=>s===""?"''":s.match(/[()}<>$|&;"'\n\t ]/)?s.match(/['\t\p{C}]/u)?s.match(/'/)?`"${s.replace(/["$\t\p{C}]/u,IKe)}"`:`$'${s.replace(/[\t\p{C}]/u,ite)}'`:`'${s}'`:s;switch(t.type){case"text":return r(t.text);case"glob":return t.pattern;case"shell":return e(`$(${fE(t.shell)})`,t.quoted);case"variable":return e(typeof t.defaultValue>"u"?typeof t.alternativeValue>"u"?`\${${t.name}}`:t.alternativeValue.length===0?`\${${t.name}:+}`:`\${${t.name}:+${t.alternativeValue.map(s=>Sd(s)).join(" ")}}`:t.defaultValue.length===0?`\${${t.name}:-}`:`\${${t.name}:-${t.defaultValue.map(s=>Sd(s)).join(" ")}}`,t.quoted);case"arithmetic":return`$(( ${Dx(t.arithmetic)} ))`;default:throw new Error(`Unsupported argument segment type: "${t.type}"`)}}function Dx(t){let e=a=>{switch(a){case"addition":return"+";case"subtraction":return"-";case"multiplication":return"*";case"division":return"/";default:throw new Error(`Can't extract operator from arithmetic expression of type "${a}"`)}},r=(a,n)=>n?`( ${a} )`:a,s=a=>r(Dx(a),!["number","variable"].includes(a.type));switch(t.type){case"number":return String(t.value);case"variable":return t.name;default:return`${s(t.left)} ${e(t.type)} ${s(t.right)}`}}var rte,nte,EKe,ite,IKe,ste=It(()=>{rte=et(tte());nte=new Map([["\f","\\f"],[` +`,"\\n"],["\r","\\r"],[" ","\\t"],["\v","\\v"],["\0","\\0"]]),EKe=new Map([["\\","\\\\"],["$","\\$"],['"','\\"'],...Array.from(nte,([t,e])=>[t,`"$'${e}'"`])]),ite=t=>nte.get(t)??`\\x${t.charCodeAt(0).toString(16).padStart(2,"0")}`,IKe=t=>EKe.get(t)??`"$'${ite(t)}'"`});var ate=L((U5t,ote)=>{"use strict";function CKe(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Dd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Dd)}CKe(Dd,Error);Dd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;CAe&&(Ae=W,ce=[]),ce.push(_e))}function rt(_e,x){return new Dd(_e,null,null,x)}function Fe(_e,x,w){return new Dd(Dd.buildMessage(_e,x),_e,x,w)}function Ne(){var _e,x,w,b;return _e=W,x=Pe(),x!==r?(t.charCodeAt(W)===47?(w=n,W++):(w=r,me===0&&j(c)),w!==r?(b=Pe(),b!==r?(te=_e,x=f(x,b),_e=x):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r),_e===r&&(_e=W,x=Pe(),x!==r&&(te=_e,x=p(x)),_e=x),_e}function Pe(){var _e,x,w,b;return _e=W,x=Ye(),x!==r?(t.charCodeAt(W)===64?(w=h,W++):(w=r,me===0&&j(E)),w!==r?(b=it(),b!==r?(te=_e,x=C(x,b),_e=x):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r),_e===r&&(_e=W,x=Ye(),x!==r&&(te=_e,x=S(x)),_e=x),_e}function Ye(){var _e,x,w,b,y;return _e=W,t.charCodeAt(W)===64?(x=h,W++):(x=r,me===0&&j(E)),x!==r?(w=ke(),w!==r?(t.charCodeAt(W)===47?(b=n,W++):(b=r,me===0&&j(c)),b!==r?(y=ke(),y!==r?(te=_e,x=P(),_e=x):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r)):(W=_e,_e=r),_e===r&&(_e=W,x=ke(),x!==r&&(te=_e,x=P()),_e=x),_e}function ke(){var _e,x,w;if(_e=W,x=[],I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R)),w!==r)for(;w!==r;)x.push(w),I.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(R));else x=r;return x!==r&&(te=_e,x=P()),_e=x,_e}function it(){var _e,x,w;if(_e=W,x=[],N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U)),w!==r)for(;w!==r;)x.push(w),N.test(t.charAt(W))?(w=t.charAt(W),W++):(w=r,me===0&&j(U));else x=r;return x!==r&&(te=_e,x=P()),_e=x,_e}if(pe=a(),pe!==r&&W===t.length)return pe;throw pe!==r&&W{lte=et(ate())});var Pd=L((j5t,bd)=>{"use strict";function ute(t){return typeof t>"u"||t===null}function BKe(t){return typeof t=="object"&&t!==null}function vKe(t){return Array.isArray(t)?t:ute(t)?[]:[t]}function SKe(t,e){var r,s,a,n;if(e)for(n=Object.keys(e),r=0,s=n.length;r{"use strict";function W2(t,e){Error.call(this),this.name="YAMLException",this.reason=t,this.mark=e,this.message=(this.reason||"(unknown reason)")+(this.mark?" "+this.mark.toString():""),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=new Error().stack||""}W2.prototype=Object.create(Error.prototype);W2.prototype.constructor=W2;W2.prototype.toString=function(e){var r=this.name+": ";return r+=this.reason||"(unknown reason)",!e&&this.mark&&(r+=" "+this.mark.toString()),r};fte.exports=W2});var hte=L((G5t,pte)=>{"use strict";var Ate=Pd();function tU(t,e,r,s,a){this.name=t,this.buffer=e,this.position=r,this.line=s,this.column=a}tU.prototype.getSnippet=function(e,r){var s,a,n,c,f;if(!this.buffer)return null;for(e=e||4,r=r||75,s="",a=this.position;a>0&&`\0\r +\x85\u2028\u2029`.indexOf(this.buffer.charAt(a-1))===-1;)if(a-=1,this.position-a>r/2-1){s=" ... ",a+=5;break}for(n="",c=this.position;cr/2-1){n=" ... ",c-=5;break}return f=this.buffer.slice(a,c),Ate.repeat(" ",e)+s+f+n+` +`+Ate.repeat(" ",e+this.position-a+s.length)+"^"};tU.prototype.toString=function(e){var r,s="";return this.name&&(s+='in "'+this.name+'" '),s+="at line "+(this.line+1)+", column "+(this.column+1),e||(r=this.getSnippet(),r&&(s+=`: +`+r)),s};pte.exports=tU});var Ds=L((W5t,dte)=>{"use strict";var gte=pE(),PKe=["kind","resolve","construct","instanceOf","predicate","represent","defaultStyle","styleAliases"],xKe=["scalar","sequence","mapping"];function kKe(t){var e={};return t!==null&&Object.keys(t).forEach(function(r){t[r].forEach(function(s){e[String(s)]=r})}),e}function QKe(t,e){if(e=e||{},Object.keys(e).forEach(function(r){if(PKe.indexOf(r)===-1)throw new gte('Unknown option "'+r+'" is met in definition of "'+t+'" YAML type.')}),this.tag=t,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(r){return r},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.defaultStyle=e.defaultStyle||null,this.styleAliases=kKe(e.styleAliases||null),xKe.indexOf(this.kind)===-1)throw new gte('Unknown kind "'+this.kind+'" is specified for "'+t+'" YAML type.')}dte.exports=QKe});var xd=L((Y5t,yte)=>{"use strict";var mte=Pd(),xx=pE(),TKe=Ds();function rU(t,e,r){var s=[];return t.include.forEach(function(a){r=rU(a,e,r)}),t[e].forEach(function(a){r.forEach(function(n,c){n.tag===a.tag&&n.kind===a.kind&&s.push(c)}),r.push(a)}),r.filter(function(a,n){return s.indexOf(n)===-1})}function RKe(){var t={scalar:{},sequence:{},mapping:{},fallback:{}},e,r;function s(a){t[a.kind][a.tag]=t.fallback[a.tag]=a}for(e=0,r=arguments.length;e{"use strict";var FKe=Ds();Ete.exports=new FKe("tag:yaml.org,2002:str",{kind:"scalar",construct:function(t){return t!==null?t:""}})});var wte=L((K5t,Cte)=>{"use strict";var NKe=Ds();Cte.exports=new NKe("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(t){return t!==null?t:[]}})});var vte=L((J5t,Bte)=>{"use strict";var OKe=Ds();Bte.exports=new OKe("tag:yaml.org,2002:map",{kind:"mapping",construct:function(t){return t!==null?t:{}}})});var kx=L((z5t,Ste)=>{"use strict";var LKe=xd();Ste.exports=new LKe({explicit:[Ite(),wte(),vte()]})});var bte=L((Z5t,Dte)=>{"use strict";var MKe=Ds();function _Ke(t){if(t===null)return!0;var e=t.length;return e===1&&t==="~"||e===4&&(t==="null"||t==="Null"||t==="NULL")}function UKe(){return null}function HKe(t){return t===null}Dte.exports=new MKe("tag:yaml.org,2002:null",{kind:"scalar",resolve:_Ke,construct:UKe,predicate:HKe,represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})});var xte=L((X5t,Pte)=>{"use strict";var jKe=Ds();function qKe(t){if(t===null)return!1;var e=t.length;return e===4&&(t==="true"||t==="True"||t==="TRUE")||e===5&&(t==="false"||t==="False"||t==="FALSE")}function GKe(t){return t==="true"||t==="True"||t==="TRUE"}function WKe(t){return Object.prototype.toString.call(t)==="[object Boolean]"}Pte.exports=new jKe("tag:yaml.org,2002:bool",{kind:"scalar",resolve:qKe,construct:GKe,predicate:WKe,represent:{lowercase:function(t){return t?"true":"false"},uppercase:function(t){return t?"TRUE":"FALSE"},camelcase:function(t){return t?"True":"False"}},defaultStyle:"lowercase"})});var Qte=L(($5t,kte)=>{"use strict";var YKe=Pd(),VKe=Ds();function KKe(t){return 48<=t&&t<=57||65<=t&&t<=70||97<=t&&t<=102}function JKe(t){return 48<=t&&t<=55}function zKe(t){return 48<=t&&t<=57}function ZKe(t){if(t===null)return!1;var e=t.length,r=0,s=!1,a;if(!e)return!1;if(a=t[r],(a==="-"||a==="+")&&(a=t[++r]),a==="0"){if(r+1===e)return!0;if(a=t[++r],a==="b"){for(r++;r=0?"0b"+t.toString(2):"-0b"+t.toString(2).slice(1)},octal:function(t){return t>=0?"0"+t.toString(8):"-0"+t.toString(8).slice(1)},decimal:function(t){return t.toString(10)},hexadecimal:function(t){return t>=0?"0x"+t.toString(16).toUpperCase():"-0x"+t.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})});var Fte=L((e9t,Rte)=>{"use strict";var Tte=Pd(),eJe=Ds(),tJe=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function rJe(t){return!(t===null||!tJe.test(t)||t[t.length-1]==="_")}function nJe(t){var e,r,s,a;return e=t.replace(/_/g,"").toLowerCase(),r=e[0]==="-"?-1:1,a=[],"+-".indexOf(e[0])>=0&&(e=e.slice(1)),e===".inf"?r===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===".nan"?NaN:e.indexOf(":")>=0?(e.split(":").forEach(function(n){a.unshift(parseFloat(n,10))}),e=0,s=1,a.forEach(function(n){e+=n*s,s*=60}),r*e):r*parseFloat(e,10)}var iJe=/^[-+]?[0-9]+e/;function sJe(t,e){var r;if(isNaN(t))switch(e){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===t)switch(e){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===t)switch(e){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(Tte.isNegativeZero(t))return"-0.0";return r=t.toString(10),iJe.test(r)?r.replace("e",".e"):r}function oJe(t){return Object.prototype.toString.call(t)==="[object Number]"&&(t%1!==0||Tte.isNegativeZero(t))}Rte.exports=new eJe("tag:yaml.org,2002:float",{kind:"scalar",resolve:rJe,construct:nJe,predicate:oJe,represent:sJe,defaultStyle:"lowercase"})});var nU=L((t9t,Nte)=>{"use strict";var aJe=xd();Nte.exports=new aJe({include:[kx()],implicit:[bte(),xte(),Qte(),Fte()]})});var iU=L((r9t,Ote)=>{"use strict";var lJe=xd();Ote.exports=new lJe({include:[nU()]})});var Ute=L((n9t,_te)=>{"use strict";var cJe=Ds(),Lte=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),Mte=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function uJe(t){return t===null?!1:Lte.exec(t)!==null||Mte.exec(t)!==null}function fJe(t){var e,r,s,a,n,c,f,p=0,h=null,E,C,S;if(e=Lte.exec(t),e===null&&(e=Mte.exec(t)),e===null)throw new Error("Date resolve error");if(r=+e[1],s=+e[2]-1,a=+e[3],!e[4])return new Date(Date.UTC(r,s,a));if(n=+e[4],c=+e[5],f=+e[6],e[7]){for(p=e[7].slice(0,3);p.length<3;)p+="0";p=+p}return e[9]&&(E=+e[10],C=+(e[11]||0),h=(E*60+C)*6e4,e[9]==="-"&&(h=-h)),S=new Date(Date.UTC(r,s,a,n,c,f,p)),h&&S.setTime(S.getTime()-h),S}function AJe(t){return t.toISOString()}_te.exports=new cJe("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:uJe,construct:fJe,instanceOf:Date,represent:AJe})});var jte=L((i9t,Hte)=>{"use strict";var pJe=Ds();function hJe(t){return t==="<<"||t===null}Hte.exports=new pJe("tag:yaml.org,2002:merge",{kind:"scalar",resolve:hJe})});var Wte=L((s9t,Gte)=>{"use strict";var kd;try{qte=ye,kd=qte("buffer").Buffer}catch{}var qte,gJe=Ds(),sU=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= +\r`;function dJe(t){if(t===null)return!1;var e,r,s=0,a=t.length,n=sU;for(r=0;r64)){if(e<0)return!1;s+=6}return s%8===0}function mJe(t){var e,r,s=t.replace(/[\r\n=]/g,""),a=s.length,n=sU,c=0,f=[];for(e=0;e>16&255),f.push(c>>8&255),f.push(c&255)),c=c<<6|n.indexOf(s.charAt(e));return r=a%4*6,r===0?(f.push(c>>16&255),f.push(c>>8&255),f.push(c&255)):r===18?(f.push(c>>10&255),f.push(c>>2&255)):r===12&&f.push(c>>4&255),kd?kd.from?kd.from(f):new kd(f):f}function yJe(t){var e="",r=0,s,a,n=t.length,c=sU;for(s=0;s>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]),r=(r<<8)+t[s];return a=n%3,a===0?(e+=c[r>>18&63],e+=c[r>>12&63],e+=c[r>>6&63],e+=c[r&63]):a===2?(e+=c[r>>10&63],e+=c[r>>4&63],e+=c[r<<2&63],e+=c[64]):a===1&&(e+=c[r>>2&63],e+=c[r<<4&63],e+=c[64],e+=c[64]),e}function EJe(t){return kd&&kd.isBuffer(t)}Gte.exports=new gJe("tag:yaml.org,2002:binary",{kind:"scalar",resolve:dJe,construct:mJe,predicate:EJe,represent:yJe})});var Vte=L((a9t,Yte)=>{"use strict";var IJe=Ds(),CJe=Object.prototype.hasOwnProperty,wJe=Object.prototype.toString;function BJe(t){if(t===null)return!0;var e=[],r,s,a,n,c,f=t;for(r=0,s=f.length;r{"use strict";var SJe=Ds(),DJe=Object.prototype.toString;function bJe(t){if(t===null)return!0;var e,r,s,a,n,c=t;for(n=new Array(c.length),e=0,r=c.length;e{"use strict";var xJe=Ds(),kJe=Object.prototype.hasOwnProperty;function QJe(t){if(t===null)return!0;var e,r=t;for(e in r)if(kJe.call(r,e)&&r[e]!==null)return!1;return!0}function TJe(t){return t!==null?t:{}}zte.exports=new xJe("tag:yaml.org,2002:set",{kind:"mapping",resolve:QJe,construct:TJe})});var gE=L((u9t,Xte)=>{"use strict";var RJe=xd();Xte.exports=new RJe({include:[iU()],implicit:[Ute(),jte()],explicit:[Wte(),Vte(),Jte(),Zte()]})});var ere=L((f9t,$te)=>{"use strict";var FJe=Ds();function NJe(){return!0}function OJe(){}function LJe(){return""}function MJe(t){return typeof t>"u"}$te.exports=new FJe("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:NJe,construct:OJe,predicate:MJe,represent:LJe})});var rre=L((A9t,tre)=>{"use strict";var _Je=Ds();function UJe(t){if(t===null||t.length===0)return!1;var e=t,r=/\/([gim]*)$/.exec(t),s="";return!(e[0]==="/"&&(r&&(s=r[1]),s.length>3||e[e.length-s.length-1]!=="/"))}function HJe(t){var e=t,r=/\/([gim]*)$/.exec(t),s="";return e[0]==="/"&&(r&&(s=r[1]),e=e.slice(1,e.length-s.length-1)),new RegExp(e,s)}function jJe(t){var e="/"+t.source+"/";return t.global&&(e+="g"),t.multiline&&(e+="m"),t.ignoreCase&&(e+="i"),e}function qJe(t){return Object.prototype.toString.call(t)==="[object RegExp]"}tre.exports=new _Je("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:UJe,construct:HJe,predicate:qJe,represent:jJe})});var sre=L((p9t,ire)=>{"use strict";var Qx;try{nre=ye,Qx=nre("esprima")}catch{typeof window<"u"&&(Qx=window.esprima)}var nre,GJe=Ds();function WJe(t){if(t===null)return!1;try{var e="("+t+")",r=Qx.parse(e,{range:!0});return!(r.type!=="Program"||r.body.length!==1||r.body[0].type!=="ExpressionStatement"||r.body[0].expression.type!=="ArrowFunctionExpression"&&r.body[0].expression.type!=="FunctionExpression")}catch{return!1}}function YJe(t){var e="("+t+")",r=Qx.parse(e,{range:!0}),s=[],a;if(r.type!=="Program"||r.body.length!==1||r.body[0].type!=="ExpressionStatement"||r.body[0].expression.type!=="ArrowFunctionExpression"&&r.body[0].expression.type!=="FunctionExpression")throw new Error("Failed to resolve function");return r.body[0].expression.params.forEach(function(n){s.push(n.name)}),a=r.body[0].expression.body.range,r.body[0].expression.body.type==="BlockStatement"?new Function(s,e.slice(a[0]+1,a[1]-1)):new Function(s,"return "+e.slice(a[0],a[1]))}function VJe(t){return t.toString()}function KJe(t){return Object.prototype.toString.call(t)==="[object Function]"}ire.exports=new GJe("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:WJe,construct:YJe,predicate:KJe,represent:VJe})});var Y2=L((g9t,are)=>{"use strict";var ore=xd();are.exports=ore.DEFAULT=new ore({include:[gE()],explicit:[ere(),rre(),sre()]})});var Dre=L((d9t,V2)=>{"use strict";var wp=Pd(),hre=pE(),JJe=hte(),gre=gE(),zJe=Y2(),a0=Object.prototype.hasOwnProperty,Tx=1,dre=2,mre=3,Rx=4,oU=1,ZJe=2,lre=3,XJe=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,$Je=/[\x85\u2028\u2029]/,eze=/[,\[\]\{\}]/,yre=/^(?:!|!!|![a-z\-]+!)$/i,Ere=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function cre(t){return Object.prototype.toString.call(t)}function qf(t){return t===10||t===13}function Td(t){return t===9||t===32}function nl(t){return t===9||t===32||t===10||t===13}function dE(t){return t===44||t===91||t===93||t===123||t===125}function tze(t){var e;return 48<=t&&t<=57?t-48:(e=t|32,97<=e&&e<=102?e-97+10:-1)}function rze(t){return t===120?2:t===117?4:t===85?8:0}function nze(t){return 48<=t&&t<=57?t-48:-1}function ure(t){return t===48?"\0":t===97?"\x07":t===98?"\b":t===116||t===9?" ":t===110?` +`:t===118?"\v":t===102?"\f":t===114?"\r":t===101?"\x1B":t===32?" ":t===34?'"':t===47?"/":t===92?"\\":t===78?"\x85":t===95?"\xA0":t===76?"\u2028":t===80?"\u2029":""}function ize(t){return t<=65535?String.fromCharCode(t):String.fromCharCode((t-65536>>10)+55296,(t-65536&1023)+56320)}var Ire=new Array(256),Cre=new Array(256);for(Qd=0;Qd<256;Qd++)Ire[Qd]=ure(Qd)?1:0,Cre[Qd]=ure(Qd);var Qd;function sze(t,e){this.input=t,this.filename=e.filename||null,this.schema=e.schema||zJe,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=t.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function wre(t,e){return new hre(e,new JJe(t.filename,t.input,t.position,t.line,t.position-t.lineStart))}function Rr(t,e){throw wre(t,e)}function Fx(t,e){t.onWarning&&t.onWarning.call(null,wre(t,e))}var fre={YAML:function(e,r,s){var a,n,c;e.version!==null&&Rr(e,"duplication of %YAML directive"),s.length!==1&&Rr(e,"YAML directive accepts exactly one argument"),a=/^([0-9]+)\.([0-9]+)$/.exec(s[0]),a===null&&Rr(e,"ill-formed argument of the YAML directive"),n=parseInt(a[1],10),c=parseInt(a[2],10),n!==1&&Rr(e,"unacceptable YAML version of the document"),e.version=s[0],e.checkLineBreaks=c<2,c!==1&&c!==2&&Fx(e,"unsupported YAML version of the document")},TAG:function(e,r,s){var a,n;s.length!==2&&Rr(e,"TAG directive accepts exactly two arguments"),a=s[0],n=s[1],yre.test(a)||Rr(e,"ill-formed tag handle (first argument) of the TAG directive"),a0.call(e.tagMap,a)&&Rr(e,'there is a previously declared suffix for "'+a+'" tag handle'),Ere.test(n)||Rr(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[a]=n}};function o0(t,e,r,s){var a,n,c,f;if(e1&&(t.result+=wp.repeat(` +`,e-1))}function oze(t,e,r){var s,a,n,c,f,p,h,E,C=t.kind,S=t.result,P;if(P=t.input.charCodeAt(t.position),nl(P)||dE(P)||P===35||P===38||P===42||P===33||P===124||P===62||P===39||P===34||P===37||P===64||P===96||(P===63||P===45)&&(a=t.input.charCodeAt(t.position+1),nl(a)||r&&dE(a)))return!1;for(t.kind="scalar",t.result="",n=c=t.position,f=!1;P!==0;){if(P===58){if(a=t.input.charCodeAt(t.position+1),nl(a)||r&&dE(a))break}else if(P===35){if(s=t.input.charCodeAt(t.position-1),nl(s))break}else{if(t.position===t.lineStart&&Nx(t)||r&&dE(P))break;if(qf(P))if(p=t.line,h=t.lineStart,E=t.lineIndent,as(t,!1,-1),t.lineIndent>=e){f=!0,P=t.input.charCodeAt(t.position);continue}else{t.position=c,t.line=p,t.lineStart=h,t.lineIndent=E;break}}f&&(o0(t,n,c,!1),lU(t,t.line-p),n=c=t.position,f=!1),Td(P)||(c=t.position+1),P=t.input.charCodeAt(++t.position)}return o0(t,n,c,!1),t.result?!0:(t.kind=C,t.result=S,!1)}function aze(t,e){var r,s,a;if(r=t.input.charCodeAt(t.position),r!==39)return!1;for(t.kind="scalar",t.result="",t.position++,s=a=t.position;(r=t.input.charCodeAt(t.position))!==0;)if(r===39)if(o0(t,s,t.position,!0),r=t.input.charCodeAt(++t.position),r===39)s=t.position,t.position++,a=t.position;else return!0;else qf(r)?(o0(t,s,a,!0),lU(t,as(t,!1,e)),s=a=t.position):t.position===t.lineStart&&Nx(t)?Rr(t,"unexpected end of the document within a single quoted scalar"):(t.position++,a=t.position);Rr(t,"unexpected end of the stream within a single quoted scalar")}function lze(t,e){var r,s,a,n,c,f;if(f=t.input.charCodeAt(t.position),f!==34)return!1;for(t.kind="scalar",t.result="",t.position++,r=s=t.position;(f=t.input.charCodeAt(t.position))!==0;){if(f===34)return o0(t,r,t.position,!0),t.position++,!0;if(f===92){if(o0(t,r,t.position,!0),f=t.input.charCodeAt(++t.position),qf(f))as(t,!1,e);else if(f<256&&Ire[f])t.result+=Cre[f],t.position++;else if((c=rze(f))>0){for(a=c,n=0;a>0;a--)f=t.input.charCodeAt(++t.position),(c=tze(f))>=0?n=(n<<4)+c:Rr(t,"expected hexadecimal character");t.result+=ize(n),t.position++}else Rr(t,"unknown escape sequence");r=s=t.position}else qf(f)?(o0(t,r,s,!0),lU(t,as(t,!1,e)),r=s=t.position):t.position===t.lineStart&&Nx(t)?Rr(t,"unexpected end of the document within a double quoted scalar"):(t.position++,s=t.position)}Rr(t,"unexpected end of the stream within a double quoted scalar")}function cze(t,e){var r=!0,s,a=t.tag,n,c=t.anchor,f,p,h,E,C,S={},P,I,R,N;if(N=t.input.charCodeAt(t.position),N===91)p=93,C=!1,n=[];else if(N===123)p=125,C=!0,n={};else return!1;for(t.anchor!==null&&(t.anchorMap[t.anchor]=n),N=t.input.charCodeAt(++t.position);N!==0;){if(as(t,!0,e),N=t.input.charCodeAt(t.position),N===p)return t.position++,t.tag=a,t.anchor=c,t.kind=C?"mapping":"sequence",t.result=n,!0;r||Rr(t,"missed comma between flow collection entries"),I=P=R=null,h=E=!1,N===63&&(f=t.input.charCodeAt(t.position+1),nl(f)&&(h=E=!0,t.position++,as(t,!0,e))),s=t.line,yE(t,e,Tx,!1,!0),I=t.tag,P=t.result,as(t,!0,e),N=t.input.charCodeAt(t.position),(E||t.line===s)&&N===58&&(h=!0,N=t.input.charCodeAt(++t.position),as(t,!0,e),yE(t,e,Tx,!1,!0),R=t.result),C?mE(t,n,S,I,P,R):h?n.push(mE(t,null,S,I,P,R)):n.push(P),as(t,!0,e),N=t.input.charCodeAt(t.position),N===44?(r=!0,N=t.input.charCodeAt(++t.position)):r=!1}Rr(t,"unexpected end of the stream within a flow collection")}function uze(t,e){var r,s,a=oU,n=!1,c=!1,f=e,p=0,h=!1,E,C;if(C=t.input.charCodeAt(t.position),C===124)s=!1;else if(C===62)s=!0;else return!1;for(t.kind="scalar",t.result="";C!==0;)if(C=t.input.charCodeAt(++t.position),C===43||C===45)oU===a?a=C===43?lre:ZJe:Rr(t,"repeat of a chomping mode identifier");else if((E=nze(C))>=0)E===0?Rr(t,"bad explicit indentation width of a block scalar; it cannot be less than one"):c?Rr(t,"repeat of an indentation width identifier"):(f=e+E-1,c=!0);else break;if(Td(C)){do C=t.input.charCodeAt(++t.position);while(Td(C));if(C===35)do C=t.input.charCodeAt(++t.position);while(!qf(C)&&C!==0)}for(;C!==0;){for(aU(t),t.lineIndent=0,C=t.input.charCodeAt(t.position);(!c||t.lineIndentf&&(f=t.lineIndent),qf(C)){p++;continue}if(t.lineIndente)&&p!==0)Rr(t,"bad indentation of a sequence entry");else if(t.lineIndente)&&(yE(t,e,Rx,!0,a)&&(I?S=t.result:P=t.result),I||(mE(t,h,E,C,S,P,n,c),C=S=P=null),as(t,!0,-1),N=t.input.charCodeAt(t.position)),t.lineIndent>e&&N!==0)Rr(t,"bad indentation of a mapping entry");else if(t.lineIndente?p=1:t.lineIndent===e?p=0:t.lineIndente?p=1:t.lineIndent===e?p=0:t.lineIndent tag; it should be "scalar", not "'+t.kind+'"'),C=0,S=t.implicitTypes.length;C tag; it should be "'+P.kind+'", not "'+t.kind+'"'),P.resolve(t.result)?(t.result=P.construct(t.result),t.anchor!==null&&(t.anchorMap[t.anchor]=t.result)):Rr(t,"cannot resolve a node with !<"+t.tag+"> explicit tag")):Rr(t,"unknown tag !<"+t.tag+">");return t.listener!==null&&t.listener("close",t),t.tag!==null||t.anchor!==null||E}function gze(t){var e=t.position,r,s,a,n=!1,c;for(t.version=null,t.checkLineBreaks=t.legacy,t.tagMap={},t.anchorMap={};(c=t.input.charCodeAt(t.position))!==0&&(as(t,!0,-1),c=t.input.charCodeAt(t.position),!(t.lineIndent>0||c!==37));){for(n=!0,c=t.input.charCodeAt(++t.position),r=t.position;c!==0&&!nl(c);)c=t.input.charCodeAt(++t.position);for(s=t.input.slice(r,t.position),a=[],s.length<1&&Rr(t,"directive name must not be less than one character in length");c!==0;){for(;Td(c);)c=t.input.charCodeAt(++t.position);if(c===35){do c=t.input.charCodeAt(++t.position);while(c!==0&&!qf(c));break}if(qf(c))break;for(r=t.position;c!==0&&!nl(c);)c=t.input.charCodeAt(++t.position);a.push(t.input.slice(r,t.position))}c!==0&&aU(t),a0.call(fre,s)?fre[s](t,s,a):Fx(t,'unknown document directive "'+s+'"')}if(as(t,!0,-1),t.lineIndent===0&&t.input.charCodeAt(t.position)===45&&t.input.charCodeAt(t.position+1)===45&&t.input.charCodeAt(t.position+2)===45?(t.position+=3,as(t,!0,-1)):n&&Rr(t,"directives end mark is expected"),yE(t,t.lineIndent-1,Rx,!1,!0),as(t,!0,-1),t.checkLineBreaks&&$Je.test(t.input.slice(e,t.position))&&Fx(t,"non-ASCII line breaks are interpreted as content"),t.documents.push(t.result),t.position===t.lineStart&&Nx(t)){t.input.charCodeAt(t.position)===46&&(t.position+=3,as(t,!0,-1));return}if(t.position"u"&&(r=e,e=null);var s=Bre(t,r);if(typeof e!="function")return s;for(var a=0,n=s.length;a"u"&&(r=e,e=null),vre(t,e,wp.extend({schema:gre},r))}function mze(t,e){return Sre(t,wp.extend({schema:gre},e))}V2.exports.loadAll=vre;V2.exports.load=Sre;V2.exports.safeLoadAll=dze;V2.exports.safeLoad=mze});var Jre=L((m9t,AU)=>{"use strict";var J2=Pd(),z2=pE(),yze=Y2(),Eze=gE(),Fre=Object.prototype.toString,Nre=Object.prototype.hasOwnProperty,Ize=9,K2=10,Cze=13,wze=32,Bze=33,vze=34,Ore=35,Sze=37,Dze=38,bze=39,Pze=42,Lre=44,xze=45,Mre=58,kze=61,Qze=62,Tze=63,Rze=64,_re=91,Ure=93,Fze=96,Hre=123,Nze=124,jre=125,jo={};jo[0]="\\0";jo[7]="\\a";jo[8]="\\b";jo[9]="\\t";jo[10]="\\n";jo[11]="\\v";jo[12]="\\f";jo[13]="\\r";jo[27]="\\e";jo[34]='\\"';jo[92]="\\\\";jo[133]="\\N";jo[160]="\\_";jo[8232]="\\L";jo[8233]="\\P";var Oze=["y","Y","yes","Yes","YES","on","On","ON","n","N","no","No","NO","off","Off","OFF"];function Lze(t,e){var r,s,a,n,c,f,p;if(e===null)return{};for(r={},s=Object.keys(e),a=0,n=s.length;a0?t.charCodeAt(n-1):null,S=S&&xre(c,f)}else{for(n=0;ns&&t[C+1]!==" ",C=n);else if(!EE(c))return Ox;f=n>0?t.charCodeAt(n-1):null,S=S&&xre(c,f)}h=h||E&&n-C-1>s&&t[C+1]!==" "}return!p&&!h?S&&!a(t)?Gre:Wre:r>9&&qre(t)?Ox:h?Vre:Yre}function qze(t,e,r,s){t.dump=function(){if(e.length===0)return"''";if(!t.noCompatMode&&Oze.indexOf(e)!==-1)return"'"+e+"'";var a=t.indent*Math.max(1,r),n=t.lineWidth===-1?-1:Math.max(Math.min(t.lineWidth,40),t.lineWidth-a),c=s||t.flowLevel>-1&&r>=t.flowLevel;function f(p){return _ze(t,p)}switch(jze(e,c,t.indent,n,f)){case Gre:return e;case Wre:return"'"+e.replace(/'/g,"''")+"'";case Yre:return"|"+kre(e,t.indent)+Qre(Pre(e,a));case Vre:return">"+kre(e,t.indent)+Qre(Pre(Gze(e,n),a));case Ox:return'"'+Wze(e,n)+'"';default:throw new z2("impossible error: invalid scalar style")}}()}function kre(t,e){var r=qre(t)?String(e):"",s=t[t.length-1]===` +`,a=s&&(t[t.length-2]===` +`||t===` +`),n=a?"+":s?"":"-";return r+n+` +`}function Qre(t){return t[t.length-1]===` +`?t.slice(0,-1):t}function Gze(t,e){for(var r=/(\n+)([^\n]*)/g,s=function(){var h=t.indexOf(` +`);return h=h!==-1?h:t.length,r.lastIndex=h,Tre(t.slice(0,h),e)}(),a=t[0]===` +`||t[0]===" ",n,c;c=r.exec(t);){var f=c[1],p=c[2];n=p[0]===" ",s+=f+(!a&&!n&&p!==""?` +`:"")+Tre(p,e),a=n}return s}function Tre(t,e){if(t===""||t[0]===" ")return t;for(var r=/ [^ ]/g,s,a=0,n,c=0,f=0,p="";s=r.exec(t);)f=s.index,f-a>e&&(n=c>a?c:f,p+=` +`+t.slice(a,n),a=n+1),c=f;return p+=` +`,t.length-a>e&&c>a?p+=t.slice(a,c)+` +`+t.slice(c+1):p+=t.slice(a),p.slice(1)}function Wze(t){for(var e="",r,s,a,n=0;n=55296&&r<=56319&&(s=t.charCodeAt(n+1),s>=56320&&s<=57343)){e+=bre((r-55296)*1024+s-56320+65536),n++;continue}a=jo[r],e+=!a&&EE(r)?t[n]:a||bre(r)}return e}function Yze(t,e,r){var s="",a=t.tag,n,c;for(n=0,c=r.length;n1024&&(E+="? "),E+=t.dump+(t.condenseFlow?'"':"")+":"+(t.condenseFlow?"":" "),Rd(t,e,h,!1,!1)&&(E+=t.dump,s+=E));t.tag=a,t.dump="{"+s+"}"}function Jze(t,e,r,s){var a="",n=t.tag,c=Object.keys(r),f,p,h,E,C,S;if(t.sortKeys===!0)c.sort();else if(typeof t.sortKeys=="function")c.sort(t.sortKeys);else if(t.sortKeys)throw new z2("sortKeys must be a boolean or a function");for(f=0,p=c.length;f1024,C&&(t.dump&&K2===t.dump.charCodeAt(0)?S+="?":S+="? "),S+=t.dump,C&&(S+=cU(t,e)),Rd(t,e+1,E,!0,C)&&(t.dump&&K2===t.dump.charCodeAt(0)?S+=":":S+=": ",S+=t.dump,a+=S));t.tag=n,t.dump=a||"{}"}function Rre(t,e,r){var s,a,n,c,f,p;for(a=r?t.explicitTypes:t.implicitTypes,n=0,c=a.length;n tag resolver accepts not "'+p+'" style');t.dump=s}return!0}return!1}function Rd(t,e,r,s,a,n){t.tag=null,t.dump=r,Rre(t,r,!1)||Rre(t,r,!0);var c=Fre.call(t.dump);s&&(s=t.flowLevel<0||t.flowLevel>e);var f=c==="[object Object]"||c==="[object Array]",p,h;if(f&&(p=t.duplicates.indexOf(r),h=p!==-1),(t.tag!==null&&t.tag!=="?"||h||t.indent!==2&&e>0)&&(a=!1),h&&t.usedDuplicates[p])t.dump="*ref_"+p;else{if(f&&h&&!t.usedDuplicates[p]&&(t.usedDuplicates[p]=!0),c==="[object Object]")s&&Object.keys(t.dump).length!==0?(Jze(t,e,t.dump,a),h&&(t.dump="&ref_"+p+t.dump)):(Kze(t,e,t.dump),h&&(t.dump="&ref_"+p+" "+t.dump));else if(c==="[object Array]"){var E=t.noArrayIndent&&e>0?e-1:e;s&&t.dump.length!==0?(Vze(t,E,t.dump,a),h&&(t.dump="&ref_"+p+t.dump)):(Yze(t,E,t.dump),h&&(t.dump="&ref_"+p+" "+t.dump))}else if(c==="[object String]")t.tag!=="?"&&qze(t,t.dump,e,n);else{if(t.skipInvalid)return!1;throw new z2("unacceptable kind of an object to dump "+c)}t.tag!==null&&t.tag!=="?"&&(t.dump="!<"+t.tag+"> "+t.dump)}return!0}function zze(t,e){var r=[],s=[],a,n;for(uU(t,r,s),a=0,n=s.length;a{"use strict";var Lx=Dre(),zre=Jre();function Mx(t){return function(){throw new Error("Function "+t+" is deprecated and cannot be used.")}}Gi.exports.Type=Ds();Gi.exports.Schema=xd();Gi.exports.FAILSAFE_SCHEMA=kx();Gi.exports.JSON_SCHEMA=nU();Gi.exports.CORE_SCHEMA=iU();Gi.exports.DEFAULT_SAFE_SCHEMA=gE();Gi.exports.DEFAULT_FULL_SCHEMA=Y2();Gi.exports.load=Lx.load;Gi.exports.loadAll=Lx.loadAll;Gi.exports.safeLoad=Lx.safeLoad;Gi.exports.safeLoadAll=Lx.safeLoadAll;Gi.exports.dump=zre.dump;Gi.exports.safeDump=zre.safeDump;Gi.exports.YAMLException=pE();Gi.exports.MINIMAL_SCHEMA=kx();Gi.exports.SAFE_SCHEMA=gE();Gi.exports.DEFAULT_SCHEMA=Y2();Gi.exports.scan=Mx("scan");Gi.exports.parse=Mx("parse");Gi.exports.compose=Mx("compose");Gi.exports.addConstructor=Mx("addConstructor")});var $re=L((E9t,Xre)=>{"use strict";var Xze=Zre();Xre.exports=Xze});var tne=L((I9t,ene)=>{"use strict";function $ze(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function Fd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,Fd)}$ze(Fd,Error);Fd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C({[ht]:Oe})))},Ae=function(ee){return ee},ce=function(ee){return ee},me=La("correct indentation"),pe=" ",Be=dn(" ",!1),Ce=function(ee){return ee.length===lr*St},g=function(ee){return ee.length===(lr+1)*St},we=function(){return lr++,!0},Ee=function(){return lr--,!0},fe=function(){return ca()},se=La("pseudostring"),X=/^[^\r\n\t ?:,\][{}#&*!|>'"%@`\-]/,De=Jn(["\r",` +`," "," ","?",":",",","]","[","{","}","#","&","*","!","|",">","'",'"',"%","@","`","-"],!0,!1),Re=/^[^\r\n\t ,\][{}:#"']/,gt=Jn(["\r",` +`," "," ",",","]","[","{","}",":","#",'"',"'"],!0,!1),j=function(){return ca().replace(/^ *| *$/g,"")},rt="--",Fe=dn("--",!1),Ne=/^[a-zA-Z\/0-9]/,Pe=Jn([["a","z"],["A","Z"],"/",["0","9"]],!1,!1),Ye=/^[^\r\n\t :,]/,ke=Jn(["\r",` +`," "," ",":",","],!0,!1),it="null",_e=dn("null",!1),x=function(){return null},w="true",b=dn("true",!1),y=function(){return!0},F="false",z=dn("false",!1),Z=function(){return!1},$=La("string"),oe='"',xe=dn('"',!1),Te=function(){return""},lt=function(ee){return ee},Et=function(ee){return ee.join("")},qt=/^[^"\\\0-\x1F\x7F]/,ir=Jn(['"',"\\",["\0",""],"\x7F"],!0,!1),Pt='\\"',gn=dn('\\"',!1),Pr=function(){return'"'},Ir="\\\\",Nr=dn("\\\\",!1),nn=function(){return"\\"},oi="\\/",wo=dn("\\/",!1),rs=function(){return"/"},eo="\\b",Bo=dn("\\b",!1),Hi=function(){return"\b"},to="\\f",vo=dn("\\f",!1),RA=function(){return"\f"},pf="\\n",Eh=dn("\\n",!1),Ih=function(){return` +`},ro="\\r",jn=dn("\\r",!1),Rs=function(){return"\r"},no="\\t",lu=dn("\\t",!1),cu=function(){return" "},uu="\\u",FA=dn("\\u",!1),NA=function(ee,Ie,Oe,ht){return String.fromCharCode(parseInt(`0x${ee}${Ie}${Oe}${ht}`))},aa=/^[0-9a-fA-F]/,la=Jn([["0","9"],["a","f"],["A","F"]],!1,!1),OA=La("blank space"),gr=/^[ \t]/,So=Jn([" "," "],!1,!1),Me=La("white space"),fu=/^[ \t\n\r]/,Cr=Jn([" "," ",` +`,"\r"],!1,!1),hf=`\r +`,LA=dn(`\r +`,!1),MA=` +`,Au=dn(` +`,!1),pu="\r",ac=dn("\r",!1),ve=0,Nt=0,lc=[{line:1,column:1}],Ni=0,io=[],Rt=0,xn;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function ca(){return t.substring(Nt,ve)}function ji(){return Ua(Nt,ve)}function Oi(ee,Ie){throw Ie=Ie!==void 0?Ie:Ua(Nt,ve),gf([La(ee)],t.substring(Nt,ve),Ie)}function Oa(ee,Ie){throw Ie=Ie!==void 0?Ie:Ua(Nt,ve),Ha(ee,Ie)}function dn(ee,Ie){return{type:"literal",text:ee,ignoreCase:Ie}}function Jn(ee,Ie,Oe){return{type:"class",parts:ee,inverted:Ie,ignoreCase:Oe}}function hu(){return{type:"any"}}function Ch(){return{type:"end"}}function La(ee){return{type:"other",description:ee}}function Ma(ee){var Ie=lc[ee],Oe;if(Ie)return Ie;for(Oe=ee-1;!lc[Oe];)Oe--;for(Ie=lc[Oe],Ie={line:Ie.line,column:Ie.column};OeNi&&(Ni=ve,io=[]),io.push(ee))}function Ha(ee,Ie){return new Fd(ee,null,null,Ie)}function gf(ee,Ie,Oe){return new Fd(Fd.buildMessage(ee,Ie),ee,Ie,Oe)}function cc(){var ee;return ee=_A(),ee}function wn(){var ee,Ie,Oe;for(ee=ve,Ie=[],Oe=ua();Oe!==r;)Ie.push(Oe),Oe=ua();return Ie!==r&&(Nt=ee,Ie=n(Ie)),ee=Ie,ee}function ua(){var ee,Ie,Oe,ht,mt;return ee=ve,Ie=vl(),Ie!==r?(t.charCodeAt(ve)===45?(Oe=c,ve++):(Oe=r,Rt===0&&Xe(f)),Oe!==r?(ht=Qn(),ht!==r?(mt=fa(),mt!==r?(Nt=ee,Ie=p(mt),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee}function _A(){var ee,Ie,Oe;for(ee=ve,Ie=[],Oe=UA();Oe!==r;)Ie.push(Oe),Oe=UA();return Ie!==r&&(Nt=ee,Ie=h(Ie)),ee=Ie,ee}function UA(){var ee,Ie,Oe,ht,mt,Dt,tr,fn,ai;if(ee=ve,Ie=Qn(),Ie===r&&(Ie=null),Ie!==r){if(Oe=ve,t.charCodeAt(ve)===35?(ht=E,ve++):(ht=r,Rt===0&&Xe(C)),ht!==r){if(mt=[],Dt=ve,tr=ve,Rt++,fn=st(),Rt--,fn===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(fn=t.charAt(ve),ve++):(fn=r,Rt===0&&Xe(S)),fn!==r?(tr=[tr,fn],Dt=tr):(ve=Dt,Dt=r)):(ve=Dt,Dt=r),Dt!==r)for(;Dt!==r;)mt.push(Dt),Dt=ve,tr=ve,Rt++,fn=st(),Rt--,fn===r?tr=void 0:(ve=tr,tr=r),tr!==r?(t.length>ve?(fn=t.charAt(ve),ve++):(fn=r,Rt===0&&Xe(S)),fn!==r?(tr=[tr,fn],Dt=tr):(ve=Dt,Dt=r)):(ve=Dt,Dt=r);else mt=r;mt!==r?(ht=[ht,mt],Oe=ht):(ve=Oe,Oe=r)}else ve=Oe,Oe=r;if(Oe===r&&(Oe=null),Oe!==r){if(ht=[],mt=Je(),mt!==r)for(;mt!==r;)ht.push(mt),mt=Je();else ht=r;ht!==r?(Nt=ee,Ie=P(),ee=Ie):(ve=ee,ee=r)}else ve=ee,ee=r}else ve=ee,ee=r;if(ee===r&&(ee=ve,Ie=vl(),Ie!==r?(Oe=ja(),Oe!==r?(ht=Qn(),ht===r&&(ht=null),ht!==r?(t.charCodeAt(ve)===58?(mt=I,ve++):(mt=r,Rt===0&&Xe(R)),mt!==r?(Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(tr=fa(),tr!==r?(Nt=ee,Ie=N(Oe,tr),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r&&(ee=ve,Ie=vl(),Ie!==r?(Oe=ns(),Oe!==r?(ht=Qn(),ht===r&&(ht=null),ht!==r?(t.charCodeAt(ve)===58?(mt=I,ve++):(mt=r,Rt===0&&Xe(R)),mt!==r?(Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(tr=fa(),tr!==r?(Nt=ee,Ie=N(Oe,tr),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r))){if(ee=ve,Ie=vl(),Ie!==r)if(Oe=ns(),Oe!==r)if(ht=Qn(),ht!==r)if(mt=gu(),mt!==r){if(Dt=[],tr=Je(),tr!==r)for(;tr!==r;)Dt.push(tr),tr=Je();else Dt=r;Dt!==r?(Nt=ee,Ie=N(Oe,mt),ee=Ie):(ve=ee,ee=r)}else ve=ee,ee=r;else ve=ee,ee=r;else ve=ee,ee=r;else ve=ee,ee=r;if(ee===r)if(ee=ve,Ie=vl(),Ie!==r)if(Oe=ns(),Oe!==r){if(ht=[],mt=ve,Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&Xe(W)),tr!==r?(fn=Qn(),fn===r&&(fn=null),fn!==r?(ai=ns(),ai!==r?(Nt=mt,Dt=te(Oe,ai),mt=Dt):(ve=mt,mt=r)):(ve=mt,mt=r)):(ve=mt,mt=r)):(ve=mt,mt=r),mt!==r)for(;mt!==r;)ht.push(mt),mt=ve,Dt=Qn(),Dt===r&&(Dt=null),Dt!==r?(t.charCodeAt(ve)===44?(tr=U,ve++):(tr=r,Rt===0&&Xe(W)),tr!==r?(fn=Qn(),fn===r&&(fn=null),fn!==r?(ai=ns(),ai!==r?(Nt=mt,Dt=te(Oe,ai),mt=Dt):(ve=mt,mt=r)):(ve=mt,mt=r)):(ve=mt,mt=r)):(ve=mt,mt=r);else ht=r;ht!==r?(mt=Qn(),mt===r&&(mt=null),mt!==r?(t.charCodeAt(ve)===58?(Dt=I,ve++):(Dt=r,Rt===0&&Xe(R)),Dt!==r?(tr=Qn(),tr===r&&(tr=null),tr!==r?(fn=fa(),fn!==r?(Nt=ee,Ie=ie(Oe,ht,fn),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)}else ve=ee,ee=r;else ve=ee,ee=r}return ee}function fa(){var ee,Ie,Oe,ht,mt,Dt,tr;if(ee=ve,Ie=ve,Rt++,Oe=ve,ht=st(),ht!==r?(mt=Mt(),mt!==r?(t.charCodeAt(ve)===45?(Dt=c,ve++):(Dt=r,Rt===0&&Xe(f)),Dt!==r?(tr=Qn(),tr!==r?(ht=[ht,mt,Dt,tr],Oe=ht):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r)):(ve=Oe,Oe=r),Rt--,Oe!==r?(ve=Ie,Ie=void 0):Ie=r,Ie!==r?(Oe=Je(),Oe!==r?(ht=kn(),ht!==r?(mt=wn(),mt!==r?(Dt=Aa(),Dt!==r?(Nt=ee,Ie=Ae(mt),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r&&(ee=ve,Ie=st(),Ie!==r?(Oe=kn(),Oe!==r?(ht=_A(),ht!==r?(mt=Aa(),mt!==r?(Nt=ee,Ie=Ae(ht),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r))if(ee=ve,Ie=uc(),Ie!==r){if(Oe=[],ht=Je(),ht!==r)for(;ht!==r;)Oe.push(ht),ht=Je();else Oe=r;Oe!==r?(Nt=ee,Ie=ce(Ie),ee=Ie):(ve=ee,ee=r)}else ve=ee,ee=r;return ee}function vl(){var ee,Ie,Oe;for(Rt++,ee=ve,Ie=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));Oe!==r;)Ie.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));return Ie!==r?(Nt=ve,Oe=Ce(Ie),Oe?Oe=void 0:Oe=r,Oe!==r?(Ie=[Ie,Oe],ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r),Rt--,ee===r&&(Ie=r,Rt===0&&Xe(me)),ee}function Mt(){var ee,Ie,Oe;for(ee=ve,Ie=[],t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));Oe!==r;)Ie.push(Oe),t.charCodeAt(ve)===32?(Oe=pe,ve++):(Oe=r,Rt===0&&Xe(Be));return Ie!==r?(Nt=ve,Oe=g(Ie),Oe?Oe=void 0:Oe=r,Oe!==r?(Ie=[Ie,Oe],ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r),ee}function kn(){var ee;return Nt=ve,ee=we(),ee?ee=void 0:ee=r,ee}function Aa(){var ee;return Nt=ve,ee=Ee(),ee?ee=void 0:ee=r,ee}function ja(){var ee;return ee=Sl(),ee===r&&(ee=fc()),ee}function ns(){var ee,Ie,Oe;if(ee=Sl(),ee===r){if(ee=ve,Ie=[],Oe=qa(),Oe!==r)for(;Oe!==r;)Ie.push(Oe),Oe=qa();else Ie=r;Ie!==r&&(Nt=ee,Ie=fe()),ee=Ie}return ee}function uc(){var ee;return ee=Li(),ee===r&&(ee=Cs(),ee===r&&(ee=Sl(),ee===r&&(ee=fc()))),ee}function gu(){var ee;return ee=Li(),ee===r&&(ee=Sl(),ee===r&&(ee=qa())),ee}function fc(){var ee,Ie,Oe,ht,mt,Dt;if(Rt++,ee=ve,X.test(t.charAt(ve))?(Ie=t.charAt(ve),ve++):(Ie=r,Rt===0&&Xe(De)),Ie!==r){for(Oe=[],ht=ve,mt=Qn(),mt===r&&(mt=null),mt!==r?(Re.test(t.charAt(ve))?(Dt=t.charAt(ve),ve++):(Dt=r,Rt===0&&Xe(gt)),Dt!==r?(mt=[mt,Dt],ht=mt):(ve=ht,ht=r)):(ve=ht,ht=r);ht!==r;)Oe.push(ht),ht=ve,mt=Qn(),mt===r&&(mt=null),mt!==r?(Re.test(t.charAt(ve))?(Dt=t.charAt(ve),ve++):(Dt=r,Rt===0&&Xe(gt)),Dt!==r?(mt=[mt,Dt],ht=mt):(ve=ht,ht=r)):(ve=ht,ht=r);Oe!==r?(Nt=ee,Ie=j(),ee=Ie):(ve=ee,ee=r)}else ve=ee,ee=r;return Rt--,ee===r&&(Ie=r,Rt===0&&Xe(se)),ee}function qa(){var ee,Ie,Oe,ht,mt;if(ee=ve,t.substr(ve,2)===rt?(Ie=rt,ve+=2):(Ie=r,Rt===0&&Xe(Fe)),Ie===r&&(Ie=null),Ie!==r)if(Ne.test(t.charAt(ve))?(Oe=t.charAt(ve),ve++):(Oe=r,Rt===0&&Xe(Pe)),Oe!==r){for(ht=[],Ye.test(t.charAt(ve))?(mt=t.charAt(ve),ve++):(mt=r,Rt===0&&Xe(ke));mt!==r;)ht.push(mt),Ye.test(t.charAt(ve))?(mt=t.charAt(ve),ve++):(mt=r,Rt===0&&Xe(ke));ht!==r?(Nt=ee,Ie=j(),ee=Ie):(ve=ee,ee=r)}else ve=ee,ee=r;else ve=ee,ee=r;return ee}function Li(){var ee,Ie;return ee=ve,t.substr(ve,4)===it?(Ie=it,ve+=4):(Ie=r,Rt===0&&Xe(_e)),Ie!==r&&(Nt=ee,Ie=x()),ee=Ie,ee}function Cs(){var ee,Ie;return ee=ve,t.substr(ve,4)===w?(Ie=w,ve+=4):(Ie=r,Rt===0&&Xe(b)),Ie!==r&&(Nt=ee,Ie=y()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,5)===F?(Ie=F,ve+=5):(Ie=r,Rt===0&&Xe(z)),Ie!==r&&(Nt=ee,Ie=Z()),ee=Ie),ee}function Sl(){var ee,Ie,Oe,ht;return Rt++,ee=ve,t.charCodeAt(ve)===34?(Ie=oe,ve++):(Ie=r,Rt===0&&Xe(xe)),Ie!==r?(t.charCodeAt(ve)===34?(Oe=oe,ve++):(Oe=r,Rt===0&&Xe(xe)),Oe!==r?(Nt=ee,Ie=Te(),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r),ee===r&&(ee=ve,t.charCodeAt(ve)===34?(Ie=oe,ve++):(Ie=r,Rt===0&&Xe(xe)),Ie!==r?(Oe=df(),Oe!==r?(t.charCodeAt(ve)===34?(ht=oe,ve++):(ht=r,Rt===0&&Xe(xe)),ht!==r?(Nt=ee,Ie=lt(Oe),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)),Rt--,ee===r&&(Ie=r,Rt===0&&Xe($)),ee}function df(){var ee,Ie,Oe;if(ee=ve,Ie=[],Oe=Ac(),Oe!==r)for(;Oe!==r;)Ie.push(Oe),Oe=Ac();else Ie=r;return Ie!==r&&(Nt=ee,Ie=Et(Ie)),ee=Ie,ee}function Ac(){var ee,Ie,Oe,ht,mt,Dt;return qt.test(t.charAt(ve))?(ee=t.charAt(ve),ve++):(ee=r,Rt===0&&Xe(ir)),ee===r&&(ee=ve,t.substr(ve,2)===Pt?(Ie=Pt,ve+=2):(Ie=r,Rt===0&&Xe(gn)),Ie!==r&&(Nt=ee,Ie=Pr()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===Ir?(Ie=Ir,ve+=2):(Ie=r,Rt===0&&Xe(Nr)),Ie!==r&&(Nt=ee,Ie=nn()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===oi?(Ie=oi,ve+=2):(Ie=r,Rt===0&&Xe(wo)),Ie!==r&&(Nt=ee,Ie=rs()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===eo?(Ie=eo,ve+=2):(Ie=r,Rt===0&&Xe(Bo)),Ie!==r&&(Nt=ee,Ie=Hi()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===to?(Ie=to,ve+=2):(Ie=r,Rt===0&&Xe(vo)),Ie!==r&&(Nt=ee,Ie=RA()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===pf?(Ie=pf,ve+=2):(Ie=r,Rt===0&&Xe(Eh)),Ie!==r&&(Nt=ee,Ie=Ih()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===ro?(Ie=ro,ve+=2):(Ie=r,Rt===0&&Xe(jn)),Ie!==r&&(Nt=ee,Ie=Rs()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===no?(Ie=no,ve+=2):(Ie=r,Rt===0&&Xe(lu)),Ie!==r&&(Nt=ee,Ie=cu()),ee=Ie,ee===r&&(ee=ve,t.substr(ve,2)===uu?(Ie=uu,ve+=2):(Ie=r,Rt===0&&Xe(FA)),Ie!==r?(Oe=wi(),Oe!==r?(ht=wi(),ht!==r?(mt=wi(),mt!==r?(Dt=wi(),Dt!==r?(Nt=ee,Ie=NA(Oe,ht,mt,Dt),ee=Ie):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)):(ve=ee,ee=r)))))))))),ee}function wi(){var ee;return aa.test(t.charAt(ve))?(ee=t.charAt(ve),ve++):(ee=r,Rt===0&&Xe(la)),ee}function Qn(){var ee,Ie;if(Rt++,ee=[],gr.test(t.charAt(ve))?(Ie=t.charAt(ve),ve++):(Ie=r,Rt===0&&Xe(So)),Ie!==r)for(;Ie!==r;)ee.push(Ie),gr.test(t.charAt(ve))?(Ie=t.charAt(ve),ve++):(Ie=r,Rt===0&&Xe(So));else ee=r;return Rt--,ee===r&&(Ie=r,Rt===0&&Xe(OA)),ee}function pc(){var ee,Ie;if(Rt++,ee=[],fu.test(t.charAt(ve))?(Ie=t.charAt(ve),ve++):(Ie=r,Rt===0&&Xe(Cr)),Ie!==r)for(;Ie!==r;)ee.push(Ie),fu.test(t.charAt(ve))?(Ie=t.charAt(ve),ve++):(Ie=r,Rt===0&&Xe(Cr));else ee=r;return Rt--,ee===r&&(Ie=r,Rt===0&&Xe(Me)),ee}function Je(){var ee,Ie,Oe,ht,mt,Dt;if(ee=ve,Ie=st(),Ie!==r){for(Oe=[],ht=ve,mt=Qn(),mt===r&&(mt=null),mt!==r?(Dt=st(),Dt!==r?(mt=[mt,Dt],ht=mt):(ve=ht,ht=r)):(ve=ht,ht=r);ht!==r;)Oe.push(ht),ht=ve,mt=Qn(),mt===r&&(mt=null),mt!==r?(Dt=st(),Dt!==r?(mt=[mt,Dt],ht=mt):(ve=ht,ht=r)):(ve=ht,ht=r);Oe!==r?(Ie=[Ie,Oe],ee=Ie):(ve=ee,ee=r)}else ve=ee,ee=r;return ee}function st(){var ee;return t.substr(ve,2)===hf?(ee=hf,ve+=2):(ee=r,Rt===0&&Xe(LA)),ee===r&&(t.charCodeAt(ve)===10?(ee=MA,ve++):(ee=r,Rt===0&&Xe(Au)),ee===r&&(t.charCodeAt(ve)===13?(ee=pu,ve++):(ee=r,Rt===0&&Xe(ac)))),ee}let St=2,lr=0;if(xn=a(),xn!==r&&ve===t.length)return xn;throw xn!==r&&ve"u"?!0:typeof t=="object"&&t!==null&&!Array.isArray(t)?Object.keys(t).every(e=>sne(t[e])):!1}function pU(t,e,r){if(t===null)return`null +`;if(typeof t=="number"||typeof t=="boolean")return`${t.toString()} +`;if(typeof t=="string")return`${nne(t)} +`;if(Array.isArray(t)){if(t.length===0)return`[] +`;let s=" ".repeat(e);return` +${t.map(n=>`${s}- ${pU(n,e+1,!1)}`).join("")}`}if(typeof t=="object"&&t){let[s,a]=t instanceof _x?[t.data,!1]:[t,!0],n=" ".repeat(e),c=Object.keys(s);a&&c.sort((p,h)=>{let E=rne.indexOf(p),C=rne.indexOf(h);return E===-1&&C===-1?ph?1:0:E!==-1&&C===-1?-1:E===-1&&C!==-1?1:E-C});let f=c.filter(p=>!sne(s[p])).map((p,h)=>{let E=s[p],C=nne(p),S=pU(E,e+1,!0),P=h>0||r?n:"",I=C.length>1024?`? ${C} +${P}:`:`${C}:`,R=S.startsWith(` +`)?S:` ${S}`;return`${P}${I}${R}`}).join(e===0?` +`:"")||` +`;return r?` +${f}`:`${f}`}throw new Error(`Unsupported value type (${t})`)}function il(t){try{let e=pU(t,0,!1);return e!==` +`?e:""}catch(e){throw e.location&&(e.message=e.message.replace(/(\.)?$/,` (line ${e.location.start.line}, column ${e.location.start.column})$1`)),e}}function rZe(t){return t.endsWith(` +`)||(t+=` +`),(0,ine.parse)(t)}function iZe(t){if(nZe.test(t))return rZe(t);let e=(0,Ux.safeLoad)(t,{schema:Ux.FAILSAFE_SCHEMA,json:!0});if(e==null)return{};if(typeof e!="object")throw new Error(`Expected an indexed object, got a ${typeof e} instead. Does your file follow Yaml's rules?`);if(Array.isArray(e))throw new Error("Expected an indexed object, got an array instead. Does your file follow Yaml's rules?");return e}function ls(t){return iZe(t)}var Ux,ine,tZe,rne,_x,nZe,one=It(()=>{Ux=et($re()),ine=et(tne()),tZe=/^(?![-?:,\][{}#&*!|>'"%@` \t\r\n]).([ \t]*(?![,\][{}:# \t\r\n]).)*$/,rne=["__metadata","version","resolution","dependencies","peerDependencies","dependenciesMeta","peerDependenciesMeta","binaries"],_x=class{constructor(e){this.data=e}};il.PreserveOrdering=_x;nZe=/^(#.*(\r?\n))*?#\s+yarn\s+lockfile\s+v1\r?\n/i});var Z2={};Vt(Z2,{parseResolution:()=>bx,parseShell:()=>vx,parseSyml:()=>ls,stringifyArgument:()=>$_,stringifyArgumentSegment:()=>eU,stringifyArithmeticExpression:()=>Dx,stringifyCommand:()=>X_,stringifyCommandChain:()=>AE,stringifyCommandChainThen:()=>Z_,stringifyCommandLine:()=>Sx,stringifyCommandLineThen:()=>z_,stringifyEnvSegment:()=>Bx,stringifyRedirectArgument:()=>G2,stringifyResolution:()=>Px,stringifyShell:()=>fE,stringifyShellLine:()=>fE,stringifySyml:()=>il,stringifyValueArgument:()=>Sd});var Bc=It(()=>{ste();cte();one()});var lne=L((S9t,hU)=>{"use strict";var sZe=t=>{let e=!1,r=!1,s=!1;for(let a=0;a{if(!(typeof t=="string"||Array.isArray(t)))throw new TypeError("Expected the input to be `string | string[]`");e=Object.assign({pascalCase:!1},e);let r=a=>e.pascalCase?a.charAt(0).toUpperCase()+a.slice(1):a;return Array.isArray(t)?t=t.map(a=>a.trim()).filter(a=>a.length).join("-"):t=t.trim(),t.length===0?"":t.length===1?e.pascalCase?t.toUpperCase():t.toLowerCase():(t!==t.toLowerCase()&&(t=sZe(t)),t=t.replace(/^[_.\- ]+/,"").toLowerCase().replace(/[_.\- ]+(\w|$)/g,(a,n)=>n.toUpperCase()).replace(/\d+(\w|$)/g,a=>a.toUpperCase()),r(t))};hU.exports=ane;hU.exports.default=ane});var cne=L((D9t,oZe)=>{oZe.exports=[{name:"Agola CI",constant:"AGOLA",env:"AGOLA_GIT_REF",pr:"AGOLA_PULL_REQUEST_ID"},{name:"Appcircle",constant:"APPCIRCLE",env:"AC_APPCIRCLE"},{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"TF_BUILD",pr:{BUILD_REASON:"PullRequest"}},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"Codefresh",constant:"CODEFRESH",env:"CF_BUILD_ID",pr:{any:["CF_PULL_REQUEST_NUMBER","CF_PULL_REQUEST_ID"]}},{name:"Codemagic",constant:"CODEMAGIC",env:"CM_BUILD_ID",pr:"CM_PULL_REQUEST"},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"Earthly",constant:"EARTHLY",env:"EARTHLY_CI"},{name:"Expo Application Services",constant:"EAS",env:"EAS_BUILD"},{name:"Gerrit",constant:"GERRIT",env:"GERRIT_PROJECT"},{name:"Gitea Actions",constant:"GITEA_ACTIONS",env:"GITEA_ACTIONS"},{name:"GitHub Actions",constant:"GITHUB_ACTIONS",env:"GITHUB_ACTIONS",pr:{GITHUB_EVENT_NAME:"pull_request"}},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI",pr:"CI_MERGE_REQUEST_ID"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"Google Cloud Build",constant:"GOOGLE_CLOUD_BUILD",env:"BUILDER_OUTPUT"},{name:"Harness CI",constant:"HARNESS",env:"HARNESS_BUILD_ID"},{name:"Heroku",constant:"HEROKU",env:{env:"NODE",includes:"/app/.heroku/node/bin/node"}},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"LayerCI",constant:"LAYERCI",env:"LAYERCI",pr:"LAYERCI_PULL_REQUEST"},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Nevercode",constant:"NEVERCODE",env:"NEVERCODE",pr:{env:"NEVERCODE_PULL_REQUEST",ne:"false"}},{name:"Prow",constant:"PROW",env:"PROW_JOB_ID"},{name:"ReleaseHub",constant:"RELEASEHUB",env:"RELEASE_BUILD_ID"},{name:"Render",constant:"RENDER",env:"RENDER",pr:{IS_PULL_REQUEST:"true"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Screwdriver",constant:"SCREWDRIVER",env:"SCREWDRIVER",pr:{env:"SD_PULL_REQUEST",ne:"false"}},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Sourcehut",constant:"SOURCEHUT",env:{CI_NAME:"sourcehut"}},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}},{name:"Vela",constant:"VELA",env:"VELA",pr:{VELA_PULL_REQUEST:"1"}},{name:"Vercel",constant:"VERCEL",env:{any:["NOW_BUILDER","VERCEL"]},pr:"VERCEL_GIT_PULL_REQUEST_ID"},{name:"Visual Studio App Center",constant:"APPCENTER",env:"APPCENTER_BUILD_ID"},{name:"Woodpecker",constant:"WOODPECKER",env:{CI:"woodpecker"},pr:{CI_BUILD_EVENT:"pull_request"}},{name:"Xcode Cloud",constant:"XCODE_CLOUD",env:"CI_XCODE_PROJECT",pr:"CI_PULL_REQUEST_NUMBER"},{name:"Xcode Server",constant:"XCODE_SERVER",env:"XCS"}]});var Nd=L(_l=>{"use strict";var fne=cne(),bs=process.env;Object.defineProperty(_l,"_vendors",{value:fne.map(function(t){return t.constant})});_l.name=null;_l.isPR=null;fne.forEach(function(t){let r=(Array.isArray(t.env)?t.env:[t.env]).every(function(s){return une(s)});if(_l[t.constant]=r,!!r)switch(_l.name=t.name,typeof t.pr){case"string":_l.isPR=!!bs[t.pr];break;case"object":"env"in t.pr?_l.isPR=t.pr.env in bs&&bs[t.pr.env]!==t.pr.ne:"any"in t.pr?_l.isPR=t.pr.any.some(function(s){return!!bs[s]}):_l.isPR=une(t.pr);break;default:_l.isPR=null}});_l.isCI=!!(bs.CI!=="false"&&(bs.BUILD_ID||bs.BUILD_NUMBER||bs.CI||bs.CI_APP_ID||bs.CI_BUILD_ID||bs.CI_BUILD_NUMBER||bs.CI_NAME||bs.CONTINUOUS_INTEGRATION||bs.RUN_ID||_l.name));function une(t){return typeof t=="string"?!!bs[t]:"env"in t?bs[t.env]&&bs[t.env].includes(t.includes):"any"in t?t.any.some(function(e){return!!bs[e]}):Object.keys(t).every(function(e){return bs[e]===t[e]})}});var ei,En,Od,gU,Hx,Ane,dU,mU,jx=It(()=>{(function(t){t.StartOfInput="\0",t.EndOfInput="",t.EndOfPartialInput=""})(ei||(ei={}));(function(t){t[t.InitialNode=0]="InitialNode",t[t.SuccessNode=1]="SuccessNode",t[t.ErrorNode=2]="ErrorNode",t[t.CustomNode=3]="CustomNode"})(En||(En={}));Od=-1,gU=/^(-h|--help)(?:=([0-9]+))?$/,Hx=/^(--[a-z]+(?:-[a-z]+)*|-[a-zA-Z]+)$/,Ane=/^-[a-zA-Z]{2,}$/,dU=/^([^=]+)=([\s\S]*)$/,mU=process.env.DEBUG_CLI==="1"});var nt,IE,qx,yU,Gx=It(()=>{jx();nt=class extends Error{constructor(e){super(e),this.clipanion={type:"usage"},this.name="UsageError"}},IE=class extends Error{constructor(e,r){if(super(),this.input=e,this.candidates=r,this.clipanion={type:"none"},this.name="UnknownSyntaxError",this.candidates.length===0)this.message="Command not found, but we're not sure what's the alternative.";else if(this.candidates.every(s=>s.reason!==null&&s.reason===r[0].reason)){let[{reason:s}]=this.candidates;this.message=`${s} + +${this.candidates.map(({usage:a})=>`$ ${a}`).join(` +`)}`}else if(this.candidates.length===1){let[{usage:s}]=this.candidates;this.message=`Command not found; did you mean: + +$ ${s} +${yU(e)}`}else this.message=`Command not found; did you mean one of: + +${this.candidates.map(({usage:s},a)=>`${`${a}.`.padStart(4)} ${s}`).join(` +`)} + +${yU(e)}`}},qx=class extends Error{constructor(e,r){super(),this.input=e,this.usages=r,this.clipanion={type:"none"},this.name="AmbiguousSyntaxError",this.message=`Cannot find which to pick amongst the following alternatives: + +${this.usages.map((s,a)=>`${`${a}.`.padStart(4)} ${s}`).join(` +`)} + +${yU(e)}`}},yU=t=>`While running ${t.filter(e=>e!==ei.EndOfInput&&e!==ei.EndOfPartialInput).map(e=>{let r=JSON.stringify(e);return e.match(/\s/)||e.length===0||r!==`"${e}"`?r:e}).join(" ")}`});function aZe(t){let e=t.split(` +`),r=e.filter(a=>a.match(/\S/)),s=r.length>0?r.reduce((a,n)=>Math.min(a,n.length-n.trimStart().length),Number.MAX_VALUE):0;return e.map(a=>a.slice(s).trimRight()).join(` +`)}function qo(t,{format:e,paragraphs:r}){return t=t.replace(/\r\n?/g,` +`),t=aZe(t),t=t.replace(/^\n+|\n+$/g,""),t=t.replace(/^(\s*)-([^\n]*?)\n+/gm,`$1-$2 + +`),t=t.replace(/\n(\n)?\n*/g,(s,a)=>a||" "),r&&(t=t.split(/\n/).map(s=>{let a=s.match(/^\s*[*-][\t ]+(.*)/);if(!a)return s.match(/(.{1,80})(?: |$)/g).join(` +`);let n=s.length-s.trimStart().length;return a[1].match(new RegExp(`(.{1,${78-n}})(?: |$)`,"g")).map((c,f)=>" ".repeat(n)+(f===0?"- ":" ")+c).join(` +`)}).join(` + +`)),t=t.replace(/(`+)((?:.|[\n])*?)\1/g,(s,a,n)=>e.code(a+n+a)),t=t.replace(/(\*\*)((?:.|[\n])*?)\1/g,(s,a,n)=>e.bold(a+n+a)),t?`${t} +`:""}var EU,pne,hne,IU=It(()=>{EU=Array(80).fill("\u2501");for(let t=0;t<=24;++t)EU[EU.length-t]=`\x1B[38;5;${232+t}m\u2501`;pne={header:t=>`\x1B[1m\u2501\u2501\u2501 ${t}${t.length<75?` ${EU.slice(t.length+5).join("")}`:":"}\x1B[0m`,bold:t=>`\x1B[1m${t}\x1B[22m`,error:t=>`\x1B[31m\x1B[1m${t}\x1B[22m\x1B[39m`,code:t=>`\x1B[36m${t}\x1B[39m`},hne={header:t=>t,bold:t=>t,error:t=>t,code:t=>t}});function Ea(t){return{...t,[X2]:!0}}function Gf(t,e){return typeof t>"u"?[t,e]:typeof t=="object"&&t!==null&&!Array.isArray(t)?[void 0,t]:[t,e]}function Wx(t,{mergeName:e=!1}={}){let r=t.match(/^([^:]+): (.*)$/m);if(!r)return"validation failed";let[,s,a]=r;return e&&(a=a[0].toLowerCase()+a.slice(1)),a=s!=="."||!e?`${s.replace(/^\.(\[|$)/,"$1")}: ${a}`:`: ${a}`,a}function $2(t,e){return e.length===1?new nt(`${t}${Wx(e[0],{mergeName:!0})}`):new nt(`${t}: +${e.map(r=>` +- ${Wx(r)}`).join("")}`)}function Ld(t,e,r){if(typeof r>"u")return e;let s=[],a=[],n=f=>{let p=e;return e=f,n.bind(null,p)};if(!r(e,{errors:s,coercions:a,coercion:n}))throw $2(`Invalid value for ${t}`,s);for(let[,f]of a)f();return e}var X2,Bp=It(()=>{Gx();X2=Symbol("clipanion/isOption")});var Ia={};Vt(Ia,{KeyRelationship:()=>Wf,TypeAssertionError:()=>c0,applyCascade:()=>rB,as:()=>DZe,assert:()=>BZe,assertWithErrors:()=>vZe,cascade:()=>Jx,fn:()=>bZe,hasAtLeastOneKey:()=>bU,hasExactLength:()=>Ene,hasForbiddenKeys:()=>YZe,hasKeyRelationship:()=>iB,hasMaxLength:()=>xZe,hasMinLength:()=>PZe,hasMutuallyExclusiveKeys:()=>VZe,hasRequiredKeys:()=>WZe,hasUniqueItems:()=>kZe,isArray:()=>Yx,isAtLeast:()=>SU,isAtMost:()=>RZe,isBase64:()=>HZe,isBoolean:()=>gZe,isDate:()=>mZe,isDict:()=>IZe,isEnum:()=>Ao,isHexColor:()=>UZe,isISO8601:()=>_Ze,isInExclusiveRange:()=>NZe,isInInclusiveRange:()=>FZe,isInstanceOf:()=>wZe,isInteger:()=>DU,isJSON:()=>jZe,isLiteral:()=>dne,isLowerCase:()=>OZe,isMap:()=>EZe,isNegative:()=>QZe,isNullable:()=>GZe,isNumber:()=>BU,isObject:()=>mne,isOneOf:()=>vU,isOptional:()=>qZe,isPartial:()=>CZe,isPayload:()=>dZe,isPositive:()=>TZe,isRecord:()=>Kx,isSet:()=>yZe,isString:()=>wE,isTuple:()=>Vx,isUUID4:()=>MZe,isUnknown:()=>wU,isUpperCase:()=>LZe,makeTrait:()=>yne,makeValidator:()=>Wr,matchesRegExp:()=>tB,softAssert:()=>SZe});function ti(t){return t===null?"null":t===void 0?"undefined":t===""?"an empty string":typeof t=="symbol"?`<${t.toString()}>`:Array.isArray(t)?"an array":JSON.stringify(t)}function CE(t,e){if(t.length===0)return"nothing";if(t.length===1)return ti(t[0]);let r=t.slice(0,-1),s=t[t.length-1],a=t.length>2?`, ${e} `:` ${e} `;return`${r.map(n=>ti(n)).join(", ")}${a}${ti(s)}`}function l0(t,e){var r,s,a;return typeof e=="number"?`${(r=t?.p)!==null&&r!==void 0?r:"."}[${e}]`:lZe.test(e)?`${(s=t?.p)!==null&&s!==void 0?s:""}.${e}`:`${(a=t?.p)!==null&&a!==void 0?a:"."}[${JSON.stringify(e)}]`}function CU(t,e,r){return t===1?e:r}function mr({errors:t,p:e}={},r){return t?.push(`${e??"."}: ${r}`),!1}function pZe(t,e){return r=>{t[e]=r}}function Yf(t,e){return r=>{let s=t[e];return t[e]=r,Yf(t,e).bind(null,s)}}function eB(t,e,r){let s=()=>(t(r()),a),a=()=>(t(e),s);return s}function wU(){return Wr({test:(t,e)=>!0})}function dne(t){return Wr({test:(e,r)=>e!==t?mr(r,`Expected ${ti(t)} (got ${ti(e)})`):!0})}function wE(){return Wr({test:(t,e)=>typeof t!="string"?mr(e,`Expected a string (got ${ti(t)})`):!0})}function Ao(t){let e=Array.isArray(t)?t:Object.values(t),r=e.every(a=>typeof a=="string"||typeof a=="number"),s=new Set(e);return s.size===1?dne([...s][0]):Wr({test:(a,n)=>s.has(a)?!0:r?mr(n,`Expected one of ${CE(e,"or")} (got ${ti(a)})`):mr(n,`Expected a valid enumeration value (got ${ti(a)})`)})}function gZe(){return Wr({test:(t,e)=>{var r;if(typeof t!="boolean"){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s=hZe.get(t);if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a boolean (got ${ti(t)})`)}return!0}})}function BU(){return Wr({test:(t,e)=>{var r;if(typeof t!="number"){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s;if(typeof t=="string"){let a;try{a=JSON.parse(t)}catch{}if(typeof a=="number")if(JSON.stringify(a)===t)s=a;else return mr(e,`Received a number that can't be safely represented by the runtime (${t})`)}if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a number (got ${ti(t)})`)}return!0}})}function dZe(t){return Wr({test:(e,r)=>{var s;if(typeof r?.coercions>"u")return mr(r,"The isPayload predicate can only be used with coercion enabled");if(typeof r.coercion>"u")return mr(r,"Unbound coercion result");if(typeof e!="string")return mr(r,`Expected a string (got ${ti(e)})`);let a;try{a=JSON.parse(e)}catch{return mr(r,`Expected a JSON string (got ${ti(e)})`)}let n={value:a};return t(a,Object.assign(Object.assign({},r),{coercion:Yf(n,"value")}))?(r.coercions.push([(s=r.p)!==null&&s!==void 0?s:".",r.coercion.bind(null,n.value)]),!0):!1}})}function mZe(){return Wr({test:(t,e)=>{var r;if(!(t instanceof Date)){if(typeof e?.coercions<"u"){if(typeof e?.coercion>"u")return mr(e,"Unbound coercion result");let s;if(typeof t=="string"&&gne.test(t))s=new Date(t);else{let a;if(typeof t=="string"){let n;try{n=JSON.parse(t)}catch{}typeof n=="number"&&(a=n)}else typeof t=="number"&&(a=t);if(typeof a<"u")if(Number.isSafeInteger(a)||!Number.isSafeInteger(a*1e3))s=new Date(a*1e3);else return mr(e,`Received a timestamp that can't be safely represented by the runtime (${t})`)}if(typeof s<"u")return e.coercions.push([(r=e.p)!==null&&r!==void 0?r:".",e.coercion.bind(null,s)]),!0}return mr(e,`Expected a date (got ${ti(t)})`)}return!0}})}function Yx(t,{delimiter:e}={}){return Wr({test:(r,s)=>{var a;let n=r;if(typeof r=="string"&&typeof e<"u"&&typeof s?.coercions<"u"){if(typeof s?.coercion>"u")return mr(s,"Unbound coercion result");r=r.split(e)}if(!Array.isArray(r))return mr(s,`Expected an array (got ${ti(r)})`);let c=!0;for(let f=0,p=r.length;f{var n,c;if(Object.getPrototypeOf(s).toString()==="[object Set]")if(typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");let f=[...s],p=[...s];if(!r(p,Object.assign(Object.assign({},a),{coercion:void 0})))return!1;let h=()=>p.some((E,C)=>E!==f[C])?new Set(p):s;return a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",eB(a.coercion,s,h)]),!0}else{let f=!0;for(let p of s)if(f=t(p,Object.assign({},a))&&f,!f&&a?.errors==null)break;return f}if(typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");let f={value:s};return r(s,Object.assign(Object.assign({},a),{coercion:Yf(f,"value")}))?(a.coercions.push([(c=a.p)!==null&&c!==void 0?c:".",eB(a.coercion,s,()=>new Set(f.value))]),!0):!1}return mr(a,`Expected a set (got ${ti(s)})`)}})}function EZe(t,e){let r=Yx(Vx([t,e])),s=Kx(e,{keys:t});return Wr({test:(a,n)=>{var c,f,p;if(Object.getPrototypeOf(a).toString()==="[object Map]")if(typeof n?.coercions<"u"){if(typeof n?.coercion>"u")return mr(n,"Unbound coercion result");let h=[...a],E=[...a];if(!r(E,Object.assign(Object.assign({},n),{coercion:void 0})))return!1;let C=()=>E.some((S,P)=>S[0]!==h[P][0]||S[1]!==h[P][1])?new Map(E):a;return n.coercions.push([(c=n.p)!==null&&c!==void 0?c:".",eB(n.coercion,a,C)]),!0}else{let h=!0;for(let[E,C]of a)if(h=t(E,Object.assign({},n))&&h,!h&&n?.errors==null||(h=e(C,Object.assign(Object.assign({},n),{p:l0(n,E)}))&&h,!h&&n?.errors==null))break;return h}if(typeof n?.coercions<"u"){if(typeof n?.coercion>"u")return mr(n,"Unbound coercion result");let h={value:a};return Array.isArray(a)?r(a,Object.assign(Object.assign({},n),{coercion:void 0}))?(n.coercions.push([(f=n.p)!==null&&f!==void 0?f:".",eB(n.coercion,a,()=>new Map(h.value))]),!0):!1:s(a,Object.assign(Object.assign({},n),{coercion:Yf(h,"value")}))?(n.coercions.push([(p=n.p)!==null&&p!==void 0?p:".",eB(n.coercion,a,()=>new Map(Object.entries(h.value)))]),!0):!1}return mr(n,`Expected a map (got ${ti(a)})`)}})}function Vx(t,{delimiter:e}={}){let r=Ene(t.length);return Wr({test:(s,a)=>{var n;if(typeof s=="string"&&typeof e<"u"&&typeof a?.coercions<"u"){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");s=s.split(e),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,s)])}if(!Array.isArray(s))return mr(a,`Expected a tuple (got ${ti(s)})`);let c=r(s,Object.assign({},a));for(let f=0,p=s.length;f{var n;if(Array.isArray(s)&&typeof a?.coercions<"u")return typeof a?.coercion>"u"?mr(a,"Unbound coercion result"):r(s,Object.assign(Object.assign({},a),{coercion:void 0}))?(s=Object.fromEntries(s),a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,s)]),!0):!1;if(typeof s!="object"||s===null)return mr(a,`Expected an object (got ${ti(s)})`);let c=Object.keys(s),f=!0;for(let p=0,h=c.length;p{if(typeof a!="object"||a===null)return mr(n,`Expected an object (got ${ti(a)})`);let c=new Set([...r,...Object.keys(a)]),f={},p=!0;for(let h of c){if(h==="constructor"||h==="__proto__")p=mr(Object.assign(Object.assign({},n),{p:l0(n,h)}),"Unsafe property name");else{let E=Object.prototype.hasOwnProperty.call(t,h)?t[h]:void 0,C=Object.prototype.hasOwnProperty.call(a,h)?a[h]:void 0;typeof E<"u"?p=E(C,Object.assign(Object.assign({},n),{p:l0(n,h),coercion:Yf(a,h)}))&&p:e===null?p=mr(Object.assign(Object.assign({},n),{p:l0(n,h)}),`Extraneous property (got ${ti(C)})`):Object.defineProperty(f,h,{enumerable:!0,get:()=>C,set:pZe(a,h)})}if(!p&&n?.errors==null)break}return e!==null&&(p||n?.errors!=null)&&(p=e(f,n)&&p),p}});return Object.assign(s,{properties:t})}function CZe(t){return mne(t,{extra:Kx(wU())})}function yne(t){return()=>t}function Wr({test:t}){return yne(t)()}function BZe(t,e){if(!e(t))throw new c0}function vZe(t,e){let r=[];if(!e(t,{errors:r}))throw new c0({errors:r})}function SZe(t,e){}function DZe(t,e,{coerce:r=!1,errors:s,throw:a}={}){let n=s?[]:void 0;if(!r){if(e(t,{errors:n}))return a?t:{value:t,errors:void 0};if(a)throw new c0({errors:n});return{value:void 0,errors:n??!0}}let c={value:t},f=Yf(c,"value"),p=[];if(!e(t,{errors:n,coercion:f,coercions:p})){if(a)throw new c0({errors:n});return{value:void 0,errors:n??!0}}for(let[,h]of p)h();return a?c.value:{value:c.value,errors:void 0}}function bZe(t,e){let r=Vx(t);return(...s)=>{if(!r(s))throw new c0;return e(...s)}}function PZe(t){return Wr({test:(e,r)=>e.length>=t?!0:mr(r,`Expected to have a length of at least ${t} elements (got ${e.length})`)})}function xZe(t){return Wr({test:(e,r)=>e.length<=t?!0:mr(r,`Expected to have a length of at most ${t} elements (got ${e.length})`)})}function Ene(t){return Wr({test:(e,r)=>e.length!==t?mr(r,`Expected to have a length of exactly ${t} elements (got ${e.length})`):!0})}function kZe({map:t}={}){return Wr({test:(e,r)=>{let s=new Set,a=new Set;for(let n=0,c=e.length;nt<=0?!0:mr(e,`Expected to be negative (got ${t})`)})}function TZe(){return Wr({test:(t,e)=>t>=0?!0:mr(e,`Expected to be positive (got ${t})`)})}function SU(t){return Wr({test:(e,r)=>e>=t?!0:mr(r,`Expected to be at least ${t} (got ${e})`)})}function RZe(t){return Wr({test:(e,r)=>e<=t?!0:mr(r,`Expected to be at most ${t} (got ${e})`)})}function FZe(t,e){return Wr({test:(r,s)=>r>=t&&r<=e?!0:mr(s,`Expected to be in the [${t}; ${e}] range (got ${r})`)})}function NZe(t,e){return Wr({test:(r,s)=>r>=t&&re!==Math.round(e)?mr(r,`Expected to be an integer (got ${e})`):!t&&!Number.isSafeInteger(e)?mr(r,`Expected to be a safe integer (got ${e})`):!0})}function tB(t){return Wr({test:(e,r)=>t.test(e)?!0:mr(r,`Expected to match the pattern ${t.toString()} (got ${ti(e)})`)})}function OZe(){return Wr({test:(t,e)=>t!==t.toLowerCase()?mr(e,`Expected to be all-lowercase (got ${t})`):!0})}function LZe(){return Wr({test:(t,e)=>t!==t.toUpperCase()?mr(e,`Expected to be all-uppercase (got ${t})`):!0})}function MZe(){return Wr({test:(t,e)=>AZe.test(t)?!0:mr(e,`Expected to be a valid UUID v4 (got ${ti(t)})`)})}function _Ze(){return Wr({test:(t,e)=>gne.test(t)?!0:mr(e,`Expected to be a valid ISO 8601 date string (got ${ti(t)})`)})}function UZe({alpha:t=!1}){return Wr({test:(e,r)=>(t?cZe.test(e):uZe.test(e))?!0:mr(r,`Expected to be a valid hexadecimal color string (got ${ti(e)})`)})}function HZe(){return Wr({test:(t,e)=>fZe.test(t)?!0:mr(e,`Expected to be a valid base 64 string (got ${ti(t)})`)})}function jZe(t=wU()){return Wr({test:(e,r)=>{let s;try{s=JSON.parse(e)}catch{return mr(r,`Expected to be a valid JSON string (got ${ti(e)})`)}return t(s,r)}})}function Jx(t,...e){let r=Array.isArray(e[0])?e[0]:e;return Wr({test:(s,a)=>{var n,c;let f={value:s},p=typeof a?.coercions<"u"?Yf(f,"value"):void 0,h=typeof a?.coercions<"u"?[]:void 0;if(!t(s,Object.assign(Object.assign({},a),{coercion:p,coercions:h})))return!1;let E=[];if(typeof h<"u")for(let[,C]of h)E.push(C());try{if(typeof a?.coercions<"u"){if(f.value!==s){if(typeof a?.coercion>"u")return mr(a,"Unbound coercion result");a.coercions.push([(n=a.p)!==null&&n!==void 0?n:".",a.coercion.bind(null,f.value)])}(c=a?.coercions)===null||c===void 0||c.push(...h)}return r.every(C=>C(f.value,a))}finally{for(let C of E)C()}}})}function rB(t,...e){let r=Array.isArray(e[0])?e[0]:e;return Jx(t,r)}function qZe(t){return Wr({test:(e,r)=>typeof e>"u"?!0:t(e,r)})}function GZe(t){return Wr({test:(e,r)=>e===null?!0:t(e,r)})}function WZe(t,e){var r;let s=new Set(t),a=nB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)||p.push(h);return p.length>0?mr(c,`Missing required ${CU(p.length,"property","properties")} ${CE(p,"and")}`):!0}})}function bU(t,e){var r;let s=new Set(t),a=nB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>Object.keys(n).some(h=>a(s,h,n))?!0:mr(c,`Missing at least one property from ${CE(Array.from(s),"or")}`)})}function YZe(t,e){var r;let s=new Set(t),a=nB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>0?mr(c,`Forbidden ${CU(p.length,"property","properties")} ${CE(p,"and")}`):!0}})}function VZe(t,e){var r;let s=new Set(t),a=nB[(r=e?.missingIf)!==null&&r!==void 0?r:"missing"];return Wr({test:(n,c)=>{let f=new Set(Object.keys(n)),p=[];for(let h of s)a(f,h,n)&&p.push(h);return p.length>1?mr(c,`Mutually exclusive properties ${CE(p,"and")}`):!0}})}function iB(t,e,r,s){var a,n;let c=new Set((a=s?.ignore)!==null&&a!==void 0?a:[]),f=nB[(n=s?.missingIf)!==null&&n!==void 0?n:"missing"],p=new Set(r),h=KZe[e],E=e===Wf.Forbids?"or":"and";return Wr({test:(C,S)=>{let P=new Set(Object.keys(C));if(!f(P,t,C)||c.has(C[t]))return!0;let I=[];for(let R of p)(f(P,R,C)&&!c.has(C[R]))!==h.expect&&I.push(R);return I.length>=1?mr(S,`Property "${t}" ${h.message} ${CU(I.length,"property","properties")} ${CE(I,E)}`):!0}})}var lZe,cZe,uZe,fZe,AZe,gne,hZe,wZe,vU,c0,nB,Wf,KZe,Ul=It(()=>{lZe=/^[a-zA-Z_][a-zA-Z0-9_]*$/;cZe=/^#[0-9a-f]{6}$/i,uZe=/^#[0-9a-f]{6}([0-9a-f]{2})?$/i,fZe=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/,AZe=/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12}$/i,gne=/^(?:[1-9]\d{3}(-?)(?:(?:0[1-9]|1[0-2])\1(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])\1(?:29|30)|(?:0[13578]|1[02])(?:\1)31|00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[0-5]))|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)(?:(-?)02(?:\2)29|-?366))T(?:[01]\d|2[0-3])(:?)[0-5]\d(?:\3[0-5]\d)?(?:Z|[+-][01]\d(?:\3[0-5]\d)?)$/;hZe=new Map([["true",!0],["True",!0],["1",!0],[1,!0],["false",!1],["False",!1],["0",!1],[0,!1]]);wZe=t=>Wr({test:(e,r)=>e instanceof t?!0:mr(r,`Expected an instance of ${t.name} (got ${ti(e)})`)}),vU=(t,{exclusive:e=!1}={})=>Wr({test:(r,s)=>{var a,n,c;let f=[],p=typeof s?.errors<"u"?[]:void 0;for(let h=0,E=t.length;h1?mr(s,`Expected to match exactly a single predicate (matched ${f.join(", ")})`):(c=s?.errors)===null||c===void 0||c.push(...p),!1}});c0=class extends Error{constructor({errors:e}={}){let r="Type mismatch";if(e&&e.length>0){r+=` +`;for(let s of e)r+=` +- ${s}`}super(r)}};nB={missing:(t,e)=>t.has(e),undefined:(t,e,r)=>t.has(e)&&typeof r[e]<"u",nil:(t,e,r)=>t.has(e)&&r[e]!=null,falsy:(t,e,r)=>t.has(e)&&!!r[e]};(function(t){t.Forbids="Forbids",t.Requires="Requires"})(Wf||(Wf={}));KZe={[Wf.Forbids]:{expect:!1,message:"forbids using"},[Wf.Requires]:{expect:!0,message:"requires using"}}});var ot,u0=It(()=>{Bp();ot=class{constructor(){this.help=!1}static Usage(e){return e}async catch(e){throw e}async validateAndExecute(){let r=this.constructor.schema;if(Array.isArray(r)){let{isDict:a,isUnknown:n,applyCascade:c}=await Promise.resolve().then(()=>(Ul(),Ia)),f=c(a(n()),r),p=[],h=[];if(!f(this,{errors:p,coercions:h}))throw $2("Invalid option schema",p);for(let[,C]of h)C()}else if(r!=null)throw new Error("Invalid command schema");let s=await this.execute();return typeof s<"u"?s:0}};ot.isOption=X2;ot.Default=[]});function sl(t){mU&&console.log(t)}function Cne(){let t={nodes:[]};for(let e=0;e{if(e.has(s))return;e.add(s);let a=t.nodes[s];for(let c of Object.values(a.statics))for(let{to:f}of c)r(f);for(let[,{to:c}]of a.dynamics)r(c);for(let{to:c}of a.shortcuts)r(c);let n=new Set(a.shortcuts.map(({to:c})=>c));for(;a.shortcuts.length>0;){let{to:c}=a.shortcuts.shift(),f=t.nodes[c];for(let[p,h]of Object.entries(f.statics)){let E=Object.prototype.hasOwnProperty.call(a.statics,p)?a.statics[p]:a.statics[p]=[];for(let C of h)E.some(({to:S})=>C.to===S)||E.push(C)}for(let[p,h]of f.dynamics)a.dynamics.some(([E,{to:C}])=>p===E&&h.to===C)||a.dynamics.push([p,h]);for(let p of f.shortcuts)n.has(p.to)||(a.shortcuts.push(p),n.add(p.to))}};r(En.InitialNode)}function ZZe(t,{prefix:e=""}={}){if(mU){sl(`${e}Nodes are:`);for(let r=0;rE!==En.ErrorNode).map(({state:E})=>({usage:E.candidateUsage,reason:null})));if(h.every(({node:E})=>E===En.ErrorNode))throw new IE(e,h.map(({state:E})=>({usage:E.candidateUsage,reason:E.errorMessage})));s=eXe(h)}if(s.length>0){sl(" Results:");for(let n of s)sl(` - ${n.node} -> ${JSON.stringify(n.state)}`)}else sl(" No results");return s}function $Ze(t,e,{endToken:r=ei.EndOfInput}={}){let s=XZe(t,[...e,r]);return tXe(e,s.map(({state:a})=>a))}function eXe(t){let e=0;for(let{state:r}of t)r.path.length>e&&(e=r.path.length);return t.filter(({state:r})=>r.path.length===e)}function tXe(t,e){let r=e.filter(S=>S.selectedIndex!==null),s=r.filter(S=>!S.partial);if(s.length>0&&(r=s),r.length===0)throw new Error;let a=r.filter(S=>S.selectedIndex===Od||S.requiredOptions.every(P=>P.some(I=>S.options.find(R=>R.name===I))));if(a.length===0)throw new IE(t,r.map(S=>({usage:S.candidateUsage,reason:null})));let n=0;for(let S of a)S.path.length>n&&(n=S.path.length);let c=a.filter(S=>S.path.length===n),f=S=>S.positionals.filter(({extra:P})=>!P).length+S.options.length,p=c.map(S=>({state:S,positionalCount:f(S)})),h=0;for(let{positionalCount:S}of p)S>h&&(h=S);let E=p.filter(({positionalCount:S})=>S===h).map(({state:S})=>S),C=rXe(E);if(C.length>1)throw new qx(t,C.map(S=>S.candidateUsage));return C[0]}function rXe(t){let e=[],r=[];for(let s of t)s.selectedIndex===Od?r.push(s):e.push(s);return r.length>0&&e.push({...Ine,path:wne(...r.map(s=>s.path)),options:r.reduce((s,a)=>s.concat(a.options),[])}),e}function wne(t,e,...r){return e===void 0?Array.from(t):wne(t.filter((s,a)=>s===e[a]),...r)}function Hl(){return{dynamics:[],shortcuts:[],statics:{}}}function Bne(t){return t===En.SuccessNode||t===En.ErrorNode}function PU(t,e=0){return{to:Bne(t.to)?t.to:t.to>=En.CustomNode?t.to+e-En.CustomNode+1:t.to+e,reducer:t.reducer}}function nXe(t,e=0){let r=Hl();for(let[s,a]of t.dynamics)r.dynamics.push([s,PU(a,e)]);for(let s of t.shortcuts)r.shortcuts.push(PU(s,e));for(let[s,a]of Object.entries(t.statics))r.statics[s]=a.map(n=>PU(n,e));return r}function js(t,e,r,s,a){t.nodes[e].dynamics.push([r,{to:s,reducer:a}])}function BE(t,e,r,s){t.nodes[e].shortcuts.push({to:r,reducer:s})}function Ca(t,e,r,s,a){(Object.prototype.hasOwnProperty.call(t.nodes[e].statics,r)?t.nodes[e].statics[r]:t.nodes[e].statics[r]=[]).push({to:s,reducer:a})}function zx(t,e,r,s,a){if(Array.isArray(e)){let[n,...c]=e;return t[n](r,s,a,...c)}else return t[e](r,s,a)}var Ine,iXe,xU,jl,kU,Zx,Xx=It(()=>{jx();Gx();Ine={candidateUsage:null,requiredOptions:[],errorMessage:null,ignoreOptions:!1,path:[],positionals:[],options:[],remainder:null,selectedIndex:Od,partial:!1,tokens:[]};iXe={always:()=>!0,isOptionLike:(t,e)=>!t.ignoreOptions&&e!=="-"&&e.startsWith("-"),isNotOptionLike:(t,e)=>t.ignoreOptions||e==="-"||!e.startsWith("-"),isOption:(t,e,r,s)=>!t.ignoreOptions&&e===s,isBatchOption:(t,e,r,s)=>!t.ignoreOptions&&Ane.test(e)&&[...e.slice(1)].every(a=>s.has(`-${a}`)),isBoundOption:(t,e,r,s,a)=>{let n=e.match(dU);return!t.ignoreOptions&&!!n&&Hx.test(n[1])&&s.has(n[1])&&a.filter(c=>c.nameSet.includes(n[1])).every(c=>c.allowBinding)},isNegatedOption:(t,e,r,s)=>!t.ignoreOptions&&e===`--no-${s.slice(2)}`,isHelp:(t,e)=>!t.ignoreOptions&&gU.test(e),isUnsupportedOption:(t,e,r,s)=>!t.ignoreOptions&&e.startsWith("-")&&Hx.test(e)&&!s.has(e),isInvalidOption:(t,e)=>!t.ignoreOptions&&e.startsWith("-")&&!Hx.test(e)},xU={setCandidateState:(t,e,r,s)=>({...t,...s}),setSelectedIndex:(t,e,r,s)=>({...t,selectedIndex:s}),setPartialIndex:(t,e,r,s)=>({...t,selectedIndex:s,partial:!0}),pushBatch:(t,e,r,s)=>{let a=t.options.slice(),n=t.tokens.slice();for(let c=1;c{let[,s,a]=e.match(dU),n=t.options.concat({name:s,value:a}),c=t.tokens.concat([{segmentIndex:r,type:"option",slice:[0,s.length],option:s},{segmentIndex:r,type:"assign",slice:[s.length,s.length+1]},{segmentIndex:r,type:"value",slice:[s.length+1,s.length+a.length+1]}]);return{...t,options:n,tokens:c}},pushPath:(t,e,r)=>{let s=t.path.concat(e),a=t.tokens.concat({segmentIndex:r,type:"path"});return{...t,path:s,tokens:a}},pushPositional:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!1}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushExtra:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:!0}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushExtraNoLimits:(t,e,r)=>{let s=t.positionals.concat({value:e,extra:jl}),a=t.tokens.concat({segmentIndex:r,type:"positional"});return{...t,positionals:s,tokens:a}},pushTrue:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!0}),n=t.tokens.concat({segmentIndex:r,type:"option",option:s});return{...t,options:a,tokens:n}},pushFalse:(t,e,r,s)=>{let a=t.options.concat({name:s,value:!1}),n=t.tokens.concat({segmentIndex:r,type:"option",option:s});return{...t,options:a,tokens:n}},pushUndefined:(t,e,r,s)=>{let a=t.options.concat({name:e,value:void 0}),n=t.tokens.concat({segmentIndex:r,type:"option",option:e});return{...t,options:a,tokens:n}},pushStringValue:(t,e,r)=>{var s;let a=t.options[t.options.length-1],n=t.options.slice(),c=t.tokens.concat({segmentIndex:r,type:"value"});return a.value=((s=a.value)!==null&&s!==void 0?s:[]).concat([e]),{...t,options:n,tokens:c}},setStringValue:(t,e,r)=>{let s=t.options[t.options.length-1],a=t.options.slice(),n=t.tokens.concat({segmentIndex:r,type:"value"});return s.value=e,{...t,options:a,tokens:n}},inhibateOptions:t=>({...t,ignoreOptions:!0}),useHelp:(t,e,r,s)=>{let[,,a]=e.match(gU);return typeof a<"u"?{...t,options:[{name:"-c",value:String(s)},{name:"-i",value:a}]}:{...t,options:[{name:"-c",value:String(s)}]}},setError:(t,e,r,s)=>e===ei.EndOfInput||e===ei.EndOfPartialInput?{...t,errorMessage:`${s}.`}:{...t,errorMessage:`${s} ("${e}").`},setOptionArityError:(t,e)=>{let r=t.options[t.options.length-1];return{...t,errorMessage:`Not enough arguments to option ${r.name}.`}}},jl=Symbol(),kU=class{constructor(e,r){this.allOptionNames=new Map,this.arity={leading:[],trailing:[],extra:[],proxy:!1},this.options=[],this.paths=[],this.cliIndex=e,this.cliOpts=r}addPath(e){this.paths.push(e)}setArity({leading:e=this.arity.leading,trailing:r=this.arity.trailing,extra:s=this.arity.extra,proxy:a=this.arity.proxy}){Object.assign(this.arity,{leading:e,trailing:r,extra:s,proxy:a})}addPositional({name:e="arg",required:r=!0}={}){if(!r&&this.arity.extra===jl)throw new Error("Optional parameters cannot be declared when using .rest() or .proxy()");if(!r&&this.arity.trailing.length>0)throw new Error("Optional parameters cannot be declared after the required trailing positional arguments");!r&&this.arity.extra!==jl?this.arity.extra.push(e):this.arity.extra!==jl&&this.arity.extra.length===0?this.arity.leading.push(e):this.arity.trailing.push(e)}addRest({name:e="arg",required:r=0}={}){if(this.arity.extra===jl)throw new Error("Infinite lists cannot be declared multiple times in the same command");if(this.arity.trailing.length>0)throw new Error("Infinite lists cannot be declared after the required trailing positional arguments");for(let s=0;s1)throw new Error("The arity cannot be higher than 1 when the option only supports the --arg=value syntax");if(!Number.isInteger(s))throw new Error(`The arity must be an integer, got ${s}`);if(s<0)throw new Error(`The arity must be positive, got ${s}`);let f=e.reduce((p,h)=>h.length>p.length?h:p,"");for(let p of e)this.allOptionNames.set(p,f);this.options.push({preferredName:f,nameSet:e,description:r,arity:s,hidden:a,required:n,allowBinding:c})}setContext(e){this.context=e}usage({detailed:e=!0,inlineOptions:r=!0}={}){let s=[this.cliOpts.binaryName],a=[];if(this.paths.length>0&&s.push(...this.paths[0]),e){for(let{preferredName:c,nameSet:f,arity:p,hidden:h,description:E,required:C}of this.options){if(h)continue;let S=[];for(let I=0;I`:`[${P}]`)}s.push(...this.arity.leading.map(c=>`<${c}>`)),this.arity.extra===jl?s.push("..."):s.push(...this.arity.extra.map(c=>`[${c}]`)),s.push(...this.arity.trailing.map(c=>`<${c}>`))}return{usage:s.join(" "),options:a}}compile(){if(typeof this.context>"u")throw new Error("Assertion failed: No context attached");let e=Cne(),r=En.InitialNode,s=this.usage().usage,a=this.options.filter(f=>f.required).map(f=>f.nameSet);r=Mu(e,Hl()),Ca(e,En.InitialNode,ei.StartOfInput,r,["setCandidateState",{candidateUsage:s,requiredOptions:a}]);let n=this.arity.proxy?"always":"isNotOptionLike",c=this.paths.length>0?this.paths:[[]];for(let f of c){let p=r;if(f.length>0){let S=Mu(e,Hl());BE(e,p,S),this.registerOptions(e,S),p=S}for(let S=0;S0||!this.arity.proxy){let S=Mu(e,Hl());js(e,p,"isHelp",S,["useHelp",this.cliIndex]),js(e,S,"always",S,"pushExtra"),Ca(e,S,ei.EndOfInput,En.SuccessNode,["setSelectedIndex",Od]),this.registerOptions(e,p)}this.arity.leading.length>0&&(Ca(e,p,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ca(e,p,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex]));let h=p;for(let S=0;S0||S+1!==this.arity.leading.length)&&(Ca(e,P,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ca(e,P,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex])),js(e,h,"isNotOptionLike",P,"pushPositional"),h=P}let E=h;if(this.arity.extra===jl||this.arity.extra.length>0){let S=Mu(e,Hl());if(BE(e,h,S),this.arity.extra===jl){let P=Mu(e,Hl());this.arity.proxy||this.registerOptions(e,P),js(e,h,n,P,"pushExtraNoLimits"),js(e,P,n,P,"pushExtraNoLimits"),BE(e,P,S)}else for(let P=0;P0)&&this.registerOptions(e,I),js(e,E,n,I,"pushExtra"),BE(e,I,S),E=I}E=S}this.arity.trailing.length>0&&(Ca(e,E,ei.EndOfInput,En.ErrorNode,["setError","Not enough positional arguments"]),Ca(e,E,ei.EndOfPartialInput,En.SuccessNode,["setPartialIndex",this.cliIndex]));let C=E;for(let S=0;S=0&&e{let c=n?ei.EndOfPartialInput:ei.EndOfInput;return $Ze(s,a,{endToken:c})}}}}});function Sne(){return $x.default&&"getColorDepth"in $x.default.WriteStream.prototype?$x.default.WriteStream.prototype.getColorDepth():process.env.FORCE_COLOR==="0"?1:process.env.FORCE_COLOR==="1"||typeof process.stdout<"u"&&process.stdout.isTTY?8:1}function Dne(t){let e=vne;if(typeof e>"u"){if(t.stdout===process.stdout&&t.stderr===process.stderr)return null;let{AsyncLocalStorage:r}=ye("async_hooks");e=vne=new r;let s=process.stdout._write;process.stdout._write=function(n,c,f){let p=e.getStore();return typeof p>"u"?s.call(this,n,c,f):p.stdout.write(n,c,f)};let a=process.stderr._write;process.stderr._write=function(n,c,f){let p=e.getStore();return typeof p>"u"?a.call(this,n,c,f):p.stderr.write(n,c,f)}}return r=>e.run(t,r)}var $x,vne,bne=It(()=>{$x=et(ye("tty"),1)});var ek,Pne=It(()=>{u0();ek=class t extends ot{constructor(e){super(),this.contexts=e,this.commands=[]}static from(e,r){let s=new t(r);s.path=e.path;for(let a of e.options)switch(a.name){case"-c":s.commands.push(Number(a.value));break;case"-i":s.index=Number(a.value);break}return s}async execute(){let e=this.commands;if(typeof this.index<"u"&&this.index>=0&&this.index1){this.context.stdout.write(`Multiple commands match your selection: +`),this.context.stdout.write(` +`);let r=0;for(let s of this.commands)this.context.stdout.write(this.cli.usage(this.contexts[s].commandClass,{prefix:`${r++}. `.padStart(5)}));this.context.stdout.write(` +`),this.context.stdout.write(`Run again with -h= to see the longer details of any of those commands. +`)}}}});async function Qne(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=Rne(t);return wa.from(r,e).runExit(s,a)}async function Tne(...t){let{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}=Rne(t);return wa.from(r,e).run(s,a)}function Rne(t){let e,r,s,a;switch(typeof process<"u"&&typeof process.argv<"u"&&(s=process.argv.slice(2)),t.length){case 1:r=t[0];break;case 2:t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],Array.isArray(t[1])?s=t[1]:a=t[1]):(e=t[0],r=t[1]);break;case 3:Array.isArray(t[2])?(e=t[0],r=t[1],s=t[2]):t[0]&&t[0].prototype instanceof ot||Array.isArray(t[0])?(r=t[0],s=t[1],a=t[2]):(e=t[0],r=t[1],a=t[2]);break;default:e=t[0],r=t[1],s=t[2],a=t[3];break}if(typeof s>"u")throw new Error("The argv parameter must be provided when running Clipanion outside of a Node context");return{resolvedOptions:e,resolvedCommandClasses:r,resolvedArgv:s,resolvedContext:a}}function kne(t){return t()}var xne,wa,Fne=It(()=>{jx();Xx();IU();bne();u0();Pne();xne=Symbol("clipanion/errorCommand");wa=class t{constructor({binaryLabel:e,binaryName:r="...",binaryVersion:s,enableCapture:a=!1,enableColors:n}={}){this.registrations=new Map,this.builder=new Zx({binaryName:r}),this.binaryLabel=e,this.binaryName=r,this.binaryVersion=s,this.enableCapture=a,this.enableColors=n}static from(e,r={}){let s=new t(r),a=Array.isArray(e)?e:[e];for(let n of a)s.register(n);return s}register(e){var r;let s=new Map,a=new e;for(let p in a){let h=a[p];typeof h=="object"&&h!==null&&h[ot.isOption]&&s.set(p,h)}let n=this.builder.command(),c=n.cliIndex,f=(r=e.paths)!==null&&r!==void 0?r:a.paths;if(typeof f<"u")for(let p of f)n.addPath(p);this.registrations.set(e,{specs:s,builder:n,index:c});for(let[p,{definition:h}]of s.entries())h(n,p);n.setContext({commandClass:e})}process(e,r){let{input:s,context:a,partial:n}=typeof e=="object"&&Array.isArray(e)?{input:e,context:r}:e,{contexts:c,process:f}=this.builder.compile(),p=f(s,{partial:n}),h={...t.defaultContext,...a};switch(p.selectedIndex){case Od:{let E=ek.from(p,c);return E.context=h,E.tokens=p.tokens,E}default:{let{commandClass:E}=c[p.selectedIndex],C=this.registrations.get(E);if(typeof C>"u")throw new Error("Assertion failed: Expected the command class to have been registered.");let S=new E;S.context=h,S.tokens=p.tokens,S.path=p.path;try{for(let[P,{transformer:I}]of C.specs.entries())S[P]=I(C.builder,P,p,h);return S}catch(P){throw P[xne]=S,P}}break}}async run(e,r){var s,a;let n,c={...t.defaultContext,...r},f=(s=this.enableColors)!==null&&s!==void 0?s:c.colorDepth>1;if(!Array.isArray(e))n=e;else try{n=this.process(e,c)}catch(E){return c.stdout.write(this.error(E,{colored:f})),1}if(n.help)return c.stdout.write(this.usage(n,{colored:f,detailed:!0})),0;n.context=c,n.cli={binaryLabel:this.binaryLabel,binaryName:this.binaryName,binaryVersion:this.binaryVersion,enableCapture:this.enableCapture,enableColors:this.enableColors,definitions:()=>this.definitions(),definition:E=>this.definition(E),error:(E,C)=>this.error(E,C),format:E=>this.format(E),process:(E,C)=>this.process(E,{...c,...C}),run:(E,C)=>this.run(E,{...c,...C}),usage:(E,C)=>this.usage(E,C)};let p=this.enableCapture&&(a=Dne(c))!==null&&a!==void 0?a:kne,h;try{h=await p(()=>n.validateAndExecute().catch(E=>n.catch(E).then(()=>0)))}catch(E){return c.stdout.write(this.error(E,{colored:f,command:n})),1}return h}async runExit(e,r){process.exitCode=await this.run(e,r)}definition(e,{colored:r=!1}={}){if(!e.usage)return null;let{usage:s}=this.getUsageByRegistration(e,{detailed:!1}),{usage:a,options:n}=this.getUsageByRegistration(e,{detailed:!0,inlineOptions:!1}),c=typeof e.usage.category<"u"?qo(e.usage.category,{format:this.format(r),paragraphs:!1}):void 0,f=typeof e.usage.description<"u"?qo(e.usage.description,{format:this.format(r),paragraphs:!1}):void 0,p=typeof e.usage.details<"u"?qo(e.usage.details,{format:this.format(r),paragraphs:!0}):void 0,h=typeof e.usage.examples<"u"?e.usage.examples.map(([E,C])=>[qo(E,{format:this.format(r),paragraphs:!1}),C.replace(/\$0/g,this.binaryName)]):void 0;return{path:s,usage:a,category:c,description:f,details:p,examples:h,options:n}}definitions({colored:e=!1}={}){let r=[];for(let s of this.registrations.keys()){let a=this.definition(s,{colored:e});a&&r.push(a)}return r}usage(e=null,{colored:r,detailed:s=!1,prefix:a="$ "}={}){var n;if(e===null){for(let p of this.registrations.keys()){let h=p.paths,E=typeof p.usage<"u";if(!h||h.length===0||h.length===1&&h[0].length===0||((n=h?.some(P=>P.length===0))!==null&&n!==void 0?n:!1))if(e){e=null;break}else e=p;else if(E){e=null;continue}}e&&(s=!0)}let c=e!==null&&e instanceof ot?e.constructor:e,f="";if(c)if(s){let{description:p="",details:h="",examples:E=[]}=c.usage||{};p!==""&&(f+=qo(p,{format:this.format(r),paragraphs:!1}).replace(/^./,P=>P.toUpperCase()),f+=` +`),(h!==""||E.length>0)&&(f+=`${this.format(r).header("Usage")} +`,f+=` +`);let{usage:C,options:S}=this.getUsageByRegistration(c,{inlineOptions:!1});if(f+=`${this.format(r).bold(a)}${C} +`,S.length>0){f+=` +`,f+=`${this.format(r).header("Options")} +`;let P=S.reduce((I,R)=>Math.max(I,R.definition.length),0);f+=` +`;for(let{definition:I,description:R}of S)f+=` ${this.format(r).bold(I.padEnd(P))} ${qo(R,{format:this.format(r),paragraphs:!1})}`}if(h!==""&&(f+=` +`,f+=`${this.format(r).header("Details")} +`,f+=` +`,f+=qo(h,{format:this.format(r),paragraphs:!0})),E.length>0){f+=` +`,f+=`${this.format(r).header("Examples")} +`;for(let[P,I]of E)f+=` +`,f+=qo(P,{format:this.format(r),paragraphs:!1}),f+=`${I.replace(/^/m,` ${this.format(r).bold(a)}`).replace(/\$0/g,this.binaryName)} +`}}else{let{usage:p}=this.getUsageByRegistration(c);f+=`${this.format(r).bold(a)}${p} +`}else{let p=new Map;for(let[S,{index:P}]of this.registrations.entries()){if(typeof S.usage>"u")continue;let I=typeof S.usage.category<"u"?qo(S.usage.category,{format:this.format(r),paragraphs:!1}):null,R=p.get(I);typeof R>"u"&&p.set(I,R=[]);let{usage:N}=this.getUsageByIndex(P);R.push({commandClass:S,usage:N})}let h=Array.from(p.keys()).sort((S,P)=>S===null?-1:P===null?1:S.localeCompare(P,"en",{usage:"sort",caseFirst:"upper"})),E=typeof this.binaryLabel<"u",C=typeof this.binaryVersion<"u";E||C?(E&&C?f+=`${this.format(r).header(`${this.binaryLabel} - ${this.binaryVersion}`)} + +`:E?f+=`${this.format(r).header(`${this.binaryLabel}`)} +`:f+=`${this.format(r).header(`${this.binaryVersion}`)} +`,f+=` ${this.format(r).bold(a)}${this.binaryName} +`):f+=`${this.format(r).bold(a)}${this.binaryName} +`;for(let S of h){let P=p.get(S).slice().sort((R,N)=>R.usage.localeCompare(N.usage,"en",{usage:"sort",caseFirst:"upper"})),I=S!==null?S.trim():"General commands";f+=` +`,f+=`${this.format(r).header(`${I}`)} +`;for(let{commandClass:R,usage:N}of P){let U=R.usage.description||"undocumented";f+=` +`,f+=` ${this.format(r).bold(N)} +`,f+=` ${qo(U,{format:this.format(r),paragraphs:!1})}`}}f+=` +`,f+=qo("You can also print more details about any of these commands by calling them with the `-h,--help` flag right after the command name.",{format:this.format(r),paragraphs:!0})}return f}error(e,r){var s,{colored:a,command:n=(s=e[xne])!==null&&s!==void 0?s:null}=r===void 0?{}:r;(!e||typeof e!="object"||!("stack"in e))&&(e=new Error(`Execution failed with a non-error rejection (rejected value: ${JSON.stringify(e)})`));let c="",f=e.name.replace(/([a-z])([A-Z])/g,"$1 $2");f==="Error"&&(f="Internal Error"),c+=`${this.format(a).error(f)}: ${e.message} +`;let p=e.clipanion;return typeof p<"u"?p.type==="usage"&&(c+=` +`,c+=this.usage(n)):e.stack&&(c+=`${e.stack.replace(/^.*\n/,"")} +`),c}format(e){var r;return((r=e??this.enableColors)!==null&&r!==void 0?r:t.defaultContext.colorDepth>1)?pne:hne}getUsageByRegistration(e,r){let s=this.registrations.get(e);if(typeof s>"u")throw new Error("Assertion failed: Unregistered command");return this.getUsageByIndex(s.index,r)}getUsageByIndex(e,r){return this.builder.getBuilderByIndex(e).usage(r)}};wa.defaultContext={env:process.env,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr,colorDepth:Sne()}});var sB,Nne=It(()=>{u0();sB=class extends ot{async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.definitions(),null,2)} +`)}};sB.paths=[["--clipanion=definitions"]]});var oB,One=It(()=>{u0();oB=class extends ot{async execute(){this.context.stdout.write(this.cli.usage())}};oB.paths=[["-h"],["--help"]]});function tk(t={}){return Ea({definition(e,r){var s;e.addProxy({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){return s.positionals.map(({value:a})=>a)}})}var QU=It(()=>{Bp()});var aB,Lne=It(()=>{u0();QU();aB=class extends ot{constructor(){super(...arguments),this.args=tk()}async execute(){this.context.stdout.write(`${JSON.stringify(this.cli.process(this.args).tokens,null,2)} +`)}};aB.paths=[["--clipanion=tokens"]]});var lB,Mne=It(()=>{u0();lB=class extends ot{async execute(){var e;this.context.stdout.write(`${(e=this.cli.binaryVersion)!==null&&e!==void 0?e:""} +`)}};lB.paths=[["-v"],["--version"]]});var TU={};Vt(TU,{DefinitionsCommand:()=>sB,HelpCommand:()=>oB,TokensCommand:()=>aB,VersionCommand:()=>lB});var _ne=It(()=>{Nne();One();Lne();Mne()});function Une(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(","),f=new Set(c);return Ea({definition(p){p.addOption({names:c,arity:n,hidden:a?.hidden,description:a?.description,required:a.required})},transformer(p,h,E){let C,S=typeof s<"u"?[...s]:void 0;for(let{name:P,value:I}of E.options)f.has(P)&&(C=P,S=S??[],S.push(I));return typeof S<"u"?Ld(C??h,S,a.validator):S}})}var Hne=It(()=>{Bp()});function jne(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(","),c=new Set(n);return Ea({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E=S);return E}})}var qne=It(()=>{Bp()});function Gne(t,e,r){let[s,a]=Gf(e,r??{}),n=t.split(","),c=new Set(n);return Ea({definition(f){f.addOption({names:n,allowBinding:!1,arity:0,hidden:a.hidden,description:a.description,required:a.required})},transformer(f,p,h){let E=s;for(let{name:C,value:S}of h.options)c.has(C)&&(E??(E=0),S?E+=1:E=0);return E}})}var Wne=It(()=>{Bp()});function Yne(t={}){return Ea({definition(e,r){var s;e.addRest({name:(s=t.name)!==null&&s!==void 0?s:r,required:t.required})},transformer(e,r,s){let a=c=>{let f=s.positionals[c];return f.extra===jl||f.extra===!1&&cc)}})}var Vne=It(()=>{Xx();Bp()});function sXe(t,e,r){let[s,a]=Gf(e,r??{}),{arity:n=1}=a,c=t.split(","),f=new Set(c);return Ea({definition(p){p.addOption({names:c,arity:a.tolerateBoolean?0:n,hidden:a.hidden,description:a.description,required:a.required})},transformer(p,h,E,C){let S,P=s;typeof a.env<"u"&&C.env[a.env]&&(S=a.env,P=C.env[a.env]);for(let{name:I,value:R}of E.options)f.has(I)&&(S=I,P=R);return typeof P=="string"?Ld(S??h,P,a.validator):P}})}function oXe(t={}){let{required:e=!0}=t;return Ea({definition(r,s){var a;r.addPositional({name:(a=t.name)!==null&&a!==void 0?a:s,required:t.required})},transformer(r,s,a){var n;for(let c=0;c{Xx();Bp()});var ge={};Vt(ge,{Array:()=>Une,Boolean:()=>jne,Counter:()=>Gne,Proxy:()=>tk,Rest:()=>Yne,String:()=>Kne,applyValidator:()=>Ld,cleanValidationError:()=>Wx,formatError:()=>$2,isOptionSymbol:()=>X2,makeCommandOption:()=>Ea,rerouteArguments:()=>Gf});var zne=It(()=>{Bp();QU();Hne();qne();Wne();Vne();Jne()});var cB={};Vt(cB,{Builtins:()=>TU,Cli:()=>wa,Command:()=>ot,Option:()=>ge,UsageError:()=>nt,formatMarkdownish:()=>qo,run:()=>Tne,runExit:()=>Qne});var Wt=It(()=>{Gx();IU();u0();Fne();_ne();zne()});var Zne=L((RWt,aXe)=>{aXe.exports={name:"dotenv",version:"16.3.1",description:"Loads environment variables from .env file",main:"lib/main.js",types:"lib/main.d.ts",exports:{".":{types:"./lib/main.d.ts",require:"./lib/main.js",default:"./lib/main.js"},"./config":"./config.js","./config.js":"./config.js","./lib/env-options":"./lib/env-options.js","./lib/env-options.js":"./lib/env-options.js","./lib/cli-options":"./lib/cli-options.js","./lib/cli-options.js":"./lib/cli-options.js","./package.json":"./package.json"},scripts:{"dts-check":"tsc --project tests/types/tsconfig.json",lint:"standard","lint-readme":"standard-markdown",pretest:"npm run lint && npm run dts-check",test:"tap tests/*.js --100 -Rspec",prerelease:"npm test",release:"standard-version"},repository:{type:"git",url:"git://github.com/motdotla/dotenv.git"},funding:"https://github.com/motdotla/dotenv?sponsor=1",keywords:["dotenv","env",".env","environment","variables","config","settings"],readmeFilename:"README.md",license:"BSD-2-Clause",devDependencies:{"@definitelytyped/dtslint":"^0.0.133","@types/node":"^18.11.3",decache:"^4.6.1",sinon:"^14.0.1",standard:"^17.0.0","standard-markdown":"^7.1.0","standard-version":"^9.5.0",tap:"^16.3.0",tar:"^6.1.11",typescript:"^4.8.4"},engines:{node:">=12"},browser:{fs:!1}}});var tie=L((FWt,vp)=>{var Xne=ye("fs"),FU=ye("path"),lXe=ye("os"),cXe=ye("crypto"),uXe=Zne(),NU=uXe.version,fXe=/(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;function AXe(t){let e={},r=t.toString();r=r.replace(/\r\n?/mg,` +`);let s;for(;(s=fXe.exec(r))!=null;){let a=s[1],n=s[2]||"";n=n.trim();let c=n[0];n=n.replace(/^(['"`])([\s\S]*)\1$/mg,"$2"),c==='"'&&(n=n.replace(/\\n/g,` +`),n=n.replace(/\\r/g,"\r")),e[a]=n}return e}function pXe(t){let e=eie(t),r=qs.configDotenv({path:e});if(!r.parsed)throw new Error(`MISSING_DATA: Cannot parse ${e} for an unknown reason`);let s=$ne(t).split(","),a=s.length,n;for(let c=0;c=a)throw f}return qs.parse(n)}function hXe(t){console.log(`[dotenv@${NU}][INFO] ${t}`)}function gXe(t){console.log(`[dotenv@${NU}][WARN] ${t}`)}function RU(t){console.log(`[dotenv@${NU}][DEBUG] ${t}`)}function $ne(t){return t&&t.DOTENV_KEY&&t.DOTENV_KEY.length>0?t.DOTENV_KEY:process.env.DOTENV_KEY&&process.env.DOTENV_KEY.length>0?process.env.DOTENV_KEY:""}function dXe(t,e){let r;try{r=new URL(e)}catch(f){throw f.code==="ERR_INVALID_URL"?new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenv.org/vault/.env.vault?environment=development"):f}let s=r.password;if(!s)throw new Error("INVALID_DOTENV_KEY: Missing key part");let a=r.searchParams.get("environment");if(!a)throw new Error("INVALID_DOTENV_KEY: Missing environment part");let n=`DOTENV_VAULT_${a.toUpperCase()}`,c=t.parsed[n];if(!c)throw new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${n} in your .env.vault file.`);return{ciphertext:c,key:s}}function eie(t){let e=FU.resolve(process.cwd(),".env");return t&&t.path&&t.path.length>0&&(e=t.path),e.endsWith(".vault")?e:`${e}.vault`}function mXe(t){return t[0]==="~"?FU.join(lXe.homedir(),t.slice(1)):t}function yXe(t){hXe("Loading env from encrypted .env.vault");let e=qs._parseVault(t),r=process.env;return t&&t.processEnv!=null&&(r=t.processEnv),qs.populate(r,e,t),{parsed:e}}function EXe(t){let e=FU.resolve(process.cwd(),".env"),r="utf8",s=!!(t&&t.debug);t&&(t.path!=null&&(e=mXe(t.path)),t.encoding!=null&&(r=t.encoding));try{let a=qs.parse(Xne.readFileSync(e,{encoding:r})),n=process.env;return t&&t.processEnv!=null&&(n=t.processEnv),qs.populate(n,a,t),{parsed:a}}catch(a){return s&&RU(`Failed to load ${e} ${a.message}`),{error:a}}}function IXe(t){let e=eie(t);return $ne(t).length===0?qs.configDotenv(t):Xne.existsSync(e)?qs._configVault(t):(gXe(`You set DOTENV_KEY but you are missing a .env.vault file at ${e}. Did you forget to build it?`),qs.configDotenv(t))}function CXe(t,e){let r=Buffer.from(e.slice(-64),"hex"),s=Buffer.from(t,"base64"),a=s.slice(0,12),n=s.slice(-16);s=s.slice(12,-16);try{let c=cXe.createDecipheriv("aes-256-gcm",r,a);return c.setAuthTag(n),`${c.update(s)}${c.final()}`}catch(c){let f=c instanceof RangeError,p=c.message==="Invalid key length",h=c.message==="Unsupported state or unable to authenticate data";if(f||p){let E="INVALID_DOTENV_KEY: It must be 64 characters long (or more)";throw new Error(E)}else if(h){let E="DECRYPTION_FAILED: Please check your DOTENV_KEY";throw new Error(E)}else throw console.error("Error: ",c.code),console.error("Error: ",c.message),c}}function wXe(t,e,r={}){let s=!!(r&&r.debug),a=!!(r&&r.override);if(typeof e!="object")throw new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");for(let n of Object.keys(e))Object.prototype.hasOwnProperty.call(t,n)?(a===!0&&(t[n]=e[n]),s&&RU(a===!0?`"${n}" is already defined and WAS overwritten`:`"${n}" is already defined and was NOT overwritten`)):t[n]=e[n]}var qs={configDotenv:EXe,_configVault:yXe,_parseVault:pXe,config:IXe,decrypt:CXe,parse:AXe,populate:wXe};vp.exports.configDotenv=qs.configDotenv;vp.exports._configVault=qs._configVault;vp.exports._parseVault=qs._parseVault;vp.exports.config=qs.config;vp.exports.decrypt=qs.decrypt;vp.exports.parse=qs.parse;vp.exports.populate=qs.populate;vp.exports=qs});var nie=L((NWt,rie)=>{"use strict";rie.exports=(t,...e)=>new Promise(r=>{r(t(...e))})});var Md=L((OWt,OU)=>{"use strict";var BXe=nie(),iie=t=>{if(t<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let e=[],r=0,s=()=>{r--,e.length>0&&e.shift()()},a=(f,p,...h)=>{r++;let E=BXe(f,...h);p(E),E.then(s,s)},n=(f,p,...h)=>{rnew Promise(h=>n(f,h,...p));return Object.defineProperties(c,{activeCount:{get:()=>r},pendingCount:{get:()=>e.length}}),c};OU.exports=iie;OU.exports.default=iie});function Vf(t){return`YN${t.toString(10).padStart(4,"0")}`}function rk(t){let e=Number(t.slice(2));if(typeof Dr[e]>"u")throw new Error(`Unknown message name: "${t}"`);return e}var Dr,nk=It(()=>{Dr=(Me=>(Me[Me.UNNAMED=0]="UNNAMED",Me[Me.EXCEPTION=1]="EXCEPTION",Me[Me.MISSING_PEER_DEPENDENCY=2]="MISSING_PEER_DEPENDENCY",Me[Me.CYCLIC_DEPENDENCIES=3]="CYCLIC_DEPENDENCIES",Me[Me.DISABLED_BUILD_SCRIPTS=4]="DISABLED_BUILD_SCRIPTS",Me[Me.BUILD_DISABLED=5]="BUILD_DISABLED",Me[Me.SOFT_LINK_BUILD=6]="SOFT_LINK_BUILD",Me[Me.MUST_BUILD=7]="MUST_BUILD",Me[Me.MUST_REBUILD=8]="MUST_REBUILD",Me[Me.BUILD_FAILED=9]="BUILD_FAILED",Me[Me.RESOLVER_NOT_FOUND=10]="RESOLVER_NOT_FOUND",Me[Me.FETCHER_NOT_FOUND=11]="FETCHER_NOT_FOUND",Me[Me.LINKER_NOT_FOUND=12]="LINKER_NOT_FOUND",Me[Me.FETCH_NOT_CACHED=13]="FETCH_NOT_CACHED",Me[Me.YARN_IMPORT_FAILED=14]="YARN_IMPORT_FAILED",Me[Me.REMOTE_INVALID=15]="REMOTE_INVALID",Me[Me.REMOTE_NOT_FOUND=16]="REMOTE_NOT_FOUND",Me[Me.RESOLUTION_PACK=17]="RESOLUTION_PACK",Me[Me.CACHE_CHECKSUM_MISMATCH=18]="CACHE_CHECKSUM_MISMATCH",Me[Me.UNUSED_CACHE_ENTRY=19]="UNUSED_CACHE_ENTRY",Me[Me.MISSING_LOCKFILE_ENTRY=20]="MISSING_LOCKFILE_ENTRY",Me[Me.WORKSPACE_NOT_FOUND=21]="WORKSPACE_NOT_FOUND",Me[Me.TOO_MANY_MATCHING_WORKSPACES=22]="TOO_MANY_MATCHING_WORKSPACES",Me[Me.CONSTRAINTS_MISSING_DEPENDENCY=23]="CONSTRAINTS_MISSING_DEPENDENCY",Me[Me.CONSTRAINTS_INCOMPATIBLE_DEPENDENCY=24]="CONSTRAINTS_INCOMPATIBLE_DEPENDENCY",Me[Me.CONSTRAINTS_EXTRANEOUS_DEPENDENCY=25]="CONSTRAINTS_EXTRANEOUS_DEPENDENCY",Me[Me.CONSTRAINTS_INVALID_DEPENDENCY=26]="CONSTRAINTS_INVALID_DEPENDENCY",Me[Me.CANT_SUGGEST_RESOLUTIONS=27]="CANT_SUGGEST_RESOLUTIONS",Me[Me.FROZEN_LOCKFILE_EXCEPTION=28]="FROZEN_LOCKFILE_EXCEPTION",Me[Me.CROSS_DRIVE_VIRTUAL_LOCAL=29]="CROSS_DRIVE_VIRTUAL_LOCAL",Me[Me.FETCH_FAILED=30]="FETCH_FAILED",Me[Me.DANGEROUS_NODE_MODULES=31]="DANGEROUS_NODE_MODULES",Me[Me.NODE_GYP_INJECTED=32]="NODE_GYP_INJECTED",Me[Me.AUTHENTICATION_NOT_FOUND=33]="AUTHENTICATION_NOT_FOUND",Me[Me.INVALID_CONFIGURATION_KEY=34]="INVALID_CONFIGURATION_KEY",Me[Me.NETWORK_ERROR=35]="NETWORK_ERROR",Me[Me.LIFECYCLE_SCRIPT=36]="LIFECYCLE_SCRIPT",Me[Me.CONSTRAINTS_MISSING_FIELD=37]="CONSTRAINTS_MISSING_FIELD",Me[Me.CONSTRAINTS_INCOMPATIBLE_FIELD=38]="CONSTRAINTS_INCOMPATIBLE_FIELD",Me[Me.CONSTRAINTS_EXTRANEOUS_FIELD=39]="CONSTRAINTS_EXTRANEOUS_FIELD",Me[Me.CONSTRAINTS_INVALID_FIELD=40]="CONSTRAINTS_INVALID_FIELD",Me[Me.AUTHENTICATION_INVALID=41]="AUTHENTICATION_INVALID",Me[Me.PROLOG_UNKNOWN_ERROR=42]="PROLOG_UNKNOWN_ERROR",Me[Me.PROLOG_SYNTAX_ERROR=43]="PROLOG_SYNTAX_ERROR",Me[Me.PROLOG_EXISTENCE_ERROR=44]="PROLOG_EXISTENCE_ERROR",Me[Me.STACK_OVERFLOW_RESOLUTION=45]="STACK_OVERFLOW_RESOLUTION",Me[Me.AUTOMERGE_FAILED_TO_PARSE=46]="AUTOMERGE_FAILED_TO_PARSE",Me[Me.AUTOMERGE_IMMUTABLE=47]="AUTOMERGE_IMMUTABLE",Me[Me.AUTOMERGE_SUCCESS=48]="AUTOMERGE_SUCCESS",Me[Me.AUTOMERGE_REQUIRED=49]="AUTOMERGE_REQUIRED",Me[Me.DEPRECATED_CLI_SETTINGS=50]="DEPRECATED_CLI_SETTINGS",Me[Me.PLUGIN_NAME_NOT_FOUND=51]="PLUGIN_NAME_NOT_FOUND",Me[Me.INVALID_PLUGIN_REFERENCE=52]="INVALID_PLUGIN_REFERENCE",Me[Me.CONSTRAINTS_AMBIGUITY=53]="CONSTRAINTS_AMBIGUITY",Me[Me.CACHE_OUTSIDE_PROJECT=54]="CACHE_OUTSIDE_PROJECT",Me[Me.IMMUTABLE_INSTALL=55]="IMMUTABLE_INSTALL",Me[Me.IMMUTABLE_CACHE=56]="IMMUTABLE_CACHE",Me[Me.INVALID_MANIFEST=57]="INVALID_MANIFEST",Me[Me.PACKAGE_PREPARATION_FAILED=58]="PACKAGE_PREPARATION_FAILED",Me[Me.INVALID_RANGE_PEER_DEPENDENCY=59]="INVALID_RANGE_PEER_DEPENDENCY",Me[Me.INCOMPATIBLE_PEER_DEPENDENCY=60]="INCOMPATIBLE_PEER_DEPENDENCY",Me[Me.DEPRECATED_PACKAGE=61]="DEPRECATED_PACKAGE",Me[Me.INCOMPATIBLE_OS=62]="INCOMPATIBLE_OS",Me[Me.INCOMPATIBLE_CPU=63]="INCOMPATIBLE_CPU",Me[Me.FROZEN_ARTIFACT_EXCEPTION=64]="FROZEN_ARTIFACT_EXCEPTION",Me[Me.TELEMETRY_NOTICE=65]="TELEMETRY_NOTICE",Me[Me.PATCH_HUNK_FAILED=66]="PATCH_HUNK_FAILED",Me[Me.INVALID_CONFIGURATION_VALUE=67]="INVALID_CONFIGURATION_VALUE",Me[Me.UNUSED_PACKAGE_EXTENSION=68]="UNUSED_PACKAGE_EXTENSION",Me[Me.REDUNDANT_PACKAGE_EXTENSION=69]="REDUNDANT_PACKAGE_EXTENSION",Me[Me.AUTO_NM_SUCCESS=70]="AUTO_NM_SUCCESS",Me[Me.NM_CANT_INSTALL_EXTERNAL_SOFT_LINK=71]="NM_CANT_INSTALL_EXTERNAL_SOFT_LINK",Me[Me.NM_PRESERVE_SYMLINKS_REQUIRED=72]="NM_PRESERVE_SYMLINKS_REQUIRED",Me[Me.UPDATE_LOCKFILE_ONLY_SKIP_LINK=73]="UPDATE_LOCKFILE_ONLY_SKIP_LINK",Me[Me.NM_HARDLINKS_MODE_DOWNGRADED=74]="NM_HARDLINKS_MODE_DOWNGRADED",Me[Me.PROLOG_INSTANTIATION_ERROR=75]="PROLOG_INSTANTIATION_ERROR",Me[Me.INCOMPATIBLE_ARCHITECTURE=76]="INCOMPATIBLE_ARCHITECTURE",Me[Me.GHOST_ARCHITECTURE=77]="GHOST_ARCHITECTURE",Me[Me.RESOLUTION_MISMATCH=78]="RESOLUTION_MISMATCH",Me[Me.PROLOG_LIMIT_EXCEEDED=79]="PROLOG_LIMIT_EXCEEDED",Me[Me.NETWORK_DISABLED=80]="NETWORK_DISABLED",Me[Me.NETWORK_UNSAFE_HTTP=81]="NETWORK_UNSAFE_HTTP",Me[Me.RESOLUTION_FAILED=82]="RESOLUTION_FAILED",Me[Me.AUTOMERGE_GIT_ERROR=83]="AUTOMERGE_GIT_ERROR",Me[Me.CONSTRAINTS_CHECK_FAILED=84]="CONSTRAINTS_CHECK_FAILED",Me[Me.UPDATED_RESOLUTION_RECORD=85]="UPDATED_RESOLUTION_RECORD",Me[Me.EXPLAIN_PEER_DEPENDENCIES_CTA=86]="EXPLAIN_PEER_DEPENDENCIES_CTA",Me[Me.MIGRATION_SUCCESS=87]="MIGRATION_SUCCESS",Me[Me.VERSION_NOTICE=88]="VERSION_NOTICE",Me[Me.TIPS_NOTICE=89]="TIPS_NOTICE",Me[Me.OFFLINE_MODE_ENABLED=90]="OFFLINE_MODE_ENABLED",Me[Me.INVALID_PROVENANCE_ENVIRONMENT=91]="INVALID_PROVENANCE_ENVIRONMENT",Me))(Dr||{})});var uB=L((MWt,sie)=>{var vXe="2.0.0",SXe=Number.MAX_SAFE_INTEGER||9007199254740991,DXe=16,bXe=250,PXe=["major","premajor","minor","preminor","patch","prepatch","prerelease"];sie.exports={MAX_LENGTH:256,MAX_SAFE_COMPONENT_LENGTH:DXe,MAX_SAFE_BUILD_LENGTH:bXe,MAX_SAFE_INTEGER:SXe,RELEASE_TYPES:PXe,SEMVER_SPEC_VERSION:vXe,FLAG_INCLUDE_PRERELEASE:1,FLAG_LOOSE:2}});var fB=L((_Wt,oie)=>{var xXe=typeof process=="object"&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?(...t)=>console.error("SEMVER",...t):()=>{};oie.exports=xXe});var vE=L((Sp,aie)=>{var{MAX_SAFE_COMPONENT_LENGTH:LU,MAX_SAFE_BUILD_LENGTH:kXe,MAX_LENGTH:QXe}=uB(),TXe=fB();Sp=aie.exports={};var RXe=Sp.re=[],FXe=Sp.safeRe=[],rr=Sp.src=[],nr=Sp.t={},NXe=0,MU="[a-zA-Z0-9-]",OXe=[["\\s",1],["\\d",QXe],[MU,kXe]],LXe=t=>{for(let[e,r]of OXe)t=t.split(`${e}*`).join(`${e}{0,${r}}`).split(`${e}+`).join(`${e}{1,${r}}`);return t},Kr=(t,e,r)=>{let s=LXe(e),a=NXe++;TXe(t,a,e),nr[t]=a,rr[a]=e,RXe[a]=new RegExp(e,r?"g":void 0),FXe[a]=new RegExp(s,r?"g":void 0)};Kr("NUMERICIDENTIFIER","0|[1-9]\\d*");Kr("NUMERICIDENTIFIERLOOSE","\\d+");Kr("NONNUMERICIDENTIFIER",`\\d*[a-zA-Z-]${MU}*`);Kr("MAINVERSION",`(${rr[nr.NUMERICIDENTIFIER]})\\.(${rr[nr.NUMERICIDENTIFIER]})\\.(${rr[nr.NUMERICIDENTIFIER]})`);Kr("MAINVERSIONLOOSE",`(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})\\.(${rr[nr.NUMERICIDENTIFIERLOOSE]})`);Kr("PRERELEASEIDENTIFIER",`(?:${rr[nr.NUMERICIDENTIFIER]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Kr("PRERELEASEIDENTIFIERLOOSE",`(?:${rr[nr.NUMERICIDENTIFIERLOOSE]}|${rr[nr.NONNUMERICIDENTIFIER]})`);Kr("PRERELEASE",`(?:-(${rr[nr.PRERELEASEIDENTIFIER]}(?:\\.${rr[nr.PRERELEASEIDENTIFIER]})*))`);Kr("PRERELEASELOOSE",`(?:-?(${rr[nr.PRERELEASEIDENTIFIERLOOSE]}(?:\\.${rr[nr.PRERELEASEIDENTIFIERLOOSE]})*))`);Kr("BUILDIDENTIFIER",`${MU}+`);Kr("BUILD",`(?:\\+(${rr[nr.BUILDIDENTIFIER]}(?:\\.${rr[nr.BUILDIDENTIFIER]})*))`);Kr("FULLPLAIN",`v?${rr[nr.MAINVERSION]}${rr[nr.PRERELEASE]}?${rr[nr.BUILD]}?`);Kr("FULL",`^${rr[nr.FULLPLAIN]}$`);Kr("LOOSEPLAIN",`[v=\\s]*${rr[nr.MAINVERSIONLOOSE]}${rr[nr.PRERELEASELOOSE]}?${rr[nr.BUILD]}?`);Kr("LOOSE",`^${rr[nr.LOOSEPLAIN]}$`);Kr("GTLT","((?:<|>)?=?)");Kr("XRANGEIDENTIFIERLOOSE",`${rr[nr.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`);Kr("XRANGEIDENTIFIER",`${rr[nr.NUMERICIDENTIFIER]}|x|X|\\*`);Kr("XRANGEPLAIN",`[v=\\s]*(${rr[nr.XRANGEIDENTIFIER]})(?:\\.(${rr[nr.XRANGEIDENTIFIER]})(?:\\.(${rr[nr.XRANGEIDENTIFIER]})(?:${rr[nr.PRERELEASE]})?${rr[nr.BUILD]}?)?)?`);Kr("XRANGEPLAINLOOSE",`[v=\\s]*(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:\\.(${rr[nr.XRANGEIDENTIFIERLOOSE]})(?:${rr[nr.PRERELEASELOOSE]})?${rr[nr.BUILD]}?)?)?`);Kr("XRANGE",`^${rr[nr.GTLT]}\\s*${rr[nr.XRANGEPLAIN]}$`);Kr("XRANGELOOSE",`^${rr[nr.GTLT]}\\s*${rr[nr.XRANGEPLAINLOOSE]}$`);Kr("COERCEPLAIN",`(^|[^\\d])(\\d{1,${LU}})(?:\\.(\\d{1,${LU}}))?(?:\\.(\\d{1,${LU}}))?`);Kr("COERCE",`${rr[nr.COERCEPLAIN]}(?:$|[^\\d])`);Kr("COERCEFULL",rr[nr.COERCEPLAIN]+`(?:${rr[nr.PRERELEASE]})?(?:${rr[nr.BUILD]})?(?:$|[^\\d])`);Kr("COERCERTL",rr[nr.COERCE],!0);Kr("COERCERTLFULL",rr[nr.COERCEFULL],!0);Kr("LONETILDE","(?:~>?)");Kr("TILDETRIM",`(\\s*)${rr[nr.LONETILDE]}\\s+`,!0);Sp.tildeTrimReplace="$1~";Kr("TILDE",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAIN]}$`);Kr("TILDELOOSE",`^${rr[nr.LONETILDE]}${rr[nr.XRANGEPLAINLOOSE]}$`);Kr("LONECARET","(?:\\^)");Kr("CARETTRIM",`(\\s*)${rr[nr.LONECARET]}\\s+`,!0);Sp.caretTrimReplace="$1^";Kr("CARET",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAIN]}$`);Kr("CARETLOOSE",`^${rr[nr.LONECARET]}${rr[nr.XRANGEPLAINLOOSE]}$`);Kr("COMPARATORLOOSE",`^${rr[nr.GTLT]}\\s*(${rr[nr.LOOSEPLAIN]})$|^$`);Kr("COMPARATOR",`^${rr[nr.GTLT]}\\s*(${rr[nr.FULLPLAIN]})$|^$`);Kr("COMPARATORTRIM",`(\\s*)${rr[nr.GTLT]}\\s*(${rr[nr.LOOSEPLAIN]}|${rr[nr.XRANGEPLAIN]})`,!0);Sp.comparatorTrimReplace="$1$2$3";Kr("HYPHENRANGE",`^\\s*(${rr[nr.XRANGEPLAIN]})\\s+-\\s+(${rr[nr.XRANGEPLAIN]})\\s*$`);Kr("HYPHENRANGELOOSE",`^\\s*(${rr[nr.XRANGEPLAINLOOSE]})\\s+-\\s+(${rr[nr.XRANGEPLAINLOOSE]})\\s*$`);Kr("STAR","(<|>)?=?\\s*\\*");Kr("GTE0","^\\s*>=\\s*0\\.0\\.0\\s*$");Kr("GTE0PRE","^\\s*>=\\s*0\\.0\\.0-0\\s*$")});var ik=L((UWt,lie)=>{var MXe=Object.freeze({loose:!0}),_Xe=Object.freeze({}),UXe=t=>t?typeof t!="object"?MXe:t:_Xe;lie.exports=UXe});var _U=L((HWt,fie)=>{var cie=/^[0-9]+$/,uie=(t,e)=>{let r=cie.test(t),s=cie.test(e);return r&&s&&(t=+t,e=+e),t===e?0:r&&!s?-1:s&&!r?1:tuie(e,t);fie.exports={compareIdentifiers:uie,rcompareIdentifiers:HXe}});var Go=L((jWt,gie)=>{var sk=fB(),{MAX_LENGTH:Aie,MAX_SAFE_INTEGER:ok}=uB(),{safeRe:pie,t:hie}=vE(),jXe=ik(),{compareIdentifiers:SE}=_U(),UU=class t{constructor(e,r){if(r=jXe(r),e instanceof t){if(e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease)return e;e=e.version}else if(typeof e!="string")throw new TypeError(`Invalid version. Must be a string. Got type "${typeof e}".`);if(e.length>Aie)throw new TypeError(`version is longer than ${Aie} characters`);sk("SemVer",e,r),this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease;let s=e.trim().match(r.loose?pie[hie.LOOSE]:pie[hie.FULL]);if(!s)throw new TypeError(`Invalid Version: ${e}`);if(this.raw=e,this.major=+s[1],this.minor=+s[2],this.patch=+s[3],this.major>ok||this.major<0)throw new TypeError("Invalid major version");if(this.minor>ok||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>ok||this.patch<0)throw new TypeError("Invalid patch version");s[4]?this.prerelease=s[4].split(".").map(a=>{if(/^[0-9]+$/.test(a)){let n=+a;if(n>=0&&n=0;)typeof this.prerelease[n]=="number"&&(this.prerelease[n]++,n=-2);if(n===-1){if(r===this.prerelease.join(".")&&s===!1)throw new Error("invalid increment argument: identifier already exists");this.prerelease.push(a)}}if(r){let n=[r,a];s===!1&&(n=[r]),SE(this.prerelease[0],r)===0?isNaN(this.prerelease[1])&&(this.prerelease=n):this.prerelease=n}break}default:throw new Error(`invalid increment argument: ${e}`)}return this.raw=this.format(),this.build.length&&(this.raw+=`+${this.build.join(".")}`),this}};gie.exports=UU});var _d=L((qWt,mie)=>{var die=Go(),qXe=(t,e,r=!1)=>{if(t instanceof die)return t;try{return new die(t,e)}catch(s){if(!r)return null;throw s}};mie.exports=qXe});var Eie=L((GWt,yie)=>{var GXe=_d(),WXe=(t,e)=>{let r=GXe(t,e);return r?r.version:null};yie.exports=WXe});var Cie=L((WWt,Iie)=>{var YXe=_d(),VXe=(t,e)=>{let r=YXe(t.trim().replace(/^[=v]+/,""),e);return r?r.version:null};Iie.exports=VXe});var vie=L((YWt,Bie)=>{var wie=Go(),KXe=(t,e,r,s,a)=>{typeof r=="string"&&(a=s,s=r,r=void 0);try{return new wie(t instanceof wie?t.version:t,r).inc(e,s,a).version}catch{return null}};Bie.exports=KXe});var bie=L((VWt,Die)=>{var Sie=_d(),JXe=(t,e)=>{let r=Sie(t,null,!0),s=Sie(e,null,!0),a=r.compare(s);if(a===0)return null;let n=a>0,c=n?r:s,f=n?s:r,p=!!c.prerelease.length;if(!!f.prerelease.length&&!p)return!f.patch&&!f.minor?"major":c.patch?"patch":c.minor?"minor":"major";let E=p?"pre":"";return r.major!==s.major?E+"major":r.minor!==s.minor?E+"minor":r.patch!==s.patch?E+"patch":"prerelease"};Die.exports=JXe});var xie=L((KWt,Pie)=>{var zXe=Go(),ZXe=(t,e)=>new zXe(t,e).major;Pie.exports=ZXe});var Qie=L((JWt,kie)=>{var XXe=Go(),$Xe=(t,e)=>new XXe(t,e).minor;kie.exports=$Xe});var Rie=L((zWt,Tie)=>{var e$e=Go(),t$e=(t,e)=>new e$e(t,e).patch;Tie.exports=t$e});var Nie=L((ZWt,Fie)=>{var r$e=_d(),n$e=(t,e)=>{let r=r$e(t,e);return r&&r.prerelease.length?r.prerelease:null};Fie.exports=n$e});var vc=L((XWt,Lie)=>{var Oie=Go(),i$e=(t,e,r)=>new Oie(t,r).compare(new Oie(e,r));Lie.exports=i$e});var _ie=L(($Wt,Mie)=>{var s$e=vc(),o$e=(t,e,r)=>s$e(e,t,r);Mie.exports=o$e});var Hie=L((eYt,Uie)=>{var a$e=vc(),l$e=(t,e)=>a$e(t,e,!0);Uie.exports=l$e});var ak=L((tYt,qie)=>{var jie=Go(),c$e=(t,e,r)=>{let s=new jie(t,r),a=new jie(e,r);return s.compare(a)||s.compareBuild(a)};qie.exports=c$e});var Wie=L((rYt,Gie)=>{var u$e=ak(),f$e=(t,e)=>t.sort((r,s)=>u$e(r,s,e));Gie.exports=f$e});var Vie=L((nYt,Yie)=>{var A$e=ak(),p$e=(t,e)=>t.sort((r,s)=>A$e(s,r,e));Yie.exports=p$e});var AB=L((iYt,Kie)=>{var h$e=vc(),g$e=(t,e,r)=>h$e(t,e,r)>0;Kie.exports=g$e});var lk=L((sYt,Jie)=>{var d$e=vc(),m$e=(t,e,r)=>d$e(t,e,r)<0;Jie.exports=m$e});var HU=L((oYt,zie)=>{var y$e=vc(),E$e=(t,e,r)=>y$e(t,e,r)===0;zie.exports=E$e});var jU=L((aYt,Zie)=>{var I$e=vc(),C$e=(t,e,r)=>I$e(t,e,r)!==0;Zie.exports=C$e});var ck=L((lYt,Xie)=>{var w$e=vc(),B$e=(t,e,r)=>w$e(t,e,r)>=0;Xie.exports=B$e});var uk=L((cYt,$ie)=>{var v$e=vc(),S$e=(t,e,r)=>v$e(t,e,r)<=0;$ie.exports=S$e});var qU=L((uYt,ese)=>{var D$e=HU(),b$e=jU(),P$e=AB(),x$e=ck(),k$e=lk(),Q$e=uk(),T$e=(t,e,r,s)=>{switch(e){case"===":return typeof t=="object"&&(t=t.version),typeof r=="object"&&(r=r.version),t===r;case"!==":return typeof t=="object"&&(t=t.version),typeof r=="object"&&(r=r.version),t!==r;case"":case"=":case"==":return D$e(t,r,s);case"!=":return b$e(t,r,s);case">":return P$e(t,r,s);case">=":return x$e(t,r,s);case"<":return k$e(t,r,s);case"<=":return Q$e(t,r,s);default:throw new TypeError(`Invalid operator: ${e}`)}};ese.exports=T$e});var rse=L((fYt,tse)=>{var R$e=Go(),F$e=_d(),{safeRe:fk,t:Ak}=vE(),N$e=(t,e)=>{if(t instanceof R$e)return t;if(typeof t=="number"&&(t=String(t)),typeof t!="string")return null;e=e||{};let r=null;if(!e.rtl)r=t.match(e.includePrerelease?fk[Ak.COERCEFULL]:fk[Ak.COERCE]);else{let p=e.includePrerelease?fk[Ak.COERCERTLFULL]:fk[Ak.COERCERTL],h;for(;(h=p.exec(t))&&(!r||r.index+r[0].length!==t.length);)(!r||h.index+h[0].length!==r.index+r[0].length)&&(r=h),p.lastIndex=h.index+h[1].length+h[2].length;p.lastIndex=-1}if(r===null)return null;let s=r[2],a=r[3]||"0",n=r[4]||"0",c=e.includePrerelease&&r[5]?`-${r[5]}`:"",f=e.includePrerelease&&r[6]?`+${r[6]}`:"";return F$e(`${s}.${a}.${n}${c}${f}`,e)};tse.exports=N$e});var ise=L((AYt,nse)=>{"use strict";nse.exports=function(t){t.prototype[Symbol.iterator]=function*(){for(let e=this.head;e;e=e.next)yield e.value}}});var pk=L((pYt,sse)=>{"use strict";sse.exports=Fn;Fn.Node=Ud;Fn.create=Fn;function Fn(t){var e=this;if(e instanceof Fn||(e=new Fn),e.tail=null,e.head=null,e.length=0,t&&typeof t.forEach=="function")t.forEach(function(a){e.push(a)});else if(arguments.length>0)for(var r=0,s=arguments.length;r1)r=e;else if(this.head)s=this.head.next,r=this.head.value;else throw new TypeError("Reduce of empty list with no initial value");for(var a=0;s!==null;a++)r=t(r,s.value,a),s=s.next;return r};Fn.prototype.reduceReverse=function(t,e){var r,s=this.tail;if(arguments.length>1)r=e;else if(this.tail)s=this.tail.prev,r=this.tail.value;else throw new TypeError("Reduce of empty list with no initial value");for(var a=this.length-1;s!==null;a--)r=t(r,s.value,a),s=s.prev;return r};Fn.prototype.toArray=function(){for(var t=new Array(this.length),e=0,r=this.head;r!==null;e++)t[e]=r.value,r=r.next;return t};Fn.prototype.toArrayReverse=function(){for(var t=new Array(this.length),e=0,r=this.tail;r!==null;e++)t[e]=r.value,r=r.prev;return t};Fn.prototype.slice=function(t,e){e=e||this.length,e<0&&(e+=this.length),t=t||0,t<0&&(t+=this.length);var r=new Fn;if(ethis.length&&(e=this.length);for(var s=0,a=this.head;a!==null&&sthis.length&&(e=this.length);for(var s=this.length,a=this.tail;a!==null&&s>e;s--)a=a.prev;for(;a!==null&&s>t;s--,a=a.prev)r.push(a.value);return r};Fn.prototype.splice=function(t,e,...r){t>this.length&&(t=this.length-1),t<0&&(t=this.length+t);for(var s=0,a=this.head;a!==null&&s{"use strict";var _$e=pk(),Hd=Symbol("max"),bp=Symbol("length"),DE=Symbol("lengthCalculator"),hB=Symbol("allowStale"),jd=Symbol("maxAge"),Dp=Symbol("dispose"),ose=Symbol("noDisposeOnSet"),Gs=Symbol("lruList"),_u=Symbol("cache"),lse=Symbol("updateAgeOnGet"),GU=()=>1,YU=class{constructor(e){if(typeof e=="number"&&(e={max:e}),e||(e={}),e.max&&(typeof e.max!="number"||e.max<0))throw new TypeError("max must be a non-negative number");let r=this[Hd]=e.max||1/0,s=e.length||GU;if(this[DE]=typeof s!="function"?GU:s,this[hB]=e.stale||!1,e.maxAge&&typeof e.maxAge!="number")throw new TypeError("maxAge must be a number");this[jd]=e.maxAge||0,this[Dp]=e.dispose,this[ose]=e.noDisposeOnSet||!1,this[lse]=e.updateAgeOnGet||!1,this.reset()}set max(e){if(typeof e!="number"||e<0)throw new TypeError("max must be a non-negative number");this[Hd]=e||1/0,pB(this)}get max(){return this[Hd]}set allowStale(e){this[hB]=!!e}get allowStale(){return this[hB]}set maxAge(e){if(typeof e!="number")throw new TypeError("maxAge must be a non-negative number");this[jd]=e,pB(this)}get maxAge(){return this[jd]}set lengthCalculator(e){typeof e!="function"&&(e=GU),e!==this[DE]&&(this[DE]=e,this[bp]=0,this[Gs].forEach(r=>{r.length=this[DE](r.value,r.key),this[bp]+=r.length})),pB(this)}get lengthCalculator(){return this[DE]}get length(){return this[bp]}get itemCount(){return this[Gs].length}rforEach(e,r){r=r||this;for(let s=this[Gs].tail;s!==null;){let a=s.prev;ase(this,e,s,r),s=a}}forEach(e,r){r=r||this;for(let s=this[Gs].head;s!==null;){let a=s.next;ase(this,e,s,r),s=a}}keys(){return this[Gs].toArray().map(e=>e.key)}values(){return this[Gs].toArray().map(e=>e.value)}reset(){this[Dp]&&this[Gs]&&this[Gs].length&&this[Gs].forEach(e=>this[Dp](e.key,e.value)),this[_u]=new Map,this[Gs]=new _$e,this[bp]=0}dump(){return this[Gs].map(e=>hk(this,e)?!1:{k:e.key,v:e.value,e:e.now+(e.maxAge||0)}).toArray().filter(e=>e)}dumpLru(){return this[Gs]}set(e,r,s){if(s=s||this[jd],s&&typeof s!="number")throw new TypeError("maxAge must be a number");let a=s?Date.now():0,n=this[DE](r,e);if(this[_u].has(e)){if(n>this[Hd])return bE(this,this[_u].get(e)),!1;let p=this[_u].get(e).value;return this[Dp]&&(this[ose]||this[Dp](e,p.value)),p.now=a,p.maxAge=s,p.value=r,this[bp]+=n-p.length,p.length=n,this.get(e),pB(this),!0}let c=new VU(e,r,n,a,s);return c.length>this[Hd]?(this[Dp]&&this[Dp](e,r),!1):(this[bp]+=c.length,this[Gs].unshift(c),this[_u].set(e,this[Gs].head),pB(this),!0)}has(e){if(!this[_u].has(e))return!1;let r=this[_u].get(e).value;return!hk(this,r)}get(e){return WU(this,e,!0)}peek(e){return WU(this,e,!1)}pop(){let e=this[Gs].tail;return e?(bE(this,e),e.value):null}del(e){bE(this,this[_u].get(e))}load(e){this.reset();let r=Date.now();for(let s=e.length-1;s>=0;s--){let a=e[s],n=a.e||0;if(n===0)this.set(a.k,a.v);else{let c=n-r;c>0&&this.set(a.k,a.v,c)}}}prune(){this[_u].forEach((e,r)=>WU(this,r,!1))}},WU=(t,e,r)=>{let s=t[_u].get(e);if(s){let a=s.value;if(hk(t,a)){if(bE(t,s),!t[hB])return}else r&&(t[lse]&&(s.value.now=Date.now()),t[Gs].unshiftNode(s));return a.value}},hk=(t,e)=>{if(!e||!e.maxAge&&!t[jd])return!1;let r=Date.now()-e.now;return e.maxAge?r>e.maxAge:t[jd]&&r>t[jd]},pB=t=>{if(t[bp]>t[Hd])for(let e=t[Gs].tail;t[bp]>t[Hd]&&e!==null;){let r=e.prev;bE(t,e),e=r}},bE=(t,e)=>{if(e){let r=e.value;t[Dp]&&t[Dp](r.key,r.value),t[bp]-=r.length,t[_u].delete(r.key),t[Gs].removeNode(e)}},VU=class{constructor(e,r,s,a,n){this.key=e,this.value=r,this.length=s,this.now=a,this.maxAge=n||0}},ase=(t,e,r,s)=>{let a=r.value;hk(t,a)&&(bE(t,r),t[hB]||(a=void 0)),a&&e.call(s,a.value,a.key,t)};cse.exports=YU});var Sc=L((gYt,hse)=>{var KU=class t{constructor(e,r){if(r=H$e(r),e instanceof t)return e.loose===!!r.loose&&e.includePrerelease===!!r.includePrerelease?e:new t(e.raw,r);if(e instanceof JU)return this.raw=e.value,this.set=[[e]],this.format(),this;if(this.options=r,this.loose=!!r.loose,this.includePrerelease=!!r.includePrerelease,this.raw=e.trim().split(/\s+/).join(" "),this.set=this.raw.split("||").map(s=>this.parseRange(s.trim())).filter(s=>s.length),!this.set.length)throw new TypeError(`Invalid SemVer Range: ${this.raw}`);if(this.set.length>1){let s=this.set[0];if(this.set=this.set.filter(a=>!Ase(a[0])),this.set.length===0)this.set=[s];else if(this.set.length>1){for(let a of this.set)if(a.length===1&&K$e(a[0])){this.set=[a];break}}}this.format()}format(){return this.range=this.set.map(e=>e.join(" ").trim()).join("||").trim(),this.range}toString(){return this.range}parseRange(e){let s=((this.options.includePrerelease&&Y$e)|(this.options.loose&&V$e))+":"+e,a=fse.get(s);if(a)return a;let n=this.options.loose,c=n?ol[Ba.HYPHENRANGELOOSE]:ol[Ba.HYPHENRANGE];e=e.replace(c,iet(this.options.includePrerelease)),vi("hyphen replace",e),e=e.replace(ol[Ba.COMPARATORTRIM],q$e),vi("comparator trim",e),e=e.replace(ol[Ba.TILDETRIM],G$e),vi("tilde trim",e),e=e.replace(ol[Ba.CARETTRIM],W$e),vi("caret trim",e);let f=e.split(" ").map(C=>J$e(C,this.options)).join(" ").split(/\s+/).map(C=>net(C,this.options));n&&(f=f.filter(C=>(vi("loose invalid filter",C,this.options),!!C.match(ol[Ba.COMPARATORLOOSE])))),vi("range list",f);let p=new Map,h=f.map(C=>new JU(C,this.options));for(let C of h){if(Ase(C))return[C];p.set(C.value,C)}p.size>1&&p.has("")&&p.delete("");let E=[...p.values()];return fse.set(s,E),E}intersects(e,r){if(!(e instanceof t))throw new TypeError("a Range is required");return this.set.some(s=>pse(s,r)&&e.set.some(a=>pse(a,r)&&s.every(n=>a.every(c=>n.intersects(c,r)))))}test(e){if(!e)return!1;if(typeof e=="string")try{e=new j$e(e,this.options)}catch{return!1}for(let r=0;rt.value==="<0.0.0-0",K$e=t=>t.value==="",pse=(t,e)=>{let r=!0,s=t.slice(),a=s.pop();for(;r&&s.length;)r=s.every(n=>a.intersects(n,e)),a=s.pop();return r},J$e=(t,e)=>(vi("comp",t,e),t=X$e(t,e),vi("caret",t),t=z$e(t,e),vi("tildes",t),t=eet(t,e),vi("xrange",t),t=ret(t,e),vi("stars",t),t),va=t=>!t||t.toLowerCase()==="x"||t==="*",z$e=(t,e)=>t.trim().split(/\s+/).map(r=>Z$e(r,e)).join(" "),Z$e=(t,e)=>{let r=e.loose?ol[Ba.TILDELOOSE]:ol[Ba.TILDE];return t.replace(r,(s,a,n,c,f)=>{vi("tilde",t,s,a,n,c,f);let p;return va(a)?p="":va(n)?p=`>=${a}.0.0 <${+a+1}.0.0-0`:va(c)?p=`>=${a}.${n}.0 <${a}.${+n+1}.0-0`:f?(vi("replaceTilde pr",f),p=`>=${a}.${n}.${c}-${f} <${a}.${+n+1}.0-0`):p=`>=${a}.${n}.${c} <${a}.${+n+1}.0-0`,vi("tilde return",p),p})},X$e=(t,e)=>t.trim().split(/\s+/).map(r=>$$e(r,e)).join(" "),$$e=(t,e)=>{vi("caret",t,e);let r=e.loose?ol[Ba.CARETLOOSE]:ol[Ba.CARET],s=e.includePrerelease?"-0":"";return t.replace(r,(a,n,c,f,p)=>{vi("caret",t,a,n,c,f,p);let h;return va(n)?h="":va(c)?h=`>=${n}.0.0${s} <${+n+1}.0.0-0`:va(f)?n==="0"?h=`>=${n}.${c}.0${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.0${s} <${+n+1}.0.0-0`:p?(vi("replaceCaret pr",p),n==="0"?c==="0"?h=`>=${n}.${c}.${f}-${p} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}-${p} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f}-${p} <${+n+1}.0.0-0`):(vi("no pr"),n==="0"?c==="0"?h=`>=${n}.${c}.${f}${s} <${n}.${c}.${+f+1}-0`:h=`>=${n}.${c}.${f}${s} <${n}.${+c+1}.0-0`:h=`>=${n}.${c}.${f} <${+n+1}.0.0-0`),vi("caret return",h),h})},eet=(t,e)=>(vi("replaceXRanges",t,e),t.split(/\s+/).map(r=>tet(r,e)).join(" ")),tet=(t,e)=>{t=t.trim();let r=e.loose?ol[Ba.XRANGELOOSE]:ol[Ba.XRANGE];return t.replace(r,(s,a,n,c,f,p)=>{vi("xRange",t,s,a,n,c,f,p);let h=va(n),E=h||va(c),C=E||va(f),S=C;return a==="="&&S&&(a=""),p=e.includePrerelease?"-0":"",h?a===">"||a==="<"?s="<0.0.0-0":s="*":a&&S?(E&&(c=0),f=0,a===">"?(a=">=",E?(n=+n+1,c=0,f=0):(c=+c+1,f=0)):a==="<="&&(a="<",E?n=+n+1:c=+c+1),a==="<"&&(p="-0"),s=`${a+n}.${c}.${f}${p}`):E?s=`>=${n}.0.0${p} <${+n+1}.0.0-0`:C&&(s=`>=${n}.${c}.0${p} <${n}.${+c+1}.0-0`),vi("xRange return",s),s})},ret=(t,e)=>(vi("replaceStars",t,e),t.trim().replace(ol[Ba.STAR],"")),net=(t,e)=>(vi("replaceGTE0",t,e),t.trim().replace(ol[e.includePrerelease?Ba.GTE0PRE:Ba.GTE0],"")),iet=t=>(e,r,s,a,n,c,f,p,h,E,C,S,P)=>(va(s)?r="":va(a)?r=`>=${s}.0.0${t?"-0":""}`:va(n)?r=`>=${s}.${a}.0${t?"-0":""}`:c?r=`>=${r}`:r=`>=${r}${t?"-0":""}`,va(h)?p="":va(E)?p=`<${+h+1}.0.0-0`:va(C)?p=`<${h}.${+E+1}.0-0`:S?p=`<=${h}.${E}.${C}-${S}`:t?p=`<${h}.${E}.${+C+1}-0`:p=`<=${p}`,`${r} ${p}`.trim()),set=(t,e,r)=>{for(let s=0;s0){let a=t[s].semver;if(a.major===e.major&&a.minor===e.minor&&a.patch===e.patch)return!0}return!1}return!0}});var gB=L((dYt,Ise)=>{var dB=Symbol("SemVer ANY"),XU=class t{static get ANY(){return dB}constructor(e,r){if(r=gse(r),e instanceof t){if(e.loose===!!r.loose)return e;e=e.value}e=e.trim().split(/\s+/).join(" "),ZU("comparator",e,r),this.options=r,this.loose=!!r.loose,this.parse(e),this.semver===dB?this.value="":this.value=this.operator+this.semver.version,ZU("comp",this)}parse(e){let r=this.options.loose?dse[mse.COMPARATORLOOSE]:dse[mse.COMPARATOR],s=e.match(r);if(!s)throw new TypeError(`Invalid comparator: ${e}`);this.operator=s[1]!==void 0?s[1]:"",this.operator==="="&&(this.operator=""),s[2]?this.semver=new yse(s[2],this.options.loose):this.semver=dB}toString(){return this.value}test(e){if(ZU("Comparator.test",e,this.options.loose),this.semver===dB||e===dB)return!0;if(typeof e=="string")try{e=new yse(e,this.options)}catch{return!1}return zU(e,this.operator,this.semver,this.options)}intersects(e,r){if(!(e instanceof t))throw new TypeError("a Comparator is required");return this.operator===""?this.value===""?!0:new Ese(e.value,r).test(this.value):e.operator===""?e.value===""?!0:new Ese(this.value,r).test(e.semver):(r=gse(r),r.includePrerelease&&(this.value==="<0.0.0-0"||e.value==="<0.0.0-0")||!r.includePrerelease&&(this.value.startsWith("<0.0.0")||e.value.startsWith("<0.0.0"))?!1:!!(this.operator.startsWith(">")&&e.operator.startsWith(">")||this.operator.startsWith("<")&&e.operator.startsWith("<")||this.semver.version===e.semver.version&&this.operator.includes("=")&&e.operator.includes("=")||zU(this.semver,"<",e.semver,r)&&this.operator.startsWith(">")&&e.operator.startsWith("<")||zU(this.semver,">",e.semver,r)&&this.operator.startsWith("<")&&e.operator.startsWith(">")))}};Ise.exports=XU;var gse=ik(),{safeRe:dse,t:mse}=vE(),zU=qU(),ZU=fB(),yse=Go(),Ese=Sc()});var mB=L((mYt,Cse)=>{var oet=Sc(),aet=(t,e,r)=>{try{e=new oet(e,r)}catch{return!1}return e.test(t)};Cse.exports=aet});var Bse=L((yYt,wse)=>{var cet=Sc(),uet=(t,e)=>new cet(t,e).set.map(r=>r.map(s=>s.value).join(" ").trim().split(" "));wse.exports=uet});var Sse=L((EYt,vse)=>{var fet=Go(),Aet=Sc(),pet=(t,e,r)=>{let s=null,a=null,n=null;try{n=new Aet(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===-1)&&(s=c,a=new fet(s,r))}),s};vse.exports=pet});var bse=L((IYt,Dse)=>{var het=Go(),get=Sc(),det=(t,e,r)=>{let s=null,a=null,n=null;try{n=new get(e,r)}catch{return null}return t.forEach(c=>{n.test(c)&&(!s||a.compare(c)===1)&&(s=c,a=new het(s,r))}),s};Dse.exports=det});var kse=L((CYt,xse)=>{var $U=Go(),met=Sc(),Pse=AB(),yet=(t,e)=>{t=new met(t,e);let r=new $U("0.0.0");if(t.test(r)||(r=new $U("0.0.0-0"),t.test(r)))return r;r=null;for(let s=0;s{let f=new $U(c.semver.version);switch(c.operator){case">":f.prerelease.length===0?f.patch++:f.prerelease.push(0),f.raw=f.format();case"":case">=":(!n||Pse(f,n))&&(n=f);break;case"<":case"<=":break;default:throw new Error(`Unexpected operation: ${c.operator}`)}}),n&&(!r||Pse(r,n))&&(r=n)}return r&&t.test(r)?r:null};xse.exports=yet});var Tse=L((wYt,Qse)=>{var Eet=Sc(),Iet=(t,e)=>{try{return new Eet(t,e).range||"*"}catch{return null}};Qse.exports=Iet});var gk=L((BYt,Ose)=>{var Cet=Go(),Nse=gB(),{ANY:wet}=Nse,Bet=Sc(),vet=mB(),Rse=AB(),Fse=lk(),Det=uk(),bet=ck(),Pet=(t,e,r,s)=>{t=new Cet(t,s),e=new Bet(e,s);let a,n,c,f,p;switch(r){case">":a=Rse,n=Det,c=Fse,f=">",p=">=";break;case"<":a=Fse,n=bet,c=Rse,f="<",p="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(vet(t,e,s))return!1;for(let h=0;h{P.semver===wet&&(P=new Nse(">=0.0.0")),C=C||P,S=S||P,a(P.semver,C.semver,s)?C=P:c(P.semver,S.semver,s)&&(S=P)}),C.operator===f||C.operator===p||(!S.operator||S.operator===f)&&n(t,S.semver))return!1;if(S.operator===p&&c(t,S.semver))return!1}return!0};Ose.exports=Pet});var Mse=L((vYt,Lse)=>{var xet=gk(),ket=(t,e,r)=>xet(t,e,">",r);Lse.exports=ket});var Use=L((SYt,_se)=>{var Qet=gk(),Tet=(t,e,r)=>Qet(t,e,"<",r);_se.exports=Tet});var qse=L((DYt,jse)=>{var Hse=Sc(),Ret=(t,e,r)=>(t=new Hse(t,r),e=new Hse(e,r),t.intersects(e,r));jse.exports=Ret});var Wse=L((bYt,Gse)=>{var Fet=mB(),Net=vc();Gse.exports=(t,e,r)=>{let s=[],a=null,n=null,c=t.sort((E,C)=>Net(E,C,r));for(let E of c)Fet(E,e,r)?(n=E,a||(a=E)):(n&&s.push([a,n]),n=null,a=null);a&&s.push([a,null]);let f=[];for(let[E,C]of s)E===C?f.push(E):!C&&E===c[0]?f.push("*"):C?E===c[0]?f.push(`<=${C}`):f.push(`${E} - ${C}`):f.push(`>=${E}`);let p=f.join(" || "),h=typeof e.raw=="string"?e.raw:String(e);return p.length{var Yse=Sc(),t4=gB(),{ANY:e4}=t4,yB=mB(),r4=vc(),Oet=(t,e,r={})=>{if(t===e)return!0;t=new Yse(t,r),e=new Yse(e,r);let s=!1;e:for(let a of t.set){for(let n of e.set){let c=Met(a,n,r);if(s=s||c!==null,c)continue e}if(s)return!1}return!0},Let=[new t4(">=0.0.0-0")],Vse=[new t4(">=0.0.0")],Met=(t,e,r)=>{if(t===e)return!0;if(t.length===1&&t[0].semver===e4){if(e.length===1&&e[0].semver===e4)return!0;r.includePrerelease?t=Let:t=Vse}if(e.length===1&&e[0].semver===e4){if(r.includePrerelease)return!0;e=Vse}let s=new Set,a,n;for(let P of t)P.operator===">"||P.operator===">="?a=Kse(a,P,r):P.operator==="<"||P.operator==="<="?n=Jse(n,P,r):s.add(P.semver);if(s.size>1)return null;let c;if(a&&n){if(c=r4(a.semver,n.semver,r),c>0)return null;if(c===0&&(a.operator!==">="||n.operator!=="<="))return null}for(let P of s){if(a&&!yB(P,String(a),r)||n&&!yB(P,String(n),r))return null;for(let I of e)if(!yB(P,String(I),r))return!1;return!0}let f,p,h,E,C=n&&!r.includePrerelease&&n.semver.prerelease.length?n.semver:!1,S=a&&!r.includePrerelease&&a.semver.prerelease.length?a.semver:!1;C&&C.prerelease.length===1&&n.operator==="<"&&C.prerelease[0]===0&&(C=!1);for(let P of e){if(E=E||P.operator===">"||P.operator===">=",h=h||P.operator==="<"||P.operator==="<=",a){if(S&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===S.major&&P.semver.minor===S.minor&&P.semver.patch===S.patch&&(S=!1),P.operator===">"||P.operator===">="){if(f=Kse(a,P,r),f===P&&f!==a)return!1}else if(a.operator===">="&&!yB(a.semver,String(P),r))return!1}if(n){if(C&&P.semver.prerelease&&P.semver.prerelease.length&&P.semver.major===C.major&&P.semver.minor===C.minor&&P.semver.patch===C.patch&&(C=!1),P.operator==="<"||P.operator==="<="){if(p=Jse(n,P,r),p===P&&p!==n)return!1}else if(n.operator==="<="&&!yB(n.semver,String(P),r))return!1}if(!P.operator&&(n||a)&&c!==0)return!1}return!(a&&h&&!n&&c!==0||n&&E&&!a&&c!==0||S||C)},Kse=(t,e,r)=>{if(!t)return e;let s=r4(t.semver,e.semver,r);return s>0?t:s<0||e.operator===">"&&t.operator===">="?e:t},Jse=(t,e,r)=>{if(!t)return e;let s=r4(t.semver,e.semver,r);return s<0?t:s>0||e.operator==="<"&&t.operator==="<="?e:t};zse.exports=Oet});var fi=L((xYt,eoe)=>{var n4=vE(),Xse=uB(),_et=Go(),$se=_U(),Uet=_d(),Het=Eie(),jet=Cie(),qet=vie(),Get=bie(),Wet=xie(),Yet=Qie(),Vet=Rie(),Ket=Nie(),Jet=vc(),zet=_ie(),Zet=Hie(),Xet=ak(),$et=Wie(),ett=Vie(),ttt=AB(),rtt=lk(),ntt=HU(),itt=jU(),stt=ck(),ott=uk(),att=qU(),ltt=rse(),ctt=gB(),utt=Sc(),ftt=mB(),Att=Bse(),ptt=Sse(),htt=bse(),gtt=kse(),dtt=Tse(),mtt=gk(),ytt=Mse(),Ett=Use(),Itt=qse(),Ctt=Wse(),wtt=Zse();eoe.exports={parse:Uet,valid:Het,clean:jet,inc:qet,diff:Get,major:Wet,minor:Yet,patch:Vet,prerelease:Ket,compare:Jet,rcompare:zet,compareLoose:Zet,compareBuild:Xet,sort:$et,rsort:ett,gt:ttt,lt:rtt,eq:ntt,neq:itt,gte:stt,lte:ott,cmp:att,coerce:ltt,Comparator:ctt,Range:utt,satisfies:ftt,toComparators:Att,maxSatisfying:ptt,minSatisfying:htt,minVersion:gtt,validRange:dtt,outside:mtt,gtr:ytt,ltr:Ett,intersects:Itt,simplifyRange:Ctt,subset:wtt,SemVer:_et,re:n4.re,src:n4.src,tokens:n4.t,SEMVER_SPEC_VERSION:Xse.SEMVER_SPEC_VERSION,RELEASE_TYPES:Xse.RELEASE_TYPES,compareIdentifiers:$se.compareIdentifiers,rcompareIdentifiers:$se.rcompareIdentifiers}});var roe=L((kYt,toe)=>{"use strict";function Btt(t,e){function r(){this.constructor=t}r.prototype=e.prototype,t.prototype=new r}function qd(t,e,r,s){this.message=t,this.expected=e,this.found=r,this.location=s,this.name="SyntaxError",typeof Error.captureStackTrace=="function"&&Error.captureStackTrace(this,qd)}Btt(qd,Error);qd.buildMessage=function(t,e){var r={literal:function(h){return'"'+a(h.text)+'"'},class:function(h){var E="",C;for(C=0;C0){for(C=1,S=1;C{switch(Te[1]){case"|":return xe|Te[3];case"&":return xe&Te[3];case"^":return xe^Te[3]}},$)},S="!",P=Fe("!",!1),I=function($){return!$},R="(",N=Fe("(",!1),U=")",W=Fe(")",!1),te=function($){return $},ie=/^[^ \t\n\r()!|&\^]/,Ae=Ne([" "," ",` +`,"\r","(",")","!","|","&","^"],!0,!1),ce=function($){return e.queryPattern.test($)},me=function($){return e.checkFn($)},pe=ke("whitespace"),Be=/^[ \t\n\r]/,Ce=Ne([" "," ",` +`,"\r"],!1,!1),g=0,we=0,Ee=[{line:1,column:1}],fe=0,se=[],X=0,De;if("startRule"in e){if(!(e.startRule in s))throw new Error(`Can't start parsing from rule "`+e.startRule+'".');a=s[e.startRule]}function Re(){return t.substring(we,g)}function gt(){return _e(we,g)}function j($,oe){throw oe=oe!==void 0?oe:_e(we,g),b([ke($)],t.substring(we,g),oe)}function rt($,oe){throw oe=oe!==void 0?oe:_e(we,g),w($,oe)}function Fe($,oe){return{type:"literal",text:$,ignoreCase:oe}}function Ne($,oe,xe){return{type:"class",parts:$,inverted:oe,ignoreCase:xe}}function Pe(){return{type:"any"}}function Ye(){return{type:"end"}}function ke($){return{type:"other",description:$}}function it($){var oe=Ee[$],xe;if(oe)return oe;for(xe=$-1;!Ee[xe];)xe--;for(oe=Ee[xe],oe={line:oe.line,column:oe.column};xe<$;)t.charCodeAt(xe)===10?(oe.line++,oe.column=1):oe.column++,xe++;return Ee[$]=oe,oe}function _e($,oe){var xe=it($),Te=it(oe);return{start:{offset:$,line:xe.line,column:xe.column},end:{offset:oe,line:Te.line,column:Te.column}}}function x($){gfe&&(fe=g,se=[]),se.push($))}function w($,oe){return new qd($,null,null,oe)}function b($,oe,xe){return new qd(qd.buildMessage($,oe),$,oe,xe)}function y(){var $,oe,xe,Te,lt,Et,qt,ir;if($=g,oe=F(),oe!==r){for(xe=[],Te=g,lt=Z(),lt!==r?(t.charCodeAt(g)===124?(Et=n,g++):(Et=r,X===0&&x(c)),Et===r&&(t.charCodeAt(g)===38?(Et=f,g++):(Et=r,X===0&&x(p)),Et===r&&(t.charCodeAt(g)===94?(Et=h,g++):(Et=r,X===0&&x(E)))),Et!==r?(qt=Z(),qt!==r?(ir=F(),ir!==r?(lt=[lt,Et,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);Te!==r;)xe.push(Te),Te=g,lt=Z(),lt!==r?(t.charCodeAt(g)===124?(Et=n,g++):(Et=r,X===0&&x(c)),Et===r&&(t.charCodeAt(g)===38?(Et=f,g++):(Et=r,X===0&&x(p)),Et===r&&(t.charCodeAt(g)===94?(Et=h,g++):(Et=r,X===0&&x(E)))),Et!==r?(qt=Z(),qt!==r?(ir=F(),ir!==r?(lt=[lt,Et,qt,ir],Te=lt):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r)):(g=Te,Te=r);xe!==r?(we=$,oe=C(oe,xe),$=oe):(g=$,$=r)}else g=$,$=r;return $}function F(){var $,oe,xe,Te,lt,Et;return $=g,t.charCodeAt(g)===33?(oe=S,g++):(oe=r,X===0&&x(P)),oe!==r?(xe=F(),xe!==r?(we=$,oe=I(xe),$=oe):(g=$,$=r)):(g=$,$=r),$===r&&($=g,t.charCodeAt(g)===40?(oe=R,g++):(oe=r,X===0&&x(N)),oe!==r?(xe=Z(),xe!==r?(Te=y(),Te!==r?(lt=Z(),lt!==r?(t.charCodeAt(g)===41?(Et=U,g++):(Et=r,X===0&&x(W)),Et!==r?(we=$,oe=te(Te),$=oe):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r)):(g=$,$=r),$===r&&($=z())),$}function z(){var $,oe,xe,Te,lt;if($=g,oe=Z(),oe!==r){if(xe=g,Te=[],ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,X===0&&x(Ae)),lt!==r)for(;lt!==r;)Te.push(lt),ie.test(t.charAt(g))?(lt=t.charAt(g),g++):(lt=r,X===0&&x(Ae));else Te=r;Te!==r?xe=t.substring(xe,g):xe=Te,xe!==r?(we=g,Te=ce(xe),Te?Te=void 0:Te=r,Te!==r?(we=$,oe=me(xe),$=oe):(g=$,$=r)):(g=$,$=r)}else g=$,$=r;return $}function Z(){var $,oe;for(X++,$=[],Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,X===0&&x(Ce));oe!==r;)$.push(oe),Be.test(t.charAt(g))?(oe=t.charAt(g),g++):(oe=r,X===0&&x(Ce));return X--,$===r&&(oe=r,X===0&&x(pe)),$}if(De=a(),De!==r&&g===t.length)return De;throw De!==r&&g{var{parse:Stt}=roe();dk.makeParser=(t=/[a-z]+/)=>(e,r)=>Stt(e,{queryPattern:t,checkFn:r});dk.parse=dk.makeParser()});var soe=L((TYt,ioe)=>{"use strict";ioe.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}});var i4=L((RYt,aoe)=>{var EB=soe(),ooe={};for(let t of Object.keys(EB))ooe[EB[t]]=t;var hr={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};aoe.exports=hr;for(let t of Object.keys(hr)){if(!("channels"in hr[t]))throw new Error("missing channels property: "+t);if(!("labels"in hr[t]))throw new Error("missing channel labels property: "+t);if(hr[t].labels.length!==hr[t].channels)throw new Error("channel and label counts mismatch: "+t);let{channels:e,labels:r}=hr[t];delete hr[t].channels,delete hr[t].labels,Object.defineProperty(hr[t],"channels",{value:e}),Object.defineProperty(hr[t],"labels",{value:r})}hr.rgb.hsl=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(e,r,s),n=Math.max(e,r,s),c=n-a,f,p;n===a?f=0:e===n?f=(r-s)/c:r===n?f=2+(s-e)/c:s===n&&(f=4+(e-r)/c),f=Math.min(f*60,360),f<0&&(f+=360);let h=(a+n)/2;return n===a?p=0:h<=.5?p=c/(n+a):p=c/(2-n-a),[f,p*100,h*100]};hr.rgb.hsv=function(t){let e,r,s,a,n,c=t[0]/255,f=t[1]/255,p=t[2]/255,h=Math.max(c,f,p),E=h-Math.min(c,f,p),C=function(S){return(h-S)/6/E+1/2};return E===0?(a=0,n=0):(n=E/h,e=C(c),r=C(f),s=C(p),c===h?a=s-r:f===h?a=1/3+e-s:p===h&&(a=2/3+r-e),a<0?a+=1:a>1&&(a-=1)),[a*360,n*100,h*100]};hr.rgb.hwb=function(t){let e=t[0],r=t[1],s=t[2],a=hr.rgb.hsl(t)[0],n=1/255*Math.min(e,Math.min(r,s));return s=1-1/255*Math.max(e,Math.max(r,s)),[a,n*100,s*100]};hr.rgb.cmyk=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.min(1-e,1-r,1-s),n=(1-e-a)/(1-a)||0,c=(1-r-a)/(1-a)||0,f=(1-s-a)/(1-a)||0;return[n*100,c*100,f*100,a*100]};function Dtt(t,e){return(t[0]-e[0])**2+(t[1]-e[1])**2+(t[2]-e[2])**2}hr.rgb.keyword=function(t){let e=ooe[t];if(e)return e;let r=1/0,s;for(let a of Object.keys(EB)){let n=EB[a],c=Dtt(t,n);c.04045?((e+.055)/1.055)**2.4:e/12.92,r=r>.04045?((r+.055)/1.055)**2.4:r/12.92,s=s>.04045?((s+.055)/1.055)**2.4:s/12.92;let a=e*.4124+r*.3576+s*.1805,n=e*.2126+r*.7152+s*.0722,c=e*.0193+r*.1192+s*.9505;return[a*100,n*100,c*100]};hr.rgb.lab=function(t){let e=hr.rgb.xyz(t),r=e[0],s=e[1],a=e[2];r/=95.047,s/=100,a/=108.883,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116,a=a>.008856?a**(1/3):7.787*a+16/116;let n=116*s-16,c=500*(r-s),f=200*(s-a);return[n,c,f]};hr.hsl.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a,n,c;if(r===0)return c=s*255,[c,c,c];s<.5?a=s*(1+r):a=s+r-s*r;let f=2*s-a,p=[0,0,0];for(let h=0;h<3;h++)n=e+1/3*-(h-1),n<0&&n++,n>1&&n--,6*n<1?c=f+(a-f)*6*n:2*n<1?c=a:3*n<2?c=f+(a-f)*(2/3-n)*6:c=f,p[h]=c*255;return p};hr.hsl.hsv=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=r,n=Math.max(s,.01);s*=2,r*=s<=1?s:2-s,a*=n<=1?n:2-n;let c=(s+r)/2,f=s===0?2*a/(n+a):2*r/(s+r);return[e,f*100,c*100]};hr.hsv.rgb=function(t){let e=t[0]/60,r=t[1]/100,s=t[2]/100,a=Math.floor(e)%6,n=e-Math.floor(e),c=255*s*(1-r),f=255*s*(1-r*n),p=255*s*(1-r*(1-n));switch(s*=255,a){case 0:return[s,p,c];case 1:return[f,s,c];case 2:return[c,s,p];case 3:return[c,f,s];case 4:return[p,c,s];case 5:return[s,c,f]}};hr.hsv.hsl=function(t){let e=t[0],r=t[1]/100,s=t[2]/100,a=Math.max(s,.01),n,c;c=(2-r)*s;let f=(2-r)*a;return n=r*a,n/=f<=1?f:2-f,n=n||0,c/=2,[e,n*100,c*100]};hr.hwb.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100,a=r+s,n;a>1&&(r/=a,s/=a);let c=Math.floor(6*e),f=1-s;n=6*e-c,c&1&&(n=1-n);let p=r+n*(f-r),h,E,C;switch(c){default:case 6:case 0:h=f,E=p,C=r;break;case 1:h=p,E=f,C=r;break;case 2:h=r,E=f,C=p;break;case 3:h=r,E=p,C=f;break;case 4:h=p,E=r,C=f;break;case 5:h=f,E=r,C=p;break}return[h*255,E*255,C*255]};hr.cmyk.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a=t[3]/100,n=1-Math.min(1,e*(1-a)+a),c=1-Math.min(1,r*(1-a)+a),f=1-Math.min(1,s*(1-a)+a);return[n*255,c*255,f*255]};hr.xyz.rgb=function(t){let e=t[0]/100,r=t[1]/100,s=t[2]/100,a,n,c;return a=e*3.2406+r*-1.5372+s*-.4986,n=e*-.9689+r*1.8758+s*.0415,c=e*.0557+r*-.204+s*1.057,a=a>.0031308?1.055*a**(1/2.4)-.055:a*12.92,n=n>.0031308?1.055*n**(1/2.4)-.055:n*12.92,c=c>.0031308?1.055*c**(1/2.4)-.055:c*12.92,a=Math.min(Math.max(0,a),1),n=Math.min(Math.max(0,n),1),c=Math.min(Math.max(0,c),1),[a*255,n*255,c*255]};hr.xyz.lab=function(t){let e=t[0],r=t[1],s=t[2];e/=95.047,r/=100,s/=108.883,e=e>.008856?e**(1/3):7.787*e+16/116,r=r>.008856?r**(1/3):7.787*r+16/116,s=s>.008856?s**(1/3):7.787*s+16/116;let a=116*r-16,n=500*(e-r),c=200*(r-s);return[a,n,c]};hr.lab.xyz=function(t){let e=t[0],r=t[1],s=t[2],a,n,c;n=(e+16)/116,a=r/500+n,c=n-s/200;let f=n**3,p=a**3,h=c**3;return n=f>.008856?f:(n-16/116)/7.787,a=p>.008856?p:(a-16/116)/7.787,c=h>.008856?h:(c-16/116)/7.787,a*=95.047,n*=100,c*=108.883,[a,n,c]};hr.lab.lch=function(t){let e=t[0],r=t[1],s=t[2],a;a=Math.atan2(s,r)*360/2/Math.PI,a<0&&(a+=360);let c=Math.sqrt(r*r+s*s);return[e,c,a]};hr.lch.lab=function(t){let e=t[0],r=t[1],a=t[2]/360*2*Math.PI,n=r*Math.cos(a),c=r*Math.sin(a);return[e,n,c]};hr.rgb.ansi16=function(t,e=null){let[r,s,a]=t,n=e===null?hr.rgb.hsv(t)[2]:e;if(n=Math.round(n/50),n===0)return 30;let c=30+(Math.round(a/255)<<2|Math.round(s/255)<<1|Math.round(r/255));return n===2&&(c+=60),c};hr.hsv.ansi16=function(t){return hr.rgb.ansi16(hr.hsv.rgb(t),t[2])};hr.rgb.ansi256=function(t){let e=t[0],r=t[1],s=t[2];return e===r&&r===s?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(r/255*5)+Math.round(s/255*5)};hr.ansi16.rgb=function(t){let e=t%10;if(e===0||e===7)return t>50&&(e+=3.5),e=e/10.5*255,[e,e,e];let r=(~~(t>50)+1)*.5,s=(e&1)*r*255,a=(e>>1&1)*r*255,n=(e>>2&1)*r*255;return[s,a,n]};hr.ansi256.rgb=function(t){if(t>=232){let n=(t-232)*10+8;return[n,n,n]}t-=16;let e,r=Math.floor(t/36)/5*255,s=Math.floor((e=t%36)/6)/5*255,a=e%6/5*255;return[r,s,a]};hr.rgb.hex=function(t){let r=(((Math.round(t[0])&255)<<16)+((Math.round(t[1])&255)<<8)+(Math.round(t[2])&255)).toString(16).toUpperCase();return"000000".substring(r.length)+r};hr.hex.rgb=function(t){let e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];let r=e[0];e[0].length===3&&(r=r.split("").map(f=>f+f).join(""));let s=parseInt(r,16),a=s>>16&255,n=s>>8&255,c=s&255;return[a,n,c]};hr.rgb.hcg=function(t){let e=t[0]/255,r=t[1]/255,s=t[2]/255,a=Math.max(Math.max(e,r),s),n=Math.min(Math.min(e,r),s),c=a-n,f,p;return c<1?f=n/(1-c):f=0,c<=0?p=0:a===e?p=(r-s)/c%6:a===r?p=2+(s-e)/c:p=4+(e-r)/c,p/=6,p%=1,[p*360,c*100,f*100]};hr.hsl.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=r<.5?2*e*r:2*e*(1-r),a=0;return s<1&&(a=(r-.5*s)/(1-s)),[t[0],s*100,a*100]};hr.hsv.hcg=function(t){let e=t[1]/100,r=t[2]/100,s=e*r,a=0;return s<1&&(a=(r-s)/(1-s)),[t[0],s*100,a*100]};hr.hcg.rgb=function(t){let e=t[0]/360,r=t[1]/100,s=t[2]/100;if(r===0)return[s*255,s*255,s*255];let a=[0,0,0],n=e%1*6,c=n%1,f=1-c,p=0;switch(Math.floor(n)){case 0:a[0]=1,a[1]=c,a[2]=0;break;case 1:a[0]=f,a[1]=1,a[2]=0;break;case 2:a[0]=0,a[1]=1,a[2]=c;break;case 3:a[0]=0,a[1]=f,a[2]=1;break;case 4:a[0]=c,a[1]=0,a[2]=1;break;default:a[0]=1,a[1]=0,a[2]=f}return p=(1-r)*s,[(r*a[0]+p)*255,(r*a[1]+p)*255,(r*a[2]+p)*255]};hr.hcg.hsv=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e),a=0;return s>0&&(a=e/s),[t[0],a*100,s*100]};hr.hcg.hsl=function(t){let e=t[1]/100,s=t[2]/100*(1-e)+.5*e,a=0;return s>0&&s<.5?a=e/(2*s):s>=.5&&s<1&&(a=e/(2*(1-s))),[t[0],a*100,s*100]};hr.hcg.hwb=function(t){let e=t[1]/100,r=t[2]/100,s=e+r*(1-e);return[t[0],(s-e)*100,(1-s)*100]};hr.hwb.hcg=function(t){let e=t[1]/100,s=1-t[2]/100,a=s-e,n=0;return a<1&&(n=(s-a)/(1-a)),[t[0],a*100,n*100]};hr.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]};hr.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]};hr.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]};hr.gray.hsl=function(t){return[0,0,t[0]]};hr.gray.hsv=hr.gray.hsl;hr.gray.hwb=function(t){return[0,100,t[0]]};hr.gray.cmyk=function(t){return[0,0,0,t[0]]};hr.gray.lab=function(t){return[t[0],0,0]};hr.gray.hex=function(t){let e=Math.round(t[0]/100*255)&255,s=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(s.length)+s};hr.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}});var coe=L((FYt,loe)=>{var mk=i4();function btt(){let t={},e=Object.keys(mk);for(let r=e.length,s=0;s{var s4=i4(),Qtt=coe(),PE={},Ttt=Object.keys(s4);function Rtt(t){let e=function(...r){let s=r[0];return s==null?s:(s.length>1&&(r=s),t(r))};return"conversion"in t&&(e.conversion=t.conversion),e}function Ftt(t){let e=function(...r){let s=r[0];if(s==null)return s;s.length>1&&(r=s);let a=t(r);if(typeof a=="object")for(let n=a.length,c=0;c{PE[t]={},Object.defineProperty(PE[t],"channels",{value:s4[t].channels}),Object.defineProperty(PE[t],"labels",{value:s4[t].labels});let e=Qtt(t);Object.keys(e).forEach(s=>{let a=e[s];PE[t][s]=Ftt(a),PE[t][s].raw=Rtt(a)})});uoe.exports=PE});var IB=L((OYt,doe)=>{"use strict";var Aoe=(t,e)=>(...r)=>`\x1B[${t(...r)+e}m`,poe=(t,e)=>(...r)=>{let s=t(...r);return`\x1B[${38+e};5;${s}m`},hoe=(t,e)=>(...r)=>{let s=t(...r);return`\x1B[${38+e};2;${s[0]};${s[1]};${s[2]}m`},yk=t=>t,goe=(t,e,r)=>[t,e,r],xE=(t,e,r)=>{Object.defineProperty(t,e,{get:()=>{let s=r();return Object.defineProperty(t,e,{value:s,enumerable:!0,configurable:!0}),s},enumerable:!0,configurable:!0})},o4,kE=(t,e,r,s)=>{o4===void 0&&(o4=foe());let a=s?10:0,n={};for(let[c,f]of Object.entries(o4)){let p=c==="ansi16"?"ansi":c;c===e?n[p]=t(r,a):typeof f=="object"&&(n[p]=t(f[e],a))}return n};function Ntt(){let t=new Map,e={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};e.color.gray=e.color.blackBright,e.bgColor.bgGray=e.bgColor.bgBlackBright,e.color.grey=e.color.blackBright,e.bgColor.bgGrey=e.bgColor.bgBlackBright;for(let[r,s]of Object.entries(e)){for(let[a,n]of Object.entries(s))e[a]={open:`\x1B[${n[0]}m`,close:`\x1B[${n[1]}m`},s[a]=e[a],t.set(n[0],n[1]);Object.defineProperty(e,r,{value:s,enumerable:!1})}return Object.defineProperty(e,"codes",{value:t,enumerable:!1}),e.color.close="\x1B[39m",e.bgColor.close="\x1B[49m",xE(e.color,"ansi",()=>kE(Aoe,"ansi16",yk,!1)),xE(e.color,"ansi256",()=>kE(poe,"ansi256",yk,!1)),xE(e.color,"ansi16m",()=>kE(hoe,"rgb",goe,!1)),xE(e.bgColor,"ansi",()=>kE(Aoe,"ansi16",yk,!0)),xE(e.bgColor,"ansi256",()=>kE(poe,"ansi256",yk,!0)),xE(e.bgColor,"ansi16m",()=>kE(hoe,"rgb",goe,!0)),e}Object.defineProperty(doe,"exports",{enumerable:!0,get:Ntt})});var yoe=L((LYt,moe)=>{"use strict";moe.exports=(t,e=process.argv)=>{let r=t.startsWith("-")?"":t.length===1?"-":"--",s=e.indexOf(r+t),a=e.indexOf("--");return s!==-1&&(a===-1||s{"use strict";var Ott=ye("os"),Eoe=ye("tty"),Dc=yoe(),{env:Ps}=process,f0;Dc("no-color")||Dc("no-colors")||Dc("color=false")||Dc("color=never")?f0=0:(Dc("color")||Dc("colors")||Dc("color=true")||Dc("color=always"))&&(f0=1);"FORCE_COLOR"in Ps&&(Ps.FORCE_COLOR==="true"?f0=1:Ps.FORCE_COLOR==="false"?f0=0:f0=Ps.FORCE_COLOR.length===0?1:Math.min(parseInt(Ps.FORCE_COLOR,10),3));function a4(t){return t===0?!1:{level:t,hasBasic:!0,has256:t>=2,has16m:t>=3}}function l4(t,e){if(f0===0)return 0;if(Dc("color=16m")||Dc("color=full")||Dc("color=truecolor"))return 3;if(Dc("color=256"))return 2;if(t&&!e&&f0===void 0)return 0;let r=f0||0;if(Ps.TERM==="dumb")return r;if(process.platform==="win32"){let s=Ott.release().split(".");return Number(s[0])>=10&&Number(s[2])>=10586?Number(s[2])>=14931?3:2:1}if("CI"in Ps)return["TRAVIS","CIRCLECI","APPVEYOR","GITLAB_CI"].some(s=>s in Ps)||Ps.CI_NAME==="codeship"?1:r;if("TEAMCITY_VERSION"in Ps)return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(Ps.TEAMCITY_VERSION)?1:0;if("GITHUB_ACTIONS"in Ps)return 1;if(Ps.COLORTERM==="truecolor")return 3;if("TERM_PROGRAM"in Ps){let s=parseInt((Ps.TERM_PROGRAM_VERSION||"").split(".")[0],10);switch(Ps.TERM_PROGRAM){case"iTerm.app":return s>=3?3:2;case"Apple_Terminal":return 2}}return/-256(color)?$/i.test(Ps.TERM)?2:/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(Ps.TERM)||"COLORTERM"in Ps?1:r}function Ltt(t){let e=l4(t,t&&t.isTTY);return a4(e)}Ioe.exports={supportsColor:Ltt,stdout:a4(l4(!0,Eoe.isatty(1))),stderr:a4(l4(!0,Eoe.isatty(2)))}});var woe=L((_Yt,Coe)=>{"use strict";var Mtt=(t,e,r)=>{let s=t.indexOf(e);if(s===-1)return t;let a=e.length,n=0,c="";do c+=t.substr(n,s-n)+e+r,n=s+a,s=t.indexOf(e,n);while(s!==-1);return c+=t.substr(n),c},_tt=(t,e,r,s)=>{let a=0,n="";do{let c=t[s-1]==="\r";n+=t.substr(a,(c?s-1:s)-a)+e+(c?`\r +`:` +`)+r,a=s+1,s=t.indexOf(` +`,a)}while(s!==-1);return n+=t.substr(a),n};Coe.exports={stringReplaceAll:Mtt,stringEncaseCRLFWithFirstIndex:_tt}});var boe=L((UYt,Doe)=>{"use strict";var Utt=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,Boe=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,Htt=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,jtt=/\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.)|([^\\])/gi,qtt=new Map([["n",` +`],["r","\r"],["t"," "],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e","\x1B"],["a","\x07"]]);function Soe(t){let e=t[0]==="u",r=t[1]==="{";return e&&!r&&t.length===5||t[0]==="x"&&t.length===3?String.fromCharCode(parseInt(t.slice(1),16)):e&&r?String.fromCodePoint(parseInt(t.slice(2,-1),16)):qtt.get(t)||t}function Gtt(t,e){let r=[],s=e.trim().split(/\s*,\s*/g),a;for(let n of s){let c=Number(n);if(!Number.isNaN(c))r.push(c);else if(a=n.match(Htt))r.push(a[2].replace(jtt,(f,p,h)=>p?Soe(p):h));else throw new Error(`Invalid Chalk template style argument: ${n} (in style '${t}')`)}return r}function Wtt(t){Boe.lastIndex=0;let e=[],r;for(;(r=Boe.exec(t))!==null;){let s=r[1];if(r[2]){let a=Gtt(s,r[2]);e.push([s].concat(a))}else e.push([s])}return e}function voe(t,e){let r={};for(let a of e)for(let n of a.styles)r[n[0]]=a.inverse?null:n.slice(1);let s=t;for(let[a,n]of Object.entries(r))if(Array.isArray(n)){if(!(a in s))throw new Error(`Unknown Chalk style: ${a}`);s=n.length>0?s[a](...n):s[a]}return s}Doe.exports=(t,e)=>{let r=[],s=[],a=[];if(e.replace(Utt,(n,c,f,p,h,E)=>{if(c)a.push(Soe(c));else if(p){let C=a.join("");a=[],s.push(r.length===0?C:voe(t,r)(C)),r.push({inverse:f,styles:Wtt(p)})}else if(h){if(r.length===0)throw new Error("Found extraneous } in Chalk template literal");s.push(voe(t,r)(a.join(""))),a=[],r.pop()}else a.push(E)}),s.push(a.join("")),r.length>0){let n=`Chalk template literal is missing ${r.length} closing bracket${r.length===1?"":"s"} (\`}\`)`;throw new Error(n)}return s.join("")}});var g4=L((HYt,Qoe)=>{"use strict";var CB=IB(),{stdout:f4,stderr:A4}=c4(),{stringReplaceAll:Ytt,stringEncaseCRLFWithFirstIndex:Vtt}=woe(),Poe=["ansi","ansi","ansi256","ansi16m"],QE=Object.create(null),Ktt=(t,e={})=>{if(e.level>3||e.level<0)throw new Error("The `level` option should be an integer from 0 to 3");let r=f4?f4.level:0;t.level=e.level===void 0?r:e.level},p4=class{constructor(e){return xoe(e)}},xoe=t=>{let e={};return Ktt(e,t),e.template=(...r)=>Ztt(e.template,...r),Object.setPrototypeOf(e,Ek.prototype),Object.setPrototypeOf(e.template,e),e.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},e.template.Instance=p4,e.template};function Ek(t){return xoe(t)}for(let[t,e]of Object.entries(CB))QE[t]={get(){let r=Ik(this,h4(e.open,e.close,this._styler),this._isEmpty);return Object.defineProperty(this,t,{value:r}),r}};QE.visible={get(){let t=Ik(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:t}),t}};var koe=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(let t of koe)QE[t]={get(){let{level:e}=this;return function(...r){let s=h4(CB.color[Poe[e]][t](...r),CB.color.close,this._styler);return Ik(this,s,this._isEmpty)}}};for(let t of koe){let e="bg"+t[0].toUpperCase()+t.slice(1);QE[e]={get(){let{level:r}=this;return function(...s){let a=h4(CB.bgColor[Poe[r]][t](...s),CB.bgColor.close,this._styler);return Ik(this,a,this._isEmpty)}}}}var Jtt=Object.defineProperties(()=>{},{...QE,level:{enumerable:!0,get(){return this._generator.level},set(t){this._generator.level=t}}}),h4=(t,e,r)=>{let s,a;return r===void 0?(s=t,a=e):(s=r.openAll+t,a=e+r.closeAll),{open:t,close:e,openAll:s,closeAll:a,parent:r}},Ik=(t,e,r)=>{let s=(...a)=>ztt(s,a.length===1?""+a[0]:a.join(" "));return s.__proto__=Jtt,s._generator=t,s._styler=e,s._isEmpty=r,s},ztt=(t,e)=>{if(t.level<=0||!e)return t._isEmpty?"":e;let r=t._styler;if(r===void 0)return e;let{openAll:s,closeAll:a}=r;if(e.indexOf("\x1B")!==-1)for(;r!==void 0;)e=Ytt(e,r.close,r.open),r=r.parent;let n=e.indexOf(` +`);return n!==-1&&(e=Vtt(e,a,s,n)),s+e+a},u4,Ztt=(t,...e)=>{let[r]=e;if(!Array.isArray(r))return e.join(" ");let s=e.slice(1),a=[r.raw[0]];for(let n=1;n{"use strict";bc.isInteger=t=>typeof t=="number"?Number.isInteger(t):typeof t=="string"&&t.trim()!==""?Number.isInteger(Number(t)):!1;bc.find=(t,e)=>t.nodes.find(r=>r.type===e);bc.exceedsLimit=(t,e,r=1,s)=>s===!1||!bc.isInteger(t)||!bc.isInteger(e)?!1:(Number(e)-Number(t))/Number(r)>=s;bc.escapeNode=(t,e=0,r)=>{let s=t.nodes[e];s&&(r&&s.type===r||s.type==="open"||s.type==="close")&&s.escaped!==!0&&(s.value="\\"+s.value,s.escaped=!0)};bc.encloseBrace=t=>t.type!=="brace"||t.commas>>0+t.ranges>>0?!1:(t.invalid=!0,!0);bc.isInvalidBrace=t=>t.type!=="brace"?!1:t.invalid===!0||t.dollar?!0:!(t.commas>>0+t.ranges>>0)||t.open!==!0||t.close!==!0?(t.invalid=!0,!0):!1;bc.isOpenOrClose=t=>t.type==="open"||t.type==="close"?!0:t.open===!0||t.close===!0;bc.reduce=t=>t.reduce((e,r)=>(r.type==="text"&&e.push(r.value),r.type==="range"&&(r.type="text"),e),[]);bc.flatten=(...t)=>{let e=[],r=s=>{for(let a=0;a{"use strict";var Toe=Ck();Roe.exports=(t,e={})=>{let r=(s,a={})=>{let n=e.escapeInvalid&&Toe.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f="";if(s.value)return(n||c)&&Toe.isOpenOrClose(s)?"\\"+s.value:s.value;if(s.value)return s.value;if(s.nodes)for(let p of s.nodes)f+=r(p);return f};return r(t)}});var Noe=L((GYt,Foe)=>{"use strict";Foe.exports=function(t){return typeof t=="number"?t-t===0:typeof t=="string"&&t.trim()!==""?Number.isFinite?Number.isFinite(+t):isFinite(+t):!1}});var Goe=L((WYt,qoe)=>{"use strict";var Ooe=Noe(),Gd=(t,e,r)=>{if(Ooe(t)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(e===void 0||t===e)return String(t);if(Ooe(e)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let s={relaxZeros:!0,...r};typeof s.strictZeros=="boolean"&&(s.relaxZeros=s.strictZeros===!1);let a=String(s.relaxZeros),n=String(s.shorthand),c=String(s.capture),f=String(s.wrap),p=t+":"+e+"="+a+n+c+f;if(Gd.cache.hasOwnProperty(p))return Gd.cache[p].result;let h=Math.min(t,e),E=Math.max(t,e);if(Math.abs(h-E)===1){let R=t+"|"+e;return s.capture?`(${R})`:s.wrap===!1?R:`(?:${R})`}let C=joe(t)||joe(e),S={min:t,max:e,a:h,b:E},P=[],I=[];if(C&&(S.isPadded=C,S.maxLen=String(S.max).length),h<0){let R=E<0?Math.abs(E):1;I=Loe(R,Math.abs(h),S,s),h=S.a=0}return E>=0&&(P=Loe(h,E,S,s)),S.negatives=I,S.positives=P,S.result=Xtt(I,P,s),s.capture===!0?S.result=`(${S.result})`:s.wrap!==!1&&P.length+I.length>1&&(S.result=`(?:${S.result})`),Gd.cache[p]=S,S.result};function Xtt(t,e,r){let s=d4(t,e,"-",!1,r)||[],a=d4(e,t,"",!1,r)||[],n=d4(t,e,"-?",!0,r)||[];return s.concat(n).concat(a).join("|")}function $tt(t,e){let r=1,s=1,a=_oe(t,r),n=new Set([e]);for(;t<=a&&a<=e;)n.add(a),r+=1,a=_oe(t,r);for(a=Uoe(e+1,s)-1;t1&&f.count.pop(),f.count.push(E.count[0]),f.string=f.pattern+Hoe(f.count),c=h+1;continue}r.isPadded&&(C=irt(h,r,s)),E.string=C+E.pattern+Hoe(E.count),n.push(E),c=h+1,f=E}return n}function d4(t,e,r,s,a){let n=[];for(let c of t){let{string:f}=c;!s&&!Moe(e,"string",f)&&n.push(r+f),s&&Moe(e,"string",f)&&n.push(r+f)}return n}function trt(t,e){let r=[];for(let s=0;se?1:e>t?-1:0}function Moe(t,e,r){return t.some(s=>s[e]===r)}function _oe(t,e){return Number(String(t).slice(0,-e)+"9".repeat(e))}function Uoe(t,e){return t-t%Math.pow(10,e)}function Hoe(t){let[e=0,r=""]=t;return r||e>1?`{${e+(r?","+r:"")}}`:""}function nrt(t,e,r){return`[${t}${e-t===1?"":"-"}${e}]`}function joe(t){return/^-?(0+)\d/.test(t)}function irt(t,e,r){if(!e.isPadded)return t;let s=Math.abs(e.maxLen-String(t).length),a=r.relaxZeros!==!1;switch(s){case 0:return"";case 1:return a?"0?":"0";case 2:return a?"0{0,2}":"00";default:return a?`0{0,${s}}`:`0{${s}}`}}Gd.cache={};Gd.clearCache=()=>Gd.cache={};qoe.exports=Gd});var E4=L((YYt,Xoe)=>{"use strict";var srt=ye("util"),Voe=Goe(),Woe=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),ort=t=>e=>t===!0?Number(e):String(e),m4=t=>typeof t=="number"||typeof t=="string"&&t!=="",BB=t=>Number.isInteger(+t),y4=t=>{let e=`${t}`,r=-1;if(e[0]==="-"&&(e=e.slice(1)),e==="0")return!1;for(;e[++r]==="0";);return r>0},art=(t,e,r)=>typeof t=="string"||typeof e=="string"?!0:r.stringify===!0,lrt=(t,e,r)=>{if(e>0){let s=t[0]==="-"?"-":"";s&&(t=t.slice(1)),t=s+t.padStart(s?e-1:e,"0")}return r===!1?String(t):t},Yoe=(t,e)=>{let r=t[0]==="-"?"-":"";for(r&&(t=t.slice(1),e--);t.length{t.negatives.sort((c,f)=>cf?1:0),t.positives.sort((c,f)=>cf?1:0);let r=e.capture?"":"?:",s="",a="",n;return t.positives.length&&(s=t.positives.join("|")),t.negatives.length&&(a=`-(${r}${t.negatives.join("|")})`),s&&a?n=`${s}|${a}`:n=s||a,e.wrap?`(${r}${n})`:n},Koe=(t,e,r,s)=>{if(r)return Voe(t,e,{wrap:!1,...s});let a=String.fromCharCode(t);if(t===e)return a;let n=String.fromCharCode(e);return`[${a}-${n}]`},Joe=(t,e,r)=>{if(Array.isArray(t)){let s=r.wrap===!0,a=r.capture?"":"?:";return s?`(${a}${t.join("|")})`:t.join("|")}return Voe(t,e,r)},zoe=(...t)=>new RangeError("Invalid range arguments: "+srt.inspect(...t)),Zoe=(t,e,r)=>{if(r.strictRanges===!0)throw zoe([t,e]);return[]},urt=(t,e)=>{if(e.strictRanges===!0)throw new TypeError(`Expected step "${t}" to be a number`);return[]},frt=(t,e,r=1,s={})=>{let a=Number(t),n=Number(e);if(!Number.isInteger(a)||!Number.isInteger(n)){if(s.strictRanges===!0)throw zoe([t,e]);return[]}a===0&&(a=0),n===0&&(n=0);let c=a>n,f=String(t),p=String(e),h=String(r);r=Math.max(Math.abs(r),1);let E=y4(f)||y4(p)||y4(h),C=E?Math.max(f.length,p.length,h.length):0,S=E===!1&&art(t,e,s)===!1,P=s.transform||ort(S);if(s.toRegex&&r===1)return Koe(Yoe(t,C),Yoe(e,C),!0,s);let I={negatives:[],positives:[]},R=W=>I[W<0?"negatives":"positives"].push(Math.abs(W)),N=[],U=0;for(;c?a>=n:a<=n;)s.toRegex===!0&&r>1?R(a):N.push(lrt(P(a,U),C,S)),a=c?a-r:a+r,U++;return s.toRegex===!0?r>1?crt(I,s):Joe(N,null,{wrap:!1,...s}):N},Art=(t,e,r=1,s={})=>{if(!BB(t)&&t.length>1||!BB(e)&&e.length>1)return Zoe(t,e,s);let a=s.transform||(S=>String.fromCharCode(S)),n=`${t}`.charCodeAt(0),c=`${e}`.charCodeAt(0),f=n>c,p=Math.min(n,c),h=Math.max(n,c);if(s.toRegex&&r===1)return Koe(p,h,!1,s);let E=[],C=0;for(;f?n>=c:n<=c;)E.push(a(n,C)),n=f?n-r:n+r,C++;return s.toRegex===!0?Joe(E,null,{wrap:!1,options:s}):E},Bk=(t,e,r,s={})=>{if(e==null&&m4(t))return[t];if(!m4(t)||!m4(e))return Zoe(t,e,s);if(typeof r=="function")return Bk(t,e,1,{transform:r});if(Woe(r))return Bk(t,e,0,r);let a={...s};return a.capture===!0&&(a.wrap=!0),r=r||a.step||1,BB(r)?BB(t)&&BB(e)?frt(t,e,r,a):Art(t,e,Math.max(Math.abs(r),1),a):r!=null&&!Woe(r)?urt(r,a):Bk(t,e,1,r)};Xoe.exports=Bk});var tae=L((VYt,eae)=>{"use strict";var prt=E4(),$oe=Ck(),hrt=(t,e={})=>{let r=(s,a={})=>{let n=$oe.isInvalidBrace(a),c=s.invalid===!0&&e.escapeInvalid===!0,f=n===!0||c===!0,p=e.escapeInvalid===!0?"\\":"",h="";if(s.isOpen===!0||s.isClose===!0)return p+s.value;if(s.type==="open")return f?p+s.value:"(";if(s.type==="close")return f?p+s.value:")";if(s.type==="comma")return s.prev.type==="comma"?"":f?s.value:"|";if(s.value)return s.value;if(s.nodes&&s.ranges>0){let E=$oe.reduce(s.nodes),C=prt(...E,{...e,wrap:!1,toRegex:!0});if(C.length!==0)return E.length>1&&C.length>1?`(${C})`:C}if(s.nodes)for(let E of s.nodes)h+=r(E,s);return h};return r(t)};eae.exports=hrt});var iae=L((KYt,nae)=>{"use strict";var grt=E4(),rae=wk(),TE=Ck(),Wd=(t="",e="",r=!1)=>{let s=[];if(t=[].concat(t),e=[].concat(e),!e.length)return t;if(!t.length)return r?TE.flatten(e).map(a=>`{${a}}`):e;for(let a of t)if(Array.isArray(a))for(let n of a)s.push(Wd(n,e,r));else for(let n of e)r===!0&&typeof n=="string"&&(n=`{${n}}`),s.push(Array.isArray(n)?Wd(a,n,r):a+n);return TE.flatten(s)},drt=(t,e={})=>{let r=e.rangeLimit===void 0?1e3:e.rangeLimit,s=(a,n={})=>{a.queue=[];let c=n,f=n.queue;for(;c.type!=="brace"&&c.type!=="root"&&c.parent;)c=c.parent,f=c.queue;if(a.invalid||a.dollar){f.push(Wd(f.pop(),rae(a,e)));return}if(a.type==="brace"&&a.invalid!==!0&&a.nodes.length===2){f.push(Wd(f.pop(),["{}"]));return}if(a.nodes&&a.ranges>0){let C=TE.reduce(a.nodes);if(TE.exceedsLimit(...C,e.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let S=grt(...C,e);S.length===0&&(S=rae(a,e)),f.push(Wd(f.pop(),S)),a.nodes=[];return}let p=TE.encloseBrace(a),h=a.queue,E=a;for(;E.type!=="brace"&&E.type!=="root"&&E.parent;)E=E.parent,h=E.queue;for(let C=0;C{"use strict";sae.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` +`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var fae=L((zYt,uae)=>{"use strict";var mrt=wk(),{MAX_LENGTH:aae,CHAR_BACKSLASH:I4,CHAR_BACKTICK:yrt,CHAR_COMMA:Ert,CHAR_DOT:Irt,CHAR_LEFT_PARENTHESES:Crt,CHAR_RIGHT_PARENTHESES:wrt,CHAR_LEFT_CURLY_BRACE:Brt,CHAR_RIGHT_CURLY_BRACE:vrt,CHAR_LEFT_SQUARE_BRACKET:lae,CHAR_RIGHT_SQUARE_BRACKET:cae,CHAR_DOUBLE_QUOTE:Srt,CHAR_SINGLE_QUOTE:Drt,CHAR_NO_BREAK_SPACE:brt,CHAR_ZERO_WIDTH_NOBREAK_SPACE:Prt}=oae(),xrt=(t,e={})=>{if(typeof t!="string")throw new TypeError("Expected a string");let r=e||{},s=typeof r.maxLength=="number"?Math.min(aae,r.maxLength):aae;if(t.length>s)throw new SyntaxError(`Input length (${t.length}), exceeds max characters (${s})`);let a={type:"root",input:t,nodes:[]},n=[a],c=a,f=a,p=0,h=t.length,E=0,C=0,S,P={},I=()=>t[E++],R=N=>{if(N.type==="text"&&f.type==="dot"&&(f.type="text"),f&&f.type==="text"&&N.type==="text"){f.value+=N.value;return}return c.nodes.push(N),N.parent=c,N.prev=f,f=N,N};for(R({type:"bos"});E0){if(c.ranges>0){c.ranges=0;let N=c.nodes.shift();c.nodes=[N,{type:"text",value:mrt(c)}]}R({type:"comma",value:S}),c.commas++;continue}if(S===Irt&&C>0&&c.commas===0){let N=c.nodes;if(C===0||N.length===0){R({type:"text",value:S});continue}if(f.type==="dot"){if(c.range=[],f.value+=S,f.type="range",c.nodes.length!==3&&c.nodes.length!==5){c.invalid=!0,c.ranges=0,f.type="text";continue}c.ranges++,c.args=[];continue}if(f.type==="range"){N.pop();let U=N[N.length-1];U.value+=f.value+S,f=U,c.ranges--;continue}R({type:"dot",value:S});continue}R({type:"text",value:S})}do if(c=n.pop(),c.type!=="root"){c.nodes.forEach(W=>{W.nodes||(W.type==="open"&&(W.isOpen=!0),W.type==="close"&&(W.isClose=!0),W.nodes||(W.type="text"),W.invalid=!0)});let N=n[n.length-1],U=N.nodes.indexOf(c);N.nodes.splice(U,1,...c.nodes)}while(n.length>0);return R({type:"eos"}),a};uae.exports=xrt});var hae=L((ZYt,pae)=>{"use strict";var Aae=wk(),krt=tae(),Qrt=iae(),Trt=fae(),ql=(t,e={})=>{let r=[];if(Array.isArray(t))for(let s of t){let a=ql.create(s,e);Array.isArray(a)?r.push(...a):r.push(a)}else r=[].concat(ql.create(t,e));return e&&e.expand===!0&&e.nodupes===!0&&(r=[...new Set(r)]),r};ql.parse=(t,e={})=>Trt(t,e);ql.stringify=(t,e={})=>Aae(typeof t=="string"?ql.parse(t,e):t,e);ql.compile=(t,e={})=>(typeof t=="string"&&(t=ql.parse(t,e)),krt(t,e));ql.expand=(t,e={})=>{typeof t=="string"&&(t=ql.parse(t,e));let r=Qrt(t,e);return e.noempty===!0&&(r=r.filter(Boolean)),e.nodupes===!0&&(r=[...new Set(r)]),r};ql.create=(t,e={})=>t===""||t.length<3?[t]:e.expand!==!0?ql.compile(t,e):ql.expand(t,e);pae.exports=ql});var vB=L((XYt,Eae)=>{"use strict";var Rrt=ye("path"),Kf="\\\\/",gae=`[^${Kf}]`,Pp="\\.",Frt="\\+",Nrt="\\?",vk="\\/",Ort="(?=.)",dae="[^/]",C4=`(?:${vk}|$)`,mae=`(?:^|${vk})`,w4=`${Pp}{1,2}${C4}`,Lrt=`(?!${Pp})`,Mrt=`(?!${mae}${w4})`,_rt=`(?!${Pp}{0,1}${C4})`,Urt=`(?!${w4})`,Hrt=`[^.${vk}]`,jrt=`${dae}*?`,yae={DOT_LITERAL:Pp,PLUS_LITERAL:Frt,QMARK_LITERAL:Nrt,SLASH_LITERAL:vk,ONE_CHAR:Ort,QMARK:dae,END_ANCHOR:C4,DOTS_SLASH:w4,NO_DOT:Lrt,NO_DOTS:Mrt,NO_DOT_SLASH:_rt,NO_DOTS_SLASH:Urt,QMARK_NO_DOT:Hrt,STAR:jrt,START_ANCHOR:mae},qrt={...yae,SLASH_LITERAL:`[${Kf}]`,QMARK:gae,STAR:`${gae}*?`,DOTS_SLASH:`${Pp}{1,2}(?:[${Kf}]|$)`,NO_DOT:`(?!${Pp})`,NO_DOTS:`(?!(?:^|[${Kf}])${Pp}{1,2}(?:[${Kf}]|$))`,NO_DOT_SLASH:`(?!${Pp}{0,1}(?:[${Kf}]|$))`,NO_DOTS_SLASH:`(?!${Pp}{1,2}(?:[${Kf}]|$))`,QMARK_NO_DOT:`[^.${Kf}]`,START_ANCHOR:`(?:^|[${Kf}])`,END_ANCHOR:`(?:[${Kf}]|$)`},Grt={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Eae.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:Grt,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:Rrt.sep,extglobChars(t){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${t.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(t){return t===!0?qrt:yae}}});var SB=L(al=>{"use strict";var Wrt=ye("path"),Yrt=process.platform==="win32",{REGEX_BACKSLASH:Vrt,REGEX_REMOVE_BACKSLASH:Krt,REGEX_SPECIAL_CHARS:Jrt,REGEX_SPECIAL_CHARS_GLOBAL:zrt}=vB();al.isObject=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);al.hasRegexChars=t=>Jrt.test(t);al.isRegexChar=t=>t.length===1&&al.hasRegexChars(t);al.escapeRegex=t=>t.replace(zrt,"\\$1");al.toPosixSlashes=t=>t.replace(Vrt,"/");al.removeBackslashes=t=>t.replace(Krt,e=>e==="\\"?"":e);al.supportsLookbehinds=()=>{let t=process.version.slice(1).split(".").map(Number);return t.length===3&&t[0]>=9||t[0]===8&&t[1]>=10};al.isWindows=t=>t&&typeof t.windows=="boolean"?t.windows:Yrt===!0||Wrt.sep==="\\";al.escapeLast=(t,e,r)=>{let s=t.lastIndexOf(e,r);return s===-1?t:t[s-1]==="\\"?al.escapeLast(t,e,s-1):`${t.slice(0,s)}\\${t.slice(s)}`};al.removePrefix=(t,e={})=>{let r=t;return r.startsWith("./")&&(r=r.slice(2),e.prefix="./"),r};al.wrapOutput=(t,e={},r={})=>{let s=r.contains?"":"^",a=r.contains?"":"$",n=`${s}(?:${t})${a}`;return e.negated===!0&&(n=`(?:^(?!${n}).*$)`),n}});var bae=L((eVt,Dae)=>{"use strict";var Iae=SB(),{CHAR_ASTERISK:B4,CHAR_AT:Zrt,CHAR_BACKWARD_SLASH:DB,CHAR_COMMA:Xrt,CHAR_DOT:v4,CHAR_EXCLAMATION_MARK:S4,CHAR_FORWARD_SLASH:Sae,CHAR_LEFT_CURLY_BRACE:D4,CHAR_LEFT_PARENTHESES:b4,CHAR_LEFT_SQUARE_BRACKET:$rt,CHAR_PLUS:ent,CHAR_QUESTION_MARK:Cae,CHAR_RIGHT_CURLY_BRACE:tnt,CHAR_RIGHT_PARENTHESES:wae,CHAR_RIGHT_SQUARE_BRACKET:rnt}=vB(),Bae=t=>t===Sae||t===DB,vae=t=>{t.isPrefix!==!0&&(t.depth=t.isGlobstar?1/0:1)},nnt=(t,e)=>{let r=e||{},s=t.length-1,a=r.parts===!0||r.scanToEnd===!0,n=[],c=[],f=[],p=t,h=-1,E=0,C=0,S=!1,P=!1,I=!1,R=!1,N=!1,U=!1,W=!1,te=!1,ie=!1,Ae=!1,ce=0,me,pe,Be={value:"",depth:0,isGlob:!1},Ce=()=>h>=s,g=()=>p.charCodeAt(h+1),we=()=>(me=pe,p.charCodeAt(++h));for(;h0&&(fe=p.slice(0,E),p=p.slice(E),C-=E),Ee&&I===!0&&C>0?(Ee=p.slice(0,C),se=p.slice(C)):I===!0?(Ee="",se=p):Ee=p,Ee&&Ee!==""&&Ee!=="/"&&Ee!==p&&Bae(Ee.charCodeAt(Ee.length-1))&&(Ee=Ee.slice(0,-1)),r.unescape===!0&&(se&&(se=Iae.removeBackslashes(se)),Ee&&W===!0&&(Ee=Iae.removeBackslashes(Ee)));let X={prefix:fe,input:t,start:E,base:Ee,glob:se,isBrace:S,isBracket:P,isGlob:I,isExtglob:R,isGlobstar:N,negated:te,negatedExtglob:ie};if(r.tokens===!0&&(X.maxDepth=0,Bae(pe)||c.push(Be),X.tokens=c),r.parts===!0||r.tokens===!0){let De;for(let Re=0;Re{"use strict";var Sk=vB(),Gl=SB(),{MAX_LENGTH:Dk,POSIX_REGEX_SOURCE:int,REGEX_NON_SPECIAL_CHARS:snt,REGEX_SPECIAL_CHARS_BACKREF:ont,REPLACEMENTS:Pae}=Sk,ant=(t,e)=>{if(typeof e.expandRange=="function")return e.expandRange(...t,e);t.sort();let r=`[${t.join("-")}]`;try{new RegExp(r)}catch{return t.map(a=>Gl.escapeRegex(a)).join("..")}return r},RE=(t,e)=>`Missing ${t}: "${e}" - use "\\\\${e}" to match literal characters`,P4=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");t=Pae[t]||t;let r={...e},s=typeof r.maxLength=="number"?Math.min(Dk,r.maxLength):Dk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);let n={type:"bos",value:"",output:r.prepend||""},c=[n],f=r.capture?"":"?:",p=Gl.isWindows(e),h=Sk.globChars(p),E=Sk.extglobChars(h),{DOT_LITERAL:C,PLUS_LITERAL:S,SLASH_LITERAL:P,ONE_CHAR:I,DOTS_SLASH:R,NO_DOT:N,NO_DOT_SLASH:U,NO_DOTS_SLASH:W,QMARK:te,QMARK_NO_DOT:ie,STAR:Ae,START_ANCHOR:ce}=h,me=x=>`(${f}(?:(?!${ce}${x.dot?R:C}).)*?)`,pe=r.dot?"":N,Be=r.dot?te:ie,Ce=r.bash===!0?me(r):Ae;r.capture&&(Ce=`(${Ce})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let g={input:t,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:c};t=Gl.removePrefix(t,g),a=t.length;let we=[],Ee=[],fe=[],se=n,X,De=()=>g.index===a-1,Re=g.peek=(x=1)=>t[g.index+x],gt=g.advance=()=>t[++g.index]||"",j=()=>t.slice(g.index+1),rt=(x="",w=0)=>{g.consumed+=x,g.index+=w},Fe=x=>{g.output+=x.output!=null?x.output:x.value,rt(x.value)},Ne=()=>{let x=1;for(;Re()==="!"&&(Re(2)!=="("||Re(3)==="?");)gt(),g.start++,x++;return x%2===0?!1:(g.negated=!0,g.start++,!0)},Pe=x=>{g[x]++,fe.push(x)},Ye=x=>{g[x]--,fe.pop()},ke=x=>{if(se.type==="globstar"){let w=g.braces>0&&(x.type==="comma"||x.type==="brace"),b=x.extglob===!0||we.length&&(x.type==="pipe"||x.type==="paren");x.type!=="slash"&&x.type!=="paren"&&!w&&!b&&(g.output=g.output.slice(0,-se.output.length),se.type="star",se.value="*",se.output=Ce,g.output+=se.output)}if(we.length&&x.type!=="paren"&&(we[we.length-1].inner+=x.value),(x.value||x.output)&&Fe(x),se&&se.type==="text"&&x.type==="text"){se.value+=x.value,se.output=(se.output||"")+x.value;return}x.prev=se,c.push(x),se=x},it=(x,w)=>{let b={...E[w],conditions:1,inner:""};b.prev=se,b.parens=g.parens,b.output=g.output;let y=(r.capture?"(":"")+b.open;Pe("parens"),ke({type:x,value:w,output:g.output?"":I}),ke({type:"paren",extglob:!0,value:gt(),output:y}),we.push(b)},_e=x=>{let w=x.close+(r.capture?")":""),b;if(x.type==="negate"){let y=Ce;if(x.inner&&x.inner.length>1&&x.inner.includes("/")&&(y=me(r)),(y!==Ce||De()||/^\)+$/.test(j()))&&(w=x.close=`)$))${y}`),x.inner.includes("*")&&(b=j())&&/^\.[^\\/.]+$/.test(b)){let F=P4(b,{...e,fastpaths:!1}).output;w=x.close=`)${F})${y})`}x.prev.type==="bos"&&(g.negatedExtglob=!0)}ke({type:"paren",extglob:!0,value:X,output:w}),Ye("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(t)){let x=!1,w=t.replace(ont,(b,y,F,z,Z,$)=>z==="\\"?(x=!0,b):z==="?"?y?y+z+(Z?te.repeat(Z.length):""):$===0?Be+(Z?te.repeat(Z.length):""):te.repeat(F.length):z==="."?C.repeat(F.length):z==="*"?y?y+z+(Z?Ce:""):Ce:y?b:`\\${b}`);return x===!0&&(r.unescape===!0?w=w.replace(/\\/g,""):w=w.replace(/\\+/g,b=>b.length%2===0?"\\\\":b?"\\":"")),w===t&&r.contains===!0?(g.output=t,g):(g.output=Gl.wrapOutput(w,g,e),g)}for(;!De();){if(X=gt(),X==="\0")continue;if(X==="\\"){let b=Re();if(b==="/"&&r.bash!==!0||b==="."||b===";")continue;if(!b){X+="\\",ke({type:"text",value:X});continue}let y=/^\\+/.exec(j()),F=0;if(y&&y[0].length>2&&(F=y[0].length,g.index+=F,F%2!==0&&(X+="\\")),r.unescape===!0?X=gt():X+=gt(),g.brackets===0){ke({type:"text",value:X});continue}}if(g.brackets>0&&(X!=="]"||se.value==="["||se.value==="[^")){if(r.posix!==!1&&X===":"){let b=se.value.slice(1);if(b.includes("[")&&(se.posix=!0,b.includes(":"))){let y=se.value.lastIndexOf("["),F=se.value.slice(0,y),z=se.value.slice(y+2),Z=int[z];if(Z){se.value=F+Z,g.backtrack=!0,gt(),!n.output&&c.indexOf(se)===1&&(n.output=I);continue}}}(X==="["&&Re()!==":"||X==="-"&&Re()==="]")&&(X=`\\${X}`),X==="]"&&(se.value==="["||se.value==="[^")&&(X=`\\${X}`),r.posix===!0&&X==="!"&&se.value==="["&&(X="^"),se.value+=X,Fe({value:X});continue}if(g.quotes===1&&X!=='"'){X=Gl.escapeRegex(X),se.value+=X,Fe({value:X});continue}if(X==='"'){g.quotes=g.quotes===1?0:1,r.keepQuotes===!0&&ke({type:"text",value:X});continue}if(X==="("){Pe("parens"),ke({type:"paren",value:X});continue}if(X===")"){if(g.parens===0&&r.strictBrackets===!0)throw new SyntaxError(RE("opening","("));let b=we[we.length-1];if(b&&g.parens===b.parens+1){_e(we.pop());continue}ke({type:"paren",value:X,output:g.parens?")":"\\)"}),Ye("parens");continue}if(X==="["){if(r.nobracket===!0||!j().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(RE("closing","]"));X=`\\${X}`}else Pe("brackets");ke({type:"bracket",value:X});continue}if(X==="]"){if(r.nobracket===!0||se&&se.type==="bracket"&&se.value.length===1){ke({type:"text",value:X,output:`\\${X}`});continue}if(g.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(RE("opening","["));ke({type:"text",value:X,output:`\\${X}`});continue}Ye("brackets");let b=se.value.slice(1);if(se.posix!==!0&&b[0]==="^"&&!b.includes("/")&&(X=`/${X}`),se.value+=X,Fe({value:X}),r.literalBrackets===!1||Gl.hasRegexChars(b))continue;let y=Gl.escapeRegex(se.value);if(g.output=g.output.slice(0,-se.value.length),r.literalBrackets===!0){g.output+=y,se.value=y;continue}se.value=`(${f}${y}|${se.value})`,g.output+=se.value;continue}if(X==="{"&&r.nobrace!==!0){Pe("braces");let b={type:"brace",value:X,output:"(",outputIndex:g.output.length,tokensIndex:g.tokens.length};Ee.push(b),ke(b);continue}if(X==="}"){let b=Ee[Ee.length-1];if(r.nobrace===!0||!b){ke({type:"text",value:X,output:X});continue}let y=")";if(b.dots===!0){let F=c.slice(),z=[];for(let Z=F.length-1;Z>=0&&(c.pop(),F[Z].type!=="brace");Z--)F[Z].type!=="dots"&&z.unshift(F[Z].value);y=ant(z,r),g.backtrack=!0}if(b.comma!==!0&&b.dots!==!0){let F=g.output.slice(0,b.outputIndex),z=g.tokens.slice(b.tokensIndex);b.value=b.output="\\{",X=y="\\}",g.output=F;for(let Z of z)g.output+=Z.output||Z.value}ke({type:"brace",value:X,output:y}),Ye("braces"),Ee.pop();continue}if(X==="|"){we.length>0&&we[we.length-1].conditions++,ke({type:"text",value:X});continue}if(X===","){let b=X,y=Ee[Ee.length-1];y&&fe[fe.length-1]==="braces"&&(y.comma=!0,b="|"),ke({type:"comma",value:X,output:b});continue}if(X==="/"){if(se.type==="dot"&&g.index===g.start+1){g.start=g.index+1,g.consumed="",g.output="",c.pop(),se=n;continue}ke({type:"slash",value:X,output:P});continue}if(X==="."){if(g.braces>0&&se.type==="dot"){se.value==="."&&(se.output=C);let b=Ee[Ee.length-1];se.type="dots",se.output+=X,se.value+=X,b.dots=!0;continue}if(g.braces+g.parens===0&&se.type!=="bos"&&se.type!=="slash"){ke({type:"text",value:X,output:C});continue}ke({type:"dot",value:X,output:C});continue}if(X==="?"){if(!(se&&se.value==="(")&&r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){it("qmark",X);continue}if(se&&se.type==="paren"){let y=Re(),F=X;if(y==="<"&&!Gl.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(se.value==="("&&!/[!=<:]/.test(y)||y==="<"&&!/<([!=]|\w+>)/.test(j()))&&(F=`\\${X}`),ke({type:"text",value:X,output:F});continue}if(r.dot!==!0&&(se.type==="slash"||se.type==="bos")){ke({type:"qmark",value:X,output:ie});continue}ke({type:"qmark",value:X,output:te});continue}if(X==="!"){if(r.noextglob!==!0&&Re()==="("&&(Re(2)!=="?"||!/[!=<:]/.test(Re(3)))){it("negate",X);continue}if(r.nonegate!==!0&&g.index===0){Ne();continue}}if(X==="+"){if(r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){it("plus",X);continue}if(se&&se.value==="("||r.regex===!1){ke({type:"plus",value:X,output:S});continue}if(se&&(se.type==="bracket"||se.type==="paren"||se.type==="brace")||g.parens>0){ke({type:"plus",value:X});continue}ke({type:"plus",value:S});continue}if(X==="@"){if(r.noextglob!==!0&&Re()==="("&&Re(2)!=="?"){ke({type:"at",extglob:!0,value:X,output:""});continue}ke({type:"text",value:X});continue}if(X!=="*"){(X==="$"||X==="^")&&(X=`\\${X}`);let b=snt.exec(j());b&&(X+=b[0],g.index+=b[0].length),ke({type:"text",value:X});continue}if(se&&(se.type==="globstar"||se.star===!0)){se.type="star",se.star=!0,se.value+=X,se.output=Ce,g.backtrack=!0,g.globstar=!0,rt(X);continue}let x=j();if(r.noextglob!==!0&&/^\([^?]/.test(x)){it("star",X);continue}if(se.type==="star"){if(r.noglobstar===!0){rt(X);continue}let b=se.prev,y=b.prev,F=b.type==="slash"||b.type==="bos",z=y&&(y.type==="star"||y.type==="globstar");if(r.bash===!0&&(!F||x[0]&&x[0]!=="/")){ke({type:"star",value:X,output:""});continue}let Z=g.braces>0&&(b.type==="comma"||b.type==="brace"),$=we.length&&(b.type==="pipe"||b.type==="paren");if(!F&&b.type!=="paren"&&!Z&&!$){ke({type:"star",value:X,output:""});continue}for(;x.slice(0,3)==="/**";){let oe=t[g.index+4];if(oe&&oe!=="/")break;x=x.slice(3),rt("/**",3)}if(b.type==="bos"&&De()){se.type="globstar",se.value+=X,se.output=me(r),g.output=se.output,g.globstar=!0,rt(X);continue}if(b.type==="slash"&&b.prev.type!=="bos"&&!z&&De()){g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type="globstar",se.output=me(r)+(r.strictSlashes?")":"|$)"),se.value+=X,g.globstar=!0,g.output+=b.output+se.output,rt(X);continue}if(b.type==="slash"&&b.prev.type!=="bos"&&x[0]==="/"){let oe=x[1]!==void 0?"|$":"";g.output=g.output.slice(0,-(b.output+se.output).length),b.output=`(?:${b.output}`,se.type="globstar",se.output=`${me(r)}${P}|${P}${oe})`,se.value+=X,g.output+=b.output+se.output,g.globstar=!0,rt(X+gt()),ke({type:"slash",value:"/",output:""});continue}if(b.type==="bos"&&x[0]==="/"){se.type="globstar",se.value+=X,se.output=`(?:^|${P}|${me(r)}${P})`,g.output=se.output,g.globstar=!0,rt(X+gt()),ke({type:"slash",value:"/",output:""});continue}g.output=g.output.slice(0,-se.output.length),se.type="globstar",se.output=me(r),se.value+=X,g.output+=se.output,g.globstar=!0,rt(X);continue}let w={type:"star",value:X,output:Ce};if(r.bash===!0){w.output=".*?",(se.type==="bos"||se.type==="slash")&&(w.output=pe+w.output),ke(w);continue}if(se&&(se.type==="bracket"||se.type==="paren")&&r.regex===!0){w.output=X,ke(w);continue}(g.index===g.start||se.type==="slash"||se.type==="dot")&&(se.type==="dot"?(g.output+=U,se.output+=U):r.dot===!0?(g.output+=W,se.output+=W):(g.output+=pe,se.output+=pe),Re()!=="*"&&(g.output+=I,se.output+=I)),ke(w)}for(;g.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(RE("closing","]"));g.output=Gl.escapeLast(g.output,"["),Ye("brackets")}for(;g.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(RE("closing",")"));g.output=Gl.escapeLast(g.output,"("),Ye("parens")}for(;g.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(RE("closing","}"));g.output=Gl.escapeLast(g.output,"{"),Ye("braces")}if(r.strictSlashes!==!0&&(se.type==="star"||se.type==="bracket")&&ke({type:"maybe_slash",value:"",output:`${P}?`}),g.backtrack===!0){g.output="";for(let x of g.tokens)g.output+=x.output!=null?x.output:x.value,x.suffix&&(g.output+=x.suffix)}return g};P4.fastpaths=(t,e)=>{let r={...e},s=typeof r.maxLength=="number"?Math.min(Dk,r.maxLength):Dk,a=t.length;if(a>s)throw new SyntaxError(`Input length: ${a}, exceeds maximum allowed length: ${s}`);t=Pae[t]||t;let n=Gl.isWindows(e),{DOT_LITERAL:c,SLASH_LITERAL:f,ONE_CHAR:p,DOTS_SLASH:h,NO_DOT:E,NO_DOTS:C,NO_DOTS_SLASH:S,STAR:P,START_ANCHOR:I}=Sk.globChars(n),R=r.dot?C:E,N=r.dot?S:E,U=r.capture?"":"?:",W={negated:!1,prefix:""},te=r.bash===!0?".*?":P;r.capture&&(te=`(${te})`);let ie=pe=>pe.noglobstar===!0?te:`(${U}(?:(?!${I}${pe.dot?h:c}).)*?)`,Ae=pe=>{switch(pe){case"*":return`${R}${p}${te}`;case".*":return`${c}${p}${te}`;case"*.*":return`${R}${te}${c}${p}${te}`;case"*/*":return`${R}${te}${f}${p}${N}${te}`;case"**":return R+ie(r);case"**/*":return`(?:${R}${ie(r)}${f})?${N}${p}${te}`;case"**/*.*":return`(?:${R}${ie(r)}${f})?${N}${te}${c}${p}${te}`;case"**/.*":return`(?:${R}${ie(r)}${f})?${c}${p}${te}`;default:{let Be=/^(.*?)\.(\w+)$/.exec(pe);if(!Be)return;let Ce=Ae(Be[1]);return Ce?Ce+c+Be[2]:void 0}}},ce=Gl.removePrefix(t,W),me=Ae(ce);return me&&r.strictSlashes!==!0&&(me+=`${f}?`),me};xae.exports=P4});var Tae=L((rVt,Qae)=>{"use strict";var lnt=ye("path"),cnt=bae(),x4=kae(),k4=SB(),unt=vB(),fnt=t=>t&&typeof t=="object"&&!Array.isArray(t),Xi=(t,e,r=!1)=>{if(Array.isArray(t)){let E=t.map(S=>Xi(S,e,r));return S=>{for(let P of E){let I=P(S);if(I)return I}return!1}}let s=fnt(t)&&t.tokens&&t.input;if(t===""||typeof t!="string"&&!s)throw new TypeError("Expected pattern to be a non-empty string");let a=e||{},n=k4.isWindows(e),c=s?Xi.compileRe(t,e):Xi.makeRe(t,e,!1,!0),f=c.state;delete c.state;let p=()=>!1;if(a.ignore){let E={...e,ignore:null,onMatch:null,onResult:null};p=Xi(a.ignore,E,r)}let h=(E,C=!1)=>{let{isMatch:S,match:P,output:I}=Xi.test(E,c,e,{glob:t,posix:n}),R={glob:t,state:f,regex:c,posix:n,input:E,output:I,match:P,isMatch:S};return typeof a.onResult=="function"&&a.onResult(R),S===!1?(R.isMatch=!1,C?R:!1):p(E)?(typeof a.onIgnore=="function"&&a.onIgnore(R),R.isMatch=!1,C?R:!1):(typeof a.onMatch=="function"&&a.onMatch(R),C?R:!0)};return r&&(h.state=f),h};Xi.test=(t,e,r,{glob:s,posix:a}={})=>{if(typeof t!="string")throw new TypeError("Expected input to be a string");if(t==="")return{isMatch:!1,output:""};let n=r||{},c=n.format||(a?k4.toPosixSlashes:null),f=t===s,p=f&&c?c(t):t;return f===!1&&(p=c?c(t):t,f=p===s),(f===!1||n.capture===!0)&&(n.matchBase===!0||n.basename===!0?f=Xi.matchBase(t,e,r,a):f=e.exec(p)),{isMatch:!!f,match:f,output:p}};Xi.matchBase=(t,e,r,s=k4.isWindows(r))=>(e instanceof RegExp?e:Xi.makeRe(e,r)).test(lnt.basename(t));Xi.isMatch=(t,e,r)=>Xi(e,r)(t);Xi.parse=(t,e)=>Array.isArray(t)?t.map(r=>Xi.parse(r,e)):x4(t,{...e,fastpaths:!1});Xi.scan=(t,e)=>cnt(t,e);Xi.compileRe=(t,e,r=!1,s=!1)=>{if(r===!0)return t.output;let a=e||{},n=a.contains?"":"^",c=a.contains?"":"$",f=`${n}(?:${t.output})${c}`;t&&t.negated===!0&&(f=`^(?!${f}).*$`);let p=Xi.toRegex(f,e);return s===!0&&(p.state=t),p};Xi.makeRe=(t,e={},r=!1,s=!1)=>{if(!t||typeof t!="string")throw new TypeError("Expected a non-empty string");let a={negated:!1,fastpaths:!0};return e.fastpaths!==!1&&(t[0]==="."||t[0]==="*")&&(a.output=x4.fastpaths(t,e)),a.output||(a=x4(t,e)),Xi.compileRe(a,e,r,s)};Xi.toRegex=(t,e)=>{try{let r=e||{};return new RegExp(t,r.flags||(r.nocase?"i":""))}catch(r){if(e&&e.debug===!0)throw r;return/$^/}};Xi.constants=unt;Qae.exports=Xi});var Fae=L((nVt,Rae)=>{"use strict";Rae.exports=Tae()});var Sa=L((iVt,Mae)=>{"use strict";var Oae=ye("util"),Lae=hae(),Jf=Fae(),Q4=SB(),Nae=t=>t===""||t==="./",ki=(t,e,r)=>{e=[].concat(e),t=[].concat(t);let s=new Set,a=new Set,n=new Set,c=0,f=E=>{n.add(E.output),r&&r.onResult&&r.onResult(E)};for(let E=0;E!s.has(E));if(r&&h.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${e.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?e.map(E=>E.replace(/\\/g,"")):e}return h};ki.match=ki;ki.matcher=(t,e)=>Jf(t,e);ki.isMatch=(t,e,r)=>Jf(e,r)(t);ki.any=ki.isMatch;ki.not=(t,e,r={})=>{e=[].concat(e).map(String);let s=new Set,a=[],n=f=>{r.onResult&&r.onResult(f),a.push(f.output)},c=new Set(ki(t,e,{...r,onResult:n}));for(let f of a)c.has(f)||s.add(f);return[...s]};ki.contains=(t,e,r)=>{if(typeof t!="string")throw new TypeError(`Expected a string: "${Oae.inspect(t)}"`);if(Array.isArray(e))return e.some(s=>ki.contains(t,s,r));if(typeof e=="string"){if(Nae(t)||Nae(e))return!1;if(t.includes(e)||t.startsWith("./")&&t.slice(2).includes(e))return!0}return ki.isMatch(t,e,{...r,contains:!0})};ki.matchKeys=(t,e,r)=>{if(!Q4.isObject(t))throw new TypeError("Expected the first argument to be an object");let s=ki(Object.keys(t),e,r),a={};for(let n of s)a[n]=t[n];return a};ki.some=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(s.some(c=>n(c)))return!0}return!1};ki.every=(t,e,r)=>{let s=[].concat(t);for(let a of[].concat(e)){let n=Jf(String(a),r);if(!s.every(c=>n(c)))return!1}return!0};ki.all=(t,e,r)=>{if(typeof t!="string")throw new TypeError(`Expected a string: "${Oae.inspect(t)}"`);return[].concat(e).every(s=>Jf(s,r)(t))};ki.capture=(t,e,r)=>{let s=Q4.isWindows(r),n=Jf.makeRe(String(t),{...r,capture:!0}).exec(s?Q4.toPosixSlashes(e):e);if(n)return n.slice(1).map(c=>c===void 0?"":c)};ki.makeRe=(...t)=>Jf.makeRe(...t);ki.scan=(...t)=>Jf.scan(...t);ki.parse=(t,e)=>{let r=[];for(let s of[].concat(t||[]))for(let a of Lae(String(s),e))r.push(Jf.parse(a,e));return r};ki.braces=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");return e&&e.nobrace===!0||!/\{.*\}/.test(t)?[t]:Lae(t,e)};ki.braceExpand=(t,e)=>{if(typeof t!="string")throw new TypeError("Expected a string");return ki.braces(t,{...e,expand:!0})};Mae.exports=ki});var Uae=L((sVt,_ae)=>{"use strict";_ae.exports=({onlyFirst:t=!1}={})=>{let e=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(e,t?void 0:"g")}});var bk=L((oVt,Hae)=>{"use strict";var Ant=Uae();Hae.exports=t=>typeof t=="string"?t.replace(Ant(),""):t});var qae=L((aVt,jae)=>{function pnt(){this.__data__=[],this.size=0}jae.exports=pnt});var FE=L((lVt,Gae)=>{function hnt(t,e){return t===e||t!==t&&e!==e}Gae.exports=hnt});var bB=L((cVt,Wae)=>{var gnt=FE();function dnt(t,e){for(var r=t.length;r--;)if(gnt(t[r][0],e))return r;return-1}Wae.exports=dnt});var Vae=L((uVt,Yae)=>{var mnt=bB(),ynt=Array.prototype,Ent=ynt.splice;function Int(t){var e=this.__data__,r=mnt(e,t);if(r<0)return!1;var s=e.length-1;return r==s?e.pop():Ent.call(e,r,1),--this.size,!0}Yae.exports=Int});var Jae=L((fVt,Kae)=>{var Cnt=bB();function wnt(t){var e=this.__data__,r=Cnt(e,t);return r<0?void 0:e[r][1]}Kae.exports=wnt});var Zae=L((AVt,zae)=>{var Bnt=bB();function vnt(t){return Bnt(this.__data__,t)>-1}zae.exports=vnt});var $ae=L((pVt,Xae)=>{var Snt=bB();function Dnt(t,e){var r=this.__data__,s=Snt(r,t);return s<0?(++this.size,r.push([t,e])):r[s][1]=e,this}Xae.exports=Dnt});var PB=L((hVt,ele)=>{var bnt=qae(),Pnt=Vae(),xnt=Jae(),knt=Zae(),Qnt=$ae();function NE(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e{var Tnt=PB();function Rnt(){this.__data__=new Tnt,this.size=0}tle.exports=Rnt});var ile=L((dVt,nle)=>{function Fnt(t){var e=this.__data__,r=e.delete(t);return this.size=e.size,r}nle.exports=Fnt});var ole=L((mVt,sle)=>{function Nnt(t){return this.__data__.get(t)}sle.exports=Nnt});var lle=L((yVt,ale)=>{function Ont(t){return this.__data__.has(t)}ale.exports=Ont});var T4=L((EVt,cle)=>{var Lnt=typeof global=="object"&&global&&global.Object===Object&&global;cle.exports=Lnt});var Pc=L((IVt,ule)=>{var Mnt=T4(),_nt=typeof self=="object"&&self&&self.Object===Object&&self,Unt=Mnt||_nt||Function("return this")();ule.exports=Unt});var Yd=L((CVt,fle)=>{var Hnt=Pc(),jnt=Hnt.Symbol;fle.exports=jnt});var gle=L((wVt,hle)=>{var Ale=Yd(),ple=Object.prototype,qnt=ple.hasOwnProperty,Gnt=ple.toString,xB=Ale?Ale.toStringTag:void 0;function Wnt(t){var e=qnt.call(t,xB),r=t[xB];try{t[xB]=void 0;var s=!0}catch{}var a=Gnt.call(t);return s&&(e?t[xB]=r:delete t[xB]),a}hle.exports=Wnt});var mle=L((BVt,dle)=>{var Ynt=Object.prototype,Vnt=Ynt.toString;function Knt(t){return Vnt.call(t)}dle.exports=Knt});var Vd=L((vVt,Ile)=>{var yle=Yd(),Jnt=gle(),znt=mle(),Znt="[object Null]",Xnt="[object Undefined]",Ele=yle?yle.toStringTag:void 0;function $nt(t){return t==null?t===void 0?Xnt:Znt:Ele&&Ele in Object(t)?Jnt(t):znt(t)}Ile.exports=$nt});var Wl=L((SVt,Cle)=>{function eit(t){var e=typeof t;return t!=null&&(e=="object"||e=="function")}Cle.exports=eit});var Pk=L((DVt,wle)=>{var tit=Vd(),rit=Wl(),nit="[object AsyncFunction]",iit="[object Function]",sit="[object GeneratorFunction]",oit="[object Proxy]";function ait(t){if(!rit(t))return!1;var e=tit(t);return e==iit||e==sit||e==nit||e==oit}wle.exports=ait});var vle=L((bVt,Ble)=>{var lit=Pc(),cit=lit["__core-js_shared__"];Ble.exports=cit});var ble=L((PVt,Dle)=>{var R4=vle(),Sle=function(){var t=/[^.]+$/.exec(R4&&R4.keys&&R4.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}();function uit(t){return!!Sle&&Sle in t}Dle.exports=uit});var F4=L((xVt,Ple)=>{var fit=Function.prototype,Ait=fit.toString;function pit(t){if(t!=null){try{return Ait.call(t)}catch{}try{return t+""}catch{}}return""}Ple.exports=pit});var kle=L((kVt,xle)=>{var hit=Pk(),git=ble(),dit=Wl(),mit=F4(),yit=/[\\^$.*+?()[\]{}|]/g,Eit=/^\[object .+?Constructor\]$/,Iit=Function.prototype,Cit=Object.prototype,wit=Iit.toString,Bit=Cit.hasOwnProperty,vit=RegExp("^"+wit.call(Bit).replace(yit,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");function Sit(t){if(!dit(t)||git(t))return!1;var e=hit(t)?vit:Eit;return e.test(mit(t))}xle.exports=Sit});var Tle=L((QVt,Qle)=>{function Dit(t,e){return t?.[e]}Qle.exports=Dit});var A0=L((TVt,Rle)=>{var bit=kle(),Pit=Tle();function xit(t,e){var r=Pit(t,e);return bit(r)?r:void 0}Rle.exports=xit});var xk=L((RVt,Fle)=>{var kit=A0(),Qit=Pc(),Tit=kit(Qit,"Map");Fle.exports=Tit});var kB=L((FVt,Nle)=>{var Rit=A0(),Fit=Rit(Object,"create");Nle.exports=Fit});var Mle=L((NVt,Lle)=>{var Ole=kB();function Nit(){this.__data__=Ole?Ole(null):{},this.size=0}Lle.exports=Nit});var Ule=L((OVt,_le)=>{function Oit(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}_le.exports=Oit});var jle=L((LVt,Hle)=>{var Lit=kB(),Mit="__lodash_hash_undefined__",_it=Object.prototype,Uit=_it.hasOwnProperty;function Hit(t){var e=this.__data__;if(Lit){var r=e[t];return r===Mit?void 0:r}return Uit.call(e,t)?e[t]:void 0}Hle.exports=Hit});var Gle=L((MVt,qle)=>{var jit=kB(),qit=Object.prototype,Git=qit.hasOwnProperty;function Wit(t){var e=this.__data__;return jit?e[t]!==void 0:Git.call(e,t)}qle.exports=Wit});var Yle=L((_Vt,Wle)=>{var Yit=kB(),Vit="__lodash_hash_undefined__";function Kit(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=Yit&&e===void 0?Vit:e,this}Wle.exports=Kit});var Kle=L((UVt,Vle)=>{var Jit=Mle(),zit=Ule(),Zit=jle(),Xit=Gle(),$it=Yle();function OE(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e{var Jle=Kle(),est=PB(),tst=xk();function rst(){this.size=0,this.__data__={hash:new Jle,map:new(tst||est),string:new Jle}}zle.exports=rst});var $le=L((jVt,Xle)=>{function nst(t){var e=typeof t;return e=="string"||e=="number"||e=="symbol"||e=="boolean"?t!=="__proto__":t===null}Xle.exports=nst});var QB=L((qVt,ece)=>{var ist=$le();function sst(t,e){var r=t.__data__;return ist(e)?r[typeof e=="string"?"string":"hash"]:r.map}ece.exports=sst});var rce=L((GVt,tce)=>{var ost=QB();function ast(t){var e=ost(this,t).delete(t);return this.size-=e?1:0,e}tce.exports=ast});var ice=L((WVt,nce)=>{var lst=QB();function cst(t){return lst(this,t).get(t)}nce.exports=cst});var oce=L((YVt,sce)=>{var ust=QB();function fst(t){return ust(this,t).has(t)}sce.exports=fst});var lce=L((VVt,ace)=>{var Ast=QB();function pst(t,e){var r=Ast(this,t),s=r.size;return r.set(t,e),this.size+=r.size==s?0:1,this}ace.exports=pst});var kk=L((KVt,cce)=>{var hst=Zle(),gst=rce(),dst=ice(),mst=oce(),yst=lce();function LE(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e{var Est=PB(),Ist=xk(),Cst=kk(),wst=200;function Bst(t,e){var r=this.__data__;if(r instanceof Est){var s=r.__data__;if(!Ist||s.length{var vst=PB(),Sst=rle(),Dst=ile(),bst=ole(),Pst=lle(),xst=fce();function ME(t){var e=this.__data__=new vst(t);this.size=e.size}ME.prototype.clear=Sst;ME.prototype.delete=Dst;ME.prototype.get=bst;ME.prototype.has=Pst;ME.prototype.set=xst;Ace.exports=ME});var hce=L((ZVt,pce)=>{var kst="__lodash_hash_undefined__";function Qst(t){return this.__data__.set(t,kst),this}pce.exports=Qst});var dce=L((XVt,gce)=>{function Tst(t){return this.__data__.has(t)}gce.exports=Tst});var yce=L(($Vt,mce)=>{var Rst=kk(),Fst=hce(),Nst=dce();function Tk(t){var e=-1,r=t==null?0:t.length;for(this.__data__=new Rst;++e{function Ost(t,e){for(var r=-1,s=t==null?0:t.length;++r{function Lst(t,e){return t.has(e)}Cce.exports=Lst});var N4=L((r7t,Bce)=>{var Mst=yce(),_st=Ice(),Ust=wce(),Hst=1,jst=2;function qst(t,e,r,s,a,n){var c=r&Hst,f=t.length,p=e.length;if(f!=p&&!(c&&p>f))return!1;var h=n.get(t),E=n.get(e);if(h&&E)return h==e&&E==t;var C=-1,S=!0,P=r&jst?new Mst:void 0;for(n.set(t,e),n.set(e,t);++C{var Gst=Pc(),Wst=Gst.Uint8Array;vce.exports=Wst});var Dce=L((i7t,Sce)=>{function Yst(t){var e=-1,r=Array(t.size);return t.forEach(function(s,a){r[++e]=[a,s]}),r}Sce.exports=Yst});var Pce=L((s7t,bce)=>{function Vst(t){var e=-1,r=Array(t.size);return t.forEach(function(s){r[++e]=s}),r}bce.exports=Vst});var Rce=L((o7t,Tce)=>{var xce=Yd(),kce=O4(),Kst=FE(),Jst=N4(),zst=Dce(),Zst=Pce(),Xst=1,$st=2,eot="[object Boolean]",tot="[object Date]",rot="[object Error]",not="[object Map]",iot="[object Number]",sot="[object RegExp]",oot="[object Set]",aot="[object String]",lot="[object Symbol]",cot="[object ArrayBuffer]",uot="[object DataView]",Qce=xce?xce.prototype:void 0,L4=Qce?Qce.valueOf:void 0;function fot(t,e,r,s,a,n,c){switch(r){case uot:if(t.byteLength!=e.byteLength||t.byteOffset!=e.byteOffset)return!1;t=t.buffer,e=e.buffer;case cot:return!(t.byteLength!=e.byteLength||!n(new kce(t),new kce(e)));case eot:case tot:case iot:return Kst(+t,+e);case rot:return t.name==e.name&&t.message==e.message;case sot:case aot:return t==e+"";case not:var f=zst;case oot:var p=s&Xst;if(f||(f=Zst),t.size!=e.size&&!p)return!1;var h=c.get(t);if(h)return h==e;s|=$st,c.set(t,e);var E=Jst(f(t),f(e),s,a,n,c);return c.delete(t),E;case lot:if(L4)return L4.call(t)==L4.call(e)}return!1}Tce.exports=fot});var Rk=L((a7t,Fce)=>{function Aot(t,e){for(var r=-1,s=e.length,a=t.length;++r{var pot=Array.isArray;Nce.exports=pot});var M4=L((c7t,Oce)=>{var hot=Rk(),got=xc();function dot(t,e,r){var s=e(t);return got(t)?s:hot(s,r(t))}Oce.exports=dot});var Mce=L((u7t,Lce)=>{function mot(t,e){for(var r=-1,s=t==null?0:t.length,a=0,n=[];++r{function yot(){return[]}_ce.exports=yot});var Fk=L((A7t,Hce)=>{var Eot=Mce(),Iot=_4(),Cot=Object.prototype,wot=Cot.propertyIsEnumerable,Uce=Object.getOwnPropertySymbols,Bot=Uce?function(t){return t==null?[]:(t=Object(t),Eot(Uce(t),function(e){return wot.call(t,e)}))}:Iot;Hce.exports=Bot});var qce=L((p7t,jce)=>{function vot(t,e){for(var r=-1,s=Array(t);++r{function Sot(t){return t!=null&&typeof t=="object"}Gce.exports=Sot});var Yce=L((g7t,Wce)=>{var Dot=Vd(),bot=zf(),Pot="[object Arguments]";function xot(t){return bot(t)&&Dot(t)==Pot}Wce.exports=xot});var TB=L((d7t,Jce)=>{var Vce=Yce(),kot=zf(),Kce=Object.prototype,Qot=Kce.hasOwnProperty,Tot=Kce.propertyIsEnumerable,Rot=Vce(function(){return arguments}())?Vce:function(t){return kot(t)&&Qot.call(t,"callee")&&!Tot.call(t,"callee")};Jce.exports=Rot});var Zce=L((m7t,zce)=>{function Fot(){return!1}zce.exports=Fot});var FB=L((RB,_E)=>{var Not=Pc(),Oot=Zce(),eue=typeof RB=="object"&&RB&&!RB.nodeType&&RB,Xce=eue&&typeof _E=="object"&&_E&&!_E.nodeType&&_E,Lot=Xce&&Xce.exports===eue,$ce=Lot?Not.Buffer:void 0,Mot=$ce?$ce.isBuffer:void 0,_ot=Mot||Oot;_E.exports=_ot});var NB=L((y7t,tue)=>{var Uot=9007199254740991,Hot=/^(?:0|[1-9]\d*)$/;function jot(t,e){var r=typeof t;return e=e??Uot,!!e&&(r=="number"||r!="symbol"&&Hot.test(t))&&t>-1&&t%1==0&&t{var qot=9007199254740991;function Got(t){return typeof t=="number"&&t>-1&&t%1==0&&t<=qot}rue.exports=Got});var iue=L((I7t,nue)=>{var Wot=Vd(),Yot=Nk(),Vot=zf(),Kot="[object Arguments]",Jot="[object Array]",zot="[object Boolean]",Zot="[object Date]",Xot="[object Error]",$ot="[object Function]",eat="[object Map]",tat="[object Number]",rat="[object Object]",nat="[object RegExp]",iat="[object Set]",sat="[object String]",oat="[object WeakMap]",aat="[object ArrayBuffer]",lat="[object DataView]",cat="[object Float32Array]",uat="[object Float64Array]",fat="[object Int8Array]",Aat="[object Int16Array]",pat="[object Int32Array]",hat="[object Uint8Array]",gat="[object Uint8ClampedArray]",dat="[object Uint16Array]",mat="[object Uint32Array]",Si={};Si[cat]=Si[uat]=Si[fat]=Si[Aat]=Si[pat]=Si[hat]=Si[gat]=Si[dat]=Si[mat]=!0;Si[Kot]=Si[Jot]=Si[aat]=Si[zot]=Si[lat]=Si[Zot]=Si[Xot]=Si[$ot]=Si[eat]=Si[tat]=Si[rat]=Si[nat]=Si[iat]=Si[sat]=Si[oat]=!1;function yat(t){return Vot(t)&&Yot(t.length)&&!!Si[Wot(t)]}nue.exports=yat});var Ok=L((C7t,sue)=>{function Eat(t){return function(e){return t(e)}}sue.exports=Eat});var Lk=L((OB,UE)=>{var Iat=T4(),oue=typeof OB=="object"&&OB&&!OB.nodeType&&OB,LB=oue&&typeof UE=="object"&&UE&&!UE.nodeType&&UE,Cat=LB&&LB.exports===oue,U4=Cat&&Iat.process,wat=function(){try{var t=LB&&LB.require&&LB.require("util").types;return t||U4&&U4.binding&&U4.binding("util")}catch{}}();UE.exports=wat});var Mk=L((w7t,cue)=>{var Bat=iue(),vat=Ok(),aue=Lk(),lue=aue&&aue.isTypedArray,Sat=lue?vat(lue):Bat;cue.exports=Sat});var H4=L((B7t,uue)=>{var Dat=qce(),bat=TB(),Pat=xc(),xat=FB(),kat=NB(),Qat=Mk(),Tat=Object.prototype,Rat=Tat.hasOwnProperty;function Fat(t,e){var r=Pat(t),s=!r&&bat(t),a=!r&&!s&&xat(t),n=!r&&!s&&!a&&Qat(t),c=r||s||a||n,f=c?Dat(t.length,String):[],p=f.length;for(var h in t)(e||Rat.call(t,h))&&!(c&&(h=="length"||a&&(h=="offset"||h=="parent")||n&&(h=="buffer"||h=="byteLength"||h=="byteOffset")||kat(h,p)))&&f.push(h);return f}uue.exports=Fat});var _k=L((v7t,fue)=>{var Nat=Object.prototype;function Oat(t){var e=t&&t.constructor,r=typeof e=="function"&&e.prototype||Nat;return t===r}fue.exports=Oat});var j4=L((S7t,Aue)=>{function Lat(t,e){return function(r){return t(e(r))}}Aue.exports=Lat});var hue=L((D7t,pue)=>{var Mat=j4(),_at=Mat(Object.keys,Object);pue.exports=_at});var due=L((b7t,gue)=>{var Uat=_k(),Hat=hue(),jat=Object.prototype,qat=jat.hasOwnProperty;function Gat(t){if(!Uat(t))return Hat(t);var e=[];for(var r in Object(t))qat.call(t,r)&&r!="constructor"&&e.push(r);return e}gue.exports=Gat});var MB=L((P7t,mue)=>{var Wat=Pk(),Yat=Nk();function Vat(t){return t!=null&&Yat(t.length)&&!Wat(t)}mue.exports=Vat});var Uk=L((x7t,yue)=>{var Kat=H4(),Jat=due(),zat=MB();function Zat(t){return zat(t)?Kat(t):Jat(t)}yue.exports=Zat});var q4=L((k7t,Eue)=>{var Xat=M4(),$at=Fk(),elt=Uk();function tlt(t){return Xat(t,elt,$at)}Eue.exports=tlt});var wue=L((Q7t,Cue)=>{var Iue=q4(),rlt=1,nlt=Object.prototype,ilt=nlt.hasOwnProperty;function slt(t,e,r,s,a,n){var c=r&rlt,f=Iue(t),p=f.length,h=Iue(e),E=h.length;if(p!=E&&!c)return!1;for(var C=p;C--;){var S=f[C];if(!(c?S in e:ilt.call(e,S)))return!1}var P=n.get(t),I=n.get(e);if(P&&I)return P==e&&I==t;var R=!0;n.set(t,e),n.set(e,t);for(var N=c;++C{var olt=A0(),alt=Pc(),llt=olt(alt,"DataView");Bue.exports=llt});var Due=L((R7t,Sue)=>{var clt=A0(),ult=Pc(),flt=clt(ult,"Promise");Sue.exports=flt});var Pue=L((F7t,bue)=>{var Alt=A0(),plt=Pc(),hlt=Alt(plt,"Set");bue.exports=hlt});var kue=L((N7t,xue)=>{var glt=A0(),dlt=Pc(),mlt=glt(dlt,"WeakMap");xue.exports=mlt});var _B=L((O7t,Lue)=>{var G4=vue(),W4=xk(),Y4=Due(),V4=Pue(),K4=kue(),Oue=Vd(),HE=F4(),Que="[object Map]",ylt="[object Object]",Tue="[object Promise]",Rue="[object Set]",Fue="[object WeakMap]",Nue="[object DataView]",Elt=HE(G4),Ilt=HE(W4),Clt=HE(Y4),wlt=HE(V4),Blt=HE(K4),Kd=Oue;(G4&&Kd(new G4(new ArrayBuffer(1)))!=Nue||W4&&Kd(new W4)!=Que||Y4&&Kd(Y4.resolve())!=Tue||V4&&Kd(new V4)!=Rue||K4&&Kd(new K4)!=Fue)&&(Kd=function(t){var e=Oue(t),r=e==ylt?t.constructor:void 0,s=r?HE(r):"";if(s)switch(s){case Elt:return Nue;case Ilt:return Que;case Clt:return Tue;case wlt:return Rue;case Blt:return Fue}return e});Lue.exports=Kd});var Wue=L((L7t,Gue)=>{var J4=Qk(),vlt=N4(),Slt=Rce(),Dlt=wue(),Mue=_B(),_ue=xc(),Uue=FB(),blt=Mk(),Plt=1,Hue="[object Arguments]",jue="[object Array]",Hk="[object Object]",xlt=Object.prototype,que=xlt.hasOwnProperty;function klt(t,e,r,s,a,n){var c=_ue(t),f=_ue(e),p=c?jue:Mue(t),h=f?jue:Mue(e);p=p==Hue?Hk:p,h=h==Hue?Hk:h;var E=p==Hk,C=h==Hk,S=p==h;if(S&&Uue(t)){if(!Uue(e))return!1;c=!0,E=!1}if(S&&!E)return n||(n=new J4),c||blt(t)?vlt(t,e,r,s,a,n):Slt(t,e,p,r,s,a,n);if(!(r&Plt)){var P=E&&que.call(t,"__wrapped__"),I=C&&que.call(e,"__wrapped__");if(P||I){var R=P?t.value():t,N=I?e.value():e;return n||(n=new J4),a(R,N,r,s,n)}}return S?(n||(n=new J4),Dlt(t,e,r,s,a,n)):!1}Gue.exports=klt});var Jue=L((M7t,Kue)=>{var Qlt=Wue(),Yue=zf();function Vue(t,e,r,s,a){return t===e?!0:t==null||e==null||!Yue(t)&&!Yue(e)?t!==t&&e!==e:Qlt(t,e,r,s,Vue,a)}Kue.exports=Vue});var Zue=L((_7t,zue)=>{var Tlt=Jue();function Rlt(t,e){return Tlt(t,e)}zue.exports=Rlt});var z4=L((U7t,Xue)=>{var Flt=A0(),Nlt=function(){try{var t=Flt(Object,"defineProperty");return t({},"",{}),t}catch{}}();Xue.exports=Nlt});var jk=L((H7t,efe)=>{var $ue=z4();function Olt(t,e,r){e=="__proto__"&&$ue?$ue(t,e,{configurable:!0,enumerable:!0,value:r,writable:!0}):t[e]=r}efe.exports=Olt});var Z4=L((j7t,tfe)=>{var Llt=jk(),Mlt=FE();function _lt(t,e,r){(r!==void 0&&!Mlt(t[e],r)||r===void 0&&!(e in t))&&Llt(t,e,r)}tfe.exports=_lt});var nfe=L((q7t,rfe)=>{function Ult(t){return function(e,r,s){for(var a=-1,n=Object(e),c=s(e),f=c.length;f--;){var p=c[t?f:++a];if(r(n[p],p,n)===!1)break}return e}}rfe.exports=Ult});var sfe=L((G7t,ife)=>{var Hlt=nfe(),jlt=Hlt();ife.exports=jlt});var X4=L((UB,jE)=>{var qlt=Pc(),cfe=typeof UB=="object"&&UB&&!UB.nodeType&&UB,ofe=cfe&&typeof jE=="object"&&jE&&!jE.nodeType&&jE,Glt=ofe&&ofe.exports===cfe,afe=Glt?qlt.Buffer:void 0,lfe=afe?afe.allocUnsafe:void 0;function Wlt(t,e){if(e)return t.slice();var r=t.length,s=lfe?lfe(r):new t.constructor(r);return t.copy(s),s}jE.exports=Wlt});var qk=L((W7t,ffe)=>{var ufe=O4();function Ylt(t){var e=new t.constructor(t.byteLength);return new ufe(e).set(new ufe(t)),e}ffe.exports=Ylt});var $4=L((Y7t,Afe)=>{var Vlt=qk();function Klt(t,e){var r=e?Vlt(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.length)}Afe.exports=Klt});var Gk=L((V7t,pfe)=>{function Jlt(t,e){var r=-1,s=t.length;for(e||(e=Array(s));++r{var zlt=Wl(),hfe=Object.create,Zlt=function(){function t(){}return function(e){if(!zlt(e))return{};if(hfe)return hfe(e);t.prototype=e;var r=new t;return t.prototype=void 0,r}}();gfe.exports=Zlt});var Wk=L((J7t,mfe)=>{var Xlt=j4(),$lt=Xlt(Object.getPrototypeOf,Object);mfe.exports=$lt});var e3=L((z7t,yfe)=>{var ect=dfe(),tct=Wk(),rct=_k();function nct(t){return typeof t.constructor=="function"&&!rct(t)?ect(tct(t)):{}}yfe.exports=nct});var Ife=L((Z7t,Efe)=>{var ict=MB(),sct=zf();function oct(t){return sct(t)&&ict(t)}Efe.exports=oct});var t3=L((X7t,wfe)=>{var act=Vd(),lct=Wk(),cct=zf(),uct="[object Object]",fct=Function.prototype,Act=Object.prototype,Cfe=fct.toString,pct=Act.hasOwnProperty,hct=Cfe.call(Object);function gct(t){if(!cct(t)||act(t)!=uct)return!1;var e=lct(t);if(e===null)return!0;var r=pct.call(e,"constructor")&&e.constructor;return typeof r=="function"&&r instanceof r&&Cfe.call(r)==hct}wfe.exports=gct});var r3=L(($7t,Bfe)=>{function dct(t,e){if(!(e==="constructor"&&typeof t[e]=="function")&&e!="__proto__")return t[e]}Bfe.exports=dct});var Yk=L((eKt,vfe)=>{var mct=jk(),yct=FE(),Ect=Object.prototype,Ict=Ect.hasOwnProperty;function Cct(t,e,r){var s=t[e];(!(Ict.call(t,e)&&yct(s,r))||r===void 0&&!(e in t))&&mct(t,e,r)}vfe.exports=Cct});var Jd=L((tKt,Sfe)=>{var wct=Yk(),Bct=jk();function vct(t,e,r,s){var a=!r;r||(r={});for(var n=-1,c=e.length;++n{function Sct(t){var e=[];if(t!=null)for(var r in Object(t))e.push(r);return e}Dfe.exports=Sct});var xfe=L((nKt,Pfe)=>{var Dct=Wl(),bct=_k(),Pct=bfe(),xct=Object.prototype,kct=xct.hasOwnProperty;function Qct(t){if(!Dct(t))return Pct(t);var e=bct(t),r=[];for(var s in t)s=="constructor"&&(e||!kct.call(t,s))||r.push(s);return r}Pfe.exports=Qct});var qE=L((iKt,kfe)=>{var Tct=H4(),Rct=xfe(),Fct=MB();function Nct(t){return Fct(t)?Tct(t,!0):Rct(t)}kfe.exports=Nct});var Tfe=L((sKt,Qfe)=>{var Oct=Jd(),Lct=qE();function Mct(t){return Oct(t,Lct(t))}Qfe.exports=Mct});var Mfe=L((oKt,Lfe)=>{var Rfe=Z4(),_ct=X4(),Uct=$4(),Hct=Gk(),jct=e3(),Ffe=TB(),Nfe=xc(),qct=Ife(),Gct=FB(),Wct=Pk(),Yct=Wl(),Vct=t3(),Kct=Mk(),Ofe=r3(),Jct=Tfe();function zct(t,e,r,s,a,n,c){var f=Ofe(t,r),p=Ofe(e,r),h=c.get(p);if(h){Rfe(t,r,h);return}var E=n?n(f,p,r+"",t,e,c):void 0,C=E===void 0;if(C){var S=Nfe(p),P=!S&&Gct(p),I=!S&&!P&&Kct(p);E=p,S||P||I?Nfe(f)?E=f:qct(f)?E=Hct(f):P?(C=!1,E=_ct(p,!0)):I?(C=!1,E=Uct(p,!0)):E=[]:Vct(p)||Ffe(p)?(E=f,Ffe(f)?E=Jct(f):(!Yct(f)||Wct(f))&&(E=jct(p))):C=!1}C&&(c.set(p,E),a(E,p,s,n,c),c.delete(p)),Rfe(t,r,E)}Lfe.exports=zct});var Hfe=L((aKt,Ufe)=>{var Zct=Qk(),Xct=Z4(),$ct=sfe(),eut=Mfe(),tut=Wl(),rut=qE(),nut=r3();function _fe(t,e,r,s,a){t!==e&&$ct(e,function(n,c){if(a||(a=new Zct),tut(n))eut(t,e,c,r,_fe,s,a);else{var f=s?s(nut(t,c),n,c+"",t,e,a):void 0;f===void 0&&(f=n),Xct(t,c,f)}},rut)}Ufe.exports=_fe});var n3=L((lKt,jfe)=>{function iut(t){return t}jfe.exports=iut});var Gfe=L((cKt,qfe)=>{function sut(t,e,r){switch(r.length){case 0:return t.call(e);case 1:return t.call(e,r[0]);case 2:return t.call(e,r[0],r[1]);case 3:return t.call(e,r[0],r[1],r[2])}return t.apply(e,r)}qfe.exports=sut});var i3=L((uKt,Yfe)=>{var out=Gfe(),Wfe=Math.max;function aut(t,e,r){return e=Wfe(e===void 0?t.length-1:e,0),function(){for(var s=arguments,a=-1,n=Wfe(s.length-e,0),c=Array(n);++a{function lut(t){return function(){return t}}Vfe.exports=lut});var Zfe=L((AKt,zfe)=>{var cut=Kfe(),Jfe=z4(),uut=n3(),fut=Jfe?function(t,e){return Jfe(t,"toString",{configurable:!0,enumerable:!1,value:cut(e),writable:!0})}:uut;zfe.exports=fut});var $fe=L((pKt,Xfe)=>{var Aut=800,put=16,hut=Date.now;function gut(t){var e=0,r=0;return function(){var s=hut(),a=put-(s-r);if(r=s,a>0){if(++e>=Aut)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}Xfe.exports=gut});var s3=L((hKt,eAe)=>{var dut=Zfe(),mut=$fe(),yut=mut(dut);eAe.exports=yut});var rAe=L((gKt,tAe)=>{var Eut=n3(),Iut=i3(),Cut=s3();function wut(t,e){return Cut(Iut(t,e,Eut),t+"")}tAe.exports=wut});var iAe=L((dKt,nAe)=>{var But=FE(),vut=MB(),Sut=NB(),Dut=Wl();function but(t,e,r){if(!Dut(r))return!1;var s=typeof e;return(s=="number"?vut(r)&&Sut(e,r.length):s=="string"&&e in r)?But(r[e],t):!1}nAe.exports=but});var oAe=L((mKt,sAe)=>{var Put=rAe(),xut=iAe();function kut(t){return Put(function(e,r){var s=-1,a=r.length,n=a>1?r[a-1]:void 0,c=a>2?r[2]:void 0;for(n=t.length>3&&typeof n=="function"?(a--,n):void 0,c&&xut(r[0],r[1],c)&&(n=a<3?void 0:n,a=1),e=Object(e);++s{var Qut=Hfe(),Tut=oAe(),Rut=Tut(function(t,e,r,s){Qut(t,e,r,s)});aAe.exports=Rut});var je={};Vt(je,{AsyncActions:()=>l3,BufferStream:()=>a3,CachingStrategy:()=>IAe,DefaultStream:()=>c3,allSettledSafe:()=>Uu,assertNever:()=>f3,bufferStream:()=>WE,buildIgnorePattern:()=>Uut,convertMapsToIndexableObjects:()=>Kk,dynamicRequire:()=>kp,escapeRegExp:()=>Nut,getArrayWithDefault:()=>jB,getFactoryWithDefault:()=>Vl,getMapWithDefault:()=>A3,getSetWithDefault:()=>xp,groupBy:()=>qut,isIndexableObject:()=>o3,isPathLike:()=>Hut,isTaggedYarnVersion:()=>Fut,makeDeferred:()=>mAe,mapAndFilter:()=>Yl,mapAndFind:()=>p0,mergeIntoTarget:()=>wAe,overrideType:()=>Out,parseBoolean:()=>qB,parseInt:()=>YE,parseOptionalBoolean:()=>CAe,plural:()=>Vk,prettifyAsyncErrors:()=>GE,prettifySyncErrors:()=>p3,releaseAfterUseAsync:()=>Mut,replaceEnvVariables:()=>Jk,sortMap:()=>Ws,toMerged:()=>jut,tryParseOptionalBoolean:()=>h3,validateEnum:()=>Lut});function Fut(t){return!!(hAe.default.valid(t)&&t.match(/^[^-]+(-rc\.[0-9]+)?$/))}function Vk(t,{one:e,more:r,zero:s=r}){return t===0?s:t===1?e:r}function Nut(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Out(t){}function f3(t){throw new Error(`Assertion failed: Unexpected object '${t}'`)}function Lut(t,e){let r=Object.values(t);if(!r.includes(e))throw new nt(`Invalid value for enumeration: ${JSON.stringify(e)} (expected one of ${r.map(s=>JSON.stringify(s)).join(", ")})`);return e}function Yl(t,e){let r=[];for(let s of t){let a=e(s);a!==gAe&&r.push(a)}return r}function p0(t,e){for(let r of t){let s=e(r);if(s!==dAe)return s}}function o3(t){return typeof t=="object"&&t!==null}async function Uu(t){let e=await Promise.allSettled(t),r=[];for(let s of e){if(s.status==="rejected")throw s.reason;r.push(s.value)}return r}function Kk(t){if(t instanceof Map&&(t=Object.fromEntries(t)),o3(t))for(let e of Object.keys(t)){let r=t[e];o3(r)&&(t[e]=Kk(r))}return t}function Vl(t,e,r){let s=t.get(e);return typeof s>"u"&&t.set(e,s=r()),s}function jB(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=[]),r}function xp(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=new Set),r}function A3(t,e){let r=t.get(e);return typeof r>"u"&&t.set(e,r=new Map),r}async function Mut(t,e){if(e==null)return await t();try{return await t()}finally{await e()}}async function GE(t,e){try{return await t()}catch(r){throw r.message=e(r.message),r}}function p3(t,e){try{return t()}catch(r){throw r.message=e(r.message),r}}async function WE(t){return await new Promise((e,r)=>{let s=[];t.on("error",a=>{r(a)}),t.on("data",a=>{s.push(a)}),t.on("end",()=>{e(Buffer.concat(s))})})}function mAe(){let t,e;return{promise:new Promise((s,a)=>{t=s,e=a}),resolve:t,reject:e}}function yAe(t){return HB(ue.fromPortablePath(t))}function EAe(path){let physicalPath=ue.fromPortablePath(path),currentCacheEntry=HB.cache[physicalPath];delete HB.cache[physicalPath];let result;try{result=yAe(physicalPath);let freshCacheEntry=HB.cache[physicalPath],dynamicModule=eval("module"),freshCacheIndex=dynamicModule.children.indexOf(freshCacheEntry);freshCacheIndex!==-1&&dynamicModule.children.splice(freshCacheIndex,1)}finally{HB.cache[physicalPath]=currentCacheEntry}return result}function _ut(t){let e=cAe.get(t),r=le.statSync(t);if(e?.mtime===r.mtimeMs)return e.instance;let s=EAe(t);return cAe.set(t,{mtime:r.mtimeMs,instance:s}),s}function kp(t,{cachingStrategy:e=2}={}){switch(e){case 0:return EAe(t);case 1:return _ut(t);case 2:return yAe(t);default:throw new Error("Unsupported caching strategy")}}function Ws(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function Uut(t){return t.length===0?null:t.map(e=>`(${AAe.default.makeRe(e,{windows:!1,dot:!0}).source})`).join("|")}function Jk(t,{env:e}){let r=/\${(?[\d\w_]+)(?:)?(?:-(?[^}]*))?}/g;return t.replace(r,(...s)=>{let{variableName:a,colon:n,fallback:c}=s[s.length-1],f=Object.hasOwn(e,a),p=e[a];if(p||f&&!n)return p;if(c!=null)return c;throw new nt(`Environment variable not found (${a})`)})}function qB(t){switch(t){case"true":case"1":case 1:case!0:return!0;case"false":case"0":case 0:case!1:return!1;default:throw new Error(`Couldn't parse "${t}" as a boolean`)}}function CAe(t){return typeof t>"u"?t:qB(t)}function h3(t){try{return CAe(t)}catch{return null}}function Hut(t){return!!(ue.isAbsolute(t)||t.match(/^(\.{1,2}|~)\//))}function wAe(t,...e){let r=c=>({value:c}),s=r(t),a=e.map(c=>r(c)),{value:n}=(0,fAe.default)(s,...a,(c,f)=>{if(Array.isArray(c)&&Array.isArray(f)){for(let p of f)c.find(h=>(0,uAe.default)(h,p))||c.push(p);return c}});return n}function jut(...t){return wAe({},...t)}function qut(t,e){let r=Object.create(null);for(let s of t){let a=s[e];r[a]??=[],r[a].push(s)}return r}function YE(t){return typeof t=="string"?Number.parseInt(t,10):t}var uAe,fAe,AAe,pAe,hAe,u3,gAe,dAe,a3,l3,c3,HB,cAe,IAe,kc=It(()=>{bt();Wt();uAe=et(Zue()),fAe=et(lAe()),AAe=et(Sa()),pAe=et(Md()),hAe=et(fi()),u3=ye("stream");gAe=Symbol();Yl.skip=gAe;dAe=Symbol();p0.skip=dAe;a3=class extends u3.Transform{constructor(){super(...arguments);this.chunks=[]}_transform(r,s,a){if(s!=="buffer"||!Buffer.isBuffer(r))throw new Error("Assertion failed: BufferStream only accept buffers");this.chunks.push(r),a(null,null)}_flush(r){r(null,Buffer.concat(this.chunks))}};l3=class{constructor(e){this.deferred=new Map;this.promises=new Map;this.limit=(0,pAe.default)(e)}set(e,r){let s=this.deferred.get(e);typeof s>"u"&&this.deferred.set(e,s=mAe());let a=this.limit(()=>r());return this.promises.set(e,a),a.then(()=>{this.promises.get(e)===a&&s.resolve()},n=>{this.promises.get(e)===a&&s.reject(n)}),s.promise}reduce(e,r){let s=this.promises.get(e)??Promise.resolve();this.set(e,()=>r(s))}async wait(){await Promise.all(this.promises.values())}},c3=class extends u3.Transform{constructor(r=Buffer.alloc(0)){super();this.active=!0;this.ifEmpty=r}_transform(r,s,a){if(s!=="buffer"||!Buffer.isBuffer(r))throw new Error("Assertion failed: DefaultStream only accept buffers");this.active=!1,a(null,r)}_flush(r){this.active&&this.ifEmpty.length>0?r(null,this.ifEmpty):r(null)}},HB=eval("require");cAe=new Map;IAe=(s=>(s[s.NoCache=0]="NoCache",s[s.FsTime=1]="FsTime",s[s.Node=2]="Node",s))(IAe||{})});var VE,g3,d3,BAe=It(()=>{VE=(r=>(r.HARD="HARD",r.SOFT="SOFT",r))(VE||{}),g3=(s=>(s.Dependency="Dependency",s.PeerDependency="PeerDependency",s.PeerDependencyMeta="PeerDependencyMeta",s))(g3||{}),d3=(s=>(s.Inactive="inactive",s.Redundant="redundant",s.Active="active",s))(d3||{})});var he={};Vt(he,{LogLevel:()=>eQ,Style:()=>Zk,Type:()=>Ct,addLogFilterSupport:()=>YB,applyColor:()=>po,applyHyperlink:()=>JE,applyStyle:()=>zd,json:()=>Zd,jsonOrPretty:()=>Yut,mark:()=>C3,pretty:()=>Ut,prettyField:()=>Zf,prettyList:()=>I3,prettyTruncatedLocatorList:()=>$k,stripAnsi:()=>KE.default,supportsColor:()=>Xk,supportsHyperlinks:()=>E3,tuple:()=>Hu});function vAe(t){let e=["KiB","MiB","GiB","TiB"],r=e.length;for(;r>1&&t<1024**r;)r-=1;let s=1024**r;return`${Math.floor(t*100/s)/100} ${e[r-1]}`}function Hu(t,e){return[e,t]}function zd(t,e,r){return t.get("enableColors")&&r&2&&(e=WB.default.bold(e)),e}function po(t,e,r){if(!t.get("enableColors"))return e;let s=Gut.get(r);if(s===null)return e;let a=typeof s>"u"?r:y3.level>=3?s[0]:s[1],n=typeof a=="number"?m3.ansi256(a):a.startsWith("#")?m3.hex(a):m3[a];if(typeof n!="function")throw new Error(`Invalid format type ${a}`);return n(e)}function JE(t,e,r){return t.get("enableHyperlinks")?Wut?`\x1B]8;;${r}\x1B\\${e}\x1B]8;;\x1B\\`:`\x1B]8;;${r}\x07${e}\x1B]8;;\x07`:e}function Ut(t,e,r){if(e===null)return po(t,"null",Ct.NULL);if(Object.hasOwn(zk,r))return zk[r].pretty(t,e);if(typeof e!="string")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof e}`);return po(t,e,r)}function I3(t,e,r,{separator:s=", "}={}){return[...e].map(a=>Ut(t,a,r)).join(s)}function Zd(t,e){if(t===null)return null;if(Object.hasOwn(zk,e))return zk[e].json(t);if(typeof t!="string")throw new Error(`Assertion failed: Expected the value to be a string, got ${typeof t}`);return t}function Yut(t,e,[r,s]){return t?Zd(r,s):Ut(e,r,s)}function C3(t){return{Check:po(t,"\u2713","green"),Cross:po(t,"\u2718","red"),Question:po(t,"?","cyan")}}function Zf(t,{label:e,value:[r,s]}){return`${Ut(t,e,Ct.CODE)}: ${Ut(t,r,s)}`}function $k(t,e,r){let s=[],a=[...e],n=r;for(;a.length>0;){let h=a[0],E=`${Yr(t,h)}, `,C=w3(h).length+2;if(s.length>0&&nh).join("").slice(0,-2);let c="X".repeat(a.length.toString().length),f=`and ${c} more.`,p=a.length;for(;s.length>1&&nh).join(""),f.replace(c,Ut(t,p,Ct.NUMBER))].join("")}function YB(t,{configuration:e}){let r=e.get("logFilters"),s=new Map,a=new Map,n=[];for(let C of r){let S=C.get("level");if(typeof S>"u")continue;let P=C.get("code");typeof P<"u"&&s.set(P,S);let I=C.get("text");typeof I<"u"&&a.set(I,S);let R=C.get("pattern");typeof R<"u"&&n.push([SAe.default.matcher(R,{contains:!0}),S])}n.reverse();let c=(C,S,P)=>{if(C===null||C===0)return P;let I=a.size>0||n.length>0?(0,KE.default)(S):S;if(a.size>0){let R=a.get(I);if(typeof R<"u")return R??P}if(n.length>0){for(let[R,N]of n)if(R(I))return N??P}if(s.size>0){let R=s.get(Vf(C));if(typeof R<"u")return R??P}return P},f=t.reportInfo,p=t.reportWarning,h=t.reportError,E=function(C,S,P,I){switch(c(S,P,I)){case"info":f.call(C,S,P);break;case"warning":p.call(C,S??0,P);break;case"error":h.call(C,S??0,P);break}};t.reportInfo=function(...C){return E(this,...C,"info")},t.reportWarning=function(...C){return E(this,...C,"warning")},t.reportError=function(...C){return E(this,...C,"error")}}var WB,GB,SAe,KE,DAe,Ct,Zk,y3,Xk,E3,m3,Gut,Wo,zk,Wut,eQ,Qc=It(()=>{bt();WB=et(g4()),GB=et(Nd());Wt();SAe=et(Sa()),KE=et(bk()),DAe=ye("util");nk();Yo();Ct={NO_HINT:"NO_HINT",ID:"ID",NULL:"NULL",SCOPE:"SCOPE",NAME:"NAME",RANGE:"RANGE",REFERENCE:"REFERENCE",NUMBER:"NUMBER",PATH:"PATH",URL:"URL",ADDED:"ADDED",REMOVED:"REMOVED",CODE:"CODE",INSPECT:"INSPECT",DURATION:"DURATION",SIZE:"SIZE",SIZE_DIFF:"SIZE_DIFF",IDENT:"IDENT",DESCRIPTOR:"DESCRIPTOR",LOCATOR:"LOCATOR",RESOLUTION:"RESOLUTION",DEPENDENT:"DEPENDENT",PACKAGE_EXTENSION:"PACKAGE_EXTENSION",SETTING:"SETTING",MARKDOWN:"MARKDOWN",MARKDOWN_INLINE:"MARKDOWN_INLINE"},Zk=(e=>(e[e.BOLD=2]="BOLD",e))(Zk||{}),y3=GB.default.GITHUB_ACTIONS?{level:2}:WB.default.supportsColor?{level:WB.default.supportsColor.level}:{level:0},Xk=y3.level!==0,E3=Xk&&!GB.default.GITHUB_ACTIONS&&!GB.default.CIRCLE&&!GB.default.GITLAB,m3=new WB.default.Instance(y3),Gut=new Map([[Ct.NO_HINT,null],[Ct.NULL,["#a853b5",129]],[Ct.SCOPE,["#d75f00",166]],[Ct.NAME,["#d7875f",173]],[Ct.RANGE,["#00afaf",37]],[Ct.REFERENCE,["#87afff",111]],[Ct.NUMBER,["#ffd700",220]],[Ct.PATH,["#d75fd7",170]],[Ct.URL,["#d75fd7",170]],[Ct.ADDED,["#5faf00",70]],[Ct.REMOVED,["#ff3131",160]],[Ct.CODE,["#87afff",111]],[Ct.SIZE,["#ffd700",220]]]),Wo=t=>t;zk={[Ct.ID]:Wo({pretty:(t,e)=>typeof e=="number"?po(t,`${e}`,Ct.NUMBER):po(t,e,Ct.CODE),json:t=>t}),[Ct.INSPECT]:Wo({pretty:(t,e)=>(0,DAe.inspect)(e,{depth:1/0,colors:t.get("enableColors"),compact:!0,breakLength:1/0}),json:t=>t}),[Ct.NUMBER]:Wo({pretty:(t,e)=>po(t,`${e}`,Ct.NUMBER),json:t=>t}),[Ct.IDENT]:Wo({pretty:(t,e)=>$i(t,e),json:t=>cn(t)}),[Ct.LOCATOR]:Wo({pretty:(t,e)=>Yr(t,e),json:t=>cl(t)}),[Ct.DESCRIPTOR]:Wo({pretty:(t,e)=>ri(t,e),json:t=>ll(t)}),[Ct.RESOLUTION]:Wo({pretty:(t,{descriptor:e,locator:r})=>VB(t,e,r),json:({descriptor:t,locator:e})=>({descriptor:ll(t),locator:e!==null?cl(e):null})}),[Ct.DEPENDENT]:Wo({pretty:(t,{locator:e,descriptor:r})=>B3(t,e,r),json:({locator:t,descriptor:e})=>({locator:cl(t),descriptor:ll(e)})}),[Ct.PACKAGE_EXTENSION]:Wo({pretty:(t,e)=>{switch(e.type){case"Dependency":return`${$i(t,e.parentDescriptor)} \u27A4 ${po(t,"dependencies",Ct.CODE)} \u27A4 ${$i(t,e.descriptor)}`;case"PeerDependency":return`${$i(t,e.parentDescriptor)} \u27A4 ${po(t,"peerDependencies",Ct.CODE)} \u27A4 ${$i(t,e.descriptor)}`;case"PeerDependencyMeta":return`${$i(t,e.parentDescriptor)} \u27A4 ${po(t,"peerDependenciesMeta",Ct.CODE)} \u27A4 ${$i(t,Da(e.selector))} \u27A4 ${po(t,e.key,Ct.CODE)}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${e.type}`)}},json:t=>{switch(t.type){case"Dependency":return`${cn(t.parentDescriptor)} > ${cn(t.descriptor)}`;case"PeerDependency":return`${cn(t.parentDescriptor)} >> ${cn(t.descriptor)}`;case"PeerDependencyMeta":return`${cn(t.parentDescriptor)} >> ${t.selector} / ${t.key}`;default:throw new Error(`Assertion failed: Unsupported package extension type: ${t.type}`)}}}),[Ct.SETTING]:Wo({pretty:(t,e)=>(t.get(e),JE(t,po(t,e,Ct.CODE),`https://yarnpkg.com/configuration/yarnrc#${e}`)),json:t=>t}),[Ct.DURATION]:Wo({pretty:(t,e)=>{if(e>1e3*60){let r=Math.floor(e/1e3/60),s=Math.ceil((e-r*60*1e3)/1e3);return s===0?`${r}m`:`${r}m ${s}s`}else{let r=Math.floor(e/1e3),s=e-r*1e3;return s===0?`${r}s`:`${r}s ${s}ms`}},json:t=>t}),[Ct.SIZE]:Wo({pretty:(t,e)=>po(t,vAe(e),Ct.NUMBER),json:t=>t}),[Ct.SIZE_DIFF]:Wo({pretty:(t,e)=>{let r=e>=0?"+":"-",s=r==="+"?Ct.REMOVED:Ct.ADDED;return po(t,`${r} ${vAe(Math.max(Math.abs(e),1))}`,s)},json:t=>t}),[Ct.PATH]:Wo({pretty:(t,e)=>po(t,ue.fromPortablePath(e),Ct.PATH),json:t=>ue.fromPortablePath(t)}),[Ct.MARKDOWN]:Wo({pretty:(t,{text:e,format:r,paragraphs:s})=>qo(e,{format:r,paragraphs:s}),json:({text:t})=>t}),[Ct.MARKDOWN_INLINE]:Wo({pretty:(t,e)=>(e=e.replace(/(`+)((?:.|[\n])*?)\1/g,(r,s,a)=>Ut(t,s+a+s,Ct.CODE)),e=e.replace(/(\*\*)((?:.|[\n])*?)\1/g,(r,s,a)=>zd(t,a,2)),e),json:t=>t})};Wut=!!process.env.KONSOLE_VERSION;eQ=(a=>(a.Error="error",a.Warning="warning",a.Info="info",a.Discard="discard",a))(eQ||{})});var bAe=L(zE=>{"use strict";Object.defineProperty(zE,"__esModule",{value:!0});zE.splitWhen=zE.flatten=void 0;function Vut(t){return t.reduce((e,r)=>[].concat(e,r),[])}zE.flatten=Vut;function Kut(t,e){let r=[[]],s=0;for(let a of t)e(a)?(s++,r[s]=[]):r[s].push(a);return r}zE.splitWhen=Kut});var PAe=L(tQ=>{"use strict";Object.defineProperty(tQ,"__esModule",{value:!0});tQ.isEnoentCodeError=void 0;function Jut(t){return t.code==="ENOENT"}tQ.isEnoentCodeError=Jut});var xAe=L(rQ=>{"use strict";Object.defineProperty(rQ,"__esModule",{value:!0});rQ.createDirentFromStats=void 0;var v3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function zut(t,e){return new v3(t,e)}rQ.createDirentFromStats=zut});var RAe=L(cs=>{"use strict";Object.defineProperty(cs,"__esModule",{value:!0});cs.convertPosixPathToPattern=cs.convertWindowsPathToPattern=cs.convertPathToPattern=cs.escapePosixPath=cs.escapeWindowsPath=cs.escape=cs.removeLeadingDotSegment=cs.makeAbsolute=cs.unixify=void 0;var Zut=ye("os"),Xut=ye("path"),kAe=Zut.platform()==="win32",$ut=2,eft=/(\\?)([()*?[\]{|}]|^!|[!+@](?=\()|\\(?![!()*+?@[\]{|}]))/g,tft=/(\\?)([()[\]{}]|^!|[!+@](?=\())/g,rft=/^\\\\([.?])/,nft=/\\(?![!()+@[\]{}])/g;function ift(t){return t.replace(/\\/g,"/")}cs.unixify=ift;function sft(t,e){return Xut.resolve(t,e)}cs.makeAbsolute=sft;function oft(t){if(t.charAt(0)==="."){let e=t.charAt(1);if(e==="/"||e==="\\")return t.slice($ut)}return t}cs.removeLeadingDotSegment=oft;cs.escape=kAe?S3:D3;function S3(t){return t.replace(tft,"\\$2")}cs.escapeWindowsPath=S3;function D3(t){return t.replace(eft,"\\$2")}cs.escapePosixPath=D3;cs.convertPathToPattern=kAe?QAe:TAe;function QAe(t){return S3(t).replace(rft,"//$1").replace(nft,"/")}cs.convertWindowsPathToPattern=QAe;function TAe(t){return D3(t)}cs.convertPosixPathToPattern=TAe});var NAe=L((RKt,FAe)=>{FAe.exports=function(e){if(typeof e!="string"||e==="")return!1;for(var r;r=/(\\).|([@?!+*]\(.*\))/g.exec(e);){if(r[2])return!0;e=e.slice(r.index+r[0].length)}return!1}});var MAe=L((FKt,LAe)=>{var aft=NAe(),OAe={"{":"}","(":")","[":"]"},lft=function(t){if(t[0]==="!")return!0;for(var e=0,r=-2,s=-2,a=-2,n=-2,c=-2;ee&&(c===-1||c>s||(c=t.indexOf("\\",e),c===-1||c>s)))||a!==-1&&t[e]==="{"&&t[e+1]!=="}"&&(a=t.indexOf("}",e),a>e&&(c=t.indexOf("\\",e),c===-1||c>a))||n!==-1&&t[e]==="("&&t[e+1]==="?"&&/[:!=]/.test(t[e+2])&&t[e+3]!==")"&&(n=t.indexOf(")",e),n>e&&(c=t.indexOf("\\",e),c===-1||c>n))||r!==-1&&t[e]==="("&&t[e+1]!=="|"&&(rr&&(c=t.indexOf("\\",r),c===-1||c>n))))return!0;if(t[e]==="\\"){var f=t[e+1];e+=2;var p=OAe[f];if(p){var h=t.indexOf(p,e);h!==-1&&(e=h+1)}if(t[e]==="!")return!0}else e++}return!1},cft=function(t){if(t[0]==="!")return!0;for(var e=0;e{"use strict";var uft=MAe(),fft=ye("path").posix.dirname,Aft=ye("os").platform()==="win32",b3="/",pft=/\\/g,hft=/[\{\[].*[\}\]]$/,gft=/(^|[^\\])([\{\[]|\([^\)]+$)/,dft=/\\([\!\*\?\|\[\]\(\)\{\}])/g;_Ae.exports=function(e,r){var s=Object.assign({flipBackslashes:!0},r);s.flipBackslashes&&Aft&&e.indexOf(b3)<0&&(e=e.replace(pft,b3)),hft.test(e)&&(e+=b3),e+="a";do e=fft(e);while(uft(e)||gft.test(e));return e.replace(dft,"$1")}});var KAe=L(jr=>{"use strict";Object.defineProperty(jr,"__esModule",{value:!0});jr.removeDuplicateSlashes=jr.matchAny=jr.convertPatternsToRe=jr.makeRe=jr.getPatternParts=jr.expandBraceExpansion=jr.expandPatternsWithBraceExpansion=jr.isAffectDepthOfReadingPattern=jr.endsWithSlashGlobStar=jr.hasGlobStar=jr.getBaseDirectory=jr.isPatternRelatedToParentDirectory=jr.getPatternsOutsideCurrentDirectory=jr.getPatternsInsideCurrentDirectory=jr.getPositivePatterns=jr.getNegativePatterns=jr.isPositivePattern=jr.isNegativePattern=jr.convertToNegativePattern=jr.convertToPositivePattern=jr.isDynamicPattern=jr.isStaticPattern=void 0;var mft=ye("path"),yft=UAe(),P3=Sa(),HAe="**",Eft="\\",Ift=/[*?]|^!/,Cft=/\[[^[]*]/,wft=/(?:^|[^!*+?@])\([^(]*\|[^|]*\)/,Bft=/[!*+?@]\([^(]*\)/,vft=/,|\.\./,Sft=/(?!^)\/{2,}/g;function jAe(t,e={}){return!qAe(t,e)}jr.isStaticPattern=jAe;function qAe(t,e={}){return t===""?!1:!!(e.caseSensitiveMatch===!1||t.includes(Eft)||Ift.test(t)||Cft.test(t)||wft.test(t)||e.extglob!==!1&&Bft.test(t)||e.braceExpansion!==!1&&Dft(t))}jr.isDynamicPattern=qAe;function Dft(t){let e=t.indexOf("{");if(e===-1)return!1;let r=t.indexOf("}",e+1);if(r===-1)return!1;let s=t.slice(e,r);return vft.test(s)}function bft(t){return nQ(t)?t.slice(1):t}jr.convertToPositivePattern=bft;function Pft(t){return"!"+t}jr.convertToNegativePattern=Pft;function nQ(t){return t.startsWith("!")&&t[1]!=="("}jr.isNegativePattern=nQ;function GAe(t){return!nQ(t)}jr.isPositivePattern=GAe;function xft(t){return t.filter(nQ)}jr.getNegativePatterns=xft;function kft(t){return t.filter(GAe)}jr.getPositivePatterns=kft;function Qft(t){return t.filter(e=>!x3(e))}jr.getPatternsInsideCurrentDirectory=Qft;function Tft(t){return t.filter(x3)}jr.getPatternsOutsideCurrentDirectory=Tft;function x3(t){return t.startsWith("..")||t.startsWith("./..")}jr.isPatternRelatedToParentDirectory=x3;function Rft(t){return yft(t,{flipBackslashes:!1})}jr.getBaseDirectory=Rft;function Fft(t){return t.includes(HAe)}jr.hasGlobStar=Fft;function WAe(t){return t.endsWith("/"+HAe)}jr.endsWithSlashGlobStar=WAe;function Nft(t){let e=mft.basename(t);return WAe(t)||jAe(e)}jr.isAffectDepthOfReadingPattern=Nft;function Oft(t){return t.reduce((e,r)=>e.concat(YAe(r)),[])}jr.expandPatternsWithBraceExpansion=Oft;function YAe(t){let e=P3.braces(t,{expand:!0,nodupes:!0,keepEscaping:!0});return e.sort((r,s)=>r.length-s.length),e.filter(r=>r!=="")}jr.expandBraceExpansion=YAe;function Lft(t,e){let{parts:r}=P3.scan(t,Object.assign(Object.assign({},e),{parts:!0}));return r.length===0&&(r=[t]),r[0].startsWith("/")&&(r[0]=r[0].slice(1),r.unshift("")),r}jr.getPatternParts=Lft;function VAe(t,e){return P3.makeRe(t,e)}jr.makeRe=VAe;function Mft(t,e){return t.map(r=>VAe(r,e))}jr.convertPatternsToRe=Mft;function _ft(t,e){return e.some(r=>r.test(t))}jr.matchAny=_ft;function Uft(t){return t.replace(Sft,"/")}jr.removeDuplicateSlashes=Uft});var XAe=L((LKt,ZAe)=>{"use strict";var Hft=ye("stream"),JAe=Hft.PassThrough,jft=Array.prototype.slice;ZAe.exports=qft;function qft(){let t=[],e=jft.call(arguments),r=!1,s=e[e.length-1];s&&!Array.isArray(s)&&s.pipe==null?e.pop():s={};let a=s.end!==!1,n=s.pipeError===!0;s.objectMode==null&&(s.objectMode=!0),s.highWaterMark==null&&(s.highWaterMark=64*1024);let c=JAe(s);function f(){for(let E=0,C=arguments.length;E0||(r=!1,p())}function P(I){function R(){I.removeListener("merge2UnpipeEnd",R),I.removeListener("end",R),n&&I.removeListener("error",N),S()}function N(U){c.emit("error",U)}if(I._readableState.endEmitted)return S();I.on("merge2UnpipeEnd",R),I.on("end",R),n&&I.on("error",N),I.pipe(c,{end:!1}),I.resume()}for(let I=0;I{"use strict";Object.defineProperty(iQ,"__esModule",{value:!0});iQ.merge=void 0;var Gft=XAe();function Wft(t){let e=Gft(t);return t.forEach(r=>{r.once("error",s=>e.emit("error",s))}),e.once("close",()=>$Ae(t)),e.once("end",()=>$Ae(t)),e}iQ.merge=Wft;function $Ae(t){t.forEach(e=>e.emit("close"))}});var tpe=L(ZE=>{"use strict";Object.defineProperty(ZE,"__esModule",{value:!0});ZE.isEmpty=ZE.isString=void 0;function Yft(t){return typeof t=="string"}ZE.isString=Yft;function Vft(t){return t===""}ZE.isEmpty=Vft});var Qp=L(Vo=>{"use strict";Object.defineProperty(Vo,"__esModule",{value:!0});Vo.string=Vo.stream=Vo.pattern=Vo.path=Vo.fs=Vo.errno=Vo.array=void 0;var Kft=bAe();Vo.array=Kft;var Jft=PAe();Vo.errno=Jft;var zft=xAe();Vo.fs=zft;var Zft=RAe();Vo.path=Zft;var Xft=KAe();Vo.pattern=Xft;var $ft=epe();Vo.stream=$ft;var eAt=tpe();Vo.string=eAt});var spe=L(Ko=>{"use strict";Object.defineProperty(Ko,"__esModule",{value:!0});Ko.convertPatternGroupToTask=Ko.convertPatternGroupsToTasks=Ko.groupPatternsByBaseDirectory=Ko.getNegativePatternsAsPositive=Ko.getPositivePatterns=Ko.convertPatternsToTasks=Ko.generate=void 0;var ju=Qp();function tAt(t,e){let r=rpe(t,e),s=rpe(e.ignore,e),a=npe(r),n=ipe(r,s),c=a.filter(E=>ju.pattern.isStaticPattern(E,e)),f=a.filter(E=>ju.pattern.isDynamicPattern(E,e)),p=k3(c,n,!1),h=k3(f,n,!0);return p.concat(h)}Ko.generate=tAt;function rpe(t,e){let r=t;return e.braceExpansion&&(r=ju.pattern.expandPatternsWithBraceExpansion(r)),e.baseNameMatch&&(r=r.map(s=>s.includes("/")?s:`**/${s}`)),r.map(s=>ju.pattern.removeDuplicateSlashes(s))}function k3(t,e,r){let s=[],a=ju.pattern.getPatternsOutsideCurrentDirectory(t),n=ju.pattern.getPatternsInsideCurrentDirectory(t),c=Q3(a),f=Q3(n);return s.push(...T3(c,e,r)),"."in f?s.push(R3(".",n,e,r)):s.push(...T3(f,e,r)),s}Ko.convertPatternsToTasks=k3;function npe(t){return ju.pattern.getPositivePatterns(t)}Ko.getPositivePatterns=npe;function ipe(t,e){return ju.pattern.getNegativePatterns(t).concat(e).map(ju.pattern.convertToPositivePattern)}Ko.getNegativePatternsAsPositive=ipe;function Q3(t){let e={};return t.reduce((r,s)=>{let a=ju.pattern.getBaseDirectory(s);return a in r?r[a].push(s):r[a]=[s],r},e)}Ko.groupPatternsByBaseDirectory=Q3;function T3(t,e,r){return Object.keys(t).map(s=>R3(s,t[s],e,r))}Ko.convertPatternGroupsToTasks=T3;function R3(t,e,r,s){return{dynamic:s,positive:e,negative:r,base:t,patterns:[].concat(e,r.map(ju.pattern.convertToNegativePattern))}}Ko.convertPatternGroupToTask=R3});var ape=L(sQ=>{"use strict";Object.defineProperty(sQ,"__esModule",{value:!0});sQ.read=void 0;function rAt(t,e,r){e.fs.lstat(t,(s,a)=>{if(s!==null){ope(r,s);return}if(!a.isSymbolicLink()||!e.followSymbolicLink){F3(r,a);return}e.fs.stat(t,(n,c)=>{if(n!==null){if(e.throwErrorOnBrokenSymbolicLink){ope(r,n);return}F3(r,a);return}e.markSymbolicLink&&(c.isSymbolicLink=()=>!0),F3(r,c)})})}sQ.read=rAt;function ope(t,e){t(e)}function F3(t,e){t(null,e)}});var lpe=L(oQ=>{"use strict";Object.defineProperty(oQ,"__esModule",{value:!0});oQ.read=void 0;function nAt(t,e){let r=e.fs.lstatSync(t);if(!r.isSymbolicLink()||!e.followSymbolicLink)return r;try{let s=e.fs.statSync(t);return e.markSymbolicLink&&(s.isSymbolicLink=()=>!0),s}catch(s){if(!e.throwErrorOnBrokenSymbolicLink)return r;throw s}}oQ.read=nAt});var cpe=L(h0=>{"use strict";Object.defineProperty(h0,"__esModule",{value:!0});h0.createFileSystemAdapter=h0.FILE_SYSTEM_ADAPTER=void 0;var aQ=ye("fs");h0.FILE_SYSTEM_ADAPTER={lstat:aQ.lstat,stat:aQ.stat,lstatSync:aQ.lstatSync,statSync:aQ.statSync};function iAt(t){return t===void 0?h0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},h0.FILE_SYSTEM_ADAPTER),t)}h0.createFileSystemAdapter=iAt});var upe=L(O3=>{"use strict";Object.defineProperty(O3,"__esModule",{value:!0});var sAt=cpe(),N3=class{constructor(e={}){this._options=e,this.followSymbolicLink=this._getValue(this._options.followSymbolicLink,!0),this.fs=sAt.createFileSystemAdapter(this._options.fs),this.markSymbolicLink=this._getValue(this._options.markSymbolicLink,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0)}_getValue(e,r){return e??r}};O3.default=N3});var Xd=L(g0=>{"use strict";Object.defineProperty(g0,"__esModule",{value:!0});g0.statSync=g0.stat=g0.Settings=void 0;var fpe=ape(),oAt=lpe(),L3=upe();g0.Settings=L3.default;function aAt(t,e,r){if(typeof e=="function"){fpe.read(t,M3(),e);return}fpe.read(t,M3(e),r)}g0.stat=aAt;function lAt(t,e){let r=M3(e);return oAt.read(t,r)}g0.statSync=lAt;function M3(t={}){return t instanceof L3.default?t:new L3.default(t)}});var hpe=L((VKt,ppe)=>{var Ape;ppe.exports=typeof queueMicrotask=="function"?queueMicrotask.bind(typeof window<"u"?window:global):t=>(Ape||(Ape=Promise.resolve())).then(t).catch(e=>setTimeout(()=>{throw e},0))});var dpe=L((KKt,gpe)=>{gpe.exports=uAt;var cAt=hpe();function uAt(t,e){let r,s,a,n=!0;Array.isArray(t)?(r=[],s=t.length):(a=Object.keys(t),r={},s=a.length);function c(p){function h(){e&&e(p,r),e=null}n?cAt(h):h()}function f(p,h,E){r[p]=E,(--s===0||h)&&c(h)}s?a?a.forEach(function(p){t[p](function(h,E){f(p,h,E)})}):t.forEach(function(p,h){p(function(E,C){f(h,E,C)})}):c(null),n=!1}});var _3=L(cQ=>{"use strict";Object.defineProperty(cQ,"__esModule",{value:!0});cQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=void 0;var lQ=process.versions.node.split(".");if(lQ[0]===void 0||lQ[1]===void 0)throw new Error(`Unexpected behavior. The 'process.versions.node' variable has invalid value: ${process.versions.node}`);var mpe=Number.parseInt(lQ[0],10),fAt=Number.parseInt(lQ[1],10),ype=10,AAt=10,pAt=mpe>ype,hAt=mpe===ype&&fAt>=AAt;cQ.IS_SUPPORT_READDIR_WITH_FILE_TYPES=pAt||hAt});var Epe=L(uQ=>{"use strict";Object.defineProperty(uQ,"__esModule",{value:!0});uQ.createDirentFromStats=void 0;var U3=class{constructor(e,r){this.name=e,this.isBlockDevice=r.isBlockDevice.bind(r),this.isCharacterDevice=r.isCharacterDevice.bind(r),this.isDirectory=r.isDirectory.bind(r),this.isFIFO=r.isFIFO.bind(r),this.isFile=r.isFile.bind(r),this.isSocket=r.isSocket.bind(r),this.isSymbolicLink=r.isSymbolicLink.bind(r)}};function gAt(t,e){return new U3(t,e)}uQ.createDirentFromStats=gAt});var H3=L(fQ=>{"use strict";Object.defineProperty(fQ,"__esModule",{value:!0});fQ.fs=void 0;var dAt=Epe();fQ.fs=dAt});var j3=L(AQ=>{"use strict";Object.defineProperty(AQ,"__esModule",{value:!0});AQ.joinPathSegments=void 0;function mAt(t,e,r){return t.endsWith(r)?t+e:t+r+e}AQ.joinPathSegments=mAt});var Spe=L(d0=>{"use strict";Object.defineProperty(d0,"__esModule",{value:!0});d0.readdir=d0.readdirWithFileTypes=d0.read=void 0;var yAt=Xd(),Ipe=dpe(),EAt=_3(),Cpe=H3(),wpe=j3();function IAt(t,e,r){if(!e.stats&&EAt.IS_SUPPORT_READDIR_WITH_FILE_TYPES){Bpe(t,e,r);return}vpe(t,e,r)}d0.read=IAt;function Bpe(t,e,r){e.fs.readdir(t,{withFileTypes:!0},(s,a)=>{if(s!==null){pQ(r,s);return}let n=a.map(f=>({dirent:f,name:f.name,path:wpe.joinPathSegments(t,f.name,e.pathSegmentSeparator)}));if(!e.followSymbolicLinks){q3(r,n);return}let c=n.map(f=>CAt(f,e));Ipe(c,(f,p)=>{if(f!==null){pQ(r,f);return}q3(r,p)})})}d0.readdirWithFileTypes=Bpe;function CAt(t,e){return r=>{if(!t.dirent.isSymbolicLink()){r(null,t);return}e.fs.stat(t.path,(s,a)=>{if(s!==null){if(e.throwErrorOnBrokenSymbolicLink){r(s);return}r(null,t);return}t.dirent=Cpe.fs.createDirentFromStats(t.name,a),r(null,t)})}}function vpe(t,e,r){e.fs.readdir(t,(s,a)=>{if(s!==null){pQ(r,s);return}let n=a.map(c=>{let f=wpe.joinPathSegments(t,c,e.pathSegmentSeparator);return p=>{yAt.stat(f,e.fsStatSettings,(h,E)=>{if(h!==null){p(h);return}let C={name:c,path:f,dirent:Cpe.fs.createDirentFromStats(c,E)};e.stats&&(C.stats=E),p(null,C)})}});Ipe(n,(c,f)=>{if(c!==null){pQ(r,c);return}q3(r,f)})})}d0.readdir=vpe;function pQ(t,e){t(e)}function q3(t,e){t(null,e)}});var kpe=L(m0=>{"use strict";Object.defineProperty(m0,"__esModule",{value:!0});m0.readdir=m0.readdirWithFileTypes=m0.read=void 0;var wAt=Xd(),BAt=_3(),Dpe=H3(),bpe=j3();function vAt(t,e){return!e.stats&&BAt.IS_SUPPORT_READDIR_WITH_FILE_TYPES?Ppe(t,e):xpe(t,e)}m0.read=vAt;function Ppe(t,e){return e.fs.readdirSync(t,{withFileTypes:!0}).map(s=>{let a={dirent:s,name:s.name,path:bpe.joinPathSegments(t,s.name,e.pathSegmentSeparator)};if(a.dirent.isSymbolicLink()&&e.followSymbolicLinks)try{let n=e.fs.statSync(a.path);a.dirent=Dpe.fs.createDirentFromStats(a.name,n)}catch(n){if(e.throwErrorOnBrokenSymbolicLink)throw n}return a})}m0.readdirWithFileTypes=Ppe;function xpe(t,e){return e.fs.readdirSync(t).map(s=>{let a=bpe.joinPathSegments(t,s,e.pathSegmentSeparator),n=wAt.statSync(a,e.fsStatSettings),c={name:s,path:a,dirent:Dpe.fs.createDirentFromStats(s,n)};return e.stats&&(c.stats=n),c})}m0.readdir=xpe});var Qpe=L(y0=>{"use strict";Object.defineProperty(y0,"__esModule",{value:!0});y0.createFileSystemAdapter=y0.FILE_SYSTEM_ADAPTER=void 0;var XE=ye("fs");y0.FILE_SYSTEM_ADAPTER={lstat:XE.lstat,stat:XE.stat,lstatSync:XE.lstatSync,statSync:XE.statSync,readdir:XE.readdir,readdirSync:XE.readdirSync};function SAt(t){return t===void 0?y0.FILE_SYSTEM_ADAPTER:Object.assign(Object.assign({},y0.FILE_SYSTEM_ADAPTER),t)}y0.createFileSystemAdapter=SAt});var Tpe=L(W3=>{"use strict";Object.defineProperty(W3,"__esModule",{value:!0});var DAt=ye("path"),bAt=Xd(),PAt=Qpe(),G3=class{constructor(e={}){this._options=e,this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!1),this.fs=PAt.createFileSystemAdapter(this._options.fs),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,DAt.sep),this.stats=this._getValue(this._options.stats,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!0),this.fsStatSettings=new bAt.Settings({followSymbolicLink:this.followSymbolicLinks,fs:this.fs,throwErrorOnBrokenSymbolicLink:this.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};W3.default=G3});var hQ=L(E0=>{"use strict";Object.defineProperty(E0,"__esModule",{value:!0});E0.Settings=E0.scandirSync=E0.scandir=void 0;var Rpe=Spe(),xAt=kpe(),Y3=Tpe();E0.Settings=Y3.default;function kAt(t,e,r){if(typeof e=="function"){Rpe.read(t,V3(),e);return}Rpe.read(t,V3(e),r)}E0.scandir=kAt;function QAt(t,e){let r=V3(e);return xAt.read(t,r)}E0.scandirSync=QAt;function V3(t={}){return t instanceof Y3.default?t:new Y3.default(t)}});var Npe=L((iJt,Fpe)=>{"use strict";function TAt(t){var e=new t,r=e;function s(){var n=e;return n.next?e=n.next:(e=new t,r=e),n.next=null,n}function a(n){r.next=n,r=n}return{get:s,release:a}}Fpe.exports=TAt});var Lpe=L((sJt,K3)=>{"use strict";var RAt=Npe();function Ope(t,e,r){if(typeof t=="function"&&(r=e,e=t,t=null),!(r>=1))throw new Error("fastqueue concurrency must be equal to or greater than 1");var s=RAt(FAt),a=null,n=null,c=0,f=null,p={push:R,drain:Tc,saturated:Tc,pause:E,paused:!1,get concurrency(){return r},set concurrency(Ae){if(!(Ae>=1))throw new Error("fastqueue concurrency must be equal to or greater than 1");if(r=Ae,!p.paused)for(;a&&c=r||p.paused?n?(n.next=me,n=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function N(Ae,ce){var me=s.get();me.context=t,me.release=U,me.value=Ae,me.callback=ce||Tc,me.errorHandler=f,c>=r||p.paused?a?(me.next=a,a=me):(a=me,n=me,p.saturated()):(c++,e.call(t,me.value,me.worked))}function U(Ae){Ae&&s.release(Ae);var ce=a;ce&&c<=r?p.paused?c--:(n===a&&(n=null),a=ce.next,ce.next=null,e.call(t,ce.value,ce.worked),n===null&&p.empty()):--c===0&&p.drain()}function W(){a=null,n=null,p.drain=Tc}function te(){a=null,n=null,p.drain(),p.drain=Tc}function ie(Ae){f=Ae}}function Tc(){}function FAt(){this.value=null,this.callback=Tc,this.next=null,this.release=Tc,this.context=null,this.errorHandler=null;var t=this;this.worked=function(r,s){var a=t.callback,n=t.errorHandler,c=t.value;t.value=null,t.callback=Tc,t.errorHandler&&n(r,c),a.call(t.context,r,s),t.release(t)}}function NAt(t,e,r){typeof t=="function"&&(r=e,e=t,t=null);function s(E,C){e.call(this,E).then(function(S){C(null,S)},C)}var a=Ope(t,s,r),n=a.push,c=a.unshift;return a.push=f,a.unshift=p,a.drained=h,a;function f(E){var C=new Promise(function(S,P){n(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(Tc),C}function p(E){var C=new Promise(function(S,P){c(E,function(I,R){if(I){P(I);return}S(R)})});return C.catch(Tc),C}function h(){if(a.idle())return new Promise(function(S){S()});var E=a.drain,C=new Promise(function(S){a.drain=function(){E(),S()}});return C}}K3.exports=Ope;K3.exports.promise=NAt});var gQ=L(Xf=>{"use strict";Object.defineProperty(Xf,"__esModule",{value:!0});Xf.joinPathSegments=Xf.replacePathSegmentSeparator=Xf.isAppliedFilter=Xf.isFatalError=void 0;function OAt(t,e){return t.errorFilter===null?!0:!t.errorFilter(e)}Xf.isFatalError=OAt;function LAt(t,e){return t===null||t(e)}Xf.isAppliedFilter=LAt;function MAt(t,e){return t.split(/[/\\]/).join(e)}Xf.replacePathSegmentSeparator=MAt;function _At(t,e,r){return t===""?e:t.endsWith(r)?t+e:t+r+e}Xf.joinPathSegments=_At});var Z3=L(z3=>{"use strict";Object.defineProperty(z3,"__esModule",{value:!0});var UAt=gQ(),J3=class{constructor(e,r){this._root=e,this._settings=r,this._root=UAt.replacePathSegmentSeparator(e,r.pathSegmentSeparator)}};z3.default=J3});var e8=L($3=>{"use strict";Object.defineProperty($3,"__esModule",{value:!0});var HAt=ye("events"),jAt=hQ(),qAt=Lpe(),dQ=gQ(),GAt=Z3(),X3=class extends GAt.default{constructor(e,r){super(e,r),this._settings=r,this._scandir=jAt.scandir,this._emitter=new HAt.EventEmitter,this._queue=qAt(this._worker.bind(this),this._settings.concurrency),this._isFatalError=!1,this._isDestroyed=!1,this._queue.drain=()=>{this._isFatalError||this._emitter.emit("end")}}read(){return this._isFatalError=!1,this._isDestroyed=!1,setImmediate(()=>{this._pushToQueue(this._root,this._settings.basePath)}),this._emitter}get isDestroyed(){return this._isDestroyed}destroy(){if(this._isDestroyed)throw new Error("The reader is already destroyed");this._isDestroyed=!0,this._queue.killAndDrain()}onEntry(e){this._emitter.on("entry",e)}onError(e){this._emitter.once("error",e)}onEnd(e){this._emitter.once("end",e)}_pushToQueue(e,r){let s={directory:e,base:r};this._queue.push(s,a=>{a!==null&&this._handleError(a)})}_worker(e,r){this._scandir(e.directory,this._settings.fsScandirSettings,(s,a)=>{if(s!==null){r(s,void 0);return}for(let n of a)this._handleEntry(n,e.base);r(null,void 0)})}_handleError(e){this._isDestroyed||!dQ.isFatalError(this._settings,e)||(this._isFatalError=!0,this._isDestroyed=!0,this._emitter.emit("error",e))}_handleEntry(e,r){if(this._isDestroyed||this._isFatalError)return;let s=e.path;r!==void 0&&(e.path=dQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),dQ.isAppliedFilter(this._settings.entryFilter,e)&&this._emitEntry(e),e.dirent.isDirectory()&&dQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_emitEntry(e){this._emitter.emit("entry",e)}};$3.default=X3});var Mpe=L(r8=>{"use strict";Object.defineProperty(r8,"__esModule",{value:!0});var WAt=e8(),t8=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new WAt.default(this._root,this._settings),this._storage=[]}read(e){this._reader.onError(r=>{YAt(e,r)}),this._reader.onEntry(r=>{this._storage.push(r)}),this._reader.onEnd(()=>{VAt(e,this._storage)}),this._reader.read()}};r8.default=t8;function YAt(t,e){t(e)}function VAt(t,e){t(null,e)}});var _pe=L(i8=>{"use strict";Object.defineProperty(i8,"__esModule",{value:!0});var KAt=ye("stream"),JAt=e8(),n8=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new JAt.default(this._root,this._settings),this._stream=new KAt.Readable({objectMode:!0,read:()=>{},destroy:()=>{this._reader.isDestroyed||this._reader.destroy()}})}read(){return this._reader.onError(e=>{this._stream.emit("error",e)}),this._reader.onEntry(e=>{this._stream.push(e)}),this._reader.onEnd(()=>{this._stream.push(null)}),this._reader.read(),this._stream}};i8.default=n8});var Upe=L(o8=>{"use strict";Object.defineProperty(o8,"__esModule",{value:!0});var zAt=hQ(),mQ=gQ(),ZAt=Z3(),s8=class extends ZAt.default{constructor(){super(...arguments),this._scandir=zAt.scandirSync,this._storage=[],this._queue=new Set}read(){return this._pushToQueue(this._root,this._settings.basePath),this._handleQueue(),this._storage}_pushToQueue(e,r){this._queue.add({directory:e,base:r})}_handleQueue(){for(let e of this._queue.values())this._handleDirectory(e.directory,e.base)}_handleDirectory(e,r){try{let s=this._scandir(e,this._settings.fsScandirSettings);for(let a of s)this._handleEntry(a,r)}catch(s){this._handleError(s)}}_handleError(e){if(mQ.isFatalError(this._settings,e))throw e}_handleEntry(e,r){let s=e.path;r!==void 0&&(e.path=mQ.joinPathSegments(r,e.name,this._settings.pathSegmentSeparator)),mQ.isAppliedFilter(this._settings.entryFilter,e)&&this._pushToStorage(e),e.dirent.isDirectory()&&mQ.isAppliedFilter(this._settings.deepFilter,e)&&this._pushToQueue(s,r===void 0?void 0:e.path)}_pushToStorage(e){this._storage.push(e)}};o8.default=s8});var Hpe=L(l8=>{"use strict";Object.defineProperty(l8,"__esModule",{value:!0});var XAt=Upe(),a8=class{constructor(e,r){this._root=e,this._settings=r,this._reader=new XAt.default(this._root,this._settings)}read(){return this._reader.read()}};l8.default=a8});var jpe=L(u8=>{"use strict";Object.defineProperty(u8,"__esModule",{value:!0});var $At=ye("path"),ept=hQ(),c8=class{constructor(e={}){this._options=e,this.basePath=this._getValue(this._options.basePath,void 0),this.concurrency=this._getValue(this._options.concurrency,Number.POSITIVE_INFINITY),this.deepFilter=this._getValue(this._options.deepFilter,null),this.entryFilter=this._getValue(this._options.entryFilter,null),this.errorFilter=this._getValue(this._options.errorFilter,null),this.pathSegmentSeparator=this._getValue(this._options.pathSegmentSeparator,$At.sep),this.fsScandirSettings=new ept.Settings({followSymbolicLinks:this._options.followSymbolicLinks,fs:this._options.fs,pathSegmentSeparator:this._options.pathSegmentSeparator,stats:this._options.stats,throwErrorOnBrokenSymbolicLink:this._options.throwErrorOnBrokenSymbolicLink})}_getValue(e,r){return e??r}};u8.default=c8});var EQ=L($f=>{"use strict";Object.defineProperty($f,"__esModule",{value:!0});$f.Settings=$f.walkStream=$f.walkSync=$f.walk=void 0;var qpe=Mpe(),tpt=_pe(),rpt=Hpe(),f8=jpe();$f.Settings=f8.default;function npt(t,e,r){if(typeof e=="function"){new qpe.default(t,yQ()).read(e);return}new qpe.default(t,yQ(e)).read(r)}$f.walk=npt;function ipt(t,e){let r=yQ(e);return new rpt.default(t,r).read()}$f.walkSync=ipt;function spt(t,e){let r=yQ(e);return new tpt.default(t,r).read()}$f.walkStream=spt;function yQ(t={}){return t instanceof f8.default?t:new f8.default(t)}});var IQ=L(p8=>{"use strict";Object.defineProperty(p8,"__esModule",{value:!0});var opt=ye("path"),apt=Xd(),Gpe=Qp(),A8=class{constructor(e){this._settings=e,this._fsStatSettings=new apt.Settings({followSymbolicLink:this._settings.followSymbolicLinks,fs:this._settings.fs,throwErrorOnBrokenSymbolicLink:this._settings.followSymbolicLinks})}_getFullEntryPath(e){return opt.resolve(this._settings.cwd,e)}_makeEntry(e,r){let s={name:r,path:r,dirent:Gpe.fs.createDirentFromStats(r,e)};return this._settings.stats&&(s.stats=e),s}_isFatalError(e){return!Gpe.errno.isEnoentCodeError(e)&&!this._settings.suppressErrors}};p8.default=A8});var d8=L(g8=>{"use strict";Object.defineProperty(g8,"__esModule",{value:!0});var lpt=ye("stream"),cpt=Xd(),upt=EQ(),fpt=IQ(),h8=class extends fpt.default{constructor(){super(...arguments),this._walkStream=upt.walkStream,this._stat=cpt.stat}dynamic(e,r){return this._walkStream(e,r)}static(e,r){let s=e.map(this._getFullEntryPath,this),a=new lpt.PassThrough({objectMode:!0});a._write=(n,c,f)=>this._getEntry(s[n],e[n],r).then(p=>{p!==null&&r.entryFilter(p)&&a.push(p),n===s.length-1&&a.end(),f()}).catch(f);for(let n=0;nthis._makeEntry(a,r)).catch(a=>{if(s.errorFilter(a))return null;throw a})}_getStat(e){return new Promise((r,s)=>{this._stat(e,this._fsStatSettings,(a,n)=>a===null?r(n):s(a))})}};g8.default=h8});var Wpe=L(y8=>{"use strict";Object.defineProperty(y8,"__esModule",{value:!0});var Apt=EQ(),ppt=IQ(),hpt=d8(),m8=class extends ppt.default{constructor(){super(...arguments),this._walkAsync=Apt.walk,this._readerStream=new hpt.default(this._settings)}dynamic(e,r){return new Promise((s,a)=>{this._walkAsync(e,r,(n,c)=>{n===null?s(c):a(n)})})}async static(e,r){let s=[],a=this._readerStream.static(e,r);return new Promise((n,c)=>{a.once("error",c),a.on("data",f=>s.push(f)),a.once("end",()=>n(s))})}};y8.default=m8});var Ype=L(I8=>{"use strict";Object.defineProperty(I8,"__esModule",{value:!0});var KB=Qp(),E8=class{constructor(e,r,s){this._patterns=e,this._settings=r,this._micromatchOptions=s,this._storage=[],this._fillStorage()}_fillStorage(){for(let e of this._patterns){let r=this._getPatternSegments(e),s=this._splitSegmentsIntoSections(r);this._storage.push({complete:s.length<=1,pattern:e,segments:r,sections:s})}}_getPatternSegments(e){return KB.pattern.getPatternParts(e,this._micromatchOptions).map(s=>KB.pattern.isDynamicPattern(s,this._settings)?{dynamic:!0,pattern:s,patternRe:KB.pattern.makeRe(s,this._micromatchOptions)}:{dynamic:!1,pattern:s})}_splitSegmentsIntoSections(e){return KB.array.splitWhen(e,r=>r.dynamic&&KB.pattern.hasGlobStar(r.pattern))}};I8.default=E8});var Vpe=L(w8=>{"use strict";Object.defineProperty(w8,"__esModule",{value:!0});var gpt=Ype(),C8=class extends gpt.default{match(e){let r=e.split("/"),s=r.length,a=this._storage.filter(n=>!n.complete||n.segments.length>s);for(let n of a){let c=n.sections[0];if(!n.complete&&s>c.length||r.every((p,h)=>{let E=n.segments[h];return!!(E.dynamic&&E.patternRe.test(p)||!E.dynamic&&E.pattern===p)}))return!0}return!1}};w8.default=C8});var Kpe=L(v8=>{"use strict";Object.defineProperty(v8,"__esModule",{value:!0});var CQ=Qp(),dpt=Vpe(),B8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r}getFilter(e,r,s){let a=this._getMatcher(r),n=this._getNegativePatternsRe(s);return c=>this._filter(e,c,a,n)}_getMatcher(e){return new dpt.default(e,this._settings,this._micromatchOptions)}_getNegativePatternsRe(e){let r=e.filter(CQ.pattern.isAffectDepthOfReadingPattern);return CQ.pattern.convertPatternsToRe(r,this._micromatchOptions)}_filter(e,r,s,a){if(this._isSkippedByDeep(e,r.path)||this._isSkippedSymbolicLink(r))return!1;let n=CQ.path.removeLeadingDotSegment(r.path);return this._isSkippedByPositivePatterns(n,s)?!1:this._isSkippedByNegativePatterns(n,a)}_isSkippedByDeep(e,r){return this._settings.deep===1/0?!1:this._getEntryLevel(e,r)>=this._settings.deep}_getEntryLevel(e,r){let s=r.split("/").length;if(e==="")return s;let a=e.split("/").length;return s-a}_isSkippedSymbolicLink(e){return!this._settings.followSymbolicLinks&&e.dirent.isSymbolicLink()}_isSkippedByPositivePatterns(e,r){return!this._settings.baseNameMatch&&!r.match(e)}_isSkippedByNegativePatterns(e,r){return!CQ.pattern.matchAny(e,r)}};v8.default=B8});var Jpe=L(D8=>{"use strict";Object.defineProperty(D8,"__esModule",{value:!0});var $d=Qp(),S8=class{constructor(e,r){this._settings=e,this._micromatchOptions=r,this.index=new Map}getFilter(e,r){let s=$d.pattern.convertPatternsToRe(e,this._micromatchOptions),a=$d.pattern.convertPatternsToRe(r,Object.assign(Object.assign({},this._micromatchOptions),{dot:!0}));return n=>this._filter(n,s,a)}_filter(e,r,s){let a=$d.path.removeLeadingDotSegment(e.path);if(this._settings.unique&&this._isDuplicateEntry(a)||this._onlyFileFilter(e)||this._onlyDirectoryFilter(e)||this._isSkippedByAbsoluteNegativePatterns(a,s))return!1;let n=e.dirent.isDirectory(),c=this._isMatchToPatterns(a,r,n)&&!this._isMatchToPatterns(a,s,n);return this._settings.unique&&c&&this._createIndexRecord(a),c}_isDuplicateEntry(e){return this.index.has(e)}_createIndexRecord(e){this.index.set(e,void 0)}_onlyFileFilter(e){return this._settings.onlyFiles&&!e.dirent.isFile()}_onlyDirectoryFilter(e){return this._settings.onlyDirectories&&!e.dirent.isDirectory()}_isSkippedByAbsoluteNegativePatterns(e,r){if(!this._settings.absolute)return!1;let s=$d.path.makeAbsolute(this._settings.cwd,e);return $d.pattern.matchAny(s,r)}_isMatchToPatterns(e,r,s){let a=$d.pattern.matchAny(e,r);return!a&&s?$d.pattern.matchAny(e+"/",r):a}};D8.default=S8});var zpe=L(P8=>{"use strict";Object.defineProperty(P8,"__esModule",{value:!0});var mpt=Qp(),b8=class{constructor(e){this._settings=e}getFilter(){return e=>this._isNonFatalError(e)}_isNonFatalError(e){return mpt.errno.isEnoentCodeError(e)||this._settings.suppressErrors}};P8.default=b8});var Xpe=L(k8=>{"use strict";Object.defineProperty(k8,"__esModule",{value:!0});var Zpe=Qp(),x8=class{constructor(e){this._settings=e}getTransformer(){return e=>this._transform(e)}_transform(e){let r=e.path;return this._settings.absolute&&(r=Zpe.path.makeAbsolute(this._settings.cwd,r),r=Zpe.path.unixify(r)),this._settings.markDirectories&&e.dirent.isDirectory()&&(r+="/"),this._settings.objectMode?Object.assign(Object.assign({},e),{path:r}):r}};k8.default=x8});var wQ=L(T8=>{"use strict";Object.defineProperty(T8,"__esModule",{value:!0});var ypt=ye("path"),Ept=Kpe(),Ipt=Jpe(),Cpt=zpe(),wpt=Xpe(),Q8=class{constructor(e){this._settings=e,this.errorFilter=new Cpt.default(this._settings),this.entryFilter=new Ipt.default(this._settings,this._getMicromatchOptions()),this.deepFilter=new Ept.default(this._settings,this._getMicromatchOptions()),this.entryTransformer=new wpt.default(this._settings)}_getRootDirectory(e){return ypt.resolve(this._settings.cwd,e.base)}_getReaderOptions(e){let r=e.base==="."?"":e.base;return{basePath:r,pathSegmentSeparator:"/",concurrency:this._settings.concurrency,deepFilter:this.deepFilter.getFilter(r,e.positive,e.negative),entryFilter:this.entryFilter.getFilter(e.positive,e.negative),errorFilter:this.errorFilter.getFilter(),followSymbolicLinks:this._settings.followSymbolicLinks,fs:this._settings.fs,stats:this._settings.stats,throwErrorOnBrokenSymbolicLink:this._settings.throwErrorOnBrokenSymbolicLink,transform:this.entryTransformer.getTransformer()}}_getMicromatchOptions(){return{dot:this._settings.dot,matchBase:this._settings.baseNameMatch,nobrace:!this._settings.braceExpansion,nocase:!this._settings.caseSensitiveMatch,noext:!this._settings.extglob,noglobstar:!this._settings.globstar,posix:!0,strictSlashes:!1}}};T8.default=Q8});var $pe=L(F8=>{"use strict";Object.defineProperty(F8,"__esModule",{value:!0});var Bpt=Wpe(),vpt=wQ(),R8=class extends vpt.default{constructor(){super(...arguments),this._reader=new Bpt.default(this._settings)}async read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return(await this.api(r,e,s)).map(n=>s.transform(n))}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};F8.default=R8});var ehe=L(O8=>{"use strict";Object.defineProperty(O8,"__esModule",{value:!0});var Spt=ye("stream"),Dpt=d8(),bpt=wQ(),N8=class extends bpt.default{constructor(){super(...arguments),this._reader=new Dpt.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e),a=this.api(r,e,s),n=new Spt.Readable({objectMode:!0,read:()=>{}});return a.once("error",c=>n.emit("error",c)).on("data",c=>n.emit("data",s.transform(c))).once("end",()=>n.emit("end")),n.once("close",()=>a.destroy()),n}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};O8.default=N8});var the=L(M8=>{"use strict";Object.defineProperty(M8,"__esModule",{value:!0});var Ppt=Xd(),xpt=EQ(),kpt=IQ(),L8=class extends kpt.default{constructor(){super(...arguments),this._walkSync=xpt.walkSync,this._statSync=Ppt.statSync}dynamic(e,r){return this._walkSync(e,r)}static(e,r){let s=[];for(let a of e){let n=this._getFullEntryPath(a),c=this._getEntry(n,a,r);c===null||!r.entryFilter(c)||s.push(c)}return s}_getEntry(e,r,s){try{let a=this._getStat(e);return this._makeEntry(a,r)}catch(a){if(s.errorFilter(a))return null;throw a}}_getStat(e){return this._statSync(e,this._fsStatSettings)}};M8.default=L8});var rhe=L(U8=>{"use strict";Object.defineProperty(U8,"__esModule",{value:!0});var Qpt=the(),Tpt=wQ(),_8=class extends Tpt.default{constructor(){super(...arguments),this._reader=new Qpt.default(this._settings)}read(e){let r=this._getRootDirectory(e),s=this._getReaderOptions(e);return this.api(r,e,s).map(s.transform)}api(e,r,s){return r.dynamic?this._reader.dynamic(e,s):this._reader.static(r.patterns,s)}};U8.default=_8});var nhe=L(eI=>{"use strict";Object.defineProperty(eI,"__esModule",{value:!0});eI.DEFAULT_FILE_SYSTEM_ADAPTER=void 0;var $E=ye("fs"),Rpt=ye("os"),Fpt=Math.max(Rpt.cpus().length,1);eI.DEFAULT_FILE_SYSTEM_ADAPTER={lstat:$E.lstat,lstatSync:$E.lstatSync,stat:$E.stat,statSync:$E.statSync,readdir:$E.readdir,readdirSync:$E.readdirSync};var H8=class{constructor(e={}){this._options=e,this.absolute=this._getValue(this._options.absolute,!1),this.baseNameMatch=this._getValue(this._options.baseNameMatch,!1),this.braceExpansion=this._getValue(this._options.braceExpansion,!0),this.caseSensitiveMatch=this._getValue(this._options.caseSensitiveMatch,!0),this.concurrency=this._getValue(this._options.concurrency,Fpt),this.cwd=this._getValue(this._options.cwd,process.cwd()),this.deep=this._getValue(this._options.deep,1/0),this.dot=this._getValue(this._options.dot,!1),this.extglob=this._getValue(this._options.extglob,!0),this.followSymbolicLinks=this._getValue(this._options.followSymbolicLinks,!0),this.fs=this._getFileSystemMethods(this._options.fs),this.globstar=this._getValue(this._options.globstar,!0),this.ignore=this._getValue(this._options.ignore,[]),this.markDirectories=this._getValue(this._options.markDirectories,!1),this.objectMode=this._getValue(this._options.objectMode,!1),this.onlyDirectories=this._getValue(this._options.onlyDirectories,!1),this.onlyFiles=this._getValue(this._options.onlyFiles,!0),this.stats=this._getValue(this._options.stats,!1),this.suppressErrors=this._getValue(this._options.suppressErrors,!1),this.throwErrorOnBrokenSymbolicLink=this._getValue(this._options.throwErrorOnBrokenSymbolicLink,!1),this.unique=this._getValue(this._options.unique,!0),this.onlyDirectories&&(this.onlyFiles=!1),this.stats&&(this.objectMode=!0),this.ignore=[].concat(this.ignore)}_getValue(e,r){return e===void 0?r:e}_getFileSystemMethods(e={}){return Object.assign(Object.assign({},eI.DEFAULT_FILE_SYSTEM_ADAPTER),e)}};eI.default=H8});var BQ=L((kJt,she)=>{"use strict";var ihe=spe(),Npt=$pe(),Opt=ehe(),Lpt=rhe(),j8=nhe(),Rc=Qp();async function q8(t,e){qu(t);let r=G8(t,Npt.default,e),s=await Promise.all(r);return Rc.array.flatten(s)}(function(t){t.glob=t,t.globSync=e,t.globStream=r,t.async=t;function e(h,E){qu(h);let C=G8(h,Lpt.default,E);return Rc.array.flatten(C)}t.sync=e;function r(h,E){qu(h);let C=G8(h,Opt.default,E);return Rc.stream.merge(C)}t.stream=r;function s(h,E){qu(h);let C=[].concat(h),S=new j8.default(E);return ihe.generate(C,S)}t.generateTasks=s;function a(h,E){qu(h);let C=new j8.default(E);return Rc.pattern.isDynamicPattern(h,C)}t.isDynamicPattern=a;function n(h){return qu(h),Rc.path.escape(h)}t.escapePath=n;function c(h){return qu(h),Rc.path.convertPathToPattern(h)}t.convertPathToPattern=c;let f;(function(h){function E(S){return qu(S),Rc.path.escapePosixPath(S)}h.escapePath=E;function C(S){return qu(S),Rc.path.convertPosixPathToPattern(S)}h.convertPathToPattern=C})(f=t.posix||(t.posix={}));let p;(function(h){function E(S){return qu(S),Rc.path.escapeWindowsPath(S)}h.escapePath=E;function C(S){return qu(S),Rc.path.convertWindowsPathToPattern(S)}h.convertPathToPattern=C})(p=t.win32||(t.win32={}))})(q8||(q8={}));function G8(t,e,r){let s=[].concat(t),a=new j8.default(r),n=ihe.generate(s,a),c=new e(a);return n.map(c.read,c)}function qu(t){if(![].concat(t).every(s=>Rc.string.isString(s)&&!Rc.string.isEmpty(s)))throw new TypeError("Patterns must be a string (non empty) or an array of strings")}she.exports=q8});var Nn={};Vt(Nn,{checksumFile:()=>SQ,checksumPattern:()=>DQ,makeHash:()=>us});function us(...t){let e=(0,vQ.createHash)("sha512"),r="";for(let s of t)typeof s=="string"?r+=s:s&&(r&&(e.update(r),r=""),e.update(s));return r&&e.update(r),e.digest("hex")}async function SQ(t,{baseFs:e,algorithm:r}={baseFs:le,algorithm:"sha512"}){let s=await e.openPromise(t,"r");try{let n=Buffer.allocUnsafeSlow(65536),c=(0,vQ.createHash)(r),f=0;for(;(f=await e.readPromise(s,n,0,65536))!==0;)c.update(f===65536?n:n.slice(0,f));return c.digest("hex")}finally{await e.closePromise(s)}}async function DQ(t,{cwd:e}){let s=(await(0,W8.default)(t,{cwd:ue.fromPortablePath(e),onlyDirectories:!0})).map(f=>`${f}/**/*`),a=await(0,W8.default)([t,...s],{cwd:ue.fromPortablePath(e),onlyFiles:!1});a.sort();let n=await Promise.all(a.map(async f=>{let p=[Buffer.from(f)],h=K.join(e,ue.toPortablePath(f)),E=await le.lstatPromise(h);return E.isSymbolicLink()?p.push(Buffer.from(await le.readlinkPromise(h))):E.isFile()&&p.push(await le.readFilePromise(h)),p.join("\0")})),c=(0,vQ.createHash)("sha512");for(let f of n)c.update(f);return c.digest("hex")}var vQ,W8,I0=It(()=>{bt();vQ=ye("crypto"),W8=et(BQ())});var q={};Vt(q,{allPeerRequests:()=>nv,areDescriptorsEqual:()=>uhe,areIdentsEqual:()=>XB,areLocatorsEqual:()=>$B,areVirtualPackagesEquivalent:()=>Ypt,bindDescriptor:()=>Gpt,bindLocator:()=>Wpt,convertDescriptorToLocator:()=>bQ,convertLocatorToDescriptor:()=>V8,convertPackageToLocator:()=>Hpt,convertToIdent:()=>Upt,convertToManifestRange:()=>rht,copyPackage:()=>zB,devirtualizeDescriptor:()=>ZB,devirtualizeLocator:()=>rI,ensureDevirtualizedDescriptor:()=>jpt,ensureDevirtualizedLocator:()=>qpt,getIdentVendorPath:()=>Z8,isPackageCompatible:()=>TQ,isVirtualDescriptor:()=>Tp,isVirtualLocator:()=>Gu,makeDescriptor:()=>On,makeIdent:()=>ba,makeLocator:()=>Ys,makeRange:()=>kQ,parseDescriptor:()=>C0,parseFileStyleRange:()=>eht,parseIdent:()=>Da,parseLocator:()=>Rp,parseRange:()=>em,prettyDependent:()=>B3,prettyDescriptor:()=>ri,prettyIdent:()=>$i,prettyLocator:()=>Yr,prettyLocatorNoColors:()=>w3,prettyRange:()=>iI,prettyReference:()=>tv,prettyResolution:()=>VB,prettyWorkspace:()=>rv,renamePackage:()=>K8,slugifyIdent:()=>Y8,slugifyLocator:()=>nI,sortDescriptors:()=>sI,stringifyDescriptor:()=>ll,stringifyIdent:()=>cn,stringifyLocator:()=>cl,tryParseDescriptor:()=>ev,tryParseIdent:()=>fhe,tryParseLocator:()=>xQ,tryParseRange:()=>$pt,unwrapIdentFromScope:()=>iht,virtualizeDescriptor:()=>J8,virtualizePackage:()=>z8,wrapIdentIntoScope:()=>nht});function ba(t,e){if(t?.startsWith("@"))throw new Error("Invalid scope: don't prefix it with '@'");return{identHash:us(t,e),scope:t,name:e}}function On(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:us(t.identHash,e),range:e}}function Ys(t,e){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:us(t.identHash,e),reference:e}}function Upt(t){return{identHash:t.identHash,scope:t.scope,name:t.name}}function bQ(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.descriptorHash,reference:t.range}}function V8(t){return{identHash:t.identHash,scope:t.scope,name:t.name,descriptorHash:t.locatorHash,range:t.reference}}function Hpt(t){return{identHash:t.identHash,scope:t.scope,name:t.name,locatorHash:t.locatorHash,reference:t.reference}}function K8(t,e){return{identHash:e.identHash,scope:e.scope,name:e.name,locatorHash:e.locatorHash,reference:e.reference,version:t.version,languageName:t.languageName,linkType:t.linkType,conditions:t.conditions,dependencies:new Map(t.dependencies),peerDependencies:new Map(t.peerDependencies),dependenciesMeta:new Map(t.dependenciesMeta),peerDependenciesMeta:new Map(t.peerDependenciesMeta),bin:new Map(t.bin)}}function zB(t){return K8(t,t)}function J8(t,e){if(e.includes("#"))throw new Error("Invalid entropy");return On(t,`virtual:${e}#${t.range}`)}function z8(t,e){if(e.includes("#"))throw new Error("Invalid entropy");return K8(t,Ys(t,`virtual:${e}#${t.reference}`))}function Tp(t){return t.range.startsWith(JB)}function Gu(t){return t.reference.startsWith(JB)}function ZB(t){if(!Tp(t))throw new Error("Not a virtual descriptor");return On(t,t.range.replace(PQ,""))}function rI(t){if(!Gu(t))throw new Error("Not a virtual descriptor");return Ys(t,t.reference.replace(PQ,""))}function jpt(t){return Tp(t)?On(t,t.range.replace(PQ,"")):t}function qpt(t){return Gu(t)?Ys(t,t.reference.replace(PQ,"")):t}function Gpt(t,e){return t.range.includes("::")?t:On(t,`${t.range}::${tI.default.stringify(e)}`)}function Wpt(t,e){return t.reference.includes("::")?t:Ys(t,`${t.reference}::${tI.default.stringify(e)}`)}function XB(t,e){return t.identHash===e.identHash}function uhe(t,e){return t.descriptorHash===e.descriptorHash}function $B(t,e){return t.locatorHash===e.locatorHash}function Ypt(t,e){if(!Gu(t))throw new Error("Invalid package type");if(!Gu(e))throw new Error("Invalid package type");if(!XB(t,e)||t.dependencies.size!==e.dependencies.size)return!1;for(let r of t.dependencies.values()){let s=e.dependencies.get(r.identHash);if(!s||!uhe(r,s))return!1}return!0}function Da(t){let e=fhe(t);if(!e)throw new Error(`Invalid ident (${t})`);return e}function fhe(t){let e=t.match(Vpt);if(!e)return null;let[,r,s]=e;return ba(typeof r<"u"?r:null,s)}function C0(t,e=!1){let r=ev(t,e);if(!r)throw new Error(`Invalid descriptor (${t})`);return r}function ev(t,e=!1){let r=e?t.match(Kpt):t.match(Jpt);if(!r)return null;let[,s,a,n]=r;if(n==="unknown")throw new Error(`Invalid range (${t})`);let c=typeof s<"u"?s:null,f=typeof n<"u"?n:"unknown";return On(ba(c,a),f)}function Rp(t,e=!1){let r=xQ(t,e);if(!r)throw new Error(`Invalid locator (${t})`);return r}function xQ(t,e=!1){let r=e?t.match(zpt):t.match(Zpt);if(!r)return null;let[,s,a,n]=r;if(n==="unknown")throw new Error(`Invalid reference (${t})`);let c=typeof s<"u"?s:null,f=typeof n<"u"?n:"unknown";return Ys(ba(c,a),f)}function em(t,e){let r=t.match(Xpt);if(r===null)throw new Error(`Invalid range (${t})`);let s=typeof r[1]<"u"?r[1]:null;if(typeof e?.requireProtocol=="string"&&s!==e.requireProtocol)throw new Error(`Invalid protocol (${s})`);if(e?.requireProtocol&&s===null)throw new Error(`Missing protocol (${s})`);let a=typeof r[3]<"u"?decodeURIComponent(r[2]):null;if(e?.requireSource&&a===null)throw new Error(`Missing source (${t})`);let n=typeof r[3]<"u"?decodeURIComponent(r[3]):decodeURIComponent(r[2]),c=e?.parseSelector?tI.default.parse(n):n,f=typeof r[4]<"u"?tI.default.parse(r[4]):null;return{protocol:s,source:a,selector:c,params:f}}function $pt(t,e){try{return em(t,e)}catch{return null}}function eht(t,{protocol:e}){let{selector:r,params:s}=em(t,{requireProtocol:e,requireBindings:!0});if(typeof s.locator!="string")throw new Error(`Assertion failed: Invalid bindings for ${t}`);return{parentLocator:Rp(s.locator,!0),path:r}}function ohe(t){return t=t.replaceAll("%","%25"),t=t.replaceAll(":","%3A"),t=t.replaceAll("#","%23"),t}function tht(t){return t===null?!1:Object.entries(t).length>0}function kQ({protocol:t,source:e,selector:r,params:s}){let a="";return t!==null&&(a+=`${t}`),e!==null&&(a+=`${ohe(e)}#`),a+=ohe(r),tht(s)&&(a+=`::${tI.default.stringify(s)}`),a}function rht(t){let{params:e,protocol:r,source:s,selector:a}=em(t);for(let n in e)n.startsWith("__")&&delete e[n];return kQ({protocol:r,source:s,params:e,selector:a})}function cn(t){return t.scope?`@${t.scope}/${t.name}`:`${t.name}`}function nht(t,e){return t.scope?ba(e,`${t.scope}__${t.name}`):ba(e,t.name)}function iht(t,e){if(t.scope!==e)return t;let r=t.name.indexOf("__");if(r===-1)return ba(null,t.name);let s=t.name.slice(0,r),a=t.name.slice(r+2);return ba(s,a)}function ll(t){return t.scope?`@${t.scope}/${t.name}@${t.range}`:`${t.name}@${t.range}`}function cl(t){return t.scope?`@${t.scope}/${t.name}@${t.reference}`:`${t.name}@${t.reference}`}function Y8(t){return t.scope!==null?`@${t.scope}-${t.name}`:t.name}function nI(t){let{protocol:e,selector:r}=em(t.reference),s=e!==null?e.replace(sht,""):"exotic",a=ahe.default.valid(r),n=a!==null?`${s}-${a}`:`${s}`,c=10;return t.scope?`${Y8(t)}-${n}-${t.locatorHash.slice(0,c)}`:`${Y8(t)}-${n}-${t.locatorHash.slice(0,c)}`}function $i(t,e){return e.scope?`${Ut(t,`@${e.scope}/`,Ct.SCOPE)}${Ut(t,e.name,Ct.NAME)}`:`${Ut(t,e.name,Ct.NAME)}`}function QQ(t){if(t.startsWith(JB)){let e=QQ(t.substring(t.indexOf("#")+1)),r=t.substring(JB.length,JB.length+Mpt);return`${e} [${r}]`}else return t.replace(oht,"?[...]")}function iI(t,e){return`${Ut(t,QQ(e),Ct.RANGE)}`}function ri(t,e){return`${$i(t,e)}${Ut(t,"@",Ct.RANGE)}${iI(t,e.range)}`}function tv(t,e){return`${Ut(t,QQ(e),Ct.REFERENCE)}`}function Yr(t,e){return`${$i(t,e)}${Ut(t,"@",Ct.REFERENCE)}${tv(t,e.reference)}`}function w3(t){return`${cn(t)}@${QQ(t.reference)}`}function sI(t){return Ws(t,[e=>cn(e),e=>e.range])}function rv(t,e){return $i(t,e.anchoredLocator)}function VB(t,e,r){let s=Tp(e)?ZB(e):e;return r===null?`${ri(t,s)} \u2192 ${C3(t).Cross}`:s.identHash===r.identHash?`${ri(t,s)} \u2192 ${tv(t,r.reference)}`:`${ri(t,s)} \u2192 ${Yr(t,r)}`}function B3(t,e,r){return r===null?`${Yr(t,e)}`:`${Yr(t,e)} (via ${iI(t,r.range)})`}function Z8(t){return`node_modules/${cn(t)}`}function TQ(t,e){return t.conditions?_pt(t.conditions,r=>{let[,s,a]=r.match(che),n=e[s];return n?n.includes(a):!0}):!0}function nv(t){let e=new Set;if("children"in t)e.add(t);else for(let r of t.requests.values())e.add(r);for(let r of e)for(let s of r.children.values())e.add(s);return e}var tI,ahe,lhe,JB,Mpt,che,_pt,PQ,Vpt,Kpt,Jpt,zpt,Zpt,Xpt,sht,oht,Yo=It(()=>{tI=et(ye("querystring")),ahe=et(fi()),lhe=et(noe());Qc();I0();kc();Yo();JB="virtual:",Mpt=5,che=/(os|cpu|libc)=([a-z0-9_-]+)/,_pt=(0,lhe.makeParser)(che);PQ=/^[^#]*#/;Vpt=/^(?:@([^/]+?)\/)?([^@/]+)$/;Kpt=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))$/,Jpt=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))?$/;zpt=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))$/,Zpt=/^(?:@([^/]+?)\/)?([^@/]+?)(?:@(.+))?$/;Xpt=/^([^#:]*:)?((?:(?!::)[^#])*)(?:#((?:(?!::).)*))?(?:::(.*))?$/;sht=/:$/;oht=/\?.*/});var Ahe,phe=It(()=>{Yo();Ahe={hooks:{reduceDependency:(t,e,r,s,{resolver:a,resolveOptions:n})=>{for(let{pattern:c,reference:f}of e.topLevelWorkspace.manifest.resolutions){if(c.from&&(c.from.fullName!==cn(r)||e.configuration.normalizeLocator(Ys(Da(c.from.fullName),c.from.description??r.reference)).locatorHash!==r.locatorHash)||c.descriptor.fullName!==cn(t)||e.configuration.normalizeDependency(On(Rp(c.descriptor.fullName),c.descriptor.description??t.range)).descriptorHash!==t.descriptorHash)continue;return a.bindDescriptor(e.configuration.normalizeDependency(On(t,f)),e.topLevelWorkspace.anchoredLocator,n)}return t},validateProject:async(t,e)=>{for(let r of t.workspaces){let s=rv(t.configuration,r);await t.configuration.triggerHook(a=>a.validateWorkspace,r,{reportWarning:(a,n)=>e.reportWarning(a,`${s}: ${n}`),reportError:(a,n)=>e.reportError(a,`${s}: ${n}`)})}},validateWorkspace:async(t,e)=>{let{manifest:r}=t;r.resolutions.length&&t.cwd!==t.project.cwd&&r.errors.push(new Error("Resolutions field will be ignored"));for(let s of r.errors)e.reportWarning(57,s.message)}}}});var yi,tm=It(()=>{yi=class t{static{this.protocol="workspace:"}supportsDescriptor(e,r){return!!(e.range.startsWith(t.protocol)||r.project.tryWorkspaceByDescriptor(e)!==null)}supportsLocator(e,r){return!!e.reference.startsWith(t.protocol)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[s.project.getWorkspaceByDescriptor(e).anchoredLocator]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.getWorkspaceByCwd(e.reference.slice(t.protocol.length));return{...e,version:s.manifest.version||"0.0.0",languageName:"unknown",linkType:"SOFT",conditions:null,dependencies:r.project.configuration.normalizeDependencyMap(new Map([...s.manifest.dependencies,...s.manifest.devDependencies])),peerDependencies:new Map([...s.manifest.peerDependencies]),dependenciesMeta:s.manifest.dependenciesMeta,peerDependenciesMeta:s.manifest.peerDependenciesMeta,bin:s.manifest.bin}}}});var Or={};Vt(Or,{SemVer:()=>yhe.SemVer,clean:()=>lht,getComparator:()=>dhe,mergeComparators:()=>X8,satisfiesWithPrereleases:()=>eA,simplifyRanges:()=>$8,stringifyComparator:()=>mhe,validRange:()=>ul});function eA(t,e,r=!1){if(!t)return!1;let s=`${e}${r}`,a=hhe.get(s);if(typeof a>"u")try{a=new Fp.default.Range(e,{includePrerelease:!0,loose:r})}catch{return!1}finally{hhe.set(s,a||null)}else if(a===null)return!1;let n;try{n=new Fp.default.SemVer(t,a)}catch{return!1}return a.test(n)?!0:(n.prerelease&&(n.prerelease=[]),a.set.some(c=>{for(let f of c)f.semver.prerelease&&(f.semver.prerelease=[]);return c.every(f=>f.test(n))}))}function ul(t){if(t.indexOf(":")!==-1)return null;let e=ghe.get(t);if(typeof e<"u")return e;try{e=new Fp.default.Range(t)}catch{e=null}return ghe.set(t,e),e}function lht(t){let e=aht.exec(t);return e?e[1]:null}function dhe(t){if(t.semver===Fp.default.Comparator.ANY)return{gt:null,lt:null};switch(t.operator){case"":return{gt:[">=",t.semver],lt:["<=",t.semver]};case">":case">=":return{gt:[t.operator,t.semver],lt:null};case"<":case"<=":return{gt:null,lt:[t.operator,t.semver]};default:throw new Error(`Assertion failed: Unexpected comparator operator (${t.operator})`)}}function X8(t){if(t.length===0)return null;let e=null,r=null;for(let s of t){if(s.gt){let a=e!==null?Fp.default.compare(s.gt[1],e[1]):null;(a===null||a>0||a===0&&s.gt[0]===">")&&(e=s.gt)}if(s.lt){let a=r!==null?Fp.default.compare(s.lt[1],r[1]):null;(a===null||a<0||a===0&&s.lt[0]==="<")&&(r=s.lt)}}if(e&&r){let s=Fp.default.compare(e[1],r[1]);if(s===0&&(e[0]===">"||r[0]==="<")||s>0)return null}return{gt:e,lt:r}}function mhe(t){if(t.gt&&t.lt){if(t.gt[0]===">="&&t.lt[0]==="<="&&t.gt[1].version===t.lt[1].version)return t.gt[1].version;if(t.gt[0]===">="&&t.lt[0]==="<"){if(t.lt[1].version===`${t.gt[1].major+1}.0.0-0`)return`^${t.gt[1].version}`;if(t.lt[1].version===`${t.gt[1].major}.${t.gt[1].minor+1}.0-0`)return`~${t.gt[1].version}`}}let e=[];return t.gt&&e.push(t.gt[0]+t.gt[1].version),t.lt&&e.push(t.lt[0]+t.lt[1].version),e.length?e.join(" "):"*"}function $8(t){let e=t.map(cht).map(s=>ul(s).set.map(a=>a.map(n=>dhe(n)))),r=e.shift().map(s=>X8(s)).filter(s=>s!==null);for(let s of e){let a=[];for(let n of r)for(let c of s){let f=X8([n,...c]);f!==null&&a.push(f)}r=a}return r.length===0?null:r.map(s=>mhe(s)).join(" || ")}function cht(t){let e=t.split("||");if(e.length>1){let r=new Set;for(let s of e)e.some(a=>a!==s&&Fp.default.subset(s,a))||r.add(s);if(r.size{Fp=et(fi()),yhe=et(fi()),hhe=new Map;ghe=new Map;aht=/^(?:[\sv=]*?)((0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\s*)$/});function Ehe(t){let e=t.match(/^[ \t]+/m);return e?e[0]:" "}function Ihe(t){return t.charCodeAt(0)===65279?t.slice(1):t}function Pa(t){return t.replace(/\\/g,"/")}function RQ(t,{yamlCompatibilityMode:e}){return e?h3(t):typeof t>"u"||typeof t=="boolean"?t:null}function Che(t,e){let r=e.search(/[^!]/);if(r===-1)return"invalid";let s=r%2===0?"":"!",a=e.slice(r);return`${s}${t}=${a}`}function eH(t,e){return e.length===1?Che(t,e[0]):`(${e.map(r=>Che(t,r)).join(" | ")})`}var whe,Ht,oI=It(()=>{bt();Bc();whe=et(fi());tm();kc();Np();Yo();Ht=class t{constructor(){this.indent=" ";this.name=null;this.version=null;this.os=null;this.cpu=null;this.libc=null;this.type=null;this.packageManager=null;this.private=!1;this.license=null;this.main=null;this.module=null;this.browser=null;this.languageName=null;this.bin=new Map;this.scripts=new Map;this.dependencies=new Map;this.devDependencies=new Map;this.peerDependencies=new Map;this.workspaceDefinitions=[];this.dependenciesMeta=new Map;this.peerDependenciesMeta=new Map;this.resolutions=[];this.files=null;this.publishConfig=null;this.installConfig=null;this.preferUnplugged=null;this.raw={};this.errors=[]}static{this.fileName="package.json"}static{this.allDependencies=["dependencies","devDependencies","peerDependencies"]}static{this.hardDependencies=["dependencies","devDependencies"]}static async tryFind(e,{baseFs:r=new Yn}={}){let s=K.join(e,"package.json");try{return await t.fromFile(s,{baseFs:r})}catch(a){if(a.code==="ENOENT")return null;throw a}}static async find(e,{baseFs:r}={}){let s=await t.tryFind(e,{baseFs:r});if(s===null)throw new Error("Manifest not found");return s}static async fromFile(e,{baseFs:r=new Yn}={}){let s=new t;return await s.loadFile(e,{baseFs:r}),s}static fromText(e){let r=new t;return r.loadFromText(e),r}loadFromText(e){let r;try{r=JSON.parse(Ihe(e)||"{}")}catch(s){throw s.message+=` (when parsing ${e})`,s}this.load(r),this.indent=Ehe(e)}async loadFile(e,{baseFs:r=new Yn}){let s=await r.readFilePromise(e,"utf8"),a;try{a=JSON.parse(Ihe(s)||"{}")}catch(n){throw n.message+=` (when parsing ${e})`,n}this.load(a),this.indent=Ehe(s)}load(e,{yamlCompatibilityMode:r=!1}={}){if(typeof e!="object"||e===null)throw new Error(`Utterly invalid manifest data (${e})`);this.raw=e;let s=[];if(this.name=null,typeof e.name=="string")try{this.name=Da(e.name)}catch{s.push(new Error("Parsing failed for the 'name' field"))}if(typeof e.version=="string"?this.version=e.version:this.version=null,Array.isArray(e.os)){let n=[];this.os=n;for(let c of e.os)typeof c!="string"?s.push(new Error("Parsing failed for the 'os' field")):n.push(c)}else this.os=null;if(Array.isArray(e.cpu)){let n=[];this.cpu=n;for(let c of e.cpu)typeof c!="string"?s.push(new Error("Parsing failed for the 'cpu' field")):n.push(c)}else this.cpu=null;if(Array.isArray(e.libc)){let n=[];this.libc=n;for(let c of e.libc)typeof c!="string"?s.push(new Error("Parsing failed for the 'libc' field")):n.push(c)}else this.libc=null;if(typeof e.type=="string"?this.type=e.type:this.type=null,typeof e.packageManager=="string"?this.packageManager=e.packageManager:this.packageManager=null,typeof e.private=="boolean"?this.private=e.private:this.private=!1,typeof e.license=="string"?this.license=e.license:this.license=null,typeof e.languageName=="string"?this.languageName=e.languageName:this.languageName=null,typeof e.main=="string"?this.main=Pa(e.main):this.main=null,typeof e.module=="string"?this.module=Pa(e.module):this.module=null,e.browser!=null)if(typeof e.browser=="string")this.browser=Pa(e.browser);else{this.browser=new Map;for(let[n,c]of Object.entries(e.browser))this.browser.set(Pa(n),typeof c=="string"?Pa(c):c)}else this.browser=null;if(this.bin=new Map,typeof e.bin=="string")e.bin.trim()===""?s.push(new Error("Invalid bin field")):this.name!==null?this.bin.set(this.name.name,Pa(e.bin)):s.push(new Error("String bin field, but no attached package name"));else if(typeof e.bin=="object"&&e.bin!==null)for(let[n,c]of Object.entries(e.bin)){if(typeof c!="string"||c.trim()===""){s.push(new Error(`Invalid bin definition for '${n}'`));continue}let f=Da(n);this.bin.set(f.name,Pa(c))}if(this.scripts=new Map,typeof e.scripts=="object"&&e.scripts!==null)for(let[n,c]of Object.entries(e.scripts)){if(typeof c!="string"){s.push(new Error(`Invalid script definition for '${n}'`));continue}this.scripts.set(n,c)}if(this.dependencies=new Map,typeof e.dependencies=="object"&&e.dependencies!==null)for(let[n,c]of Object.entries(e.dependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p)}if(this.devDependencies=new Map,typeof e.devDependencies=="object"&&e.devDependencies!==null)for(let[n,c]of Object.entries(e.devDependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.devDependencies.set(p.identHash,p)}if(this.peerDependencies=new Map,typeof e.peerDependencies=="object"&&e.peerDependencies!==null)for(let[n,c]of Object.entries(e.peerDependencies)){let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}(typeof c!="string"||!c.startsWith(yi.protocol)&&!ul(c))&&(s.push(new Error(`Invalid dependency range for '${n}'`)),c="*");let p=On(f,c);this.peerDependencies.set(p.identHash,p)}typeof e.workspaces=="object"&&e.workspaces!==null&&e.workspaces.nohoist&&s.push(new Error("'nohoist' is deprecated, please use 'installConfig.hoistingLimits' instead"));let a=Array.isArray(e.workspaces)?e.workspaces:typeof e.workspaces=="object"&&e.workspaces!==null&&Array.isArray(e.workspaces.packages)?e.workspaces.packages:[];this.workspaceDefinitions=[];for(let n of a){if(typeof n!="string"){s.push(new Error(`Invalid workspace definition for '${n}'`));continue}this.workspaceDefinitions.push({pattern:n})}if(this.dependenciesMeta=new Map,typeof e.dependenciesMeta=="object"&&e.dependenciesMeta!==null)for(let[n,c]of Object.entries(e.dependenciesMeta)){if(typeof c!="object"||c===null){s.push(new Error(`Invalid meta field for '${n}`));continue}let f=C0(n),p=this.ensureDependencyMeta(f),h=RQ(c.built,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid built meta field for '${n}'`));continue}let E=RQ(c.optional,{yamlCompatibilityMode:r});if(E===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}let C=RQ(c.unplugged,{yamlCompatibilityMode:r});if(C===null){s.push(new Error(`Invalid unplugged meta field for '${n}'`));continue}Object.assign(p,{built:h,optional:E,unplugged:C})}if(this.peerDependenciesMeta=new Map,typeof e.peerDependenciesMeta=="object"&&e.peerDependenciesMeta!==null)for(let[n,c]of Object.entries(e.peerDependenciesMeta)){if(typeof c!="object"||c===null){s.push(new Error(`Invalid meta field for '${n}'`));continue}let f=C0(n),p=this.ensurePeerDependencyMeta(f),h=RQ(c.optional,{yamlCompatibilityMode:r});if(h===null){s.push(new Error(`Invalid optional meta field for '${n}'`));continue}Object.assign(p,{optional:h})}if(this.resolutions=[],typeof e.resolutions=="object"&&e.resolutions!==null)for(let[n,c]of Object.entries(e.resolutions)){if(typeof c!="string"){s.push(new Error(`Invalid resolution entry for '${n}'`));continue}try{this.resolutions.push({pattern:bx(n),reference:c})}catch(f){s.push(f);continue}}if(Array.isArray(e.files)){this.files=new Set;for(let n of e.files){if(typeof n!="string"){s.push(new Error(`Invalid files entry for '${n}'`));continue}this.files.add(n)}}else this.files=null;if(typeof e.publishConfig=="object"&&e.publishConfig!==null){if(this.publishConfig={},typeof e.publishConfig.access=="string"&&(this.publishConfig.access=e.publishConfig.access),typeof e.publishConfig.main=="string"&&(this.publishConfig.main=Pa(e.publishConfig.main)),typeof e.publishConfig.module=="string"&&(this.publishConfig.module=Pa(e.publishConfig.module)),e.publishConfig.browser!=null)if(typeof e.publishConfig.browser=="string")this.publishConfig.browser=Pa(e.publishConfig.browser);else{this.publishConfig.browser=new Map;for(let[n,c]of Object.entries(e.publishConfig.browser))this.publishConfig.browser.set(Pa(n),typeof c=="string"?Pa(c):c)}if(typeof e.publishConfig.registry=="string"&&(this.publishConfig.registry=e.publishConfig.registry),typeof e.publishConfig.bin=="string")this.name!==null?this.publishConfig.bin=new Map([[this.name.name,Pa(e.publishConfig.bin)]]):s.push(new Error("String bin field, but no attached package name"));else if(typeof e.publishConfig.bin=="object"&&e.publishConfig.bin!==null){this.publishConfig.bin=new Map;for(let[n,c]of Object.entries(e.publishConfig.bin)){if(typeof c!="string"){s.push(new Error(`Invalid bin definition for '${n}'`));continue}this.publishConfig.bin.set(n,Pa(c))}}if(Array.isArray(e.publishConfig.executableFiles)){this.publishConfig.executableFiles=new Set;for(let n of e.publishConfig.executableFiles){if(typeof n!="string"){s.push(new Error("Invalid executable file definition"));continue}this.publishConfig.executableFiles.add(Pa(n))}}}else this.publishConfig=null;if(typeof e.installConfig=="object"&&e.installConfig!==null){this.installConfig={};for(let n of Object.keys(e.installConfig))n==="hoistingLimits"?typeof e.installConfig.hoistingLimits=="string"?this.installConfig.hoistingLimits=e.installConfig.hoistingLimits:s.push(new Error("Invalid hoisting limits definition")):n=="selfReferences"?typeof e.installConfig.selfReferences=="boolean"?this.installConfig.selfReferences=e.installConfig.selfReferences:s.push(new Error("Invalid selfReferences definition, must be a boolean value")):s.push(new Error(`Unrecognized installConfig key: ${n}`))}else this.installConfig=null;if(typeof e.optionalDependencies=="object"&&e.optionalDependencies!==null)for(let[n,c]of Object.entries(e.optionalDependencies)){if(typeof c!="string"){s.push(new Error(`Invalid dependency range for '${n}'`));continue}let f;try{f=Da(n)}catch{s.push(new Error(`Parsing failed for the dependency name '${n}'`));continue}let p=On(f,c);this.dependencies.set(p.identHash,p);let h=On(f,"unknown"),E=this.ensureDependencyMeta(h);Object.assign(E,{optional:!0})}typeof e.preferUnplugged=="boolean"?this.preferUnplugged=e.preferUnplugged:this.preferUnplugged=null,this.errors=s}getForScope(e){switch(e){case"dependencies":return this.dependencies;case"devDependencies":return this.devDependencies;case"peerDependencies":return this.peerDependencies;default:throw new Error(`Unsupported value ("${e}")`)}}hasConsumerDependency(e){return!!(this.dependencies.has(e.identHash)||this.peerDependencies.has(e.identHash))}hasHardDependency(e){return!!(this.dependencies.has(e.identHash)||this.devDependencies.has(e.identHash))}hasSoftDependency(e){return!!this.peerDependencies.has(e.identHash)}hasDependency(e){return!!(this.hasHardDependency(e)||this.hasSoftDependency(e))}getConditions(){let e=[];return this.os&&this.os.length>0&&e.push(eH("os",this.os)),this.cpu&&this.cpu.length>0&&e.push(eH("cpu",this.cpu)),this.libc&&this.libc.length>0&&e.push(eH("libc",this.libc)),e.length>0?e.join(" & "):null}ensureDependencyMeta(e){if(e.range!=="unknown"&&!whe.default.valid(e.range))throw new Error(`Invalid meta field range for '${ll(e)}'`);let r=cn(e),s=e.range!=="unknown"?e.range:null,a=this.dependenciesMeta.get(r);a||this.dependenciesMeta.set(r,a=new Map);let n=a.get(s);return n||a.set(s,n={}),n}ensurePeerDependencyMeta(e){if(e.range!=="unknown")throw new Error(`Invalid meta field range for '${ll(e)}'`);let r=cn(e),s=this.peerDependenciesMeta.get(r);return s||this.peerDependenciesMeta.set(r,s={}),s}setRawField(e,r,{after:s=[]}={}){let a=new Set(s.filter(n=>Object.hasOwn(this.raw,n)));if(a.size===0||Object.hasOwn(this.raw,e))this.raw[e]=r;else{let n=this.raw,c=this.raw={},f=!1;for(let p of Object.keys(n))c[p]=n[p],f||(a.delete(p),a.size===0&&(c[e]=r,f=!0))}}exportTo(e,{compatibilityMode:r=!0}={}){if(Object.assign(e,this.raw),this.name!==null?e.name=cn(this.name):delete e.name,this.version!==null?e.version=this.version:delete e.version,this.os!==null?e.os=this.os:delete e.os,this.cpu!==null?e.cpu=this.cpu:delete e.cpu,this.type!==null?e.type=this.type:delete e.type,this.packageManager!==null?e.packageManager=this.packageManager:delete e.packageManager,this.private?e.private=!0:delete e.private,this.license!==null?e.license=this.license:delete e.license,this.languageName!==null?e.languageName=this.languageName:delete e.languageName,this.main!==null?e.main=this.main:delete e.main,this.module!==null?e.module=this.module:delete e.module,this.browser!==null){let n=this.browser;typeof n=="string"?e.browser=n:n instanceof Map&&(e.browser=Object.assign({},...Array.from(n.keys()).sort().map(c=>({[c]:n.get(c)}))))}else delete e.browser;this.bin.size===1&&this.name!==null&&this.bin.has(this.name.name)?e.bin=this.bin.get(this.name.name):this.bin.size>0?e.bin=Object.assign({},...Array.from(this.bin.keys()).sort().map(n=>({[n]:this.bin.get(n)}))):delete e.bin,this.workspaceDefinitions.length>0?this.raw.workspaces&&!Array.isArray(this.raw.workspaces)?e.workspaces={...this.raw.workspaces,packages:this.workspaceDefinitions.map(({pattern:n})=>n)}:e.workspaces=this.workspaceDefinitions.map(({pattern:n})=>n):this.raw.workspaces&&!Array.isArray(this.raw.workspaces)&&Object.keys(this.raw.workspaces).length>0?e.workspaces=this.raw.workspaces:delete e.workspaces;let s=[],a=[];for(let n of this.dependencies.values()){let c=this.dependenciesMeta.get(cn(n)),f=!1;if(r&&c){let p=c.get(null);p&&p.optional&&(f=!0)}f?a.push(n):s.push(n)}s.length>0?e.dependencies=Object.assign({},...sI(s).map(n=>({[cn(n)]:n.range}))):delete e.dependencies,a.length>0?e.optionalDependencies=Object.assign({},...sI(a).map(n=>({[cn(n)]:n.range}))):delete e.optionalDependencies,this.devDependencies.size>0?e.devDependencies=Object.assign({},...sI(this.devDependencies.values()).map(n=>({[cn(n)]:n.range}))):delete e.devDependencies,this.peerDependencies.size>0?e.peerDependencies=Object.assign({},...sI(this.peerDependencies.values()).map(n=>({[cn(n)]:n.range}))):delete e.peerDependencies,e.dependenciesMeta={};for(let[n,c]of Ws(this.dependenciesMeta.entries(),([f,p])=>f))for(let[f,p]of Ws(c.entries(),([h,E])=>h!==null?`0${h}`:"1")){let h=f!==null?ll(On(Da(n),f)):n,E={...p};r&&f===null&&delete E.optional,Object.keys(E).length!==0&&(e.dependenciesMeta[h]=E)}if(Object.keys(e.dependenciesMeta).length===0&&delete e.dependenciesMeta,this.peerDependenciesMeta.size>0?e.peerDependenciesMeta=Object.assign({},...Ws(this.peerDependenciesMeta.entries(),([n,c])=>n).map(([n,c])=>({[n]:c}))):delete e.peerDependenciesMeta,this.resolutions.length>0?e.resolutions=Object.assign({},...this.resolutions.map(({pattern:n,reference:c})=>({[Px(n)]:c}))):delete e.resolutions,this.files!==null?e.files=Array.from(this.files):delete e.files,this.preferUnplugged!==null?e.preferUnplugged=this.preferUnplugged:delete e.preferUnplugged,this.scripts!==null&&this.scripts.size>0){e.scripts??={};for(let n of Object.keys(e.scripts))this.scripts.has(n)||delete e.scripts[n];for(let[n,c]of this.scripts.entries())e.scripts[n]=c}else delete e.scripts;return e}}});var vhe=L((WJt,Bhe)=>{var uht=Pc(),fht=function(){return uht.Date.now()};Bhe.exports=fht});var Dhe=L((YJt,She)=>{var Aht=/\s/;function pht(t){for(var e=t.length;e--&&Aht.test(t.charAt(e)););return e}She.exports=pht});var Phe=L((VJt,bhe)=>{var hht=Dhe(),ght=/^\s+/;function dht(t){return t&&t.slice(0,hht(t)+1).replace(ght,"")}bhe.exports=dht});var aI=L((KJt,xhe)=>{var mht=Vd(),yht=zf(),Eht="[object Symbol]";function Iht(t){return typeof t=="symbol"||yht(t)&&mht(t)==Eht}xhe.exports=Iht});var Rhe=L((JJt,The)=>{var Cht=Phe(),khe=Wl(),wht=aI(),Qhe=NaN,Bht=/^[-+]0x[0-9a-f]+$/i,vht=/^0b[01]+$/i,Sht=/^0o[0-7]+$/i,Dht=parseInt;function bht(t){if(typeof t=="number")return t;if(wht(t))return Qhe;if(khe(t)){var e=typeof t.valueOf=="function"?t.valueOf():t;t=khe(e)?e+"":e}if(typeof t!="string")return t===0?t:+t;t=Cht(t);var r=vht.test(t);return r||Sht.test(t)?Dht(t.slice(2),r?2:8):Bht.test(t)?Qhe:+t}The.exports=bht});var Ohe=L((zJt,Nhe)=>{var Pht=Wl(),tH=vhe(),Fhe=Rhe(),xht="Expected a function",kht=Math.max,Qht=Math.min;function Tht(t,e,r){var s,a,n,c,f,p,h=0,E=!1,C=!1,S=!0;if(typeof t!="function")throw new TypeError(xht);e=Fhe(e)||0,Pht(r)&&(E=!!r.leading,C="maxWait"in r,n=C?kht(Fhe(r.maxWait)||0,e):n,S="trailing"in r?!!r.trailing:S);function P(ce){var me=s,pe=a;return s=a=void 0,h=ce,c=t.apply(pe,me),c}function I(ce){return h=ce,f=setTimeout(U,e),E?P(ce):c}function R(ce){var me=ce-p,pe=ce-h,Be=e-me;return C?Qht(Be,n-pe):Be}function N(ce){var me=ce-p,pe=ce-h;return p===void 0||me>=e||me<0||C&&pe>=n}function U(){var ce=tH();if(N(ce))return W(ce);f=setTimeout(U,R(ce))}function W(ce){return f=void 0,S&&s?P(ce):(s=a=void 0,c)}function te(){f!==void 0&&clearTimeout(f),h=0,s=p=a=f=void 0}function ie(){return f===void 0?c:W(tH())}function Ae(){var ce=tH(),me=N(ce);if(s=arguments,a=this,p=ce,me){if(f===void 0)return I(p);if(C)return clearTimeout(f),f=setTimeout(U,e),P(p)}return f===void 0&&(f=setTimeout(U,e)),c}return Ae.cancel=te,Ae.flush=ie,Ae}Nhe.exports=Tht});var rH=L((ZJt,Lhe)=>{var Rht=Ohe(),Fht=Wl(),Nht="Expected a function";function Oht(t,e,r){var s=!0,a=!0;if(typeof t!="function")throw new TypeError(Nht);return Fht(r)&&(s="leading"in r?!!r.leading:s,a="trailing"in r?!!r.trailing:a),Rht(t,e,{leading:s,maxWait:e,trailing:a})}Lhe.exports=Oht});function Mht(t){return typeof t.reportCode<"u"}var Mhe,_he,Uhe,Lht,Yt,ho,Fc=It(()=>{Mhe=et(rH()),_he=ye("stream"),Uhe=ye("string_decoder"),Lht=15,Yt=class extends Error{constructor(r,s,a){super(s);this.reportExtra=a;this.reportCode=r}};ho=class{constructor(){this.cacheHits=new Set;this.cacheMisses=new Set;this.reportedInfos=new Set;this.reportedWarnings=new Set;this.reportedErrors=new Set}getRecommendedLength(){return 180}reportCacheHit(e){this.cacheHits.add(e.locatorHash)}reportCacheMiss(e,r){this.cacheMisses.add(e.locatorHash)}static progressViaCounter(e){let r=0,s,a=new Promise(p=>{s=p}),n=p=>{let h=s;a=new Promise(E=>{s=E}),r=p,h()},c=(p=0)=>{n(r+1)},f=async function*(){for(;r{r=c}),a=(0,Mhe.default)(c=>{let f=r;s=new Promise(p=>{r=p}),e=c,f()},1e3/Lht),n=async function*(){for(;;)await s,yield{title:e}}();return{[Symbol.asyncIterator](){return n},hasProgress:!1,hasTitle:!0,setTitle:a}}async startProgressPromise(e,r){let s=this.reportProgress(e);try{return await r(e)}finally{s.stop()}}startProgressSync(e,r){let s=this.reportProgress(e);try{return r(e)}finally{s.stop()}}reportInfoOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedInfos.has(a)||(this.reportedInfos.add(a),this.reportInfo(e,r),s?.reportExtra?.(this))}reportWarningOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedWarnings.has(a)||(this.reportedWarnings.add(a),this.reportWarning(e,r),s?.reportExtra?.(this))}reportErrorOnce(e,r,s){let a=s&&s.key?s.key:r;this.reportedErrors.has(a)||(this.reportedErrors.add(a),this.reportError(e,r),s?.reportExtra?.(this))}reportExceptionOnce(e){Mht(e)?this.reportErrorOnce(e.reportCode,e.message,{key:e,reportExtra:e.reportExtra}):this.reportErrorOnce(1,e.stack||e.message,{key:e})}createStreamReporter(e=null){let r=new _he.PassThrough,s=new Uhe.StringDecoder,a="";return r.on("data",n=>{let c=s.write(n),f;do if(f=c.indexOf(` +`),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a="",e!==null?this.reportInfo(null,`${e} ${p}`):this.reportInfo(null,p)}while(f!==-1);a+=c}),r.on("end",()=>{let n=s.end();n!==""&&(e!==null?this.reportInfo(null,`${e} ${n}`):this.reportInfo(null,n))}),r}}});var lI,nH=It(()=>{Fc();Yo();lI=class{constructor(e){this.fetchers=e}supports(e,r){return!!this.tryFetcher(e,r)}getLocalPath(e,r){return this.getFetcher(e,r).getLocalPath(e,r)}async fetch(e,r){return await this.getFetcher(e,r).fetch(e,r)}tryFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));return s||null}getFetcher(e,r){let s=this.fetchers.find(a=>a.supports(e,r));if(!s)throw new Yt(11,`${Yr(r.project.configuration,e)} isn't supported by any available fetcher`);return s}}});var rm,iH=It(()=>{Yo();rm=class{constructor(e){this.resolvers=e.filter(r=>r)}supportsDescriptor(e,r){return!!this.tryResolverByDescriptor(e,r)}supportsLocator(e,r){return!!this.tryResolverByLocator(e,r)}shouldPersistResolution(e,r){return this.getResolverByLocator(e,r).shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.getResolverByDescriptor(e,s).bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.getResolverByDescriptor(e,r).getResolutionDependencies(e,r)}async getCandidates(e,r,s){return await this.getResolverByDescriptor(e,s).getCandidates(e,r,s)}async getSatisfying(e,r,s,a){return this.getResolverByDescriptor(e,a).getSatisfying(e,r,s,a)}async resolve(e,r){return await this.getResolverByLocator(e,r).resolve(e,r)}tryResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));return s||null}getResolverByDescriptor(e,r){let s=this.resolvers.find(a=>a.supportsDescriptor(e,r));if(!s)throw new Error(`${ri(r.project.configuration,e)} isn't supported by any available resolver`);return s}tryResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));return s||null}getResolverByLocator(e,r){let s=this.resolvers.find(a=>a.supportsLocator(e,r));if(!s)throw new Error(`${Yr(r.project.configuration,e)} isn't supported by any available resolver`);return s}}});var cI,sH=It(()=>{bt();Yo();cI=class{supports(e){return!!e.reference.startsWith("virtual:")}getLocalPath(e,r){let s=e.reference.indexOf("#");if(s===-1)throw new Error("Invalid virtual package reference");let a=e.reference.slice(s+1),n=Ys(e,a);return r.fetcher.getLocalPath(n,r)}async fetch(e,r){let s=e.reference.indexOf("#");if(s===-1)throw new Error("Invalid virtual package reference");let a=e.reference.slice(s+1),n=Ys(e,a),c=await r.fetcher.fetch(n,r);return await this.ensureVirtualLink(e,c,r)}getLocatorFilename(e){return nI(e)}async ensureVirtualLink(e,r,s){let a=r.packageFs.getRealPath(),n=s.project.configuration.get("virtualFolder"),c=this.getLocatorFilename(e),f=fo.makeVirtualPath(n,c,a),p=new Hf(f,{baseFs:r.packageFs,pathUtils:K});return{...r,packageFs:p}}}});var FQ,Hhe=It(()=>{FQ=class t{static{this.protocol="virtual:"}static isVirtualDescriptor(e){return!!e.range.startsWith(t.protocol)}static isVirtualLocator(e){return!!e.reference.startsWith(t.protocol)}supportsDescriptor(e,r){return t.isVirtualDescriptor(e)}supportsLocator(e,r){return t.isVirtualLocator(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){throw new Error('Assertion failed: calling "bindDescriptor" on a virtual descriptor is unsupported')}getResolutionDependencies(e,r){throw new Error('Assertion failed: calling "getResolutionDependencies" on a virtual descriptor is unsupported')}async getCandidates(e,r,s){throw new Error('Assertion failed: calling "getCandidates" on a virtual descriptor is unsupported')}async getSatisfying(e,r,s,a){throw new Error('Assertion failed: calling "getSatisfying" on a virtual descriptor is unsupported')}async resolve(e,r){throw new Error('Assertion failed: calling "resolve" on a virtual locator is unsupported')}}});var uI,oH=It(()=>{bt();tm();uI=class{supports(e){return!!e.reference.startsWith(yi.protocol)}getLocalPath(e,r){return this.getWorkspace(e,r).cwd}async fetch(e,r){let s=this.getWorkspace(e,r).cwd;return{packageFs:new Sn(s),prefixPath:vt.dot,localPath:s}}getWorkspace(e,r){return r.project.getWorkspaceByCwd(e.reference.slice(yi.protocol.length))}}});function iv(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function jhe(t){return typeof t>"u"?3:iv(t)?0:Array.isArray(t)?1:2}function cH(t,e){return Object.hasOwn(t,e)}function Uht(t){return iv(t)&&cH(t,"onConflict")&&typeof t.onConflict=="string"}function Hht(t){if(typeof t>"u")return{onConflict:"default",value:t};if(!Uht(t))return{onConflict:"default",value:t};if(cH(t,"value"))return t;let{onConflict:e,...r}=t;return{onConflict:e,value:r}}function qhe(t,e){let r=iv(t)&&cH(t,e)?t[e]:void 0;return Hht(r)}function fI(t,e){return[t,e,Ghe]}function uH(t){return Array.isArray(t)?t[2]===Ghe:!1}function aH(t,e){if(iv(t)){let r={};for(let s of Object.keys(t))r[s]=aH(t[s],e);return fI(e,r)}return Array.isArray(t)?fI(e,t.map(r=>aH(r,e))):fI(e,t)}function lH(t,e,r,s,a){let n,c=[],f=a,p=0;for(let E=a-1;E>=s;--E){let[C,S]=t[E],{onConflict:P,value:I}=qhe(S,r),R=jhe(I);if(R!==3){if(n??=R,R!==n||P==="hardReset"){p=f;break}if(R===2)return fI(C,I);if(c.unshift([C,I]),P==="reset"){p=E;break}P==="extend"&&E===s&&(s=0),f=E}}if(typeof n>"u")return null;let h=c.map(([E])=>E).join(", ");switch(n){case 1:return fI(h,new Array().concat(...c.map(([E,C])=>C.map(S=>aH(S,E)))));case 0:{let E=Object.assign({},...c.map(([,R])=>R)),C=Object.keys(E),S={},P=t.map(([R,N])=>[R,qhe(N,r).value]),I=_ht(P,([R,N])=>{let U=jhe(N);return U!==0&&U!==3});if(I!==-1){let R=P.slice(I+1);for(let N of C)S[N]=lH(R,e,N,0,R.length)}else for(let R of C)S[R]=lH(P,e,R,p,P.length);return fI(h,S)}default:throw new Error("Assertion failed: Non-extendable value type")}}function Whe(t){return lH(t.map(([e,r])=>[e,{".":r}]),[],".",0,t.length)}function sv(t){return uH(t)?t[1]:t}function NQ(t){let e=uH(t)?t[1]:t;if(Array.isArray(e))return e.map(r=>NQ(r));if(iv(e)){let r={};for(let[s,a]of Object.entries(e))r[s]=NQ(a);return r}return e}function fH(t){return uH(t)?t[0]:null}var _ht,Ghe,Yhe=It(()=>{_ht=(t,e,r)=>{let s=[...t];return s.reverse(),s.findIndex(e,r)};Ghe=Symbol()});var OQ={};Vt(OQ,{getDefaultGlobalFolder:()=>pH,getHomeFolder:()=>AI,isFolderInside:()=>hH});function pH(){if(process.platform==="win32"){let t=ue.toPortablePath(process.env.LOCALAPPDATA||ue.join((0,AH.homedir)(),"AppData","Local"));return K.resolve(t,"Yarn/Berry")}if(process.env.XDG_DATA_HOME){let t=ue.toPortablePath(process.env.XDG_DATA_HOME);return K.resolve(t,"yarn/berry")}return K.resolve(AI(),".yarn/berry")}function AI(){return ue.toPortablePath((0,AH.homedir)()||"/usr/local/share")}function hH(t,e){let r=K.relative(e,t);return r&&!r.startsWith("..")&&!K.isAbsolute(r)}var AH,LQ=It(()=>{bt();AH=ye("os")});var zhe=L(pI=>{"use strict";var uzt=ye("net"),qht=ye("tls"),gH=ye("http"),Vhe=ye("https"),Ght=ye("events"),fzt=ye("assert"),Wht=ye("util");pI.httpOverHttp=Yht;pI.httpsOverHttp=Vht;pI.httpOverHttps=Kht;pI.httpsOverHttps=Jht;function Yht(t){var e=new Op(t);return e.request=gH.request,e}function Vht(t){var e=new Op(t);return e.request=gH.request,e.createSocket=Khe,e.defaultPort=443,e}function Kht(t){var e=new Op(t);return e.request=Vhe.request,e}function Jht(t){var e=new Op(t);return e.request=Vhe.request,e.createSocket=Khe,e.defaultPort=443,e}function Op(t){var e=this;e.options=t||{},e.proxyOptions=e.options.proxy||{},e.maxSockets=e.options.maxSockets||gH.Agent.defaultMaxSockets,e.requests=[],e.sockets=[],e.on("free",function(s,a,n,c){for(var f=Jhe(a,n,c),p=0,h=e.requests.length;p=this.maxSockets){n.requests.push(c);return}n.createSocket(c,function(f){f.on("free",p),f.on("close",h),f.on("agentRemove",h),e.onSocket(f);function p(){n.emit("free",f,c)}function h(E){n.removeSocket(f),f.removeListener("free",p),f.removeListener("close",h),f.removeListener("agentRemove",h)}})};Op.prototype.createSocket=function(e,r){var s=this,a={};s.sockets.push(a);var n=dH({},s.proxyOptions,{method:"CONNECT",path:e.host+":"+e.port,agent:!1,headers:{host:e.host+":"+e.port}});e.localAddress&&(n.localAddress=e.localAddress),n.proxyAuth&&(n.headers=n.headers||{},n.headers["Proxy-Authorization"]="Basic "+new Buffer(n.proxyAuth).toString("base64")),w0("making CONNECT request");var c=s.request(n);c.useChunkedEncodingByDefault=!1,c.once("response",f),c.once("upgrade",p),c.once("connect",h),c.once("error",E),c.end();function f(C){C.upgrade=!0}function p(C,S,P){process.nextTick(function(){h(C,S,P)})}function h(C,S,P){if(c.removeAllListeners(),S.removeAllListeners(),C.statusCode!==200){w0("tunneling socket could not be established, statusCode=%d",C.statusCode),S.destroy();var I=new Error("tunneling socket could not be established, statusCode="+C.statusCode);I.code="ECONNRESET",e.request.emit("error",I),s.removeSocket(a);return}if(P.length>0){w0("got illegal response body from proxy"),S.destroy();var I=new Error("got illegal response body from proxy");I.code="ECONNRESET",e.request.emit("error",I),s.removeSocket(a);return}return w0("tunneling connection has established"),s.sockets[s.sockets.indexOf(a)]=S,r(S)}function E(C){c.removeAllListeners(),w0(`tunneling socket could not be established, cause=%s +`,C.message,C.stack);var S=new Error("tunneling socket could not be established, cause="+C.message);S.code="ECONNRESET",e.request.emit("error",S),s.removeSocket(a)}};Op.prototype.removeSocket=function(e){var r=this.sockets.indexOf(e);if(r!==-1){this.sockets.splice(r,1);var s=this.requests.shift();s&&this.createSocket(s,function(a){s.request.onSocket(a)})}};function Khe(t,e){var r=this;Op.prototype.createSocket.call(r,t,function(s){var a=t.request.getHeader("host"),n=dH({},r.options,{socket:s,servername:a?a.replace(/:.*$/,""):t.host}),c=qht.connect(0,n);r.sockets[r.sockets.indexOf(s)]=c,e(c)})}function Jhe(t,e,r){return typeof t=="string"?{host:t,port:e,localAddress:r}:t}function dH(t){for(var e=1,r=arguments.length;e{Zhe.exports=zhe()});var Mp=L((Lp,MQ)=>{"use strict";Object.defineProperty(Lp,"__esModule",{value:!0});var $he=["Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","Float32Array","Float64Array","BigInt64Array","BigUint64Array"];function zht(t){return $he.includes(t)}var Zht=["Function","Generator","AsyncGenerator","GeneratorFunction","AsyncGeneratorFunction","AsyncFunction","Observable","Array","Buffer","Blob","Object","RegExp","Date","Error","Map","Set","WeakMap","WeakSet","ArrayBuffer","SharedArrayBuffer","DataView","Promise","URL","FormData","URLSearchParams","HTMLElement",...$he];function Xht(t){return Zht.includes(t)}var $ht=["null","undefined","string","number","bigint","boolean","symbol"];function e0t(t){return $ht.includes(t)}function hI(t){return e=>typeof e===t}var{toString:e0e}=Object.prototype,ov=t=>{let e=e0e.call(t).slice(8,-1);if(/HTML\w+Element/.test(e)&&be.domElement(t))return"HTMLElement";if(Xht(e))return e},Ai=t=>e=>ov(e)===t;function be(t){if(t===null)return"null";switch(typeof t){case"undefined":return"undefined";case"string":return"string";case"number":return"number";case"boolean":return"boolean";case"function":return"Function";case"bigint":return"bigint";case"symbol":return"symbol";default:}if(be.observable(t))return"Observable";if(be.array(t))return"Array";if(be.buffer(t))return"Buffer";let e=ov(t);if(e)return e;if(t instanceof String||t instanceof Boolean||t instanceof Number)throw new TypeError("Please don't use object wrappers for primitive types");return"Object"}be.undefined=hI("undefined");be.string=hI("string");var t0t=hI("number");be.number=t=>t0t(t)&&!be.nan(t);be.bigint=hI("bigint");be.function_=hI("function");be.null_=t=>t===null;be.class_=t=>be.function_(t)&&t.toString().startsWith("class ");be.boolean=t=>t===!0||t===!1;be.symbol=hI("symbol");be.numericString=t=>be.string(t)&&!be.emptyStringOrWhitespace(t)&&!Number.isNaN(Number(t));be.array=(t,e)=>Array.isArray(t)?be.function_(e)?t.every(e):!0:!1;be.buffer=t=>{var e,r,s,a;return(a=(s=(r=(e=t)===null||e===void 0?void 0:e.constructor)===null||r===void 0?void 0:r.isBuffer)===null||s===void 0?void 0:s.call(r,t))!==null&&a!==void 0?a:!1};be.blob=t=>Ai("Blob")(t);be.nullOrUndefined=t=>be.null_(t)||be.undefined(t);be.object=t=>!be.null_(t)&&(typeof t=="object"||be.function_(t));be.iterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.iterator])};be.asyncIterable=t=>{var e;return be.function_((e=t)===null||e===void 0?void 0:e[Symbol.asyncIterator])};be.generator=t=>{var e,r;return be.iterable(t)&&be.function_((e=t)===null||e===void 0?void 0:e.next)&&be.function_((r=t)===null||r===void 0?void 0:r.throw)};be.asyncGenerator=t=>be.asyncIterable(t)&&be.function_(t.next)&&be.function_(t.throw);be.nativePromise=t=>Ai("Promise")(t);var r0t=t=>{var e,r;return be.function_((e=t)===null||e===void 0?void 0:e.then)&&be.function_((r=t)===null||r===void 0?void 0:r.catch)};be.promise=t=>be.nativePromise(t)||r0t(t);be.generatorFunction=Ai("GeneratorFunction");be.asyncGeneratorFunction=t=>ov(t)==="AsyncGeneratorFunction";be.asyncFunction=t=>ov(t)==="AsyncFunction";be.boundFunction=t=>be.function_(t)&&!t.hasOwnProperty("prototype");be.regExp=Ai("RegExp");be.date=Ai("Date");be.error=Ai("Error");be.map=t=>Ai("Map")(t);be.set=t=>Ai("Set")(t);be.weakMap=t=>Ai("WeakMap")(t);be.weakSet=t=>Ai("WeakSet")(t);be.int8Array=Ai("Int8Array");be.uint8Array=Ai("Uint8Array");be.uint8ClampedArray=Ai("Uint8ClampedArray");be.int16Array=Ai("Int16Array");be.uint16Array=Ai("Uint16Array");be.int32Array=Ai("Int32Array");be.uint32Array=Ai("Uint32Array");be.float32Array=Ai("Float32Array");be.float64Array=Ai("Float64Array");be.bigInt64Array=Ai("BigInt64Array");be.bigUint64Array=Ai("BigUint64Array");be.arrayBuffer=Ai("ArrayBuffer");be.sharedArrayBuffer=Ai("SharedArrayBuffer");be.dataView=Ai("DataView");be.enumCase=(t,e)=>Object.values(e).includes(t);be.directInstanceOf=(t,e)=>Object.getPrototypeOf(t)===e.prototype;be.urlInstance=t=>Ai("URL")(t);be.urlString=t=>{if(!be.string(t))return!1;try{return new URL(t),!0}catch{return!1}};be.truthy=t=>!!t;be.falsy=t=>!t;be.nan=t=>Number.isNaN(t);be.primitive=t=>be.null_(t)||e0t(typeof t);be.integer=t=>Number.isInteger(t);be.safeInteger=t=>Number.isSafeInteger(t);be.plainObject=t=>{if(e0e.call(t)!=="[object Object]")return!1;let e=Object.getPrototypeOf(t);return e===null||e===Object.getPrototypeOf({})};be.typedArray=t=>zht(ov(t));var n0t=t=>be.safeInteger(t)&&t>=0;be.arrayLike=t=>!be.nullOrUndefined(t)&&!be.function_(t)&&n0t(t.length);be.inRange=(t,e)=>{if(be.number(e))return t>=Math.min(0,e)&&t<=Math.max(e,0);if(be.array(e)&&e.length===2)return t>=Math.min(...e)&&t<=Math.max(...e);throw new TypeError(`Invalid range: ${JSON.stringify(e)}`)};var i0t=1,s0t=["innerHTML","ownerDocument","style","attributes","nodeValue"];be.domElement=t=>be.object(t)&&t.nodeType===i0t&&be.string(t.nodeName)&&!be.plainObject(t)&&s0t.every(e=>e in t);be.observable=t=>{var e,r,s,a;return t?t===((r=(e=t)[Symbol.observable])===null||r===void 0?void 0:r.call(e))||t===((a=(s=t)["@@observable"])===null||a===void 0?void 0:a.call(s)):!1};be.nodeStream=t=>be.object(t)&&be.function_(t.pipe)&&!be.observable(t);be.infinite=t=>t===1/0||t===-1/0;var t0e=t=>e=>be.integer(e)&&Math.abs(e%2)===t;be.evenInteger=t0e(0);be.oddInteger=t0e(1);be.emptyArray=t=>be.array(t)&&t.length===0;be.nonEmptyArray=t=>be.array(t)&&t.length>0;be.emptyString=t=>be.string(t)&&t.length===0;var o0t=t=>be.string(t)&&!/\S/.test(t);be.emptyStringOrWhitespace=t=>be.emptyString(t)||o0t(t);be.nonEmptyString=t=>be.string(t)&&t.length>0;be.nonEmptyStringAndNotWhitespace=t=>be.string(t)&&!be.emptyStringOrWhitespace(t);be.emptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length===0;be.nonEmptyObject=t=>be.object(t)&&!be.map(t)&&!be.set(t)&&Object.keys(t).length>0;be.emptySet=t=>be.set(t)&&t.size===0;be.nonEmptySet=t=>be.set(t)&&t.size>0;be.emptyMap=t=>be.map(t)&&t.size===0;be.nonEmptyMap=t=>be.map(t)&&t.size>0;be.propertyKey=t=>be.any([be.string,be.number,be.symbol],t);be.formData=t=>Ai("FormData")(t);be.urlSearchParams=t=>Ai("URLSearchParams")(t);var r0e=(t,e,r)=>{if(!be.function_(e))throw new TypeError(`Invalid predicate: ${JSON.stringify(e)}`);if(r.length===0)throw new TypeError("Invalid number of values");return t.call(r,e)};be.any=(t,...e)=>(be.array(t)?t:[t]).some(s=>r0e(Array.prototype.some,s,e));be.all=(t,...e)=>r0e(Array.prototype.every,t,e);var _t=(t,e,r,s={})=>{if(!t){let{multipleValues:a}=s,n=a?`received values of types ${[...new Set(r.map(c=>`\`${be(c)}\``))].join(", ")}`:`received value of type \`${be(r)}\``;throw new TypeError(`Expected value which is \`${e}\`, ${n}.`)}};Lp.assert={undefined:t=>_t(be.undefined(t),"undefined",t),string:t=>_t(be.string(t),"string",t),number:t=>_t(be.number(t),"number",t),bigint:t=>_t(be.bigint(t),"bigint",t),function_:t=>_t(be.function_(t),"Function",t),null_:t=>_t(be.null_(t),"null",t),class_:t=>_t(be.class_(t),"Class",t),boolean:t=>_t(be.boolean(t),"boolean",t),symbol:t=>_t(be.symbol(t),"symbol",t),numericString:t=>_t(be.numericString(t),"string with a number",t),array:(t,e)=>{_t(be.array(t),"Array",t),e&&t.forEach(e)},buffer:t=>_t(be.buffer(t),"Buffer",t),blob:t=>_t(be.blob(t),"Blob",t),nullOrUndefined:t=>_t(be.nullOrUndefined(t),"null or undefined",t),object:t=>_t(be.object(t),"Object",t),iterable:t=>_t(be.iterable(t),"Iterable",t),asyncIterable:t=>_t(be.asyncIterable(t),"AsyncIterable",t),generator:t=>_t(be.generator(t),"Generator",t),asyncGenerator:t=>_t(be.asyncGenerator(t),"AsyncGenerator",t),nativePromise:t=>_t(be.nativePromise(t),"native Promise",t),promise:t=>_t(be.promise(t),"Promise",t),generatorFunction:t=>_t(be.generatorFunction(t),"GeneratorFunction",t),asyncGeneratorFunction:t=>_t(be.asyncGeneratorFunction(t),"AsyncGeneratorFunction",t),asyncFunction:t=>_t(be.asyncFunction(t),"AsyncFunction",t),boundFunction:t=>_t(be.boundFunction(t),"Function",t),regExp:t=>_t(be.regExp(t),"RegExp",t),date:t=>_t(be.date(t),"Date",t),error:t=>_t(be.error(t),"Error",t),map:t=>_t(be.map(t),"Map",t),set:t=>_t(be.set(t),"Set",t),weakMap:t=>_t(be.weakMap(t),"WeakMap",t),weakSet:t=>_t(be.weakSet(t),"WeakSet",t),int8Array:t=>_t(be.int8Array(t),"Int8Array",t),uint8Array:t=>_t(be.uint8Array(t),"Uint8Array",t),uint8ClampedArray:t=>_t(be.uint8ClampedArray(t),"Uint8ClampedArray",t),int16Array:t=>_t(be.int16Array(t),"Int16Array",t),uint16Array:t=>_t(be.uint16Array(t),"Uint16Array",t),int32Array:t=>_t(be.int32Array(t),"Int32Array",t),uint32Array:t=>_t(be.uint32Array(t),"Uint32Array",t),float32Array:t=>_t(be.float32Array(t),"Float32Array",t),float64Array:t=>_t(be.float64Array(t),"Float64Array",t),bigInt64Array:t=>_t(be.bigInt64Array(t),"BigInt64Array",t),bigUint64Array:t=>_t(be.bigUint64Array(t),"BigUint64Array",t),arrayBuffer:t=>_t(be.arrayBuffer(t),"ArrayBuffer",t),sharedArrayBuffer:t=>_t(be.sharedArrayBuffer(t),"SharedArrayBuffer",t),dataView:t=>_t(be.dataView(t),"DataView",t),enumCase:(t,e)=>_t(be.enumCase(t,e),"EnumCase",t),urlInstance:t=>_t(be.urlInstance(t),"URL",t),urlString:t=>_t(be.urlString(t),"string with a URL",t),truthy:t=>_t(be.truthy(t),"truthy",t),falsy:t=>_t(be.falsy(t),"falsy",t),nan:t=>_t(be.nan(t),"NaN",t),primitive:t=>_t(be.primitive(t),"primitive",t),integer:t=>_t(be.integer(t),"integer",t),safeInteger:t=>_t(be.safeInteger(t),"integer",t),plainObject:t=>_t(be.plainObject(t),"plain object",t),typedArray:t=>_t(be.typedArray(t),"TypedArray",t),arrayLike:t=>_t(be.arrayLike(t),"array-like",t),domElement:t=>_t(be.domElement(t),"HTMLElement",t),observable:t=>_t(be.observable(t),"Observable",t),nodeStream:t=>_t(be.nodeStream(t),"Node.js Stream",t),infinite:t=>_t(be.infinite(t),"infinite number",t),emptyArray:t=>_t(be.emptyArray(t),"empty array",t),nonEmptyArray:t=>_t(be.nonEmptyArray(t),"non-empty array",t),emptyString:t=>_t(be.emptyString(t),"empty string",t),emptyStringOrWhitespace:t=>_t(be.emptyStringOrWhitespace(t),"empty string or whitespace",t),nonEmptyString:t=>_t(be.nonEmptyString(t),"non-empty string",t),nonEmptyStringAndNotWhitespace:t=>_t(be.nonEmptyStringAndNotWhitespace(t),"non-empty string and not whitespace",t),emptyObject:t=>_t(be.emptyObject(t),"empty object",t),nonEmptyObject:t=>_t(be.nonEmptyObject(t),"non-empty object",t),emptySet:t=>_t(be.emptySet(t),"empty set",t),nonEmptySet:t=>_t(be.nonEmptySet(t),"non-empty set",t),emptyMap:t=>_t(be.emptyMap(t),"empty map",t),nonEmptyMap:t=>_t(be.nonEmptyMap(t),"non-empty map",t),propertyKey:t=>_t(be.propertyKey(t),"PropertyKey",t),formData:t=>_t(be.formData(t),"FormData",t),urlSearchParams:t=>_t(be.urlSearchParams(t),"URLSearchParams",t),evenInteger:t=>_t(be.evenInteger(t),"even integer",t),oddInteger:t=>_t(be.oddInteger(t),"odd integer",t),directInstanceOf:(t,e)=>_t(be.directInstanceOf(t,e),"T",t),inRange:(t,e)=>_t(be.inRange(t,e),"in range",t),any:(t,...e)=>_t(be.any(t,...e),"predicate returns truthy for any value",e,{multipleValues:!0}),all:(t,...e)=>_t(be.all(t,...e),"predicate returns truthy for all values",e,{multipleValues:!0})};Object.defineProperties(be,{class:{value:be.class_},function:{value:be.function_},null:{value:be.null_}});Object.defineProperties(Lp.assert,{class:{value:Lp.assert.class_},function:{value:Lp.assert.function_},null:{value:Lp.assert.null_}});Lp.default=be;MQ.exports=be;MQ.exports.default=be;MQ.exports.assert=Lp.assert});var n0e=L((hzt,mH)=>{"use strict";var _Q=class extends Error{constructor(e){super(e||"Promise was canceled"),this.name="CancelError"}get isCanceled(){return!0}},UQ=class t{static fn(e){return(...r)=>new t((s,a,n)=>{r.push(n),e(...r).then(s,a)})}constructor(e){this._cancelHandlers=[],this._isPending=!0,this._isCanceled=!1,this._rejectOnCancel=!0,this._promise=new Promise((r,s)=>{this._reject=s;let a=f=>{this._isPending=!1,r(f)},n=f=>{this._isPending=!1,s(f)},c=f=>{if(!this._isPending)throw new Error("The `onCancel` handler was attached after the promise settled.");this._cancelHandlers.push(f)};return Object.defineProperties(c,{shouldReject:{get:()=>this._rejectOnCancel,set:f=>{this._rejectOnCancel=f}}}),e(a,n,c)})}then(e,r){return this._promise.then(e,r)}catch(e){return this._promise.catch(e)}finally(e){return this._promise.finally(e)}cancel(e){if(!(!this._isPending||this._isCanceled)){if(this._cancelHandlers.length>0)try{for(let r of this._cancelHandlers)r()}catch(r){this._reject(r)}this._isCanceled=!0,this._rejectOnCancel&&this._reject(new _Q(e))}}get isCanceled(){return this._isCanceled}};Object.setPrototypeOf(UQ.prototype,Promise.prototype);mH.exports=UQ;mH.exports.CancelError=_Q});var i0e=L((EH,IH)=>{"use strict";Object.defineProperty(EH,"__esModule",{value:!0});function a0t(t){return t.encrypted}var yH=(t,e)=>{let r;typeof e=="function"?r={connect:e}:r=e;let s=typeof r.connect=="function",a=typeof r.secureConnect=="function",n=typeof r.close=="function",c=()=>{s&&r.connect(),a0t(t)&&a&&(t.authorized?r.secureConnect():t.authorizationError||t.once("secureConnect",r.secureConnect)),n&&t.once("close",r.close)};t.writable&&!t.connecting?c():t.connecting?t.once("connect",c):t.destroyed&&n&&r.close(t._hadError)};EH.default=yH;IH.exports=yH;IH.exports.default=yH});var s0e=L((wH,BH)=>{"use strict";Object.defineProperty(wH,"__esModule",{value:!0});var l0t=i0e(),c0t=Number(process.versions.node.split(".")[0]),CH=t=>{let e={start:Date.now(),socket:void 0,lookup:void 0,connect:void 0,secureConnect:void 0,upload:void 0,response:void 0,end:void 0,error:void 0,abort:void 0,phases:{wait:void 0,dns:void 0,tcp:void 0,tls:void 0,request:void 0,firstByte:void 0,download:void 0,total:void 0}};t.timings=e;let r=c=>{let f=c.emit.bind(c);c.emit=(p,...h)=>(p==="error"&&(e.error=Date.now(),e.phases.total=e.error-e.start,c.emit=f),f(p,...h))};r(t),t.prependOnceListener("abort",()=>{e.abort=Date.now(),(!e.response||c0t>=13)&&(e.phases.total=Date.now()-e.start)});let s=c=>{e.socket=Date.now(),e.phases.wait=e.socket-e.start;let f=()=>{e.lookup=Date.now(),e.phases.dns=e.lookup-e.socket};c.prependOnceListener("lookup",f),l0t.default(c,{connect:()=>{e.connect=Date.now(),e.lookup===void 0&&(c.removeListener("lookup",f),e.lookup=e.connect,e.phases.dns=e.lookup-e.socket),e.phases.tcp=e.connect-e.lookup},secureConnect:()=>{e.secureConnect=Date.now(),e.phases.tls=e.secureConnect-e.connect}})};t.socket?s(t.socket):t.prependOnceListener("socket",s);let a=()=>{var c;e.upload=Date.now(),e.phases.request=e.upload-(c=e.secureConnect,c??e.connect)};return(typeof t.writableFinished=="boolean"?t.writableFinished:t.finished&&t.outputSize===0&&(!t.socket||t.socket.writableLength===0))?a():t.prependOnceListener("finish",a),t.prependOnceListener("response",c=>{e.response=Date.now(),e.phases.firstByte=e.response-e.upload,c.timings=e,r(c),c.prependOnceListener("end",()=>{e.end=Date.now(),e.phases.download=e.end-e.response,e.phases.total=e.end-e.start})}),e};wH.default=CH;BH.exports=CH;BH.exports.default=CH});var A0e=L((gzt,DH)=>{"use strict";var{V4MAPPED:u0t,ADDRCONFIG:f0t,ALL:f0e,promises:{Resolver:o0e},lookup:A0t}=ye("dns"),{promisify:vH}=ye("util"),p0t=ye("os"),gI=Symbol("cacheableLookupCreateConnection"),SH=Symbol("cacheableLookupInstance"),a0e=Symbol("expires"),h0t=typeof f0e=="number",l0e=t=>{if(!(t&&typeof t.createConnection=="function"))throw new Error("Expected an Agent instance as the first argument")},g0t=t=>{for(let e of t)e.family!==6&&(e.address=`::ffff:${e.address}`,e.family=6)},c0e=()=>{let t=!1,e=!1;for(let r of Object.values(p0t.networkInterfaces()))for(let s of r)if(!s.internal&&(s.family==="IPv6"?e=!0:t=!0,t&&e))return{has4:t,has6:e};return{has4:t,has6:e}},d0t=t=>Symbol.iterator in t,u0e={ttl:!0},m0t={all:!0},HQ=class{constructor({cache:e=new Map,maxTtl:r=1/0,fallbackDuration:s=3600,errorTtl:a=.15,resolver:n=new o0e,lookup:c=A0t}={}){if(this.maxTtl=r,this.errorTtl=a,this._cache=e,this._resolver=n,this._dnsLookup=vH(c),this._resolver instanceof o0e?(this._resolve4=this._resolver.resolve4.bind(this._resolver),this._resolve6=this._resolver.resolve6.bind(this._resolver)):(this._resolve4=vH(this._resolver.resolve4.bind(this._resolver)),this._resolve6=vH(this._resolver.resolve6.bind(this._resolver))),this._iface=c0e(),this._pending={},this._nextRemovalTime=!1,this._hostnamesToFallback=new Set,s<1)this._fallback=!1;else{this._fallback=!0;let f=setInterval(()=>{this._hostnamesToFallback.clear()},s*1e3);f.unref&&f.unref()}this.lookup=this.lookup.bind(this),this.lookupAsync=this.lookupAsync.bind(this)}set servers(e){this.clear(),this._resolver.setServers(e)}get servers(){return this._resolver.getServers()}lookup(e,r,s){if(typeof r=="function"?(s=r,r={}):typeof r=="number"&&(r={family:r}),!s)throw new Error("Callback must be a function.");this.lookupAsync(e,r).then(a=>{r.all?s(null,a):s(null,a.address,a.family,a.expires,a.ttl)},s)}async lookupAsync(e,r={}){typeof r=="number"&&(r={family:r});let s=await this.query(e);if(r.family===6){let a=s.filter(n=>n.family===6);r.hints&u0t&&(h0t&&r.hints&f0e||a.length===0)?g0t(s):s=a}else r.family===4&&(s=s.filter(a=>a.family===4));if(r.hints&f0t){let{_iface:a}=this;s=s.filter(n=>n.family===6?a.has6:a.has4)}if(s.length===0){let a=new Error(`cacheableLookup ENOTFOUND ${e}`);throw a.code="ENOTFOUND",a.hostname=e,a}return r.all?s:s[0]}async query(e){let r=await this._cache.get(e);if(!r){let s=this._pending[e];if(s)r=await s;else{let a=this.queryAndCache(e);this._pending[e]=a,r=await a}}return r=r.map(s=>({...s})),r}async _resolve(e){let r=async h=>{try{return await h}catch(E){if(E.code==="ENODATA"||E.code==="ENOTFOUND")return[];throw E}},[s,a]=await Promise.all([this._resolve4(e,u0e),this._resolve6(e,u0e)].map(h=>r(h))),n=0,c=0,f=0,p=Date.now();for(let h of s)h.family=4,h.expires=p+h.ttl*1e3,n=Math.max(n,h.ttl);for(let h of a)h.family=6,h.expires=p+h.ttl*1e3,c=Math.max(c,h.ttl);return s.length>0?a.length>0?f=Math.min(n,c):f=n:f=c,{entries:[...s,...a],cacheTtl:f}}async _lookup(e){try{return{entries:await this._dnsLookup(e,{all:!0}),cacheTtl:0}}catch{return{entries:[],cacheTtl:0}}}async _set(e,r,s){if(this.maxTtl>0&&s>0){s=Math.min(s,this.maxTtl)*1e3,r[a0e]=Date.now()+s;try{await this._cache.set(e,r,s)}catch(a){this.lookupAsync=async()=>{let n=new Error("Cache Error. Please recreate the CacheableLookup instance.");throw n.cause=a,n}}d0t(this._cache)&&this._tick(s)}}async queryAndCache(e){if(this._hostnamesToFallback.has(e))return this._dnsLookup(e,m0t);try{let r=await this._resolve(e);r.entries.length===0&&this._fallback&&(r=await this._lookup(e),r.entries.length!==0&&this._hostnamesToFallback.add(e));let s=r.entries.length===0?this.errorTtl:r.cacheTtl;return await this._set(e,r.entries,s),delete this._pending[e],r.entries}catch(r){throw delete this._pending[e],r}}_tick(e){let r=this._nextRemovalTime;(!r||e{this._nextRemovalTime=!1;let s=1/0,a=Date.now();for(let[n,c]of this._cache){let f=c[a0e];a>=f?this._cache.delete(n):f("lookup"in r||(r.lookup=this.lookup),e[gI](r,s))}uninstall(e){if(l0e(e),e[gI]){if(e[SH]!==this)throw new Error("The agent is not owned by this CacheableLookup instance");e.createConnection=e[gI],delete e[gI],delete e[SH]}}updateInterfaceInfo(){let{_iface:e}=this;this._iface=c0e(),(e.has4&&!this._iface.has4||e.has6&&!this._iface.has6)&&this._cache.clear()}clear(e){if(e){this._cache.delete(e);return}this._cache.clear()}};DH.exports=HQ;DH.exports.default=HQ});var g0e=L((dzt,bH)=>{"use strict";var y0t=typeof URL>"u"?ye("url").URL:URL,E0t="text/plain",I0t="us-ascii",p0e=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),C0t=(t,{stripHash:e})=>{let r=t.match(/^data:([^,]*?),([^#]*?)(?:#(.*))?$/);if(!r)throw new Error(`Invalid URL: ${t}`);let s=r[1].split(";"),a=r[2],n=e?"":r[3],c=!1;s[s.length-1]==="base64"&&(s.pop(),c=!0);let f=(s.shift()||"").toLowerCase(),h=[...s.map(E=>{let[C,S=""]=E.split("=").map(P=>P.trim());return C==="charset"&&(S=S.toLowerCase(),S===I0t)?"":`${C}${S?`=${S}`:""}`}).filter(Boolean)];return c&&h.push("base64"),(h.length!==0||f&&f!==E0t)&&h.unshift(f),`data:${h.join(";")},${c?a.trim():a}${n?`#${n}`:""}`},h0e=(t,e)=>{if(e={defaultProtocol:"http:",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripWWW:!0,removeQueryParameters:[/^utm_\w+/i],removeTrailingSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},Reflect.has(e,"normalizeHttps"))throw new Error("options.normalizeHttps is renamed to options.forceHttp");if(Reflect.has(e,"normalizeHttp"))throw new Error("options.normalizeHttp is renamed to options.forceHttps");if(Reflect.has(e,"stripFragment"))throw new Error("options.stripFragment is renamed to options.stripHash");if(t=t.trim(),/^data:/i.test(t))return C0t(t,e);let r=t.startsWith("//");!r&&/^\.*\//.test(t)||(t=t.replace(/^(?!(?:\w+:)?\/\/)|^\/\//,e.defaultProtocol));let a=new y0t(t);if(e.forceHttp&&e.forceHttps)throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");if(e.forceHttp&&a.protocol==="https:"&&(a.protocol="http:"),e.forceHttps&&a.protocol==="http:"&&(a.protocol="https:"),e.stripAuthentication&&(a.username="",a.password=""),e.stripHash&&(a.hash=""),a.pathname&&(a.pathname=a.pathname.replace(/((?!:).|^)\/{2,}/g,(n,c)=>/^(?!\/)/g.test(c)?`${c}/`:"/")),a.pathname&&(a.pathname=decodeURI(a.pathname)),e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let n=a.pathname.split("/"),c=n[n.length-1];p0e(c,e.removeDirectoryIndex)&&(n=n.slice(0,n.length-1),a.pathname=n.slice(1).join("/")+"/")}if(a.hostname&&(a.hostname=a.hostname.replace(/\.$/,""),e.stripWWW&&/^www\.([a-z\-\d]{2,63})\.([a-z.]{2,5})$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\./,""))),Array.isArray(e.removeQueryParameters))for(let n of[...a.searchParams.keys()])p0e(n,e.removeQueryParameters)&&a.searchParams.delete(n);return e.sortQueryParameters&&a.searchParams.sort(),e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\/$/,"")),t=a.toString(),(e.removeTrailingSlash||a.pathname==="/")&&a.hash===""&&(t=t.replace(/\/$/,"")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\/\//,"//")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\/\//,"")),t};bH.exports=h0e;bH.exports.default=h0e});var y0e=L((mzt,m0e)=>{m0e.exports=d0e;function d0e(t,e){if(t&&e)return d0e(t)(e);if(typeof t!="function")throw new TypeError("need wrapper function");return Object.keys(t).forEach(function(s){r[s]=t[s]}),r;function r(){for(var s=new Array(arguments.length),a=0;a{var E0e=y0e();PH.exports=E0e(jQ);PH.exports.strict=E0e(I0e);jQ.proto=jQ(function(){Object.defineProperty(Function.prototype,"once",{value:function(){return jQ(this)},configurable:!0}),Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return I0e(this)},configurable:!0})});function jQ(t){var e=function(){return e.called?e.value:(e.called=!0,e.value=t.apply(this,arguments))};return e.called=!1,e}function I0e(t){var e=function(){if(e.called)throw new Error(e.onceError);return e.called=!0,e.value=t.apply(this,arguments)},r=t.name||"Function wrapped with `once`";return e.onceError=r+" shouldn't be called more than once",e.called=!1,e}});var kH=L((Ezt,w0e)=>{var w0t=xH(),B0t=function(){},v0t=function(t){return t.setHeader&&typeof t.abort=="function"},S0t=function(t){return t.stdio&&Array.isArray(t.stdio)&&t.stdio.length===3},C0e=function(t,e,r){if(typeof e=="function")return C0e(t,null,e);e||(e={}),r=w0t(r||B0t);var s=t._writableState,a=t._readableState,n=e.readable||e.readable!==!1&&t.readable,c=e.writable||e.writable!==!1&&t.writable,f=function(){t.writable||p()},p=function(){c=!1,n||r.call(t)},h=function(){n=!1,c||r.call(t)},E=function(I){r.call(t,I?new Error("exited with error code: "+I):null)},C=function(I){r.call(t,I)},S=function(){if(n&&!(a&&a.ended))return r.call(t,new Error("premature close"));if(c&&!(s&&s.ended))return r.call(t,new Error("premature close"))},P=function(){t.req.on("finish",p)};return v0t(t)?(t.on("complete",p),t.on("abort",S),t.req?P():t.on("request",P)):c&&!s&&(t.on("end",f),t.on("close",f)),S0t(t)&&t.on("exit",E),t.on("end",h),t.on("finish",p),e.error!==!1&&t.on("error",C),t.on("close",S),function(){t.removeListener("complete",p),t.removeListener("abort",S),t.removeListener("request",P),t.req&&t.req.removeListener("finish",p),t.removeListener("end",f),t.removeListener("close",f),t.removeListener("finish",p),t.removeListener("exit",E),t.removeListener("end",h),t.removeListener("error",C),t.removeListener("close",S)}};w0e.exports=C0e});var S0e=L((Izt,v0e)=>{var D0t=xH(),b0t=kH(),QH=ye("fs"),av=function(){},P0t=/^v?\.0/.test(process.version),qQ=function(t){return typeof t=="function"},x0t=function(t){return!P0t||!QH?!1:(t instanceof(QH.ReadStream||av)||t instanceof(QH.WriteStream||av))&&qQ(t.close)},k0t=function(t){return t.setHeader&&qQ(t.abort)},Q0t=function(t,e,r,s){s=D0t(s);var a=!1;t.on("close",function(){a=!0}),b0t(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,x0t(t))return t.close(av);if(k0t(t))return t.abort();if(qQ(t.destroy))return t.destroy();s(c||new Error("stream was destroyed"))}}},B0e=function(t){t()},T0t=function(t,e){return t.pipe(e)},R0t=function(){var t=Array.prototype.slice.call(arguments),e=qQ(t[t.length-1]||av)&&t.pop()||av;if(Array.isArray(t[0])&&(t=t[0]),t.length<2)throw new Error("pump requires two streams per minimum");var r,s=t.map(function(a,n){var c=n0;return Q0t(a,c,f,function(p){r||(r=p),p&&s.forEach(B0e),!c&&(s.forEach(B0e),e(r))})});return t.reduce(T0t)};v0e.exports=R0t});var b0e=L((Czt,D0e)=>{"use strict";var{PassThrough:F0t}=ye("stream");D0e.exports=t=>{t={...t};let{array:e}=t,{encoding:r}=t,s=r==="buffer",a=!1;e?a=!(r||s):r=r||"utf8",s&&(r=null);let n=new F0t({objectMode:a});r&&n.setEncoding(r);let c=0,f=[];return n.on("data",p=>{f.push(p),a?c=f.length:c+=p.length}),n.getBufferedValue=()=>e?f:s?Buffer.concat(f,c):f.join(""),n.getBufferedLength=()=>c,n}});var P0e=L((wzt,dI)=>{"use strict";var N0t=S0e(),O0t=b0e(),GQ=class extends Error{constructor(){super("maxBuffer exceeded"),this.name="MaxBufferError"}};async function WQ(t,e){if(!t)return Promise.reject(new Error("Expected a stream"));e={maxBuffer:1/0,...e};let{maxBuffer:r}=e,s;return await new Promise((a,n)=>{let c=f=>{f&&(f.bufferedData=s.getBufferedValue()),n(f)};s=N0t(t,O0t(e),f=>{if(f){c(f);return}a()}),s.on("data",()=>{s.getBufferedLength()>r&&c(new GQ)})}),s.getBufferedValue()}dI.exports=WQ;dI.exports.default=WQ;dI.exports.buffer=(t,e)=>WQ(t,{...e,encoding:"buffer"});dI.exports.array=(t,e)=>WQ(t,{...e,array:!0});dI.exports.MaxBufferError=GQ});var k0e=L((vzt,x0e)=>{"use strict";var L0t=new Set([200,203,204,206,300,301,308,404,405,410,414,501]),M0t=new Set([200,203,204,300,301,302,303,307,308,404,405,410,414,501]),_0t=new Set([500,502,503,504]),U0t={date:!0,connection:!0,"keep-alive":!0,"proxy-authenticate":!0,"proxy-authorization":!0,te:!0,trailer:!0,"transfer-encoding":!0,upgrade:!0},H0t={"content-length":!0,"content-encoding":!0,"transfer-encoding":!0,"content-range":!0};function nm(t){let e=parseInt(t,10);return isFinite(e)?e:0}function j0t(t){return t?_0t.has(t.status):!0}function TH(t){let e={};if(!t)return e;let r=t.trim().split(/,/);for(let s of r){let[a,n]=s.split(/=/,2);e[a.trim()]=n===void 0?!0:n.trim().replace(/^"|"$/g,"")}return e}function q0t(t){let e=[];for(let r in t){let s=t[r];e.push(s===!0?r:r+"="+s)}if(e.length)return e.join(", ")}x0e.exports=class{constructor(e,r,{shared:s,cacheHeuristic:a,immutableMinTimeToLive:n,ignoreCargoCult:c,_fromObject:f}={}){if(f){this._fromObject(f);return}if(!r||!r.headers)throw Error("Response headers missing");this._assertRequestHasHeaders(e),this._responseTime=this.now(),this._isShared=s!==!1,this._cacheHeuristic=a!==void 0?a:.1,this._immutableMinTtl=n!==void 0?n:24*3600*1e3,this._status="status"in r?r.status:200,this._resHeaders=r.headers,this._rescc=TH(r.headers["cache-control"]),this._method="method"in e?e.method:"GET",this._url=e.url,this._host=e.headers.host,this._noAuthorization=!e.headers.authorization,this._reqHeaders=r.headers.vary?e.headers:null,this._reqcc=TH(e.headers["cache-control"]),c&&"pre-check"in this._rescc&&"post-check"in this._rescc&&(delete this._rescc["pre-check"],delete this._rescc["post-check"],delete this._rescc["no-cache"],delete this._rescc["no-store"],delete this._rescc["must-revalidate"],this._resHeaders=Object.assign({},this._resHeaders,{"cache-control":q0t(this._rescc)}),delete this._resHeaders.expires,delete this._resHeaders.pragma),r.headers["cache-control"]==null&&/no-cache/.test(r.headers.pragma)&&(this._rescc["no-cache"]=!0)}now(){return Date.now()}storable(){return!!(!this._reqcc["no-store"]&&(this._method==="GET"||this._method==="HEAD"||this._method==="POST"&&this._hasExplicitExpiration())&&M0t.has(this._status)&&!this._rescc["no-store"]&&(!this._isShared||!this._rescc.private)&&(!this._isShared||this._noAuthorization||this._allowsStoringAuthenticated())&&(this._resHeaders.expires||this._rescc["max-age"]||this._isShared&&this._rescc["s-maxage"]||this._rescc.public||L0t.has(this._status)))}_hasExplicitExpiration(){return this._isShared&&this._rescc["s-maxage"]||this._rescc["max-age"]||this._resHeaders.expires}_assertRequestHasHeaders(e){if(!e||!e.headers)throw Error("Request headers missing")}satisfiesWithoutRevalidation(e){this._assertRequestHasHeaders(e);let r=TH(e.headers["cache-control"]);return r["no-cache"]||/no-cache/.test(e.headers.pragma)||r["max-age"]&&this.age()>r["max-age"]||r["min-fresh"]&&this.timeToLive()<1e3*r["min-fresh"]||this.stale()&&!(r["max-stale"]&&!this._rescc["must-revalidate"]&&(r["max-stale"]===!0||r["max-stale"]>this.age()-this.maxAge()))?!1:this._requestMatches(e,!1)}_requestMatches(e,r){return(!this._url||this._url===e.url)&&this._host===e.headers.host&&(!e.method||this._method===e.method||r&&e.method==="HEAD")&&this._varyMatches(e)}_allowsStoringAuthenticated(){return this._rescc["must-revalidate"]||this._rescc.public||this._rescc["s-maxage"]}_varyMatches(e){if(!this._resHeaders.vary)return!0;if(this._resHeaders.vary==="*")return!1;let r=this._resHeaders.vary.trim().toLowerCase().split(/\s*,\s*/);for(let s of r)if(e.headers[s]!==this._reqHeaders[s])return!1;return!0}_copyWithoutHopByHopHeaders(e){let r={};for(let s in e)U0t[s]||(r[s]=e[s]);if(e.connection){let s=e.connection.trim().split(/\s*,\s*/);for(let a of s)delete r[a]}if(r.warning){let s=r.warning.split(/,/).filter(a=>!/^\s*1[0-9][0-9]/.test(a));s.length?r.warning=s.join(",").trim():delete r.warning}return r}responseHeaders(){let e=this._copyWithoutHopByHopHeaders(this._resHeaders),r=this.age();return r>3600*24&&!this._hasExplicitExpiration()&&this.maxAge()>3600*24&&(e.warning=(e.warning?`${e.warning}, `:"")+'113 - "rfc7234 5.5.4"'),e.age=`${Math.round(r)}`,e.date=new Date(this.now()).toUTCString(),e}date(){let e=Date.parse(this._resHeaders.date);return isFinite(e)?e:this._responseTime}age(){let e=this._ageValue(),r=(this.now()-this._responseTime)/1e3;return e+r}_ageValue(){return nm(this._resHeaders.age)}maxAge(){if(!this.storable()||this._rescc["no-cache"]||this._isShared&&this._resHeaders["set-cookie"]&&!this._rescc.public&&!this._rescc.immutable||this._resHeaders.vary==="*")return 0;if(this._isShared){if(this._rescc["proxy-revalidate"])return 0;if(this._rescc["s-maxage"])return nm(this._rescc["s-maxage"])}if(this._rescc["max-age"])return nm(this._rescc["max-age"]);let e=this._rescc.immutable?this._immutableMinTtl:0,r=this.date();if(this._resHeaders.expires){let s=Date.parse(this._resHeaders.expires);return Number.isNaN(s)||ss)return Math.max(e,(r-s)/1e3*this._cacheHeuristic)}return e}timeToLive(){let e=this.maxAge()-this.age(),r=e+nm(this._rescc["stale-if-error"]),s=e+nm(this._rescc["stale-while-revalidate"]);return Math.max(0,e,r,s)*1e3}stale(){return this.maxAge()<=this.age()}_useStaleIfError(){return this.maxAge()+nm(this._rescc["stale-if-error"])>this.age()}useStaleWhileRevalidate(){return this.maxAge()+nm(this._rescc["stale-while-revalidate"])>this.age()}static fromObject(e){return new this(void 0,void 0,{_fromObject:e})}_fromObject(e){if(this._responseTime)throw Error("Reinitialized");if(!e||e.v!==1)throw Error("Invalid serialization");this._responseTime=e.t,this._isShared=e.sh,this._cacheHeuristic=e.ch,this._immutableMinTtl=e.imm!==void 0?e.imm:24*3600*1e3,this._status=e.st,this._resHeaders=e.resh,this._rescc=e.rescc,this._method=e.m,this._url=e.u,this._host=e.h,this._noAuthorization=e.a,this._reqHeaders=e.reqh,this._reqcc=e.reqcc}toObject(){return{v:1,t:this._responseTime,sh:this._isShared,ch:this._cacheHeuristic,imm:this._immutableMinTtl,st:this._status,resh:this._resHeaders,rescc:this._rescc,m:this._method,u:this._url,h:this._host,a:this._noAuthorization,reqh:this._reqHeaders,reqcc:this._reqcc}}revalidationHeaders(e){this._assertRequestHasHeaders(e);let r=this._copyWithoutHopByHopHeaders(e.headers);if(delete r["if-range"],!this._requestMatches(e,!0)||!this.storable())return delete r["if-none-match"],delete r["if-modified-since"],r;if(this._resHeaders.etag&&(r["if-none-match"]=r["if-none-match"]?`${r["if-none-match"]}, ${this._resHeaders.etag}`:this._resHeaders.etag),r["accept-ranges"]||r["if-match"]||r["if-unmodified-since"]||this._method&&this._method!="GET"){if(delete r["if-modified-since"],r["if-none-match"]){let a=r["if-none-match"].split(/,/).filter(n=>!/^\s*W\//.test(n));a.length?r["if-none-match"]=a.join(",").trim():delete r["if-none-match"]}}else this._resHeaders["last-modified"]&&!r["if-modified-since"]&&(r["if-modified-since"]=this._resHeaders["last-modified"]);return r}revalidatedPolicy(e,r){if(this._assertRequestHasHeaders(e),this._useStaleIfError()&&j0t(r))return{modified:!1,matches:!1,policy:this};if(!r||!r.headers)throw Error("Response headers missing");let s=!1;if(r.status!==void 0&&r.status!=304?s=!1:r.headers.etag&&!/^\s*W\//.test(r.headers.etag)?s=this._resHeaders.etag&&this._resHeaders.etag.replace(/^\s*W\//,"")===r.headers.etag:this._resHeaders.etag&&r.headers.etag?s=this._resHeaders.etag.replace(/^\s*W\//,"")===r.headers.etag.replace(/^\s*W\//,""):this._resHeaders["last-modified"]?s=this._resHeaders["last-modified"]===r.headers["last-modified"]:!this._resHeaders.etag&&!this._resHeaders["last-modified"]&&!r.headers.etag&&!r.headers["last-modified"]&&(s=!0),!s)return{policy:new this.constructor(e,r),modified:r.status!=304,matches:!1};let a={};for(let c in this._resHeaders)a[c]=c in r.headers&&!H0t[c]?r.headers[c]:this._resHeaders[c];let n=Object.assign({},r,{status:this._status,method:this._method,headers:a});return{policy:new this.constructor(e,n,{shared:this._isShared,cacheHeuristic:this._cacheHeuristic,immutableMinTimeToLive:this._immutableMinTtl}),modified:!1,matches:!0}}}});var YQ=L((Szt,Q0e)=>{"use strict";Q0e.exports=t=>{let e={};for(let[r,s]of Object.entries(t))e[r.toLowerCase()]=s;return e}});var R0e=L((Dzt,T0e)=>{"use strict";var G0t=ye("stream").Readable,W0t=YQ(),RH=class extends G0t{constructor(e,r,s,a){if(typeof e!="number")throw new TypeError("Argument `statusCode` should be a number");if(typeof r!="object")throw new TypeError("Argument `headers` should be an object");if(!(s instanceof Buffer))throw new TypeError("Argument `body` should be a buffer");if(typeof a!="string")throw new TypeError("Argument `url` should be a string");super(),this.statusCode=e,this.headers=W0t(r),this.body=s,this.url=a}_read(){this.push(this.body),this.push(null)}};T0e.exports=RH});var N0e=L((bzt,F0e)=>{"use strict";var Y0t=["destroy","setTimeout","socket","headers","trailers","rawHeaders","statusCode","httpVersion","httpVersionMinor","httpVersionMajor","rawTrailers","statusMessage"];F0e.exports=(t,e)=>{let r=new Set(Object.keys(t).concat(Y0t));for(let s of r)s in e||(e[s]=typeof t[s]=="function"?t[s].bind(t):t[s])}});var L0e=L((Pzt,O0e)=>{"use strict";var V0t=ye("stream").PassThrough,K0t=N0e(),J0t=t=>{if(!(t&&t.pipe))throw new TypeError("Parameter `response` must be a response stream.");let e=new V0t;return K0t(t,e),t.pipe(e)};O0e.exports=J0t});var M0e=L(FH=>{FH.stringify=function t(e){if(typeof e>"u")return e;if(e&&Buffer.isBuffer(e))return JSON.stringify(":base64:"+e.toString("base64"));if(e&&e.toJSON&&(e=e.toJSON()),e&&typeof e=="object"){var r="",s=Array.isArray(e);r=s?"[":"{";var a=!0;for(var n in e){var c=typeof e[n]=="function"||!s&&typeof e[n]>"u";Object.hasOwnProperty.call(e,n)&&!c&&(a||(r+=","),a=!1,s?e[n]==null?r+="null":r+=t(e[n]):e[n]!==void 0&&(r+=t(n)+":"+t(e[n])))}return r+=s?"]":"}",r}else return typeof e=="string"?JSON.stringify(/^:/.test(e)?":"+e:e):typeof e>"u"?"null":JSON.stringify(e)};FH.parse=function(t){return JSON.parse(t,function(e,r){return typeof r=="string"?/^:base64:/.test(r)?Buffer.from(r.substring(8),"base64"):/^:/.test(r)?r.substring(1):r:r})}});var j0e=L((kzt,H0e)=>{"use strict";var z0t=ye("events"),_0e=M0e(),Z0t=t=>{let e={redis:"@keyv/redis",rediss:"@keyv/redis",mongodb:"@keyv/mongo",mongo:"@keyv/mongo",sqlite:"@keyv/sqlite",postgresql:"@keyv/postgres",postgres:"@keyv/postgres",mysql:"@keyv/mysql",etcd:"@keyv/etcd",offline:"@keyv/offline",tiered:"@keyv/tiered"};if(t.adapter||t.uri){let r=t.adapter||/^[^:+]*/.exec(t.uri)[0];return new(ye(e[r]))(t)}return new Map},U0e=["sqlite","postgres","mysql","mongo","redis","tiered"],NH=class extends z0t{constructor(e,{emitErrors:r=!0,...s}={}){if(super(),this.opts={namespace:"keyv",serialize:_0e.stringify,deserialize:_0e.parse,...typeof e=="string"?{uri:e}:e,...s},!this.opts.store){let n={...this.opts};this.opts.store=Z0t(n)}if(this.opts.compression){let n=this.opts.compression;this.opts.serialize=n.serialize.bind(n),this.opts.deserialize=n.deserialize.bind(n)}typeof this.opts.store.on=="function"&&r&&this.opts.store.on("error",n=>this.emit("error",n)),this.opts.store.namespace=this.opts.namespace;let a=n=>async function*(){for await(let[c,f]of typeof n=="function"?n(this.opts.store.namespace):n){let p=await this.opts.deserialize(f);if(!(this.opts.store.namespace&&!c.includes(this.opts.store.namespace))){if(typeof p.expires=="number"&&Date.now()>p.expires){this.delete(c);continue}yield[this._getKeyUnprefix(c),p.value]}}};typeof this.opts.store[Symbol.iterator]=="function"&&this.opts.store instanceof Map?this.iterator=a(this.opts.store):typeof this.opts.store.iterator=="function"&&this.opts.store.opts&&this._checkIterableAdaptar()&&(this.iterator=a(this.opts.store.iterator.bind(this.opts.store)))}_checkIterableAdaptar(){return U0e.includes(this.opts.store.opts.dialect)||U0e.findIndex(e=>this.opts.store.opts.url.includes(e))>=0}_getKeyPrefix(e){return`${this.opts.namespace}:${e}`}_getKeyPrefixArray(e){return e.map(r=>`${this.opts.namespace}:${r}`)}_getKeyUnprefix(e){return e.split(":").splice(1).join(":")}get(e,r){let{store:s}=this.opts,a=Array.isArray(e),n=a?this._getKeyPrefixArray(e):this._getKeyPrefix(e);if(a&&s.getMany===void 0){let c=[];for(let f of n)c.push(Promise.resolve().then(()=>s.get(f)).then(p=>typeof p=="string"?this.opts.deserialize(p):this.opts.compression?this.opts.deserialize(p):p).then(p=>{if(p!=null)return typeof p.expires=="number"&&Date.now()>p.expires?this.delete(f).then(()=>{}):r&&r.raw?p:p.value}));return Promise.allSettled(c).then(f=>{let p=[];for(let h of f)p.push(h.value);return p})}return Promise.resolve().then(()=>a?s.getMany(n):s.get(n)).then(c=>typeof c=="string"?this.opts.deserialize(c):this.opts.compression?this.opts.deserialize(c):c).then(c=>{if(c!=null)return a?c.map((f,p)=>{if(typeof f=="string"&&(f=this.opts.deserialize(f)),f!=null){if(typeof f.expires=="number"&&Date.now()>f.expires){this.delete(e[p]).then(()=>{});return}return r&&r.raw?f:f.value}}):typeof c.expires=="number"&&Date.now()>c.expires?this.delete(e).then(()=>{}):r&&r.raw?c:c.value})}set(e,r,s){let a=this._getKeyPrefix(e);typeof s>"u"&&(s=this.opts.ttl),s===0&&(s=void 0);let{store:n}=this.opts;return Promise.resolve().then(()=>{let c=typeof s=="number"?Date.now()+s:null;return typeof r=="symbol"&&this.emit("error","symbol cannot be serialized"),r={value:r,expires:c},this.opts.serialize(r)}).then(c=>n.set(a,c,s)).then(()=>!0)}delete(e){let{store:r}=this.opts;if(Array.isArray(e)){let a=this._getKeyPrefixArray(e);if(r.deleteMany===void 0){let n=[];for(let c of a)n.push(r.delete(c));return Promise.allSettled(n).then(c=>c.every(f=>f.value===!0))}return Promise.resolve().then(()=>r.deleteMany(a))}let s=this._getKeyPrefix(e);return Promise.resolve().then(()=>r.delete(s))}clear(){let{store:e}=this.opts;return Promise.resolve().then(()=>e.clear())}has(e){let r=this._getKeyPrefix(e),{store:s}=this.opts;return Promise.resolve().then(async()=>typeof s.has=="function"?s.has(r):await s.get(r)!==void 0)}disconnect(){let{store:e}=this.opts;if(typeof e.disconnect=="function")return e.disconnect()}};H0e.exports=NH});var W0e=L((Tzt,G0e)=>{"use strict";var X0t=ye("events"),VQ=ye("url"),$0t=g0e(),egt=P0e(),OH=k0e(),q0e=R0e(),tgt=YQ(),rgt=L0e(),ngt=j0e(),lv=class t{constructor(e,r){if(typeof e!="function")throw new TypeError("Parameter `request` must be a function");return this.cache=new ngt({uri:typeof r=="string"&&r,store:typeof r!="string"&&r,namespace:"cacheable-request"}),this.createCacheableRequest(e)}createCacheableRequest(e){return(r,s)=>{let a;if(typeof r=="string")a=LH(VQ.parse(r)),r={};else if(r instanceof VQ.URL)a=LH(VQ.parse(r.toString())),r={};else{let[C,...S]=(r.path||"").split("?"),P=S.length>0?`?${S.join("?")}`:"";a=LH({...r,pathname:C,search:P})}r={headers:{},method:"GET",cache:!0,strictTtl:!1,automaticFailover:!1,...r,...igt(a)},r.headers=tgt(r.headers);let n=new X0t,c=$0t(VQ.format(a),{stripWWW:!1,removeTrailingSlash:!1,stripAuthentication:!1}),f=`${r.method}:${c}`,p=!1,h=!1,E=C=>{h=!0;let S=!1,P,I=new Promise(N=>{P=()=>{S||(S=!0,N())}}),R=N=>{if(p&&!C.forceRefresh){N.status=N.statusCode;let W=OH.fromObject(p.cachePolicy).revalidatedPolicy(C,N);if(!W.modified){let te=W.policy.responseHeaders();N=new q0e(p.statusCode,te,p.body,p.url),N.cachePolicy=W.policy,N.fromCache=!0}}N.fromCache||(N.cachePolicy=new OH(C,N,C),N.fromCache=!1);let U;C.cache&&N.cachePolicy.storable()?(U=rgt(N),(async()=>{try{let W=egt.buffer(N);if(await Promise.race([I,new Promise(ce=>N.once("end",ce))]),S)return;let te=await W,ie={cachePolicy:N.cachePolicy.toObject(),url:N.url,statusCode:N.fromCache?p.statusCode:N.statusCode,body:te},Ae=C.strictTtl?N.cachePolicy.timeToLive():void 0;C.maxTtl&&(Ae=Ae?Math.min(Ae,C.maxTtl):C.maxTtl),await this.cache.set(f,ie,Ae)}catch(W){n.emit("error",new t.CacheError(W))}})()):C.cache&&p&&(async()=>{try{await this.cache.delete(f)}catch(W){n.emit("error",new t.CacheError(W))}})(),n.emit("response",U||N),typeof s=="function"&&s(U||N)};try{let N=e(C,R);N.once("error",P),N.once("abort",P),n.emit("request",N)}catch(N){n.emit("error",new t.RequestError(N))}};return(async()=>{let C=async P=>{await Promise.resolve();let I=P.cache?await this.cache.get(f):void 0;if(typeof I>"u")return E(P);let R=OH.fromObject(I.cachePolicy);if(R.satisfiesWithoutRevalidation(P)&&!P.forceRefresh){let N=R.responseHeaders(),U=new q0e(I.statusCode,N,I.body,I.url);U.cachePolicy=R,U.fromCache=!0,n.emit("response",U),typeof s=="function"&&s(U)}else p=I,P.headers=R.revalidationHeaders(P),E(P)},S=P=>n.emit("error",new t.CacheError(P));this.cache.once("error",S),n.on("response",()=>this.cache.removeListener("error",S));try{await C(r)}catch(P){r.automaticFailover&&!h&&E(r),n.emit("error",new t.CacheError(P))}})(),n}}};function igt(t){let e={...t};return e.path=`${t.pathname||"/"}${t.search||""}`,delete e.pathname,delete e.search,e}function LH(t){return{protocol:t.protocol,auth:t.auth,hostname:t.hostname||t.host||"localhost",port:t.port,pathname:t.pathname,search:t.search}}lv.RequestError=class extends Error{constructor(t){super(t.message),this.name="RequestError",Object.assign(this,t)}};lv.CacheError=class extends Error{constructor(t){super(t.message),this.name="CacheError",Object.assign(this,t)}};G0e.exports=lv});var V0e=L((Nzt,Y0e)=>{"use strict";var sgt=["aborted","complete","headers","httpVersion","httpVersionMinor","httpVersionMajor","method","rawHeaders","rawTrailers","setTimeout","socket","statusCode","statusMessage","trailers","url"];Y0e.exports=(t,e)=>{if(e._readableState.autoDestroy)throw new Error("The second stream must have the `autoDestroy` option set to `false`");let r=new Set(Object.keys(t).concat(sgt)),s={};for(let a of r)a in e||(s[a]={get(){let n=t[a];return typeof n=="function"?n.bind(t):n},set(n){t[a]=n},enumerable:!0,configurable:!1});return Object.defineProperties(e,s),t.once("aborted",()=>{e.destroy(),e.emit("aborted")}),t.once("close",()=>{t.complete&&e.readable?e.once("end",()=>{e.emit("close")}):e.emit("close")}),e}});var J0e=L((Ozt,K0e)=>{"use strict";var{Transform:ogt,PassThrough:agt}=ye("stream"),MH=ye("zlib"),lgt=V0e();K0e.exports=t=>{let e=(t.headers["content-encoding"]||"").toLowerCase();if(!["gzip","deflate","br"].includes(e))return t;let r=e==="br";if(r&&typeof MH.createBrotliDecompress!="function")return t.destroy(new Error("Brotli is not supported on Node.js < 12")),t;let s=!0,a=new ogt({transform(f,p,h){s=!1,h(null,f)},flush(f){f()}}),n=new agt({autoDestroy:!1,destroy(f,p){t.destroy(),p(f)}}),c=r?MH.createBrotliDecompress():MH.createUnzip();return c.once("error",f=>{if(s&&!t.readable){n.end();return}n.destroy(f)}),lgt(t,n),t.pipe(a).pipe(c).pipe(n),n}});var UH=L((Lzt,z0e)=>{"use strict";var _H=class{constructor(e={}){if(!(e.maxSize&&e.maxSize>0))throw new TypeError("`maxSize` must be a number greater than 0");this.maxSize=e.maxSize,this.onEviction=e.onEviction,this.cache=new Map,this.oldCache=new Map,this._size=0}_set(e,r){if(this.cache.set(e,r),this._size++,this._size>=this.maxSize){if(this._size=0,typeof this.onEviction=="function")for(let[s,a]of this.oldCache.entries())this.onEviction(s,a);this.oldCache=this.cache,this.cache=new Map}}get(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e)){let r=this.oldCache.get(e);return this.oldCache.delete(e),this._set(e,r),r}}set(e,r){return this.cache.has(e)?this.cache.set(e,r):this._set(e,r),this}has(e){return this.cache.has(e)||this.oldCache.has(e)}peek(e){if(this.cache.has(e))return this.cache.get(e);if(this.oldCache.has(e))return this.oldCache.get(e)}delete(e){let r=this.cache.delete(e);return r&&this._size--,this.oldCache.delete(e)||r}clear(){this.cache.clear(),this.oldCache.clear(),this._size=0}*keys(){for(let[e]of this)yield e}*values(){for(let[,e]of this)yield e}*[Symbol.iterator](){for(let e of this.cache)yield e;for(let e of this.oldCache){let[r]=e;this.cache.has(r)||(yield e)}}get size(){let e=0;for(let r of this.oldCache.keys())this.cache.has(r)||e++;return Math.min(this._size+e,this.maxSize)}};z0e.exports=_H});var jH=L((Mzt,ege)=>{"use strict";var cgt=ye("events"),ugt=ye("tls"),fgt=ye("http2"),Agt=UH(),xa=Symbol("currentStreamsCount"),Z0e=Symbol("request"),Nc=Symbol("cachedOriginSet"),mI=Symbol("gracefullyClosing"),pgt=["maxDeflateDynamicTableSize","maxSessionMemory","maxHeaderListPairs","maxOutstandingPings","maxReservedRemoteStreams","maxSendHeaderBlockLength","paddingStrategy","localAddress","path","rejectUnauthorized","minDHSize","ca","cert","clientCertEngine","ciphers","key","pfx","servername","minVersion","maxVersion","secureProtocol","crl","honorCipherOrder","ecdhCurve","dhparam","secureOptions","sessionIdContext"],hgt=(t,e,r)=>{let s=0,a=t.length;for(;s>>1;r(t[n],e)?s=n+1:a=n}return s},ggt=(t,e)=>t.remoteSettings.maxConcurrentStreams>e.remoteSettings.maxConcurrentStreams,HH=(t,e)=>{for(let r of t)r[Nc].lengthe[Nc].includes(s))&&r[xa]+e[xa]<=e.remoteSettings.maxConcurrentStreams&&$0e(r)},dgt=(t,e)=>{for(let r of t)e[Nc].lengthr[Nc].includes(s))&&e[xa]+r[xa]<=r.remoteSettings.maxConcurrentStreams&&$0e(e)},X0e=({agent:t,isFree:e})=>{let r={};for(let s in t.sessions){let n=t.sessions[s].filter(c=>{let f=c[im.kCurrentStreamsCount]{t[mI]=!0,t[xa]===0&&t.close()},im=class t extends cgt{constructor({timeout:e=6e4,maxSessions:r=1/0,maxFreeSessions:s=10,maxCachedTlsSessions:a=100}={}){super(),this.sessions={},this.queue={},this.timeout=e,this.maxSessions=r,this.maxFreeSessions=s,this._freeSessionsCount=0,this._sessionsCount=0,this.settings={enablePush:!1},this.tlsSessionCache=new Agt({maxSize:a})}static normalizeOrigin(e,r){return typeof e=="string"&&(e=new URL(e)),r&&e.hostname!==r&&(e.hostname=r),e.origin}normalizeOptions(e){let r="";if(e)for(let s of pgt)e[s]&&(r+=`:${e[s]}`);return r}_tryToCreateNewSession(e,r){if(!(e in this.queue)||!(r in this.queue[e]))return;let s=this.queue[e][r];this._sessionsCount{Array.isArray(s)?(s=[...s],a()):s=[{resolve:a,reject:n}];let c=this.normalizeOptions(r),f=t.normalizeOrigin(e,r&&r.servername);if(f===void 0){for(let{reject:E}of s)E(new TypeError("The `origin` argument needs to be a string or an URL object"));return}if(c in this.sessions){let E=this.sessions[c],C=-1,S=-1,P;for(let I of E){let R=I.remoteSettings.maxConcurrentStreams;if(R=R||I[mI]||I.destroyed)continue;P||(C=R),N>S&&(P=I,S=N)}}if(P){if(s.length!==1){for(let{reject:I}of s){let R=new Error(`Expected the length of listeners to be 1, got ${s.length}. +Please report this to https://github.com/szmarczak/http2-wrapper/`);I(R)}return}s[0].resolve(P);return}}if(c in this.queue){if(f in this.queue[c]){this.queue[c][f].listeners.push(...s),this._tryToCreateNewSession(c,f);return}}else this.queue[c]={};let p=()=>{c in this.queue&&this.queue[c][f]===h&&(delete this.queue[c][f],Object.keys(this.queue[c]).length===0&&delete this.queue[c])},h=()=>{let E=`${f}:${c}`,C=!1;try{let S=fgt.connect(e,{createConnection:this.createConnection,settings:this.settings,session:this.tlsSessionCache.get(E),...r});S[xa]=0,S[mI]=!1;let P=()=>S[xa]{this.tlsSessionCache.set(E,N)}),S.once("error",N=>{for(let{reject:U}of s)U(N);this.tlsSessionCache.delete(E)}),S.setTimeout(this.timeout,()=>{S.destroy()}),S.once("close",()=>{if(C){I&&this._freeSessionsCount--,this._sessionsCount--;let N=this.sessions[c];N.splice(N.indexOf(S),1),N.length===0&&delete this.sessions[c]}else{let N=new Error("Session closed without receiving a SETTINGS frame");N.code="HTTP2WRAPPER_NOSETTINGS";for(let{reject:U}of s)U(N);p()}this._tryToCreateNewSession(c,f)});let R=()=>{if(!(!(c in this.queue)||!P())){for(let N of S[Nc])if(N in this.queue[c]){let{listeners:U}=this.queue[c][N];for(;U.length!==0&&P();)U.shift().resolve(S);let W=this.queue[c];if(W[N].listeners.length===0&&(delete W[N],Object.keys(W).length===0)){delete this.queue[c];break}if(!P())break}}};S.on("origin",()=>{S[Nc]=S.originSet,P()&&(R(),HH(this.sessions[c],S))}),S.once("remoteSettings",()=>{if(S.ref(),S.unref(),this._sessionsCount++,h.destroyed){let N=new Error("Agent has been destroyed");for(let U of s)U.reject(N);S.destroy();return}S[Nc]=S.originSet;{let N=this.sessions;if(c in N){let U=N[c];U.splice(hgt(U,S,ggt),0,S)}else N[c]=[S]}this._freeSessionsCount+=1,C=!0,this.emit("session",S),R(),p(),S[xa]===0&&this._freeSessionsCount>this.maxFreeSessions&&S.close(),s.length!==0&&(this.getSession(f,r,s),s.length=0),S.on("remoteSettings",()=>{R(),HH(this.sessions[c],S)})}),S[Z0e]=S.request,S.request=(N,U)=>{if(S[mI])throw new Error("The session is gracefully closing. No new streams are allowed.");let W=S[Z0e](N,U);return S.ref(),++S[xa],S[xa]===S.remoteSettings.maxConcurrentStreams&&this._freeSessionsCount--,W.once("close",()=>{if(I=P(),--S[xa],!S.destroyed&&!S.closed&&(dgt(this.sessions[c],S),P()&&!S.closed)){I||(this._freeSessionsCount++,I=!0);let te=S[xa]===0;te&&S.unref(),te&&(this._freeSessionsCount>this.maxFreeSessions||S[mI])?S.close():(HH(this.sessions[c],S),R())}}),W}}catch(S){for(let P of s)P.reject(S);p()}};h.listeners=s,h.completed=!1,h.destroyed=!1,this.queue[c][f]=h,this._tryToCreateNewSession(c,f)})}request(e,r,s,a){return new Promise((n,c)=>{this.getSession(e,r,[{reject:c,resolve:f=>{try{n(f.request(s,a))}catch(p){c(p)}}}])})}createConnection(e,r){return t.connect(e,r)}static connect(e,r){r.ALPNProtocols=["h2"];let s=e.port||443,a=e.hostname||e.host;return typeof r.servername>"u"&&(r.servername=a),ugt.connect(s,a,r)}closeFreeSessions(){for(let e of Object.values(this.sessions))for(let r of e)r[xa]===0&&r.close()}destroy(e){for(let r of Object.values(this.sessions))for(let s of r)s.destroy(e);for(let r of Object.values(this.queue))for(let s of Object.values(r))s.destroyed=!0;this.queue={}}get freeSessions(){return X0e({agent:this,isFree:!0})}get busySessions(){return X0e({agent:this,isFree:!1})}};im.kCurrentStreamsCount=xa;im.kGracefullyClosing=mI;ege.exports={Agent:im,globalAgent:new im}});var GH=L((_zt,tge)=>{"use strict";var{Readable:mgt}=ye("stream"),qH=class extends mgt{constructor(e,r){super({highWaterMark:r,autoDestroy:!1}),this.statusCode=null,this.statusMessage="",this.httpVersion="2.0",this.httpVersionMajor=2,this.httpVersionMinor=0,this.headers={},this.trailers={},this.req=null,this.aborted=!1,this.complete=!1,this.upgrade=null,this.rawHeaders=[],this.rawTrailers=[],this.socket=e,this.connection=e,this._dumped=!1}_destroy(e){this.req._request.destroy(e)}setTimeout(e,r){return this.req.setTimeout(e,r),this}_dump(){this._dumped||(this._dumped=!0,this.removeAllListeners("data"),this.resume())}_read(){this.req&&this.req._request.resume()}};tge.exports=qH});var WH=L((Uzt,rge)=>{"use strict";rge.exports=t=>{let e={protocol:t.protocol,hostname:typeof t.hostname=="string"&&t.hostname.startsWith("[")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||""}${t.search||""}`};return typeof t.port=="string"&&t.port.length!==0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||""}:${t.password||""}`),e}});var ige=L((Hzt,nge)=>{"use strict";nge.exports=(t,e,r)=>{for(let s of r)t.on(s,(...a)=>e.emit(s,...a))}});var oge=L((jzt,sge)=>{"use strict";sge.exports=t=>{switch(t){case":method":case":scheme":case":authority":case":path":return!0;default:return!1}}});var lge=L((Gzt,age)=>{"use strict";var yI=(t,e,r)=>{age.exports[e]=class extends t{constructor(...a){super(typeof r=="string"?r:r(a)),this.name=`${super.name} [${e}]`,this.code=e}}};yI(TypeError,"ERR_INVALID_ARG_TYPE",t=>{let e=t[0].includes(".")?"property":"argument",r=t[1],s=Array.isArray(r);return s&&(r=`${r.slice(0,-1).join(", ")} or ${r.slice(-1)}`),`The "${t[0]}" ${e} must be ${s?"one of":"of"} type ${r}. Received ${typeof t[2]}`});yI(TypeError,"ERR_INVALID_PROTOCOL",t=>`Protocol "${t[0]}" not supported. Expected "${t[1]}"`);yI(Error,"ERR_HTTP_HEADERS_SENT",t=>`Cannot ${t[0]} headers after they are sent to the client`);yI(TypeError,"ERR_INVALID_HTTP_TOKEN",t=>`${t[0]} must be a valid HTTP token [${t[1]}]`);yI(TypeError,"ERR_HTTP_INVALID_HEADER_VALUE",t=>`Invalid value "${t[0]} for header "${t[1]}"`);yI(TypeError,"ERR_INVALID_CHAR",t=>`Invalid character in ${t[0]} [${t[1]}]`)});var zH=L((Wzt,gge)=>{"use strict";var ygt=ye("http2"),{Writable:Egt}=ye("stream"),{Agent:cge,globalAgent:Igt}=jH(),Cgt=GH(),wgt=WH(),Bgt=ige(),vgt=oge(),{ERR_INVALID_ARG_TYPE:YH,ERR_INVALID_PROTOCOL:Sgt,ERR_HTTP_HEADERS_SENT:uge,ERR_INVALID_HTTP_TOKEN:Dgt,ERR_HTTP_INVALID_HEADER_VALUE:bgt,ERR_INVALID_CHAR:Pgt}=lge(),{HTTP2_HEADER_STATUS:fge,HTTP2_HEADER_METHOD:Age,HTTP2_HEADER_PATH:pge,HTTP2_METHOD_CONNECT:xgt}=ygt.constants,Jo=Symbol("headers"),VH=Symbol("origin"),KH=Symbol("session"),hge=Symbol("options"),KQ=Symbol("flushedHeaders"),cv=Symbol("jobs"),kgt=/^[\^`\-\w!#$%&*+.|~]+$/,Qgt=/[^\t\u0020-\u007E\u0080-\u00FF]/,JH=class extends Egt{constructor(e,r,s){super({autoDestroy:!1});let a=typeof e=="string"||e instanceof URL;if(a&&(e=wgt(e instanceof URL?e:new URL(e))),typeof r=="function"||r===void 0?(s=r,r=a?e:{...e}):r={...e,...r},r.h2session)this[KH]=r.h2session;else if(r.agent===!1)this.agent=new cge({maxFreeSessions:0});else if(typeof r.agent>"u"||r.agent===null)typeof r.createConnection=="function"?(this.agent=new cge({maxFreeSessions:0}),this.agent.createConnection=r.createConnection):this.agent=Igt;else if(typeof r.agent.request=="function")this.agent=r.agent;else throw new YH("options.agent",["Agent-like Object","undefined","false"],r.agent);if(r.protocol&&r.protocol!=="https:")throw new Sgt(r.protocol,"https:");let n=r.port||r.defaultPort||this.agent&&this.agent.defaultPort||443,c=r.hostname||r.host||"localhost";delete r.hostname,delete r.host,delete r.port;let{timeout:f}=r;if(r.timeout=void 0,this[Jo]=Object.create(null),this[cv]=[],this.socket=null,this.connection=null,this.method=r.method||"GET",this.path=r.path,this.res=null,this.aborted=!1,this.reusedSocket=!1,r.headers)for(let[p,h]of Object.entries(r.headers))this.setHeader(p,h);r.auth&&!("authorization"in this[Jo])&&(this[Jo].authorization="Basic "+Buffer.from(r.auth).toString("base64")),r.session=r.tlsSession,r.path=r.socketPath,this[hge]=r,n===443?(this[VH]=`https://${c}`,":authority"in this[Jo]||(this[Jo][":authority"]=c)):(this[VH]=`https://${c}:${n}`,":authority"in this[Jo]||(this[Jo][":authority"]=`${c}:${n}`)),f&&this.setTimeout(f),s&&this.once("response",s),this[KQ]=!1}get method(){return this[Jo][Age]}set method(e){e&&(this[Jo][Age]=e.toUpperCase())}get path(){return this[Jo][pge]}set path(e){e&&(this[Jo][pge]=e)}get _mustNotHaveABody(){return this.method==="GET"||this.method==="HEAD"||this.method==="DELETE"}_write(e,r,s){if(this._mustNotHaveABody){s(new Error("The GET, HEAD and DELETE methods must NOT have a body"));return}this.flushHeaders();let a=()=>this._request.write(e,r,s);this._request?a():this[cv].push(a)}_final(e){if(this.destroyed)return;this.flushHeaders();let r=()=>{if(this._mustNotHaveABody){e();return}this._request.end(e)};this._request?r():this[cv].push(r)}abort(){this.res&&this.res.complete||(this.aborted||process.nextTick(()=>this.emit("abort")),this.aborted=!0,this.destroy())}_destroy(e,r){this.res&&this.res._dump(),this._request&&this._request.destroy(),r(e)}async flushHeaders(){if(this[KQ]||this.destroyed)return;this[KQ]=!0;let e=this.method===xgt,r=s=>{if(this._request=s,this.destroyed){s.destroy();return}e||Bgt(s,this,["timeout","continue","close","error"]);let a=c=>(...f)=>{!this.writable&&!this.destroyed?c(...f):this.once("finish",()=>{c(...f)})};s.once("response",a((c,f,p)=>{let h=new Cgt(this.socket,s.readableHighWaterMark);this.res=h,h.req=this,h.statusCode=c[fge],h.headers=c,h.rawHeaders=p,h.once("end",()=>{this.aborted?(h.aborted=!0,h.emit("aborted")):(h.complete=!0,h.socket=null,h.connection=null)}),e?(h.upgrade=!0,this.emit("connect",h,s,Buffer.alloc(0))?this.emit("close"):s.destroy()):(s.on("data",E=>{!h._dumped&&!h.push(E)&&s.pause()}),s.once("end",()=>{h.push(null)}),this.emit("response",h)||h._dump())})),s.once("headers",a(c=>this.emit("information",{statusCode:c[fge]}))),s.once("trailers",a((c,f,p)=>{let{res:h}=this;h.trailers=c,h.rawTrailers=p}));let{socket:n}=s.session;this.socket=n,this.connection=n;for(let c of this[cv])c();this.emit("socket",this.socket)};if(this[KH])try{r(this[KH].request(this[Jo]))}catch(s){this.emit("error",s)}else{this.reusedSocket=!0;try{r(await this.agent.request(this[VH],this[hge],this[Jo]))}catch(s){this.emit("error",s)}}}getHeader(e){if(typeof e!="string")throw new YH("name","string",e);return this[Jo][e.toLowerCase()]}get headersSent(){return this[KQ]}removeHeader(e){if(typeof e!="string")throw new YH("name","string",e);if(this.headersSent)throw new uge("remove");delete this[Jo][e.toLowerCase()]}setHeader(e,r){if(this.headersSent)throw new uge("set");if(typeof e!="string"||!kgt.test(e)&&!vgt(e))throw new Dgt("Header name",e);if(typeof r>"u")throw new bgt(r,e);if(Qgt.test(r))throw new Pgt("header content",e);this[Jo][e.toLowerCase()]=r}setNoDelay(){}setSocketKeepAlive(){}setTimeout(e,r){let s=()=>this._request.setTimeout(e,r);return this._request?s():this[cv].push(s),this}get maxHeadersCount(){if(!this.destroyed&&this._request)return this._request.session.localSettings.maxHeaderListSize}set maxHeadersCount(e){}};gge.exports=JH});var mge=L((Yzt,dge)=>{"use strict";var Tgt=ye("tls");dge.exports=(t={},e=Tgt.connect)=>new Promise((r,s)=>{let a=!1,n,c=async()=>{await p,n.off("timeout",f),n.off("error",s),t.resolveSocket?(r({alpnProtocol:n.alpnProtocol,socket:n,timeout:a}),a&&(await Promise.resolve(),n.emit("timeout"))):(n.destroy(),r({alpnProtocol:n.alpnProtocol,timeout:a}))},f=async()=>{a=!0,c()},p=(async()=>{try{n=await e(t,c),n.on("error",s),n.once("timeout",f)}catch(h){s(h)}})()})});var Ege=L((Vzt,yge)=>{"use strict";var Rgt=ye("net");yge.exports=t=>{let e=t.host,r=t.headers&&t.headers.host;return r&&(r.startsWith("[")?r.indexOf("]")===-1?e=r:e=r.slice(1,-1):e=r.split(":",1)[0]),Rgt.isIP(e)?"":e}});var wge=L((Kzt,XH)=>{"use strict";var Ige=ye("http"),ZH=ye("https"),Fgt=mge(),Ngt=UH(),Ogt=zH(),Lgt=Ege(),Mgt=WH(),JQ=new Ngt({maxSize:100}),uv=new Map,Cge=(t,e,r)=>{e._httpMessage={shouldKeepAlive:!0};let s=()=>{t.emit("free",e,r)};e.on("free",s);let a=()=>{t.removeSocket(e,r)};e.on("close",a);let n=()=>{t.removeSocket(e,r),e.off("close",a),e.off("free",s),e.off("agentRemove",n)};e.on("agentRemove",n),t.emit("free",e,r)},_gt=async t=>{let e=`${t.host}:${t.port}:${t.ALPNProtocols.sort()}`;if(!JQ.has(e)){if(uv.has(e))return(await uv.get(e)).alpnProtocol;let{path:r,agent:s}=t;t.path=t.socketPath;let a=Fgt(t);uv.set(e,a);try{let{socket:n,alpnProtocol:c}=await a;if(JQ.set(e,c),t.path=r,c==="h2")n.destroy();else{let{globalAgent:f}=ZH,p=ZH.Agent.prototype.createConnection;s?s.createConnection===p?Cge(s,n,t):n.destroy():f.createConnection===p?Cge(f,n,t):n.destroy()}return uv.delete(e),c}catch(n){throw uv.delete(e),n}}return JQ.get(e)};XH.exports=async(t,e,r)=>{if((typeof t=="string"||t instanceof URL)&&(t=Mgt(new URL(t))),typeof e=="function"&&(r=e,e=void 0),e={ALPNProtocols:["h2","http/1.1"],...t,...e,resolveSocket:!0},!Array.isArray(e.ALPNProtocols)||e.ALPNProtocols.length===0)throw new Error("The `ALPNProtocols` option must be an Array with at least one entry");e.protocol=e.protocol||"https:";let s=e.protocol==="https:";e.host=e.hostname||e.host||"localhost",e.session=e.tlsSession,e.servername=e.servername||Lgt(e),e.port=e.port||(s?443:80),e._defaultAgent=s?ZH.globalAgent:Ige.globalAgent;let a=e.agent;if(a){if(a.addRequest)throw new Error("The `options.agent` object can contain only `http`, `https` or `http2` properties");e.agent=a[s?"https":"http"]}return s&&await _gt(e)==="h2"?(a&&(e.agent=a.http2),new Ogt(e,r)):Ige.request(e,r)};XH.exports.protocolCache=JQ});var vge=L((Jzt,Bge)=>{"use strict";var Ugt=ye("http2"),Hgt=jH(),$H=zH(),jgt=GH(),qgt=wge(),Ggt=(t,e,r)=>new $H(t,e,r),Wgt=(t,e,r)=>{let s=new $H(t,e,r);return s.end(),s};Bge.exports={...Ugt,ClientRequest:$H,IncomingMessage:jgt,...Hgt,request:Ggt,get:Wgt,auto:qgt}});var tj=L(ej=>{"use strict";Object.defineProperty(ej,"__esModule",{value:!0});var Sge=Mp();ej.default=t=>Sge.default.nodeStream(t)&&Sge.default.function_(t.getBoundary)});var xge=L(rj=>{"use strict";Object.defineProperty(rj,"__esModule",{value:!0});var bge=ye("fs"),Pge=ye("util"),Dge=Mp(),Ygt=tj(),Vgt=Pge.promisify(bge.stat);rj.default=async(t,e)=>{if(e&&"content-length"in e)return Number(e["content-length"]);if(!t)return 0;if(Dge.default.string(t))return Buffer.byteLength(t);if(Dge.default.buffer(t))return t.length;if(Ygt.default(t))return Pge.promisify(t.getLength.bind(t))();if(t instanceof bge.ReadStream){let{size:r}=await Vgt(t.path);return r===0?void 0:r}}});var ij=L(nj=>{"use strict";Object.defineProperty(nj,"__esModule",{value:!0});function Kgt(t,e,r){let s={};for(let a of r)s[a]=(...n)=>{e.emit(a,...n)},t.on(a,s[a]);return()=>{for(let a of r)t.off(a,s[a])}}nj.default=Kgt});var kge=L(sj=>{"use strict";Object.defineProperty(sj,"__esModule",{value:!0});sj.default=()=>{let t=[];return{once(e,r,s){e.once(r,s),t.push({origin:e,event:r,fn:s})},unhandleAll(){for(let e of t){let{origin:r,event:s,fn:a}=e;r.removeListener(s,a)}t.length=0}}}});var Tge=L(fv=>{"use strict";Object.defineProperty(fv,"__esModule",{value:!0});fv.TimeoutError=void 0;var Jgt=ye("net"),zgt=kge(),Qge=Symbol("reentry"),Zgt=()=>{},zQ=class extends Error{constructor(e,r){super(`Timeout awaiting '${r}' for ${e}ms`),this.event=r,this.name="TimeoutError",this.code="ETIMEDOUT"}};fv.TimeoutError=zQ;fv.default=(t,e,r)=>{if(Qge in t)return Zgt;t[Qge]=!0;let s=[],{once:a,unhandleAll:n}=zgt.default(),c=(C,S,P)=>{var I;let R=setTimeout(S,C,C,P);(I=R.unref)===null||I===void 0||I.call(R);let N=()=>{clearTimeout(R)};return s.push(N),N},{host:f,hostname:p}=r,h=(C,S)=>{t.destroy(new zQ(C,S))},E=()=>{for(let C of s)C();n()};if(t.once("error",C=>{if(E(),t.listenerCount("error")===0)throw C}),t.once("close",E),a(t,"response",C=>{a(C,"end",E)}),typeof e.request<"u"&&c(e.request,h,"request"),typeof e.socket<"u"){let C=()=>{h(e.socket,"socket")};t.setTimeout(e.socket,C),s.push(()=>{t.removeListener("timeout",C)})}return a(t,"socket",C=>{var S;let{socketPath:P}=t;if(C.connecting){let I=!!(P??Jgt.isIP((S=p??f)!==null&&S!==void 0?S:"")!==0);if(typeof e.lookup<"u"&&!I&&typeof C.address().address>"u"){let R=c(e.lookup,h,"lookup");a(C,"lookup",R)}if(typeof e.connect<"u"){let R=()=>c(e.connect,h,"connect");I?a(C,"connect",R()):a(C,"lookup",N=>{N===null&&a(C,"connect",R())})}typeof e.secureConnect<"u"&&r.protocol==="https:"&&a(C,"connect",()=>{let R=c(e.secureConnect,h,"secureConnect");a(C,"secureConnect",R)})}if(typeof e.send<"u"){let I=()=>c(e.send,h,"send");C.connecting?a(C,"connect",()=>{a(t,"upload-complete",I())}):a(t,"upload-complete",I())}}),typeof e.response<"u"&&a(t,"upload-complete",()=>{let C=c(e.response,h,"response");a(t,"response",C)}),E}});var Fge=L(oj=>{"use strict";Object.defineProperty(oj,"__esModule",{value:!0});var Rge=Mp();oj.default=t=>{t=t;let e={protocol:t.protocol,hostname:Rge.default.string(t.hostname)&&t.hostname.startsWith("[")?t.hostname.slice(1,-1):t.hostname,host:t.host,hash:t.hash,search:t.search,pathname:t.pathname,href:t.href,path:`${t.pathname||""}${t.search||""}`};return Rge.default.string(t.port)&&t.port.length>0&&(e.port=Number(t.port)),(t.username||t.password)&&(e.auth=`${t.username||""}:${t.password||""}`),e}});var Nge=L(aj=>{"use strict";Object.defineProperty(aj,"__esModule",{value:!0});var Xgt=ye("url"),$gt=["protocol","host","hostname","port","pathname","search"];aj.default=(t,e)=>{var r,s;if(e.path){if(e.pathname)throw new TypeError("Parameters `path` and `pathname` are mutually exclusive.");if(e.search)throw new TypeError("Parameters `path` and `search` are mutually exclusive.");if(e.searchParams)throw new TypeError("Parameters `path` and `searchParams` are mutually exclusive.")}if(e.search&&e.searchParams)throw new TypeError("Parameters `search` and `searchParams` are mutually exclusive.");if(!t){if(!e.protocol)throw new TypeError("No URL protocol specified");t=`${e.protocol}//${(s=(r=e.hostname)!==null&&r!==void 0?r:e.host)!==null&&s!==void 0?s:""}`}let a=new Xgt.URL(t);if(e.path){let n=e.path.indexOf("?");n===-1?e.pathname=e.path:(e.pathname=e.path.slice(0,n),e.search=e.path.slice(n+1)),delete e.path}for(let n of $gt)e[n]&&(a[n]=e[n].toString());return a}});var Oge=L(cj=>{"use strict";Object.defineProperty(cj,"__esModule",{value:!0});var lj=class{constructor(){this.weakMap=new WeakMap,this.map=new Map}set(e,r){typeof e=="object"?this.weakMap.set(e,r):this.map.set(e,r)}get(e){return typeof e=="object"?this.weakMap.get(e):this.map.get(e)}has(e){return typeof e=="object"?this.weakMap.has(e):this.map.has(e)}};cj.default=lj});var fj=L(uj=>{"use strict";Object.defineProperty(uj,"__esModule",{value:!0});var edt=async t=>{let e=[],r=0;for await(let s of t)e.push(s),r+=Buffer.byteLength(s);return Buffer.isBuffer(e[0])?Buffer.concat(e,r):Buffer.from(e.join(""))};uj.default=edt});var Mge=L(sm=>{"use strict";Object.defineProperty(sm,"__esModule",{value:!0});sm.dnsLookupIpVersionToFamily=sm.isDnsLookupIpVersion=void 0;var Lge={auto:0,ipv4:4,ipv6:6};sm.isDnsLookupIpVersion=t=>t in Lge;sm.dnsLookupIpVersionToFamily=t=>{if(sm.isDnsLookupIpVersion(t))return Lge[t];throw new Error("Invalid DNS lookup IP version")}});var Aj=L(ZQ=>{"use strict";Object.defineProperty(ZQ,"__esModule",{value:!0});ZQ.isResponseOk=void 0;ZQ.isResponseOk=t=>{let{statusCode:e}=t,r=t.request.options.followRedirect?299:399;return e>=200&&e<=r||e===304}});var Uge=L(pj=>{"use strict";Object.defineProperty(pj,"__esModule",{value:!0});var _ge=new Set;pj.default=t=>{_ge.has(t)||(_ge.add(t),process.emitWarning(`Got: ${t}`,{type:"DeprecationWarning"}))}});var Hge=L(hj=>{"use strict";Object.defineProperty(hj,"__esModule",{value:!0});var Di=Mp(),tdt=(t,e)=>{if(Di.default.null_(t.encoding))throw new TypeError("To get a Buffer, set `options.responseType` to `buffer` instead");Di.assert.any([Di.default.string,Di.default.undefined],t.encoding),Di.assert.any([Di.default.boolean,Di.default.undefined],t.resolveBodyOnly),Di.assert.any([Di.default.boolean,Di.default.undefined],t.methodRewriting),Di.assert.any([Di.default.boolean,Di.default.undefined],t.isStream),Di.assert.any([Di.default.string,Di.default.undefined],t.responseType),t.responseType===void 0&&(t.responseType="text");let{retry:r}=t;if(e?t.retry={...e.retry}:t.retry={calculateDelay:s=>s.computedValue,limit:0,methods:[],statusCodes:[],errorCodes:[],maxRetryAfter:void 0},Di.default.object(r)?(t.retry={...t.retry,...r},t.retry.methods=[...new Set(t.retry.methods.map(s=>s.toUpperCase()))],t.retry.statusCodes=[...new Set(t.retry.statusCodes)],t.retry.errorCodes=[...new Set(t.retry.errorCodes)]):Di.default.number(r)&&(t.retry.limit=r),Di.default.undefined(t.retry.maxRetryAfter)&&(t.retry.maxRetryAfter=Math.min(...[t.timeout.request,t.timeout.connect].filter(Di.default.number))),Di.default.object(t.pagination)){e&&(t.pagination={...e.pagination,...t.pagination});let{pagination:s}=t;if(!Di.default.function_(s.transform))throw new Error("`options.pagination.transform` must be implemented");if(!Di.default.function_(s.shouldContinue))throw new Error("`options.pagination.shouldContinue` must be implemented");if(!Di.default.function_(s.filter))throw new TypeError("`options.pagination.filter` must be implemented");if(!Di.default.function_(s.paginate))throw new Error("`options.pagination.paginate` must be implemented")}return t.responseType==="json"&&t.headers.accept===void 0&&(t.headers.accept="application/json"),t};hj.default=tdt});var jge=L(Av=>{"use strict";Object.defineProperty(Av,"__esModule",{value:!0});Av.retryAfterStatusCodes=void 0;Av.retryAfterStatusCodes=new Set([413,429,503]);var rdt=({attemptCount:t,retryOptions:e,error:r,retryAfter:s})=>{if(t>e.limit)return 0;let a=e.methods.includes(r.options.method),n=e.errorCodes.includes(r.code),c=r.response&&e.statusCodes.includes(r.response.statusCode);if(!a||!n&&!c)return 0;if(r.response){if(s)return e.maxRetryAfter===void 0||s>e.maxRetryAfter?0:s;if(r.response.statusCode===413)return 0}let f=Math.random()*100;return 2**(t-1)*1e3+f};Av.default=rdt});var gv=L(Ln=>{"use strict";Object.defineProperty(Ln,"__esModule",{value:!0});Ln.UnsupportedProtocolError=Ln.ReadError=Ln.TimeoutError=Ln.UploadError=Ln.CacheError=Ln.HTTPError=Ln.MaxRedirectsError=Ln.RequestError=Ln.setNonEnumerableProperties=Ln.knownHookEvents=Ln.withoutBody=Ln.kIsNormalizedAlready=void 0;var qge=ye("util"),Gge=ye("stream"),ndt=ye("fs"),B0=ye("url"),Wge=ye("http"),gj=ye("http"),idt=ye("https"),sdt=s0e(),odt=A0e(),Yge=W0e(),adt=J0e(),ldt=vge(),cdt=YQ(),at=Mp(),udt=xge(),Vge=tj(),fdt=ij(),Kge=Tge(),Adt=Fge(),Jge=Nge(),pdt=Oge(),hdt=fj(),zge=Mge(),gdt=Aj(),v0=Uge(),ddt=Hge(),mdt=jge(),dj,go=Symbol("request"),eT=Symbol("response"),EI=Symbol("responseSize"),II=Symbol("downloadedSize"),CI=Symbol("bodySize"),wI=Symbol("uploadedSize"),XQ=Symbol("serverResponsesPiped"),Zge=Symbol("unproxyEvents"),Xge=Symbol("isFromCache"),mj=Symbol("cancelTimeouts"),$ge=Symbol("startedReading"),BI=Symbol("stopReading"),$Q=Symbol("triggerRead"),S0=Symbol("body"),pv=Symbol("jobs"),ede=Symbol("originalResponse"),tde=Symbol("retryTimeout");Ln.kIsNormalizedAlready=Symbol("isNormalizedAlready");var ydt=at.default.string(process.versions.brotli);Ln.withoutBody=new Set(["GET","HEAD"]);Ln.knownHookEvents=["init","beforeRequest","beforeRedirect","beforeError","beforeRetry","afterResponse"];function Edt(t){for(let e in t){let r=t[e];if(!at.default.string(r)&&!at.default.number(r)&&!at.default.boolean(r)&&!at.default.null_(r)&&!at.default.undefined(r))throw new TypeError(`The \`searchParams\` value '${String(r)}' must be a string, number, boolean or null`)}}function Idt(t){return at.default.object(t)&&!("statusCode"in t)}var yj=new pdt.default,Cdt=async t=>new Promise((e,r)=>{let s=a=>{r(a)};t.pending||e(),t.once("error",s),t.once("ready",()=>{t.off("error",s),e()})}),wdt=new Set([300,301,302,303,304,307,308]),Bdt=["context","body","json","form"];Ln.setNonEnumerableProperties=(t,e)=>{let r={};for(let s of t)if(s)for(let a of Bdt)a in s&&(r[a]={writable:!0,configurable:!0,enumerable:!1,value:s[a]});Object.defineProperties(e,r)};var fs=class extends Error{constructor(e,r,s){var a;if(super(e),Error.captureStackTrace(this,this.constructor),this.name="RequestError",this.code=r.code,s instanceof aT?(Object.defineProperty(this,"request",{enumerable:!1,value:s}),Object.defineProperty(this,"response",{enumerable:!1,value:s[eT]}),Object.defineProperty(this,"options",{enumerable:!1,value:s.options})):Object.defineProperty(this,"options",{enumerable:!1,value:s}),this.timings=(a=this.request)===null||a===void 0?void 0:a.timings,at.default.string(r.stack)&&at.default.string(this.stack)){let n=this.stack.indexOf(this.message)+this.message.length,c=this.stack.slice(n).split(` +`).reverse(),f=r.stack.slice(r.stack.indexOf(r.message)+r.message.length).split(` +`).reverse();for(;f.length!==0&&f[0]===c[0];)c.shift();this.stack=`${this.stack.slice(0,n)}${c.reverse().join(` +`)}${f.reverse().join(` +`)}`}}};Ln.RequestError=fs;var tT=class extends fs{constructor(e){super(`Redirected ${e.options.maxRedirects} times. Aborting.`,{},e),this.name="MaxRedirectsError"}};Ln.MaxRedirectsError=tT;var rT=class extends fs{constructor(e){super(`Response code ${e.statusCode} (${e.statusMessage})`,{},e.request),this.name="HTTPError"}};Ln.HTTPError=rT;var nT=class extends fs{constructor(e,r){super(e.message,e,r),this.name="CacheError"}};Ln.CacheError=nT;var iT=class extends fs{constructor(e,r){super(e.message,e,r),this.name="UploadError"}};Ln.UploadError=iT;var sT=class extends fs{constructor(e,r,s){super(e.message,e,s),this.name="TimeoutError",this.event=e.event,this.timings=r}};Ln.TimeoutError=sT;var hv=class extends fs{constructor(e,r){super(e.message,e,r),this.name="ReadError"}};Ln.ReadError=hv;var oT=class extends fs{constructor(e){super(`Unsupported protocol "${e.url.protocol}"`,{},e),this.name="UnsupportedProtocolError"}};Ln.UnsupportedProtocolError=oT;var vdt=["socket","connect","continue","information","upgrade","timeout"],aT=class extends Gge.Duplex{constructor(e,r={},s){super({autoDestroy:!1,highWaterMark:0}),this[II]=0,this[wI]=0,this.requestInitialized=!1,this[XQ]=new Set,this.redirects=[],this[BI]=!1,this[$Q]=!1,this[pv]=[],this.retryCount=0,this._progressCallbacks=[];let a=()=>this._unlockWrite(),n=()=>this._lockWrite();this.on("pipe",h=>{h.prependListener("data",a),h.on("data",n),h.prependListener("end",a),h.on("end",n)}),this.on("unpipe",h=>{h.off("data",a),h.off("data",n),h.off("end",a),h.off("end",n)}),this.on("pipe",h=>{h instanceof gj.IncomingMessage&&(this.options.headers={...h.headers,...this.options.headers})});let{json:c,body:f,form:p}=r;if((c||f||p)&&this._lockWrite(),Ln.kIsNormalizedAlready in r)this.options=r;else try{this.options=this.constructor.normalizeArguments(e,r,s)}catch(h){at.default.nodeStream(r.body)&&r.body.destroy(),this.destroy(h);return}(async()=>{var h;try{this.options.body instanceof ndt.ReadStream&&await Cdt(this.options.body);let{url:E}=this.options;if(!E)throw new TypeError("Missing `url` property");if(this.requestUrl=E.toString(),decodeURI(this.requestUrl),await this._finalizeBody(),await this._makeRequest(),this.destroyed){(h=this[go])===null||h===void 0||h.destroy();return}for(let C of this[pv])C();this[pv].length=0,this.requestInitialized=!0}catch(E){if(E instanceof fs){this._beforeError(E);return}this.destroyed||this.destroy(E)}})()}static normalizeArguments(e,r,s){var a,n,c,f,p;let h=r;if(at.default.object(e)&&!at.default.urlInstance(e))r={...s,...e,...r};else{if(e&&r&&r.url!==void 0)throw new TypeError("The `url` option is mutually exclusive with the `input` argument");r={...s,...r},e!==void 0&&(r.url=e),at.default.urlInstance(r.url)&&(r.url=new B0.URL(r.url.toString()))}if(r.cache===!1&&(r.cache=void 0),r.dnsCache===!1&&(r.dnsCache=void 0),at.assert.any([at.default.string,at.default.undefined],r.method),at.assert.any([at.default.object,at.default.undefined],r.headers),at.assert.any([at.default.string,at.default.urlInstance,at.default.undefined],r.prefixUrl),at.assert.any([at.default.object,at.default.undefined],r.cookieJar),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.searchParams),at.assert.any([at.default.object,at.default.string,at.default.undefined],r.cache),at.assert.any([at.default.object,at.default.number,at.default.undefined],r.timeout),at.assert.any([at.default.object,at.default.undefined],r.context),at.assert.any([at.default.object,at.default.undefined],r.hooks),at.assert.any([at.default.boolean,at.default.undefined],r.decompress),at.assert.any([at.default.boolean,at.default.undefined],r.ignoreInvalidCookies),at.assert.any([at.default.boolean,at.default.undefined],r.followRedirect),at.assert.any([at.default.number,at.default.undefined],r.maxRedirects),at.assert.any([at.default.boolean,at.default.undefined],r.throwHttpErrors),at.assert.any([at.default.boolean,at.default.undefined],r.http2),at.assert.any([at.default.boolean,at.default.undefined],r.allowGetBody),at.assert.any([at.default.string,at.default.undefined],r.localAddress),at.assert.any([zge.isDnsLookupIpVersion,at.default.undefined],r.dnsLookupIpVersion),at.assert.any([at.default.object,at.default.undefined],r.https),at.assert.any([at.default.boolean,at.default.undefined],r.rejectUnauthorized),r.https&&(at.assert.any([at.default.boolean,at.default.undefined],r.https.rejectUnauthorized),at.assert.any([at.default.function_,at.default.undefined],r.https.checkServerIdentity),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificateAuthority),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.key),at.assert.any([at.default.string,at.default.object,at.default.array,at.default.undefined],r.https.certificate),at.assert.any([at.default.string,at.default.undefined],r.https.passphrase),at.assert.any([at.default.string,at.default.buffer,at.default.array,at.default.undefined],r.https.pfx)),at.assert.any([at.default.object,at.default.undefined],r.cacheOptions),at.default.string(r.method)?r.method=r.method.toUpperCase():r.method="GET",r.headers===s?.headers?r.headers={...r.headers}:r.headers=cdt({...s?.headers,...r.headers}),"slashes"in r)throw new TypeError("The legacy `url.Url` has been deprecated. Use `URL` instead.");if("auth"in r)throw new TypeError("Parameter `auth` is deprecated. Use `username` / `password` instead.");if("searchParams"in r&&r.searchParams&&r.searchParams!==s?.searchParams){let P;if(at.default.string(r.searchParams)||r.searchParams instanceof B0.URLSearchParams)P=new B0.URLSearchParams(r.searchParams);else{Edt(r.searchParams),P=new B0.URLSearchParams;for(let I in r.searchParams){let R=r.searchParams[I];R===null?P.append(I,""):R!==void 0&&P.append(I,R)}}(a=s?.searchParams)===null||a===void 0||a.forEach((I,R)=>{P.has(R)||P.append(R,I)}),r.searchParams=P}if(r.username=(n=r.username)!==null&&n!==void 0?n:"",r.password=(c=r.password)!==null&&c!==void 0?c:"",at.default.undefined(r.prefixUrl)?r.prefixUrl=(f=s?.prefixUrl)!==null&&f!==void 0?f:"":(r.prefixUrl=r.prefixUrl.toString(),r.prefixUrl!==""&&!r.prefixUrl.endsWith("/")&&(r.prefixUrl+="/")),at.default.string(r.url)){if(r.url.startsWith("/"))throw new Error("`input` must not start with a slash when using `prefixUrl`");r.url=Jge.default(r.prefixUrl+r.url,r)}else(at.default.undefined(r.url)&&r.prefixUrl!==""||r.protocol)&&(r.url=Jge.default(r.prefixUrl,r));if(r.url){"port"in r&&delete r.port;let{prefixUrl:P}=r;Object.defineProperty(r,"prefixUrl",{set:R=>{let N=r.url;if(!N.href.startsWith(R))throw new Error(`Cannot change \`prefixUrl\` from ${P} to ${R}: ${N.href}`);r.url=new B0.URL(R+N.href.slice(P.length)),P=R},get:()=>P});let{protocol:I}=r.url;if(I==="unix:"&&(I="http:",r.url=new B0.URL(`http://unix${r.url.pathname}${r.url.search}`)),r.searchParams&&(r.url.search=r.searchParams.toString()),I!=="http:"&&I!=="https:")throw new oT(r);r.username===""?r.username=r.url.username:r.url.username=r.username,r.password===""?r.password=r.url.password:r.url.password=r.password}let{cookieJar:E}=r;if(E){let{setCookie:P,getCookieString:I}=E;at.assert.function_(P),at.assert.function_(I),P.length===4&&I.length===0&&(P=qge.promisify(P.bind(r.cookieJar)),I=qge.promisify(I.bind(r.cookieJar)),r.cookieJar={setCookie:P,getCookieString:I})}let{cache:C}=r;if(C&&(yj.has(C)||yj.set(C,new Yge((P,I)=>{let R=P[go](P,I);return at.default.promise(R)&&(R.once=(N,U)=>{if(N==="error")R.catch(U);else if(N==="abort")(async()=>{try{(await R).once("abort",U)}catch{}})();else throw new Error(`Unknown HTTP2 promise event: ${N}`);return R}),R},C))),r.cacheOptions={...r.cacheOptions},r.dnsCache===!0)dj||(dj=new odt.default),r.dnsCache=dj;else if(!at.default.undefined(r.dnsCache)&&!r.dnsCache.lookup)throw new TypeError(`Parameter \`dnsCache\` must be a CacheableLookup instance or a boolean, got ${at.default(r.dnsCache)}`);at.default.number(r.timeout)?r.timeout={request:r.timeout}:s&&r.timeout!==s.timeout?r.timeout={...s.timeout,...r.timeout}:r.timeout={...r.timeout},r.context||(r.context={});let S=r.hooks===s?.hooks;r.hooks={...r.hooks};for(let P of Ln.knownHookEvents)if(P in r.hooks)if(at.default.array(r.hooks[P]))r.hooks[P]=[...r.hooks[P]];else throw new TypeError(`Parameter \`${P}\` must be an Array, got ${at.default(r.hooks[P])}`);else r.hooks[P]=[];if(s&&!S)for(let P of Ln.knownHookEvents)s.hooks[P].length>0&&(r.hooks[P]=[...s.hooks[P],...r.hooks[P]]);if("family"in r&&v0.default('"options.family" was never documented, please use "options.dnsLookupIpVersion"'),s?.https&&(r.https={...s.https,...r.https}),"rejectUnauthorized"in r&&v0.default('"options.rejectUnauthorized" is now deprecated, please use "options.https.rejectUnauthorized"'),"checkServerIdentity"in r&&v0.default('"options.checkServerIdentity" was never documented, please use "options.https.checkServerIdentity"'),"ca"in r&&v0.default('"options.ca" was never documented, please use "options.https.certificateAuthority"'),"key"in r&&v0.default('"options.key" was never documented, please use "options.https.key"'),"cert"in r&&v0.default('"options.cert" was never documented, please use "options.https.certificate"'),"passphrase"in r&&v0.default('"options.passphrase" was never documented, please use "options.https.passphrase"'),"pfx"in r&&v0.default('"options.pfx" was never documented, please use "options.https.pfx"'),"followRedirects"in r)throw new TypeError("The `followRedirects` option does not exist. Use `followRedirect` instead.");if(r.agent){for(let P in r.agent)if(P!=="http"&&P!=="https"&&P!=="http2")throw new TypeError(`Expected the \`options.agent\` properties to be \`http\`, \`https\` or \`http2\`, got \`${P}\``)}return r.maxRedirects=(p=r.maxRedirects)!==null&&p!==void 0?p:0,Ln.setNonEnumerableProperties([s,h],r),ddt.default(r,s)}_lockWrite(){let e=()=>{throw new TypeError("The payload has been already provided")};this.write=e,this.end=e}_unlockWrite(){this.write=super.write,this.end=super.end}async _finalizeBody(){let{options:e}=this,{headers:r}=e,s=!at.default.undefined(e.form),a=!at.default.undefined(e.json),n=!at.default.undefined(e.body),c=s||a||n,f=Ln.withoutBody.has(e.method)&&!(e.method==="GET"&&e.allowGetBody);if(this._cannotHaveBody=f,c){if(f)throw new TypeError(`The \`${e.method}\` method cannot be used with a body`);if([n,s,a].filter(p=>p).length>1)throw new TypeError("The `body`, `json` and `form` options are mutually exclusive");if(n&&!(e.body instanceof Gge.Readable)&&!at.default.string(e.body)&&!at.default.buffer(e.body)&&!Vge.default(e.body))throw new TypeError("The `body` option must be a stream.Readable, string or Buffer");if(s&&!at.default.object(e.form))throw new TypeError("The `form` option must be an Object");{let p=!at.default.string(r["content-type"]);n?(Vge.default(e.body)&&p&&(r["content-type"]=`multipart/form-data; boundary=${e.body.getBoundary()}`),this[S0]=e.body):s?(p&&(r["content-type"]="application/x-www-form-urlencoded"),this[S0]=new B0.URLSearchParams(e.form).toString()):(p&&(r["content-type"]="application/json"),this[S0]=e.stringifyJson(e.json));let h=await udt.default(this[S0],e.headers);at.default.undefined(r["content-length"])&&at.default.undefined(r["transfer-encoding"])&&!f&&!at.default.undefined(h)&&(r["content-length"]=String(h))}}else f?this._lockWrite():this._unlockWrite();this[CI]=Number(r["content-length"])||void 0}async _onResponseBase(e){let{options:r}=this,{url:s}=r;this[ede]=e,r.decompress&&(e=adt(e));let a=e.statusCode,n=e;n.statusMessage=n.statusMessage?n.statusMessage:Wge.STATUS_CODES[a],n.url=r.url.toString(),n.requestUrl=this.requestUrl,n.redirectUrls=this.redirects,n.request=this,n.isFromCache=e.fromCache||!1,n.ip=this.ip,n.retryCount=this.retryCount,this[Xge]=n.isFromCache,this[EI]=Number(e.headers["content-length"])||void 0,this[eT]=e,e.once("end",()=>{this[EI]=this[II],this.emit("downloadProgress",this.downloadProgress)}),e.once("error",f=>{e.destroy(),this._beforeError(new hv(f,this))}),e.once("aborted",()=>{this._beforeError(new hv({name:"Error",message:"The server aborted pending request",code:"ECONNRESET"},this))}),this.emit("downloadProgress",this.downloadProgress);let c=e.headers["set-cookie"];if(at.default.object(r.cookieJar)&&c){let f=c.map(async p=>r.cookieJar.setCookie(p,s.toString()));r.ignoreInvalidCookies&&(f=f.map(async p=>p.catch(()=>{})));try{await Promise.all(f)}catch(p){this._beforeError(p);return}}if(r.followRedirect&&e.headers.location&&wdt.has(a)){if(e.resume(),this[go]&&(this[mj](),delete this[go],this[Zge]()),(a===303&&r.method!=="GET"&&r.method!=="HEAD"||!r.methodRewriting)&&(r.method="GET","body"in r&&delete r.body,"json"in r&&delete r.json,"form"in r&&delete r.form,this[S0]=void 0,delete r.headers["content-length"]),this.redirects.length>=r.maxRedirects){this._beforeError(new tT(this));return}try{let p=Buffer.from(e.headers.location,"binary").toString(),h=new B0.URL(p,s),E=h.toString();decodeURI(E),h.hostname!==s.hostname||h.port!==s.port?("host"in r.headers&&delete r.headers.host,"cookie"in r.headers&&delete r.headers.cookie,"authorization"in r.headers&&delete r.headers.authorization,(r.username||r.password)&&(r.username="",r.password="")):(h.username=r.username,h.password=r.password),this.redirects.push(E),r.url=h;for(let C of r.hooks.beforeRedirect)await C(r,n);this.emit("redirect",n,r),await this._makeRequest()}catch(p){this._beforeError(p);return}return}if(r.isStream&&r.throwHttpErrors&&!gdt.isResponseOk(n)){this._beforeError(new rT(n));return}e.on("readable",()=>{this[$Q]&&this._read()}),this.on("resume",()=>{e.resume()}),this.on("pause",()=>{e.pause()}),e.once("end",()=>{this.push(null)}),this.emit("response",e);for(let f of this[XQ])if(!f.headersSent){for(let p in e.headers){let h=r.decompress?p!=="content-encoding":!0,E=e.headers[p];h&&f.setHeader(p,E)}f.statusCode=a}}async _onResponse(e){try{await this._onResponseBase(e)}catch(r){this._beforeError(r)}}_onRequest(e){let{options:r}=this,{timeout:s,url:a}=r;sdt.default(e),this[mj]=Kge.default(e,s,a);let n=r.cache?"cacheableResponse":"response";e.once(n,p=>{this._onResponse(p)}),e.once("error",p=>{var h;e.destroy(),(h=e.res)===null||h===void 0||h.removeAllListeners("end"),p=p instanceof Kge.TimeoutError?new sT(p,this.timings,this):new fs(p.message,p,this),this._beforeError(p)}),this[Zge]=fdt.default(e,this,vdt),this[go]=e,this.emit("uploadProgress",this.uploadProgress);let c=this[S0],f=this.redirects.length===0?this:e;at.default.nodeStream(c)?(c.pipe(f),c.once("error",p=>{this._beforeError(new iT(p,this))})):(this._unlockWrite(),at.default.undefined(c)?(this._cannotHaveBody||this._noPipe)&&(f.end(),this._lockWrite()):(this._writeRequest(c,void 0,()=>{}),f.end(),this._lockWrite())),this.emit("request",e)}async _createCacheableRequest(e,r){return new Promise((s,a)=>{Object.assign(r,Adt.default(e)),delete r.url;let n,c=yj.get(r.cache)(r,async f=>{f._readableState.autoDestroy=!1,n&&(await n).emit("cacheableResponse",f),s(f)});r.url=e,c.once("error",a),c.once("request",async f=>{n=f,s(n)})})}async _makeRequest(){var e,r,s,a,n;let{options:c}=this,{headers:f}=c;for(let U in f)if(at.default.undefined(f[U]))delete f[U];else if(at.default.null_(f[U]))throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${U}\` header`);if(c.decompress&&at.default.undefined(f["accept-encoding"])&&(f["accept-encoding"]=ydt?"gzip, deflate, br":"gzip, deflate"),c.cookieJar){let U=await c.cookieJar.getCookieString(c.url.toString());at.default.nonEmptyString(U)&&(c.headers.cookie=U)}for(let U of c.hooks.beforeRequest){let W=await U(c);if(!at.default.undefined(W)){c.request=()=>W;break}}c.body&&this[S0]!==c.body&&(this[S0]=c.body);let{agent:p,request:h,timeout:E,url:C}=c;if(c.dnsCache&&!("lookup"in c)&&(c.lookup=c.dnsCache.lookup),C.hostname==="unix"){let U=/(?.+?):(?.+)/.exec(`${C.pathname}${C.search}`);if(U?.groups){let{socketPath:W,path:te}=U.groups;Object.assign(c,{socketPath:W,path:te,host:""})}}let S=C.protocol==="https:",P;c.http2?P=ldt.auto:P=S?idt.request:Wge.request;let I=(e=c.request)!==null&&e!==void 0?e:P,R=c.cache?this._createCacheableRequest:I;p&&!c.http2&&(c.agent=p[S?"https":"http"]),c[go]=I,delete c.request,delete c.timeout;let N=c;if(N.shared=(r=c.cacheOptions)===null||r===void 0?void 0:r.shared,N.cacheHeuristic=(s=c.cacheOptions)===null||s===void 0?void 0:s.cacheHeuristic,N.immutableMinTimeToLive=(a=c.cacheOptions)===null||a===void 0?void 0:a.immutableMinTimeToLive,N.ignoreCargoCult=(n=c.cacheOptions)===null||n===void 0?void 0:n.ignoreCargoCult,c.dnsLookupIpVersion!==void 0)try{N.family=zge.dnsLookupIpVersionToFamily(c.dnsLookupIpVersion)}catch{throw new Error("Invalid `dnsLookupIpVersion` option value")}c.https&&("rejectUnauthorized"in c.https&&(N.rejectUnauthorized=c.https.rejectUnauthorized),c.https.checkServerIdentity&&(N.checkServerIdentity=c.https.checkServerIdentity),c.https.certificateAuthority&&(N.ca=c.https.certificateAuthority),c.https.certificate&&(N.cert=c.https.certificate),c.https.key&&(N.key=c.https.key),c.https.passphrase&&(N.passphrase=c.https.passphrase),c.https.pfx&&(N.pfx=c.https.pfx));try{let U=await R(C,N);at.default.undefined(U)&&(U=P(C,N)),c.request=h,c.timeout=E,c.agent=p,c.https&&("rejectUnauthorized"in c.https&&delete N.rejectUnauthorized,c.https.checkServerIdentity&&delete N.checkServerIdentity,c.https.certificateAuthority&&delete N.ca,c.https.certificate&&delete N.cert,c.https.key&&delete N.key,c.https.passphrase&&delete N.passphrase,c.https.pfx&&delete N.pfx),Idt(U)?this._onRequest(U):this.writable?(this.once("finish",()=>{this._onResponse(U)}),this._unlockWrite(),this.end(),this._lockWrite()):this._onResponse(U)}catch(U){throw U instanceof Yge.CacheError?new nT(U,this):new fs(U.message,U,this)}}async _error(e){try{for(let r of this.options.hooks.beforeError)e=await r(e)}catch(r){e=new fs(r.message,r,this)}this.destroy(e)}_beforeError(e){if(this[BI])return;let{options:r}=this,s=this.retryCount+1;this[BI]=!0,e instanceof fs||(e=new fs(e.message,e,this));let a=e,{response:n}=a;(async()=>{if(n&&!n.body){n.setEncoding(this._readableState.encoding);try{n.rawBody=await hdt.default(n),n.body=n.rawBody.toString()}catch{}}if(this.listenerCount("retry")!==0){let c;try{let f;n&&"retry-after"in n.headers&&(f=Number(n.headers["retry-after"]),Number.isNaN(f)?(f=Date.parse(n.headers["retry-after"])-Date.now(),f<=0&&(f=1)):f*=1e3),c=await r.retry.calculateDelay({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:mdt.default({attemptCount:s,retryOptions:r.retry,error:a,retryAfter:f,computedValue:0})})}catch(f){this._error(new fs(f.message,f,this));return}if(c){let f=async()=>{try{for(let p of this.options.hooks.beforeRetry)await p(this.options,a,s)}catch(p){this._error(new fs(p.message,e,this));return}this.destroyed||(this.destroy(),this.emit("retry",s,e))};this[tde]=setTimeout(f,c);return}}this._error(a)})()}_read(){this[$Q]=!0;let e=this[eT];if(e&&!this[BI]){e.readableLength&&(this[$Q]=!1);let r;for(;(r=e.read())!==null;){this[II]+=r.length,this[$ge]=!0;let s=this.downloadProgress;s.percent<1&&this.emit("downloadProgress",s),this.push(r)}}}_write(e,r,s){let a=()=>{this._writeRequest(e,r,s)};this.requestInitialized?a():this[pv].push(a)}_writeRequest(e,r,s){this[go].destroyed||(this._progressCallbacks.push(()=>{this[wI]+=Buffer.byteLength(e,r);let a=this.uploadProgress;a.percent<1&&this.emit("uploadProgress",a)}),this[go].write(e,r,a=>{!a&&this._progressCallbacks.length>0&&this._progressCallbacks.shift()(),s(a)}))}_final(e){let r=()=>{for(;this._progressCallbacks.length!==0;)this._progressCallbacks.shift()();if(!(go in this)){e();return}if(this[go].destroyed){e();return}this[go].end(s=>{s||(this[CI]=this[wI],this.emit("uploadProgress",this.uploadProgress),this[go].emit("upload-complete")),e(s)})};this.requestInitialized?r():this[pv].push(r)}_destroy(e,r){var s;this[BI]=!0,clearTimeout(this[tde]),go in this&&(this[mj](),!((s=this[eT])===null||s===void 0)&&s.complete||this[go].destroy()),e!==null&&!at.default.undefined(e)&&!(e instanceof fs)&&(e=new fs(e.message,e,this)),r(e)}get _isAboutToError(){return this[BI]}get ip(){var e;return(e=this.socket)===null||e===void 0?void 0:e.remoteAddress}get aborted(){var e,r,s;return((r=(e=this[go])===null||e===void 0?void 0:e.destroyed)!==null&&r!==void 0?r:this.destroyed)&&!(!((s=this[ede])===null||s===void 0)&&s.complete)}get socket(){var e,r;return(r=(e=this[go])===null||e===void 0?void 0:e.socket)!==null&&r!==void 0?r:void 0}get downloadProgress(){let e;return this[EI]?e=this[II]/this[EI]:this[EI]===this[II]?e=1:e=0,{percent:e,transferred:this[II],total:this[EI]}}get uploadProgress(){let e;return this[CI]?e=this[wI]/this[CI]:this[CI]===this[wI]?e=1:e=0,{percent:e,transferred:this[wI],total:this[CI]}}get timings(){var e;return(e=this[go])===null||e===void 0?void 0:e.timings}get isFromCache(){return this[Xge]}pipe(e,r){if(this[$ge])throw new Error("Failed to pipe. The response has been emitted already.");return e instanceof gj.ServerResponse&&this[XQ].add(e),super.pipe(e,r)}unpipe(e){return e instanceof gj.ServerResponse&&this[XQ].delete(e),super.unpipe(e),this}};Ln.default=aT});var dv=L(Wu=>{"use strict";var Sdt=Wu&&Wu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Ddt=Wu&&Wu.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&Sdt(e,t,r)};Object.defineProperty(Wu,"__esModule",{value:!0});Wu.CancelError=Wu.ParseError=void 0;var rde=gv(),Ej=class extends rde.RequestError{constructor(e,r){let{options:s}=r.request;super(`${e.message} in "${s.url.toString()}"`,e,r.request),this.name="ParseError"}};Wu.ParseError=Ej;var Ij=class extends rde.RequestError{constructor(e){super("Promise was canceled",{},e),this.name="CancelError"}get isCanceled(){return!0}};Wu.CancelError=Ij;Ddt(gv(),Wu)});var ide=L(Cj=>{"use strict";Object.defineProperty(Cj,"__esModule",{value:!0});var nde=dv(),bdt=(t,e,r,s)=>{let{rawBody:a}=t;try{if(e==="text")return a.toString(s);if(e==="json")return a.length===0?"":r(a.toString());if(e==="buffer")return a;throw new nde.ParseError({message:`Unknown body type '${e}'`,name:"Error"},t)}catch(n){throw new nde.ParseError(n,t)}};Cj.default=bdt});var wj=L(D0=>{"use strict";var Pdt=D0&&D0.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),xdt=D0&&D0.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&Pdt(e,t,r)};Object.defineProperty(D0,"__esModule",{value:!0});var kdt=ye("events"),Qdt=Mp(),Tdt=n0e(),lT=dv(),sde=ide(),ode=gv(),Rdt=ij(),Fdt=fj(),ade=Aj(),Ndt=["request","response","redirect","uploadProgress","downloadProgress"];function lde(t){let e,r,s=new kdt.EventEmitter,a=new Tdt((c,f,p)=>{let h=E=>{let C=new ode.default(void 0,t);C.retryCount=E,C._noPipe=!0,p(()=>C.destroy()),p.shouldReject=!1,p(()=>f(new lT.CancelError(C))),e=C,C.once("response",async I=>{var R;if(I.retryCount=E,I.request.aborted)return;let N;try{N=await Fdt.default(C),I.rawBody=N}catch{return}if(C._isAboutToError)return;let U=((R=I.headers["content-encoding"])!==null&&R!==void 0?R:"").toLowerCase(),W=["gzip","deflate","br"].includes(U),{options:te}=C;if(W&&!te.decompress)I.body=N;else try{I.body=sde.default(I,te.responseType,te.parseJson,te.encoding)}catch(ie){if(I.body=N.toString(),ade.isResponseOk(I)){C._beforeError(ie);return}}try{for(let[ie,Ae]of te.hooks.afterResponse.entries())I=await Ae(I,async ce=>{let me=ode.default.normalizeArguments(void 0,{...ce,retry:{calculateDelay:()=>0},throwHttpErrors:!1,resolveBodyOnly:!1},te);me.hooks.afterResponse=me.hooks.afterResponse.slice(0,ie);for(let Be of me.hooks.beforeRetry)await Be(me);let pe=lde(me);return p(()=>{pe.catch(()=>{}),pe.cancel()}),pe})}catch(ie){C._beforeError(new lT.RequestError(ie.message,ie,C));return}if(!ade.isResponseOk(I)){C._beforeError(new lT.HTTPError(I));return}r=I,c(C.options.resolveBodyOnly?I.body:I)});let S=I=>{if(a.isCanceled)return;let{options:R}=C;if(I instanceof lT.HTTPError&&!R.throwHttpErrors){let{response:N}=I;c(C.options.resolveBodyOnly?N.body:N);return}f(I)};C.once("error",S);let P=C.options.body;C.once("retry",(I,R)=>{var N,U;if(P===((N=R.request)===null||N===void 0?void 0:N.options.body)&&Qdt.default.nodeStream((U=R.request)===null||U===void 0?void 0:U.options.body)){S(R);return}h(I)}),Rdt.default(C,s,Ndt)};h(0)});a.on=(c,f)=>(s.on(c,f),a);let n=c=>{let f=(async()=>{await a;let{options:p}=r.request;return sde.default(r,c,p.parseJson,p.encoding)})();return Object.defineProperties(f,Object.getOwnPropertyDescriptors(a)),f};return a.json=()=>{let{headers:c}=e.options;return!e.writableFinished&&c.accept===void 0&&(c.accept="application/json"),n("json")},a.buffer=()=>n("buffer"),a.text=()=>n("text"),a}D0.default=lde;xdt(dv(),D0)});var cde=L(Bj=>{"use strict";Object.defineProperty(Bj,"__esModule",{value:!0});var Odt=dv();function Ldt(t,...e){let r=(async()=>{if(t instanceof Odt.RequestError)try{for(let a of e)if(a)for(let n of a)t=await n(t)}catch(a){t=a}throw t})(),s=()=>r;return r.json=s,r.text=s,r.buffer=s,r.on=s,r}Bj.default=Ldt});var Ade=L(vj=>{"use strict";Object.defineProperty(vj,"__esModule",{value:!0});var ude=Mp();function fde(t){for(let e of Object.values(t))(ude.default.plainObject(e)||ude.default.array(e))&&fde(e);return Object.freeze(t)}vj.default=fde});var hde=L(pde=>{"use strict";Object.defineProperty(pde,"__esModule",{value:!0})});var Sj=L(Lc=>{"use strict";var Mdt=Lc&&Lc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),_dt=Lc&&Lc.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&Mdt(e,t,r)};Object.defineProperty(Lc,"__esModule",{value:!0});Lc.defaultHandler=void 0;var gde=Mp(),Oc=wj(),Udt=cde(),uT=gv(),Hdt=Ade(),jdt={RequestError:Oc.RequestError,CacheError:Oc.CacheError,ReadError:Oc.ReadError,HTTPError:Oc.HTTPError,MaxRedirectsError:Oc.MaxRedirectsError,TimeoutError:Oc.TimeoutError,ParseError:Oc.ParseError,CancelError:Oc.CancelError,UnsupportedProtocolError:Oc.UnsupportedProtocolError,UploadError:Oc.UploadError},qdt=async t=>new Promise(e=>{setTimeout(e,t)}),{normalizeArguments:cT}=uT.default,dde=(...t)=>{let e;for(let r of t)e=cT(void 0,r,e);return e},Gdt=t=>t.isStream?new uT.default(void 0,t):Oc.default(t),Wdt=t=>"defaults"in t&&"options"in t.defaults,Ydt=["get","post","put","patch","head","delete"];Lc.defaultHandler=(t,e)=>e(t);var mde=(t,e)=>{if(t)for(let r of t)r(e)},yde=t=>{t._rawHandlers=t.handlers,t.handlers=t.handlers.map(s=>(a,n)=>{let c,f=s(a,p=>(c=n(p),c));if(f!==c&&!a.isStream&&c){let p=f,{then:h,catch:E,finally:C}=p;Object.setPrototypeOf(p,Object.getPrototypeOf(c)),Object.defineProperties(p,Object.getOwnPropertyDescriptors(c)),p.then=h,p.catch=E,p.finally=C}return f});let e=(s,a={},n)=>{var c,f;let p=0,h=E=>t.handlers[p++](E,p===t.handlers.length?Gdt:h);if(gde.default.plainObject(s)){let E={...s,...a};uT.setNonEnumerableProperties([s,a],E),a=E,s=void 0}try{let E;try{mde(t.options.hooks.init,a),mde((c=a.hooks)===null||c===void 0?void 0:c.init,a)}catch(S){E=S}let C=cT(s,a,n??t.options);if(C[uT.kIsNormalizedAlready]=!0,E)throw new Oc.RequestError(E.message,E,C);return h(C)}catch(E){if(a.isStream)throw E;return Udt.default(E,t.options.hooks.beforeError,(f=a.hooks)===null||f===void 0?void 0:f.beforeError)}};e.extend=(...s)=>{let a=[t.options],n=[...t._rawHandlers],c;for(let f of s)Wdt(f)?(a.push(f.defaults.options),n.push(...f.defaults._rawHandlers),c=f.defaults.mutableDefaults):(a.push(f),"handlers"in f&&n.push(...f.handlers),c=f.mutableDefaults);return n=n.filter(f=>f!==Lc.defaultHandler),n.length===0&&n.push(Lc.defaultHandler),yde({options:dde(...a),handlers:n,mutableDefaults:!!c})};let r=async function*(s,a){let n=cT(s,a,t.options);n.resolveBodyOnly=!1;let c=n.pagination;if(!gde.default.object(c))throw new TypeError("`options.pagination` must be implemented");let f=[],{countLimit:p}=c,h=0;for(;h{let n=[];for await(let c of r(s,a))n.push(c);return n},e.paginate.each=r,e.stream=(s,a)=>e(s,{...a,isStream:!0});for(let s of Ydt)e[s]=(a,n)=>e(a,{...n,method:s}),e.stream[s]=(a,n)=>e(a,{...n,method:s,isStream:!0});return Object.assign(e,jdt),Object.defineProperty(e,"defaults",{value:t.mutableDefaults?t:Hdt.default(t),writable:t.mutableDefaults,configurable:t.mutableDefaults,enumerable:!0}),e.mergeOptions=dde,e};Lc.default=yde;_dt(hde(),Lc)});var Cde=L((_p,fT)=>{"use strict";var Vdt=_p&&_p.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Ede=_p&&_p.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&Vdt(e,t,r)};Object.defineProperty(_p,"__esModule",{value:!0});var Kdt=ye("url"),Ide=Sj(),Jdt={options:{method:"GET",retry:{limit:2,methods:["GET","PUT","HEAD","DELETE","OPTIONS","TRACE"],statusCodes:[408,413,429,500,502,503,504,521,522,524],errorCodes:["ETIMEDOUT","ECONNRESET","EADDRINUSE","ECONNREFUSED","EPIPE","ENOTFOUND","ENETUNREACH","EAI_AGAIN"],maxRetryAfter:void 0,calculateDelay:({computedValue:t})=>t},timeout:{},headers:{"user-agent":"got (https://github.com/sindresorhus/got)"},hooks:{init:[],beforeRequest:[],beforeRedirect:[],beforeRetry:[],beforeError:[],afterResponse:[]},cache:void 0,dnsCache:void 0,decompress:!0,throwHttpErrors:!0,followRedirect:!0,isStream:!1,responseType:"text",resolveBodyOnly:!1,maxRedirects:10,prefixUrl:"",methodRewriting:!0,ignoreInvalidCookies:!1,context:{},http2:!1,allowGetBody:!1,https:void 0,pagination:{transform:t=>t.request.options.responseType==="json"?t.body:JSON.parse(t.body),paginate:t=>{if(!Reflect.has(t.headers,"link"))return!1;let e=t.headers.link.split(","),r;for(let s of e){let a=s.split(";");if(a[1].includes("next")){r=a[0].trimStart().trim(),r=r.slice(1,-1);break}}return r?{url:new Kdt.URL(r)}:!1},filter:()=>!0,shouldContinue:()=>!0,countLimit:1/0,backoff:0,requestLimit:1e4,stackAllItems:!0},parseJson:t=>JSON.parse(t),stringifyJson:t=>JSON.stringify(t),cacheOptions:{}},handlers:[Ide.defaultHandler],mutableDefaults:!1},Dj=Ide.default(Jdt);_p.default=Dj;fT.exports=Dj;fT.exports.default=Dj;fT.exports.__esModule=!0;Ede(Sj(),_p);Ede(wj(),_p)});var An={};Vt(An,{Method:()=>Pde,del:()=>emt,get:()=>kj,getNetworkSettings:()=>bde,post:()=>Qj,put:()=>$dt,request:()=>mv});function vde(t){let e=new URL(t),r={host:e.hostname,headers:{}};return e.port&&(r.port=Number(e.port)),e.username&&e.password&&(r.proxyAuth=`${e.username}:${e.password}`),{proxy:r}}async function bj(t){return Vl(Bde,t,()=>le.readFilePromise(t).then(e=>(Bde.set(t,e),e)))}function Xdt({statusCode:t,statusMessage:e},r){let s=Ut(r,t,Ct.NUMBER),a=`https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/${t}`;return JE(r,`${s}${e?` (${e})`:""}`,a)}async function AT(t,{configuration:e,customErrorMessage:r}){try{return await t}catch(s){if(s.name!=="HTTPError")throw s;let a=r?.(s,e)??s.response.body?.error;a==null&&(s.message.startsWith("Response code")?a="The remote server failed to provide the requested resource":a=s.message),s.code==="ETIMEDOUT"&&s.event==="socket"&&(a+=`(can be increased via ${Ut(e,"httpTimeout",Ct.SETTING)})`);let n=new Yt(35,a,c=>{s.response&&c.reportError(35,` ${Zf(e,{label:"Response Code",value:Hu(Ct.NO_HINT,Xdt(s.response,e))})}`),s.request&&(c.reportError(35,` ${Zf(e,{label:"Request Method",value:Hu(Ct.NO_HINT,s.request.options.method)})}`),c.reportError(35,` ${Zf(e,{label:"Request URL",value:Hu(Ct.URL,s.request.requestUrl)})}`)),s.request.redirects.length>0&&c.reportError(35,` ${Zf(e,{label:"Request Redirects",value:Hu(Ct.NO_HINT,I3(e,s.request.redirects,Ct.URL))})}`),s.request.retryCount===s.request.options.retry.limit&&c.reportError(35,` ${Zf(e,{label:"Request Retry Count",value:Hu(Ct.NO_HINT,`${Ut(e,s.request.retryCount,Ct.NUMBER)} (can be increased via ${Ut(e,"httpRetry",Ct.SETTING)})`)})}`)});throw n.originalError=s,n}}function bde(t,e){let r=[...e.configuration.get("networkSettings")].sort(([c],[f])=>f.length-c.length),s={enableNetwork:void 0,httpsCaFilePath:void 0,httpProxy:void 0,httpsProxy:void 0,httpsKeyFilePath:void 0,httpsCertFilePath:void 0},a=Object.keys(s),n=typeof t=="string"?new URL(t):t;for(let[c,f]of r)if(xj.default.isMatch(n.hostname,c))for(let p of a){let h=f.get(p);h!==null&&typeof s[p]>"u"&&(s[p]=h)}for(let c of a)typeof s[c]>"u"&&(s[c]=e.configuration.get(c));return s}async function mv(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c="GET",wrapNetworkRequest:f}){let p={target:t,body:e,configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c},h=async()=>await tmt(t,e,p),E=typeof f<"u"?await f(h,p):h;return await(await r.reduceHook(S=>S.wrapNetworkRequest,E,p))()}async function kj(t,{configuration:e,jsonResponse:r,customErrorMessage:s,wrapNetworkRequest:a,...n}){let c=()=>AT(mv(t,null,{configuration:e,wrapNetworkRequest:a,...n}),{configuration:e,customErrorMessage:s}).then(p=>p.body),f=await(typeof a<"u"?c():Vl(wde,t,()=>c().then(p=>(wde.set(t,p),p))));return r?JSON.parse(f.toString()):f}async function $dt(t,e,{customErrorMessage:r,...s}){return(await AT(mv(t,e,{...s,method:"PUT"}),{customErrorMessage:r,configuration:s.configuration})).body}async function Qj(t,e,{customErrorMessage:r,...s}){return(await AT(mv(t,e,{...s,method:"POST"}),{customErrorMessage:r,configuration:s.configuration})).body}async function emt(t,{customErrorMessage:e,...r}){return(await AT(mv(t,null,{...r,method:"DELETE"}),{customErrorMessage:e,configuration:r.configuration})).body}async function tmt(t,e,{configuration:r,headers:s,jsonRequest:a,jsonResponse:n,method:c="GET"}){let f=typeof t=="string"?new URL(t):t,p=bde(f,{configuration:r});if(p.enableNetwork===!1)throw new Yt(80,`Request to '${f.href}' has been blocked because of your configuration settings`);if(f.protocol==="http:"&&!xj.default.isMatch(f.hostname,r.get("unsafeHttpWhitelist")))throw new Yt(81,`Unsafe http requests must be explicitly whitelisted in your configuration (${f.hostname})`);let E={agent:{http:p.httpProxy?Pj.default.httpOverHttp(vde(p.httpProxy)):zdt,https:p.httpsProxy?Pj.default.httpsOverHttp(vde(p.httpsProxy)):Zdt},headers:s,method:c};E.responseType=n?"json":"buffer",e!==null&&(Buffer.isBuffer(e)||!a&&typeof e=="string"?E.body=e:E.json=e);let C=r.get("httpTimeout"),S=r.get("httpRetry"),P=r.get("enableStrictSsl"),I=p.httpsCaFilePath,R=p.httpsCertFilePath,N=p.httpsKeyFilePath,{default:U}=await Promise.resolve().then(()=>et(Cde())),W=I?await bj(I):void 0,te=R?await bj(R):void 0,ie=N?await bj(N):void 0,Ae=U.extend({timeout:{socket:C},retry:S,https:{rejectUnauthorized:P,certificateAuthority:W,certificate:te,key:ie},...E});return r.getLimit("networkConcurrency")(()=>Ae(f))}var Sde,Dde,xj,Pj,wde,Bde,zdt,Zdt,Pde,pT=It(()=>{bt();Sde=ye("https"),Dde=ye("http"),xj=et(Sa()),Pj=et(Xhe());Fc();Qc();kc();wde=new Map,Bde=new Map,zdt=new Dde.Agent({keepAlive:!0}),Zdt=new Sde.Agent({keepAlive:!0});Pde=(a=>(a.GET="GET",a.PUT="PUT",a.POST="POST",a.DELETE="DELETE",a))(Pde||{})});var As={};Vt(As,{availableParallelism:()=>Rj,getArchitecture:()=>yv,getArchitectureName:()=>omt,getArchitectureSet:()=>Tj,getCaller:()=>umt,major:()=>rmt,openUrl:()=>nmt});function smt(){if(process.platform==="darwin"||process.platform==="win32")return null;let t;try{t=le.readFileSync(imt)}catch{}if(typeof t<"u"){if(t&&(t.includes("GLIBC")||t.includes("libc")))return"glibc";if(t&&t.includes("musl"))return"musl"}let r=(process.report?.getReport()??{}).sharedObjects??[],s=/\/(?:(ld-linux-|[^/]+-linux-gnu\/)|(libc.musl-|ld-musl-))/;return p0(r,a=>{let n=a.match(s);if(!n)return p0.skip;if(n[1])return"glibc";if(n[2])return"musl";throw new Error("Assertion failed: Expected the libc variant to have been detected")})??null}function yv(){return kde=kde??{os:process.platform,cpu:process.arch,libc:smt()}}function omt(t=yv()){return t.libc?`${t.os}-${t.cpu}-${t.libc}`:`${t.os}-${t.cpu}`}function Tj(){let t=yv();return Qde=Qde??{os:[t.os],cpu:[t.cpu],libc:t.libc?[t.libc]:[]}}function cmt(t){let e=amt.exec(t);if(!e)return null;let r=e[2]&&e[2].indexOf("native")===0,s=e[2]&&e[2].indexOf("eval")===0,a=lmt.exec(e[2]);return s&&a!=null&&(e[2]=a[1],e[3]=a[2],e[4]=a[3]),{file:r?null:e[2],methodName:e[1]||"",arguments:r?[e[2]]:[],line:e[3]?+e[3]:null,column:e[4]?+e[4]:null}}function umt(){let e=new Error().stack.split(` +`)[3];return cmt(e)}function Rj(){return typeof hT.default.availableParallelism<"u"?hT.default.availableParallelism():Math.max(1,hT.default.cpus().length)}var hT,rmt,xde,nmt,imt,kde,Qde,amt,lmt,gT=It(()=>{bt();hT=et(ye("os"));dT();kc();rmt=Number(process.versions.node.split(".")[0]),xde=new Map([["darwin","open"],["linux","xdg-open"],["win32","explorer.exe"]]).get(process.platform),nmt=typeof xde<"u"?async t=>{try{return await Fj(xde,[t],{cwd:K.cwd()}),!0}catch{return!1}}:void 0,imt="/usr/bin/ldd";amt=/^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack||\/|[a-z]:\\|\\\\).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,lmt=/\((\S*)(?::(\d+))(?::(\d+))\)/});function _j(t,e,r,s,a){let n=sv(r);if(s.isArray||s.type==="ANY"&&Array.isArray(n))return Array.isArray(n)?n.map((c,f)=>Nj(t,`${e}[${f}]`,c,s,a)):String(n).split(/,/).map(c=>Nj(t,e,c,s,a));if(Array.isArray(n))throw new Error(`Non-array configuration settings "${e}" cannot be an array`);return Nj(t,e,r,s,a)}function Nj(t,e,r,s,a){let n=sv(r);switch(s.type){case"ANY":return NQ(n);case"SHAPE":return hmt(t,e,r,s,a);case"MAP":return gmt(t,e,r,s,a)}if(n===null&&!s.isNullable&&s.default!==null)throw new Error(`Non-nullable configuration settings "${e}" cannot be set to null`);if(s.values?.includes(n))return n;let f=(()=>{if(s.type==="BOOLEAN"&&typeof n!="string")return qB(n);if(typeof n!="string")throw new Error(`Expected configuration setting "${e}" to be a string, got ${typeof n}`);let p=Jk(n,{env:t.env});switch(s.type){case"ABSOLUTE_PATH":{let h=a,E=fH(r);return E&&E[0]!=="<"&&(h=K.dirname(E)),K.resolve(h,ue.toPortablePath(p))}case"LOCATOR_LOOSE":return Rp(p,!1);case"NUMBER":return parseInt(p);case"LOCATOR":return Rp(p);case"BOOLEAN":return qB(p);default:return p}})();if(s.values&&!s.values.includes(f))throw new Error(`Invalid value, expected one of ${s.values.join(", ")}`);return f}function hmt(t,e,r,s,a){let n=sv(r);if(typeof n!="object"||Array.isArray(n))throw new nt(`Object configuration settings "${e}" must be an object`);let c=Uj(t,s,{ignoreArrays:!0});if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=`${e}.${f}`;if(!s.properties[f])throw new nt(`Unrecognized configuration settings found: ${e}.${f} - run "yarn config -v" to see the list of settings supported in Yarn`);c.set(f,_j(t,h,p,s.properties[f],a))}return c}function gmt(t,e,r,s,a){let n=sv(r),c=new Map;if(typeof n!="object"||Array.isArray(n))throw new nt(`Map configuration settings "${e}" must be an object`);if(n===null)return c;for(let[f,p]of Object.entries(n)){let h=s.normalizeKeys?s.normalizeKeys(f):f,E=`${e}['${h}']`,C=s.valueDefinition;c.set(h,_j(t,E,p,C,a))}return c}function Uj(t,e,{ignoreArrays:r=!1}={}){switch(e.type){case"SHAPE":{if(e.isArray&&!r)return[];let s=new Map;for(let[a,n]of Object.entries(e.properties))s.set(a,Uj(t,n));return s}case"MAP":return e.isArray&&!r?[]:new Map;case"ABSOLUTE_PATH":return e.default===null?null:t.projectCwd===null?Array.isArray(e.default)?e.default.map(s=>K.normalize(s)):K.isAbsolute(e.default)?K.normalize(e.default):e.isNullable?null:void 0:Array.isArray(e.default)?e.default.map(s=>K.resolve(t.projectCwd,s)):K.resolve(t.projectCwd,e.default);default:return e.default}}function yT(t,e,r){if(e.type==="SECRET"&&typeof t=="string"&&r.hideSecrets)return pmt;if(e.type==="ABSOLUTE_PATH"&&typeof t=="string"&&r.getNativePaths)return ue.fromPortablePath(t);if(e.isArray&&Array.isArray(t)){let s=[];for(let a of t)s.push(yT(a,e,r));return s}if(e.type==="MAP"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=yT(n,e.valueDefinition,r);typeof c<"u"&&s.set(a,c)}return s}if(e.type==="SHAPE"&&t instanceof Map){if(t.size===0)return;let s=new Map;for(let[a,n]of t.entries()){let c=e.properties[a],f=yT(n,c,r);typeof f<"u"&&s.set(a,f)}return s}return t}function dmt(){let t={};for(let[e,r]of Object.entries(process.env))e=e.toLowerCase(),e.startsWith(ET)&&(e=(0,Rde.default)(e.slice(ET.length)),t[e]=r);return t}function Lj(){let t=`${ET}rc_filename`;for(let[e,r]of Object.entries(process.env))if(e.toLowerCase()===t&&typeof r=="string")return r;return Mj}async function Tde(t){try{return await le.readFilePromise(t)}catch{return Buffer.of()}}async function mmt(t,e){return Buffer.compare(...await Promise.all([Tde(t),Tde(e)]))===0}async function ymt(t,e){let[r,s]=await Promise.all([le.statPromise(t),le.statPromise(e)]);return r.dev===s.dev&&r.ino===s.ino}async function Imt({configuration:t,selfPath:e}){let r=t.get("yarnPath");return t.get("ignorePath")||r===null||r===e||await Emt(r,e)?null:r}var Rde,Up,Fde,Nde,Ode,Oj,fmt,Ev,Amt,Hp,ET,Mj,pmt,Iv,Lde,IT,mT,Emt,ze,Cv=It(()=>{bt();Bc();Rde=et(lne()),Up=et(Nd());Wt();Fde=et(tie()),Nde=ye("module"),Ode=et(Md()),Oj=ye("stream");phe();oI();nH();iH();sH();Hhe();oH();tm();Yhe();LQ();Qc();I0();pT();kc();gT();Np();Yo();fmt=function(){if(!Up.GITHUB_ACTIONS||!process.env.GITHUB_EVENT_PATH)return!1;let t=ue.toPortablePath(process.env.GITHUB_EVENT_PATH),e;try{e=le.readJsonSync(t)}catch{return!1}return!(!("repository"in e)||!e.repository||(e.repository.private??!0))}(),Ev=new Set(["@yarnpkg/plugin-constraints","@yarnpkg/plugin-exec","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"]),Amt=new Set(["isTestEnv","injectNpmUser","injectNpmPassword","injectNpm2FaToken","zipDataEpilogue","cacheCheckpointOverride","cacheVersionOverride","lockfileVersionOverride","binFolder","version","flags","profile","gpg","ignoreNode","wrapOutput","home","confDir","registry","ignoreCwd"]),Hp=/^(?!v)[a-z0-9._-]+$/i,ET="yarn_",Mj=".yarnrc.yml",pmt="********",Iv=(E=>(E.ANY="ANY",E.BOOLEAN="BOOLEAN",E.ABSOLUTE_PATH="ABSOLUTE_PATH",E.LOCATOR="LOCATOR",E.LOCATOR_LOOSE="LOCATOR_LOOSE",E.NUMBER="NUMBER",E.STRING="STRING",E.SECRET="SECRET",E.SHAPE="SHAPE",E.MAP="MAP",E))(Iv||{}),Lde=Ct,IT=(r=>(r.JUNCTIONS="junctions",r.SYMLINKS="symlinks",r))(IT||{}),mT={lastUpdateCheck:{description:"Last timestamp we checked whether new Yarn versions were available",type:"STRING",default:null},yarnPath:{description:"Path to the local executable that must be used over the global one",type:"ABSOLUTE_PATH",default:null},ignorePath:{description:"If true, the local executable will be ignored when using the global one",type:"BOOLEAN",default:!1},globalFolder:{description:"Folder where all system-global files are stored",type:"ABSOLUTE_PATH",default:pH()},cacheFolder:{description:"Folder where the cache files must be written",type:"ABSOLUTE_PATH",default:"./.yarn/cache"},compressionLevel:{description:"Zip files compression level, from 0 to 9 or mixed (a variant of 9, which stores some files uncompressed, when compression doesn't yield good results)",type:"NUMBER",values:["mixed",0,1,2,3,4,5,6,7,8,9],default:0},virtualFolder:{description:"Folder where the virtual packages (cf doc) will be mapped on the disk (must be named __virtual__)",type:"ABSOLUTE_PATH",default:"./.yarn/__virtual__"},installStatePath:{description:"Path of the file where the install state will be persisted",type:"ABSOLUTE_PATH",default:"./.yarn/install-state.gz"},immutablePatterns:{description:"Array of glob patterns; files matching them won't be allowed to change during immutable installs",type:"STRING",default:[],isArray:!0},rcFilename:{description:"Name of the files where the configuration can be found",type:"STRING",default:Lj()},enableGlobalCache:{description:"If true, the system-wide cache folder will be used regardless of `cache-folder`",type:"BOOLEAN",default:!0},cacheMigrationMode:{description:"Defines the conditions under which Yarn upgrades should cause the cache archives to be regenerated.",type:"STRING",values:["always","match-spec","required-only"],default:"always"},enableColors:{description:"If true, the CLI is allowed to use colors in its output",type:"BOOLEAN",default:Xk,defaultText:""},enableHyperlinks:{description:"If true, the CLI is allowed to use hyperlinks in its output",type:"BOOLEAN",default:E3,defaultText:""},enableInlineBuilds:{description:"If true, the CLI will print the build output on the command line",type:"BOOLEAN",default:Up.isCI,defaultText:""},enableMessageNames:{description:"If true, the CLI will prefix most messages with codes suitable for search engines",type:"BOOLEAN",default:!0},enableProgressBars:{description:"If true, the CLI is allowed to show a progress bar for long-running events",type:"BOOLEAN",default:!Up.isCI,defaultText:""},enableTimers:{description:"If true, the CLI is allowed to print the time spent executing commands",type:"BOOLEAN",default:!0},enableTips:{description:"If true, installs will print a helpful message every day of the week",type:"BOOLEAN",default:!Up.isCI,defaultText:""},preferInteractive:{description:"If true, the CLI will automatically use the interactive mode when called from a TTY",type:"BOOLEAN",default:!1},preferTruncatedLines:{description:"If true, the CLI will truncate lines that would go beyond the size of the terminal",type:"BOOLEAN",default:!1},progressBarStyle:{description:"Which style of progress bar should be used (only when progress bars are enabled)",type:"STRING",default:void 0,defaultText:""},defaultLanguageName:{description:"Default language mode that should be used when a package doesn't offer any insight",type:"STRING",default:"node"},defaultProtocol:{description:"Default resolution protocol used when resolving pure semver and tag ranges",type:"STRING",default:"npm:"},enableTransparentWorkspaces:{description:"If false, Yarn won't automatically resolve workspace dependencies unless they use the `workspace:` protocol",type:"BOOLEAN",default:!0},supportedArchitectures:{description:"Architectures that Yarn will fetch and inject into the resolver",type:"SHAPE",properties:{os:{description:"Array of supported process.platform strings, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]},cpu:{description:"Array of supported process.arch strings, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]},libc:{description:"Array of supported libc libraries, or null to target them all",type:"STRING",isArray:!0,isNullable:!0,default:["current"]}}},enableMirror:{description:"If true, the downloaded packages will be retrieved and stored in both the local and global folders",type:"BOOLEAN",default:!0},enableNetwork:{description:"If false, Yarn will refuse to use the network if required to",type:"BOOLEAN",default:!0},enableOfflineMode:{description:"If true, Yarn will attempt to retrieve files and metadata from the global cache rather than the network",type:"BOOLEAN",default:!1},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:"STRING",default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:"STRING",default:null},unsafeHttpWhitelist:{description:"List of the hostnames for which http queries are allowed (glob patterns are supported)",type:"STRING",default:[],isArray:!0},httpTimeout:{description:"Timeout of each http request in milliseconds",type:"NUMBER",default:6e4},httpRetry:{description:"Retry times on http failure",type:"NUMBER",default:3},networkConcurrency:{description:"Maximal number of concurrent requests",type:"NUMBER",default:50},taskPoolConcurrency:{description:"Maximal amount of concurrent heavy task processing",type:"NUMBER",default:Rj()},taskPoolMode:{description:"Execution strategy for heavy tasks",type:"STRING",values:["async","workers"],default:"workers"},networkSettings:{description:"Network settings per hostname (glob patterns are supported)",type:"MAP",valueDefinition:{description:"",type:"SHAPE",properties:{httpsCaFilePath:{description:"Path to file containing one or multiple Certificate Authority signing certificates",type:"ABSOLUTE_PATH",default:null},enableNetwork:{description:"If false, the package manager will refuse to use the network if required to",type:"BOOLEAN",default:null},httpProxy:{description:"URL of the http proxy that must be used for outgoing http requests",type:"STRING",default:null},httpsProxy:{description:"URL of the http proxy that must be used for outgoing https requests",type:"STRING",default:null},httpsKeyFilePath:{description:"Path to file containing private key in PEM format",type:"ABSOLUTE_PATH",default:null},httpsCertFilePath:{description:"Path to file containing certificate chain in PEM format",type:"ABSOLUTE_PATH",default:null}}}},httpsCaFilePath:{description:"A path to a file containing one or multiple Certificate Authority signing certificates",type:"ABSOLUTE_PATH",default:null},httpsKeyFilePath:{description:"Path to file containing private key in PEM format",type:"ABSOLUTE_PATH",default:null},httpsCertFilePath:{description:"Path to file containing certificate chain in PEM format",type:"ABSOLUTE_PATH",default:null},enableStrictSsl:{description:"If false, SSL certificate errors will be ignored",type:"BOOLEAN",default:!0},logFilters:{description:"Overrides for log levels",type:"SHAPE",isArray:!0,concatenateValues:!0,properties:{code:{description:"Code of the messages covered by this override",type:"STRING",default:void 0},text:{description:"Code of the texts covered by this override",type:"STRING",default:void 0},pattern:{description:"Code of the patterns covered by this override",type:"STRING",default:void 0},level:{description:"Log level override, set to null to remove override",type:"STRING",values:Object.values(eQ),isNullable:!0,default:void 0}}},enableTelemetry:{description:"If true, telemetry will be periodically sent, following the rules in https://yarnpkg.com/advanced/telemetry",type:"BOOLEAN",default:!0},telemetryInterval:{description:"Minimal amount of time between two telemetry uploads, in days",type:"NUMBER",default:7},telemetryUserId:{description:"If you desire to tell us which project you are, you can set this field. Completely optional and opt-in.",type:"STRING",default:null},enableHardenedMode:{description:"If true, automatically enable --check-resolutions --refresh-lockfile on installs",type:"BOOLEAN",default:Up.isPR&&fmt,defaultText:""},enableScripts:{description:"If true, packages are allowed to have install scripts by default",type:"BOOLEAN",default:!0},enableStrictSettings:{description:"If true, unknown settings will cause Yarn to abort",type:"BOOLEAN",default:!0},enableImmutableCache:{description:"If true, the cache is reputed immutable and actions that would modify it will throw",type:"BOOLEAN",default:!1},enableCacheClean:{description:"If false, disallows the `cache clean` command",type:"BOOLEAN",default:!0},checksumBehavior:{description:"Enumeration defining what to do when a checksum doesn't match expectations",type:"STRING",default:"throw"},injectEnvironmentFiles:{description:"List of all the environment files that Yarn should inject inside the process when it starts",type:"ABSOLUTE_PATH",default:[".env.yarn?"],isArray:!0},packageExtensions:{description:"Map of package corrections to apply on the dependency tree",type:"MAP",valueDefinition:{description:"The extension that will be applied to any package whose version matches the specified range",type:"SHAPE",properties:{dependencies:{description:"The set of dependencies that must be made available to the current package in order for it to work properly",type:"MAP",valueDefinition:{description:"A range",type:"STRING"}},peerDependencies:{description:"Inherited dependencies - the consumer of the package will be tasked to provide them",type:"MAP",valueDefinition:{description:"A semver range",type:"STRING"}},peerDependenciesMeta:{description:"Extra information related to the dependencies listed in the peerDependencies field",type:"MAP",valueDefinition:{description:"The peerDependency meta",type:"SHAPE",properties:{optional:{description:"If true, the selected peer dependency will be marked as optional by the package manager and the consumer omitting it won't be reported as an error",type:"BOOLEAN",default:!1}}}}}}}};Emt=process.platform==="win32"?mmt:ymt;ze=class t{constructor(e){this.isCI=Up.isCI;this.projectCwd=null;this.plugins=new Map;this.settings=new Map;this.values=new Map;this.sources=new Map;this.invalid=new Map;this.env={};this.limits=new Map;this.packageExtensions=null;this.startingCwd=e}static{this.deleteProperty=Symbol()}static{this.telemetry=null}static create(e,r,s){let a=new t(e);typeof r<"u"&&!(r instanceof Map)&&(a.projectCwd=r),a.importSettings(mT);let n=typeof s<"u"?s:r instanceof Map?r:new Map;for(let[c,f]of n)a.activatePlugin(c,f);return a}static async find(e,r,{strict:s=!0,usePathCheck:a=null,useRc:n=!0}={}){let c=dmt();delete c.rcFilename;let f=new t(e),p=await t.findRcFiles(e),h=await t.findFolderRcFile(AI());h&&(p.find(me=>me.path===h.path)||p.unshift(h));let E=Whe(p.map(ce=>[ce.path,ce.data])),C=vt.dot,S=new Set(Object.keys(mT)),P=({yarnPath:ce,ignorePath:me,injectEnvironmentFiles:pe})=>({yarnPath:ce,ignorePath:me,injectEnvironmentFiles:pe}),I=({yarnPath:ce,ignorePath:me,injectEnvironmentFiles:pe,...Be})=>{let Ce={};for(let[g,we]of Object.entries(Be))S.has(g)&&(Ce[g]=we);return Ce},R=({yarnPath:ce,ignorePath:me,...pe})=>{let Be={};for(let[Ce,g]of Object.entries(pe))S.has(Ce)||(Be[Ce]=g);return Be};if(f.importSettings(P(mT)),f.useWithSource("",P(c),e,{strict:!1}),E){let[ce,me]=E;f.useWithSource(ce,P(me),C,{strict:!1})}if(a){if(await Imt({configuration:f,selfPath:a})!==null)return f;f.useWithSource("",{ignorePath:!0},e,{strict:!1,overwrite:!0})}let N=await t.findProjectCwd(e);f.startingCwd=e,f.projectCwd=N;let U=Object.assign(Object.create(null),process.env);f.env=U;let W=await Promise.all(f.get("injectEnvironmentFiles").map(async ce=>{let me=ce.endsWith("?")?await le.readFilePromise(ce.slice(0,-1),"utf8").catch(()=>""):await le.readFilePromise(ce,"utf8");return(0,Fde.parse)(me)}));for(let ce of W)for(let[me,pe]of Object.entries(ce))f.env[me]=Jk(pe,{env:U});if(f.importSettings(I(mT)),f.useWithSource("",I(c),e,{strict:s}),E){let[ce,me]=E;f.useWithSource(ce,I(me),C,{strict:s})}let te=ce=>"default"in ce?ce.default:ce,ie=new Map([["@@core",Ahe]]);if(r!==null)for(let ce of r.plugins.keys())ie.set(ce,te(r.modules.get(ce)));for(let[ce,me]of ie)f.activatePlugin(ce,me);let Ae=new Map([]);if(r!==null){let ce=new Map;for(let[Be,Ce]of r.modules)ce.set(Be,()=>Ce);let me=new Set,pe=async(Be,Ce)=>{let{factory:g,name:we}=kp(Be);if(!g||me.has(we))return;let Ee=new Map(ce),fe=X=>{if((0,Nde.isBuiltin)(X))return kp(X);if(Ee.has(X))return Ee.get(X)();throw new nt(`This plugin cannot access the package referenced via ${X} which is neither a builtin, nor an exposed entry`)},se=await GE(async()=>te(await g(fe)),X=>`${X} (when initializing ${we}, defined in ${Ce})`);ce.set(we,()=>se),me.add(we),Ae.set(we,se)};if(c.plugins)for(let Be of c.plugins.split(";")){let Ce=K.resolve(e,ue.toPortablePath(Be));await pe(Ce,"")}for(let{path:Be,cwd:Ce,data:g}of p)if(n&&Array.isArray(g.plugins))for(let we of g.plugins){let Ee=typeof we!="string"?we.path:we,fe=we?.spec??"",se=we?.checksum??"";if(Ev.has(fe))continue;let X=K.resolve(Ce,ue.toPortablePath(Ee));if(!await le.existsPromise(X)){if(!fe){let gt=Ut(f,K.basename(X,".cjs"),Ct.NAME),j=Ut(f,".gitignore",Ct.NAME),rt=Ut(f,f.values.get("rcFilename"),Ct.NAME),Fe=Ut(f,"https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored",Ct.URL);throw new nt(`Missing source for the ${gt} plugin - please try to remove the plugin from ${rt} then reinstall it manually. This error usually occurs because ${j} is incorrect, check ${Fe} to make sure your plugin folder isn't gitignored.`)}if(!fe.match(/^https?:/)){let gt=Ut(f,K.basename(X,".cjs"),Ct.NAME),j=Ut(f,f.values.get("rcFilename"),Ct.NAME);throw new nt(`Failed to recognize the source for the ${gt} plugin - please try to delete the plugin from ${j} then reinstall it manually.`)}let De=await kj(fe,{configuration:f}),Re=us(De);if(se&&se!==Re){let gt=Ut(f,K.basename(X,".cjs"),Ct.NAME),j=Ut(f,f.values.get("rcFilename"),Ct.NAME),rt=Ut(f,`yarn plugin import ${fe}`,Ct.CODE);throw new nt(`Failed to fetch the ${gt} plugin from its remote location: its checksum seems to have changed. If this is expected, please remove the plugin from ${j} then run ${rt} to reimport it.`)}await le.mkdirPromise(K.dirname(X),{recursive:!0}),await le.writeFilePromise(X,De)}await pe(X,Be)}}for(let[ce,me]of Ae)f.activatePlugin(ce,me);if(f.useWithSource("",R(c),e,{strict:s}),E){let[ce,me]=E;f.useWithSource(ce,R(me),C,{strict:s})}return f.get("enableGlobalCache")&&(f.values.set("cacheFolder",`${f.get("globalFolder")}/cache`),f.sources.set("cacheFolder","")),f}static async findRcFiles(e){let r=Lj(),s=[],a=e,n=null;for(;a!==n;){n=a;let c=K.join(n,r);if(le.existsSync(c)){let f,p;try{p=await le.readFilePromise(c,"utf8"),f=ls(p)}catch{let h="";throw p?.match(/^\s+(?!-)[^:]+\s+\S+/m)&&(h=" (in particular, make sure you list the colons after each key name)"),new nt(`Parse error when loading ${c}; please check it's proper Yaml${h}`)}s.unshift({path:c,cwd:n,data:f})}a=K.dirname(n)}return s}static async findFolderRcFile(e){let r=K.join(e,Er.rc),s;try{s=await le.readFilePromise(r,"utf8")}catch(n){if(n.code==="ENOENT")return null;throw n}let a=ls(s);return{path:r,cwd:e,data:a}}static async findProjectCwd(e){let r=null,s=e,a=null;for(;s!==a;){if(a=s,le.existsSync(K.join(a,Er.lockfile)))return a;le.existsSync(K.join(a,Er.manifest))&&(r=a),s=K.dirname(a)}return r}static async updateConfiguration(e,r,s={}){let a=Lj(),n=K.join(e,a),c=le.existsSync(n)?ls(await le.readFilePromise(n,"utf8")):{},f=!1,p;if(typeof r=="function"){try{p=r(c)}catch{p=r({})}if(p===c)return!1}else{p=c;for(let h of Object.keys(r)){let E=c[h],C=r[h],S;if(typeof C=="function")try{S=C(E)}catch{S=C(void 0)}else S=C;E!==S&&(S===t.deleteProperty?delete p[h]:p[h]=S,f=!0)}if(!f)return!1}return await le.changeFilePromise(n,il(p),{automaticNewlines:!0}),!0}static async addPlugin(e,r){r.length!==0&&await t.updateConfiguration(e,s=>{let a=s.plugins??[];if(a.length===0)return{...s,plugins:r};let n=[],c=[...r];for(let f of a){let p=typeof f!="string"?f.path:f,h=c.find(E=>E.path===p);h?(n.push(h),c=c.filter(E=>E!==h)):n.push(f)}return n.push(...c),{...s,plugins:n}})}static async updateHomeConfiguration(e){let r=AI();return await t.updateConfiguration(r,e)}activatePlugin(e,r){this.plugins.set(e,r),typeof r.configuration<"u"&&this.importSettings(r.configuration)}importSettings(e){for(let[r,s]of Object.entries(e))if(s!=null){if(this.settings.has(r))throw new Error(`Cannot redefine settings "${r}"`);this.settings.set(r,s),this.values.set(r,Uj(this,s))}}useWithSource(e,r,s,a){try{this.use(e,r,s,a)}catch(n){throw n.message+=` (in ${Ut(this,e,Ct.PATH)})`,n}}use(e,r,s,{strict:a=!0,overwrite:n=!1}={}){a=a&&this.get("enableStrictSettings");for(let c of["enableStrictSettings",...Object.keys(r)]){let f=r[c],p=fH(f);if(p&&(e=p),typeof f>"u"||c==="plugins"||e===""&&Amt.has(c))continue;if(c==="rcFilename")throw new nt(`The rcFilename settings can only be set via ${`${ET}RC_FILENAME`.toUpperCase()}, not via a rc file`);let h=this.settings.get(c);if(!h){let C=AI(),S=e[0]!=="<"?K.dirname(e):null;if(a&&!(S!==null?C===S:!1))throw new nt(`Unrecognized or legacy configuration settings found: ${c} - run "yarn config -v" to see the list of settings supported in Yarn`);this.invalid.set(c,e);continue}if(this.sources.has(c)&&!(n||h.type==="MAP"||h.isArray&&h.concatenateValues))continue;let E;try{E=_j(this,c,f,h,s)}catch(C){throw C.message+=` in ${Ut(this,e,Ct.PATH)}`,C}if(c==="enableStrictSettings"&&e!==""){a=E;continue}if(h.type==="MAP"){let C=this.values.get(c);this.values.set(c,new Map(n?[...C,...E]:[...E,...C])),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else if(h.isArray&&h.concatenateValues){let C=this.values.get(c);this.values.set(c,n?[...C,...E]:[...E,...C]),this.sources.set(c,`${this.sources.get(c)}, ${e}`)}else this.values.set(c,E),this.sources.set(c,e)}}get(e){if(!this.values.has(e))throw new Error(`Invalid configuration key "${e}"`);return this.values.get(e)}getSpecial(e,{hideSecrets:r=!1,getNativePaths:s=!1}){let a=this.get(e),n=this.settings.get(e);if(typeof n>"u")throw new nt(`Couldn't find a configuration settings named "${e}"`);return yT(a,n,{hideSecrets:r,getNativePaths:s})}getSubprocessStreams(e,{header:r,prefix:s,report:a}){let n,c,f=le.createWriteStream(e);if(this.get("enableInlineBuilds")){let p=a.createStreamReporter(`${s} ${Ut(this,"STDOUT","green")}`),h=a.createStreamReporter(`${s} ${Ut(this,"STDERR","red")}`);n=new Oj.PassThrough,n.pipe(p),n.pipe(f),c=new Oj.PassThrough,c.pipe(h),c.pipe(f)}else n=f,c=f,typeof r<"u"&&n.write(`${r} +`);return{stdout:n,stderr:c}}makeResolver(){let e=[];for(let r of this.plugins.values())for(let s of r.resolvers||[])e.push(new s);return new rm([new FQ,new yi,...e])}makeFetcher(){let e=[];for(let r of this.plugins.values())for(let s of r.fetchers||[])e.push(new s);return new lI([new cI,new uI,...e])}getLinkers(){let e=[];for(let r of this.plugins.values())for(let s of r.linkers||[])e.push(new s);return e}getSupportedArchitectures(){let e=yv(),r=this.get("supportedArchitectures"),s=r.get("os");s!==null&&(s=s.map(c=>c==="current"?e.os:c));let a=r.get("cpu");a!==null&&(a=a.map(c=>c==="current"?e.cpu:c));let n=r.get("libc");return n!==null&&(n=Yl(n,c=>c==="current"?e.libc??Yl.skip:c)),{os:s,cpu:a,libc:n}}isInteractive({interactive:e,stdout:r}){return r.isTTY?e??this.get("preferInteractive"):!1}async getPackageExtensions(){if(this.packageExtensions!==null)return this.packageExtensions;this.packageExtensions=new Map;let e=this.packageExtensions,r=(s,a,{userProvided:n=!1}={})=>{if(!ul(s.range))throw new Error("Only semver ranges are allowed as keys for the packageExtensions setting");let c=new Ht;c.load(a,{yamlCompatibilityMode:!0});let f=jB(e,s.identHash),p=[];f.push([s.range,p]);let h={status:"inactive",userProvided:n,parentDescriptor:s};for(let E of c.dependencies.values())p.push({...h,type:"Dependency",descriptor:E});for(let E of c.peerDependencies.values())p.push({...h,type:"PeerDependency",descriptor:E});for(let[E,C]of c.peerDependenciesMeta)for(let[S,P]of Object.entries(C))p.push({...h,type:"PeerDependencyMeta",selector:E,key:S,value:P})};await this.triggerHook(s=>s.registerPackageExtensions,this,r);for(let[s,a]of this.get("packageExtensions"))r(C0(s,!0),Kk(a),{userProvided:!0});return e}normalizeLocator(e){return ul(e.reference)?Ys(e,`${this.get("defaultProtocol")}${e.reference}`):Hp.test(e.reference)?Ys(e,`${this.get("defaultProtocol")}${e.reference}`):e}normalizeDependency(e){return ul(e.range)?On(e,`${this.get("defaultProtocol")}${e.range}`):Hp.test(e.range)?On(e,`${this.get("defaultProtocol")}${e.range}`):e}normalizeDependencyMap(e){return new Map([...e].map(([r,s])=>[r,this.normalizeDependency(s)]))}normalizePackage(e,{packageExtensions:r}){let s=zB(e),a=r.get(e.identHash);if(typeof a<"u"){let c=e.version;if(c!==null){for(let[f,p]of a)if(eA(c,f))for(let h of p)switch(h.status==="inactive"&&(h.status="redundant"),h.type){case"Dependency":typeof s.dependencies.get(h.descriptor.identHash)>"u"&&(h.status="active",s.dependencies.set(h.descriptor.identHash,this.normalizeDependency(h.descriptor)));break;case"PeerDependency":typeof s.peerDependencies.get(h.descriptor.identHash)>"u"&&(h.status="active",s.peerDependencies.set(h.descriptor.identHash,h.descriptor));break;case"PeerDependencyMeta":{let E=s.peerDependenciesMeta.get(h.selector);(typeof E>"u"||!Object.hasOwn(E,h.key)||E[h.key]!==h.value)&&(h.status="active",Vl(s.peerDependenciesMeta,h.selector,()=>({}))[h.key]=h.value)}break;default:f3(h)}}}let n=c=>c.scope?`${c.scope}__${c.name}`:`${c.name}`;for(let c of s.peerDependenciesMeta.keys()){let f=Da(c);s.peerDependencies.has(f.identHash)||s.peerDependencies.set(f.identHash,On(f,"*"))}for(let c of s.peerDependencies.values()){if(c.scope==="types")continue;let f=n(c),p=ba("types",f),h=cn(p);s.peerDependencies.has(p.identHash)||s.peerDependenciesMeta.has(h)||(s.peerDependencies.set(p.identHash,On(p,"*")),s.peerDependenciesMeta.set(h,{optional:!0}))}return s.dependencies=new Map(Ws(s.dependencies,([,c])=>ll(c))),s.peerDependencies=new Map(Ws(s.peerDependencies,([,c])=>ll(c))),s}getLimit(e){return Vl(this.limits,e,()=>(0,Ode.default)(this.get(e)))}async triggerHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);n&&await n(...r)}}async triggerMultipleHooks(e,r){for(let s of r)await this.triggerHook(e,...s)}async reduceHook(e,r,...s){let a=r;for(let n of this.plugins.values()){let c=n.hooks;if(!c)continue;let f=e(c);f&&(a=await f(a,...s))}return a}async firstHook(e,...r){for(let s of this.plugins.values()){let a=s.hooks;if(!a)continue;let n=e(a);if(!n)continue;let c=await n(...r);if(typeof c<"u")return c}return null}}});var Gr={};Vt(Gr,{EndStrategy:()=>Gj,ExecError:()=>CT,PipeError:()=>wv,execvp:()=>Fj,pipevp:()=>Yu});function om(t){return t!==null&&typeof t.fd=="number"}function Hj(){}function jj(){for(let t of am)t.kill()}async function Yu(t,e,{cwd:r,env:s=process.env,strict:a=!1,stdin:n=null,stdout:c,stderr:f,end:p=2}){let h=["pipe","pipe","pipe"];n===null?h[0]="ignore":om(n)&&(h[0]=n),om(c)&&(h[1]=c),om(f)&&(h[2]=f);let E=(0,qj.default)(t,e,{cwd:ue.fromPortablePath(r),env:{...s,PWD:ue.fromPortablePath(r)},stdio:h});am.add(E),am.size===1&&(process.on("SIGINT",Hj),process.on("SIGTERM",jj)),!om(n)&&n!==null&&n.pipe(E.stdin),om(c)||E.stdout.pipe(c,{end:!1}),om(f)||E.stderr.pipe(f,{end:!1});let C=()=>{for(let S of new Set([c,f]))om(S)||S.end()};return new Promise((S,P)=>{E.on("error",I=>{am.delete(E),am.size===0&&(process.off("SIGINT",Hj),process.off("SIGTERM",jj)),(p===2||p===1)&&C(),P(I)}),E.on("close",(I,R)=>{am.delete(E),am.size===0&&(process.off("SIGINT",Hj),process.off("SIGTERM",jj)),(p===2||p===1&&I!==0)&&C(),I===0||!a?S({code:Wj(I,R)}):P(new wv({fileName:t,code:I,signal:R}))})})}async function Fj(t,e,{cwd:r,env:s=process.env,encoding:a="utf8",strict:n=!1}){let c=["ignore","pipe","pipe"],f=[],p=[],h=ue.fromPortablePath(r);typeof s.PWD<"u"&&(s={...s,PWD:h});let E=(0,qj.default)(t,e,{cwd:h,env:s,stdio:c});return E.stdout.on("data",C=>{f.push(C)}),E.stderr.on("data",C=>{p.push(C)}),await new Promise((C,S)=>{E.on("error",P=>{let I=ze.create(r),R=Ut(I,t,Ct.PATH);S(new Yt(1,`Process ${R} failed to spawn`,N=>{N.reportError(1,` ${Zf(I,{label:"Thrown Error",value:Hu(Ct.NO_HINT,P.message)})}`)}))}),E.on("close",(P,I)=>{let R=a==="buffer"?Buffer.concat(f):Buffer.concat(f).toString(a),N=a==="buffer"?Buffer.concat(p):Buffer.concat(p).toString(a);P===0||!n?C({code:Wj(P,I),stdout:R,stderr:N}):S(new CT({fileName:t,code:P,signal:I,stdout:R,stderr:N}))})})}function Wj(t,e){let r=Cmt.get(e);return typeof r<"u"?128+r:t??1}function wmt(t,e,{configuration:r,report:s}){s.reportError(1,` ${Zf(r,t!==null?{label:"Exit Code",value:Hu(Ct.NUMBER,t)}:{label:"Exit Signal",value:Hu(Ct.CODE,e)})}`)}var qj,Gj,wv,CT,am,Cmt,dT=It(()=>{bt();qj=et(J_());Cv();Fc();Qc();Gj=(s=>(s[s.Never=0]="Never",s[s.ErrorCode=1]="ErrorCode",s[s.Always=2]="Always",s))(Gj||{}),wv=class extends Yt{constructor({fileName:e,code:r,signal:s}){let a=ze.create(K.cwd()),n=Ut(a,e,Ct.PATH);super(1,`Child ${n} reported an error`,c=>{wmt(r,s,{configuration:a,report:c})}),this.code=Wj(r,s)}},CT=class extends wv{constructor({fileName:e,code:r,signal:s,stdout:a,stderr:n}){super({fileName:e,code:r,signal:s}),this.stdout=a,this.stderr=n}};am=new Set;Cmt=new Map([["SIGINT",2],["SIGQUIT",3],["SIGKILL",9],["SIGTERM",15]])});function _de(t){Mde=t}function Bv(){return typeof Yj>"u"&&(Yj=Mde()),Yj}var Yj,Mde,Vj=It(()=>{Mde=()=>{throw new Error("Assertion failed: No libzip instance is available, and no factory was configured")}});var Ude=L((wT,Jj)=>{var Bmt=Object.assign({},ye("fs")),Kj=function(){var t=typeof document<"u"&&document.currentScript?document.currentScript.src:void 0;return typeof __filename<"u"&&(t=t||__filename),function(e){e=e||{};var r=typeof e<"u"?e:{},s,a;r.ready=new Promise(function(Je,st){s=Je,a=st});var n={},c;for(c in r)r.hasOwnProperty(c)&&(n[c]=r[c]);var f=[],p="./this.program",h=function(Je,st){throw st},E=!1,C=!0,S="";function P(Je){return r.locateFile?r.locateFile(Je,S):S+Je}var I,R,N,U;C&&(E?S=ye("path").dirname(S)+"/":S=__dirname+"/",I=function(st,St){var lr=Me(st);return lr?St?lr:lr.toString():(N||(N=Bmt),U||(U=ye("path")),st=U.normalize(st),N.readFileSync(st,St?null:"utf8"))},R=function(st){var St=I(st,!0);return St.buffer||(St=new Uint8Array(St)),we(St.buffer),St},process.argv.length>1&&(p=process.argv[1].replace(/\\/g,"/")),f=process.argv.slice(2),h=function(Je){process.exit(Je)},r.inspect=function(){return"[Emscripten Module object]"});var W=r.print||console.log.bind(console),te=r.printErr||console.warn.bind(console);for(c in n)n.hasOwnProperty(c)&&(r[c]=n[c]);n=null,r.arguments&&(f=r.arguments),r.thisProgram&&(p=r.thisProgram),r.quit&&(h=r.quit);var ie=0,Ae=function(Je){ie=Je},ce;r.wasmBinary&&(ce=r.wasmBinary);var me=r.noExitRuntime||!0;typeof WebAssembly!="object"&&rs("no native wasm support detected");function pe(Je,st,St){switch(st=st||"i8",st.charAt(st.length-1)==="*"&&(st="i32"),st){case"i1":return Ye[Je>>0];case"i8":return Ye[Je>>0];case"i16":return Ih((Je>>1)*2);case"i32":return ro((Je>>2)*4);case"i64":return ro((Je>>2)*4);case"float":return pf((Je>>2)*4);case"double":return Eh((Je>>3)*8);default:rs("invalid type for getValue: "+st)}return null}var Be,Ce=!1,g;function we(Je,st){Je||rs("Assertion failed: "+st)}function Ee(Je){var st=r["_"+Je];return we(st,"Cannot call unknown function "+Je+", make sure it is exported"),st}function fe(Je,st,St,lr,ee){var Ie={string:function(qi){var Tn=0;if(qi!=null&&qi!==0){var Ga=(qi.length<<2)+1;Tn=wi(Ga),gt(qi,Tn,Ga)}return Tn},array:function(qi){var Tn=wi(qi.length);return Fe(qi,Tn),Tn}};function Oe(qi){return st==="string"?De(qi):st==="boolean"?!!qi:qi}var ht=Ee(Je),mt=[],Dt=0;if(lr)for(var tr=0;tr=St)&&ke[lr];)++lr;return X.decode(ke.subarray(Je,lr))}function Re(Je,st,St,lr){if(!(lr>0))return 0;for(var ee=St,Ie=St+lr-1,Oe=0;Oe=55296&&ht<=57343){var mt=Je.charCodeAt(++Oe);ht=65536+((ht&1023)<<10)|mt&1023}if(ht<=127){if(St>=Ie)break;st[St++]=ht}else if(ht<=2047){if(St+1>=Ie)break;st[St++]=192|ht>>6,st[St++]=128|ht&63}else if(ht<=65535){if(St+2>=Ie)break;st[St++]=224|ht>>12,st[St++]=128|ht>>6&63,st[St++]=128|ht&63}else{if(St+3>=Ie)break;st[St++]=240|ht>>18,st[St++]=128|ht>>12&63,st[St++]=128|ht>>6&63,st[St++]=128|ht&63}}return st[St]=0,St-ee}function gt(Je,st,St){return Re(Je,ke,st,St)}function j(Je){for(var st=0,St=0;St=55296&&lr<=57343&&(lr=65536+((lr&1023)<<10)|Je.charCodeAt(++St)&1023),lr<=127?++st:lr<=2047?st+=2:lr<=65535?st+=3:st+=4}return st}function rt(Je){var st=j(Je)+1,St=Ma(st);return St&&Re(Je,Ye,St,st),St}function Fe(Je,st){Ye.set(Je,st)}function Ne(Je,st){return Je%st>0&&(Je+=st-Je%st),Je}var Pe,Ye,ke,it,_e,x,w,b,y,F;function z(Je){Pe=Je,r.HEAP_DATA_VIEW=F=new DataView(Je),r.HEAP8=Ye=new Int8Array(Je),r.HEAP16=it=new Int16Array(Je),r.HEAP32=x=new Int32Array(Je),r.HEAPU8=ke=new Uint8Array(Je),r.HEAPU16=_e=new Uint16Array(Je),r.HEAPU32=w=new Uint32Array(Je),r.HEAPF32=b=new Float32Array(Je),r.HEAPF64=y=new Float64Array(Je)}var Z=r.INITIAL_MEMORY||16777216,$,oe=[],xe=[],Te=[],lt=!1;function Et(){if(r.preRun)for(typeof r.preRun=="function"&&(r.preRun=[r.preRun]);r.preRun.length;)Pt(r.preRun.shift());Rs(oe)}function qt(){lt=!0,Rs(xe)}function ir(){if(r.postRun)for(typeof r.postRun=="function"&&(r.postRun=[r.postRun]);r.postRun.length;)Pr(r.postRun.shift());Rs(Te)}function Pt(Je){oe.unshift(Je)}function gn(Je){xe.unshift(Je)}function Pr(Je){Te.unshift(Je)}var Ir=0,Nr=null,nn=null;function oi(Je){Ir++,r.monitorRunDependencies&&r.monitorRunDependencies(Ir)}function wo(Je){if(Ir--,r.monitorRunDependencies&&r.monitorRunDependencies(Ir),Ir==0&&(Nr!==null&&(clearInterval(Nr),Nr=null),nn)){var st=nn;nn=null,st()}}r.preloadedImages={},r.preloadedAudios={};function rs(Je){r.onAbort&&r.onAbort(Je),Je+="",te(Je),Ce=!0,g=1,Je="abort("+Je+"). Build with -s ASSERTIONS=1 for more info.";var st=new WebAssembly.RuntimeError(Je);throw a(st),st}var eo="data:application/octet-stream;base64,";function Bo(Je){return Je.startsWith(eo)}var Hi="data:application/octet-stream;base64,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QCAAKAJIIgEgASADaiADEAcaAkAgAyAAKAJsIgFNBEAgACABIANrNgJsDAELIABCADcCbAsgACAAKAJoIANrIgE2AmggACAAKAJYIANrNgJYIAEgACgChC5JBEAgACABNgKELgsgAEH8gAEoAgARAwAgAiADaiECCwJAIAAoAgAiASgCBCIERQ0AIAAoAjwhBSAAIAIgBCACIARJGyICBH8gACgCSCAAKAJoaiAFaiEFIAEgBCACazYCBAJAAkACQAJAIAEoAhwiBCgCFEEBaw4CAQACCyAEQaABaiAFIAEoAgAgAkHcgAEoAgARCAAMAgsgASABKAIwIAUgASgCACACQcSAASgCABEEADYCMAwBCyAFIAEoAgAgAhAHGgsgASABKAIAIAJqNgIAIAEgASgCCCACajYCCCAAKAI8BSAFCyACaiICNgI8AkAgACgChC4iASACakEDSQ0AIAAoAmggAWshAQJAIAAoAnRBgQhPBEAgACAAIAAoAkggAWoiAi0AACACLQABIAAoAnwRAAA2AlQMAQsgAUUNACAAIAFBAWsgACgChAERAgAaCyAAKAKELiAAKAI8IgJBAUZrIgRFDQAgACABIAQgACgCgAERBQAgACAAKAKELiAEazYChC4gACgCPCECCyACQYUCSw0AIAAoAgAoAgRFDQAgACgCMCEBDAELCwJAIAAoAkQiAiAAKAJAIgNNDQAgAAJ/IAAoAjwgACgCaGoiASADSwRAIAAoAkggAWpBACACIAFrIgNBggIgA0GCAkkbIgMQGSABIANqDAELIAFBggJqIgEgA00NASAAKAJIIANqQQAgAiADayICIAEgA2siAyACIANJGyIDEBkgACgCQCADags2AkALC50CAQF/AkAgAAJ/IAAoAqAuIgFBwABGBEAgACgCBCAAKAIQaiAAKQOYLjcAACAAQgA3A5guIAAgACgCEEEIajYCEEEADAELIAFBIE4EQCAAKAIEIAAoAhBqIAApA5guPgAAIAAgAEGcLmo1AgA3A5guIAAgACgCEEEEajYCECAAIAAoAqAuQSBrIgE2AqAuCyABQRBOBEAgACgCBCAAKAIQaiAAKQOYLj0AACAAIAAoAhBBAmo2AhAgACAAKQOYLkIQiDcDmC4gACAAKAKgLkEQayIBNgKgLgsgAUEISA0BIAAgACgCECIBQQFqNgIQIAEgACgCBGogACkDmC48AAAgACAAKQOYLkIIiDcDmC4gACgCoC5BCGsLNgKgLgsLEAAgACgCCBAGIABBADYCCAvwAQECf0F/IQECQCAALQAoDQAgACgCJEEDRgRAIABBDGoEQCAAQQA2AhAgAEEXNgIMC0F/DwsCQCAAKAIgBEAgACkDGELAAINCAFINASAAQQxqBEAgAEEANgIQIABBHTYCDAtBfw8LAkAgACgCACICRQ0AIAIQMkF/Sg0AIAAoAgAhASAAQQxqIgAEQCAAIAEoAgw2AgAgACABKAIQNgIEC0F/DwsgAEEAQgBBABAOQn9VDQAgACgCACIARQ0BIAAQGhpBfw8LQQAhASAAQQA7ATQgAEEMagRAIABCADcCDAsgACAAKAIgQQFqNgIgCyABCzsAIAAtACgEfkJ/BSAAKAIgRQRAIABBDGoiAARAIABBADYCBCAAQRI2AgALQn8PCyAAQQBCAEEHEA4LC5oIAQt/IABFBEAgARAJDwsgAUFATwRAQYSEAUEwNgIAQQAPCwJ/QRAgAUELakF4cSABQQtJGyEGIABBCGsiBSgCBCIJQXhxIQQCQCAJQQNxRQRAQQAgBkGAAkkNAhogBkEEaiAETQRAIAUhAiAEIAZrQZSIASgCAEEBdE0NAgtBAAwCCyAEIAVqIQcCQCAEIAZPBEAgBCAGayIDQRBJDQEgBSAJQQFxIAZyQQJyNgIEIAUgBmoiAiADQQNyNgIEIAcgBygCBEEBcjYCBCACIAMQOwwBCyAHQcyEASgCAEYEQEHAhAEoAgAgBGoiBCAGTQ0CIAUgCUEBcSAGckECcjYCBCAFIAZqIgMgBCAGayICQQFyNgIEQcCEASACNgIAQcyEASADNgIADAELIAdByIQBKAIARgRAQbyEASgCACAEaiIDIAZJDQICQCADIAZrIgJBEE8EQCAFIAlBAXEgBnJBAnI2AgQgBSAGaiIEIAJBAXI2AgQgAyAFaiIDIAI2AgAgAyADKAIEQX5xNgIEDAELIAUgCUEBcSADckECcjYCBCADIAVqIgIgAigCBEEBcjYCBEEAIQJBACEEC0HIhAEgBDYCAEG8hAEgAjYCAAwBCyAHKAIEIgNBAnENASADQXhxIARqIgogBkkNASAKIAZrIQwCQCADQf8BTQRAIAcoAggiBCADQQN2IgJBA3RB3IQBakYaIAQgBygCDCIDRgRAQbSEAUG0hAEoAgBBfiACd3E2AgAMAgsgBCADNgIMIAMgBDYCCAwBCyAHKAIYIQsCQCAHIAcoAgwiCEcEQCAHKAIIIgJBxIQBKAIASRogAiAINgIMIAggAjYCCAwBCwJAIAdBFGoiBCgCACICDQAgB0EQaiIEKAIAIgINAEEAIQgMAQsDQCAEIQMgAiIIQRRqIgQoAgAiAg0AIAhBEGohBCAIKAIQIgINAAsgA0EANgIACyALRQ0AAkAgByAHKAIcIgNBAnRB5IYBaiICKAIARgRAIAIgCDYCACAIDQFBuIQBQbiEASgCAEF+IAN3cTYCAAwCCyALQRBBFCALKAIQIAdGG2ogCDYCACAIRQ0BCyAIIAs2AhggBygCECICBEAgCCACNgIQIAIgCDYCGAsgBygCFCICRQ0AIAggAjYCFCACIAg2AhgLIAxBD00EQCAFIAlBAXEgCnJBAnI2AgQgBSAKaiICIAIoAgRBAXI2AgQMAQsgBSAJQQFxIAZyQQJyNgIEIAUgBmoiAyAMQQNyNgIEIAUgCmoiAiACKAIEQQFyNgIEIAMgDBA7CyAFIQILIAILIgIEQCACQQhqDwsgARAJIgVFBEBBAA8LIAUgAEF8QXggAEEEaygCACICQQNxGyACQXhxaiICIAEgASACSxsQBxogABAGIAUL6QEBA38CQCABRQ0AIAJBgDBxIgIEfwJ/IAJBgCBHBEBBAiACQYAQRg0BGiADBEAgA0EANgIEIANBEjYCAAtBAA8LQQQLIQJBAAVBAQshBkEUEAkiBEUEQCADBEAgA0EANgIEIANBDjYCAAtBAA8LIAQgAUEBahAJIgU2AgAgBUUEQCAEEAZBAA8LIAUgACABEAcgAWpBADoAACAEQQA2AhAgBEIANwMIIAQgATsBBCAGDQAgBCACECNBBUcNACAEKAIAEAYgBCgCDBAGIAQQBkEAIQQgAwRAIANBADYCBCADQRI2AgALCyAEC7UBAQJ/AkACQAJAAkACQAJAAkAgAC0ABQRAIAAtAABBAnFFDQELIAAoAjAQECAAQQA2AjAgAC0ABUUNAQsgAC0AAEEIcUUNAQsgACgCNBAcIABBADYCNCAALQAFRQ0BCyAALQAAQQRxRQ0BCyAAKAI4EBAgAEEANgI4IAAtAAVFDQELIAAtAABBgAFxRQ0BCyAAKAJUIgEEfyABQQAgARAiEBkgACgCVAVBAAsQBiAAQQA2AlQLC9wMAgl/AX4jAEFAaiIGJAACQAJAAkACQAJAIAEoAjBBABAjIgVBAkZBACABKAI4QQAQIyIEQQFGGw0AIAVBAUZBACAEQQJGGw0AIAVBAkciAw0BIARBAkcNAQsgASABLwEMQYAQcjsBDEEAIQMMAQsgASABLwEMQf/vA3E7AQxBACEFIANFBEBB9eABIAEoAjAgAEEIahBpIgVFDQILIAJBgAJxBEAgBSEDDAELIARBAkcEQCAFIQMMAQtB9cYBIAEoAjggAEEIahBpIgNFBEAgBRAcDAILIAMgBTYCAAsgASABLwEMQf7/A3EgAS8BUiIFQQBHcjsBDAJAAkACQAJAAn8CQAJAIAEpAyhC/v///w9WDQAgASkDIEL+////D1YNACACQYAEcUUNASABKQNIQv////8PVA0BCyAFQYECa0H//wNxQQNJIQdBAQwBCyAFQYECa0H//wNxIQQgAkGACnFBgApHDQEgBEEDSSEHQQALIQkgBkIcEBciBEUEQCAAQQhqIgAEQCAAQQA2AgQgAEEONgIACyADEBwMBQsgAkGACHEhBQJAAkAgAkGAAnEEQAJAIAUNACABKQMgQv////8PVg0AIAEpAyhCgICAgBBUDQMLIAQgASkDKBAYIAEpAyAhDAwBCwJAAkACQCAFDQAgASkDIEL/////D1YNACABKQMoIgxC/////w9WDQEgASkDSEKAgICAEFQNBAsgASkDKCIMQv////8PVA0BCyAEIAwQGAsgASkDICIMQv////8PWgRAIAQgDBAYCyABKQNIIgxC/////w9UDQELIAQgDBAYCyAELQAARQRAIABBCGoiAARAIABBADYCBCAAQRQ2AgALIAQQCCADEBwMBQtBASEKQQEgBC0AAAR+IAQpAxAFQgALp0H//wNxIAYQRyEFIAQQCCAFIAM2AgAgBw0BDAILIAMhBSAEQQJLDQELIAZCBxAXIgRFBEAgAEEIaiIABEAgAEEANgIEIABBDjYCAAsgBRAcDAMLIARBAhANIARBhxJBAhAsIAQgAS0AUhBwIAQgAS8BEBANIAQtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAsgBBAIDAILQYGyAkEHIAYQRyEDIAQQCCADIAU2AgBBASEL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NgIEIABB7A42AhggGiEdDAMLIARB/wFxIgJBwABxRQRAIAggDSADLwECQQJ0aiAdp0F/IAJ0QX9zcUECdGoiAy0AASICayEIIB0gAq2IIR0gAy0AACIERQ0HDAELCyAEQSBxBEAgB0G//gA2AgQgASECDAgLIAdB0f4ANgIEIABB0A42AhggASECDAcLIB1BfyAGdEF/c62DIBt8IhunIQUgCCAEQQ9xIgNrIQggGiAErUIPg4ghHSABIBdrIgYgAjMBAiAaQX8gA3RBf3Otg3ynIgRPDQIgBCAGayIGIBlNDQEgBygCjEdFDQEgB0HR/gA2AgQgAEG5DDYCGAsgASECIAshBQwFCwJAIA5FBEAgDCARIAZraiEDDAELIAYgDk0EQCAMIA4gBmtqIQMMAQsgDCARIAYgDmsiBmtqIQMgBSAGTQ0AIAUgBmshBQJAAkAgASADTSABIA8gAWusIhogBq0iGyAaIBtUGyIapyIGaiICIANLcQ0AIAMgBmogAUsgASADT3ENACABIAMgBhAHGiACIQEMAQsgASADIAMgAWsiASABQR91IgFqIAFzIgIQByACaiEBIBogAq0iHn0iHFANACACIANqIQIDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgASACKQAANwAAIAEgAikAGDcAGCABIAIpABA3ABAgASACKQAINwAIIBpCIH0hGiACQSBqIQIgAUEgaiEBIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAEgAikAADcAACABIAIpABg3ABggASACKQAQNwAQIAEgAikACDcACCABIAIpADg3ADggASACKQAwNwAwIAEgAikAKDcAKCABIAIpACA3ACAgASACKQBYNwBYIAEgAikAUDcAUCABIAIpAEg3AEggASACKQBANwBAIAEgAikAYDcAYCABIAIpAGg3AGggASACKQBwNwBwIAEgAikAeDcAeCACQYABaiECIAFBgAFqIQEgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAEgAikAADcAACABIAIpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCABIAIpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCABIAIoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCABIAIvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCABIAItAAA6AAAgAkEBaiECIAFBAWohAQsgHEIAUg0ACwsgDiEGIAwhAwsgBSAGSwRAAkACQCABIANNIAEgDyABa6wiGiAGrSIbIBogG1QbIhqnIglqIgIgA0txDQAgAyAJaiABSyABIANPcQ0AIAEgAyAJEAcaDAELIAEgAyADIAFrIgEgAUEfdSIBaiABcyIBEAcgAWohAiAaIAGtIh59IhxQDQAgASADaiEBA0ACQCAcIB4gHCAeVBsiG0IgVARAIBshGgwBCyAbIhpCIH0iIEIFiEIBfEIDgyIfUEUEQANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCAaQiB9IRogAUEgaiEBIAJBIGohAiAfQgF9Ih9CAFINAAsLICBC4ABUDQADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggAiABKQA4NwA4IAIgASkAMDcAMCACIAEpACg3ACggAiABKQAgNwAgIAIgASkAWDcAWCACIAEpAFA3AFAgAiABKQBINwBIIAIgASkAQDcAQCACIAEpAGA3AGAgAiABKQBoNwBoIAIgASkAcDcAcCACIAEpAHg3AHggAUGAAWohASACQYABaiECIBpCgAF9IhpCH1YNAAsLIBpCEFoEQCACIAEpAAA3AAAgAiABKQAINwAIIBpCEH0hGiACQRBqIQIgAUEQaiEBCyAaQghaBEAgAiABKQAANwAAIBpCCH0hGiACQQhqIQIgAUEIaiEBCyAaQgRaBEAgAiABKAAANgAAIBpCBH0hGiACQQRqIQIgAUEEaiEBCyAaQgJaBEAgAiABLwAAOwAAIBpCAn0hGiACQQJqIQIgAUECaiEBCyAcIBt9IRwgGlBFBEAgAiABLQAAOgAAIAJBAWohAiABQQFqIQELIBxCAFINAAsLIAUgBmshAUEAIARrIQUCQCAEQQdLBEAgBCEDDAELIAEgBE0EQCAEIQMMAQsgAiAEayEFA0ACQCACIAUpAAA3AAAgBEEBdCEDIAEgBGshASACIARqIQIgBEEDSw0AIAMhBCABIANLDQELC0EAIANrIQULIAIgBWohBAJAIAUgDyACa6wiGiABrSIbIBogG1QbIhqnIgFIIAVBf0pxDQAgBUEBSCABIARqIAJLcQ0AIAIgBCABEAcgAWohAgwDCyACIAQgAyADQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANAiABIARqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAILAkAgASADTSABIA8gAWusIhogBa0iGyAaIBtUGyIapyIEaiICIANLcQ0AIAMgBGogAUsgASADT3ENACABIAMgBBAHGgwCCyABIAMgAyABayIBIAFBH3UiAWogAXMiARAHIAFqIQIgGiABrSIefSIcUA0BIAEgA2ohAQNAAkAgHCAeIBwgHlQbIhtCIFQEQCAbIRoMAQsgGyIaQiB9IiBCBYhCAXxCA4MiH1BFBEADQCACIAEpAAA3AAAgAiABKQAYNwAYIAIgASkAEDcAECACIAEpAAg3AAggGkIgfSEaIAFBIGohASACQSBqIQIgH0IBfSIfQgBSDQALCyAgQuAAVA0AA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIAIgASkAODcAOCACIAEpADA3ADAgAiABKQAoNwAoIAIgASkAIDcAICACIAEpAFg3AFggAiABKQBQNwBQIAIgASkASDcASCACIAEpAEA3AEAgAiABKQBgNwBgIAIgASkAaDcAaCACIAEpAHA3AHAgAiABKQB4NwB4IAFBgAFqIQEgAkGAAWohAiAaQoABfSIaQh9WDQALCyAaQhBaBEAgAiABKQAANwAAIAIgASkACDcACCAaQhB9IRogAkEQaiECIAFBEGohAQsgGkIIWgRAIAIgASkAADcAACAaQgh9IRogAkEIaiECIAFBCGohAQsgGkIEWgRAIAIgASgAADYAACAaQgR9IRogAkEEaiECIAFBBGohAQsgGkICWgRAIAIgAS8AADsAACAaQgJ9IRogAkECaiECIAFBAmohAQsgHCAbfSEcIBpQRQRAIAIgAS0AADoAACACQQFqIQIgAUEBaiEBCyAcUEUNAAsMAQsCQAJAIBYEQAJAIAQgBUkEQCAHKAKYRyAESw0BCyABIARrIQMCQEEAIARrIgVBf0ogDyABa6wiGiAbIBogG1QbIhqnIgIgBUpxDQAgBUEBSCACIANqIAFLcQ0AIAEgAyACEAcgAmohAgwFCyABIAMgBCAEQR91IgFqIAFzIgEQByABaiECIBogAa0iHn0iHFANBCABIANqIQEDQAJAIBwgHiAcIB5UGyIbQiBUBEAgGyEaDAELIBsiGkIgfSIgQgWIQgF8QgODIh9QRQRAA0AgAiABKQAANwAAIAIgASkAGDcAGCACIAEpABA3ABAgAiABKQAINwAIIBpCIH0hGiABQSBqIQEgAkEgaiECIB9CAX0iH0IAUg0ACwsgIELgAFQNAANAIAIgASkAADcAACACIAEpABg3ABggAiABKQAQNwAQIAIgASkACDcACCACIAEpADg3ADggAiABKQAwNwAwIAIgASkAKDcAKCACIAEpACA3ACAgAiABKQBYNwBYIAIgASkAUDcAUCACIAEpAEg3AEggAiABKQBANwBAIAIgASkAYDcAYCACIAEpAGg3AGggAiABKQBwNwBwIAIgASkAeDcAeCABQYABaiEBIAJBgAFqIQIgGkKAAX0iGkIfVg0ACwsgGkIQWgRAIAIgASkAADcAACACIAEpAAg3AAggGkIQfSEaIAJBEGohAiABQRBqIQELIBpCCFoEQCACIAEpAAA3AAAgGkIIfSEaIAJBCGohAiABQQhqIQELIBpCBFoEQCACIAEoAAA2AAAgGkIEfSEaIAJBBGohAiABQQRqIQELIBpCAloEQCACIAEvAAA7AAAgGkICfSEaIAJBAmohAiABQQJqIQELIBwgG30hHCAaUEUEQCACIAEtAAA6AAAgAkEBaiECIAFBAWohAQsgHFBFDQALDAQLIBAgAWsiCUEBaiIGIAUgBSAGSxshAyABIARrIQIgAUEHcUUNAiADRQ0CIAEgAi0AADoAACACQQFqIQIgAUEBaiIGQQdxQQAgA0EBayIFGw0BIAYhASAFIQMgCSEGDAILAkAgBCAFSQRAIAcoAphHIARLDQELIAEgASAEayIGKQAANwAAIAEgBUEBa0EHcUEBaiIDaiECIAUgA2siBEUNAyADIAZqIQEDQCACIAEpAAA3AAAgAUEIaiEBIAJBCGohAiAEQQhrIgQNAAsMAwsgASAEIAUQPyECDAILIAEgAi0AADoAASAJQQFrIQYgA0ECayEFIAJBAWohAgJAIAFBAmoiCkEHcUUNACAFRQ0AIAEgAi0AADoAAiAJQQJrIQYgA0EDayEFIAJBAWohAgJAIAFBA2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAAyAJQQNrIQYgA0EEayEFIAJBAWohAgJAIAFBBGoiCkEHcUUNACAFRQ0AIAEgAi0AADoABCAJQQRrIQYgA0EFayEFIAJBAWohAgJAIAFBBWoiCkEHcUUNACAFRQ0AIAEgAi0AADoABSAJQQVrIQYgA0EGayEFIAJBAWohAgJAIAFBBmoiCkEHcUUNACAFRQ0AIAEgAi0AADoABiAJQQZrIQYgA0EHayEFIAJBAWohAgJAIAFBB2oiCkEHcUUNACAFRQ0AIAEgAi0AADoAByAJQQdrIQYgA0EIayEDIAFBCGohASACQQFqIQIMBgsgCiEBIAUhAwwFCyAKIQEgBSEDDAQLIAohASAFIQMMAwsgCiEBIAUhAwwCCyAKIQEgBSEDDAELIAohASAFIQMLAkACQCAGQRdNBEAgA0UNASADQQFrIQUgA0EHcSIEBEADQCABIAItAAA6AAAgA0EBayEDIAFBAWohASACQQFqIQIgBEEBayIEDQALCyAFQQdJDQEDQCABIAItAAA6AAAgASACLQABOgABIAEgAi0AAjoAAiABIAItAAM6AAMgASACLQAEOgAEIAEgAi0ABToABSABIAItAAY6AAYgASACLQAHOgAHIAFBCGohASACQQhqIQIgA0EIayIDDQALDAELIAMNAQsgASECDAELIAEgBCADED8hAgsgCyEFDAELIAEgAy0AAjoAACABQQFqIQILIAUgFE8NACACIBNJDQELCyAAIAI2AgwgACAFIAhBA3ZrIgE2AgAgACATIAJrQYMCajYCECAAIBQgAWtBDmo2AgQgByAIQQdxIgA2AogBIAcgHUJ/IACthkJ/hYM+AoQBC+cFAQR/IAMgAiACIANLGyEEIAAgAWshAgJAIABBB3FFDQAgBEUNACAAIAItAAA6AAAgA0EBayEGIAJBAWohAiAAQQFqIgdBB3FBACAEQQFrIgUbRQRAIAchACAFIQQgBiEDDAELIAAgAi0AADoAASADQQJrIQYgBEECayEFIAJBAWohAgJAIABBAmoiB0EHcUUNACAFRQ0AIAAgAi0AADoAAiADQQNrIQYgBEEDayEFIAJBAWohAgJAIABBA2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAAyADQQRrIQYgBEEEayEFIAJBAWohAgJAIABBBGoiB0EHcUUNACAFRQ0AIAAgAi0AADoABCADQQVrIQYgBEEFayEFIAJBAWohAgJAIABBBWoiB0EHcUUNACAFRQ0AIAAgAi0AADoABSADQQZrIQYgBEEGayEFIAJBAWohAgJAIABBBmoiB0EHcUUNACAFRQ0AIAAgAi0AADoABiADQQdrIQYgBEEHayEFIAJBAWohAgJAIABBB2oiB0EHcUUNACAFRQ0AIAAgAi0AADoAByADQQhrIQMgBEEIayEEIABBCGohACACQQFqIQIMBgsgByEAIAUhBCAGIQMMBQsgByEAIAUhBCAGIQMMBAsgByEAIAUhBCAGIQMMAwsgByEAIAUhBCAGIQMMAgsgByEAIAUhBCAGIQMMAQsgByEAIAUhBCAGIQMLAkAgA0EXTQRAIARFDQEgBEEBayEBIARBB3EiAwRAA0AgACACLQAAOgAAIARBAWshBCAAQQFqIQAgAkEBaiECIANBAWsi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CyAAIAMtAAAEfiADKQMQBUIAC6dB//8DcSADKAIEEEcLIQUgAxAICyAEQRBqJAAgBQvgAQICfwF+QTAQCSICRQRAIAEEQCABQQA2AgQgAUEONgIAC0EADwsgAkIANwMIIAJBADYCACACQgA3AxAgAkIANwMYIAJCADcDICACQgA3ACUgAFAEQCACDwsCQCAAQv////8AVg0AIACnQQR0EAkiA0UNACACIAM2AgBBACEBQgEhBANAIAMgAUEEdGoiAUIANwIAIAFCADcABSAAIARSBEAgBKchASAEQgF8IQQMAQsLIAIgADcDCCACIAA3AxAgAg8LIAEEQCABQQA2AgQgAUEONgIAC0EAEBAgAhAGQQAL7gECA38BfiMAQRBrIgQkAAJAIARBDGpCBBAXIgNFBEBBfyECDAELAkAgAQRAIAJBgAZxIQUDQAJAIAUgASgCBHFFDQACQCADKQMIQgBUBEAgA0EAOgAADAELIANCADcDECADQQE6AAALIAMgAS8BCBANIAMgAS8BChANIAMtAABFBEAgAEEIaiIABEAgAEEANgIEIABBFDYCAAtBfyECDAQLQX8hAiAAIARBDGpCBBAbQQBIDQMgATMBCiIGUA0AIAAgASgCDCAGEBtBAEgNAwsgASgCACIBDQALC0EAIQILIAMQCAsgBEEQaiQAIAILPAEBfyAABEAgAUGABnEhAQNAIAEgACgCBHEEQCACIAAvAQpqQQRqIQILIAAoAgAiAA0ACwsgAkH//wNxC5wBAQN/IABFBEBBAA8LIAAhAwNAAn8CQAJAIAAvAQgiAUH04AFNBEAgAUEBRg0BIAFB9cYBRg0BDAILIAFBgbICRg0AIAFB9eABRw0BCyAAKAIAIQEgAEEANgIAIAAoAgwQBiAAEAYgASADIAAgA0YbIQMCQCACRQRAQQAhAgwBCyACIAE2AgALIAEMAQsgACICKAIACyIADQALIAMLsgQCBX8BfgJAAkACQCAAIAGtEBciAQRAIAEtAAANAUEAIQAMAgsgBARAIARBADYCBCAEQQ42AgALQQAPC0EAIQADQCABLQAABH4gASkDCCABKQMQfQVCAAtCBFQNASABEAwhByABIAEQDCIGrRATIghFBEBBACECIAQEQCAEQQA2AgQgBEEVNgIACyABEAggAEUNAwNAIAAoAgAhASAAKAIMEAYg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DCAAQRg2AggLDAMLIAAgBiAFLwFqQQAgBCABEQYAIQEgBhALIAFFDQILAkAgDUUEQCABIQMMAQsgACABIAUvAWgQgQEhAyABEAsgA0UNAQsCQCAJRSAMckUEQCADIQEMAQsgACADQQEQgAEhASADEAsgAUUNAQsgASEDDAELQQAhAwsgBUHwAGokACADC4UBAQF/IAFFBEAgAEEIaiIABEAgAEEANgIEIABBEjYCAAtBAA8LQTgQCSIDRQRAIABBCGoiAARAIABBADYCBCAAQQ42AgALQQAPCyADQQA2AhAgA0IANwIIIANCADcDKCADQQA2AgQgAyACNgIAIANCADcDGCADQQA2AjAgACABQTsgAxBCCw8AIAAgASACQQBBABCCAQusAgECfyABRQRAIABBCGoiAARAIABBADYCBCAAQRI2AgALQQAPCwJAIAJBfUsNACACQf//A3FBCEYNACAAQQhqIgAEQCAAQQA2AgQgAEEQNgIAC0EADwsCQEGwwAAQCSIFBEAgBUEANgIIIAVCADcCACAFQYiBAUGogQEgAxs2AqhAIAUgAjYCFCAFIAM6ABAgBUEAOgAPIAVBADsBDCAFIAMgAkF9SyIGcToADiAFQQggAiAGG0H//wNxIAQgBUGIgQFBqIEBIAMbKAIAEQAAIgI2AqxAIAINASAFEDEgBRAGCyAAQQhqIgAEQCAAQQA2AgQgAEEONgIAC0EADwsgACABQTogBRBCIgAEfyAABSAFKAKsQCAFKAKoQCgCBBEDACAFEDEgBRAGQQALC6ABAQF/IAIgACgCBCIDIAIgA0kbIgIEQCAAIAMgAms2AgQCQAJAAkACQCAAKAIcIgMoAhRBAWsOAgEAAgsgA0GgAWogASAAKAIAIAJB3IABKAIAEQgADAILIAAgACgCMCABIAAoAgAgAkHEgAEoAgARBAA2AjAMAQsgASAAKAIAIAIQBxoLIAAgACgCACACajYCACAAIAAoAgggAmo2AggLC7cCAQR/QX4hAgJAIABFDQAgACgCIEUNACAAKAIkIgRFDQAgACgCHCIBRQ0AIAEoAgAgAEcNAAJAAkAgASgCICIDQTlrDjkBAgICAgICAgICAgIBAgICAQICAgICAgICAgICAgICAgICAQICAgICAgICAgICAQICAgICAgICAgEACyADQZoFRg0AIANBKkcNAQsCfwJ/An8gASgCBCICBEAgBCAAKAIoIAIQHiAAKAIcIQELIAEoAlAiAgsEQCAAKAIkIAAoAiggAhAeIAAoAhwhAQsgASgCTCICCwRAIAAoAiQgACgCKCACEB4gACgCHCEBCyABKAJIIgILBEAgACgCJCAAKAIoIAIQHiAAKAIcIQELIAAoAiQgACgCKCABEB4gAEEANgIcQX1BACADQfEARhshAgsgAgvrCQEIfyAAKAIwIgMgACgCDEEFayICIAIgA0sbIQggACgCACIEKAIEIQkgAUEERiEHAkADQCAEKAIQIgMgACgCoC5BKmpBA3UiAkkEQEEBIQYMAgsgCCADIAJrIgMgACgCaCAAKAJYayICIAQoAgRqIgVB//8DIAVB//8DSRsiBiADIAZJGyIDSwRAQQEhBiADQQBHIAdyRQ0CIAFFDQIgAyAFRw0CCyAAQQBBACAHIAMgBUZxIgUQOSAAIAAoAhBBBGsiBDYCECAAKAIEIARqIAM7AAAgACAAKAIQQQJqIgQ2AhAgACgCBCAEaiADQX9zOwAAIAAgACgCEEECajYCECAAKAIAEAoCfyACBEAgACgCACgCDCAAKAJIIAAoAlhqIAMgAiACIANLGyICEAcaIAAoAgAiBCAEKAIMIAJqNgIMIAQgBCgCECACazYCECAEIAQoAhQgAmo2AhQgACAAKAJYIAJqNgJYIAMgAmshAwsgAwsEQCAAKAIAIgIgAigCDCADEIMBIAAoAgAiAiACKAIMIANqNgIMIAIgAigCECADazYCECACIAIoAhQgA2o2AhQLIAAoAgAhBCAFRQ0AC0EAIQYLAkAgCSAEKAIEayICRQRAIAAoAmghAwwBCwJAIAAoAjAiAyACTQRAIABBAjYCgC4gACgCSCAEKAIAIANrIAMQBxogACAAKAIwIgM2AoQuIAAgAzYCaAwBCyACIAAoAkQgACgCaCIFa08EQCAAIAUgA2siBDYCaCAAKAJIIgUgAyAFaiAEEAcaIAAoAoAuIgNBAU0EQCAAIANBAWo2AoAuCyAAIAAoAmgiBSAAKAKELiIDIAMgBUsbNgKELiAAKAIAIQQLIAAoAkggBWogBCgCACACayACEAcaIAAgACgCaCACaiIDNgJoIAAgACgCMCAAKAKELiIEayIFIAIgAiAFSxsgBGo2AoQuCyAAIAM2AlgLIAAgAyAAKAJAIgIgAiADSRs2AkBBAyECAkAgBkUNACAAKAIAIgUoAgQhAgJAAkAgAUF7cUUNACACDQBBASECIAMgACgCWEYNAiAAKAJEIANrIQRBACECDAELIAIgACgCRCADayIETQ0AIAAoAlgiByAAKAIwIgZIDQAgACADIAZrIgM2AmggACAHIAZrNgJYIAAoAkgiAiACIAZqIAMQBxogACgCgC4iA0EBTQRAIAAgA0EBajYCgC4LIAAgACgCaCIDIAAoAoQuIgIgAiADSxs2AoQuIAAoAjAgBGohBCAAKAIAIgUoAgQhAgsCQCACIAQgAiAESRsiAkUEQCAAKAIwIQUMAQsgBSAAKAJIIANqIAIQgwEgACAAKAJoIAJqIgM2AmggACAAKAIwIgUgACgChC4iBGsiBiACIAIgBksbIARqNgKELgsgACADIAAoAkAiAiACIANJGzYCQCADIAAoAlgiBmsiAyAFIAAoAgwgACgCoC5BKmpBA3VrIgJB//8DIAJB//8DSRsiBCAEIAVLG0kEQEEAIQIgAUEERiADQQBHckUNASABRQ0BIAAoAgAoAgQNASADIARLDQELQQAhAiABQQRGBEAgACgCACgCBEUgAyAETXEhAgsgACAAKAJIIAZqIAQgAyADIARLGyIBIAIQOSAAIAAoAlggAWo2AlggACgCABAKQQJBACACGw8LIAIL/woCCn8DfiAAKQOYLiENIAAoAqAuIQQgAkEATgRAQQRBAyABLwECIggbIQlBB0GKASAIGyEFQX8hCgNAIAghByABIAsiDEEBaiILQQJ0ai8BAiEIAkACQCAGQQFqIgMgBU4NACAHIAhHDQAgAyEGDAELAkAgAyAJSARAIAAgB0ECdGoiBkHOFWohCSAGQcwVaiEKA0AgCjMBACEPAn8gBCAJLwEAIgZqIgVBP00EQCAPIASthiANhCENIAUMAQsgBEHAAEYEQCAAKAIEIAAoAhBqIA03AAAgACAAKAIQQQhqNgIQIA8hDSAGDAELIAAoAgQgACgCEGogDyAErYYgDYQ3AAAgACAAKAIQQQhqNgIQIA9BwAAgBGutiCENIAVBQGoLIQQgA0EBayIDDQALDAELIAcEQAJAIAcgCkYEQCANIQ8gBCEFIAMhBgwBCyAAIAdBAnRqIgNBzBVqMwEAIQ8gBCADQc4Vai8BACIDaiIFQT9NBEAgDyAErYYgDYQhDwwBCyAEQcAARgRAIAAoAgQgACgCEGogDTcAACAAIAAoAhBBCGo2AhAgAyEFDAELIAAoAgQgACgCEGogDyAErYYgDYQ3AAAgACAAKAIQQQhqNgIQIAVBQGohBSAPQcAAIARrrYghDwsgADMBjBYhDgJAIAUgAC8BjhYiBGoiA0E/TQRAIA4gBa2GIA+EIQ4MAQsgBUHAAEYEQCAAKAIEIAAoAhBqIA83AAAgACAAKAIQQQhqNgIQIAQhAwwBCyAAKAIEIAAoAhBqIA4gBa2GIA+ENwAAIAAgACgCEEEIajYCECADQUBqIQMgDkHAACAFa62IIQ4LIAasQgN9IQ0gA0E9TQRAIANBAmohBCANIAOthiAOhCENDAILIANBwABGBEAgACgCBCAAKAIQaiAONwAAIAAgACgCEEEIajYCEEECIQQMAgsgACgCBCAAKAIQaiANIAOthiAOhDcAACAAIAAoAhBBCGo2AhAgA0E+ayEEIA1BwAAgA2utiCENDAELIAZBCUwEQCAAMwGQFiEOAkAgBCAALwGSFiIFaiIDQT9NBEAgDiAErYYgDYQhDgwBCyAEQcAARgRAIAAoAgQgACgCEGogDTcAACAAIAAoAhBBCGo2AhAgBSEDDAELIAAoAgQgACgCEGogDiAErYYgDYQ3AAAgACAAKAIQQQhqNgIQIANBQGohAyAOQcAAIARrrYghDgsgBqxCAn0hDSADQTxNBEAgA0EDaiEEIA0gA62GIA6EIQ0MAgsgA0HAAEYEQCAAKAIEIAAoAhBqIA43AAAgACAAKAIQQQhqNgIQQQMhBAwCCyAAKAIEIAAoAhBqIA0gA62GIA6ENwAAIAAgACgCEEEIajYCECADQT1rIQQgDUHAACADa62IIQ0MAQsgADMBlBYhDgJAIAQgAC8BlhYiBWoiA0E/TQRAIA4gBK2GIA2EIQ4MAQsgBEHAAEYEQCAAKAIEIAAoAhBqIA03AAAgACAAKAIQQQhqNgIQIAUhAwwBCyAAKAIEIAAoAhBqIA4gBK2GIA2ENwAAIAAgACgCEEEIajYCECADQUBqIQMgDkHAACAEa62IIQ4LIAatQgp9IQ0gA0E4TQRAIANBB2ohBCANIAOthiAOhCENDAELIANBwABGBEAgACgCBCAAKAIQaiAONwAAIAAgACgCEEEIajYCEEEHIQQMAQsgACgCBCAAKAIQaiANIAOthiAOhDcAACAAIAAoAhBBCGo2AhAgA0E5ayEEIA1BwAAgA2utiCENC0EAIQYCfyAIRQRAQYoBIQVBAwwBC0EGQQcgByAIRiIDGyEFQQNBBCADGwshCSAHIQoLIAIgDEcNAAsLIAAgBDYCoC4gACANNwOYLgv5BQIIfwJ+AkAgACgC8C1FBEAgACkDmC4hCyAAKAKgLiEDDAELA0AgCSIDQQNqIQkgAyAAKALsLWoiAy0AAiEFIAApA5guIQwgACgCoC4hBAJAIAMvAAAiB0UEQCABIAVBAnRqIgMzAQAhCyAEIAMvAQIiBWoiA0E/TQRAIAsgBK2GIAyEIQsMAgsgBEHAAEYEQCAAKAIEIAAoAhBqIAw3AAAgACAAKAIQQQhqNgIQIAUhAwwCCyAAKAIEIAAoAhBqIAsgBK2GIAyENwAAIAAgACgCEEEIajYCECADQUBqIQMgC0HAACAEa62IIQsMAQsgBUGAzwBqLQAAIghBAnQiBiABaiIDQYQIajMBACELIANBhghqLwEAIQMgCEEIa0ETTQRAIAUgBkGA0QBqKAIAa60gA62GIAuEIQsgBkHA0wBqKAIAIANqIQMLIAMgAiAHQQFrIgcgB0EHdkGAAmogB0GAAkkbQYDLAGotAAAiBUECdCIIaiIKLwECaiEGIAozAQAgA62GIAuEIQsgBCAFQQRJBH8gBgUgByAIQYDSAGooAgBrrSAGrYYgC4QhCyAIQcDUAGooAgAgBmoLIgVqIgNBP00EQCALIASthiAMhCELDAELIARBwABGBEAgACgCBCAAKAIQaiAMNwAAIAAgACgCEEEIajYCECAFIQMMAQsgACgCBCAAKAIQaiALIASthiAMhDcAACAAIAAoAhBBCGo2AhAgA0FAaiEDIAtBwAAgBGutiCELCyAAIAs3A5guIAAgAzYCoC4gCSAAKALwLUkNAAsLIAFBgAhqMwEAIQwCQCADIAFBgghqLwEAIgJqIgFBP00EQCAMIAOthiALhCEMDAELIANBwABGBEAgACgCBCAAKAIQaiALNwAAIAAgACgCEEEIajYCECACIQEMAQsgACgCBCAAKAIQaiAMIAOthiALhDcAACAAIAAoAhBBCGo2AhAgAUFAaiEBIAxBwAAgA2utiCEMCyAAIAw3A5guIAAgATYCoC4L8AQBA38gAEHkAWohAgNAIAIgAUECdCIDakEAOwEAIAIgA0EEcmpBADsBACABQQJqIgFBngJHDQALIABBADsBzBUgAEEAOwHYEyAAQZQWakEAOwEAIABBkBZqQQA7AQAgAEGMFmpBADsBACAAQYgWakEAOwEAIABBhBZqQQA7AQAgAEGAFmpBADsBACAAQfwVakEAOwEAIABB+BVqQQA7AQAgAEH0FWpBADsBACAAQfAVakEAOwEAIABB7BVqQQA7AQAgAEHoFWpBADsBACAAQeQVakEAOwEAIABB4BVqQQA7AQAgAEHcFWpBADsBACAAQdgVakEAOwEAIABB1BVqQQA7AQAgAEHQFWpBADsBACAAQcwUakEAOwEAIABByBRqQQA7AQAgAEHEFGpBADsBACAAQcAUakEAOwEAIABBvBRqQQA7AQAgAEG4FGpBADsBACAAQbQUakEAOwEAIABBsBRqQQA7AQAgAEGsFGpBADsBACAAQagUakEAOwEAIABBpBRqQQA7AQAgAEGgFGpBADsBACAAQZwUakEAOwEAIABBmBRqQQA7AQAgAEGUFGpBADsBACAAQZAUakEAOwEAIABBjBRqQQA7AQAgAEGIFGpBADsBACAAQYQUakEAOwEAIABBgBRqQQA7AQAgAEH8E2pBADsBACAAQfgTakEAOwEAIABB9BNqQQA7AQAgAEHwE2pBADsBACAAQewTakEAOwEAIABB6BNqQQA7AQAgAEHkE2pBADsBACAAQeATakEAOwEAIABB3BNqQQA7AQAgAEIANwL8LSAAQeQJakEBOwEAIABBADYC+C0gAEEANgLwLQuKAwIGfwR+QcgAEAkiBEUEQEEADwsgBEIANwMAIARCADcDMCAEQQA2AiggBEIANwMgIARCADcDGCAEQgA3AxAgBEIANwMIIARCADcDOCABUARAIARBCBAJIgA2AgQgAEUEQCAEEAYgAwRAIANBADYCBCADQQ42AgALQQAPCyAAQgA3AwAgBA8LAkAgAaciBUEEdBAJIgZFDQAgBCAGNgIAIAVBA3RBCGoQCSIFRQ0AIAQgATcDECAEIAU2AgQDQCAAIAynIghBBHRqIgcpAwgiDVBFBEAgBygCACIHRQRAIAMEQCADQQA2AgQgA0ESNgIACyAGEAYgBRAGIAQQBkEADwsgBiAKp0EEdGoiCSANNwMIIAkgBzYCACAFIAhBA3RqIAs3AwAgCyANfCELIApCAXwhCgsgDEIBfCIMIAFSDQALIAQgCjcDCCAEQgAgCiACGzcDGCAFIAqnQQN0aiALNwMAIAQgCzcDMCAEDwsgAwRAIANBADYCBCADQQ42AgALIAYQBiAEEAZBAAvlAQIDfwF+QX8hBQJAIAAgASACQQAQJiIERQ0AIAAgASACEIsBIgZFDQACfgJAIAJBCHENACAAKAJAIAGnQQR0aigCCCICRQ0AIAIgAxAhQQBOBEAgAykDAAwCCyAAQQhqIgAEQCAAQQA2AgQgAEEPNgIAC0F/DwsgAxAqIAMgBCgCGDYCLCADIAQpAyg3AxggAyAEKAIUNgIoIAMgBCkDIDcDICADIAQoAhA7ATAgAyAELwFSOwEyQvwBQtwBIAQtAAYbCyEHIAMgBjYCCCADIAE3AxAgAyAHQgOENwMAQQAhBQsgBQspAQF/IAAgASACIABBCGoiABAmIgNFBEBBAA8LIAMoAjBBACACIAAQJQuAAwEGfwJ/An9BMCABQYB/Sw0BGgJ/IAFBgH9PBEBBhIQBQTA2AgBBAAwBC0EAQRAgAUELakF4cSABQQtJGyIFQcwAahAJIgFFDQAaIAFBCGshAgJAIAFBP3FFBEAgAiEBDAELIAFBBGsiBigCACIHQXhxIAFBP2pBQHFBCGsiASABQUBrIAEgAmtBD0sbIgEgAmsiA2shBCAHQQNxRQRAIAIoAgAhAiABIAQ2AgQgASACIANqNgIADAELIAEgBCABKAIEQQFxckECcjYCBCABIARqIgQgBCgCBEEBcjYCBCAGIAMgBigCAEEBcXJBAnI2AgAgAiADaiIEIAQoAgRBAXI2AgQgAiADEDsLAkAgASgCBCICQQNxRQ0AIAJBeHEiAyAFQRBqTQ0AIAEgBSACQQFxckECcjYCBCABIAVqIgIgAyAFayIFQQNyNgIEIAEgA2oiAyADKAIEQQFyNgIEIAIgBRA7CyABQQhqCyIBRQsEQEEwDwsgACABNgIAQQALCwoAIABBiIQBEAQL6AIBBX8gACgCUCEBIAAvATAhBEEEIQUDQCABQQAgAS8BACICIARrIgMgAiADSRs7AQAgAUEAIAEvAQIiAiAEayIDIAIgA0kbOwECIAFBACABLwEEIgIgBGsiAyACIANJGzsBBCABQQAgAS8BBiICIARrIgMgAiADSRs7AQYgBUGAgARGRQRAIAFBCGohASAFQQRqIQUMAQsLAkAgBEUNACAEQQNxIQUgACgCTCEBIARBAWtBA08EQCAEIAVrIQADQCABQQAgAS8BACICIARrIgMgAiADSRs7AQAgAUEAIAEvAQIiAiAEayIDIAIgA0kbOwECIAFBACABLwEEIgIgBGsiAyACIANJGzsBBCABQQAgAS8BBiICIARrIgMgAiADSRs7AQYgAUEIaiEBIABBBGsiAA0ACwsgBUUNAANAIAFBACABLwEAIgAgBGsiAiAAIAJJGzsBACABQQJqIQEgBUEBayIFDQALCwuDAQEEfyACQQFOBEAgAiAAKAJIIAFqIgJqIQMgACgCUCEEA0AgBCACKAAAQbHz3fF5bEEPdkH+/wdxaiIFLwEAIgYgAUH//wNxRwRAIAAoAkwgASAAKAI4cUH//wNxQQF0aiAGOwEAIAUgATsBAAsgAUEBaiEBIAJBAWoiAiADSQ0ACwsLUAECfyABIAAoAlAgACgCSCABaigAAEGx893xeWxBD3ZB/v8HcWoiAy8BACICRwRAIAAoAkwgACgCOCABcUEBdGogAjsBACADIAE7AQALIAILugEBAX8jAEEQayICJAAgAkEAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgARBYIAJBEGokAAu9AQEBfyMAQRBrIgEkACABQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgAEEANgJAIAFBEGokAEEAC70BAQF/IwBBEGsiASQAIAFBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAKAJAIQAgAUEQaiQAIAALvgEBAX8jAEEQayIEJAAgBEEAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgASACIAMQVyAEQRBqJAALygEAIwBBEGsiAyQAIANBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAIAAoAkAgASACQdSAASgCABEAADYCQCADQRBqJAALwAEBAX8jAEEQayIDJAAgA0EAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgASACEF0hACADQRBqJAAgAAu+AQEBfyMAQRBrIgIkACACQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgACABEFwhACACQRBqJAAgAAu2AQEBfyMAQRBrIgAkACAAQQA6AAhBgIEBQQI2AgBB/IABQQM2AgBB+IABQQQ2AgBB9IABQQU2AgBB8IABQQY2AgBB7IABQQc2AgBB6IABQQg2AgBB5IABQQk2AgBB4IABQQo2AgBB3IABQQs2AgBB2IABQQw2AgBB1IABQQ02AgBB0IABQQ42AgBBzIABQQ82AgBByIABQRA2AgBBxIABQRE2AgBBwIABQRI2AgAgAEEQaiQAQQgLwgEBAX8jAEEQayIEJAAgBEEAOgAIQYCBAUECNgIAQfyAAUEDNgIAQfiAAUEENgIAQfSAAUEFNgIAQfCAAUEGNgIAQeyAAUEHNgIAQeiAAUEINgIAQeSAAUEJNgIAQeCAAUEKNgIAQdyAAUELNgIAQdiAAUEMNgIAQdSAAUENNgIAQdCAAUEONgIAQcyAAUEPNgIAQciAAUEQNgIAQcSAAUERNgIAQcCAAUESNgIAIAAgASACIAMQWSEAIARBEGokACAAC8IBAQF/IwBBEGsiBCQAIARBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAIAEgAiADEFYhACAEQRBqJAAgAAsHACAALwEwC8ABAQF/IwBBEGsiAyQAIANBADoACEGAgQFBAjYCAEH8gAFBAzYCAEH4gAFBBDYCAEH0gAFBBTYCAEHwgAFBBjYCAEHsgAFBBzYCAEHogAFBCDYCAEHkgAFBCTYCAEHggAFBCjYCAEHcgAFBCzYCAEHYgAFBDDYCAEHUgAFBDTYCAEHQgAFBDjYCAEHMgAFBDzYCAEHIgAFBEDYCAEHEgAFBETYCAEHAgAFBEjYCACAAIAEgAhBVIQAgA0EQaiQAIAALBwAgACgCQAsaACAAIAAoAkAgASACQdSAASgCABEAADYCQAsLACAAQQA2AkBBAAsHACAAKAIgCwQAQQgLzgUCA34BfyMAQYBAaiIIJAACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAEDhECAwwFAAEECAkJCQkJCQcJBgkLIANCCFoEfiACIAEoAmQ2AgAgAiABKAJoNgIEQggFQn8LIQYMCwsgARAGDAoLIAEoAhAiAgRAIAIgASkDGCABQeQAaiICEEEiA1ANCCABKQMIIgVCf4UgA1QEQCACBEAgAkEANgIEIAJBFTYCAAsMCQsgAUEANgIQIAEgAyAFfDcDCCABIAEpAwAgA3w3AwALIAEtAHgEQCABKQMAIQUMCQtCACEDIAEpAwAiBVAEQCABQgA3AyAMCgsDQCAAIAggBSADfSIFQoDAACAFQoDAAFQbEBEiB0J/VwRAIAFB5ABqIgEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwJCyAHUEUEQCABKQMAIgUgAyAHfCIDWA0KDAELCyABQeQAagRAIAFBADYCaCABQRE2AmQLDAcLIAEpAwggASkDICIFfSIHIAMgAyAHVhsiA1ANCAJAIAEtAHhFDQAgACAFQQAQFEF/Sg0AIAFB5ABqIgEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwHCyAAIAIgAxARIgZCf1cEQCABQeQAagRAIAFBADYCaCABQRE2AmQLDAcLIAEgASkDICAGfCIDNwMgIAZCAFINCEIAIQYgAyABKQMIWg0IIAFB5ABqBEAgAUEANgJoIAFBETYCZAsMBgsgASkDICABKQMAIgV9IAEpAwggBX0gAiADIAFB5ABqEEQiA0IAUw0FIAEgASkDACADfDcDIAwHCyACIAFBKGoQYEEfdawhBgwGCyABMABgIQYMBQsgASkDcCEGDAQLIAEpAyAgASkDAH0hBgwDCyABQeQAagRAIAFBADYCaCABQRw2AmQLC0J/IQYMAQsgASAFNwMgCyAIQYBAayQAIAYLBwAgACgCAAsPACAAIAAoAjBBAWo2AjALGABB+IMBQgA3AgBBgIQBQQA2AgBB+IMBCwcAIABBDGoLBwAgACgCLAsHACAAKAIoCwcAIAAoAhgLFQAgACABrSACrUIghoQgAyAEEIoBCxMBAX4gABAzIgFCIIinEAAgAacLbwEBfiABrSACrUIghoQhBSMAQRBrIgEkAAJ/IABFBEAgBVBFBEAgBARAIARBADYCBCAEQRI2AgALQQAMAgtBAEIAIAMgBBA6DAELIAEgBTcDCCABIAA2AgAgAUIBIAMgBBA6CyEAIAFBEGokACAACxQAIAAgASACrSADrUIghoQgBBBSC9oCAgJ/AX4CfyABrSACrUIghoQiByAAKQMwVEEAIARBCkkbRQRAIABBCGoEQCAAQQA2AgwgAEESNgIIC0F/DAELIAAtABhBAnEEQCAAQQhqBEAgAEEANgIMIABBGTYCCAtBfwwBCyADBH8gA0H//wNxQQhGIANBfUtyBUEBC0UEQCAAQQhqBEAgAEEANgIMIABBEDYCCAtBfwwBCyAAKAJAIgEgB6ciBUEEdGooAgAiAgR/IAIoAhAgA0YFIANBf0YLIQYgASAFQQR0aiIBIQUgASgCBCEBAkAgBgRAIAFFDQEgAUEAOwFQIAEgASgCAEF+cSIANgIAIAANASABECAgBUEANgIEQQAMAgsCQCABDQAgBSACECsiATYCBCABDQAgAEEIagRAIABBADYCDCAAQQ42AggLQX8MAgsgASAEOwFQIAEgAzYCECABIAEoAgBBAXI2AgALQQALCxwBAX4gACABIAIgAEEIahBMIgNCIIinEAAgA6cLHwEBfiAAIAEgAq0gA61CIIaEEBEiBEIgiKcQACAEpwteAQF+An5CfyAARQ0AGiAAKQMwIgIgAUEIcUUNABpCACACUA0AGiAAKAJAIQADQCACIAKnQQR0IABqQRBrKAIADQEaIAJCAX0iAkIAUg0AC0IACyICQiCIpxAAIAKnCxMAIAAgAa0gAq1CIIaEIAMQiwELnwEBAn4CfiACrSADrUIghoQhBUJ/IQQCQCAARQ0AIAAoAgQNACAAQQRqIQIgBUJ/VwRAIAIEQCACQQA2AgQgAkESNgIAC0J/DAILQgAhBCAALQAQDQAgBVANACAAKAIUIAEgBRARIgRCf1UNACAAKAIUIQAgAgRAIAIgACgCDDYCACACIAAoAhA2AgQLQn8hBAsgBAsiBEIgiKcQACAEpwueAQEBfwJ/IAAgACABrSACrUIghoQgAyAAKAIcEH8iAQRAIAEQMkF/TARAIABBCGoEQCAAIAEoAgw2AgggACABKAIQNgIMCyABEAtBAAwCC0EYEAkiBEUEQCAAQQhqBEAgAEEANgIMIABBDjYCCAsgARALQQAMAgsgBCAANgIAIARBADYCDCAEQgA3AgQgBCABNgIUIARBADoAEAsgBAsLsQICAX8BfgJ/QX8hBAJAIAAgAa0gAq1CIIaEIgZBAEEAECZFDQAgAC0AGEECcQRAIABBCGoEQCAAQQA2AgwgAEEZNgIIC0F/DAILIAAoAkAiASAGpyICQQR0aiIEKAIIIgUEQEEAIQQgBSADEHFBf0oNASAAQQhqBEAgAEEANgIMIABBDzYCCAtBfwwCCwJAIAQoAgAiBQRAIAUoAhQgA0YNAQsCQCABIAJBBHRqIgEoAgQiBA0AIAEgBRArIgQ2AgQgBA0AIABBCGoEQCAAQQA2AgwgAEEONgIIC0F/DAMLIAQgAzYCFCAEIAQoAgBBIHI2AgBBAAwCC0EAIQQgASACQQR0aiIBKAIEIgBFDQAgACAAKAIAQV9xIgI2AgAgAg0AIAAQICABQQA2AgQLIAQLCxQAIAAgAa0gAq1CIIaEIAQgBRBzCxIAIAAgAa0gAq1CIIaEIAMQFAtBAQF+An4gAUEAIAIbRQRAIABBCGoEQCAAQQA2AgwgAEESNgIIC0J/DAELIAAgASACIAMQdAsiBEIgiKcQACAEpwvGAwIFfwF+An4CQAJAIAAiBC0AGEECcQRAIARBCGoEQCAEQQA2AgwgBEEZNgIICwwBCyABRQRAIARBCGoEQCAEQQA2AgwgBEESNgIICwwBCyABECIiByABakEBay0AAEEvRwRAIAdBAmoQCSIARQRAIARBCGoEQCAEQQA2AgwgBEEONgIICwwCCwJAAkAgACIGIAEiBXNBA3ENACAFQQNxBEADQCAGIAUtAAAiAzoAACADRQ0DIAZBAWohBiAFQQFqIgVBA3ENAAsLIAUoAgAiA0F/cyADQYGChAhrcUGAgYKEeHENAANAIAYgAzYCACAFKAIEIQMgBkEEaiEGIAVBBGohBSADQYGChAhrIANBf3NxQYCBgoR4cUUNAAsLIAYgBS0AACIDOgAAIANFDQADQCAGIAUtAAEiAzoAASAGQQFqIQYgBUEBaiEFIAMNAAsLIAcgACIDakEvOwAACyAEQQBCAEEAEFIiAEUEQCADEAYMAQsgBCADIAEgAxsgACACEHQhCCADEAYgCEJ/VwRAIAAQCyAIDAMLIAQgCEEDQYCA/I8EEHNBf0oNASAEIAgQchoLQn8hCAsgCAsiCEIgiKcQACAIpwsQACAAIAGtIAKtQiCGhBByCxYAIAAgAa0gAq1CIIaEIAMgBCAFEGYL3iMDD38IfgF8IwBB8ABrIgkkAAJAIAFBAE5BACAAG0UEQCACBEAgAkEANgIEIAJBEjYCAAsMAQsgACkDGCISAn5BsIMBKQMAIhNCf1EEQCAJQoOAgIBwNwMwIAlChoCAgPAANwMoIAlCgYCAgCA3AyBBsIMBQQAgCUEgahAkNwMAIAlCj4CAgHA3AxAgCUKJgICAoAE3AwAgCUKMgICA0AE3AwhBuIMBQQggCRAkNwMAQbCDASkDACETCyATC4MgE1IEQCACBEAgAkEANgIEIAJBHDYCAAsMAQsgASABQRByQbiDASkDACITIBKDIBNRGyIKQRhxQRhGBEAgAgRAIAJBADYCBCACQRk2AgALDAELIAlBOGoQKgJAIAAgCUE4ahAhBEACQCAAKAIMQQVGBEAgACgCEEEsRg0BCyACBEAgAiAAKAIMNgIAIAIgACgCEDYCBAsMAgsgCkEBcUUEQCACBEAgAkEANgIEIAJBCTYCAAsMAwsgAhBJIgVFDQEgBSAKNgIEIAUgADYCACAKQRBxRQ0CIAUgBSgCFEECcjYCFCAFIAUoAhhBAnI2AhgMAgsgCkECcQRAIAIEQCACQQA2AgQgAkEKNgIACwwCCyAAEDJBf0wEQCACBEAgAiAAKAIMNgIAIAIgACgCEDYCBAsMAQsCfyAKQQhxBEACQCACEEkiAUUNACABIAo2AgQgASAANgIAIApBEHFFDQAgASABKAIUQQJyNgIUIAEgASgCGEECcjYCGAsgAQwBCyMAQUBqIg4kACAOQQhqECoCQCAAIA5BCGoQIUF/TARAIAIEQCACIAAoAgw2AgAgAiAAKAIQNgIECwwBCyAOLQAIQQRxRQRAIAIEQCACQYoBNgIEIAJBBDYCAAsMAQsgDikDICETIAIQSSIFRQRAQQAhBQwBCyAFIAo2AgQgBSAANgIAIApBEHEEQCAFIAUoAhRBAnI2AhQgBSAFKAIYQQJyNgIYCwJAAkACQCATUARAAn8gACEBAkADQCABKQMYQoCAEINCAFINASABKAIAIgENAAtBAQwBCyABQQBCAEESEA6nCw0EIAVBCGoEQCAFQQA2AgwgBUETNgIICwwBCyMAQdAAayIBJAACQCATQhVYBEAgBUEIagRAIAVBADYCDCAFQRM2AggLDAELAkACQCAFKAIAQgAgE0KqgAQgE0KqgARUGyISfUECEBRBf0oNACAFKAIAIgMoAgxBBEYEQCADKAIQQRZGDQELIAVBCGoEQCAFIAMoAgw2AgggBSADKAIQNgIMCwwBCyAFKAIAEDMiE0J/VwRAIAUoAgAhAyAFQQhqIggEQCAIIAMoAgw2AgAgCCADKAIQNgIECwwBCyAFKAIAIBJBACAFQQhqIg8QLSIERQ0BIBJCqoAEWgRAAkAgBCkDCEIUVARAIARBADoAAAwBCyAEQhQ3AxAgBEEBOgAACwsgAQRAIAFBADYCBCABQRM2AgALIARCABATIQwCQCAELQAABH4gBCkDCCAEKQMQfQVCAAunIgdBEmtBA0sEQEJ/IRcDQCAMQQFrIQMgByAMakEVayEGAkADQCADQQFqIgNB0AAgBiADaxB6IgNFDQEgA0EBaiIMQZ8SQQMQPQ0ACwJAIAMgBCgCBGusIhIgBCkDCFYEQCAEQQA6AAAMAQsgBCASNwMQIARBAToAAAsgBC0AAAR+IAQpAxAFQgALIRICQCAELQAABH4gBCkDCCAEKQMQfQVCAAtCFVgEQCABBEAgAUEANgIEIAFBEzYCAAsMAQsgBEIEEBMoAABB0JaVMEcEQCABBEAgAUEANgIEIAFBEzYCAAsMAQsCQAJAAkAgEkIUVA0AIAQoAgQgEqdqQRRrKAAAQdCWmThHDQACQCASQhR9IhQgBCIDKQMIVgRAIANBADoAAAwBCyADIBQ3AxAgA0EBOgAACyAFKAIUIRAgBSgCACEGIAMtAAAEfiAEKQMQBUIACyEWIARCBBATGiAEEAwhCyAEEAwhDSAEEB0iFEJ/VwRAIAEEQCABQRY2AgQgAUEENgIACwwECyAUQjh8IhUgEyAWfCIWVgRAIAEEQCABQQA2AgQgAUEVNgIACwwECwJAAkAgEyAUVg0AIBUgEyAEKQMIfFYNAAJAIBQgE30iFSAEKQMIVgRAIANBADoAAAwBCyADIBU3AxAgA0EBOgAAC0EAIQcMAQsgBiAUQQAQFEF/TARAIAEEQCABIAYoAgw2AgAgASAGKAIQNgIECwwFC0EBIQcgBkI4IAFBEGogARAtIgNFDQQLIANCBBATKAAAQdCWmTBHBEAgAQRAIAFBADYCBCABQRU2AgALIAdFDQQgAxAIDAQLIAMQHSEVAkAgEEEEcSIGRQ0AIBQgFXxCDHwgFlENACABBEAgAUEANgIEIAFBFTYCAAsgB0UNBCADEAgMBAsgA0IEEBMaIAMQFSIQIAsgC0H//wNGGyELIAMQFSIRIA0gDUH//wNGGyENAkAgBkUNACANIBFGQQAgCyAQRhsNACABBEAgAUEANgIEIAFBFTYCAAsgB0UNBCADEAgMBAsgCyANcgRAIAEEQCABQQA2AgQgAUEBNgIACyAHRQ0EIAMQCAwECyADEB0iGCADEB1SBEAgAQRAIAFBADYCBCABQQE2AgALIAdFDQQgAxAIDAQLIAMQHSEVIAMQHSEWIAMtAABFBEAgAQRAIAFBADYCBCABQRQ2AgALIAdFDQQgAxAIDAQLIAcEQCADEAgLAkAgFkIAWQRAIBUgFnwiGSAWWg0BCyABBEAgAUEWNgIEIAFBBDYCAAsMBAsgEyAUfCIUIBlUBEAgAQRAIAFBADYCBCABQRU2AgALDAQLAkAgBkUNACAUIBlRDQAgAQRAIAFBADYCBCABQRU2AgALDAQLIBggFUIugFgNASABBEAgAUEANgIEIAFBFTYCAAsMAwsCQCASIAQpAwhWBEAgBEEAOgAADAELIAQgEjcDECAEQQE6AAALIAUoAhQhAyAELQAABH4gBCkDCCAEKQMQfQVCAAtCFVgEQCABBEAgAUEANgIEIAFBFTYCAAsMAwsgBC0AAAR+IAQpAxAFQgALIRQgBEIEEBMaIAQQFQRAIAEEQCABQQA2AgQgAUEBNgIACwwDCyAEEAwgBBAMIgZHBEAgAQRAIAFBADYCBCABQRM2AgALDAMLIAQQFSEHIAQQFa0iFiAHrSIVfCIYIBMgFHwiFFYEQCABBEAgAUEANgIEIAFBFTYCAAsMAwsCQCADQQRxRQ0AIBQgGFENACABBEAgAUEANgIEIAFBFTYCAAsMAwsgBq0gARBqIgNFDQIgAyAWNwMgIAMgFTcDGCADQQA6ACwMAQsgGCABEGoiA0UNASADIBY3AyAgAyAVNwMYIANBAToALAsCQCASQhR8IhQgBCkDCFYEQCAEQQA6AAAMAQsgBCAUNwMQIARBAToAAAsgBBAMIQYCQCADKQMYIAMpAyB8IBIgE3xWDQACQCAGRQRAIAUtAARBBHFFDQELAkAgEkIWfCISIAQpAwhWBEAgBEEAOgAADAELIAQgEjcDECAEQQE6AAALIAQtAAAEfiAEKQMIIAQpAxB9BUIACyIUIAatIhJUDQEgBS0ABEEEcUEAIBIgFFIbDQEgBkUNACADIAQgEhATIAZBACABEDUiBjYCKCAGDQAgAxAWDAILAkAgEyADKQMgIhJYBEACQCASIBN9IhIgBCkDCFYEQCAEQQA6AAAMAQsgBCASNwMQIARBAToAAAsgBCADKQMYEBMiBkUNAiAGIAMpAxgQFyIHDQEgAQRAIAFBADYCBCABQQ42AgALIAMQFgwDCyAFKAIAIBJBABAUIQcgBSgCACEGIAdBf0wEQCABBEAgASAGKAIMNgIAIAEgBigCEDYCBAsgAxAWDAMLQQAhByAGEDMgAykDIFENACABBEAgAUEANgIEIAFBEzYCAAsgAxAWDAILQgAhFAJAAkAgAykDGCIWUEUEQANAIBQgAykDCFIiC0UEQCADLQAsDQMgFkIuVA0DAn8CQCADKQMQIhVCgIAEfCISIBVaQQAgEkKAgICAAVQbRQ0AIAMoAgAgEqdBBHQQNCIGRQ0AIAMgBjYCAAJAIAMpAwgiFSASWg0AIAYgFadBBHRqIgZCADcCACAGQgA3AAUgFUIBfCIVIBJRDQADQCADKAIAIBWnQQR0aiIGQgA3AgAgBkIANwAFIBVCAXwiFSASUg0ACwsgAyASNwMIIAMgEjcDEEEBDAELIAEEQCABQQA2AgQgAUEONgIAC0EAC0UNBAtB2AAQCSIGBH8gBkIANwMgIAZBADYCGCAGQv////8PNwMQIAZBADsBDCAGQb+GKDYCCCAGQQE6AAYgBkEAOwEEIAZBADYCACAGQgA3A0ggBkGAgNiNeDYCRCAGQgA3AyggBkIANwMwIAZCADcDOCAGQUBrQQA7AQAgBkIANwNQIAYFQQALIQYgAygCACAUp0EEdGogBjYCAAJAIAYEQCAGIAUoAgAgB0EAIAEQaCISQn9VDQELIAsNBCABKAIAQRNHDQQgAQRAIAFBADYCBCABQRU2AgALDAQLIBRCAXwhFCAWIBJ9IhZCAFINAAsLIBQgAykDCFINAAJAIAUtAARBBHFFDQAgBwRAIActAAAEfyAHKQMQIAcpAwhRBUEAC0UNAgwBCyAFKAIAEDMiEkJ/VwRAIAUoAgAhBiABBEAgASAGKAIMNgIAIAEgBigCEDYCBAsgAxAWDAULIBIgAykDGCADKQMgfFINAQsgBxAIAn4gCARAAn8gF0IAVwRAIAUgCCABEEghFwsgBSADIAEQSCISIBdVCwRAIAgQFiASDAILIAMQFgwFC0IAIAUtAARBBHFFDQAaIAUgAyABEEgLIRcgAyEIDAMLIAEEQCABQQA2AgQgAUEVNgIACyAHEAggAxAWDAILIAMQFiAHEAgMAQsgAQRAIAFBADYCBCABQRU2AgALIAMQFgsCQCAMIAQoAgRrrCISIAQpAwhWBEAgBEEAOgAADAELIAQgEjcDECAEQQE6AAALIAQtAAAEfiAEKQMIIAQpAxB9BUIAC6ciB0ESa0EDSw0BCwsgBBAIIBdCf1UNAwwBCyAEEAgLIA8iAwRAIAMgASgCADYCACADIAEoAgQ2AgQLIAgQFgtBACEICyABQdAAaiQAIAgNAQsgAgRAIAIgBSgCCDYCACACIAUoAgw2AgQLDAELIAUgCCgCADYCQCAFIAgpAwg3AzAgBSAIKQMQNwM4IAUgCCgCKDYCICAIEAYgBSgCUCEIIAVBCGoiBCEBQQAhBwJAIAUpAzAiE1ANAEGAgICAeCEGAn8gE7pEAAAAAAAA6D+jRAAA4P///+9BpCIaRAAAAAAAAPBBYyAaRAAAAAAAAAAAZnEEQCAaqwwBC0EACyIDQYCAgIB4TQRAIANBAWsiA0EBdiADciIDQQJ2IANyIgNBBHYgA3IiA0EIdiADciIDQRB2IANyQQFqIQYLIAYgCCgCACIMTQ0AIAYQPCILRQRAIAEEQCABQQA2AgQgAUEONgIACwwBCwJAIAgpAwhCACAMG1AEQCAIKAIQIQ8MAQsgCCgCECEPA0AgDyAHQQJ0aigCACIBBEADQCABKAIYIQMgASALIAEoAhwgBnBBAnRqIg0oAgA2AhggDSABNgIAIAMiAQ0ACwsgB0EBaiIHIAxHDQALCyAPEAYgCCAGNgIAIAggCzYCEAsCQCAFKQMwUA0AQgAhEwJAIApBBHFFBEADQCAFKAJAIBOnQQR0aigCACgCMEEAQQAgAhAlIgFFDQQgBSgCUCABIBNBCCAEEE1FBEAgBCgCAEEKRw0DCyATQgF8IhMgBSkDMFQNAAwDCwALA0AgBSgCQCATp0EEdGooAgAoAjBBAEEAIAIQJSIBRQ0DIAUoAlAgASATQQggBBBNRQ0BIBNCAXwiEyAFKQMwVA0ACwwBCyACBEAgAiAEKAIANgIAIAIgBCgCBDYCBAsMAQsgBSAFKAIUNgIYDAELIAAgACgCMEEBajYCMCAFEEtBACEFCyAOQUBrJAAgBQsiBQ0BIAAQGhoLQQAhBQsgCUHwAGokACAFCxAAIwAgAGtBcHEiACQAIAALBgAgACQACwQAIwAL4CoDEX8IfgN8IwBBwMAAayIHJABBfyECAkAgAEUNAAJ/IAAtAChFBEBBACAAKAIYIAAoAhRGDQEaC0EBCyEBAkACQCAAKQMwIhRQRQRAIAAoAkAhCgNAIAogEqdBBHRqIgMtAAwhCwJAAkAgAygCCA0AIAsNACADKAIEIgNFDQEgAygCAEUNAQtBASEBCyAXIAtBAXOtQv8Bg3whFyASQgF8IhIgFFINAAsgF0IAUg0BCyAAKAIEQQhxIAFyRQ0BAn8gACgCACIDKAIkIgFBA0cEQCADKAIgBH9BfyADEBpBAEgNAhogAygCJAUgAQsEQCADEEMLQX8gA0EAQgBBDxAOQgBTDQEaIANBAzYCJAtBAAtBf0oNASAAKAIAKAIMQRZGBEAgACgCACgCEEEsRg0CCyAAKAIAIQEgAEEIagRAIAAgASgCDDYCCCAAIAEoAhA2AgwLDAILIAFFDQAgFCAXVARAIABBCGoEQCAAQQA2AgwgAEEUNgIICwwCCyAXp0EDdBAJIgtFDQFCfyEWQgAhEgNAAkAgCiASp0EEdGoiBigCACIDRQ0AAkAgBigCCA0AIAYtAAwNACAGKAIEIgFFDQEgASgCAEUNAQsgFiADKQNIIhMgEyAWVhshFgsgBi0ADEUEQCAXIBlYBEAgCxAGIABBCGoEQCAAQQA2AgwgAEEUNgIICwwECyALIBmnQQN0aiASNwMAIBlCAXwhGQsgEkIBfCISIBRSDQALIBcgGVYEQCALEAYgAEEIagRAIABBADYCDCAAQRQ2AggLDAILAkACQCAAKAIAKQMYQoCACINQDQACQAJAIBZCf1INACAAKQMwIhNQDQIgE0IBgyEVIAAoAkAhAwJAIBNCAVEEQEJ/IRRCACESQgAhFgwBCyATQn6DIRlCfyEUQgAhEkIAIRYDQCADIBKnQQR0aigCACIBBEAgFiABKQNIIhMgEyAWVCIBGyEWIBQgEiABGyEUCyADIBJCAYQiGKdBBHRqKAIAIgEEQCAWIAEpA0giEyATIBZUIgEbIRYgFCAYIAEbIRQLIBJCAnwhEiAZQgJ9IhlQRQ0ACwsCQCAVUA0AIAMgEqdBBHRqKAIAIgFFDQAgFiABKQNIIhMgEyAWVCIBGyEWIBQgEiABGyEUCyAUQn9RDQBCACETIwBBEGsiBiQAAkAgACAUIABBCGoiCBBBIhVQDQAgFSAAKAJAIBSnQQR0aigCACIKKQMgIhh8IhQgGFpBACAUQn9VG0UEQCAIBEAgCEEWNgIEIAhBBDYCAAsMAQsgCi0ADEEIcUUEQCAUIRMMAQsgACgCACAUQQAQFCEBIAAoAgAhAyABQX9MBEAgCARAIAggAygCDDYCACAIIAMoAhA2AgQLDAELIAMgBkEMakIEEBFCBFIEQCAAKAIAIQEgCARAIAggASgCDDYCACAIIAEoAhA2AgQLDAELIBRCBHwgFCAGKAAMQdCWncAARhtCFEIMAn9BASEBAkAgCikDKEL+////D1YNACAKKQMgQv7///8PVg0AQQAhAQsgAQsbfCIUQn9XBEAgCARAIAhBFjYCBCAIQQQ2AgALDAELIBQhEwsgBkEQaiQAIBMiFkIAUg0BIAsQBgwFCyAWUA0BCwJ/IAAoAgAiASgCJEEBRgRAIAFBDGoEQCABQQA2AhAgAUESNgIMC0F/DAELQX8gAUEAIBZBERAOQgBTDQAaIAFBATYCJEEAC0F/Sg0BC0IAIRYCfyAAKAIAIgEoAiRBAUYEQCABQQxqBEAgAUEANgIQIAFBEjYCDAtBfwwBC0F/IAFBAEIAQQgQDkIAUw0AGiABQQE2AiRBAAtBf0oNACAAKAIAIQEgAEEIagRAIAAgASgCDDYCCCAAIAEoAhA2AgwLIAsQBgwCCyAAKAJUIgIEQCACQgA3AxggAigCAEQAAAAAAAAAACACKAIMIAIoAgQRDgALIABBCGohBCAXuiEcQgAhFAJAAkACQANAIBcgFCITUgRAIBO6IByjIRsgE0IBfCIUuiAcoyEaAkAgACgCVCICRQ0AIAIgGjkDKCACIBs5AyAgAisDECAaIBuhRAAAAAAAAAAAoiAboCIaIAIrAxihY0UNACACKAIAIBogAigCDCACKAIEEQ4AIAIgGjkDGAsCfwJAIAAoAkAgCyATp0EDdGopAwAiE6dBBHRqIg0oAgAiAQRAIAEpA0ggFlQNAQsgDSgCBCEFAkACfwJAIA0oAggiAkUEQCAFRQ0BQQEgBSgCACICQQFxDQIaIAJBwABxQQZ2DAILQQEgBQ0BGgsgDSABECsiBTYCBCAFRQ0BIAJBAEcLIQZBACEJIwBBEGsiDCQAAkAgEyAAKQMwWgRAIABBCGoEQCAAQQA2AgwgAEESNgIIC0F/IQkMAQsgACgCQCIKIBOnIgNBBHRqIg8oAgAiAkUNACACLQAEDQACQCACKQNIQhp8IhhCf1cEQCAAQQhqBEAgAEEWNgIMIABBBDYCCAsMAQtBfyEJIAAoAgAgGEEAEBRBf0wEQCAAKAIAIQIgAEEIagRAIAAgAigCDDYCCCAAIAIoAhA2AgwLDAILIAAoAgBCBCAMQQxqIABBCGoiDhAtIhBFDQEgEBAMIQEgEBAMIQggEC0AAAR/IBApAxAgECkDCFEFQQALIQIgEBAIIAJFBEAgDgRAIA5BADYCBCAOQRQ2AgALDAILAkAgCEUNACAAKAIAIAGtQQEQFEF/TARAQYSEASgCACECIA4EQCAOIAI2AgQgDkEENgIACwwDC0EAIAAoAgAgCEEAIA4QRSIBRQ0BIAEgCEGAAiAMQQhqIA4QbiECIAEQBiACRQ0BIAwoAggiAkUNACAMIAIQbSICNgIIIA8oAgAoAjQgAhBvIQIgDygCACACNgI0CyAPKAIAIgJBAToABEEAIQkgCiADQQR0aigCBCIBRQ0BIAEtAAQNASACKAI0IQIgAUEBOgAEIAEgAjYCNAwBC0F/IQkLIAxBEGokACAJQQBIDQUgACgCABAfIhhCAFMNBSAFIBg3A0ggBgRAQQAhDCANKAIIIg0hASANRQRAIAAgACATQQhBABB/IgwhASAMRQ0HCwJAAkAgASAHQQhqECFBf0wEQCAEBEAgBCABKAIMNgIAIAQgASgCEDYCBAsMAQsgBykDCCISQsAAg1AEQCAHQQA7ATggByASQsAAhCISNwMICwJAAkAgBSgCECICQX5PBEAgBy8BOCIDRQ0BIAUgAzYCECADIQIMAgsgAg0AIBJCBINQDQAgByAHKQMgNwMoIAcgEkIIhCISNwMIQQAhAgwBCyAHIBJC9////w+DIhI3AwgLIBJCgAGDUARAIAdBADsBOiAHIBJCgAGEIhI3AwgLAn8gEkIEg1AEQEJ/IRVBgAoMAQsgBSAHKQMgIhU3AyggEkIIg1AEQAJAAkACQAJAQQggAiACQX1LG0H//wNxDg0CAwMDAwMDAwEDAwMAAwtBgApBgAIgFUKUwuTzD1YbDAQLQYAKQYACIBVCg4Ow/w9WGwwDC0GACkGAAiAVQv////8PVhsMAgtBgApBgAIgFUIAUhsMAQsgBSAHKQMoNwMgQYACCyEPIAAoAgAQHyITQn9XBEAgACgCACECIAQEQCAEIAIoAgw2AgAgBCACKAIQNgIECwwBCyAFIAUvAQxB9/8DcTsBDCAAIAUgDxA3IgpBAEgNACAHLwE4IghBCCAFKAIQIgMgA0F9SxtB//8DcSICRyEGAkACQAJAAkACQAJAAkAgAiAIRwRAIANBAEchAwwBC0EAIQMgBS0AAEGAAXFFDQELIAUvAVIhCSAHLwE6IQIMAQsgBS8BUiIJIAcvAToiAkYNAQsgASABKAIwQQFqNgIwIAJB//8DcQ0BIAEhAgwCCyABIAEoAjBBAWo2AjBBACEJDAILQSZBACAHLwE6QQFGGyICRQRAIAQEQCAEQQA2AgQgBEEYNgIACyABEAsMAwsgACABIAcvATpBACAAKAIcIAIRBgAhAiABEAsgAkUNAgsgCUEARyEJIAhBAEcgBnFFBEAgAiEBDAELIAAgAiAHLwE4EIEBIQEgAhALIAFFDQELAkAgCEUgBnJFBEAgASECDAELIAAgAUEAEIABIQIgARALIAJFDQELAkAgA0UEQCACIQMMAQsgACACIAUoAhBBASAFLwFQEIIBIQMgAhALIANFDQELAkAgCUUEQCADIQEMAQsgBSgCVCIBRQRAIAAoAhwhAQsCfyAFLwFSGkEBCwRAIAQEQCAEQQA2AgQgBEEYNgIACyADEAsMAgsgACADIAUvAVJBASABQQARBgAhASADEAsgAUUNAQsgACgCABAfIhhCf1cEQCAAKAIAIQIgBARAIAQgAigCDDYCACAEIAIoAhA2AgQLDAELAkAgARAyQQBOBEACfwJAAkAgASAHQUBrQoDAABARIhJCAVMNAEIAIRkgFUIAVQRAIBW5IRoDQCAAIAdBQGsgEhAbQQBIDQMCQCASQoDAAFINACAAKAJUIgJFDQAgAiAZQoBAfSIZuSAaoxB7CyABIAdBQGtCgMAAEBEiEkIAVQ0ACwwBCwNAIAAgB0FAayASEBtBAEgNAiABIAdBQGtCgMAAEBEiEkIAVQ0ACwtBACASQn9VDQEaIAQEQCAEIAEoAgw2AgAgBCABKAIQNgIECwtBfwshAiABEBoaDAELIAQEQCAEIAEoAgw2AgAgBCABKAIQNgIEC0F/IQILIAEgB0EIahAhQX9MBEAgBARAIAQgASgCDDYCACAEIAEoAhA2AgQLQX8hAgsCf0EAIQkCQCABIgNFDQADQCADLQAaQQFxBEBB/wEhCSADQQBCAEEQEA4iFUIAUw0CIBVCBFkEQCADQQxqBEAgA0EANgIQIANBFDYCDAsMAwsgFachCQwCCyADKAIAIgMNAAsLIAlBGHRBGHUiA0F/TAsEQCAEBEAgBCABKAIMNgIAIAQgASgCEDYCBAsgARALDAELIAEQCyACQQBIDQAgACgCABAfIRUgACgCACECIBVCf1cEQCAEBEAgBCACKAIMNgIAIAQgAigCEDYCBAsMAQsgAiATEHVBf0wEQCAAKAIAIQIgBARAIAQgAigCDDYCACAEIAIoAhA2AgQLDAELIAcpAwgiE0LkAINC5ABSBEAgBARAIARBADYCBCAEQRQ2AgALDAELAkAgBS0AAEEgcQ0AIBNCEINQRQRAIAUgBygCMDYCFAwBCyAFQRRqEAEaCyAFIAcvATg2AhAgBSAHKAI0NgIYIAcpAyAhEyAFIBUgGH03AyAgBSATNwMoIAUgBS8BDEH5/wNxIANB/wFxQQF0cjsBDCAPQQp2IQNBPyEBAkACQAJAAkAgBSgCECICQQxrDgMAAQIBCyAFQS47AQoMAgtBLSEBIAMNACAFKQMoQv7///8PVg0AIAUpAyBC/v///w9WDQBBFCEBIAJBCEYNACAFLwFSQQFGDQAgBSgCMCICBH8gAi8BBAVBAAtB//8DcSICBEAgAiAFKAIwKAIAakEBay0AAEEvRg0BC0EKIQELIAUgATsBCgsgACAFIA8QNyICQQBIDQAgAiAKRwRAIAQEQCAEQQA2AgQgBEEUNgIACwwBCyAAKAIAIBUQdUF/Sg0BIAAoAgAhAiAEBEAgBCACKAIMNgIAIAQgAigCEDYCBAsLIA0NByAMEAsMBwsgDQ0CIAwQCwwCCyAFIAUvAQxB9/8DcTsBDCAAIAVBgAIQN0EASA0FIAAgEyAEEEEiE1ANBSAAKAIAIBNBABAUQX9MBEAgACgCACECIAQEQCAEIAIoAgw2AgAgBCACKAIQNgIECwwGCyAFKQMgIRIjAEGAQGoiAyQAAkAgElBFBEAgAEEIaiECIBK6IRoDQEF/IQEgACgCACADIBJCgMAAIBJCgMAAVBsiEyACEGVBAEgNAiAAIAMgExAbQQBIDQIgACgCVCAaIBIgE30iErqhIBqjEHsgEkIAUg0ACwtBACEBCyADQYBAayQAIAFBf0oNAUEBIREgAUEcdkEIcUEIRgwCCyAEBEAgBEEANgIEIARBDjYCAAsMBAtBAAtFDQELCyARDQBBfyECAkAgACgCABAfQgBTDQAgFyEUQQAhCkIAIRcjAEHwAGsiESQAAkAgACgCABAfIhVCAFkEQCAUUEUEQANAIAAgACgCQCALIBenQQN0aigCAEEEdGoiAygCBCIBBH8gAQUgAygCAAtBgAQQNyIBQQBIBEBCfyEXDAQLIAFBAEcgCnIhCiAXQgF8IhcgFFINAAsLQn8hFyAAKAIAEB8iGEJ/VwRAIAAoAgAhASAAQQhqBEAgACABKAIMNgIIIAAgASgCEDYCDAsMAgsgEULiABAXIgZFBEAgAEEIagRAIABBADYCDCAAQQ42AggLDAILIBggFX0hEyAVQv////8PViAUQv//A1ZyIApyQQFxBEAgBkGZEkEEECwgBkIsEBggBkEtEA0gBkEtEA0gBkEAEBIgBkEAEBIgBiAUEBggBiAUEBggBiATEBggBiAVEBggBkGUEkEEECwgBkEAEBIgBiAYEBggBkEBEBILIAZBnhJBBBAsIAZBABASIAYgFEL//wMgFEL//wNUG6dB//8DcSIBEA0gBiABEA0gBkF/IBOnIBNC/v///w9WGxASIAZBfyAVpyAVQv7///8PVhsQEiAGIABBJEEgIAAtACgbaigCACIDBH8gAy8BBAVBAAtB//8DcRANIAYtAABFBEAgAEEIagRAIABBADYCDCAAQRQ2AggLIAYQCAwCCyAAIAYoAgQgBi0AAAR+IAYpAxAFQgALEBshASAGEAggAUEASA0BIAMEQCAAIAMoAgAgAzMBBBAbQQBIDQILIBMhFwwBCyAAKAIAIQEgAEEIagRAIAAgASgCDDYCCCAAIAEoAhA2AgwLQn8hFwsgEUHwAGokACAXQgBTDQAgACgCABAfQj+HpyECCyALEAYgAkEASA0BAn8gACgCACIBKAIkQQFHBEAgAUEMagRAIAFBADYCECABQRI2AgwLQX8MAQsgASgCICICQQJPBEAgAUEMagRAIAFBADYCECABQR02AgwLQX8MAQsCQCACQQFHDQAgARAaQQBODQBBfwwBCyABQQBCAEEJEA5Cf1cEQCABQQI2AiRBfwwBCyABQQA2AiRBAAtFDQIgACgCACECIAQEQCAEIAIoAgw2AgAgBCACKAIQNgIECwwBCyALEAYLIAAoAlQQfCAAKAIAEENBfyECDAILIAAoAlQQfAsgABBLQQAhAgsgB0HAwABqJAAgAgtFAEHwgwFCADcDAEHogwFCADcDAEHggwFCADcDAEHYgwFCADcDAEHQgwFCADcDAEHIgwFCADcDAEHAgwFCADcDAEHAgwELoQMBCH8jAEGgAWsiAiQAIAAQMQJAAn8CQCAAKAIAIgFBAE4EQCABQbATKAIASA0BCyACIAE2AhAgAkEgakH2ESACQRBqEHZBASEGIAJBIGohBCACQSBqECIhA0EADAELIAFBAnQiAUGwEmooAgAhBQJ/AkACQCABQcATaigCAEEBaw4CAAEECyAAKAIEIQNB9IIBKAIAIQdBACEBAkACQANAIAMgAUHQ8QBqLQAARwRAQdcAIQQgAUEBaiIBQdcARw0BDAILCyABIgQNAEGw8gAhAwwBC0Gw8gAhAQNAIAEtAAAhCCABQQFqIgMhASAIDQAgAyEBIARBAWsiBA0ACwsgBygCFBogAwwBC0EAIAAoAgRrQQJ0QdjAAGooAgALIgRFDQEgBBAiIQMgBUUEQEEAIQVBASEGQQAMAQsgBRAiQQJqCyEBIAEgA2pBAWoQCSIBRQRAQegSKAIAIQUMAQsgAiAENgIIIAJBrBJBkRIgBhs2AgQgAkGsEiAFIAYbNgIAIAFBqwogAhB2IAAgATYCCCABIQULIAJBoAFqJAAgBQszAQF/IAAoAhQiAyABIAIgACgCECADayIBIAEgAksbIgEQBxogACAAKAIUIAFqNgIUIAILBgBBsIgBCwYAQayIAQsGAEGkiAELBwAgAEEEagsHACAAQQhqCyYBAX8gACgCFCIBBEAgARALCyAAKAIEIQEgAEEEahAxIAAQBiABC6kBAQN/AkAgAC0AACICRQ0AA0AgAS0AACIERQRAIAIhAwwCCwJAIAIgBEYNACACQSByIAIgAkHBAGtBGkkbIAEtAAAiAkEgciACIAJBwQBrQRpJG0YNACAALQAAIQMMAgsgAUEBaiEBIAAtAAEhAiAAQQFqIQAgAg0ACwsgA0H/AXEiAEEgciAAIABBwQBrQRpJGyABLQAAIgBBIHIgACAAQcEAa0EaSRtrC8sGAgJ+An8jAEHgAGsiByQAAkACQAJAAkACQAJAAkACQAJAAkACQCAEDg8AAQoCAwQGBwgICAgICAUICyABQgA3AyAMCQsgACACIAMQESIFQn9XBEAgAUEIaiIBBEAgASAAKAIMNgIAIAEgACgCEDYCBAsMCAsCQCAFUARAIAEpAygiAyABKQMgUg0BIAEgAzcDGCABQQE2AgQgASgCAEUNASAAIAdBKGoQIUF/TARAIAFBCGoiAQRAIAEgACgCDDYCACABIAAoAhA2AgQLDAoLAkAgBykDKCIDQiCDUA0AIAcoAlQgASgCMEYNACABQQhqBEAgAUEANgIMIAFBBzYCCAsMCgsgA0IEg1ANASAHKQNAIAEpAxhRDQEgAUEIagRAIAFBADYCDCABQRU2AggLDAkLIAEoAgQNACABKQMoIgMgASkDICIGVA0AIAUgAyAGfSIDWA0AIAEoAjAhBANAIAECfyAFIAN9IgZC/////w8gBkL/////D1QbIganIQBBACACIAOnaiIIRQ0AGiAEIAggAEHUgAEoAgARAAALIgQ2AjAgASABKQMoIAZ8NwMoIAUgAyAGfCIDVg0ACwsgASABKQMgIAV8NwMgDAgLIAEoAgRFDQcgAiABKQMYIgM3AxggASgCMCEAIAJBADYCMCACIAM3AyAgAiAANgIsIAIgAikDAELsAYQ3AwAMBwsgA0IIWgR+IAIgASgCCDYCACACIAEoAgw2AgRCCAVCfwshBQwGCyABEAYMBQtCfyEFIAApAxgiA0J/VwRAIAFBCGoiAQRAIAEgACgCDDYCACABIAAoAhA2AgQLDAULIAdBfzYCGCAHQo+AgICAAjcDECAHQoyAgIDQATcDCCAHQomAgICgATcDACADQQggBxAkQn+FgyEFDAQLIANCD1gEQCABQQhqBEAgAUEANgIMIAFBEjYCCAsMAwsgAkUNAgJAIAAgAikDACACKAIIEBRBAE4EQCAAEDMiA0J/VQ0BCyABQQhqIgEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwDCyABIAM3AyAMAwsgASkDICEFDAILIAFBCGoEQCABQQA2AgwgAUEcNgIICwtCfyEFCyAHQeAAaiQAIAULjAcCAn4CfyMAQRBrIgckAAJAAkACQAJAAkACQAJAAkACQAJAIAQOEQABAgMFBggICAgICAgIBwgECAsgAUJ/NwMgIAFBADoADyABQQA7AQwgAUIANwMYIAEoAqxAIAEoAqhAKAIMEQEArUIBfSEFDAgLQn8hBSABKAIADQdCACEFIANQDQcgAS0ADQ0HIAFBKGohBAJAA0ACQCAHIAMgBX03AwggASgCrEAgAiAFp2ogB0EIaiABKAKoQCgCHBEAACEIQgAgBykDCCAIQQJGGyAFfCEFAkACQAJAIAhBAWsOAwADAQILIAFBAToADSABKQMgIgNCf1cEQCABBEAgAUEANgIEIAFBFDYCAAsMBQsgAS0ADkUNBCADIAVWDQQgASADNwMYIAFBAToADyACIAQgA6cQBxogASkDGCEFDAwLIAEtAAwNAyAAIARCgMAAEBEiBkJ/VwRAIAEEQCABIAAoAgw2AgAgASAAKAIQNgIECwwECyAGUARAIAFBAToADCABKAKsQCABKAKoQCgCGBEDACABKQMgQn9VDQEgAUIANwMgDAELAkAgASkDIEIAWQRAIAFBADoADgwBCyABIAY3AyALIAEoAqxAIAQgBiABKAKoQCgCFBEPABoLIAMgBVYNAQwCCwsgASgCAA0AIAEEQCABQQA2AgQgAUEUNgIACwsgBVBFBEAgAUEAOgAOIAEgASkDGCAFfDcDGAwIC0J/QgAgASgCABshBQwHCyABKAKsQCABKAKoQCgCEBEBAK1CAX0hBQwGCyABLQAQBEAgAS0ADQRAIAIgAS0ADwR/QQAFQQggASgCFCIAIABBfUsbCzsBMCACIAEpAxg3AyAgAiACKQMAQsgAhDcDAAwHCyACIAIpAwBCt////w+DNwMADAYLIAJBADsBMCACKQMAIQMgAS0ADQRAIAEpAxghBSACIANCxACENwMAIAIgBTcDGEIAIQUMBgsgAiADQrv///8Pg0LAAIQ3AwAMBQsgAS0ADw0EIAEoAqxAIAEoAqhAKAIIEQEArCEFDAQLIANCCFoEfiACIAEoAgA2AgAgAiABKAIENgIEQggFQn8LIQUMAwsgAUUNAiABKAKsQCABKAKoQCgCBBEDACABEDEgARAGDAILIAdBfzYCAEEQIAcQJEI/hCEFDAELIAEEQCABQQA2AgQgAUEUNgIAC0J/IQULIAdBEGokACAFC2MAQcgAEAkiAEUEQEGEhAEoAgAhASACBEAgAiABNgIEIAJBATYCAAsgAA8LIABBADoADCAAQQA6AAQgACACNgIAIABBADYCOCAAQgA3AzAgACABQQkgAUEBa0EJSRs2AgggAAu3fAIefwZ+IAIpAwAhIiAAIAE2AhwgACAiQv////8PICJC/////w9UGz4CICAAQRBqIQECfyAALQAEBEACfyAALQAMQQJ0IQpBfiEEAkACQAJAIAEiBUUNACAFKAIgRQ0AIAUoAiRFDQAgBSgCHCIDRQ0AIAMoAgAgBUcNAAJAAkAgAygCICIGQTlrDjkBAgICAgICAgICAgIBAgICAQICAgICAgICAgICAgICAgICAQICAgICAgICAgICAQICAgICAgICAgEACyAGQZoFRg0AIAZBKkcNAQsgCkEFSw0AAkACQCAFKAIMRQ0AIAUoAgQiAQRAIAUoAgBFDQELIAZBmgVHDQEgCkEERg0BCyAFQeDAACgCADYCGEF+DAQLIAUoAhBFDQEgAygCJCEEIAMgCjYCJAJAIAMoAhAEQCADEDACQCAFKAIQIgYgAygCECIIIAYgCEkbIgFFDQAgBSgCDCADKAIIIAEQBxogBSAFKAIMIAFqNgIMIAMgAygCCCABajYCCCAFIAUoAhQgAWo2AhQgBSAFKAIQIAFrIgY2AhAgAyADKAIQIAFrIgg2AhAgCA0AIAMgAygCBDYCCEEAIQgLIAYEQCADKAIgIQYMAgsMBAsgAQ0AIApBAXRBd0EAIApBBEsbaiAEQQF0QXdBACAEQQRKG2pKDQAgCkEERg0ADAILAkACQAJAAkACQCAGQSpHBEAgBkGaBUcNASAFKAIERQ0DDAcLIAMoAhRFBEAgA0HxADYCIAwCCyADKAI0QQx0QYDwAWshBAJAIAMoAowBQQJODQAgAygCiAEiAUEBTA0AIAFBBUwEQCAEQcAAciEEDAELQYABQcABIAFBBkYbIARyIQQLIAMoAgQgCGogBEEgciAEIAMoAmgbIgFBH3AgAXJBH3NBCHQgAUGA/gNxQQh2cjsAACADIAMoAhBBAmoiATYCECADKAJoBEAgAygCBCABaiAFKAIwIgFBGHQgAUEIdEGAgPwHcXIgAUEIdkGA/gNxIAFBGHZycjYAACADIAMoAhBBBGo2AhALIAVBATYCMCADQfEANgIgIAUQCiADKAIQDQcgAygCICEGCwJAAkACQAJAIAZBOUYEfyADQaABakHkgAEoAgARAQAaIAMgAygCECIBQQFqNgIQIAEgAygCBGpBHzoAACADIAMoAhAiAUEBajYCECABIAMoAgRqQYsBOgAAIAMgAygCECIBQQFqNgIQIAEgAygCBGpBCDoAAAJAIAMoAhwiAUUEQCADKAIEIAMoAhBqQQA2AAAgAyADKAIQIgFBBWo2AhAgASADKAIEakEAOgAEQQIhBCADKAKIASIBQQlHBEBBBCABQQJIQQJ0IAMoAowBQQFKGyEECyADIAMoAhAiAUEBajYCECABIAMoAgRqIAQ6AAAgAyADKAIQIgFBAWo2AhAgASADKAIEakEDOgAAIANB8QA2AiAgBRAKIAMoAhBFDQEMDQsgASgCJCELIAEoAhwhCSABKAIQIQggASgCLCENIAEoAgAhBiADIAMoAhAiAUEBajYCEEECIQQgASADKAIEaiANQQBHQQF0IAZBAEdyIAhBAEdBAnRyIAlBAEdBA3RyIAtBAEdBBHRyOgAAIAMoAgQgAygCEGogAygCHCgCBDYAACADIAMoAhAiDUEEaiIGNgIQIAMoAogBIgFBCUcEQEEEIAFBAkhBAnQgAygCjAFBAUobIQQLIAMgDUEFajYCECADKAIEIAZqIAQ6AAAgAygCHCgCDCEEIAMgAygCECIBQQFqNgIQIAEgAygCBGogBDoAACADKAIcIgEoAhAEfyADKAIEIAMoAhBqIAEoAhQ7AAAgAyADKAIQQQJqNgIQIAMoAhwFIAELKAIsBEAgBQJ/IAUoAjAhBiADKAIQIQRBACADKAIEIgFFDQAaIAYgASAEQdSAASgCABEAAAs2AjALIANBxQA2AiAgA0EANgIYDAILIAMoAiAFIAYLQcUAaw4jAAQEBAEEBAQEBAQEBAQEBAQEBAQEBAIEBAQEBAQEBAQEBAMECyADKAIcIgEoAhAiBgRAIAMoAgwiCCADKAIQIgQgAS8BFCADKAIYIg1rIglqSQRAA0AgAygCBCAEaiAGIA1qIAggBGsiCBAHGiADIAMoAgwiDTYCEAJAIAMoAhwoAixFDQAgBCANTw0AIAUCfyAFKAIwIQZBACADKAIEIARqIgFFDQAaIAYgASANIARrQdSAASgCABEAAAs2AjALIAMgAygCGCAIajYCGCAFKAIcIgYQMAJAIAUoAhAiBCAGKAIQIgEgASAESxsiAUUNACAFKAIMIAYoAgggARAHGiAFIAUoAgwgAWo2AgwgBiAGKAIIIAFqNgIIIAUgBSgCFCABajYCFCAFIAUoAhAgAWs2AhAgBiAGKAIQIAFrIgE2AhAgAQ0AIAYgBigCBDYCCAsgAygCEA0MIAMoAhghDSADKAIcKAIQIQZBACEEIAkgCGsiCSADKAIMIghLDQALCyADKAIEIARqIAYgDWogCRAHGiADIAMoAhAgCWoiDTYCEAJAIAMoAhwoAixFDQAgBCANTw0AIAUCfyAFKAIwIQZBACADKAIEIARqIgFFDQAaIAYgASANIARrQdSAASgCABEAAAs2AjALIANBADYCGAsgA0HJADYCIAsgAygCHCgCHARAIAMoAhAiBCEJA0ACQCAEIAMoAgxHDQACQCADKAIcKAIsRQ0AIAQgCU0NACAFAn8gBSgCMCEGQQAgAygCBCAJaiIBRQ0AGiAGIAEgBCAJa0HUgAEoAgARAAALNgIwCyAFKAIcIgYQMAJAIAUoAhAiBCAGKAIQIgEgASAESxsiAUUNACAFKAIMIAYoAgggARAHGiAFIAUoAgwgAWo2AgwgBiAGKAIIIAFqNgIIIAUgBSgCFCABajYCFCAFIAUoAhAgAWs2AhAgBiAGKAIQIAFrIgE2AhAgAQ0AIAYgBigCBDYCCAtBACEEQQAhCSADKAIQRQ0ADAsLIAMoAhwoAhwhBiADIAMoAhgiAUEBajYCGCABIAZqLQAAIQEgAyAEQQFqNgIQIAMoAgQgBGogAToAACABBEAgAygCECEEDAELCwJAIAMoAhwoAixFDQAgAygCECIGIAlNDQAgBQJ/IAUoAjAhBEEAIAMoAgQgCWoiAUUNABogBCABIAYgCWtB1IABKAIAEQAACzYCMAsgA0EANgIYCyADQdsANgIgCwJAIAMoAhwoAiRFDQAgAygCECIEIQkDQAJAIAQgAygCDEcNAAJAIAMoAhwoAixFDQAgBCAJTQ0AIAUCfyAFKAIwIQZBACADKAIEIAlqIgFFDQAaIAYgASAEIAlrQdSAASgCABEAAAs2AjALIAUoAhwiBhAwAkAgBSgCECIEIAYoAhAiASABIARLGyIBRQ0AIAUoAgwgBigCCCABEAcaIAUgBSgCDCABajYCDCAGIAYoAgggAWo2AgggBSAFKAIUIAFqNgIUIAUgBSgCECABazYCECAGIAYoAhAgAWsiATYCECABDQAgBiAGKAIENgIIC0EAIQRBACEJIAMoAhBFDQAMCgsgAygCHCgCJCEGIAMgAygCGCIBQQFqNgIYIAEgBmotAAAhASADIARBAWo2AhAgAygCBCAEaiABOgAAIAEEQCADKAIQIQQMAQsLIAMoAhwoAixFDQAgAygCECIGIAlNDQAgBQJ/IAUoAjAhBEEAIAMoAgQgCWoiAUUNABogBCABIAYgCWtB1IABKAIAEQAACzYCMAsgA0HnADYCIAsCQCADKAIcKAIsBEAgAygCDCADKAIQIgFBAmpJBH8gBRAKIAMoAhANAkEABSABCyADKAIEaiAFKAIwOwAAIAMgAygCEEECajYCECADQaABakHkgAEoAgARAQAaCyADQfEANgIgIAUQCiADKAIQRQ0BDAcLDAYLIAUoAgQNAQsgAygCPA0AIApFDQEgAygCIEGaBUYNAQsCfyADKAKIASIBRQRAIAMgChCFAQwBCwJAAkACQCADKAKMAUECaw4CAAECCwJ/AkADQAJAAkAgAygCPA0AIAMQLyADKAI8DQAgCg0BQQAMBAsgAygCSCADKAJoai0AACEEIAMgAygC8C0iAUEBajYC8C0gASADKALsLWpBADoAACADIAMoAvAtIgFBAWo2AvAtIAEgAygC7C1qQQA6AAAgAyADKALwLSIBQQFqNgLwLSABIAMoAuwtaiAEOgAAIAMgBEECdGoiASABLwHkAUEBajsB5AEgAyADKAI8QQFrNgI8IAMgAygCaEEBaiIBNgJoIAMoAvAtIAMoAvQtRw0BQQAhBCADIAMoAlgiBkEATgR/IAMoAkggBmoFQQALIAEgBmtBABAPIAMgAygCaDYCWCADKAIAEAogAygCACgCEA0BDAILCyADQQA2AoQuIApBBEYEQCADIAMoAlgiAUEATgR/IAMoAkggAWoFQQALIAMoAmggAWtBARAPIAMgAygCaDYCWCADKAIAEApBA0ECIAMoAgAoAhAbDAILIAMoAvAtBEBBACEEIAMgAygCWCIBQQBOBH8gAygCSCABagVBAAsgAygCaCABa0EAEA8gAyADKAJoNgJYIAMoAgAQCiADKAIAKAIQRQ0BC0EBIQQLIAQL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bGl0ZXJhbC9sZW5ndGhzIHNldABpbnZhbGlkIGNvZGUgbGVuZ3RocyBzZXQAdW5rbm93biBoZWFkZXIgZmxhZ3Mgc2V0AGludmFsaWQgZGlzdGFuY2VzIHNldABpbnZhbGlkIGJpdCBsZW5ndGggcmVwZWF0AEZpbGUgYWxyZWFkeSBleGlzdHMAdG9vIG1hbnkgbGVuZ3RoIG9yIGRpc3RhbmNlIHN5bWJvbHMAaW52YWxpZCBzdG9yZWQgYmxvY2sgbGVuZ3RocwAlcyVzJXMAYnVmZmVyIGVycm9yAE5vIGVycm9yAHN0cmVhbSBlcnJvcgBUZWxsIGVycm9yAEludGVybmFsIGVycm9yAFNlZWsgZXJyb3IAV3JpdGUgZXJyb3IAZmlsZSBlcnJvcgBSZWFkIGVycm9yAFpsaWIgZXJyb3IAZGF0YSBlcnJvcgBDUkMgZXJyb3IAaW5jb21wYXRpYmxlIHZlcnNpb24AaW52YWxpZCBjb2RlIC0tIG1pc3NpbmcgZW5kLW9mLWJsb2NrAGluY29ycmVjdCBoZWFkZXIgY2hlY2sAaW5jb3JyZWN0IGxlbmd0aCBjaGVjawBpbmNvcnJlY3QgZGF0YSBjaGVjawBpbnZhbGlkIGRpc3RhbmNlIHRvbyBmYXIgYmFjawBoZWFkZXIgY3JjIG1pc21hdGNoADEuMi4xMy56bGliLW5nAGludmFsaWQgd2luZG93IHNpemUAUmVhZC1vbmx5IGFyY2hpdmUATm90IGEgemlwIGFyY2hpdmUAUmVzb3VyY2Ugc3RpbGwgaW4gdXNlAE1hbGxvYyBmYWlsdXJlAGludmFsaWQgYmxvY2sgdHlwZQBGYWlsdXJlIHRvIGNyZWF0ZSB0ZW1wb3JhcnkgZmlsZQBDYW4ndCBvcGVuIGZpbGUATm8gc3VjaCBmaWxlAFByZW1hdHVyZSBlbmQgb2YgZmlsZQBDYW4ndCByZW1vdmUgZmlsZQBpbnZhbGlkIGxpdGVyYWwvbGVuZ3RoIGNvZGUAaW52YWxpZCBkaXN0YW5jZSBjb2RlAHVua25vd24gY29tcHJlc3Npb24gbWV0aG9kAHN0cmVhbSBlbmQAQ29tcHJlc3NlZCBkYXRhIGludmFsaWQATXVsdGktZGlzayB6aXAgYXJjaGl2ZXMgbm90IHN1cHBvcnRl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UgAIANIACAAyAAgAsgAIAHIACADyAAgACgAIAIoACABKAAgAygAIACoACACqAAgAagAIAOoACAAaAAgAmgAIAFoACADaAAgAOgAIALoACAB6AAgA+gAIAAYACACGAAgARgAIAMYACAAmAAgApgAIAGYACADmAAgAFgAIAJYACABWAAgA1gAIADYACAC2AAgAdgAIAPYACAAOAAgAjgAIAE4ACADOAAgALgAIAK4ACABuAAgA7gAIAB4ACACeAAgAXgAIAN4ACAA+AAgAvgAIAH4ACAD+AAgAAQAIAIEACABBAAgAwQAIACEACAChAAgAYQAIAOEACAARAAgAkQAIAFEACADRAAgAMQAIALEACABxAAgA8QAIAAkACACJAAgASQAIAMkACAApAAgAqQAIAGkACADpAAgAGQAIAJkACABZAAgA2QAIADkACAC5AAgAeQAIAPkACAAFAAgAhQAIAEUACADFAAgAJQAIAKUACABlAAgA5QAIABUACACVAAgAVQAIANUACAA1AAgAtQAIAHUACAD1AAgADQAIAI0ACABNAAgAzQAIAC0ACACtAAgAbQAIAO0ACAAdAAgAnQAIAF0ACADdAAgAPQAIAL0ACAB9AAgA/QAIABMACQATAQkAkwAJAJMBCQBTAAkAUwEJANMACQDTAQkAMwAJADMBCQCzAAkAswEJAHMACQBzAQkA8wAJAPMBCQALAAkACwEJAIsACQCLAQkASwAJAEsBCQDLAAkAywEJACsACQArAQkAqwAJAKsBCQBrAAkAawEJAOsACQDrAQkAGwAJABsBCQCbAAkAmwEJAFsACQBbAQkA2wAJANsBCQA7AAkAOwEJALsACQC7AQkAewAJAHsBCQD7AAkA+wEJAAcACQAHAQkAhwAJAIcBCQBHAAkARwEJAMcACQDHAQkAJwAJACcBCQCnAAkApwEJAGcACQBnAQkA5wAJAOcBCQAXAAkAFwEJAJcACQCXAQkAVwAJAFcBCQDXAAkA1wEJADcACQA3AQkAtwAJALcBCQB3AAkAdwEJAPcACQD3AQkADwAJAA8BCQCPAAkAjwEJAE8ACQBPAQkAzwAJAM8BCQAvAAkALwEJAK8ACQCvAQkAbwAJAG8BCQDvAAkA7wEJAB8ACQAfAQkAnwAJAJ8BCQBfAAkAXwEJAN8ACQDfAQkAPwAJAD8BCQC/AAkA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SXMgYSBkaXJlY3RvcnkAVGV4dCBmaWxlIGJ1c3kARXhlYyBmb3JtYXQgZXJyb3IASW52YWxpZCBhcmd1bWVudABBcmd1bWVudCBsaXN0IHRvbyBsb25nAFN5bWJvbGljIGxpbmsgbG9vcABGaWxlbmFtZSB0b28gbG9uZwBUb28gbWFueSBvcGVuIGZpbGVzIGluIHN5c3RlbQBObyBmaWxlIGRlc2NyaXB0b3JzIGF2YWlsYWJsZQBCYWQgZmlsZSBkZXNjcmlwdG9yAE5vIGNoaWxkIHByb2Nlc3MAQmFkIGFkZHJlc3MARmlsZSB0b28gbGFyZ2UAVG9vIG1hbnkgbGlua3MATm8gbG9ja3MgYXZhaWxhYmxlAFJlc291cmNlIGRlYWRsb2NrIHdvdWxkIG9jY3VyAFN0YXRlIG5vdCByZWNvdmVyYWJsZQBQcmV2aW91cyBvd25lciBkaWVkAE9wZXJhdGlvbiBjYW5jZWxlZABGdW5jdGlvbiBub3QgaW1wbGVtZW50ZWQATm8gbWVzc2FnZSBvZiBkZXNpcmVkIHR5cGUASWRlbnRpZmllciByZW1vdmVkAERldmljZSBub3QgYSBzdHJlYW0ATm8gZGF0YSBhdmFpbGFibGUARGV2aWNlIHRpbWVvdXQAT3V0IG9mIHN0cmVhbXMgcmVzb3VyY2VzAExpbmsgaGFzIGJlZW4gc2V2ZXJlZABQcm90b2NvbCBlcnJvcgBCYWQgbWVzc2FnZQBGaWxlIGRlc2NyaXB0b3IgaW4gYmFkIHN0YXRlAE5vdCBhIHNvY2tldABEZXN0aW5hdGlvbiBhZGRyZXNzIHJlcXVpcmVkAE1lc3NhZ2UgdG9vIGxhcmdlAFByb3RvY29sIHdyb25nIHR5cGUgZm9yIHNvY2tldABQcm90b2NvbCBub3QgYXZhaWxhYmxlAFByb3RvY29sIG5vdCBzdXBwb3J0ZWQAU29ja2V0IHR5cGUgbm90IHN1cHBvcnRlZABOb3Qgc3VwcG9ydGVkAFByb3RvY29sIGZhbWlseSBub3Qgc3VwcG9ydGVkAEFkZHJlc3MgZmFtaWx5IG5vdCBzdXBwb3J0ZWQgYnkgcHJvdG9jb2wAQWRkcmVzcyBub3QgYXZhaWxhYmxlAE5ldHdvcmsgaXMgZG93bgBOZXR3b3JrIHVucmVhY2hhYmxlAENvbm5lY3Rpb24gcmVzZXQgYnkgbmV0d29yawBDb25uZWN0aW9uIGFib3J0ZWQATm8gYnVmZmVyIHNwYWNlIGF2YWlsYWJsZQBTb2NrZXQgaXMgY29ubmVjdGVkAFNvY2tldCBub3QgY29ubmVjdGVkAENhbm5vdCBzZW5kIGFmdGVyIHNvY2tldCBzaHV0ZG93bgBPcGVyYXRpb24gYWxyZWFkeSBpbiBwcm9ncmVzcwBPcGVyYXRpb24gaW4gcHJvZ3Jlc3MAU3RhbGUgZmlsZSBoYW5kbGUAUmVtb3RlIEkvTyBlcnJvcgBRdW90YSBleGNlZWRlZABObyBtZWRpdW0gZm91bmQAV3JvbmcgbWVkaXVtIHR5cGUATm8gZXJyb3IgaW5mb3JtYXRpb24AQcCAAQuFARMAAAAUAAAAFQAAABYAAAAXAAAAGAAAABkAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAgERQADEAAAAyAAAAMwAAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADIAAAAzAAAANAAAADUAAAA2AAAANwAAADgAQfSCAQsCXEQAQbCDAQsQ/////////////////////w==";Bo(Hi)||(Hi=P(Hi));function to(Je){try{if(Je==Hi&&ce)return new Uint8Array(ce);var st=Me(Je);if(st)return st;if(R)return R(Je);throw"sync fetching of the wasm failed: you can preload it to Module['wasmBinary'] manually, or emcc.py will do that for you when generating HTML (but not JS)"}catch(St){rs(St)}}function vo(Je,st){var St,lr,ee;try{ee=to(Je),lr=new WebAssembly.Module(ee),St=new WebAssembly.Instance(lr,st)}catch(Oe){var Ie=Oe.toString();throw te("failed to compile wasm module: "+Ie),(Ie.includes("imported Memory")||Ie.includes("memory import"))&&te("Memory size incompatibility issues may be due to changing INITIAL_MEMORY at runtime to something too large. Use ALLOW_MEMORY_GROWTH to allow any size memory (and also make sure not to set INITIAL_MEMORY at runtime to something smaller than it was at compile time)."),Oe}return[St,lr]}function RA(){var Je={a:fu};function st(ee,Ie){var Oe=ee.exports;r.asm=Oe,Be=r.asm.g,z(Be.buffer),$=r.asm.W,gn(r.asm.h),wo("wasm-instantiate")}if(oi("wasm-instantiate"),r.instantiateWasm)try{var St=r.instantiateWasm(Je,st);return St}catch(ee){return te("Module.instantiateWasm callback failed with error: "+ee),!1}var lr=vo(Hi,Je);return st(lr[0]),r.asm}function pf(Je){return F.getFloat32(Je,!0)}function Eh(Je){return F.getFloat64(Je,!0)}function Ih(Je){return F.getInt16(Je,!0)}function ro(Je){return F.getInt32(Je,!0)}function jn(Je,st){F.setInt32(Je,st,!0)}function Rs(Je){for(;Je.length>0;){var st=Je.shift();if(typeof st=="function"){st(r);continue}var St=st.func;typeof St=="number"?st.arg===void 0?$.get(St)():$.get(St)(st.arg):St(st.arg===void 0?null:st.arg)}}function no(Je,st){var St=new Date(ro((Je>>2)*4)*1e3);jn((st>>2)*4,St.getUTCSeconds()),jn((st+4>>2)*4,St.getUTCMinutes()),jn((st+8>>2)*4,St.getUTCHours()),jn((st+12>>2)*4,St.getUTCDate()),jn((st+16>>2)*4,St.getUTCMonth()),jn((st+20>>2)*4,St.getUTCFullYear()-1900),jn((st+24>>2)*4,St.getUTCDay()),jn((st+36>>2)*4,0),jn((st+32>>2)*4,0);var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),ee=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((st+28>>2)*4,ee),no.GMTString||(no.GMTString=rt("GMT")),jn((st+40>>2)*4,no.GMTString),st}function lu(Je,st){return no(Je,st)}function cu(Je,st,St){ke.copyWithin(Je,st,st+St)}function uu(Je){try{return Be.grow(Je-Pe.byteLength+65535>>>16),z(Be.buffer),1}catch{}}function FA(Je){var st=ke.length;Je=Je>>>0;var St=2147483648;if(Je>St)return!1;for(var lr=1;lr<=4;lr*=2){var ee=st*(1+.2/lr);ee=Math.min(ee,Je+100663296);var Ie=Math.min(St,Ne(Math.max(Je,ee),65536)),Oe=uu(Ie);if(Oe)return!0}return!1}function NA(Je){Ae(Je)}function aa(Je){var st=Date.now()/1e3|0;return Je&&jn((Je>>2)*4,st),st}function la(){if(la.called)return;la.called=!0;var Je=new Date().getFullYear(),st=new Date(Je,0,1),St=new Date(Je,6,1),lr=st.getTimezoneOffset(),ee=St.getTimezoneOffset(),Ie=Math.max(lr,ee);jn((Sl()>>2)*4,Ie*60),jn((Cs()>>2)*4,+(lr!=ee));function Oe(fn){var ai=fn.toTimeString().match(/\(([A-Za-z ]+)\)$/);return ai?ai[1]:"GMT"}var ht=Oe(st),mt=Oe(St),Dt=rt(ht),tr=rt(mt);ee>2)*4,Dt),jn((Li()+4>>2)*4,tr)):(jn((Li()>>2)*4,tr),jn((Li()+4>>2)*4,Dt))}function OA(Je){la();var st=Date.UTC(ro((Je+20>>2)*4)+1900,ro((Je+16>>2)*4),ro((Je+12>>2)*4),ro((Je+8>>2)*4),ro((Je+4>>2)*4),ro((Je>>2)*4),0),St=new Date(st);jn((Je+24>>2)*4,St.getUTCDay());var lr=Date.UTC(St.getUTCFullYear(),0,1,0,0,0,0),ee=(St.getTime()-lr)/(1e3*60*60*24)|0;return jn((Je+28>>2)*4,ee),St.getTime()/1e3|0}var gr=typeof atob=="function"?atob:function(Je){var st="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",St="",lr,ee,Ie,Oe,ht,mt,Dt,tr=0;Je=Je.replace(/[^A-Za-z0-9\+\/\=]/g,"");do Oe=st.indexOf(Je.charAt(tr++)),ht=st.indexOf(Je.charAt(tr++)),mt=st.indexOf(Je.charAt(tr++)),Dt=st.indexOf(Je.charAt(tr++)),lr=Oe<<2|ht>>4,ee=(ht&15)<<4|mt>>2,Ie=(mt&3)<<6|Dt,St=St+String.fromCharCode(lr),mt!==64&&(St=St+String.fromCharCode(ee)),Dt!==64&&(St=St+String.fromCharCode(Ie));while(tr0||(Et(),Ir>0))return;function st(){Qn||(Qn=!0,r.calledRun=!0,!Ce&&(qt(),s(r),r.onRuntimeInitialized&&r.onRuntimeInitialized(),ir()))}r.setStatus?(r.setStatus("Running..."),setTimeout(function(){setTimeout(function(){r.setStatus("")},1),st()},1)):st()}if(r.run=pc,r.preInit)for(typeof r.preInit=="function"&&(r.preInit=[r.preInit]);r.preInit.length>0;)r.preInit.pop()();return pc(),e}}();typeof wT=="object"&&typeof Jj=="object"?Jj.exports=Kj:typeof define=="function"&&define.amd?define([],function(){return Kj}):typeof wT=="object"&&(wT.createModule=Kj)});var jp,Hde,jde,qde=It(()=>{jp=["number","number"],Hde=(X=>(X[X.ZIP_ER_OK=0]="ZIP_ER_OK",X[X.ZIP_ER_MULTIDISK=1]="ZIP_ER_MULTIDISK",X[X.ZIP_ER_RENAME=2]="ZIP_ER_RENAME",X[X.ZIP_ER_CLOSE=3]="ZIP_ER_CLOSE",X[X.ZIP_ER_SEEK=4]="ZIP_ER_SEEK",X[X.ZIP_ER_READ=5]="ZIP_ER_READ",X[X.ZIP_ER_WRITE=6]="ZIP_ER_WRITE",X[X.ZIP_ER_CRC=7]="ZIP_ER_CRC",X[X.ZIP_ER_ZIPCLOSED=8]="ZIP_ER_ZIPCLOSED",X[X.ZIP_ER_NOENT=9]="ZIP_ER_NOENT",X[X.ZIP_ER_EXISTS=10]="ZIP_ER_EXISTS",X[X.ZIP_ER_OPEN=11]="ZIP_ER_OPEN",X[X.ZIP_ER_TMPOPEN=12]="ZIP_ER_TMPOPEN",X[X.ZIP_ER_ZLIB=13]="ZIP_ER_ZLIB",X[X.ZIP_ER_MEMORY=14]="ZIP_ER_MEMORY",X[X.ZIP_ER_CHANGED=15]="ZIP_ER_CHANGED",X[X.ZIP_ER_COMPNOTSUPP=16]="ZIP_ER_COMPNOTSUPP",X[X.ZIP_ER_EOF=17]="ZIP_ER_EOF",X[X.ZIP_ER_INVAL=18]="ZIP_ER_INVAL",X[X.ZIP_ER_NOZIP=19]="ZIP_ER_NOZIP",X[X.ZIP_ER_INTERNAL=20]="ZIP_ER_INTERNAL",X[X.ZIP_ER_INCONS=21]="ZIP_ER_INCONS",X[X.ZIP_ER_REMOVE=22]="ZIP_ER_REMOVE",X[X.ZIP_ER_DELETED=23]="ZIP_ER_DELETED",X[X.ZIP_ER_ENCRNOTSUPP=24]="ZIP_ER_ENCRNOTSUPP",X[X.ZIP_ER_RDONLY=25]="ZIP_ER_RDONLY",X[X.ZIP_ER_NOPASSWD=26]="ZIP_ER_NOPASSWD",X[X.ZIP_ER_WRONGPASSWD=27]="ZIP_ER_WRONGPASSWD",X[X.ZIP_ER_OPNOTSUPP=28]="ZIP_ER_OPNOTSUPP",X[X.ZIP_ER_INUSE=29]="ZIP_ER_INUSE",X[X.ZIP_ER_TELL=30]="ZIP_ER_TELL",X[X.ZIP_ER_COMPRESSED_DATA=31]="ZIP_ER_COMPRESSED_DATA",X))(Hde||{}),jde=t=>({get HEAPU8(){return t.HEAPU8},errors:Hde,SEEK_SET:0,SEEK_CUR:1,SEEK_END:2,ZIP_CHECKCONS:4,ZIP_EXCL:2,ZIP_RDONLY:16,ZIP_FL_OVERWRITE:8192,ZIP_FL_COMPRESSED:4,ZIP_OPSYS_DOS:0,ZIP_OPSYS_AMIGA:1,ZIP_OPSYS_OPENVMS:2,ZIP_OPSYS_UNIX:3,ZIP_OPSYS_VM_CMS:4,ZIP_OPSYS_ATARI_ST:5,ZIP_OPSYS_OS_2:6,ZIP_OPSYS_MACINTOSH:7,ZIP_OPSYS_Z_SYSTEM:8,ZIP_OPSYS_CPM:9,ZIP_OPSYS_WINDOWS_NTFS:10,ZIP_OPSYS_MVS:11,ZIP_OPSYS_VSE:12,ZIP_OPSYS_ACORN_RISC:13,ZIP_OPSYS_VFAT:14,ZIP_OPSYS_ALTERNATE_MVS:15,ZIP_OPSYS_BEOS:16,ZIP_OPSYS_TANDEM:17,ZIP_OPSYS_OS_400:18,ZIP_OPSYS_OS_X:19,ZIP_CM_DEFAULT:-1,ZIP_CM_STORE:0,ZIP_CM_DEFLATE:8,uint08S:t._malloc(1),uint32S:t._malloc(4),malloc:t._malloc,free:t._free,getValue:t.getValue,openFromSource:t.cwrap("zip_open_from_source","number",["number","number","number"]),close:t.cwrap("zip_close","number",["number"]),discard:t.cwrap("zip_discard",null,["number"]),getError:t.cwrap("zip_get_error","number",["number"]),getName:t.cwrap("zip_get_name","string",["number","number","number"]),getNumEntries:t.cwrap("zip_get_num_entries","number",["number","number"]),delete:t.cwrap("zip_delete","number",["number","number"]),statIndex:t.cwrap("zip_stat_index","number",["number",...jp,"number","number"]),fopenIndex:t.cwrap("zip_fopen_index","number",["number",...jp,"number"]),fread:t.cwrap("zip_fread","number",["number","number","number","number"]),fclose:t.cwrap("zip_fclose","number",["number"]),dir:{add:t.cwrap("zip_dir_add","number",["number","string"])},file:{add:t.cwrap("zip_file_add","number",["number","string","number","number"]),getError:t.cwrap("zip_file_get_error","number",["number"]),getExternalAttributes:t.cwrap("zip_file_get_external_attributes","number",["number",...jp,"number","number","number"]),setExternalAttributes:t.cwrap("zip_file_set_external_attributes","number",["number",...jp,"number","number","number"]),setMtime:t.cwrap("zip_file_set_mtime","number",["number",...jp,"number","number"]),setCompression:t.cwrap("zip_set_file_compression","number",["number",...jp,"number","number"])},ext:{countSymlinks:t.cwrap("zip_ext_count_symlinks","number",["number"])},error:{initWithCode:t.cwrap("zip_error_init_with_code",null,["number","number"]),strerror:t.cwrap("zip_error_strerror","string",["number"])},name:{locate:t.cwrap("zip_name_locate","number",["number","string","number"])},source:{fromUnattachedBuffer:t.cwrap("zip_source_buffer_create","number",["number",...jp,"number","number"]),fromBuffer:t.cwrap("zip_source_buffer","number",["number","number",...jp,"number"]),free:t.cwrap("zip_source_free",null,["number"]),keep:t.cwrap("zip_source_keep",null,["number"]),open:t.cwrap("zip_source_open","number",["number"]),close:t.cwrap("zip_source_close","number",["number"]),seek:t.cwrap("zip_source_seek","number",["number",...jp,"number"]),tell:t.cwrap("zip_source_tell","number",["number"]),read:t.cwrap("zip_source_read","number",["number","number","number"]),error:t.cwrap("zip_source_error","number",["number"])},struct:{statS:t.cwrap("zipstruct_statS","number",[]),statSize:t.cwrap("zipstruct_stat_size","number",["number"]),statCompSize:t.cwrap("zipstruct_stat_comp_size","number",["number"]),statCompMethod:t.cwrap("zipstruct_stat_comp_method","number",["number"]),statMtime:t.cwrap("zipstruct_stat_mtime","number",["number"]),statCrc:t.cwrap("zipstruct_stat_crc","number",["number"]),errorS:t.cwrap("zipstruct_errorS","number",[]),errorCodeZip:t.cwrap("zipstruct_error_code_zip","number",["number"])}})});function zj(t,e){let r=t.indexOf(e);if(r<=0)return null;let s=r;for(;r>=0&&(s=r+e.length,t[s]!==K.sep);){if(t[r-1]===K.sep)return null;r=t.indexOf(e,s)}return t.length>s&&t[s]!==K.sep?null:t.slice(0,s)}var tA,Gde=It(()=>{bt();bt();rA();tA=class t extends n0{static async openPromise(e,r){let s=new t(r);try{return await e(s)}finally{s.saveAndClose()}}constructor(e={}){let r=e.fileExtensions,s=e.readOnlyArchives,a=typeof r>"u"?f=>zj(f,".zip"):f=>{for(let p of r){let h=zj(f,p);if(h)return h}return null},n=(f,p)=>new ps(p,{baseFs:f,readOnly:s,stats:f.statSync(p),customZipImplementation:e.customZipImplementation}),c=async(f,p)=>{let h={baseFs:f,readOnly:s,stats:await f.statPromise(p),customZipImplementation:e.customZipImplementation};return()=>new ps(p,h)};super({...e,factorySync:n,factoryPromise:c,getMountPoint:a})}}});var Zj,vI,Xj=It(()=>{Vj();Zj=class extends Error{constructor(e,r){super(e),this.name="Libzip Error",this.code=r}},vI=class{constructor(e){this.filesShouldBeCached=!0;let r="buffer"in e?e.buffer:e.baseFs.readFileSync(e.path);this.libzip=Bv();let s=this.libzip.malloc(4);try{let c=0;e.readOnly&&(c|=this.libzip.ZIP_RDONLY);let f=this.allocateUnattachedSource(r);try{this.zip=this.libzip.openFromSource(f,c,s),this.lzSource=f}catch(p){throw this.libzip.source.free(f),p}if(this.zip===0){let p=this.libzip.struct.errorS();throw this.libzip.error.initWithCode(p,this.libzip.getValue(s,"i32")),this.makeLibzipError(p)}}finally{this.libzip.free(s)}let a=this.libzip.getNumEntries(this.zip,0),n=new Array(a);for(let c=0;c>>0,n=this.libzip.struct.statMtime(r)>>>0,c=this.libzip.struct.statCrc(r)>>>0;return{size:a,mtime:n,crc:c}}makeLibzipError(e){let r=this.libzip.struct.errorCodeZip(e),s=this.libzip.error.strerror(e),a=new Zj(s,this.libzip.errors[r]);if(r===this.libzip.errors.ZIP_ER_CHANGED)throw new Error(`Assertion failed: Unexpected libzip error: ${a.message}`);return a}setFileSource(e,r,s){let a=this.allocateSource(s);try{let n=this.libzip.file.add(this.zip,e,a,this.libzip.ZIP_FL_OVERWRITE);if(n===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(r!==null&&this.libzip.file.setCompression(this.zip,n,0,r[0],r[1])===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return n}catch(n){throw this.libzip.source.free(a),n}}setMtime(e,r){if(this.libzip.file.setMtime(this.zip,e,0,r,0)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}getExternalAttributes(e){if(this.libzip.file.getExternalAttributes(this.zip,e,0,0,this.libzip.uint08S,this.libzip.uint32S)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let s=this.libzip.getValue(this.libzip.uint08S,"i8")>>>0,a=this.libzip.getValue(this.libzip.uint32S,"i32")>>>0;return[s,a]}setExternalAttributes(e,r,s){if(this.libzip.file.setExternalAttributes(this.zip,e,0,0,r,s)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}locate(e){return this.libzip.name.locate(this.zip,e,0)}getFileSource(e){let r=this.libzip.struct.statS();if(this.libzip.statIndex(this.zip,e,0,0,r)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));let a=this.libzip.struct.statCompSize(r),n=this.libzip.struct.statCompMethod(r),c=this.libzip.malloc(a);try{let f=this.libzip.fopenIndex(this.zip,e,0,this.libzip.ZIP_FL_COMPRESSED);if(f===0)throw this.makeLibzipError(this.libzip.getError(this.zip));try{let p=this.libzip.fread(f,c,a,0);if(p===-1)throw this.makeLibzipError(this.libzip.file.getError(f));if(pa)throw new Error("Overread");let h=this.libzip.HEAPU8.subarray(c,c+a);return{data:Buffer.from(h),compressionMethod:n}}finally{this.libzip.fclose(f)}}finally{this.libzip.free(c)}}deleteEntry(e){if(this.libzip.delete(this.zip,e)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip))}addDirectory(e){let r=this.libzip.dir.add(this.zip,e);if(r===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));return r}getBufferAndClose(){try{if(this.libzip.source.keep(this.lzSource),this.libzip.close(this.zip)===-1)throw this.makeLibzipError(this.libzip.getError(this.zip));if(this.libzip.source.open(this.lzSource)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_END)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let e=this.libzip.source.tell(this.lzSource);if(e===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(this.libzip.source.seek(this.lzSource,0,0,this.libzip.SEEK_SET)===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));let r=this.libzip.malloc(e);if(!r)throw new Error("Couldn't allocate enough memory");try{let s=this.libzip.source.read(this.lzSource,r,e);if(s===-1)throw this.makeLibzipError(this.libzip.source.error(this.lzSource));if(se)throw new Error("Overread");let a=Buffer.from(this.libzip.HEAPU8.subarray(r,r+e));return process.env.YARN_IS_TEST_ENV&&process.env.YARN_ZIP_DATA_EPILOGUE&&(a=Buffer.concat([a,Buffer.from(process.env.YARN_ZIP_DATA_EPILOGUE)])),a}finally{this.libzip.free(r)}}finally{this.libzip.source.close(this.lzSource),this.libzip.source.free(this.lzSource)}}allocateBuffer(e){Buffer.isBuffer(e)||(e=Buffer.from(e));let r=this.libzip.malloc(e.byteLength);if(!r)throw new Error("Couldn't allocate enough memory");return new Uint8Array(this.libzip.HEAPU8.buffer,r,e.byteLength).set(e),{buffer:r,byteLength:e.byteLength}}allocateUnattachedSource(e){let r=this.libzip.struct.errorS(),{buffer:s,byteLength:a}=this.allocateBuffer(e),n=this.libzip.source.fromUnattachedBuffer(s,a,0,1,r);if(n===0)throw this.libzip.free(r),this.makeLibzipError(r);return n}allocateSource(e){let{buffer:r,byteLength:s}=this.allocateBuffer(e),a=this.libzip.source.fromBuffer(this.zip,r,s,0,1);if(a===0)throw this.libzip.free(r),this.makeLibzipError(this.libzip.getError(this.zip));return a}discard(){this.libzip.discard(this.zip)}}});function vmt(t){if(typeof t=="string"&&String(+t)===t)return+t;if(typeof t=="number"&&Number.isFinite(t))return t<0?Date.now()/1e3:t;if(Wde.types.isDate(t))return t.getTime()/1e3;throw new Error("Invalid time")}function BT(){return Buffer.from([80,75,5,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])}var ka,$j,Wde,e6,lm,t6,r6,Yde,ps,vT=It(()=>{bt();bt();bt();bt();bt();bt();ka=ye("fs"),$j=ye("stream"),Wde=ye("util"),e6=et(ye("zlib"));Xj();lm=3,t6=0,r6=8,Yde="mixed";ps=class extends Uf{constructor(r,s={}){super();this.listings=new Map;this.entries=new Map;this.fileSources=new Map;this.fds=new Map;this.nextFd=0;this.ready=!1;this.readOnly=!1;s.readOnly&&(this.readOnly=!0);let a=s;this.level=typeof a.level<"u"?a.level:Yde;let n=s.customZipImplementation??vI;if(typeof r=="string"){let{baseFs:f=new Yn}=a;this.baseFs=f,this.path=r}else this.path=null,this.baseFs=null;if(s.stats)this.stats=s.stats;else if(typeof r=="string")try{this.stats=this.baseFs.statSync(r)}catch(f){if(f.code==="ENOENT"&&a.create)this.stats=el.makeDefaultStats();else throw f}else this.stats=el.makeDefaultStats();typeof r=="string"?s.create?this.zipImpl=new n({buffer:BT(),readOnly:this.readOnly}):this.zipImpl=new n({path:r,baseFs:this.baseFs,readOnly:this.readOnly,size:this.stats.size}):this.zipImpl=new n({buffer:r??BT(),readOnly:this.readOnly}),this.listings.set(vt.root,new Set);let c=this.zipImpl.getListings();for(let f=0;f{this.closeSync(f)}})}async readPromise(r,s,a,n,c){return this.readSync(r,s,a,n,c)}readSync(r,s,a=0,n=s.byteLength,c=-1){let f=this.fds.get(r);if(typeof f>"u")throw or.EBADF("read");let p=c===-1||c===null?f.cursor:c,h=this.readFileSync(f.p);h.copy(s,a,p,p+n);let E=Math.max(0,Math.min(h.length-p,n));return(c===-1||c===null)&&(f.cursor+=E),E}async writePromise(r,s,a,n,c){return typeof s=="string"?this.writeSync(r,s,c):this.writeSync(r,s,a,n,c)}writeSync(r,s,a,n,c){throw typeof this.fds.get(r)>"u"?or.EBADF("read"):new Error("Unimplemented")}async closePromise(r){return this.closeSync(r)}closeSync(r){if(typeof this.fds.get(r)>"u")throw or.EBADF("read");this.fds.delete(r)}createReadStream(r,{encoding:s}={}){if(r===null)throw new Error("Unimplemented");let a=this.openSync(r,"r"),n=Object.assign(new $j.PassThrough({emitClose:!0,autoDestroy:!0,destroy:(f,p)=>{clearImmediate(c),this.closeSync(a),p(f)}}),{close(){n.destroy()},bytesRead:0,path:r,pending:!1}),c=setImmediate(async()=>{try{let f=await this.readFilePromise(r,s);n.bytesRead=f.length,n.end(f)}catch(f){n.destroy(f)}});return n}createWriteStream(r,{encoding:s}={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);if(r===null)throw new Error("Unimplemented");let a=[],n=this.openSync(r,"w"),c=Object.assign(new $j.PassThrough({autoDestroy:!0,emitClose:!0,destroy:(f,p)=>{try{f?p(f):(this.writeFileSync(r,Buffer.concat(a),s),p(null))}catch(h){p(h)}finally{this.closeSync(n)}}}),{close(){c.destroy()},bytesWritten:0,path:r,pending:!1});return c.on("data",f=>{let p=Buffer.from(f);c.bytesWritten+=p.length,a.push(p)}),c}async realpathPromise(r){return this.realpathSync(r)}realpathSync(r){let s=this.resolveFilename(`lstat '${r}'`,r);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`lstat '${r}'`);return s}async existsPromise(r){return this.existsSync(r)}existsSync(r){if(!this.ready)throw or.EBUSY(`archive closed, existsSync '${r}'`);if(this.symlinkCount===0){let a=K.resolve(vt.root,r);return this.entries.has(a)||this.listings.has(a)}let s;try{s=this.resolveFilename(`stat '${r}'`,r,void 0,!1)}catch{return!1}return s===void 0?!1:this.entries.has(s)||this.listings.has(s)}async accessPromise(r,s){return this.accessSync(r,s)}accessSync(r,s=ka.constants.F_OK){let a=this.resolveFilename(`access '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`access '${r}'`);if(this.readOnly&&s&ka.constants.W_OK)throw or.EROFS(`access '${r}'`)}async statPromise(r,s={bigint:!1}){return s.bigint?this.statSync(r,{bigint:!0}):this.statSync(r)}statSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`stat '${r}'`,r,void 0,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`stat '${r}'`)}if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`stat '${r}'`);return this.statImpl(`stat '${r}'`,a,s)}}async fstatPromise(r,s){return this.fstatSync(r,s)}fstatSync(r,s){let a=this.fds.get(r);if(typeof a>"u")throw or.EBADF("fstatSync");let{p:n}=a,c=this.resolveFilename(`stat '${n}'`,n);if(!this.entries.has(c)&&!this.listings.has(c))throw or.ENOENT(`stat '${n}'`);if(n[n.length-1]==="/"&&!this.listings.has(c))throw or.ENOTDIR(`stat '${n}'`);return this.statImpl(`fstat '${n}'`,c,s)}async lstatPromise(r,s={bigint:!1}){return s.bigint?this.lstatSync(r,{bigint:!0}):this.lstatSync(r)}lstatSync(r,s={bigint:!1,throwIfNoEntry:!0}){let a=this.resolveFilename(`lstat '${r}'`,r,!1,s.throwIfNoEntry);if(a!==void 0){if(!this.entries.has(a)&&!this.listings.has(a)){if(s.throwIfNoEntry===!1)return;throw or.ENOENT(`lstat '${r}'`)}if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`lstat '${r}'`);return this.statImpl(`lstat '${r}'`,a,s)}}statImpl(r,s,a={}){let n=this.entries.get(s);if(typeof n<"u"){let c=this.zipImpl.stat(n),f=c.crc,p=c.size,h=c.mtime*1e3,E=this.stats.uid,C=this.stats.gid,S=512,P=Math.ceil(c.size/S),I=h,R=h,N=h,U=new Date(I),W=new Date(R),te=new Date(N),ie=new Date(h),Ae=this.listings.has(s)?ka.constants.S_IFDIR:this.isSymbolicLink(n)?ka.constants.S_IFLNK:ka.constants.S_IFREG,ce=Ae===ka.constants.S_IFDIR?493:420,me=Ae|this.getUnixMode(n,ce)&511,pe=Object.assign(new el.StatEntry,{uid:E,gid:C,size:p,blksize:S,blocks:P,atime:U,birthtime:W,ctime:te,mtime:ie,atimeMs:I,birthtimeMs:R,ctimeMs:N,mtimeMs:h,mode:me,crc:f});return a.bigint===!0?el.convertToBigIntStats(pe):pe}if(this.listings.has(s)){let c=this.stats.uid,f=this.stats.gid,p=0,h=512,E=0,C=this.stats.mtimeMs,S=this.stats.mtimeMs,P=this.stats.mtimeMs,I=this.stats.mtimeMs,R=new Date(C),N=new Date(S),U=new Date(P),W=new Date(I),te=ka.constants.S_IFDIR|493,Ae=Object.assign(new el.StatEntry,{uid:c,gid:f,size:p,blksize:h,blocks:E,atime:R,birthtime:N,ctime:U,mtime:W,atimeMs:C,birthtimeMs:S,ctimeMs:P,mtimeMs:I,mode:te,crc:0});return a.bigint===!0?el.convertToBigIntStats(Ae):Ae}throw new Error("Unreachable")}getUnixMode(r,s){let[a,n]=this.zipImpl.getExternalAttributes(r);return a!==lm?s:n>>>16}registerListing(r){let s=this.listings.get(r);if(s)return s;this.registerListing(K.dirname(r)).add(K.basename(r));let n=new Set;return this.listings.set(r,n),n}registerEntry(r,s){this.registerListing(K.dirname(r)).add(K.basename(r)),this.entries.set(r,s)}unregisterListing(r){this.listings.delete(r),this.listings.get(K.dirname(r))?.delete(K.basename(r))}unregisterEntry(r){this.unregisterListing(r);let s=this.entries.get(r);this.entries.delete(r),!(typeof s>"u")&&(this.fileSources.delete(s),this.isSymbolicLink(s)&&this.symlinkCount--)}deleteEntry(r,s){this.unregisterEntry(r),this.zipImpl.deleteEntry(s)}resolveFilename(r,s,a=!0,n=!0){if(!this.ready)throw or.EBUSY(`archive closed, ${r}`);let c=K.resolve(vt.root,s);if(c==="/")return vt.root;let f=this.entries.get(c);if(a&&f!==void 0)if(this.symlinkCount!==0&&this.isSymbolicLink(f)){let p=this.getFileSource(f).toString();return this.resolveFilename(r,K.resolve(K.dirname(c),p),!0,n)}else return c;for(;;){let p=this.resolveFilename(r,K.dirname(c),!0,n);if(p===void 0)return p;let h=this.listings.has(p),E=this.entries.has(p);if(!h&&!E){if(n===!1)return;throw or.ENOENT(r)}if(!h)throw or.ENOTDIR(r);if(c=K.resolve(p,K.basename(c)),!a||this.symlinkCount===0)break;let C=this.zipImpl.locate(c.slice(1));if(C===-1)break;if(this.isSymbolicLink(C)){let S=this.getFileSource(C).toString();c=K.resolve(K.dirname(c),S)}else break}return c}setFileSource(r,s){let a=Buffer.isBuffer(s)?s:Buffer.from(s),n=K.relative(vt.root,r),c=null;this.level!=="mixed"&&(c=[this.level===0?t6:r6,this.level]);let f=this.zipImpl.setFileSource(n,c,a);return this.fileSources.set(f,a),f}isSymbolicLink(r){if(this.symlinkCount===0)return!1;let[s,a]=this.zipImpl.getExternalAttributes(r);return s!==lm?!1:(a>>>16&ka.constants.S_IFMT)===ka.constants.S_IFLNK}getFileSource(r,s={asyncDecompress:!1}){let a=this.fileSources.get(r);if(typeof a<"u")return a;let{data:n,compressionMethod:c}=this.zipImpl.getFileSource(r);if(c===t6)return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,n),n;if(c===r6){if(s.asyncDecompress)return new Promise((f,p)=>{e6.default.inflateRaw(n,(h,E)=>{h?p(h):(this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,E),f(E))})});{let f=e6.default.inflateRawSync(n);return this.zipImpl.filesShouldBeCached&&this.fileSources.set(r,f),f}}else throw new Error(`Unsupported compression method: ${c}`)}async fchmodPromise(r,s){return this.chmodPromise(this.fdToPath(r,"fchmod"),s)}fchmodSync(r,s){return this.chmodSync(this.fdToPath(r,"fchmodSync"),s)}async chmodPromise(r,s){return this.chmodSync(r,s)}chmodSync(r,s){if(this.readOnly)throw or.EROFS(`chmod '${r}'`);s&=493;let a=this.resolveFilename(`chmod '${r}'`,r,!1),n=this.entries.get(a);if(typeof n>"u")throw new Error(`Assertion failed: The entry should have been registered (${a})`);let f=this.getUnixMode(n,ka.constants.S_IFREG|0)&-512|s;this.zipImpl.setExternalAttributes(n,lm,f<<16)}async fchownPromise(r,s,a){return this.chownPromise(this.fdToPath(r,"fchown"),s,a)}fchownSync(r,s,a){return this.chownSync(this.fdToPath(r,"fchownSync"),s,a)}async chownPromise(r,s,a){return this.chownSync(r,s,a)}chownSync(r,s,a){throw new Error("Unimplemented")}async renamePromise(r,s){return this.renameSync(r,s)}renameSync(r,s){throw new Error("Unimplemented")}async copyFilePromise(r,s,a){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=await this.getFileSource(n,{asyncDecompress:!0}),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}copyFileSync(r,s,a=0){let{indexSource:n,indexDest:c,resolvedDestP:f}=this.prepareCopyFile(r,s,a),p=this.getFileSource(n),h=this.setFileSource(f,p);h!==c&&this.registerEntry(f,h)}prepareCopyFile(r,s,a=0){if(this.readOnly)throw or.EROFS(`copyfile '${r} -> '${s}'`);if(a&ka.constants.COPYFILE_FICLONE_FORCE)throw or.ENOSYS("unsupported clone operation",`copyfile '${r}' -> ${s}'`);let n=this.resolveFilename(`copyfile '${r} -> ${s}'`,r),c=this.entries.get(n);if(typeof c>"u")throw or.EINVAL(`copyfile '${r}' -> '${s}'`);let f=this.resolveFilename(`copyfile '${r}' -> ${s}'`,s),p=this.entries.get(f);if(a&(ka.constants.COPYFILE_EXCL|ka.constants.COPYFILE_FICLONE_FORCE)&&typeof p<"u")throw or.EEXIST(`copyfile '${r}' -> '${s}'`);return{indexSource:c,resolvedDestP:f,indexDest:p}}async appendFilePromise(r,s,a){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>"u"?a={flag:"a"}:typeof a=="string"?a={flag:"a",encoding:a}:typeof a.flag>"u"&&(a={flag:"a",...a}),this.writeFilePromise(r,s,a)}appendFileSync(r,s,a={}){if(this.readOnly)throw or.EROFS(`open '${r}'`);return typeof a>"u"?a={flag:"a"}:typeof a=="string"?a={flag:"a",encoding:a}:typeof a.flag>"u"&&(a={flag:"a",...a}),this.writeFileSync(r,s,a)}fdToPath(r,s){let a=this.fds.get(r)?.p;if(typeof a>"u")throw or.EBADF(s);return a}async writeFilePromise(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a=="object"&&a.flag&&a.flag.includes("a")&&(s=Buffer.concat([await this.getFileSource(f,{asyncDecompress:!0}),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&await this.chmodPromise(p,c)}writeFileSync(r,s,a){let{encoding:n,mode:c,index:f,resolvedP:p}=this.prepareWriteFile(r,a);f!==void 0&&typeof a=="object"&&a.flag&&a.flag.includes("a")&&(s=Buffer.concat([this.getFileSource(f),Buffer.from(s)])),n!==null&&(s=s.toString(n));let h=this.setFileSource(p,s);h!==f&&this.registerEntry(p,h),c!==null&&this.chmodSync(p,c)}prepareWriteFile(r,s){if(typeof r=="number"&&(r=this.fdToPath(r,"read")),this.readOnly)throw or.EROFS(`open '${r}'`);let a=this.resolveFilename(`open '${r}'`,r);if(this.listings.has(a))throw or.EISDIR(`open '${r}'`);let n=null,c=null;typeof s=="string"?n=s:typeof s=="object"&&({encoding:n=null,mode:c=null}=s);let f=this.entries.get(a);return{encoding:n,mode:c,resolvedP:a,index:f}}async unlinkPromise(r){return this.unlinkSync(r)}unlinkSync(r){if(this.readOnly)throw or.EROFS(`unlink '${r}'`);let s=this.resolveFilename(`unlink '${r}'`,r);if(this.listings.has(s))throw or.EISDIR(`unlink '${r}'`);let a=this.entries.get(s);if(typeof a>"u")throw or.EINVAL(`unlink '${r}'`);this.deleteEntry(s,a)}async utimesPromise(r,s,a){return this.utimesSync(r,s,a)}utimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`utimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r);this.utimesImpl(n,a)}async lutimesPromise(r,s,a){return this.lutimesSync(r,s,a)}lutimesSync(r,s,a){if(this.readOnly)throw or.EROFS(`lutimes '${r}'`);let n=this.resolveFilename(`utimes '${r}'`,r,!1);this.utimesImpl(n,a)}utimesImpl(r,s){this.listings.has(r)&&(this.entries.has(r)||this.hydrateDirectory(r));let a=this.entries.get(r);if(a===void 0)throw new Error("Unreachable");this.zipImpl.setMtime(a,vmt(s))}async mkdirPromise(r,s){return this.mkdirSync(r,s)}mkdirSync(r,{mode:s=493,recursive:a=!1}={}){if(a)return this.mkdirpSync(r,{chmod:s});if(this.readOnly)throw or.EROFS(`mkdir '${r}'`);let n=this.resolveFilename(`mkdir '${r}'`,r);if(this.entries.has(n)||this.listings.has(n))throw or.EEXIST(`mkdir '${r}'`);this.hydrateDirectory(n),this.chmodSync(n,s)}async rmdirPromise(r,s){return this.rmdirSync(r,s)}rmdirSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rmdir '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rmdir '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rmdir '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rmdir '${r}'`);let c=this.entries.get(a);if(typeof c>"u")throw or.EINVAL(`rmdir '${r}'`);this.deleteEntry(r,c)}async rmPromise(r,s){return this.rmSync(r,s)}rmSync(r,{recursive:s=!1}={}){if(this.readOnly)throw or.EROFS(`rm '${r}'`);if(s){this.removeSync(r);return}let a=this.resolveFilename(`rm '${r}'`,r),n=this.listings.get(a);if(!n)throw or.ENOTDIR(`rm '${r}'`);if(n.size>0)throw or.ENOTEMPTY(`rm '${r}'`);let c=this.entries.get(a);if(typeof c>"u")throw or.EINVAL(`rm '${r}'`);this.deleteEntry(r,c)}hydrateDirectory(r){let s=this.zipImpl.addDirectory(K.relative(vt.root,r));return this.registerListing(r),this.registerEntry(r,s),s}async linkPromise(r,s){return this.linkSync(r,s)}linkSync(r,s){throw or.EOPNOTSUPP(`link '${r}' -> '${s}'`)}async symlinkPromise(r,s){return this.symlinkSync(r,s)}symlinkSync(r,s){if(this.readOnly)throw or.EROFS(`symlink '${r}' -> '${s}'`);let a=this.resolveFilename(`symlink '${r}' -> '${s}'`,s);if(this.listings.has(a))throw or.EISDIR(`symlink '${r}' -> '${s}'`);if(this.entries.has(a))throw or.EEXIST(`symlink '${r}' -> '${s}'`);let n=this.setFileSource(a,r);this.registerEntry(a,n),this.zipImpl.setExternalAttributes(n,lm,(ka.constants.S_IFLNK|511)<<16),this.symlinkCount+=1}async readFilePromise(r,s){typeof s=="object"&&(s=s?s.encoding:void 0);let a=await this.readFileBuffer(r,{asyncDecompress:!0});return s?a.toString(s):a}readFileSync(r,s){typeof s=="object"&&(s=s?s.encoding:void 0);let a=this.readFileBuffer(r);return s?a.toString(s):a}readFileBuffer(r,s={asyncDecompress:!1}){typeof r=="number"&&(r=this.fdToPath(r,"read"));let a=this.resolveFilename(`open '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`open '${r}'`);if(r[r.length-1]==="/"&&!this.listings.has(a))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(a))throw or.EISDIR("read");let n=this.entries.get(a);if(n===void 0)throw new Error("Unreachable");return this.getFileSource(n,s)}async readdirPromise(r,s){return this.readdirSync(r,s)}readdirSync(r,s){let a=this.resolveFilename(`scandir '${r}'`,r);if(!this.entries.has(a)&&!this.listings.has(a))throw or.ENOENT(`scandir '${r}'`);let n=this.listings.get(a);if(!n)throw or.ENOTDIR(`scandir '${r}'`);if(s?.recursive)if(s?.withFileTypes){let c=Array.from(n,f=>Object.assign(this.statImpl("lstat",K.join(r,f)),{name:f,path:vt.dot}));for(let f of c){if(!f.isDirectory())continue;let p=K.join(f.path,f.name),h=this.listings.get(K.join(a,p));for(let E of h)c.push(Object.assign(this.statImpl("lstat",K.join(r,p,E)),{name:E,path:p}))}return c}else{let c=[...n];for(let f of c){let p=this.listings.get(K.join(a,f));if(!(typeof p>"u"))for(let h of p)c.push(K.join(f,h))}return c}else return s?.withFileTypes?Array.from(n,c=>Object.assign(this.statImpl("lstat",K.join(r,c)),{name:c,path:void 0})):[...n]}async readlinkPromise(r){let s=this.prepareReadlink(r);return(await this.getFileSource(s,{asyncDecompress:!0})).toString()}readlinkSync(r){let s=this.prepareReadlink(r);return this.getFileSource(s).toString()}prepareReadlink(r){let s=this.resolveFilename(`readlink '${r}'`,r,!1);if(!this.entries.has(s)&&!this.listings.has(s))throw or.ENOENT(`readlink '${r}'`);if(r[r.length-1]==="/"&&!this.listings.has(s))throw or.ENOTDIR(`open '${r}'`);if(this.listings.has(s))throw or.EINVAL(`readlink '${r}'`);let a=this.entries.get(s);if(a===void 0)throw new Error("Unreachable");if(!this.isSymbolicLink(a))throw or.EINVAL(`readlink '${r}'`);return a}async truncatePromise(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>"u")throw or.EINVAL(`open '${r}'`);let c=await this.getFileSource(n,{asyncDecompress:!0}),f=Buffer.alloc(s,0);return c.copy(f),await this.writeFilePromise(r,f)}truncateSync(r,s=0){let a=this.resolveFilename(`open '${r}'`,r),n=this.entries.get(a);if(typeof n>"u")throw or.EINVAL(`open '${r}'`);let c=this.getFileSource(n),f=Buffer.alloc(s,0);return c.copy(f),this.writeFileSync(r,f)}async ftruncatePromise(r,s){return this.truncatePromise(this.fdToPath(r,"ftruncate"),s)}ftruncateSync(r,s){return this.truncateSync(this.fdToPath(r,"ftruncateSync"),s)}watch(r,s,a){let n;switch(typeof s){case"function":case"string":case"undefined":n=!0;break;default:({persistent:n=!0}=s);break}if(!n)return{on:()=>{},close:()=>{}};let c=setInterval(()=>{},24*60*60*1e3);return{on:()=>{},close:()=>{clearInterval(c)}}}watchFile(r,s,a){let n=K.resolve(vt.root,r);return sE(this,n,s,a)}unwatchFile(r,s){let a=K.resolve(vt.root,r);return yd(this,a,s)}}});function Kde(t,e,r=Buffer.alloc(0),s){let a=new ps(r),n=C=>C===e||C.startsWith(`${e}/`)?C.slice(0,e.length):null,c=async(C,S)=>()=>a,f=(C,S)=>a,p={...t},h=new Yn(p),E=new n0({baseFs:h,getMountPoint:n,factoryPromise:c,factorySync:f,magicByte:21,maxAge:1/0,typeCheck:s?.typeCheck});return j2(Vde.default,new i0(E)),a}var Vde,Jde=It(()=>{bt();Vde=et(ye("fs"));vT()});var zde=It(()=>{Gde();vT();Jde()});var n6,vv,ST,Zde=It(()=>{bt();vT();n6={CENTRAL_DIRECTORY:33639248,END_OF_CENTRAL_DIRECTORY:101010256},vv=22,ST=class t{constructor(e){this.filesShouldBeCached=!1;if("buffer"in e)throw new Error("Buffer based zip archives are not supported");if(!e.readOnly)throw new Error("Writable zip archives are not supported");this.baseFs=e.baseFs,this.fd=this.baseFs.openSync(e.path,"r");try{this.entries=t.readZipSync(this.fd,this.baseFs,e.size)}catch(r){throw this.baseFs.closeSync(this.fd),this.fd="closed",r}}static readZipSync(e,r,s){if(s=0;N--)if(n.readUInt32LE(N)===n6.END_OF_CENTRAL_DIRECTORY){a=N;break}if(a===-1)throw new Error("Not a zip archive")}let c=n.readUInt16LE(a+10),f=n.readUInt32LE(a+12),p=n.readUInt32LE(a+16),h=n.readUInt16LE(a+20);if(a+h+vv>n.length)throw new Error("Zip archive inconsistent");if(c==65535||f==4294967295||p==4294967295)throw new Error("Zip 64 is not supported");if(f>s)throw new Error("Zip archive inconsistent");if(c>f/46)throw new Error("Zip archive inconsistent");let E=Buffer.alloc(f);if(r.readSync(e,E,0,E.length,p)!==E.length)throw new Error("Zip archive inconsistent");let C=[],S=0,P=0,I=0;for(;PE.length)throw new Error("Zip archive inconsistent");if(E.readUInt32LE(S)!==n6.CENTRAL_DIRECTORY)throw new Error("Zip archive inconsistent");let N=E.readUInt16LE(S+4)>>>8;if(E.readUInt16LE(S+8)&1)throw new Error("Encrypted zip files are not supported");let W=E.readUInt16LE(S+10),te=E.readUInt32LE(S+16),ie=E.readUInt16LE(S+28),Ae=E.readUInt16LE(S+30),ce=E.readUInt16LE(S+32),me=E.readUInt32LE(S+42),pe=E.toString("utf8",S+46,S+46+ie).replaceAll("\0"," ");if(pe.includes("\0"))throw new Error("Invalid ZIP file");let Be=E.readUInt32LE(S+20),Ce=E.readUInt32LE(S+38);C.push({name:pe,os:N,mtime:ui.SAFE_TIME,crc:te,compressionMethod:W,isSymbolicLink:N===lm&&(Ce>>>16&ui.S_IFMT)===ui.S_IFLNK,size:E.readUInt32LE(S+24),compressedSize:Be,externalAttributes:Ce,localHeaderOffset:me}),I+=Be,P+=1,S+=46+ie+Ae+ce}if(I>s)throw new Error("Zip archive inconsistent");if(S!==E.length)throw new Error("Zip archive inconsistent");return C}getExternalAttributes(e){let r=this.entries[e];return[r.os,r.externalAttributes]}getListings(){return this.entries.map(e=>e.name)}getSymlinkCount(){let e=0;for(let r of this.entries)r.isSymbolicLink&&(e+=1);return e}stat(e){let r=this.entries[e];return{crc:r.crc,mtime:r.mtime,size:r.size}}locate(e){for(let r=0;rYde,DEFLATE:()=>r6,JsZipImpl:()=>ST,LibZipImpl:()=>vI,STORE:()=>t6,ZIP_UNIX:()=>lm,ZipFS:()=>ps,ZipOpenFS:()=>tA,getArchivePart:()=>zj,getLibzipPromise:()=>Dmt,getLibzipSync:()=>Smt,makeEmptyArchive:()=>BT,mountMemoryDrive:()=>Kde});function Smt(){return Bv()}async function Dmt(){return Bv()}var Xde,rA=It(()=>{Vj();Xde=et(Ude());qde();zde();Zde();Xj();_de(()=>{let t=(0,Xde.default)();return jde(t)})});var Dv,$de=It(()=>{bt();Wt();bv();Dv=class extends ot{constructor(){super(...arguments);this.cwd=ge.String("--cwd",process.cwd(),{description:"The directory to run the command in"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.usage={description:"run a command using yarn's portable shell",details:` + This command will run a command using Yarn's portable shell. + + Make sure to escape glob patterns, redirections, and other features that might be expanded by your own shell. + + Note: To escape something from Yarn's shell, you might have to escape it twice, the first time from your own shell. + + Note: Don't use this command in Yarn scripts, as Yarn's shell is automatically used. + + For a list of features, visit: https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-shell/README.md. + `,examples:[["Run a simple command","$0 echo Hello"],["Run a command with a glob pattern","$0 echo '*.js'"],["Run a command with a redirection","$0 echo Hello World '>' hello.txt"],["Run a command with an escaped glob pattern (The double escape is needed in Unix shells)",`$0 echo '"*.js"'`],["Run a command with a variable (Double quotes are needed in Unix shells, to prevent them from expanding the variable)",'$0 "GREETING=Hello echo $GREETING World"']]}}async execute(){let r=this.args.length>0?`${this.commandName} ${this.args.join(" ")}`:this.commandName;return await SI(r,[],{cwd:ue.toPortablePath(this.cwd),stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}}});var Kl,eme=It(()=>{Kl=class extends Error{constructor(e){super(e),this.name="ShellError"}}});var PT={};Vt(PT,{fastGlobOptions:()=>nme,isBraceExpansion:()=>i6,isGlobPattern:()=>bmt,match:()=>Pmt,micromatchOptions:()=>bT});function bmt(t){if(!DT.default.scan(t,bT).isGlob)return!1;try{DT.default.parse(t,bT)}catch{return!1}return!0}function Pmt(t,{cwd:e,baseFs:r}){return(0,tme.default)(t,{...nme,cwd:ue.fromPortablePath(e),fs:Cx(rme.default,new i0(r))})}function i6(t){return DT.default.scan(t,bT).isBrace}var tme,rme,DT,bT,nme,ime=It(()=>{bt();tme=et(BQ()),rme=et(ye("fs")),DT=et(Sa()),bT={strictBrackets:!0},nme={onlyDirectories:!1,onlyFiles:!1}});function s6(){}function o6(){for(let t of cm)t.kill()}function lme(t,e,r,s){return a=>{let n=a[0]instanceof nA.Transform?"pipe":a[0],c=a[1]instanceof nA.Transform?"pipe":a[1],f=a[2]instanceof nA.Transform?"pipe":a[2],p=(0,ome.default)(t,e,{...s,stdio:[n,c,f]});return cm.add(p),cm.size===1&&(process.on("SIGINT",s6),process.on("SIGTERM",o6)),a[0]instanceof nA.Transform&&a[0].pipe(p.stdin),a[1]instanceof nA.Transform&&p.stdout.pipe(a[1],{end:!1}),a[2]instanceof nA.Transform&&p.stderr.pipe(a[2],{end:!1}),{stdin:p.stdin,promise:new Promise(h=>{p.on("error",E=>{switch(cm.delete(p),cm.size===0&&(process.off("SIGINT",s6),process.off("SIGTERM",o6)),E.code){case"ENOENT":a[2].write(`command not found: ${t} +`),h(127);break;case"EACCES":a[2].write(`permission denied: ${t} +`),h(128);break;default:a[2].write(`uncaught error: ${E.message} +`),h(1);break}}),p.on("close",E=>{cm.delete(p),cm.size===0&&(process.off("SIGINT",s6),process.off("SIGTERM",o6)),h(E!==null?E:129)})})}}}function cme(t){return e=>{let r=e[0]==="pipe"?new nA.PassThrough:e[0];return{stdin:r,promise:Promise.resolve().then(()=>t({stdin:r,stdout:e[1],stderr:e[2]}))}}}function xT(t,e){return l6.start(t,e)}function sme(t,e=null){let r=new nA.PassThrough,s=new ame.StringDecoder,a="";return r.on("data",n=>{let c=s.write(n),f;do if(f=c.indexOf(` +`),f!==-1){let p=a+c.substring(0,f);c=c.substring(f+1),a="",t(e!==null?`${e} ${p}`:p)}while(f!==-1);a+=c}),r.on("end",()=>{let n=s.end();n!==""&&t(e!==null?`${e} ${n}`:n)}),r}function ume(t,{prefix:e}){return{stdout:sme(r=>t.stdout.write(`${r} +`),t.stdout.isTTY?e:null),stderr:sme(r=>t.stderr.write(`${r} +`),t.stderr.isTTY?e:null)}}var ome,nA,ame,cm,Mc,a6,l6,c6=It(()=>{ome=et(J_()),nA=ye("stream"),ame=ye("string_decoder"),cm=new Set;Mc=class{constructor(e){this.stream=e}close(){}get(){return this.stream}},a6=class{constructor(){this.stream=null}close(){if(this.stream===null)throw new Error("Assertion failed: No stream attached");this.stream.end()}attach(e){this.stream=e}get(){if(this.stream===null)throw new Error("Assertion failed: No stream attached");return this.stream}},l6=class t{constructor(e,r){this.stdin=null;this.stdout=null;this.stderr=null;this.pipe=null;this.ancestor=e,this.implementation=r}static start(e,{stdin:r,stdout:s,stderr:a}){let n=new t(null,e);return n.stdin=r,n.stdout=s,n.stderr=a,n}pipeTo(e,r=1){let s=new t(this,e),a=new a6;return s.pipe=a,s.stdout=this.stdout,s.stderr=this.stderr,(r&1)===1?this.stdout=a:this.ancestor!==null&&(this.stderr=this.ancestor.stdout),(r&2)===2?this.stderr=a:this.ancestor!==null&&(this.stderr=this.ancestor.stderr),s}async exec(){let e=["ignore","ignore","ignore"];if(this.pipe)e[0]="pipe";else{if(this.stdin===null)throw new Error("Assertion failed: No input stream registered");e[0]=this.stdin.get()}let r;if(this.stdout===null)throw new Error("Assertion failed: No output stream registered");r=this.stdout,e[1]=r.get();let s;if(this.stderr===null)throw new Error("Assertion failed: No error stream registered");s=this.stderr,e[2]=s.get();let a=this.implementation(e);return this.pipe&&this.pipe.attach(a.stdin),await a.promise.then(n=>(r.close(),s.close(),n))}async run(){let e=[];for(let s=this;s;s=s.ancestor)e.push(s.exec());return(await Promise.all(e))[0]}}});var Qv={};Vt(Qv,{EntryCommand:()=>Dv,ShellError:()=>Kl,execute:()=>SI,globUtils:()=>PT});function fme(t,e,r){let s=new Jl.PassThrough({autoDestroy:!0});switch(t){case 0:(e&1)===1&&r.stdin.pipe(s,{end:!1}),(e&2)===2&&r.stdin instanceof Jl.Writable&&s.pipe(r.stdin,{end:!1});break;case 1:(e&1)===1&&r.stdout.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stdout,{end:!1});break;case 2:(e&1)===1&&r.stderr.pipe(s,{end:!1}),(e&2)===2&&s.pipe(r.stderr,{end:!1});break;default:throw new Kl(`Bad file descriptor: "${t}"`)}return s}function QT(t,e={}){let r={...t,...e};return r.environment={...t.environment,...e.environment},r.variables={...t.variables,...e.variables},r}async function kmt(t,e,r){let s=[],a=new Jl.PassThrough;return a.on("data",n=>s.push(n)),await TT(t,e,QT(r,{stdout:a})),Buffer.concat(s).toString().replace(/[\r\n]+$/,"")}async function Ame(t,e,r){let s=t.map(async n=>{let c=await um(n.args,e,r);return{name:n.name,value:c.join(" ")}});return(await Promise.all(s)).reduce((n,c)=>(n[c.name]=c.value,n),{})}function kT(t){return t.match(/[^ \r\n\t]+/g)||[]}async function yme(t,e,r,s,a=s){switch(t.name){case"$":s(String(process.pid));break;case"#":s(String(e.args.length));break;case"@":if(t.quoted)for(let n of e.args)a(n);else for(let n of e.args){let c=kT(n);for(let f=0;f=0&&n"u"&&(t.defaultValue?c=(await um(t.defaultValue,e,r)).join(" "):t.alternativeValue&&(c="")),typeof c>"u")throw f?new Kl(`Unbound argument #${n}`):new Kl(`Unbound variable "${t.name}"`);if(t.quoted)s(c);else{let p=kT(c);for(let E=0;Es.push(n));let a=Number(s.join(" "));return Number.isNaN(a)?Pv({type:"variable",name:s.join(" ")},e,r):Pv({type:"number",value:a},e,r)}else return Qmt[t.type](await Pv(t.left,e,r),await Pv(t.right,e,r))}async function um(t,e,r){let s=new Map,a=[],n=[],c=E=>{n.push(E)},f=()=>{n.length>0&&a.push(n.join("")),n=[]},p=E=>{c(E),f()},h=(E,C,S)=>{let P=JSON.stringify({type:E,fd:C}),I=s.get(P);typeof I>"u"&&s.set(P,I=[]),I.push(S)};for(let E of t){let C=!1;switch(E.type){case"redirection":{let S=await um(E.args,e,r);for(let P of S)h(E.subtype,E.fd,P)}break;case"argument":for(let S of E.segments)switch(S.type){case"text":c(S.text);break;case"glob":c(S.pattern),C=!0;break;case"shell":{let P=await kmt(S.shell,e,r);if(S.quoted)c(P);else{let I=kT(P);for(let R=0;R"u")throw new Error("Assertion failed: Expected a glob pattern to have been set");let P=await e.glob.match(S,{cwd:r.cwd,baseFs:e.baseFs});if(P.length===0){let I=i6(S)?". Note: Brace expansion of arbitrary strings isn't currently supported. For more details, please read this issue: https://github.com/yarnpkg/berry/issues/22":"";throw new Kl(`No matches found: "${S}"${I}`)}for(let I of P.sort())p(I)}}if(s.size>0){let E=[];for(let[C,S]of s.entries())E.splice(E.length,0,C,String(S.length),...S);a.splice(0,0,"__ysh_set_redirects",...E,"--")}return a}function xv(t,e,r){e.builtins.has(t[0])||(t=["command",...t]);let s=ue.fromPortablePath(r.cwd),a=r.environment;typeof a.PWD<"u"&&(a={...a,PWD:s});let[n,...c]=t;if(n==="command")return lme(c[0],c.slice(1),e,{cwd:s,env:a});let f=e.builtins.get(n);if(typeof f>"u")throw new Error(`Assertion failed: A builtin should exist for "${n}"`);return cme(async({stdin:p,stdout:h,stderr:E})=>{let{stdin:C,stdout:S,stderr:P}=r;r.stdin=p,r.stdout=h,r.stderr=E;try{return await f(c,e,r)}finally{r.stdin=C,r.stdout=S,r.stderr=P}})}function Tmt(t,e,r){return s=>{let a=new Jl.PassThrough,n=TT(t,e,QT(r,{stdin:a}));return{stdin:a,promise:n}}}function Rmt(t,e,r){return s=>{let a=new Jl.PassThrough,n=TT(t,e,r);return{stdin:a,promise:n}}}function pme(t,e,r,s){if(e.length===0)return t;{let a;do a=String(Math.random());while(Object.hasOwn(s.procedures,a));return s.procedures={...s.procedures},s.procedures[a]=t,xv([...e,"__ysh_run_procedure",a],r,s)}}async function hme(t,e,r){let s=t,a=null,n=null;for(;s;){let c=s.then?{...r}:r,f;switch(s.type){case"command":{let p=await um(s.args,e,r),h=await Ame(s.envs,e,r);f=s.envs.length?xv(p,e,QT(c,{environment:h})):xv(p,e,c)}break;case"subshell":{let p=await um(s.args,e,r),h=Tmt(s.subshell,e,c);f=pme(h,p,e,c)}break;case"group":{let p=await um(s.args,e,r),h=Rmt(s.group,e,c);f=pme(h,p,e,c)}break;case"envs":{let p=await Ame(s.envs,e,r);c.environment={...c.environment,...p},f=xv(["true"],e,c)}break}if(typeof f>"u")throw new Error("Assertion failed: An action should have been generated");if(a===null)n=xT(f,{stdin:new Mc(c.stdin),stdout:new Mc(c.stdout),stderr:new Mc(c.stderr)});else{if(n===null)throw new Error("Assertion failed: The execution pipeline should have been setup");switch(a){case"|":n=n.pipeTo(f,1);break;case"|&":n=n.pipeTo(f,3);break}}s.then?(a=s.then.type,s=s.then.chain):s=null}if(n===null)throw new Error("Assertion failed: The execution pipeline should have been setup");return await n.run()}async function Fmt(t,e,r,{background:s=!1}={}){function a(n){let c=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],f=c[n%c.length];return gme.default.hex(f)}if(s){let n=r.nextBackgroundJobIndex++,c=a(n),f=`[${n}]`,p=c(f),{stdout:h,stderr:E}=ume(r,{prefix:p});return r.backgroundJobs.push(hme(t,e,QT(r,{stdout:h,stderr:E})).catch(C=>E.write(`${C.message} +`)).finally(()=>{r.stdout.isTTY&&r.stdout.write(`Job ${p}, '${c(AE(t))}' has ended +`)})),0}return await hme(t,e,r)}async function Nmt(t,e,r,{background:s=!1}={}){let a,n=f=>{a=f,r.variables["?"]=String(f)},c=async f=>{try{return await Fmt(f.chain,e,r,{background:s&&typeof f.then>"u"})}catch(p){if(!(p instanceof Kl))throw p;return r.stderr.write(`${p.message} +`),1}};for(n(await c(t));t.then;){if(r.exitCode!==null)return r.exitCode;switch(t.then.type){case"&&":a===0&&n(await c(t.then.line));break;case"||":a!==0&&n(await c(t.then.line));break;default:throw new Error(`Assertion failed: Unsupported command type: "${t.then.type}"`)}t=t.then.line}return a}async function TT(t,e,r){let s=r.backgroundJobs;r.backgroundJobs=[];let a=0;for(let{command:n,type:c}of t){if(a=await Nmt(n,e,r,{background:c==="&"}),r.exitCode!==null)return r.exitCode;r.variables["?"]=String(a)}return await Promise.all(r.backgroundJobs),r.backgroundJobs=s,a}function Eme(t){switch(t.type){case"variable":return t.name==="@"||t.name==="#"||t.name==="*"||Number.isFinite(parseInt(t.name,10))||"defaultValue"in t&&!!t.defaultValue&&t.defaultValue.some(e=>kv(e))||"alternativeValue"in t&&!!t.alternativeValue&&t.alternativeValue.some(e=>kv(e));case"arithmetic":return u6(t.arithmetic);case"shell":return f6(t.shell);default:return!1}}function kv(t){switch(t.type){case"redirection":return t.args.some(e=>kv(e));case"argument":return t.segments.some(e=>Eme(e));default:throw new Error(`Assertion failed: Unsupported argument type: "${t.type}"`)}}function u6(t){switch(t.type){case"variable":return Eme(t);case"number":return!1;default:return u6(t.left)||u6(t.right)}}function f6(t){return t.some(({command:e})=>{for(;e;){let r=e.chain;for(;r;){let s;switch(r.type){case"subshell":s=f6(r.subshell);break;case"command":s=r.envs.some(a=>a.args.some(n=>kv(n)))||r.args.some(a=>kv(a));break}if(s)return!0;if(!r.then)break;r=r.then.chain}if(!e.then)break;e=e.then.line}return!1})}async function SI(t,e=[],{baseFs:r=new Yn,builtins:s={},cwd:a=ue.toPortablePath(process.cwd()),env:n=process.env,stdin:c=process.stdin,stdout:f=process.stdout,stderr:p=process.stderr,variables:h={},glob:E=PT}={}){let C={};for(let[I,R]of Object.entries(n))typeof R<"u"&&(C[I]=R);let S=new Map(xmt);for(let[I,R]of Object.entries(s))S.set(I,R);c===null&&(c=new Jl.PassThrough,c.end());let P=vx(t,E);if(!f6(P)&&P.length>0&&e.length>0){let{command:I}=P[P.length-1];for(;I.then;)I=I.then.line;let R=I.chain;for(;R.then;)R=R.then.chain;R.type==="command"&&(R.args=R.args.concat(e.map(N=>({type:"argument",segments:[{type:"text",text:N}]}))))}return await TT(P,{args:e,baseFs:r,builtins:S,initialStdin:c,initialStdout:f,initialStderr:p,glob:E},{cwd:a,environment:C,exitCode:null,procedures:{},stdin:c,stdout:f,stderr:p,variables:Object.assign({},h,{"?":0}),nextBackgroundJobIndex:1,backgroundJobs:[]})}var gme,dme,Jl,mme,xmt,Qmt,bv=It(()=>{bt();Bc();gme=et(g4()),dme=ye("os"),Jl=ye("stream"),mme=ye("timers/promises");$de();eme();ime();c6();c6();xmt=new Map([["cd",async([t=(0,dme.homedir)(),...e],r,s)=>{let a=K.resolve(s.cwd,ue.toPortablePath(t));if(!(await r.baseFs.statPromise(a).catch(c=>{throw c.code==="ENOENT"?new Kl(`cd: no such file or directory: ${t}`):c})).isDirectory())throw new Kl(`cd: not a directory: ${t}`);return s.cwd=a,0}],["pwd",async(t,e,r)=>(r.stdout.write(`${ue.fromPortablePath(r.cwd)} +`),0)],[":",async(t,e,r)=>0],["true",async(t,e,r)=>0],["false",async(t,e,r)=>1],["exit",async([t,...e],r,s)=>s.exitCode=parseInt(t??s.variables["?"],10)],["echo",async(t,e,r)=>(r.stdout.write(`${t.join(" ")} +`),0)],["sleep",async([t],e,r)=>{if(typeof t>"u")throw new Kl("sleep: missing operand");let s=Number(t);if(Number.isNaN(s))throw new Kl(`sleep: invalid time interval '${t}'`);return await(0,mme.setTimeout)(1e3*s,0)}],["unset",async(t,e,r)=>{for(let s of t)delete r.environment[s],delete r.variables[s];return 0}],["__ysh_run_procedure",async(t,e,r)=>{let s=r.procedures[t[0]];return await xT(s,{stdin:new Mc(r.stdin),stdout:new Mc(r.stdout),stderr:new Mc(r.stderr)}).run()}],["__ysh_set_redirects",async(t,e,r)=>{let s=r.stdin,a=r.stdout,n=r.stderr,c=[],f=[],p=[],h=0;for(;t[h]!=="--";){let C=t[h++],{type:S,fd:P}=JSON.parse(C),I=W=>{switch(P){case null:case 0:c.push(W);break;default:throw new Error(`Unsupported file descriptor: "${P}"`)}},R=W=>{switch(P){case null:case 1:f.push(W);break;case 2:p.push(W);break;default:throw new Error(`Unsupported file descriptor: "${P}"`)}},N=Number(t[h++]),U=h+N;for(let W=h;We.baseFs.createReadStream(K.resolve(r.cwd,ue.toPortablePath(t[W]))));break;case"<<<":I(()=>{let te=new Jl.PassThrough;return process.nextTick(()=>{te.write(`${t[W]} +`),te.end()}),te});break;case"<&":I(()=>fme(Number(t[W]),1,r));break;case">":case">>":{let te=K.resolve(r.cwd,ue.toPortablePath(t[W]));R(te==="/dev/null"?new Jl.Writable({autoDestroy:!0,emitClose:!0,write(ie,Ae,ce){setImmediate(ce)}}):e.baseFs.createWriteStream(te,S===">>"?{flags:"a"}:void 0))}break;case">&":R(fme(Number(t[W]),2,r));break;default:throw new Error(`Assertion failed: Unsupported redirection type: "${S}"`)}}if(c.length>0){let C=new Jl.PassThrough;s=C;let S=P=>{if(P===c.length)C.end();else{let I=c[P]();I.pipe(C,{end:!1}),I.on("end",()=>{S(P+1)})}};S(0)}if(f.length>0){let C=new Jl.PassThrough;a=C;for(let S of f)C.pipe(S)}if(p.length>0){let C=new Jl.PassThrough;n=C;for(let S of p)C.pipe(S)}let E=await xT(xv(t.slice(h+1),e,r),{stdin:new Mc(s),stdout:new Mc(a),stderr:new Mc(n)}).run();return await Promise.all(f.map(C=>new Promise((S,P)=>{C.on("error",I=>{P(I)}),C.on("close",()=>{S()}),C.end()}))),await Promise.all(p.map(C=>new Promise((S,P)=>{C.on("error",I=>{P(I)}),C.on("close",()=>{S()}),C.end()}))),E}]]);Qmt={addition:(t,e)=>t+e,subtraction:(t,e)=>t-e,multiplication:(t,e)=>t*e,division:(t,e)=>Math.trunc(t/e)}});var RT=L((y$t,Ime)=>{function Omt(t,e){for(var r=-1,s=t==null?0:t.length,a=Array(s);++r{var Cme=Yd(),Lmt=RT(),Mmt=xc(),_mt=aI(),Umt=1/0,wme=Cme?Cme.prototype:void 0,Bme=wme?wme.toString:void 0;function vme(t){if(typeof t=="string")return t;if(Mmt(t))return Lmt(t,vme)+"";if(_mt(t))return Bme?Bme.call(t):"";var e=t+"";return e=="0"&&1/t==-Umt?"-0":e}Sme.exports=vme});var Tv=L((I$t,bme)=>{var Hmt=Dme();function jmt(t){return t==null?"":Hmt(t)}bme.exports=jmt});var A6=L((C$t,Pme)=>{function qmt(t,e,r){var s=-1,a=t.length;e<0&&(e=-e>a?0:a+e),r=r>a?a:r,r<0&&(r+=a),a=e>r?0:r-e>>>0,e>>>=0;for(var n=Array(a);++s{var Gmt=A6();function Wmt(t,e,r){var s=t.length;return r=r===void 0?s:r,!e&&r>=s?t:Gmt(t,e,r)}xme.exports=Wmt});var p6=L((B$t,Qme)=>{var Ymt="\\ud800-\\udfff",Vmt="\\u0300-\\u036f",Kmt="\\ufe20-\\ufe2f",Jmt="\\u20d0-\\u20ff",zmt=Vmt+Kmt+Jmt,Zmt="\\ufe0e\\ufe0f",Xmt="\\u200d",$mt=RegExp("["+Xmt+Ymt+zmt+Zmt+"]");function eyt(t){return $mt.test(t)}Qme.exports=eyt});var Rme=L((v$t,Tme)=>{function tyt(t){return t.split("")}Tme.exports=tyt});var Hme=L((S$t,Ume)=>{var Fme="\\ud800-\\udfff",ryt="\\u0300-\\u036f",nyt="\\ufe20-\\ufe2f",iyt="\\u20d0-\\u20ff",syt=ryt+nyt+iyt,oyt="\\ufe0e\\ufe0f",ayt="["+Fme+"]",h6="["+syt+"]",g6="\\ud83c[\\udffb-\\udfff]",lyt="(?:"+h6+"|"+g6+")",Nme="[^"+Fme+"]",Ome="(?:\\ud83c[\\udde6-\\uddff]){2}",Lme="[\\ud800-\\udbff][\\udc00-\\udfff]",cyt="\\u200d",Mme=lyt+"?",_me="["+oyt+"]?",uyt="(?:"+cyt+"(?:"+[Nme,Ome,Lme].join("|")+")"+_me+Mme+")*",fyt=_me+Mme+uyt,Ayt="(?:"+[Nme+h6+"?",h6,Ome,Lme,ayt].join("|")+")",pyt=RegExp(g6+"(?="+g6+")|"+Ayt+fyt,"g");function hyt(t){return t.match(pyt)||[]}Ume.exports=hyt});var qme=L((D$t,jme)=>{var gyt=Rme(),dyt=p6(),myt=Hme();function yyt(t){return dyt(t)?myt(t):gyt(t)}jme.exports=yyt});var Wme=L((b$t,Gme)=>{var Eyt=kme(),Iyt=p6(),Cyt=qme(),wyt=Tv();function Byt(t){return function(e){e=wyt(e);var r=Iyt(e)?Cyt(e):void 0,s=r?r[0]:e.charAt(0),a=r?Eyt(r,1).join(""):e.slice(1);return s[t]()+a}}Gme.exports=Byt});var Vme=L((P$t,Yme)=>{var vyt=Wme(),Syt=vyt("toUpperCase");Yme.exports=Syt});var d6=L((x$t,Kme)=>{var Dyt=Tv(),byt=Vme();function Pyt(t){return byt(Dyt(t).toLowerCase())}Kme.exports=Pyt});var Jme=L((k$t,FT)=>{function xyt(){var t=0,e=1,r=2,s=3,a=4,n=5,c=6,f=7,p=8,h=9,E=10,C=11,S=12,P=13,I=14,R=15,N=16,U=17,W=0,te=1,ie=2,Ae=3,ce=4;function me(g,we){return 55296<=g.charCodeAt(we)&&g.charCodeAt(we)<=56319&&56320<=g.charCodeAt(we+1)&&g.charCodeAt(we+1)<=57343}function pe(g,we){we===void 0&&(we=0);var Ee=g.charCodeAt(we);if(55296<=Ee&&Ee<=56319&&we=1){var fe=g.charCodeAt(we-1),se=Ee;return 55296<=fe&&fe<=56319?(fe-55296)*1024+(se-56320)+65536:se}return Ee}function Be(g,we,Ee){var fe=[g].concat(we).concat([Ee]),se=fe[fe.length-2],X=Ee,De=fe.lastIndexOf(I);if(De>1&&fe.slice(1,De).every(function(j){return j==s})&&[s,P,U].indexOf(g)==-1)return ie;var Re=fe.lastIndexOf(a);if(Re>0&&fe.slice(1,Re).every(function(j){return j==a})&&[S,a].indexOf(se)==-1)return fe.filter(function(j){return j==a}).length%2==1?Ae:ce;if(se==t&&X==e)return W;if(se==r||se==t||se==e)return X==I&&we.every(function(j){return j==s})?ie:te;if(X==r||X==t||X==e)return te;if(se==c&&(X==c||X==f||X==h||X==E))return W;if((se==h||se==f)&&(X==f||X==p))return W;if((se==E||se==p)&&X==p)return W;if(X==s||X==R)return W;if(X==n)return W;if(se==S)return W;var gt=fe.indexOf(s)!=-1?fe.lastIndexOf(s)-1:fe.length-2;return[P,U].indexOf(fe[gt])!=-1&&fe.slice(gt+1,-1).every(function(j){return j==s})&&X==I||se==R&&[N,U].indexOf(X)!=-1?W:we.indexOf(a)!=-1?ie:se==a&&X==a?W:te}this.nextBreak=function(g,we){if(we===void 0&&(we=0),we<0)return 0;if(we>=g.length-1)return g.length;for(var Ee=Ce(pe(g,we)),fe=[],se=we+1;se{var kyt=/^(.*?)(\x1b\[[^m]+m|\x1b\]8;;.*?(\x1b\\|\u0007))/,NT;function Qyt(){if(NT)return NT;if(typeof Intl.Segmenter<"u"){let t=new Intl.Segmenter("en",{granularity:"grapheme"});return NT=e=>Array.from(t.segment(e),({segment:r})=>r)}else{let t=Jme(),e=new t;return NT=r=>e.splitGraphemes(r)}}zme.exports=(t,e=0,r=t.length)=>{if(e<0||r<0)throw new RangeError("Negative indices aren't supported by this implementation");let s=r-e,a="",n=0,c=0;for(;t.length>0;){let f=t.match(kyt)||[t,t,void 0],p=Qyt()(f[1]),h=Math.min(e-n,p.length);p=p.slice(h);let E=Math.min(s-c,p.length);a+=p.slice(0,E).join(""),n+=h,c+=E,typeof f[2]<"u"&&(a+=f[2]),t=t.slice(f[0].length)}return a}});var un,Rv=It(()=>{un=process.env.YARN_IS_TEST_ENV?"0.0.0":"4.9.1"});function nye(t,{configuration:e,json:r}){if(!e.get("enableMessageNames"))return"";let a=Vf(t===null?0:t);return!r&&t===null?Ut(e,a,"grey"):a}function m6(t,{configuration:e,json:r}){let s=nye(t,{configuration:e,json:r});if(!s||t===null||t===0)return s;let a=Dr[t],n=`https://yarnpkg.com/advanced/error-codes#${s}---${a}`.toLowerCase();return JE(e,s,n)}async function DI({configuration:t,stdout:e,forceError:r},s){let a=await Ot.start({configuration:t,stdout:e,includeFooter:!1},async n=>{let c=!1,f=!1;for(let p of s)typeof p.option<"u"&&(p.error||r?(f=!0,n.reportError(50,p.message)):(c=!0,n.reportWarning(50,p.message)),p.callback?.());c&&!f&&n.reportSeparator()});return a.hasErrors()?a.exitCode():null}var tye,OT,Tyt,Xme,$me,b0,rye,eye,Ryt,Fyt,LT,Nyt,Ot,Fv=It(()=>{tye=et(Zme()),OT=et(Nd());nk();Fc();Rv();Qc();Tyt="\xB7",Xme=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],$me=80,b0=OT.default.GITHUB_ACTIONS?{start:t=>`::group::${t} +`,end:t=>`::endgroup:: +`}:OT.default.TRAVIS?{start:t=>`travis_fold:start:${t} +`,end:t=>`travis_fold:end:${t} +`}:OT.default.GITLAB?{start:t=>`section_start:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\W+/g,"_")}[collapsed=true]\r\x1B[0K${t} +`,end:t=>`section_end:${Math.floor(Date.now()/1e3)}:${t.toLowerCase().replace(/\W+/g,"_")}\r\x1B[0K`}:null,rye=b0!==null,eye=new Date,Ryt=["iTerm.app","Apple_Terminal","WarpTerminal","vscode"].includes(process.env.TERM_PROGRAM)||!!process.env.WT_SESSION,Fyt=t=>t,LT=Fyt({patrick:{date:[17,3],chars:["\u{1F340}","\u{1F331}"],size:40},simba:{date:[19,7],chars:["\u{1F981}","\u{1F334}"],size:40},jack:{date:[31,10],chars:["\u{1F383}","\u{1F987}"],size:40},hogsfather:{date:[31,12],chars:["\u{1F389}","\u{1F384}"],size:40},default:{chars:["=","-"],size:80}}),Nyt=Ryt&&Object.keys(LT).find(t=>{let e=LT[t];return!(e.date&&(e.date[0]!==eye.getDate()||e.date[1]!==eye.getMonth()+1))})||"default";Ot=class extends ho{constructor({configuration:r,stdout:s,json:a=!1,forceSectionAlignment:n=!1,includeNames:c=!0,includePrefix:f=!0,includeFooter:p=!0,includeLogs:h=!a,includeInfos:E=h,includeWarnings:C=h}){super();this.uncommitted=new Set;this.warningCount=0;this.errorCount=0;this.timerFooter=[];this.startTime=Date.now();this.indent=0;this.level=0;this.progress=new Map;this.progressTime=0;this.progressFrame=0;this.progressTimeout=null;this.progressStyle=null;this.progressMaxScaledSize=null;if(YB(this,{configuration:r}),this.configuration=r,this.forceSectionAlignment=n,this.includeNames=c,this.includePrefix=f,this.includeFooter=p,this.includeInfos=E,this.includeWarnings=C,this.json=a,this.stdout=s,r.get("enableProgressBars")&&!a&&s.isTTY&&s.columns>22){let S=r.get("progressBarStyle")||Nyt;if(!Object.hasOwn(LT,S))throw new Error("Assertion failed: Invalid progress bar style");this.progressStyle=LT[S];let P=Math.min(this.getRecommendedLength(),80);this.progressMaxScaledSize=Math.floor(this.progressStyle.size*P/80)}}static async start(r,s){let a=new this(r),n=process.emitWarning;process.emitWarning=(c,f)=>{if(typeof c!="string"){let h=c;c=h.message,f=f??h.name}let p=typeof f<"u"?`${f}: ${c}`:c;a.reportWarning(0,p)},r.includeVersion&&a.reportInfo(0,zd(r.configuration,`Yarn ${un}`,2));try{await s(a)}catch(c){a.reportExceptionOnce(c)}finally{await a.finalize(),process.emitWarning=n}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}getRecommendedLength(){let s=this.progressStyle!==null?this.stdout.columns-1:super.getRecommendedLength();return Math.max(40,s-12-this.indent*2)}startSectionSync({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}async startSectionPromise({reportHeader:r,reportFooter:s,skipIfEmpty:a},n){let c={committed:!1,action:()=>{r?.()}};a?this.uncommitted.add(c):(c.action(),c.committed=!0);let f=Date.now();try{return await n()}catch(p){throw this.reportExceptionOnce(p),p}finally{let p=Date.now();this.uncommitted.delete(c),c.committed&&s?.(p-f)}}startTimerImpl(r,s,a){return{cb:typeof s=="function"?s:a,reportHeader:()=>{this.level+=1,this.reportInfo(null,`\u250C ${r}`),this.indent+=1,b0!==null&&!this.json&&this.includeInfos&&this.stdout.write(b0.start(r))},reportFooter:f=>{if(this.indent-=1,b0!==null&&!this.json&&this.includeInfos){this.stdout.write(b0.end(r));for(let p of this.timerFooter)p()}this.configuration.get("enableTimers")&&f>200?this.reportInfo(null,`\u2514 Completed in ${Ut(this.configuration,f,Ct.DURATION)}`):this.reportInfo(null,"\u2514 Completed"),this.level-=1},skipIfEmpty:(typeof s=="function"?{}:s).skipIfEmpty}}startTimerSync(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionSync(c,n)}async startTimerPromise(r,s,a){let{cb:n,...c}=this.startTimerImpl(r,s,a);return this.startSectionPromise(c,n)}reportSeparator(){this.indent===0?this.writeLine(""):this.reportInfo(null,"")}reportInfo(r,s){if(!this.includeInfos)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"",c=`${this.formatPrefix(n,"blueBright")}${s}`;this.json?this.reportJson({type:"info",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(c)}reportWarning(r,s){if(this.warningCount+=1,!this.includeWarnings)return;this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"";this.json?this.reportJson({type:"warning",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,"yellowBright")}${s}`)}reportError(r,s){this.errorCount+=1,this.timerFooter.push(()=>this.reportErrorImpl(r,s)),this.reportErrorImpl(r,s)}reportErrorImpl(r,s){this.commit();let a=this.formatNameWithHyperlink(r),n=a?`${a}: `:"";this.json?this.reportJson({type:"error",name:r,displayName:this.formatName(r),indent:this.formatIndent(),data:s}):this.writeLine(`${this.formatPrefix(n,"redBright")}${s}`,{truncate:!1})}reportFold(r,s){if(!b0)return;let a=`${b0.start(r)}${s}${b0.end(r)}`;this.timerFooter.push(()=>this.stdout.write(a))}reportProgress(r){if(this.progressStyle===null)return{...Promise.resolve(),stop:()=>{}};if(r.hasProgress&&r.hasTitle)throw new Error("Unimplemented: Progress bars can't have both progress and titles.");let s=!1,a=Promise.resolve().then(async()=>{let c={progress:r.hasProgress?0:void 0,title:r.hasTitle?"":void 0};this.progress.set(r,{definition:c,lastScaledSize:r.hasProgress?-1:void 0,lastTitle:void 0}),this.refreshProgress({delta:-1});for await(let{progress:f,title:p}of r)s||c.progress===f&&c.title===p||(c.progress=f,c.title=p,this.refreshProgress());n()}),n=()=>{s||(s=!0,this.progress.delete(r),this.refreshProgress({delta:1}))};return{...a,stop:n}}reportJson(r){this.json&&this.writeLine(`${JSON.stringify(r)}`)}async finalize(){if(!this.includeFooter)return;let r="";this.errorCount>0?r="Failed with errors":this.warningCount>0?r="Done with warnings":r="Done";let s=Ut(this.configuration,Date.now()-this.startTime,Ct.DURATION),a=this.configuration.get("enableTimers")?`${r} in ${s}`:r;this.errorCount>0?this.reportError(0,a):this.warningCount>0?this.reportWarning(0,a):this.reportInfo(0,a)}writeLine(r,{truncate:s}={}){this.clearProgress({clear:!0}),this.stdout.write(`${this.truncate(r,{truncate:s})} +`),this.writeProgress()}writeLines(r,{truncate:s}={}){this.clearProgress({delta:r.length});for(let a of r)this.stdout.write(`${this.truncate(a,{truncate:s})} +`);this.writeProgress()}commit(){let r=this.uncommitted;this.uncommitted=new Set;for(let s of r)s.committed=!0,s.action()}clearProgress({delta:r=0,clear:s=!1}){this.progressStyle!==null&&this.progress.size+r>0&&(this.stdout.write(`\x1B[${this.progress.size+r}A`),(r>0||s)&&this.stdout.write("\x1B[0J"))}writeProgress(){if(this.progressStyle===null||(this.progressTimeout!==null&&clearTimeout(this.progressTimeout),this.progressTimeout=null,this.progress.size===0))return;let r=Date.now();r-this.progressTime>$me&&(this.progressFrame=(this.progressFrame+1)%Xme.length,this.progressTime=r);let s=Xme[this.progressFrame];for(let a of this.progress.values()){let n="";if(typeof a.lastScaledSize<"u"){let h=this.progressStyle.chars[0].repeat(a.lastScaledSize),E=this.progressStyle.chars[1].repeat(this.progressMaxScaledSize-a.lastScaledSize);n=` ${h}${E}`}let c=this.formatName(null),f=c?`${c}: `:"",p=a.definition.title?` ${a.definition.title}`:"";this.stdout.write(`${Ut(this.configuration,"\u27A4","blueBright")} ${f}${s}${n}${p} +`)}this.progressTimeout=setTimeout(()=>{this.refreshProgress({force:!0})},$me)}refreshProgress({delta:r=0,force:s=!1}={}){let a=!1,n=!1;if(s||this.progress.size===0)a=!0;else for(let c of this.progress.values()){let f=typeof c.definition.progress<"u"?Math.trunc(this.progressMaxScaledSize*c.definition.progress):void 0,p=c.lastScaledSize;c.lastScaledSize=f;let h=c.lastTitle;if(c.lastTitle=c.definition.title,f!==p||(n=h!==c.definition.title)){a=!0;break}}a&&(this.clearProgress({delta:r,clear:n}),this.writeProgress())}truncate(r,{truncate:s}={}){return this.progressStyle===null&&(s=!1),typeof s>"u"&&(s=this.configuration.get("preferTruncatedLines")),s&&(r=(0,tye.default)(r,0,this.stdout.columns-1)),r}formatName(r){return this.includeNames?nye(r,{configuration:this.configuration,json:this.json}):""}formatPrefix(r,s){return this.includePrefix?`${Ut(this.configuration,"\u27A4",s)} ${r}${this.formatIndent()}`:""}formatNameWithHyperlink(r){return this.includeNames?m6(r,{configuration:this.configuration,json:this.json}):""}formatIndent(){return this.level>0||!this.forceSectionAlignment?"\u2502 ".repeat(this.indent):`${Tyt} `}}});var In={};Vt(In,{PackageManager:()=>oye,detectPackageManager:()=>aye,executePackageAccessibleBinary:()=>Aye,executePackageScript:()=>MT,executePackageShellcode:()=>y6,executeWorkspaceAccessibleBinary:()=>jyt,executeWorkspaceLifecycleScript:()=>uye,executeWorkspaceScript:()=>cye,getPackageAccessibleBinaries:()=>_T,getWorkspaceAccessibleBinaries:()=>fye,hasPackageScript:()=>_yt,hasWorkspaceScript:()=>E6,isNodeScript:()=>I6,makeScriptEnv:()=>Nv,maybeExecuteWorkspaceLifecycleScript:()=>Hyt,prepareExternalProject:()=>Myt});async function P0(t,e,r,s=[]){if(process.platform==="win32"){let a=`@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @"${r}" ${s.map(n=>`"${n.replace('"','""')}"`).join(" ")} %*`;await le.writeFilePromise(K.format({dir:t,name:e,ext:".cmd"}),a)}await le.writeFilePromise(K.join(t,e),`#!/bin/sh +exec "${r}" ${s.map(a=>`'${a.replace(/'/g,`'"'"'`)}'`).join(" ")} "$@" +`,{mode:493})}async function aye(t){let e=await Ht.tryFind(t);if(e?.packageManager){let s=xQ(e.packageManager);if(s?.name){let a=`found ${JSON.stringify({packageManager:e.packageManager})} in manifest`,[n]=s.reference.split(".");switch(s.name){case"yarn":return{packageManagerField:!0,packageManager:Number(n)===1?"Yarn Classic":"Yarn",reason:a};case"npm":return{packageManagerField:!0,packageManager:"npm",reason:a};case"pnpm":return{packageManagerField:!0,packageManager:"pnpm",reason:a}}}}let r;try{r=await le.readFilePromise(K.join(t,Er.lockfile),"utf8")}catch{}return r!==void 0?r.match(/^__metadata:$/m)?{packageManager:"Yarn",reason:'"__metadata" key found in yarn.lock'}:{packageManager:"Yarn Classic",reason:'"__metadata" key not found in yarn.lock, must be a Yarn classic lockfile'}:le.existsSync(K.join(t,"package-lock.json"))?{packageManager:"npm",reason:`found npm's "package-lock.json" lockfile`}:le.existsSync(K.join(t,"pnpm-lock.yaml"))?{packageManager:"pnpm",reason:`found pnpm's "pnpm-lock.yaml" lockfile`}:null}async function Nv({project:t,locator:e,binFolder:r,ignoreCorepack:s,lifecycleScript:a,baseEnv:n=t?.configuration.env??process.env}){let c={};for(let[E,C]of Object.entries(n))typeof C<"u"&&(c[E.toLowerCase()!=="path"?E:"PATH"]=C);let f=ue.fromPortablePath(r);c.BERRY_BIN_FOLDER=ue.fromPortablePath(f);let p=process.env.COREPACK_ROOT&&!s?ue.join(process.env.COREPACK_ROOT,"dist/yarn.js"):process.argv[1];if(await Promise.all([P0(r,"node",process.execPath),...un!==null?[P0(r,"run",process.execPath,[p,"run"]),P0(r,"yarn",process.execPath,[p]),P0(r,"yarnpkg",process.execPath,[p]),P0(r,"node-gyp",process.execPath,[p,"run","--top-level","node-gyp"])]:[]]),t&&(c.INIT_CWD=ue.fromPortablePath(t.configuration.startingCwd),c.PROJECT_CWD=ue.fromPortablePath(t.cwd)),c.PATH=c.PATH?`${f}${ue.delimiter}${c.PATH}`:`${f}`,c.npm_execpath=`${f}${ue.sep}yarn`,c.npm_node_execpath=`${f}${ue.sep}node`,e){if(!t)throw new Error("Assertion failed: Missing project");let E=t.tryWorkspaceByLocator(e),C=E?E.manifest.version??"":t.storedPackages.get(e.locatorHash).version??"";c.npm_package_name=cn(e),c.npm_package_version=C;let S;if(E)S=E.cwd;else{let P=t.storedPackages.get(e.locatorHash);if(!P)throw new Error(`Package for ${Yr(t.configuration,e)} not found in the project`);let I=t.configuration.getLinkers(),R={project:t,report:new Ot({stdout:new x0.PassThrough,configuration:t.configuration})},N=I.find(U=>U.supportsPackage(P,R));if(!N)throw new Error(`The package ${Yr(t.configuration,P)} isn't supported by any of the available linkers`);S=await N.findPackageLocation(P,R)}c.npm_package_json=ue.fromPortablePath(K.join(S,Er.manifest))}let h=un!==null?`yarn/${un}`:`yarn/${kp("@yarnpkg/core").version}-core`;return c.npm_config_user_agent=`${h} npm/? node/${process.version} ${process.platform} ${process.arch}`,a&&(c.npm_lifecycle_event=a),t&&await t.configuration.triggerHook(E=>E.setupScriptEnvironment,t,c,async(E,C,S)=>await P0(r,E,C,S)),c}async function Myt(t,e,{configuration:r,report:s,workspace:a=null,locator:n=null}){await Lyt(async()=>{await le.mktempPromise(async c=>{let f=K.join(c,"pack.log"),p=null,{stdout:h,stderr:E}=r.getSubprocessStreams(f,{prefix:ue.fromPortablePath(t),report:s}),C=n&&Gu(n)?rI(n):n,S=C?cl(C):"an external project";h.write(`Packing ${S} from sources +`);let P=await aye(t),I;P!==null?(h.write(`Using ${P.packageManager} for bootstrap. Reason: ${P.reason} + +`),I=P.packageManager):(h.write(`No package manager configuration detected; defaulting to Yarn + +`),I="Yarn");let R=I==="Yarn"&&!P?.packageManagerField;await le.mktempPromise(async N=>{let U=await Nv({binFolder:N,ignoreCorepack:R,baseEnv:{...process.env,COREPACK_ENABLE_AUTO_PIN:"0"}}),te=new Map([["Yarn Classic",async()=>{let Ae=a!==null?["workspace",a]:[],ce=K.join(t,Er.manifest),me=await le.readFilePromise(ce),pe=await Yu(process.execPath,[process.argv[1],"set","version","classic","--only-if-needed","--yarn-path"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(pe.code!==0)return pe.code;await le.writeFilePromise(ce,me),await le.appendFilePromise(K.join(t,".npmignore"),`/.yarn +`),h.write(` +`),delete U.NODE_ENV;let Be=await Yu("yarn",["install"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(Be.code!==0)return Be.code;h.write(` +`);let Ce=await Yu("yarn",[...Ae,"pack","--filename",ue.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return Ce.code!==0?Ce.code:0}],["Yarn",async()=>{let Ae=a!==null?["workspace",a]:[];U.YARN_ENABLE_INLINE_BUILDS="1";let ce=K.join(t,Er.lockfile);await le.existsPromise(ce)||await le.writeFilePromise(ce,"");let me=await Yu("yarn",[...Ae,"pack","--install-if-needed","--filename",ue.fromPortablePath(e)],{cwd:t,env:U,stdin:p,stdout:h,stderr:E});return me.code!==0?me.code:0}],["npm",async()=>{if(a!==null){let we=new x0.PassThrough,Ee=WE(we);we.pipe(h,{end:!1});let fe=await Yu("npm",["--version"],{cwd:t,env:U,stdin:p,stdout:we,stderr:E,end:0});if(we.end(),fe.code!==0)return h.end(),E.end(),fe.code;let se=(await Ee).toString().trim();if(!eA(se,">=7.x")){let X=ba(null,"npm"),De=On(X,se),Re=On(X,">=7.x");throw new Error(`Workspaces aren't supported by ${ri(r,De)}; please upgrade to ${ri(r,Re)} (npm has been detected as the primary package manager for ${Ut(r,t,Ct.PATH)})`)}}let Ae=a!==null?["--workspace",a]:[];delete U.npm_config_user_agent,delete U.npm_config_production,delete U.NPM_CONFIG_PRODUCTION,delete U.NODE_ENV;let ce=await Yu("npm",["install","--legacy-peer-deps"],{cwd:t,env:U,stdin:p,stdout:h,stderr:E,end:1});if(ce.code!==0)return ce.code;let me=new x0.PassThrough,pe=WE(me);me.pipe(h);let Be=await Yu("npm",["pack","--silent",...Ae],{cwd:t,env:U,stdin:p,stdout:me,stderr:E});if(Be.code!==0)return Be.code;let Ce=(await pe).toString().trim().replace(/^.*\n/s,""),g=K.resolve(t,ue.toPortablePath(Ce));return await le.renamePromise(g,e),0}]]).get(I);if(typeof te>"u")throw new Error("Assertion failed: Unsupported workflow");let ie=await te();if(!(ie===0||typeof ie>"u"))throw le.detachTemp(c),new Yt(58,`Packing the package failed (exit code ${ie}, logs can be found here: ${Ut(r,f,Ct.PATH)})`)})})})}async function _yt(t,e,{project:r}){let s=r.tryWorkspaceByLocator(t);if(s!==null)return E6(s,e);let a=r.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r.configuration,t)} not found in the project`);return await tA.openPromise(async n=>{let c=r.configuration,f=r.configuration.getLinkers(),p={project:r,report:new Ot({stdout:new x0.PassThrough,configuration:c})},h=f.find(P=>P.supportsPackage(a,p));if(!h)throw new Error(`The package ${Yr(r.configuration,a)} isn't supported by any of the available linkers`);let E=await h.findPackageLocation(a,p),C=new Sn(E,{baseFs:n});return(await Ht.find(vt.dot,{baseFs:C})).scripts.has(e)})}async function MT(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await le.mktempPromise(async p=>{let{manifest:h,env:E,cwd:C}=await lye(t,{project:a,binFolder:p,cwd:s,lifecycleScript:e}),S=h.scripts.get(e);if(typeof S>"u")return 1;let P=async()=>await SI(S,r,{cwd:C,env:E,stdin:n,stdout:c,stderr:f});return await(await a.configuration.reduceHook(R=>R.wrapScriptExecution,P,a,t,e,{script:S,args:r,cwd:C,env:E,stdin:n,stdout:c,stderr:f}))()})}async function y6(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f}){return await le.mktempPromise(async p=>{let{env:h,cwd:E}=await lye(t,{project:a,binFolder:p,cwd:s});return await SI(e,r,{cwd:E,env:h,stdin:n,stdout:c,stderr:f})})}async function Uyt(t,{binFolder:e,cwd:r,lifecycleScript:s}){let a=await Nv({project:t.project,locator:t.anchoredLocator,binFolder:e,lifecycleScript:s});return await C6(e,await fye(t)),typeof r>"u"&&(r=K.dirname(await le.realpathPromise(K.join(t.cwd,"package.json")))),{manifest:t.manifest,binFolder:e,env:a,cwd:r}}async function lye(t,{project:e,binFolder:r,cwd:s,lifecycleScript:a}){let n=e.tryWorkspaceByLocator(t);if(n!==null)return Uyt(n,{binFolder:r,cwd:s,lifecycleScript:a});let c=e.storedPackages.get(t.locatorHash);if(!c)throw new Error(`Package for ${Yr(e.configuration,t)} not found in the project`);return await tA.openPromise(async f=>{let p=e.configuration,h=e.configuration.getLinkers(),E={project:e,report:new Ot({stdout:new x0.PassThrough,configuration:p})},C=h.find(N=>N.supportsPackage(c,E));if(!C)throw new Error(`The package ${Yr(e.configuration,c)} isn't supported by any of the available linkers`);let S=await Nv({project:e,locator:t,binFolder:r,lifecycleScript:a});await C6(r,await _T(t,{project:e}));let P=await C.findPackageLocation(c,E),I=new Sn(P,{baseFs:f}),R=await Ht.find(vt.dot,{baseFs:I});return typeof s>"u"&&(s=P),{manifest:R,binFolder:r,env:S,cwd:s}})}async function cye(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c}){return await MT(t.anchoredLocator,e,r,{cwd:s,project:t.project,stdin:a,stdout:n,stderr:c})}function E6(t,e){return t.manifest.scripts.has(e)}async function uye(t,e,{cwd:r,report:s}){let{configuration:a}=t.project,n=null;await le.mktempPromise(async c=>{let f=K.join(c,`${e}.log`),p=`# This file contains the result of Yarn calling the "${e}" lifecycle script inside a workspace ("${ue.fromPortablePath(t.cwd)}") +`,{stdout:h,stderr:E}=a.getSubprocessStreams(f,{report:s,prefix:Yr(a,t.anchoredLocator),header:p});s.reportInfo(36,`Calling the "${e}" lifecycle script`);let C=await cye(t,e,[],{cwd:r,stdin:n,stdout:h,stderr:E});if(h.end(),E.end(),C!==0)throw le.detachTemp(c),new Yt(36,`${(0,iye.default)(e)} script failed (exit code ${Ut(a,C,Ct.NUMBER)}, logs can be found here: ${Ut(a,f,Ct.PATH)}); run ${Ut(a,`yarn ${e}`,Ct.CODE)} to investigate`)})}async function Hyt(t,e,r){E6(t,e)&&await uye(t,e,r)}function I6(t){let e=K.extname(t);if(e.match(/\.[cm]?[jt]sx?$/))return!0;if(e===".exe"||e===".bin")return!1;let r=Buffer.alloc(4),s;try{s=le.openSync(t,"r")}catch{return!0}try{le.readSync(s,r,0,r.length,0)}finally{le.closeSync(s)}let a=r.readUint32BE();return!(a===3405691582||a===3489328638||a===2135247942||(a&4294901760)===1297743872)}async function _T(t,{project:e}){let r=e.configuration,s=new Map,a=e.storedPackages.get(t.locatorHash);if(!a)throw new Error(`Package for ${Yr(r,t)} not found in the project`);let n=new x0.Writable,c=r.getLinkers(),f={project:e,report:new Ot({configuration:r,stdout:n})},p=new Set([t.locatorHash]);for(let E of a.dependencies.values()){let C=e.storedResolutions.get(E.descriptorHash);if(!C)throw new Error(`Assertion failed: The resolution (${ri(r,E)}) should have been registered`);p.add(C)}let h=await Promise.all(Array.from(p,async E=>{let C=e.storedPackages.get(E);if(!C)throw new Error(`Assertion failed: The package (${E}) should have been registered`);if(C.bin.size===0)return Yl.skip;let S=c.find(I=>I.supportsPackage(C,f));if(!S)return Yl.skip;let P=null;try{P=await S.findPackageLocation(C,f)}catch(I){if(I.code==="LOCATOR_NOT_INSTALLED")return Yl.skip;throw I}return{dependency:C,packageLocation:P}}));for(let E of h){if(E===Yl.skip)continue;let{dependency:C,packageLocation:S}=E;for(let[P,I]of C.bin){let R=K.resolve(S,I);s.set(P,[C,ue.fromPortablePath(R),I6(R)])}}return s}async function fye(t){return await _T(t.anchoredLocator,{project:t.project})}async function C6(t,e){await Promise.all(Array.from(e,([r,[,s,a]])=>a?P0(t,r,process.execPath,[s]):P0(t,r,s,[])))}async function Aye(t,e,r,{cwd:s,project:a,stdin:n,stdout:c,stderr:f,nodeArgs:p=[],packageAccessibleBinaries:h}){h??=await _T(t,{project:a});let E=h.get(e);if(!E)throw new Error(`Binary not found (${e}) for ${Yr(a.configuration,t)}`);return await le.mktempPromise(async C=>{let[,S]=E,P=await Nv({project:a,locator:t,binFolder:C});await C6(P.BERRY_BIN_FOLDER,h);let I=I6(ue.toPortablePath(S))?Yu(process.execPath,[...p,S,...r],{cwd:s,env:P,stdin:n,stdout:c,stderr:f}):Yu(S,r,{cwd:s,env:P,stdin:n,stdout:c,stderr:f}),R;try{R=await I}finally{await le.removePromise(P.BERRY_BIN_FOLDER)}return R.code})}async function jyt(t,e,r,{cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f}){return await Aye(t.anchoredLocator,e,r,{project:t.project,cwd:s,stdin:a,stdout:n,stderr:c,packageAccessibleBinaries:f})}var iye,sye,x0,oye,Oyt,Lyt,w6=It(()=>{bt();bt();rA();bv();iye=et(d6()),sye=et(Md()),x0=ye("stream");oI();Fc();Fv();Rv();dT();Qc();kc();Np();Yo();oye=(a=>(a.Yarn1="Yarn Classic",a.Yarn2="Yarn",a.Npm="npm",a.Pnpm="pnpm",a))(oye||{});Oyt=2,Lyt=(0,sye.default)(Oyt)});var bI=L((X$t,hye)=>{"use strict";var pye=new Map([["C","cwd"],["f","file"],["z","gzip"],["P","preservePaths"],["U","unlink"],["strip-components","strip"],["stripComponents","strip"],["keep-newer","newer"],["keepNewer","newer"],["keep-newer-files","newer"],["keepNewerFiles","newer"],["k","keep"],["keep-existing","keep"],["keepExisting","keep"],["m","noMtime"],["no-mtime","noMtime"],["p","preserveOwner"],["L","follow"],["h","follow"]]);hye.exports=t=>t?Object.keys(t).map(e=>[pye.has(e)?pye.get(e):e,t[e]]).reduce((e,r)=>(e[r[0]]=r[1],e),Object.create(null)):{}});var xI=L(($$t,Bye)=>{"use strict";var gye=typeof process=="object"&&process?process:{stdout:null,stderr:null},qyt=ye("events"),dye=ye("stream"),mye=ye("string_decoder").StringDecoder,qp=Symbol("EOF"),Gp=Symbol("maybeEmitEnd"),k0=Symbol("emittedEnd"),UT=Symbol("emittingEnd"),Ov=Symbol("emittedError"),HT=Symbol("closed"),yye=Symbol("read"),jT=Symbol("flush"),Eye=Symbol("flushChunk"),fl=Symbol("encoding"),Wp=Symbol("decoder"),qT=Symbol("flowing"),Lv=Symbol("paused"),PI=Symbol("resume"),Vs=Symbol("bufferLength"),B6=Symbol("bufferPush"),v6=Symbol("bufferShift"),zo=Symbol("objectMode"),Zo=Symbol("destroyed"),S6=Symbol("emitData"),Iye=Symbol("emitEnd"),D6=Symbol("emitEnd2"),Yp=Symbol("async"),Mv=t=>Promise.resolve().then(t),Cye=global._MP_NO_ITERATOR_SYMBOLS_!=="1",Gyt=Cye&&Symbol.asyncIterator||Symbol("asyncIterator not implemented"),Wyt=Cye&&Symbol.iterator||Symbol("iterator not implemented"),Yyt=t=>t==="end"||t==="finish"||t==="prefinish",Vyt=t=>t instanceof ArrayBuffer||typeof t=="object"&&t.constructor&&t.constructor.name==="ArrayBuffer"&&t.byteLength>=0,Kyt=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),GT=class{constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[PI](),r.on("drain",this.ondrain)}unpipe(){this.dest.removeListener("drain",this.ondrain)}proxyErrors(){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},b6=class extends GT{unpipe(){this.src.removeListener("error",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit("error",a),e.on("error",this.proxyErrors)}};Bye.exports=class wye extends dye{constructor(e){super(),this[qT]=!1,this[Lv]=!1,this.pipes=[],this.buffer=[],this[zo]=e&&e.objectMode||!1,this[zo]?this[fl]=null:this[fl]=e&&e.encoding||null,this[fl]==="buffer"&&(this[fl]=null),this[Yp]=e&&!!e.async||!1,this[Wp]=this[fl]?new mye(this[fl]):null,this[qp]=!1,this[k0]=!1,this[UT]=!1,this[HT]=!1,this[Ov]=null,this.writable=!0,this.readable=!0,this[Vs]=0,this[Zo]=!1}get bufferLength(){return this[Vs]}get encoding(){return this[fl]}set encoding(e){if(this[zo])throw new Error("cannot set encoding in objectMode");if(this[fl]&&e!==this[fl]&&(this[Wp]&&this[Wp].lastNeed||this[Vs]))throw new Error("cannot change encoding");this[fl]!==e&&(this[Wp]=e?new mye(e):null,this.buffer.length&&(this.buffer=this.buffer.map(r=>this[Wp].write(r)))),this[fl]=e}setEncoding(e){this.encoding=e}get objectMode(){return this[zo]}set objectMode(e){this[zo]=this[zo]||!!e}get async(){return this[Yp]}set async(e){this[Yp]=this[Yp]||!!e}write(e,r,s){if(this[qp])throw new Error("write after end");if(this[Zo])return this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"})),!0;typeof r=="function"&&(s=r,r="utf8"),r||(r="utf8");let a=this[Yp]?Mv:n=>n();return!this[zo]&&!Buffer.isBuffer(e)&&(Kyt(e)?e=Buffer.from(e.buffer,e.byteOffset,e.byteLength):Vyt(e)?e=Buffer.from(e):typeof e!="string"&&(this.objectMode=!0)),this[zo]?(this.flowing&&this[Vs]!==0&&this[jT](!0),this.flowing?this.emit("data",e):this[B6](e),this[Vs]!==0&&this.emit("readable"),s&&a(s),this.flowing):e.length?(typeof e=="string"&&!(r===this[fl]&&!this[Wp].lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[fl]&&(e=this[Wp].write(e)),this.flowing&&this[Vs]!==0&&this[jT](!0),this.flowing?this.emit("data",e):this[B6](e),this[Vs]!==0&&this.emit("readable"),s&&a(s),this.flowing):(this[Vs]!==0&&this.emit("readable"),s&&a(s),this.flowing)}read(e){if(this[Zo])return null;if(this[Vs]===0||e===0||e>this[Vs])return this[Gp](),null;this[zo]&&(e=null),this.buffer.length>1&&!this[zo]&&(this.encoding?this.buffer=[this.buffer.join("")]:this.buffer=[Buffer.concat(this.buffer,this[Vs])]);let r=this[yye](e||null,this.buffer[0]);return this[Gp](),r}[yye](e,r){return e===r.length||e===null?this[v6]():(this.buffer[0]=r.slice(e),r=r.slice(0,e),this[Vs]-=e),this.emit("data",r),!this.buffer.length&&!this[qp]&&this.emit("drain"),r}end(e,r,s){return typeof e=="function"&&(s=e,e=null),typeof r=="function"&&(s=r,r="utf8"),e&&this.write(e,r),s&&this.once("end",s),this[qp]=!0,this.writable=!1,(this.flowing||!this[Lv])&&this[Gp](),this}[PI](){this[Zo]||(this[Lv]=!1,this[qT]=!0,this.emit("resume"),this.buffer.length?this[jT]():this[qp]?this[Gp]():this.emit("drain"))}resume(){return this[PI]()}pause(){this[qT]=!1,this[Lv]=!0}get destroyed(){return this[Zo]}get flowing(){return this[qT]}get paused(){return this[Lv]}[B6](e){this[zo]?this[Vs]+=1:this[Vs]+=e.length,this.buffer.push(e)}[v6](){return this.buffer.length&&(this[zo]?this[Vs]-=1:this[Vs]-=this.buffer[0].length),this.buffer.shift()}[jT](e){do;while(this[Eye](this[v6]()));!e&&!this.buffer.length&&!this[qp]&&this.emit("drain")}[Eye](e){return e?(this.emit("data",e),this.flowing):!1}pipe(e,r){if(this[Zo])return;let s=this[k0];return r=r||{},e===gye.stdout||e===gye.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this.pipes.push(r.proxyErrors?new b6(this,e,r):new GT(this,e,r)),this[Yp]?Mv(()=>this[PI]()):this[PI]()),e}unpipe(e){let r=this.pipes.find(s=>s.dest===e);r&&(this.pipes.splice(this.pipes.indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);return e==="data"&&!this.pipes.length&&!this.flowing?this[PI]():e==="readable"&&this[Vs]!==0?super.emit("readable"):Yyt(e)&&this[k0]?(super.emit(e),this.removeAllListeners(e)):e==="error"&&this[Ov]&&(this[Yp]?Mv(()=>r.call(this,this[Ov])):r.call(this,this[Ov])),s}get emittedEnd(){return this[k0]}[Gp](){!this[UT]&&!this[k0]&&!this[Zo]&&this.buffer.length===0&&this[qp]&&(this[UT]=!0,this.emit("end"),this.emit("prefinish"),this.emit("finish"),this[HT]&&this.emit("close"),this[UT]=!1)}emit(e,r,...s){if(e!=="error"&&e!=="close"&&e!==Zo&&this[Zo])return;if(e==="data")return r?this[Yp]?Mv(()=>this[S6](r)):this[S6](r):!1;if(e==="end")return this[Iye]();if(e==="close"){if(this[HT]=!0,!this[k0]&&!this[Zo])return;let n=super.emit("close");return this.removeAllListeners("close"),n}else if(e==="error"){this[Ov]=r;let n=super.emit("error",r);return this[Gp](),n}else if(e==="resume"){let n=super.emit("resume");return this[Gp](),n}else if(e==="finish"||e==="prefinish"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,r,...s);return this[Gp](),a}[S6](e){for(let s of this.pipes)s.dest.write(e)===!1&&this.pause();let r=super.emit("data",e);return this[Gp](),r}[Iye](){this[k0]||(this[k0]=!0,this.readable=!1,this[Yp]?Mv(()=>this[D6]()):this[D6]())}[D6](){if(this[Wp]){let r=this[Wp].end();if(r){for(let s of this.pipes)s.dest.write(r);super.emit("data",r)}}for(let r of this.pipes)r.end();let e=super.emit("end");return this.removeAllListeners("end"),e}collect(){let e=[];this[zo]||(e.dataLength=0);let r=this.promise();return this.on("data",s=>{e.push(s),this[zo]||(e.dataLength+=s.length)}),r.then(()=>e)}concat(){return this[zo]?Promise.reject(new Error("cannot concat in objectMode")):this.collect().then(e=>this[zo]?Promise.reject(new Error("cannot concat in objectMode")):this[fl]?e.join(""):Buffer.concat(e,e.dataLength))}promise(){return new Promise((e,r)=>{this.on(Zo,()=>r(new Error("stream destroyed"))),this.on("error",s=>r(s)),this.on("end",()=>e())})}[Gyt](){return{next:()=>{let r=this.read();if(r!==null)return Promise.resolve({done:!1,value:r});if(this[qp])return Promise.resolve({done:!0});let s=null,a=null,n=h=>{this.removeListener("data",c),this.removeListener("end",f),a(h)},c=h=>{this.removeListener("error",n),this.removeListener("end",f),this.pause(),s({value:h,done:!!this[qp]})},f=()=>{this.removeListener("error",n),this.removeListener("data",c),s({done:!0})},p=()=>n(new Error("stream destroyed"));return new Promise((h,E)=>{a=E,s=h,this.once(Zo,p),this.once("error",n),this.once("end",f),this.once("data",c)})}}}[Wyt](){return{next:()=>{let r=this.read();return{value:r,done:r===null}}}}destroy(e){return this[Zo]?(e?this.emit("error",e):this.emit(Zo),this):(this[Zo]=!0,this.buffer.length=0,this[Vs]=0,typeof this.close=="function"&&!this[HT]&&this.close(),e?this.emit("error",e):this.emit(Zo),this)}static isStream(e){return!!e&&(e instanceof wye||e instanceof dye||e instanceof qyt&&(typeof e.pipe=="function"||typeof e.write=="function"&&typeof e.end=="function"))}}});var Sye=L((eer,vye)=>{var Jyt=ye("zlib").constants||{ZLIB_VERNUM:4736};vye.exports=Object.freeze(Object.assign(Object.create(null),{Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_VERSION_ERROR:-6,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,DEFLATE:1,INFLATE:2,GZIP:3,GUNZIP:4,DEFLATERAW:5,INFLATERAW:6,UNZIP:7,BROTLI_DECODE:8,BROTLI_ENCODE:9,Z_MIN_WINDOWBITS:8,Z_MAX_WINDOWBITS:15,Z_DEFAULT_WINDOWBITS:15,Z_MIN_CHUNK:64,Z_MAX_CHUNK:1/0,Z_DEFAULT_CHUNK:16384,Z_MIN_MEMLEVEL:1,Z_MAX_MEMLEVEL:9,Z_DEFAULT_MEMLEVEL:8,Z_MIN_LEVEL:-1,Z_MAX_LEVEL:9,Z_DEFAULT_LEVEL:-1,BROTLI_OPERATION_PROCESS:0,BROTLI_OPERATION_FLUSH:1,BROTLI_OPERATION_FINISH:2,BROTLI_OPERATION_EMIT_METADATA:3,BROTLI_MODE_GENERIC:0,BROTLI_MODE_TEXT:1,BROTLI_MODE_FONT:2,BROTLI_DEFAULT_MODE:0,BROTLI_MIN_QUALITY:0,BROTLI_MAX_QUALITY:11,BROTLI_DEFAULT_QUALITY:11,BROTLI_MIN_WINDOW_BITS:10,BROTLI_MAX_WINDOW_BITS:24,BROTLI_LARGE_MAX_WINDOW_BITS:30,BROTLI_DEFAULT_WINDOW:22,BROTLI_MIN_INPUT_BLOCK_BITS:16,BROTLI_MAX_INPUT_BLOCK_BITS:24,BROTLI_PARAM_MODE:0,BROTLI_PARAM_QUALITY:1,BROTLI_PARAM_LGWIN:2,BROTLI_PARAM_LGBLOCK:3,BROTLI_PARAM_DISABLE_LITERAL_CONTEXT_MODELING:4,BROTLI_PARAM_SIZE_HINT:5,BROTLI_PARAM_LARGE_WINDOW:6,BROTLI_PARAM_NPOSTFIX:7,BROTLI_PARAM_NDIRECT:8,BROTLI_DECODER_RESULT_ERROR:0,BROTLI_DECODER_RESULT_SUCCESS:1,BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:2,BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_PARAM_DISABLE_RING_BUFFER_REALLOCATION:0,BROTLI_DECODER_PARAM_LARGE_WINDOW:1,BROTLI_DECODER_NO_ERROR:0,BROTLI_DECODER_SUCCESS:1,BROTLI_DECODER_NEEDS_MORE_INPUT:2,BROTLI_DECODER_NEEDS_MORE_OUTPUT:3,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE:-1,BROTLI_DECODER_ERROR_FORMAT_RESERVED:-2,BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE:-3,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET:-4,BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME:-5,BROTLI_DECODER_ERROR_FORMAT_CL_SPACE:-6,BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE:-7,BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT:-8,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1:-9,BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2:-10,BROTLI_DECODER_ERROR_FORMAT_TRANSFORM:-11,BROTLI_DECODER_ERROR_FORMAT_DICTIONARY:-12,BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS:-13,BROTLI_DECODER_ERROR_FORMAT_PADDING_1:-14,BROTLI_DECODER_ERROR_FORMAT_PADDING_2:-15,BROTLI_DECODER_ERROR_FORMAT_DISTANCE:-16,BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET:-19,BROTLI_DECODER_ERROR_INVALID_ARGUMENTS:-20,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES:-21,BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS:-22,BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP:-25,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1:-26,BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2:-27,BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES:-30,BROTLI_DECODER_ERROR_UNREACHABLE:-31},Jyt))});var q6=L(zl=>{"use strict";var T6=ye("assert"),Q0=ye("buffer").Buffer,Pye=ye("zlib"),fm=zl.constants=Sye(),zyt=xI(),Dye=Q0.concat,Am=Symbol("_superWrite"),QI=class extends Error{constructor(e){super("zlib: "+e.message),this.code=e.code,this.errno=e.errno,this.code||(this.code="ZLIB_ERROR"),this.message="zlib: "+e.message,Error.captureStackTrace(this,this.constructor)}get name(){return"ZlibError"}},Zyt=Symbol("opts"),_v=Symbol("flushFlag"),bye=Symbol("finishFlushFlag"),j6=Symbol("fullFlushFlag"),Ei=Symbol("handle"),WT=Symbol("onError"),kI=Symbol("sawError"),P6=Symbol("level"),x6=Symbol("strategy"),k6=Symbol("ended"),ter=Symbol("_defaultFullFlush"),YT=class extends zyt{constructor(e,r){if(!e||typeof e!="object")throw new TypeError("invalid options for ZlibBase constructor");super(e),this[kI]=!1,this[k6]=!1,this[Zyt]=e,this[_v]=e.flush,this[bye]=e.finishFlush;try{this[Ei]=new Pye[r](e)}catch(s){throw new QI(s)}this[WT]=s=>{this[kI]||(this[kI]=!0,this.close(),this.emit("error",s))},this[Ei].on("error",s=>this[WT](new QI(s))),this.once("end",()=>this.close)}close(){this[Ei]&&(this[Ei].close(),this[Ei]=null,this.emit("close"))}reset(){if(!this[kI])return T6(this[Ei],"zlib binding closed"),this[Ei].reset()}flush(e){this.ended||(typeof e!="number"&&(e=this[j6]),this.write(Object.assign(Q0.alloc(0),{[_v]:e})))}end(e,r,s){return e&&this.write(e,r),this.flush(this[bye]),this[k6]=!0,super.end(null,null,s)}get ended(){return this[k6]}write(e,r,s){if(typeof r=="function"&&(s=r,r="utf8"),typeof e=="string"&&(e=Q0.from(e,r)),this[kI])return;T6(this[Ei],"zlib binding closed");let a=this[Ei]._handle,n=a.close;a.close=()=>{};let c=this[Ei].close;this[Ei].close=()=>{},Q0.concat=h=>h;let f;try{let h=typeof e[_v]=="number"?e[_v]:this[_v];f=this[Ei]._processChunk(e,h),Q0.concat=Dye}catch(h){Q0.concat=Dye,this[WT](new QI(h))}finally{this[Ei]&&(this[Ei]._handle=a,a.close=n,this[Ei].close=c,this[Ei].removeAllListeners("error"))}this[Ei]&&this[Ei].on("error",h=>this[WT](new QI(h)));let p;if(f)if(Array.isArray(f)&&f.length>0){p=this[Am](Q0.from(f[0]));for(let h=1;h{this.flush(a),n()};try{this[Ei].params(e,r)}finally{this[Ei].flush=s}this[Ei]&&(this[P6]=e,this[x6]=r)}}}},R6=class extends Vp{constructor(e){super(e,"Deflate")}},F6=class extends Vp{constructor(e){super(e,"Inflate")}},Q6=Symbol("_portable"),N6=class extends Vp{constructor(e){super(e,"Gzip"),this[Q6]=e&&!!e.portable}[Am](e){return this[Q6]?(this[Q6]=!1,e[9]=255,super[Am](e)):super[Am](e)}},O6=class extends Vp{constructor(e){super(e,"Gunzip")}},L6=class extends Vp{constructor(e){super(e,"DeflateRaw")}},M6=class extends Vp{constructor(e){super(e,"InflateRaw")}},_6=class extends Vp{constructor(e){super(e,"Unzip")}},VT=class extends YT{constructor(e,r){e=e||{},e.flush=e.flush||fm.BROTLI_OPERATION_PROCESS,e.finishFlush=e.finishFlush||fm.BROTLI_OPERATION_FINISH,super(e,r),this[j6]=fm.BROTLI_OPERATION_FLUSH}},U6=class extends VT{constructor(e){super(e,"BrotliCompress")}},H6=class extends VT{constructor(e){super(e,"BrotliDecompress")}};zl.Deflate=R6;zl.Inflate=F6;zl.Gzip=N6;zl.Gunzip=O6;zl.DeflateRaw=L6;zl.InflateRaw=M6;zl.Unzip=_6;typeof Pye.BrotliCompress=="function"?(zl.BrotliCompress=U6,zl.BrotliDecompress=H6):zl.BrotliCompress=zl.BrotliDecompress=class{constructor(){throw new Error("Brotli is not supported in this version of Node.js")}}});var TI=L((ier,xye)=>{var Xyt=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform;xye.exports=Xyt!=="win32"?t=>t:t=>t&&t.replace(/\\/g,"/")});var KT=L((oer,kye)=>{"use strict";var $yt=xI(),G6=TI(),W6=Symbol("slurp");kye.exports=class extends $yt{constructor(e,r,s){switch(super(),this.pause(),this.extended=r,this.globalExtended=s,this.header=e,this.startBlockSize=512*Math.ceil(e.size/512),this.blockRemain=this.startBlockSize,this.remain=e.size,this.type=e.type,this.meta=!1,this.ignore=!1,this.type){case"File":case"OldFile":case"Link":case"SymbolicLink":case"CharacterDevice":case"BlockDevice":case"Directory":case"FIFO":case"ContiguousFile":case"GNUDumpDir":break;case"NextFileHasLongLinkpath":case"NextFileHasLongPath":case"OldGnuLongPath":case"GlobalExtendedHeader":case"ExtendedHeader":case"OldExtendedHeader":this.meta=!0;break;default:this.ignore=!0}this.path=G6(e.path),this.mode=e.mode,this.mode&&(this.mode=this.mode&4095),this.uid=e.uid,this.gid=e.gid,this.uname=e.uname,this.gname=e.gname,this.size=e.size,this.mtime=e.mtime,this.atime=e.atime,this.ctime=e.ctime,this.linkpath=G6(e.linkpath),this.uname=e.uname,this.gname=e.gname,r&&this[W6](r),s&&this[W6](s,!0)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error("writing more to entry than is appropriate");let s=this.remain,a=this.blockRemain;return this.remain=Math.max(0,s-r),this.blockRemain=Math.max(0,a-r),this.ignore?!0:s>=r?super.write(e):super.write(e.slice(0,s))}[W6](e,r){for(let s in e)e[s]!==null&&e[s]!==void 0&&!(r&&s==="path")&&(this[s]=s==="path"||s==="linkpath"?G6(e[s]):e[s])}}});var Y6=L(JT=>{"use strict";JT.name=new Map([["0","File"],["","OldFile"],["1","Link"],["2","SymbolicLink"],["3","CharacterDevice"],["4","BlockDevice"],["5","Directory"],["6","FIFO"],["7","ContiguousFile"],["g","GlobalExtendedHeader"],["x","ExtendedHeader"],["A","SolarisACL"],["D","GNUDumpDir"],["I","Inode"],["K","NextFileHasLongLinkpath"],["L","NextFileHasLongPath"],["M","ContinuationFile"],["N","OldGnuLongPath"],["S","SparseFile"],["V","TapeVolumeHeader"],["X","OldExtendedHeader"]]);JT.code=new Map(Array.from(JT.name).map(t=>[t[1],t[0]]))});var Fye=L((ler,Rye)=>{"use strict";var eEt=(t,e)=>{if(Number.isSafeInteger(t))t<0?rEt(t,e):tEt(t,e);else throw Error("cannot encode number outside of javascript safe integer range");return e},tEt=(t,e)=>{e[0]=128;for(var r=e.length;r>1;r--)e[r-1]=t&255,t=Math.floor(t/256)},rEt=(t,e)=>{e[0]=255;var r=!1;t=t*-1;for(var s=e.length;s>1;s--){var a=t&255;t=Math.floor(t/256),r?e[s-1]=Qye(a):a===0?e[s-1]=0:(r=!0,e[s-1]=Tye(a))}},nEt=t=>{let e=t[0],r=e===128?sEt(t.slice(1,t.length)):e===255?iEt(t):null;if(r===null)throw Error("invalid base256 encoding");if(!Number.isSafeInteger(r))throw Error("parsed number outside of javascript safe integer range");return r},iEt=t=>{for(var e=t.length,r=0,s=!1,a=e-1;a>-1;a--){var n=t[a],c;s?c=Qye(n):n===0?c=n:(s=!0,c=Tye(n)),c!==0&&(r-=c*Math.pow(256,e-a-1))}return r},sEt=t=>{for(var e=t.length,r=0,s=e-1;s>-1;s--){var a=t[s];a!==0&&(r+=a*Math.pow(256,e-s-1))}return r},Qye=t=>(255^t)&255,Tye=t=>(255^t)+1&255;Rye.exports={encode:eEt,parse:nEt}});var FI=L((cer,Oye)=>{"use strict";var V6=Y6(),RI=ye("path").posix,Nye=Fye(),K6=Symbol("slurp"),Zl=Symbol("type"),Z6=class{constructor(e,r,s,a){this.cksumValid=!1,this.needPax=!1,this.nullBlock=!1,this.block=null,this.path=null,this.mode=null,this.uid=null,this.gid=null,this.size=null,this.mtime=null,this.cksum=null,this[Zl]="0",this.linkpath=null,this.uname=null,this.gname=null,this.devmaj=0,this.devmin=0,this.atime=null,this.ctime=null,Buffer.isBuffer(e)?this.decode(e,r||0,s,a):e&&this.set(e)}decode(e,r,s,a){if(r||(r=0),!e||!(e.length>=r+512))throw new Error("need 512 bytes for header");if(this.path=pm(e,r,100),this.mode=T0(e,r+100,8),this.uid=T0(e,r+108,8),this.gid=T0(e,r+116,8),this.size=T0(e,r+124,12),this.mtime=J6(e,r+136,12),this.cksum=T0(e,r+148,12),this[K6](s),this[K6](a,!0),this[Zl]=pm(e,r+156,1),this[Zl]===""&&(this[Zl]="0"),this[Zl]==="0"&&this.path.substr(-1)==="/"&&(this[Zl]="5"),this[Zl]==="5"&&(this.size=0),this.linkpath=pm(e,r+157,100),e.slice(r+257,r+265).toString()==="ustar\x0000")if(this.uname=pm(e,r+265,32),this.gname=pm(e,r+297,32),this.devmaj=T0(e,r+329,8),this.devmin=T0(e,r+337,8),e[r+475]!==0){let c=pm(e,r+345,155);this.path=c+"/"+this.path}else{let c=pm(e,r+345,130);c&&(this.path=c+"/"+this.path),this.atime=J6(e,r+476,12),this.ctime=J6(e,r+488,12)}let n=8*32;for(let c=r;c=r+512))throw new Error("need 512 bytes for header");let s=this.ctime||this.atime?130:155,a=oEt(this.path||"",s),n=a[0],c=a[1];this.needPax=a[2],this.needPax=hm(e,r,100,n)||this.needPax,this.needPax=R0(e,r+100,8,this.mode)||this.needPax,this.needPax=R0(e,r+108,8,this.uid)||this.needPax,this.needPax=R0(e,r+116,8,this.gid)||this.needPax,this.needPax=R0(e,r+124,12,this.size)||this.needPax,this.needPax=z6(e,r+136,12,this.mtime)||this.needPax,e[r+156]=this[Zl].charCodeAt(0),this.needPax=hm(e,r+157,100,this.linkpath)||this.needPax,e.write("ustar\x0000",r+257,8),this.needPax=hm(e,r+265,32,this.uname)||this.needPax,this.needPax=hm(e,r+297,32,this.gname)||this.needPax,this.needPax=R0(e,r+329,8,this.devmaj)||this.needPax,this.needPax=R0(e,r+337,8,this.devmin)||this.needPax,this.needPax=hm(e,r+345,s,c)||this.needPax,e[r+475]!==0?this.needPax=hm(e,r+345,155,c)||this.needPax:(this.needPax=hm(e,r+345,130,c)||this.needPax,this.needPax=z6(e,r+476,12,this.atime)||this.needPax,this.needPax=z6(e,r+488,12,this.ctime)||this.needPax);let f=8*32;for(let p=r;p{let s=t,a="",n,c=RI.parse(t).root||".";if(Buffer.byteLength(s)<100)n=[s,a,!1];else{a=RI.dirname(s),s=RI.basename(s);do Buffer.byteLength(s)<=100&&Buffer.byteLength(a)<=e?n=[s,a,!1]:Buffer.byteLength(s)>100&&Buffer.byteLength(a)<=e?n=[s.substr(0,99),a,!0]:(s=RI.join(RI.basename(a),s),a=RI.dirname(a));while(a!==c&&!n);n||(n=[t.substr(0,99),"",!0])}return n},pm=(t,e,r)=>t.slice(e,e+r).toString("utf8").replace(/\0.*/,""),J6=(t,e,r)=>aEt(T0(t,e,r)),aEt=t=>t===null?null:new Date(t*1e3),T0=(t,e,r)=>t[e]&128?Nye.parse(t.slice(e,e+r)):cEt(t,e,r),lEt=t=>isNaN(t)?null:t,cEt=(t,e,r)=>lEt(parseInt(t.slice(e,e+r).toString("utf8").replace(/\0.*$/,"").trim(),8)),uEt={12:8589934591,8:2097151},R0=(t,e,r,s)=>s===null?!1:s>uEt[r]||s<0?(Nye.encode(s,t.slice(e,e+r)),!0):(fEt(t,e,r,s),!1),fEt=(t,e,r,s)=>t.write(AEt(s,r),e,r,"ascii"),AEt=(t,e)=>pEt(Math.floor(t).toString(8),e),pEt=(t,e)=>(t.length===e-1?t:new Array(e-t.length-1).join("0")+t+" ")+"\0",z6=(t,e,r,s)=>s===null?!1:R0(t,e,r,s.getTime()/1e3),hEt=new Array(156).join("\0"),hm=(t,e,r,s)=>s===null?!1:(t.write(s+hEt,e,r,"utf8"),s.length!==Buffer.byteLength(s)||s.length>r);Oye.exports=Z6});var zT=L((uer,Lye)=>{"use strict";var gEt=FI(),dEt=ye("path"),Uv=class{constructor(e,r){this.atime=e.atime||null,this.charset=e.charset||null,this.comment=e.comment||null,this.ctime=e.ctime||null,this.gid=e.gid||null,this.gname=e.gname||null,this.linkpath=e.linkpath||null,this.mtime=e.mtime||null,this.path=e.path||null,this.size=e.size||null,this.uid=e.uid||null,this.uname=e.uname||null,this.dev=e.dev||null,this.ino=e.ino||null,this.nlink=e.nlink||null,this.global=r||!1}encode(){let e=this.encodeBody();if(e==="")return null;let r=Buffer.byteLength(e),s=512*Math.ceil(1+r/512),a=Buffer.allocUnsafe(s);for(let n=0;n<512;n++)a[n]=0;new gEt({path:("PaxHeader/"+dEt.basename(this.path)).slice(0,99),mode:this.mode||420,uid:this.uid||null,gid:this.gid||null,size:r,mtime:this.mtime||null,type:this.global?"GlobalExtendedHeader":"ExtendedHeader",linkpath:"",uname:this.uname||"",gname:this.gname||"",devmaj:0,devmin:0,atime:this.atime||null,ctime:this.ctime||null}).encode(a),a.write(e,512,r,"utf8");for(let n=r+512;n=Math.pow(10,n)&&(n+=1),n+a+s}};Uv.parse=(t,e,r)=>new Uv(mEt(yEt(t),e),r);var mEt=(t,e)=>e?Object.keys(t).reduce((r,s)=>(r[s]=t[s],r),e):t,yEt=t=>t.replace(/\n$/,"").split(` +`).reduce(EEt,Object.create(null)),EEt=(t,e)=>{let r=parseInt(e,10);if(r!==Buffer.byteLength(e)+1)return t;e=e.substr((r+" ").length);let s=e.split("="),a=s.shift().replace(/^SCHILY\.(dev|ino|nlink)/,"$1");if(!a)return t;let n=s.join("=");return t[a]=/^([A-Z]+\.)?([mac]|birth|creation)time$/.test(a)?new Date(n*1e3):/^[0-9]+$/.test(n)?+n:n,t};Lye.exports=Uv});var NI=L((fer,Mye)=>{Mye.exports=t=>{let e=t.length-1,r=-1;for(;e>-1&&t.charAt(e)==="/";)r=e,e--;return r===-1?t:t.slice(0,r)}});var ZT=L((Aer,_ye)=>{"use strict";_ye.exports=t=>class extends t{warn(e,r,s={}){this.file&&(s.file=this.file),this.cwd&&(s.cwd=this.cwd),s.code=r instanceof Error&&r.code||e,s.tarCode=e,!this.strict&&s.recoverable!==!1?(r instanceof Error&&(s=Object.assign(r,s),r=r.message),this.emit("warn",s.tarCode,r,s)):r instanceof Error?this.emit("error",Object.assign(r,s)):this.emit("error",Object.assign(new Error(`${e}: ${r}`),s))}}});var $6=L((her,Uye)=>{"use strict";var XT=["|","<",">","?",":"],X6=XT.map(t=>String.fromCharCode(61440+t.charCodeAt(0))),IEt=new Map(XT.map((t,e)=>[t,X6[e]])),CEt=new Map(X6.map((t,e)=>[t,XT[e]]));Uye.exports={encode:t=>XT.reduce((e,r)=>e.split(r).join(IEt.get(r)),t),decode:t=>X6.reduce((e,r)=>e.split(r).join(CEt.get(r)),t)}});var eq=L((ger,jye)=>{var{isAbsolute:wEt,parse:Hye}=ye("path").win32;jye.exports=t=>{let e="",r=Hye(t);for(;wEt(t)||r.root;){let s=t.charAt(0)==="/"&&t.slice(0,4)!=="//?/"?"/":r.root;t=t.substr(s.length),e+=s,r=Hye(t)}return[e,t]}});var Gye=L((der,qye)=>{"use strict";qye.exports=(t,e,r)=>(t&=4095,r&&(t=(t|384)&-19),e&&(t&256&&(t|=64),t&32&&(t|=8),t&4&&(t|=1)),t)});var uq=L((Eer,iEe)=>{"use strict";var Zye=xI(),Xye=zT(),$ye=FI(),sA=ye("fs"),Wye=ye("path"),iA=TI(),BEt=NI(),eEe=(t,e)=>e?(t=iA(t).replace(/^\.(\/|$)/,""),BEt(e)+"/"+t):iA(t),vEt=16*1024*1024,Yye=Symbol("process"),Vye=Symbol("file"),Kye=Symbol("directory"),rq=Symbol("symlink"),Jye=Symbol("hardlink"),Hv=Symbol("header"),$T=Symbol("read"),nq=Symbol("lstat"),eR=Symbol("onlstat"),iq=Symbol("onread"),sq=Symbol("onreadlink"),oq=Symbol("openfile"),aq=Symbol("onopenfile"),F0=Symbol("close"),tR=Symbol("mode"),lq=Symbol("awaitDrain"),tq=Symbol("ondrain"),oA=Symbol("prefix"),zye=Symbol("hadError"),tEe=ZT(),SEt=$6(),rEe=eq(),nEe=Gye(),rR=tEe(class extends Zye{constructor(e,r){if(r=r||{},super(r),typeof e!="string")throw new TypeError("path is required");this.path=iA(e),this.portable=!!r.portable,this.myuid=process.getuid&&process.getuid()||0,this.myuser=process.env.USER||"",this.maxReadSize=r.maxReadSize||vEt,this.linkCache=r.linkCache||new Map,this.statCache=r.statCache||new Map,this.preservePaths=!!r.preservePaths,this.cwd=iA(r.cwd||process.cwd()),this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.mtime=r.mtime||null,this.prefix=r.prefix?iA(r.prefix):null,this.fd=null,this.blockLen=null,this.blockRemain=null,this.buf=null,this.offset=null,this.length=null,this.pos=null,this.remain=null,typeof r.onwarn=="function"&&this.on("warn",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=rEe(this.path);a&&(this.path=n,s=a)}this.win32=!!r.win32||process.platform==="win32",this.win32&&(this.path=SEt.decode(this.path.replace(/\\/g,"/")),e=e.replace(/\\/g,"/")),this.absolute=iA(r.absolute||Wye.resolve(this.cwd,e)),this.path===""&&(this.path="./"),s&&this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.statCache.has(this.absolute)?this[eR](this.statCache.get(this.absolute)):this[nq]()}emit(e,...r){return e==="error"&&(this[zye]=!0),super.emit(e,...r)}[nq](){sA.lstat(this.absolute,(e,r)=>{if(e)return this.emit("error",e);this[eR](r)})}[eR](e){this.statCache.set(this.absolute,e),this.stat=e,e.isFile()||(e.size=0),this.type=bEt(e),this.emit("stat",e),this[Yye]()}[Yye](){switch(this.type){case"File":return this[Vye]();case"Directory":return this[Kye]();case"SymbolicLink":return this[rq]();default:return this.end()}}[tR](e){return nEe(e,this.type==="Directory",this.portable)}[oA](e){return eEe(e,this.prefix)}[Hv](){this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.header=new $ye({path:this[oA](this.path),linkpath:this.type==="Link"?this[oA](this.linkpath):this.linkpath,mode:this[tR](this.stat.mode),uid:this.portable?null:this.stat.uid,gid:this.portable?null:this.stat.gid,size:this.stat.size,mtime:this.noMtime?null:this.mtime||this.stat.mtime,type:this.type,uname:this.portable?null:this.stat.uid===this.myuid?this.myuser:"",atime:this.portable?null:this.stat.atime,ctime:this.portable?null:this.stat.ctime}),this.header.encode()&&!this.noPax&&super.write(new Xye({atime:this.portable?null:this.header.atime,ctime:this.portable?null:this.header.ctime,gid:this.portable?null:this.header.gid,mtime:this.noMtime?null:this.mtime||this.header.mtime,path:this[oA](this.path),linkpath:this.type==="Link"?this[oA](this.linkpath):this.linkpath,size:this.header.size,uid:this.portable?null:this.header.uid,uname:this.portable?null:this.header.uname,dev:this.portable?null:this.stat.dev,ino:this.portable?null:this.stat.ino,nlink:this.portable?null:this.stat.nlink}).encode()),super.write(this.header.block)}[Kye](){this.path.substr(-1)!=="/"&&(this.path+="/"),this.stat.size=0,this[Hv](),this.end()}[rq](){sA.readlink(this.absolute,(e,r)=>{if(e)return this.emit("error",e);this[sq](r)})}[sq](e){this.linkpath=iA(e),this[Hv](),this.end()}[Jye](e){this.type="Link",this.linkpath=iA(Wye.relative(this.cwd,e)),this.stat.size=0,this[Hv](),this.end()}[Vye](){if(this.stat.nlink>1){let e=this.stat.dev+":"+this.stat.ino;if(this.linkCache.has(e)){let r=this.linkCache.get(e);if(r.indexOf(this.cwd)===0)return this[Jye](r)}this.linkCache.set(e,this.absolute)}if(this[Hv](),this.stat.size===0)return this.end();this[oq]()}[oq](){sA.open(this.absolute,"r",(e,r)=>{if(e)return this.emit("error",e);this[aq](r)})}[aq](e){if(this.fd=e,this[zye])return this[F0]();this.blockLen=512*Math.ceil(this.stat.size/512),this.blockRemain=this.blockLen;let r=Math.min(this.blockLen,this.maxReadSize);this.buf=Buffer.allocUnsafe(r),this.offset=0,this.pos=0,this.remain=this.stat.size,this.length=this.buf.length,this[$T]()}[$T](){let{fd:e,buf:r,offset:s,length:a,pos:n}=this;sA.read(e,r,s,a,n,(c,f)=>{if(c)return this[F0](()=>this.emit("error",c));this[iq](f)})}[F0](e){sA.close(this.fd,e)}[iq](e){if(e<=0&&this.remain>0){let a=new Error("encountered unexpected EOF");return a.path=this.absolute,a.syscall="read",a.code="EOF",this[F0](()=>this.emit("error",a))}if(e>this.remain){let a=new Error("did not encounter expected EOF");return a.path=this.absolute,a.syscall="read",a.code="EOF",this[F0](()=>this.emit("error",a))}if(e===this.remain)for(let a=e;athis[tq]())}[lq](e){this.once("drain",e)}write(e){if(this.blockRemaine?this.emit("error",e):this.end());this.offset>=this.length&&(this.buf=Buffer.allocUnsafe(Math.min(this.blockRemain,this.buf.length)),this.offset=0),this.length=this.buf.length-this.offset,this[$T]()}}),cq=class extends rR{[nq](){this[eR](sA.lstatSync(this.absolute))}[rq](){this[sq](sA.readlinkSync(this.absolute))}[oq](){this[aq](sA.openSync(this.absolute,"r"))}[$T](){let e=!0;try{let{fd:r,buf:s,offset:a,length:n,pos:c}=this,f=sA.readSync(r,s,a,n,c);this[iq](f),e=!1}finally{if(e)try{this[F0](()=>{})}catch{}}}[lq](e){e()}[F0](e){sA.closeSync(this.fd),e()}},DEt=tEe(class extends Zye{constructor(e,r){r=r||{},super(r),this.preservePaths=!!r.preservePaths,this.portable=!!r.portable,this.strict=!!r.strict,this.noPax=!!r.noPax,this.noMtime=!!r.noMtime,this.readEntry=e,this.type=e.type,this.type==="Directory"&&this.portable&&(this.noMtime=!0),this.prefix=r.prefix||null,this.path=iA(e.path),this.mode=this[tR](e.mode),this.uid=this.portable?null:e.uid,this.gid=this.portable?null:e.gid,this.uname=this.portable?null:e.uname,this.gname=this.portable?null:e.gname,this.size=e.size,this.mtime=this.noMtime?null:r.mtime||e.mtime,this.atime=this.portable?null:e.atime,this.ctime=this.portable?null:e.ctime,this.linkpath=iA(e.linkpath),typeof r.onwarn=="function"&&this.on("warn",r.onwarn);let s=!1;if(!this.preservePaths){let[a,n]=rEe(this.path);a&&(this.path=n,s=a)}this.remain=e.size,this.blockRemain=e.startBlockSize,this.header=new $ye({path:this[oA](this.path),linkpath:this.type==="Link"?this[oA](this.linkpath):this.linkpath,mode:this.mode,uid:this.portable?null:this.uid,gid:this.portable?null:this.gid,size:this.size,mtime:this.noMtime?null:this.mtime,type:this.type,uname:this.portable?null:this.uname,atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime}),s&&this.warn("TAR_ENTRY_INFO",`stripping ${s} from absolute path`,{entry:this,path:s+this.path}),this.header.encode()&&!this.noPax&&super.write(new Xye({atime:this.portable?null:this.atime,ctime:this.portable?null:this.ctime,gid:this.portable?null:this.gid,mtime:this.noMtime?null:this.mtime,path:this[oA](this.path),linkpath:this.type==="Link"?this[oA](this.linkpath):this.linkpath,size:this.size,uid:this.portable?null:this.uid,uname:this.portable?null:this.uname,dev:this.portable?null:this.readEntry.dev,ino:this.portable?null:this.readEntry.ino,nlink:this.portable?null:this.readEntry.nlink}).encode()),super.write(this.header.block),e.pipe(this)}[oA](e){return eEe(e,this.prefix)}[tR](e){return nEe(e,this.type==="Directory",this.portable)}write(e){let r=e.length;if(r>this.blockRemain)throw new Error("writing more to entry than is appropriate");return this.blockRemain-=r,super.write(e)}end(){return this.blockRemain&&super.write(Buffer.alloc(this.blockRemain)),super.end()}});rR.Sync=cq;rR.Tar=DEt;var bEt=t=>t.isFile()?"File":t.isDirectory()?"Directory":t.isSymbolicLink()?"SymbolicLink":"Unsupported";iEe.exports=rR});var fR=L((Cer,fEe)=>{"use strict";var cR=class{constructor(e,r){this.path=e||"./",this.absolute=r,this.entry=null,this.stat=null,this.readdir=null,this.pending=!1,this.ignore=!1,this.piped=!1}},PEt=xI(),xEt=q6(),kEt=KT(),Eq=uq(),QEt=Eq.Sync,TEt=Eq.Tar,REt=pk(),sEe=Buffer.alloc(1024),sR=Symbol("onStat"),nR=Symbol("ended"),aA=Symbol("queue"),OI=Symbol("current"),gm=Symbol("process"),iR=Symbol("processing"),oEe=Symbol("processJob"),lA=Symbol("jobs"),fq=Symbol("jobDone"),oR=Symbol("addFSEntry"),aEe=Symbol("addTarEntry"),gq=Symbol("stat"),dq=Symbol("readdir"),aR=Symbol("onreaddir"),lR=Symbol("pipe"),lEe=Symbol("entry"),Aq=Symbol("entryOpt"),mq=Symbol("writeEntryClass"),uEe=Symbol("write"),pq=Symbol("ondrain"),uR=ye("fs"),cEe=ye("path"),FEt=ZT(),hq=TI(),Iq=FEt(class extends PEt{constructor(e){super(e),e=e||Object.create(null),this.opt=e,this.file=e.file||"",this.cwd=e.cwd||process.cwd(),this.maxReadSize=e.maxReadSize,this.preservePaths=!!e.preservePaths,this.strict=!!e.strict,this.noPax=!!e.noPax,this.prefix=hq(e.prefix||""),this.linkCache=e.linkCache||new Map,this.statCache=e.statCache||new Map,this.readdirCache=e.readdirCache||new Map,this[mq]=Eq,typeof e.onwarn=="function"&&this.on("warn",e.onwarn),this.portable=!!e.portable,this.zip=null,e.gzip?(typeof e.gzip!="object"&&(e.gzip={}),this.portable&&(e.gzip.portable=!0),this.zip=new xEt.Gzip(e.gzip),this.zip.on("data",r=>super.write(r)),this.zip.on("end",r=>super.end()),this.zip.on("drain",r=>this[pq]()),this.on("resume",r=>this.zip.resume())):this.on("drain",this[pq]),this.noDirRecurse=!!e.noDirRecurse,this.follow=!!e.follow,this.noMtime=!!e.noMtime,this.mtime=e.mtime||null,this.filter=typeof e.filter=="function"?e.filter:r=>!0,this[aA]=new REt,this[lA]=0,this.jobs=+e.jobs||4,this[iR]=!1,this[nR]=!1}[uEe](e){return super.write(e)}add(e){return this.write(e),this}end(e){return e&&this.write(e),this[nR]=!0,this[gm](),this}write(e){if(this[nR])throw new Error("write after end");return e instanceof kEt?this[aEe](e):this[oR](e),this.flowing}[aEe](e){let r=hq(cEe.resolve(this.cwd,e.path));if(!this.filter(e.path,e))e.resume();else{let s=new cR(e.path,r,!1);s.entry=new TEt(e,this[Aq](s)),s.entry.on("end",a=>this[fq](s)),this[lA]+=1,this[aA].push(s)}this[gm]()}[oR](e){let r=hq(cEe.resolve(this.cwd,e));this[aA].push(new cR(e,r)),this[gm]()}[gq](e){e.pending=!0,this[lA]+=1;let r=this.follow?"stat":"lstat";uR[r](e.absolute,(s,a)=>{e.pending=!1,this[lA]-=1,s?this.emit("error",s):this[sR](e,a)})}[sR](e,r){this.statCache.set(e.absolute,r),e.stat=r,this.filter(e.path,r)||(e.ignore=!0),this[gm]()}[dq](e){e.pending=!0,this[lA]+=1,uR.readdir(e.absolute,(r,s)=>{if(e.pending=!1,this[lA]-=1,r)return this.emit("error",r);this[aR](e,s)})}[aR](e,r){this.readdirCache.set(e.absolute,r),e.readdir=r,this[gm]()}[gm](){if(!this[iR]){this[iR]=!0;for(let e=this[aA].head;e!==null&&this[lA]this.warn(r,s,a),noPax:this.noPax,cwd:this.cwd,absolute:e.absolute,preservePaths:this.preservePaths,maxReadSize:this.maxReadSize,strict:this.strict,portable:this.portable,linkCache:this.linkCache,statCache:this.statCache,noMtime:this.noMtime,mtime:this.mtime,prefix:this.prefix}}[lEe](e){this[lA]+=1;try{return new this[mq](e.path,this[Aq](e)).on("end",()=>this[fq](e)).on("error",r=>this.emit("error",r))}catch(r){this.emit("error",r)}}[pq](){this[OI]&&this[OI].entry&&this[OI].entry.resume()}[lR](e){e.piped=!0,e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n==="./"?"":n.replace(/\/*$/,"/");this[oR](c+a)});let r=e.entry,s=this.zip;s?r.on("data",a=>{s.write(a)||r.pause()}):r.on("data",a=>{super.write(a)||r.pause()})}pause(){return this.zip&&this.zip.pause(),super.pause()}}),yq=class extends Iq{constructor(e){super(e),this[mq]=QEt}pause(){}resume(){}[gq](e){let r=this.follow?"statSync":"lstatSync";this[sR](e,uR[r](e.absolute))}[dq](e,r){this[aR](e,uR.readdirSync(e.absolute))}[lR](e){let r=e.entry,s=this.zip;e.readdir&&e.readdir.forEach(a=>{let n=e.path,c=n==="./"?"":n.replace(/\/*$/,"/");this[oR](c+a)}),s?r.on("data",a=>{s.write(a)}):r.on("data",a=>{super[uEe](a)})}};Iq.Sync=yq;fEe.exports=Iq});var GI=L(qv=>{"use strict";var NEt=xI(),OEt=ye("events").EventEmitter,Al=ye("fs"),Bq=Al.writev;if(!Bq){let t=process.binding("fs"),e=t.FSReqWrap||t.FSReqCallback;Bq=(r,s,a,n)=>{let c=(p,h)=>n(p,h,s),f=new e;f.oncomplete=c,t.writeBuffers(r,s,a,f)}}var jI=Symbol("_autoClose"),Vu=Symbol("_close"),jv=Symbol("_ended"),ni=Symbol("_fd"),AEe=Symbol("_finished"),O0=Symbol("_flags"),Cq=Symbol("_flush"),vq=Symbol("_handleChunk"),Sq=Symbol("_makeBuf"),dR=Symbol("_mode"),AR=Symbol("_needDrain"),UI=Symbol("_onerror"),qI=Symbol("_onopen"),wq=Symbol("_onread"),MI=Symbol("_onwrite"),L0=Symbol("_open"),Kp=Symbol("_path"),dm=Symbol("_pos"),cA=Symbol("_queue"),_I=Symbol("_read"),pEe=Symbol("_readSize"),N0=Symbol("_reading"),pR=Symbol("_remain"),hEe=Symbol("_size"),hR=Symbol("_write"),LI=Symbol("_writing"),gR=Symbol("_defaultFlag"),HI=Symbol("_errored"),mR=class extends NEt{constructor(e,r){if(r=r||{},super(r),this.readable=!0,this.writable=!1,typeof e!="string")throw new TypeError("path must be a string");this[HI]=!1,this[ni]=typeof r.fd=="number"?r.fd:null,this[Kp]=e,this[pEe]=r.readSize||16*1024*1024,this[N0]=!1,this[hEe]=typeof r.size=="number"?r.size:1/0,this[pR]=this[hEe],this[jI]=typeof r.autoClose=="boolean"?r.autoClose:!0,typeof this[ni]=="number"?this[_I]():this[L0]()}get fd(){return this[ni]}get path(){return this[Kp]}write(){throw new TypeError("this is a readable stream")}end(){throw new TypeError("this is a readable stream")}[L0](){Al.open(this[Kp],"r",(e,r)=>this[qI](e,r))}[qI](e,r){e?this[UI](e):(this[ni]=r,this.emit("open",r),this[_I]())}[Sq](){return Buffer.allocUnsafe(Math.min(this[pEe],this[pR]))}[_I](){if(!this[N0]){this[N0]=!0;let e=this[Sq]();if(e.length===0)return process.nextTick(()=>this[wq](null,0,e));Al.read(this[ni],e,0,e.length,null,(r,s,a)=>this[wq](r,s,a))}}[wq](e,r,s){this[N0]=!1,e?this[UI](e):this[vq](r,s)&&this[_I]()}[Vu](){if(this[jI]&&typeof this[ni]=="number"){let e=this[ni];this[ni]=null,Al.close(e,r=>r?this.emit("error",r):this.emit("close"))}}[UI](e){this[N0]=!0,this[Vu](),this.emit("error",e)}[vq](e,r){let s=!1;return this[pR]-=e,e>0&&(s=super.write(ethis[qI](e,r))}[qI](e,r){this[gR]&&this[O0]==="r+"&&e&&e.code==="ENOENT"?(this[O0]="w",this[L0]()):e?this[UI](e):(this[ni]=r,this.emit("open",r),this[Cq]())}end(e,r){return e&&this.write(e,r),this[jv]=!0,!this[LI]&&!this[cA].length&&typeof this[ni]=="number"&&this[MI](null,0),this}write(e,r){return typeof e=="string"&&(e=Buffer.from(e,r)),this[jv]?(this.emit("error",new Error("write() after end()")),!1):this[ni]===null||this[LI]||this[cA].length?(this[cA].push(e),this[AR]=!0,!1):(this[LI]=!0,this[hR](e),!0)}[hR](e){Al.write(this[ni],e,0,e.length,this[dm],(r,s)=>this[MI](r,s))}[MI](e,r){e?this[UI](e):(this[dm]!==null&&(this[dm]+=r),this[cA].length?this[Cq]():(this[LI]=!1,this[jv]&&!this[AEe]?(this[AEe]=!0,this[Vu](),this.emit("finish")):this[AR]&&(this[AR]=!1,this.emit("drain"))))}[Cq](){if(this[cA].length===0)this[jv]&&this[MI](null,0);else if(this[cA].length===1)this[hR](this[cA].pop());else{let e=this[cA];this[cA]=[],Bq(this[ni],e,this[dm],(r,s)=>this[MI](r,s))}}[Vu](){if(this[jI]&&typeof this[ni]=="number"){let e=this[ni];this[ni]=null,Al.close(e,r=>r?this.emit("error",r):this.emit("close"))}}},bq=class extends yR{[L0](){let e;if(this[gR]&&this[O0]==="r+")try{e=Al.openSync(this[Kp],this[O0],this[dR])}catch(r){if(r.code==="ENOENT")return this[O0]="w",this[L0]();throw r}else e=Al.openSync(this[Kp],this[O0],this[dR]);this[qI](null,e)}[Vu](){if(this[jI]&&typeof this[ni]=="number"){let e=this[ni];this[ni]=null,Al.closeSync(e),this.emit("close")}}[hR](e){let r=!0;try{this[MI](null,Al.writeSync(this[ni],e,0,e.length,this[dm])),r=!1}finally{if(r)try{this[Vu]()}catch{}}}};qv.ReadStream=mR;qv.ReadStreamSync=Dq;qv.WriteStream=yR;qv.WriteStreamSync=bq});var SR=L((ver,CEe)=>{"use strict";var LEt=ZT(),MEt=FI(),_Et=ye("events"),UEt=pk(),HEt=1024*1024,jEt=KT(),gEe=zT(),qEt=q6(),Pq=Buffer.from([31,139]),_c=Symbol("state"),mm=Symbol("writeEntry"),Jp=Symbol("readEntry"),xq=Symbol("nextEntry"),dEe=Symbol("processEntry"),Uc=Symbol("extendedHeader"),Gv=Symbol("globalExtendedHeader"),M0=Symbol("meta"),mEe=Symbol("emitMeta"),bi=Symbol("buffer"),zp=Symbol("queue"),ym=Symbol("ended"),yEe=Symbol("emittedEnd"),Em=Symbol("emit"),pl=Symbol("unzip"),ER=Symbol("consumeChunk"),IR=Symbol("consumeChunkSub"),kq=Symbol("consumeBody"),EEe=Symbol("consumeMeta"),IEe=Symbol("consumeHeader"),CR=Symbol("consuming"),Qq=Symbol("bufferConcat"),Tq=Symbol("maybeEnd"),Wv=Symbol("writing"),_0=Symbol("aborted"),wR=Symbol("onDone"),Im=Symbol("sawValidEntry"),BR=Symbol("sawNullBlock"),vR=Symbol("sawEOF"),GEt=t=>!0;CEe.exports=LEt(class extends _Et{constructor(e){e=e||{},super(e),this.file=e.file||"",this[Im]=null,this.on(wR,r=>{(this[_c]==="begin"||this[Im]===!1)&&this.warn("TAR_BAD_ARCHIVE","Unrecognized archive format")}),e.ondone?this.on(wR,e.ondone):this.on(wR,r=>{this.emit("prefinish"),this.emit("finish"),this.emit("end"),this.emit("close")}),this.strict=!!e.strict,this.maxMetaEntrySize=e.maxMetaEntrySize||HEt,this.filter=typeof e.filter=="function"?e.filter:GEt,this.writable=!0,this.readable=!1,this[zp]=new UEt,this[bi]=null,this[Jp]=null,this[mm]=null,this[_c]="begin",this[M0]="",this[Uc]=null,this[Gv]=null,this[ym]=!1,this[pl]=null,this[_0]=!1,this[BR]=!1,this[vR]=!1,typeof e.onwarn=="function"&&this.on("warn",e.onwarn),typeof e.onentry=="function"&&this.on("entry",e.onentry)}[IEe](e,r){this[Im]===null&&(this[Im]=!1);let s;try{s=new MEt(e,r,this[Uc],this[Gv])}catch(a){return this.warn("TAR_ENTRY_INVALID",a)}if(s.nullBlock)this[BR]?(this[vR]=!0,this[_c]==="begin"&&(this[_c]="header"),this[Em]("eof")):(this[BR]=!0,this[Em]("nullBlock"));else if(this[BR]=!1,!s.cksumValid)this.warn("TAR_ENTRY_INVALID","checksum failure",{header:s});else if(!s.path)this.warn("TAR_ENTRY_INVALID","path is required",{header:s});else{let a=s.type;if(/^(Symbolic)?Link$/.test(a)&&!s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath required",{header:s});else if(!/^(Symbolic)?Link$/.test(a)&&s.linkpath)this.warn("TAR_ENTRY_INVALID","linkpath forbidden",{header:s});else{let n=this[mm]=new jEt(s,this[Uc],this[Gv]);if(!this[Im])if(n.remain){let c=()=>{n.invalid||(this[Im]=!0)};n.on("end",c)}else this[Im]=!0;n.meta?n.size>this.maxMetaEntrySize?(n.ignore=!0,this[Em]("ignoredEntry",n),this[_c]="ignore",n.resume()):n.size>0&&(this[M0]="",n.on("data",c=>this[M0]+=c),this[_c]="meta"):(this[Uc]=null,n.ignore=n.ignore||!this.filter(n.path,n),n.ignore?(this[Em]("ignoredEntry",n),this[_c]=n.remain?"ignore":"header",n.resume()):(n.remain?this[_c]="body":(this[_c]="header",n.end()),this[Jp]?this[zp].push(n):(this[zp].push(n),this[xq]())))}}}[dEe](e){let r=!0;return e?Array.isArray(e)?this.emit.apply(this,e):(this[Jp]=e,this.emit("entry",e),e.emittedEnd||(e.on("end",s=>this[xq]()),r=!1)):(this[Jp]=null,r=!1),r}[xq](){do;while(this[dEe](this[zp].shift()));if(!this[zp].length){let e=this[Jp];!e||e.flowing||e.size===e.remain?this[Wv]||this.emit("drain"):e.once("drain",s=>this.emit("drain"))}}[kq](e,r){let s=this[mm],a=s.blockRemain,n=a>=e.length&&r===0?e:e.slice(r,r+a);return s.write(n),s.blockRemain||(this[_c]="header",this[mm]=null,s.end()),n.length}[EEe](e,r){let s=this[mm],a=this[kq](e,r);return this[mm]||this[mEe](s),a}[Em](e,r,s){!this[zp].length&&!this[Jp]?this.emit(e,r,s):this[zp].push([e,r,s])}[mEe](e){switch(this[Em]("meta",this[M0]),e.type){case"ExtendedHeader":case"OldExtendedHeader":this[Uc]=gEe.parse(this[M0],this[Uc],!1);break;case"GlobalExtendedHeader":this[Gv]=gEe.parse(this[M0],this[Gv],!0);break;case"NextFileHasLongPath":case"OldGnuLongPath":this[Uc]=this[Uc]||Object.create(null),this[Uc].path=this[M0].replace(/\0.*/,"");break;case"NextFileHasLongLinkpath":this[Uc]=this[Uc]||Object.create(null),this[Uc].linkpath=this[M0].replace(/\0.*/,"");break;default:throw new Error("unknown meta: "+e.type)}}abort(e){this[_0]=!0,this.emit("abort",e),this.warn("TAR_ABORT",e,{recoverable:!1})}write(e){if(this[_0])return;if(this[pl]===null&&e){if(this[bi]&&(e=Buffer.concat([this[bi],e]),this[bi]=null),e.lengththis[ER](n)),this[pl].on("error",n=>this.abort(n)),this[pl].on("end",n=>{this[ym]=!0,this[ER]()}),this[Wv]=!0;let a=this[pl][s?"end":"write"](e);return this[Wv]=!1,a}}this[Wv]=!0,this[pl]?this[pl].write(e):this[ER](e),this[Wv]=!1;let r=this[zp].length?!1:this[Jp]?this[Jp].flowing:!0;return!r&&!this[zp].length&&this[Jp].once("drain",s=>this.emit("drain")),r}[Qq](e){e&&!this[_0]&&(this[bi]=this[bi]?Buffer.concat([this[bi],e]):e)}[Tq](){if(this[ym]&&!this[yEe]&&!this[_0]&&!this[CR]){this[yEe]=!0;let e=this[mm];if(e&&e.blockRemain){let r=this[bi]?this[bi].length:0;this.warn("TAR_BAD_ARCHIVE",`Truncated input (needed ${e.blockRemain} more bytes, only ${r} available)`,{entry:e}),this[bi]&&e.write(this[bi]),e.end()}this[Em](wR)}}[ER](e){if(this[CR])this[Qq](e);else if(!e&&!this[bi])this[Tq]();else{if(this[CR]=!0,this[bi]){this[Qq](e);let r=this[bi];this[bi]=null,this[IR](r)}else this[IR](e);for(;this[bi]&&this[bi].length>=512&&!this[_0]&&!this[vR];){let r=this[bi];this[bi]=null,this[IR](r)}this[CR]=!1}(!this[bi]||this[ym])&&this[Tq]()}[IR](e){let r=0,s=e.length;for(;r+512<=s&&!this[_0]&&!this[vR];)switch(this[_c]){case"begin":case"header":this[IEe](e,r),r+=512;break;case"ignore":case"body":r+=this[kq](e,r);break;case"meta":r+=this[EEe](e,r);break;default:throw new Error("invalid state: "+this[_c])}r{"use strict";var WEt=bI(),BEe=SR(),WI=ye("fs"),YEt=GI(),wEe=ye("path"),Rq=NI();SEe.exports=(t,e,r)=>{typeof t=="function"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e=="function"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=WEt(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return e.length&&KEt(s,e),s.noResume||VEt(s),s.file&&s.sync?JEt(s):s.file?zEt(s,r):vEe(s)};var VEt=t=>{let e=t.onentry;t.onentry=e?r=>{e(r),r.resume()}:r=>r.resume()},KEt=(t,e)=>{let r=new Map(e.map(n=>[Rq(n),!0])),s=t.filter,a=(n,c)=>{let f=c||wEe.parse(n).root||".",p=n===f?!1:r.has(n)?r.get(n):a(wEe.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(Rq(n)):n=>a(Rq(n))},JEt=t=>{let e=vEe(t),r=t.file,s=!0,a;try{let n=WI.statSync(r),c=t.maxReadSize||16*1024*1024;if(n.size{let r=new BEe(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on("error",f),r.on("end",c),WI.stat(a,(p,h)=>{if(p)f(p);else{let E=new YEt.ReadStream(a,{readSize:s,size:h.size});E.on("error",f),E.pipe(r)}})});return e?n.then(e,e):n},vEe=t=>new BEe(t)});var QEe=L((Der,kEe)=>{"use strict";var ZEt=bI(),bR=fR(),DEe=GI(),bEe=DR(),PEe=ye("path");kEe.exports=(t,e,r)=>{if(typeof e=="function"&&(r=e),Array.isArray(t)&&(e=t,t={}),!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");e=Array.from(e);let s=ZEt(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return s.file&&s.sync?XEt(s,e):s.file?$Et(s,e,r):s.sync?eIt(s,e):tIt(s,e)};var XEt=(t,e)=>{let r=new bR.Sync(t),s=new DEe.WriteStreamSync(t.file,{mode:t.mode||438});r.pipe(s),xEe(r,e)},$Et=(t,e,r)=>{let s=new bR(t),a=new DEe.WriteStream(t.file,{mode:t.mode||438});s.pipe(a);let n=new Promise((c,f)=>{a.on("error",f),a.on("close",c),s.on("error",f)});return Fq(s,e),r?n.then(r,r):n},xEe=(t,e)=>{e.forEach(r=>{r.charAt(0)==="@"?bEe({file:PEe.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},Fq=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)==="@")return bEe({file:PEe.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>Fq(t,e));t.add(r)}t.end()},eIt=(t,e)=>{let r=new bR.Sync(t);return xEe(r,e),r},tIt=(t,e)=>{let r=new bR(t);return Fq(r,e),r}});var Nq=L((ber,MEe)=>{"use strict";var rIt=bI(),TEe=fR(),Xl=ye("fs"),REe=GI(),FEe=DR(),NEe=ye("path"),OEe=FI();MEe.exports=(t,e,r)=>{let s=rIt(t);if(!s.file)throw new TypeError("file is required");if(s.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");return e=Array.from(e),s.sync?nIt(s,e):sIt(s,e,r)};var nIt=(t,e)=>{let r=new TEe.Sync(t),s=!0,a,n;try{try{a=Xl.openSync(t.file,"r+")}catch(p){if(p.code==="ENOENT")a=Xl.openSync(t.file,"w+");else throw p}let c=Xl.fstatSync(a),f=Buffer.alloc(512);e:for(n=0;nc.size)break;n+=h,t.mtimeCache&&t.mtimeCache.set(p.path,p.mtime)}s=!1,iIt(t,r,n,a,e)}finally{if(s)try{Xl.closeSync(a)}catch{}}},iIt=(t,e,r,s,a)=>{let n=new REe.WriteStreamSync(t.file,{fd:s,start:r});e.pipe(n),oIt(e,a)},sIt=(t,e,r)=>{e=Array.from(e);let s=new TEe(t),a=(c,f,p)=>{let h=(I,R)=>{I?Xl.close(c,N=>p(I)):p(null,R)},E=0;if(f===0)return h(null,0);let C=0,S=Buffer.alloc(512),P=(I,R)=>{if(I)return h(I);if(C+=R,C<512&&R)return Xl.read(c,S,C,S.length-C,E+C,P);if(E===0&&S[0]===31&&S[1]===139)return h(new Error("cannot append to compressed archives"));if(C<512)return h(null,E);let N=new OEe(S);if(!N.cksumValid)return h(null,E);let U=512*Math.ceil(N.size/512);if(E+U+512>f||(E+=U+512,E>=f))return h(null,E);t.mtimeCache&&t.mtimeCache.set(N.path,N.mtime),C=0,Xl.read(c,S,0,512,E,P)};Xl.read(c,S,0,512,E,P)},n=new Promise((c,f)=>{s.on("error",f);let p="r+",h=(E,C)=>{if(E&&E.code==="ENOENT"&&p==="r+")return p="w+",Xl.open(t.file,p,h);if(E)return f(E);Xl.fstat(C,(S,P)=>{if(S)return Xl.close(C,()=>f(S));a(C,P.size,(I,R)=>{if(I)return f(I);let N=new REe.WriteStream(t.file,{fd:C,start:R});s.pipe(N),N.on("error",f),N.on("close",c),LEe(s,e)})})};Xl.open(t.file,p,h)});return r?n.then(r,r):n},oIt=(t,e)=>{e.forEach(r=>{r.charAt(0)==="@"?FEe({file:NEe.resolve(t.cwd,r.substr(1)),sync:!0,noResume:!0,onentry:s=>t.add(s)}):t.add(r)}),t.end()},LEe=(t,e)=>{for(;e.length;){let r=e.shift();if(r.charAt(0)==="@")return FEe({file:NEe.resolve(t.cwd,r.substr(1)),noResume:!0,onentry:s=>t.add(s)}).then(s=>LEe(t,e));t.add(r)}t.end()}});var UEe=L((Per,_Ee)=>{"use strict";var aIt=bI(),lIt=Nq();_Ee.exports=(t,e,r)=>{let s=aIt(t);if(!s.file)throw new TypeError("file is required");if(s.gzip)throw new TypeError("cannot append to compressed archives");if(!e||!Array.isArray(e)||!e.length)throw new TypeError("no files or directories specified");return e=Array.from(e),cIt(s),lIt(s,e,r)};var cIt=t=>{let e=t.filter;t.mtimeCache||(t.mtimeCache=new Map),t.filter=e?(r,s)=>e(r,s)&&!(t.mtimeCache.get(r)>s.mtime):(r,s)=>!(t.mtimeCache.get(r)>s.mtime)}});var qEe=L((xer,jEe)=>{var{promisify:HEe}=ye("util"),U0=ye("fs"),uIt=t=>{if(!t)t={mode:511,fs:U0};else if(typeof t=="object")t={mode:511,fs:U0,...t};else if(typeof t=="number")t={mode:t,fs:U0};else if(typeof t=="string")t={mode:parseInt(t,8),fs:U0};else throw new TypeError("invalid options argument");return t.mkdir=t.mkdir||t.fs.mkdir||U0.mkdir,t.mkdirAsync=HEe(t.mkdir),t.stat=t.stat||t.fs.stat||U0.stat,t.statAsync=HEe(t.stat),t.statSync=t.statSync||t.fs.statSync||U0.statSync,t.mkdirSync=t.mkdirSync||t.fs.mkdirSync||U0.mkdirSync,t};jEe.exports=uIt});var WEe=L((ker,GEe)=>{var fIt=process.platform,{resolve:AIt,parse:pIt}=ye("path"),hIt=t=>{if(/\0/.test(t))throw Object.assign(new TypeError("path must be a string without null bytes"),{path:t,code:"ERR_INVALID_ARG_VALUE"});if(t=AIt(t),fIt==="win32"){let e=/[*|"<>?:]/,{root:r}=pIt(t);if(e.test(t.substr(r.length)))throw Object.assign(new Error("Illegal characters in path."),{path:t,code:"EINVAL"})}return t};GEe.exports=hIt});var zEe=L((Qer,JEe)=>{var{dirname:YEe}=ye("path"),VEe=(t,e,r=void 0)=>r===e?Promise.resolve():t.statAsync(e).then(s=>s.isDirectory()?r:void 0,s=>s.code==="ENOENT"?VEe(t,YEe(e),e):void 0),KEe=(t,e,r=void 0)=>{if(r!==e)try{return t.statSync(e).isDirectory()?r:void 0}catch(s){return s.code==="ENOENT"?KEe(t,YEe(e),e):void 0}};JEe.exports={findMade:VEe,findMadeSync:KEe}});var Mq=L((Ter,XEe)=>{var{dirname:ZEe}=ye("path"),Oq=(t,e,r)=>{e.recursive=!1;let s=ZEe(t);return s===t?e.mkdirAsync(t,e).catch(a=>{if(a.code!=="EISDIR")throw a}):e.mkdirAsync(t,e).then(()=>r||t,a=>{if(a.code==="ENOENT")return Oq(s,e).then(n=>Oq(t,e,n));if(a.code!=="EEXIST"&&a.code!=="EROFS")throw a;return e.statAsync(t).then(n=>{if(n.isDirectory())return r;throw a},()=>{throw a})})},Lq=(t,e,r)=>{let s=ZEe(t);if(e.recursive=!1,s===t)try{return e.mkdirSync(t,e)}catch(a){if(a.code!=="EISDIR")throw a;return}try{return e.mkdirSync(t,e),r||t}catch(a){if(a.code==="ENOENT")return Lq(t,e,Lq(s,e,r));if(a.code!=="EEXIST"&&a.code!=="EROFS")throw a;try{if(!e.statSync(t).isDirectory())throw a}catch{throw a}}};XEe.exports={mkdirpManual:Oq,mkdirpManualSync:Lq}});var tIe=L((Rer,eIe)=>{var{dirname:$Ee}=ye("path"),{findMade:gIt,findMadeSync:dIt}=zEe(),{mkdirpManual:mIt,mkdirpManualSync:yIt}=Mq(),EIt=(t,e)=>(e.recursive=!0,$Ee(t)===t?e.mkdirAsync(t,e):gIt(e,t).then(s=>e.mkdirAsync(t,e).then(()=>s).catch(a=>{if(a.code==="ENOENT")return mIt(t,e);throw a}))),IIt=(t,e)=>{if(e.recursive=!0,$Ee(t)===t)return e.mkdirSync(t,e);let s=dIt(e,t);try{return e.mkdirSync(t,e),s}catch(a){if(a.code==="ENOENT")return yIt(t,e);throw a}};eIe.exports={mkdirpNative:EIt,mkdirpNativeSync:IIt}});var sIe=L((Fer,iIe)=>{var rIe=ye("fs"),CIt=process.version,_q=CIt.replace(/^v/,"").split("."),nIe=+_q[0]>10||+_q[0]==10&&+_q[1]>=12,wIt=nIe?t=>t.mkdir===rIe.mkdir:()=>!1,BIt=nIe?t=>t.mkdirSync===rIe.mkdirSync:()=>!1;iIe.exports={useNative:wIt,useNativeSync:BIt}});var fIe=L((Ner,uIe)=>{var YI=qEe(),VI=WEe(),{mkdirpNative:oIe,mkdirpNativeSync:aIe}=tIe(),{mkdirpManual:lIe,mkdirpManualSync:cIe}=Mq(),{useNative:vIt,useNativeSync:SIt}=sIe(),KI=(t,e)=>(t=VI(t),e=YI(e),vIt(e)?oIe(t,e):lIe(t,e)),DIt=(t,e)=>(t=VI(t),e=YI(e),SIt(e)?aIe(t,e):cIe(t,e));KI.sync=DIt;KI.native=(t,e)=>oIe(VI(t),YI(e));KI.manual=(t,e)=>lIe(VI(t),YI(e));KI.nativeSync=(t,e)=>aIe(VI(t),YI(e));KI.manualSync=(t,e)=>cIe(VI(t),YI(e));uIe.exports=KI});var yIe=L((Oer,mIe)=>{"use strict";var Hc=ye("fs"),Cm=ye("path"),bIt=Hc.lchown?"lchown":"chown",PIt=Hc.lchownSync?"lchownSync":"chownSync",pIe=Hc.lchown&&!process.version.match(/v1[1-9]+\./)&&!process.version.match(/v10\.[6-9]/),AIe=(t,e,r)=>{try{return Hc[PIt](t,e,r)}catch(s){if(s.code!=="ENOENT")throw s}},xIt=(t,e,r)=>{try{return Hc.chownSync(t,e,r)}catch(s){if(s.code!=="ENOENT")throw s}},kIt=pIe?(t,e,r,s)=>a=>{!a||a.code!=="EISDIR"?s(a):Hc.chown(t,e,r,s)}:(t,e,r,s)=>s,Uq=pIe?(t,e,r)=>{try{return AIe(t,e,r)}catch(s){if(s.code!=="EISDIR")throw s;xIt(t,e,r)}}:(t,e,r)=>AIe(t,e,r),QIt=process.version,hIe=(t,e,r)=>Hc.readdir(t,e,r),TIt=(t,e)=>Hc.readdirSync(t,e);/^v4\./.test(QIt)&&(hIe=(t,e,r)=>Hc.readdir(t,r));var PR=(t,e,r,s)=>{Hc[bIt](t,e,r,kIt(t,e,r,a=>{s(a&&a.code!=="ENOENT"?a:null)}))},gIe=(t,e,r,s,a)=>{if(typeof e=="string")return Hc.lstat(Cm.resolve(t,e),(n,c)=>{if(n)return a(n.code!=="ENOENT"?n:null);c.name=e,gIe(t,c,r,s,a)});if(e.isDirectory())Hq(Cm.resolve(t,e.name),r,s,n=>{if(n)return a(n);let c=Cm.resolve(t,e.name);PR(c,r,s,a)});else{let n=Cm.resolve(t,e.name);PR(n,r,s,a)}},Hq=(t,e,r,s)=>{hIe(t,{withFileTypes:!0},(a,n)=>{if(a){if(a.code==="ENOENT")return s();if(a.code!=="ENOTDIR"&&a.code!=="ENOTSUP")return s(a)}if(a||!n.length)return PR(t,e,r,s);let c=n.length,f=null,p=h=>{if(!f){if(h)return s(f=h);if(--c===0)return PR(t,e,r,s)}};n.forEach(h=>gIe(t,h,e,r,p))})},RIt=(t,e,r,s)=>{if(typeof e=="string")try{let a=Hc.lstatSync(Cm.resolve(t,e));a.name=e,e=a}catch(a){if(a.code==="ENOENT")return;throw a}e.isDirectory()&&dIe(Cm.resolve(t,e.name),r,s),Uq(Cm.resolve(t,e.name),r,s)},dIe=(t,e,r)=>{let s;try{s=TIt(t,{withFileTypes:!0})}catch(a){if(a.code==="ENOENT")return;if(a.code==="ENOTDIR"||a.code==="ENOTSUP")return Uq(t,e,r);throw a}return s&&s.length&&s.forEach(a=>RIt(t,a,e,r)),Uq(t,e,r)};mIe.exports=Hq;Hq.sync=dIe});var wIe=L((Ler,jq)=>{"use strict";var EIe=fIe(),jc=ye("fs"),xR=ye("path"),IIe=yIe(),Ku=TI(),kR=class extends Error{constructor(e,r){super("Cannot extract through symbolic link"),this.path=r,this.symlink=e}get name(){return"SylinkError"}},QR=class extends Error{constructor(e,r){super(r+": Cannot cd into '"+e+"'"),this.path=e,this.code=r}get name(){return"CwdError"}},TR=(t,e)=>t.get(Ku(e)),Yv=(t,e,r)=>t.set(Ku(e),r),FIt=(t,e)=>{jc.stat(t,(r,s)=>{(r||!s.isDirectory())&&(r=new QR(t,r&&r.code||"ENOTDIR")),e(r)})};jq.exports=(t,e,r)=>{t=Ku(t);let s=e.umask,a=e.mode|448,n=(a&s)!==0,c=e.uid,f=e.gid,p=typeof c=="number"&&typeof f=="number"&&(c!==e.processUid||f!==e.processGid),h=e.preserve,E=e.unlink,C=e.cache,S=Ku(e.cwd),P=(N,U)=>{N?r(N):(Yv(C,t,!0),U&&p?IIe(U,c,f,W=>P(W)):n?jc.chmod(t,a,r):r())};if(C&&TR(C,t)===!0)return P();if(t===S)return FIt(t,P);if(h)return EIe(t,{mode:a}).then(N=>P(null,N),P);let R=Ku(xR.relative(S,t)).split("/");RR(S,R,a,C,E,S,null,P)};var RR=(t,e,r,s,a,n,c,f)=>{if(!e.length)return f(null,c);let p=e.shift(),h=Ku(xR.resolve(t+"/"+p));if(TR(s,h))return RR(h,e,r,s,a,n,c,f);jc.mkdir(h,r,CIe(h,e,r,s,a,n,c,f))},CIe=(t,e,r,s,a,n,c,f)=>p=>{p?jc.lstat(t,(h,E)=>{if(h)h.path=h.path&&Ku(h.path),f(h);else if(E.isDirectory())RR(t,e,r,s,a,n,c,f);else if(a)jc.unlink(t,C=>{if(C)return f(C);jc.mkdir(t,r,CIe(t,e,r,s,a,n,c,f))});else{if(E.isSymbolicLink())return f(new kR(t,t+"/"+e.join("/")));f(p)}}):(c=c||t,RR(t,e,r,s,a,n,c,f))},NIt=t=>{let e=!1,r="ENOTDIR";try{e=jc.statSync(t).isDirectory()}catch(s){r=s.code}finally{if(!e)throw new QR(t,r)}};jq.exports.sync=(t,e)=>{t=Ku(t);let r=e.umask,s=e.mode|448,a=(s&r)!==0,n=e.uid,c=e.gid,f=typeof n=="number"&&typeof c=="number"&&(n!==e.processUid||c!==e.processGid),p=e.preserve,h=e.unlink,E=e.cache,C=Ku(e.cwd),S=N=>{Yv(E,t,!0),N&&f&&IIe.sync(N,n,c),a&&jc.chmodSync(t,s)};if(E&&TR(E,t)===!0)return S();if(t===C)return NIt(C),S();if(p)return S(EIe.sync(t,s));let I=Ku(xR.relative(C,t)).split("/"),R=null;for(let N=I.shift(),U=C;N&&(U+="/"+N);N=I.shift())if(U=Ku(xR.resolve(U)),!TR(E,U))try{jc.mkdirSync(U,s),R=R||U,Yv(E,U,!0)}catch{let te=jc.lstatSync(U);if(te.isDirectory()){Yv(E,U,!0);continue}else if(h){jc.unlinkSync(U),jc.mkdirSync(U,s),R=R||U,Yv(E,U,!0);continue}else if(te.isSymbolicLink())return new kR(U,U+"/"+I.join("/"))}return S(R)}});var Gq=L((Mer,BIe)=>{var qq=Object.create(null),{hasOwnProperty:OIt}=Object.prototype;BIe.exports=t=>(OIt.call(qq,t)||(qq[t]=t.normalize("NFKD")),qq[t])});var bIe=L((_er,DIe)=>{var vIe=ye("assert"),LIt=Gq(),MIt=NI(),{join:SIe}=ye("path"),_It=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,UIt=_It==="win32";DIe.exports=()=>{let t=new Map,e=new Map,r=h=>h.split("/").slice(0,-1).reduce((C,S)=>(C.length&&(S=SIe(C[C.length-1],S)),C.push(S||"/"),C),[]),s=new Set,a=h=>{let E=e.get(h);if(!E)throw new Error("function does not have any path reservations");return{paths:E.paths.map(C=>t.get(C)),dirs:[...E.dirs].map(C=>t.get(C))}},n=h=>{let{paths:E,dirs:C}=a(h);return E.every(S=>S[0]===h)&&C.every(S=>S[0]instanceof Set&&S[0].has(h))},c=h=>s.has(h)||!n(h)?!1:(s.add(h),h(()=>f(h)),!0),f=h=>{if(!s.has(h))return!1;let{paths:E,dirs:C}=e.get(h),S=new Set;return E.forEach(P=>{let I=t.get(P);vIe.equal(I[0],h),I.length===1?t.delete(P):(I.shift(),typeof I[0]=="function"?S.add(I[0]):I[0].forEach(R=>S.add(R)))}),C.forEach(P=>{let I=t.get(P);vIe(I[0]instanceof Set),I[0].size===1&&I.length===1?t.delete(P):I[0].size===1?(I.shift(),S.add(I[0])):I[0].delete(h)}),s.delete(h),S.forEach(P=>c(P)),!0};return{check:n,reserve:(h,E)=>{h=UIt?["win32 parallelization disabled"]:h.map(S=>LIt(MIt(SIe(S))).toLowerCase());let C=new Set(h.map(S=>r(S)).reduce((S,P)=>S.concat(P)));return e.set(E,{dirs:C,paths:h}),h.forEach(S=>{let P=t.get(S);P?P.push(E):t.set(S,[E])}),C.forEach(S=>{let P=t.get(S);P?P[P.length-1]instanceof Set?P[P.length-1].add(E):P.push(new Set([E])):t.set(S,[new Set([E])])}),c(E)}}}});var kIe=L((Uer,xIe)=>{var HIt=process.platform,jIt=HIt==="win32",qIt=global.__FAKE_TESTING_FS__||ye("fs"),{O_CREAT:GIt,O_TRUNC:WIt,O_WRONLY:YIt,UV_FS_O_FILEMAP:PIe=0}=qIt.constants,VIt=jIt&&!!PIe,KIt=512*1024,JIt=PIe|WIt|GIt|YIt;xIe.exports=VIt?t=>t"w"});var $q=L((Her,GIe)=>{"use strict";var zIt=ye("assert"),ZIt=SR(),Mn=ye("fs"),XIt=GI(),Zp=ye("path"),HIe=wIe(),QIe=$6(),$It=bIe(),eCt=eq(),$l=TI(),tCt=NI(),rCt=Gq(),TIe=Symbol("onEntry"),Vq=Symbol("checkFs"),RIe=Symbol("checkFs2"),OR=Symbol("pruneCache"),Kq=Symbol("isReusable"),qc=Symbol("makeFs"),Jq=Symbol("file"),zq=Symbol("directory"),LR=Symbol("link"),FIe=Symbol("symlink"),NIe=Symbol("hardlink"),OIe=Symbol("unsupported"),LIe=Symbol("checkPath"),H0=Symbol("mkdir"),Xo=Symbol("onError"),FR=Symbol("pending"),MIe=Symbol("pend"),JI=Symbol("unpend"),Wq=Symbol("ended"),Yq=Symbol("maybeClose"),Zq=Symbol("skip"),Vv=Symbol("doChown"),Kv=Symbol("uid"),Jv=Symbol("gid"),zv=Symbol("checkedCwd"),jIe=ye("crypto"),qIe=kIe(),nCt=process.env.TESTING_TAR_FAKE_PLATFORM||process.platform,Zv=nCt==="win32",iCt=(t,e)=>{if(!Zv)return Mn.unlink(t,e);let r=t+".DELETE."+jIe.randomBytes(16).toString("hex");Mn.rename(t,r,s=>{if(s)return e(s);Mn.unlink(r,e)})},sCt=t=>{if(!Zv)return Mn.unlinkSync(t);let e=t+".DELETE."+jIe.randomBytes(16).toString("hex");Mn.renameSync(t,e),Mn.unlinkSync(e)},_Ie=(t,e,r)=>t===t>>>0?t:e===e>>>0?e:r,UIe=t=>rCt(tCt($l(t))).toLowerCase(),oCt=(t,e)=>{e=UIe(e);for(let r of t.keys()){let s=UIe(r);(s===e||s.indexOf(e+"/")===0)&&t.delete(r)}},aCt=t=>{for(let e of t.keys())t.delete(e)},Xv=class extends ZIt{constructor(e){if(e||(e={}),e.ondone=r=>{this[Wq]=!0,this[Yq]()},super(e),this[zv]=!1,this.reservations=$It(),this.transform=typeof e.transform=="function"?e.transform:null,this.writable=!0,this.readable=!1,this[FR]=0,this[Wq]=!1,this.dirCache=e.dirCache||new Map,typeof e.uid=="number"||typeof e.gid=="number"){if(typeof e.uid!="number"||typeof e.gid!="number")throw new TypeError("cannot set owner without number uid and gid");if(e.preserveOwner)throw new TypeError("cannot preserve owner in archive and also set owner explicitly");this.uid=e.uid,this.gid=e.gid,this.setOwner=!0}else this.uid=null,this.gid=null,this.setOwner=!1;e.preserveOwner===void 0&&typeof e.uid!="number"?this.preserveOwner=process.getuid&&process.getuid()===0:this.preserveOwner=!!e.preserveOwner,this.processUid=(this.preserveOwner||this.setOwner)&&process.getuid?process.getuid():null,this.processGid=(this.preserveOwner||this.setOwner)&&process.getgid?process.getgid():null,this.forceChown=e.forceChown===!0,this.win32=!!e.win32||Zv,this.newer=!!e.newer,this.keep=!!e.keep,this.noMtime=!!e.noMtime,this.preservePaths=!!e.preservePaths,this.unlink=!!e.unlink,this.cwd=$l(Zp.resolve(e.cwd||process.cwd())),this.strip=+e.strip||0,this.processUmask=e.noChmod?0:process.umask(),this.umask=typeof e.umask=="number"?e.umask:this.processUmask,this.dmode=e.dmode||511&~this.umask,this.fmode=e.fmode||438&~this.umask,this.on("entry",r=>this[TIe](r))}warn(e,r,s={}){return(e==="TAR_BAD_ARCHIVE"||e==="TAR_ABORT")&&(s.recoverable=!1),super.warn(e,r,s)}[Yq](){this[Wq]&&this[FR]===0&&(this.emit("prefinish"),this.emit("finish"),this.emit("end"),this.emit("close"))}[LIe](e){if(this.strip){let r=$l(e.path).split("/");if(r.length=this.strip)e.linkpath=s.slice(this.strip).join("/");else return!1}}if(!this.preservePaths){let r=$l(e.path),s=r.split("/");if(s.includes("..")||Zv&&/^[a-z]:\.\.$/i.test(s[0]))return this.warn("TAR_ENTRY_ERROR","path contains '..'",{entry:e,path:r}),!1;let[a,n]=eCt(r);a&&(e.path=n,this.warn("TAR_ENTRY_INFO",`stripping ${a} from absolute path`,{entry:e,path:r}))}if(Zp.isAbsolute(e.path)?e.absolute=$l(Zp.resolve(e.path)):e.absolute=$l(Zp.resolve(this.cwd,e.path)),!this.preservePaths&&e.absolute.indexOf(this.cwd+"/")!==0&&e.absolute!==this.cwd)return this.warn("TAR_ENTRY_ERROR","path escaped extraction target",{entry:e,path:$l(e.path),resolvedPath:e.absolute,cwd:this.cwd}),!1;if(e.absolute===this.cwd&&e.type!=="Directory"&&e.type!=="GNUDumpDir")return!1;if(this.win32){let{root:r}=Zp.win32.parse(e.absolute);e.absolute=r+QIe.encode(e.absolute.substr(r.length));let{root:s}=Zp.win32.parse(e.path);e.path=s+QIe.encode(e.path.substr(s.length))}return!0}[TIe](e){if(!this[LIe](e))return e.resume();switch(zIt.equal(typeof e.absolute,"string"),e.type){case"Directory":case"GNUDumpDir":e.mode&&(e.mode=e.mode|448);case"File":case"OldFile":case"ContiguousFile":case"Link":case"SymbolicLink":return this[Vq](e);case"CharacterDevice":case"BlockDevice":case"FIFO":default:return this[OIe](e)}}[Xo](e,r){e.name==="CwdError"?this.emit("error",e):(this.warn("TAR_ENTRY_ERROR",e,{entry:r}),this[JI](),r.resume())}[H0](e,r,s){HIe($l(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r,noChmod:this.noChmod},s)}[Vv](e){return this.forceChown||this.preserveOwner&&(typeof e.uid=="number"&&e.uid!==this.processUid||typeof e.gid=="number"&&e.gid!==this.processGid)||typeof this.uid=="number"&&this.uid!==this.processUid||typeof this.gid=="number"&&this.gid!==this.processGid}[Kv](e){return _Ie(this.uid,e.uid,this.processUid)}[Jv](e){return _Ie(this.gid,e.gid,this.processGid)}[Jq](e,r){let s=e.mode&4095||this.fmode,a=new XIt.WriteStream(e.absolute,{flags:qIe(e.size),mode:s,autoClose:!1});a.on("error",p=>{a.fd&&Mn.close(a.fd,()=>{}),a.write=()=>!0,this[Xo](p,e),r()});let n=1,c=p=>{if(p){a.fd&&Mn.close(a.fd,()=>{}),this[Xo](p,e),r();return}--n===0&&Mn.close(a.fd,h=>{h?this[Xo](h,e):this[JI](),r()})};a.on("finish",p=>{let h=e.absolute,E=a.fd;if(e.mtime&&!this.noMtime){n++;let C=e.atime||new Date,S=e.mtime;Mn.futimes(E,C,S,P=>P?Mn.utimes(h,C,S,I=>c(I&&P)):c())}if(this[Vv](e)){n++;let C=this[Kv](e),S=this[Jv](e);Mn.fchown(E,C,S,P=>P?Mn.chown(h,C,S,I=>c(I&&P)):c())}c()});let f=this.transform&&this.transform(e)||e;f!==e&&(f.on("error",p=>{this[Xo](p,e),r()}),e.pipe(f)),f.pipe(a)}[zq](e,r){let s=e.mode&4095||this.dmode;this[H0](e.absolute,s,a=>{if(a){this[Xo](a,e),r();return}let n=1,c=f=>{--n===0&&(r(),this[JI](),e.resume())};e.mtime&&!this.noMtime&&(n++,Mn.utimes(e.absolute,e.atime||new Date,e.mtime,c)),this[Vv](e)&&(n++,Mn.chown(e.absolute,this[Kv](e),this[Jv](e),c)),c()})}[OIe](e){e.unsupported=!0,this.warn("TAR_ENTRY_UNSUPPORTED",`unsupported entry type: ${e.type}`,{entry:e}),e.resume()}[FIe](e,r){this[LR](e,e.linkpath,"symlink",r)}[NIe](e,r){let s=$l(Zp.resolve(this.cwd,e.linkpath));this[LR](e,s,"link",r)}[MIe](){this[FR]++}[JI](){this[FR]--,this[Yq]()}[Zq](e){this[JI](),e.resume()}[Kq](e,r){return e.type==="File"&&!this.unlink&&r.isFile()&&r.nlink<=1&&!Zv}[Vq](e){this[MIe]();let r=[e.path];e.linkpath&&r.push(e.linkpath),this.reservations.reserve(r,s=>this[RIe](e,s))}[OR](e){e.type==="SymbolicLink"?aCt(this.dirCache):e.type!=="Directory"&&oCt(this.dirCache,e.absolute)}[RIe](e,r){this[OR](e);let s=f=>{this[OR](e),r(f)},a=()=>{this[H0](this.cwd,this.dmode,f=>{if(f){this[Xo](f,e),s();return}this[zv]=!0,n()})},n=()=>{if(e.absolute!==this.cwd){let f=$l(Zp.dirname(e.absolute));if(f!==this.cwd)return this[H0](f,this.dmode,p=>{if(p){this[Xo](p,e),s();return}c()})}c()},c=()=>{Mn.lstat(e.absolute,(f,p)=>{if(p&&(this.keep||this.newer&&p.mtime>e.mtime)){this[Zq](e),s();return}if(f||this[Kq](e,p))return this[qc](null,e,s);if(p.isDirectory()){if(e.type==="Directory"){let h=!this.noChmod&&e.mode&&(p.mode&4095)!==e.mode,E=C=>this[qc](C,e,s);return h?Mn.chmod(e.absolute,e.mode,E):E()}if(e.absolute!==this.cwd)return Mn.rmdir(e.absolute,h=>this[qc](h,e,s))}if(e.absolute===this.cwd)return this[qc](null,e,s);iCt(e.absolute,h=>this[qc](h,e,s))})};this[zv]?n():a()}[qc](e,r,s){if(e){this[Xo](e,r),s();return}switch(r.type){case"File":case"OldFile":case"ContiguousFile":return this[Jq](r,s);case"Link":return this[NIe](r,s);case"SymbolicLink":return this[FIe](r,s);case"Directory":case"GNUDumpDir":return this[zq](r,s)}}[LR](e,r,s,a){Mn[s](r,e.absolute,n=>{n?this[Xo](n,e):(this[JI](),e.resume()),a()})}},NR=t=>{try{return[null,t()]}catch(e){return[e,null]}},Xq=class extends Xv{[qc](e,r){return super[qc](e,r,()=>{})}[Vq](e){if(this[OR](e),!this[zv]){let n=this[H0](this.cwd,this.dmode);if(n)return this[Xo](n,e);this[zv]=!0}if(e.absolute!==this.cwd){let n=$l(Zp.dirname(e.absolute));if(n!==this.cwd){let c=this[H0](n,this.dmode);if(c)return this[Xo](c,e)}}let[r,s]=NR(()=>Mn.lstatSync(e.absolute));if(s&&(this.keep||this.newer&&s.mtime>e.mtime))return this[Zq](e);if(r||this[Kq](e,s))return this[qc](null,e);if(s.isDirectory()){if(e.type==="Directory"){let c=!this.noChmod&&e.mode&&(s.mode&4095)!==e.mode,[f]=c?NR(()=>{Mn.chmodSync(e.absolute,e.mode)}):[];return this[qc](f,e)}let[n]=NR(()=>Mn.rmdirSync(e.absolute));this[qc](n,e)}let[a]=e.absolute===this.cwd?[]:NR(()=>sCt(e.absolute));this[qc](a,e)}[Jq](e,r){let s=e.mode&4095||this.fmode,a=f=>{let p;try{Mn.closeSync(n)}catch(h){p=h}(f||p)&&this[Xo](f||p,e),r()},n;try{n=Mn.openSync(e.absolute,qIe(e.size),s)}catch(f){return a(f)}let c=this.transform&&this.transform(e)||e;c!==e&&(c.on("error",f=>this[Xo](f,e)),e.pipe(c)),c.on("data",f=>{try{Mn.writeSync(n,f,0,f.length)}catch(p){a(p)}}),c.on("end",f=>{let p=null;if(e.mtime&&!this.noMtime){let h=e.atime||new Date,E=e.mtime;try{Mn.futimesSync(n,h,E)}catch(C){try{Mn.utimesSync(e.absolute,h,E)}catch{p=C}}}if(this[Vv](e)){let h=this[Kv](e),E=this[Jv](e);try{Mn.fchownSync(n,h,E)}catch(C){try{Mn.chownSync(e.absolute,h,E)}catch{p=p||C}}}a(p)})}[zq](e,r){let s=e.mode&4095||this.dmode,a=this[H0](e.absolute,s);if(a){this[Xo](a,e),r();return}if(e.mtime&&!this.noMtime)try{Mn.utimesSync(e.absolute,e.atime||new Date,e.mtime)}catch{}if(this[Vv](e))try{Mn.chownSync(e.absolute,this[Kv](e),this[Jv](e))}catch{}r(),e.resume()}[H0](e,r){try{return HIe.sync($l(e),{uid:this.uid,gid:this.gid,processUid:this.processUid,processGid:this.processGid,umask:this.processUmask,preserve:this.preservePaths,unlink:this.unlink,cache:this.dirCache,cwd:this.cwd,mode:r})}catch(s){return s}}[LR](e,r,s,a){try{Mn[s+"Sync"](r,e.absolute),a(),e.resume()}catch(n){return this[Xo](n,e)}}};Xv.Sync=Xq;GIe.exports=Xv});var JIe=L((jer,KIe)=>{"use strict";var lCt=bI(),MR=$q(),YIe=ye("fs"),VIe=GI(),WIe=ye("path"),eG=NI();KIe.exports=(t,e,r)=>{typeof t=="function"?(r=t,e=null,t={}):Array.isArray(t)&&(e=t,t={}),typeof e=="function"&&(r=e,e=null),e?e=Array.from(e):e=[];let s=lCt(t);if(s.sync&&typeof r=="function")throw new TypeError("callback not supported for sync tar functions");if(!s.file&&typeof r=="function")throw new TypeError("callback only supported with file option");return e.length&&cCt(s,e),s.file&&s.sync?uCt(s):s.file?fCt(s,r):s.sync?ACt(s):pCt(s)};var cCt=(t,e)=>{let r=new Map(e.map(n=>[eG(n),!0])),s=t.filter,a=(n,c)=>{let f=c||WIe.parse(n).root||".",p=n===f?!1:r.has(n)?r.get(n):a(WIe.dirname(n),f);return r.set(n,p),p};t.filter=s?(n,c)=>s(n,c)&&a(eG(n)):n=>a(eG(n))},uCt=t=>{let e=new MR.Sync(t),r=t.file,s=YIe.statSync(r),a=t.maxReadSize||16*1024*1024;new VIe.ReadStreamSync(r,{readSize:a,size:s.size}).pipe(e)},fCt=(t,e)=>{let r=new MR(t),s=t.maxReadSize||16*1024*1024,a=t.file,n=new Promise((c,f)=>{r.on("error",f),r.on("close",c),YIe.stat(a,(p,h)=>{if(p)f(p);else{let E=new VIe.ReadStream(a,{readSize:s,size:h.size});E.on("error",f),E.pipe(r)}})});return e?n.then(e,e):n},ACt=t=>new MR.Sync(t),pCt=t=>new MR(t)});var zIe=L(xs=>{"use strict";xs.c=xs.create=QEe();xs.r=xs.replace=Nq();xs.t=xs.list=DR();xs.u=xs.update=UEe();xs.x=xs.extract=JIe();xs.Pack=fR();xs.Unpack=$q();xs.Parse=SR();xs.ReadEntry=KT();xs.WriteEntry=uq();xs.Header=FI();xs.Pax=zT();xs.types=Y6()});var tG,ZIe,j0,$v,eS,XIe=It(()=>{tG=et(Md()),ZIe=ye("worker_threads"),j0=Symbol("kTaskInfo"),$v=class{constructor(e,r){this.fn=e;this.limit=(0,tG.default)(r.poolSize)}run(e){return this.limit(()=>this.fn(e))}},eS=class{constructor(e,r){this.source=e;this.workers=[];this.limit=(0,tG.default)(r.poolSize),this.cleanupInterval=setInterval(()=>{if(this.limit.pendingCount===0&&this.limit.activeCount===0){let s=this.workers.pop();s?s.terminate():clearInterval(this.cleanupInterval)}},5e3).unref()}createWorker(){this.cleanupInterval.refresh();let e=new ZIe.Worker(this.source,{eval:!0,execArgv:[...process.execArgv,"--unhandled-rejections=strict"]});return e.on("message",r=>{if(!e[j0])throw new Error("Assertion failed: Worker sent a result without having a task assigned");e[j0].resolve(r),e[j0]=null,e.unref(),this.workers.push(e)}),e.on("error",r=>{e[j0]?.reject(r),e[j0]=null}),e.on("exit",r=>{r!==0&&e[j0]?.reject(new Error(`Worker exited with code ${r}`)),e[j0]=null}),e}run(e){return this.limit(()=>{let r=this.workers.pop()??this.createWorker();return r.ref(),new Promise((s,a)=>{r[j0]={resolve:s,reject:a},r.postMessage(e)})})}}});var eCe=L((Yer,$Ie)=>{var rG;$Ie.exports.getContent=()=>(typeof rG>"u"&&(rG=ye("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),rG)});var hs={};Vt(hs,{convertToZip:()=>mCt,convertToZipWorker:()=>sG,extractArchiveTo:()=>sCe,getDefaultTaskPool:()=>nCe,getTaskPoolForConfiguration:()=>iCe,makeArchiveFromDirectory:()=>dCt});function hCt(t,e){switch(t){case"async":return new $v(sG,{poolSize:e});case"workers":return new eS((0,iG.getContent)(),{poolSize:e});default:throw new Error(`Assertion failed: Unknown value ${t} for taskPoolMode`)}}function nCe(){return typeof nG>"u"&&(nG=hCt("workers",As.availableParallelism())),nG}function iCe(t){return typeof t>"u"?nCe():Vl(gCt,t,()=>{let e=t.get("taskPoolMode"),r=t.get("taskPoolConcurrency");switch(e){case"async":return new $v(sG,{poolSize:r});case"workers":return new eS((0,iG.getContent)(),{poolSize:r});default:throw new Error(`Assertion failed: Unknown value ${e} for taskPoolMode`)}})}async function sG(t){let{tmpFile:e,tgz:r,compressionLevel:s,extractBufferOpts:a}=t,n=new ps(e,{create:!0,level:s,stats:el.makeDefaultStats()}),c=Buffer.from(r.buffer,r.byteOffset,r.byteLength);return await sCe(c,n,a),n.saveAndClose(),e}async function dCt(t,{baseFs:e=new Yn,prefixPath:r=vt.root,compressionLevel:s,inMemory:a=!1}={}){let n;if(a)n=new ps(null,{level:s});else{let f=await le.mktempPromise(),p=K.join(f,"archive.zip");n=new ps(p,{create:!0,level:s})}let c=K.resolve(vt.root,r);return await n.copyPromise(c,t,{baseFs:e,stableTime:!0,stableSort:!0}),n}async function mCt(t,e={}){let r=await le.mktempPromise(),s=K.join(r,"archive.zip"),a=e.compressionLevel??e.configuration?.get("compressionLevel")??"mixed",n={prefixPath:e.prefixPath,stripComponents:e.stripComponents};return await(e.taskPool??iCe(e.configuration)).run({tmpFile:s,tgz:t,compressionLevel:a,extractBufferOpts:n}),new ps(s,{level:e.compressionLevel})}async function*yCt(t){let e=new rCe.default.Parse,r=new tCe.PassThrough({objectMode:!0,autoDestroy:!0,emitClose:!0});e.on("entry",s=>{r.write(s)}),e.on("error",s=>{r.destroy(s)}),e.on("close",()=>{r.destroyed||r.end()}),e.end(t);for await(let s of r){let a=s;yield a,a.resume()}}async function sCe(t,e,{stripComponents:r=0,prefixPath:s=vt.dot}={}){function a(n){if(n.path[0]==="/")return!0;let c=n.path.split(/\//g);return!!(c.some(f=>f==="..")||c.length<=r)}for await(let n of yCt(t)){if(a(n))continue;let c=K.normalize(ue.toPortablePath(n.path)).replace(/\/$/,"").split(/\//g);if(c.length<=r)continue;let f=c.slice(r).join("/"),p=K.join(s,f),h=420;switch((n.type==="Directory"||(n.mode??0)&73)&&(h|=73),n.type){case"Directory":e.mkdirpSync(K.dirname(p),{chmod:493,utimes:[ui.SAFE_TIME,ui.SAFE_TIME]}),e.mkdirSync(p,{mode:h}),e.utimesSync(p,ui.SAFE_TIME,ui.SAFE_TIME);break;case"OldFile":case"File":e.mkdirpSync(K.dirname(p),{chmod:493,utimes:[ui.SAFE_TIME,ui.SAFE_TIME]}),e.writeFileSync(p,await WE(n),{mode:h}),e.utimesSync(p,ui.SAFE_TIME,ui.SAFE_TIME);break;case"SymbolicLink":e.mkdirpSync(K.dirname(p),{chmod:493,utimes:[ui.SAFE_TIME,ui.SAFE_TIME]}),e.symlinkSync(n.linkpath,p),e.lutimesSync(p,ui.SAFE_TIME,ui.SAFE_TIME);break}}return e}var tCe,rCe,iG,nG,gCt,oCe=It(()=>{Ve();bt();rA();tCe=ye("stream"),rCe=et(zIe());XIe();kc();iG=et(eCe());gCt=new WeakMap});var lCe=L((oG,aCe)=>{(function(t,e){typeof oG=="object"?aCe.exports=e():typeof define=="function"&&define.amd?define(e):t.treeify=e()})(oG,function(){function t(a,n){var c=n?"\u2514":"\u251C";return a?c+="\u2500 ":c+="\u2500\u2500\u2510",c}function e(a,n){var c=[];for(var f in a)a.hasOwnProperty(f)&&(n&&typeof a[f]=="function"||c.push(f));return c}function r(a,n,c,f,p,h,E){var C="",S=0,P,I,R=f.slice(0);if(R.push([n,c])&&f.length>0&&(f.forEach(function(U,W){W>0&&(C+=(U[1]?" ":"\u2502")+" "),!I&&U[0]===n&&(I=!0)}),C+=t(a,c)+a,p&&(typeof n!="object"||n instanceof Date)&&(C+=": "+n),I&&(C+=" (circular ref.)"),E(C)),!I&&typeof n=="object"){var N=e(n,h);N.forEach(function(U){P=++S===N.length,r(U,n[U],P,R,p,h,E)})}}var s={};return s.asLines=function(a,n,c,f){var p=typeof c!="function"?c:!1;r(".",a,!1,[],n,p,f||c)},s.asTree=function(a,n,c){var f="";return r(".",a,!1,[],n,c,function(p){f+=p+` +`}),f},s})});var ks={};Vt(ks,{emitList:()=>ECt,emitTree:()=>ACe,treeNodeToJson:()=>fCe,treeNodeToTreeify:()=>uCe});function uCe(t,{configuration:e}){let r={},s=0,a=(n,c)=>{let f=Array.isArray(n)?n.entries():Object.entries(n);for(let[p,h]of f){if(!h)continue;let{label:E,value:C,children:S}=h,P=[];typeof E<"u"&&P.push(zd(e,E,2)),typeof C<"u"&&P.push(Ut(e,C[0],C[1])),P.length===0&&P.push(zd(e,`${p}`,2));let I=P.join(": ").trim(),R=`\0${s++}\0`,N=c[`${R}${I}`]={};typeof S<"u"&&a(S,N)}};if(typeof t.children>"u")throw new Error("The root node must only contain children");return a(t.children,r),r}function fCe(t){let e=r=>{if(typeof r.children>"u"){if(typeof r.value>"u")throw new Error("Assertion failed: Expected a value to be set if the children are missing");return Zd(r.value[0],r.value[1])}let s=Array.isArray(r.children)?r.children.entries():Object.entries(r.children??{}),a=Array.isArray(r.children)?[]:{};for(let[n,c]of s)c&&(a[ICt(n)]=e(c));return typeof r.value>"u"?a:{value:Zd(r.value[0],r.value[1]),children:a}};return e(t)}function ECt(t,{configuration:e,stdout:r,json:s}){let a=t.map(n=>({value:n}));ACe({children:a},{configuration:e,stdout:r,json:s})}function ACe(t,{configuration:e,stdout:r,json:s,separators:a=0}){if(s){let c=Array.isArray(t.children)?t.children.values():Object.values(t.children??{});for(let f of c)f&&r.write(`${JSON.stringify(fCe(f))} +`);return}let n=(0,cCe.asTree)(uCe(t,{configuration:e}),!1,!1);if(n=n.replace(/\0[0-9]+\0/g,""),a>=1&&(n=n.replace(/^([├└]─)/gm,`\u2502 +$1`).replace(/^│\n/,"")),a>=2)for(let c=0;c<2;++c)n=n.replace(/^([│ ].{2}[├│ ].{2}[^\n]+\n)(([│ ]).{2}[├└].{2}[^\n]*\n[│ ].{2}[│ ].{2}[├└]─)/gm,`$1$3 \u2502 +$2`).replace(/^│\n/,"");if(a>=3)throw new Error("Only the first two levels are accepted by treeUtils.emitTree");r.write(n)}function ICt(t){return typeof t=="string"?t.replace(/^\0[0-9]+\0/,""):t}var cCe,pCe=It(()=>{cCe=et(lCe());Qc()});var _R,hCe=It(()=>{_R=class{constructor(e){this.releaseFunction=e;this.map=new Map}addOrCreate(e,r){let s=this.map.get(e);if(typeof s<"u"){if(s.refCount<=0)throw new Error(`Race condition in RefCountedMap. While adding a new key the refCount is: ${s.refCount} for ${JSON.stringify(e)}`);return s.refCount++,{value:s.value,release:()=>this.release(e)}}else{let a=r();return this.map.set(e,{refCount:1,value:a}),{value:a,release:()=>this.release(e)}}}release(e){let r=this.map.get(e);if(!r)throw new Error(`Unbalanced calls to release. No known instances of: ${JSON.stringify(e)}`);let s=r.refCount;if(s<=0)throw new Error(`Unbalanced calls to release. Too many release vs alloc refcount would become: ${s-1} of ${JSON.stringify(e)}`);s==1?(this.map.delete(e),this.releaseFunction(r.value)):r.refCount--}}});function tS(t){let e=t.match(CCt);if(!e?.groups)throw new Error("Assertion failed: Expected the checksum to match the requested pattern");let r=e.groups.cacheVersion?parseInt(e.groups.cacheVersion):null;return{cacheKey:e.groups.cacheKey??null,cacheVersion:r,cacheSpec:e.groups.cacheSpec??null,hash:e.groups.hash}}var gCe,aG,lG,UR,Jr,CCt,cG=It(()=>{Ve();bt();bt();rA();gCe=ye("crypto"),aG=et(ye("fs"));hCe();Fc();I0();kc();Yo();lG=YE(process.env.YARN_CACHE_CHECKPOINT_OVERRIDE??process.env.YARN_CACHE_VERSION_OVERRIDE??9),UR=YE(process.env.YARN_CACHE_VERSION_OVERRIDE??10),Jr=class t{constructor(e,{configuration:r,immutable:s=r.get("enableImmutableCache"),check:a=!1}){this.markedFiles=new Set;this.mutexes=new Map;this.refCountedZipFsCache=new _R(e=>{e.discardAndClose()});this.cacheId=`-${(0,gCe.randomBytes)(8).toString("hex")}.tmp`;this.configuration=r,this.cwd=e,this.immutable=s,this.check=a;let{cacheSpec:n,cacheKey:c}=t.getCacheKey(r);this.cacheSpec=n,this.cacheKey=c}static async find(e,{immutable:r,check:s}={}){let a=new t(e.get("cacheFolder"),{configuration:e,immutable:r,check:s});return await a.setup(),a}static getCacheKey(e){let r=e.get("compressionLevel"),s=r!=="mixed"?`c${r}`:"";return{cacheKey:[UR,s].join(""),cacheSpec:s}}get mirrorCwd(){if(!this.configuration.get("enableMirror"))return null;let e=`${this.configuration.get("globalFolder")}/cache`;return e!==this.cwd?e:null}getVersionFilename(e){return`${nI(e)}-${this.cacheKey}.zip`}getChecksumFilename(e,r){let a=tS(r).hash.slice(0,10);return`${nI(e)}-${a}.zip`}isChecksumCompatible(e){if(e===null)return!1;let{cacheVersion:r,cacheSpec:s}=tS(e);if(r===null||r{let pe=new ps,Be=K.join(vt.root,Z8(e));return pe.mkdirSync(Be,{recursive:!0}),pe.writeJsonSync(K.join(Be,Er.manifest),{name:cn(e),mocked:!0}),pe},E=async(pe,{isColdHit:Be,controlPath:Ce=null})=>{if(Ce===null&&c.unstablePackages?.has(e.locatorHash))return{isValid:!0,hash:null};let g=r&&!Be?tS(r).cacheKey:this.cacheKey,we=!c.skipIntegrityCheck||!r?`${g}/${await SQ(pe)}`:r;if(Ce!==null){let fe=!c.skipIntegrityCheck||!r?`${this.cacheKey}/${await SQ(Ce)}`:r;if(we!==fe)throw new Yt(18,"The remote archive doesn't match the local checksum - has the local cache been corrupted?")}let Ee=null;switch(r!==null&&we!==r&&(this.check?Ee="throw":tS(r).cacheKey!==tS(we).cacheKey?Ee="update":Ee=this.configuration.get("checksumBehavior")),Ee){case null:case"update":return{isValid:!0,hash:we};case"ignore":return{isValid:!0,hash:r};case"reset":return{isValid:!1,hash:r};default:case"throw":throw new Yt(18,"The remote archive doesn't match the expected checksum")}},C=async pe=>{if(!n)throw new Error(`Cache check required but no loader configured for ${Yr(this.configuration,e)}`);let Be=await n(),Ce=Be.getRealPath();Be.saveAndClose(),await le.chmodPromise(Ce,420);let g=await E(pe,{controlPath:Ce,isColdHit:!1});if(!g.isValid)throw new Error("Assertion failed: Expected a valid checksum");return g.hash},S=async()=>{if(f===null||!await le.existsPromise(f)){let pe=await n(),Be=pe.getRealPath();return pe.saveAndClose(),{source:"loader",path:Be}}return{source:"mirror",path:f}},P=async()=>{if(!n)throw new Error(`Cache entry required but missing for ${Yr(this.configuration,e)}`);if(this.immutable)throw new Yt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}`);let{path:pe,source:Be}=await S(),{hash:Ce}=await E(pe,{isColdHit:!0}),g=this.getLocatorPath(e,Ce),we=[];Be!=="mirror"&&f!==null&&we.push(async()=>{let fe=`${f}${this.cacheId}`;await le.copyFilePromise(pe,fe,aG.default.constants.COPYFILE_FICLONE),await le.chmodPromise(fe,420),await le.renamePromise(fe,f)}),(!c.mirrorWriteOnly||f===null)&&we.push(async()=>{let fe=`${g}${this.cacheId}`;await le.copyFilePromise(pe,fe,aG.default.constants.COPYFILE_FICLONE),await le.chmodPromise(fe,420),await le.renamePromise(fe,g)});let Ee=c.mirrorWriteOnly?f??g:g;return await Promise.all(we.map(fe=>fe())),[!1,Ee,Ce]},I=async()=>{let Be=(async()=>{let Ce=c.unstablePackages?.has(e.locatorHash),g=Ce||!r||this.isChecksumCompatible(r)?this.getLocatorPath(e,r):null,we=g!==null?this.markedFiles.has(g)||await p.existsPromise(g):!1,Ee=!!c.mockedPackages?.has(e.locatorHash)&&(!this.check||!we),fe=Ee||we,se=fe?s:a;if(se&&se(),fe){let X=null,De=g;if(!Ee)if(this.check)X=await C(De);else{let Re=await E(De,{isColdHit:!1});if(Re.isValid)X=Re.hash;else return P()}return[Ee,De,X]}else{if(this.immutable&&Ce)throw new Yt(56,`Cache entry required but missing for ${Yr(this.configuration,e)}; consider defining ${he.pretty(this.configuration,"supportedArchitectures",he.Type.CODE)} to cache packages for multiple systems`);return P()}})();this.mutexes.set(e.locatorHash,Be);try{return await Be}finally{this.mutexes.delete(e.locatorHash)}};for(let pe;pe=this.mutexes.get(e.locatorHash);)await pe;let[R,N,U]=await I();R||this.markedFiles.add(N);let W=()=>this.refCountedZipFsCache.addOrCreate(N,()=>R?h():new ps(N,{baseFs:p,readOnly:!0})),te,ie=new oE(()=>p3(()=>(te=W(),te.value),pe=>`Failed to open the cache entry for ${Yr(this.configuration,e)}: ${pe}`),K),Ae=new Hf(N,{baseFs:ie,pathUtils:K}),ce=()=>{te?.release()},me=c.unstablePackages?.has(e.locatorHash)?null:U;return[Ae,ce,me]}},CCt=/^(?:(?(?[0-9]+)(?.*))\/)?(?.*)$/});var HR,dCe=It(()=>{HR=(r=>(r[r.SCRIPT=0]="SCRIPT",r[r.SHELLCODE=1]="SHELLCODE",r))(HR||{})});var wCt,zI,uG=It(()=>{bt();Bc();Np();Yo();wCt=[[/^(git(?:\+(?:https|ssh))?:\/\/.*(?:\.git)?)#(.*)$/,(t,e,r,s)=>`${r}#commit=${s}`],[/^https:\/\/((?:[^/]+?)@)?codeload\.github\.com\/([^/]+\/[^/]+)\/tar\.gz\/([0-9a-f]+)$/,(t,e,r="",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https:\/\/((?:[^/]+?)@)?github\.com\/([^/]+\/[^/]+?)(?:\.git)?#([0-9a-f]+)$/,(t,e,r="",s,a)=>`https://${r}github.com/${s}.git#commit=${a}`],[/^https?:\/\/[^/]+\/(?:[^/]+\/)*(?:@.+(?:\/|(?:%2f)))?([^/]+)\/(?:-|download)\/\1-[^/]+\.tgz(?:#|$)/,t=>`npm:${t}`],[/^https:\/\/npm\.pkg\.github\.com\/download\/(?:@[^/]+)\/(?:[^/]+)\/(?:[^/]+)\/(?:[0-9a-f]+)(?:#|$)/,t=>`npm:${t}`],[/^https:\/\/npm\.fontawesome\.com\/(?:@[^/]+)\/([^/]+)\/-\/([^/]+)\/\1-\2.tgz(?:#|$)/,t=>`npm:${t}`],[/^https?:\/\/[^/]+\/.*\/(@[^/]+)\/([^/]+)\/-\/\1\/\2-(?:[.\d\w-]+)\.tgz(?:#|$)/,(t,e)=>kQ({protocol:"npm:",source:null,selector:t,params:{__archiveUrl:e}})],[/^[^/]+\.tgz#[0-9a-f]+$/,t=>`npm:${t}`]],zI=class{constructor(e){this.resolver=e;this.resolutions=null}async setup(e,{report:r}){let s=K.join(e.cwd,Er.lockfile);if(!le.existsSync(s))return;let a=await le.readFilePromise(s,"utf8"),n=ls(a);if(Object.hasOwn(n,"__metadata"))return;let c=this.resolutions=new Map;for(let f of Object.keys(n)){let p=ev(f);if(!p){r.reportWarning(14,`Failed to parse the string "${f}" into a proper descriptor`);continue}let h=ul(p.range)?On(p,`npm:${p.range}`):p,{version:E,resolved:C}=n[f];if(!C)continue;let S;for(let[I,R]of wCt){let N=C.match(I);if(N){S=R(E,...N);break}}if(!S){r.reportWarning(14,`${ri(e.configuration,h)}: Only some patterns can be imported from legacy lockfiles (not "${C}")`);continue}let P=h;try{let I=em(h.range),R=ev(I.selector,!0);R&&(P=R)}catch{}c.set(h.descriptorHash,Ys(P,S))}}supportsDescriptor(e,r){return this.resolutions?this.resolutions.has(e.descriptorHash):!1}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!this.resolutions)throw new Error("Assertion failed: The resolution store should have been setup");let a=this.resolutions.get(e.descriptorHash);if(!a)throw new Error("Assertion failed: The resolution should have been registered");let n=V8(a),c=s.project.configuration.normalizeDependency(n);return await this.resolver.getCandidates(c,r,s)}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){throw new Error("Assertion failed: This resolver doesn't support resolving locators to packages")}}});var uA,mCe=It(()=>{Fc();Fv();Qc();uA=class extends ho{constructor({configuration:r,stdout:s,suggestInstall:a=!0}){super();this.errorCount=0;YB(this,{configuration:r}),this.configuration=r,this.stdout=s,this.suggestInstall=a}static async start(r,s){let a=new this(r);try{await s(a)}catch(n){a.reportExceptionOnce(n)}finally{await a.finalize()}return a}hasErrors(){return this.errorCount>0}exitCode(){return this.hasErrors()?1:0}reportCacheHit(r){}reportCacheMiss(r){}startSectionSync(r,s){return s()}async startSectionPromise(r,s){return await s()}startTimerSync(r,s,a){return(typeof s=="function"?s:a)()}async startTimerPromise(r,s,a){return await(typeof s=="function"?s:a)()}reportSeparator(){}reportInfo(r,s){}reportWarning(r,s){}reportError(r,s){this.errorCount+=1,this.stdout.write(`${Ut(this.configuration,"\u27A4","redBright")} ${this.formatNameWithHyperlink(r)}: ${s} +`)}reportProgress(r){return{...Promise.resolve().then(async()=>{for await(let{}of r);}),stop:()=>{}}}reportJson(r){}reportFold(r,s){}async finalize(){this.errorCount>0&&(this.stdout.write(` +`),this.stdout.write(`${Ut(this.configuration,"\u27A4","redBright")} Errors happened when preparing the environment required to run this command. +`),this.suggestInstall&&this.stdout.write(`${Ut(this.configuration,"\u27A4","redBright")} This might be caused by packages being missing from the lockfile, in which case running "yarn install" might help. +`))}formatNameWithHyperlink(r){return m6(r,{configuration:this.configuration,json:!1})}}});var ZI,fG=It(()=>{Yo();ZI=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return!!(r.project.storedResolutions.get(e.descriptorHash)||r.project.originalPackages.has(bQ(e).locatorHash))}supportsLocator(e,r){return!!(r.project.originalPackages.has(e.locatorHash)&&!r.project.lockfileNeedsRefresh)}shouldPersistResolution(e,r){throw new Error("The shouldPersistResolution method shouldn't be called on the lockfile resolver, which would always answer yes")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){let a=s.project.storedResolutions.get(e.descriptorHash);if(a){let c=s.project.originalPackages.get(a);if(c)return[c]}let n=s.project.originalPackages.get(bQ(e).locatorHash);if(n)return[n];throw new Error("Resolution expected from the lockfile data")}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let s=r.project.originalPackages.get(e.locatorHash);if(!s)throw new Error("The lockfile resolver isn't meant to resolve packages - they should already have been stored into a cache");return s}}});function Xp(){}function BCt(t,e,r,s,a){for(var n=0,c=e.length,f=0,p=0;nP.length?R:P}),h.value=t.join(E)}else h.value=t.join(r.slice(f,f+h.count));f+=h.count,h.added||(p+=h.count)}}var S=e[c-1];return c>1&&typeof S.value=="string"&&(S.added||S.removed)&&t.equals("",S.value)&&(e[c-2].value+=S.value,e.pop()),e}function vCt(t){return{newPos:t.newPos,components:t.components.slice(0)}}function SCt(t,e){if(typeof t=="function")e.callback=t;else if(t)for(var r in t)t.hasOwnProperty(r)&&(e[r]=t[r]);return e}function ICe(t,e,r){return r=SCt(r,{ignoreWhitespace:!0}),dG.diff(t,e,r)}function DCt(t,e,r){return mG.diff(t,e,r)}function jR(t){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?jR=function(e){return typeof e}:jR=function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},jR(t)}function AG(t){return xCt(t)||kCt(t)||QCt(t)||TCt()}function xCt(t){if(Array.isArray(t))return pG(t)}function kCt(t){if(typeof Symbol<"u"&&Symbol.iterator in Object(t))return Array.from(t)}function QCt(t,e){if(t){if(typeof t=="string")return pG(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set")return Array.from(t);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return pG(t,e)}}function pG(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,s=new Array(e);r"u"&&(c.context=4);var f=DCt(r,s,c);if(!f)return;f.push({value:"",lines:[]});function p(U){return U.map(function(W){return" "+W})}for(var h=[],E=0,C=0,S=[],P=1,I=1,R=function(W){var te=f[W],ie=te.lines||te.value.replace(/\n$/,"").split(` +`);if(te.lines=ie,te.added||te.removed){var Ae;if(!E){var ce=f[W-1];E=P,C=I,ce&&(S=c.context>0?p(ce.lines.slice(-c.context)):[],E-=S.length,C-=S.length)}(Ae=S).push.apply(Ae,AG(ie.map(function(fe){return(te.added?"+":"-")+fe}))),te.added?I+=ie.length:P+=ie.length}else{if(E)if(ie.length<=c.context*2&&W=f.length-2&&ie.length<=c.context){var g=/\n$/.test(r),we=/\n$/.test(s),Ee=ie.length==0&&S.length>Ce.oldLines;!g&&Ee&&r.length>0&&S.splice(Ce.oldLines,0,"\\ No newline at end of file"),(!g&&!Ee||!we)&&S.push("\\ No newline at end of file")}h.push(Ce),E=0,C=0,S=[]}P+=ie.length,I+=ie.length}},N=0;N{Xp.prototype={diff:function(e,r){var s=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},a=s.callback;typeof s=="function"&&(a=s,s={}),this.options=s;var n=this;function c(R){return a?(setTimeout(function(){a(void 0,R)},0),!0):R}e=this.castInput(e),r=this.castInput(r),e=this.removeEmpty(this.tokenize(e)),r=this.removeEmpty(this.tokenize(r));var f=r.length,p=e.length,h=1,E=f+p;s.maxEditLength&&(E=Math.min(E,s.maxEditLength));var C=[{newPos:-1,components:[]}],S=this.extractCommon(C[0],r,e,0);if(C[0].newPos+1>=f&&S+1>=p)return c([{value:this.join(r),count:r.length}]);function P(){for(var R=-1*h;R<=h;R+=2){var N=void 0,U=C[R-1],W=C[R+1],te=(W?W.newPos:0)-R;U&&(C[R-1]=void 0);var ie=U&&U.newPos+1=f&&te+1>=p)return c(BCt(n,N.components,r,e,n.useLongestToken));C[R]=N}h++}if(a)(function R(){setTimeout(function(){if(h>E)return a();P()||R()},0)})();else for(;h<=E;){var I=P();if(I)return I}},pushComponent:function(e,r,s){var a=e[e.length-1];a&&a.added===r&&a.removed===s?e[e.length-1]={count:a.count+1,added:r,removed:s}:e.push({count:1,added:r,removed:s})},extractCommon:function(e,r,s,a){for(var n=r.length,c=s.length,f=e.newPos,p=f-a,h=0;f+1"u"?r:c}:s;return typeof t=="string"?t:JSON.stringify(hG(t,null,null,a),a," ")};rS.equals=function(t,e){return Xp.prototype.equals.call(rS,t.replace(/,([\r\n])/g,"$1"),e.replace(/,([\r\n])/g,"$1"))};gG=new Xp;gG.tokenize=function(t){return t.slice()};gG.join=gG.removeEmpty=function(t){return t}});var BCe=L((Str,wCe)=>{var FCt=xc(),NCt=aI(),OCt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,LCt=/^\w*$/;function MCt(t,e){if(FCt(t))return!1;var r=typeof t;return r=="number"||r=="symbol"||r=="boolean"||t==null||NCt(t)?!0:LCt.test(t)||!OCt.test(t)||e!=null&&t in Object(e)}wCe.exports=MCt});var DCe=L((Dtr,SCe)=>{var vCe=kk(),_Ct="Expected a function";function EG(t,e){if(typeof t!="function"||e!=null&&typeof e!="function")throw new TypeError(_Ct);var r=function(){var s=arguments,a=e?e.apply(this,s):s[0],n=r.cache;if(n.has(a))return n.get(a);var c=t.apply(this,s);return r.cache=n.set(a,c)||n,c};return r.cache=new(EG.Cache||vCe),r}EG.Cache=vCe;SCe.exports=EG});var PCe=L((btr,bCe)=>{var UCt=DCe(),HCt=500;function jCt(t){var e=UCt(t,function(s){return r.size===HCt&&r.clear(),s}),r=e.cache;return e}bCe.exports=jCt});var IG=L((Ptr,xCe)=>{var qCt=PCe(),GCt=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,WCt=/\\(\\)?/g,YCt=qCt(function(t){var e=[];return t.charCodeAt(0)===46&&e.push(""),t.replace(GCt,function(r,s,a,n){e.push(a?n.replace(WCt,"$1"):s||r)}),e});xCe.exports=YCt});var wm=L((xtr,kCe)=>{var VCt=xc(),KCt=BCe(),JCt=IG(),zCt=Tv();function ZCt(t,e){return VCt(t)?t:KCt(t,e)?[t]:JCt(zCt(t))}kCe.exports=ZCt});var XI=L((ktr,QCe)=>{var XCt=aI(),$Ct=1/0;function ewt(t){if(typeof t=="string"||XCt(t))return t;var e=t+"";return e=="0"&&1/t==-$Ct?"-0":e}QCe.exports=ewt});var qR=L((Qtr,TCe)=>{var twt=wm(),rwt=XI();function nwt(t,e){e=twt(e,t);for(var r=0,s=e.length;t!=null&&r{var iwt=Yk(),swt=wm(),owt=NB(),RCe=Wl(),awt=XI();function lwt(t,e,r,s){if(!RCe(t))return t;e=swt(e,t);for(var a=-1,n=e.length,c=n-1,f=t;f!=null&&++a{var cwt=qR(),uwt=CG(),fwt=wm();function Awt(t,e,r){for(var s=-1,a=e.length,n={};++s{function pwt(t,e){return t!=null&&e in Object(t)}LCe.exports=pwt});var wG=L((Ntr,_Ce)=>{var hwt=wm(),gwt=TB(),dwt=xc(),mwt=NB(),ywt=Nk(),Ewt=XI();function Iwt(t,e,r){e=hwt(e,t);for(var s=-1,a=e.length,n=!1;++s{var Cwt=MCe(),wwt=wG();function Bwt(t,e){return t!=null&&wwt(t,e,Cwt)}UCe.exports=Bwt});var qCe=L((Ltr,jCe)=>{var vwt=OCe(),Swt=HCe();function Dwt(t,e){return vwt(t,e,function(r,s){return Swt(t,s)})}jCe.exports=Dwt});var VCe=L((Mtr,YCe)=>{var GCe=Yd(),bwt=TB(),Pwt=xc(),WCe=GCe?GCe.isConcatSpreadable:void 0;function xwt(t){return Pwt(t)||bwt(t)||!!(WCe&&t&&t[WCe])}YCe.exports=xwt});var zCe=L((_tr,JCe)=>{var kwt=Rk(),Qwt=VCe();function KCe(t,e,r,s,a){var n=-1,c=t.length;for(r||(r=Qwt),a||(a=[]);++n0&&r(f)?e>1?KCe(f,e-1,r,s,a):kwt(a,f):s||(a[a.length]=f)}return a}JCe.exports=KCe});var XCe=L((Utr,ZCe)=>{var Twt=zCe();function Rwt(t){var e=t==null?0:t.length;return e?Twt(t,1):[]}ZCe.exports=Rwt});var BG=L((Htr,$Ce)=>{var Fwt=XCe(),Nwt=i3(),Owt=s3();function Lwt(t){return Owt(Nwt(t,void 0,Fwt),t+"")}$Ce.exports=Lwt});var vG=L((jtr,ewe)=>{var Mwt=qCe(),_wt=BG(),Uwt=_wt(function(t,e){return t==null?{}:Mwt(t,e)});ewe.exports=Uwt});var GR,twe=It(()=>{Fc();GR=class{constructor(e){this.resolver=e}supportsDescriptor(e,r){return this.resolver.supportsDescriptor(e,r)}supportsLocator(e,r){return this.resolver.supportsLocator(e,r)}shouldPersistResolution(e,r){return this.resolver.shouldPersistResolution(e,r)}bindDescriptor(e,r,s){return this.resolver.bindDescriptor(e,r,s)}getResolutionDependencies(e,r){return this.resolver.getResolutionDependencies(e,r)}async getCandidates(e,r,s){throw new Yt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}async getSatisfying(e,r,s,a){throw new Yt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}async resolve(e,r){throw new Yt(20,`This package doesn't seem to be present in your lockfile; run "yarn install" to update the lockfile`)}}});var Wi,SG=It(()=>{Fc();Wi=class extends ho{reportCacheHit(e){}reportCacheMiss(e){}startSectionSync(e,r){return r()}async startSectionPromise(e,r){return await r()}startTimerSync(e,r,s){return(typeof r=="function"?r:s)()}async startTimerPromise(e,r,s){return await(typeof r=="function"?r:s)()}reportSeparator(){}reportInfo(e,r){}reportWarning(e,r){}reportError(e,r){}reportProgress(e){return{...Promise.resolve().then(async()=>{for await(let{}of e);}),stop:()=>{}}}reportJson(e){}reportFold(e,r){}async finalize(){}}});var rwe,$I,DG=It(()=>{bt();rwe=et(BQ());oI();tm();Qc();I0();Np();Yo();$I=class{constructor(e,{project:r}){this.workspacesCwds=new Set;this.project=r,this.cwd=e}async setup(){this.manifest=await Ht.tryFind(this.cwd)??new Ht,this.relativeCwd=K.relative(this.project.cwd,this.cwd)||vt.dot;let e=this.manifest.name?this.manifest.name:ba(null,`${this.computeCandidateName()}-${us(this.relativeCwd).substring(0,6)}`);this.anchoredDescriptor=On(e,`${yi.protocol}${this.relativeCwd}`),this.anchoredLocator=Ys(e,`${yi.protocol}${this.relativeCwd}`);let r=this.manifest.workspaceDefinitions.map(({pattern:a})=>a);if(r.length===0)return;let s=await(0,rwe.default)(r,{cwd:ue.fromPortablePath(this.cwd),onlyDirectories:!0,ignore:["**/node_modules","**/.git","**/.yarn"]});s.sort(),await s.reduce(async(a,n)=>{let c=K.resolve(this.cwd,ue.toPortablePath(n)),f=await le.existsPromise(K.join(c,"package.json"));await a,f&&this.workspacesCwds.add(c)},Promise.resolve())}get anchoredPackage(){let e=this.project.storedPackages.get(this.anchoredLocator.locatorHash);if(!e)throw new Error(`Assertion failed: Expected workspace ${rv(this.project.configuration,this)} (${Ut(this.project.configuration,K.join(this.cwd,Er.manifest),Ct.PATH)}) to have been resolved. Run "yarn install" to update the lockfile`);return e}accepts(e){let r=e.indexOf(":"),s=r!==-1?e.slice(0,r+1):null,a=r!==-1?e.slice(r+1):e;if(s===yi.protocol&&K.normalize(a)===this.relativeCwd||s===yi.protocol&&(a==="*"||a==="^"||a==="~"))return!0;let n=ul(a);return n?s===yi.protocol?n.test(this.manifest.version??"0.0.0"):this.project.configuration.get("enableTransparentWorkspaces")&&this.manifest.version!==null?n.test(this.manifest.version):!1:!1}computeCandidateName(){return this.cwd===this.project.cwd?"root-workspace":`${K.basename(this.cwd)}`||"unnamed-workspace"}getRecursiveWorkspaceDependencies({dependencies:e=Ht.hardDependencies}={}){let r=new Set,s=a=>{for(let n of e)for(let c of a.manifest[n].values()){let f=this.project.tryWorkspaceByDescriptor(c);f===null||r.has(f)||(r.add(f),s(f))}};return s(this),r}getRecursiveWorkspaceDependents({dependencies:e=Ht.hardDependencies}={}){let r=new Set,s=a=>{for(let n of this.project.workspaces)e.some(f=>[...n.manifest[f].values()].some(p=>{let h=this.project.tryWorkspaceByDescriptor(p);return h!==null&&$B(h.anchoredLocator,a.anchoredLocator)}))&&!r.has(n)&&(r.add(n),s(n))};return s(this),r}getRecursiveWorkspaceChildren(){let e=new Set([this]);for(let r of e)for(let s of r.workspacesCwds){let a=this.project.workspacesByCwd.get(s);a&&e.add(a)}return e.delete(this),Array.from(e)}async persistManifest(){let e={};this.manifest.exportTo(e);let r=K.join(this.cwd,Ht.fileName),s=`${JSON.stringify(e,null,this.manifest.indent)} +`;await le.changeFilePromise(r,s,{automaticNewlines:!0}),this.manifest.raw=e}}});function Ywt({project:t,allDescriptors:e,allResolutions:r,allPackages:s,accessibleLocators:a=new Set,optionalBuilds:n=new Set,peerRequirements:c=new Map,peerWarnings:f=[],peerRequirementNodes:p=new Map,volatileDescriptors:h=new Set}){let E=new Map,C=[],S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=new Map(t.workspaces.map(ce=>{let me=ce.anchoredLocator.locatorHash,pe=s.get(me);if(typeof pe>"u")throw new Error("Assertion failed: The workspace should have an associated package");return[me,zB(pe)]})),W=()=>{let ce=le.mktempSync(),me=K.join(ce,"stacktrace.log"),pe=String(C.length+1).length,Be=C.map((Ce,g)=>`${`${g+1}.`.padStart(pe," ")} ${cl(Ce)} +`).join("");throw le.writeFileSync(me,Be),le.detachTemp(ce),new Yt(45,`Encountered a stack overflow when resolving peer dependencies; cf ${ue.fromPortablePath(me)}`)},te=ce=>{let me=r.get(ce.descriptorHash);if(typeof me>"u")throw new Error("Assertion failed: The resolution should have been registered");let pe=s.get(me);if(!pe)throw new Error("Assertion failed: The package could not be found");return pe},ie=(ce,me,pe,{top:Be,optional:Ce})=>{C.length>1e3&&W(),C.push(me);let g=Ae(ce,me,pe,{top:Be,optional:Ce});return C.pop(),g},Ae=(ce,me,pe,{top:Be,optional:Ce})=>{if(Ce||n.delete(me.locatorHash),a.has(me.locatorHash))return;a.add(me.locatorHash);let g=s.get(me.locatorHash);if(!g)throw new Error(`Assertion failed: The package (${Yr(t.configuration,me)}) should have been registered`);let we=new Set,Ee=new Map,fe=[],se=[],X=[],De=[];for(let Re of Array.from(g.dependencies.values())){if(g.peerDependencies.has(Re.identHash)&&g.locatorHash!==Be)continue;if(Tp(Re))throw new Error("Assertion failed: Virtual packages shouldn't be encountered when virtualizing a branch");h.delete(Re.descriptorHash);let gt=Ce;if(!gt){let ke=g.dependenciesMeta.get(cn(Re));if(typeof ke<"u"){let it=ke.get(null);typeof it<"u"&&it.optional&&(gt=!0)}}let j=r.get(Re.descriptorHash);if(!j)throw new Error(`Assertion failed: The resolution (${ri(t.configuration,Re)}) should have been registered`);let rt=U.get(j)||s.get(j);if(!rt)throw new Error(`Assertion failed: The package (${j}, resolved from ${ri(t.configuration,Re)}) should have been registered`);if(rt.peerDependencies.size===0){ie(Re,rt,new Map,{top:Be,optional:gt});continue}let Fe,Ne,Pe=new Set,Ye=new Map;fe.push(()=>{Fe=J8(Re,me.locatorHash),Ne=z8(rt,me.locatorHash),g.dependencies.set(Re.identHash,Fe),r.set(Fe.descriptorHash,Ne.locatorHash),e.set(Fe.descriptorHash,Fe),s.set(Ne.locatorHash,Ne),xp(R,Ne.locatorHash).add(Fe.descriptorHash),we.add(Ne.locatorHash)}),se.push(()=>{N.set(Ne.locatorHash,Ye);for(let ke of Ne.peerDependencies.values()){let _e=Vl(Ee,ke.identHash,()=>{let x=pe.get(ke.identHash)??null,w=g.dependencies.get(ke.identHash);return!w&&XB(me,ke)&&(ce.identHash===me.identHash?w=ce:(w=On(me,ce.range),e.set(w.descriptorHash,w),r.set(w.descriptorHash,me.locatorHash),h.delete(w.descriptorHash),x=null)),w||(w=On(ke,"missing:")),{subject:me,ident:ke,provided:w,root:!x,requests:new Map,hash:`p${us(me.locatorHash,ke.identHash).slice(0,5)}`}}).provided;if(_e.range==="missing:"&&Ne.dependencies.has(ke.identHash)){Ne.peerDependencies.delete(ke.identHash);continue}if(Ye.set(ke.identHash,{requester:Ne,descriptor:ke,meta:Ne.peerDependenciesMeta.get(cn(ke)),children:new Map}),Ne.dependencies.set(ke.identHash,_e),Tp(_e)){let x=r.get(_e.descriptorHash);xp(I,x).add(Ne.locatorHash)}S.set(_e.identHash,_e),_e.range==="missing:"&&Pe.add(_e.identHash)}Ne.dependencies=new Map(Ws(Ne.dependencies,([ke,it])=>cn(it)))}),X.push(()=>{if(!s.has(Ne.locatorHash))return;let ke=E.get(rt.locatorHash);typeof ke=="number"&&ke>=2&&W();let it=E.get(rt.locatorHash),_e=typeof it<"u"?it+1:1;E.set(rt.locatorHash,_e),ie(Fe,Ne,Ye,{top:Be,optional:gt}),E.set(rt.locatorHash,_e-1)}),De.push(()=>{let ke=r.get(Fe.descriptorHash);if(typeof ke>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let it=N.get(ke);if(typeof it>"u")throw new Error("Assertion failed: Expected the peer requests to be registered");for(let _e of Ee.values()){let x=it.get(_e.ident.identHash);x&&(_e.requests.set(Fe.descriptorHash,x),p.set(_e.hash,_e),_e.root||pe.get(_e.ident.identHash)?.children.set(Fe.descriptorHash,x))}if(s.has(Ne.locatorHash))for(let _e of Pe)Ne.dependencies.delete(_e)})}for(let Re of[...fe,...se])Re();for(let Re of we){we.delete(Re);let gt=s.get(Re),j=us(rI(gt).locatorHash,...Array.from(gt.dependencies.values(),Pe=>{let Ye=Pe.range!=="missing:"?r.get(Pe.descriptorHash):"missing:";if(typeof Ye>"u")throw new Error(`Assertion failed: Expected the resolution for ${ri(t.configuration,Pe)} to have been registered`);return Ye===Be?`${Ye} (top)`:Ye})),rt=P.get(j);if(typeof rt>"u"){P.set(j,gt);continue}let Fe=xp(R,rt.locatorHash);for(let Pe of R.get(gt.locatorHash)??[])r.set(Pe,rt.locatorHash),Fe.add(Pe);s.delete(gt.locatorHash),a.delete(gt.locatorHash),we.delete(gt.locatorHash);let Ne=I.get(gt.locatorHash);if(Ne!==void 0){let Pe=xp(I,rt.locatorHash);for(let Ye of Ne)Pe.add(Ye),we.add(Ye)}}for(let Re of[...X,...De])Re()};for(let ce of t.workspaces){let me=ce.anchoredLocator;h.delete(ce.anchoredDescriptor.descriptorHash),ie(ce.anchoredDescriptor,me,new Map,{top:me.locatorHash,optional:!1})}for(let ce of p.values()){if(!ce.root)continue;let me=s.get(ce.subject.locatorHash);if(typeof me>"u")continue;for(let Be of ce.requests.values()){let Ce=`p${us(ce.subject.locatorHash,cn(ce.ident),Be.requester.locatorHash).slice(0,5)}`;c.set(Ce,{subject:ce.subject.locatorHash,requested:ce.ident,rootRequester:Be.requester.locatorHash,allRequesters:Array.from(nv(Be),g=>g.requester.locatorHash)})}let pe=[...nv(ce)];if(ce.provided.range!=="missing:"){let Be=te(ce.provided),Ce=Be.version??"0.0.0",g=Ee=>{if(Ee.startsWith(yi.protocol)){if(!t.tryWorkspaceByLocator(Be))return null;Ee=Ee.slice(yi.protocol.length),(Ee==="^"||Ee==="~")&&(Ee="*")}return Ee},we=!0;for(let Ee of pe){let fe=g(Ee.descriptor.range);if(fe===null){we=!1;continue}if(!eA(Ce,fe)){we=!1;let se=`p${us(ce.subject.locatorHash,cn(ce.ident),Ee.requester.locatorHash).slice(0,5)}`;f.push({type:1,subject:me,requested:ce.ident,requester:Ee.requester,version:Ce,hash:se,requirementCount:pe.length})}}if(!we){let Ee=pe.map(fe=>g(fe.descriptor.range));f.push({type:3,node:ce,range:Ee.includes(null)?null:$8(Ee),hash:ce.hash})}}else{let Be=!0;for(let Ce of pe)if(!Ce.meta?.optional){Be=!1;let g=`p${us(ce.subject.locatorHash,cn(ce.ident),Ce.requester.locatorHash).slice(0,5)}`;f.push({type:0,subject:me,requested:ce.ident,requester:Ce.requester,hash:g})}Be||f.push({type:2,node:ce,hash:ce.hash})}}}function*Vwt(t){let e=new Map;if("children"in t)e.set(t,t);else for(let r of t.requests.values())e.set(r,r);for(let[r,s]of e){yield{request:r,root:s};for(let a of r.children.values())e.has(a)||e.set(a,s)}}function Kwt(t,e){let r=[],s=[],a=!1;for(let n of t.peerWarnings)if(!(n.type===1||n.type===0)){if(!t.tryWorkspaceByLocator(n.node.subject)){a=!0;continue}if(n.type===3){let c=t.storedResolutions.get(n.node.provided.descriptorHash);if(typeof c>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let f=t.storedPackages.get(c);if(typeof f>"u")throw new Error("Assertion failed: Expected the package to be registered");let p=p0(Vwt(n.node),({request:C,root:S})=>eA(f.version??"0.0.0",C.descriptor.range)?p0.skip:C===S?$i(t.configuration,C.requester):`${$i(t.configuration,C.requester)} (via ${$i(t.configuration,S.requester)})`),h=[...nv(n.node)].length>1?"and other dependencies request":"requests",E=n.range?iI(t.configuration,n.range):Ut(t.configuration,"but they have non-overlapping ranges!","redBright");r.push(`${$i(t.configuration,n.node.ident)} is listed by your project with version ${tv(t.configuration,f.version??"0.0.0")} (${Ut(t.configuration,n.hash,Ct.CODE)}), which doesn't satisfy what ${p} ${h} (${E}).`)}if(n.type===2){let c=n.node.requests.size>1?" and other dependencies":"";s.push(`${Yr(t.configuration,n.node.subject)} doesn't provide ${$i(t.configuration,n.node.ident)} (${Ut(t.configuration,n.hash,Ct.CODE)}), requested by ${$i(t.configuration,n.node.requests.values().next().value.requester)}${c}.`)}}e.startSectionSync({reportFooter:()=>{e.reportWarning(86,`Some peer dependencies are incorrectly met by your project; run ${Ut(t.configuration,"yarn explain peer-requirements ",Ct.CODE)} for details, where ${Ut(t.configuration,"",Ct.CODE)} is the six-letter p-prefixed code.`)},skipIfEmpty:!0},()=>{for(let n of Ws(r,c=>KE.default(c)))e.reportWarning(60,n);for(let n of Ws(s,c=>KE.default(c)))e.reportWarning(2,n)}),a&&e.reportWarning(86,`Some peer dependencies are incorrectly met by dependencies; run ${Ut(t.configuration,"yarn explain peer-requirements",Ct.CODE)} for details.`)}var WR,YR,VR,swe,xG,PG,kG,KR,Hwt,jwt,nwe,qwt,Gwt,Wwt,ec,bG,JR,iwe,Tt,owe=It(()=>{bt();bt();Bc();Wt();WR=ye("crypto");yG();YR=et(vG()),VR=et(Md()),swe=et(fi()),xG=ye("util"),PG=et(ye("v8")),kG=et(ye("zlib"));cG();Cv();uG();fG();oI();iH();Fc();twe();Fv();SG();tm();DG();LQ();Qc();I0();kc();gT();w6();Np();Yo();KR=YE(process.env.YARN_LOCKFILE_VERSION_OVERRIDE??8),Hwt=3,jwt=/ *, */g,nwe=/\/$/,qwt=32,Gwt=(0,xG.promisify)(kG.default.gzip),Wwt=(0,xG.promisify)(kG.default.gunzip),ec=(r=>(r.UpdateLockfile="update-lockfile",r.SkipBuild="skip-build",r))(ec||{}),bG={restoreLinkersCustomData:["linkersCustomData"],restoreResolutions:["accessibleLocators","conditionalLocators","disabledLocators","optionalBuilds","storedDescriptors","storedResolutions","storedPackages","lockFileChecksum"],restoreBuildState:["skippedBuilds","storedBuildState"]},JR=(a=>(a[a.NotProvided=0]="NotProvided",a[a.NotCompatible=1]="NotCompatible",a[a.NodeNotProvided=2]="NodeNotProvided",a[a.NodeNotCompatible=3]="NodeNotCompatible",a))(JR||{}),iwe=t=>us(`${Hwt}`,t),Tt=class t{constructor(e,{configuration:r}){this.resolutionAliases=new Map;this.workspaces=[];this.workspacesByCwd=new Map;this.workspacesByIdent=new Map;this.storedResolutions=new Map;this.storedDescriptors=new Map;this.storedPackages=new Map;this.storedChecksums=new Map;this.storedBuildState=new Map;this.accessibleLocators=new Set;this.conditionalLocators=new Set;this.disabledLocators=new Set;this.originalPackages=new Map;this.optionalBuilds=new Set;this.skippedBuilds=new Set;this.lockfileLastVersion=null;this.lockfileNeedsRefresh=!1;this.peerRequirements=new Map;this.peerWarnings=[];this.peerRequirementNodes=new Map;this.linkersCustomData=new Map;this.lockFileChecksum=null;this.installStateChecksum=null;this.configuration=r,this.cwd=e}static async find(e,r){if(!e.projectCwd)throw new nt(`No project found in ${r}`);let s=e.projectCwd,a=r,n=null;for(;n!==e.projectCwd;){if(n=a,le.existsSync(K.join(n,Er.manifest))){s=n;break}a=K.dirname(n)}let c=new t(e.projectCwd,{configuration:e});ze.telemetry?.reportProject(c.cwd),await c.setupResolutions(),await c.setupWorkspaces(),ze.telemetry?.reportWorkspaceCount(c.workspaces.length),ze.telemetry?.reportDependencyCount(c.workspaces.reduce((I,R)=>I+R.manifest.dependencies.size+R.manifest.devDependencies.size,0));let f=c.tryWorkspaceByCwd(s);if(f)return{project:c,workspace:f,locator:f.anchoredLocator};let p=await c.findLocatorForLocation(`${s}/`,{strict:!0});if(p)return{project:c,locator:p,workspace:null};let h=Ut(e,c.cwd,Ct.PATH),E=Ut(e,K.relative(c.cwd,s),Ct.PATH),C=`- If ${h} isn't intended to be a project, remove any yarn.lock and/or package.json file there.`,S=`- If ${h} is intended to be a project, it might be that you forgot to list ${E} in its workspace configuration.`,P=`- Finally, if ${h} is fine and you intend ${E} to be treated as a completely separate project (not even a workspace), create an empty yarn.lock file in it.`;throw new nt(`The nearest package directory (${Ut(e,s,Ct.PATH)}) doesn't seem to be part of the project declared in ${Ut(e,c.cwd,Ct.PATH)}. + +${[C,S,P].join(` +`)}`)}async setupResolutions(){this.storedResolutions=new Map,this.storedDescriptors=new Map,this.storedPackages=new Map,this.lockFileChecksum=null;let e=K.join(this.cwd,Er.lockfile),r=this.configuration.get("defaultLanguageName");if(le.existsSync(e)){let s=await le.readFilePromise(e,"utf8");this.lockFileChecksum=iwe(s);let a=ls(s);if(a.__metadata){let n=a.__metadata.version,c=a.__metadata.cacheKey;this.lockfileLastVersion=n,this.lockfileNeedsRefresh=n"u")throw new Error(`Assertion failed: Expected the lockfile entry to have a resolution field (${f})`);let h=Rp(p.resolution,!0),E=new Ht;E.load(p,{yamlCompatibilityMode:!0});let C=E.version,S=E.languageName||r,P=p.linkType.toUpperCase(),I=p.conditions??null,R=E.dependencies,N=E.peerDependencies,U=E.dependenciesMeta,W=E.peerDependenciesMeta,te=E.bin;if(p.checksum!=null){let Ae=typeof c<"u"&&!p.checksum.includes("/")?`${c}/${p.checksum}`:p.checksum;this.storedChecksums.set(h.locatorHash,Ae)}let ie={...h,version:C,languageName:S,linkType:P,conditions:I,dependencies:R,peerDependencies:N,dependenciesMeta:U,peerDependenciesMeta:W,bin:te};this.originalPackages.set(ie.locatorHash,ie);for(let Ae of f.split(jwt)){let ce=C0(Ae);n<=6&&(ce=this.configuration.normalizeDependency(ce),ce=On(ce,ce.range.replace(/^patch:[^@]+@(?!npm(:|%3A))/,"$1npm%3A"))),this.storedDescriptors.set(ce.descriptorHash,ce),this.storedResolutions.set(ce.descriptorHash,h.locatorHash)}}}else s.includes("yarn lockfile v1")&&(this.lockfileLastVersion=-1)}}async setupWorkspaces(){this.workspaces=[],this.workspacesByCwd=new Map,this.workspacesByIdent=new Map;let e=new Set,r=(0,VR.default)(4),s=async(a,n)=>{if(e.has(n))return a;e.add(n);let c=new $I(n,{project:this});await r(()=>c.setup());let f=a.then(()=>{this.addWorkspace(c)});return Array.from(c.workspacesCwds).reduce(s,f)};await s(Promise.resolve(),this.cwd)}addWorkspace(e){let r=this.workspacesByIdent.get(e.anchoredLocator.identHash);if(typeof r<"u")throw new Error(`Duplicate workspace name ${$i(this.configuration,e.anchoredLocator)}: ${ue.fromPortablePath(e.cwd)} conflicts with ${ue.fromPortablePath(r.cwd)}`);this.workspaces.push(e),this.workspacesByCwd.set(e.cwd,e),this.workspacesByIdent.set(e.anchoredLocator.identHash,e)}get topLevelWorkspace(){return this.getWorkspaceByCwd(this.cwd)}tryWorkspaceByCwd(e){K.isAbsolute(e)||(e=K.resolve(this.cwd,e)),e=K.normalize(e).replace(/\/+$/,"");let r=this.workspacesByCwd.get(e);return r||null}getWorkspaceByCwd(e){let r=this.tryWorkspaceByCwd(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByFilePath(e){let r=null;for(let s of this.workspaces)K.relative(s.cwd,e).startsWith("../")||r&&r.cwd.length>=s.cwd.length||(r=s);return r||null}getWorkspaceByFilePath(e){let r=this.tryWorkspaceByFilePath(e);if(!r)throw new Error(`Workspace not found (${e})`);return r}tryWorkspaceByIdent(e){let r=this.workspacesByIdent.get(e.identHash);return typeof r>"u"?null:r}getWorkspaceByIdent(e){let r=this.tryWorkspaceByIdent(e);if(!r)throw new Error(`Workspace not found (${$i(this.configuration,e)})`);return r}tryWorkspaceByDescriptor(e){if(e.range.startsWith(yi.protocol)){let s=e.range.slice(yi.protocol.length);if(s!=="^"&&s!=="~"&&s!=="*"&&!ul(s))return this.tryWorkspaceByCwd(s)}let r=this.tryWorkspaceByIdent(e);return r===null||(Tp(e)&&(e=ZB(e)),!r.accepts(e.range))?null:r}getWorkspaceByDescriptor(e){let r=this.tryWorkspaceByDescriptor(e);if(r===null)throw new Error(`Workspace not found (${ri(this.configuration,e)})`);return r}tryWorkspaceByLocator(e){let r=this.tryWorkspaceByIdent(e);return r===null||(Gu(e)&&(e=rI(e)),r.anchoredLocator.locatorHash!==e.locatorHash)?null:r}getWorkspaceByLocator(e){let r=this.tryWorkspaceByLocator(e);if(!r)throw new Error(`Workspace not found (${Yr(this.configuration,e)})`);return r}deleteDescriptor(e){this.storedResolutions.delete(e),this.storedDescriptors.delete(e)}deleteLocator(e){this.originalPackages.delete(e),this.storedPackages.delete(e),this.accessibleLocators.delete(e)}forgetResolution(e){if("descriptorHash"in e){let r=this.storedResolutions.get(e.descriptorHash);this.deleteDescriptor(e.descriptorHash);let s=new Set(this.storedResolutions.values());typeof r<"u"&&!s.has(r)&&this.deleteLocator(r)}if("locatorHash"in e){this.deleteLocator(e.locatorHash);for(let[r,s]of this.storedResolutions)s===e.locatorHash&&this.deleteDescriptor(r)}}forgetTransientResolutions(){let e=this.configuration.makeResolver(),r=new Map;for(let[s,a]of this.storedResolutions.entries()){let n=r.get(a);n||r.set(a,n=new Set),n.add(s)}for(let s of this.originalPackages.values()){let a;try{a=e.shouldPersistResolution(s,{project:this,resolver:e})}catch{a=!1}if(!a){this.deleteLocator(s.locatorHash);let n=r.get(s.locatorHash);if(n){r.delete(s.locatorHash);for(let c of n)this.deleteDescriptor(c)}}}}forgetVirtualResolutions(){for(let e of this.storedPackages.values())for(let[r,s]of e.dependencies)Tp(s)&&e.dependencies.set(r,ZB(s))}getDependencyMeta(e,r){let s={},n=this.topLevelWorkspace.manifest.dependenciesMeta.get(cn(e));if(!n)return s;let c=n.get(null);if(c&&Object.assign(s,c),r===null||!swe.default.valid(r))return s;for(let[f,p]of n)f!==null&&f===r&&Object.assign(s,p);return s}async findLocatorForLocation(e,{strict:r=!1}={}){let s=new Wi,a=this.configuration.getLinkers(),n={project:this,report:s};for(let c of a){let f=await c.findPackageLocator(e,n);if(f){if(r&&(await c.findPackageLocation(f,n)).replace(nwe,"")!==e.replace(nwe,""))continue;return f}}return null}async loadUserConfig(){let e=K.join(this.cwd,".pnp.cjs");await le.existsPromise(e)&&kp(e).setup();let r=K.join(this.cwd,"yarn.config.cjs");return await le.existsPromise(r)?kp(r):null}async preparePackage(e,{resolver:r,resolveOptions:s}){let a=await this.configuration.getPackageExtensions(),n=this.configuration.normalizePackage(e,{packageExtensions:a});for(let[c,f]of n.dependencies){let p=await this.configuration.reduceHook(E=>E.reduceDependency,f,this,n,f,{resolver:r,resolveOptions:s});if(!XB(f,p))throw new Error("Assertion failed: The descriptor ident cannot be changed through aliases");let h=r.bindDescriptor(p,n,s);n.dependencies.set(c,h)}return n}async resolveEverything(e){if(!this.workspacesByCwd||!this.workspacesByIdent)throw new Error("Workspaces must have been setup before calling this function");this.forgetVirtualResolutions();let r=new Map(this.originalPackages),s=[];e.lockfileOnly||this.forgetTransientResolutions();let a=e.resolver||this.configuration.makeResolver(),n=new zI(a);await n.setup(this,{report:e.report});let c=e.lockfileOnly?[new GR(a)]:[n,a],f=new rm([new ZI(a),...c]),p=new rm([...c]),h=this.configuration.makeFetcher(),E=e.lockfileOnly?{project:this,report:e.report,resolver:f}:{project:this,report:e.report,resolver:f,fetchOptions:{project:this,cache:e.cache,checksums:this.storedChecksums,report:e.report,fetcher:h,cacheOptions:{mirrorWriteOnly:!0}}},C=new Map,S=new Map,P=new Map,I=new Map,R=new Map,N=new Map,U=this.topLevelWorkspace.anchoredLocator,W=new Set,te=[],ie=Tj(),Ae=this.configuration.getSupportedArchitectures();await e.report.startProgressPromise(ho.progressViaTitle(),async se=>{let X=async rt=>{let Fe=await GE(async()=>await f.resolve(rt,E),ke=>`${Yr(this.configuration,rt)}: ${ke}`);if(!$B(rt,Fe))throw new Error(`Assertion failed: The locator cannot be changed by the resolver (went from ${Yr(this.configuration,rt)} to ${Yr(this.configuration,Fe)})`);I.set(Fe.locatorHash,Fe),!r.delete(Fe.locatorHash)&&!this.tryWorkspaceByLocator(Fe)&&s.push(Fe);let Pe=await this.preparePackage(Fe,{resolver:f,resolveOptions:E}),Ye=Uu([...Pe.dependencies.values()].map(ke=>j(ke)));return te.push(Ye),Ye.catch(()=>{}),S.set(Pe.locatorHash,Pe),Pe},De=async rt=>{let Fe=R.get(rt.locatorHash);if(typeof Fe<"u")return Fe;let Ne=Promise.resolve().then(()=>X(rt));return R.set(rt.locatorHash,Ne),Ne},Re=async(rt,Fe)=>{let Ne=await j(Fe);return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,Ne.locatorHash),Ne},gt=async rt=>{se.setTitle(ri(this.configuration,rt));let Fe=this.resolutionAliases.get(rt.descriptorHash);if(typeof Fe<"u")return Re(rt,this.storedDescriptors.get(Fe));let Ne=f.getResolutionDependencies(rt,E),Pe=Object.fromEntries(await Uu(Object.entries(Ne).map(async([it,_e])=>{let x=f.bindDescriptor(_e,U,E),w=await j(x);return W.add(w.locatorHash),[it,w]}))),ke=(await GE(async()=>await f.getCandidates(rt,Pe,E),it=>`${ri(this.configuration,rt)}: ${it}`))[0];if(typeof ke>"u")throw new Yt(82,`${ri(this.configuration,rt)}: No candidates found`);if(e.checkResolutions){let{locators:it}=await p.getSatisfying(rt,Pe,[ke],{...E,resolver:p});if(!it.find(_e=>_e.locatorHash===ke.locatorHash))throw new Yt(78,`Invalid resolution ${VB(this.configuration,rt,ke)}`)}return C.set(rt.descriptorHash,rt),P.set(rt.descriptorHash,ke.locatorHash),De(ke)},j=rt=>{let Fe=N.get(rt.descriptorHash);if(typeof Fe<"u")return Fe;C.set(rt.descriptorHash,rt);let Ne=Promise.resolve().then(()=>gt(rt));return N.set(rt.descriptorHash,Ne),Ne};for(let rt of this.workspaces){let Fe=rt.anchoredDescriptor;te.push(j(Fe))}for(;te.length>0;){let rt=[...te];te.length=0,await Uu(rt)}});let ce=Yl(r.values(),se=>this.tryWorkspaceByLocator(se)?Yl.skip:se);if(s.length>0||ce.length>0){let se=new Set(this.workspaces.flatMap(rt=>{let Fe=S.get(rt.anchoredLocator.locatorHash);if(!Fe)throw new Error("Assertion failed: The workspace should have been resolved");return Array.from(Fe.dependencies.values(),Ne=>{let Pe=P.get(Ne.descriptorHash);if(!Pe)throw new Error("Assertion failed: The resolution should have been registered");return Pe})})),X=rt=>se.has(rt.locatorHash)?"0":"1",De=rt=>cl(rt),Re=Ws(s,[X,De]),gt=Ws(ce,[X,De]),j=e.report.getRecommendedLength();Re.length>0&&e.report.reportInfo(85,`${Ut(this.configuration,"+",Ct.ADDED)} ${$k(this.configuration,Re,j)}`),gt.length>0&&e.report.reportInfo(85,`${Ut(this.configuration,"-",Ct.REMOVED)} ${$k(this.configuration,gt,j)}`)}let me=new Set(this.resolutionAliases.values()),pe=new Set(S.keys()),Be=new Set,Ce=new Map,g=[],we=new Map;Ywt({project:this,accessibleLocators:Be,volatileDescriptors:me,optionalBuilds:pe,peerRequirements:Ce,peerWarnings:g,peerRequirementNodes:we,allDescriptors:C,allResolutions:P,allPackages:S});for(let se of W)pe.delete(se);for(let se of me)C.delete(se),P.delete(se);let Ee=new Set,fe=new Set;for(let se of S.values())se.conditions!=null&&pe.has(se.locatorHash)&&(TQ(se,Ae)||(TQ(se,ie)&&e.report.reportWarningOnce(77,`${Yr(this.configuration,se)}: Your current architecture (${process.platform}-${process.arch}) is supported by this package, but is missing from the ${Ut(this.configuration,"supportedArchitectures",Ct.SETTING)} setting`),fe.add(se.locatorHash)),Ee.add(se.locatorHash));this.storedResolutions=P,this.storedDescriptors=C,this.storedPackages=S,this.accessibleLocators=Be,this.conditionalLocators=Ee,this.disabledLocators=fe,this.originalPackages=I,this.optionalBuilds=pe,this.peerRequirements=Ce,this.peerWarnings=g,this.peerRequirementNodes=we}async fetchEverything({cache:e,report:r,fetcher:s,mode:a,persistProject:n=!0}){let c={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators},f=s||this.configuration.makeFetcher(),p={checksums:this.storedChecksums,project:this,cache:e,fetcher:f,report:r,cacheOptions:c},h=Array.from(new Set(Ws(this.storedResolutions.values(),[I=>{let R=this.storedPackages.get(I);if(!R)throw new Error("Assertion failed: The locator should have been registered");return cl(R)}])));a==="update-lockfile"&&(h=h.filter(I=>!this.storedChecksums.has(I)));let E=!1,C=ho.progressViaCounter(h.length);await r.reportProgress(C);let S=(0,VR.default)(qwt);if(await Uu(h.map(I=>S(async()=>{let R=this.storedPackages.get(I);if(!R)throw new Error("Assertion failed: The locator should have been registered");if(Gu(R))return;let N;try{N=await f.fetch(R,p)}catch(U){U.message=`${Yr(this.configuration,R)}: ${U.message}`,r.reportExceptionOnce(U),E=U;return}N.checksum!=null?this.storedChecksums.set(R.locatorHash,N.checksum):this.storedChecksums.delete(R.locatorHash),N.releaseFs&&N.releaseFs()}).finally(()=>{C.tick()}))),E)throw E;let P=n&&a!=="update-lockfile"?await this.cacheCleanup({cache:e,report:r}):null;if(r.cacheMisses.size>0||P){let R=(await Promise.all([...r.cacheMisses].map(async ce=>{let me=this.storedPackages.get(ce),pe=this.storedChecksums.get(ce)??null,Be=e.getLocatorPath(me,pe);return(await le.statPromise(Be)).size}))).reduce((ce,me)=>ce+me,0)-(P?.size??0),N=r.cacheMisses.size,U=P?.count??0,W=`${Vk(N,{zero:"No new packages",one:"A package was",more:`${Ut(this.configuration,N,Ct.NUMBER)} packages were`})} added to the project`,te=`${Vk(U,{zero:"none were",one:"one was",more:`${Ut(this.configuration,U,Ct.NUMBER)} were`})} removed`,ie=R!==0?` (${Ut(this.configuration,R,Ct.SIZE_DIFF)})`:"",Ae=U>0?N>0?`${W}, and ${te}${ie}.`:`${W}, but ${te}${ie}.`:`${W}${ie}.`;r.reportInfo(13,Ae)}}async linkEverything({cache:e,report:r,fetcher:s,mode:a}){let n={mockedPackages:this.disabledLocators,unstablePackages:this.conditionalLocators,skipIntegrityCheck:!0},c=s||this.configuration.makeFetcher(),f={checksums:this.storedChecksums,project:this,cache:e,fetcher:c,report:r,cacheOptions:n},p=this.configuration.getLinkers(),h={project:this,report:r},E=new Map(p.map(Ee=>{let fe=Ee.makeInstaller(h),se=Ee.getCustomDataKey(),X=this.linkersCustomData.get(se);return typeof X<"u"&&fe.attachCustomData(X),[Ee,fe]})),C=new Map,S=new Map,P=new Map,I=new Map(await Uu([...this.accessibleLocators].map(async Ee=>{let fe=this.storedPackages.get(Ee);if(!fe)throw new Error("Assertion failed: The locator should have been registered");return[Ee,await c.fetch(fe,f)]}))),R=[],N=new Set,U=[];for(let Ee of this.accessibleLocators){let fe=this.storedPackages.get(Ee);if(typeof fe>"u")throw new Error("Assertion failed: The locator should have been registered");let se=I.get(fe.locatorHash);if(typeof se>"u")throw new Error("Assertion failed: The fetch result should have been registered");let X=[],De=gt=>{X.push(gt)},Re=this.tryWorkspaceByLocator(fe);if(Re!==null){let gt=[],{scripts:j}=Re.manifest;for(let Fe of["preinstall","install","postinstall"])j.has(Fe)&>.push({type:0,script:Fe});try{for(let[Fe,Ne]of E)if(Fe.supportsPackage(fe,h)&&(await Ne.installPackage(fe,se,{holdFetchResult:De})).buildRequest!==null)throw new Error("Assertion failed: Linkers can't return build directives for workspaces; this responsibility befalls to the Yarn core")}finally{X.length===0?se.releaseFs?.():R.push(Uu(X).catch(()=>{}).then(()=>{se.releaseFs?.()}))}let rt=K.join(se.packageFs.getRealPath(),se.prefixPath);S.set(fe.locatorHash,rt),!Gu(fe)&>.length>0&&P.set(fe.locatorHash,{buildDirectives:gt,buildLocations:[rt]})}else{let gt=p.find(Fe=>Fe.supportsPackage(fe,h));if(!gt)throw new Yt(12,`${Yr(this.configuration,fe)} isn't supported by any available linker`);let j=E.get(gt);if(!j)throw new Error("Assertion failed: The installer should have been registered");let rt;try{rt=await j.installPackage(fe,se,{holdFetchResult:De})}finally{X.length===0?se.releaseFs?.():R.push(Uu(X).then(()=>{}).then(()=>{se.releaseFs?.()}))}C.set(fe.locatorHash,gt),S.set(fe.locatorHash,rt.packageLocation),rt.buildRequest&&rt.packageLocation&&(rt.buildRequest.skipped?(N.add(fe.locatorHash),this.skippedBuilds.has(fe.locatorHash)||U.push([fe,rt.buildRequest.explain])):P.set(fe.locatorHash,{buildDirectives:rt.buildRequest.directives,buildLocations:[rt.packageLocation]}))}}let W=new Map;for(let Ee of this.accessibleLocators){let fe=this.storedPackages.get(Ee);if(!fe)throw new Error("Assertion failed: The locator should have been registered");let se=this.tryWorkspaceByLocator(fe)!==null,X=async(De,Re)=>{let gt=S.get(fe.locatorHash);if(typeof gt>"u")throw new Error(`Assertion failed: The package (${Yr(this.configuration,fe)}) should have been registered`);let j=[];for(let rt of fe.dependencies.values()){let Fe=this.storedResolutions.get(rt.descriptorHash);if(typeof Fe>"u")throw new Error(`Assertion failed: The resolution (${ri(this.configuration,rt)}, from ${Yr(this.configuration,fe)})should have been registered`);let Ne=this.storedPackages.get(Fe);if(typeof Ne>"u")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ri(this.configuration,rt)}) should have been registered`);let Pe=this.tryWorkspaceByLocator(Ne)===null?C.get(Fe):null;if(typeof Pe>"u")throw new Error(`Assertion failed: The package (${Fe}, resolved from ${ri(this.configuration,rt)}) should have been registered`);Pe===De||Pe===null?S.get(Ne.locatorHash)!==null&&j.push([rt,Ne]):!se&>!==null&&jB(W,Fe).push(gt)}gt!==null&&await Re.attachInternalDependencies(fe,j)};if(se)for(let[De,Re]of E)De.supportsPackage(fe,h)&&await X(De,Re);else{let De=C.get(fe.locatorHash);if(!De)throw new Error("Assertion failed: The linker should have been found");let Re=E.get(De);if(!Re)throw new Error("Assertion failed: The installer should have been registered");await X(De,Re)}}for(let[Ee,fe]of W){let se=this.storedPackages.get(Ee);if(!se)throw new Error("Assertion failed: The package should have been registered");let X=C.get(se.locatorHash);if(!X)throw new Error("Assertion failed: The linker should have been found");let De=E.get(X);if(!De)throw new Error("Assertion failed: The installer should have been registered");await De.attachExternalDependents(se,fe)}let te=new Map;for(let[Ee,fe]of E){let se=await fe.finalizeInstall();for(let X of se?.records??[])X.buildRequest.skipped?(N.add(X.locator.locatorHash),this.skippedBuilds.has(X.locator.locatorHash)||U.push([X.locator,X.buildRequest.explain])):P.set(X.locator.locatorHash,{buildDirectives:X.buildRequest.directives,buildLocations:X.buildLocations});typeof se?.customData<"u"&&te.set(Ee.getCustomDataKey(),se.customData)}if(this.linkersCustomData=te,await Uu(R),a==="skip-build")return;for(let[,Ee]of Ws(U,([fe])=>cl(fe)))Ee(r);let ie=new Set(P.keys()),Ae=(0,WR.createHash)("sha512");Ae.update(process.versions.node),await this.configuration.triggerHook(Ee=>Ee.globalHashGeneration,this,Ee=>{Ae.update("\0"),Ae.update(Ee)});let ce=Ae.digest("hex"),me=new Map,pe=Ee=>{let fe=me.get(Ee.locatorHash);if(typeof fe<"u")return fe;let se=this.storedPackages.get(Ee.locatorHash);if(typeof se>"u")throw new Error("Assertion failed: The package should have been registered");let X=(0,WR.createHash)("sha512");X.update(Ee.locatorHash),me.set(Ee.locatorHash,"");for(let De of se.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(typeof Re>"u")throw new Error(`Assertion failed: The resolution (${ri(this.configuration,De)}) should have been registered`);let gt=this.storedPackages.get(Re);if(typeof gt>"u")throw new Error("Assertion failed: The package should have been registered");X.update(pe(gt))}return fe=X.digest("hex"),me.set(Ee.locatorHash,fe),fe},Be=(Ee,fe)=>{let se=(0,WR.createHash)("sha512");se.update(ce),se.update(pe(Ee));for(let X of fe)se.update(X);return se.digest("hex")},Ce=new Map,g=!1,we=Ee=>{let fe=new Set([Ee.locatorHash]);for(let se of fe){let X=this.storedPackages.get(se);if(!X)throw new Error("Assertion failed: The package should have been registered");for(let De of X.dependencies.values()){let Re=this.storedResolutions.get(De.descriptorHash);if(!Re)throw new Error(`Assertion failed: The resolution (${ri(this.configuration,De)}) should have been registered`);if(Re!==Ee.locatorHash&&ie.has(Re))return!1;let gt=this.storedPackages.get(Re);if(!gt)throw new Error("Assertion failed: The package should have been registered");let j=this.tryWorkspaceByLocator(gt);if(j){if(j.anchoredLocator.locatorHash!==Ee.locatorHash&&ie.has(j.anchoredLocator.locatorHash))return!1;fe.add(j.anchoredLocator.locatorHash)}fe.add(Re)}}return!0};for(;ie.size>0;){let Ee=ie.size,fe=[];for(let se of ie){let X=this.storedPackages.get(se);if(!X)throw new Error("Assertion failed: The package should have been registered");if(!we(X))continue;let De=P.get(X.locatorHash);if(!De)throw new Error("Assertion failed: The build directive should have been registered");let Re=Be(X,De.buildLocations);if(this.storedBuildState.get(X.locatorHash)===Re){Ce.set(X.locatorHash,Re),ie.delete(se);continue}g||(await this.persistInstallStateFile(),g=!0),this.storedBuildState.has(X.locatorHash)?r.reportInfo(8,`${Yr(this.configuration,X)} must be rebuilt because its dependency tree changed`):r.reportInfo(7,`${Yr(this.configuration,X)} must be built because it never has been before or the last one failed`);let gt=De.buildLocations.map(async j=>{if(!K.isAbsolute(j))throw new Error(`Assertion failed: Expected the build location to be absolute (not ${j})`);for(let rt of De.buildDirectives){let Fe=`# This file contains the result of Yarn building a package (${cl(X)}) +`;switch(rt.type){case 0:Fe+=`# Script name: ${rt.script} +`;break;case 1:Fe+=`# Script code: ${rt.script} +`;break}let Ne=null;if(!await le.mktempPromise(async Ye=>{let ke=K.join(Ye,"build.log"),{stdout:it,stderr:_e}=this.configuration.getSubprocessStreams(ke,{header:Fe,prefix:Yr(this.configuration,X),report:r}),x;try{switch(rt.type){case 0:x=await MT(X,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:_e});break;case 1:x=await y6(X,rt.script,[],{cwd:j,project:this,stdin:Ne,stdout:it,stderr:_e});break}}catch(y){_e.write(y.stack),x=1}if(it.end(),_e.end(),x===0)return!0;le.detachTemp(Ye);let w=`${Yr(this.configuration,X)} couldn't be built successfully (exit code ${Ut(this.configuration,x,Ct.NUMBER)}, logs can be found here: ${Ut(this.configuration,ke,Ct.PATH)})`,b=this.optionalBuilds.has(X.locatorHash);return b?r.reportInfo(9,w):r.reportError(9,w),rye&&r.reportFold(ue.fromPortablePath(ke),le.readFileSync(ke,"utf8")),b}))return!1}return!0});fe.push(...gt,Promise.allSettled(gt).then(j=>{ie.delete(se),j.every(rt=>rt.status==="fulfilled"&&rt.value===!0)&&Ce.set(X.locatorHash,Re)}))}if(await Uu(fe),Ee===ie.size){let se=Array.from(ie).map(X=>{let De=this.storedPackages.get(X);if(!De)throw new Error("Assertion failed: The package should have been registered");return Yr(this.configuration,De)}).join(", ");r.reportError(3,`Some packages have circular dependencies that make their build order unsatisfiable - as a result they won't be built (affected packages are: ${se})`);break}}this.storedBuildState=Ce,this.skippedBuilds=N}async installWithNewReport(e,r){return(await Ot.start({configuration:this.configuration,json:e.json,stdout:e.stdout,forceSectionAlignment:!0,includeLogs:!e.json&&!e.quiet,includeVersion:!0},async a=>{await this.install({...r,report:a})})).exitCode()}async install(e){let r=this.configuration.get("nodeLinker");ze.telemetry?.reportInstall(r);let s=!1;if(await e.report.startTimerPromise("Project validation",{skipIfEmpty:!0},async()=>{this.configuration.get("enableOfflineMode")&&e.report.reportWarning(90,"Offline work is enabled; Yarn won't fetch packages from the remote registry if it can avoid it"),await this.configuration.triggerHook(E=>E.validateProject,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),s=!0}})}),s)return;let a=await this.configuration.getPackageExtensions();for(let E of a.values())for(let[,C]of E)for(let S of C)S.status="inactive";let n=K.join(this.cwd,Er.lockfile),c=null;if(e.immutable)try{c=await le.readFilePromise(n,"utf8")}catch(E){throw E.code==="ENOENT"?new Yt(28,"The lockfile would have been created by this install, which is explicitly forbidden."):E}await e.report.startTimerPromise("Resolution step",async()=>{await this.resolveEverything(e)}),await e.report.startTimerPromise("Post-resolution validation",{skipIfEmpty:!0},async()=>{Kwt(this,e.report);for(let[,E]of a)for(let[,C]of E)for(let S of C)if(S.userProvided){let P=Ut(this.configuration,S,Ct.PACKAGE_EXTENSION);switch(S.status){case"inactive":e.report.reportWarning(68,`${P}: No matching package in the dependency tree; you may not need this rule anymore.`);break;case"redundant":e.report.reportWarning(69,`${P}: This rule seems redundant when applied on the original package; the extension may have been applied upstream.`);break}}if(c!==null){let E=Id(c,this.generateLockfile());if(E!==c){let C=CCe(n,n,c,E,void 0,void 0,{maxEditLength:100});if(C){e.report.reportSeparator();for(let S of C.hunks){e.report.reportInfo(null,`@@ -${S.oldStart},${S.oldLines} +${S.newStart},${S.newLines} @@`);for(let P of S.lines)P.startsWith("+")?e.report.reportError(28,Ut(this.configuration,P,Ct.ADDED)):P.startsWith("-")?e.report.reportError(28,Ut(this.configuration,P,Ct.REMOVED)):e.report.reportInfo(null,Ut(this.configuration,P,"grey"))}e.report.reportSeparator()}throw new Yt(28,"The lockfile would have been modified by this install, which is explicitly forbidden.")}}});for(let E of a.values())for(let[,C]of E)for(let S of C)S.userProvided&&S.status==="active"&&ze.telemetry?.reportPackageExtension(Zd(S,Ct.PACKAGE_EXTENSION));await e.report.startTimerPromise("Fetch step",async()=>{await this.fetchEverything(e)});let f=e.immutable?[...new Set(this.configuration.get("immutablePatterns"))].sort():[],p=await Promise.all(f.map(async E=>DQ(E,{cwd:this.cwd})));(typeof e.persistProject>"u"||e.persistProject)&&await this.persist(),await e.report.startTimerPromise("Link step",async()=>{if(e.mode==="update-lockfile"){e.report.reportWarning(73,`Skipped due to ${Ut(this.configuration,"mode=update-lockfile",Ct.CODE)}`);return}await this.linkEverything(e);let E=await Promise.all(f.map(async C=>DQ(C,{cwd:this.cwd})));for(let C=0;C{await this.configuration.triggerHook(E=>E.validateProjectAfterInstall,this,{reportWarning:(E,C)=>{e.report.reportWarning(E,C)},reportError:(E,C)=>{e.report.reportError(E,C),h=!0}})}),!h&&await this.configuration.triggerHook(E=>E.afterAllInstalled,this,e)}generateLockfile(){let e=new Map;for(let[n,c]of this.storedResolutions.entries()){let f=e.get(c);f||e.set(c,f=new Set),f.add(n)}let r={},{cacheKey:s}=Jr.getCacheKey(this.configuration);r.__metadata={version:KR,cacheKey:s};for(let[n,c]of e.entries()){let f=this.originalPackages.get(n);if(!f)continue;let p=[];for(let C of c){let S=this.storedDescriptors.get(C);if(!S)throw new Error("Assertion failed: The descriptor should have been registered");p.push(S)}let h=p.map(C=>ll(C)).sort().join(", "),E=new Ht;E.version=f.linkType==="HARD"?f.version:"0.0.0-use.local",E.languageName=f.languageName,E.dependencies=new Map(f.dependencies),E.peerDependencies=new Map(f.peerDependencies),E.dependenciesMeta=new Map(f.dependenciesMeta),E.peerDependenciesMeta=new Map(f.peerDependenciesMeta),E.bin=new Map(f.bin),r[h]={...E.exportTo({},{compatibilityMode:!1}),linkType:f.linkType.toLowerCase(),resolution:cl(f),checksum:this.storedChecksums.get(f.locatorHash),conditions:f.conditions||void 0}}return`${[`# This file is generated by running "yarn install" inside your project. +`,`# Manual changes might be lost - proceed with caution! +`].join("")} +`+il(r)}async persistLockfile(){let e=K.join(this.cwd,Er.lockfile),r="";try{r=await le.readFilePromise(e,"utf8")}catch{}let s=this.generateLockfile(),a=Id(r,s);a!==r&&(await le.writeFilePromise(e,a),this.lockFileChecksum=iwe(a),this.lockfileNeedsRefresh=!1)}async persistInstallStateFile(){let e=[];for(let c of Object.values(bG))e.push(...c);let r=(0,YR.default)(this,e),s=PG.default.serialize(r),a=us(s);if(this.installStateChecksum===a)return;let n=this.configuration.get("installStatePath");await le.mkdirPromise(K.dirname(n),{recursive:!0}),await le.writeFilePromise(n,await Gwt(s)),this.installStateChecksum=a}async restoreInstallState({restoreLinkersCustomData:e=!0,restoreResolutions:r=!0,restoreBuildState:s=!0}={}){let a=this.configuration.get("installStatePath"),n;try{let c=await Wwt(await le.readFilePromise(a));n=PG.default.deserialize(c),this.installStateChecksum=us(c)}catch{r&&await this.applyLightResolution();return}e&&typeof n.linkersCustomData<"u"&&(this.linkersCustomData=n.linkersCustomData),s&&Object.assign(this,(0,YR.default)(n,bG.restoreBuildState)),r&&(n.lockFileChecksum===this.lockFileChecksum?Object.assign(this,(0,YR.default)(n,bG.restoreResolutions)):await this.applyLightResolution())}async applyLightResolution(){await this.resolveEverything({lockfileOnly:!0,report:new Wi}),await this.persistInstallStateFile()}async persist(){let e=(0,VR.default)(4);await Promise.all([this.persistLockfile(),...this.workspaces.map(r=>e(()=>r.persistManifest()))])}async cacheCleanup({cache:e,report:r}){if(this.configuration.get("enableGlobalCache"))return null;let s=new Set([".gitignore"]);if(!hH(e.cwd,this.cwd)||!await le.existsPromise(e.cwd))return null;let a=[];for(let c of await le.readdirPromise(e.cwd)){if(s.has(c))continue;let f=K.resolve(e.cwd,c);e.markedFiles.has(f)||(e.immutable?r.reportError(56,`${Ut(this.configuration,K.basename(f),"magenta")} appears to be unused and would be marked for deletion, but the cache is immutable`):a.push(le.lstatPromise(f).then(async p=>(await le.removePromise(f),p.size))))}if(a.length===0)return null;let n=await Promise.all(a);return{count:a.length,size:n.reduce((c,f)=>c+f,0)}}}});function Jwt(t){let s=Math.floor(t.timeNow/864e5),a=t.updateInterval*864e5,n=t.state.lastUpdate??t.timeNow+a+Math.floor(a*t.randomInitialInterval),c=n+a,f=t.state.lastTips??s*864e5,p=f+864e5+8*36e5-t.timeZone,h=c<=t.timeNow,E=p<=t.timeNow,C=null;return(h||E||!t.state.lastUpdate||!t.state.lastTips)&&(C={},C.lastUpdate=h?t.timeNow:n,C.lastTips=f,C.blocks=h?{}:t.state.blocks,C.displayedTips=t.state.displayedTips),{nextState:C,triggerUpdate:h,triggerTips:E,nextTips:E?s*864e5:f}}var eC,awe=It(()=>{bt();Rv();I0();pT();kc();Np();eC=class{constructor(e,r){this.values=new Map;this.hits=new Map;this.enumerators=new Map;this.nextTips=0;this.displayedTips=[];this.shouldCommitTips=!1;this.configuration=e;let s=this.getRegistryPath();this.isNew=!le.existsSync(s),this.shouldShowTips=!1,this.sendReport(r),this.startBuffer()}commitTips(){this.shouldShowTips&&(this.shouldCommitTips=!0)}selectTip(e){let r=new Set(this.displayedTips),s=f=>f&&un?eA(un,f):!1,a=e.map((f,p)=>p).filter(f=>e[f]&&s(e[f]?.selector));if(a.length===0)return null;let n=a.filter(f=>!r.has(f));if(n.length===0){let f=Math.floor(a.length*.2);this.displayedTips=f>0?this.displayedTips.slice(-f):[],n=a.filter(p=>!r.has(p))}let c=n[Math.floor(Math.random()*n.length)];return this.displayedTips.push(c),this.commitTips(),e[c]}reportVersion(e){this.reportValue("version",e.replace(/-git\..*/,"-git"))}reportCommandName(e){this.reportValue("commandName",e||"")}reportPluginName(e){this.reportValue("pluginName",e)}reportProject(e){this.reportEnumerator("projectCount",e)}reportInstall(e){this.reportHit("installCount",e)}reportPackageExtension(e){this.reportValue("packageExtension",e)}reportWorkspaceCount(e){this.reportValue("workspaceCount",String(e))}reportDependencyCount(e){this.reportValue("dependencyCount",String(e))}reportValue(e,r){xp(this.values,e).add(r)}reportEnumerator(e,r){xp(this.enumerators,e).add(us(r))}reportHit(e,r="*"){let s=A3(this.hits,e),a=Vl(s,r,()=>0);s.set(r,a+1)}getRegistryPath(){let e=this.configuration.get("globalFolder");return K.join(e,"telemetry.json")}sendReport(e){let r=this.getRegistryPath(),s;try{s=le.readJsonSync(r)}catch{s={}}let{nextState:a,triggerUpdate:n,triggerTips:c,nextTips:f}=Jwt({state:s,timeNow:Date.now(),timeZone:new Date().getTimezoneOffset()*60*1e3,randomInitialInterval:Math.random(),updateInterval:this.configuration.get("telemetryInterval")});if(this.nextTips=f,this.displayedTips=s.displayedTips??[],a!==null)try{le.mkdirSync(K.dirname(r),{recursive:!0}),le.writeJsonSync(r,a)}catch{return!1}if(c&&this.configuration.get("enableTips")&&(this.shouldShowTips=!0),n){let p=s.blocks??{};if(Object.keys(p).length===0){let h=`https://browser-http-intake.logs.datadoghq.eu/v1/input/${e}?ddsource=yarn`,E=C=>Qj(h,C,{configuration:this.configuration}).catch(()=>{});for(let[C,S]of Object.entries(s.blocks??{})){if(Object.keys(S).length===0)continue;let P=S;P.userId=C,P.reportType="primary";for(let N of Object.keys(P.enumerators??{}))P.enumerators[N]=P.enumerators[N].length;E(P);let I=new Map,R=20;for(let[N,U]of Object.entries(P.values))U.length>0&&I.set(N,U.slice(0,R));for(;I.size>0;){let N={};N.userId=C,N.reportType="secondary",N.metrics={};for(let[U,W]of I)N.metrics[U]=W.shift(),W.length===0&&I.delete(U);E(N)}}}}return!0}applyChanges(){let e=this.getRegistryPath(),r;try{r=le.readJsonSync(e)}catch{r={}}let s=this.configuration.get("telemetryUserId")??"*",a=r.blocks=r.blocks??{},n=a[s]=a[s]??{};for(let c of this.hits.keys()){let f=n.hits=n.hits??{},p=f[c]=f[c]??{};for(let[h,E]of this.hits.get(c))p[h]=(p[h]??0)+E}for(let c of["values","enumerators"])for(let f of this[c].keys()){let p=n[c]=n[c]??{};p[f]=[...new Set([...p[f]??[],...this[c].get(f)??[]])]}this.shouldCommitTips&&(r.lastTips=this.nextTips,r.displayedTips=this.displayedTips),le.mkdirSync(K.dirname(e),{recursive:!0}),le.writeJsonSync(e,r)}startBuffer(){process.on("exit",()=>{try{this.applyChanges()}catch{}})}}});var nS={};Vt(nS,{BuildDirectiveType:()=>HR,CACHE_CHECKPOINT:()=>lG,CACHE_VERSION:()=>UR,Cache:()=>Jr,Configuration:()=>ze,DEFAULT_RC_FILENAME:()=>Mj,FormatType:()=>Lde,InstallMode:()=>ec,LEGACY_PLUGINS:()=>Ev,LOCKFILE_VERSION:()=>KR,LegacyMigrationResolver:()=>zI,LightReport:()=>uA,LinkType:()=>VE,LockfileResolver:()=>ZI,Manifest:()=>Ht,MessageName:()=>Dr,MultiFetcher:()=>lI,PackageExtensionStatus:()=>d3,PackageExtensionType:()=>g3,PeerWarningType:()=>JR,Project:()=>Tt,Report:()=>ho,ReportError:()=>Yt,SettingsType:()=>Iv,StreamReport:()=>Ot,TAG_REGEXP:()=>Hp,TelemetryManager:()=>eC,ThrowReport:()=>Wi,VirtualFetcher:()=>cI,WindowsLinkType:()=>IT,Workspace:()=>$I,WorkspaceFetcher:()=>uI,WorkspaceResolver:()=>yi,YarnVersion:()=>un,execUtils:()=>Gr,folderUtils:()=>OQ,formatUtils:()=>he,hashUtils:()=>Nn,httpUtils:()=>An,miscUtils:()=>je,nodeUtils:()=>As,parseMessageName:()=>rk,reportOptionDeprecations:()=>DI,scriptUtils:()=>In,semverUtils:()=>Or,stringifyMessageName:()=>Vf,structUtils:()=>q,tgzUtils:()=>hs,treeUtils:()=>ks});var Ve=It(()=>{dT();LQ();Qc();I0();pT();kc();gT();w6();Np();Yo();oCe();pCe();cG();Cv();Cv();dCe();uG();mCe();fG();oI();nk();nH();owe();Fc();Fv();awe();SG();sH();oH();tm();DG();Rv();BAe()});var pwe=L((hnr,sS)=>{"use strict";var Zwt=process.env.TERM_PROGRAM==="Hyper",Xwt=process.platform==="win32",uwe=process.platform==="linux",QG={ballotDisabled:"\u2612",ballotOff:"\u2610",ballotOn:"\u2611",bullet:"\u2022",bulletWhite:"\u25E6",fullBlock:"\u2588",heart:"\u2764",identicalTo:"\u2261",line:"\u2500",mark:"\u203B",middot:"\xB7",minus:"\uFF0D",multiplication:"\xD7",obelus:"\xF7",pencilDownRight:"\u270E",pencilRight:"\u270F",pencilUpRight:"\u2710",percent:"%",pilcrow2:"\u2761",pilcrow:"\xB6",plusMinus:"\xB1",section:"\xA7",starsOff:"\u2606",starsOn:"\u2605",upDownArrow:"\u2195"},fwe=Object.assign({},QG,{check:"\u221A",cross:"\xD7",ellipsisLarge:"...",ellipsis:"...",info:"i",question:"?",questionSmall:"?",pointer:">",pointerSmall:"\xBB",radioOff:"( )",radioOn:"(*)",warning:"\u203C"}),Awe=Object.assign({},QG,{ballotCross:"\u2718",check:"\u2714",cross:"\u2716",ellipsisLarge:"\u22EF",ellipsis:"\u2026",info:"\u2139",question:"?",questionFull:"\uFF1F",questionSmall:"\uFE56",pointer:uwe?"\u25B8":"\u276F",pointerSmall:uwe?"\u2023":"\u203A",radioOff:"\u25EF",radioOn:"\u25C9",warning:"\u26A0"});sS.exports=Xwt&&!Zwt?fwe:Awe;Reflect.defineProperty(sS.exports,"common",{enumerable:!1,value:QG});Reflect.defineProperty(sS.exports,"windows",{enumerable:!1,value:fwe});Reflect.defineProperty(sS.exports,"other",{enumerable:!1,value:Awe})});var Ju=L((gnr,TG)=>{"use strict";var $wt=t=>t!==null&&typeof t=="object"&&!Array.isArray(t),e1t=/[\u001b\u009b][[\]#;?()]*(?:(?:(?:[^\W_]*;?[^\W_]*)\u0007)|(?:(?:[0-9]{1,4}(;[0-9]{0,4})*)?[~0-9=<>cf-nqrtyA-PRZ]))/g,hwe=()=>{let t={enabled:!0,visible:!0,styles:{},keys:{}};"FORCE_COLOR"in process.env&&(t.enabled=process.env.FORCE_COLOR!=="0");let e=n=>{let c=n.open=`\x1B[${n.codes[0]}m`,f=n.close=`\x1B[${n.codes[1]}m`,p=n.regex=new RegExp(`\\u001b\\[${n.codes[1]}m`,"g");return n.wrap=(h,E)=>{h.includes(f)&&(h=h.replace(p,f+c));let C=c+h+f;return E?C.replace(/\r*\n/g,`${f}$&${c}`):C},n},r=(n,c,f)=>typeof n=="function"?n(c):n.wrap(c,f),s=(n,c)=>{if(n===""||n==null)return"";if(t.enabled===!1)return n;if(t.visible===!1)return"";let f=""+n,p=f.includes(` +`),h=c.length;for(h>0&&c.includes("unstyle")&&(c=[...new Set(["unstyle",...c])].reverse());h-- >0;)f=r(t.styles[c[h]],f,p);return f},a=(n,c,f)=>{t.styles[n]=e({name:n,codes:c}),(t.keys[f]||(t.keys[f]=[])).push(n),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(h){t.alias(n,h)},get(){let h=E=>s(E,h.stack);return Reflect.setPrototypeOf(h,t),h.stack=this.stack?this.stack.concat(n):[n],h}})};return a("reset",[0,0],"modifier"),a("bold",[1,22],"modifier"),a("dim",[2,22],"modifier"),a("italic",[3,23],"modifier"),a("underline",[4,24],"modifier"),a("inverse",[7,27],"modifier"),a("hidden",[8,28],"modifier"),a("strikethrough",[9,29],"modifier"),a("black",[30,39],"color"),a("red",[31,39],"color"),a("green",[32,39],"color"),a("yellow",[33,39],"color"),a("blue",[34,39],"color"),a("magenta",[35,39],"color"),a("cyan",[36,39],"color"),a("white",[37,39],"color"),a("gray",[90,39],"color"),a("grey",[90,39],"color"),a("bgBlack",[40,49],"bg"),a("bgRed",[41,49],"bg"),a("bgGreen",[42,49],"bg"),a("bgYellow",[43,49],"bg"),a("bgBlue",[44,49],"bg"),a("bgMagenta",[45,49],"bg"),a("bgCyan",[46,49],"bg"),a("bgWhite",[47,49],"bg"),a("blackBright",[90,39],"bright"),a("redBright",[91,39],"bright"),a("greenBright",[92,39],"bright"),a("yellowBright",[93,39],"bright"),a("blueBright",[94,39],"bright"),a("magentaBright",[95,39],"bright"),a("cyanBright",[96,39],"bright"),a("whiteBright",[97,39],"bright"),a("bgBlackBright",[100,49],"bgBright"),a("bgRedBright",[101,49],"bgBright"),a("bgGreenBright",[102,49],"bgBright"),a("bgYellowBright",[103,49],"bgBright"),a("bgBlueBright",[104,49],"bgBright"),a("bgMagentaBright",[105,49],"bgBright"),a("bgCyanBright",[106,49],"bgBright"),a("bgWhiteBright",[107,49],"bgBright"),t.ansiRegex=e1t,t.hasColor=t.hasAnsi=n=>(t.ansiRegex.lastIndex=0,typeof n=="string"&&n!==""&&t.ansiRegex.test(n)),t.alias=(n,c)=>{let f=typeof c=="string"?t[c]:c;if(typeof f!="function")throw new TypeError("Expected alias to be the name of an existing color (string) or a function");f.stack||(Reflect.defineProperty(f,"name",{value:n}),t.styles[n]=f,f.stack=[n]),Reflect.defineProperty(t,n,{configurable:!0,enumerable:!0,set(p){t.alias(n,p)},get(){let p=h=>s(h,p.stack);return Reflect.setPrototypeOf(p,t),p.stack=this.stack?this.stack.concat(f.stack):f.stack,p}})},t.theme=n=>{if(!$wt(n))throw new TypeError("Expected theme to be an object");for(let c of Object.keys(n))t.alias(c,n[c]);return t},t.alias("unstyle",n=>typeof n=="string"&&n!==""?(t.ansiRegex.lastIndex=0,n.replace(t.ansiRegex,"")):""),t.alias("noop",n=>n),t.none=t.clear=t.noop,t.stripColor=t.unstyle,t.symbols=pwe(),t.define=a,t};TG.exports=hwe();TG.exports.create=hwe});var $o=L(pn=>{"use strict";var t1t=Object.prototype.toString,Gc=Ju(),gwe=!1,RG=[],dwe={yellow:"blue",cyan:"red",green:"magenta",black:"white",blue:"yellow",red:"cyan",magenta:"green",white:"black"};pn.longest=(t,e)=>t.reduce((r,s)=>Math.max(r,e?s[e].length:s.length),0);pn.hasColor=t=>!!t&&Gc.hasColor(t);var ZR=pn.isObject=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);pn.nativeType=t=>t1t.call(t).slice(8,-1).toLowerCase().replace(/\s/g,"");pn.isAsyncFn=t=>pn.nativeType(t)==="asyncfunction";pn.isPrimitive=t=>t!=null&&typeof t!="object"&&typeof t!="function";pn.resolve=(t,e,...r)=>typeof e=="function"?e.call(t,...r):e;pn.scrollDown=(t=[])=>[...t.slice(1),t[0]];pn.scrollUp=(t=[])=>[t.pop(),...t];pn.reorder=(t=[])=>{let e=t.slice();return e.sort((r,s)=>r.index>s.index?1:r.index{let s=t.length,a=r===s?0:r<0?s-1:r,n=t[e];t[e]=t[a],t[a]=n};pn.width=(t,e=80)=>{let r=t&&t.columns?t.columns:e;return t&&typeof t.getWindowSize=="function"&&(r=t.getWindowSize()[0]),process.platform==="win32"?r-1:r};pn.height=(t,e=20)=>{let r=t&&t.rows?t.rows:e;return t&&typeof t.getWindowSize=="function"&&(r=t.getWindowSize()[1]),r};pn.wordWrap=(t,e={})=>{if(!t)return t;typeof e=="number"&&(e={width:e});let{indent:r="",newline:s=` +`+r,width:a=80}=e,n=(s+r).match(/[^\S\n]/g)||[];a-=n.length;let c=`.{1,${a}}([\\s\\u200B]+|$)|[^\\s\\u200B]+?([\\s\\u200B]+|$)`,f=t.trim(),p=new RegExp(c,"g"),h=f.match(p)||[];return h=h.map(E=>E.replace(/\n$/,"")),e.padEnd&&(h=h.map(E=>E.padEnd(a," "))),e.padStart&&(h=h.map(E=>E.padStart(a," "))),r+h.join(s)};pn.unmute=t=>{let e=t.stack.find(s=>Gc.keys.color.includes(s));return e?Gc[e]:t.stack.find(s=>s.slice(2)==="bg")?Gc[e.slice(2)]:s=>s};pn.pascal=t=>t?t[0].toUpperCase()+t.slice(1):"";pn.inverse=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>Gc.keys.color.includes(s));if(e){let s=Gc["bg"+pn.pascal(e)];return s?s.black:t}let r=t.stack.find(s=>s.slice(0,2)==="bg");return r?Gc[r.slice(2).toLowerCase()]||t:Gc.none};pn.complement=t=>{if(!t||!t.stack)return t;let e=t.stack.find(s=>Gc.keys.color.includes(s)),r=t.stack.find(s=>s.slice(0,2)==="bg");if(e&&!r)return Gc[dwe[e]||e];if(r){let s=r.slice(2).toLowerCase(),a=dwe[s];return a&&Gc["bg"+pn.pascal(a)]||t}return Gc.none};pn.meridiem=t=>{let e=t.getHours(),r=t.getMinutes(),s=e>=12?"pm":"am";e=e%12;let a=e===0?12:e,n=r<10?"0"+r:r;return a+":"+n+" "+s};pn.set=(t={},e="",r)=>e.split(".").reduce((s,a,n,c)=>{let f=c.length-1>n?s[a]||{}:r;return!pn.isObject(f)&&n{let s=t[e]==null?e.split(".").reduce((a,n)=>a&&a[n],t):t[e];return s??r};pn.mixin=(t,e)=>{if(!ZR(t))return e;if(!ZR(e))return t;for(let r of Object.keys(e)){let s=Object.getOwnPropertyDescriptor(e,r);if(s.hasOwnProperty("value"))if(t.hasOwnProperty(r)&&ZR(s.value)){let a=Object.getOwnPropertyDescriptor(t,r);ZR(a.value)?t[r]=pn.merge({},t[r],e[r]):Reflect.defineProperty(t,r,s)}else Reflect.defineProperty(t,r,s);else Reflect.defineProperty(t,r,s)}return t};pn.merge=(...t)=>{let e={};for(let r of t)pn.mixin(e,r);return e};pn.mixinEmitter=(t,e)=>{let r=e.constructor.prototype;for(let s of Object.keys(r)){let a=r[s];typeof a=="function"?pn.define(t,s,a.bind(e)):pn.define(t,s,a)}};pn.onExit=t=>{let e=(r,s)=>{gwe||(gwe=!0,RG.forEach(a=>a()),r===!0&&process.exit(128+s))};RG.length===0&&(process.once("SIGTERM",e.bind(null,!0,15)),process.once("SIGINT",e.bind(null,!0,2)),process.once("exit",e)),RG.push(t)};pn.define=(t,e,r)=>{Reflect.defineProperty(t,e,{value:r})};pn.defineExport=(t,e,r)=>{let s;Reflect.defineProperty(t,e,{enumerable:!0,configurable:!0,set(a){s=a},get(){return s?s():r()}})}});var mwe=L(iC=>{"use strict";iC.ctrl={a:"first",b:"backward",c:"cancel",d:"deleteForward",e:"last",f:"forward",g:"reset",i:"tab",k:"cutForward",l:"reset",n:"newItem",m:"cancel",j:"submit",p:"search",r:"remove",s:"save",u:"undo",w:"cutLeft",x:"toggleCursor",v:"paste"};iC.shift={up:"shiftUp",down:"shiftDown",left:"shiftLeft",right:"shiftRight",tab:"prev"};iC.fn={up:"pageUp",down:"pageDown",left:"pageLeft",right:"pageRight",delete:"deleteForward"};iC.option={b:"backward",f:"forward",d:"cutRight",left:"cutLeft",up:"altUp",down:"altDown"};iC.keys={pageup:"pageUp",pagedown:"pageDown",home:"home",end:"end",cancel:"cancel",delete:"deleteForward",backspace:"delete",down:"down",enter:"submit",escape:"cancel",left:"left",space:"space",number:"number",return:"submit",right:"right",tab:"next",up:"up"}});var Iwe=L((ynr,Ewe)=>{"use strict";var ywe=ye("readline"),r1t=mwe(),n1t=/^(?:\x1b)([a-zA-Z0-9])$/,i1t=/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/,s1t={OP:"f1",OQ:"f2",OR:"f3",OS:"f4","[11~":"f1","[12~":"f2","[13~":"f3","[14~":"f4","[[A":"f1","[[B":"f2","[[C":"f3","[[D":"f4","[[E":"f5","[15~":"f5","[17~":"f6","[18~":"f7","[19~":"f8","[20~":"f9","[21~":"f10","[23~":"f11","[24~":"f12","[A":"up","[B":"down","[C":"right","[D":"left","[E":"clear","[F":"end","[H":"home",OA:"up",OB:"down",OC:"right",OD:"left",OE:"clear",OF:"end",OH:"home","[1~":"home","[2~":"insert","[3~":"delete","[4~":"end","[5~":"pageup","[6~":"pagedown","[[5~":"pageup","[[6~":"pagedown","[7~":"home","[8~":"end","[a":"up","[b":"down","[c":"right","[d":"left","[e":"clear","[2$":"insert","[3$":"delete","[5$":"pageup","[6$":"pagedown","[7$":"home","[8$":"end",Oa:"up",Ob:"down",Oc:"right",Od:"left",Oe:"clear","[2^":"insert","[3^":"delete","[5^":"pageup","[6^":"pagedown","[7^":"home","[8^":"end","[Z":"tab"};function o1t(t){return["[a","[b","[c","[d","[e","[2$","[3$","[5$","[6$","[7$","[8$","[Z"].includes(t)}function a1t(t){return["Oa","Ob","Oc","Od","Oe","[2^","[3^","[5^","[6^","[7^","[8^"].includes(t)}var XR=(t="",e={})=>{let r,s={name:e.name,ctrl:!1,meta:!1,shift:!1,option:!1,sequence:t,raw:t,...e};if(Buffer.isBuffer(t)?t[0]>127&&t[1]===void 0?(t[0]-=128,t="\x1B"+String(t)):t=String(t):t!==void 0&&typeof t!="string"?t=String(t):t||(t=s.sequence||""),s.sequence=s.sequence||t||s.name,t==="\r")s.raw=void 0,s.name="return";else if(t===` +`)s.name="enter";else if(t===" ")s.name="tab";else if(t==="\b"||t==="\x7F"||t==="\x1B\x7F"||t==="\x1B\b")s.name="backspace",s.meta=t.charAt(0)==="\x1B";else if(t==="\x1B"||t==="\x1B\x1B")s.name="escape",s.meta=t.length===2;else if(t===" "||t==="\x1B ")s.name="space",s.meta=t.length===2;else if(t<="")s.name=String.fromCharCode(t.charCodeAt(0)+97-1),s.ctrl=!0;else if(t.length===1&&t>="0"&&t<="9")s.name="number";else if(t.length===1&&t>="a"&&t<="z")s.name=t;else if(t.length===1&&t>="A"&&t<="Z")s.name=t.toLowerCase(),s.shift=!0;else if(r=n1t.exec(t))s.meta=!0,s.shift=/^[A-Z]$/.test(r[1]);else if(r=i1t.exec(t)){let a=[...t];a[0]==="\x1B"&&a[1]==="\x1B"&&(s.option=!0);let n=[r[1],r[2],r[4],r[6]].filter(Boolean).join(""),c=(r[3]||r[5]||1)-1;s.ctrl=!!(c&4),s.meta=!!(c&10),s.shift=!!(c&1),s.code=n,s.name=s1t[n],s.shift=o1t(n)||s.shift,s.ctrl=a1t(n)||s.ctrl}return s};XR.listen=(t={},e)=>{let{stdin:r}=t;if(!r||r!==process.stdin&&!r.isTTY)throw new Error("Invalid stream passed");let s=ywe.createInterface({terminal:!0,input:r});ywe.emitKeypressEvents(r,s);let a=(f,p)=>e(f,XR(f,p),s),n=r.isRaw;return r.isTTY&&r.setRawMode(!0),r.on("keypress",a),s.resume(),()=>{r.isTTY&&r.setRawMode(n),r.removeListener("keypress",a),s.pause(),s.close()}};XR.action=(t,e,r)=>{let s={...r1t,...r};return e.ctrl?(e.action=s.ctrl[e.name],e):e.option&&s.option?(e.action=s.option[e.name],e):e.shift?(e.action=s.shift[e.name],e):(e.action=s.keys[e.name],e)};Ewe.exports=XR});var wwe=L((Enr,Cwe)=>{"use strict";Cwe.exports=t=>{t.timers=t.timers||{};let e=t.options.timers;if(e)for(let r of Object.keys(e)){let s=e[r];typeof s=="number"&&(s={interval:s}),l1t(t,r,s)}};function l1t(t,e,r={}){let s=t.timers[e]={name:e,start:Date.now(),ms:0,tick:0},a=r.interval||120;s.frames=r.frames||[],s.loading=!0;let n=setInterval(()=>{s.ms=Date.now()-s.start,s.tick++,t.render()},a);return s.stop=()=>{s.loading=!1,clearInterval(n)},Reflect.defineProperty(s,"interval",{value:n}),t.once("close",()=>s.stop()),s.stop}});var vwe=L((Inr,Bwe)=>{"use strict";var{define:c1t,width:u1t}=$o(),FG=class{constructor(e){let r=e.options;c1t(this,"_prompt",e),this.type=e.type,this.name=e.name,this.message="",this.header="",this.footer="",this.error="",this.hint="",this.input="",this.cursor=0,this.index=0,this.lines=0,this.tick=0,this.prompt="",this.buffer="",this.width=u1t(r.stdout||process.stdout),Object.assign(this,r),this.name=this.name||this.message,this.message=this.message||this.name,this.symbols=e.symbols,this.styles=e.styles,this.required=new Set,this.cancelled=!1,this.submitted=!1}clone(){let e={...this};return e.status=this.status,e.buffer=Buffer.from(e.buffer),delete e.clone,e}set color(e){this._color=e}get color(){let e=this.prompt.styles;if(this.cancelled)return e.cancelled;if(this.submitted)return e.submitted;let r=this._color||e[this.status];return typeof r=="function"?r:e.pending}set loading(e){this._loading=e}get loading(){return typeof this._loading=="boolean"?this._loading:this.loadingChoices?"choices":!1}get status(){return this.cancelled?"cancelled":this.submitted?"submitted":"pending"}};Bwe.exports=FG});var Dwe=L((Cnr,Swe)=>{"use strict";var NG=$o(),mo=Ju(),OG={default:mo.noop,noop:mo.noop,set inverse(t){this._inverse=t},get inverse(){return this._inverse||NG.inverse(this.primary)},set complement(t){this._complement=t},get complement(){return this._complement||NG.complement(this.primary)},primary:mo.cyan,success:mo.green,danger:mo.magenta,strong:mo.bold,warning:mo.yellow,muted:mo.dim,disabled:mo.gray,dark:mo.dim.gray,underline:mo.underline,set info(t){this._info=t},get info(){return this._info||this.primary},set em(t){this._em=t},get em(){return this._em||this.primary.underline},set heading(t){this._heading=t},get heading(){return this._heading||this.muted.underline},set pending(t){this._pending=t},get pending(){return this._pending||this.primary},set submitted(t){this._submitted=t},get submitted(){return this._submitted||this.success},set cancelled(t){this._cancelled=t},get cancelled(){return this._cancelled||this.danger},set typing(t){this._typing=t},get typing(){return this._typing||this.dim},set placeholder(t){this._placeholder=t},get placeholder(){return this._placeholder||this.primary.dim},set highlight(t){this._highlight=t},get highlight(){return this._highlight||this.inverse}};OG.merge=(t={})=>{t.styles&&typeof t.styles.enabled=="boolean"&&(mo.enabled=t.styles.enabled),t.styles&&typeof t.styles.visible=="boolean"&&(mo.visible=t.styles.visible);let e=NG.merge({},OG,t.styles);delete e.merge;for(let r of Object.keys(mo))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>mo[r]});for(let r of Object.keys(mo.styles))e.hasOwnProperty(r)||Reflect.defineProperty(e,r,{get:()=>mo[r]});return e};Swe.exports=OG});var Pwe=L((wnr,bwe)=>{"use strict";var LG=process.platform==="win32",$p=Ju(),f1t=$o(),MG={...$p.symbols,upDownDoubleArrow:"\u21D5",upDownDoubleArrow2:"\u2B0D",upDownArrow:"\u2195",asterisk:"*",asterism:"\u2042",bulletWhite:"\u25E6",electricArrow:"\u2301",ellipsisLarge:"\u22EF",ellipsisSmall:"\u2026",fullBlock:"\u2588",identicalTo:"\u2261",indicator:$p.symbols.check,leftAngle:"\u2039",mark:"\u203B",minus:"\u2212",multiplication:"\xD7",obelus:"\xF7",percent:"%",pilcrow:"\xB6",pilcrow2:"\u2761",pencilUpRight:"\u2710",pencilDownRight:"\u270E",pencilRight:"\u270F",plus:"+",plusMinus:"\xB1",pointRight:"\u261E",rightAngle:"\u203A",section:"\xA7",hexagon:{off:"\u2B21",on:"\u2B22",disabled:"\u2B22"},ballot:{on:"\u2611",off:"\u2610",disabled:"\u2612"},stars:{on:"\u2605",off:"\u2606",disabled:"\u2606"},folder:{on:"\u25BC",off:"\u25B6",disabled:"\u25B6"},prefix:{pending:$p.symbols.question,submitted:$p.symbols.check,cancelled:$p.symbols.cross},separator:{pending:$p.symbols.pointerSmall,submitted:$p.symbols.middot,cancelled:$p.symbols.middot},radio:{off:LG?"( )":"\u25EF",on:LG?"(*)":"\u25C9",disabled:LG?"(|)":"\u24BE"},numbers:["\u24EA","\u2460","\u2461","\u2462","\u2463","\u2464","\u2465","\u2466","\u2467","\u2468","\u2469","\u246A","\u246B","\u246C","\u246D","\u246E","\u246F","\u2470","\u2471","\u2472","\u2473","\u3251","\u3252","\u3253","\u3254","\u3255","\u3256","\u3257","\u3258","\u3259","\u325A","\u325B","\u325C","\u325D","\u325E","\u325F","\u32B1","\u32B2","\u32B3","\u32B4","\u32B5","\u32B6","\u32B7","\u32B8","\u32B9","\u32BA","\u32BB","\u32BC","\u32BD","\u32BE","\u32BF"]};MG.merge=t=>{let e=f1t.merge({},$p.symbols,MG,t.symbols);return delete e.merge,e};bwe.exports=MG});var kwe=L((Bnr,xwe)=>{"use strict";var A1t=Dwe(),p1t=Pwe(),h1t=$o();xwe.exports=t=>{t.options=h1t.merge({},t.options.theme,t.options),t.symbols=p1t.merge(t.options),t.styles=A1t.merge(t.options)}});var Nwe=L((Rwe,Fwe)=>{"use strict";var Qwe=process.env.TERM_PROGRAM==="Apple_Terminal",g1t=Ju(),_G=$o(),zu=Fwe.exports=Rwe,Mi="\x1B[",Twe="\x07",UG=!1,q0=zu.code={bell:Twe,beep:Twe,beginning:`${Mi}G`,down:`${Mi}J`,esc:Mi,getPosition:`${Mi}6n`,hide:`${Mi}?25l`,line:`${Mi}2K`,lineEnd:`${Mi}K`,lineStart:`${Mi}1K`,restorePosition:Mi+(Qwe?"8":"u"),savePosition:Mi+(Qwe?"7":"s"),screen:`${Mi}2J`,show:`${Mi}?25h`,up:`${Mi}1J`},Bm=zu.cursor={get hidden(){return UG},hide(){return UG=!0,q0.hide},show(){return UG=!1,q0.show},forward:(t=1)=>`${Mi}${t}C`,backward:(t=1)=>`${Mi}${t}D`,nextLine:(t=1)=>`${Mi}E`.repeat(t),prevLine:(t=1)=>`${Mi}F`.repeat(t),up:(t=1)=>t?`${Mi}${t}A`:"",down:(t=1)=>t?`${Mi}${t}B`:"",right:(t=1)=>t?`${Mi}${t}C`:"",left:(t=1)=>t?`${Mi}${t}D`:"",to(t,e){return e?`${Mi}${e+1};${t+1}H`:`${Mi}${t+1}G`},move(t=0,e=0){let r="";return r+=t<0?Bm.left(-t):t>0?Bm.right(t):"",r+=e<0?Bm.up(-e):e>0?Bm.down(e):"",r},restore(t={}){let{after:e,cursor:r,initial:s,input:a,prompt:n,size:c,value:f}=t;if(s=_G.isPrimitive(s)?String(s):"",a=_G.isPrimitive(a)?String(a):"",f=_G.isPrimitive(f)?String(f):"",c){let p=zu.cursor.up(c)+zu.cursor.to(n.length),h=a.length-r;return h>0&&(p+=zu.cursor.left(h)),p}if(f||e){let p=!a&&s?-s.length:-a.length+r;return e&&(p-=e.length),a===""&&s&&!n.includes(s)&&(p+=s.length),zu.cursor.move(p)}}},HG=zu.erase={screen:q0.screen,up:q0.up,down:q0.down,line:q0.line,lineEnd:q0.lineEnd,lineStart:q0.lineStart,lines(t){let e="";for(let r=0;r{if(!e)return HG.line+Bm.to(0);let r=n=>[...g1t.unstyle(n)].length,s=t.split(/\r?\n/),a=0;for(let n of s)a+=1+Math.floor(Math.max(r(n)-1,0)/e);return(HG.line+Bm.prevLine()).repeat(a-1)+HG.line+Bm.to(0)}});var sC=L((vnr,Lwe)=>{"use strict";var d1t=ye("events"),Owe=Ju(),jG=Iwe(),m1t=wwe(),y1t=vwe(),E1t=kwe(),hl=$o(),vm=Nwe(),qG=class t extends d1t{constructor(e={}){super(),this.name=e.name,this.type=e.type,this.options=e,E1t(this),m1t(this),this.state=new y1t(this),this.initial=[e.initial,e.default].find(r=>r!=null),this.stdout=e.stdout||process.stdout,this.stdin=e.stdin||process.stdin,this.scale=e.scale||1,this.term=this.options.term||process.env.TERM_PROGRAM,this.margin=C1t(this.options.margin),this.setMaxListeners(0),I1t(this)}async keypress(e,r={}){this.keypressed=!0;let s=jG.action(e,jG(e,r),this.options.actions);this.state.keypress=s,this.emit("keypress",e,s),this.emit("state",this.state.clone());let a=this.options[s.action]||this[s.action]||this.dispatch;if(typeof a=="function")return await a.call(this,e,s);this.alert()}alert(){delete this.state.alert,this.options.show===!1?this.emit("alert"):this.stdout.write(vm.code.beep)}cursorHide(){this.stdout.write(vm.cursor.hide()),hl.onExit(()=>this.cursorShow())}cursorShow(){this.stdout.write(vm.cursor.show())}write(e){e&&(this.stdout&&this.state.show!==!1&&this.stdout.write(e),this.state.buffer+=e)}clear(e=0){let r=this.state.buffer;this.state.buffer="",!(!r&&!e||this.options.show===!1)&&this.stdout.write(vm.cursor.down(e)+vm.clear(r,this.width))}restore(){if(this.state.closed||this.options.show===!1)return;let{prompt:e,after:r,rest:s}=this.sections(),{cursor:a,initial:n="",input:c="",value:f=""}=this,p=this.state.size=s.length,h={after:r,cursor:a,initial:n,input:c,prompt:e,size:p,value:f},E=vm.cursor.restore(h);E&&this.stdout.write(E)}sections(){let{buffer:e,input:r,prompt:s}=this.state;s=Owe.unstyle(s);let a=Owe.unstyle(e),n=a.indexOf(s),c=a.slice(0,n),p=a.slice(n).split(` +`),h=p[0],E=p[p.length-1],S=(s+(r?" "+r:"")).length,P=Se.call(this,this.value),this.result=()=>s.call(this,this.value),typeof r.initial=="function"&&(this.initial=await r.initial.call(this,this)),typeof r.onRun=="function"&&await r.onRun.call(this,this),typeof r.onSubmit=="function"){let a=r.onSubmit.bind(this),n=this.submit.bind(this);delete this.options.onSubmit,this.submit=async()=>(await a(this.name,this.value,this),n())}await this.start(),await this.render()}render(){throw new Error("expected prompt to have a custom render method")}run(){return new Promise(async(e,r)=>{if(this.once("submit",e),this.once("cancel",r),await this.skip())return this.render=()=>{},this.submit();await this.initialize(),this.emit("run")})}async element(e,r,s){let{options:a,state:n,symbols:c,timers:f}=this,p=f&&f[e];n.timer=p;let h=a[e]||n[e]||c[e],E=r&&r[e]!=null?r[e]:await h;if(E==="")return E;let C=await this.resolve(E,n,r,s);return!C&&r&&r[e]?this.resolve(h,n,r,s):C}async prefix(){let e=await this.element("prefix")||this.symbols,r=this.timers&&this.timers.prefix,s=this.state;return s.timer=r,hl.isObject(e)&&(e=e[s.status]||e.pending),hl.hasColor(e)?e:(this.styles[s.status]||this.styles.pending)(e)}async message(){let e=await this.element("message");return hl.hasColor(e)?e:this.styles.strong(e)}async separator(){let e=await this.element("separator")||this.symbols,r=this.timers&&this.timers.separator,s=this.state;s.timer=r;let a=e[s.status]||e.pending||s.separator,n=await this.resolve(a,s);return hl.isObject(n)&&(n=n[s.status]||n.pending),hl.hasColor(n)?n:this.styles.muted(n)}async pointer(e,r){let s=await this.element("pointer",e,r);if(typeof s=="string"&&hl.hasColor(s))return s;if(s){let a=this.styles,n=this.index===r,c=n?a.primary:h=>h,f=await this.resolve(s[n?"on":"off"]||s,this.state),p=hl.hasColor(f)?f:c(f);return n?p:" ".repeat(f.length)}}async indicator(e,r){let s=await this.element("indicator",e,r);if(typeof s=="string"&&hl.hasColor(s))return s;if(s){let a=this.styles,n=e.enabled===!0,c=n?a.success:a.dark,f=s[n?"on":"off"]||s;return hl.hasColor(f)?f:c(f)}return""}body(){return null}footer(){if(this.state.status==="pending")return this.element("footer")}header(){if(this.state.status==="pending")return this.element("header")}async hint(){if(this.state.status==="pending"&&!this.isValue(this.state.input)){let e=await this.element("hint");return hl.hasColor(e)?e:this.styles.muted(e)}}error(e){return this.state.submitted?"":e||this.state.error}format(e){return e}result(e){return e}validate(e){return this.options.required===!0?this.isValue(e):!0}isValue(e){return e!=null&&e!==""}resolve(e,...r){return hl.resolve(this,e,...r)}get base(){return t.prototype}get style(){return this.styles[this.state.status]}get height(){return this.options.rows||hl.height(this.stdout,25)}get width(){return this.options.columns||hl.width(this.stdout,80)}get size(){return{width:this.width,height:this.height}}set cursor(e){this.state.cursor=e}get cursor(){return this.state.cursor}set input(e){this.state.input=e}get input(){return this.state.input}set value(e){this.state.value=e}get value(){let{input:e,value:r}=this.state,s=[r,e].find(this.isValue.bind(this));return this.isValue(s)?s:this.initial}static get prompt(){return e=>new this(e).run()}};function I1t(t){let e=a=>t[a]===void 0||typeof t[a]=="function",r=["actions","choices","initial","margin","roles","styles","symbols","theme","timers","value"],s=["body","footer","error","header","hint","indicator","message","prefix","separator","skip"];for(let a of Object.keys(t.options)){if(r.includes(a)||/^on[A-Z]/.test(a))continue;let n=t.options[a];typeof n=="function"&&e(a)?s.includes(a)||(t[a]=n.bind(t)):typeof t[a]!="function"&&(t[a]=n)}}function C1t(t){typeof t=="number"&&(t=[t,t,t,t]);let e=[].concat(t||[]),r=a=>a%2===0?` +`:" ",s=[];for(let a=0;a<4;a++){let n=r(a);e[a]?s.push(n.repeat(e[a])):s.push("")}return s}Lwe.exports=qG});var Uwe=L((Snr,_we)=>{"use strict";var w1t=$o(),Mwe={default(t,e){return e},checkbox(t,e){throw new Error("checkbox role is not implemented yet")},editable(t,e){throw new Error("editable role is not implemented yet")},expandable(t,e){throw new Error("expandable role is not implemented yet")},heading(t,e){return e.disabled="",e.indicator=[e.indicator," "].find(r=>r!=null),e.message=e.message||"",e},input(t,e){throw new Error("input role is not implemented yet")},option(t,e){return Mwe.default(t,e)},radio(t,e){throw new Error("radio role is not implemented yet")},separator(t,e){return e.disabled="",e.indicator=[e.indicator," "].find(r=>r!=null),e.message=e.message||t.symbols.line.repeat(5),e},spacer(t,e){return e}};_we.exports=(t,e={})=>{let r=w1t.merge({},Mwe,e.roles);return r[t]||r.default}});var oS=L((Dnr,qwe)=>{"use strict";var B1t=Ju(),v1t=sC(),S1t=Uwe(),$R=$o(),{reorder:GG,scrollUp:D1t,scrollDown:b1t,isObject:Hwe,swap:P1t}=$R,WG=class extends v1t{constructor(e){super(e),this.cursorHide(),this.maxSelected=e.maxSelected||1/0,this.multiple=e.multiple||!1,this.initial=e.initial||0,this.delay=e.delay||0,this.longest=0,this.num=""}async initialize(){typeof this.options.initial=="function"&&(this.initial=await this.options.initial.call(this)),await this.reset(!0),await super.initialize()}async reset(){let{choices:e,initial:r,autofocus:s,suggest:a}=this.options;if(this.state._choices=[],this.state.choices=[],this.choices=await Promise.all(await this.toChoices(e)),this.choices.forEach(n=>n.enabled=!1),typeof a!="function"&&this.selectable.length===0)throw new Error("At least one choice must be selectable");Hwe(r)&&(r=Object.keys(r)),Array.isArray(r)?(s!=null&&(this.index=this.findIndex(s)),r.forEach(n=>this.enable(this.find(n))),await this.render()):(s!=null&&(r=s),typeof r=="string"&&(r=this.findIndex(r)),typeof r=="number"&&r>-1&&(this.index=Math.max(0,Math.min(r,this.choices.length)),this.enable(this.find(this.index)))),this.isDisabled(this.focused)&&await this.down()}async toChoices(e,r){this.state.loadingChoices=!0;let s=[],a=0,n=async(c,f)=>{typeof c=="function"&&(c=await c.call(this)),c instanceof Promise&&(c=await c);for(let p=0;p(this.state.loadingChoices=!1,c))}async toChoice(e,r,s){if(typeof e=="function"&&(e=await e.call(this,this)),e instanceof Promise&&(e=await e),typeof e=="string"&&(e={name:e}),e.normalized)return e;e.normalized=!0;let a=e.value;if(e=S1t(e.role,this.options)(this,e),typeof e.disabled=="string"&&!e.hint&&(e.hint=e.disabled,e.disabled=!0),e.disabled===!0&&e.hint==null&&(e.hint="(disabled)"),e.index!=null)return e;e.name=e.name||e.key||e.title||e.value||e.message,e.message=e.message||e.name||"",e.value=[e.value,e.name].find(this.isValue.bind(this)),e.input="",e.index=r,e.cursor=0,$R.define(e,"parent",s),e.level=s?s.level+1:1,e.indent==null&&(e.indent=s?s.indent+" ":e.indent||""),e.path=s?s.path+"."+e.name:e.name,e.enabled=!!(this.multiple&&!this.isDisabled(e)&&(e.enabled||this.isSelected(e))),this.isDisabled(e)||(this.longest=Math.max(this.longest,B1t.unstyle(e.message).length));let c={...e};return e.reset=(f=c.input,p=c.value)=>{for(let h of Object.keys(c))e[h]=c[h];e.input=f,e.value=p},a==null&&typeof e.initial=="function"&&(e.input=await e.initial.call(this,this.state,e,r)),e}async onChoice(e,r){this.emit("choice",e,r,this),typeof e.onChoice=="function"&&await e.onChoice.call(this,this.state,e,r)}async addChoice(e,r,s){let a=await this.toChoice(e,r,s);return this.choices.push(a),this.index=this.choices.length-1,this.limit=this.choices.length,a}async newItem(e,r,s){let a={name:"New choice name?",editable:!0,newChoice:!0,...e},n=await this.addChoice(a,r,s);return n.updateChoice=()=>{delete n.newChoice,n.name=n.message=n.input,n.input="",n.cursor=0},this.render()}indent(e){return e.indent==null?e.level>1?" ".repeat(e.level-1):"":e.indent}dispatch(e,r){if(this.multiple&&this[r.name])return this[r.name]();this.alert()}focus(e,r){return typeof r!="boolean"&&(r=e.enabled),r&&!e.enabled&&this.selected.length>=this.maxSelected?this.alert():(this.index=e.index,e.enabled=r&&!this.isDisabled(e),e)}space(){return this.multiple?(this.toggle(this.focused),this.render()):this.alert()}a(){if(this.maxSelectedr.enabled);return this.choices.forEach(r=>r.enabled=!e),this.render()}i(){return this.choices.length-this.selected.length>this.maxSelected?this.alert():(this.choices.forEach(e=>e.enabled=!e.enabled),this.render())}g(e=this.focused){return this.choices.some(r=>!!r.parent)?(this.toggle(e.parent&&!e.choices?e.parent:e),this.render()):this.a()}toggle(e,r){if(!e.enabled&&this.selected.length>=this.maxSelected)return this.alert();typeof r!="boolean"&&(r=!e.enabled),e.enabled=r,e.choices&&e.choices.forEach(a=>this.toggle(a,r));let s=e.parent;for(;s;){let a=s.choices.filter(n=>this.isDisabled(n));s.enabled=a.every(n=>n.enabled===!0),s=s.parent}return jwe(this,this.choices),this.emit("toggle",e,this),e}enable(e){return this.selected.length>=this.maxSelected?this.alert():(e.enabled=!this.isDisabled(e),e.choices&&e.choices.forEach(this.enable.bind(this)),e)}disable(e){return e.enabled=!1,e.choices&&e.choices.forEach(this.disable.bind(this)),e}number(e){this.num+=e;let r=s=>{let a=Number(s);if(a>this.choices.length-1)return this.alert();let n=this.focused,c=this.choices.find(f=>a===f.index);if(!c.enabled&&this.selected.length>=this.maxSelected)return this.alert();if(this.visible.indexOf(c)===-1){let f=GG(this.choices),p=f.indexOf(c);if(n.index>p){let h=f.slice(p,p+this.limit),E=f.filter(C=>!h.includes(C));this.choices=h.concat(E)}else{let h=p-this.limit+1;this.choices=f.slice(h).concat(f.slice(0,h))}}return this.index=this.choices.indexOf(c),this.toggle(this.focused),this.render()};return clearTimeout(this.numberTimeout),new Promise(s=>{let a=this.choices.length,n=this.num,c=(f=!1,p)=>{clearTimeout(this.numberTimeout),f&&(p=r(n)),this.num="",s(p)};if(n==="0"||n.length===1&&+(n+"0")>a)return c(!0);if(Number(n)>a)return c(!1,this.alert());this.numberTimeout=setTimeout(()=>c(!0),this.delay)})}home(){return this.choices=GG(this.choices),this.index=0,this.render()}end(){let e=this.choices.length-this.limit,r=GG(this.choices);return this.choices=r.slice(e).concat(r.slice(0,e)),this.index=this.limit-1,this.render()}first(){return this.index=0,this.render()}last(){return this.index=this.visible.length-1,this.render()}prev(){return this.visible.length<=1?this.alert():this.up()}next(){return this.visible.length<=1?this.alert():this.down()}right(){return this.cursor>=this.input.length?this.alert():(this.cursor++,this.render())}left(){return this.cursor<=0?this.alert():(this.cursor--,this.render())}up(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===0?this.alert():e>r&&s===0?this.scrollUp():(this.index=(s-1%e+e)%e,this.isDisabled()?this.up():this.render())}down(){let e=this.choices.length,r=this.visible.length,s=this.index;return this.options.scroll===!1&&s===r-1?this.alert():e>r&&s===r-1?this.scrollDown():(this.index=(s+1)%e,this.isDisabled()?this.down():this.render())}scrollUp(e=0){return this.choices=D1t(this.choices),this.index=e,this.isDisabled()?this.up():this.render()}scrollDown(e=this.visible.length-1){return this.choices=b1t(this.choices),this.index=e,this.isDisabled()?this.down():this.render()}async shiftUp(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index-1),await this.up(),this.sorting=!1;return}return this.scrollUp(this.index)}async shiftDown(){if(this.options.sort===!0){this.sorting=!0,this.swap(this.index+1),await this.down(),this.sorting=!1;return}return this.scrollDown(this.index)}pageUp(){return this.visible.length<=1?this.alert():(this.limit=Math.max(this.limit-1,0),this.index=Math.min(this.limit-1,this.index),this._limit=this.limit,this.isDisabled()?this.up():this.render())}pageDown(){return this.visible.length>=this.choices.length?this.alert():(this.index=Math.max(0,this.index),this.limit=Math.min(this.limit+1,this.choices.length),this._limit=this.limit,this.isDisabled()?this.down():this.render())}swap(e){P1t(this.choices,this.index,e)}isDisabled(e=this.focused){return e&&["disabled","collapsed","hidden","completing","readonly"].some(s=>e[s]===!0)?!0:e&&e.role==="heading"}isEnabled(e=this.focused){if(Array.isArray(e))return e.every(r=>this.isEnabled(r));if(e.choices){let r=e.choices.filter(s=>!this.isDisabled(s));return e.enabled&&r.every(s=>this.isEnabled(s))}return e.enabled&&!this.isDisabled(e)}isChoice(e,r){return e.name===r||e.index===Number(r)}isSelected(e){return Array.isArray(this.initial)?this.initial.some(r=>this.isChoice(e,r)):this.isChoice(e,this.initial)}map(e=[],r="value"){return[].concat(e||[]).reduce((s,a)=>(s[a]=this.find(a,r),s),{})}filter(e,r){let a=typeof e=="function"?e:(f,p)=>[f.name,p].includes(e),c=(this.options.multiple?this.state._choices:this.choices).filter(a);return r?c.map(f=>f[r]):c}find(e,r){if(Hwe(e))return r?e[r]:e;let a=typeof e=="function"?e:(c,f)=>[c.name,f].includes(e),n=this.choices.find(a);if(n)return r?n[r]:n}findIndex(e){return this.choices.indexOf(this.find(e))}async submit(){let e=this.focused;if(!e)return this.alert();if(e.newChoice)return e.input?(e.updateChoice(),this.render()):this.alert();if(this.choices.some(c=>c.newChoice))return this.alert();let{reorder:r,sort:s}=this.options,a=this.multiple===!0,n=this.selected;return n===void 0?this.alert():(Array.isArray(n)&&r!==!1&&s!==!0&&(n=$R.reorder(n)),this.value=a?n.map(c=>c.name):n.name,super.submit())}set choices(e=[]){this.state._choices=this.state._choices||[],this.state.choices=e;for(let r of e)this.state._choices.some(s=>s.name===r.name)||this.state._choices.push(r);if(!this._initial&&this.options.initial){this._initial=!0;let r=this.initial;if(typeof r=="string"||typeof r=="number"){let s=this.find(r);s&&(this.initial=s.index,this.focus(s,!0))}}}get choices(){return jwe(this,this.state.choices||[])}set visible(e){this.state.visible=e}get visible(){return(this.state.visible||this.choices).slice(0,this.limit)}set limit(e){this.state.limit=e}get limit(){let{state:e,options:r,choices:s}=this,a=e.limit||this._limit||r.limit||s.length;return Math.min(a,this.height)}set value(e){super.value=e}get value(){return typeof super.value!="string"&&super.value===this.initial?this.input:super.value}set index(e){this.state.index=e}get index(){return Math.max(0,this.state?this.state.index:0)}get enabled(){return this.filter(this.isEnabled.bind(this))}get focused(){let e=this.choices[this.index];return e&&this.state.submitted&&this.multiple!==!0&&(e.enabled=!0),e}get selectable(){return this.choices.filter(e=>!this.isDisabled(e))}get selected(){return this.multiple?this.enabled:this.focused}};function jwe(t,e){if(e instanceof Promise)return e;if(typeof e=="function"){if($R.isAsyncFn(e))return e;e=e.call(t,t)}for(let r of e){if(Array.isArray(r.choices)){let s=r.choices.filter(a=>!t.isDisabled(a));r.enabled=s.every(a=>a.enabled===!0)}t.isDisabled(r)===!0&&delete r.enabled}return e}qwe.exports=WG});var G0=L((bnr,Gwe)=>{"use strict";var x1t=oS(),YG=$o(),VG=class extends x1t{constructor(e){super(e),this.emptyError=this.options.emptyError||"No items were selected"}async dispatch(e,r){if(this.multiple)return this[r.name]?await this[r.name](e,r):await super.dispatch(e,r);this.alert()}separator(){if(this.options.separator)return super.separator();let e=this.styles.muted(this.symbols.ellipsis);return this.state.submitted?super.separator():e}pointer(e,r){return!this.multiple||this.options.pointer?super.pointer(e,r):""}indicator(e,r){return this.multiple?super.indicator(e,r):""}choiceMessage(e,r){let s=this.resolve(e.message,this.state,e,r);return e.role==="heading"&&!YG.hasColor(s)&&(s=this.styles.strong(s)),this.resolve(s,this.state,e,r)}choiceSeparator(){return":"}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await this.indicator(e,r)+(e.pad||""),c=await this.resolve(e.hint,this.state,e,r);c&&!YG.hasColor(c)&&(c=this.styles.muted(c));let f=this.indent(e),p=await this.choiceMessage(e,r),h=()=>[this.margin[3],f+a+n,p,this.margin[1],c].filter(Boolean).join(" ");return e.role==="heading"?h():e.disabled?(YG.hasColor(p)||(p=this.styles.disabled(p)),h()):(s&&(p=this.styles.em(p)),h())}async renderChoices(){if(this.state.loading==="choices")return this.styles.warning("Loading choices");if(this.state.submitted)return"";let e=this.visible.map(async(n,c)=>await this.renderChoice(n,c)),r=await Promise.all(e);r.length||r.push(this.styles.danger("No matching choices"));let s=this.margin[0]+r.join(` +`),a;return this.options.choicesHeader&&(a=await this.resolve(this.options.choicesHeader,this.state)),[a,s].filter(Boolean).join(` +`)}format(){return!this.state.submitted||this.state.cancelled?"":Array.isArray(this.selected)?this.selected.map(e=>this.styles.primary(e.name)).join(", "):this.styles.primary(this.selected.name)}async render(){let{submitted:e,size:r}=this.state,s="",a=await this.header(),n=await this.prefix(),c=await this.separator(),f=await this.message();this.options.promptLine!==!1&&(s=[n,f,c,""].join(" "),this.state.prompt=s);let p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();p&&(s+=p),h&&!s.includes(h)&&(s+=" "+h),e&&!p&&!E.trim()&&this.multiple&&this.emptyError!=null&&(s+=this.styles.danger(this.emptyError)),this.clear(r),this.write([a,s,E,C].filter(Boolean).join(` +`)),this.write(this.margin[2]),this.restore()}};Gwe.exports=VG});var Ywe=L((Pnr,Wwe)=>{"use strict";var k1t=G0(),Q1t=(t,e)=>{let r=t.toLowerCase();return s=>{let n=s.toLowerCase().indexOf(r),c=e(s.slice(n,n+r.length));return n>=0?s.slice(0,n)+c+s.slice(n+r.length):s}},KG=class extends k1t{constructor(e){super(e),this.cursorShow()}moveCursor(e){this.state.cursor+=e}dispatch(e){return this.append(e)}space(e){return this.options.multiple?super.space(e):this.append(e)}append(e){let{cursor:r,input:s}=this.state;return this.input=s.slice(0,r)+e+s.slice(r),this.moveCursor(1),this.complete()}delete(){let{cursor:e,input:r}=this.state;return r?(this.input=r.slice(0,e-1)+r.slice(e),this.moveCursor(-1),this.complete()):this.alert()}deleteForward(){let{cursor:e,input:r}=this.state;return r[e]===void 0?this.alert():(this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.complete())}number(e){return this.append(e)}async complete(){this.completing=!0,this.choices=await this.suggest(this.input,this.state._choices),this.state.limit=void 0,this.index=Math.min(Math.max(this.visible.length-1,0),this.index),await this.render(),this.completing=!1}suggest(e=this.input,r=this.state._choices){if(typeof this.options.suggest=="function")return this.options.suggest.call(this,e,r);let s=e.toLowerCase();return r.filter(a=>a.message.toLowerCase().includes(s))}pointer(){return""}format(){if(!this.focused)return this.input;if(this.options.multiple&&this.state.submitted)return this.selected.map(e=>this.styles.primary(e.message)).join(", ");if(this.state.submitted){let e=this.value=this.input=this.focused.value;return this.styles.primary(e)}return this.input}async render(){if(this.state.status!=="pending")return super.render();let e=this.options.highlight?this.options.highlight.bind(this):this.styles.placeholder,r=Q1t(this.input,e),s=this.choices;this.choices=s.map(a=>({...a,message:r(a.message)})),await super.render(),this.choices=s}submit(){return this.options.multiple&&(this.value=this.selected.map(e=>e.name)),super.submit()}};Wwe.exports=KG});var zG=L((xnr,Vwe)=>{"use strict";var JG=$o();Vwe.exports=(t,e={})=>{t.cursorHide();let{input:r="",initial:s="",pos:a,showCursor:n=!0,color:c}=e,f=c||t.styles.placeholder,p=JG.inverse(t.styles.primary),h=R=>p(t.styles.black(R)),E=r,C=" ",S=h(C);if(t.blink&&t.blink.off===!0&&(h=R=>R,S=""),n&&a===0&&s===""&&r==="")return h(C);if(n&&a===0&&(r===s||r===""))return h(s[0])+f(s.slice(1));s=JG.isPrimitive(s)?`${s}`:"",r=JG.isPrimitive(r)?`${r}`:"";let P=s&&s.startsWith(r)&&s!==r,I=P?h(s[r.length]):S;if(a!==r.length&&n===!0&&(E=r.slice(0,a)+h(r[a])+r.slice(a+1),I=""),n===!1&&(I=""),P){let R=t.styles.unstyle(E+I);return E+I+f(s.slice(R.length))}return E+I}});var eF=L((knr,Kwe)=>{"use strict";var T1t=Ju(),R1t=G0(),F1t=zG(),ZG=class extends R1t{constructor(e){super({...e,multiple:!0}),this.type="form",this.initial=this.options.initial,this.align=[this.options.align,"right"].find(r=>r!=null),this.emptyError="",this.values={}}async reset(e){return await super.reset(),e===!0&&(this._index=this.index),this.index=this._index,this.values={},this.choices.forEach(r=>r.reset&&r.reset()),this.render()}dispatch(e){return!!e&&this.append(e)}append(e){let r=this.focused;if(!r)return this.alert();let{cursor:s,input:a}=r;return r.value=r.input=a.slice(0,s)+e+a.slice(s),r.cursor++,this.render()}delete(){let e=this.focused;if(!e||e.cursor<=0)return this.alert();let{cursor:r,input:s}=e;return e.value=e.input=s.slice(0,r-1)+s.slice(r),e.cursor--,this.render()}deleteForward(){let e=this.focused;if(!e)return this.alert();let{cursor:r,input:s}=e;if(s[r]===void 0)return this.alert();let a=`${s}`.slice(0,r)+`${s}`.slice(r+1);return e.value=e.input=a,this.render()}right(){let e=this.focused;return e?e.cursor>=e.input.length?this.alert():(e.cursor++,this.render()):this.alert()}left(){let e=this.focused;return e?e.cursor<=0?this.alert():(e.cursor--,this.render()):this.alert()}space(e,r){return this.dispatch(e,r)}number(e,r){return this.dispatch(e,r)}next(){let e=this.focused;if(!e)return this.alert();let{initial:r,input:s}=e;return r&&r.startsWith(s)&&s!==r?(e.value=e.input=r,e.cursor=e.value.length,this.render()):super.next()}prev(){let e=this.focused;return e?e.cursor===0?super.prev():(e.value=e.input="",e.cursor=0,this.render()):this.alert()}separator(){return""}format(e){return this.state.submitted?"":super.format(e)}pointer(){return""}indicator(e){return e.input?"\u29BF":"\u2299"}async choiceSeparator(e,r){let s=await this.resolve(e.separator,this.state,e,r)||":";return s?" "+this.styles.disabled(s):""}async renderChoice(e,r){await this.onChoice(e,r);let{state:s,styles:a}=this,{cursor:n,initial:c="",name:f,hint:p,input:h=""}=e,{muted:E,submitted:C,primary:S,danger:P}=a,I=p,R=this.index===r,N=e.validate||(()=>!0),U=await this.choiceSeparator(e,r),W=e.message;this.align==="right"&&(W=W.padStart(this.longest+1," ")),this.align==="left"&&(W=W.padEnd(this.longest+1," "));let te=this.values[f]=h||c,ie=h?"success":"dark";await N.call(e,te,this.state)!==!0&&(ie="danger");let Ae=a[ie],ce=Ae(await this.indicator(e,r))+(e.pad||""),me=this.indent(e),pe=()=>[me,ce,W+U,h,I].filter(Boolean).join(" ");if(s.submitted)return W=T1t.unstyle(W),h=C(h),I="",pe();if(e.format)h=await e.format.call(this,h,e,r);else{let Be=this.styles.muted;h=F1t(this,{input:h,initial:c,pos:n,showCursor:R,color:Be})}return this.isValue(h)||(h=this.styles.muted(this.symbols.ellipsis)),e.result&&(this.values[f]=await e.result.call(this,te,e,r)),R&&(W=S(W)),e.error?h+=(h?" ":"")+P(e.error.trim()):e.hint&&(h+=(h?" ":"")+E(e.hint.trim())),pe()}async submit(){return this.value=this.values,super.base.submit.call(this)}};Kwe.exports=ZG});var XG=L((Qnr,zwe)=>{"use strict";var N1t=eF(),O1t=()=>{throw new Error("expected prompt to have a custom authenticate method")},Jwe=(t=O1t)=>{class e extends N1t{constructor(s){super(s)}async submit(){this.value=await t.call(this,this.values,this.state),super.base.submit.call(this)}static create(s){return Jwe(s)}}return e};zwe.exports=Jwe()});var $we=L((Tnr,Xwe)=>{"use strict";var L1t=XG();function M1t(t,e){return t.username===this.options.username&&t.password===this.options.password}var Zwe=(t=M1t)=>{let e=[{name:"username",message:"username"},{name:"password",message:"password",format(s){return this.options.showPassword?s:(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(s.length))}}];class r extends L1t.create(t){constructor(a){super({...a,choices:e})}static create(a){return Zwe(a)}}return r};Xwe.exports=Zwe()});var tF=L((Rnr,e1e)=>{"use strict";var _1t=sC(),{isPrimitive:U1t,hasColor:H1t}=$o(),$G=class extends _1t{constructor(e){super(e),this.cursorHide()}async initialize(){let e=await this.resolve(this.initial,this.state);this.input=await this.cast(e),await super.initialize()}dispatch(e){return this.isValue(e)?(this.input=e,this.submit()):this.alert()}format(e){let{styles:r,state:s}=this;return s.submitted?r.success(e):r.primary(e)}cast(e){return this.isTrue(e)}isTrue(e){return/^[ty1]/i.test(e)}isFalse(e){return/^[fn0]/i.test(e)}isValue(e){return U1t(e)&&(this.isTrue(e)||this.isFalse(e))}async hint(){if(this.state.status==="pending"){let e=await this.element("hint");return H1t(e)?e:this.styles.muted(e)}}async render(){let{input:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=this.styles.muted(this.default),f=[s,n,c,a].filter(Boolean).join(" ");this.state.prompt=f;let p=await this.header(),h=this.value=this.cast(e),E=await this.format(h),C=await this.error()||await this.hint(),S=await this.footer();C&&!f.includes(C)&&(E+=" "+C),f+=" "+E,this.clear(r),this.write([p,f,S].filter(Boolean).join(` +`)),this.restore()}set value(e){super.value=e}get value(){return this.cast(super.value)}};e1e.exports=$G});var r1e=L((Fnr,t1e)=>{"use strict";var j1t=tF(),e5=class extends j1t{constructor(e){super(e),this.default=this.options.default||(this.initial?"(Y/n)":"(y/N)")}};t1e.exports=e5});var i1e=L((Nnr,n1e)=>{"use strict";var q1t=G0(),G1t=eF(),oC=G1t.prototype,t5=class extends q1t{constructor(e){super({...e,multiple:!0}),this.align=[this.options.align,"left"].find(r=>r!=null),this.emptyError="",this.values={}}dispatch(e,r){let s=this.focused,a=s.parent||{};return!s.editable&&!a.editable&&(e==="a"||e==="i")?super[e]():oC.dispatch.call(this,e,r)}append(e,r){return oC.append.call(this,e,r)}delete(e,r){return oC.delete.call(this,e,r)}space(e){return this.focused.editable?this.append(e):super.space()}number(e){return this.focused.editable?this.append(e):super.number(e)}next(){return this.focused.editable?oC.next.call(this):super.next()}prev(){return this.focused.editable?oC.prev.call(this):super.prev()}async indicator(e,r){let s=e.indicator||"",a=e.editable?s:super.indicator(e,r);return await this.resolve(a,this.state,e,r)||""}indent(e){return e.role==="heading"?"":e.editable?" ":" "}async renderChoice(e,r){return e.indent="",e.editable?oC.renderChoice.call(this,e,r):super.renderChoice(e,r)}error(){return""}footer(){return this.state.error}async validate(){let e=!0;for(let r of this.choices){if(typeof r.validate!="function"||r.role==="heading")continue;let s=r.parent?this.value[r.parent.name]:this.value;if(r.editable?s=r.value===r.name?r.initial||"":r.value:this.isDisabled(r)||(s=r.enabled===!0),e=await r.validate(s,this.state),e!==!0)break}return e!==!0&&(this.state.error=typeof e=="string"?e:"Invalid Input"),e}submit(){if(this.focused.newChoice===!0)return super.submit();if(this.choices.some(e=>e.newChoice))return this.alert();this.value={};for(let e of this.choices){let r=e.parent?this.value[e.parent.name]:this.value;if(e.role==="heading"){this.value[e.name]={};continue}e.editable?r[e.name]=e.value===e.name?e.initial||"":e.value:this.isDisabled(e)||(r[e.name]=e.enabled===!0)}return this.base.submit.call(this)}};n1e.exports=t5});var Sm=L((Onr,s1e)=>{"use strict";var W1t=sC(),Y1t=zG(),{isPrimitive:V1t}=$o(),r5=class extends W1t{constructor(e){super(e),this.initial=V1t(this.initial)?String(this.initial):"",this.initial&&this.cursorHide(),this.state.prevCursor=0,this.state.clipboard=[]}async keypress(e,r={}){let s=this.state.prevKeypress;return this.state.prevKeypress=r,this.options.multiline===!0&&r.name==="return"&&(!s||s.name!=="return")?this.append(` +`,r):super.keypress(e,r)}moveCursor(e){this.cursor+=e}reset(){return this.input=this.value="",this.cursor=0,this.render()}dispatch(e,r){if(!e||r.ctrl||r.code)return this.alert();this.append(e)}append(e){let{cursor:r,input:s}=this.state;this.input=`${s}`.slice(0,r)+e+`${s}`.slice(r),this.moveCursor(String(e).length),this.render()}insert(e){this.append(e)}delete(){let{cursor:e,input:r}=this.state;if(e<=0)return this.alert();this.input=`${r}`.slice(0,e-1)+`${r}`.slice(e),this.moveCursor(-1),this.render()}deleteForward(){let{cursor:e,input:r}=this.state;if(r[e]===void 0)return this.alert();this.input=`${r}`.slice(0,e)+`${r}`.slice(e+1),this.render()}cutForward(){let e=this.cursor;if(this.input.length<=e)return this.alert();this.state.clipboard.push(this.input.slice(e)),this.input=this.input.slice(0,e),this.render()}cutLeft(){let e=this.cursor;if(e===0)return this.alert();let r=this.input.slice(0,e),s=this.input.slice(e),a=r.split(" ");this.state.clipboard.push(a.pop()),this.input=a.join(" "),this.cursor=this.input.length,this.input+=s,this.render()}paste(){if(!this.state.clipboard.length)return this.alert();this.insert(this.state.clipboard.pop()),this.render()}toggleCursor(){this.state.prevCursor?(this.cursor=this.state.prevCursor,this.state.prevCursor=0):(this.state.prevCursor=this.cursor,this.cursor=0),this.render()}first(){this.cursor=0,this.render()}last(){this.cursor=this.input.length-1,this.render()}next(){let e=this.initial!=null?String(this.initial):"";if(!e||!e.startsWith(this.input))return this.alert();this.input=this.initial,this.cursor=this.initial.length,this.render()}prev(){if(!this.input)return this.alert();this.reset()}backward(){return this.left()}forward(){return this.right()}right(){return this.cursor>=this.input.length?this.alert():(this.moveCursor(1),this.render())}left(){return this.cursor<=0?this.alert():(this.moveCursor(-1),this.render())}isValue(e){return!!e}async format(e=this.value){let r=await this.resolve(this.initial,this.state);return this.state.submitted?this.styles.submitted(e||r):Y1t(this,{input:e,initial:r,pos:this.cursor})}async render(){let e=this.state.size,r=await this.prefix(),s=await this.separator(),a=await this.message(),n=[r,a,s].filter(Boolean).join(" ");this.state.prompt=n;let c=await this.header(),f=await this.format(),p=await this.error()||await this.hint(),h=await this.footer();p&&!f.includes(p)&&(f+=" "+p),n+=" "+f,this.clear(e),this.write([c,n,h].filter(Boolean).join(` +`)),this.restore()}};s1e.exports=r5});var a1e=L((Lnr,o1e)=>{"use strict";var K1t=t=>t.filter((e,r)=>t.lastIndexOf(e)===r),rF=t=>K1t(t).filter(Boolean);o1e.exports=(t,e={},r="")=>{let{past:s=[],present:a=""}=e,n,c;switch(t){case"prev":case"undo":return n=s.slice(0,s.length-1),c=s[s.length-1]||"",{past:rF([r,...n]),present:c};case"next":case"redo":return n=s.slice(1),c=s[0]||"",{past:rF([...n,r]),present:c};case"save":return{past:rF([...s,r]),present:""};case"remove":return c=rF(s.filter(f=>f!==r)),a="",c.length&&(a=c.pop()),{past:c,present:a};default:throw new Error(`Invalid action: "${t}"`)}}});var i5=L((Mnr,c1e)=>{"use strict";var J1t=Sm(),l1e=a1e(),n5=class extends J1t{constructor(e){super(e);let r=this.options.history;if(r&&r.store){let s=r.values||this.initial;this.autosave=!!r.autosave,this.store=r.store,this.data=this.store.get("values")||{past:[],present:s},this.initial=this.data.present||this.data.past[this.data.past.length-1]}}completion(e){return this.store?(this.data=l1e(e,this.data,this.input),this.data.present?(this.input=this.data.present,this.cursor=this.input.length,this.render()):this.alert()):this.alert()}altUp(){return this.completion("prev")}altDown(){return this.completion("next")}prev(){return this.save(),super.prev()}save(){this.store&&(this.data=l1e("save",this.data,this.input),this.store.set("values",this.data))}submit(){return this.store&&this.autosave===!0&&this.save(),super.submit()}};c1e.exports=n5});var f1e=L((_nr,u1e)=>{"use strict";var z1t=Sm(),s5=class extends z1t{format(){return""}};u1e.exports=s5});var p1e=L((Unr,A1e)=>{"use strict";var Z1t=Sm(),o5=class extends Z1t{constructor(e={}){super(e),this.sep=this.options.separator||/, */,this.initial=e.initial||""}split(e=this.value){return e?String(e).split(this.sep):[]}format(){let e=this.state.submitted?this.styles.primary:r=>r;return this.list.map(e).join(", ")}async submit(e){let r=this.state.error||await this.validate(this.list,this.state);return r!==!0?(this.state.error=r,super.submit()):(this.value=this.list,super.submit())}get list(){return this.split()}};A1e.exports=o5});var g1e=L((Hnr,h1e)=>{"use strict";var X1t=G0(),a5=class extends X1t{constructor(e){super({...e,multiple:!0})}};h1e.exports=a5});var c5=L((jnr,d1e)=>{"use strict";var $1t=Sm(),l5=class extends $1t{constructor(e={}){super({style:"number",...e}),this.min=this.isValue(e.min)?this.toNumber(e.min):-1/0,this.max=this.isValue(e.max)?this.toNumber(e.max):1/0,this.delay=e.delay!=null?e.delay:1e3,this.float=e.float!==!1,this.round=e.round===!0||e.float===!1,this.major=e.major||10,this.minor=e.minor||1,this.initial=e.initial!=null?e.initial:"",this.input=String(this.initial),this.cursor=this.input.length,this.cursorShow()}append(e){return!/[-+.]/.test(e)||e==="."&&this.input.includes(".")?this.alert("invalid number"):super.append(e)}number(e){return super.append(e)}next(){return this.input&&this.input!==this.initial?this.alert():this.isValue(this.initial)?(this.input=this.initial,this.cursor=String(this.initial).length,this.render()):this.alert()}up(e){let r=e||this.minor,s=this.toNumber(this.input);return s>this.max+r?this.alert():(this.input=`${s+r}`,this.render())}down(e){let r=e||this.minor,s=this.toNumber(this.input);return sthis.isValue(r));return this.value=this.toNumber(e||0),super.submit()}};d1e.exports=l5});var y1e=L((qnr,m1e)=>{m1e.exports=c5()});var I1e=L((Gnr,E1e)=>{"use strict";var e2t=Sm(),u5=class extends e2t{constructor(e){super(e),this.cursorShow()}format(e=this.input){return this.keypressed?(this.state.submitted?this.styles.primary:this.styles.muted)(this.symbols.asterisk.repeat(e.length)):""}};E1e.exports=u5});var B1e=L((Wnr,w1e)=>{"use strict";var t2t=Ju(),r2t=oS(),C1e=$o(),f5=class extends r2t{constructor(e={}){super(e),this.widths=[].concat(e.messageWidth||50),this.align=[].concat(e.align||"left"),this.linebreak=e.linebreak||!1,this.edgeLength=e.edgeLength||3,this.newline=e.newline||` + `;let r=e.startNumber||1;typeof this.scale=="number"&&(this.scaleKey=!1,this.scale=Array(this.scale).fill(0).map((s,a)=>({name:a+r})))}async reset(){return this.tableized=!1,await super.reset(),this.render()}tableize(){if(this.tableized===!0)return;this.tableized=!0;let e=0;for(let r of this.choices){e=Math.max(e,r.message.length),r.scaleIndex=r.initial||2,r.scale=[];for(let s=0;s=this.scale.length-1?this.alert():(e.scaleIndex++,this.render())}left(){let e=this.focused;return e.scaleIndex<=0?this.alert():(e.scaleIndex--,this.render())}indent(){return""}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.index)).join(", "):""}pointer(){return""}renderScaleKey(){return this.scaleKey===!1||this.state.submitted?"":["",...this.scale.map(s=>` ${s.name} - ${s.message}`)].map(s=>this.styles.muted(s)).join(` +`)}renderScaleHeading(e){let r=this.scale.map(p=>p.name);typeof this.options.renderScaleHeading=="function"&&(r=this.options.renderScaleHeading.call(this,e));let s=this.scaleLength-r.join("").length,a=Math.round(s/(r.length-1)),c=r.map(p=>this.styles.strong(p)).join(" ".repeat(a)),f=" ".repeat(this.widths[0]);return this.margin[3]+f+this.margin[1]+c}scaleIndicator(e,r,s){if(typeof this.options.scaleIndicator=="function")return this.options.scaleIndicator.call(this,e,r,s);let a=e.scaleIndex===r.index;return r.disabled?this.styles.hint(this.symbols.radio.disabled):a?this.styles.success(this.symbols.radio.on):this.symbols.radio.off}renderScale(e,r){let s=e.scale.map(n=>this.scaleIndicator(e,n,r)),a=this.term==="Hyper"?"":" ";return s.join(a+this.symbols.line.repeat(this.edgeLength))}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=await this.pointer(e,r),n=await e.hint;n&&!C1e.hasColor(n)&&(n=this.styles.muted(n));let c=I=>this.margin[3]+I.replace(/\s+$/,"").padEnd(this.widths[0]," "),f=this.newline,p=this.indent(e),h=await this.resolve(e.message,this.state,e,r),E=await this.renderScale(e,r),C=this.margin[1]+this.margin[3];this.scaleLength=t2t.unstyle(E).length,this.widths[0]=Math.min(this.widths[0],this.width-this.scaleLength-C.length);let P=C1e.wordWrap(h,{width:this.widths[0],newline:f}).split(` +`).map(I=>c(I)+this.margin[1]);return s&&(E=this.styles.info(E),P=P.map(I=>this.styles.info(I))),P[0]+=E,this.linebreak&&P.push(""),[p+a,P.join(` +`)].filter(Boolean)}async renderChoices(){if(this.state.submitted)return"";this.tableize();let e=this.visible.map(async(a,n)=>await this.renderChoice(a,n)),r=await Promise.all(e),s=await this.renderScaleHeading();return this.margin[0]+[s,...r.map(a=>a.join(" "))].join(` +`)}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c="";this.options.promptLine!==!1&&(c=[s,n,a,""].join(" "),this.state.prompt=c);let f=await this.header(),p=await this.format(),h=await this.renderScaleKey(),E=await this.error()||await this.hint(),C=await this.renderChoices(),S=await this.footer(),P=this.emptyError;p&&(c+=p),E&&!c.includes(E)&&(c+=" "+E),e&&!p&&!C.trim()&&this.multiple&&P!=null&&(c+=this.styles.danger(P)),this.clear(r),this.write([f,c,h,C,S].filter(Boolean).join(` +`)),this.state.submitted||this.write(this.margin[2]),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIndex;return this.base.submit.call(this)}};w1e.exports=f5});var D1e=L((Ynr,S1e)=>{"use strict";var v1e=Ju(),n2t=(t="")=>typeof t=="string"?t.replace(/^['"]|['"]$/g,""):"",p5=class{constructor(e){this.name=e.key,this.field=e.field||{},this.value=n2t(e.initial||this.field.initial||""),this.message=e.message||this.name,this.cursor=0,this.input="",this.lines=[]}},i2t=async(t={},e={},r=s=>s)=>{let s=new Set,a=t.fields||[],n=t.template,c=[],f=[],p=[],h=1;typeof n=="function"&&(n=await n());let E=-1,C=()=>n[++E],S=()=>n[E+1],P=I=>{I.line=h,c.push(I)};for(P({type:"bos",value:""});Eie.name===U.key);U.field=a.find(ie=>ie.name===U.key),te||(te=new p5(U),f.push(te)),te.lines.push(U.line-1);continue}let R=c[c.length-1];R.type==="text"&&R.line===h?R.value+=I:P({type:"text",value:I})}return P({type:"eos",value:""}),{input:n,tabstops:c,unique:s,keys:p,items:f}};S1e.exports=async t=>{let e=t.options,r=new Set(e.required===!0?[]:e.required||[]),s={...e.values,...e.initial},{tabstops:a,items:n,keys:c}=await i2t(e,s),f=A5("result",t,e),p=A5("format",t,e),h=A5("validate",t,e,!0),E=t.isValue.bind(t);return async(C={},S=!1)=>{let P=0;C.required=r,C.items=n,C.keys=c,C.output="";let I=async(W,te,ie,Ae)=>{let ce=await h(W,te,ie,Ae);return ce===!1?"Invalid field "+ie.name:ce};for(let W of a){let te=W.value,ie=W.key;if(W.type!=="template"){te&&(C.output+=te);continue}if(W.type==="template"){let Ae=n.find(Ce=>Ce.name===ie);e.required===!0&&C.required.add(Ae.name);let ce=[Ae.input,C.values[Ae.value],Ae.value,te].find(E),pe=(Ae.field||{}).message||W.inner;if(S){let Ce=await I(C.values[ie],C,Ae,P);if(Ce&&typeof Ce=="string"||Ce===!1){C.invalid.set(ie,Ce);continue}C.invalid.delete(ie);let g=await f(C.values[ie],C,Ae,P);C.output+=v1e.unstyle(g);continue}Ae.placeholder=!1;let Be=te;te=await p(te,C,Ae,P),ce!==te?(C.values[ie]=ce,te=t.styles.typing(ce),C.missing.delete(pe)):(C.values[ie]=void 0,ce=`<${pe}>`,te=t.styles.primary(ce),Ae.placeholder=!0,C.required.has(ie)&&C.missing.add(pe)),C.missing.has(pe)&&C.validating&&(te=t.styles.warning(ce)),C.invalid.has(ie)&&C.validating&&(te=t.styles.danger(ce)),P===C.index&&(Be!==te?te=t.styles.underline(te):te=t.styles.heading(v1e.unstyle(te))),P++}te&&(C.output+=te)}let R=C.output.split(` +`).map(W=>" "+W),N=n.length,U=0;for(let W of n)C.invalid.has(W.name)&&W.lines.forEach(te=>{R[te][0]===" "&&(R[te]=C.styles.danger(C.symbols.bullet)+R[te].slice(1))}),t.isValue(C.values[W.name])&&U++;return C.completed=(U/N*100).toFixed(0),C.output=R.join(` +`),C.output}};function A5(t,e,r,s){return(a,n,c,f)=>typeof c.field[t]=="function"?c.field[t].call(e,a,n,c,f):[s,a].find(p=>e.isValue(p))}});var P1e=L((Vnr,b1e)=>{"use strict";var s2t=Ju(),o2t=D1e(),a2t=sC(),h5=class extends a2t{constructor(e){super(e),this.cursorHide(),this.reset(!0)}async initialize(){this.interpolate=await o2t(this),await super.initialize()}async reset(e){this.state.keys=[],this.state.invalid=new Map,this.state.missing=new Set,this.state.completed=0,this.state.values={},e!==!0&&(await this.initialize(),await this.render())}moveCursor(e){let r=this.getItem();this.cursor+=e,r.cursor+=e}dispatch(e,r){if(!r.code&&!r.ctrl&&e!=null&&this.getItem()){this.append(e,r);return}this.alert()}append(e,r){let s=this.getItem(),a=s.input.slice(0,this.cursor),n=s.input.slice(this.cursor);this.input=s.input=`${a}${e}${n}`,this.moveCursor(1),this.render()}delete(){let e=this.getItem();if(this.cursor<=0||!e.input)return this.alert();let r=e.input.slice(this.cursor),s=e.input.slice(0,this.cursor-1);this.input=e.input=`${s}${r}`,this.moveCursor(-1),this.render()}increment(e){return e>=this.state.keys.length-1?0:e+1}decrement(e){return e<=0?this.state.keys.length-1:e-1}first(){this.state.index=0,this.render()}last(){this.state.index=this.state.keys.length-1,this.render()}right(){if(this.cursor>=this.input.length)return this.alert();this.moveCursor(1),this.render()}left(){if(this.cursor<=0)return this.alert();this.moveCursor(-1),this.render()}prev(){this.state.index=this.decrement(this.state.index),this.getItem(),this.render()}next(){this.state.index=this.increment(this.state.index),this.getItem(),this.render()}up(){this.prev()}down(){this.next()}format(e){let r=this.state.completed<100?this.styles.warning:this.styles.success;return this.state.submitted===!0&&this.state.completed!==100&&(r=this.styles.danger),r(`${this.state.completed}% completed`)}async render(){let{index:e,keys:r=[],submitted:s,size:a}=this.state,n=[this.options.newline,` +`].find(W=>W!=null),c=await this.prefix(),f=await this.separator(),p=await this.message(),h=[c,p,f].filter(Boolean).join(" ");this.state.prompt=h;let E=await this.header(),C=await this.error()||"",S=await this.hint()||"",P=s?"":await this.interpolate(this.state),I=this.state.key=r[e]||"",R=await this.format(I),N=await this.footer();R&&(h+=" "+R),S&&!R&&this.state.completed===0&&(h+=" "+S),this.clear(a);let U=[E,h,P,N,C.trim()];this.write(U.filter(Boolean).join(n)),this.restore()}getItem(e){let{items:r,keys:s,index:a}=this.state,n=r.find(c=>c.name===s[a]);return n&&n.input!=null&&(this.input=n.input,this.cursor=n.cursor),n}async submit(){typeof this.interpolate!="function"&&await this.initialize(),await this.interpolate(this.state,!0);let{invalid:e,missing:r,output:s,values:a}=this.state;if(e.size){let f="";for(let[p,h]of e)f+=`Invalid ${p}: ${h} +`;return this.state.error=f,super.submit()}if(r.size)return this.state.error="Required: "+[...r.keys()].join(", "),super.submit();let c=s2t.unstyle(s).split(` +`).map(f=>f.slice(1)).join(` +`);return this.value={values:a,result:c},super.submit()}};b1e.exports=h5});var k1e=L((Knr,x1e)=>{"use strict";var l2t="(Use + to sort)",c2t=G0(),g5=class extends c2t{constructor(e){super({...e,reorder:!1,sort:!0,multiple:!0}),this.state.hint=[this.options.hint,l2t].find(this.isValue.bind(this))}indicator(){return""}async renderChoice(e,r){let s=await super.renderChoice(e,r),a=this.symbols.identicalTo+" ",n=this.index===r&&this.sorting?this.styles.muted(a):" ";return this.options.drag===!1&&(n=""),this.options.numbered===!0?n+`${r+1} - `+s:n+s}get selected(){return this.choices}submit(){return this.value=this.choices.map(e=>e.value),super.submit()}};x1e.exports=g5});var T1e=L((Jnr,Q1e)=>{"use strict";var u2t=oS(),d5=class extends u2t{constructor(e={}){if(super(e),this.emptyError=e.emptyError||"No items were selected",this.term=process.env.TERM_PROGRAM,!this.options.header){let r=["","4 - Strongly Agree","3 - Agree","2 - Neutral","1 - Disagree","0 - Strongly Disagree",""];r=r.map(s=>this.styles.muted(s)),this.state.header=r.join(` + `)}}async toChoices(...e){if(this.createdScales)return!1;this.createdScales=!0;let r=await super.toChoices(...e);for(let s of r)s.scale=f2t(5,this.options),s.scaleIdx=2;return r}dispatch(){this.alert()}space(){let e=this.focused,r=e.scale[e.scaleIdx],s=r.selected;return e.scale.forEach(a=>a.selected=!1),r.selected=!s,this.render()}indicator(){return""}pointer(){return""}separator(){return this.styles.muted(this.symbols.ellipsis)}right(){let e=this.focused;return e.scaleIdx>=e.scale.length-1?this.alert():(e.scaleIdx++,this.render())}left(){let e=this.focused;return e.scaleIdx<=0?this.alert():(e.scaleIdx--,this.render())}indent(){return" "}async renderChoice(e,r){await this.onChoice(e,r);let s=this.index===r,a=this.term==="Hyper",n=a?9:8,c=a?"":" ",f=this.symbols.line.repeat(n),p=" ".repeat(n+(a?0:1)),h=te=>(te?this.styles.success("\u25C9"):"\u25EF")+c,E=r+1+".",C=s?this.styles.heading:this.styles.noop,S=await this.resolve(e.message,this.state,e,r),P=this.indent(e),I=P+e.scale.map((te,ie)=>h(ie===e.scaleIdx)).join(f),R=te=>te===e.scaleIdx?C(te):te,N=P+e.scale.map((te,ie)=>R(ie)).join(p),U=()=>[E,S].filter(Boolean).join(" "),W=()=>[U(),I,N," "].filter(Boolean).join(` +`);return s&&(I=this.styles.cyan(I),N=this.styles.cyan(N)),W()}async renderChoices(){if(this.state.submitted)return"";let e=this.visible.map(async(s,a)=>await this.renderChoice(s,a)),r=await Promise.all(e);return r.length||r.push(this.styles.danger("No matching choices")),r.join(` +`)}format(){return this.state.submitted?this.choices.map(r=>this.styles.info(r.scaleIdx)).join(", "):""}async render(){let{submitted:e,size:r}=this.state,s=await this.prefix(),a=await this.separator(),n=await this.message(),c=[s,n,a].filter(Boolean).join(" ");this.state.prompt=c;let f=await this.header(),p=await this.format(),h=await this.error()||await this.hint(),E=await this.renderChoices(),C=await this.footer();(p||!h)&&(c+=" "+p),h&&!c.includes(h)&&(c+=" "+h),e&&!p&&!E&&this.multiple&&this.type!=="form"&&(c+=this.styles.danger(this.emptyError)),this.clear(r),this.write([c,f,E,C].filter(Boolean).join(` +`)),this.restore()}submit(){this.value={};for(let e of this.choices)this.value[e.name]=e.scaleIdx;return this.base.submit.call(this)}};function f2t(t,e={}){if(Array.isArray(e.scale))return e.scale.map(s=>({...s}));let r=[];for(let s=1;s{R1e.exports=i5()});var O1e=L((Znr,N1e)=>{"use strict";var A2t=tF(),m5=class extends A2t{async initialize(){await super.initialize(),this.value=this.initial=!!this.options.initial,this.disabled=this.options.disabled||"no",this.enabled=this.options.enabled||"yes",await this.render()}reset(){this.value=this.initial,this.render()}delete(){this.alert()}toggle(){this.value=!this.value,this.render()}enable(){if(this.value===!0)return this.alert();this.value=!0,this.render()}disable(){if(this.value===!1)return this.alert();this.value=!1,this.render()}up(){this.toggle()}down(){this.toggle()}right(){this.toggle()}left(){this.toggle()}next(){this.toggle()}prev(){this.toggle()}dispatch(e="",r){switch(e.toLowerCase()){case" ":return this.toggle();case"1":case"y":case"t":return this.enable();case"0":case"n":case"f":return this.disable();default:return this.alert()}}format(){let e=s=>this.styles.primary.underline(s);return[this.value?this.disabled:e(this.disabled),this.value?e(this.enabled):this.enabled].join(this.styles.muted(" / "))}async render(){let{size:e}=this.state,r=await this.header(),s=await this.prefix(),a=await this.separator(),n=await this.message(),c=await this.format(),f=await this.error()||await this.hint(),p=await this.footer(),h=[s,n,a,c].join(" ");this.state.prompt=h,f&&!h.includes(f)&&(h+=" "+f),this.clear(e),this.write([r,h,p].filter(Boolean).join(` +`)),this.write(this.margin[2]),this.restore()}};N1e.exports=m5});var M1e=L((Xnr,L1e)=>{"use strict";var p2t=G0(),y5=class extends p2t{constructor(e){if(super(e),typeof this.options.correctChoice!="number"||this.options.correctChoice<0)throw new Error("Please specify the index of the correct answer from the list of choices")}async toChoices(e,r){let s=await super.toChoices(e,r);if(s.length<2)throw new Error("Please give at least two choices to the user");if(this.options.correctChoice>s.length)throw new Error("Please specify the index of the correct answer from the list of choices");return s}check(e){return e.index===this.options.correctChoice}async result(e){return{selectedAnswer:e,correctAnswer:this.options.choices[this.options.correctChoice].value,correct:await this.check(this.state)}}};L1e.exports=y5});var U1e=L(E5=>{"use strict";var _1e=$o(),Qs=(t,e)=>{_1e.defineExport(E5,t,e),_1e.defineExport(E5,t.toLowerCase(),e)};Qs("AutoComplete",()=>Ywe());Qs("BasicAuth",()=>$we());Qs("Confirm",()=>r1e());Qs("Editable",()=>i1e());Qs("Form",()=>eF());Qs("Input",()=>i5());Qs("Invisible",()=>f1e());Qs("List",()=>p1e());Qs("MultiSelect",()=>g1e());Qs("Numeral",()=>y1e());Qs("Password",()=>I1e());Qs("Scale",()=>B1e());Qs("Select",()=>G0());Qs("Snippet",()=>P1e());Qs("Sort",()=>k1e());Qs("Survey",()=>T1e());Qs("Text",()=>F1e());Qs("Toggle",()=>O1e());Qs("Quiz",()=>M1e())});var j1e=L((eir,H1e)=>{H1e.exports={ArrayPrompt:oS(),AuthPrompt:XG(),BooleanPrompt:tF(),NumberPrompt:c5(),StringPrompt:Sm()}});var lS=L((tir,G1e)=>{"use strict";var q1e=ye("assert"),C5=ye("events"),W0=$o(),Zu=class extends C5{constructor(e,r){super(),this.options=W0.merge({},e),this.answers={...r}}register(e,r){if(W0.isObject(e)){for(let a of Object.keys(e))this.register(a,e[a]);return this}q1e.equal(typeof r,"function","expected a function");let s=e.toLowerCase();return r.prototype instanceof this.Prompt?this.prompts[s]=r:this.prompts[s]=r(this.Prompt,this),this}async prompt(e=[]){for(let r of[].concat(e))try{typeof r=="function"&&(r=await r.call(this)),await this.ask(W0.merge({},this.options,r))}catch(s){return Promise.reject(s)}return this.answers}async ask(e){typeof e=="function"&&(e=await e.call(this));let r=W0.merge({},this.options,e),{type:s,name:a}=e,{set:n,get:c}=W0;if(typeof s=="function"&&(s=await s.call(this,e,this.answers)),!s)return this.answers[a];q1e(this.prompts[s],`Prompt "${s}" is not registered`);let f=new this.prompts[s](r),p=c(this.answers,a);f.state.answers=this.answers,f.enquirer=this,a&&f.on("submit",E=>{this.emit("answer",a,E,f),n(this.answers,a,E)});let h=f.emit.bind(f);return f.emit=(...E)=>(this.emit.call(this,...E),h(...E)),this.emit("prompt",f,this),r.autofill&&p!=null?(f.value=f.input=p,r.autofill==="show"&&await f.submit()):p=f.value=await f.run(),p}use(e){return e.call(this,this),this}set Prompt(e){this._Prompt=e}get Prompt(){return this._Prompt||this.constructor.Prompt}get prompts(){return this.constructor.prompts}static set Prompt(e){this._Prompt=e}static get Prompt(){return this._Prompt||sC()}static get prompts(){return U1e()}static get types(){return j1e()}static get prompt(){let e=(r,...s)=>{let a=new this(...s),n=a.emit.bind(a);return a.emit=(...c)=>(e.emit(...c),n(...c)),a.prompt(r)};return W0.mixinEmitter(e,new C5),e}};W0.mixinEmitter(Zu,new C5);var I5=Zu.prompts;for(let t of Object.keys(I5)){let e=t.toLowerCase(),r=s=>new I5[t](s).run();Zu.prompt[e]=r,Zu[e]=r,Zu[t]||Reflect.defineProperty(Zu,t,{get:()=>I5[t]})}var aS=t=>{W0.defineExport(Zu,t,()=>Zu.types[t])};aS("ArrayPrompt");aS("AuthPrompt");aS("BooleanPrompt");aS("NumberPrompt");aS("StringPrompt");G1e.exports=Zu});var AS=L((Lir,Z1e)=>{var I2t=qR();function C2t(t,e,r){var s=t==null?void 0:I2t(t,e);return s===void 0?r:s}Z1e.exports=C2t});var e2e=L((qir,$1e)=>{function w2t(t,e){for(var r=-1,s=t==null?0:t.length;++r{var B2t=Jd(),v2t=Uk();function S2t(t,e){return t&&B2t(e,v2t(e),t)}t2e.exports=S2t});var i2e=L((Wir,n2e)=>{var D2t=Jd(),b2t=qE();function P2t(t,e){return t&&D2t(e,b2t(e),t)}n2e.exports=P2t});var o2e=L((Yir,s2e)=>{var x2t=Jd(),k2t=Fk();function Q2t(t,e){return x2t(t,k2t(t),e)}s2e.exports=Q2t});var b5=L((Vir,a2e)=>{var T2t=Rk(),R2t=Wk(),F2t=Fk(),N2t=_4(),O2t=Object.getOwnPropertySymbols,L2t=O2t?function(t){for(var e=[];t;)T2t(e,F2t(t)),t=R2t(t);return e}:N2t;a2e.exports=L2t});var c2e=L((Kir,l2e)=>{var M2t=Jd(),_2t=b5();function U2t(t,e){return M2t(t,_2t(t),e)}l2e.exports=U2t});var P5=L((Jir,u2e)=>{var H2t=M4(),j2t=b5(),q2t=qE();function G2t(t){return H2t(t,q2t,j2t)}u2e.exports=G2t});var A2e=L((zir,f2e)=>{var W2t=Object.prototype,Y2t=W2t.hasOwnProperty;function V2t(t){var e=t.length,r=new t.constructor(e);return e&&typeof t[0]=="string"&&Y2t.call(t,"index")&&(r.index=t.index,r.input=t.input),r}f2e.exports=V2t});var h2e=L((Zir,p2e)=>{var K2t=qk();function J2t(t,e){var r=e?K2t(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)}p2e.exports=J2t});var d2e=L((Xir,g2e)=>{var z2t=/\w*$/;function Z2t(t){var e=new t.constructor(t.source,z2t.exec(t));return e.lastIndex=t.lastIndex,e}g2e.exports=Z2t});var C2e=L(($ir,I2e)=>{var m2e=Yd(),y2e=m2e?m2e.prototype:void 0,E2e=y2e?y2e.valueOf:void 0;function X2t(t){return E2e?Object(E2e.call(t)):{}}I2e.exports=X2t});var B2e=L((esr,w2e)=>{var $2t=qk(),eBt=h2e(),tBt=d2e(),rBt=C2e(),nBt=$4(),iBt="[object Boolean]",sBt="[object Date]",oBt="[object Map]",aBt="[object Number]",lBt="[object RegExp]",cBt="[object Set]",uBt="[object String]",fBt="[object Symbol]",ABt="[object ArrayBuffer]",pBt="[object DataView]",hBt="[object Float32Array]",gBt="[object Float64Array]",dBt="[object Int8Array]",mBt="[object Int16Array]",yBt="[object Int32Array]",EBt="[object Uint8Array]",IBt="[object Uint8ClampedArray]",CBt="[object Uint16Array]",wBt="[object Uint32Array]";function BBt(t,e,r){var s=t.constructor;switch(e){case ABt:return $2t(t);case iBt:case sBt:return new s(+t);case pBt:return eBt(t,r);case hBt:case gBt:case dBt:case mBt:case yBt:case EBt:case IBt:case CBt:case wBt:return nBt(t,r);case oBt:return new s;case aBt:case uBt:return new s(t);case lBt:return tBt(t);case cBt:return new s;case fBt:return rBt(t)}}w2e.exports=BBt});var S2e=L((tsr,v2e)=>{var vBt=_B(),SBt=zf(),DBt="[object Map]";function bBt(t){return SBt(t)&&vBt(t)==DBt}v2e.exports=bBt});var x2e=L((rsr,P2e)=>{var PBt=S2e(),xBt=Ok(),D2e=Lk(),b2e=D2e&&D2e.isMap,kBt=b2e?xBt(b2e):PBt;P2e.exports=kBt});var Q2e=L((nsr,k2e)=>{var QBt=_B(),TBt=zf(),RBt="[object Set]";function FBt(t){return TBt(t)&&QBt(t)==RBt}k2e.exports=FBt});var N2e=L((isr,F2e)=>{var NBt=Q2e(),OBt=Ok(),T2e=Lk(),R2e=T2e&&T2e.isSet,LBt=R2e?OBt(R2e):NBt;F2e.exports=LBt});var x5=L((ssr,_2e)=>{var MBt=Qk(),_Bt=e2e(),UBt=Yk(),HBt=r2e(),jBt=i2e(),qBt=X4(),GBt=Gk(),WBt=o2e(),YBt=c2e(),VBt=q4(),KBt=P5(),JBt=_B(),zBt=A2e(),ZBt=B2e(),XBt=e3(),$Bt=xc(),evt=FB(),tvt=x2e(),rvt=Wl(),nvt=N2e(),ivt=Uk(),svt=qE(),ovt=1,avt=2,lvt=4,O2e="[object Arguments]",cvt="[object Array]",uvt="[object Boolean]",fvt="[object Date]",Avt="[object Error]",L2e="[object Function]",pvt="[object GeneratorFunction]",hvt="[object Map]",gvt="[object Number]",M2e="[object Object]",dvt="[object RegExp]",mvt="[object Set]",yvt="[object String]",Evt="[object Symbol]",Ivt="[object WeakMap]",Cvt="[object ArrayBuffer]",wvt="[object DataView]",Bvt="[object Float32Array]",vvt="[object Float64Array]",Svt="[object Int8Array]",Dvt="[object Int16Array]",bvt="[object Int32Array]",Pvt="[object Uint8Array]",xvt="[object Uint8ClampedArray]",kvt="[object Uint16Array]",Qvt="[object Uint32Array]",Ii={};Ii[O2e]=Ii[cvt]=Ii[Cvt]=Ii[wvt]=Ii[uvt]=Ii[fvt]=Ii[Bvt]=Ii[vvt]=Ii[Svt]=Ii[Dvt]=Ii[bvt]=Ii[hvt]=Ii[gvt]=Ii[M2e]=Ii[dvt]=Ii[mvt]=Ii[yvt]=Ii[Evt]=Ii[Pvt]=Ii[xvt]=Ii[kvt]=Ii[Qvt]=!0;Ii[Avt]=Ii[L2e]=Ii[Ivt]=!1;function iF(t,e,r,s,a,n){var c,f=e&ovt,p=e&avt,h=e&lvt;if(r&&(c=a?r(t,s,a,n):r(t)),c!==void 0)return c;if(!rvt(t))return t;var E=$Bt(t);if(E){if(c=zBt(t),!f)return GBt(t,c)}else{var C=JBt(t),S=C==L2e||C==pvt;if(evt(t))return qBt(t,f);if(C==M2e||C==O2e||S&&!a){if(c=p||S?{}:XBt(t),!f)return p?YBt(t,jBt(c,t)):WBt(t,HBt(c,t))}else{if(!Ii[C])return a?t:{};c=ZBt(t,C,f)}}n||(n=new MBt);var P=n.get(t);if(P)return P;n.set(t,c),nvt(t)?t.forEach(function(N){c.add(iF(N,e,r,N,t,n))}):tvt(t)&&t.forEach(function(N,U){c.set(U,iF(N,e,r,U,t,n))});var I=h?p?KBt:VBt:p?svt:ivt,R=E?void 0:I(t);return _Bt(R||t,function(N,U){R&&(U=N,N=t[U]),UBt(c,U,iF(N,e,r,U,t,n))}),c}_2e.exports=iF});var k5=L((osr,U2e)=>{var Tvt=x5(),Rvt=1,Fvt=4;function Nvt(t){return Tvt(t,Rvt|Fvt)}U2e.exports=Nvt});var Q5=L((asr,H2e)=>{var Ovt=CG();function Lvt(t,e,r){return t==null?t:Ovt(t,e,r)}H2e.exports=Lvt});var Y2e=L((psr,W2e)=>{var Mvt=Object.prototype,_vt=Mvt.hasOwnProperty;function Uvt(t,e){return t!=null&&_vt.call(t,e)}W2e.exports=Uvt});var K2e=L((hsr,V2e)=>{var Hvt=Y2e(),jvt=wG();function qvt(t,e){return t!=null&&jvt(t,e,Hvt)}V2e.exports=qvt});var z2e=L((gsr,J2e)=>{function Gvt(t){var e=t==null?0:t.length;return e?t[e-1]:void 0}J2e.exports=Gvt});var X2e=L((dsr,Z2e)=>{var Wvt=qR(),Yvt=A6();function Vvt(t,e){return e.length<2?t:Wvt(t,Yvt(e,0,-1))}Z2e.exports=Vvt});var R5=L((msr,$2e)=>{var Kvt=wm(),Jvt=z2e(),zvt=X2e(),Zvt=XI();function Xvt(t,e){return e=Kvt(e,t),t=zvt(t,e),t==null||delete t[Zvt(Jvt(e))]}$2e.exports=Xvt});var F5=L((ysr,eBe)=>{var $vt=R5();function eSt(t,e){return t==null?!0:$vt(t,e)}eBe.exports=eSt});var sBe=L((Vsr,nSt)=>{nSt.exports={name:"@yarnpkg/cli",version:"4.9.1",license:"BSD-2-Clause",main:"./sources/index.ts",exports:{".":"./sources/index.ts","./polyfills":"./sources/polyfills.ts","./package.json":"./package.json"},dependencies:{"@yarnpkg/core":"workspace:^","@yarnpkg/fslib":"workspace:^","@yarnpkg/libzip":"workspace:^","@yarnpkg/parsers":"workspace:^","@yarnpkg/plugin-compat":"workspace:^","@yarnpkg/plugin-constraints":"workspace:^","@yarnpkg/plugin-dlx":"workspace:^","@yarnpkg/plugin-essentials":"workspace:^","@yarnpkg/plugin-exec":"workspace:^","@yarnpkg/plugin-file":"workspace:^","@yarnpkg/plugin-git":"workspace:^","@yarnpkg/plugin-github":"workspace:^","@yarnpkg/plugin-http":"workspace:^","@yarnpkg/plugin-init":"workspace:^","@yarnpkg/plugin-interactive-tools":"workspace:^","@yarnpkg/plugin-jsr":"workspace:^","@yarnpkg/plugin-link":"workspace:^","@yarnpkg/plugin-nm":"workspace:^","@yarnpkg/plugin-npm":"workspace:^","@yarnpkg/plugin-npm-cli":"workspace:^","@yarnpkg/plugin-pack":"workspace:^","@yarnpkg/plugin-patch":"workspace:^","@yarnpkg/plugin-pnp":"workspace:^","@yarnpkg/plugin-pnpm":"workspace:^","@yarnpkg/plugin-stage":"workspace:^","@yarnpkg/plugin-typescript":"workspace:^","@yarnpkg/plugin-version":"workspace:^","@yarnpkg/plugin-workspace-tools":"workspace:^","@yarnpkg/shell":"workspace:^","ci-info":"^4.0.0",clipanion:"^4.0.0-rc.2",semver:"^7.1.2",tslib:"^2.4.0",typanion:"^3.14.0"},devDependencies:{"@types/semver":"^7.1.0","@yarnpkg/builder":"workspace:^","@yarnpkg/monorepo":"workspace:^","@yarnpkg/pnpify":"workspace:^"},peerDependencies:{"@yarnpkg/core":"workspace:^"},scripts:{postpack:"rm -rf lib",prepack:'run build:compile "$(pwd)"',"build:cli+hook":"run build:pnp:hook && builder build bundle","build:cli":"builder build bundle","run:cli":"builder run","update-local":"run build:cli --no-git-hash && rsync -a --delete bundles/ bin/"},publishConfig:{main:"./lib/index.js",bin:null,exports:{".":"./lib/index.js","./package.json":"./package.json"}},files:["/lib/**/*","!/lib/pluginConfiguration.*","!/lib/cli.*"],"@yarnpkg/builder":{bundles:{standard:["@yarnpkg/plugin-essentials","@yarnpkg/plugin-compat","@yarnpkg/plugin-constraints","@yarnpkg/plugin-dlx","@yarnpkg/plugin-exec","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-jsr","@yarnpkg/plugin-link","@yarnpkg/plugin-nm","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp","@yarnpkg/plugin-pnpm","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"]}},repository:{type:"git",url:"git+https://github.com/yarnpkg/berry.git",directory:"packages/yarnpkg-cli"},engines:{node:">=18.12.0"}}});var q5=L((Dcr,mBe)=>{"use strict";mBe.exports=function(e,r){r===!0&&(r=0);var s="";if(typeof e=="string")try{s=new URL(e).protocol}catch{}else e&&e.constructor===URL&&(s=e.protocol);var a=s.split(/\:|\+/).filter(Boolean);return typeof r=="number"?a[r]:a}});var EBe=L((bcr,yBe)=>{"use strict";var BSt=q5();function vSt(t){var e={protocols:[],protocol:null,port:null,resource:"",host:"",user:"",password:"",pathname:"",hash:"",search:"",href:t,query:{},parse_failed:!1};try{var r=new URL(t);e.protocols=BSt(r),e.protocol=e.protocols[0],e.port=r.port,e.resource=r.hostname,e.host=r.host,e.user=r.username||"",e.password=r.password||"",e.pathname=r.pathname,e.hash=r.hash.slice(1),e.search=r.search.slice(1),e.href=r.href,e.query=Object.fromEntries(r.searchParams)}catch{e.protocols=["file"],e.protocol=e.protocols[0],e.port="",e.resource="",e.user="",e.pathname="",e.hash="",e.search="",e.href=t,e.query={},e.parse_failed=!0}return e}yBe.exports=vSt});var wBe=L((Pcr,CBe)=>{"use strict";var SSt=EBe();function DSt(t){return t&&typeof t=="object"&&"default"in t?t:{default:t}}var bSt=DSt(SSt),PSt="text/plain",xSt="us-ascii",IBe=(t,e)=>e.some(r=>r instanceof RegExp?r.test(t):r===t),kSt=(t,{stripHash:e})=>{let r=/^data:(?[^,]*?),(?[^#]*?)(?:#(?.*))?$/.exec(t);if(!r)throw new Error(`Invalid URL: ${t}`);let{type:s,data:a,hash:n}=r.groups,c=s.split(";");n=e?"":n;let f=!1;c[c.length-1]==="base64"&&(c.pop(),f=!0);let p=(c.shift()||"").toLowerCase(),E=[...c.map(C=>{let[S,P=""]=C.split("=").map(I=>I.trim());return S==="charset"&&(P=P.toLowerCase(),P===xSt)?"":`${S}${P?`=${P}`:""}`}).filter(Boolean)];return f&&E.push("base64"),(E.length>0||p&&p!==PSt)&&E.unshift(p),`data:${E.join(";")},${f?a.trim():a}${n?`#${n}`:""}`};function QSt(t,e){if(e={defaultProtocol:"http:",normalizeProtocol:!0,forceHttp:!1,forceHttps:!1,stripAuthentication:!0,stripHash:!1,stripTextFragment:!0,stripWWW:!0,removeQueryParameters:[/^utm_\w+/i],removeTrailingSlash:!0,removeSingleSlash:!0,removeDirectoryIndex:!1,sortQueryParameters:!0,...e},t=t.trim(),/^data:/i.test(t))return kSt(t,e);if(/^view-source:/i.test(t))throw new Error("`view-source:` is not supported as it is a non-standard protocol");let r=t.startsWith("//");!r&&/^\.*\//.test(t)||(t=t.replace(/^(?!(?:\w+:)?\/\/)|^\/\//,e.defaultProtocol));let a=new URL(t);if(e.forceHttp&&e.forceHttps)throw new Error("The `forceHttp` and `forceHttps` options cannot be used together");if(e.forceHttp&&a.protocol==="https:"&&(a.protocol="http:"),e.forceHttps&&a.protocol==="http:"&&(a.protocol="https:"),e.stripAuthentication&&(a.username="",a.password=""),e.stripHash?a.hash="":e.stripTextFragment&&(a.hash=a.hash.replace(/#?:~:text.*?$/i,"")),a.pathname){let c=/\b[a-z][a-z\d+\-.]{1,50}:\/\//g,f=0,p="";for(;;){let E=c.exec(a.pathname);if(!E)break;let C=E[0],S=E.index,P=a.pathname.slice(f,S);p+=P.replace(/\/{2,}/g,"/"),p+=C,f=S+C.length}let h=a.pathname.slice(f,a.pathname.length);p+=h.replace(/\/{2,}/g,"/"),a.pathname=p}if(a.pathname)try{a.pathname=decodeURI(a.pathname)}catch{}if(e.removeDirectoryIndex===!0&&(e.removeDirectoryIndex=[/^index\.[a-z]+$/]),Array.isArray(e.removeDirectoryIndex)&&e.removeDirectoryIndex.length>0){let c=a.pathname.split("/"),f=c[c.length-1];IBe(f,e.removeDirectoryIndex)&&(c=c.slice(0,-1),a.pathname=c.slice(1).join("/")+"/")}if(a.hostname&&(a.hostname=a.hostname.replace(/\.$/,""),e.stripWWW&&/^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(a.hostname)&&(a.hostname=a.hostname.replace(/^www\./,""))),Array.isArray(e.removeQueryParameters))for(let c of[...a.searchParams.keys()])IBe(c,e.removeQueryParameters)&&a.searchParams.delete(c);if(e.removeQueryParameters===!0&&(a.search=""),e.sortQueryParameters){a.searchParams.sort();try{a.search=decodeURIComponent(a.search)}catch{}}e.removeTrailingSlash&&(a.pathname=a.pathname.replace(/\/$/,""));let n=t;return t=a.toString(),!e.removeSingleSlash&&a.pathname==="/"&&!n.endsWith("/")&&a.hash===""&&(t=t.replace(/\/$/,"")),(e.removeTrailingSlash||a.pathname==="/")&&a.hash===""&&e.removeSingleSlash&&(t=t.replace(/\/$/,"")),r&&!e.normalizeProtocol&&(t=t.replace(/^http:\/\//,"//")),e.stripProtocol&&(t=t.replace(/^(?:https?:)?\/\//,"")),t}var G5=(t,e=!1)=>{let r=/^(?:([a-z_][a-z0-9_-]{0,31})@|https?:\/\/)([\w\.\-@]+)[\/:]([\~,\.\w,\-,\_,\/]+?(?:\.git|\/)?)$/,s=n=>{let c=new Error(n);throw c.subject_url=t,c};(typeof t!="string"||!t.trim())&&s("Invalid url."),t.length>G5.MAX_INPUT_LENGTH&&s("Input exceeds maximum length. If needed, change the value of parseUrl.MAX_INPUT_LENGTH."),e&&(typeof e!="object"&&(e={stripHash:!1}),t=QSt(t,e));let a=bSt.default(t);if(a.parse_failed){let n=a.href.match(r);n?(a.protocols=["ssh"],a.protocol="ssh",a.resource=n[2],a.host=n[2],a.user=n[1],a.pathname=`/${n[3]}`,a.parse_failed=!1):s("URL parsing failed.")}return a};G5.MAX_INPUT_LENGTH=2048;CBe.exports=G5});var SBe=L((xcr,vBe)=>{"use strict";var TSt=q5();function BBe(t){if(Array.isArray(t))return t.indexOf("ssh")!==-1||t.indexOf("rsync")!==-1;if(typeof t!="string")return!1;var e=TSt(t);if(t=t.substring(t.indexOf("://")+3),BBe(e))return!0;var r=new RegExp(".([a-zA-Z\\d]+):(\\d+)/");return!t.match(r)&&t.indexOf("@"){"use strict";var RSt=wBe(),DBe=SBe();function FSt(t){var e=RSt(t);return e.token="",e.password==="x-oauth-basic"?e.token=e.user:e.user==="x-token-auth"&&(e.token=e.password),DBe(e.protocols)||e.protocols.length===0&&DBe(t)?e.protocol="ssh":e.protocols.length?e.protocol=e.protocols[0]:(e.protocol="file",e.protocols=["file"]),e.href=e.href.replace(/\/$/,""),e}bBe.exports=FSt});var kBe=L((Qcr,xBe)=>{"use strict";var NSt=PBe();function W5(t){if(typeof t!="string")throw new Error("The url must be a string.");var e=/^([a-z\d-]{1,39})\/([-\.\w]{1,100})$/i;e.test(t)&&(t="https://github.com/"+t);var r=NSt(t),s=r.resource.split("."),a=null;switch(r.toString=function(N){return W5.stringify(this,N)},r.source=s.length>2?s.slice(1-s.length).join("."):r.source=r.resource,r.git_suffix=/\.git$/.test(r.pathname),r.name=decodeURIComponent((r.pathname||r.href).replace(/(^\/)|(\/$)/g,"").replace(/\.git$/,"")),r.owner=decodeURIComponent(r.user),r.source){case"git.cloudforge.com":r.owner=r.user,r.organization=s[0],r.source="cloudforge.com";break;case"visualstudio.com":if(r.resource==="vs-ssh.visualstudio.com"){a=r.name.split("/"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3],r.full_name=a[2]+"/"+a[3]);break}else{a=r.name.split("/"),a.length===2?(r.owner=a[1],r.name=a[1],r.full_name="_git/"+r.name):a.length===3?(r.name=a[2],a[0]==="DefaultCollection"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+"/_git/"+r.name):(r.owner=a[0],r.full_name=r.owner+"/_git/"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+"/"+r.owner+"/_git/"+r.name);break}case"dev.azure.com":case"azure.com":if(r.resource==="ssh.dev.azure.com"){a=r.name.split("/"),a.length===4&&(r.organization=a[1],r.owner=a[2],r.name=a[3]);break}else{a=r.name.split("/"),a.length===5?(r.organization=a[0],r.owner=a[1],r.name=a[4],r.full_name="_git/"+r.name):a.length===3?(r.name=a[2],a[0]==="DefaultCollection"?(r.owner=a[2],r.organization=a[0],r.full_name=r.organization+"/_git/"+r.name):(r.owner=a[0],r.full_name=r.owner+"/_git/"+r.name)):a.length===4&&(r.organization=a[0],r.owner=a[1],r.name=a[3],r.full_name=r.organization+"/"+r.owner+"/_git/"+r.name),r.query&&r.query.path&&(r.filepath=r.query.path.replace(/^\/+/g,"")),r.query&&r.query.version&&(r.ref=r.query.version.replace(/^GB/,""));break}default:a=r.name.split("/");var n=a.length-1;if(a.length>=2){var c=a.indexOf("-",2),f=a.indexOf("blob",2),p=a.indexOf("tree",2),h=a.indexOf("commit",2),E=a.indexOf("src",2),C=a.indexOf("raw",2),S=a.indexOf("edit",2);n=c>0?c-1:f>0?f-1:p>0?p-1:h>0?h-1:E>0?E-1:C>0?C-1:S>0?S-1:n,r.owner=a.slice(0,n).join("/"),r.name=a[n],h&&(r.commit=a[n+2])}r.ref="",r.filepathtype="",r.filepath="";var P=a.length>n&&a[n+1]==="-"?n+1:n;a.length>P+2&&["raw","src","blob","tree","edit"].indexOf(a[P+1])>=0&&(r.filepathtype=a[P+1],r.ref=a[P+2],a.length>P+3&&(r.filepath=a.slice(P+3).join("/"))),r.organization=r.owner;break}r.full_name||(r.full_name=r.owner,r.name&&(r.full_name&&(r.full_name+="/"),r.full_name+=r.name)),r.owner.startsWith("scm/")&&(r.source="bitbucket-server",r.owner=r.owner.replace("scm/",""),r.organization=r.owner,r.full_name=r.owner+"/"+r.name);var I=/(projects|users)\/(.*?)\/repos\/(.*?)((\/.*$)|$)/,R=I.exec(r.pathname);return R!=null&&(r.source="bitbucket-server",R[1]==="users"?r.owner="~"+R[2]:r.owner=R[2],r.organization=r.owner,r.name=R[3],a=R[4].split("/"),a.length>1&&(["raw","browse"].indexOf(a[1])>=0?(r.filepathtype=a[1],a.length>2&&(r.filepath=a.slice(2).join("/"))):a[1]==="commits"&&a.length>2&&(r.commit=a[2])),r.full_name=r.owner+"/"+r.name,r.query.at?r.ref=r.query.at:r.ref=""),r}W5.stringify=function(t,e){e=e||(t.protocols&&t.protocols.length?t.protocols.join("+"):t.protocol);var r=t.port?":"+t.port:"",s=t.user||"git",a=t.git_suffix?".git":"";switch(e){case"ssh":return r?"ssh://"+s+"@"+t.resource+r+"/"+t.full_name+a:s+"@"+t.resource+":"+t.full_name+a;case"git+ssh":case"ssh+git":case"ftp":case"ftps":return e+"://"+s+"@"+t.resource+r+"/"+t.full_name+a;case"http":case"https":var n=t.token?OSt(t):t.user&&(t.protocols.includes("http")||t.protocols.includes("https"))?t.user+"@":"";return e+"://"+n+t.resource+r+"/"+LSt(t)+a;default:return t.href}};function OSt(t){switch(t.source){case"bitbucket.org":return"x-token-auth:"+t.token+"@";default:return t.token+"@"}}function LSt(t){switch(t.source){case"bitbucket-server":return"scm/"+t.full_name;default:return""+t.full_name}}xBe.exports=W5});var YBe=L((ufr,WBe)=>{var KSt=RT(),JSt=Gk(),zSt=xc(),ZSt=aI(),XSt=IG(),$St=XI(),eDt=Tv();function tDt(t){return zSt(t)?KSt(t,$St):ZSt(t)?[t]:JSt(XSt(eDt(t)))}WBe.exports=tDt});function sDt(t,e){return e===1&&iDt.has(t[0])}function ES(t){let e=Array.isArray(t)?t:(0,JBe.default)(t);return e.map((s,a)=>rDt.test(s)?`[${s}]`:nDt.test(s)&&!sDt(e,a)?`.${s}`:`[${JSON.stringify(s)}]`).join("").replace(/^\./,"")}function oDt(t,e){let r=[];if(e.methodName!==null&&r.push(he.pretty(t,e.methodName,he.Type.CODE)),e.file!==null){let s=[];s.push(he.pretty(t,e.file,he.Type.PATH)),e.line!==null&&(s.push(he.pretty(t,e.line,he.Type.NUMBER)),e.column!==null&&s.push(he.pretty(t,e.column,he.Type.NUMBER))),r.push(`(${s.join(he.pretty(t,":","grey"))})`)}return r.join(" ")}function lF(t,{manifestUpdates:e,reportedErrors:r},{fix:s}={}){let a=new Map,n=new Map,c=[...r.keys()].map(f=>[f,new Map]);for(let[f,p]of[...c,...e]){let h=r.get(f)?.map(P=>({text:P,fixable:!1}))??[],E=!1,C=t.getWorkspaceByCwd(f),S=C.manifest.exportTo({});for(let[P,I]of p){if(I.size>1){let R=[...I].map(([N,U])=>{let W=he.pretty(t.configuration,N,he.Type.INSPECT),te=U.size>0?oDt(t.configuration,U.values().next().value):null;return te!==null?` +${W} at ${te}`:` +${W}`}).join("");h.push({text:`Conflict detected in constraint targeting ${he.pretty(t.configuration,P,he.Type.CODE)}; conflicting values are:${R}`,fixable:!1})}else{let[[R]]=I,N=(0,VBe.default)(S,P);if(JSON.stringify(N)===JSON.stringify(R))continue;if(!s){let U=typeof N>"u"?`Missing field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}`:typeof R>"u"?`Extraneous field ${he.pretty(t.configuration,P,he.Type.CODE)} currently set to ${he.pretty(t.configuration,N,he.Type.INSPECT)}`:`Invalid field ${he.pretty(t.configuration,P,he.Type.CODE)}; expected ${he.pretty(t.configuration,R,he.Type.INSPECT)}, found ${he.pretty(t.configuration,N,he.Type.INSPECT)}`;h.push({text:U,fixable:!0});continue}typeof R>"u"?(0,zBe.default)(S,P):(0,KBe.default)(S,P,R),E=!0}E&&a.set(C,S)}h.length>0&&n.set(C,h)}return{changedWorkspaces:a,remainingErrors:n}}function ZBe(t,{configuration:e}){let r={children:[]};for(let[s,a]of t){let n=[];for(let f of a){let p=f.text.split(/\n/);f.fixable&&(p[0]=`${he.pretty(e,"\u2699","gray")} ${p[0]}`),n.push({value:he.tuple(he.Type.NO_HINT,p[0]),children:p.slice(1).map(h=>({value:he.tuple(he.Type.NO_HINT,h)}))})}let c={value:he.tuple(he.Type.LOCATOR,s.anchoredLocator),children:je.sortMap(n,f=>f.value[1])};r.children.push(c)}return r.children=je.sortMap(r.children,s=>s.value[1]),r}var VBe,KBe,JBe,zBe,VC,rDt,nDt,iDt,IS=It(()=>{Ve();VBe=et(AS()),KBe=et(Q5()),JBe=et(YBe()),zBe=et(F5()),VC=class{constructor(e){this.indexedFields=e;this.items=[];this.indexes={};this.clear()}clear(){this.items=[];for(let e of this.indexedFields)this.indexes[e]=new Map}insert(e){this.items.push(e);for(let r of this.indexedFields){let s=Object.hasOwn(e,r)?e[r]:void 0;if(typeof s>"u")continue;je.getArrayWithDefault(this.indexes[r],s).push(e)}return e}find(e){if(typeof e>"u")return this.items;let r=Object.entries(e);if(r.length===0)return this.items;let s=[],a;for(let[c,f]of r){let p=c,h=Object.hasOwn(this.indexes,p)?this.indexes[p]:void 0;if(typeof h>"u"){s.push([p,f]);continue}let E=new Set(h.get(f)??[]);if(E.size===0)return[];if(typeof a>"u")a=E;else for(let C of a)E.has(C)||a.delete(C);if(a.size===0)break}let n=[...a??[]];return s.length>0&&(n=n.filter(c=>{for(let[f,p]of s)if(!(typeof p<"u"?Object.hasOwn(c,f)&&c[f]===p:Object.hasOwn(c,f)===!1))return!1;return!0})),n}},rDt=/^[0-9]+$/,nDt=/^[a-zA-Z0-9_]+$/,iDt=new Set(["scripts",...Ht.allDependencies])});var XBe=L((wfr,s9)=>{var aDt;(function(t){var e=function(){return{"append/2":[new t.type.Rule(new t.type.Term("append",[new t.type.Var("X"),new t.type.Var("L")]),new t.type.Term("foldl",[new t.type.Term("append",[]),new t.type.Var("X"),new t.type.Term("[]",[]),new t.type.Var("L")]))],"append/3":[new t.type.Rule(new t.type.Term("append",[new t.type.Term("[]",[]),new t.type.Var("X"),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("append",[new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("S")])]),new t.type.Term("append",[new t.type.Var("T"),new t.type.Var("X"),new t.type.Var("S")]))],"member/2":[new t.type.Rule(new t.type.Term("member",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("_")])]),null),new t.type.Rule(new t.type.Term("member",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("Xs")])]),new t.type.Term("member",[new t.type.Var("X"),new t.type.Var("Xs")]))],"permutation/2":[new t.type.Rule(new t.type.Term("permutation",[new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("permutation",[new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("permutation",[new t.type.Var("T"),new t.type.Var("P")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("P")]),new t.type.Term("append",[new t.type.Var("X"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("Y")]),new t.type.Var("S")])])]))],"maplist/2":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("X")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("Xs")])]))],"maplist/3":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs")])]))],"maplist/4":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs")])]))],"maplist/5":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds")])]))],"maplist/6":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es")])]))],"maplist/7":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")]),new t.type.Term(".",[new t.type.Var("F"),new t.type.Var("Fs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E"),new t.type.Var("F")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es"),new t.type.Var("Fs")])]))],"maplist/8":[new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("A"),new t.type.Var("As")]),new t.type.Term(".",[new t.type.Var("B"),new t.type.Var("Bs")]),new t.type.Term(".",[new t.type.Var("C"),new t.type.Var("Cs")]),new t.type.Term(".",[new t.type.Var("D"),new t.type.Var("Ds")]),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Es")]),new t.type.Term(".",[new t.type.Var("F"),new t.type.Var("Fs")]),new t.type.Term(".",[new t.type.Var("G"),new t.type.Var("Gs")])]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P"),new t.type.Var("A"),new t.type.Var("B"),new t.type.Var("C"),new t.type.Var("D"),new t.type.Var("E"),new t.type.Var("F"),new t.type.Var("G")]),new t.type.Term("maplist",[new t.type.Var("P"),new t.type.Var("As"),new t.type.Var("Bs"),new t.type.Var("Cs"),new t.type.Var("Ds"),new t.type.Var("Es"),new t.type.Var("Fs"),new t.type.Var("Gs")])]))],"include/3":[new t.type.Rule(new t.type.Term("include",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("include",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("A")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("A"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term("[]",[])]),new t.type.Var("B")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("F"),new t.type.Var("B")]),new t.type.Term(",",[new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("F")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("S")])]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("L"),new t.type.Var("S")])]),new t.type.Term("include",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("S")])])])])]))],"exclude/3":[new t.type.Rule(new t.type.Term("exclude",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Term("[]",[])]),null),new t.type.Rule(new t.type.Term("exclude",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("exclude",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("E")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term("[]",[])]),new t.type.Var("Q")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("R"),new t.type.Var("Q")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("R")]),new t.type.Term(",",[new t.type.Term("!",[]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("E")])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("E")])])])])])])]))],"foldl/4":[new t.type.Rule(new t.type.Term("foldl",[new t.type.Var("_"),new t.type.Term("[]",[]),new t.type.Var("I"),new t.type.Var("I")]),null),new t.type.Rule(new t.type.Term("foldl",[new t.type.Var("P"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Var("T")]),new t.type.Var("I"),new t.type.Var("R")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P"),new t.type.Var("L")]),new t.type.Term(",",[new t.type.Term("append",[new t.type.Var("L"),new t.type.Term(".",[new t.type.Var("I"),new t.type.Term(".",[new t.type.Var("H"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])])])]),new t.type.Var("L2")]),new t.type.Term(",",[new t.type.Term("=..",[new t.type.Var("P2"),new t.type.Var("L2")]),new t.type.Term(",",[new t.type.Term("call",[new t.type.Var("P2")]),new t.type.Term("foldl",[new t.type.Var("P"),new t.type.Var("T"),new t.type.Var("X"),new t.type.Var("R")])])])])]))],"select/3":[new t.type.Rule(new t.type.Term("select",[new t.type.Var("E"),new t.type.Term(".",[new t.type.Var("E"),new t.type.Var("Xs")]),new t.type.Var("Xs")]),null),new t.type.Rule(new t.type.Term("select",[new t.type.Var("E"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Ys")])]),new t.type.Term("select",[new t.type.Var("E"),new t.type.Var("Xs"),new t.type.Var("Ys")]))],"sum_list/2":[new t.type.Rule(new t.type.Term("sum_list",[new t.type.Term("[]",[]),new t.type.Num(0,!1)]),null),new t.type.Rule(new t.type.Term("sum_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("sum_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term("is",[new t.type.Var("S"),new t.type.Term("+",[new t.type.Var("X"),new t.type.Var("Y")])])]))],"max_list/2":[new t.type.Rule(new t.type.Term("max_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("max_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("max_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Var("Y")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("X")]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("Y")])])]))],"min_list/2":[new t.type.Rule(new t.type.Term("min_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("min_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("min_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term(";",[new t.type.Term(",",[new t.type.Term("=<",[new t.type.Var("X"),new t.type.Var("Y")]),new t.type.Term(",",[new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("X")]),new t.type.Term("!",[])])]),new t.type.Term("=",[new t.type.Var("S"),new t.type.Var("Y")])])]))],"prod_list/2":[new t.type.Rule(new t.type.Term("prod_list",[new t.type.Term("[]",[]),new t.type.Num(1,!1)]),null),new t.type.Rule(new t.type.Term("prod_list",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("S")]),new t.type.Term(",",[new t.type.Term("prod_list",[new t.type.Var("Xs"),new t.type.Var("Y")]),new t.type.Term("is",[new t.type.Var("S"),new t.type.Term("*",[new t.type.Var("X"),new t.type.Var("Y")])])]))],"last/2":[new t.type.Rule(new t.type.Term("last",[new t.type.Term(".",[new t.type.Var("X"),new t.type.Term("[]",[])]),new t.type.Var("X")]),null),new t.type.Rule(new t.type.Term("last",[new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("Xs")]),new t.type.Var("X")]),new t.type.Term("last",[new t.type.Var("Xs"),new t.type.Var("X")]))],"prefix/2":[new t.type.Rule(new t.type.Term("prefix",[new t.type.Var("Part"),new t.type.Var("Whole")]),new t.type.Term("append",[new t.type.Var("Part"),new t.type.Var("_"),new t.type.Var("Whole")]))],"nth0/3":[new t.type.Rule(new t.type.Term("nth0",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")])]),new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")]),new t.type.Term("!",[])])])]))],"nth1/3":[new t.type.Rule(new t.type.Term("nth1",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")])]),new t.type.Term(",",[new t.type.Term(">",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("_")]),new t.type.Term("!",[])])])]))],"nth0/4":[new t.type.Rule(new t.type.Term("nth0",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")])]),new t.type.Term(",",[new t.type.Term(">=",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(0,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term("!",[])])])]))],"nth1/4":[new t.type.Rule(new t.type.Term("nth1",[new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term(";",[new t.type.Term("->",[new t.type.Term("var",[new t.type.Var("X")]),new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")])]),new t.type.Term(",",[new t.type.Term(">",[new t.type.Var("X"),new t.type.Num(0,!1)]),new t.type.Term(",",[new t.type.Term("nth",[new t.type.Num(1,!1),new t.type.Var("X"),new t.type.Var("Y"),new t.type.Var("Z"),new t.type.Var("W")]),new t.type.Term("!",[])])])]))],"nth/5":[new t.type.Rule(new t.type.Term("nth",[new t.type.Var("N"),new t.type.Var("N"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("X"),new t.type.Var("Xs")]),null),new t.type.Rule(new t.type.Term("nth",[new t.type.Var("N"),new t.type.Var("O"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Xs")]),new t.type.Var("Y"),new t.type.Term(".",[new t.type.Var("X"),new t.type.Var("Ys")])]),new t.type.Term(",",[new t.type.Term("is",[new t.type.Var("M"),new t.type.Term("+",[new t.type.Var("N"),new t.type.Num(1,!1)])]),new t.type.Term("nth",[new t.type.Var("M"),new t.type.Var("O"),new t.type.Var("Xs"),new t.type.Var("Y"),new t.type.Var("Ys")])]))],"length/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(!t.type.is_variable(f)&&!t.type.is_integer(f))s.throw_error(t.error.type("integer",f,n.indicator));else if(t.type.is_integer(f)&&f.value<0)s.throw_error(t.error.domain("not_less_than_zero",f,n.indicator));else{var p=new t.type.Term("length",[c,new t.type.Num(0,!1),f]);t.type.is_integer(f)&&(p=new t.type.Term(",",[p,new t.type.Term("!",[])])),s.prepend([new t.type.State(a.goal.replace(p),a.substitution,a)])}},"length/3":[new t.type.Rule(new t.type.Term("length",[new t.type.Term("[]",[]),new t.type.Var("N"),new t.type.Var("N")]),null),new t.type.Rule(new t.type.Term("length",[new t.type.Term(".",[new t.type.Var("_"),new t.type.Var("X")]),new t.type.Var("A"),new t.type.Var("N")]),new t.type.Term(",",[new t.type.Term("succ",[new t.type.Var("A"),new t.type.Var("B")]),new t.type.Term("length",[new t.type.Var("X"),new t.type.Var("B"),new t.type.Var("N")])]))],"replicate/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_integer(f))s.throw_error(t.error.type("integer",f,n.indicator));else if(f.value<0)s.throw_error(t.error.domain("not_less_than_zero",f,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=new t.type.Term("[]"),E=0;E0;C--)E[C].equals(E[C-1])&&E.splice(C,1);for(var S=new t.type.Term("[]"),C=E.length-1;C>=0;C--)S=new t.type.Term(".",[E[C],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,f])),a.substitution,a)])}}},"msort/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else{for(var p=[],h=c;h.indicator==="./2";)p.push(h.args[0]),h=h.args[1];if(t.type.is_variable(h))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(h))s.throw_error(t.error.type("list",c,n.indicator));else{for(var E=p.sort(t.compare),C=new t.type.Term("[]"),S=E.length-1;S>=0;S--)C=new t.type.Term(".",[E[S],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,f])),a.substitution,a)])}}},"keysort/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else{for(var p=[],h,E=c;E.indicator==="./2";){if(h=E.args[0],t.type.is_variable(h)){s.throw_error(t.error.instantiation(n.indicator));return}else if(!t.type.is_term(h)||h.indicator!=="-/2"){s.throw_error(t.error.type("pair",h,n.indicator));return}h.args[0].pair=h.args[1],p.push(h.args[0]),E=E.args[1]}if(t.type.is_variable(E))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_empty_list(E))s.throw_error(t.error.type("list",c,n.indicator));else{for(var C=p.sort(t.compare),S=new t.type.Term("[]"),P=C.length-1;P>=0;P--)S=new t.type.Term(".",[new t.type.Term("-",[C[P],C[P].pair]),S]),delete C[P].pair;s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,f])),a.substitution,a)])}}},"take/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type("integer",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator==="./2";)E.push(C.args[0]),C=C.args[1],h--;if(h===0){for(var S=new t.type.Term("[]"),h=E.length-1;h>=0;h--)S=new t.type.Term(".",[E[h],S]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[S,p])),a.substitution,a)])}}},"drop/3":function(s,a,n){var c=n.args[0],f=n.args[1],p=n.args[2];if(t.type.is_variable(f)||t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!t.type.is_integer(c))s.throw_error(t.error.type("integer",c,n.indicator));else if(!t.type.is_variable(p)&&!t.type.is_list(p))s.throw_error(t.error.type("list",p,n.indicator));else{for(var h=c.value,E=[],C=f;h>0&&C.indicator==="./2";)E.push(C.args[0]),C=C.args[1],h--;h===0&&s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,p])),a.substitution,a)])}},"reverse/2":function(s,a,n){var c=n.args[0],f=n.args[1],p=t.type.is_instantiated_list(c),h=t.type.is_instantiated_list(f);if(t.type.is_variable(c)&&t.type.is_variable(f))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_variable(c)&&!t.type.is_fully_list(c))s.throw_error(t.error.type("list",c,n.indicator));else if(!t.type.is_variable(f)&&!t.type.is_fully_list(f))s.throw_error(t.error.type("list",f,n.indicator));else if(!p&&!h)s.throw_error(t.error.instantiation(n.indicator));else{for(var E=p?c:f,C=new t.type.Term("[]",[]);E.indicator==="./2";)C=new t.type.Term(".",[E.args[0],C]),E=E.args[1];s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[C,p?f:c])),a.substitution,a)])}},"list_to_set/2":function(s,a,n){var c=n.args[0],f=n.args[1];if(t.type.is_variable(c))s.throw_error(t.error.instantiation(n.indicator));else{for(var p=c,h=[];p.indicator==="./2";)h.push(p.args[0]),p=p.args[1];if(t.type.is_variable(p))s.throw_error(t.error.instantiation(n.indicator));else if(!t.type.is_term(p)||p.indicator!=="[]/0")s.throw_error(t.error.type("list",c,n.indicator));else{for(var E=[],C=new t.type.Term("[]",[]),S,P=0;P=0;P--)C=new t.type.Term(".",[E[P],C]);s.prepend([new t.type.State(a.goal.replace(new t.type.Term("=",[f,C])),a.substitution,a)])}}}}},r=["append/2","append/3","member/2","permutation/2","maplist/2","maplist/3","maplist/4","maplist/5","maplist/6","maplist/7","maplist/8","include/3","exclude/3","foldl/4","sum_list/2","max_list/2","min_list/2","prod_list/2","last/2","prefix/2","nth0/3","nth1/3","nth0/4","nth1/4","length/2","replicate/3","select/3","sort/2","msort/2","keysort/2","take/3","drop/3","reverse/2","list_to_set/2"];typeof s9<"u"?s9.exports=function(s){t=s,new t.type.Module("lists",e(),r)}:new t.type.Module("lists",e(),r)})(aDt)});var pve=L($r=>{"use strict";var Pm=process.platform==="win32",o9="aes-256-cbc",lDt="sha256",tve="The current environment doesn't support interactive reading from TTY.",ii=ye("fs"),$Be=process.binding("tty_wrap").TTY,l9=ye("child_process"),K0=ye("path"),c9={prompt:"> ",hideEchoBack:!1,mask:"*",limit:[],limitMessage:"Input another, please.$<( [)limit(])>",defaultInput:"",trueValue:[],falseValue:[],caseSensitive:!1,keepWhitespace:!1,encoding:"utf8",bufferSize:1024,print:void 0,history:!0,cd:!1,phContent:void 0,preCheck:void 0},eh="none",$u,JC,eve=!1,V0,uF,a9,cDt=0,h9="",bm=[],fF,rve=!1,u9=!1,CS=!1;function nve(t){function e(r){return r.replace(/[^\w\u0080-\uFFFF]/g,function(s){return"#"+s.charCodeAt(0)+";"})}return uF.concat(function(r){var s=[];return Object.keys(r).forEach(function(a){r[a]==="boolean"?t[a]&&s.push("--"+a):r[a]==="string"&&t[a]&&s.push("--"+a,e(t[a]))}),s}({display:"string",displayOnly:"boolean",keyIn:"boolean",hideEchoBack:"boolean",mask:"string",limit:"string",caseSensitive:"boolean"}))}function uDt(t,e){function r(U){var W,te="",ie;for(a9=a9||ye("os").tmpdir();;){W=K0.join(a9,U+te);try{ie=ii.openSync(W,"wx")}catch(Ae){if(Ae.code==="EEXIST"){te++;continue}else throw Ae}ii.closeSync(ie);break}return W}var s,a,n,c={},f,p,h=r("readline-sync.stdout"),E=r("readline-sync.stderr"),C=r("readline-sync.exit"),S=r("readline-sync.done"),P=ye("crypto"),I,R,N;I=P.createHash(lDt),I.update(""+process.pid+cDt+++Math.random()),N=I.digest("hex"),R=P.createDecipher(o9,N),s=nve(t),Pm?(a=process.env.ComSpec||"cmd.exe",process.env.Q='"',n=["/V:ON","/S","/C","(%Q%"+a+"%Q% /V:ON /S /C %Q%%Q%"+V0+"%Q%"+s.map(function(U){return" %Q%"+U+"%Q%"}).join("")+" & (echo !ERRORLEVEL!)>%Q%"+C+"%Q%%Q%) 2>%Q%"+E+"%Q% |%Q%"+process.execPath+"%Q% %Q%"+__dirname+"\\encrypt.js%Q% %Q%"+o9+"%Q% %Q%"+N+"%Q% >%Q%"+h+"%Q% & (echo 1)>%Q%"+S+"%Q%"]):(a="/bin/sh",n=["-c",'("'+V0+'"'+s.map(function(U){return" '"+U.replace(/'/g,"'\\''")+"'"}).join("")+'; echo $?>"'+C+'") 2>"'+E+'" |"'+process.execPath+'" "'+__dirname+'/encrypt.js" "'+o9+'" "'+N+'" >"'+h+'"; echo 1 >"'+S+'"']),CS&&CS("_execFileSync",s);try{l9.spawn(a,n,e)}catch(U){c.error=new Error(U.message),c.error.method="_execFileSync - spawn",c.error.program=a,c.error.args=n}for(;ii.readFileSync(S,{encoding:t.encoding}).trim()!=="1";);return(f=ii.readFileSync(C,{encoding:t.encoding}).trim())==="0"?c.input=R.update(ii.readFileSync(h,{encoding:"binary"}),"hex",t.encoding)+R.final(t.encoding):(p=ii.readFileSync(E,{encoding:t.encoding}).trim(),c.error=new Error(tve+(p?` +`+p:"")),c.error.method="_execFileSync",c.error.program=a,c.error.args=n,c.error.extMessage=p,c.error.exitCode=+f),ii.unlinkSync(h),ii.unlinkSync(E),ii.unlinkSync(C),ii.unlinkSync(S),c}function fDt(t){var e,r={},s,a={env:process.env,encoding:t.encoding};if(V0||(Pm?process.env.PSModulePath?(V0="powershell.exe",uF=["-ExecutionPolicy","Bypass","-File",__dirname+"\\read.ps1"]):(V0="cscript.exe",uF=["//nologo",__dirname+"\\read.cs.js"]):(V0="/bin/sh",uF=[__dirname+"/read.sh"])),Pm&&!process.env.PSModulePath&&(a.stdio=[process.stdin]),l9.execFileSync){e=nve(t),CS&&CS("execFileSync",e);try{r.input=l9.execFileSync(V0,e,a)}catch(n){s=n.stderr?(n.stderr+"").trim():"",r.error=new Error(tve+(s?` +`+s:"")),r.error.method="execFileSync",r.error.program=V0,r.error.args=e,r.error.extMessage=s,r.error.exitCode=n.status,r.error.code=n.code,r.error.signal=n.signal}}else r=uDt(t,a);return r.error||(r.input=r.input.replace(/^\s*'|'\s*$/g,""),t.display=""),r}function f9(t){var e="",r=t.display,s=!t.display&&t.keyIn&&t.hideEchoBack&&!t.mask;function a(){var n=fDt(t);if(n.error)throw n.error;return n.input}return u9&&u9(t),function(){var n,c,f;function p(){return n||(n=process.binding("fs"),c=process.binding("constants")),n}if(typeof eh=="string")if(eh=null,Pm){if(f=function(h){var E=h.replace(/^\D+/,"").split("."),C=0;return(E[0]=+E[0])&&(C+=E[0]*1e4),(E[1]=+E[1])&&(C+=E[1]*100),(E[2]=+E[2])&&(C+=E[2]),C}(process.version),!(f>=20302&&f<40204||f>=5e4&&f<50100||f>=50600&&f<60200)&&process.stdin.isTTY)process.stdin.pause(),eh=process.stdin.fd,JC=process.stdin._handle;else try{eh=p().open("CONIN$",c.O_RDWR,parseInt("0666",8)),JC=new $Be(eh,!0)}catch{}if(process.stdout.isTTY)$u=process.stdout.fd;else{try{$u=ii.openSync("\\\\.\\CON","w")}catch{}if(typeof $u!="number")try{$u=p().open("CONOUT$",c.O_RDWR,parseInt("0666",8))}catch{}}}else{if(process.stdin.isTTY){process.stdin.pause();try{eh=ii.openSync("/dev/tty","r"),JC=process.stdin._handle}catch{}}else try{eh=ii.openSync("/dev/tty","r"),JC=new $Be(eh,!1)}catch{}if(process.stdout.isTTY)$u=process.stdout.fd;else try{$u=ii.openSync("/dev/tty","w")}catch{}}}(),function(){var n,c,f=!t.hideEchoBack&&!t.keyIn,p,h,E,C,S;fF="";function P(I){return I===eve?!0:JC.setRawMode(I)!==0?!1:(eve=I,!0)}if(rve||!JC||typeof $u!="number"&&(t.display||!f)){e=a();return}if(t.display&&(ii.writeSync($u,t.display),t.display=""),!t.displayOnly){if(!P(!f)){e=a();return}for(h=t.keyIn?1:t.bufferSize,p=Buffer.allocUnsafe&&Buffer.alloc?Buffer.alloc(h):new Buffer(h),t.keyIn&&t.limit&&(c=new RegExp("[^"+t.limit+"]","g"+(t.caseSensitive?"":"i")));;){E=0;try{E=ii.readSync(eh,p,0,h)}catch(I){if(I.code!=="EOF"){P(!1),e+=a();return}}if(E>0?(C=p.toString(t.encoding,0,E),fF+=C):(C=` +`,fF+="\0"),C&&typeof(S=(C.match(/^(.*?)[\r\n]/)||[])[1])=="string"&&(C=S,n=!0),C&&(C=C.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"")),C&&c&&(C=C.replace(c,"")),C&&(f||(t.hideEchoBack?t.mask&&ii.writeSync($u,new Array(C.length+1).join(t.mask)):ii.writeSync($u,C)),e+=C),!t.keyIn&&n||t.keyIn&&e.length>=h)break}!f&&!s&&ii.writeSync($u,` +`),P(!1)}}(),t.print&&!s&&t.print(r+(t.displayOnly?"":(t.hideEchoBack?new Array(e.length+1).join(t.mask):e)+` +`),t.encoding),t.displayOnly?"":h9=t.keepWhitespace||t.keyIn?e:e.trim()}function ADt(t,e){var r=[];function s(a){a!=null&&(Array.isArray(a)?a.forEach(s):(!e||e(a))&&r.push(a))}return s(t),r}function g9(t){return t.replace(/[\x00-\x7f]/g,function(e){return"\\x"+("00"+e.charCodeAt().toString(16)).substr(-2)})}function Ks(){var t=Array.prototype.slice.call(arguments),e,r;return t.length&&typeof t[0]=="boolean"&&(r=t.shift(),r&&(e=Object.keys(c9),t.unshift(c9))),t.reduce(function(s,a){return a==null||(a.hasOwnProperty("noEchoBack")&&!a.hasOwnProperty("hideEchoBack")&&(a.hideEchoBack=a.noEchoBack,delete a.noEchoBack),a.hasOwnProperty("noTrim")&&!a.hasOwnProperty("keepWhitespace")&&(a.keepWhitespace=a.noTrim,delete a.noTrim),r||(e=Object.keys(a)),e.forEach(function(n){var c;if(a.hasOwnProperty(n))switch(c=a[n],n){case"mask":case"limitMessage":case"defaultInput":case"encoding":c=c!=null?c+"":"",c&&n!=="limitMessage"&&(c=c.replace(/[\r\n]/g,"")),s[n]=c;break;case"bufferSize":!isNaN(c=parseInt(c,10))&&typeof c=="number"&&(s[n]=c);break;case"displayOnly":case"keyIn":case"hideEchoBack":case"caseSensitive":case"keepWhitespace":case"history":case"cd":s[n]=!!c;break;case"limit":case"trueValue":case"falseValue":s[n]=ADt(c,function(f){var p=typeof f;return p==="string"||p==="number"||p==="function"||f instanceof RegExp}).map(function(f){return typeof f=="string"?f.replace(/[\r\n]/g,""):f});break;case"print":case"phContent":case"preCheck":s[n]=typeof c=="function"?c:void 0;break;case"prompt":case"display":s[n]=c??"";break}})),s},{})}function A9(t,e,r){return e.some(function(s){var a=typeof s;return a==="string"?r?t===s:t.toLowerCase()===s.toLowerCase():a==="number"?parseFloat(t)===s:a==="function"?s(t):s instanceof RegExp?s.test(t):!1})}function d9(t,e){var r=K0.normalize(Pm?(process.env.HOMEDRIVE||"")+(process.env.HOMEPATH||""):process.env.HOME||"").replace(/[\/\\]+$/,"");return t=K0.normalize(t),e?t.replace(/^~(?=\/|\\|$)/,r):t.replace(new RegExp("^"+g9(r)+"(?=\\/|\\\\|$)",Pm?"i":""),"~")}function zC(t,e){var r="(?:\\(([\\s\\S]*?)\\))?(\\w+|.-.)(?:\\(([\\s\\S]*?)\\))?",s=new RegExp("(\\$)?(\\$<"+r+">)","g"),a=new RegExp("(\\$)?(\\$\\{"+r+"\\})","g");function n(c,f,p,h,E,C){var S;return f||typeof(S=e(E))!="string"?p:S?(h||"")+S+(C||""):""}return t.replace(s,n).replace(a,n)}function ive(t,e,r){var s,a=[],n=-1,c=0,f="",p;function h(E,C){return C.length>3?(E.push(C[0]+"..."+C[C.length-1]),p=!0):C.length&&(E=E.concat(C)),E}return s=t.reduce(function(E,C){return E.concat((C+"").split(""))},[]).reduce(function(E,C){var S,P;return e||(C=C.toLowerCase()),S=/^\d$/.test(C)?1:/^[A-Z]$/.test(C)?2:/^[a-z]$/.test(C)?3:0,r&&S===0?f+=C:(P=C.charCodeAt(0),S&&S===n&&P===c+1?a.push(C):(E=h(E,a),a=[C],n=S),c=P),E},[]),s=h(s,a),f&&(s.push(f),p=!0),{values:s,suppressed:p}}function sve(t,e){return t.join(t.length>2?", ":e?" / ":"/")}function ove(t,e){var r,s,a={},n;if(e.phContent&&(r=e.phContent(t,e)),typeof r!="string")switch(t){case"hideEchoBack":case"mask":case"defaultInput":case"caseSensitive":case"keepWhitespace":case"encoding":case"bufferSize":case"history":case"cd":r=e.hasOwnProperty(t)?typeof e[t]=="boolean"?e[t]?"on":"off":e[t]+"":"";break;case"limit":case"trueValue":case"falseValue":s=e[e.hasOwnProperty(t+"Src")?t+"Src":t],e.keyIn?(a=ive(s,e.caseSensitive),s=a.values):s=s.filter(function(c){var f=typeof c;return f==="string"||f==="number"}),r=sve(s,a.suppressed);break;case"limitCount":case"limitCountNotZero":r=e[e.hasOwnProperty("limitSrc")?"limitSrc":"limit"].length,r=r||t!=="limitCountNotZero"?r+"":"";break;case"lastInput":r=h9;break;case"cwd":case"CWD":case"cwdHome":r=process.cwd(),t==="CWD"?r=K0.basename(r):t==="cwdHome"&&(r=d9(r));break;case"date":case"time":case"localeDate":case"localeTime":r=new Date()["to"+t.replace(/^./,function(c){return c.toUpperCase()})+"String"]();break;default:typeof(n=(t.match(/^history_m(\d+)$/)||[])[1])=="string"&&(r=bm[bm.length-n]||"")}return r}function ave(t){var e=/^(.)-(.)$/.exec(t),r="",s,a,n,c;if(!e)return null;for(s=e[1].charCodeAt(0),a=e[2].charCodeAt(0),c=s +And the length must be: $`,trueValue:null,falseValue:null,caseSensitive:!0},e,{history:!1,cd:!1,phContent:function(P){return P==="charlist"?r.text:P==="length"?s+"..."+a:null}}),c,f,p,h,E,C,S;for(e=e||{},c=zC(e.charlist?e.charlist+"":"$",ave),(isNaN(s=parseInt(e.min,10))||typeof s!="number")&&(s=12),(isNaN(a=parseInt(e.max,10))||typeof a!="number")&&(a=24),h=new RegExp("^["+g9(c)+"]{"+s+","+a+"}$"),r=ive([c],n.caseSensitive,!0),r.text=sve(r.values,r.suppressed),f=e.confirmMessage!=null?e.confirmMessage:"Reinput a same one to confirm it: ",p=e.unmatchMessage!=null?e.unmatchMessage:"It differs from first one. Hit only the Enter key if you want to retry from first one.",t==null&&(t="Input new password: "),E=n.limitMessage;!S;)n.limit=h,n.limitMessage=E,C=$r.question(t,n),n.limit=[C,""],n.limitMessage=p,S=$r.question(f,n);return C};function uve(t,e,r){var s;function a(n){return s=r(n),!isNaN(s)&&typeof s=="number"}return $r.question(t,Ks({limitMessage:"Input valid number, please."},e,{limit:a,cd:!1})),s}$r.questionInt=function(t,e){return uve(t,e,function(r){return parseInt(r,10)})};$r.questionFloat=function(t,e){return uve(t,e,parseFloat)};$r.questionPath=function(t,e){var r,s="",a=Ks({hideEchoBack:!1,limitMessage:`$Input valid path, please.$<( Min:)min>$<( Max:)max>`,history:!0,cd:!0},e,{keepWhitespace:!1,limit:function(n){var c,f,p;n=d9(n,!0),s="";function h(E){E.split(/\/|\\/).reduce(function(C,S){var P=K0.resolve(C+=S+K0.sep);if(!ii.existsSync(P))ii.mkdirSync(P);else if(!ii.statSync(P).isDirectory())throw new Error("Non directory already exists: "+P);return C},"")}try{if(c=ii.existsSync(n),r=c?ii.realpathSync(n):K0.resolve(n),!e.hasOwnProperty("exists")&&!c||typeof e.exists=="boolean"&&e.exists!==c)return s=(c?"Already exists":"No such file or directory")+": "+r,!1;if(!c&&e.create&&(e.isDirectory?h(r):(h(K0.dirname(r)),ii.closeSync(ii.openSync(r,"w"))),r=ii.realpathSync(r)),c&&(e.min||e.max||e.isFile||e.isDirectory)){if(f=ii.statSync(r),e.isFile&&!f.isFile())return s="Not file: "+r,!1;if(e.isDirectory&&!f.isDirectory())return s="Not directory: "+r,!1;if(e.min&&f.size<+e.min||e.max&&f.size>+e.max)return s="Size "+f.size+" is out of range: "+r,!1}if(typeof e.validate=="function"&&(p=e.validate(r))!==!0)return typeof p=="string"&&(s=p),!1}catch(E){return s=E+"",!1}return!0},phContent:function(n){return n==="error"?s:n!=="min"&&n!=="max"?null:e.hasOwnProperty(n)?e[n]+"":""}});return e=e||{},t==null&&(t='Input path (you can "cd" and "pwd"): '),$r.question(t,a),r};function fve(t,e){var r={},s={};return typeof t=="object"?(Object.keys(t).forEach(function(a){typeof t[a]=="function"&&(s[e.caseSensitive?a:a.toLowerCase()]=t[a])}),r.preCheck=function(a){var n;return r.args=p9(a),n=r.args[0]||"",e.caseSensitive||(n=n.toLowerCase()),r.hRes=n!=="_"&&s.hasOwnProperty(n)?s[n].apply(a,r.args.slice(1)):s.hasOwnProperty("_")?s._.apply(a,r.args):null,{res:a,forceNext:!1}},s.hasOwnProperty("_")||(r.limit=function(){var a=r.args[0]||"";return e.caseSensitive||(a=a.toLowerCase()),s.hasOwnProperty(a)})):r.preCheck=function(a){return r.args=p9(a),r.hRes=typeof t=="function"?t.apply(a,r.args):!0,{res:a,forceNext:!1}},r}$r.promptCL=function(t,e){var r=Ks({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},e),s=fve(t,r);return r.limit=s.limit,r.preCheck=s.preCheck,$r.prompt(r),s.args};$r.promptLoop=function(t,e){for(var r=Ks({hideEchoBack:!1,trueValue:null,falseValue:null,caseSensitive:!1,history:!0},e);!t($r.prompt(r)););};$r.promptCLLoop=function(t,e){var r=Ks({hideEchoBack:!1,limitMessage:"Requested command is not available.",caseSensitive:!1,history:!0},e),s=fve(t,r);for(r.limit=s.limit,r.preCheck=s.preCheck;$r.prompt(r),!s.hRes;);};$r.promptSimShell=function(t){return $r.prompt(Ks({hideEchoBack:!1,history:!0},t,{prompt:function(){return Pm?"$>":(process.env.USER||"")+(process.env.HOSTNAME?"@"+process.env.HOSTNAME.replace(/\..*$/,""):"")+":$$ "}()}))};function Ave(t,e,r){var s;return t==null&&(t="Are you sure? "),(!e||e.guide!==!1)&&(t+="")&&(t=t.replace(/\s*:?\s*$/,"")+" [y/n]: "),s=$r.keyIn(t,Ks(e,{hideEchoBack:!1,limit:r,trueValue:"y",falseValue:"n",caseSensitive:!1})),typeof s=="boolean"?s:""}$r.keyInYN=function(t,e){return Ave(t,e)};$r.keyInYNStrict=function(t,e){return Ave(t,e,"yn")};$r.keyInPause=function(t,e){t==null&&(t="Continue..."),(!e||e.guide!==!1)&&(t+="")&&(t=t.replace(/\s+$/,"")+" (Hit any key)"),$r.keyIn(t,Ks({limit:null},e,{hideEchoBack:!0,mask:""}))};$r.keyInSelect=function(t,e,r){var s=Ks({hideEchoBack:!1},r,{trueValue:null,falseValue:null,caseSensitive:!1,phContent:function(p){return p==="itemsCount"?t.length+"":p==="firstItem"?(t[0]+"").trim():p==="lastItem"?(t[t.length-1]+"").trim():null}}),a="",n={},c=49,f=` +`;if(!Array.isArray(t)||!t.length||t.length>35)throw"`items` must be Array (max length: 35).";return t.forEach(function(p,h){var E=String.fromCharCode(c);a+=E,n[E]=h,f+="["+E+"] "+(p+"").trim()+` +`,c=c===57?97:c+1}),(!r||r.cancel!==!1)&&(a+="0",n[0]=-1,f+="[0] "+(r&&r.cancel!=null&&typeof r.cancel!="boolean"?(r.cancel+"").trim():"CANCEL")+` +`),s.limit=a,f+=` +`,e==null&&(e="Choose one from list: "),(e+="")&&((!r||r.guide!==!1)&&(e=e.replace(/\s*:?\s*$/,"")+" [$]: "),f+=e),n[$r.keyIn(f,s).toLowerCase()]};$r.getRawInput=function(){return fF};function wS(t,e){var r;return e.length&&(r={},r[t]=e[0]),$r.setDefaultOptions(r)[t]}$r.setPrint=function(){return wS("print",arguments)};$r.setPrompt=function(){return wS("prompt",arguments)};$r.setEncoding=function(){return wS("encoding",arguments)};$r.setMask=function(){return wS("mask",arguments)};$r.setBufferSize=function(){return wS("bufferSize",arguments)}});var m9=L((vfr,tc)=>{(function(){var t={major:0,minor:2,patch:66,status:"beta"};tau_file_system={files:{},open:function(w,b,y){var F=tau_file_system.files[w];if(!F){if(y==="read")return null;F={path:w,text:"",type:b,get:function(z,Z){return Z===this.text.length||Z>this.text.length?"end_of_file":this.text.substring(Z,Z+z)},put:function(z,Z){return Z==="end_of_file"?(this.text+=z,!0):Z==="past_end_of_file"?null:(this.text=this.text.substring(0,Z)+z+this.text.substring(Z+z.length),!0)},get_byte:function(z){if(z==="end_of_stream")return-1;var Z=Math.floor(z/2);if(this.text.length<=Z)return-1;var $=n(this.text[Math.floor(z/2)],0);return z%2===0?$&255:$/256>>>0},put_byte:function(z,Z){var $=Z==="end_of_stream"?this.text.length:Math.floor(Z/2);if(this.text.length<$)return null;var oe=this.text.length===$?-1:n(this.text[Math.floor(Z/2)],0);return Z%2===0?(oe=oe/256>>>0,oe=(oe&255)<<8|z&255):(oe=oe&255,oe=(z&255)<<8|oe&255),this.text.length===$?this.text+=c(oe):this.text=this.text.substring(0,$)+c(oe)+this.text.substring($+1),!0},flush:function(){return!0},close:function(){var z=tau_file_system.files[this.path];return z?!0:null}},tau_file_system.files[w]=F}return y==="write"&&(F.text=""),F}},tau_user_input={buffer:"",get:function(w,b){for(var y;tau_user_input.buffer.length\?\@\^\~\\]+|'(?:[^']*?(?:\\(?:x?\d+)?\\)*(?:'')*(?:\\')*)*')/,number:/^(?:0o[0-7]+|0x[0-9a-fA-F]+|0b[01]+|0'(?:''|\\[abfnrtv\\'"`]|\\x?\d+\\|[^\\])|\d+(?:\.\d+(?:[eE][+-]?\d+)?)?)/,string:/^(?:"([^"]|""|\\")*"|`([^`]|``|\\`)*`)/,l_brace:/^(?:\[)/,r_brace:/^(?:\])/,l_bracket:/^(?:\{)/,r_bracket:/^(?:\})/,bar:/^(?:\|)/,l_paren:/^(?:\()/,r_paren:/^(?:\))/};function N(w,b){return w.get_flag("char_conversion").id==="on"?b.replace(/./g,function(y){return w.get_char_conversion(y)}):b}function U(w){this.thread=w,this.text="",this.tokens=[]}U.prototype.set_last_tokens=function(w){return this.tokens=w},U.prototype.new_text=function(w){this.text=w,this.tokens=[]},U.prototype.get_tokens=function(w){var b,y=0,F=0,z=0,Z=[],$=!1;if(w){var oe=this.tokens[w-1];y=oe.len,b=N(this.thread,this.text.substr(oe.len)),F=oe.line,z=oe.start}else b=this.text;if(/^\s*$/.test(b))return null;for(;b!=="";){var xe=[],Te=!1;if(/^\n/.exec(b)!==null){F++,z=0,y++,b=b.replace(/\n/,""),$=!0;continue}for(var lt in R)if(R.hasOwnProperty(lt)){var Et=R[lt].exec(b);Et&&xe.push({value:Et[0],name:lt,matches:Et})}if(!xe.length)return this.set_last_tokens([{value:b,matches:[],name:"lexical",line:F,start:z}]);var oe=r(xe,function(Pr,Ir){return Pr.value.length>=Ir.value.length?Pr:Ir});switch(oe.start=z,oe.line=F,b=b.replace(oe.value,""),z+=oe.value.length,y+=oe.value.length,oe.name){case"atom":oe.raw=oe.value,oe.value.charAt(0)==="'"&&(oe.value=S(oe.value.substr(1,oe.value.length-2),"'"),oe.value===null&&(oe.name="lexical",oe.value="unknown escape sequence"));break;case"number":oe.float=oe.value.substring(0,2)!=="0x"&&oe.value.match(/[.eE]/)!==null&&oe.value!=="0'.",oe.value=I(oe.value),oe.blank=Te;break;case"string":var qt=oe.value.charAt(0);oe.value=S(oe.value.substr(1,oe.value.length-2),qt),oe.value===null&&(oe.name="lexical",oe.value="unknown escape sequence");break;case"whitespace":var ir=Z[Z.length-1];ir&&(ir.space=!0),Te=!0;continue;case"r_bracket":Z.length>0&&Z[Z.length-1].name==="l_bracket"&&(oe=Z.pop(),oe.name="atom",oe.value="{}",oe.raw="{}",oe.space=!1);break;case"r_brace":Z.length>0&&Z[Z.length-1].name==="l_brace"&&(oe=Z.pop(),oe.name="atom",oe.value="[]",oe.raw="[]",oe.space=!1);break}oe.len=y,Z.push(oe),Te=!1}var Pt=this.set_last_tokens(Z);return Pt.length===0?null:Pt};function W(w,b,y,F,z){if(!b[y])return{type:f,value:x.error.syntax(b[y-1],"expression expected",!0)};var Z;if(F==="0"){var $=b[y];switch($.name){case"number":return{type:p,len:y+1,value:new x.type.Num($.value,$.float)};case"variable":return{type:p,len:y+1,value:new x.type.Var($.value)};case"string":var oe;switch(w.get_flag("double_quotes").id){case"atom":oe=new j($.value,[]);break;case"codes":oe=new j("[]",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(".",[new x.type.Num(n($.value,xe),!1),oe]);break;case"chars":oe=new j("[]",[]);for(var xe=$.value.length-1;xe>=0;xe--)oe=new j(".",[new x.type.Term($.value.charAt(xe),[]),oe]);break}return{type:p,len:y+1,value:oe};case"l_paren":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name==="r_paren"?(Pt.len++,Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],") or operator expected",!b[Pt.len])};case"l_bracket":var Pt=W(w,b,y+1,w.__get_max_priority(),!0);return Pt.type!==p?Pt:b[Pt.len]&&b[Pt.len].name==="r_bracket"?(Pt.len++,Pt.value=new j("{}",[Pt.value]),Pt):{type:f,derived:!0,value:x.error.syntax(b[Pt.len]?b[Pt.len]:b[Pt.len-1],"} or operator expected",!b[Pt.len])}}var Te=te(w,b,y,z);return Te.type===p||Te.derived||(Te=ie(w,b,y),Te.type===p||Te.derived)?Te:{type:f,derived:!1,value:x.error.syntax(b[y],"unexpected token")}}var lt=w.__get_max_priority(),Et=w.__get_next_priority(F),qt=y;if(b[y].name==="atom"&&b[y+1]&&(b[y].space||b[y+1].name!=="l_paren")){var $=b[y++],ir=w.__lookup_operator_classes(F,$.value);if(ir&&ir.indexOf("fy")>-1){var Pt=W(w,b,y,F,z);if(Pt.type!==f)return $.value==="-"&&!$.space&&x.type.is_number(Pt.value)?{value:new x.type.Num(-Pt.value.value,Pt.value.is_float),len:Pt.len,type:p}:{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};Z=Pt}else if(ir&&ir.indexOf("fx")>-1){var Pt=W(w,b,y,Et,z);if(Pt.type!==f)return{value:new x.type.Term($.value,[Pt.value]),len:Pt.len,type:p};Z=Pt}}y=qt;var Pt=W(w,b,y,Et,z);if(Pt.type===p){y=Pt.len;var $=b[y];if(b[y]&&(b[y].name==="atom"&&w.__lookup_operator_classes(F,$.value)||b[y].name==="bar"&&w.__lookup_operator_classes(F,"|"))){var gn=Et,Pr=F,ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf("xf")>-1)return{value:new x.type.Term($.value,[Pt.value]),len:++Pt.len,type:p};if(ir.indexOf("xfx")>-1){var Ir=W(w,b,y+1,gn,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(ir.indexOf("xfy")>-1){var Ir=W(w,b,y+1,Pr,z);return Ir.type===p?{value:new x.type.Term($.value,[Pt.value,Ir.value]),len:Ir.len,type:p}:(Ir.derived=!0,Ir)}else if(Pt.type!==f)for(;;){y=Pt.len;var $=b[y];if($&&$.name==="atom"&&w.__lookup_operator_classes(F,$.value)){var ir=w.__lookup_operator_classes(F,$.value);if(ir.indexOf("yf")>-1)Pt={value:new x.type.Term($.value,[Pt.value]),len:++y,type:p};else if(ir.indexOf("yfx")>-1){var Ir=W(w,b,++y,gn,z);if(Ir.type===f)return Ir.derived=!0,Ir;y=Ir.len,Pt={value:new x.type.Term($.value,[Pt.value,Ir.value]),len:y,type:p}}else break}else break}}else Z={type:f,value:x.error.syntax(b[Pt.len-1],"operator expected")};return Pt}return Pt}function te(w,b,y,F){if(!b[y]||b[y].name==="atom"&&b[y].raw==="."&&!F&&(b[y].space||!b[y+1]||b[y+1].name!=="l_paren"))return{type:f,derived:!1,value:x.error.syntax(b[y-1],"unfounded token")};var z=b[y],Z=[];if(b[y].name==="atom"&&b[y].raw!==","){if(y++,b[y-1].space)return{type:p,len:y,value:new x.type.Term(z.value,Z)};if(b[y]&&b[y].name==="l_paren"){if(b[y+1]&&b[y+1].name==="r_paren")return{type:f,derived:!0,value:x.error.syntax(b[y+1],"argument expected")};var $=W(w,b,++y,"999",!0);if($.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],"argument expected",!b[y])};for(Z.push($.value),y=$.len;b[y]&&b[y].name==="atom"&&b[y].value===",";){if($=W(w,b,y+1,"999",!0),$.type===f)return $.derived?$:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};Z.push($.value),y=$.len}if(b[y]&&b[y].name==="r_paren")y++;else return{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],", or ) expected",!b[y])}}return{type:p,len:y,value:new x.type.Term(z.value,Z)}}return{type:f,derived:!1,value:x.error.syntax(b[y],"term expected")}}function ie(w,b,y){if(!b[y])return{type:f,derived:!1,value:x.error.syntax(b[y-1],"[ expected")};if(b[y]&&b[y].name==="l_brace"){var F=W(w,b,++y,"999",!0),z=[F.value],Z=void 0;if(F.type===f)return b[y]&&b[y].name==="r_brace"?{type:p,len:y+1,value:new x.type.Term("[]",[])}:{type:f,derived:!0,value:x.error.syntax(b[y],"] expected")};for(y=F.len;b[y]&&b[y].name==="atom"&&b[y].value===",";){if(F=W(w,b,y+1,"999",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};z.push(F.value),y=F.len}var $=!1;if(b[y]&&b[y].name==="bar"){if($=!0,F=W(w,b,y+1,"999",!0),F.type===f)return F.derived?F:{type:f,derived:!0,value:x.error.syntax(b[y+1]?b[y+1]:b[y],"argument expected",!b[y+1])};Z=F.value,y=F.len}return b[y]&&b[y].name==="r_brace"?{type:p,len:y+1,value:g(z,Z)}:{type:f,derived:!0,value:x.error.syntax(b[y]?b[y]:b[y-1],$?"] expected":", or | or ] expected",!b[y])}}return{type:f,derived:!1,value:x.error.syntax(b[y],"list expected")}}function Ae(w,b,y){var F=b[y].line,z=W(w,b,y,w.__get_max_priority(),!1),Z=null,$;if(z.type!==f)if(y=z.len,b[y]&&b[y].name==="atom"&&b[y].raw===".")if(y++,x.type.is_term(z.value)){if(z.value.indicator===":-/2"?(Z=new x.type.Rule(z.value.args[0],Ce(z.value.args[1])),$={value:Z,len:y,type:p}):z.value.indicator==="-->/2"?(Z=pe(new x.type.Rule(z.value.args[0],z.value.args[1]),w),Z.body=Ce(Z.body),$={value:Z,len:y,type:x.type.is_rule(Z)?p:f}):(Z=new x.type.Rule(z.value,null),$={value:Z,len:y,type:p}),Z){var oe=Z.singleton_variables();oe.length>0&&w.throw_warning(x.warning.singleton(oe,Z.head.indicator,F))}return $}else return{type:f,value:x.error.syntax(b[y],"callable expected")};else return{type:f,value:x.error.syntax(b[y]?b[y]:b[y-1],". or operator expected")};return z}function ce(w,b,y){y=y||{},y.from=y.from?y.from:"$tau-js",y.reconsult=y.reconsult!==void 0?y.reconsult:!0;var F=new U(w),z={},Z;F.new_text(b);var $=0,oe=F.get_tokens($);do{if(oe===null||!oe[$])break;var xe=Ae(w,oe,$);if(xe.type===f)return new j("throw",[xe.value]);if(xe.value.body===null&&xe.value.head.indicator==="?-/1"){var Te=new it(w.session);Te.add_goal(xe.value.head.args[0]),Te.answer(function(Et){x.type.is_error(Et)?w.throw_warning(Et.args[0]):(Et===!1||Et===null)&&w.throw_warning(x.warning.failed_goal(xe.value.head.args[0],xe.len))}),$=xe.len;var lt=!0}else if(xe.value.body===null&&xe.value.head.indicator===":-/1"){var lt=w.run_directive(xe.value.head.args[0]);$=xe.len,xe.value.head.args[0].indicator==="char_conversion/2"&&(oe=F.get_tokens($),$=0)}else{Z=xe.value.head.indicator,y.reconsult!==!1&&z[Z]!==!0&&!w.is_multifile_predicate(Z)&&(w.session.rules[Z]=a(w.session.rules[Z]||[],function(qt){return qt.dynamic}),z[Z]=!0);var lt=w.add_rule(xe.value,y);$=xe.len}if(!lt)return lt}while(!0);return!0}function me(w,b){var y=new U(w);y.new_text(b);var F=0;do{var z=y.get_tokens(F);if(z===null)break;var Z=W(w,z,0,w.__get_max_priority(),!1);if(Z.type!==f){var $=Z.len,oe=$;if(z[$]&&z[$].name==="atom"&&z[$].raw===".")w.add_goal(Ce(Z.value));else{var xe=z[$];return new j("throw",[x.error.syntax(xe||z[$-1],". or operator expected",!xe)])}F=Z.len+1}else return new j("throw",[Z.value])}while(!0);return!0}function pe(w,b){w=w.rename(b);var y=b.next_free_variable(),F=Be(w.body,y,b);return F.error?F.value:(w.body=F.value,w.head.args=w.head.args.concat([y,F.variable]),w.head=new j(w.head.id,w.head.args),w)}function Be(w,b,y){var F;if(x.type.is_term(w)&&w.indicator==="!/0")return{value:w,variable:b,error:!1};if(x.type.is_term(w)&&w.indicator===",/2"){var z=Be(w.args[0],b,y);if(z.error)return z;var Z=Be(w.args[1],z.variable,y);return Z.error?Z:{value:new j(",",[z.value,Z.value]),variable:Z.variable,error:!1}}else{if(x.type.is_term(w)&&w.indicator==="{}/1")return{value:w.args[0],variable:b,error:!1};if(x.type.is_empty_list(w))return{value:new j("true",[]),variable:b,error:!1};if(x.type.is_list(w)){F=y.next_free_variable();for(var $=w,oe;$.indicator==="./2";)oe=$,$=$.args[1];return x.type.is_variable($)?{value:x.error.instantiation("DCG"),variable:b,error:!0}:x.type.is_empty_list($)?(oe.args[1]=F,{value:new j("=",[b,w]),variable:F,error:!1}):{value:x.error.type("list",w,"DCG"),variable:b,error:!0}}else return x.type.is_callable(w)?(F=y.next_free_variable(),w.args=w.args.concat([b,F]),w=new j(w.id,w.args),{value:w,variable:F,error:!1}):{value:x.error.type("callable",w,"DCG"),variable:b,error:!0}}}function Ce(w){return x.type.is_variable(w)?new j("call",[w]):x.type.is_term(w)&&[",/2",";/2","->/2"].indexOf(w.indicator)!==-1?new j(w.id,[Ce(w.args[0]),Ce(w.args[1])]):w}function g(w,b){for(var y=b||new x.type.Term("[]",[]),F=w.length-1;F>=0;F--)y=new x.type.Term(".",[w[F],y]);return y}function we(w,b){for(var y=w.length-1;y>=0;y--)w[y]===b&&w.splice(y,1)}function Ee(w){for(var b={},y=[],F=0;F=0;b--)if(w.charAt(b)==="/")return new j("/",[new j(w.substring(0,b)),new Re(parseInt(w.substring(b+1)),!1)])}function De(w){this.id=w}function Re(w,b){this.is_float=b!==void 0?b:parseInt(w)!==w,this.value=this.is_float?w:parseInt(w)}var gt=0;function j(w,b,y){this.ref=y||++gt,this.id=w,this.args=b||[],this.indicator=w+"/"+this.args.length}var rt=0;function Fe(w,b,y,F,z,Z){this.id=rt++,this.stream=w,this.mode=b,this.alias=y,this.type=F!==void 0?F:"text",this.reposition=z!==void 0?z:!0,this.eof_action=Z!==void 0?Z:"eof_code",this.position=this.mode==="append"?"end_of_stream":0,this.output=this.mode==="write"||this.mode==="append",this.input=this.mode==="read"}function Ne(w){w=w||{},this.links=w}function Pe(w,b,y){b=b||new Ne,y=y||null,this.goal=w,this.substitution=b,this.parent=y}function Ye(w,b,y){this.head=w,this.body=b,this.dynamic=y||!1}function ke(w){w=w===void 0||w<=0?1e3:w,this.rules={},this.src_predicates={},this.rename=0,this.modules=[],this.thread=new it(this),this.total_threads=1,this.renamed_variables={},this.public_predicates={},this.multifile_predicates={},this.limit=w,this.streams={user_input:new Fe(typeof tc<"u"&&tc.exports?nodejs_user_input:tau_user_input,"read","user_input","text",!1,"reset"),user_output:new Fe(typeof tc<"u"&&tc.exports?nodejs_user_output:tau_user_output,"write","user_output","text",!1,"eof_code")},this.file_system=typeof tc<"u"&&tc.exports?nodejs_file_system:tau_file_system,this.standard_input=this.streams.user_input,this.standard_output=this.streams.user_output,this.current_input=this.streams.user_input,this.current_output=this.streams.user_output,this.format_success=function(b){return b.substitution},this.format_error=function(b){return b.goal},this.flag={bounded:x.flag.bounded.value,max_integer:x.flag.max_integer.value,min_integer:x.flag.min_integer.value,integer_rounding_function:x.flag.integer_rounding_function.value,char_conversion:x.flag.char_conversion.value,debug:x.flag.debug.value,max_arity:x.flag.max_arity.value,unknown:x.flag.unknown.value,double_quotes:x.flag.double_quotes.value,occurs_check:x.flag.occurs_check.value,dialect:x.flag.dialect.value,version_data:x.flag.version_data.value,nodejs:x.flag.nodejs.value},this.__loaded_modules=[],this.__char_conversion={},this.__operators={1200:{":-":["fx","xfx"],"-->":["xfx"],"?-":["fx"]},1100:{";":["xfy"]},1050:{"->":["xfy"]},1e3:{",":["xfy"]},900:{"\\+":["fy"]},700:{"=":["xfx"],"\\=":["xfx"],"==":["xfx"],"\\==":["xfx"],"@<":["xfx"],"@=<":["xfx"],"@>":["xfx"],"@>=":["xfx"],"=..":["xfx"],is:["xfx"],"=:=":["xfx"],"=\\=":["xfx"],"<":["xfx"],"=<":["xfx"],">":["xfx"],">=":["xfx"]},600:{":":["xfy"]},500:{"+":["yfx"],"-":["yfx"],"/\\":["yfx"],"\\/":["yfx"]},400:{"*":["yfx"],"/":["yfx"],"//":["yfx"],rem:["yfx"],mod:["yfx"],"<<":["yfx"],">>":["yfx"]},200:{"**":["xfx"],"^":["xfy"],"-":["fy"],"+":["fy"],"\\":["fy"]}}}function it(w){this.epoch=Date.now(),this.session=w,this.session.total_threads++,this.total_steps=0,this.cpu_time=0,this.cpu_time_last=0,this.points=[],this.debugger=!1,this.debugger_states=[],this.level="top_level/0",this.__calls=[],this.current_limit=this.session.limit,this.warnings=[]}function _e(w,b,y){this.id=w,this.rules=b,this.exports=y,x.module[w]=this}_e.prototype.exports_predicate=function(w){return this.exports.indexOf(w)!==-1},De.prototype.unify=function(w,b){if(b&&e(w.variables(),this.id)!==-1&&!x.type.is_variable(w))return null;var y={};return y[this.id]=w,new Ne(y)},Re.prototype.unify=function(w,b){return x.type.is_number(w)&&this.value===w.value&&this.is_float===w.is_float?new Ne:null},j.prototype.unify=function(w,b){if(x.type.is_term(w)&&this.indicator===w.indicator){for(var y=new Ne,F=0;F=0){var F=this.args[0].value,z=Math.floor(F/26),Z=F%26;return"ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Z]+(z!==0?z:"")}switch(this.indicator){case"[]/0":case"{}/0":case"!/0":return this.id;case"{}/1":return"{"+this.args[0].toString(w)+"}";case"./2":for(var $="["+this.args[0].toString(w),oe=this.args[1];oe.indicator==="./2";)$+=", "+oe.args[0].toString(w),oe=oe.args[1];return oe.indicator!=="[]/0"&&($+="|"+oe.toString(w)),$+="]",$;case",/2":return"("+this.args[0].toString(w)+", "+this.args[1].toString(w)+")";default:var xe=this.id,Te=w.session?w.session.lookup_operator(this.id,this.args.length):null;if(w.session===void 0||w.ignore_ops||Te===null)return w.quoted&&!/^(!|,|;|[a-z][0-9a-zA-Z_]*)$/.test(xe)&&xe!=="{}"&&xe!=="[]"&&(xe="'"+P(xe)+"'"),xe+(this.args.length?"("+s(this.args,function(ir){return ir.toString(w)}).join(", ")+")":"");var lt=Te.priority>b.priority||Te.priority===b.priority&&(Te.class==="xfy"&&this.indicator!==b.indicator||Te.class==="yfx"&&this.indicator!==b.indicator||this.indicator===b.indicator&&Te.class==="yfx"&&y==="right"||this.indicator===b.indicator&&Te.class==="xfy"&&y==="left");Te.indicator=this.indicator;var Et=lt?"(":"",qt=lt?")":"";return this.args.length===0?"("+this.id+")":["fy","fx"].indexOf(Te.class)!==-1?Et+xe+" "+this.args[0].toString(w,Te)+qt:["yf","xf"].indexOf(Te.class)!==-1?Et+this.args[0].toString(w,Te)+" "+xe+qt:Et+this.args[0].toString(w,Te,"left")+" "+this.id+" "+this.args[1].toString(w,Te,"right")+qt}},Fe.prototype.toString=function(w){return"("+this.id+")"},Ne.prototype.toString=function(w){var b="{";for(var y in this.links)this.links.hasOwnProperty(y)&&(b!=="{"&&(b+=", "),b+=y+"/"+this.links[y].toString(w));return b+="}",b},Pe.prototype.toString=function(w){return this.goal===null?"<"+this.substitution.toString(w)+">":"<"+this.goal.toString(w)+", "+this.substitution.toString(w)+">"},Ye.prototype.toString=function(w){return this.body?this.head.toString(w)+" :- "+this.body.toString(w)+".":this.head.toString(w)+"."},ke.prototype.toString=function(w){for(var b="",y=0;y=0;z--)F=new j(".",[b[z],F]);return F}return new j(this.id,s(this.args,function(Z){return Z.apply(w)}),this.ref)},Fe.prototype.apply=function(w){return this},Ye.prototype.apply=function(w){return new Ye(this.head.apply(w),this.body!==null?this.body.apply(w):null)},Ne.prototype.apply=function(w){var b,y={};for(b in this.links)this.links.hasOwnProperty(b)&&(y[b]=this.links[b].apply(w));return new Ne(y)},j.prototype.select=function(){for(var w=this;w.indicator===",/2";)w=w.args[0];return w},j.prototype.replace=function(w){return this.indicator===",/2"?this.args[0].indicator===",/2"?new j(",",[this.args[0].replace(w),this.args[1]]):w===null?this.args[1]:new j(",",[w,this.args[1]]):w},j.prototype.search=function(w){if(x.type.is_term(w)&&w.ref!==void 0&&this.ref===w.ref)return!0;for(var b=0;bb&&F0&&(b=this.head_point().substitution.domain());e(b,x.format_variable(this.session.rename))!==-1;)this.session.rename++;if(w.id==="_")return new De(x.format_variable(this.session.rename));this.session.renamed_variables[w.id]=x.format_variable(this.session.rename)}return new De(this.session.renamed_variables[w.id])},ke.prototype.next_free_variable=function(){return this.thread.next_free_variable()},it.prototype.next_free_variable=function(){this.session.rename++;var w=[];for(this.points.length>0&&(w=this.head_point().substitution.domain());e(w,x.format_variable(this.session.rename))!==-1;)this.session.rename++;return new De(x.format_variable(this.session.rename))},ke.prototype.is_public_predicate=function(w){return!this.public_predicates.hasOwnProperty(w)||this.public_predicates[w]===!0},it.prototype.is_public_predicate=function(w){return this.session.is_public_predicate(w)},ke.prototype.is_multifile_predicate=function(w){return this.multifile_predicates.hasOwnProperty(w)&&this.multifile_predicates[w]===!0},it.prototype.is_multifile_predicate=function(w){return this.session.is_multifile_predicate(w)},ke.prototype.prepend=function(w){return this.thread.prepend(w)},it.prototype.prepend=function(w){for(var b=w.length-1;b>=0;b--)this.points.push(w[b])},ke.prototype.success=function(w,b){return this.thread.success(w,b)},it.prototype.success=function(w,y){var y=typeof y>"u"?w:y;this.prepend([new Pe(w.goal.replace(null),w.substitution,y)])},ke.prototype.throw_error=function(w){return this.thread.throw_error(w)},it.prototype.throw_error=function(w){this.prepend([new Pe(new j("throw",[w]),new Ne,null,null)])},ke.prototype.step_rule=function(w,b){return this.thread.step_rule(w,b)},it.prototype.step_rule=function(w,b){var y=b.indicator;if(w==="user"&&(w=null),w===null&&this.session.rules.hasOwnProperty(y))return this.session.rules[y];for(var F=w===null?this.session.modules:e(this.session.modules,w)===-1?[]:[w],z=0;z1)&&this.again()},ke.prototype.answers=function(w,b,y){return this.thread.answers(w,b,y)},it.prototype.answers=function(w,b,y){var F=b||1e3,z=this;if(b<=0){y&&y();return}this.answer(function(Z){w(Z),Z!==!1?setTimeout(function(){z.answers(w,b-1,y)},1):y&&y()})},ke.prototype.again=function(w){return this.thread.again(w)},it.prototype.again=function(w){for(var b,y=Date.now();this.__calls.length>0;){for(this.warnings=[],w!==!1&&(this.current_limit=this.session.limit);this.current_limit>0&&this.points.length>0&&this.head_point().goal!==null&&!x.type.is_error(this.head_point().goal);)if(this.current_limit--,this.step()===!0)return;var F=Date.now();this.cpu_time_last=F-y,this.cpu_time+=this.cpu_time_last;var z=this.__calls.shift();this.current_limit<=0?z(null):this.points.length===0?z(!1):x.type.is_error(this.head_point().goal)?(b=this.session.format_error(this.points.pop()),this.points=[],z(b)):(this.debugger&&this.debugger_states.push(this.head_point()),b=this.session.format_success(this.points.pop()),z(b))}},ke.prototype.unfold=function(w){if(w.body===null)return!1;var b=w.head,y=w.body,F=y.select(),z=new it(this),Z=[];z.add_goal(F),z.step();for(var $=z.points.length-1;$>=0;$--){var oe=z.points[$],xe=b.apply(oe.substitution),Te=y.replace(oe.goal);Te!==null&&(Te=Te.apply(oe.substitution)),Z.push(new Ye(xe,Te))}var lt=this.rules[b.indicator],Et=e(lt,w);return Z.length>0&&Et!==-1?(lt.splice.apply(lt,[Et,1].concat(Z)),!0):!1},it.prototype.unfold=function(w){return this.session.unfold(w)},De.prototype.interpret=function(w){return x.error.instantiation(w.level)},Re.prototype.interpret=function(w){return this},j.prototype.interpret=function(w){return x.type.is_unitary_list(this)?this.args[0].interpret(w):x.operate(w,this)},De.prototype.compare=function(w){return this.idw.id?1:0},Re.prototype.compare=function(w){if(this.value===w.value&&this.is_float===w.is_float)return 0;if(this.valuew.value)return 1},j.prototype.compare=function(w){if(this.args.lengthw.args.length||this.args.length===w.args.length&&this.id>w.id)return 1;for(var b=0;bF)return 1;if(w.constructor===Re){if(w.is_float&&b.is_float)return 0;if(w.is_float)return-1;if(b.is_float)return 1}return 0},is_substitution:function(w){return w instanceof Ne},is_state:function(w){return w instanceof Pe},is_rule:function(w){return w instanceof Ye},is_variable:function(w){return w instanceof De},is_stream:function(w){return w instanceof Fe},is_anonymous_var:function(w){return w instanceof De&&w.id==="_"},is_callable:function(w){return w instanceof j},is_number:function(w){return w instanceof Re},is_integer:function(w){return w instanceof Re&&!w.is_float},is_float:function(w){return w instanceof Re&&w.is_float},is_term:function(w){return w instanceof j},is_atom:function(w){return w instanceof j&&w.args.length===0},is_ground:function(w){if(w instanceof De)return!1;if(w instanceof j){for(var b=0;b0},is_list:function(w){return w instanceof j&&(w.indicator==="[]/0"||w.indicator==="./2")},is_empty_list:function(w){return w instanceof j&&w.indicator==="[]/0"},is_non_empty_list:function(w){return w instanceof j&&w.indicator==="./2"},is_fully_list:function(w){for(;w instanceof j&&w.indicator==="./2";)w=w.args[1];return w instanceof De||w instanceof j&&w.indicator==="[]/0"},is_instantiated_list:function(w){for(;w instanceof j&&w.indicator==="./2";)w=w.args[1];return w instanceof j&&w.indicator==="[]/0"},is_unitary_list:function(w){return w instanceof j&&w.indicator==="./2"&&w.args[1]instanceof j&&w.args[1].indicator==="[]/0"},is_character:function(w){return w instanceof j&&(w.id.length===1||w.id.length>0&&w.id.length<=2&&n(w.id,0)>=65536)},is_character_code:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=1114111},is_byte:function(w){return w instanceof Re&&!w.is_float&&w.value>=0&&w.value<=255},is_operator:function(w){return w instanceof j&&x.arithmetic.evaluation[w.indicator]},is_directive:function(w){return w instanceof j&&x.directive[w.indicator]!==void 0},is_builtin:function(w){return w instanceof j&&x.predicate[w.indicator]!==void 0},is_error:function(w){return w instanceof j&&w.indicator==="throw/1"},is_predicate_indicator:function(w){return w instanceof j&&w.indicator==="//2"&&w.args[0]instanceof j&&w.args[0].args.length===0&&w.args[1]instanceof Re&&w.args[1].is_float===!1},is_flag:function(w){return w instanceof j&&w.args.length===0&&x.flag[w.id]!==void 0},is_value_flag:function(w,b){if(!x.type.is_flag(w))return!1;for(var y in x.flag[w.id].allowed)if(x.flag[w.id].allowed.hasOwnProperty(y)&&x.flag[w.id].allowed[y].equals(b))return!0;return!1},is_io_mode:function(w){return x.type.is_atom(w)&&["read","write","append"].indexOf(w.id)!==-1},is_stream_option:function(w){return x.type.is_term(w)&&(w.indicator==="alias/1"&&x.type.is_atom(w.args[0])||w.indicator==="reposition/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="type/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="text"||w.args[0].id==="binary")||w.indicator==="eof_action/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="error"||w.args[0].id==="eof_code"||w.args[0].id==="reset"))},is_stream_position:function(w){return x.type.is_integer(w)&&w.value>=0||x.type.is_atom(w)&&(w.id==="end_of_stream"||w.id==="past_end_of_stream")},is_stream_property:function(w){return x.type.is_term(w)&&(w.indicator==="input/0"||w.indicator==="output/0"||w.indicator==="alias/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator==="file_name/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0]))||w.indicator==="position/1"&&(x.type.is_variable(w.args[0])||x.type.is_stream_position(w.args[0]))||w.indicator==="reposition/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false"))||w.indicator==="type/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="text"||w.args[0].id==="binary"))||w.indicator==="mode/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="read"||w.args[0].id==="write"||w.args[0].id==="append"))||w.indicator==="eof_action/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="error"||w.args[0].id==="eof_code"||w.args[0].id==="reset"))||w.indicator==="end_of_stream/1"&&(x.type.is_variable(w.args[0])||x.type.is_atom(w.args[0])&&(w.args[0].id==="at"||w.args[0].id==="past"||w.args[0].id==="not")))},is_streamable:function(w){return w.__proto__.stream!==void 0},is_read_option:function(w){return x.type.is_term(w)&&["variables/1","variable_names/1","singletons/1"].indexOf(w.indicator)!==-1},is_write_option:function(w){return x.type.is_term(w)&&(w.indicator==="quoted/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="ignore_ops/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")||w.indicator==="numbervars/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false"))},is_close_option:function(w){return x.type.is_term(w)&&w.indicator==="force/1"&&x.type.is_atom(w.args[0])&&(w.args[0].id==="true"||w.args[0].id==="false")},is_modifiable_flag:function(w){return x.type.is_flag(w)&&x.flag[w.id].changeable},is_module:function(w){return w instanceof j&&w.indicator==="library/1"&&w.args[0]instanceof j&&w.args[0].args.length===0&&x.module[w.args[0].id]!==void 0}},arithmetic:{evaluation:{"e/0":{type_args:null,type_result:!0,fn:function(w){return Math.E}},"pi/0":{type_args:null,type_result:!0,fn:function(w){return Math.PI}},"tau/0":{type_args:null,type_result:!0,fn:function(w){return 2*Math.PI}},"epsilon/0":{type_args:null,type_result:!0,fn:function(w){return Number.EPSILON}},"+/1":{type_args:null,type_result:null,fn:function(w,b){return w}},"-/1":{type_args:null,type_result:null,fn:function(w,b){return-w}},"\\/1":{type_args:!1,type_result:!1,fn:function(w,b){return~w}},"abs/1":{type_args:null,type_result:null,fn:function(w,b){return Math.abs(w)}},"sign/1":{type_args:null,type_result:null,fn:function(w,b){return Math.sign(w)}},"float_integer_part/1":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},"float_fractional_part/1":{type_args:!0,type_result:!0,fn:function(w,b){return w-parseInt(w)}},"float/1":{type_args:null,type_result:!0,fn:function(w,b){return parseFloat(w)}},"floor/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.floor(w)}},"truncate/1":{type_args:!0,type_result:!1,fn:function(w,b){return parseInt(w)}},"round/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.round(w)}},"ceiling/1":{type_args:!0,type_result:!1,fn:function(w,b){return Math.ceil(w)}},"sin/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.sin(w)}},"cos/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.cos(w)}},"tan/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.tan(w)}},"asin/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.asin(w)}},"acos/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.acos(w)}},"atan/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.atan(w)}},"atan2/2":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.atan2(w,b)}},"exp/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.exp(w)}},"sqrt/1":{type_args:null,type_result:!0,fn:function(w,b){return Math.sqrt(w)}},"log/1":{type_args:null,type_result:!0,fn:function(w,b){return w>0?Math.log(w):x.error.evaluation("undefined",b.__call_indicator)}},"+/2":{type_args:null,type_result:null,fn:function(w,b,y){return w+b}},"-/2":{type_args:null,type_result:null,fn:function(w,b,y){return w-b}},"*/2":{type_args:null,type_result:null,fn:function(w,b,y){return w*b}},"//2":{type_args:null,type_result:!0,fn:function(w,b,y){return b?w/b:x.error.evaluation("zero_division",y.__call_indicator)}},"///2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?parseInt(w/b):x.error.evaluation("zero_division",y.__call_indicator)}},"**/2":{type_args:null,type_result:!0,fn:function(w,b,y){return Math.pow(w,b)}},"^/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.pow(w,b)}},"<>/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w>>b}},"/\\/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w&b}},"\\//2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w|b}},"xor/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return w^b}},"rem/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w%b:x.error.evaluation("zero_division",y.__call_indicator)}},"mod/2":{type_args:!1,type_result:!1,fn:function(w,b,y){return b?w-parseInt(w/b)*b:x.error.evaluation("zero_division",y.__call_indicator)}},"max/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.max(w,b)}},"min/2":{type_args:null,type_result:null,fn:function(w,b,y){return Math.min(w,b)}}}},directive:{"dynamic/1":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_compound(y)||y.indicator!=="//2")w.throw_error(x.error.type("predicate_indicator",y,b.indicator));else if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type("atom",y.args[0],b.indicator));else if(!x.type.is_integer(y.args[1]))w.throw_error(x.error.type("integer",y.args[1],b.indicator));else{var F=b.args[0].args[0].id+"/"+b.args[0].args[1].value;w.session.public_predicates[F]=!0,w.session.rules[F]||(w.session.rules[F]=[])}},"multifile/1":function(w,b){var y=b.args[0];x.type.is_variable(y)?w.throw_error(x.error.instantiation(b.indicator)):!x.type.is_compound(y)||y.indicator!=="//2"?w.throw_error(x.error.type("predicate_indicator",y,b.indicator)):x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1])?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y.args[0])?x.type.is_integer(y.args[1])?w.session.multifile_predicates[b.args[0].args[0].id+"/"+b.args[0].args[1].value]=!0:w.throw_error(x.error.type("integer",y.args[1],b.indicator)):w.throw_error(x.error.type("atom",y.args[0],b.indicator))},"set_prolog_flag/2":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_atom(y)?x.type.is_flag(y)?x.type.is_value_flag(y,F)?x.type.is_modifiable_flag(y)?w.session.flag[y.id]=F:w.throw_error(x.error.permission("modify","flag",y)):w.throw_error(x.error.domain("flag_value",new j("+",[y,F]),b.indicator)):w.throw_error(x.error.domain("prolog_flag",y,b.indicator)):w.throw_error(x.error.type("atom",y,b.indicator))},"use_module/1":function(w,b){var y=b.args[0];if(x.type.is_variable(y))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_term(y))w.throw_error(x.error.type("term",y,b.indicator));else if(x.type.is_module(y)){var F=y.args[0].id;e(w.session.modules,F)===-1&&w.session.modules.push(F)}},"char_conversion/2":function(w,b){var y=b.args[0],F=b.args[1];x.type.is_variable(y)||x.type.is_variable(F)?w.throw_error(x.error.instantiation(b.indicator)):x.type.is_character(y)?x.type.is_character(F)?y.id===F.id?delete w.session.__char_conversion[y.id]:w.session.__char_conversion[y.id]=F.id:w.throw_error(x.error.type("character",F,b.indicator)):w.throw_error(x.error.type("character",y,b.indicator))},"op/3":function(w,b){var y=b.args[0],F=b.args[1],z=b.args[2];if(x.type.is_variable(y)||x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(b.indicator));else if(!x.type.is_integer(y))w.throw_error(x.error.type("integer",y,b.indicator));else if(!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,b.indicator));else if(!x.type.is_atom(z))w.throw_error(x.error.type("atom",z,b.indicator));else if(y.value<0||y.value>1200)w.throw_error(x.error.domain("operator_priority",y,b.indicator));else if(z.id===",")w.throw_error(x.error.permission("modify","operator",z,b.indicator));else if(z.id==="|"&&(y.value<1001||F.id.length!==3))w.throw_error(x.error.permission("modify","operator",z,b.indicator));else if(["fy","fx","yf","xf","xfx","yfx","xfy"].indexOf(F.id)===-1)w.throw_error(x.error.domain("operator_specifier",F,b.indicator));else{var Z={prefix:null,infix:null,postfix:null};for(var $ in w.session.__operators)if(w.session.__operators.hasOwnProperty($)){var oe=w.session.__operators[$][z.id];oe&&(e(oe,"fx")!==-1&&(Z.prefix={priority:$,type:"fx"}),e(oe,"fy")!==-1&&(Z.prefix={priority:$,type:"fy"}),e(oe,"xf")!==-1&&(Z.postfix={priority:$,type:"xf"}),e(oe,"yf")!==-1&&(Z.postfix={priority:$,type:"yf"}),e(oe,"xfx")!==-1&&(Z.infix={priority:$,type:"xfx"}),e(oe,"xfy")!==-1&&(Z.infix={priority:$,type:"xfy"}),e(oe,"yfx")!==-1&&(Z.infix={priority:$,type:"yfx"}))}var xe;switch(F.id){case"fy":case"fx":xe="prefix";break;case"yf":case"xf":xe="postfix";break;default:xe="infix";break}if(((Z.prefix&&xe==="prefix"||Z.postfix&&xe==="postfix"||Z.infix&&xe==="infix")&&Z[xe].type!==F.id||Z.infix&&xe==="postfix"||Z.postfix&&xe==="infix")&&y.value!==0)w.throw_error(x.error.permission("create","operator",z,b.indicator));else return Z[xe]&&(we(w.session.__operators[Z[xe].priority][z.id],F.id),w.session.__operators[Z[xe].priority][z.id].length===0&&delete w.session.__operators[Z[xe].priority][z.id]),y.value>0&&(w.session.__operators[y.value]||(w.session.__operators[y.value.toString()]={}),w.session.__operators[y.value][z.id]||(w.session.__operators[y.value][z.id]=[]),w.session.__operators[y.value][z.id].push(F.id)),!0}}},predicate:{"op/3":function(w,b,y){x.directive["op/3"](w,y)&&w.success(b)},"current_op/3":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2],$=[];for(var oe in w.session.__operators)for(var xe in w.session.__operators[oe])for(var Te=0;Te/2"){var F=w.points,z=w.session.format_success,Z=w.session.format_error;w.session.format_success=function(Te){return Te.substitution},w.session.format_error=function(Te){return Te.goal},w.points=[new Pe(y.args[0].args[0],b.substitution,b)];var $=function(Te){w.points=F,w.session.format_success=z,w.session.format_error=Z,Te===!1?w.prepend([new Pe(b.goal.replace(y.args[1]),b.substitution,b)]):x.type.is_error(Te)?w.throw_error(Te.args[0]):Te===null?(w.prepend([b]),w.__calls.shift()(null)):w.prepend([new Pe(b.goal.replace(y.args[0].args[1]).apply(Te),b.substitution.apply(Te),b)])};w.__calls.unshift($)}else{var oe=new Pe(b.goal.replace(y.args[0]),b.substitution,b),xe=new Pe(b.goal.replace(y.args[1]),b.substitution,b);w.prepend([oe,xe])}},"!/0":function(w,b,y){var F,z,Z=[];for(F=b,z=null;F.parent!==null&&F.parent.goal.search(y);)if(z=F,F=F.parent,F.goal!==null){var $=F.goal.select();if($&&$.id==="call"&&$.search(y)){F=z;break}}for(var oe=w.points.length-1;oe>=0;oe--){for(var xe=w.points[oe],Te=xe.parent;Te!==null&&Te!==F.parent;)Te=Te.parent;Te===null&&Te!==F.parent&&Z.push(xe)}w.points=Z.reverse(),w.success(b)},"\\+/1":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(w.level)):x.type.is_callable(F)?w.prepend([new Pe(b.goal.replace(new j(",",[new j(",",[new j("call",[F]),new j("!",[])]),new j("fail",[])])),b.substitution,b),new Pe(b.goal.replace(null),b.substitution,b)]):w.throw_error(x.error.type("callable",F,w.level))},"->/2":function(w,b,y){var F=b.goal.replace(new j(",",[y.args[0],new j(",",[new j("!"),y.args[1]])]));w.prepend([new Pe(F,b.substitution,b)])},"fail/0":function(w,b,y){},"false/0":function(w,b,y){},"true/0":function(w,b,y){w.success(b)},"call/1":se(1),"call/2":se(2),"call/3":se(3),"call/4":se(4),"call/5":se(5),"call/6":se(6),"call/7":se(7),"call/8":se(8),"once/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("call",[F]),new j("!",[])])),b.substitution,b)])},"forall/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("\\+",[new j(",",[new j("call",[F]),new j("\\+",[new j("call",[z])])])])),b.substitution,b)])},"repeat/0":function(w,b,y){w.prepend([new Pe(b.goal.replace(null),b.substitution,b),b])},"throw/1":function(w,b,y){x.type.is_variable(y.args[0])?w.throw_error(x.error.instantiation(w.level)):w.throw_error(y.args[0])},"catch/3":function(w,b,y){var F=w.points;w.points=[],w.prepend([new Pe(y.args[0],b.substitution,b)]);var z=w.session.format_success,Z=w.session.format_error;w.session.format_success=function(oe){return oe.substitution},w.session.format_error=function(oe){return oe.goal};var $=function(oe){var xe=w.points;if(w.points=F,w.session.format_success=z,w.session.format_error=Z,x.type.is_error(oe)){for(var Te=[],lt=w.points.length-1;lt>=0;lt--){for(var ir=w.points[lt],Et=ir.parent;Et!==null&&Et!==b.parent;)Et=Et.parent;Et===null&&Et!==b.parent&&Te.push(ir)}w.points=Te;var qt=w.get_flag("occurs_check").indicator==="true/0",ir=new Pe,Pt=x.unify(oe.args[0],y.args[1],qt);Pt!==null?(ir.substitution=b.substitution.apply(Pt),ir.goal=b.goal.replace(y.args[2]).apply(Pt),ir.parent=b,w.prepend([ir])):w.throw_error(oe.args[0])}else if(oe!==!1){for(var gn=oe===null?[]:[new Pe(b.goal.apply(oe).replace(null),b.substitution.apply(oe),b)],Pr=[],lt=xe.length-1;lt>=0;lt--){Pr.push(xe[lt]);var Ir=xe[lt].goal!==null?xe[lt].goal.select():null;if(x.type.is_term(Ir)&&Ir.indicator==="!/0")break}var Nr=s(Pr,function(nn){return nn.goal===null&&(nn.goal=new j("true",[])),nn=new Pe(b.goal.replace(new j("catch",[nn.goal,y.args[1],y.args[2]])),b.substitution.apply(nn.substitution),nn.parent),nn.exclude=y.args[0].variables(),nn}).reverse();w.prepend(Nr),w.prepend(gn),oe===null&&(this.current_limit=0,w.__calls.shift()(null))}};w.__calls.unshift($)},"=/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=new Pe,Z=x.unify(y.args[0],y.args[1],F);Z!==null&&(z.goal=b.goal.apply(Z).replace(null),z.substitution=b.substitution.apply(Z),z.parent=b,w.prepend([z]))},"unify_with_occurs_check/2":function(w,b,y){var F=new Pe,z=x.unify(y.args[0],y.args[1],!0);z!==null&&(F.goal=b.goal.apply(z).replace(null),F.substitution=b.substitution.apply(z),F.parent=b,w.prepend([F]))},"\\=/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=x.unify(y.args[0],y.args[1],F);z===null&&w.success(b)},"subsumes_term/2":function(w,b,y){var F=w.get_flag("occurs_check").indicator==="true/0",z=x.unify(y.args[1],y.args[0],F);z!==null&&y.args[1].apply(z).equals(y.args[1])&&w.success(b)},"findall/3":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(z))w.throw_error(x.error.type("callable",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_list(Z))w.throw_error(x.error.type("list",Z,y.indicator));else{var $=w.next_free_variable(),oe=new j(",",[z,new j("=",[$,F])]),xe=w.points,Te=w.session.limit,lt=w.session.format_success;w.session.format_success=function(ir){return ir.substitution},w.add_goal(oe,!0,b);var Et=[],qt=function(ir){if(ir!==!1&&ir!==null&&!x.type.is_error(ir))w.__calls.unshift(qt),Et.push(ir.links[$.id]),w.session.limit=w.current_limit;else if(w.points=xe,w.session.limit=Te,w.session.format_success=lt,x.type.is_error(ir))w.throw_error(ir.args[0]);else if(w.current_limit>0){for(var Pt=new j("[]"),gn=Et.length-1;gn>=0;gn--)Pt=new j(".",[Et[gn],Pt]);w.prepend([new Pe(b.goal.replace(new j("=",[Z,Pt])),b.substitution,b)])}};w.__calls.unshift(qt)}},"bagof/3":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2];if(x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(Z))w.throw_error(x.error.type("callable",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type("list",$,y.indicator));else{var oe=w.next_free_variable(),xe;Z.indicator==="^/2"?(xe=Z.args[0].variables(),Z=Z.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=Z.variables().filter(function(Nr){return e(xe,Nr)===-1}),lt=new j("[]"),Et=Te.length-1;Et>=0;Et--)lt=new j(".",[new De(Te[Et]),lt]);var qt=new j(",",[Z,new j("=",[oe,new j(",",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Nr){return Nr.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Nr){if(Nr!==!1&&Nr!==null&&!x.type.is_error(Nr)){w.__calls.unshift(Ir);var nn=!1,oi=Nr.links[oe.id].args[0],wo=Nr.links[oe.id].args[1];for(var rs in Pr)if(Pr.hasOwnProperty(rs)){var eo=Pr[rs];if(eo.variables.equals(oi)){eo.answers.push(wo),nn=!0;break}}nn||Pr.push({variables:oi,answers:[wo]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Nr))w.throw_error(Nr.args[0]);else if(w.current_limit>0){for(var Bo=[],Hi=0;Hi=0;vo--)to=new j(".",[Nr[vo],to]);Bo.push(new Pe(b.goal.replace(new j(",",[new j("=",[lt,Pr[Hi].variables]),new j("=",[$,to])])),b.substitution,b))}w.prepend(Bo)}};w.__calls.unshift(Ir)}},"setof/3":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2];if(x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(Z))w.throw_error(x.error.type("callable",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_list($))w.throw_error(x.error.type("list",$,y.indicator));else{var oe=w.next_free_variable(),xe;Z.indicator==="^/2"?(xe=Z.args[0].variables(),Z=Z.args[1]):xe=[],xe=xe.concat(z.variables());for(var Te=Z.variables().filter(function(Nr){return e(xe,Nr)===-1}),lt=new j("[]"),Et=Te.length-1;Et>=0;Et--)lt=new j(".",[new De(Te[Et]),lt]);var qt=new j(",",[Z,new j("=",[oe,new j(",",[lt,z])])]),ir=w.points,Pt=w.session.limit,gn=w.session.format_success;w.session.format_success=function(Nr){return Nr.substitution},w.add_goal(qt,!0,b);var Pr=[],Ir=function(Nr){if(Nr!==!1&&Nr!==null&&!x.type.is_error(Nr)){w.__calls.unshift(Ir);var nn=!1,oi=Nr.links[oe.id].args[0],wo=Nr.links[oe.id].args[1];for(var rs in Pr)if(Pr.hasOwnProperty(rs)){var eo=Pr[rs];if(eo.variables.equals(oi)){eo.answers.push(wo),nn=!0;break}}nn||Pr.push({variables:oi,answers:[wo]}),w.session.limit=w.current_limit}else if(w.points=ir,w.session.limit=Pt,w.session.format_success=gn,x.type.is_error(Nr))w.throw_error(Nr.args[0]);else if(w.current_limit>0){for(var Bo=[],Hi=0;Hi=0;vo--)to=new j(".",[Nr[vo],to]);Bo.push(new Pe(b.goal.replace(new j(",",[new j("=",[lt,Pr[Hi].variables]),new j("=",[$,to])])),b.substitution,b))}w.prepend(Bo)}};w.__calls.unshift(Ir)}},"functor/3":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2];if(x.type.is_variable(z)&&(x.type.is_variable(Z)||x.type.is_variable($)))w.throw_error(x.error.instantiation("functor/3"));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type("integer",y.args[2],"functor/3"));else if(!x.type.is_variable(Z)&&!x.type.is_atomic(Z))w.throw_error(x.error.type("atomic",y.args[1],"functor/3"));else if(x.type.is_integer(Z)&&x.type.is_integer($)&&$.value!==0)w.throw_error(x.error.type("atom",y.args[1],"functor/3"));else if(x.type.is_variable(z)){if(y.args[2].value>=0){for(var oe=[],xe=0;xe<$.value;xe++)oe.push(w.next_free_variable());var Te=x.type.is_integer(Z)?Z:new j(Z.id,oe);w.prepend([new Pe(b.goal.replace(new j("=",[z,Te])),b.substitution,b)])}}else{var lt=x.type.is_integer(z)?z:new j(z.id,[]),Et=x.type.is_integer(z)?new Re(0,!1):new Re(z.args.length,!1),qt=new j(",",[new j("=",[lt,Z]),new j("=",[Et,$])]);w.prepend([new Pe(b.goal.replace(qt),b.substitution,b)])}},"arg/3":function(w,b,y){if(x.type.is_variable(y.args[0])||x.type.is_variable(y.args[1]))w.throw_error(x.error.instantiation(y.indicator));else if(y.args[0].value<0)w.throw_error(x.error.domain("not_less_than_zero",y.args[0],y.indicator));else if(!x.type.is_compound(y.args[1]))w.throw_error(x.error.type("compound",y.args[1],y.indicator));else{var F=y.args[0].value;if(F>0&&F<=y.args[1].args.length){var z=new j("=",[y.args[1].args[F-1],y.args[2]]);w.prepend([new Pe(b.goal.replace(z),b.substitution,b)])}}},"=../2":function(w,b,y){var F;if(x.type.is_variable(y.args[0])&&(x.type.is_variable(y.args[1])||x.type.is_non_empty_list(y.args[1])&&x.type.is_variable(y.args[1].args[0])))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_fully_list(y.args[1]))w.throw_error(x.error.type("list",y.args[1],y.indicator));else if(x.type.is_variable(y.args[0])){if(!x.type.is_variable(y.args[1])){var Z=[];for(F=y.args[1].args[1];F.indicator==="./2";)Z.push(F.args[0]),F=F.args[1];x.type.is_variable(y.args[0])&&x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):Z.length===0&&x.type.is_compound(y.args[1].args[0])?w.throw_error(x.error.type("atomic",y.args[1].args[0],y.indicator)):Z.length>0&&(x.type.is_compound(y.args[1].args[0])||x.type.is_number(y.args[1].args[0]))?w.throw_error(x.error.type("atom",y.args[1].args[0],y.indicator)):Z.length===0?w.prepend([new Pe(b.goal.replace(new j("=",[y.args[1].args[0],y.args[0]],b)),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j("=",[new j(y.args[1].args[0].id,Z),y.args[0]])),b.substitution,b)])}}else{if(x.type.is_atomic(y.args[0]))F=new j(".",[y.args[0],new j("[]")]);else{F=new j("[]");for(var z=y.args[0].args.length-1;z>=0;z--)F=new j(".",[y.args[0].args[z],F]);F=new j(".",[new j(y.args[0].id),F])}w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b)])}},"copy_term/2":function(w,b,y){var F=y.args[0].rename(w);w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b.parent)])},"term_variables/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_fully_list(z))w.throw_error(x.error.type("list",z,y.indicator));else{var Z=g(s(Ee(F.variables()),function($){return new De($)}));w.prepend([new Pe(b.goal.replace(new j("=",[z,Z])),b.substitution,b)])}},"clause/2":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_callable(y.args[1]))w.throw_error(x.error.type("callable",y.args[1],y.indicator));else if(w.session.rules[y.args[0].indicator]!==void 0)if(w.is_public_predicate(y.args[0].indicator)){var F=[];for(var z in w.session.rules[y.args[0].indicator])if(w.session.rules[y.args[0].indicator].hasOwnProperty(z)){var Z=w.session.rules[y.args[0].indicator][z];w.session.renamed_variables={},Z=Z.rename(w),Z.body===null&&(Z.body=new j("true"));var $=new j(",",[new j("=",[Z.head,y.args[0]]),new j("=",[Z.body,y.args[1]])]);F.push(new Pe(b.goal.replace($),b.substitution,b))}w.prepend(F)}else w.throw_error(x.error.permission("access","private_procedure",y.args[0].indicator,y.indicator))},"current_predicate/1":function(w,b,y){var F=y.args[0];if(!x.type.is_variable(F)&&(!x.type.is_compound(F)||F.indicator!=="//2"))w.throw_error(x.error.type("predicate_indicator",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[0])&&!x.type.is_atom(F.args[0]))w.throw_error(x.error.type("atom",F.args[0],y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_variable(F.args[1])&&!x.type.is_integer(F.args[1]))w.throw_error(x.error.type("integer",F.args[1],y.indicator));else{var z=[];for(var Z in w.session.rules)if(w.session.rules.hasOwnProperty(Z)){var $=Z.lastIndexOf("/"),oe=Z.substr(0,$),xe=parseInt(Z.substr($+1,Z.length-($+1))),Te=new j("/",[new j(oe),new Re(xe,!1)]),lt=new j("=",[Te,F]);z.push(new Pe(b.goal.replace(lt),b.substitution,b))}w.prepend(z)}},"asserta/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type("callable",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator]=[new Ye(F,z,!0)].concat(w.session.rules[F.indicator]),w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F.indicator,y.indicator)):w.throw_error(x.error.type("callable",F,y.indicator))}},"assertz/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=Ce(y.args[0].args[1])):(F=y.args[0],z=null),x.type.is_callable(F)?z!==null&&!x.type.is_callable(z)?w.throw_error(x.error.type("callable",z,y.indicator)):w.is_public_predicate(F.indicator)?(w.session.rules[F.indicator]===void 0&&(w.session.rules[F.indicator]=[]),w.session.public_predicates[F.indicator]=!0,w.session.rules[F.indicator].push(new Ye(F,z,!0)),w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F.indicator,y.indicator)):w.throw_error(x.error.type("callable",F,y.indicator))}},"retract/1":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_callable(y.args[0]))w.throw_error(x.error.type("callable",y.args[0],y.indicator));else{var F,z;if(y.args[0].indicator===":-/2"?(F=y.args[0].args[0],z=y.args[0].args[1]):(F=y.args[0],z=new j("true")),typeof b.retract>"u")if(w.is_public_predicate(F.indicator)){if(w.session.rules[F.indicator]!==void 0){for(var Z=[],$=0;$w.get_flag("max_arity").value)w.throw_error(x.error.representation("max_arity",y.indicator));else{var F=y.args[0].args[0].id+"/"+y.args[0].args[1].value;w.is_public_predicate(F)?(delete w.session.rules[F],w.success(b)):w.throw_error(x.error.permission("modify","static_procedure",F,y.indicator))}},"atom_length/2":function(w,b,y){if(x.type.is_variable(y.args[0]))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_atom(y.args[0]))w.throw_error(x.error.type("atom",y.args[0],y.indicator));else if(!x.type.is_variable(y.args[1])&&!x.type.is_integer(y.args[1]))w.throw_error(x.error.type("integer",y.args[1],y.indicator));else if(x.type.is_integer(y.args[1])&&y.args[1].value<0)w.throw_error(x.error.domain("not_less_than_zero",y.args[1],y.indicator));else{var F=new Re(y.args[0].id.length,!1);w.prepend([new Pe(b.goal.replace(new j("=",[F,y.args[1]])),b.substitution,b)])}},"atom_concat/3":function(w,b,y){var F,z,Z=y.args[0],$=y.args[1],oe=y.args[2];if(x.type.is_variable(oe)&&(x.type.is_variable(Z)||x.type.is_variable($)))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_atom(Z))w.throw_error(x.error.type("atom",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_atom($))w.throw_error(x.error.type("atom",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_atom(oe))w.throw_error(x.error.type("atom",oe,y.indicator));else{var xe=x.type.is_variable(Z),Te=x.type.is_variable($);if(!xe&&!Te)z=new j("=",[oe,new j(Z.id+$.id)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]);else if(xe&&!Te)F=oe.id.substr(0,oe.id.length-$.id.length),F+$.id===oe.id&&(z=new j("=",[Z,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else if(Te&&!xe)F=oe.id.substr(Z.id.length),Z.id+F===oe.id&&(z=new j("=",[$,new j(F)]),w.prepend([new Pe(b.goal.replace(z),b.substitution,b)]));else{for(var lt=[],Et=0;Et<=oe.id.length;Et++){var qt=new j(oe.id.substr(0,Et)),ir=new j(oe.id.substr(Et));z=new j(",",[new j("=",[qt,Z]),new j("=",[ir,$])]),lt.push(new Pe(b.goal.replace(z),b.substitution,b))}w.prepend(lt)}}},"sub_atom/5":function(w,b,y){var F,z=y.args[0],Z=y.args[1],$=y.args[2],oe=y.args[3],xe=y.args[4];if(x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_integer(Z))w.throw_error(x.error.type("integer",Z,y.indicator));else if(!x.type.is_variable($)&&!x.type.is_integer($))w.throw_error(x.error.type("integer",$,y.indicator));else if(!x.type.is_variable(oe)&&!x.type.is_integer(oe))w.throw_error(x.error.type("integer",oe,y.indicator));else if(x.type.is_integer(Z)&&Z.value<0)w.throw_error(x.error.domain("not_less_than_zero",Z,y.indicator));else if(x.type.is_integer($)&&$.value<0)w.throw_error(x.error.domain("not_less_than_zero",$,y.indicator));else if(x.type.is_integer(oe)&&oe.value<0)w.throw_error(x.error.domain("not_less_than_zero",oe,y.indicator));else{var Te=[],lt=[],Et=[];if(x.type.is_variable(Z))for(F=0;F<=z.id.length;F++)Te.push(F);else Te.push(Z.value);if(x.type.is_variable($))for(F=0;F<=z.id.length;F++)lt.push(F);else lt.push($.value);if(x.type.is_variable(oe))for(F=0;F<=z.id.length;F++)Et.push(F);else Et.push(oe.value);var qt=[];for(var ir in Te)if(Te.hasOwnProperty(ir)){F=Te[ir];for(var Pt in lt)if(lt.hasOwnProperty(Pt)){var gn=lt[Pt],Pr=z.id.length-F-gn;if(e(Et,Pr)!==-1&&F+gn+Pr===z.id.length){var Ir=z.id.substr(F,gn);if(z.id===z.id.substr(0,F)+Ir+z.id.substr(F+gn,Pr)){var Nr=new j("=",[new j(Ir),xe]),nn=new j("=",[Z,new Re(F)]),oi=new j("=",[$,new Re(gn)]),wo=new j("=",[oe,new Re(Pr)]),rs=new j(",",[new j(",",[new j(",",[nn,oi]),wo]),Nr]);qt.push(new Pe(b.goal.replace(rs),b.substitution,b))}}}}w.prepend(qt)}},"atom_chars/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te="";oe.indicator==="./2";){if(x.type.is_character(oe.args[0]))Te+=oe.args[0].id;else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character",oe.args[0],y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type("list",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[new j(Te),F])),b.substitution,b)])}else{for(var Z=new j("[]"),$=F.id.length-1;$>=0;$--)Z=new j(".",[new j(F.id.charAt($)),Z]);w.prepend([new Pe(b.goal.replace(new j("=",[z,Z])),b.substitution,b)])}},"atom_codes/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_variable(F)){for(var oe=z,xe=x.type.is_variable(F),Te="";oe.indicator==="./2";){if(x.type.is_character_code(oe.args[0]))Te+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0])&&xe){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.representation("character_code",y.indicator));return}oe=oe.args[1]}x.type.is_variable(oe)&&xe?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)?w.throw_error(x.error.type("list",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[new j(Te),F])),b.substitution,b)])}else{for(var Z=new j("[]"),$=F.id.length-1;$>=0;$--)Z=new j(".",[new Re(n(F.id,$),!1),Z]);w.prepend([new Pe(b.goal.replace(new j("=",[z,Z])),b.substitution,b)])}},"char_code/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(x.type.is_variable(F)&&x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_character(F))w.throw_error(x.error.type("character",F,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_integer(z))w.throw_error(x.error.type("integer",z,y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_character_code(z))w.throw_error(x.error.representation("character_code",y.indicator));else if(x.type.is_variable(z)){var Z=new Re(n(F.id,0),!1);w.prepend([new Pe(b.goal.replace(new j("=",[Z,z])),b.substitution,b)])}else{var $=new j(c(z.value));w.prepend([new Pe(b.goal.replace(new j("=",[$,F])),b.substitution,b)])}},"number_chars/2":function(w,b,y){var F,z=y.args[0],Z=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type("number",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_list(Z))w.throw_error(x.error.type("list",Z,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(Z)){var oe=Z,xe=!0;for(F="";oe.indicator==="./2";){if(x.type.is_character(oe.args[0]))F+=oe.args[0].id;else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type("list",Z,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate("parseable_number",y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var Et=new j("[]"),qt=F.length-1;qt>=0;qt--)Et=new j(".",[new j(F.charAt(qt)),Et]);w.prepend([new Pe(b.goal.replace(new j("=",[Z,Et])),b.substitution,b)])}}},"number_codes/2":function(w,b,y){var F,z=y.args[0],Z=y.args[1];if(x.type.is_variable(z)&&x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(z)&&!x.type.is_number(z))w.throw_error(x.error.type("number",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_list(Z))w.throw_error(x.error.type("list",Z,y.indicator));else{var $=x.type.is_variable(z);if(!x.type.is_variable(Z)){var oe=Z,xe=!0;for(F="";oe.indicator==="./2";){if(x.type.is_character_code(oe.args[0]))F+=c(oe.args[0].value);else if(x.type.is_variable(oe.args[0]))xe=!1;else if(!x.type.is_variable(oe.args[0])){w.throw_error(x.error.type("character_code",oe.args[0],y.indicator));return}oe=oe.args[1]}if(xe=xe&&x.type.is_empty_list(oe),!x.type.is_empty_list(oe)&&!x.type.is_variable(oe)){w.throw_error(x.error.type("list",Z,y.indicator));return}if(!xe&&$){w.throw_error(x.error.instantiation(y.indicator));return}else if(xe)if(x.type.is_variable(oe)&&$){w.throw_error(x.error.instantiation(y.indicator));return}else{var Te=w.parse(F),lt=Te.value;!x.type.is_number(lt)||Te.tokens[Te.tokens.length-1].space?w.throw_error(x.error.syntax_by_predicate("parseable_number",y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,lt])),b.substitution,b)]);return}}if(!$){F=z.toString();for(var Et=new j("[]"),qt=F.length-1;qt>=0;qt--)Et=new j(".",[new Re(n(F,qt),!1),Et]);w.prepend([new Pe(b.goal.replace(new j("=",[Z,Et])),b.substitution,b)])}}},"upcase_atom/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type("atom",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,new j(F.id.toUpperCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type("atom",F,y.indicator))},"downcase_atom/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?!x.type.is_variable(z)&&!x.type.is_atom(z)?w.throw_error(x.error.type("atom",z,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[z,new j(F.id.toLowerCase(),[])])),b.substitution,b)]):w.throw_error(x.error.type("atom",F,y.indicator))},"atomic_list_concat/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("atomic_list_concat",[F,new j("",[]),z])),b.substitution,b)])},"atomic_list_concat/3":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(x.type.is_variable(z)||x.type.is_variable(F)&&x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_list(F))w.throw_error(x.error.type("list",F,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_atom(Z))w.throw_error(x.error.type("atom",Z,y.indicator));else if(x.type.is_variable(Z)){for(var oe="",xe=F;x.type.is_term(xe)&&xe.indicator==="./2";){if(!x.type.is_atom(xe.args[0])&&!x.type.is_number(xe.args[0])){w.throw_error(x.error.type("atomic",xe.args[0],y.indicator));return}oe!==""&&(oe+=z.id),x.type.is_atom(xe.args[0])?oe+=xe.args[0].id:oe+=""+xe.args[0].value,xe=xe.args[1]}oe=new j(oe,[]),x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_term(xe)||xe.indicator!=="[]/0"?w.throw_error(x.error.type("list",F,y.indicator)):w.prepend([new Pe(b.goal.replace(new j("=",[oe,Z])),b.substitution,b)])}else{var $=g(s(Z.id.split(z.id),function(Te){return new j(Te,[])}));w.prepend([new Pe(b.goal.replace(new j("=",[$,F])),b.substitution,b)])}},"@=/2":function(w,b,y){x.compare(y.args[0],y.args[1])>0&&w.success(b)},"@>=/2":function(w,b,y){x.compare(y.args[0],y.args[1])>=0&&w.success(b)},"compare/3":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(x.type.is_atom(F)&&["<",">","="].indexOf(F.id)===-1)w.throw_error(x.type.domain("order",F,y.indicator));else{var $=x.compare(z,Z);$=$===0?"=":$===-1?"<":">",w.prepend([new Pe(b.goal.replace(new j("=",[F,new j($,[])])),b.substitution,b)])}},"is/2":function(w,b,y){var F=y.args[1].interpret(w);x.type.is_number(F)?w.prepend([new Pe(b.goal.replace(new j("=",[y.args[0],F],w.level)),b.substitution,b)]):w.throw_error(F)},"between/3":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2];if(x.type.is_variable(F)||x.type.is_variable(z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_integer(F))w.throw_error(x.error.type("integer",F,y.indicator));else if(!x.type.is_integer(z))w.throw_error(x.error.type("integer",z,y.indicator));else if(!x.type.is_variable(Z)&&!x.type.is_integer(Z))w.throw_error(x.error.type("integer",Z,y.indicator));else if(x.type.is_variable(Z)){var $=[new Pe(b.goal.replace(new j("=",[Z,F])),b.substitution,b)];F.value=Z.value&&w.success(b)},"succ/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)&&x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):!x.type.is_variable(F)&&!x.type.is_integer(F)?w.throw_error(x.error.type("integer",F,y.indicator)):!x.type.is_variable(z)&&!x.type.is_integer(z)?w.throw_error(x.error.type("integer",z,y.indicator)):!x.type.is_variable(F)&&F.value<0?w.throw_error(x.error.domain("not_less_than_zero",F,y.indicator)):!x.type.is_variable(z)&&z.value<0?w.throw_error(x.error.domain("not_less_than_zero",z,y.indicator)):(x.type.is_variable(z)||z.value>0)&&(x.type.is_variable(F)?w.prepend([new Pe(b.goal.replace(new j("=",[F,new Re(z.value-1,!1)])),b.substitution,b)]):w.prepend([new Pe(b.goal.replace(new j("=",[z,new Re(F.value+1,!1)])),b.substitution,b)]))},"=:=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F===0&&w.success(b)},"=\\=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F!==0&&w.success(b)},"/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>0&&w.success(b)},">=/2":function(w,b,y){var F=x.arithmetic_compare(w,y.args[0],y.args[1]);x.type.is_term(F)?w.throw_error(F):F>=0&&w.success(b)},"var/1":function(w,b,y){x.type.is_variable(y.args[0])&&w.success(b)},"atom/1":function(w,b,y){x.type.is_atom(y.args[0])&&w.success(b)},"atomic/1":function(w,b,y){x.type.is_atomic(y.args[0])&&w.success(b)},"compound/1":function(w,b,y){x.type.is_compound(y.args[0])&&w.success(b)},"integer/1":function(w,b,y){x.type.is_integer(y.args[0])&&w.success(b)},"float/1":function(w,b,y){x.type.is_float(y.args[0])&&w.success(b)},"number/1":function(w,b,y){x.type.is_number(y.args[0])&&w.success(b)},"nonvar/1":function(w,b,y){x.type.is_variable(y.args[0])||w.success(b)},"ground/1":function(w,b,y){y.variables().length===0&&w.success(b)},"acyclic_term/1":function(w,b,y){for(var F=b.substitution.apply(b.substitution),z=y.args[0].variables(),Z=0;Z0?Pt[Pt.length-1]:null,Pt!==null&&(qt=W(w,Pt,0,w.__get_max_priority(),!1))}if(qt.type===p&&qt.len===Pt.length-1&&gn.value==="."){qt=qt.value.rename(w);var Pr=new j("=",[z,qt]);if(oe.variables){var Ir=g(s(Ee(qt.variables()),function(Nr){return new De(Nr)}));Pr=new j(",",[Pr,new j("=",[oe.variables,Ir])])}if(oe.variable_names){var Ir=g(s(Ee(qt.variables()),function(nn){var oi;for(oi in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(oi)&&w.session.renamed_variables[oi]===nn)break;return new j("=",[new j(oi,[]),new De(nn)])}));Pr=new j(",",[Pr,new j("=",[oe.variable_names,Ir])])}if(oe.singletons){var Ir=g(s(new Ye(qt,null).singleton_variables(),function(nn){var oi;for(oi in w.session.renamed_variables)if(w.session.renamed_variables.hasOwnProperty(oi)&&w.session.renamed_variables[oi]===nn)break;return new j("=",[new j(oi,[]),new De(nn)])}));Pr=new j(",",[Pr,new j("=",[oe.singletons,Ir])])}w.prepend([new Pe(b.goal.replace(Pr),b.substitution,b)])}else qt.type===p?w.throw_error(x.error.syntax(Pt[qt.len],"unexpected token",!1)):w.throw_error(qt.value)}}},"write/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write",[new De("S"),F])])),b.substitution,b)])},"write/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("false",[])]),new j(".",[new j("ignore_ops",[new j("false")]),new j(".",[new j("numbervars",[new j("true")]),new j("[]",[])])])])])),b.substitution,b)])},"writeq/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("writeq",[new De("S"),F])])),b.substitution,b)])},"writeq/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("true",[])]),new j(".",[new j("ignore_ops",[new j("false")]),new j(".",[new j("numbervars",[new j("true")]),new j("[]",[])])])])])),b.substitution,b)])},"write_canonical/1":function(w,b,y){var F=y.args[0];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write_canonical",[new De("S"),F])])),b.substitution,b)])},"write_canonical/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j("write_term",[F,z,new j(".",[new j("quoted",[new j("true",[])]),new j(".",[new j("ignore_ops",[new j("true")]),new j(".",[new j("numbervars",[new j("false")]),new j("[]",[])])])])])),b.substitution,b)])},"write_term/2":function(w,b,y){var F=y.args[0],z=y.args[1];w.prepend([new Pe(b.goal.replace(new j(",",[new j("current_output",[new De("S")]),new j("write_term",[new De("S"),F,z])])),b.substitution,b)])},"write_term/3":function(w,b,y){var F=y.args[0],z=y.args[1],Z=y.args[2],$=x.type.is_stream(F)?F:w.get_stream_by_alias(F.id);if(x.type.is_variable(F)||x.type.is_variable(Z))w.throw_error(x.error.instantiation(y.indicator));else if(!x.type.is_list(Z))w.throw_error(x.error.type("list",Z,y.indicator));else if(!x.type.is_stream(F)&&!x.type.is_atom(F))w.throw_error(x.error.domain("stream_or_alias",F,y.indicator));else if(!x.type.is_stream($)||$.stream===null)w.throw_error(x.error.existence("stream",F,y.indicator));else if($.input)w.throw_error(x.error.permission("output","stream",F,y.indicator));else if($.type==="binary")w.throw_error(x.error.permission("output","binary_stream",F,y.indicator));else if($.position==="past_end_of_stream"&&$.eof_action==="error")w.throw_error(x.error.permission("output","past_end_of_stream",F,y.indicator));else{for(var oe={},xe=Z,Te;x.type.is_term(xe)&&xe.indicator==="./2";){if(Te=xe.args[0],x.type.is_variable(Te)){w.throw_error(x.error.instantiation(y.indicator));return}else if(!x.type.is_write_option(Te)){w.throw_error(x.error.domain("write_option",Te,y.indicator));return}oe[Te.id]=Te.args[0].id==="true",xe=xe.args[1]}if(xe.indicator!=="[]/0"){x.type.is_variable(xe)?w.throw_error(x.error.instantiation(y.indicator)):w.throw_error(x.error.type("list",Z,y.indicator));return}else{oe.session=w.session;var lt=z.toString(oe);$.stream.put(lt,$.position),typeof $.position=="number"&&($.position+=lt.length),w.success(b)}}},"halt/0":function(w,b,y){w.points=[]},"halt/1":function(w,b,y){var F=y.args[0];x.type.is_variable(F)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_integer(F)?w.points=[]:w.throw_error(x.error.type("integer",F,y.indicator))},"current_prolog_flag/2":function(w,b,y){var F=y.args[0],z=y.args[1];if(!x.type.is_variable(F)&&!x.type.is_atom(F))w.throw_error(x.error.type("atom",F,y.indicator));else if(!x.type.is_variable(F)&&!x.type.is_flag(F))w.throw_error(x.error.domain("prolog_flag",F,y.indicator));else{var Z=[];for(var $ in x.flag)if(x.flag.hasOwnProperty($)){var oe=new j(",",[new j("=",[new j($),F]),new j("=",[w.get_flag($),z])]);Z.push(new Pe(b.goal.replace(oe),b.substitution,b))}w.prepend(Z)}},"set_prolog_flag/2":function(w,b,y){var F=y.args[0],z=y.args[1];x.type.is_variable(F)||x.type.is_variable(z)?w.throw_error(x.error.instantiation(y.indicator)):x.type.is_atom(F)?x.type.is_flag(F)?x.type.is_value_flag(F,z)?x.type.is_modifiable_flag(F)?(w.session.flag[F.id]=z,w.success(b)):w.throw_error(x.error.permission("modify","flag",F)):w.throw_error(x.error.domain("flag_value",new j("+",[F,z]),y.indicator)):w.throw_error(x.error.domain("prolog_flag",F,y.indicator)):w.throw_error(x.error.type("atom",F,y.indicator))}},flag:{bounded:{allowed:[new j("true"),new j("false")],value:new j("true"),changeable:!1},max_integer:{allowed:[new Re(Number.MAX_SAFE_INTEGER)],value:new Re(Number.MAX_SAFE_INTEGER),changeable:!1},min_integer:{allowed:[new Re(Number.MIN_SAFE_INTEGER)],value:new Re(Number.MIN_SAFE_INTEGER),changeable:!1},integer_rounding_function:{allowed:[new j("down"),new j("toward_zero")],value:new j("toward_zero"),changeable:!1},char_conversion:{allowed:[new j("on"),new j("off")],value:new j("on"),changeable:!0},debug:{allowed:[new j("on"),new j("off")],value:new j("off"),changeable:!0},max_arity:{allowed:[new j("unbounded")],value:new j("unbounded"),changeable:!1},unknown:{allowed:[new j("error"),new j("fail"),new j("warning")],value:new j("error"),changeable:!0},double_quotes:{allowed:[new j("chars"),new j("codes"),new j("atom")],value:new j("codes"),changeable:!0},occurs_check:{allowed:[new j("false"),new j("true")],value:new j("false"),changeable:!0},dialect:{allowed:[new j("tau")],value:new j("tau"),changeable:!1},version_data:{allowed:[new j("tau",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)])],value:new j("tau",[new Re(t.major,!1),new Re(t.minor,!1),new Re(t.patch,!1),new j(t.status)]),changeable:!1},nodejs:{allowed:[new j("yes"),new j("no")],value:new j(typeof tc<"u"&&tc.exports?"yes":"no"),changeable:!1}},unify:function(w,b,y){y=y===void 0?!1:y;for(var F=[{left:w,right:b}],z={};F.length!==0;){var Z=F.pop();if(w=Z.left,b=Z.right,x.type.is_term(w)&&x.type.is_term(b)){if(w.indicator!==b.indicator)return null;for(var $=0;$z.value?1:0:z}else return F},operate:function(w,b){if(x.type.is_operator(b)){for(var y=x.type.is_operator(b),F=[],z,Z=!1,$=0;$w.get_flag("max_integer").value||z0?w.start+w.matches[0].length:w.start,z=y?new j("token_not_found"):new j("found",[new j(w.value.toString())]),Z=new j(".",[new j("line",[new Re(w.line+1)]),new j(".",[new j("column",[new Re(F+1)]),new j(".",[z,new j("[]",[])])])]);return new j("error",[new j("syntax_error",[new j(b)]),Z])},syntax_by_predicate:function(w,b){return new j("error",[new j("syntax_error",[new j(w)]),X(b)])}},warning:{singleton:function(w,b,y){for(var F=new j("[]"),z=w.length-1;z>=0;z--)F=new j(".",[new De(w[z]),F]);return new j("warning",[new j("singleton_variables",[F,X(b)]),new j(".",[new j("line",[new Re(y,!1)]),new j("[]")])])},failed_goal:function(w,b){return new j("warning",[new j("failed_goal",[w]),new j(".",[new j("line",[new Re(b,!1)]),new j("[]")])])}},format_variable:function(w){return"_"+w},format_answer:function(w,b,F){b instanceof ke&&(b=b.thread);var F=F||{};if(F.session=b?b.session:void 0,x.type.is_error(w))return"uncaught exception: "+w.args[0].toString();if(w===!1)return"false.";if(w===null)return"limit exceeded ;";var z=0,Z="";if(x.type.is_substitution(w)){var $=w.domain(!0);w=w.filter(function(Te,lt){return!x.type.is_variable(lt)||$.indexOf(lt.id)!==-1&&Te!==lt.id})}for(var oe in w.links)w.links.hasOwnProperty(oe)&&(z++,Z!==""&&(Z+=", "),Z+=oe.toString(F)+" = "+w.links[oe].toString(F));var xe=typeof b>"u"||b.points.length>0?" ;":".";return z===0?"true"+xe:Z+xe},flatten_error:function(w){if(!x.type.is_error(w))return null;w=w.args[0];var b={};return b.type=w.args[0].id,b.thrown=b.type==="syntax_error"?null:w.args[1].id,b.expected=null,b.found=null,b.representation=null,b.existence=null,b.existence_type=null,b.line=null,b.column=null,b.permission_operation=null,b.permission_type=null,b.evaluation_type=null,b.type==="type_error"||b.type==="domain_error"?(b.expected=w.args[0].args[0].id,b.found=w.args[0].args[1].toString()):b.type==="syntax_error"?w.args[1].indicator==="./2"?(b.expected=w.args[0].args[0].id,b.found=w.args[1].args[1].args[1].args[0],b.found=b.found.id==="token_not_found"?b.found.id:b.found.args[0].id,b.line=w.args[1].args[0].args[0].value,b.column=w.args[1].args[1].args[0].args[0].value):b.thrown=w.args[1].id:b.type==="permission_error"?(b.found=w.args[0].args[2].toString(),b.permission_operation=w.args[0].args[0].id,b.permission_type=w.args[0].args[1].id):b.type==="evaluation_error"?b.evaluation_type=w.args[0].args[0].id:b.type==="representation_error"?b.representation=w.args[0].args[0].id:b.type==="existence_error"&&(b.existence=w.args[0].args[1].toString(),b.existence_type=w.args[0].args[0].id),b},create:function(w){return new x.type.Session(w)}};typeof tc<"u"?tc.exports=x:window.pl=x})()});function hve(t,e,r){t.prepend(r.map(s=>new gl.default.type.State(e.goal.replace(s),e.substitution,e)))}function y9(t){let e=dve.get(t.session);if(e==null)throw new Error("Assertion failed: A project should have been registered for the active session");return e}function mve(t,e){dve.set(t,e),t.consult(`:- use_module(library(${gDt.id})).`)}var E9,gl,gve,J0,pDt,hDt,dve,gDt,yve=It(()=>{Ve();E9=et(AS()),gl=et(m9()),gve=et(ye("vm")),{is_atom:J0,is_variable:pDt,is_instantiated_list:hDt}=gl.default.type;dve=new WeakMap;gDt=new gl.default.type.Module("constraints",{"project_workspaces_by_descriptor/3":(t,e,r)=>{let[s,a,n]=r.args;if(!J0(s)||!J0(a)){t.throw_error(gl.default.error.instantiation(r.indicator));return}let c=q.parseIdent(s.id),f=q.makeDescriptor(c,a.id),h=y9(t).tryWorkspaceByDescriptor(f);pDt(n)&&h!==null&&hve(t,e,[new gl.default.type.Term("=",[n,new gl.default.type.Term(String(h.relativeCwd))])]),J0(n)&&h!==null&&h.relativeCwd===n.id&&t.success(e)},"workspace_field/3":(t,e,r)=>{let[s,a,n]=r.args;if(!J0(s)||!J0(a)){t.throw_error(gl.default.error.instantiation(r.indicator));return}let f=y9(t).tryWorkspaceByCwd(s.id);if(f==null)return;let p=(0,E9.default)(f.manifest.raw,a.id);typeof p>"u"||hve(t,e,[new gl.default.type.Term("=",[n,new gl.default.type.Term(typeof p=="object"?JSON.stringify(p):p)])])},"workspace_field_test/3":(t,e,r)=>{let[s,a,n]=r.args;t.prepend([new gl.default.type.State(e.goal.replace(new gl.default.type.Term("workspace_field_test",[s,a,n,new gl.default.type.Term("[]",[])])),e.substitution,e)])},"workspace_field_test/4":(t,e,r)=>{let[s,a,n,c]=r.args;if(!J0(s)||!J0(a)||!J0(n)||!hDt(c)){t.throw_error(gl.default.error.instantiation(r.indicator));return}let p=y9(t).tryWorkspaceByCwd(s.id);if(p==null)return;let h=(0,E9.default)(p.manifest.raw,a.id);if(typeof h>"u")return;let E={$$:h};for(let[S,P]of c.toJavaScript().entries())E[`$${S}`]=P;gve.default.runInNewContext(n.id,E)&&t.success(e)}},["project_workspaces_by_descriptor/3","workspace_field/3","workspace_field_test/3","workspace_field_test/4"])});var BS={};Vt(BS,{Constraints:()=>C9,DependencyType:()=>wve});function yo(t){if(t instanceof ZC.default.type.Num)return t.value;if(t instanceof ZC.default.type.Term)switch(t.indicator){case"throw/1":return yo(t.args[0]);case"error/1":return yo(t.args[0]);case"error/2":if(t.args[0]instanceof ZC.default.type.Term&&t.args[0].indicator==="syntax_error/1")return Object.assign(yo(t.args[0]),...yo(t.args[1]));{let e=yo(t.args[0]);return e.message+=` (in ${yo(t.args[1])})`,e}case"syntax_error/1":return new Yt(43,`Syntax error: ${yo(t.args[0])}`);case"existence_error/2":return new Yt(44,`Existence error: ${yo(t.args[0])} ${yo(t.args[1])} not found`);case"instantiation_error/0":return new Yt(75,"Instantiation error: an argument is variable when an instantiated argument was expected");case"line/1":return{line:yo(t.args[0])};case"column/1":return{column:yo(t.args[0])};case"found/1":return{found:yo(t.args[0])};case"./2":return[yo(t.args[0])].concat(yo(t.args[1]));case"//2":return`${yo(t.args[0])}/${yo(t.args[1])}`;default:return t.id}throw`couldn't pretty print because of unsupported node ${t}`}function Ive(t){let e;try{e=yo(t)}catch(r){throw typeof r=="string"?new Yt(42,`Unknown error: ${t} (note: ${r})`):r}return typeof e.line<"u"&&typeof e.column<"u"&&(e.message+=` at line ${e.line}, column ${e.column}`),e}function xm(t){return t.id==="null"?null:`${t.toJavaScript()}`}function dDt(t){if(t.id==="null")return null;{let e=t.toJavaScript();if(typeof e!="string")return JSON.stringify(e);try{return JSON.stringify(JSON.parse(e))}catch{return JSON.stringify(e)}}}function z0(t){return typeof t=="string"?`'${t}'`:"[]"}var Cve,ZC,wve,Eve,I9,C9,vS=It(()=>{Ve();Ve();bt();Cve=et(XBe()),ZC=et(m9());IS();yve();(0,Cve.default)(ZC.default);wve=(s=>(s.Dependencies="dependencies",s.DevDependencies="devDependencies",s.PeerDependencies="peerDependencies",s))(wve||{}),Eve=["dependencies","devDependencies","peerDependencies"];I9=class{constructor(e,r){let s=1e3*e.workspaces.length;this.session=ZC.default.create(s),mve(this.session,e),this.session.consult(":- use_module(library(lists))."),this.session.consult(r)}fetchNextAnswer(){return new Promise(e=>{this.session.answer(r=>{e(r)})})}async*makeQuery(e){let r=this.session.query(e);if(r!==!0)throw Ive(r);for(;;){let s=await this.fetchNextAnswer();if(s===null)throw new Yt(79,"Resolution limit exceeded");if(!s)break;if(s.id==="throw")throw Ive(s);yield s}}};C9=class t{constructor(e){this.source="";this.project=e;let r=e.configuration.get("constraintsPath");le.existsSync(r)&&(this.source=le.readFileSync(r,"utf8"))}static async find(e){return new t(e)}getProjectDatabase(){let e="";for(let r of Eve)e+=`dependency_type(${r}). +`;for(let r of this.project.workspacesByCwd.values()){let s=r.relativeCwd;e+=`workspace(${z0(s)}). +`,e+=`workspace_ident(${z0(s)}, ${z0(q.stringifyIdent(r.anchoredLocator))}). +`,e+=`workspace_version(${z0(s)}, ${z0(r.manifest.version)}). +`;for(let a of Eve)for(let n of r.manifest[a].values())e+=`workspace_has_dependency(${z0(s)}, ${z0(q.stringifyIdent(n))}, ${z0(n.range)}, ${a}). +`}return e+=`workspace(_) :- false. +`,e+=`workspace_ident(_, _) :- false. +`,e+=`workspace_version(_, _) :- false. +`,e+=`workspace_has_dependency(_, _, _, _) :- false. +`,e}getDeclarations(){let e="";return e+=`gen_enforced_dependency(_, _, _, _) :- false. +`,e+=`gen_enforced_field(_, _, _) :- false. +`,e}get fullSource(){return`${this.getProjectDatabase()} +${this.source} +${this.getDeclarations()}`}createSession(){return new I9(this.project,this.fullSource)}async processClassic(){let e=this.createSession();return{enforcedDependencies:await this.genEnforcedDependencies(e),enforcedFields:await this.genEnforcedFields(e)}}async process(){let{enforcedDependencies:e,enforcedFields:r}=await this.processClassic(),s=new Map;for(let{workspace:a,dependencyIdent:n,dependencyRange:c,dependencyType:f}of e){let p=ES([f,q.stringifyIdent(n)]),h=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(h,p).set(c??void 0,new Set)}for(let{workspace:a,fieldPath:n,fieldValue:c}of r){let f=ES(n),p=je.getMapWithDefault(s,a.cwd);je.getMapWithDefault(p,f).set(JSON.parse(c)??void 0,new Set)}return{manifestUpdates:s,reportedErrors:new Map}}async genEnforcedDependencies(e){let r=[];for await(let s of e.makeQuery("workspace(WorkspaceCwd), dependency_type(DependencyType), gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange, DependencyType).")){let a=K.resolve(this.project.cwd,xm(s.links.WorkspaceCwd)),n=xm(s.links.DependencyIdent),c=xm(s.links.DependencyRange),f=xm(s.links.DependencyType);if(a===null||n===null)throw new Error("Invalid rule");let p=this.project.getWorkspaceByCwd(a),h=q.parseIdent(n);r.push({workspace:p,dependencyIdent:h,dependencyRange:c,dependencyType:f})}return je.sortMap(r,[({dependencyRange:s})=>s!==null?"0":"1",({workspace:s})=>q.stringifyIdent(s.anchoredLocator),({dependencyIdent:s})=>q.stringifyIdent(s)])}async genEnforcedFields(e){let r=[];for await(let s of e.makeQuery("workspace(WorkspaceCwd), gen_enforced_field(WorkspaceCwd, FieldPath, FieldValue).")){let a=K.resolve(this.project.cwd,xm(s.links.WorkspaceCwd)),n=xm(s.links.FieldPath),c=dDt(s.links.FieldValue);if(a===null||n===null)throw new Error("Invalid rule");let f=this.project.getWorkspaceByCwd(a);r.push({workspace:f,fieldPath:n,fieldValue:c})}return je.sortMap(r,[({workspace:s})=>q.stringifyIdent(s.anchoredLocator),({fieldPath:s})=>s])}async*query(e){let r=this.createSession();for await(let s of r.makeQuery(e)){let a={};for(let[n,c]of Object.entries(s.links))n!=="_"&&(a[n]=xm(c));yield a}}}});var Qve=L(gF=>{"use strict";Object.defineProperty(gF,"__esModule",{value:!0});function US(t){let e=[...t.caches],r=e.shift();return r===void 0?kve():{get(s,a,n={miss:()=>Promise.resolve()}){return r.get(s,a,n).catch(()=>US({caches:e}).get(s,a,n))},set(s,a){return r.set(s,a).catch(()=>US({caches:e}).set(s,a))},delete(s){return r.delete(s).catch(()=>US({caches:e}).delete(s))},clear(){return r.clear().catch(()=>US({caches:e}).clear())}}}function kve(){return{get(t,e,r={miss:()=>Promise.resolve()}){return e().then(a=>Promise.all([a,r.miss(a)])).then(([a])=>a)},set(t,e){return Promise.resolve(e)},delete(t){return Promise.resolve()},clear(){return Promise.resolve()}}}gF.createFallbackableCache=US;gF.createNullCache=kve});var Rve=L((ihr,Tve)=>{Tve.exports=Qve()});var Fve=L(N9=>{"use strict";Object.defineProperty(N9,"__esModule",{value:!0});function NDt(t={serializable:!0}){let e={};return{get(r,s,a={miss:()=>Promise.resolve()}){let n=JSON.stringify(r);if(n in e)return Promise.resolve(t.serializable?JSON.parse(e[n]):e[n]);let c=s(),f=a&&a.miss||(()=>Promise.resolve());return c.then(p=>f(p)).then(()=>c)},set(r,s){return e[JSON.stringify(r)]=t.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete e[JSON.stringify(r)],Promise.resolve()},clear(){return e={},Promise.resolve()}}}N9.createInMemoryCache=NDt});var Ove=L((ohr,Nve)=>{Nve.exports=Fve()});var Mve=L(ef=>{"use strict";Object.defineProperty(ef,"__esModule",{value:!0});function ODt(t,e,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":e};return{headers(){return t===O9.WithinHeaders?s:{}},queryParameters(){return t===O9.WithinQueryParameters?s:{}}}}function LDt(t){let e=0,r=()=>(e++,new Promise(s=>{setTimeout(()=>{s(t(r))},Math.min(100*e,1e3))}));return t(r)}function Lve(t,e=(r,s)=>Promise.resolve()){return Object.assign(t,{wait(r){return Lve(t.then(s=>Promise.all([e(s,r),s])).then(s=>s[1]))}})}function MDt(t){let e=t.length-1;for(e;e>0;e--){let r=Math.floor(Math.random()*(e+1)),s=t[e];t[e]=t[r],t[r]=s}return t}function _Dt(t,e){return e&&Object.keys(e).forEach(r=>{t[r]=e[r](t)}),t}function UDt(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}var HDt="4.22.1",jDt=t=>()=>t.transporter.requester.destroy(),O9={WithinQueryParameters:0,WithinHeaders:1};ef.AuthMode=O9;ef.addMethods=_Dt;ef.createAuth=ODt;ef.createRetryablePromise=LDt;ef.createWaitablePromise=Lve;ef.destroy=jDt;ef.encode=UDt;ef.shuffle=MDt;ef.version=HDt});var HS=L((lhr,_ve)=>{_ve.exports=Mve()});var Uve=L(L9=>{"use strict";Object.defineProperty(L9,"__esModule",{value:!0});var qDt={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};L9.MethodEnum=qDt});var jS=L((uhr,Hve)=>{Hve.exports=Uve()});var rSe=L(Yi=>{"use strict";Object.defineProperty(Yi,"__esModule",{value:!0});var qve=jS();function M9(t,e){let r=t||{},s=r.data||{};return Object.keys(r).forEach(a=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(a)===-1&&(s[a]=r[a])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||e,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var qS={Read:1,Write:2,Any:3},aw={Up:1,Down:2,Timeouted:3},Gve=2*60*1e3;function U9(t,e=aw.Up){return{...t,status:e,lastUpdate:Date.now()}}function Wve(t){return t.status===aw.Up||Date.now()-t.lastUpdate>Gve}function Yve(t){return t.status===aw.Timeouted&&Date.now()-t.lastUpdate<=Gve}function H9(t){return typeof t=="string"?{protocol:"https",url:t,accept:qS.Any}:{protocol:t.protocol||"https",url:t.url,accept:t.accept||qS.Any}}function GDt(t,e){return Promise.all(e.map(r=>t.get(r,()=>Promise.resolve(U9(r))))).then(r=>{let s=r.filter(f=>Wve(f)),a=r.filter(f=>Yve(f)),n=[...s,...a],c=n.length>0?n.map(f=>H9(f)):e;return{getTimeout(f,p){return(a.length===0&&f===0?1:a.length+3+f)*p},statelessHosts:c}})}var WDt=({isTimedOut:t,status:e})=>!t&&~~e===0,YDt=t=>{let e=t.status;return t.isTimedOut||WDt(t)||~~(e/100)!==2&&~~(e/100)!==4},VDt=({status:t})=>~~(t/100)===2,KDt=(t,e)=>YDt(t)?e.onRetry(t):VDt(t)?e.onSuccess(t):e.onFail(t);function jve(t,e,r,s){let a=[],n=Zve(r,s),c=Xve(t,s),f=r.method,p=r.method!==qve.MethodEnum.Get?{}:{...r.data,...s.data},h={"x-algolia-agent":t.userAgent.value,...t.queryParameters,...p,...s.queryParameters},E=0,C=(S,P)=>{let I=S.pop();if(I===void 0)throw tSe(_9(a));let R={data:n,headers:c,method:f,url:Jve(I,r.path,h),connectTimeout:P(E,t.timeouts.connect),responseTimeout:P(E,s.timeout)},N=W=>{let te={request:R,response:W,host:I,triesLeft:S.length};return a.push(te),te},U={onSuccess:W=>Vve(W),onRetry(W){let te=N(W);return W.isTimedOut&&E++,Promise.all([t.logger.info("Retryable failure",j9(te)),t.hostsCache.set(I,U9(I,W.isTimedOut?aw.Timeouted:aw.Down))]).then(()=>C(S,P))},onFail(W){throw N(W),Kve(W,_9(a))}};return t.requester.send(R).then(W=>KDt(W,U))};return GDt(t.hostsCache,e).then(S=>C([...S.statelessHosts].reverse(),S.getTimeout))}function JDt(t){let{hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,hosts:p,queryParameters:h,headers:E}=t,C={hostsCache:e,logger:r,requester:s,requestsCache:a,responsesCache:n,timeouts:c,userAgent:f,headers:E,queryParameters:h,hosts:p.map(S=>H9(S)),read(S,P){let I=M9(P,C.timeouts.read),R=()=>jve(C,C.hosts.filter(W=>(W.accept&qS.Read)!==0),S,I);if((I.cacheable!==void 0?I.cacheable:S.cacheable)!==!0)return R();let U={request:S,mappedRequestOptions:I,transporter:{queryParameters:C.queryParameters,headers:C.headers}};return C.responsesCache.get(U,()=>C.requestsCache.get(U,()=>C.requestsCache.set(U,R()).then(W=>Promise.all([C.requestsCache.delete(U),W]),W=>Promise.all([C.requestsCache.delete(U),Promise.reject(W)])).then(([W,te])=>te)),{miss:W=>C.responsesCache.set(U,W)})},write(S,P){return jve(C,C.hosts.filter(I=>(I.accept&qS.Write)!==0),S,M9(P,C.timeouts.write))}};return C}function zDt(t){let e={value:`Algolia for JavaScript (${t})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return e.value.indexOf(s)===-1&&(e.value=`${e.value}${s}`),e}};return e}function Vve(t){try{return JSON.parse(t.content)}catch(e){throw eSe(e.message,t)}}function Kve({content:t,status:e},r){let s=t;try{s=JSON.parse(t).message}catch{}return $ve(s,e,r)}function ZDt(t,...e){let r=0;return t.replace(/%s/g,()=>encodeURIComponent(e[r++]))}function Jve(t,e,r){let s=zve(r),a=`${t.protocol}://${t.url}/${e.charAt(0)==="/"?e.substr(1):e}`;return s.length&&(a+=`?${s}`),a}function zve(t){let e=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(t).map(r=>ZDt("%s=%s",r,e(t[r])?JSON.stringify(t[r]):t[r])).join("&")}function Zve(t,e){if(t.method===qve.MethodEnum.Get||t.data===void 0&&e.data===void 0)return;let r=Array.isArray(t.data)?t.data:{...t.data,...e.data};return JSON.stringify(r)}function Xve(t,e){let r={...t.headers,...e.headers},s={};return Object.keys(r).forEach(a=>{let n=r[a];s[a.toLowerCase()]=n}),s}function _9(t){return t.map(e=>j9(e))}function j9(t){let e=t.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return{...t,request:{...t.request,headers:{...t.request.headers,...e}}}}function $ve(t,e,r){return{name:"ApiError",message:t,status:e,transporterStackTrace:r}}function eSe(t,e){return{name:"DeserializationError",message:t,response:e}}function tSe(t){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:t}}Yi.CallEnum=qS;Yi.HostStatusEnum=aw;Yi.createApiError=$ve;Yi.createDeserializationError=eSe;Yi.createMappedRequestOptions=M9;Yi.createRetryError=tSe;Yi.createStatefulHost=U9;Yi.createStatelessHost=H9;Yi.createTransporter=JDt;Yi.createUserAgent=zDt;Yi.deserializeFailure=Kve;Yi.deserializeSuccess=Vve;Yi.isStatefulHostTimeouted=Yve;Yi.isStatefulHostUp=Wve;Yi.serializeData=Zve;Yi.serializeHeaders=Xve;Yi.serializeQueryParameters=zve;Yi.serializeUrl=Jve;Yi.stackFrameWithoutCredentials=j9;Yi.stackTraceWithoutCredentials=_9});var GS=L((Ahr,nSe)=>{nSe.exports=rSe()});var iSe=L(X0=>{"use strict";Object.defineProperty(X0,"__esModule",{value:!0});var lw=HS(),XDt=GS(),WS=jS(),$Dt=t=>{let e=t.region||"us",r=lw.createAuth(lw.AuthMode.WithinHeaders,t.appId,t.apiKey),s=XDt.createTransporter({hosts:[{url:`analytics.${e}.algolia.com`}],...t,headers:{...r.headers(),"content-type":"application/json",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a=t.appId;return lw.addMethods({appId:a,transporter:s},t.methods)},ebt=t=>(e,r)=>t.transporter.write({method:WS.MethodEnum.Post,path:"2/abtests",data:e},r),tbt=t=>(e,r)=>t.transporter.write({method:WS.MethodEnum.Delete,path:lw.encode("2/abtests/%s",e)},r),rbt=t=>(e,r)=>t.transporter.read({method:WS.MethodEnum.Get,path:lw.encode("2/abtests/%s",e)},r),nbt=t=>e=>t.transporter.read({method:WS.MethodEnum.Get,path:"2/abtests"},e),ibt=t=>(e,r)=>t.transporter.write({method:WS.MethodEnum.Post,path:lw.encode("2/abtests/%s/stop",e)},r);X0.addABTest=ebt;X0.createAnalyticsClient=$Dt;X0.deleteABTest=tbt;X0.getABTest=rbt;X0.getABTests=nbt;X0.stopABTest=ibt});var oSe=L((hhr,sSe)=>{sSe.exports=iSe()});var lSe=L(YS=>{"use strict";Object.defineProperty(YS,"__esModule",{value:!0});var q9=HS(),sbt=GS(),aSe=jS(),obt=t=>{let e=t.region||"us",r=q9.createAuth(q9.AuthMode.WithinHeaders,t.appId,t.apiKey),s=sbt.createTransporter({hosts:[{url:`personalization.${e}.algolia.com`}],...t,headers:{...r.headers(),"content-type":"application/json",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}});return q9.addMethods({appId:t.appId,transporter:s},t.methods)},abt=t=>e=>t.transporter.read({method:aSe.MethodEnum.Get,path:"1/strategies/personalization"},e),lbt=t=>(e,r)=>t.transporter.write({method:aSe.MethodEnum.Post,path:"1/strategies/personalization",data:e},r);YS.createPersonalizationClient=obt;YS.getPersonalizationStrategy=abt;YS.setPersonalizationStrategy=lbt});var uSe=L((dhr,cSe)=>{cSe.exports=lSe()});var vSe=L(Ft=>{"use strict";Object.defineProperty(Ft,"__esModule",{value:!0});var Kt=HS(),dl=GS(),br=jS(),cbt=ye("crypto");function dF(t){let e=r=>t.request(r).then(s=>{if(t.batch!==void 0&&t.batch(s.hits),!t.shouldStop(s))return s.cursor?e({cursor:s.cursor}):e({page:(r.page||0)+1})});return e({})}var ubt=t=>{let e=t.appId,r=Kt.createAuth(t.authMode!==void 0?t.authMode:Kt.AuthMode.WithinHeaders,e,t.apiKey),s=dl.createTransporter({hosts:[{url:`${e}-dsn.algolia.net`,accept:dl.CallEnum.Read},{url:`${e}.algolia.net`,accept:dl.CallEnum.Write}].concat(Kt.shuffle([{url:`${e}-1.algolianet.com`},{url:`${e}-2.algolianet.com`},{url:`${e}-3.algolianet.com`}])),...t,headers:{...r.headers(),"content-type":"application/x-www-form-urlencoded",...t.headers},queryParameters:{...r.queryParameters(),...t.queryParameters}}),a={transporter:s,appId:e,addAlgoliaAgent(n,c){s.userAgent.add({segment:n,version:c})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return Kt.addMethods(a,t.methods)};function fSe(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function ASe(){return{name:"ObjectNotFoundError",message:"Object not found."}}function pSe(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var fbt=t=>(e,r)=>{let{queryParameters:s,...a}=r||{},n={acl:e,...s!==void 0?{queryParameters:s}:{}},c=(f,p)=>Kt.createRetryablePromise(h=>VS(t)(f.key,p).catch(E=>{if(E.status!==404)throw E;return h()}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:"1/keys",data:n},a),c)},Abt=t=>(e,r,s)=>{let a=dl.createMappedRequestOptions(s);return a.queryParameters["X-Algolia-User-ID"]=e,t.transporter.write({method:br.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},a)},pbt=t=>(e,r,s)=>t.transporter.write({method:br.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:e,cluster:r}},s),hbt=t=>(e,r)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!0,requests:{action:"addEntry",body:[]}}},r),(s,a)=>cw(t)(s.taskID,a)),mF=t=>(e,r,s)=>{let a=(n,c)=>KS(t)(e,{methods:{waitTask:gs}}).waitTask(n.taskID,c);return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/operation",e),data:{operation:"copy",destination:r}},s),a)},gbt=t=>(e,r,s)=>mF(t)(e,r,{...s,scope:[EF.Rules]}),dbt=t=>(e,r,s)=>mF(t)(e,r,{...s,scope:[EF.Settings]}),mbt=t=>(e,r,s)=>mF(t)(e,r,{...s,scope:[EF.Synonyms]}),ybt=t=>(e,r)=>e.method===br.MethodEnum.Get?t.transporter.read(e,r):t.transporter.write(e,r),Ebt=t=>(e,r)=>{let s=(a,n)=>Kt.createRetryablePromise(c=>VS(t)(e,n).then(c).catch(f=>{if(f.status!==404)throw f}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode("1/keys/%s",e)},r),s)},Ibt=t=>(e,r,s)=>{let a=r.map(n=>({action:"deleteEntry",body:{objectID:n}}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>cw(t)(n.taskID,c))},Cbt=()=>(t,e)=>{let r=dl.serializeQueryParameters(e),s=cbt.createHmac("sha256",t).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},VS=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/keys/%s",e)},r),hSe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/task/%s",e.toString())},r),wbt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"/1/dictionaries/*/settings"},e),Bbt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/logs"},e),vbt=()=>t=>{let e=Buffer.from(t,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=e.match(r);if(s===null)throw pSe();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},Sbt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping/top"},e),Dbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/clusters/mapping/%s",e)},r),bbt=t=>e=>{let{retrieveMappings:r,...s}=e||{};return r===!0&&(s.getClusters=!0),t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},KS=t=>(e,r={})=>{let s={transporter:t.transporter,appId:t.appId,indexName:e};return Kt.addMethods(s,r.methods)},Pbt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/keys"},e),xbt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters"},e),kbt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/indexes"},e),Qbt=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:"1/clusters/mapping"},e),Tbt=t=>(e,r,s)=>{let a=(n,c)=>KS(t)(e,{methods:{waitTask:gs}}).waitTask(n.taskID,c);return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/operation",e),data:{operation:"move",destination:r}},s),a)},Rbt=t=>(e,r)=>{let s=(a,n)=>Promise.all(Object.keys(a.taskID).map(c=>KS(t)(c,{methods:{waitTask:gs}}).waitTask(a.taskID[c],n)));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:e}},r),s)},Fbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:e}},r),Nbt=t=>(e,r)=>{let s=e.map(a=>({...a,params:dl.serializeQueryParameters(a.params||{})}));return t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Obt=t=>(e,r)=>Promise.all(e.map(s=>{let{facetName:a,facetQuery:n,...c}=s.params;return KS(t)(s.indexName,{methods:{searchForFacetValues:CSe}}).searchForFacetValues(a,n,{...r,...c})})),Lbt=t=>(e,r)=>{let s=dl.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=e,t.transporter.write({method:br.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Mbt=t=>(e,r,s)=>{let a=r.map(n=>({action:"addEntry",body:n}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!0,requests:a}},s),(n,c)=>cw(t)(n.taskID,c))},_bt=t=>(e,r)=>{let s=(a,n)=>Kt.createRetryablePromise(c=>VS(t)(e,n).catch(f=>{if(f.status!==404)throw f;return c()}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/keys/%s/restore",e)},r),s)},Ubt=t=>(e,r,s)=>{let a=r.map(n=>({action:"addEntry",body:n}));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("/1/dictionaries/%s/batch",e),data:{clearExistingDictionaryEntries:!1,requests:a}},s),(n,c)=>cw(t)(n.taskID,c))},Hbt=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode("/1/dictionaries/%s/search",e),data:{query:r},cacheable:!0},s),jbt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:e}},r),qbt=t=>(e,r)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:"/1/dictionaries/*/settings",data:e},r),(s,a)=>cw(t)(s.taskID,a)),Gbt=t=>(e,r)=>{let s=Object.assign({},r),{queryParameters:a,...n}=r||{},c=a?{queryParameters:a}:{},f=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],p=E=>Object.keys(s).filter(C=>f.indexOf(C)!==-1).every(C=>{if(Array.isArray(E[C])&&Array.isArray(s[C])){let S=E[C];return S.length===s[C].length&&S.every((P,I)=>P===s[C][I])}else return E[C]===s[C]}),h=(E,C)=>Kt.createRetryablePromise(S=>VS(t)(e,C).then(P=>p(P)?Promise.resolve():S()));return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Kt.encode("1/keys/%s",e),data:c},n),h)},cw=t=>(e,r)=>Kt.createRetryablePromise(s=>hSe(t)(e,r).then(a=>a.status!=="published"?s():void 0)),gSe=t=>(e,r)=>{let s=(a,n)=>gs(t)(a.taskID,n);return Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/batch",t.indexName),data:{requests:e}},r),s)},Wbt=t=>e=>dF({shouldStop:r=>r.cursor===void 0,...e,request:r=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/browse",t.indexName),data:r},e)}),Ybt=t=>e=>{let r={hitsPerPage:1e3,...e};return dF({shouldStop:s=>s.hits.length({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},Vbt=t=>e=>{let r={hitsPerPage:1e3,...e};return dF({shouldStop:s=>s.hits.length({...a,hits:a.hits.map(n=>(delete n._highlightResult,n))}))}})},yF=t=>(e,r,s)=>{let{batchSize:a,...n}=s||{},c={taskIDs:[],objectIDs:[]},f=(p=0)=>{let h=[],E;for(E=p;E({action:r,body:C})),n).then(C=>(c.objectIDs=c.objectIDs.concat(C.objectIDs),c.taskIDs.push(C.taskID),E++,f(E)))};return Kt.createWaitablePromise(f(),(p,h)=>Promise.all(p.taskIDs.map(E=>gs(t)(E,h))))},Kbt=t=>e=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/clear",t.indexName)},e),(r,s)=>gs(t)(r.taskID,s)),Jbt=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=dl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/rules/clear",t.indexName)},a),(n,c)=>gs(t)(n.taskID,c))},zbt=t=>e=>{let{forwardToReplicas:r,...s}=e||{},a=dl.createMappedRequestOptions(s);return r&&(a.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/synonyms/clear",t.indexName)},a),(n,c)=>gs(t)(n.taskID,c))},Zbt=t=>(e,r)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/deleteByQuery",t.indexName),data:e},r),(s,a)=>gs(t)(s.taskID,a)),Xbt=t=>e=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode("1/indexes/%s",t.indexName)},e),(r,s)=>gs(t)(r.taskID,s)),$bt=t=>(e,r)=>Kt.createWaitablePromise(dSe(t)([e],r).then(s=>({taskID:s.taskIDs[0]})),(s,a)=>gs(t)(s.taskID,a)),dSe=t=>(e,r)=>{let s=e.map(a=>({objectID:a}));return yF(t)(s,Qm.DeleteObject,r)},ePt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=dl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode("1/indexes/%s/rules/%s",t.indexName,e)},n),(c,f)=>gs(t)(c.taskID,f))},tPt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=dl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Delete,path:Kt.encode("1/indexes/%s/synonyms/%s",t.indexName,e)},n),(c,f)=>gs(t)(c.taskID,f))},rPt=t=>e=>mSe(t)(e).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),nPt=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode("1/answers/%s/prediction",t.indexName),data:{query:e,queryLanguages:r},cacheable:!0},s),iPt=t=>(e,r)=>{let{query:s,paginate:a,...n}=r||{},c=0,f=()=>ISe(t)(s||"",{...n,page:c}).then(p=>{for(let[h,E]of Object.entries(p.hits))if(e(E))return{object:E,position:parseInt(h,10),page:c};if(c++,a===!1||c>=p.nbPages)throw ASe();return f()});return f()},sPt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/indexes/%s/%s",t.indexName,e)},r),oPt=()=>(t,e)=>{for(let[r,s]of Object.entries(t.hits))if(s.objectID===e)return parseInt(r,10);return-1},aPt=t=>(e,r)=>{let{attributesToRetrieve:s,...a}=r||{},n=e.map(c=>({indexName:t.indexName,objectID:c,...s?{attributesToRetrieve:s}:{}}));return t.transporter.read({method:br.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:n}},a)},lPt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/indexes/%s/rules/%s",t.indexName,e)},r),mSe=t=>e=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/indexes/%s/settings",t.indexName),data:{getVersion:2}},e),cPt=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/indexes/%s/synonyms/%s",t.indexName,e)},r),ySe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Get,path:Kt.encode("1/indexes/%s/task/%s",t.indexName,e.toString())},r),uPt=t=>(e,r)=>Kt.createWaitablePromise(ESe(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>gs(t)(s.taskID,a)),ESe=t=>(e,r)=>{let{createIfNotExists:s,...a}=r||{},n=s?Qm.PartialUpdateObject:Qm.PartialUpdateObjectNoCreate;return yF(t)(e,n,a)},fPt=t=>(e,r)=>{let{safe:s,autoGenerateObjectIDIfNotExist:a,batchSize:n,...c}=r||{},f=(I,R,N,U)=>Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/operation",I),data:{operation:N,destination:R}},U),(W,te)=>gs(t)(W.taskID,te)),p=Math.random().toString(36).substring(7),h=`${t.indexName}_tmp_${p}`,E=G9({appId:t.appId,transporter:t.transporter,indexName:h}),C=[],S=f(t.indexName,h,"copy",{...c,scope:["settings","synonyms","rules"]});C.push(S);let P=(s?S.wait(c):S).then(()=>{let I=E(e,{...c,autoGenerateObjectIDIfNotExist:a,batchSize:n});return C.push(I),s?I.wait(c):I}).then(()=>{let I=f(h,t.indexName,"move",c);return C.push(I),s?I.wait(c):I}).then(()=>Promise.all(C)).then(([I,R,N])=>({objectIDs:R.objectIDs,taskIDs:[I.taskID,...R.taskIDs,N.taskID]}));return Kt.createWaitablePromise(P,(I,R)=>Promise.all(C.map(N=>N.wait(R))))},APt=t=>(e,r)=>W9(t)(e,{...r,clearExistingRules:!0}),pPt=t=>(e,r)=>Y9(t)(e,{...r,clearExistingSynonyms:!0}),hPt=t=>(e,r)=>Kt.createWaitablePromise(G9(t)([e],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,a)=>gs(t)(s.taskID,a)),G9=t=>(e,r)=>{let{autoGenerateObjectIDIfNotExist:s,...a}=r||{},n=s?Qm.AddObject:Qm.UpdateObject;if(n===Qm.UpdateObject){for(let c of e)if(c.objectID===void 0)return Kt.createWaitablePromise(Promise.reject(fSe()))}return yF(t)(e,n,a)},gPt=t=>(e,r)=>W9(t)([e],r),W9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingRules:a,...n}=r||{},c=dl.createMappedRequestOptions(n);return s&&(c.queryParameters.forwardToReplicas=1),a&&(c.queryParameters.clearExistingRules=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/rules/batch",t.indexName),data:e},c),(f,p)=>gs(t)(f.taskID,p))},dPt=t=>(e,r)=>Y9(t)([e],r),Y9=t=>(e,r)=>{let{forwardToReplicas:s,clearExistingSynonyms:a,replaceExistingSynonyms:n,...c}=r||{},f=dl.createMappedRequestOptions(c);return s&&(f.queryParameters.forwardToReplicas=1),(n||a)&&(f.queryParameters.replaceExistingSynonyms=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/synonyms/batch",t.indexName),data:e},f),(p,h)=>gs(t)(p.taskID,h))},ISe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/query",t.indexName),data:{query:e},cacheable:!0},r),CSe=t=>(e,r,s)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/facets/%s/query",t.indexName,e),data:{facetQuery:r},cacheable:!0},s),wSe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/rules/search",t.indexName),data:{query:e}},r),BSe=t=>(e,r)=>t.transporter.read({method:br.MethodEnum.Post,path:Kt.encode("1/indexes/%s/synonyms/search",t.indexName),data:{query:e}},r),mPt=t=>(e,r)=>{let{forwardToReplicas:s,...a}=r||{},n=dl.createMappedRequestOptions(a);return s&&(n.queryParameters.forwardToReplicas=1),Kt.createWaitablePromise(t.transporter.write({method:br.MethodEnum.Put,path:Kt.encode("1/indexes/%s/settings",t.indexName),data:e},n),(c,f)=>gs(t)(c.taskID,f))},gs=t=>(e,r)=>Kt.createRetryablePromise(s=>ySe(t)(e,r).then(a=>a.status!=="published"?s():void 0)),yPt={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",Inference:"inference",ListIndexes:"listIndexes",Logs:"logs",Personalization:"personalization",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},Qm={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject",DeleteIndex:"delete",ClearIndex:"clear"},EF={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},EPt={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},IPt={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};Ft.ApiKeyACLEnum=yPt;Ft.BatchActionEnum=Qm;Ft.ScopeEnum=EF;Ft.StrategyEnum=EPt;Ft.SynonymEnum=IPt;Ft.addApiKey=fbt;Ft.assignUserID=Abt;Ft.assignUserIDs=pbt;Ft.batch=gSe;Ft.browseObjects=Wbt;Ft.browseRules=Ybt;Ft.browseSynonyms=Vbt;Ft.chunkedBatch=yF;Ft.clearDictionaryEntries=hbt;Ft.clearObjects=Kbt;Ft.clearRules=Jbt;Ft.clearSynonyms=zbt;Ft.copyIndex=mF;Ft.copyRules=gbt;Ft.copySettings=dbt;Ft.copySynonyms=mbt;Ft.createBrowsablePromise=dF;Ft.createMissingObjectIDError=fSe;Ft.createObjectNotFoundError=ASe;Ft.createSearchClient=ubt;Ft.createValidUntilNotFoundError=pSe;Ft.customRequest=ybt;Ft.deleteApiKey=Ebt;Ft.deleteBy=Zbt;Ft.deleteDictionaryEntries=Ibt;Ft.deleteIndex=Xbt;Ft.deleteObject=$bt;Ft.deleteObjects=dSe;Ft.deleteRule=ePt;Ft.deleteSynonym=tPt;Ft.exists=rPt;Ft.findAnswers=nPt;Ft.findObject=iPt;Ft.generateSecuredApiKey=Cbt;Ft.getApiKey=VS;Ft.getAppTask=hSe;Ft.getDictionarySettings=wbt;Ft.getLogs=Bbt;Ft.getObject=sPt;Ft.getObjectPosition=oPt;Ft.getObjects=aPt;Ft.getRule=lPt;Ft.getSecuredApiKeyRemainingValidity=vbt;Ft.getSettings=mSe;Ft.getSynonym=cPt;Ft.getTask=ySe;Ft.getTopUserIDs=Sbt;Ft.getUserID=Dbt;Ft.hasPendingMappings=bbt;Ft.initIndex=KS;Ft.listApiKeys=Pbt;Ft.listClusters=xbt;Ft.listIndices=kbt;Ft.listUserIDs=Qbt;Ft.moveIndex=Tbt;Ft.multipleBatch=Rbt;Ft.multipleGetObjects=Fbt;Ft.multipleQueries=Nbt;Ft.multipleSearchForFacetValues=Obt;Ft.partialUpdateObject=uPt;Ft.partialUpdateObjects=ESe;Ft.removeUserID=Lbt;Ft.replaceAllObjects=fPt;Ft.replaceAllRules=APt;Ft.replaceAllSynonyms=pPt;Ft.replaceDictionaryEntries=Mbt;Ft.restoreApiKey=_bt;Ft.saveDictionaryEntries=Ubt;Ft.saveObject=hPt;Ft.saveObjects=G9;Ft.saveRule=gPt;Ft.saveRules=W9;Ft.saveSynonym=dPt;Ft.saveSynonyms=Y9;Ft.search=ISe;Ft.searchDictionaryEntries=Hbt;Ft.searchForFacetValues=CSe;Ft.searchRules=wSe;Ft.searchSynonyms=BSe;Ft.searchUserIDs=jbt;Ft.setDictionarySettings=qbt;Ft.setSettings=mPt;Ft.updateApiKey=Gbt;Ft.waitAppTask=cw;Ft.waitTask=gs});var DSe=L((yhr,SSe)=>{SSe.exports=vSe()});var bSe=L(IF=>{"use strict";Object.defineProperty(IF,"__esModule",{value:!0});function CPt(){return{debug(t,e){return Promise.resolve()},info(t,e){return Promise.resolve()},error(t,e){return Promise.resolve()}}}var wPt={Debug:1,Info:2,Error:3};IF.LogLevelEnum=wPt;IF.createNullLogger=CPt});var xSe=L((Ihr,PSe)=>{PSe.exports=bSe()});var RSe=L(V9=>{"use strict";Object.defineProperty(V9,"__esModule",{value:!0});var kSe=ye("http"),QSe=ye("https"),BPt=ye("url"),TSe={keepAlive:!0},vPt=new kSe.Agent(TSe),SPt=new QSe.Agent(TSe);function DPt({agent:t,httpAgent:e,httpsAgent:r,requesterOptions:s={}}={}){let a=e||t||vPt,n=r||t||SPt;return{send(c){return new Promise(f=>{let p=BPt.parse(c.url),h=p.query===null?p.pathname:`${p.pathname}?${p.query}`,E={...s,agent:p.protocol==="https:"?n:a,hostname:p.hostname,path:h,method:c.method,headers:{...s&&s.headers?s.headers:{},...c.headers},...p.port!==void 0?{port:p.port||""}:{}},C=(p.protocol==="https:"?QSe:kSe).request(E,R=>{let N=[];R.on("data",U=>{N=N.concat(U)}),R.on("end",()=>{clearTimeout(P),clearTimeout(I),f({status:R.statusCode||0,content:Buffer.concat(N).toString(),isTimedOut:!1})})}),S=(R,N)=>setTimeout(()=>{C.abort(),f({status:0,content:N,isTimedOut:!0})},R*1e3),P=S(c.connectTimeout,"Connection timeout"),I;C.on("error",R=>{clearTimeout(P),clearTimeout(I),f({status:0,content:R.message,isTimedOut:!1})}),C.once("response",()=>{clearTimeout(P),I=S(c.responseTimeout,"Socket timeout")}),c.data!==void 0&&C.write(c.data),C.end()})},destroy(){return a.destroy(),n.destroy(),Promise.resolve()}}}V9.createNodeHttpRequester=DPt});var NSe=L((whr,FSe)=>{FSe.exports=RSe()});var _Se=L((Bhr,MSe)=>{"use strict";var OSe=Rve(),bPt=Ove(),uw=oSe(),J9=HS(),K9=uSe(),jt=DSe(),PPt=xSe(),xPt=NSe(),kPt=GS();function LSe(t,e,r){let s={appId:t,apiKey:e,timeouts:{connect:2,read:5,write:30},requester:xPt.createNodeHttpRequester(),logger:PPt.createNullLogger(),responsesCache:OSe.createNullCache(),requestsCache:OSe.createNullCache(),hostsCache:bPt.createInMemoryCache(),userAgent:kPt.createUserAgent(J9.version).add({segment:"Node.js",version:process.versions.node})},a={...s,...r},n=()=>c=>K9.createPersonalizationClient({...s,...c,methods:{getPersonalizationStrategy:K9.getPersonalizationStrategy,setPersonalizationStrategy:K9.setPersonalizationStrategy}});return jt.createSearchClient({...a,methods:{search:jt.multipleQueries,searchForFacetValues:jt.multipleSearchForFacetValues,multipleBatch:jt.multipleBatch,multipleGetObjects:jt.multipleGetObjects,multipleQueries:jt.multipleQueries,copyIndex:jt.copyIndex,copySettings:jt.copySettings,copyRules:jt.copyRules,copySynonyms:jt.copySynonyms,moveIndex:jt.moveIndex,listIndices:jt.listIndices,getLogs:jt.getLogs,listClusters:jt.listClusters,multipleSearchForFacetValues:jt.multipleSearchForFacetValues,getApiKey:jt.getApiKey,addApiKey:jt.addApiKey,listApiKeys:jt.listApiKeys,updateApiKey:jt.updateApiKey,deleteApiKey:jt.deleteApiKey,restoreApiKey:jt.restoreApiKey,assignUserID:jt.assignUserID,assignUserIDs:jt.assignUserIDs,getUserID:jt.getUserID,searchUserIDs:jt.searchUserIDs,listUserIDs:jt.listUserIDs,getTopUserIDs:jt.getTopUserIDs,removeUserID:jt.removeUserID,hasPendingMappings:jt.hasPendingMappings,generateSecuredApiKey:jt.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:jt.getSecuredApiKeyRemainingValidity,destroy:J9.destroy,clearDictionaryEntries:jt.clearDictionaryEntries,deleteDictionaryEntries:jt.deleteDictionaryEntries,getDictionarySettings:jt.getDictionarySettings,getAppTask:jt.getAppTask,replaceDictionaryEntries:jt.replaceDictionaryEntries,saveDictionaryEntries:jt.saveDictionaryEntries,searchDictionaryEntries:jt.searchDictionaryEntries,setDictionarySettings:jt.setDictionarySettings,waitAppTask:jt.waitAppTask,customRequest:jt.customRequest,initIndex:c=>f=>jt.initIndex(c)(f,{methods:{batch:jt.batch,delete:jt.deleteIndex,findAnswers:jt.findAnswers,getObject:jt.getObject,getObjects:jt.getObjects,saveObject:jt.saveObject,saveObjects:jt.saveObjects,search:jt.search,searchForFacetValues:jt.searchForFacetValues,waitTask:jt.waitTask,setSettings:jt.setSettings,getSettings:jt.getSettings,partialUpdateObject:jt.partialUpdateObject,partialUpdateObjects:jt.partialUpdateObjects,deleteObject:jt.deleteObject,deleteObjects:jt.deleteObjects,deleteBy:jt.deleteBy,clearObjects:jt.clearObjects,browseObjects:jt.browseObjects,getObjectPosition:jt.getObjectPosition,findObject:jt.findObject,exists:jt.exists,saveSynonym:jt.saveSynonym,saveSynonyms:jt.saveSynonyms,getSynonym:jt.getSynonym,searchSynonyms:jt.searchSynonyms,browseSynonyms:jt.browseSynonyms,deleteSynonym:jt.deleteSynonym,clearSynonyms:jt.clearSynonyms,replaceAllObjects:jt.replaceAllObjects,replaceAllSynonyms:jt.replaceAllSynonyms,searchRules:jt.searchRules,getRule:jt.getRule,deleteRule:jt.deleteRule,saveRule:jt.saveRule,saveRules:jt.saveRules,replaceAllRules:jt.replaceAllRules,browseRules:jt.browseRules,clearRules:jt.clearRules}}),initAnalytics:()=>c=>uw.createAnalyticsClient({...s,...c,methods:{addABTest:uw.addABTest,getABTest:uw.getABTest,getABTests:uw.getABTests,stopABTest:uw.stopABTest,deleteABTest:uw.deleteABTest}}),initPersonalization:n,initRecommendation:()=>c=>(a.logger.info("The `initRecommendation` method is deprecated. Use `initPersonalization` instead."),n()(c))}})}LSe.version=J9.version;MSe.exports=LSe});var Z9=L((vhr,z9)=>{var USe=_Se();z9.exports=USe;z9.exports.default=USe});var eW=L((Dhr,qSe)=>{"use strict";var jSe=Object.getOwnPropertySymbols,TPt=Object.prototype.hasOwnProperty,RPt=Object.prototype.propertyIsEnumerable;function FPt(t){if(t==null)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}function NPt(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de",Object.getOwnPropertyNames(t)[0]==="5")return!1;for(var e={},r=0;r<10;r++)e["_"+String.fromCharCode(r)]=r;var s=Object.getOwnPropertyNames(e).map(function(n){return e[n]});if(s.join("")!=="0123456789")return!1;var a={};return"abcdefghijklmnopqrst".split("").forEach(function(n){a[n]=n}),Object.keys(Object.assign({},a)).join("")==="abcdefghijklmnopqrst"}catch{return!1}}qSe.exports=NPt()?Object.assign:function(t,e){for(var r,s=FPt(t),a,n=1;n{"use strict";var rW=eW(),fw=60103,YSe=60106;Dn.Fragment=60107;Dn.StrictMode=60108;Dn.Profiler=60114;var VSe=60109,KSe=60110,JSe=60112;Dn.Suspense=60113;var zSe=60115,ZSe=60116;typeof Symbol=="function"&&Symbol.for&&(Wc=Symbol.for,fw=Wc("react.element"),YSe=Wc("react.portal"),Dn.Fragment=Wc("react.fragment"),Dn.StrictMode=Wc("react.strict_mode"),Dn.Profiler=Wc("react.profiler"),VSe=Wc("react.provider"),KSe=Wc("react.context"),JSe=Wc("react.forward_ref"),Dn.Suspense=Wc("react.suspense"),zSe=Wc("react.memo"),ZSe=Wc("react.lazy"));var Wc,GSe=typeof Symbol=="function"&&Symbol.iterator;function OPt(t){return t===null||typeof t!="object"?null:(t=GSe&&t[GSe]||t["@@iterator"],typeof t=="function"?t:null)}function JS(t){for(var e="https://reactjs.org/docs/error-decoder.html?invariant="+t,r=1;r{"use strict";oDe.exports=sDe()});var lW=L((xhr,aW)=>{"use strict";var Cn=aW.exports;aW.exports.default=Cn;var Zn="\x1B[",zS="\x1B]",pw="\x07",BF=";",aDe=process.env.TERM_PROGRAM==="Apple_Terminal";Cn.cursorTo=(t,e)=>{if(typeof t!="number")throw new TypeError("The `x` argument is required");return typeof e!="number"?Zn+(t+1)+"G":Zn+(e+1)+";"+(t+1)+"H"};Cn.cursorMove=(t,e)=>{if(typeof t!="number")throw new TypeError("The `x` argument is required");let r="";return t<0?r+=Zn+-t+"D":t>0&&(r+=Zn+t+"C"),e<0?r+=Zn+-e+"A":e>0&&(r+=Zn+e+"B"),r};Cn.cursorUp=(t=1)=>Zn+t+"A";Cn.cursorDown=(t=1)=>Zn+t+"B";Cn.cursorForward=(t=1)=>Zn+t+"C";Cn.cursorBackward=(t=1)=>Zn+t+"D";Cn.cursorLeft=Zn+"G";Cn.cursorSavePosition=aDe?"\x1B7":Zn+"s";Cn.cursorRestorePosition=aDe?"\x1B8":Zn+"u";Cn.cursorGetPosition=Zn+"6n";Cn.cursorNextLine=Zn+"E";Cn.cursorPrevLine=Zn+"F";Cn.cursorHide=Zn+"?25l";Cn.cursorShow=Zn+"?25h";Cn.eraseLines=t=>{let e="";for(let r=0;r[zS,"8",BF,BF,e,pw,t,zS,"8",BF,BF,pw].join("");Cn.image=(t,e={})=>{let r=`${zS}1337;File=inline=1`;return e.width&&(r+=`;width=${e.width}`),e.height&&(r+=`;height=${e.height}`),e.preserveAspectRatio===!1&&(r+=";preserveAspectRatio=0"),r+":"+t.toString("base64")+pw};Cn.iTerm={setCwd:(t=process.cwd())=>`${zS}50;CurrentDir=${t}${pw}`,annotation:(t,e={})=>{let r=`${zS}1337;`,s=typeof e.x<"u",a=typeof e.y<"u";if((s||a)&&!(s&&a&&typeof e.length<"u"))throw new Error("`x`, `y` and `length` must be defined when `x` or `y` is defined");return t=t.replace(/\|/g,""),r+=e.isHidden?"AddHiddenAnnotation=":"AddAnnotation=",e.length>0?r+=(s?[t,e.length,e.x,e.y]:[e.length,t]).join("|"):r+=t,r+pw}}});var cDe=L((khr,cW)=>{"use strict";var lDe=(t,e)=>{for(let r of Reflect.ownKeys(e))Object.defineProperty(t,r,Object.getOwnPropertyDescriptor(e,r));return t};cW.exports=lDe;cW.exports.default=lDe});var fDe=L((Qhr,SF)=>{"use strict";var HPt=cDe(),vF=new WeakMap,uDe=(t,e={})=>{if(typeof t!="function")throw new TypeError("Expected a function");let r,s=0,a=t.displayName||t.name||"",n=function(...c){if(vF.set(n,++s),s===1)r=t.apply(this,c),t=null;else if(e.throw===!0)throw new Error(`Function \`${a}\` can only be called once`);return r};return HPt(n,t),vF.set(n,s),n};SF.exports=uDe;SF.exports.default=uDe;SF.exports.callCount=t=>{if(!vF.has(t))throw new Error(`The given function \`${t.name}\` is not wrapped by the \`onetime\` package`);return vF.get(t)}});var ADe=L((Thr,DF)=>{DF.exports=["SIGABRT","SIGALRM","SIGHUP","SIGINT","SIGTERM"];process.platform!=="win32"&&DF.exports.push("SIGVTALRM","SIGXCPU","SIGXFSZ","SIGUSR2","SIGTRAP","SIGSYS","SIGQUIT","SIGIOT");process.platform==="linux"&&DF.exports.push("SIGIO","SIGPOLL","SIGPWR","SIGSTKFLT","SIGUNUSED")});var AW=L((Rhr,dw)=>{var Qi=global.process,Tm=function(t){return t&&typeof t=="object"&&typeof t.removeListener=="function"&&typeof t.emit=="function"&&typeof t.reallyExit=="function"&&typeof t.listeners=="function"&&typeof t.kill=="function"&&typeof t.pid=="number"&&typeof t.on=="function"};Tm(Qi)?(pDe=ye("assert"),hw=ADe(),hDe=/^win/i.test(Qi.platform),ZS=ye("events"),typeof ZS!="function"&&(ZS=ZS.EventEmitter),Qi.__signal_exit_emitter__?Js=Qi.__signal_exit_emitter__:(Js=Qi.__signal_exit_emitter__=new ZS,Js.count=0,Js.emitted={}),Js.infinite||(Js.setMaxListeners(1/0),Js.infinite=!0),dw.exports=function(t,e){if(!Tm(global.process))return function(){};pDe.equal(typeof t,"function","a callback must be provided for exit handler"),gw===!1&&uW();var r="exit";e&&e.alwaysLast&&(r="afterexit");var s=function(){Js.removeListener(r,t),Js.listeners("exit").length===0&&Js.listeners("afterexit").length===0&&bF()};return Js.on(r,t),s},bF=function(){!gw||!Tm(global.process)||(gw=!1,hw.forEach(function(e){try{Qi.removeListener(e,PF[e])}catch{}}),Qi.emit=xF,Qi.reallyExit=fW,Js.count-=1)},dw.exports.unload=bF,Rm=function(e,r,s){Js.emitted[e]||(Js.emitted[e]=!0,Js.emit(e,r,s))},PF={},hw.forEach(function(t){PF[t]=function(){if(Tm(global.process)){var r=Qi.listeners(t);r.length===Js.count&&(bF(),Rm("exit",null,t),Rm("afterexit",null,t),hDe&&t==="SIGHUP"&&(t="SIGINT"),Qi.kill(Qi.pid,t))}}}),dw.exports.signals=function(){return hw},gw=!1,uW=function(){gw||!Tm(global.process)||(gw=!0,Js.count+=1,hw=hw.filter(function(e){try{return Qi.on(e,PF[e]),!0}catch{return!1}}),Qi.emit=dDe,Qi.reallyExit=gDe)},dw.exports.load=uW,fW=Qi.reallyExit,gDe=function(e){Tm(global.process)&&(Qi.exitCode=e||0,Rm("exit",Qi.exitCode,null),Rm("afterexit",Qi.exitCode,null),fW.call(Qi,Qi.exitCode))},xF=Qi.emit,dDe=function(e,r){if(e==="exit"&&Tm(global.process)){r!==void 0&&(Qi.exitCode=r);var s=xF.apply(this,arguments);return Rm("exit",Qi.exitCode,null),Rm("afterexit",Qi.exitCode,null),s}else return xF.apply(this,arguments)}):dw.exports=function(){return function(){}};var pDe,hw,hDe,ZS,Js,bF,Rm,PF,gw,uW,fW,gDe,xF,dDe});var yDe=L((Fhr,mDe)=>{"use strict";var jPt=fDe(),qPt=AW();mDe.exports=jPt(()=>{qPt(()=>{process.stderr.write("\x1B[?25h")},{alwaysLast:!0})})});var pW=L(mw=>{"use strict";var GPt=yDe(),kF=!1;mw.show=(t=process.stderr)=>{t.isTTY&&(kF=!1,t.write("\x1B[?25h"))};mw.hide=(t=process.stderr)=>{t.isTTY&&(GPt(),kF=!0,t.write("\x1B[?25l"))};mw.toggle=(t,e)=>{t!==void 0&&(kF=t),kF?mw.show(e):mw.hide(e)}});var wDe=L(XS=>{"use strict";var CDe=XS&&XS.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(XS,"__esModule",{value:!0});var EDe=CDe(lW()),IDe=CDe(pW()),WPt=(t,{showCursor:e=!1}={})=>{let r=0,s="",a=!1,n=c=>{!e&&!a&&(IDe.default.hide(),a=!0);let f=c+` +`;f!==s&&(s=f,t.write(EDe.default.eraseLines(r)+f),r=f.split(` +`).length)};return n.clear=()=>{t.write(EDe.default.eraseLines(r)),s="",r=0},n.done=()=>{s="",r=0,e||(IDe.default.show(),a=!1)},n};XS.default={create:WPt}});var BDe=L((Lhr,YPt)=>{YPt.exports=[{name:"AppVeyor",constant:"APPVEYOR",env:"APPVEYOR",pr:"APPVEYOR_PULL_REQUEST_NUMBER"},{name:"Azure Pipelines",constant:"AZURE_PIPELINES",env:"SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",pr:"SYSTEM_PULLREQUEST_PULLREQUESTID"},{name:"Bamboo",constant:"BAMBOO",env:"bamboo_planKey"},{name:"Bitbucket Pipelines",constant:"BITBUCKET",env:"BITBUCKET_COMMIT",pr:"BITBUCKET_PR_ID"},{name:"Bitrise",constant:"BITRISE",env:"BITRISE_IO",pr:"BITRISE_PULL_REQUEST"},{name:"Buddy",constant:"BUDDY",env:"BUDDY_WORKSPACE_ID",pr:"BUDDY_EXECUTION_PULL_REQUEST_ID"},{name:"Buildkite",constant:"BUILDKITE",env:"BUILDKITE",pr:{env:"BUILDKITE_PULL_REQUEST",ne:"false"}},{name:"CircleCI",constant:"CIRCLE",env:"CIRCLECI",pr:"CIRCLE_PULL_REQUEST"},{name:"Cirrus CI",constant:"CIRRUS",env:"CIRRUS_CI",pr:"CIRRUS_PR"},{name:"AWS CodeBuild",constant:"CODEBUILD",env:"CODEBUILD_BUILD_ARN"},{name:"Codeship",constant:"CODESHIP",env:{CI_NAME:"codeship"}},{name:"Drone",constant:"DRONE",env:"DRONE",pr:{DRONE_BUILD_EVENT:"pull_request"}},{name:"dsari",constant:"DSARI",env:"DSARI"},{name:"GitLab CI",constant:"GITLAB",env:"GITLAB_CI"},{name:"GoCD",constant:"GOCD",env:"GO_PIPELINE_LABEL"},{name:"Hudson",constant:"HUDSON",env:"HUDSON_URL"},{name:"Jenkins",constant:"JENKINS",env:["JENKINS_URL","BUILD_ID"],pr:{any:["ghprbPullId","CHANGE_ID"]}},{name:"Magnum CI",constant:"MAGNUM",env:"MAGNUM"},{name:"Netlify CI",constant:"NETLIFY",env:"NETLIFY_BUILD_BASE",pr:{env:"PULL_REQUEST",ne:"false"}},{name:"Sail CI",constant:"SAIL",env:"SAILCI",pr:"SAIL_PULL_REQUEST_NUMBER"},{name:"Semaphore",constant:"SEMAPHORE",env:"SEMAPHORE",pr:"PULL_REQUEST_NUMBER"},{name:"Shippable",constant:"SHIPPABLE",env:"SHIPPABLE",pr:{IS_PULL_REQUEST:"true"}},{name:"Solano CI",constant:"SOLANO",env:"TDDIUM",pr:"TDDIUM_PR_ID"},{name:"Strider CD",constant:"STRIDER",env:"STRIDER"},{name:"TaskCluster",constant:"TASKCLUSTER",env:["TASK_ID","RUN_ID"]},{name:"TeamCity",constant:"TEAMCITY",env:"TEAMCITY_VERSION"},{name:"Travis CI",constant:"TRAVIS",env:"TRAVIS",pr:{env:"TRAVIS_PULL_REQUEST",ne:"false"}}]});var DDe=L(rc=>{"use strict";var SDe=BDe(),AA=process.env;Object.defineProperty(rc,"_vendors",{value:SDe.map(function(t){return t.constant})});rc.name=null;rc.isPR=null;SDe.forEach(function(t){var e=Array.isArray(t.env)?t.env:[t.env],r=e.every(function(s){return vDe(s)});if(rc[t.constant]=r,r)switch(rc.name=t.name,typeof t.pr){case"string":rc.isPR=!!AA[t.pr];break;case"object":"env"in t.pr?rc.isPR=t.pr.env in AA&&AA[t.pr.env]!==t.pr.ne:"any"in t.pr?rc.isPR=t.pr.any.some(function(s){return!!AA[s]}):rc.isPR=vDe(t.pr);break;default:rc.isPR=null}});rc.isCI=!!(AA.CI||AA.CONTINUOUS_INTEGRATION||AA.BUILD_NUMBER||AA.RUN_ID||rc.name);function vDe(t){return typeof t=="string"?!!AA[t]:Object.keys(t).every(function(e){return AA[e]===t[e]})}});var PDe=L((_hr,bDe)=>{"use strict";bDe.exports=DDe().isCI});var kDe=L((Uhr,xDe)=>{"use strict";var VPt=t=>{let e=new Set;do for(let r of Reflect.ownKeys(t))e.add([t,r]);while((t=Reflect.getPrototypeOf(t))&&t!==Object.prototype);return e};xDe.exports=(t,{include:e,exclude:r}={})=>{let s=a=>{let n=c=>typeof c=="string"?a===c:c.test(a);return e?e.some(n):r?!r.some(n):!0};for(let[a,n]of VPt(t.constructor.prototype)){if(n==="constructor"||!s(n))continue;let c=Reflect.getOwnPropertyDescriptor(a,n);c&&typeof c.value=="function"&&(t[n]=t[n].bind(t))}return t}});var ODe=L(Vn=>{"use strict";var Ew,tD,FF,IW;typeof performance=="object"&&typeof performance.now=="function"?(QDe=performance,Vn.unstable_now=function(){return QDe.now()}):(hW=Date,TDe=hW.now(),Vn.unstable_now=function(){return hW.now()-TDe});var QDe,hW,TDe;typeof window>"u"||typeof MessageChannel!="function"?(yw=null,gW=null,dW=function(){if(yw!==null)try{var t=Vn.unstable_now();yw(!0,t),yw=null}catch(e){throw setTimeout(dW,0),e}},Ew=function(t){yw!==null?setTimeout(Ew,0,t):(yw=t,setTimeout(dW,0))},tD=function(t,e){gW=setTimeout(t,e)},FF=function(){clearTimeout(gW)},Vn.unstable_shouldYield=function(){return!1},IW=Vn.unstable_forceFrameRate=function(){}):(RDe=window.setTimeout,FDe=window.clearTimeout,typeof console<"u"&&(NDe=window.cancelAnimationFrame,typeof window.requestAnimationFrame!="function"&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills"),typeof NDe!="function"&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://reactjs.org/link/react-polyfills")),$S=!1,eD=null,QF=-1,mW=5,yW=0,Vn.unstable_shouldYield=function(){return Vn.unstable_now()>=yW},IW=function(){},Vn.unstable_forceFrameRate=function(t){0>t||125>>1,a=t[s];if(a!==void 0&&0RF(c,r))p!==void 0&&0>RF(p,c)?(t[s]=p,t[f]=r,s=f):(t[s]=c,t[n]=r,s=n);else if(p!==void 0&&0>RF(p,r))t[s]=p,t[f]=r,s=f;else break e}}return e}return null}function RF(t,e){var r=t.sortIndex-e.sortIndex;return r!==0?r:t.id-e.id}var pA=[],$0=[],KPt=1,Yc=null,ea=3,OF=!1,Fm=!1,rD=!1;function wW(t){for(var e=tf($0);e!==null;){if(e.callback===null)NF($0);else if(e.startTime<=t)NF($0),e.sortIndex=e.expirationTime,CW(pA,e);else break;e=tf($0)}}function BW(t){if(rD=!1,wW(t),!Fm)if(tf(pA)!==null)Fm=!0,Ew(vW);else{var e=tf($0);e!==null&&tD(BW,e.startTime-t)}}function vW(t,e){Fm=!1,rD&&(rD=!1,FF()),OF=!0;var r=ea;try{for(wW(e),Yc=tf(pA);Yc!==null&&(!(Yc.expirationTime>e)||t&&!Vn.unstable_shouldYield());){var s=Yc.callback;if(typeof s=="function"){Yc.callback=null,ea=Yc.priorityLevel;var a=s(Yc.expirationTime<=e);e=Vn.unstable_now(),typeof a=="function"?Yc.callback=a:Yc===tf(pA)&&NF(pA),wW(e)}else NF(pA);Yc=tf(pA)}if(Yc!==null)var n=!0;else{var c=tf($0);c!==null&&tD(BW,c.startTime-e),n=!1}return n}finally{Yc=null,ea=r,OF=!1}}var JPt=IW;Vn.unstable_IdlePriority=5;Vn.unstable_ImmediatePriority=1;Vn.unstable_LowPriority=4;Vn.unstable_NormalPriority=3;Vn.unstable_Profiling=null;Vn.unstable_UserBlockingPriority=2;Vn.unstable_cancelCallback=function(t){t.callback=null};Vn.unstable_continueExecution=function(){Fm||OF||(Fm=!0,Ew(vW))};Vn.unstable_getCurrentPriorityLevel=function(){return ea};Vn.unstable_getFirstCallbackNode=function(){return tf(pA)};Vn.unstable_next=function(t){switch(ea){case 1:case 2:case 3:var e=3;break;default:e=ea}var r=ea;ea=e;try{return t()}finally{ea=r}};Vn.unstable_pauseExecution=function(){};Vn.unstable_requestPaint=JPt;Vn.unstable_runWithPriority=function(t,e){switch(t){case 1:case 2:case 3:case 4:case 5:break;default:t=3}var r=ea;ea=t;try{return e()}finally{ea=r}};Vn.unstable_scheduleCallback=function(t,e,r){var s=Vn.unstable_now();switch(typeof r=="object"&&r!==null?(r=r.delay,r=typeof r=="number"&&0s?(t.sortIndex=r,CW($0,t),tf(pA)===null&&t===tf($0)&&(rD?FF():rD=!0,tD(BW,r-s))):(t.sortIndex=a,CW(pA,t),Fm||OF||(Fm=!0,Ew(vW))),t};Vn.unstable_wrapCallback=function(t){var e=ea;return function(){var r=ea;ea=e;try{return t.apply(this,arguments)}finally{ea=r}}}});var SW=L((jhr,LDe)=>{"use strict";LDe.exports=ODe()});var MDe=L((qhr,nD)=>{nD.exports=function(e){var r={},s=eW(),a=hn(),n=SW();function c(v){for(var D="https://reactjs.org/docs/error-decoder.html?invariant="+v,Q=1;QUe||V[Se]!==ne[Ue])return` +`+V[Se].replace(" at new "," at ");while(1<=Se&&0<=Ue);break}}}finally{ve=!1,Error.prepareStackTrace=Q}return(v=v?v.displayName||v.name:"")?ac(v):""}var lc=[],Ni=-1;function io(v){return{current:v}}function Rt(v){0>Ni||(v.current=lc[Ni],lc[Ni]=null,Ni--)}function xn(v,D){Ni++,lc[Ni]=v.current,v.current=D}var ca={},ji=io(ca),Oi=io(!1),Oa=ca;function dn(v,D){var Q=v.type.contextTypes;if(!Q)return ca;var H=v.stateNode;if(H&&H.__reactInternalMemoizedUnmaskedChildContext===D)return H.__reactInternalMemoizedMaskedChildContext;var V={},ne;for(ne in Q)V[ne]=D[ne];return H&&(v=v.stateNode,v.__reactInternalMemoizedUnmaskedChildContext=D,v.__reactInternalMemoizedMaskedChildContext=V),V}function Jn(v){return v=v.childContextTypes,v!=null}function hu(){Rt(Oi),Rt(ji)}function Ch(v,D,Q){if(ji.current!==ca)throw Error(c(168));xn(ji,D),xn(Oi,Q)}function La(v,D,Q){var H=v.stateNode;if(v=D.childContextTypes,typeof H.getChildContext!="function")return Q;H=H.getChildContext();for(var V in H)if(!(V in v))throw Error(c(108,g(D)||"Unknown",V));return s({},Q,H)}function Ma(v){return v=(v=v.stateNode)&&v.__reactInternalMemoizedMergedChildContext||ca,Oa=ji.current,xn(ji,v),xn(Oi,Oi.current),!0}function Ua(v,D,Q){var H=v.stateNode;if(!H)throw Error(c(169));Q?(v=La(v,D,Oa),H.__reactInternalMemoizedMergedChildContext=v,Rt(Oi),Rt(ji),xn(ji,v)):Rt(Oi),xn(Oi,Q)}var Xe=null,Ha=null,gf=n.unstable_now;gf();var cc=0,wn=8;function ua(v){if(1&v)return wn=15,1;if(2&v)return wn=14,2;if(4&v)return wn=13,4;var D=24&v;return D!==0?(wn=12,D):v&32?(wn=11,32):(D=192&v,D!==0?(wn=10,D):v&256?(wn=9,256):(D=3584&v,D!==0?(wn=8,D):v&4096?(wn=7,4096):(D=4186112&v,D!==0?(wn=6,D):(D=62914560&v,D!==0?(wn=5,D):v&67108864?(wn=4,67108864):v&134217728?(wn=3,134217728):(D=805306368&v,D!==0?(wn=2,D):1073741824&v?(wn=1,1073741824):(wn=8,v))))))}function _A(v){switch(v){case 99:return 15;case 98:return 10;case 97:case 96:return 8;case 95:return 2;default:return 0}}function UA(v){switch(v){case 15:case 14:return 99;case 13:case 12:case 11:case 10:return 98;case 9:case 8:case 7:case 6:case 4:case 5:return 97;case 3:case 2:case 1:return 95;case 0:return 90;default:throw Error(c(358,v))}}function fa(v,D){var Q=v.pendingLanes;if(Q===0)return wn=0;var H=0,V=0,ne=v.expiredLanes,Se=v.suspendedLanes,Ue=v.pingedLanes;if(ne!==0)H=ne,V=wn=15;else if(ne=Q&134217727,ne!==0){var At=ne&~Se;At!==0?(H=ua(At),V=wn):(Ue&=ne,Ue!==0&&(H=ua(Ue),V=wn))}else ne=Q&~Se,ne!==0?(H=ua(ne),V=wn):Ue!==0&&(H=ua(Ue),V=wn);if(H===0)return 0;if(H=31-ns(H),H=Q&((0>H?0:1<Q;Q++)D.push(v);return D}function ja(v,D,Q){v.pendingLanes|=D;var H=D-1;v.suspendedLanes&=H,v.pingedLanes&=H,v=v.eventTimes,D=31-ns(D),v[D]=Q}var ns=Math.clz32?Math.clz32:fc,uc=Math.log,gu=Math.LN2;function fc(v){return v===0?32:31-(uc(v)/gu|0)|0}var qa=n.unstable_runWithPriority,Li=n.unstable_scheduleCallback,Cs=n.unstable_cancelCallback,Sl=n.unstable_shouldYield,df=n.unstable_requestPaint,Ac=n.unstable_now,wi=n.unstable_getCurrentPriorityLevel,Qn=n.unstable_ImmediatePriority,pc=n.unstable_UserBlockingPriority,Je=n.unstable_NormalPriority,st=n.unstable_LowPriority,St=n.unstable_IdlePriority,lr={},ee=df!==void 0?df:function(){},Ie=null,Oe=null,ht=!1,mt=Ac(),Dt=1e4>mt?Ac:function(){return Ac()-mt};function tr(){switch(wi()){case Qn:return 99;case pc:return 98;case Je:return 97;case st:return 96;case St:return 95;default:throw Error(c(332))}}function fn(v){switch(v){case 99:return Qn;case 98:return pc;case 97:return Je;case 96:return st;case 95:return St;default:throw Error(c(332))}}function ai(v,D){return v=fn(v),qa(v,D)}function qi(v,D,Q){return v=fn(v),Li(v,D,Q)}function Tn(){if(Oe!==null){var v=Oe;Oe=null,Cs(v)}Ga()}function Ga(){if(!ht&&Ie!==null){ht=!0;var v=0;try{var D=Ie;ai(99,function(){for(;vRn?(Un=kr,kr=null):Un=kr.sibling;var zr=Xt($e,kr,pt[Rn],Zt);if(zr===null){kr===null&&(kr=Un);break}v&&kr&&zr.alternate===null&&D($e,kr),qe=ne(zr,qe,Rn),Xn===null?Sr=zr:Xn.sibling=zr,Xn=zr,kr=Un}if(Rn===pt.length)return Q($e,kr),Sr;if(kr===null){for(;RnRn?(Un=kr,kr=null):Un=kr.sibling;var li=Xt($e,kr,zr.value,Zt);if(li===null){kr===null&&(kr=Un);break}v&&kr&&li.alternate===null&&D($e,kr),qe=ne(li,qe,Rn),Xn===null?Sr=li:Xn.sibling=li,Xn=li,kr=Un}if(zr.done)return Q($e,kr),Sr;if(kr===null){for(;!zr.done;Rn++,zr=pt.next())zr=Lr($e,zr.value,Zt),zr!==null&&(qe=ne(zr,qe,Rn),Xn===null?Sr=zr:Xn.sibling=zr,Xn=zr);return Sr}for(kr=H($e,kr);!zr.done;Rn++,zr=pt.next())zr=zn(kr,$e,Rn,zr.value,Zt),zr!==null&&(v&&zr.alternate!==null&&kr.delete(zr.key===null?Rn:zr.key),qe=ne(zr,qe,Rn),Xn===null?Sr=zr:Xn.sibling=zr,Xn=zr);return v&&kr.forEach(function(Pu){return D($e,Pu)}),Sr}return function($e,qe,pt,Zt){var Sr=typeof pt=="object"&&pt!==null&&pt.type===E&&pt.key===null;Sr&&(pt=pt.props.children);var Xn=typeof pt=="object"&&pt!==null;if(Xn)switch(pt.$$typeof){case p:e:{for(Xn=pt.key,Sr=qe;Sr!==null;){if(Sr.key===Xn){switch(Sr.tag){case 7:if(pt.type===E){Q($e,Sr.sibling),qe=V(Sr,pt.props.children),qe.return=$e,$e=qe;break e}break;default:if(Sr.elementType===pt.type){Q($e,Sr.sibling),qe=V(Sr,pt.props),qe.ref=dt($e,Sr,pt),qe.return=$e,$e=qe;break e}}Q($e,Sr);break}else D($e,Sr);Sr=Sr.sibling}pt.type===E?(qe=Qf(pt.props.children,$e.mode,Zt,pt.key),qe.return=$e,$e=qe):(Zt=od(pt.type,pt.key,pt.props,null,$e.mode,Zt),Zt.ref=dt($e,qe,pt),Zt.return=$e,$e=Zt)}return Se($e);case h:e:{for(Sr=pt.key;qe!==null;){if(qe.key===Sr)if(qe.tag===4&&qe.stateNode.containerInfo===pt.containerInfo&&qe.stateNode.implementation===pt.implementation){Q($e,qe.sibling),qe=V(qe,pt.children||[]),qe.return=$e,$e=qe;break e}else{Q($e,qe);break}else D($e,qe);qe=qe.sibling}qe=Ro(pt,$e.mode,Zt),qe.return=$e,$e=qe}return Se($e)}if(typeof pt=="string"||typeof pt=="number")return pt=""+pt,qe!==null&&qe.tag===6?(Q($e,qe.sibling),qe=V(qe,pt),qe.return=$e,$e=qe):(Q($e,qe),qe=k2(pt,$e.mode,Zt),qe.return=$e,$e=qe),Se($e);if(yf(pt))return mi($e,qe,pt,Zt);if(Ce(pt))return Za($e,qe,pt,Zt);if(Xn&&mu($e,pt),typeof pt>"u"&&!Sr)switch($e.tag){case 1:case 22:case 0:case 11:case 15:throw Error(c(152,g($e.type)||"Component"))}return Q($e,qe)}}var _g=By(!0),n2=By(!1),bh={},ur=io(bh),zi=io(bh),Ef=io(bh);function Wa(v){if(v===bh)throw Error(c(174));return v}function Ug(v,D){xn(Ef,D),xn(zi,v),xn(ur,bh),v=gt(D),Rt(ur),xn(ur,v)}function yu(){Rt(ur),Rt(zi),Rt(Ef)}function If(v){var D=Wa(Ef.current),Q=Wa(ur.current);D=j(Q,v.type,D),Q!==D&&(xn(zi,v),xn(ur,D))}function wt(v){zi.current===v&&(Rt(ur),Rt(zi))}var gi=io(0);function WA(v){for(var D=v;D!==null;){if(D.tag===13){var Q=D.memoizedState;if(Q!==null&&(Q=Q.dehydrated,Q===null||gr(Q)||So(Q)))return D}else if(D.tag===19&&D.memoizedProps.revealOrder!==void 0){if(D.flags&64)return D}else if(D.child!==null){D.child.return=D,D=D.child;continue}if(D===v)break;for(;D.sibling===null;){if(D.return===null||D.return===v)return null;D=D.return}D.sibling.return=D.return,D=D.sibling}return null}var Ya=null,pa=null,Va=!1;function Hg(v,D){var Q=za(5,null,null,0);Q.elementType="DELETED",Q.type="DELETED",Q.stateNode=D,Q.return=v,Q.flags=8,v.lastEffect!==null?(v.lastEffect.nextEffect=Q,v.lastEffect=Q):v.firstEffect=v.lastEffect=Q}function Ph(v,D){switch(v.tag){case 5:return D=la(D,v.type,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 6:return D=OA(D,v.pendingProps),D!==null?(v.stateNode=D,!0):!1;case 13:return!1;default:return!1}}function jg(v){if(Va){var D=pa;if(D){var Q=D;if(!Ph(v,D)){if(D=Me(Q),!D||!Ph(v,D)){v.flags=v.flags&-1025|2,Va=!1,Ya=v;return}Hg(Ya,Q)}Ya=v,pa=fu(D)}else v.flags=v.flags&-1025|2,Va=!1,Ya=v}}function vy(v){for(v=v.return;v!==null&&v.tag!==5&&v.tag!==3&&v.tag!==13;)v=v.return;Ya=v}function YA(v){if(!Z||v!==Ya)return!1;if(!Va)return vy(v),Va=!0,!1;var D=v.type;if(v.tag!==5||D!=="head"&&D!=="body"&&!it(D,v.memoizedProps))for(D=pa;D;)Hg(v,D),D=Me(D);if(vy(v),v.tag===13){if(!Z)throw Error(c(316));if(v=v.memoizedState,v=v!==null?v.dehydrated:null,!v)throw Error(c(317));pa=LA(v)}else pa=Ya?Me(v.stateNode):null;return!0}function qg(){Z&&(pa=Ya=null,Va=!1)}var Eu=[];function Iu(){for(var v=0;vne))throw Error(c(301));ne+=1,xi=is=null,D.updateQueue=null,Cf.current=re,v=Q(H,V)}while(wf)}if(Cf.current=kt,D=is!==null&&is.next!==null,Cu=0,xi=is=qn=null,VA=!1,D)throw Error(c(300));return v}function ss(){var v={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return xi===null?qn.memoizedState=xi=v:xi=xi.next=v,xi}function xl(){if(is===null){var v=qn.alternate;v=v!==null?v.memoizedState:null}else v=is.next;var D=xi===null?qn.memoizedState:xi.next;if(D!==null)xi=D,is=v;else{if(v===null)throw Error(c(310));is=v,v={memoizedState:is.memoizedState,baseState:is.baseState,baseQueue:is.baseQueue,queue:is.queue,next:null},xi===null?qn.memoizedState=xi=v:xi=xi.next=v}return xi}function ko(v,D){return typeof D=="function"?D(v):D}function Bf(v){var D=xl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=is,V=H.baseQueue,ne=Q.pending;if(ne!==null){if(V!==null){var Se=V.next;V.next=ne.next,ne.next=Se}H.baseQueue=V=ne,Q.pending=null}if(V!==null){V=V.next,H=H.baseState;var Ue=Se=ne=null,At=V;do{var Gt=At.lane;if((Cu&Gt)===Gt)Ue!==null&&(Ue=Ue.next={lane:0,action:At.action,eagerReducer:At.eagerReducer,eagerState:At.eagerState,next:null}),H=At.eagerReducer===v?At.eagerState:v(H,At.action);else{var vr={lane:Gt,action:At.action,eagerReducer:At.eagerReducer,eagerState:At.eagerState,next:null};Ue===null?(Se=Ue=vr,ne=H):Ue=Ue.next=vr,qn.lanes|=Gt,$g|=Gt}At=At.next}while(At!==null&&At!==V);Ue===null?ne=H:Ue.next=Se,Do(H,D.memoizedState)||(Ke=!0),D.memoizedState=H,D.baseState=ne,D.baseQueue=Ue,Q.lastRenderedState=H}return[D.memoizedState,Q.dispatch]}function vf(v){var D=xl(),Q=D.queue;if(Q===null)throw Error(c(311));Q.lastRenderedReducer=v;var H=Q.dispatch,V=Q.pending,ne=D.memoizedState;if(V!==null){Q.pending=null;var Se=V=V.next;do ne=v(ne,Se.action),Se=Se.next;while(Se!==V);Do(ne,D.memoizedState)||(Ke=!0),D.memoizedState=ne,D.baseQueue===null&&(D.baseState=ne),Q.lastRenderedState=ne}return[ne,H]}function kl(v,D,Q){var H=D._getVersion;H=H(D._source);var V=y?D._workInProgressVersionPrimary:D._workInProgressVersionSecondary;if(V!==null?v=V===H:(v=v.mutableReadLanes,(v=(Cu&v)===v)&&(y?D._workInProgressVersionPrimary=H:D._workInProgressVersionSecondary=H,Eu.push(D))),v)return Q(D._source);throw Eu.push(D),Error(c(350))}function yn(v,D,Q,H){var V=oo;if(V===null)throw Error(c(349));var ne=D._getVersion,Se=ne(D._source),Ue=Cf.current,At=Ue.useState(function(){return kl(V,D,Q)}),Gt=At[1],vr=At[0];At=xi;var Lr=v.memoizedState,Xt=Lr.refs,zn=Xt.getSnapshot,mi=Lr.source;Lr=Lr.subscribe;var Za=qn;return v.memoizedState={refs:Xt,source:D,subscribe:H},Ue.useEffect(function(){Xt.getSnapshot=Q,Xt.setSnapshot=Gt;var $e=ne(D._source);if(!Do(Se,$e)){$e=Q(D._source),Do(vr,$e)||(Gt($e),$e=vs(Za),V.mutableReadLanes|=$e&V.pendingLanes),$e=V.mutableReadLanes,V.entangledLanes|=$e;for(var qe=V.entanglements,pt=$e;0Q?98:Q,function(){v(!0)}),ai(97I2&&(D.flags|=64,V=!0,$A(H,!1),D.lanes=33554432)}else{if(!V)if(v=WA(ne),v!==null){if(D.flags|=64,V=!0,v=v.updateQueue,v!==null&&(D.updateQueue=v,D.flags|=4),$A(H,!0),H.tail===null&&H.tailMode==="hidden"&&!ne.alternate&&!Va)return D=D.lastEffect=H.lastEffect,D!==null&&(D.nextEffect=null),null}else 2*Dt()-H.renderingStartTime>I2&&Q!==1073741824&&(D.flags|=64,V=!0,$A(H,!1),D.lanes=33554432);H.isBackwards?(ne.sibling=D.child,D.child=ne):(v=H.last,v!==null?v.sibling=ne:D.child=ne,H.last=ne)}return H.tail!==null?(v=H.tail,H.rendering=v,H.tail=v.sibling,H.lastEffect=D.lastEffect,H.renderingStartTime=Dt(),v.sibling=null,D=gi.current,xn(gi,V?D&1|2:D&1),v):null;case 23:case 24:return D2(),v!==null&&v.memoizedState!==null!=(D.memoizedState!==null)&&H.mode!=="unstable-defer-without-hiding"&&(D.flags|=4),null}throw Error(c(156,D.tag))}function $L(v){switch(v.tag){case 1:Jn(v.type)&&hu();var D=v.flags;return D&4096?(v.flags=D&-4097|64,v):null;case 3:if(yu(),Rt(Oi),Rt(ji),Iu(),D=v.flags,D&64)throw Error(c(285));return v.flags=D&-4097|64,v;case 5:return wt(v),null;case 13:return Rt(gi),D=v.flags,D&4096?(v.flags=D&-4097|64,v):null;case 19:return Rt(gi),null;case 4:return yu(),null;case 10:return Lg(v),null;case 23:case 24:return D2(),null;default:return null}}function Vg(v,D){try{var Q="",H=D;do Q+=r2(H),H=H.return;while(H);var V=Q}catch(ne){V=` +Error generating stack: `+ne.message+` +`+ne.stack}return{value:v,source:D,stack:V}}function Kg(v,D){try{console.error(D.value)}catch(Q){setTimeout(function(){throw Q})}}var eM=typeof WeakMap=="function"?WeakMap:Map;function a2(v,D,Q){Q=bl(-1,Q),Q.tag=3,Q.payload={element:null};var H=D.value;return Q.callback=function(){Uy||(Uy=!0,C2=H),Kg(v,D)},Q}function Jg(v,D,Q){Q=bl(-1,Q),Q.tag=3;var H=v.type.getDerivedStateFromError;if(typeof H=="function"){var V=D.value;Q.payload=function(){return Kg(v,D),H(V)}}var ne=v.stateNode;return ne!==null&&typeof ne.componentDidCatch=="function"&&(Q.callback=function(){typeof H!="function"&&(gc===null?gc=new Set([this]):gc.add(this),Kg(v,D));var Se=D.stack;this.componentDidCatch(D.value,{componentStack:Se!==null?Se:""})}),Q}var tM=typeof WeakSet=="function"?WeakSet:Set;function l2(v){var D=v.ref;if(D!==null)if(typeof D=="function")try{D(null)}catch(Q){kf(v,Q)}else D.current=null}function xy(v,D){switch(D.tag){case 0:case 11:case 15:case 22:return;case 1:if(D.flags&256&&v!==null){var Q=v.memoizedProps,H=v.memoizedState;v=D.stateNode,D=v.getSnapshotBeforeUpdate(D.elementType===D.type?Q:bo(D.type,Q),H),v.__reactInternalSnapshotBeforeUpdate=D}return;case 3:F&&D.flags&256&&Rs(D.stateNode.containerInfo);return;case 5:case 6:case 4:case 17:return}throw Error(c(163))}function Nh(v,D){if(D=D.updateQueue,D=D!==null?D.lastEffect:null,D!==null){var Q=D=D.next;do{if((Q.tag&v)===v){var H=Q.destroy;Q.destroy=void 0,H!==void 0&&H()}Q=Q.next}while(Q!==D)}}function vP(v,D,Q){switch(Q.tag){case 0:case 11:case 15:case 22:if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{if((v.tag&3)===3){var H=v.create;v.destroy=H()}v=v.next}while(v!==D)}if(D=Q.updateQueue,D=D!==null?D.lastEffect:null,D!==null){v=D=D.next;do{var V=v;H=V.next,V=V.tag,V&4&&V&1&&(MP(Q,v),uM(Q,v)),v=H}while(v!==D)}return;case 1:v=Q.stateNode,Q.flags&4&&(D===null?v.componentDidMount():(H=Q.elementType===Q.type?D.memoizedProps:bo(Q.type,D.memoizedProps),v.componentDidUpdate(H,D.memoizedState,v.__reactInternalSnapshotBeforeUpdate))),D=Q.updateQueue,D!==null&&Cy(Q,D,v);return;case 3:if(D=Q.updateQueue,D!==null){if(v=null,Q.child!==null)switch(Q.child.tag){case 5:v=Re(Q.child.stateNode);break;case 1:v=Q.child.stateNode}Cy(Q,D,v)}return;case 5:v=Q.stateNode,D===null&&Q.flags&4&&eo(v,Q.type,Q.memoizedProps,Q);return;case 6:return;case 4:return;case 12:return;case 13:Z&&Q.memoizedState===null&&(Q=Q.alternate,Q!==null&&(Q=Q.memoizedState,Q!==null&&(Q=Q.dehydrated,Q!==null&&Au(Q))));return;case 19:case 17:case 20:case 21:case 23:case 24:return}throw Error(c(163))}function SP(v,D){if(F)for(var Q=v;;){if(Q.tag===5){var H=Q.stateNode;D?Eh(H):ro(Q.stateNode,Q.memoizedProps)}else if(Q.tag===6)H=Q.stateNode,D?Ih(H):jn(H,Q.memoizedProps);else if((Q.tag!==23&&Q.tag!==24||Q.memoizedState===null||Q===v)&&Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===v)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===v)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}}function ky(v,D){if(Ha&&typeof Ha.onCommitFiberUnmount=="function")try{Ha.onCommitFiberUnmount(Xe,D)}catch{}switch(D.tag){case 0:case 11:case 14:case 15:case 22:if(v=D.updateQueue,v!==null&&(v=v.lastEffect,v!==null)){var Q=v=v.next;do{var H=Q,V=H.destroy;if(H=H.tag,V!==void 0)if(H&4)MP(D,Q);else{H=D;try{V()}catch(ne){kf(H,ne)}}Q=Q.next}while(Q!==v)}break;case 1:if(l2(D),v=D.stateNode,typeof v.componentWillUnmount=="function")try{v.props=D.memoizedProps,v.state=D.memoizedState,v.componentWillUnmount()}catch(ne){kf(D,ne)}break;case 5:l2(D);break;case 4:F?xP(v,D):z&&z&&(D=D.stateNode.containerInfo,v=lu(D),FA(D,v))}}function DP(v,D){for(var Q=D;;)if(ky(v,Q),Q.child===null||F&&Q.tag===4){if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return}Q.sibling.return=Q.return,Q=Q.sibling}else Q.child.return=Q,Q=Q.child}function Qy(v){v.alternate=null,v.child=null,v.dependencies=null,v.firstEffect=null,v.lastEffect=null,v.memoizedProps=null,v.memoizedState=null,v.pendingProps=null,v.return=null,v.updateQueue=null}function bP(v){return v.tag===5||v.tag===3||v.tag===4}function PP(v){if(F){e:{for(var D=v.return;D!==null;){if(bP(D))break e;D=D.return}throw Error(c(160))}var Q=D;switch(D=Q.stateNode,Q.tag){case 5:var H=!1;break;case 3:D=D.containerInfo,H=!0;break;case 4:D=D.containerInfo,H=!0;break;default:throw Error(c(161))}Q.flags&16&&(pf(D),Q.flags&=-17);e:t:for(Q=v;;){for(;Q.sibling===null;){if(Q.return===null||bP(Q.return)){Q=null;break e}Q=Q.return}for(Q.sibling.return=Q.return,Q=Q.sibling;Q.tag!==5&&Q.tag!==6&&Q.tag!==18;){if(Q.flags&2||Q.child===null||Q.tag===4)continue t;Q.child.return=Q,Q=Q.child}if(!(Q.flags&2)){Q=Q.stateNode;break e}}H?c2(v,Q,D):u2(v,Q,D)}}function c2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?to(Q,v,D):wo(Q,v);else if(H!==4&&(v=v.child,v!==null))for(c2(v,D,Q),v=v.sibling;v!==null;)c2(v,D,Q),v=v.sibling}function u2(v,D,Q){var H=v.tag,V=H===5||H===6;if(V)v=V?v.stateNode:v.stateNode.instance,D?Hi(Q,v,D):oi(Q,v);else if(H!==4&&(v=v.child,v!==null))for(u2(v,D,Q),v=v.sibling;v!==null;)u2(v,D,Q),v=v.sibling}function xP(v,D){for(var Q=D,H=!1,V,ne;;){if(!H){H=Q.return;e:for(;;){if(H===null)throw Error(c(160));switch(V=H.stateNode,H.tag){case 5:ne=!1;break e;case 3:V=V.containerInfo,ne=!0;break e;case 4:V=V.containerInfo,ne=!0;break e}H=H.return}H=!0}if(Q.tag===5||Q.tag===6)DP(v,Q),ne?RA(V,Q.stateNode):vo(V,Q.stateNode);else if(Q.tag===4){if(Q.child!==null){V=Q.stateNode.containerInfo,ne=!0,Q.child.return=Q,Q=Q.child;continue}}else if(ky(v,Q),Q.child!==null){Q.child.return=Q,Q=Q.child;continue}if(Q===D)break;for(;Q.sibling===null;){if(Q.return===null||Q.return===D)return;Q=Q.return,Q.tag===4&&(H=!1)}Q.sibling.return=Q.return,Q=Q.sibling}}function f2(v,D){if(F){switch(D.tag){case 0:case 11:case 14:case 15:case 22:Nh(3,D);return;case 1:return;case 5:var Q=D.stateNode;if(Q!=null){var H=D.memoizedProps;v=v!==null?v.memoizedProps:H;var V=D.type,ne=D.updateQueue;D.updateQueue=null,ne!==null&&Bo(Q,ne,V,v,H,D)}return;case 6:if(D.stateNode===null)throw Error(c(162));Q=D.memoizedProps,rs(D.stateNode,v!==null?v.memoizedProps:Q,Q);return;case 3:Z&&(D=D.stateNode,D.hydrate&&(D.hydrate=!1,MA(D.containerInfo)));return;case 12:return;case 13:kP(D),zg(D);return;case 19:zg(D);return;case 17:return;case 23:case 24:SP(D,D.memoizedState!==null);return}throw Error(c(163))}switch(D.tag){case 0:case 11:case 14:case 15:case 22:Nh(3,D);return;case 12:return;case 13:kP(D),zg(D);return;case 19:zg(D);return;case 3:Z&&(Q=D.stateNode,Q.hydrate&&(Q.hydrate=!1,MA(Q.containerInfo)));break;case 23:case 24:return}e:if(z){switch(D.tag){case 1:case 5:case 6:case 20:break e;case 3:case 4:D=D.stateNode,FA(D.containerInfo,D.pendingChildren);break e}throw Error(c(163))}}function kP(v){v.memoizedState!==null&&(E2=Dt(),F&&SP(v.child,!0))}function zg(v){var D=v.updateQueue;if(D!==null){v.updateQueue=null;var Q=v.stateNode;Q===null&&(Q=v.stateNode=new tM),D.forEach(function(H){var V=AM.bind(null,v,H);Q.has(H)||(Q.add(H),H.then(V,V))})}}function rM(v,D){return v!==null&&(v=v.memoizedState,v===null||v.dehydrated!==null)?(D=D.memoizedState,D!==null&&D.dehydrated===null):!1}var Ty=0,Ry=1,Fy=2,Zg=3,Ny=4;if(typeof Symbol=="function"&&Symbol.for){var Xg=Symbol.for;Ty=Xg("selector.component"),Ry=Xg("selector.has_pseudo_class"),Fy=Xg("selector.role"),Zg=Xg("selector.test_id"),Ny=Xg("selector.text")}function Oy(v){var D=$(v);if(D!=null){if(typeof D.memoizedProps["data-testname"]!="string")throw Error(c(364));return D}if(v=ir(v),v===null)throw Error(c(362));return v.stateNode.current}function Df(v,D){switch(D.$$typeof){case Ty:if(v.type===D.value)return!0;break;case Ry:e:{D=D.value,v=[v,0];for(var Q=0;Q";case Ry:return":has("+(bf(v)||"")+")";case Fy:return'[role="'+v.value+'"]';case Ny:return'"'+v.value+'"';case Zg:return'[data-testname="'+v.value+'"]';default:throw Error(c(365,v))}}function A2(v,D){var Q=[];v=[v,0];for(var H=0;HV&&(V=Se),Q&=~ne}if(Q=V,Q=Dt()-Q,Q=(120>Q?120:480>Q?480:1080>Q?1080:1920>Q?1920:3e3>Q?3e3:4320>Q?4320:1960*iM(Q/1960))-Q,10 component higher in the tree to provide a loading indicator or placeholder to display.`)}Bs!==5&&(Bs=2),At=Vg(At,Ue),Xt=Se;do{switch(Xt.tag){case 3:ne=At,Xt.flags|=4096,D&=-D,Xt.lanes|=D;var Xn=a2(Xt,ne,D);Iy(Xt,Xn);break e;case 1:ne=At;var kr=Xt.type,Rn=Xt.stateNode;if(!(Xt.flags&64)&&(typeof kr.getDerivedStateFromError=="function"||Rn!==null&&typeof Rn.componentDidCatch=="function"&&(gc===null||!gc.has(Rn)))){Xt.flags|=4096,D&=-D,Xt.lanes|=D;var Un=Jg(Xt,ne,D);Iy(Xt,Un);break e}}Xt=Xt.return}while(Xt!==null)}LP(Q)}catch(zr){D=zr,Zi===Q&&Q!==null&&(Zi=Q=Q.return);continue}break}while(!0)}function NP(){var v=My.current;return My.current=kt,v===null?kt:v}function sd(v,D){var Q=xr;xr|=16;var H=NP();oo===v&&Os===D||_h(v,D);do try{oM();break}catch(V){FP(v,V)}while(!0);if(Ng(),xr=Q,My.current=H,Zi!==null)throw Error(c(261));return oo=null,Os=0,Bs}function oM(){for(;Zi!==null;)OP(Zi)}function aM(){for(;Zi!==null&&!Sl();)OP(Zi)}function OP(v){var D=HP(v.alternate,v,ep);v.memoizedProps=v.pendingProps,D===null?LP(v):Zi=D,h2.current=null}function LP(v){var D=v;do{var Q=D.alternate;if(v=D.return,D.flags&2048){if(Q=$L(D),Q!==null){Q.flags&=2047,Zi=Q;return}v!==null&&(v.firstEffect=v.lastEffect=null,v.flags|=2048)}else{if(Q=XL(Q,D,ep),Q!==null){Zi=Q;return}if(Q=D,Q.tag!==24&&Q.tag!==23||Q.memoizedState===null||ep&1073741824||!(Q.mode&4)){for(var H=0,V=Q.child;V!==null;)H|=V.lanes|V.childLanes,V=V.sibling;Q.childLanes=H}v!==null&&!(v.flags&2048)&&(v.firstEffect===null&&(v.firstEffect=D.firstEffect),D.lastEffect!==null&&(v.lastEffect!==null&&(v.lastEffect.nextEffect=D.firstEffect),v.lastEffect=D.lastEffect),1Dt()-E2?_h(v,0):m2|=Q),da(v,D)}function AM(v,D){var Q=v.stateNode;Q!==null&&Q.delete(D),D=0,D===0&&(D=v.mode,D&2?D&4?(Su===0&&(Su=Oh),D=kn(62914560&~Su),D===0&&(D=4194304)):D=tr()===99?1:2:D=1),Q=To(),v=qy(v,D),v!==null&&(ja(v,D,Q),da(v,Q))}var HP;HP=function(v,D,Q){var H=D.lanes;if(v!==null)if(v.memoizedProps!==D.pendingProps||Oi.current)Ke=!0;else if(Q&H)Ke=!!(v.flags&16384);else{switch(Ke=!1,D.tag){case 3:by(D),qg();break;case 5:If(D);break;case 1:Jn(D.type)&&Ma(D);break;case 4:Ug(D,D.stateNode.containerInfo);break;case 10:Og(D,D.memoizedProps.value);break;case 13:if(D.memoizedState!==null)return Q&D.child.childLanes?s2(v,D,Q):(xn(gi,gi.current&1),D=Gn(v,D,Q),D!==null?D.sibling:null);xn(gi,gi.current&1);break;case 19:if(H=(Q&D.childLanes)!==0,v.flags&64){if(H)return BP(v,D,Q);D.flags|=64}var V=D.memoizedState;if(V!==null&&(V.rendering=null,V.tail=null,V.lastEffect=null),xn(gi,gi.current),H)break;return null;case 23:case 24:return D.lanes=0,di(v,D,Q)}return Gn(v,D,Q)}else Ke=!1;switch(D.lanes=0,D.tag){case 2:if(H=D.type,v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,V=dn(D,ji.current),mf(D,Q),V=Wg(null,D,H,v,V,Q),D.flags|=1,typeof V=="object"&&V!==null&&typeof V.render=="function"&&V.$$typeof===void 0){if(D.tag=1,D.memoizedState=null,D.updateQueue=null,Jn(H)){var ne=!0;Ma(D)}else ne=!1;D.memoizedState=V.state!==null&&V.state!==void 0?V.state:null,Dh(D);var Se=H.getDerivedStateFromProps;typeof Se=="function"&&jA(D,H,Se,v),V.updater=qA,D.stateNode=V,V._reactInternals=D,xo(D,H,v,Q),D=i2(null,D,H,!0,ne,Q)}else D.tag=0,ft(null,D,V,Q),D=D.child;return D;case 16:V=D.elementType;e:{switch(v!==null&&(v.alternate=null,D.alternate=null,D.flags|=2),v=D.pendingProps,ne=V._init,V=ne(V._payload),D.type=V,ne=D.tag=hM(V),v=bo(V,v),ne){case 0:D=zA(null,D,V,v,Q);break e;case 1:D=wP(null,D,V,v,Q);break e;case 11:D=dr(null,D,V,v,Q);break e;case 14:D=Br(null,D,V,bo(V.type,v),H,Q);break e}throw Error(c(306,V,""))}return D;case 0:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:bo(H,V),zA(v,D,H,V,Q);case 1:return H=D.type,V=D.pendingProps,V=D.elementType===H?V:bo(H,V),wP(v,D,H,V,Q);case 3:if(by(D),H=D.updateQueue,v===null||H===null)throw Error(c(282));if(H=D.pendingProps,V=D.memoizedState,V=V!==null?V.element:null,Mg(v,D),HA(D,H,null,Q),H=D.memoizedState.element,H===V)qg(),D=Gn(v,D,Q);else{if(V=D.stateNode,(ne=V.hydrate)&&(Z?(pa=fu(D.stateNode.containerInfo),Ya=D,ne=Va=!0):ne=!1),ne){if(Z&&(v=V.mutableSourceEagerHydrationData,v!=null))for(V=0;V=Gt&&ne>=Lr&&V<=vr&&Se<=Xt){v.splice(D,1);break}else if(H!==Gt||Q.width!==At.width||XtSe){if(!(ne!==Lr||Q.height!==At.height||vrV)){Gt>H&&(At.width+=Gt-H,At.x=H),vrne&&(At.height+=Lr-ne,At.y=ne),XtQ&&(Q=Se)),Se ")+` + +No matching component was found for: + `)+v.join(" > ")}return null},r.getPublicRootInstance=function(v){if(v=v.current,!v.child)return null;switch(v.child.tag){case 5:return Re(v.child.stateNode);default:return v.child.stateNode}},r.injectIntoDevTools=function(v){if(v={bundleType:v.bundleType,version:v.version,rendererPackageName:v.rendererPackageName,rendererConfig:v.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:f.ReactCurrentDispatcher,findHostInstanceByFiber:dM,findFiberByHostInstance:v.findFiberByHostInstance||mM,findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null},typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u")v=!1;else{var D=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!D.isDisabled&&D.supportsFiber)try{Xe=D.inject(v),Ha=D}catch{}v=!0}return v},r.observeVisibleRects=function(v,D,Q,H){if(!qt)throw Error(c(363));v=p2(v,D);var V=nn(v,Q,H).disconnect;return{disconnect:function(){V()}}},r.registerMutableSourceForHydration=function(v,D){var Q=D._getVersion;Q=Q(D._source),v.mutableSourceEagerHydrationData==null?v.mutableSourceEagerHydrationData=[D,Q]:v.mutableSourceEagerHydrationData.push(D,Q)},r.runWithPriority=function(v,D){var Q=cc;try{return cc=v,D()}finally{cc=Q}},r.shouldSuspend=function(){return!1},r.unbatchedUpdates=function(v,D){var Q=xr;xr&=-2,xr|=8;try{return v(D)}finally{xr=Q,xr===0&&(Pf(),Tn())}},r.updateContainer=function(v,D,Q,H){var V=D.current,ne=To(),Se=vs(V);e:if(Q){Q=Q._reactInternals;t:{if(we(Q)!==Q||Q.tag!==1)throw Error(c(170));var Ue=Q;do{switch(Ue.tag){case 3:Ue=Ue.stateNode.context;break t;case 1:if(Jn(Ue.type)){Ue=Ue.stateNode.__reactInternalMemoizedMergedChildContext;break t}}Ue=Ue.return}while(Ue!==null);throw Error(c(171))}if(Q.tag===1){var At=Q.type;if(Jn(At)){Q=La(Q,At,Ue);break e}}Q=Ue}else Q=ca;return D.context===null?D.context=Q:D.pendingContext=Q,D=bl(ne,Se),D.payload={element:v},H=H===void 0?null:H,H!==null&&(D.callback=H),Pl(V,D),Rl(V,Se,ne),Se},r}});var UDe=L((Ghr,_De)=>{"use strict";_De.exports=MDe()});var jDe=L((Whr,HDe)=>{"use strict";var zPt={ALIGN_COUNT:8,ALIGN_AUTO:0,ALIGN_FLEX_START:1,ALIGN_CENTER:2,ALIGN_FLEX_END:3,ALIGN_STRETCH:4,ALIGN_BASELINE:5,ALIGN_SPACE_BETWEEN:6,ALIGN_SPACE_AROUND:7,DIMENSION_COUNT:2,DIMENSION_WIDTH:0,DIMENSION_HEIGHT:1,DIRECTION_COUNT:3,DIRECTION_INHERIT:0,DIRECTION_LTR:1,DIRECTION_RTL:2,DISPLAY_COUNT:2,DISPLAY_FLEX:0,DISPLAY_NONE:1,EDGE_COUNT:9,EDGE_LEFT:0,EDGE_TOP:1,EDGE_RIGHT:2,EDGE_BOTTOM:3,EDGE_START:4,EDGE_END:5,EDGE_HORIZONTAL:6,EDGE_VERTICAL:7,EDGE_ALL:8,EXPERIMENTAL_FEATURE_COUNT:1,EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS:0,FLEX_DIRECTION_COUNT:4,FLEX_DIRECTION_COLUMN:0,FLEX_DIRECTION_COLUMN_REVERSE:1,FLEX_DIRECTION_ROW:2,FLEX_DIRECTION_ROW_REVERSE:3,JUSTIFY_COUNT:6,JUSTIFY_FLEX_START:0,JUSTIFY_CENTER:1,JUSTIFY_FLEX_END:2,JUSTIFY_SPACE_BETWEEN:3,JUSTIFY_SPACE_AROUND:4,JUSTIFY_SPACE_EVENLY:5,LOG_LEVEL_COUNT:6,LOG_LEVEL_ERROR:0,LOG_LEVEL_WARN:1,LOG_LEVEL_INFO:2,LOG_LEVEL_DEBUG:3,LOG_LEVEL_VERBOSE:4,LOG_LEVEL_FATAL:5,MEASURE_MODE_COUNT:3,MEASURE_MODE_UNDEFINED:0,MEASURE_MODE_EXACTLY:1,MEASURE_MODE_AT_MOST:2,NODE_TYPE_COUNT:2,NODE_TYPE_DEFAULT:0,NODE_TYPE_TEXT:1,OVERFLOW_COUNT:3,OVERFLOW_VISIBLE:0,OVERFLOW_HIDDEN:1,OVERFLOW_SCROLL:2,POSITION_TYPE_COUNT:2,POSITION_TYPE_RELATIVE:0,POSITION_TYPE_ABSOLUTE:1,PRINT_OPTIONS_COUNT:3,PRINT_OPTIONS_LAYOUT:1,PRINT_OPTIONS_STYLE:2,PRINT_OPTIONS_CHILDREN:4,UNIT_COUNT:4,UNIT_UNDEFINED:0,UNIT_POINT:1,UNIT_PERCENT:2,UNIT_AUTO:3,WRAP_COUNT:3,WRAP_NO_WRAP:0,WRAP_WRAP:1,WRAP_WRAP_REVERSE:2};HDe.exports=zPt});var YDe=L((Yhr,WDe)=>{"use strict";var ZPt=Object.assign||function(t){for(var e=1;e"}}]),t}(),qDe=function(){LF(t,null,[{key:"fromJS",value:function(r){var s=r.width,a=r.height;return new t(s,a)}}]);function t(e,r){bW(this,t),this.width=e,this.height=r}return LF(t,[{key:"fromJS",value:function(r){r(this.width,this.height)}},{key:"toString",value:function(){return""}}]),t}(),GDe=function(){function t(e,r){bW(this,t),this.unit=e,this.value=r}return LF(t,[{key:"fromJS",value:function(r){r(this.unit,this.value)}},{key:"toString",value:function(){switch(this.unit){case rf.UNIT_POINT:return String(this.value);case rf.UNIT_PERCENT:return this.value+"%";case rf.UNIT_AUTO:return"auto";default:return this.value+"?"}}},{key:"valueOf",value:function(){return this.value}}]),t}();WDe.exports=function(t,e){function r(c,f,p){var h=c[f];c[f]=function(){for(var E=arguments.length,C=Array(E),S=0;S1?C-1:0),P=1;P1&&arguments[1]!==void 0?arguments[1]:NaN,p=arguments.length>2&&arguments[2]!==void 0?arguments[2]:NaN,h=arguments.length>3&&arguments[3]!==void 0?arguments[3]:rf.DIRECTION_LTR;return c.call(this,f,p,h)}),ZPt({Config:e.Config,Node:e.Node,Layout:t("Layout",XPt),Size:t("Size",qDe),Value:t("Value",GDe),getInstanceCount:function(){return e.getInstanceCount.apply(e,arguments)}},rf)}});var VDe=L((exports,module)=>{(function(t,e){typeof define=="function"&&define.amd?define([],function(){return e}):typeof module=="object"&&module.exports?module.exports=e:(t.nbind=t.nbind||{}).init=e})(exports,function(Module,cb){typeof Module=="function"&&(cb=Module,Module={}),Module.onRuntimeInitialized=function(t,e){return function(){t&&t.apply(this,arguments);try{Module.ccall("nbind_init")}catch(r){e(r);return}e(null,{bind:Module._nbind_value,reflect:Module.NBind.reflect,queryType:Module.NBind.queryType,toggleLightGC:Module.toggleLightGC,lib:Module})}}(Module.onRuntimeInitialized,cb);var Module;Module||(Module=(typeof Module<"u"?Module:null)||{});var moduleOverrides={};for(var key in Module)Module.hasOwnProperty(key)&&(moduleOverrides[key]=Module[key]);var ENVIRONMENT_IS_WEB=!1,ENVIRONMENT_IS_WORKER=!1,ENVIRONMENT_IS_NODE=!1,ENVIRONMENT_IS_SHELL=!1;if(Module.ENVIRONMENT)if(Module.ENVIRONMENT==="WEB")ENVIRONMENT_IS_WEB=!0;else if(Module.ENVIRONMENT==="WORKER")ENVIRONMENT_IS_WORKER=!0;else if(Module.ENVIRONMENT==="NODE")ENVIRONMENT_IS_NODE=!0;else if(Module.ENVIRONMENT==="SHELL")ENVIRONMENT_IS_SHELL=!0;else throw new Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.");else ENVIRONMENT_IS_WEB=typeof window=="object",ENVIRONMENT_IS_WORKER=typeof importScripts=="function",ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof ye=="function"&&!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_WORKER,ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;if(ENVIRONMENT_IS_NODE){Module.print||(Module.print=console.log),Module.printErr||(Module.printErr=console.warn);var nodeFS,nodePath;Module.read=function(e,r){nodeFS||(nodeFS={}("")),nodePath||(nodePath={}("")),e=nodePath.normalize(e);var s=nodeFS.readFileSync(e);return r?s:s.toString()},Module.readBinary=function(e){var r=Module.read(e,!0);return r.buffer||(r=new Uint8Array(r)),assert(r.buffer),r},Module.load=function(e){globalEval(read(e))},Module.thisProgram||(process.argv.length>1?Module.thisProgram=process.argv[1].replace(/\\/g,"/"):Module.thisProgram="unknown-program"),Module.arguments=process.argv.slice(2),typeof module<"u"&&(module.exports=Module),Module.inspect=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL)Module.print||(Module.print=print),typeof printErr<"u"&&(Module.printErr=printErr),typeof read<"u"?Module.read=read:Module.read=function(){throw"no read() available"},Module.readBinary=function(e){if(typeof readbuffer=="function")return new Uint8Array(readbuffer(e));var r=read(e,"binary");return assert(typeof r=="object"),r},typeof scriptArgs<"u"?Module.arguments=scriptArgs:typeof arguments<"u"&&(Module.arguments=arguments),typeof quit=="function"&&(Module.quit=function(t,e){quit(t)});else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(Module.read=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.send(null),r.responseText},ENVIRONMENT_IS_WORKER&&(Module.readBinary=function(e){var r=new XMLHttpRequest;return r.open("GET",e,!1),r.responseType="arraybuffer",r.send(null),new Uint8Array(r.response)}),Module.readAsync=function(e,r,s){var a=new XMLHttpRequest;a.open("GET",e,!0),a.responseType="arraybuffer",a.onload=function(){a.status==200||a.status==0&&a.response?r(a.response):s()},a.onerror=s,a.send(null)},typeof arguments<"u"&&(Module.arguments=arguments),typeof console<"u")Module.print||(Module.print=function(e){console.log(e)}),Module.printErr||(Module.printErr=function(e){console.warn(e)});else{var TRY_USE_DUMP=!1;Module.print||(Module.print=TRY_USE_DUMP&&typeof dump<"u"?function(t){dump(t)}:function(t){})}ENVIRONMENT_IS_WORKER&&(Module.load=importScripts),typeof Module.setWindowTitle>"u"&&(Module.setWindowTitle=function(t){document.title=t})}else throw"Unknown runtime environment. Where are we?";function globalEval(t){eval.call(null,t)}!Module.load&&Module.read&&(Module.load=function(e){globalEval(Module.read(e))}),Module.print||(Module.print=function(){}),Module.printErr||(Module.printErr=Module.print),Module.arguments||(Module.arguments=[]),Module.thisProgram||(Module.thisProgram="./this.program"),Module.quit||(Module.quit=function(t,e){throw e}),Module.print=Module.print,Module.printErr=Module.printErr,Module.preRun=[],Module.postRun=[];for(var key in moduleOverrides)moduleOverrides.hasOwnProperty(key)&&(Module[key]=moduleOverrides[key]);moduleOverrides=void 0;var Runtime={setTempRet0:function(t){return tempRet0=t,t},getTempRet0:function(){return tempRet0},stackSave:function(){return STACKTOP},stackRestore:function(t){STACKTOP=t},getNativeTypeSize:function(t){switch(t){case"i1":case"i8":return 1;case"i16":return 2;case"i32":return 4;case"i64":return 8;case"float":return 4;case"double":return 8;default:{if(t[t.length-1]==="*")return Runtime.QUANTUM_SIZE;if(t[0]==="i"){var e=parseInt(t.substr(1));return assert(e%8===0),e/8}else return 0}}},getNativeFieldSize:function(t){return Math.max(Runtime.getNativeTypeSize(t),Runtime.QUANTUM_SIZE)},STACK_ALIGN:16,prepVararg:function(t,e){return e==="double"||e==="i64"?t&7&&(assert((t&7)===4),t+=4):assert((t&3)===0),t},getAlignSize:function(t,e,r){return!r&&(t=="i64"||t=="double")?8:t?Math.min(e||(t?Runtime.getNativeFieldSize(t):0),Runtime.QUANTUM_SIZE):Math.min(e,8)},dynCall:function(t,e,r){return r&&r.length?Module["dynCall_"+t].apply(null,[e].concat(r)):Module["dynCall_"+t].call(null,e)},functionPointers:[],addFunction:function(t){for(var e=0;e>2],r=(e+t+15|0)&-16;if(HEAP32[DYNAMICTOP_PTR>>2]=r,r>=TOTAL_MEMORY){var s=enlargeMemory();if(!s)return HEAP32[DYNAMICTOP_PTR>>2]=e,0}return e},alignMemory:function(t,e){var r=t=Math.ceil(t/(e||16))*(e||16);return r},makeBigInt:function(t,e,r){var s=r?+(t>>>0)+ +(e>>>0)*4294967296:+(t>>>0)+ +(e|0)*4294967296;return s},GLOBAL_BASE:8,QUANTUM_SIZE:4,__dummy__:0};Module.Runtime=Runtime;var ABORT=0,EXITSTATUS=0;function assert(t,e){t||abort("Assertion failed: "+e)}function getCFunc(ident){var func=Module["_"+ident];if(!func)try{func=eval("_"+ident)}catch(t){}return assert(func,"Cannot call unknown function "+ident+" (perhaps LLVM optimizations or closure removed it?)"),func}var cwrap,ccall;(function(){var JSfuncs={stackSave:function(){Runtime.stackSave()},stackRestore:function(){Runtime.stackRestore()},arrayToC:function(t){var e=Runtime.stackAlloc(t.length);return writeArrayToMemory(t,e),e},stringToC:function(t){var e=0;if(t!=null&&t!==0){var r=(t.length<<2)+1;e=Runtime.stackAlloc(r),stringToUTF8(t,e,r)}return e}},toC={string:JSfuncs.stringToC,array:JSfuncs.arrayToC};ccall=function(e,r,s,a,n){var c=getCFunc(e),f=[],p=0;if(a)for(var h=0;h>0]=e;break;case"i8":HEAP8[t>>0]=e;break;case"i16":HEAP16[t>>1]=e;break;case"i32":HEAP32[t>>2]=e;break;case"i64":tempI64=[e>>>0,(tempDouble=e,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[t>>2]=tempI64[0],HEAP32[t+4>>2]=tempI64[1];break;case"float":HEAPF32[t>>2]=e;break;case"double":HEAPF64[t>>3]=e;break;default:abort("invalid type for setValue: "+r)}}Module.setValue=setValue;function getValue(t,e,r){switch(e=e||"i8",e.charAt(e.length-1)==="*"&&(e="i32"),e){case"i1":return HEAP8[t>>0];case"i8":return HEAP8[t>>0];case"i16":return HEAP16[t>>1];case"i32":return HEAP32[t>>2];case"i64":return HEAP32[t>>2];case"float":return HEAPF32[t>>2];case"double":return HEAPF64[t>>3];default:abort("invalid type for setValue: "+e)}return null}Module.getValue=getValue;var ALLOC_NORMAL=0,ALLOC_STACK=1,ALLOC_STATIC=2,ALLOC_DYNAMIC=3,ALLOC_NONE=4;Module.ALLOC_NORMAL=ALLOC_NORMAL,Module.ALLOC_STACK=ALLOC_STACK,Module.ALLOC_STATIC=ALLOC_STATIC,Module.ALLOC_DYNAMIC=ALLOC_DYNAMIC,Module.ALLOC_NONE=ALLOC_NONE;function allocate(t,e,r,s){var a,n;typeof t=="number"?(a=!0,n=t):(a=!1,n=t.length);var c=typeof e=="string"?e:null,f;if(r==ALLOC_NONE?f=s:f=[typeof _malloc=="function"?_malloc:Runtime.staticAlloc,Runtime.stackAlloc,Runtime.staticAlloc,Runtime.dynamicAlloc][r===void 0?ALLOC_STATIC:r](Math.max(n,c?1:e.length)),a){var s=f,p;for(assert((f&3)==0),p=f+(n&-4);s>2]=0;for(p=f+n;s>0]=0;return f}if(c==="i8")return t.subarray||t.slice?HEAPU8.set(t,f):HEAPU8.set(new Uint8Array(t),f),f;for(var h=0,E,C,S;h>0],r|=s,!(s==0&&!e||(a++,e&&a==e)););e||(e=a);var n="";if(r<128){for(var c=1024,f;e>0;)f=String.fromCharCode.apply(String,HEAPU8.subarray(t,t+Math.min(e,c))),n=n?n+f:f,t+=c,e-=c;return n}return Module.UTF8ToString(t)}Module.Pointer_stringify=Pointer_stringify;function AsciiToString(t){for(var e="";;){var r=HEAP8[t++>>0];if(!r)return e;e+=String.fromCharCode(r)}}Module.AsciiToString=AsciiToString;function stringToAscii(t,e){return writeAsciiToMemory(t,e,!1)}Module.stringToAscii=stringToAscii;var UTF8Decoder=typeof TextDecoder<"u"?new TextDecoder("utf8"):void 0;function UTF8ArrayToString(t,e){for(var r=e;t[r];)++r;if(r-e>16&&t.subarray&&UTF8Decoder)return UTF8Decoder.decode(t.subarray(e,r));for(var s,a,n,c,f,p,h="";;){if(s=t[e++],!s)return h;if(!(s&128)){h+=String.fromCharCode(s);continue}if(a=t[e++]&63,(s&224)==192){h+=String.fromCharCode((s&31)<<6|a);continue}if(n=t[e++]&63,(s&240)==224?s=(s&15)<<12|a<<6|n:(c=t[e++]&63,(s&248)==240?s=(s&7)<<18|a<<12|n<<6|c:(f=t[e++]&63,(s&252)==248?s=(s&3)<<24|a<<18|n<<12|c<<6|f:(p=t[e++]&63,s=(s&1)<<30|a<<24|n<<18|c<<12|f<<6|p))),s<65536)h+=String.fromCharCode(s);else{var E=s-65536;h+=String.fromCharCode(55296|E>>10,56320|E&1023)}}}Module.UTF8ArrayToString=UTF8ArrayToString;function UTF8ToString(t){return UTF8ArrayToString(HEAPU8,t)}Module.UTF8ToString=UTF8ToString;function stringToUTF8Array(t,e,r,s){if(!(s>0))return 0;for(var a=r,n=r+s-1,c=0;c=55296&&f<=57343&&(f=65536+((f&1023)<<10)|t.charCodeAt(++c)&1023),f<=127){if(r>=n)break;e[r++]=f}else if(f<=2047){if(r+1>=n)break;e[r++]=192|f>>6,e[r++]=128|f&63}else if(f<=65535){if(r+2>=n)break;e[r++]=224|f>>12,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=2097151){if(r+3>=n)break;e[r++]=240|f>>18,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else if(f<=67108863){if(r+4>=n)break;e[r++]=248|f>>24,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}else{if(r+5>=n)break;e[r++]=252|f>>30,e[r++]=128|f>>24&63,e[r++]=128|f>>18&63,e[r++]=128|f>>12&63,e[r++]=128|f>>6&63,e[r++]=128|f&63}}return e[r]=0,r-a}Module.stringToUTF8Array=stringToUTF8Array;function stringToUTF8(t,e,r){return stringToUTF8Array(t,HEAPU8,e,r)}Module.stringToUTF8=stringToUTF8;function lengthBytesUTF8(t){for(var e=0,r=0;r=55296&&s<=57343&&(s=65536+((s&1023)<<10)|t.charCodeAt(++r)&1023),s<=127?++e:s<=2047?e+=2:s<=65535?e+=3:s<=2097151?e+=4:s<=67108863?e+=5:e+=6}return e}Module.lengthBytesUTF8=lengthBytesUTF8;var UTF16Decoder=typeof TextDecoder<"u"?new TextDecoder("utf-16le"):void 0;function demangle(t){var e=Module.___cxa_demangle||Module.__cxa_demangle;if(e){try{var r=t.substr(1),s=lengthBytesUTF8(r)+1,a=_malloc(s);stringToUTF8(r,a,s);var n=_malloc(4),c=e(a,0,0,n);if(getValue(n,"i32")===0&&c)return Pointer_stringify(c)}catch{}finally{a&&_free(a),n&&_free(n),c&&_free(c)}return t}return Runtime.warnOnce("warning: build with -s DEMANGLE_SUPPORT=1 to link in libcxxabi demangling"),t}function demangleAll(t){var e=/__Z[\w\d_]+/g;return t.replace(e,function(r){var s=demangle(r);return r===s?r:r+" ["+s+"]"})}function jsStackTrace(){var t=new Error;if(!t.stack){try{throw new Error(0)}catch(e){t=e}if(!t.stack)return"(no stack trace available)"}return t.stack.toString()}function stackTrace(){var t=jsStackTrace();return Module.extraStackTrace&&(t+=` +`+Module.extraStackTrace()),demangleAll(t)}Module.stackTrace=stackTrace;var HEAP,buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferViews(){Module.HEAP8=HEAP8=new Int8Array(buffer),Module.HEAP16=HEAP16=new Int16Array(buffer),Module.HEAP32=HEAP32=new Int32Array(buffer),Module.HEAPU8=HEAPU8=new Uint8Array(buffer),Module.HEAPU16=HEAPU16=new Uint16Array(buffer),Module.HEAPU32=HEAPU32=new Uint32Array(buffer),Module.HEAPF32=HEAPF32=new Float32Array(buffer),Module.HEAPF64=HEAPF64=new Float64Array(buffer)}var STATIC_BASE,STATICTOP,staticSealed,STACK_BASE,STACKTOP,STACK_MAX,DYNAMIC_BASE,DYNAMICTOP_PTR;STATIC_BASE=STATICTOP=STACK_BASE=STACKTOP=STACK_MAX=DYNAMIC_BASE=DYNAMICTOP_PTR=0,staticSealed=!1;function abortOnCannotGrowMemory(){abort("Cannot enlarge memory arrays. Either (1) compile with -s TOTAL_MEMORY=X with X higher than the current value "+TOTAL_MEMORY+", (2) compile with -s ALLOW_MEMORY_GROWTH=1 which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with -s ABORTING_MALLOC=0 ")}function enlargeMemory(){abortOnCannotGrowMemory()}var TOTAL_STACK=Module.TOTAL_STACK||5242880,TOTAL_MEMORY=Module.TOTAL_MEMORY||134217728;TOTAL_MEMORY0;){var e=t.shift();if(typeof e=="function"){e();continue}var r=e.func;typeof r=="number"?e.arg===void 0?Module.dynCall_v(r):Module.dynCall_vi(r,e.arg):r(e.arg===void 0?null:e.arg)}}var __ATPRERUN__=[],__ATINIT__=[],__ATMAIN__=[],__ATEXIT__=[],__ATPOSTRUN__=[],runtimeInitialized=!1,runtimeExited=!1;function preRun(){if(Module.preRun)for(typeof Module.preRun=="function"&&(Module.preRun=[Module.preRun]);Module.preRun.length;)addOnPreRun(Module.preRun.shift());callRuntimeCallbacks(__ATPRERUN__)}function ensureInitRuntime(){runtimeInitialized||(runtimeInitialized=!0,callRuntimeCallbacks(__ATINIT__))}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){callRuntimeCallbacks(__ATEXIT__),runtimeExited=!0}function postRun(){if(Module.postRun)for(typeof Module.postRun=="function"&&(Module.postRun=[Module.postRun]);Module.postRun.length;)addOnPostRun(Module.postRun.shift());callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(t){__ATPRERUN__.unshift(t)}Module.addOnPreRun=addOnPreRun;function addOnInit(t){__ATINIT__.unshift(t)}Module.addOnInit=addOnInit;function addOnPreMain(t){__ATMAIN__.unshift(t)}Module.addOnPreMain=addOnPreMain;function addOnExit(t){__ATEXIT__.unshift(t)}Module.addOnExit=addOnExit;function addOnPostRun(t){__ATPOSTRUN__.unshift(t)}Module.addOnPostRun=addOnPostRun;function intArrayFromString(t,e,r){var s=r>0?r:lengthBytesUTF8(t)+1,a=new Array(s),n=stringToUTF8Array(t,a,0,a.length);return e&&(a.length=n),a}Module.intArrayFromString=intArrayFromString;function intArrayToString(t){for(var e=[],r=0;r255&&(s&=255),e.push(String.fromCharCode(s))}return e.join("")}Module.intArrayToString=intArrayToString;function writeStringToMemory(t,e,r){Runtime.warnOnce("writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!");var s,a;r&&(a=e+lengthBytesUTF8(t),s=HEAP8[a]),stringToUTF8(t,e,1/0),r&&(HEAP8[a]=s)}Module.writeStringToMemory=writeStringToMemory;function writeArrayToMemory(t,e){HEAP8.set(t,e)}Module.writeArrayToMemory=writeArrayToMemory;function writeAsciiToMemory(t,e,r){for(var s=0;s>0]=t.charCodeAt(s);r||(HEAP8[e>>0]=0)}if(Module.writeAsciiToMemory=writeAsciiToMemory,(!Math.imul||Math.imul(4294967295,5)!==-5)&&(Math.imul=function t(e,r){var s=e>>>16,a=e&65535,n=r>>>16,c=r&65535;return a*c+(s*c+a*n<<16)|0}),Math.imul=Math.imul,!Math.fround){var froundBuffer=new Float32Array(1);Math.fround=function(t){return froundBuffer[0]=t,froundBuffer[0]}}Math.fround=Math.fround,Math.clz32||(Math.clz32=function(t){t=t>>>0;for(var e=0;e<32;e++)if(t&1<<31-e)return e;return 32}),Math.clz32=Math.clz32,Math.trunc||(Math.trunc=function(t){return t<0?Math.ceil(t):Math.floor(t)}),Math.trunc=Math.trunc;var Math_abs=Math.abs,Math_cos=Math.cos,Math_sin=Math.sin,Math_tan=Math.tan,Math_acos=Math.acos,Math_asin=Math.asin,Math_atan=Math.atan,Math_atan2=Math.atan2,Math_exp=Math.exp,Math_log=Math.log,Math_sqrt=Math.sqrt,Math_ceil=Math.ceil,Math_floor=Math.floor,Math_pow=Math.pow,Math_imul=Math.imul,Math_fround=Math.fround,Math_round=Math.round,Math_min=Math.min,Math_clz32=Math.clz32,Math_trunc=Math.trunc,runDependencies=0,runDependencyWatcher=null,dependenciesFulfilled=null;function getUniqueRunDependency(t){return t}function addRunDependency(t){runDependencies++,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies)}Module.addRunDependency=addRunDependency;function removeRunDependency(t){if(runDependencies--,Module.monitorRunDependencies&&Module.monitorRunDependencies(runDependencies),runDependencies==0&&(runDependencyWatcher!==null&&(clearInterval(runDependencyWatcher),runDependencyWatcher=null),dependenciesFulfilled)){var e=dependenciesFulfilled;dependenciesFulfilled=null,e()}}Module.removeRunDependency=removeRunDependency,Module.preloadedImages={},Module.preloadedAudios={};var ASM_CONSTS=[function(t,e,r,s,a,n,c,f){return _nbind.callbackSignatureList[t].apply(this,arguments)}];function _emscripten_asm_const_iiiiiiii(t,e,r,s,a,n,c,f){return ASM_CONSTS[t](e,r,s,a,n,c,f)}function _emscripten_asm_const_iiiii(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiidddddd(t,e,r,s,a,n,c,f,p){return ASM_CONSTS[t](e,r,s,a,n,c,f,p)}function _emscripten_asm_const_iiididi(t,e,r,s,a,n,c){return ASM_CONSTS[t](e,r,s,a,n,c)}function _emscripten_asm_const_iiii(t,e,r,s){return ASM_CONSTS[t](e,r,s)}function _emscripten_asm_const_iiiid(t,e,r,s,a){return ASM_CONSTS[t](e,r,s,a)}function _emscripten_asm_const_iiiiii(t,e,r,s,a,n){return ASM_CONSTS[t](e,r,s,a,n)}STATIC_BASE=Runtime.GLOBAL_BASE,STATICTOP=STATIC_BASE+12800,__ATINIT__.push({func:function(){__GLOBAL__sub_I_Yoga_cpp()}},{func:function(){__GLOBAL__sub_I_nbind_cc()}},{func:function(){__GLOBAL__sub_I_common_cc()}},{func:function(){__GLOBAL__sub_I_Binding_cc()}}),allocate([0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,192,127,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,3,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,127,0,0,192,127,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,0,0,128,191,0,0,128,191,0,0,192,127,0,0,0,0,0,0,0,0,0,0,128,63,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,3,0,0,0,1,0,0,0,2,0,0,0,0,0,0,0,190,12,0,0,200,12,0,0,208,12,0,0,216,12,0,0,230,12,0,0,242,12,0,0,1,0,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,192,127,3,0,0,0,180,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,182,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,183,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,181,45,0,0,184,45,0,0,185,45,0,0,181,45,0,0,181,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,148,4,0,0,3,0,0,0,187,45,0,0,164,4,0,0,188,45,0,0,2,0,0,0,189,45,0,0,164,4,0,0,188,45,0,0,185,45,0,0,164,4,0,0,185,45,0,0,164,4,0,0,188,45,0,0,181,45,0,0,182,45,0,0,181,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,5,0,0,0,6,0,0,0,1,0,0,0,7,0,0,0,183,45,0,0,182,45,0,0,181,45,0,0,190,45,0,0,190,45,0,0,182,45,0,0,182,45,0,0,185,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,181,45,0,0,185,45,0,0,182,45,0,0,185,45,0,0,48,5,0,0,3,0,0,0,56,5,0,0,1,0,0,0,189,45,0,0,185,45,0,0,164,4,0,0,76,5,0,0,2,0,0,0,191,45,0,0,186,45,0,0,182,45,0,0,185,45,0,0,192,45,0,0,185,45,0,0,182,45,0,0,186,45,0,0,185,45,0,0,76,5,0,0,76,5,0,0,136,5,0,0,182,45,0,0,181,45,0,0,2,0,0,0,190,45,0,0,136,5,0,0,56,19,0,0,156,5,0,0,2,0,0,0,184,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,8,0,0,0,9,0,0,0,1,0,0,0,10,0,0,0,204,5,0,0,181,45,0,0,181,45,0,0,2,0,0,0,180,45,0,0,204,5,0,0,2,0,0,0,195,45,0,0,236,5,0,0,97,19,0,0,198,45,0,0,211,45,0,0,212,45,0,0,213,45,0,0,214,45,0,0,215,45,0,0,188,45,0,0,182,45,0,0,216,45,0,0,217,45,0,0,218,45,0,0,219,45,0,0,192,45,0,0,181,45,0,0,0,0,0,0,185,45,0,0,110,19,0,0,186,45,0,0,115,19,0,0,221,45,0,0,120,19,0,0,148,4,0,0,132,19,0,0,96,6,0,0,145,19,0,0,222,45,0,0,164,19,0,0,223,45,0,0,173,19,0,0,0,0,0,0,3,0,0,0,104,6,0,0,1,0,0,0,187,45,0,0,0,0,0,0,0,0,0,0,1,0,0,0,11,0,0,0,12,0,0,0,1,0,0,0,13,0,0,0,185,45,0,0,224,45,0,0,164,6,0,0,188,45,0,0,172,6,0,0,180,6,0,0,2,0,0,0,188,6,0,0,7,0,0,0,224,45,0,0,7,0,0,0,164,6,0,0,1,0,0,0,213,45,0,0,185,45,0,0,224,45,0,0,172,6,0,0,185,45,0,0,224,45,0,0,164,6,0,0,185,45,0,0,224,45,0,0,211,45,0,0,211,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,222,45,0,0,211,45,0,0,224,45,0,0,172,6,0,0,222,45,0,0,211,45,0,0,224,45,0,0,188,45,0,0,222,45,0,0,211,45,0,0,40,7,0,0,188,45,0,0,2,0,0,0,224,45,0,0,185,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,188,45,0,0,222,45,0,0,224,45,0,0,148,4,0,0,185,45,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,148,4,0,0,185,45,0,0,164,6,0,0,148,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,14,0,0,0,15,0,0,0,1,0,0,0,16,0,0,0,148,7,0,0,2,0,0,0,225,45,0,0,183,45,0,0,188,45,0,0,168,7,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,234,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,148,45,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,9,0,0,5,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,0,0,2,0,0,0,242,45,0,0,0,4,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,110,111,100,101,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,119,104,105,99,104,32,115,116,105,108,108,32,104,97,115,32,99,104,105,108,100,114,101,110,32,97,116,116,97,99,104,101,100,0,67,97,110,110,111,116,32,114,101,115,101,116,32,97,32,110,111,100,101,32,115,116,105,108,108,32,97,116,116,97,99,104,101,100,32,116,111,32,97,32,112,97,114,101,110,116,0,67,111,117,108,100,32,110,111,116,32,97,108,108,111,99,97,116,101,32,109,101,109,111,114,121,32,102,111,114,32,99,111,110,102,105,103,0,67,97,110,110,111,116,32,115,101,116,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,67,104,105,108,100,32,97,108,114,101,97,100,121,32,104,97,115,32,97,32,112,97,114,101,110,116,44,32,105,116,32,109,117,115,116,32,98,101,32,114,101,109,111,118,101,100,32,102,105,114,115,116,46,0,67,97,110,110,111,116,32,97,100,100,32,99,104,105,108,100,58,32,78,111,100,101,115,32,119,105,116,104,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,32,99,97,110,110,111,116,32,104,97,118,101,32,99,104,105,108,100,114,101,110,46,0,79,110,108,121,32,108,101,97,102,32,110,111,100,101,115,32,119,105,116,104,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,115,115,104,111,117,108,100,32,109,97,110,117,97,108,108,121,32,109,97,114,107,32,116,104,101,109,115,101,108,118,101,115,32,97,115,32,100,105,114,116,121,0,67,97,110,110,111,116,32,103,101,116,32,108,97,121,111,117,116,32,112,114,111,112,101,114,116,105,101,115,32,111,102,32,109,117,108,116,105,45,101,100,103,101,32,115,104,111,114,116,104,97,110,100,115,0,37,115,37,100,46,123,91,115,107,105,112,112,101,100,93,32,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,61,62,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,37,115,37,100,46,123,37,115,0,42,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,97,119,58,32,37,102,32,97,104,58,32,37,102,32,37,115,10,0,37,115,37,100,46,125,37,115,0,119,109,58,32,37,115,44,32,104,109,58,32,37,115,44,32,100,58,32,40,37,102,44,32,37,102,41,32,37,115,10,0,79,117,116,32,111,102,32,99,97,99,104,101,32,101,110,116,114,105,101,115,33,10,0,83,99,97,108,101,32,102,97,99,116,111,114,32,115,104,111,117,108,100,32,110,111,116,32,98,101,32,108,101,115,115,32,116,104,97,110,32,122,101,114,111,0,105,110,105,116,105,97,108,0,37,115,10,0,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,0,85,78,68,69,70,73,78,69,68,0,69,88,65,67,84,76,89,0,65,84,95,77,79,83,84,0,76,65,89,95,85,78,68,69,70,73,78,69,68,0,76,65,89,95,69,88,65,67,84,76,89,0,76,65,89,95,65,84,95,77,79,83,84,0,97,118,97,105,108,97,98,108,101,87,105,100,116,104,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,119,105,100,116,104,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,97,118,97,105,108,97,98,108,101,72,101,105,103,104,116,32,105,115,32,105,110,100,101,102,105,110,105,116,101,32,115,111,32,104,101,105,103,104,116,77,101,97,115,117,114,101,77,111,100,101,32,109,117,115,116,32,98,101,32,89,71,77,101,97,115,117,114,101,77,111,100,101,85,110,100,101,102,105,110,101,100,0,102,108,101,120,0,115,116,114,101,116,99,104,0,109,117,108,116,105,108,105,110,101,45,115,116,114,101,116,99,104,0,69,120,112,101,99,116,101,100,32,110,111,100,101,32,116,111,32,104,97,118,101,32,99,117,115,116,111,109,32,109,101,97,115,117,114,101,32,102,117,110,99,116,105,111,110,0,109,101,97,115,117,114,101,0,69,120,112,101,99,116,32,99,117,115,116,111,109,32,98,97,115,101,108,105,110,101,32,102,117,110,99,116,105,111,110,32,116,111,32,110,111,116,32,114,101,116,117,114,110,32,78,97,78,0,97,98,115,45,109,101,97,115,117,114,101,0,97,98,115,45,108,97,121,111,117,116,0,78,111,100,101,0,99,114,101,97,116,101,68,101,102,97,117,108,116,0,99,114,101,97,116,101,87,105,116,104,67,111,110,102,105,103,0,100,101,115,116,114,111,121,0,114,101,115,101,116,0,99,111,112,121,83,116,121,108,101,0,115,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,115,101,116,80,111,115,105,116,105,111,110,0,115,101,116,80,111,115,105,116,105,111,110,80,101,114,99,101,110,116,0,115,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,115,101,116,65,108,105,103,110,73,116,101,109,115,0,115,101,116,65,108,105,103,110,83,101,108,102,0,115,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,115,101,116,70,108,101,120,87,114,97,112,0,115,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,115,101,116,77,97,114,103,105,110,0,115,101,116,77,97,114,103,105,110,80,101,114,99,101,110,116,0,115,101,116,77,97,114,103,105,110,65,117,116,111,0,115,101,116,79,118,101,114,102,108,111,119,0,115,101,116,68,105,115,112,108,97,121,0,115,101,116,70,108,101,120,0,115,101,116,70,108,101,120,66,97,115,105,115,0,115,101,116,70,108,101,120,66,97,115,105,115,80,101,114,99,101,110,116,0,115,101,116,70,108,101,120,71,114,111,119,0,115,101,116,70,108,101,120,83,104,114,105,110,107,0,115,101,116,87,105,100,116,104,0,115,101,116,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,87,105,100,116,104,65,117,116,111,0,115,101,116,72,101,105,103,104,116,0,115,101,116,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,72,101,105,103,104,116,65,117,116,111,0,115,101,116,77,105,110,87,105,100,116,104,0,115,101,116,77,105,110,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,105,110,72,101,105,103,104,116,0,115,101,116,77,105,110,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,77,97,120,87,105,100,116,104,0,115,101,116,77,97,120,87,105,100,116,104,80,101,114,99,101,110,116,0,115,101,116,77,97,120,72,101,105,103,104,116,0,115,101,116,77,97,120,72,101,105,103,104,116,80,101,114,99,101,110,116,0,115,101,116,65,115,112,101,99,116,82,97,116,105,111,0,115,101,116,66,111,114,100,101,114,0,115,101,116,80,97,100,100,105,110,103,0,115,101,116,80,97,100,100,105,110,103,80,101,114,99,101,110,116,0,103,101,116,80,111,115,105,116,105,111,110,84,121,112,101,0,103,101,116,80,111,115,105,116,105,111,110,0,103,101,116,65,108,105,103,110,67,111,110,116,101,110,116,0,103,101,116,65,108,105,103,110,73,116,101,109,115,0,103,101,116,65,108,105,103,110,83,101,108,102,0,103,101,116,70,108,101,120,68,105,114,101,99,116,105,111,110,0,103,101,116,70,108,101,120,87,114,97,112,0,103,101,116,74,117,115,116,105,102,121,67,111,110,116,101,110,116,0,103,101,116,77,97,114,103,105,110,0,103,101,116,70,108,101,120,66,97,115,105,115,0,103,101,116,70,108,101,120,71,114,111,119,0,103,101,116,70,108,101,120,83,104,114,105,110,107,0,103,101,116,87,105,100,116,104,0,103,101,116,72,101,105,103,104,116,0,103,101,116,77,105,110,87,105,100,116,104,0,103,101,116,77,105,110,72,101,105,103,104,116,0,103,101,116,77,97,120,87,105,100,116,104,0,103,101,116,77,97,120,72,101,105,103,104,116,0,103,101,116,65,115,112,101,99,116,82,97,116,105,111,0,103,101,116,66,111,114,100,101,114,0,103,101,116,79,118,101,114,102,108,111,119,0,103,101,116,68,105,115,112,108,97,121,0,103,101,116,80,97,100,100,105,110,103,0,105,110,115,101,114,116,67,104,105,108,100,0,114,101,109,111,118,101,67,104,105,108,100,0,103,101,116,67,104,105,108,100,67,111,117,110,116,0,103,101,116,80,97,114,101,110,116,0,103,101,116,67,104,105,108,100,0,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,117,110,115,101,116,77,101,97,115,117,114,101,70,117,110,99,0,109,97,114,107,68,105,114,116,121,0,105,115,68,105,114,116,121,0,99,97,108,99,117,108,97,116,101,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,76,101,102,116,0,103,101,116,67,111,109,112,117,116,101,100,82,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,84,111,112,0,103,101,116,67,111,109,112,117,116,101,100,66,111,116,116,111,109,0,103,101,116,67,111,109,112,117,116,101,100,87,105,100,116,104,0,103,101,116,67,111,109,112,117,116,101,100,72,101,105,103,104,116,0,103,101,116,67,111,109,112,117,116,101,100,76,97,121,111,117,116,0,103,101,116,67,111,109,112,117,116,101,100,77,97,114,103,105,110,0,103,101,116,67,111,109,112,117,116,101,100,66,111,114,100,101,114,0,103,101,116,67,111,109,112,117,116,101,100,80,97,100,100,105,110,103,0,67,111,110,102,105,103,0,99,114,101,97,116,101,0,115,101,116,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,115,101,116,80,111,105,110,116,83,99,97,108,101,70,97,99,116,111,114,0,105,115,69,120,112,101,114,105,109,101,110,116,97,108,70,101,97,116,117,114,101,69,110,97,98,108,101,100,0,86,97,108,117,101,0,76,97,121,111,117,116,0,83,105,122,101,0,103,101,116,73,110,115,116,97,110,99,101,67,111,117,110,116,0,73,110,116,54,52,0,1,1,1,2,2,4,4,4,4,8,8,4,8,118,111,105,100,0,98,111,111,108,0,115,116,100,58,58,115,116,114,105,110,103,0,99,98,70,117,110,99,116,105,111,110,32,38,0,99,111,110,115,116,32,99,98,70,117,110,99,116,105,111,110,32,38,0,69,120,116,101,114,110,97,108,0,66,117,102,102,101,114,0,78,66,105,110,100,73,68,0,78,66,105,110,100,0,98,105,110,100,95,118,97,108,117,101,0,114,101,102,108,101,99,116,0,113,117,101,114,121,84,121,112,101,0,108,97,108,108,111,99,0,108,114,101,115,101,116,0,123,114,101,116,117,114,110,40,95,110,98,105,110,100,46,99,97,108,108,98,97,99,107,83,105,103,110,97,116,117,114,101,76,105,115,116,91,36,48,93,46,97,112,112,108,121,40,116,104,105,115,44,97,114,103,117,109,101,110,116,115,41,41,59,125,0,95,110,98,105,110,100,95,110,101,119,0,17,0,10,0,17,17,17,0,0,0,0,5,0,0,0,0,0,0,9,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,15,10,17,17,17,3,10,7,0,1,19,9,11,11,0,0,9,6,11,0,0,11,0,6,17,0,0,0,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,17,0,10,10,17,17,17,0,10,0,0,2,0,9,11,0,0,0,9,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,13,0,0,0,4,13,0,0,0,0,9,14,0,0,0,0,0,14,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,0,15,0,0,0,0,9,16,0,0,0,0,0,16,0,0,16,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,18,0,0,0,18,18,18,0,0,0,0,0,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,10,0,0,0,0,9,11,0,0,0,0,0,11,0,0,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,0,0,0,0,12,0,0,0,0,12,0,0,0,0,9,12,0,0,0,0,0,12,0,0,12,0,0,45,43,32,32,32,48,88,48,120,0,40,110,117,108,108,41,0,45,48,88,43,48,88,32,48,88,45,48,120,43,48,120,32,48,120,0,105,110,102,0,73,78,70,0,110,97,110,0,78,65,78,0,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,46,0,84,33,34,25,13,1,2,3,17,75,28,12,16,4,11,29,18,30,39,104,110,111,112,113,98,32,5,6,15,19,20,21,26,8,22,7,40,36,23,24,9,10,14,27,31,37,35,131,130,125,38,42,43,60,61,62,63,67,71,74,77,88,89,90,91,92,93,94,95,96,97,99,100,101,102,103,105,106,107,108,114,115,116,121,122,123,124,0,73,108,108,101,103,97,108,32,98,121,116,101,32,115,101,113,117,101,110,99,101,0,68,111,109,97,105,110,32,101,114,114,111,114,0,82,101,115,117,108,116,32,110,111,116,32,114,101,112,114,101,115,101,110,116,97,98,108,101,0,78,111,116,32,97,32,116,116,121,0,80,101,114,109,105,115,115,105,111,110,32,100,101,110,105,101,100,0,79,112,101,114,97,116,105,111,110,32,110,111,116,32,112,101,114,109,105,116,116,101,100,0,78,111,32,115,117,99,104,32,102,105,108,101,32,111,114,32,100,105,114,101,99,116,111,114,121,0,78,111,32,115,117,99,104,32,112,114,111,99,101,115,115,0,70,105,108,101,32,101,120,105,115,116,115,0,86,97,108,117,101,32,116,111,111,32,108,97,114,103,101,32,102,111,114,32,100,97,116,97,32,116,121,112,101,0,78,111,32,115,112,97,99,101,32,108,101,102,116,32,111,110,32,100,101,118,105,99,101,0,79,117,116,32,111,102,32,109,101,109,111,114,121,0,82,101,115,111,117,114,99,101,32,98,117,115,121,0,73,110,116,101,114,114,117,112,116,101,100,32,115,121,115,116,101,109,32,99,97,108,108,0,82,101,115,111,117,114,99,101,32,116,101,109,112,111,114,97,114,105,108,121,32,117,110,97,118,97,105,108,97,98,108,101,0,73,110,118,97,108,105,100,32,115,101,101,107,0,67,114,111,115,115,45,100,101,118,105,99,101,32,108,105,110,107,0,82,101,97,100,45,111,110,108,121,32,102,105,108,101,32,115,121,115,116,101,109,0,68,105,114,101,99,116,111,114,121,32,110,111,116,32,101,109,112,116,121,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,112,101,101,114,0,79,112,101,114,97,116,105,111,110,32,116,105,109,101,100,32,111,117,116,0,67,111,110,110,101,99,116,105,111,110,32,114,101,102,117,115,101,100,0,72,111,115,116,32,105,115,32,100,111,119,110,0,72,111,115,116,32,105,115,32,117,110,114,101,97,99,104,97,98,108,101,0,65,100,100,114,101,115,115,32,105,110,32,117,115,101,0,66,114,111,107,101,110,32,112,105,112,101,0,73,47,79,32,101,114,114,111,114,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,32,111,114,32,97,100,100,114,101,115,115,0,66,108,111,99,107,32,100,101,118,105,99,101,32,114,101,113,117,105,114,101,100,0,78,111,32,115,117,99,104,32,100,101,118,105,99,101,0,78,111,116,32,97,32,100,105,114,101,99,116,111,114,121,0,73,115,32,97,32,100,105,114,101,99,116,111,114,121,0,84,101,120,116,32,102,105,108,101,32,98,117,115,121,0,69,120,101,99,32,102,111,114,109,97,116,32,101,114,114,111,114,0,73,110,118,97,108,105,100,32,97,114,103,117,109,101,110,116,0,65,114,103,117,109,101,110,116,32,108,105,115,116,32,116,111,111,32,108,111,110,103,0,83,121,109,98,111,108,105,99,32,108,105,110,107,32,108,111,111,112,0,70,105,108,101,110,97,109,101,32,116,111,111,32,108,111,110,103,0,84,111,111,32,109,97,110,121,32,111,112,101,110,32,102,105,108,101,115,32,105,110,32,115,121,115,116,101,109,0,78,111,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,115,32,97,118,97,105,108,97,98,108,101,0,66,97,100,32,102,105,108,101,32,100,101,115,99,114,105,112,116,111,114,0,78,111,32,99,104,105,108,100,32,112,114,111,99,101,115,115,0,66,97,100,32,97,100,100,114,101,115,115,0,70,105,108,101,32,116,111,111,32,108,97,114,103,101,0,84,111,111,32,109,97,110,121,32,108,105,110,107,115,0,78,111,32,108,111,99,107,115,32,97,118,97,105,108,97,98,108,101,0,82,101,115,111,117,114,99,101,32,100,101,97,100,108,111,99,107,32,119,111,117,108,100,32,111,99,99,117,114,0,83,116,97,116,101,32,110,111,116,32,114,101,99,111,118,101,114,97,98,108,101,0,80,114,101,118,105,111,117,115,32,111,119,110,101,114,32,100,105,101,100,0,79,112,101,114,97,116,105,111,110,32,99,97,110,99,101,108,101,100,0,70,117,110,99,116,105,111,110,32,110,111,116,32,105,109,112,108,101,109,101,110,116,101,100,0,78,111,32,109,101,115,115,97,103,101,32,111,102,32,100,101,115,105,114,101,100,32,116,121,112,101,0,73,100,101,110,116,105,102,105,101,114,32,114,101,109,111,118,101,100,0,68,101,118,105,99,101,32,110,111,116,32,97,32,115,116,114,101,97,109,0,78,111,32,100,97,116,97,32,97,118,97,105,108,97,98,108,101,0,68,101,118,105,99,101,32,116,105,109,101,111,117,116,0,79,117,116,32,111,102,32,115,116,114,101,97,109,115,32,114,101,115,111,117,114,99,101,115,0,76,105,110,107,32,104,97,115,32,98,101,101,110,32,115,101,118,101,114,101,100,0,80,114,111,116,111,99,111,108,32,101,114,114,111,114,0,66,97,100,32,109,101,115,115,97,103,101,0,70,105,108,101,32,100,101,115,99,114,105,112,116,111,114,32,105,110,32,98,97,100,32,115,116,97,116,101,0,78,111,116,32,97,32,115,111,99,107,101,116,0,68,101,115,116,105,110,97,116,105,111,110,32,97,100,100,114,101,115,115,32,114,101,113,117,105,114,101,100,0,77,101,115,115,97,103,101,32,116,111,111,32,108,97,114,103,101,0,80,114,111,116,111,99,111,108,32,119,114,111,110,103,32,116,121,112,101,32,102,111,114,32,115,111,99,107,101,116,0,80,114,111,116,111,99,111,108,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,80,114,111,116,111,99,111,108,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,83,111,99,107,101,116,32,116,121,112,101,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,78,111,116,32,115,117,112,112,111,114,116,101,100,0,80,114,111,116,111,99,111,108,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,0,65,100,100,114,101,115,115,32,102,97,109,105,108,121,32,110,111,116,32,115,117,112,112,111,114,116,101,100,32,98,121,32,112,114,111,116,111,99,111,108,0,65,100,100,114,101,115,115,32,110,111,116,32,97,118,97,105,108,97,98,108,101,0,78,101,116,119,111,114,107,32,105,115,32,100,111,119,110,0,78,101,116,119,111,114,107,32,117,110,114,101,97,99,104,97,98,108,101,0,67,111,110,110,101,99,116,105,111,110,32,114,101,115,101,116,32,98,121,32,110,101,116,119,111,114,107,0,67,111,110,110,101,99,116,105,111,110,32,97,98,111,114,116,101,100,0,78,111,32,98,117,102,102,101,114,32,115,112,97,99,101,32,97,118,97,105,108,97,98,108,101,0,83,111,99,107,101,116,32,105,115,32,99,111,110,110,101,99,116,101,100,0,83,111,99,107,101,116,32,110,111,116,32,99,111,110,110,101,99,116,101,100,0,67,97,110,110,111,116,32,115,101,110,100,32,97,102,116,101,114,32,115,111,99,107,101,116,32,115,104,117,116,100,111,119,110,0,79,112,101,114,97,116,105,111,110,32,97,108,114,101,97,100,121,32,105,110,32,112,114,111,103,114,101,115,115,0,79,112,101,114,97,116,105,111,110,32,105,110,32,112,114,111,103,114,101,115,115,0,83,116,97,108,101,32,102,105,108,101,32,104,97,110,100,108,101,0,82,101,109,111,116,101,32,73,47,79,32,101,114,114,111,114,0,81,117,111,116,97,32,101,120,99,101,101,100,101,100,0,78,111,32,109,101,100,105,117,109,32,102,111,117,110,100,0,87,114,111,110,103,32,109,101,100,105,117,109,32,116,121,112,101,0,78,111,32,101,114,114,111,114,32,105,110,102,111,114,109,97,116,105,111,110,0,0],"i8",ALLOC_NONE,Runtime.GLOBAL_BASE);var tempDoublePtr=STATICTOP;STATICTOP+=16;function _atexit(t,e){__ATEXIT__.unshift({func:t,arg:e})}function ___cxa_atexit(){return _atexit.apply(null,arguments)}function _abort(){Module.abort()}function __ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj(){Module.printErr("missing function: _ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj"),abort(-1)}function __decorate(t,e,r,s){var a=arguments.length,n=a<3?e:s===null?s=Object.getOwnPropertyDescriptor(e,r):s,c;if(typeof Reflect=="object"&&typeof Reflect.decorate=="function")n=Reflect.decorate(t,e,r,s);else for(var f=t.length-1;f>=0;f--)(c=t[f])&&(n=(a<3?c(n):a>3?c(e,r,n):c(e,r))||n);return a>3&&n&&Object.defineProperty(e,r,n),n}function _defineHidden(t){return function(e,r){Object.defineProperty(e,r,{configurable:!1,enumerable:!1,value:t,writable:!0})}}var _nbind={};function __nbind_free_external(t){_nbind.externalList[t].dereference(t)}function __nbind_reference_external(t){_nbind.externalList[t].reference()}function _llvm_stackrestore(t){var e=_llvm_stacksave,r=e.LLVM_SAVEDSTACKS[t];e.LLVM_SAVEDSTACKS.splice(t,1),Runtime.stackRestore(r)}function __nbind_register_pool(t,e,r,s){_nbind.Pool.pageSize=t,_nbind.Pool.usedPtr=e/4,_nbind.Pool.rootPtr=r,_nbind.Pool.pagePtr=s/4,HEAP32[e/4]=16909060,HEAP8[e]==1&&(_nbind.bigEndian=!0),HEAP32[e/4]=0,_nbind.makeTypeKindTbl=(n={},n[1024]=_nbind.PrimitiveType,n[64]=_nbind.Int64Type,n[2048]=_nbind.BindClass,n[3072]=_nbind.BindClassPtr,n[4096]=_nbind.SharedClassPtr,n[5120]=_nbind.ArrayType,n[6144]=_nbind.ArrayType,n[7168]=_nbind.CStringType,n[9216]=_nbind.CallbackType,n[10240]=_nbind.BindType,n),_nbind.makeTypeNameTbl={Buffer:_nbind.BufferType,External:_nbind.ExternalType,Int64:_nbind.Int64Type,_nbind_new:_nbind.CreateValueType,bool:_nbind.BooleanType,"cbFunction &":_nbind.CallbackType,"const cbFunction &":_nbind.CallbackType,"const std::string &":_nbind.StringType,"std::string":_nbind.StringType},Module.toggleLightGC=_nbind.toggleLightGC,_nbind.callUpcast=Module.dynCall_ii;var a=_nbind.makeType(_nbind.constructType,{flags:2048,id:0,name:""});a.proto=Module,_nbind.BindClass.list.push(a);var n}function _emscripten_set_main_loop_timing(t,e){if(Browser.mainLoop.timingMode=t,Browser.mainLoop.timingValue=e,!Browser.mainLoop.func)return 1;if(t==0)Browser.mainLoop.scheduler=function(){var c=Math.max(0,Browser.mainLoop.tickStartTime+e-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,c)},Browser.mainLoop.method="timeout";else if(t==1)Browser.mainLoop.scheduler=function(){Browser.requestAnimationFrame(Browser.mainLoop.runner)},Browser.mainLoop.method="rAF";else if(t==2){if(!window.setImmediate){let n=function(c){c.source===window&&c.data===s&&(c.stopPropagation(),r.shift()())};var a=n,r=[],s="setimmediate";window.addEventListener("message",n,!0),window.setImmediate=function(f){r.push(f),ENVIRONMENT_IS_WORKER?(Module.setImmediates===void 0&&(Module.setImmediates=[]),Module.setImmediates.push(f),window.postMessage({target:s})):window.postMessage(s,"*")}}Browser.mainLoop.scheduler=function(){window.setImmediate(Browser.mainLoop.runner)},Browser.mainLoop.method="immediate"}return 0}function _emscripten_get_now(){abort()}function _emscripten_set_main_loop(t,e,r,s,a){Module.noExitRuntime=!0,assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters."),Browser.mainLoop.func=t,Browser.mainLoop.arg=s;var n;typeof s<"u"?n=function(){Module.dynCall_vi(t,s)}:n=function(){Module.dynCall_v(t)};var c=Browser.mainLoop.currentlyRunningMainloop;if(Browser.mainLoop.runner=function(){if(!ABORT){if(Browser.mainLoop.queue.length>0){var p=Date.now(),h=Browser.mainLoop.queue.shift();if(h.func(h.arg),Browser.mainLoop.remainingBlockers){var E=Browser.mainLoop.remainingBlockers,C=E%1==0?E-1:Math.floor(E);h.counted?Browser.mainLoop.remainingBlockers=C:(C=C+.5,Browser.mainLoop.remainingBlockers=(8*E+C)/9)}if(console.log('main loop blocker "'+h.name+'" took '+(Date.now()-p)+" ms"),Browser.mainLoop.updateStatus(),c1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else Browser.mainLoop.timingMode==0&&(Browser.mainLoop.tickStartTime=_emscripten_get_now());Browser.mainLoop.method==="timeout"&&Module.ctx&&(Module.printErr("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!"),Browser.mainLoop.method=""),Browser.mainLoop.runIter(n),!(c0?_emscripten_set_main_loop_timing(0,1e3/e):_emscripten_set_main_loop_timing(1,1),Browser.mainLoop.scheduler()),r)throw"SimulateInfiniteLoop"}var Browser={mainLoop:{scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null,Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var t=Browser.mainLoop.timingMode,e=Browser.mainLoop.timingValue,r=Browser.mainLoop.func;Browser.mainLoop.func=null,_emscripten_set_main_loop(r,0,!1,Browser.mainLoop.arg,!0),_emscripten_set_main_loop_timing(t,e),Browser.mainLoop.scheduler()},updateStatus:function(){if(Module.setStatus){var t=Module.statusMessage||"Please wait...",e=Browser.mainLoop.remainingBlockers,r=Browser.mainLoop.expectedBlockers;e?e"u"&&(console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available."),Module.noImageDecoding=!0);var t={};t.canHandle=function(n){return!Module.noImageDecoding&&/\.(jpg|jpeg|png|bmp)$/i.test(n)},t.handle=function(n,c,f,p){var h=null;if(Browser.hasBlobConstructor)try{h=new Blob([n],{type:Browser.getMimetype(c)}),h.size!==n.length&&(h=new Blob([new Uint8Array(n).buffer],{type:Browser.getMimetype(c)}))}catch(P){Runtime.warnOnce("Blob constructor present but fails: "+P+"; falling back to blob builder")}if(!h){var E=new Browser.BlobBuilder;E.append(new Uint8Array(n).buffer),h=E.getBlob()}var C=Browser.URLObject.createObjectURL(h),S=new Image;S.onload=function(){assert(S.complete,"Image "+c+" could not be decoded");var I=document.createElement("canvas");I.width=S.width,I.height=S.height;var R=I.getContext("2d");R.drawImage(S,0,0),Module.preloadedImages[c]=I,Browser.URLObject.revokeObjectURL(C),f&&f(n)},S.onerror=function(I){console.log("Image "+C+" could not be decoded"),p&&p()},S.src=C},Module.preloadPlugins.push(t);var e={};e.canHandle=function(n){return!Module.noAudioDecoding&&n.substr(-4)in{".ogg":1,".wav":1,".mp3":1}},e.handle=function(n,c,f,p){var h=!1;function E(R){h||(h=!0,Module.preloadedAudios[c]=R,f&&f(n))}function C(){h||(h=!0,Module.preloadedAudios[c]=new Audio,p&&p())}if(Browser.hasBlobConstructor){try{var S=new Blob([n],{type:Browser.getMimetype(c)})}catch{return C()}var P=Browser.URLObject.createObjectURL(S),I=new Audio;I.addEventListener("canplaythrough",function(){E(I)},!1),I.onerror=function(N){if(h)return;console.log("warning: browser could not fully decode audio "+c+", trying slower base64 approach");function U(W){for(var te="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",ie="=",Ae="",ce=0,me=0,pe=0;pe=6;){var Be=ce>>me-6&63;me-=6,Ae+=te[Be]}return me==2?(Ae+=te[(ce&3)<<4],Ae+=ie+ie):me==4&&(Ae+=te[(ce&15)<<2],Ae+=ie),Ae}I.src="data:audio/x-"+c.substr(-3)+";base64,"+U(n),E(I)},I.src=P,Browser.safeSetTimeout(function(){E(I)},1e4)}else return C()},Module.preloadPlugins.push(e);function r(){Browser.pointerLock=document.pointerLockElement===Module.canvas||document.mozPointerLockElement===Module.canvas||document.webkitPointerLockElement===Module.canvas||document.msPointerLockElement===Module.canvas}var s=Module.canvas;s&&(s.requestPointerLock=s.requestPointerLock||s.mozRequestPointerLock||s.webkitRequestPointerLock||s.msRequestPointerLock||function(){},s.exitPointerLock=document.exitPointerLock||document.mozExitPointerLock||document.webkitExitPointerLock||document.msExitPointerLock||function(){},s.exitPointerLock=s.exitPointerLock.bind(document),document.addEventListener("pointerlockchange",r,!1),document.addEventListener("mozpointerlockchange",r,!1),document.addEventListener("webkitpointerlockchange",r,!1),document.addEventListener("mspointerlockchange",r,!1),Module.elementPointerLock&&s.addEventListener("click",function(a){!Browser.pointerLock&&Module.canvas.requestPointerLock&&(Module.canvas.requestPointerLock(),a.preventDefault())},!1))},createContext:function(t,e,r,s){if(e&&Module.ctx&&t==Module.canvas)return Module.ctx;var a,n;if(e){var c={antialias:!1,alpha:!1};if(s)for(var f in s)c[f]=s[f];n=GL.createContext(t,c),n&&(a=GL.getContext(n).GLctx)}else a=t.getContext("2d");return a?(r&&(e||assert(typeof GLctx>"u","cannot set in module if GLctx is used, but we are a non-GL context that would replace it"),Module.ctx=a,e&&GL.makeContextCurrent(n),Module.useWebGL=e,Browser.moduleContextCreatedCallbacks.forEach(function(p){p()}),Browser.init()),a):null},destroyContext:function(t,e,r){},fullscreenHandlersInstalled:!1,lockPointer:void 0,resizeCanvas:void 0,requestFullscreen:function(t,e,r){Browser.lockPointer=t,Browser.resizeCanvas=e,Browser.vrDevice=r,typeof Browser.lockPointer>"u"&&(Browser.lockPointer=!0),typeof Browser.resizeCanvas>"u"&&(Browser.resizeCanvas=!1),typeof Browser.vrDevice>"u"&&(Browser.vrDevice=null);var s=Module.canvas;function a(){Browser.isFullscreen=!1;var c=s.parentNode;(document.fullscreenElement||document.mozFullScreenElement||document.msFullscreenElement||document.webkitFullscreenElement||document.webkitCurrentFullScreenElement)===c?(s.exitFullscreen=document.exitFullscreen||document.cancelFullScreen||document.mozCancelFullScreen||document.msExitFullscreen||document.webkitCancelFullScreen||function(){},s.exitFullscreen=s.exitFullscreen.bind(document),Browser.lockPointer&&s.requestPointerLock(),Browser.isFullscreen=!0,Browser.resizeCanvas&&Browser.setFullscreenCanvasSize()):(c.parentNode.insertBefore(s,c),c.parentNode.removeChild(c),Browser.resizeCanvas&&Browser.setWindowedCanvasSize()),Module.onFullScreen&&Module.onFullScreen(Browser.isFullscreen),Module.onFullscreen&&Module.onFullscreen(Browser.isFullscreen),Browser.updateCanvasDimensions(s)}Browser.fullscreenHandlersInstalled||(Browser.fullscreenHandlersInstalled=!0,document.addEventListener("fullscreenchange",a,!1),document.addEventListener("mozfullscreenchange",a,!1),document.addEventListener("webkitfullscreenchange",a,!1),document.addEventListener("MSFullscreenChange",a,!1));var n=document.createElement("div");s.parentNode.insertBefore(n,s),n.appendChild(s),n.requestFullscreen=n.requestFullscreen||n.mozRequestFullScreen||n.msRequestFullscreen||(n.webkitRequestFullscreen?function(){n.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT)}:null)||(n.webkitRequestFullScreen?function(){n.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT)}:null),r?n.requestFullscreen({vrDisplay:r}):n.requestFullscreen()},requestFullScreen:function(t,e,r){return Module.printErr("Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead."),Browser.requestFullScreen=function(s,a,n){return Browser.requestFullscreen(s,a,n)},Browser.requestFullscreen(t,e,r)},nextRAF:0,fakeRequestAnimationFrame:function(t){var e=Date.now();if(Browser.nextRAF===0)Browser.nextRAF=e+1e3/60;else for(;e+2>=Browser.nextRAF;)Browser.nextRAF+=1e3/60;var r=Math.max(Browser.nextRAF-e,0);setTimeout(t,r)},requestAnimationFrame:function t(e){typeof window>"u"?Browser.fakeRequestAnimationFrame(e):(window.requestAnimationFrame||(window.requestAnimationFrame=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||window.oRequestAnimationFrame||Browser.fakeRequestAnimationFrame),window.requestAnimationFrame(e))},safeCallback:function(t){return function(){if(!ABORT)return t.apply(null,arguments)}},allowAsyncCallbacks:!0,queuedAsyncCallbacks:[],pauseAsyncCallbacks:function(){Browser.allowAsyncCallbacks=!1},resumeAsyncCallbacks:function(){if(Browser.allowAsyncCallbacks=!0,Browser.queuedAsyncCallbacks.length>0){var t=Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks=[],t.forEach(function(e){e()})}},safeRequestAnimationFrame:function(t){return Browser.requestAnimationFrame(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))})},safeSetTimeout:function(t,e){return Module.noExitRuntime=!0,setTimeout(function(){ABORT||(Browser.allowAsyncCallbacks?t():Browser.queuedAsyncCallbacks.push(t))},e)},safeSetInterval:function(t,e){return Module.noExitRuntime=!0,setInterval(function(){ABORT||Browser.allowAsyncCallbacks&&t()},e)},getMimetype:function(t){return{jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",bmp:"image/bmp",ogg:"audio/ogg",wav:"audio/wav",mp3:"audio/mpeg"}[t.substr(t.lastIndexOf(".")+1)]},getUserMedia:function(t){window.getUserMedia||(window.getUserMedia=navigator.getUserMedia||navigator.mozGetUserMedia),window.getUserMedia(t)},getMovementX:function(t){return t.movementX||t.mozMovementX||t.webkitMovementX||0},getMovementY:function(t){return t.movementY||t.mozMovementY||t.webkitMovementY||0},getMouseWheelDelta:function(t){var e=0;switch(t.type){case"DOMMouseScroll":e=t.detail;break;case"mousewheel":e=t.wheelDelta;break;case"wheel":e=t.deltaY;break;default:throw"unrecognized mouse wheel event: "+t.type}return e},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(t){if(Browser.pointerLock)t.type!="mousemove"&&"mozMovementX"in t?Browser.mouseMovementX=Browser.mouseMovementY=0:(Browser.mouseMovementX=Browser.getMovementX(t),Browser.mouseMovementY=Browser.getMovementY(t)),typeof SDL<"u"?(Browser.mouseX=SDL.mouseX+Browser.mouseMovementX,Browser.mouseY=SDL.mouseY+Browser.mouseMovementY):(Browser.mouseX+=Browser.mouseMovementX,Browser.mouseY+=Browser.mouseMovementY);else{var e=Module.canvas.getBoundingClientRect(),r=Module.canvas.width,s=Module.canvas.height,a=typeof window.scrollX<"u"?window.scrollX:window.pageXOffset,n=typeof window.scrollY<"u"?window.scrollY:window.pageYOffset;if(t.type==="touchstart"||t.type==="touchend"||t.type==="touchmove"){var c=t.touch;if(c===void 0)return;var f=c.pageX-(a+e.left),p=c.pageY-(n+e.top);f=f*(r/e.width),p=p*(s/e.height);var h={x:f,y:p};if(t.type==="touchstart")Browser.lastTouches[c.identifier]=h,Browser.touches[c.identifier]=h;else if(t.type==="touchend"||t.type==="touchmove"){var E=Browser.touches[c.identifier];E||(E=h),Browser.lastTouches[c.identifier]=E,Browser.touches[c.identifier]=h}return}var C=t.pageX-(a+e.left),S=t.pageY-(n+e.top);C=C*(r/e.width),S=S*(s/e.height),Browser.mouseMovementX=C-Browser.mouseX,Browser.mouseMovementY=S-Browser.mouseY,Browser.mouseX=C,Browser.mouseY=S}},asyncLoad:function(t,e,r,s){var a=s?"":"al "+t;Module.readAsync(t,function(n){assert(n,'Loading data file "'+t+'" failed (no arrayBuffer).'),e(new Uint8Array(n)),a&&removeRunDependency(a)},function(n){if(r)r();else throw'Loading data file "'+t+'" failed.'}),a&&addRunDependency(a)},resizeListeners:[],updateResizeListeners:function(){var t=Module.canvas;Browser.resizeListeners.forEach(function(e){e(t.width,t.height)})},setCanvasSize:function(t,e,r){var s=Module.canvas;Browser.updateCanvasDimensions(s,t,e),r||Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL<"u"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t|8388608,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL<"u"){var t=HEAPU32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2];t=t&-8388609,HEAP32[SDL.screen+Runtime.QUANTUM_SIZE*0>>2]=t}Browser.updateResizeListeners()},updateCanvasDimensions:function(t,e,r){e&&r?(t.widthNative=e,t.heightNative=r):(e=t.widthNative,r=t.heightNative);var s=e,a=r;if(Module.forcedAspectRatio&&Module.forcedAspectRatio>0&&(s/a>2];return e},getStr:function(){var t=Pointer_stringify(SYSCALLS.get());return t},get64:function(){var t=SYSCALLS.get(),e=SYSCALLS.get();return t>=0?assert(e===0):assert(e===-1),t},getZero:function(){assert(SYSCALLS.get()===0)}};function ___syscall6(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD();return FS.close(r),0}catch(s){return(typeof FS>"u"||!(s instanceof FS.ErrnoError))&&abort(s),-s.errno}}function ___syscall54(t,e){SYSCALLS.varargs=e;try{return 0}catch(r){return(typeof FS>"u"||!(r instanceof FS.ErrnoError))&&abort(r),-r.errno}}function _typeModule(t){var e=[[0,1,"X"],[1,1,"const X"],[128,1,"X *"],[256,1,"X &"],[384,1,"X &&"],[512,1,"std::shared_ptr"],[640,1,"std::unique_ptr"],[5120,1,"std::vector"],[6144,2,"std::array"],[9216,-1,"std::function"]];function r(p,h,E,C,S,P){if(h==1){var I=C&896;(I==128||I==256||I==384)&&(p="X const")}var R;return P?R=E.replace("X",p).replace("Y",S):R=p.replace("X",E).replace("Y",S),R.replace(/([*&]) (?=[*&])/g,"$1")}function s(p,h,E,C,S){throw new Error(p+" type "+E.replace("X",h+"?")+(C?" with flag "+C:"")+" in "+S)}function a(p,h,E,C,S,P,I,R){P===void 0&&(P="X"),R===void 0&&(R=1);var N=E(p);if(N)return N;var U=C(p),W=U.placeholderFlag,te=e[W];I&&te&&(P=r(I[2],I[0],P,te[0],"?",!0));var ie;W==0&&(ie="Unbound"),W>=10&&(ie="Corrupt"),R>20&&(ie="Deeply nested"),ie&&s(ie,p,P,W,S||"?");var Ae=U.paramList[0],ce=a(Ae,h,E,C,S,P,te,R+1),me,pe={flags:te[0],id:p,name:"",paramList:[ce]},Be=[],Ce="?";switch(U.placeholderFlag){case 1:me=ce.spec;break;case 2:if((ce.flags&15360)==1024&&ce.spec.ptrSize==1){pe.flags=7168;break}case 3:case 6:case 5:me=ce.spec,ce.flags&15360;break;case 8:Ce=""+U.paramList[1],pe.paramList.push(U.paramList[1]);break;case 9:for(var g=0,we=U.paramList[1];g>2]=t),t}function _llvm_stacksave(){var t=_llvm_stacksave;return t.LLVM_SAVEDSTACKS||(t.LLVM_SAVEDSTACKS=[]),t.LLVM_SAVEDSTACKS.push(Runtime.stackSave()),t.LLVM_SAVEDSTACKS.length-1}function ___syscall140(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.getStreamFromFD(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=SYSCALLS.get(),c=SYSCALLS.get(),f=a;return FS.llseek(r,f,c),HEAP32[n>>2]=r.position,r.getdents&&f===0&&c===0&&(r.getdents=null),0}catch(p){return(typeof FS>"u"||!(p instanceof FS.ErrnoError))&&abort(p),-p.errno}}function ___syscall146(t,e){SYSCALLS.varargs=e;try{var r=SYSCALLS.get(),s=SYSCALLS.get(),a=SYSCALLS.get(),n=0;___syscall146.buffer||(___syscall146.buffers=[null,[],[]],___syscall146.printChar=function(E,C){var S=___syscall146.buffers[E];assert(S),C===0||C===10?((E===1?Module.print:Module.printErr)(UTF8ArrayToString(S,0)),S.length=0):S.push(C)});for(var c=0;c>2],p=HEAP32[s+(c*8+4)>>2],h=0;h"u"||!(E instanceof FS.ErrnoError))&&abort(E),-E.errno}}function __nbind_finish(){for(var t=0,e=_nbind.BindClass.list;tt.pageSize/2||e>t.pageSize-r){var s=_nbind.typeNameTbl.NBind.proto;return s.lalloc(e)}else return HEAPU32[t.usedPtr]=r+e,t.rootPtr+r},t.lreset=function(e,r){var s=HEAPU32[t.pagePtr];if(s){var a=_nbind.typeNameTbl.NBind.proto;a.lreset(e,r)}else HEAPU32[t.usedPtr]=e},t}();_nbind.Pool=Pool;function constructType(t,e){var r=t==10240?_nbind.makeTypeNameTbl[e.name]||_nbind.BindType:_nbind.makeTypeKindTbl[t],s=new r(e);return typeIdTbl[e.id]=s,_nbind.typeNameTbl[e.name]=s,s}_nbind.constructType=constructType;function getType(t){return typeIdTbl[t]}_nbind.getType=getType;function queryType(t){var e=HEAPU8[t],r=_nbind.structureList[e][1];t/=4,r<0&&(++t,r=HEAPU32[t]+1);var s=Array.prototype.slice.call(HEAPU32.subarray(t+1,t+1+r));return e==9&&(s=[s[0],s.slice(1)]),{paramList:s,placeholderFlag:e}}_nbind.queryType=queryType;function getTypes(t,e){return t.map(function(r){return typeof r=="number"?_nbind.getComplexType(r,constructType,getType,queryType,e):_nbind.typeNameTbl[r]})}_nbind.getTypes=getTypes;function readTypeIdList(t,e){return Array.prototype.slice.call(HEAPU32,t/4,t/4+e)}_nbind.readTypeIdList=readTypeIdList;function readAsciiString(t){for(var e=t;HEAPU8[e++];);return String.fromCharCode.apply("",HEAPU8.subarray(t,e-1))}_nbind.readAsciiString=readAsciiString;function readPolicyList(t){var e={};if(t)for(;;){var r=HEAPU32[t/4];if(!r)break;e[readAsciiString(r)]=!0,t+=4}return e}_nbind.readPolicyList=readPolicyList;function getDynCall(t,e){var r={float32_t:"d",float64_t:"d",int64_t:"d",uint64_t:"d",void:"v"},s=t.map(function(n){return r[n.name]||"i"}).join(""),a=Module["dynCall_"+s];if(!a)throw new Error("dynCall_"+s+" not found for "+e+"("+t.map(function(n){return n.name}).join(", ")+")");return a}_nbind.getDynCall=getDynCall;function addMethod(t,e,r,s){var a=t[e];t.hasOwnProperty(e)&&a?((a.arity||a.arity===0)&&(a=_nbind.makeOverloader(a,a.arity),t[e]=a),a.addMethod(r,s)):(r.arity=s,t[e]=r)}_nbind.addMethod=addMethod;function throwError(t){throw new Error(t)}_nbind.throwError=throwError,_nbind.bigEndian=!1,_a=_typeModule(_typeModule),_nbind.Type=_a.Type,_nbind.makeType=_a.makeType,_nbind.getComplexType=_a.getComplexType,_nbind.structureList=_a.structureList;var BindType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.heap=HEAPU32,r.ptrSize=4,r}return e.prototype.needsWireRead=function(r){return!!this.wireRead||!!this.makeWireRead},e.prototype.needsWireWrite=function(r){return!!this.wireWrite||!!this.makeWireWrite},e}(_nbind.Type);_nbind.BindType=BindType;var PrimitiveType=function(t){__extends(e,t);function e(r){var s=t.call(this,r)||this,a=r.flags&32?{32:HEAPF32,64:HEAPF64}:r.flags&8?{8:HEAPU8,16:HEAPU16,32:HEAPU32}:{8:HEAP8,16:HEAP16,32:HEAP32};return s.heap=a[r.ptrSize*8],s.ptrSize=r.ptrSize,s}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a=="number")return a;throw new Error("Type mismatch")}},e}(BindType);_nbind.PrimitiveType=PrimitiveType;function pushCString(t,e){if(t==null){if(e&&e.Nullable)return 0;throw new Error("Type mismatch")}if(e&&e.Strict){if(typeof t!="string")throw new Error("Type mismatch")}else t=t.toString();var r=Module.lengthBytesUTF8(t)+1,s=_nbind.Pool.lalloc(r);return Module.stringToUTF8Array(t,HEAPU8,s,r),s}_nbind.pushCString=pushCString;function popCString(t){return t===0?null:Module.Pointer_stringify(t)}_nbind.popCString=popCString;var CStringType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=popCString,r.wireWrite=pushCString,r.readResources=[_nbind.resources.pool],r.writeResources=[_nbind.resources.pool],r}return e.prototype.makeWireWrite=function(r,s){return function(a){return pushCString(a,s)}},e}(BindType);_nbind.CStringType=CStringType;var BooleanType=function(t){__extends(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.wireRead=function(s){return!!s},r}return e.prototype.needsWireWrite=function(r){return!!r&&!!r.Strict},e.prototype.makeWireRead=function(r){return"!!("+r+")"},e.prototype.makeWireWrite=function(r,s){return s&&s.Strict&&function(a){if(typeof a=="boolean")return a;throw new Error("Type mismatch")}||r},e}(BindType);_nbind.BooleanType=BooleanType;var Wrapper=function(){function t(){}return t.prototype.persist=function(){this.__nbindState|=1},t}();_nbind.Wrapper=Wrapper;function makeBound(t,e){var r=function(s){__extends(a,s);function a(n,c,f,p){var h=s.call(this)||this;if(!(h instanceof a))return new(Function.prototype.bind.apply(a,Array.prototype.concat.apply([null],arguments)));var E=c,C=f,S=p;if(n!==_nbind.ptrMarker){var P=h.__nbindConstructor.apply(h,arguments);E=4608,S=HEAPU32[P/4],C=HEAPU32[P/4+1]}var I={configurable:!0,enumerable:!1,value:null,writable:!1},R={__nbindFlags:E,__nbindPtr:C};S&&(R.__nbindShared=S,_nbind.mark(h));for(var N=0,U=Object.keys(R);N>=1;var r=_nbind.valueList[t];return _nbind.valueList[t]=firstFreeValue,firstFreeValue=t,r}else{if(e)return _nbind.popShared(t,e);throw new Error("Invalid value slot "+t)}}_nbind.popValue=popValue;var valueBase=18446744073709552e3;function push64(t){return typeof t=="number"?t:pushValue(t)*4096+valueBase}function pop64(t){return t=3?c=Buffer.from(n):c=new Buffer(n),c.copy(s)}else getBuffer(s).set(n)}}_nbind.commitBuffer=commitBuffer;var dirtyList=[],gcTimer=0;function sweep(){for(var t=0,e=dirtyList;t>2]=DYNAMIC_BASE,staticSealed=!0;function invoke_viiiii(t,e,r,s,a,n){try{Module.dynCall_viiiii(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_vif(t,e,r){try{Module.dynCall_vif(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_vid(t,e,r){try{Module.dynCall_vid(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_fiff(t,e,r,s){try{return Module.dynCall_fiff(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_vi(t,e){try{Module.dynCall_vi(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_vii(t,e,r){try{Module.dynCall_vii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_ii(t,e){try{return Module.dynCall_ii(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_viddi(t,e,r,s,a){try{Module.dynCall_viddi(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}function invoke_vidd(t,e,r,s){try{Module.dynCall_vidd(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_iiii(t,e,r,s){try{return Module.dynCall_iiii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_diii(t,e,r,s){try{return Module.dynCall_diii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_di(t,e){try{return Module.dynCall_di(t,e)}catch(r){if(typeof r!="number"&&r!=="longjmp")throw r;Module.setThrew(1,0)}}function invoke_iid(t,e,r){try{return Module.dynCall_iid(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_iii(t,e,r){try{return Module.dynCall_iii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_viiddi(t,e,r,s,a,n){try{Module.dynCall_viiddi(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_viiiiii(t,e,r,s,a,n,c){try{Module.dynCall_viiiiii(t,e,r,s,a,n,c)}catch(f){if(typeof f!="number"&&f!=="longjmp")throw f;Module.setThrew(1,0)}}function invoke_dii(t,e,r){try{return Module.dynCall_dii(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_i(t){try{return Module.dynCall_i(t)}catch(e){if(typeof e!="number"&&e!=="longjmp")throw e;Module.setThrew(1,0)}}function invoke_iiiiii(t,e,r,s,a,n){try{return Module.dynCall_iiiiii(t,e,r,s,a,n)}catch(c){if(typeof c!="number"&&c!=="longjmp")throw c;Module.setThrew(1,0)}}function invoke_viiid(t,e,r,s,a){try{Module.dynCall_viiid(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}function invoke_viififi(t,e,r,s,a,n,c){try{Module.dynCall_viififi(t,e,r,s,a,n,c)}catch(f){if(typeof f!="number"&&f!=="longjmp")throw f;Module.setThrew(1,0)}}function invoke_viii(t,e,r,s){try{Module.dynCall_viii(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_v(t){try{Module.dynCall_v(t)}catch(e){if(typeof e!="number"&&e!=="longjmp")throw e;Module.setThrew(1,0)}}function invoke_viid(t,e,r,s){try{Module.dynCall_viid(t,e,r,s)}catch(a){if(typeof a!="number"&&a!=="longjmp")throw a;Module.setThrew(1,0)}}function invoke_idd(t,e,r){try{return Module.dynCall_idd(t,e,r)}catch(s){if(typeof s!="number"&&s!=="longjmp")throw s;Module.setThrew(1,0)}}function invoke_viiii(t,e,r,s,a){try{Module.dynCall_viiii(t,e,r,s,a)}catch(n){if(typeof n!="number"&&n!=="longjmp")throw n;Module.setThrew(1,0)}}Module.asmGlobalArg={Math,Int8Array,Int16Array,Int32Array,Uint8Array,Uint16Array,Uint32Array,Float32Array,Float64Array,NaN:NaN,Infinity:1/0},Module.asmLibraryArg={abort,assert,enlargeMemory,getTotalMemory,abortOnCannotGrowMemory,invoke_viiiii,invoke_vif,invoke_vid,invoke_fiff,invoke_vi,invoke_vii,invoke_ii,invoke_viddi,invoke_vidd,invoke_iiii,invoke_diii,invoke_di,invoke_iid,invoke_iii,invoke_viiddi,invoke_viiiiii,invoke_dii,invoke_i,invoke_iiiiii,invoke_viiid,invoke_viififi,invoke_viii,invoke_v,invoke_viid,invoke_idd,invoke_viiii,_emscripten_asm_const_iiiii,_emscripten_asm_const_iiidddddd,_emscripten_asm_const_iiiid,__nbind_reference_external,_emscripten_asm_const_iiiiiiii,_removeAccessorPrefix,_typeModule,__nbind_register_pool,__decorate,_llvm_stackrestore,___cxa_atexit,__extends,__nbind_get_value_object,__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,_emscripten_set_main_loop_timing,__nbind_register_primitive,__nbind_register_type,_emscripten_memcpy_big,__nbind_register_function,___setErrNo,__nbind_register_class,__nbind_finish,_abort,_nbind_value,_llvm_stacksave,___syscall54,_defineHidden,_emscripten_set_main_loop,_emscripten_get_now,__nbind_register_callback_signature,_emscripten_asm_const_iiiiii,__nbind_free_external,_emscripten_asm_const_iiii,_emscripten_asm_const_iiididi,___syscall6,_atexit,___syscall140,___syscall146,DYNAMICTOP_PTR,tempDoublePtr,ABORT,STACKTOP,STACK_MAX,cttz_i8,___dso_handle};var asm=function(t,e,r){var s=new t.Int8Array(r),a=new t.Int16Array(r),n=new t.Int32Array(r),c=new t.Uint8Array(r),f=new t.Uint16Array(r),p=new t.Uint32Array(r),h=new t.Float32Array(r),E=new t.Float64Array(r),C=e.DYNAMICTOP_PTR|0,S=e.tempDoublePtr|0,P=e.ABORT|0,I=e.STACKTOP|0,R=e.STACK_MAX|0,N=e.cttz_i8|0,U=e.___dso_handle|0,W=0,te=0,ie=0,Ae=0,ce=t.NaN,me=t.Infinity,pe=0,Be=0,Ce=0,g=0,we=0,Ee=0,fe=t.Math.floor,se=t.Math.abs,X=t.Math.sqrt,De=t.Math.pow,Re=t.Math.cos,gt=t.Math.sin,j=t.Math.tan,rt=t.Math.acos,Fe=t.Math.asin,Ne=t.Math.atan,Pe=t.Math.atan2,Ye=t.Math.exp,ke=t.Math.log,it=t.Math.ceil,_e=t.Math.imul,x=t.Math.min,w=t.Math.max,b=t.Math.clz32,y=t.Math.fround,F=e.abort,z=e.assert,Z=e.enlargeMemory,$=e.getTotalMemory,oe=e.abortOnCannotGrowMemory,xe=e.invoke_viiiii,Te=e.invoke_vif,lt=e.invoke_vid,Et=e.invoke_fiff,qt=e.invoke_vi,ir=e.invoke_vii,Pt=e.invoke_ii,gn=e.invoke_viddi,Pr=e.invoke_vidd,Ir=e.invoke_iiii,Nr=e.invoke_diii,nn=e.invoke_di,oi=e.invoke_iid,wo=e.invoke_iii,rs=e.invoke_viiddi,eo=e.invoke_viiiiii,Bo=e.invoke_dii,Hi=e.invoke_i,to=e.invoke_iiiiii,vo=e.invoke_viiid,RA=e.invoke_viififi,pf=e.invoke_viii,Eh=e.invoke_v,Ih=e.invoke_viid,ro=e.invoke_idd,jn=e.invoke_viiii,Rs=e._emscripten_asm_const_iiiii,no=e._emscripten_asm_const_iiidddddd,lu=e._emscripten_asm_const_iiiid,cu=e.__nbind_reference_external,uu=e._emscripten_asm_const_iiiiiiii,FA=e._removeAccessorPrefix,NA=e._typeModule,aa=e.__nbind_register_pool,la=e.__decorate,OA=e._llvm_stackrestore,gr=e.___cxa_atexit,So=e.__extends,Me=e.__nbind_get_value_object,fu=e.__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj,Cr=e._emscripten_set_main_loop_timing,hf=e.__nbind_register_primitive,LA=e.__nbind_register_type,MA=e._emscripten_memcpy_big,Au=e.__nbind_register_function,pu=e.___setErrNo,ac=e.__nbind_register_class,ve=e.__nbind_finish,Nt=e._abort,lc=e._nbind_value,Ni=e._llvm_stacksave,io=e.___syscall54,Rt=e._defineHidden,xn=e._emscripten_set_main_loop,ca=e._emscripten_get_now,ji=e.__nbind_register_callback_signature,Oi=e._emscripten_asm_const_iiiiii,Oa=e.__nbind_free_external,dn=e._emscripten_asm_const_iiii,Jn=e._emscripten_asm_const_iiididi,hu=e.___syscall6,Ch=e._atexit,La=e.___syscall140,Ma=e.___syscall146,Ua=y(0);let Xe=y(0);function Ha(o){o=o|0;var l=0;return l=I,I=I+o|0,I=I+15&-16,l|0}function gf(){return I|0}function cc(o){o=o|0,I=o}function wn(o,l){o=o|0,l=l|0,I=o,R=l}function ua(o,l){o=o|0,l=l|0,W||(W=o,te=l)}function _A(o){o=o|0,Ee=o}function UA(){return Ee|0}function fa(){var o=0,l=0;Qr(8104,8,400)|0,Qr(8504,408,540)|0,o=9044,l=o+44|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));s[9088]=0,s[9089]=1,n[2273]=0,n[2274]=948,n[2275]=948,gr(17,8104,U|0)|0}function vl(o){o=o|0,ht(o+948|0)}function Mt(o){return o=y(o),((SP(o)|0)&2147483647)>>>0>2139095040|0}function kn(o,l,u){o=o|0,l=l|0,u=u|0;e:do if(n[o+(l<<3)+4>>2]|0)o=o+(l<<3)|0;else{if((l|2|0)==3&&n[o+60>>2]|0){o=o+56|0;break}switch(l|0){case 0:case 2:case 4:case 5:{if(n[o+52>>2]|0){o=o+48|0;break e}break}default:}if(n[o+68>>2]|0){o=o+64|0;break}else{o=(l|1|0)==5?948:u;break}}while(!1);return o|0}function Aa(o){o=o|0;var l=0;return l=ex(1e3)|0,ja(o,(l|0)!=0,2456),n[2276]=(n[2276]|0)+1,Qr(l|0,8104,1e3)|0,s[o+2>>0]|0&&(n[l+4>>2]=2,n[l+12>>2]=4),n[l+976>>2]=o,l|0}function ja(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,Yg(o,5,3197,A)),I=d}function ns(){return Aa(956)|0}function uc(o){o=o|0;var l=0;return l=Jt(1e3)|0,gu(l,o),ja(n[o+976>>2]|0,1,2456),n[2276]=(n[2276]|0)+1,n[l+944>>2]=0,l|0}function gu(o,l){o=o|0,l=l|0;var u=0;Qr(o|0,l|0,948)|0,Dy(o+948|0,l+948|0),u=o+960|0,o=l+960|0,l=u+40|0;do n[u>>2]=n[o>>2],u=u+4|0,o=o+4|0;while((u|0)<(l|0))}function fc(o){o=o|0;var l=0,u=0,A=0,d=0;if(l=o+944|0,u=n[l>>2]|0,u|0&&(qa(u+948|0,o)|0,n[l>>2]=0),u=Li(o)|0,u|0){l=0;do n[(Cs(o,l)|0)+944>>2]=0,l=l+1|0;while((l|0)!=(u|0))}u=o+948|0,A=n[u>>2]|0,d=o+952|0,l=n[d>>2]|0,(l|0)!=(A|0)&&(n[d>>2]=l+(~((l+-4-A|0)>>>2)<<2)),Sl(u),tx(o),n[2276]=(n[2276]|0)+-1}function qa(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0;A=n[o>>2]|0,k=o+4|0,u=n[k>>2]|0,m=u;e:do if((A|0)==(u|0))d=A,B=4;else for(o=A;;){if((n[o>>2]|0)==(l|0)){d=o,B=4;break e}if(o=o+4|0,(o|0)==(u|0)){o=0;break}}while(!1);return(B|0)==4&&((d|0)!=(u|0)?(A=d+4|0,o=m-A|0,l=o>>2,l&&(F2(d|0,A|0,o|0)|0,u=n[k>>2]|0),o=d+(l<<2)|0,(u|0)==(o|0)||(n[k>>2]=u+(~((u+-4-o|0)>>>2)<<2)),o=1):o=0),o|0}function Li(o){return o=o|0,(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2|0}function Cs(o,l){o=o|0,l=l|0;var u=0;return u=n[o+948>>2]|0,(n[o+952>>2]|0)-u>>2>>>0>l>>>0?o=n[u+(l<<2)>>2]|0:o=0,o|0}function Sl(o){o=o|0;var l=0,u=0,A=0,d=0;A=I,I=I+32|0,l=A,d=n[o>>2]|0,u=(n[o+4>>2]|0)-d|0,((n[o+8>>2]|0)-d|0)>>>0>u>>>0&&(d=u>>2,ky(l,d,d,o+8|0),DP(o,l),Qy(l)),I=A}function df(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;_=Li(o)|0;do if(_|0){if((n[(Cs(o,0)|0)+944>>2]|0)==(o|0)){if(!(qa(o+948|0,l)|0))break;Qr(l+400|0,8504,540)|0,n[l+944>>2]=0,Oe(o);break}B=n[(n[o+976>>2]|0)+12>>2]|0,k=o+948|0,T=(B|0)==0,u=0,m=0;do A=n[(n[k>>2]|0)+(m<<2)>>2]|0,(A|0)==(l|0)?Oe(o):(d=uc(A)|0,n[(n[k>>2]|0)+(u<<2)>>2]=d,n[d+944>>2]=o,T||S_[B&15](A,d,o,u),u=u+1|0),m=m+1|0;while((m|0)!=(_|0));if(u>>>0<_>>>0){T=o+948|0,k=o+952|0,B=u,u=n[k>>2]|0;do m=(n[T>>2]|0)+(B<<2)|0,A=m+4|0,d=u-A|0,l=d>>2,l&&(F2(m|0,A|0,d|0)|0,u=n[k>>2]|0),d=u,A=m+(l<<2)|0,(d|0)!=(A|0)&&(u=d+(~((d+-4-A|0)>>>2)<<2)|0,n[k>>2]=u),B=B+1|0;while((B|0)!=(_|0))}}while(!1)}function Ac(o){o=o|0;var l=0,u=0,A=0,d=0;wi(o,(Li(o)|0)==0,2491),wi(o,(n[o+944>>2]|0)==0,2545),l=o+948|0,u=n[l>>2]|0,A=o+952|0,d=n[A>>2]|0,(d|0)!=(u|0)&&(n[A>>2]=d+(~((d+-4-u|0)>>>2)<<2)),Sl(l),l=o+976|0,u=n[l>>2]|0,Qr(o|0,8104,1e3)|0,s[u+2>>0]|0&&(n[o+4>>2]=2,n[o+12>>2]=4),n[l>>2]=u}function wi(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;d=I,I=I+16|0,A=d,l||(n[A>>2]=u,Qo(o,5,3197,A)),I=d}function Qn(){return n[2276]|0}function pc(){var o=0;return o=ex(20)|0,Je((o|0)!=0,2592),n[2277]=(n[2277]|0)+1,n[o>>2]=n[239],n[o+4>>2]=n[240],n[o+8>>2]=n[241],n[o+12>>2]=n[242],n[o+16>>2]=n[243],o|0}function Je(o,l){o=o|0,l=l|0;var u=0,A=0;A=I,I=I+16|0,u=A,o||(n[u>>2]=l,Qo(0,5,3197,u)),I=A}function st(o){o=o|0,tx(o),n[2277]=(n[2277]|0)+-1}function St(o,l){o=o|0,l=l|0;var u=0;l?(wi(o,(Li(o)|0)==0,2629),u=1):(u=0,l=0),n[o+964>>2]=l,n[o+988>>2]=u}function lr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+8|0,d=A+4|0,B=A,n[d>>2]=l,wi(o,(n[l+944>>2]|0)==0,2709),wi(o,(n[o+964>>2]|0)==0,2763),ee(o),l=o+948|0,n[B>>2]=(n[l>>2]|0)+(u<<2),n[m>>2]=n[B>>2],Ie(l,m,d)|0,n[(n[d>>2]|0)+944>>2]=o,Oe(o),I=A}function ee(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;if(u=Li(o)|0,u|0&&(n[(Cs(o,0)|0)+944>>2]|0)!=(o|0)){A=n[(n[o+976>>2]|0)+12>>2]|0,d=o+948|0,m=(A|0)==0,l=0;do B=n[(n[d>>2]|0)+(l<<2)>>2]|0,k=uc(B)|0,n[(n[d>>2]|0)+(l<<2)>>2]=k,n[k+944>>2]=o,m||S_[A&15](B,k,o,l),l=l+1|0;while((l|0)!=(u|0))}}function Ie(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0;tt=I,I=I+64|0,G=tt+52|0,k=tt+48|0,ae=tt+28|0,We=tt+24|0,Le=tt+20|0,Qe=tt,A=n[o>>2]|0,m=A,l=A+((n[l>>2]|0)-m>>2<<2)|0,A=o+4|0,d=n[A>>2]|0,B=o+8|0;do if(d>>>0<(n[B>>2]|0)>>>0){if((l|0)==(d|0)){n[l>>2]=n[u>>2],n[A>>2]=(n[A>>2]|0)+4;break}bP(o,l,d,l+4|0),l>>>0<=u>>>0&&(u=(n[A>>2]|0)>>>0>u>>>0?u+4|0:u),n[l>>2]=n[u>>2]}else{A=(d-m>>2)+1|0,d=O(o)|0,d>>>0>>0&&sn(o),M=n[o>>2]|0,_=(n[B>>2]|0)-M|0,m=_>>1,ky(Qe,_>>2>>>0>>1>>>0?m>>>0>>0?A:m:d,l-M>>2,o+8|0),M=Qe+8|0,A=n[M>>2]|0,m=Qe+12|0,_=n[m>>2]|0,B=_,T=A;do if((A|0)==(_|0)){if(_=Qe+4|0,A=n[_>>2]|0,Ze=n[Qe>>2]|0,d=Ze,A>>>0<=Ze>>>0){A=B-d>>1,A=A|0?A:1,ky(ae,A,A>>>2,n[Qe+16>>2]|0),n[We>>2]=n[_>>2],n[Le>>2]=n[M>>2],n[k>>2]=n[We>>2],n[G>>2]=n[Le>>2],c2(ae,k,G),A=n[Qe>>2]|0,n[Qe>>2]=n[ae>>2],n[ae>>2]=A,A=ae+4|0,Ze=n[_>>2]|0,n[_>>2]=n[A>>2],n[A>>2]=Ze,A=ae+8|0,Ze=n[M>>2]|0,n[M>>2]=n[A>>2],n[A>>2]=Ze,A=ae+12|0,Ze=n[m>>2]|0,n[m>>2]=n[A>>2],n[A>>2]=Ze,Qy(ae),A=n[M>>2]|0;break}m=A,B=((m-d>>2)+1|0)/-2|0,k=A+(B<<2)|0,d=T-m|0,m=d>>2,m&&(F2(k|0,A|0,d|0)|0,A=n[_>>2]|0),Ze=k+(m<<2)|0,n[M>>2]=Ze,n[_>>2]=A+(B<<2),A=Ze}while(!1);n[A>>2]=n[u>>2],n[M>>2]=(n[M>>2]|0)+4,l=PP(o,Qe,l)|0,Qy(Qe)}while(!1);return I=tt,l|0}function Oe(o){o=o|0;var l=0;do{if(l=o+984|0,s[l>>0]|0)break;s[l>>0]=1,h[o+504>>2]=y(ce),o=n[o+944>>2]|0}while(o|0)}function ht(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),yt(u))}function mt(o){return o=o|0,n[o+944>>2]|0}function Dt(o){o=o|0,wi(o,(n[o+964>>2]|0)!=0,2832),Oe(o)}function tr(o){return o=o|0,(s[o+984>>0]|0)!=0|0}function fn(o,l){o=o|0,l=l|0,qYe(o,l,400)|0&&(Qr(o|0,l|0,400)|0,Oe(o))}function ai(o){o=o|0;var l=Xe;return l=y(h[o+44>>2]),o=Mt(l)|0,y(o?y(0):l)}function qi(o){o=o|0;var l=Xe;return l=y(h[o+48>>2]),Mt(l)|0&&(l=s[(n[o+976>>2]|0)+2>>0]|0?y(1):y(0)),y(l)}function Tn(o,l){o=o|0,l=l|0,n[o+980>>2]=l}function Ga(o){return o=o|0,n[o+980>>2]|0}function my(o,l){o=o|0,l=l|0;var u=0;u=o+4|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function t2(o){return o=o|0,n[o+4>>2]|0}function Do(o,l){o=o|0,l=l|0;var u=0;u=o+8|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function yy(o){return o=o|0,n[o+8>>2]|0}function wh(o,l){o=o|0,l=l|0;var u=0;u=o+12|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function r2(o){return o=o|0,n[o+12>>2]|0}function bo(o,l){o=o|0,l=l|0;var u=0;u=o+16|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Bh(o){return o=o|0,n[o+16>>2]|0}function vh(o,l){o=o|0,l=l|0;var u=0;u=o+20|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function du(o){return o=o|0,n[o+20>>2]|0}function Sh(o,l){o=o|0,l=l|0;var u=0;u=o+24|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Ng(o){return o=o|0,n[o+24>>2]|0}function Og(o,l){o=o|0,l=l|0;var u=0;u=o+28|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Lg(o){return o=o|0,n[o+28>>2]|0}function Ey(o,l){o=o|0,l=l|0;var u=0;u=o+32|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function mf(o){return o=o|0,n[o+32>>2]|0}function Po(o,l){o=o|0,l=l|0;var u=0;u=o+36|0,(n[u>>2]|0)!=(l|0)&&(n[u>>2]=l,Oe(o))}function Dl(o){return o=o|0,n[o+36>>2]|0}function Dh(o,l){o=o|0,l=y(l);var u=0;u=o+40|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Mg(o,l){o=o|0,l=y(l);var u=0;u=o+44|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function bl(o,l){o=o|0,l=y(l);var u=0;u=o+48|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function Pl(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+52|0,d=o+56|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Iy(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+52|0,u=o+56|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function HA(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+52|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Cy(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function wy(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+132+(l<<3)|0,l=o+132+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function jA(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+132+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function qA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function Y(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+60+(l<<3)|0,l=o+60+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function xt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+60+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function GA(o,l){o=o|0,l=l|0;var u=0;u=o+60+(l<<3)+4|0,(n[u>>2]|0)!=3&&(h[o+60+(l<<3)>>2]=y(ce),n[u>>2]=3,Oe(o))}function xo(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function yf(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=m?0:2,d=o+204+(l<<3)|0,l=o+204+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function dt(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=l+204+(u<<3)|0,l=n[A+4>>2]|0,u=o,n[u>>2]=n[A>>2],n[u+4>>2]=l}function mu(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0,m=0;m=Mt(u)|0,A=(m^1)&1,d=o+276+(l<<3)|0,l=o+276+(l<<3)+4|0,m|y(h[d>>2])==u&&(n[l>>2]|0)==(A|0)||(h[d>>2]=u,n[l>>2]=A,Oe(o))}function By(o,l){return o=o|0,l=l|0,y(h[o+276+(l<<3)>>2])}function _g(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+348|0,d=o+352|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function n2(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+348|0,u=o+352|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function bh(o){o=o|0;var l=0;l=o+352|0,(n[l>>2]|0)!=3&&(h[o+348>>2]=y(ce),n[l>>2]=3,Oe(o))}function ur(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+348|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function zi(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+356|0,d=o+360|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ef(o,l){o=o|0,l=y(l);var u=0,A=0;A=o+356|0,u=o+360|0,y(h[A>>2])==l&&(n[u>>2]|0)==2||(h[A>>2]=l,A=Mt(l)|0,n[u>>2]=A?3:2,Oe(o))}function Wa(o){o=o|0;var l=0;l=o+360|0,(n[l>>2]|0)!=3&&(h[o+356>>2]=y(ce),n[l>>2]=3,Oe(o))}function Ug(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+356|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function yu(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function If(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+364|0,d=o+368|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function wt(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+364|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function gi(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function WA(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+372|0,d=o+376|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Ya(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+372|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function pa(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Va(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+380|0,d=o+384|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function Hg(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+380|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function Ph(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=(m^1)&1,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function jg(o,l){o=o|0,l=y(l);var u=0,A=0,d=0,m=0;m=Mt(l)|0,u=m?0:2,A=o+388|0,d=o+392|0,m|y(h[A>>2])==l&&(n[d>>2]|0)==(u|0)||(h[A>>2]=l,n[d>>2]=u,Oe(o))}function vy(o,l){o=o|0,l=l|0;var u=0,A=0;A=l+388|0,u=n[A+4>>2]|0,l=o,n[l>>2]=n[A>>2],n[l+4>>2]=u}function YA(o,l){o=o|0,l=y(l);var u=0;u=o+396|0,y(h[u>>2])!=l&&(h[u>>2]=l,Oe(o))}function qg(o){return o=o|0,y(h[o+396>>2])}function Eu(o){return o=o|0,y(h[o+400>>2])}function Iu(o){return o=o|0,y(h[o+404>>2])}function Cf(o){return o=o|0,y(h[o+408>>2])}function Fs(o){return o=o|0,y(h[o+412>>2])}function Cu(o){return o=o|0,y(h[o+416>>2])}function qn(o){return o=o|0,y(h[o+420>>2])}function is(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+424+(l<<2)>>2])}function xi(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+448+(l<<2)>>2])}function VA(o,l){switch(o=o|0,l=l|0,wi(o,(l|0)<6,2918),l|0){case 0:{l=(n[o+496>>2]|0)==2?5:4;break}case 2:{l=(n[o+496>>2]|0)==2?4:5;break}default:}return y(h[o+472+(l<<2)>>2])}function wf(o,l){o=o|0,l=l|0;var u=0,A=Xe;return u=n[o+4>>2]|0,(u|0)==(n[l+4>>2]|0)?u?(A=y(h[o>>2]),o=y(se(y(A-y(h[l>>2]))))>2]=0,n[A+4>>2]=0,n[A+8>>2]=0,fu(A|0,o|0,l|0,0),Qo(o,3,(s[A+11>>0]|0)<0?n[A>>2]|0:A,u),AVe(A),I=u}function ss(o,l,u,A){o=y(o),l=y(l),u=u|0,A=A|0;var d=Xe;o=y(o*l),d=y(E_(o,y(1)));do if(mn(d,y(0))|0)o=y(o-d);else{if(o=y(o-d),mn(d,y(1))|0){o=y(o+y(1));break}if(u){o=y(o+y(1));break}A||(d>y(.5)?d=y(1):(A=mn(d,y(.5))|0,d=y(A?1:0)),o=y(o+d))}while(!1);return y(o/l)}function xl(o,l,u,A,d,m,B,k,T,_,M,G,ae){o=o|0,l=y(l),u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,k=y(k),T=y(T),_=y(_),M=y(M),G=y(G),ae=ae|0;var We=0,Le=Xe,Qe=Xe,tt=Xe,Ze=Xe,ct=Xe,He=Xe;return T>2]),Le!=y(0))?(tt=y(ss(l,Le,0,0)),Ze=y(ss(A,Le,0,0)),Qe=y(ss(m,Le,0,0)),Le=y(ss(k,Le,0,0))):(Qe=m,tt=l,Le=k,Ze=A),(d|0)==(o|0)?We=mn(Qe,tt)|0:We=0,(B|0)==(u|0)?ae=mn(Le,Ze)|0:ae=0,!We&&(ct=y(l-M),!(ko(o,ct,T)|0))&&!(Bf(o,ct,d,T)|0)?We=vf(o,ct,d,m,T)|0:We=1,!ae&&(He=y(A-G),!(ko(u,He,_)|0))&&!(Bf(u,He,B,_)|0)?ae=vf(u,He,B,k,_)|0:ae=1,ae=We&ae),ae|0}function ko(o,l,u){return o=o|0,l=y(l),u=y(u),(o|0)==1?o=mn(l,u)|0:o=0,o|0}function Bf(o,l,u,A){return o=o|0,l=y(l),u=u|0,A=y(A),(o|0)==2&(u|0)==0?l>=A?o=1:o=mn(l,A)|0:o=0,o|0}function vf(o,l,u,A,d){return o=o|0,l=y(l),u=u|0,A=y(A),d=y(d),(o|0)==2&(u|0)==2&A>l?d<=l?o=1:o=mn(l,d)|0:o=0,o|0}function kl(o,l,u,A,d,m,B,k,T,_,M){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,_=_|0,M=M|0;var G=0,ae=0,We=0,Le=0,Qe=Xe,tt=Xe,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=Xe,Fo=Xe,No=Xe,Oo=0,$a=0;cr=I,I=I+160|0,$t=cr+152|0,fr=cr+120|0,qr=cr+104|0,He=cr+72|0,Le=cr+56|0,Lt=cr+8|0,ct=cr,Ge=(n[2279]|0)+1|0,n[2279]=Ge,Tr=o+984|0,s[Tr>>0]|0&&(n[o+512>>2]|0)!=(n[2278]|0)?Ze=4:(n[o+516>>2]|0)==(A|0)?Hr=0:Ze=4,(Ze|0)==4&&(n[o+520>>2]=0,n[o+924>>2]=-1,n[o+928>>2]=-1,h[o+932>>2]=y(-1),h[o+936>>2]=y(-1),Hr=1);e:do if(n[o+964>>2]|0)if(Qe=y(yn(o,2,B)),tt=y(yn(o,0,B)),G=o+916|0,No=y(h[G>>2]),Fo=y(h[o+920>>2]),Hn=y(h[o+932>>2]),xl(d,l,m,u,n[o+924>>2]|0,No,n[o+928>>2]|0,Fo,Hn,y(h[o+936>>2]),Qe,tt,M)|0)Ze=22;else if(We=n[o+520>>2]|0,!We)Ze=21;else for(ae=0;;){if(G=o+524+(ae*24|0)|0,Hn=y(h[G>>2]),Fo=y(h[o+524+(ae*24|0)+4>>2]),No=y(h[o+524+(ae*24|0)+16>>2]),xl(d,l,m,u,n[o+524+(ae*24|0)+8>>2]|0,Hn,n[o+524+(ae*24|0)+12>>2]|0,Fo,No,y(h[o+524+(ae*24|0)+20>>2]),Qe,tt,M)|0){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=We>>>0){Ze=21;break}}else{if(T){if(G=o+916|0,!(mn(y(h[G>>2]),l)|0)){Ze=21;break}if(!(mn(y(h[o+920>>2]),u)|0)){Ze=21;break}if((n[o+924>>2]|0)!=(d|0)){Ze=21;break}G=(n[o+928>>2]|0)==(m|0)?G:0,Ze=22;break}if(We=n[o+520>>2]|0,!We)Ze=21;else for(ae=0;;){if(G=o+524+(ae*24|0)|0,mn(y(h[G>>2]),l)|0&&mn(y(h[o+524+(ae*24|0)+4>>2]),u)|0&&(n[o+524+(ae*24|0)+8>>2]|0)==(d|0)&&(n[o+524+(ae*24|0)+12>>2]|0)==(m|0)){Ze=22;break e}if(ae=ae+1|0,ae>>>0>=We>>>0){Ze=21;break}}}while(!1);do if((Ze|0)==21)s[11697]|0?(G=0,Ze=28):(G=0,Ze=31);else if((Ze|0)==22){if(ae=(s[11697]|0)!=0,!((G|0)!=0&(Hr^1)))if(ae){Ze=28;break}else{Ze=31;break}Le=G+16|0,n[o+908>>2]=n[Le>>2],We=G+20|0,n[o+912>>2]=n[We>>2],(s[11698]|0)==0|ae^1||(n[ct>>2]=wu(Ge)|0,n[ct+4>>2]=Ge,Qo(o,4,2972,ct),ae=n[o+972>>2]|0,ae|0&&op[ae&127](o),d=ha(d,T)|0,m=ha(m,T)|0,$a=+y(h[Le>>2]),Oo=+y(h[We>>2]),n[Lt>>2]=d,n[Lt+4>>2]=m,E[Lt+8>>3]=+l,E[Lt+16>>3]=+u,E[Lt+24>>3]=$a,E[Lt+32>>3]=Oo,n[Lt+40>>2]=_,Qo(o,4,2989,Lt))}while(!1);return(Ze|0)==28&&(ae=wu(Ge)|0,n[Le>>2]=ae,n[Le+4>>2]=Ge,n[Le+8>>2]=Hr?3047:11699,Qo(o,4,3038,Le),ae=n[o+972>>2]|0,ae|0&&op[ae&127](o),Lt=ha(d,T)|0,Ze=ha(m,T)|0,n[He>>2]=Lt,n[He+4>>2]=Ze,E[He+8>>3]=+l,E[He+16>>3]=+u,n[He+24>>2]=_,Qo(o,4,3049,He),Ze=31),(Ze|0)==31&&(Ns(o,l,u,A,d,m,B,k,T,M),s[11697]|0&&(ae=n[2279]|0,Lt=wu(ae)|0,n[qr>>2]=Lt,n[qr+4>>2]=ae,n[qr+8>>2]=Hr?3047:11699,Qo(o,4,3083,qr),ae=n[o+972>>2]|0,ae|0&&op[ae&127](o),Lt=ha(d,T)|0,qr=ha(m,T)|0,Oo=+y(h[o+908>>2]),$a=+y(h[o+912>>2]),n[fr>>2]=Lt,n[fr+4>>2]=qr,E[fr+8>>3]=Oo,E[fr+16>>3]=$a,n[fr+24>>2]=_,Qo(o,4,3092,fr)),n[o+516>>2]=A,G||(ae=o+520|0,G=n[ae>>2]|0,(G|0)==16&&(s[11697]|0&&Qo(o,4,3124,$t),n[ae>>2]=0,G=0),T?G=o+916|0:(n[ae>>2]=G+1,G=o+524+(G*24|0)|0),h[G>>2]=l,h[G+4>>2]=u,n[G+8>>2]=d,n[G+12>>2]=m,n[G+16>>2]=n[o+908>>2],n[G+20>>2]=n[o+912>>2],G=0)),T&&(n[o+416>>2]=n[o+908>>2],n[o+420>>2]=n[o+912>>2],s[o+985>>0]=1,s[Tr>>0]=0),n[2279]=(n[2279]|0)+-1,n[o+512>>2]=n[2278],I=cr,Hr|(G|0)==0|0}function yn(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return A=y(J(o,l,u)),y(A+y(re(o,l,u)))}function Qo(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=I,I=I+16|0,d=m,n[d>>2]=A,o?A=n[o+976>>2]|0:A=0,Qh(A,o,l,u,d),I=m}function wu(o){return o=o|0,(o>>>0>60?3201:3201+(60-o)|0)|0}function ha(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+32|0,u=d+12|0,A=d,n[u>>2]=n[254],n[u+4>>2]=n[255],n[u+8>>2]=n[256],n[A>>2]=n[257],n[A+4>>2]=n[258],n[A+8>>2]=n[259],(o|0)>2?o=11699:o=n[(l?A:u)+(o<<2)>>2]|0,I=d,o|0}function Ns(o,l,u,A,d,m,B,k,T,_){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=m|0,B=y(B),k=y(k),T=T|0,_=_|0;var M=0,G=0,ae=0,We=0,Le=Xe,Qe=Xe,tt=Xe,Ze=Xe,ct=Xe,He=Xe,Ge=Xe,Lt=0,qr=0,fr=0,$t=Xe,Tr=Xe,Hr=0,cr=Xe,Hn=0,Fo=0,No=0,Oo=0,$a=0,Kh=0,Jh=0,dc=0,zh=0,Ff=0,Nf=0,Zh=0,Xh=0,$h=0,on=0,mc=0,e0=0,ku=0,t0=Xe,r0=Xe,Of=Xe,Lf=Xe,Qu=Xe,ao=0,Ml=0,ya=0,yc=0,lp=0,cp=Xe,Mf=Xe,up=Xe,fp=Xe,lo=Xe,_s=Xe,Ec=0,Wn=Xe,Ap=Xe,Lo=Xe,Tu=Xe,Mo=Xe,Ru=Xe,pp=0,hp=0,Fu=Xe,co=Xe,Ic=0,gp=0,dp=0,mp=0,Fr=Xe,ci=0,Us=0,_o=0,uo=0,Mr=0,Ar=0,Cc=0,zt=Xe,yp=0,Bi=0;Cc=I,I=I+16|0,ao=Cc+12|0,Ml=Cc+8|0,ya=Cc+4|0,yc=Cc,wi(o,(d|0)==0|(Mt(l)|0)^1,3326),wi(o,(m|0)==0|(Mt(u)|0)^1,3406),Us=ft(o,A)|0,n[o+496>>2]=Us,Mr=dr(2,Us)|0,Ar=dr(0,Us)|0,h[o+440>>2]=y(J(o,Mr,B)),h[o+444>>2]=y(re(o,Mr,B)),h[o+428>>2]=y(J(o,Ar,B)),h[o+436>>2]=y(re(o,Ar,B)),h[o+464>>2]=y(Br(o,Mr)),h[o+468>>2]=y(_n(o,Mr)),h[o+452>>2]=y(Br(o,Ar)),h[o+460>>2]=y(_n(o,Ar)),h[o+488>>2]=y(di(o,Mr,B)),h[o+492>>2]=y(ws(o,Mr,B)),h[o+476>>2]=y(di(o,Ar,B)),h[o+484>>2]=y(ws(o,Ar,B));do if(n[o+964>>2]|0)zA(o,l,u,d,m,B,k);else{if(_o=o+948|0,uo=(n[o+952>>2]|0)-(n[_o>>2]|0)>>2,!uo){wP(o,l,u,d,m,B,k);break}if(!T&&i2(o,l,u,d,m,B,k)|0)break;ee(o),mc=o+508|0,s[mc>>0]=0,Mr=dr(n[o+4>>2]|0,Us)|0,Ar=by(Mr,Us)|0,ci=de(Mr)|0,e0=n[o+8>>2]|0,gp=o+28|0,ku=(n[gp>>2]|0)!=0,Mo=ci?B:k,Fu=ci?k:B,t0=y(Rh(o,Mr,B)),r0=y(s2(o,Mr,B)),Le=y(Rh(o,Ar,B)),Ru=y(Ka(o,Mr,B)),co=y(Ka(o,Ar,B)),fr=ci?d:m,Ic=ci?m:d,Fr=ci?Ru:co,ct=ci?co:Ru,Tu=y(yn(o,2,B)),Ze=y(yn(o,0,B)),Qe=y(y(Xr(o+364|0,B))-Fr),tt=y(y(Xr(o+380|0,B))-Fr),He=y(y(Xr(o+372|0,k))-ct),Ge=y(y(Xr(o+388|0,k))-ct),Of=ci?Qe:He,Lf=ci?tt:Ge,Tu=y(l-Tu),l=y(Tu-Fr),Mt(l)|0?Fr=l:Fr=y($n(y(hd(l,tt)),Qe)),Ap=y(u-Ze),l=y(Ap-ct),Mt(l)|0?Lo=l:Lo=y($n(y(hd(l,Ge)),He)),Qe=ci?Fr:Lo,Wn=ci?Lo:Fr;e:do if((fr|0)==1)for(A=0,G=0;;){if(M=Cs(o,G)|0,!A)y(ZA(M))>y(0)&&y(Fh(M))>y(0)?A=M:A=0;else if(o2(M)|0){We=0;break e}if(G=G+1|0,G>>>0>=uo>>>0){We=A;break}}else We=0;while(!1);Lt=We+500|0,qr=We+504|0,A=0,M=0,l=y(0),ae=0;do{if(G=n[(n[_o>>2]|0)+(ae<<2)>>2]|0,(n[G+36>>2]|0)==1)Py(G),s[G+985>>0]=1,s[G+984>>0]=0;else{Sf(G),T&&kh(G,ft(G,Us)|0,Qe,Wn,Fr);do if((n[G+24>>2]|0)!=1)if((G|0)==(We|0)){n[Lt>>2]=n[2278],h[qr>>2]=y(0);break}else{BP(o,G,Fr,d,Lo,Fr,Lo,m,Us,_);break}else M|0&&(n[M+960>>2]=G),n[G+960>>2]=0,M=G,A=A|0?A:G;while(!1);_s=y(h[G+504>>2]),l=y(l+y(_s+y(yn(G,Mr,Fr))))}ae=ae+1|0}while((ae|0)!=(uo|0));for(No=l>Qe,Ec=ku&((fr|0)==2&No)?1:fr,Hn=(Ic|0)==1,$a=Hn&(T^1),Kh=(Ec|0)==1,Jh=(Ec|0)==2,dc=976+(Mr<<2)|0,zh=(Ic|2|0)==2,$h=Hn&(ku^1),Ff=1040+(Ar<<2)|0,Nf=1040+(Mr<<2)|0,Zh=976+(Ar<<2)|0,Xh=(Ic|0)!=1,No=ku&((fr|0)!=0&No),Fo=o+976|0,Hn=Hn^1,l=Qe,Hr=0,Oo=0,_s=y(0),Qu=y(0);;){e:do if(Hr>>>0>>0)for(qr=n[_o>>2]|0,ae=0,Ge=y(0),He=y(0),tt=y(0),Qe=y(0),G=0,M=0,We=Hr;;){if(Lt=n[qr+(We<<2)>>2]|0,(n[Lt+36>>2]|0)!=1&&(n[Lt+940>>2]=Oo,(n[Lt+24>>2]|0)!=1)){if(Ze=y(yn(Lt,Mr,Fr)),on=n[dc>>2]|0,u=y(Xr(Lt+380+(on<<3)|0,Mo)),ct=y(h[Lt+504>>2]),u=y(hd(u,ct)),u=y($n(y(Xr(Lt+364+(on<<3)|0,Mo)),u)),ku&(ae|0)!=0&y(Ze+y(He+u))>l){m=ae,Ze=Ge,fr=We;break e}Ze=y(Ze+u),u=y(He+Ze),Ze=y(Ge+Ze),o2(Lt)|0&&(tt=y(tt+y(ZA(Lt))),Qe=y(Qe-y(ct*y(Fh(Lt))))),M|0&&(n[M+960>>2]=Lt),n[Lt+960>>2]=0,ae=ae+1|0,M=Lt,G=G|0?G:Lt}else Ze=Ge,u=He;if(We=We+1|0,We>>>0>>0)Ge=Ze,He=u;else{m=ae,fr=We;break}}else m=0,Ze=y(0),tt=y(0),Qe=y(0),G=0,fr=Hr;while(!1);on=tt>y(0)&tty(0)&QeLf&((Mt(Lf)|0)^1))l=Lf,on=51;else if(s[(n[Fo>>2]|0)+3>>0]|0)on=51;else{if($t!=y(0)&&y(ZA(o))!=y(0)){on=53;break}l=Ze,on=53}while(!1);if((on|0)==51&&(on=0,Mt(l)|0?on=53:(Tr=y(l-Ze),cr=l)),(on|0)==53&&(on=0,Ze>2]|0,We=Try(0),He=y(Tr/$t),tt=y(0),Ze=y(0),l=y(0),M=G;do u=y(Xr(M+380+(ae<<3)|0,Mo)),Qe=y(Xr(M+364+(ae<<3)|0,Mo)),Qe=y(hd(u,y($n(Qe,y(h[M+504>>2]))))),We?(u=y(Qe*y(Fh(M))),u!=y(-0)&&(zt=y(Qe-y(ct*u)),cp=y(Gn(M,Mr,zt,cr,Fr)),zt!=cp)&&(tt=y(tt-y(cp-Qe)),l=y(l+u))):Lt&&(Mf=y(ZA(M)),Mf!=y(0))&&(zt=y(Qe+y(He*Mf)),up=y(Gn(M,Mr,zt,cr,Fr)),zt!=up)&&(tt=y(tt-y(up-Qe)),Ze=y(Ze-Mf)),M=n[M+960>>2]|0;while(M|0);if(l=y(Ge+l),Qe=y(Tr+tt),lp)l=y(0);else{ct=y($t+Ze),We=n[dc>>2]|0,Lt=Qey(0),ct=y(Qe/ct),l=y(0);do{zt=y(Xr(G+380+(We<<3)|0,Mo)),tt=y(Xr(G+364+(We<<3)|0,Mo)),tt=y(hd(zt,y($n(tt,y(h[G+504>>2]))))),Lt?(zt=y(tt*y(Fh(G))),Qe=y(-zt),zt!=y(-0)?(zt=y(He*Qe),Qe=y(Gn(G,Mr,y(tt+(qr?Qe:zt)),cr,Fr))):Qe=tt):ae&&(fp=y(ZA(G)),fp!=y(0))?Qe=y(Gn(G,Mr,y(tt+y(ct*fp)),cr,Fr)):Qe=tt,l=y(l-y(Qe-tt)),Ze=y(yn(G,Mr,Fr)),u=y(yn(G,Ar,Fr)),Qe=y(Qe+Ze),h[Ml>>2]=Qe,n[yc>>2]=1,tt=y(h[G+396>>2]);e:do if(Mt(tt)|0){M=Mt(Wn)|0;do if(!M){if(No|(so(G,Ar,Wn)|0|Hn)||(os(o,G)|0)!=4||(n[(Ql(G,Ar)|0)+4>>2]|0)==3||(n[(Tl(G,Ar)|0)+4>>2]|0)==3)break;h[ao>>2]=Wn,n[ya>>2]=1;break e}while(!1);if(so(G,Ar,Wn)|0){M=n[G+992+(n[Zh>>2]<<2)>>2]|0,zt=y(u+y(Xr(M,Wn))),h[ao>>2]=zt,M=Xh&(n[M+4>>2]|0)==2,n[ya>>2]=((Mt(zt)|0|M)^1)&1;break}else{h[ao>>2]=Wn,n[ya>>2]=M?0:2;break}}else zt=y(Qe-Ze),$t=y(zt/tt),zt=y(tt*zt),n[ya>>2]=1,h[ao>>2]=y(u+(ci?$t:zt));while(!1);Bu(G,Mr,cr,Fr,yc,Ml),Bu(G,Ar,Wn,Fr,ya,ao);do if(!(so(G,Ar,Wn)|0)&&(os(o,G)|0)==4){if((n[(Ql(G,Ar)|0)+4>>2]|0)==3){M=0;break}M=(n[(Tl(G,Ar)|0)+4>>2]|0)!=3}else M=0;while(!1);zt=y(h[Ml>>2]),$t=y(h[ao>>2]),yp=n[yc>>2]|0,Bi=n[ya>>2]|0,kl(G,ci?zt:$t,ci?$t:zt,Us,ci?yp:Bi,ci?Bi:yp,Fr,Lo,T&(M^1),3488,_)|0,s[mc>>0]=s[mc>>0]|s[G+508>>0],G=n[G+960>>2]|0}while(G|0)}}else l=y(0);if(l=y(Tr+l),Bi=l>0]=Bi|c[mc>>0],Jh&l>y(0)?(M=n[dc>>2]|0,n[o+364+(M<<3)+4>>2]|0&&(lo=y(Xr(o+364+(M<<3)|0,Mo)),lo>=y(0))?Qe=y($n(y(0),y(lo-y(cr-l)))):Qe=y(0)):Qe=l,Lt=Hr>>>0>>0,Lt){We=n[_o>>2]|0,ae=Hr,M=0;do G=n[We+(ae<<2)>>2]|0,n[G+24>>2]|0||(M=((n[(Ql(G,Mr)|0)+4>>2]|0)==3&1)+M|0,M=M+((n[(Tl(G,Mr)|0)+4>>2]|0)==3&1)|0),ae=ae+1|0;while((ae|0)!=(fr|0));M?(Ze=y(0),u=y(0)):on=101}else on=101;e:do if((on|0)==101)switch(on=0,e0|0){case 1:{M=0,Ze=y(Qe*y(.5)),u=y(0);break e}case 2:{M=0,Ze=Qe,u=y(0);break e}case 3:{if(m>>>0<=1){M=0,Ze=y(0),u=y(0);break e}u=y((m+-1|0)>>>0),M=0,Ze=y(0),u=y(y($n(Qe,y(0)))/u);break e}case 5:{u=y(Qe/y((m+1|0)>>>0)),M=0,Ze=u;break e}case 4:{u=y(Qe/y(m>>>0)),M=0,Ze=y(u*y(.5));break e}default:{M=0,Ze=y(0),u=y(0);break e}}while(!1);if(l=y(t0+Ze),Lt){tt=y(Qe/y(M|0)),ae=n[_o>>2]|0,G=Hr,Qe=y(0);do{M=n[ae+(G<<2)>>2]|0;e:do if((n[M+36>>2]|0)!=1){switch(n[M+24>>2]|0){case 1:{if(ga(M,Mr)|0){if(!T)break e;zt=y(XA(M,Mr,cr)),zt=y(zt+y(Br(o,Mr))),zt=y(zt+y(J(M,Mr,Fr))),h[M+400+(n[Nf>>2]<<2)>>2]=zt;break e}break}case 0:if(Bi=(n[(Ql(M,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=Bi?zt:l,T&&(Bi=M+400+(n[Nf>>2]<<2)|0,h[Bi>>2]=y(l+y(h[Bi>>2]))),Bi=(n[(Tl(M,Mr)|0)+4>>2]|0)==3,zt=y(tt+l),l=Bi?zt:l,$a){zt=y(u+y(yn(M,Mr,Fr))),Qe=Wn,l=y(l+y(zt+y(h[M+504>>2])));break e}else{l=y(l+y(u+y($A(M,Mr,Fr)))),Qe=y($n(Qe,y($A(M,Ar,Fr))));break e}default:}T&&(zt=y(Ze+y(Br(o,Mr))),Bi=M+400+(n[Nf>>2]<<2)|0,h[Bi>>2]=y(zt+y(h[Bi>>2])))}while(!1);G=G+1|0}while((G|0)!=(fr|0))}else Qe=y(0);if(u=y(r0+l),zh?Ze=y(y(Gn(o,Ar,y(co+Qe),Fu,B))-co):Ze=Wn,tt=y(y(Gn(o,Ar,y(co+($h?Wn:Qe)),Fu,B))-co),Lt&T){G=Hr;do{ae=n[(n[_o>>2]|0)+(G<<2)>>2]|0;do if((n[ae+36>>2]|0)!=1){if((n[ae+24>>2]|0)==1){if(ga(ae,Ar)|0){if(zt=y(XA(ae,Ar,Wn)),zt=y(zt+y(Br(o,Ar))),zt=y(zt+y(J(ae,Ar,Fr))),M=n[Ff>>2]|0,h[ae+400+(M<<2)>>2]=zt,!(Mt(zt)|0))break}else M=n[Ff>>2]|0;zt=y(Br(o,Ar)),h[ae+400+(M<<2)>>2]=y(zt+y(J(ae,Ar,Fr)));break}M=os(o,ae)|0;do if((M|0)==4){if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){on=139;break}if((n[(Tl(ae,Ar)|0)+4>>2]|0)==3){on=139;break}if(so(ae,Ar,Wn)|0){l=Le;break}yp=n[ae+908+(n[dc>>2]<<2)>>2]|0,n[ao>>2]=yp,l=y(h[ae+396>>2]),Bi=Mt(l)|0,Qe=(n[S>>2]=yp,y(h[S>>2])),Bi?l=tt:(Tr=y(yn(ae,Ar,Fr)),zt=y(Qe/l),l=y(l*Qe),l=y(Tr+(ci?zt:l))),h[Ml>>2]=l,h[ao>>2]=y(y(yn(ae,Mr,Fr))+Qe),n[ya>>2]=1,n[yc>>2]=1,Bu(ae,Mr,cr,Fr,ya,ao),Bu(ae,Ar,Wn,Fr,yc,Ml),l=y(h[ao>>2]),Tr=y(h[Ml>>2]),zt=ci?l:Tr,l=ci?Tr:l,Bi=((Mt(zt)|0)^1)&1,kl(ae,zt,l,Us,Bi,((Mt(l)|0)^1)&1,Fr,Lo,1,3493,_)|0,l=Le}else on=139;while(!1);e:do if((on|0)==139){on=0,l=y(Ze-y($A(ae,Ar,Fr)));do if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){if((n[(Tl(ae,Ar)|0)+4>>2]|0)!=3)break;l=y(Le+y($n(y(0),y(l*y(.5)))));break e}while(!1);if((n[(Tl(ae,Ar)|0)+4>>2]|0)==3){l=Le;break}if((n[(Ql(ae,Ar)|0)+4>>2]|0)==3){l=y(Le+y($n(y(0),l)));break}switch(M|0){case 1:{l=Le;break e}case 2:{l=y(Le+y(l*y(.5)));break e}default:{l=y(Le+l);break e}}}while(!1);zt=y(_s+l),Bi=ae+400+(n[Ff>>2]<<2)|0,h[Bi>>2]=y(zt+y(h[Bi>>2]))}while(!1);G=G+1|0}while((G|0)!=(fr|0))}if(_s=y(_s+tt),Qu=y($n(Qu,u)),m=Oo+1|0,fr>>>0>=uo>>>0)break;l=cr,Hr=fr,Oo=m}do if(T){if(M=m>>>0>1,!M&&!(XL(o)|0))break;if(!(Mt(Wn)|0)){l=y(Wn-_s);e:do switch(n[o+12>>2]|0){case 3:{Le=y(Le+l),He=y(0);break}case 2:{Le=y(Le+y(l*y(.5))),He=y(0);break}case 4:{Wn>_s?He=y(l/y(m>>>0)):He=y(0);break}case 7:if(Wn>_s){Le=y(Le+y(l/y(m<<1>>>0))),He=y(l/y(m>>>0)),He=M?He:y(0);break e}else{Le=y(Le+y(l*y(.5))),He=y(0);break e}case 6:{He=y(l/y(Oo>>>0)),He=Wn>_s&M?He:y(0);break}default:He=y(0)}while(!1);if(m|0)for(Lt=1040+(Ar<<2)|0,qr=976+(Ar<<2)|0,We=0,G=0;;){e:do if(G>>>0>>0)for(Qe=y(0),tt=y(0),l=y(0),ae=G;;){M=n[(n[_o>>2]|0)+(ae<<2)>>2]|0;do if((n[M+36>>2]|0)!=1&&!(n[M+24>>2]|0)){if((n[M+940>>2]|0)!=(We|0))break e;if($L(M,Ar)|0&&(zt=y(h[M+908+(n[qr>>2]<<2)>>2]),l=y($n(l,y(zt+y(yn(M,Ar,Fr)))))),(os(o,M)|0)!=5)break;lo=y(Vg(M)),lo=y(lo+y(J(M,0,Fr))),zt=y(h[M+912>>2]),zt=y(y(zt+y(yn(M,0,Fr)))-lo),lo=y($n(tt,lo)),zt=y($n(Qe,zt)),Qe=zt,tt=lo,l=y($n(l,y(lo+zt)))}while(!1);if(M=ae+1|0,M>>>0>>0)ae=M;else{ae=M;break}}else tt=y(0),l=y(0),ae=G;while(!1);if(ct=y(He+l),u=Le,Le=y(Le+ct),G>>>0>>0){Ze=y(u+tt),M=G;do{G=n[(n[_o>>2]|0)+(M<<2)>>2]|0;e:do if((n[G+36>>2]|0)!=1&&!(n[G+24>>2]|0))switch(os(o,G)|0){case 1:{zt=y(u+y(J(G,Ar,Fr))),h[G+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 3:{zt=y(y(Le-y(re(G,Ar,Fr)))-y(h[G+908+(n[qr>>2]<<2)>>2])),h[G+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 2:{zt=y(u+y(y(ct-y(h[G+908+(n[qr>>2]<<2)>>2]))*y(.5))),h[G+400+(n[Lt>>2]<<2)>>2]=zt;break e}case 4:{if(zt=y(u+y(J(G,Ar,Fr))),h[G+400+(n[Lt>>2]<<2)>>2]=zt,so(G,Ar,Wn)|0||(ci?(Qe=y(h[G+908>>2]),l=y(Qe+y(yn(G,Mr,Fr))),tt=ct):(tt=y(h[G+912>>2]),tt=y(tt+y(yn(G,Ar,Fr))),l=ct,Qe=y(h[G+908>>2])),mn(l,Qe)|0&&mn(tt,y(h[G+912>>2]))|0))break e;kl(G,l,tt,Us,1,1,Fr,Lo,1,3501,_)|0;break e}case 5:{h[G+404>>2]=y(y(Ze-y(Vg(G)))+y(XA(G,0,Wn)));break e}default:break e}while(!1);M=M+1|0}while((M|0)!=(ae|0))}if(We=We+1|0,(We|0)==(m|0))break;G=ae}}}while(!1);if(h[o+908>>2]=y(Gn(o,2,Tu,B,B)),h[o+912>>2]=y(Gn(o,0,Ap,k,B)),Ec|0&&(pp=n[o+32>>2]|0,hp=(Ec|0)==2,!(hp&(pp|0)!=2))?hp&(pp|0)==2&&(l=y(Ru+cr),l=y($n(y(hd(l,y(Kg(o,Mr,Qu,Mo)))),Ru)),on=198):(l=y(Gn(o,Mr,Qu,Mo,B)),on=198),(on|0)==198&&(h[o+908+(n[976+(Mr<<2)>>2]<<2)>>2]=l),Ic|0&&(dp=n[o+32>>2]|0,mp=(Ic|0)==2,!(mp&(dp|0)!=2))?mp&(dp|0)==2&&(l=y(co+Wn),l=y($n(y(hd(l,y(Kg(o,Ar,y(co+_s),Fu)))),co)),on=204):(l=y(Gn(o,Ar,y(co+_s),Fu,B)),on=204),(on|0)==204&&(h[o+908+(n[976+(Ar<<2)>>2]<<2)>>2]=l),T){if((n[gp>>2]|0)==2){G=976+(Ar<<2)|0,ae=1040+(Ar<<2)|0,M=0;do We=Cs(o,M)|0,n[We+24>>2]|0||(yp=n[G>>2]|0,zt=y(h[o+908+(yp<<2)>>2]),Bi=We+400+(n[ae>>2]<<2)|0,zt=y(zt-y(h[Bi>>2])),h[Bi>>2]=y(zt-y(h[We+908+(yp<<2)>>2]))),M=M+1|0;while((M|0)!=(uo|0))}if(A|0){M=ci?Ec:d;do eM(o,A,Fr,M,Lo,Us,_),A=n[A+960>>2]|0;while(A|0)}if(M=(Mr|2|0)==3,G=(Ar|2|0)==3,M|G){A=0;do ae=n[(n[_o>>2]|0)+(A<<2)>>2]|0,(n[ae+36>>2]|0)!=1&&(M&&a2(o,ae,Mr),G&&a2(o,ae,Ar)),A=A+1|0;while((A|0)!=(uo|0))}}}while(!1);I=Cc}function xh(o,l){o=o|0,l=y(l);var u=0;ja(o,l>=y(0),3147),u=l==y(0),h[o+4>>2]=u?y(0):l}function KA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=A|0;var d=Xe,m=Xe,B=0,k=0,T=0;n[2278]=(n[2278]|0)+1,Sf(o),so(o,2,l)|0?(d=y(Xr(n[o+992>>2]|0,l)),T=1,d=y(d+y(yn(o,2,l)))):(d=y(Xr(o+380|0,l)),d>=y(0)?T=2:(T=((Mt(l)|0)^1)&1,d=l)),so(o,0,u)|0?(m=y(Xr(n[o+996>>2]|0,u)),k=1,m=y(m+y(yn(o,0,l)))):(m=y(Xr(o+388|0,u)),m>=y(0)?k=2:(k=((Mt(u)|0)^1)&1,m=u)),B=o+976|0,kl(o,d,m,A,T,k,l,u,1,3189,n[B>>2]|0)|0&&(kh(o,n[o+496>>2]|0,l,u,l),JA(o,y(h[(n[B>>2]|0)+4>>2]),y(0),y(0)),s[11696]|0)&&Gg(o,7)}function Sf(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;k=I,I=I+32|0,B=k+24|0,m=k+16|0,A=k+8|0,d=k,u=0;do l=o+380+(u<<3)|0,n[o+380+(u<<3)+4>>2]|0&&(T=l,_=n[T+4>>2]|0,M=A,n[M>>2]=n[T>>2],n[M+4>>2]=_,M=o+364+(u<<3)|0,_=n[M+4>>2]|0,T=d,n[T>>2]=n[M>>2],n[T+4>>2]=_,n[m>>2]=n[A>>2],n[m+4>>2]=n[A+4>>2],n[B>>2]=n[d>>2],n[B+4>>2]=n[d+4>>2],wf(m,B)|0)||(l=o+348+(u<<3)|0),n[o+992+(u<<2)>>2]=l,u=u+1|0;while((u|0)!=2);I=k}function so(o,l,u){o=o|0,l=l|0,u=y(u);var A=0;switch(o=n[o+992+(n[976+(l<<2)>>2]<<2)>>2]|0,n[o+4>>2]|0){case 0:case 3:{o=0;break}case 1:{y(h[o>>2])>2])>2]|0){case 2:{l=y(y(y(h[o>>2])*l)/y(100));break}case 1:{l=y(h[o>>2]);break}default:l=y(ce)}return y(l)}function kh(o,l,u,A,d){o=o|0,l=l|0,u=y(u),A=y(A),d=y(d);var m=0,B=Xe;l=n[o+944>>2]|0?l:1,m=dr(n[o+4>>2]|0,l)|0,l=by(m,l)|0,u=y(vP(o,m,u)),A=y(vP(o,l,A)),B=y(u+y(J(o,m,d))),h[o+400+(n[1040+(m<<2)>>2]<<2)>>2]=B,u=y(u+y(re(o,m,d))),h[o+400+(n[1e3+(m<<2)>>2]<<2)>>2]=u,u=y(A+y(J(o,l,d))),h[o+400+(n[1040+(l<<2)>>2]<<2)>>2]=u,d=y(A+y(re(o,l,d))),h[o+400+(n[1e3+(l<<2)>>2]<<2)>>2]=d}function JA(o,l,u,A){o=o|0,l=y(l),u=y(u),A=y(A);var d=0,m=0,B=Xe,k=Xe,T=0,_=0,M=Xe,G=0,ae=Xe,We=Xe,Le=Xe,Qe=Xe;if(l!=y(0)&&(d=o+400|0,Qe=y(h[d>>2]),m=o+404|0,Le=y(h[m>>2]),G=o+416|0,We=y(h[G>>2]),_=o+420|0,B=y(h[_>>2]),ae=y(Qe+u),M=y(Le+A),A=y(ae+We),k=y(M+B),T=(n[o+988>>2]|0)==1,h[d>>2]=y(ss(Qe,l,0,T)),h[m>>2]=y(ss(Le,l,0,T)),u=y(E_(y(We*l),y(1))),mn(u,y(0))|0?m=0:m=(mn(u,y(1))|0)^1,u=y(E_(y(B*l),y(1))),mn(u,y(0))|0?d=0:d=(mn(u,y(1))|0)^1,Qe=y(ss(A,l,T&m,T&(m^1))),h[G>>2]=y(Qe-y(ss(ae,l,0,T))),Qe=y(ss(k,l,T&d,T&(d^1))),h[_>>2]=y(Qe-y(ss(M,l,0,T))),m=(n[o+952>>2]|0)-(n[o+948>>2]|0)>>2,m|0)){d=0;do JA(Cs(o,d)|0,l,ae,M),d=d+1|0;while((d|0)!=(m|0))}}function Sy(o,l,u,A,d){switch(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,u|0){case 5:case 0:{o=n$(n[489]|0,A,d)|0;break}default:o=lVe(A,d)|0}return o|0}function Yg(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;d=I,I=I+16|0,m=d,n[m>>2]=A,Qh(o,0,l,u,m),I=d}function Qh(o,l,u,A,d){if(o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,o=o|0?o:956,v$[n[o+8>>2]&1](o,l,u,A,d)|0,(u|0)==5)Nt();else return}function hc(o,l,u){o=o|0,l=l|0,u=u|0,s[o+l>>0]=u&1}function Dy(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(Th(o,A),kt(o,n[l>>2]|0,n[u>>2]|0,A))}function Th(o,l){o=o|0,l=l|0;var u=0;if((O(o)|0)>>>0>>0&&sn(o),l>>>0>1073741823)Nt();else{u=Jt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function kt(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function O(o){return o=o|0,1073741823}function J(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+96>>2]|0?o=o+92|0:o=kn(o+60|0,n[1040+(l<<2)>>2]|0,992)|0,y(Ke(o,u))}function re(o,l,u){return o=o|0,l=l|0,u=y(u),de(l)|0&&n[o+104>>2]|0?o=o+100|0:o=kn(o+60|0,n[1e3+(l<<2)>>2]|0,992)|0,y(Ke(o,u))}function de(o){return o=o|0,(o|1|0)==3|0}function Ke(o,l){return o=o|0,l=y(l),(n[o+4>>2]|0)==3?l=y(0):l=y(Xr(o,l)),y(l)}function ft(o,l){return o=o|0,l=l|0,o=n[o>>2]|0,(o|0?o:(l|0)>1?l:1)|0}function dr(o,l){o=o|0,l=l|0;var u=0;e:do if((l|0)==2){switch(o|0){case 2:{o=3;break e}case 3:break;default:{u=4;break e}}o=2}else u=4;while(!1);return o|0}function Br(o,l){o=o|0,l=l|0;var u=Xe;return de(l)|0&&n[o+312>>2]|0&&(u=y(h[o+308>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1040+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function _n(o,l){o=o|0,l=l|0;var u=Xe;return de(l)|0&&n[o+320>>2]|0&&(u=y(h[o+316>>2]),u>=y(0))||(u=y($n(y(h[(kn(o+276|0,n[1e3+(l<<2)>>2]|0,992)|0)>>2]),y(0)))),y(u)}function di(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return de(l)|0&&n[o+240>>2]|0&&(A=y(Xr(o+236|0,u)),A>=y(0))||(A=y($n(y(Xr(kn(o+204|0,n[1040+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function ws(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return de(l)|0&&n[o+248>>2]|0&&(A=y(Xr(o+244|0,u)),A>=y(0))||(A=y($n(y(Xr(kn(o+204|0,n[1e3+(l<<2)>>2]|0,992)|0,u)),y(0)))),y(A)}function zA(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=Xe,T=Xe,_=Xe,M=Xe,G=Xe,ae=Xe,We=0,Le=0,Qe=0;Qe=I,I=I+16|0,We=Qe,Le=o+964|0,wi(o,(n[Le>>2]|0)!=0,3519),k=y(Ka(o,2,l)),T=y(Ka(o,0,l)),_=y(yn(o,2,l)),M=y(yn(o,0,l)),Mt(l)|0?G=l:G=y($n(y(0),y(y(l-_)-k))),Mt(u)|0?ae=u:ae=y($n(y(0),y(y(u-M)-T))),(A|0)==1&(d|0)==1?(h[o+908>>2]=y(Gn(o,2,y(l-_),m,m)),l=y(Gn(o,0,y(u-M),B,m))):(S$[n[Le>>2]&1](We,o,G,A,ae,d),G=y(k+y(h[We>>2])),ae=y(l-_),h[o+908>>2]=y(Gn(o,2,(A|2|0)==2?G:ae,m,m)),ae=y(T+y(h[We+4>>2])),l=y(u-M),l=y(Gn(o,0,(d|2|0)==2?ae:l,B,m))),h[o+912>>2]=l,I=Qe}function wP(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=Xe,T=Xe,_=Xe,M=Xe;_=y(Ka(o,2,m)),k=y(Ka(o,0,m)),M=y(yn(o,2,m)),T=y(yn(o,0,m)),l=y(l-M),h[o+908>>2]=y(Gn(o,2,(A|2|0)==2?_:l,m,m)),u=y(u-T),h[o+912>>2]=y(Gn(o,0,(d|2|0)==2?k:u,B,m))}function i2(o,l,u,A,d,m,B){o=o|0,l=y(l),u=y(u),A=A|0,d=d|0,m=y(m),B=y(B);var k=0,T=Xe,_=Xe;return k=(A|0)==2,!(l<=y(0)&k)&&!(u<=y(0)&(d|0)==2)&&!((A|0)==1&(d|0)==1)?o=0:(T=y(yn(o,0,m)),_=y(yn(o,2,m)),k=l>2]=y(Gn(o,2,k?y(0):l,m,m)),l=y(u-T),k=u>2]=y(Gn(o,0,k?y(0):l,B,m)),o=1),o|0}function by(o,l){return o=o|0,l=l|0,Jg(o)|0?o=dr(2,l)|0:o=0,o|0}function Rh(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(di(o,l,u)),y(u+y(Br(o,l)))}function s2(o,l,u){return o=o|0,l=l|0,u=y(u),u=y(ws(o,l,u)),y(u+y(_n(o,l)))}function Ka(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return A=y(Rh(o,l,u)),y(A+y(s2(o,l,u)))}function o2(o){return o=o|0,n[o+24>>2]|0?o=0:y(ZA(o))!=y(0)?o=1:o=y(Fh(o))!=y(0),o|0}function ZA(o){o=o|0;var l=Xe;if(n[o+944>>2]|0){if(l=y(h[o+44>>2]),Mt(l)|0)return l=y(h[o+40>>2]),o=l>y(0)&((Mt(l)|0)^1),y(o?l:y(0))}else l=y(0);return y(l)}function Fh(o){o=o|0;var l=Xe,u=0,A=Xe;do if(n[o+944>>2]|0){if(l=y(h[o+48>>2]),Mt(l)|0){if(u=s[(n[o+976>>2]|0)+2>>0]|0,!(u<<24>>24)&&(A=y(h[o+40>>2]),A>24?y(1):y(0)}}else l=y(0);while(!1);return y(l)}function Py(o){o=o|0;var l=0,u=0;if(eE(o+400|0,0,540)|0,s[o+985>>0]=1,ee(o),u=Li(o)|0,u|0){l=o+948|0,o=0;do Py(n[(n[l>>2]|0)+(o<<2)>>2]|0),o=o+1|0;while((o|0)!=(u|0))}}function BP(o,l,u,A,d,m,B,k,T,_){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=y(m),B=y(B),k=k|0,T=T|0,_=_|0;var M=0,G=Xe,ae=0,We=0,Le=Xe,Qe=Xe,tt=0,Ze=Xe,ct=0,He=Xe,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,Fo=0;Hn=I,I=I+16|0,qr=Hn+12|0,fr=Hn+8|0,$t=Hn+4|0,Tr=Hn,cr=dr(n[o+4>>2]|0,T)|0,Ge=de(cr)|0,G=y(Xr(tM(l)|0,Ge?m:B)),Lt=so(l,2,m)|0,Hr=so(l,0,B)|0;do if(!(Mt(G)|0)&&!(Mt(Ge?u:d)|0)){if(M=l+504|0,!(Mt(y(h[M>>2]))|0)&&(!(l2(n[l+976>>2]|0,0)|0)||(n[l+500>>2]|0)==(n[2278]|0)))break;h[M>>2]=y($n(G,y(Ka(l,cr,m))))}else ae=7;while(!1);do if((ae|0)==7){if(ct=Ge^1,!(ct|Lt^1)){B=y(Xr(n[l+992>>2]|0,m)),h[l+504>>2]=y($n(B,y(Ka(l,2,m))));break}if(!(Ge|Hr^1)){B=y(Xr(n[l+996>>2]|0,B)),h[l+504>>2]=y($n(B,y(Ka(l,0,m))));break}h[qr>>2]=y(ce),h[fr>>2]=y(ce),n[$t>>2]=0,n[Tr>>2]=0,Ze=y(yn(l,2,m)),He=y(yn(l,0,m)),Lt?(Le=y(Ze+y(Xr(n[l+992>>2]|0,m))),h[qr>>2]=Le,n[$t>>2]=1,We=1):(We=0,Le=y(ce)),Hr?(G=y(He+y(Xr(n[l+996>>2]|0,B))),h[fr>>2]=G,n[Tr>>2]=1,M=1):(M=0,G=y(ce)),ae=n[o+32>>2]|0,Ge&(ae|0)==2?ae=2:Mt(Le)|0&&!(Mt(u)|0)&&(h[qr>>2]=u,n[$t>>2]=2,We=2,Le=u),!((ae|0)==2&ct)&&Mt(G)|0&&!(Mt(d)|0)&&(h[fr>>2]=d,n[Tr>>2]=2,M=2,G=d),Qe=y(h[l+396>>2]),tt=Mt(Qe)|0;do if(tt)ae=We;else{if((We|0)==1&ct){h[fr>>2]=y(y(Le-Ze)/Qe),n[Tr>>2]=1,M=1,ae=1;break}Ge&(M|0)==1?(h[qr>>2]=y(Qe*y(G-He)),n[$t>>2]=1,M=1,ae=1):ae=We}while(!1);Fo=Mt(u)|0,We=(os(o,l)|0)!=4,!(Ge|Lt|((A|0)!=1|Fo)|(We|(ae|0)==1))&&(h[qr>>2]=u,n[$t>>2]=1,!tt)&&(h[fr>>2]=y(y(u-Ze)/Qe),n[Tr>>2]=1,M=1),!(Hr|ct|((k|0)!=1|(Mt(d)|0))|(We|(M|0)==1))&&(h[fr>>2]=d,n[Tr>>2]=1,!tt)&&(h[qr>>2]=y(Qe*y(d-He)),n[$t>>2]=1),Bu(l,2,m,m,$t,qr),Bu(l,0,B,m,Tr,fr),u=y(h[qr>>2]),d=y(h[fr>>2]),kl(l,u,d,T,n[$t>>2]|0,n[Tr>>2]|0,m,B,0,3565,_)|0,B=y(h[l+908+(n[976+(cr<<2)>>2]<<2)>>2]),h[l+504>>2]=y($n(B,y(Ka(l,cr,m))))}while(!1);n[l+500>>2]=n[2278],I=Hn}function Gn(o,l,u,A,d){return o=o|0,l=l|0,u=y(u),A=y(A),d=y(d),A=y(Kg(o,l,u,A)),y($n(A,y(Ka(o,l,d))))}function os(o,l){return o=o|0,l=l|0,l=l+20|0,l=n[(n[l>>2]|0?l:o+16|0)>>2]|0,(l|0)==5&&Jg(n[o+4>>2]|0)|0&&(l=1),l|0}function Ql(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+96>>2]|0?l=4:l=n[1040+(l<<2)>>2]|0,o+60+(l<<3)|0}function Tl(o,l){return o=o|0,l=l|0,de(l)|0&&n[o+104>>2]|0?l=5:l=n[1e3+(l<<2)>>2]|0,o+60+(l<<3)|0}function Bu(o,l,u,A,d,m){switch(o=o|0,l=l|0,u=y(u),A=y(A),d=d|0,m=m|0,u=y(Xr(o+380+(n[976+(l<<2)>>2]<<3)|0,u)),u=y(u+y(yn(o,l,A))),n[d>>2]|0){case 2:case 1:{d=Mt(u)|0,A=y(h[m>>2]),h[m>>2]=d|A>2]=2,h[m>>2]=u);break}default:}}function ga(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,4,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1040+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function XA(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,4,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1040+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Xr(A,u))),y(u)}function $A(o,l,u){o=o|0,l=l|0,u=y(u);var A=Xe;return A=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),A=y(A+y(J(o,l,u))),y(A+y(re(o,l,u)))}function XL(o){o=o|0;var l=0,u=0,A=0;e:do if(Jg(n[o+4>>2]|0)|0)l=0;else if((n[o+16>>2]|0)!=5)if(u=Li(o)|0,!u)l=0;else for(l=0;;){if(A=Cs(o,l)|0,!(n[A+24>>2]|0)&&(n[A+20>>2]|0)==5){l=1;break e}if(l=l+1|0,l>>>0>=u>>>0){l=0;break}}else l=1;while(!1);return l|0}function $L(o,l){o=o|0,l=l|0;var u=Xe;return u=y(h[o+908+(n[976+(l<<2)>>2]<<2)>>2]),u>=y(0)&((Mt(u)|0)^1)|0}function Vg(o){o=o|0;var l=Xe,u=0,A=0,d=0,m=0,B=0,k=0,T=Xe;if(u=n[o+968>>2]|0,u)T=y(h[o+908>>2]),l=y(h[o+912>>2]),l=y(I$[u&0](o,T,l)),wi(o,(Mt(l)|0)^1,3573);else{m=Li(o)|0;do if(m|0){for(u=0,d=0;;){if(A=Cs(o,d)|0,n[A+940>>2]|0){B=8;break}if((n[A+24>>2]|0)!=1)if(k=(os(o,A)|0)==5,k){u=A;break}else u=u|0?u:A;if(d=d+1|0,d>>>0>=m>>>0){B=8;break}}if((B|0)==8&&!u)break;return l=y(Vg(u)),y(l+y(h[u+404>>2]))}while(!1);l=y(h[o+912>>2])}return y(l)}function Kg(o,l,u,A){o=o|0,l=l|0,u=y(u),A=y(A);var d=Xe,m=0;return Jg(l)|0?(l=1,m=3):de(l)|0?(l=0,m=3):(A=y(ce),d=y(ce)),(m|0)==3&&(d=y(Xr(o+364+(l<<3)|0,A)),A=y(Xr(o+380+(l<<3)|0,A))),m=A=y(0)&((Mt(A)|0)^1)),u=m?A:u,m=d>=y(0)&((Mt(d)|0)^1)&u>2]|0,m)|0,Le=by(tt,m)|0,Qe=de(tt)|0,G=y(yn(l,2,u)),ae=y(yn(l,0,u)),so(l,2,u)|0?k=y(G+y(Xr(n[l+992>>2]|0,u))):ga(l,2)|0&&xy(l,2)|0?(k=y(h[o+908>>2]),T=y(Br(o,2)),T=y(k-y(T+y(_n(o,2)))),k=y(XA(l,2,u)),k=y(Gn(l,2,y(T-y(k+y(Nh(l,2,u)))),u,u))):k=y(ce),so(l,0,d)|0?T=y(ae+y(Xr(n[l+996>>2]|0,d))):ga(l,0)|0&&xy(l,0)|0?(T=y(h[o+912>>2]),ct=y(Br(o,0)),ct=y(T-y(ct+y(_n(o,0)))),T=y(XA(l,0,d)),T=y(Gn(l,0,y(ct-y(T+y(Nh(l,0,d)))),d,u))):T=y(ce),_=Mt(k)|0,M=Mt(T)|0;do if(_^M&&(We=y(h[l+396>>2]),!(Mt(We)|0)))if(_){k=y(G+y(y(T-ae)*We));break}else{ct=y(ae+y(y(k-G)/We)),T=M?ct:T;break}while(!1);M=Mt(k)|0,_=Mt(T)|0,M|_&&(He=(M^1)&1,A=u>y(0)&((A|0)!=0&M),k=Qe?k:A?u:k,kl(l,k,T,m,Qe?He:A?2:He,M&(_^1)&1,k,T,0,3623,B)|0,k=y(h[l+908>>2]),k=y(k+y(yn(l,2,u))),T=y(h[l+912>>2]),T=y(T+y(yn(l,0,u)))),kl(l,k,T,m,1,1,k,T,1,3635,B)|0,xy(l,tt)|0&&!(ga(l,tt)|0)?(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(_n(o,tt))),ct=y(ct-y(re(l,tt,u))),ct=y(ct-y(Nh(l,tt,Qe?u:d))),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct):Ze=21;do if((Ze|0)==21){if(!(ga(l,tt)|0)&&(n[o+8>>2]|0)==1){He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct;break}!(ga(l,tt)|0)&&(n[o+8>>2]|0)==2&&(He=n[976+(tt<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(tt<<2)>>2]<<2)>>2]=ct)}while(!1);xy(l,Le)|0&&!(ga(l,Le)|0)?(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),ct=y(ct-y(_n(o,Le))),ct=y(ct-y(re(l,Le,u))),ct=y(ct-y(Nh(l,Le,Qe?d:u))),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct):Ze=30;do if((Ze|0)==30&&!(ga(l,Le)|0)){if((os(o,l)|0)==2){He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(y(ct-y(h[l+908+(He<<2)>>2]))*y(.5)),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct;break}He=(os(o,l)|0)==3,He^(n[o+28>>2]|0)==2&&(He=n[976+(Le<<2)>>2]|0,ct=y(h[o+908+(He<<2)>>2]),ct=y(ct-y(h[l+908+(He<<2)>>2])),h[l+400+(n[1040+(Le<<2)>>2]<<2)>>2]=ct)}while(!1)}function a2(o,l,u){o=o|0,l=l|0,u=u|0;var A=Xe,d=0;d=n[976+(u<<2)>>2]|0,A=y(h[l+908+(d<<2)>>2]),A=y(y(h[o+908+(d<<2)>>2])-A),A=y(A-y(h[l+400+(n[1040+(u<<2)>>2]<<2)>>2])),h[l+400+(n[1e3+(u<<2)>>2]<<2)>>2]=A}function Jg(o){return o=o|0,(o|1|0)==1|0}function tM(o){o=o|0;var l=Xe;switch(n[o+56>>2]|0){case 0:case 3:{l=y(h[o+40>>2]),l>y(0)&((Mt(l)|0)^1)?o=s[(n[o+976>>2]|0)+2>>0]|0?1056:992:o=1056;break}default:o=o+52|0}return o|0}function l2(o,l){return o=o|0,l=l|0,(s[o+l>>0]|0)!=0|0}function xy(o,l){return o=o|0,l=l|0,o=o+132|0,de(l)|0&&n[(kn(o,5,948)|0)+4>>2]|0?o=1:o=(n[(kn(o,n[1e3+(l<<2)>>2]|0,948)|0)+4>>2]|0)!=0,o|0}function Nh(o,l,u){o=o|0,l=l|0,u=y(u);var A=0,d=0;return o=o+132|0,de(l)|0&&(A=kn(o,5,948)|0,(n[A+4>>2]|0)!=0)?d=4:(A=kn(o,n[1e3+(l<<2)>>2]|0,948)|0,n[A+4>>2]|0?d=4:u=y(0)),(d|0)==4&&(u=y(Xr(A,u))),y(u)}function vP(o,l,u){return o=o|0,l=l|0,u=y(u),ga(o,l)|0?u=y(XA(o,l,u)):u=y(-y(Nh(o,l,u))),y(u)}function SP(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function ky(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Jt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function DP(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function Qy(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&yt(o)}function bP(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;if(B=o+4|0,k=n[B>>2]|0,d=k-A|0,m=d>>2,o=l+(m<<2)|0,o>>>0>>0){A=k;do n[A>>2]=n[o>>2],o=o+4|0,A=(n[B>>2]|0)+4|0,n[B>>2]=A;while(o>>>0>>0)}m|0&&F2(k+(0-m<<2)|0,l|0,d|0)|0}function PP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return k=l+4|0,T=n[k>>2]|0,d=n[o>>2]|0,B=u,m=B-d|0,A=T+(0-(m>>2)<<2)|0,n[k>>2]=A,(m|0)>0&&Qr(A|0,d|0,m|0)|0,d=o+4|0,m=l+8|0,A=(n[d>>2]|0)-B|0,(A|0)>0&&(Qr(n[m>>2]|0,u|0,A|0)|0,n[m>>2]=(n[m>>2]|0)+(A>>>2<<2)),B=n[o>>2]|0,n[o>>2]=n[k>>2],n[k>>2]=B,B=n[d>>2]|0,n[d>>2]=n[m>>2],n[m>>2]=B,B=o+8|0,u=l+12|0,o=n[B>>2]|0,n[B>>2]=n[u>>2],n[u>>2]=o,n[l>>2]=n[k>>2],T|0}function c2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(B=n[l>>2]|0,m=n[u>>2]|0,(B|0)!=(m|0)){d=o+8|0,u=((m+-4-B|0)>>>2)+1|0,o=B,A=n[d>>2]|0;do n[A>>2]=n[o>>2],A=(n[d>>2]|0)+4|0,n[d>>2]=A,o=o+4|0;while((o|0)!=(m|0));n[l>>2]=B+(u<<2)}}function u2(){fa()}function xP(){var o=0;return o=Jt(4)|0,f2(o),o|0}function f2(o){o=o|0,n[o>>2]=pc()|0}function kP(o){o=o|0,o|0&&(zg(o),yt(o))}function zg(o){o=o|0,st(n[o>>2]|0)}function rM(o,l,u){o=o|0,l=l|0,u=u|0,hc(n[o>>2]|0,l,u)}function Ty(o,l){o=o|0,l=y(l),xh(n[o>>2]|0,l)}function Ry(o,l){return o=o|0,l=l|0,l2(n[o>>2]|0,l)|0}function Fy(){var o=0;return o=Jt(8)|0,Zg(o,0),o|0}function Zg(o,l){o=o|0,l=l|0,l?l=Aa(n[l>>2]|0)|0:l=ns()|0,n[o>>2]=l,n[o+4>>2]=0,Tn(l,o)}function Ny(o){o=o|0;var l=0;return l=Jt(8)|0,Zg(l,o),l|0}function Xg(o){o=o|0,o|0&&(Oy(o),yt(o))}function Oy(o){o=o|0;var l=0;fc(n[o>>2]|0),l=o+4|0,o=n[l>>2]|0,n[l>>2]=0,o|0&&(Df(o),yt(o))}function Df(o){o=o|0,bf(o)}function bf(o){o=o|0,o=n[o>>2]|0,o|0&&Oa(o|0)}function A2(o){return o=o|0,Ga(o)|0}function p2(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Df(l),yt(l)),Ac(n[o>>2]|0)}function Ly(o,l){o=o|0,l=l|0,fn(n[o>>2]|0,n[l>>2]|0)}function nM(o,l){o=o|0,l=l|0,Sh(n[o>>2]|0,l)}function iM(o,l,u){o=o|0,l=l|0,u=+u,Cy(n[o>>2]|0,l,y(u))}function My(o,l,u){o=o|0,l=l|0,u=+u,wy(n[o>>2]|0,l,y(u))}function h2(o,l){o=o|0,l=l|0,wh(n[o>>2]|0,l)}function g2(o,l){o=o|0,l=l|0,bo(n[o>>2]|0,l)}function xr(o,l){o=o|0,l=l|0,vh(n[o>>2]|0,l)}function oo(o,l){o=o|0,l=l|0,my(n[o>>2]|0,l)}function Zi(o,l){o=o|0,l=l|0,Og(n[o>>2]|0,l)}function Os(o,l){o=o|0,l=l|0,Do(n[o>>2]|0,l)}function ep(o,l,u){o=o|0,l=l|0,u=+u,qA(n[o>>2]|0,l,y(u))}function d2(o,l,u){o=o|0,l=l|0,u=+u,Y(n[o>>2]|0,l,y(u))}function Bs(o,l){o=o|0,l=l|0,GA(n[o>>2]|0,l)}function _y(o,l){o=o|0,l=l|0,Ey(n[o>>2]|0,l)}function Oh(o,l){o=o|0,l=l|0,Po(n[o>>2]|0,l)}function $g(o,l){o=o|0,l=+l,Dh(n[o>>2]|0,y(l))}function Lh(o,l){o=o|0,l=+l,Pl(n[o>>2]|0,y(l))}function m2(o,l){o=o|0,l=+l,Iy(n[o>>2]|0,y(l))}function y2(o,l){o=o|0,l=+l,Mg(n[o>>2]|0,y(l))}function E2(o,l){o=o|0,l=+l,bl(n[o>>2]|0,y(l))}function I2(o,l){o=o|0,l=+l,_g(n[o>>2]|0,y(l))}function Pf(o,l){o=o|0,l=+l,n2(n[o>>2]|0,y(l))}function sr(o){o=o|0,bh(n[o>>2]|0)}function Uy(o,l){o=o|0,l=+l,zi(n[o>>2]|0,y(l))}function C2(o,l){o=o|0,l=+l,Ef(n[o>>2]|0,y(l))}function gc(o){o=o|0,Wa(n[o>>2]|0)}function xf(o,l){o=o|0,l=+l,yu(n[o>>2]|0,y(l))}function ed(o,l){o=o|0,l=+l,If(n[o>>2]|0,y(l))}function td(o,l){o=o|0,l=+l,gi(n[o>>2]|0,y(l))}function w2(o,l){o=o|0,l=+l,WA(n[o>>2]|0,y(l))}function B2(o,l){o=o|0,l=+l,pa(n[o>>2]|0,y(l))}function vu(o,l){o=o|0,l=+l,Va(n[o>>2]|0,y(l))}function rd(o,l){o=o|0,l=+l,Ph(n[o>>2]|0,y(l))}function v2(o,l){o=o|0,l=+l,jg(n[o>>2]|0,y(l))}function Hy(o,l){o=o|0,l=+l,YA(n[o>>2]|0,y(l))}function Su(o,l,u){o=o|0,l=l|0,u=+u,mu(n[o>>2]|0,l,y(u))}function jy(o,l,u){o=o|0,l=l|0,u=+u,xo(n[o>>2]|0,l,y(u))}function nd(o,l,u){o=o|0,l=l|0,u=+u,yf(n[o>>2]|0,l,y(u))}function id(o){return o=o|0,Ng(n[o>>2]|0)|0}function To(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,jA(d,n[l>>2]|0,u),vs(o,d),I=A}function vs(o,l){o=o|0,l=l|0,Rl(o,n[l+4>>2]|0,+y(h[l>>2]))}function Rl(o,l,u){o=o|0,l=l|0,u=+u,n[o>>2]=l,E[o+8>>3]=u}function qy(o){return o=o|0,r2(n[o>>2]|0)|0}function da(o){return o=o|0,Bh(n[o>>2]|0)|0}function QP(o){return o=o|0,du(n[o>>2]|0)|0}function Mh(o){return o=o|0,t2(n[o>>2]|0)|0}function S2(o){return o=o|0,Lg(n[o>>2]|0)|0}function sM(o){return o=o|0,yy(n[o>>2]|0)|0}function TP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,xt(d,n[l>>2]|0,u),vs(o,d),I=A}function RP(o){return o=o|0,mf(n[o>>2]|0)|0}function Gy(o){return o=o|0,Dl(n[o>>2]|0)|0}function D2(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,HA(A,n[l>>2]|0),vs(o,A),I=u}function _h(o){return o=o|0,+ +y(ai(n[o>>2]|0))}function FP(o){return o=o|0,+ +y(qi(n[o>>2]|0))}function NP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,ur(A,n[l>>2]|0),vs(o,A),I=u}function sd(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Ug(A,n[l>>2]|0),vs(o,A),I=u}function oM(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,wt(A,n[l>>2]|0),vs(o,A),I=u}function aM(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Ya(A,n[l>>2]|0),vs(o,A),I=u}function OP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,Hg(A,n[l>>2]|0),vs(o,A),I=u}function LP(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,vy(A,n[l>>2]|0),vs(o,A),I=u}function tp(o){return o=o|0,+ +y(qg(n[o>>2]|0))}function lM(o,l){return o=o|0,l=l|0,+ +y(By(n[o>>2]|0,l))}function cM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,dt(d,n[l>>2]|0,u),vs(o,d),I=A}function Du(o,l,u){o=o|0,l=l|0,u=u|0,lr(n[o>>2]|0,n[l>>2]|0,u)}function uM(o,l){o=o|0,l=l|0,df(n[o>>2]|0,n[l>>2]|0)}function MP(o){return o=o|0,Li(n[o>>2]|0)|0}function fM(o){return o=o|0,o=mt(n[o>>2]|0)|0,o?o=A2(o)|0:o=0,o|0}function _P(o,l){return o=o|0,l=l|0,o=Cs(n[o>>2]|0,l)|0,o?o=A2(o)|0:o=0,o|0}function kf(o,l){o=o|0,l=l|0;var u=0,A=0;A=Jt(4)|0,UP(A,l),u=o+4|0,l=n[u>>2]|0,n[u>>2]=A,l|0&&(Df(l),yt(l)),St(n[o>>2]|0,1)}function UP(o,l){o=o|0,l=l|0,gM(o,l)}function AM(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,HP(k,Ga(l)|0,+u,A,+d,m),h[o>>2]=y(+E[k>>3]),h[o+4>>2]=y(+E[k+8>>3]),I=B}function HP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0,k=0,T=0,_=0,M=0;B=I,I=I+32|0,M=B+8|0,_=B+20|0,T=B,k=B+16|0,E[M>>3]=u,n[_>>2]=A,E[T>>3]=d,n[k>>2]=m,Wy(o,n[l+4>>2]|0,M,_,T,k),I=B}function Wy(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0;B=I,I=I+16|0,k=B,Nl(k),l=Ls(l)|0,jP(o,l,+E[u>>3],n[A>>2]|0,+E[d>>3],n[m>>2]|0),Ol(k),I=B}function Ls(o){return o=o|0,n[o>>2]|0}function jP(o,l,u,A,d,m){o=o|0,l=l|0,u=+u,A=A|0,d=+d,m=m|0;var B=0;B=ma(b2()|0)|0,u=+Ja(u),A=Yy(A)|0,d=+Ja(d),pM(o,Jn(0,B|0,l|0,+u,A|0,+d,Yy(m)|0)|0)}function b2(){var o=0;return s[7608]|0||(x2(9120),o=7608,n[o>>2]=1,n[o+4>>2]=0),9120}function ma(o){return o=o|0,n[o+8>>2]|0}function Ja(o){return o=+o,+ +Qf(o)}function Yy(o){return o=o|0,od(o)|0}function pM(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=l,A&1?(za(u,0),Me(A|0,u|0)|0,P2(o,u),hM(u)):(n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]),I=d}function za(o,l){o=o|0,l=l|0,bu(o,l),n[o+8>>2]=0,s[o+24>>0]=0}function P2(o,l){o=o|0,l=l|0,l=l+8|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2]}function hM(o){o=o|0,s[o+24>>0]=0}function bu(o,l){o=o|0,l=l|0,n[o>>2]=l}function od(o){return o=o|0,o|0}function Qf(o){return o=+o,+o}function x2(o){o=o|0,Ro(o,k2()|0,4)}function k2(){return 1064}function Ro(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=ji(l|0,u+1|0)|0}function gM(o,l){o=o|0,l=l|0,l=n[l>>2]|0,n[o>>2]=l,cu(l|0)}function qP(o){o=o|0;var l=0,u=0;u=o+4|0,l=n[u>>2]|0,n[u>>2]=0,l|0&&(Df(l),yt(l)),St(n[o>>2]|0,0)}function GP(o){o=o|0,Dt(n[o>>2]|0)}function Vy(o){return o=o|0,tr(n[o>>2]|0)|0}function dM(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,KA(n[o>>2]|0,y(l),y(u),A)}function mM(o){return o=o|0,+ +y(Eu(n[o>>2]|0))}function v(o){return o=o|0,+ +y(Cf(n[o>>2]|0))}function D(o){return o=o|0,+ +y(Iu(n[o>>2]|0))}function Q(o){return o=o|0,+ +y(Fs(n[o>>2]|0))}function H(o){return o=o|0,+ +y(Cu(n[o>>2]|0))}function V(o){return o=o|0,+ +y(qn(n[o>>2]|0))}function ne(o,l){o=o|0,l=l|0,E[o>>3]=+y(Eu(n[l>>2]|0)),E[o+8>>3]=+y(Cf(n[l>>2]|0)),E[o+16>>3]=+y(Iu(n[l>>2]|0)),E[o+24>>3]=+y(Fs(n[l>>2]|0)),E[o+32>>3]=+y(Cu(n[l>>2]|0)),E[o+40>>3]=+y(qn(n[l>>2]|0))}function Se(o,l){return o=o|0,l=l|0,+ +y(is(n[o>>2]|0,l))}function Ue(o,l){return o=o|0,l=l|0,+ +y(xi(n[o>>2]|0,l))}function At(o,l){return o=o|0,l=l|0,+ +y(VA(n[o>>2]|0,l))}function Gt(){return Qn()|0}function vr(){Lr(),Xt(),zn(),mi(),Za(),$e()}function Lr(){Kqe(11713,4938,1)}function Xt(){pqe(10448)}function zn(){K6e(10408)}function mi(){m6e(10324)}function Za(){SHe(10096)}function $e(){qe(9132)}function qe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0,Fo=0,No=0,Oo=0,$a=0,Kh=0,Jh=0,dc=0,zh=0,Ff=0,Nf=0,Zh=0,Xh=0,$h=0,on=0,mc=0,e0=0,ku=0,t0=0,r0=0,Of=0,Lf=0,Qu=0,ao=0,Ml=0,ya=0,yc=0,lp=0,cp=0,Mf=0,up=0,fp=0,lo=0,_s=0,Ec=0,Wn=0,Ap=0,Lo=0,Tu=0,Mo=0,Ru=0,pp=0,hp=0,Fu=0,co=0,Ic=0,gp=0,dp=0,mp=0,Fr=0,ci=0,Us=0,_o=0,uo=0,Mr=0,Ar=0,Cc=0;l=I,I=I+672|0,u=l+656|0,Cc=l+648|0,Ar=l+640|0,Mr=l+632|0,uo=l+624|0,_o=l+616|0,Us=l+608|0,ci=l+600|0,Fr=l+592|0,mp=l+584|0,dp=l+576|0,gp=l+568|0,Ic=l+560|0,co=l+552|0,Fu=l+544|0,hp=l+536|0,pp=l+528|0,Ru=l+520|0,Mo=l+512|0,Tu=l+504|0,Lo=l+496|0,Ap=l+488|0,Wn=l+480|0,Ec=l+472|0,_s=l+464|0,lo=l+456|0,fp=l+448|0,up=l+440|0,Mf=l+432|0,cp=l+424|0,lp=l+416|0,yc=l+408|0,ya=l+400|0,Ml=l+392|0,ao=l+384|0,Qu=l+376|0,Lf=l+368|0,Of=l+360|0,r0=l+352|0,t0=l+344|0,ku=l+336|0,e0=l+328|0,mc=l+320|0,on=l+312|0,$h=l+304|0,Xh=l+296|0,Zh=l+288|0,Nf=l+280|0,Ff=l+272|0,zh=l+264|0,dc=l+256|0,Jh=l+248|0,Kh=l+240|0,$a=l+232|0,Oo=l+224|0,No=l+216|0,Fo=l+208|0,Hn=l+200|0,cr=l+192|0,Hr=l+184|0,Tr=l+176|0,$t=l+168|0,fr=l+160|0,qr=l+152|0,Lt=l+144|0,Ge=l+136|0,He=l+128|0,ct=l+120|0,Ze=l+112|0,tt=l+104|0,Qe=l+96|0,Le=l+88|0,We=l+80|0,ae=l+72|0,G=l+64|0,M=l+56|0,_=l+48|0,T=l+40|0,k=l+32|0,B=l+24|0,m=l+16|0,d=l+8|0,A=l,pt(o,3646),Zt(o,3651,2)|0,Sr(o,3665,2)|0,Xn(o,3682,18)|0,n[Cc>>2]=19,n[Cc+4>>2]=0,n[u>>2]=n[Cc>>2],n[u+4>>2]=n[Cc+4>>2],kr(o,3690,u)|0,n[Ar>>2]=1,n[Ar+4>>2]=0,n[u>>2]=n[Ar>>2],n[u+4>>2]=n[Ar+4>>2],Rn(o,3696,u)|0,n[Mr>>2]=2,n[Mr+4>>2]=0,n[u>>2]=n[Mr>>2],n[u+4>>2]=n[Mr+4>>2],Un(o,3706,u)|0,n[uo>>2]=1,n[uo+4>>2]=0,n[u>>2]=n[uo>>2],n[u+4>>2]=n[uo+4>>2],zr(o,3722,u)|0,n[_o>>2]=2,n[_o+4>>2]=0,n[u>>2]=n[_o>>2],n[u+4>>2]=n[_o+4>>2],zr(o,3734,u)|0,n[Us>>2]=3,n[Us+4>>2]=0,n[u>>2]=n[Us>>2],n[u+4>>2]=n[Us+4>>2],Un(o,3753,u)|0,n[ci>>2]=4,n[ci+4>>2]=0,n[u>>2]=n[ci>>2],n[u+4>>2]=n[ci+4>>2],Un(o,3769,u)|0,n[Fr>>2]=5,n[Fr+4>>2]=0,n[u>>2]=n[Fr>>2],n[u+4>>2]=n[Fr+4>>2],Un(o,3783,u)|0,n[mp>>2]=6,n[mp+4>>2]=0,n[u>>2]=n[mp>>2],n[u+4>>2]=n[mp+4>>2],Un(o,3796,u)|0,n[dp>>2]=7,n[dp+4>>2]=0,n[u>>2]=n[dp>>2],n[u+4>>2]=n[dp+4>>2],Un(o,3813,u)|0,n[gp>>2]=8,n[gp+4>>2]=0,n[u>>2]=n[gp>>2],n[u+4>>2]=n[gp+4>>2],Un(o,3825,u)|0,n[Ic>>2]=3,n[Ic+4>>2]=0,n[u>>2]=n[Ic>>2],n[u+4>>2]=n[Ic+4>>2],zr(o,3843,u)|0,n[co>>2]=4,n[co+4>>2]=0,n[u>>2]=n[co>>2],n[u+4>>2]=n[co+4>>2],zr(o,3853,u)|0,n[Fu>>2]=9,n[Fu+4>>2]=0,n[u>>2]=n[Fu>>2],n[u+4>>2]=n[Fu+4>>2],Un(o,3870,u)|0,n[hp>>2]=10,n[hp+4>>2]=0,n[u>>2]=n[hp>>2],n[u+4>>2]=n[hp+4>>2],Un(o,3884,u)|0,n[pp>>2]=11,n[pp+4>>2]=0,n[u>>2]=n[pp>>2],n[u+4>>2]=n[pp+4>>2],Un(o,3896,u)|0,n[Ru>>2]=1,n[Ru+4>>2]=0,n[u>>2]=n[Ru>>2],n[u+4>>2]=n[Ru+4>>2],li(o,3907,u)|0,n[Mo>>2]=2,n[Mo+4>>2]=0,n[u>>2]=n[Mo>>2],n[u+4>>2]=n[Mo+4>>2],li(o,3915,u)|0,n[Tu>>2]=3,n[Tu+4>>2]=0,n[u>>2]=n[Tu>>2],n[u+4>>2]=n[Tu+4>>2],li(o,3928,u)|0,n[Lo>>2]=4,n[Lo+4>>2]=0,n[u>>2]=n[Lo>>2],n[u+4>>2]=n[Lo+4>>2],li(o,3948,u)|0,n[Ap>>2]=5,n[Ap+4>>2]=0,n[u>>2]=n[Ap>>2],n[u+4>>2]=n[Ap+4>>2],li(o,3960,u)|0,n[Wn>>2]=6,n[Wn+4>>2]=0,n[u>>2]=n[Wn>>2],n[u+4>>2]=n[Wn+4>>2],li(o,3974,u)|0,n[Ec>>2]=7,n[Ec+4>>2]=0,n[u>>2]=n[Ec>>2],n[u+4>>2]=n[Ec+4>>2],li(o,3983,u)|0,n[_s>>2]=20,n[_s+4>>2]=0,n[u>>2]=n[_s>>2],n[u+4>>2]=n[_s+4>>2],kr(o,3999,u)|0,n[lo>>2]=8,n[lo+4>>2]=0,n[u>>2]=n[lo>>2],n[u+4>>2]=n[lo+4>>2],li(o,4012,u)|0,n[fp>>2]=9,n[fp+4>>2]=0,n[u>>2]=n[fp>>2],n[u+4>>2]=n[fp+4>>2],li(o,4022,u)|0,n[up>>2]=21,n[up+4>>2]=0,n[u>>2]=n[up>>2],n[u+4>>2]=n[up+4>>2],kr(o,4039,u)|0,n[Mf>>2]=10,n[Mf+4>>2]=0,n[u>>2]=n[Mf>>2],n[u+4>>2]=n[Mf+4>>2],li(o,4053,u)|0,n[cp>>2]=11,n[cp+4>>2]=0,n[u>>2]=n[cp>>2],n[u+4>>2]=n[cp+4>>2],li(o,4065,u)|0,n[lp>>2]=12,n[lp+4>>2]=0,n[u>>2]=n[lp>>2],n[u+4>>2]=n[lp+4>>2],li(o,4084,u)|0,n[yc>>2]=13,n[yc+4>>2]=0,n[u>>2]=n[yc>>2],n[u+4>>2]=n[yc+4>>2],li(o,4097,u)|0,n[ya>>2]=14,n[ya+4>>2]=0,n[u>>2]=n[ya>>2],n[u+4>>2]=n[ya+4>>2],li(o,4117,u)|0,n[Ml>>2]=15,n[Ml+4>>2]=0,n[u>>2]=n[Ml>>2],n[u+4>>2]=n[Ml+4>>2],li(o,4129,u)|0,n[ao>>2]=16,n[ao+4>>2]=0,n[u>>2]=n[ao>>2],n[u+4>>2]=n[ao+4>>2],li(o,4148,u)|0,n[Qu>>2]=17,n[Qu+4>>2]=0,n[u>>2]=n[Qu>>2],n[u+4>>2]=n[Qu+4>>2],li(o,4161,u)|0,n[Lf>>2]=18,n[Lf+4>>2]=0,n[u>>2]=n[Lf>>2],n[u+4>>2]=n[Lf+4>>2],li(o,4181,u)|0,n[Of>>2]=5,n[Of+4>>2]=0,n[u>>2]=n[Of>>2],n[u+4>>2]=n[Of+4>>2],zr(o,4196,u)|0,n[r0>>2]=6,n[r0+4>>2]=0,n[u>>2]=n[r0>>2],n[u+4>>2]=n[r0+4>>2],zr(o,4206,u)|0,n[t0>>2]=7,n[t0+4>>2]=0,n[u>>2]=n[t0>>2],n[u+4>>2]=n[t0+4>>2],zr(o,4217,u)|0,n[ku>>2]=3,n[ku+4>>2]=0,n[u>>2]=n[ku>>2],n[u+4>>2]=n[ku+4>>2],Pu(o,4235,u)|0,n[e0>>2]=1,n[e0+4>>2]=0,n[u>>2]=n[e0>>2],n[u+4>>2]=n[e0+4>>2],yM(o,4251,u)|0,n[mc>>2]=4,n[mc+4>>2]=0,n[u>>2]=n[mc>>2],n[u+4>>2]=n[mc+4>>2],Pu(o,4263,u)|0,n[on>>2]=5,n[on+4>>2]=0,n[u>>2]=n[on>>2],n[u+4>>2]=n[on+4>>2],Pu(o,4279,u)|0,n[$h>>2]=6,n[$h+4>>2]=0,n[u>>2]=n[$h>>2],n[u+4>>2]=n[$h+4>>2],Pu(o,4293,u)|0,n[Xh>>2]=7,n[Xh+4>>2]=0,n[u>>2]=n[Xh>>2],n[u+4>>2]=n[Xh+4>>2],Pu(o,4306,u)|0,n[Zh>>2]=8,n[Zh+4>>2]=0,n[u>>2]=n[Zh>>2],n[u+4>>2]=n[Zh+4>>2],Pu(o,4323,u)|0,n[Nf>>2]=9,n[Nf+4>>2]=0,n[u>>2]=n[Nf>>2],n[u+4>>2]=n[Nf+4>>2],Pu(o,4335,u)|0,n[Ff>>2]=2,n[Ff+4>>2]=0,n[u>>2]=n[Ff>>2],n[u+4>>2]=n[Ff+4>>2],yM(o,4353,u)|0,n[zh>>2]=12,n[zh+4>>2]=0,n[u>>2]=n[zh>>2],n[u+4>>2]=n[zh+4>>2],ad(o,4363,u)|0,n[dc>>2]=1,n[dc+4>>2]=0,n[u>>2]=n[dc>>2],n[u+4>>2]=n[dc+4>>2],rp(o,4376,u)|0,n[Jh>>2]=2,n[Jh+4>>2]=0,n[u>>2]=n[Jh>>2],n[u+4>>2]=n[Jh+4>>2],rp(o,4388,u)|0,n[Kh>>2]=13,n[Kh+4>>2]=0,n[u>>2]=n[Kh>>2],n[u+4>>2]=n[Kh+4>>2],ad(o,4402,u)|0,n[$a>>2]=14,n[$a+4>>2]=0,n[u>>2]=n[$a>>2],n[u+4>>2]=n[$a+4>>2],ad(o,4411,u)|0,n[Oo>>2]=15,n[Oo+4>>2]=0,n[u>>2]=n[Oo>>2],n[u+4>>2]=n[Oo+4>>2],ad(o,4421,u)|0,n[No>>2]=16,n[No+4>>2]=0,n[u>>2]=n[No>>2],n[u+4>>2]=n[No+4>>2],ad(o,4433,u)|0,n[Fo>>2]=17,n[Fo+4>>2]=0,n[u>>2]=n[Fo>>2],n[u+4>>2]=n[Fo+4>>2],ad(o,4446,u)|0,n[Hn>>2]=18,n[Hn+4>>2]=0,n[u>>2]=n[Hn>>2],n[u+4>>2]=n[Hn+4>>2],ad(o,4458,u)|0,n[cr>>2]=3,n[cr+4>>2]=0,n[u>>2]=n[cr>>2],n[u+4>>2]=n[cr+4>>2],rp(o,4471,u)|0,n[Hr>>2]=1,n[Hr+4>>2]=0,n[u>>2]=n[Hr>>2],n[u+4>>2]=n[Hr+4>>2],WP(o,4486,u)|0,n[Tr>>2]=10,n[Tr+4>>2]=0,n[u>>2]=n[Tr>>2],n[u+4>>2]=n[Tr+4>>2],Pu(o,4496,u)|0,n[$t>>2]=11,n[$t+4>>2]=0,n[u>>2]=n[$t>>2],n[u+4>>2]=n[$t+4>>2],Pu(o,4508,u)|0,n[fr>>2]=3,n[fr+4>>2]=0,n[u>>2]=n[fr>>2],n[u+4>>2]=n[fr+4>>2],yM(o,4519,u)|0,n[qr>>2]=4,n[qr+4>>2]=0,n[u>>2]=n[qr>>2],n[u+4>>2]=n[qr+4>>2],rLe(o,4530,u)|0,n[Lt>>2]=19,n[Lt+4>>2]=0,n[u>>2]=n[Lt>>2],n[u+4>>2]=n[Lt+4>>2],nLe(o,4542,u)|0,n[Ge>>2]=12,n[Ge+4>>2]=0,n[u>>2]=n[Ge>>2],n[u+4>>2]=n[Ge+4>>2],iLe(o,4554,u)|0,n[He>>2]=13,n[He+4>>2]=0,n[u>>2]=n[He>>2],n[u+4>>2]=n[He+4>>2],sLe(o,4568,u)|0,n[ct>>2]=2,n[ct+4>>2]=0,n[u>>2]=n[ct>>2],n[u+4>>2]=n[ct+4>>2],oLe(o,4578,u)|0,n[Ze>>2]=20,n[Ze+4>>2]=0,n[u>>2]=n[Ze>>2],n[u+4>>2]=n[Ze+4>>2],aLe(o,4587,u)|0,n[tt>>2]=22,n[tt+4>>2]=0,n[u>>2]=n[tt>>2],n[u+4>>2]=n[tt+4>>2],kr(o,4602,u)|0,n[Qe>>2]=23,n[Qe+4>>2]=0,n[u>>2]=n[Qe>>2],n[u+4>>2]=n[Qe+4>>2],kr(o,4619,u)|0,n[Le>>2]=14,n[Le+4>>2]=0,n[u>>2]=n[Le>>2],n[u+4>>2]=n[Le+4>>2],lLe(o,4629,u)|0,n[We>>2]=1,n[We+4>>2]=0,n[u>>2]=n[We>>2],n[u+4>>2]=n[We+4>>2],cLe(o,4637,u)|0,n[ae>>2]=4,n[ae+4>>2]=0,n[u>>2]=n[ae>>2],n[u+4>>2]=n[ae+4>>2],rp(o,4653,u)|0,n[G>>2]=5,n[G+4>>2]=0,n[u>>2]=n[G>>2],n[u+4>>2]=n[G+4>>2],rp(o,4669,u)|0,n[M>>2]=6,n[M+4>>2]=0,n[u>>2]=n[M>>2],n[u+4>>2]=n[M+4>>2],rp(o,4686,u)|0,n[_>>2]=7,n[_+4>>2]=0,n[u>>2]=n[_>>2],n[u+4>>2]=n[_+4>>2],rp(o,4701,u)|0,n[T>>2]=8,n[T+4>>2]=0,n[u>>2]=n[T>>2],n[u+4>>2]=n[T+4>>2],rp(o,4719,u)|0,n[k>>2]=9,n[k+4>>2]=0,n[u>>2]=n[k>>2],n[u+4>>2]=n[k+4>>2],rp(o,4736,u)|0,n[B>>2]=21,n[B+4>>2]=0,n[u>>2]=n[B>>2],n[u+4>>2]=n[B+4>>2],uLe(o,4754,u)|0,n[m>>2]=2,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],WP(o,4772,u)|0,n[d>>2]=3,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],WP(o,4790,u)|0,n[A>>2]=4,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],WP(o,4808,u)|0,I=l}function pt(o,l){o=o|0,l=l|0;var u=0;u=dHe()|0,n[o>>2]=u,mHe(u,l),Wh(n[o>>2]|0)}function Zt(o,l,u){return o=o|0,l=l|0,u=u|0,rHe(o,Bn(l)|0,u,0),o|0}function Sr(o,l,u){return o=o|0,l=l|0,u=u|0,U8e(o,Bn(l)|0,u,0),o|0}function Xn(o,l,u){return o=o|0,l=l|0,u=u|0,D8e(o,Bn(l)|0,u,0),o|0}function kr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u8e(o,l,d),I=A,o|0}function Rn(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],W3e(o,l,d),I=A,o|0}function Un(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],x3e(o,l,d),I=A,o|0}function zr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],p3e(o,l,d),I=A,o|0}function li(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],Z4e(o,l,d),I=A,o|0}function Pu(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],N4e(o,l,d),I=A,o|0}function yM(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],E4e(o,l,d),I=A,o|0}function ad(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],WUe(o,l,d),I=A,o|0}function rp(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],xUe(o,l,d),I=A,o|0}function WP(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],pUe(o,l,d),I=A,o|0}function rLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],Z_e(o,l,d),I=A,o|0}function nLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],N_e(o,l,d),I=A,o|0}function iLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],I_e(o,l,d),I=A,o|0}function sLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],s_e(o,l,d),I=A,o|0}function oLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],jMe(o,l,d),I=A,o|0}function aLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],SMe(o,l,d),I=A,o|0}function lLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],cMe(o,l,d),I=A,o|0}function cLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],WLe(o,l,d),I=A,o|0}function uLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],fLe(o,l,d),I=A,o|0}function fLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],ALe(o,u,d,1),I=A}function Bn(o){return o=o|0,o|0}function ALe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=EM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=pLe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,hLe(m,A)|0,A),I=d}function EM(){var o=0,l=0;if(s[7616]|0||(eZ(9136),gr(24,9136,U|0)|0,l=7616,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9136)|0)){o=9136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));eZ(9136)}return 9136}function pLe(o){return o=o|0,0}function hLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=EM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],$z(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(mLe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function vn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0;B=I,I=I+32|0,ae=B+24|0,G=B+20|0,T=B+16|0,M=B+12|0,_=B+8|0,k=B+4|0,We=B,n[G>>2]=l,n[T>>2]=u,n[M>>2]=A,n[_>>2]=d,n[k>>2]=m,m=o+28|0,n[We>>2]=n[m>>2],n[ae>>2]=n[We>>2],gLe(o+24|0,ae,G,M,_,T,k)|0,n[m>>2]=n[n[m>>2]>>2],I=B}function gLe(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,o=dLe(l)|0,l=Jt(24)|0,Xz(l+4|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0,n[B>>2]|0),n[l>>2]=n[o>>2],n[o>>2]=l,l|0}function dLe(o){return o=o|0,n[o>>2]|0}function Xz(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function yr(o,l){return o=o|0,l=l|0,l|o|0}function $z(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function mLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=yLe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,ELe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],$z(m,A,u),n[T>>2]=(n[T>>2]|0)+12,ILe(o,k),CLe(k),I=_;return}}function yLe(o){return o=o|0,357913941}function ELe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function ILe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function CLe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function eZ(o){o=o|0,vLe(o)}function wLe(o){o=o|0,BLe(o+24|0)}function Ur(o){return o=o|0,n[o>>2]|0}function BLe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function vLe(o){o=o|0;var l=0;l=en()|0,tn(o,2,3,l,SLe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function en(){return 9228}function SLe(){return 1140}function DLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=bLe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=PLe(l,A)|0,I=u,l|0}function tn(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,n[o>>2]=l,n[o+4>>2]=u,n[o+8>>2]=A,n[o+12>>2]=d,n[o+16>>2]=m}function bLe(o){return o=o|0,(n[(EM()|0)+24>>2]|0)+(o*12|0)|0}function PLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+48|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),ap[u&31](A,o),A=xLe(A)|0,I=d,A|0}function xLe(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=IM(tZ()|0)|0,A?(CM(l,A),wM(u,l),kLe(o,u),o=BM(l)|0):o=QLe(o)|0,I=d,o|0}function tZ(){var o=0;return s[7632]|0||(HLe(9184),gr(25,9184,U|0)|0,o=7632,n[o>>2]=1,n[o+4>>2]=0),9184}function IM(o){return o=o|0,n[o+36>>2]|0}function CM(o,l){o=o|0,l=l|0,n[o>>2]=l,n[o+4>>2]=o,n[o+8>>2]=0}function wM(o,l){o=o|0,l=l|0,n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=0}function kLe(o,l){o=o|0,l=l|0,NLe(l,o,o+8|0,o+16|0,o+24|0,o+32|0,o+40|0)|0}function BM(o){return o=o|0,n[(n[o+4>>2]|0)+8>>2]|0}function QLe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;T=I,I=I+16|0,u=T+4|0,A=T,d=Fl(8)|0,m=d,B=Jt(48)|0,k=B,l=k+48|0;do n[k>>2]=n[o>>2],k=k+4|0,o=o+4|0;while((k|0)<(l|0));return l=m+4|0,n[l>>2]=B,k=Jt(8)|0,B=n[l>>2]|0,n[A>>2]=0,n[u>>2]=n[A>>2],rZ(k,B,u),n[d>>2]=k,I=T,m|0}function rZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1092,n[u+12>>2]=l,n[o+4>>2]=u}function TLe(o){o=o|0,$y(o),yt(o)}function RLe(o){o=o|0,o=n[o+12>>2]|0,o|0&&yt(o)}function FLe(o){o=o|0,yt(o)}function NLe(o,l,u,A,d,m,B){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,m=OLe(n[o>>2]|0,l,u,A,d,m,B)|0,B=o+4|0,n[(n[B>>2]|0)+8>>2]=m,n[(n[B>>2]|0)+8>>2]|0}function OLe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0;var k=0,T=0;return k=I,I=I+16|0,T=k,Nl(T),o=Ls(o)|0,B=LLe(o,+E[l>>3],+E[u>>3],+E[A>>3],+E[d>>3],+E[m>>3],+E[B>>3])|0,Ol(T),I=k,B|0}function LLe(o,l,u,A,d,m,B){o=o|0,l=+l,u=+u,A=+A,d=+d,m=+m,B=+B;var k=0;return k=ma(MLe()|0)|0,l=+Ja(l),u=+Ja(u),A=+Ja(A),d=+Ja(d),m=+Ja(m),no(0,k|0,o|0,+l,+u,+A,+d,+m,+ +Ja(B))|0}function MLe(){var o=0;return s[7624]|0||(_Le(9172),o=7624,n[o>>2]=1,n[o+4>>2]=0),9172}function _Le(o){o=o|0,Ro(o,ULe()|0,6)}function ULe(){return 1112}function HLe(o){o=o|0,Uh(o)}function jLe(o){o=o|0,nZ(o+24|0),iZ(o+16|0)}function nZ(o){o=o|0,GLe(o)}function iZ(o){o=o|0,qLe(o)}function qLe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,yt(u);while(l|0);n[o>>2]=0}function GLe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,yt(u);while(l|0);n[o>>2]=0}function Uh(o){o=o|0;var l=0;n[o+16>>2]=0,n[o+20>>2]=0,l=o+24|0,n[l>>2]=0,n[o+28>>2]=l,n[o+36>>2]=0,s[o+40>>0]=0,s[o+41>>0]=0}function WLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],YLe(o,u,d,0),I=A}function YLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=vM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=VLe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,KLe(m,A)|0,A),I=d}function vM(){var o=0,l=0;if(s[7640]|0||(oZ(9232),gr(26,9232,U|0)|0,l=7640,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9232)|0)){o=9232,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));oZ(9232)}return 9232}function VLe(o){return o=o|0,0}function KLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=vM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],sZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(JLe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function sZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function JLe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=zLe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,ZLe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],sZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,XLe(o,k),$Le(k),I=_;return}}function zLe(o){return o=o|0,357913941}function ZLe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function XLe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function $Le(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function oZ(o){o=o|0,rMe(o)}function eMe(o){o=o|0,tMe(o+24|0)}function tMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function rMe(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,nMe()|0,3),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function nMe(){return 1144}function iMe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,B=m+8|0,k=m,T=sMe(o)|0,o=n[T+4>>2]|0,n[k>>2]=n[T>>2],n[k+4>>2]=o,n[B>>2]=n[k>>2],n[B+4>>2]=n[k+4>>2],oMe(l,B,u,A,d),I=m}function sMe(o){return o=o|0,(n[(vM()|0)+24>>2]|0)+(o*12|0)|0}function oMe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0;var m=0,B=0,k=0,T=0,_=0;_=I,I=I+16|0,B=_+2|0,k=_+1|0,T=_,m=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(m=n[(n[o>>2]|0)+m>>2]|0),Tf(B,u),u=+Rf(B,u),Tf(k,A),A=+Rf(k,A),np(T,d),T=ip(T,d)|0,C$[m&1](o,u,A,T),I=_}function Tf(o,l){o=o|0,l=+l}function Rf(o,l){return o=o|0,l=+l,+ +lMe(l)}function np(o,l){o=o|0,l=l|0}function ip(o,l){return o=o|0,l=l|0,aMe(l)|0}function aMe(o){return o=o|0,o|0}function lMe(o){return o=+o,+o}function cMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],uMe(o,u,d,1),I=A}function uMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=SM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=fMe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,AMe(m,A)|0,A),I=d}function SM(){var o=0,l=0;if(s[7648]|0||(lZ(9268),gr(27,9268,U|0)|0,l=7648,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9268)|0)){o=9268,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));lZ(9268)}return 9268}function fMe(o){return o=o|0,0}function AMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=SM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],aZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(pMe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function aZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function pMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=hMe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,gMe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],aZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,dMe(o,k),mMe(k),I=_;return}}function hMe(o){return o=o|0,357913941}function gMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function dMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function mMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function lZ(o){o=o|0,IMe(o)}function yMe(o){o=o|0,EMe(o+24|0)}function EMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function IMe(o){o=o|0;var l=0;l=en()|0,tn(o,2,4,l,CMe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function CMe(){return 1160}function wMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=BMe(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=vMe(l,A)|0,I=u,l|0}function BMe(o){return o=o|0,(n[(SM()|0)+24>>2]|0)+(o*12|0)|0}function vMe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),cZ(dd[u&31](o)|0)|0}function cZ(o){return o=o|0,o&1|0}function SMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],DMe(o,u,d,0),I=A}function DMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=DM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=bMe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,PMe(m,A)|0,A),I=d}function DM(){var o=0,l=0;if(s[7656]|0||(fZ(9304),gr(28,9304,U|0)|0,l=7656,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9304)|0)){o=9304,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));fZ(9304)}return 9304}function bMe(o){return o=o|0,0}function PMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=DM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],uZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(xMe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function uZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function xMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=kMe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,QMe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],uZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,TMe(o,k),RMe(k),I=_;return}}function kMe(o){return o=o|0,357913941}function QMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function TMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function RMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function fZ(o){o=o|0,OMe(o)}function FMe(o){o=o|0,NMe(o+24|0)}function NMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function OMe(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,LMe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function LMe(){return 1164}function MMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=_Me(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],UMe(l,d,u),I=A}function _Me(o){return o=o|0,(n[(DM()|0)+24>>2]|0)+(o*12|0)|0}function UMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Hh(d,u),u=jh(d,u)|0,ap[A&31](o,u),qh(d),I=m}function Hh(o,l){o=o|0,l=l|0,HMe(o,l)}function jh(o,l){return o=o|0,l=l|0,o|0}function qh(o){o=o|0,Df(o)}function HMe(o,l){o=o|0,l=l|0,bM(o,l)}function bM(o,l){o=o|0,l=l|0,n[o>>2]=l}function jMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],qMe(o,u,d,0),I=A}function qMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=PM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=GMe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,WMe(m,A)|0,A),I=d}function PM(){var o=0,l=0;if(s[7664]|0||(pZ(9340),gr(29,9340,U|0)|0,l=7664,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9340)|0)){o=9340,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));pZ(9340)}return 9340}function GMe(o){return o=o|0,0}function WMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=PM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],AZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(YMe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function AZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function YMe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=VMe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,KMe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],AZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,JMe(o,k),zMe(k),I=_;return}}function VMe(o){return o=o|0,357913941}function KMe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function JMe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function zMe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function pZ(o){o=o|0,$Me(o)}function ZMe(o){o=o|0,XMe(o+24|0)}function XMe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function $Me(o){o=o|0;var l=0;l=en()|0,tn(o,2,4,l,e_e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function e_e(){return 1180}function t_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=r_e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=n_e(l,d,u)|0,I=A,u|0}function r_e(o){return o=o|0,(n[(PM()|0)+24>>2]|0)+(o*12|0)|0}function n_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),ld(d,u),d=cd(d,u)|0,d=YP(v_[A&15](o,d)|0)|0,I=m,d|0}function ld(o,l){o=o|0,l=l|0}function cd(o,l){return o=o|0,l=l|0,i_e(l)|0}function YP(o){return o=o|0,o|0}function i_e(o){return o=o|0,o|0}function s_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],o_e(o,u,d,0),I=A}function o_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=xM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=a_e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,l_e(m,A)|0,A),I=d}function xM(){var o=0,l=0;if(s[7672]|0||(gZ(9376),gr(30,9376,U|0)|0,l=7672,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9376)|0)){o=9376,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));gZ(9376)}return 9376}function a_e(o){return o=o|0,0}function l_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=xM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],hZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(c_e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function hZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function c_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=u_e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,f_e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],hZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,A_e(o,k),p_e(k),I=_;return}}function u_e(o){return o=o|0,357913941}function f_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function A_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function p_e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function gZ(o){o=o|0,d_e(o)}function h_e(o){o=o|0,g_e(o+24|0)}function g_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function d_e(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,dZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function dZ(){return 1196}function m_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=y_e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=E_e(l,A)|0,I=u,l|0}function y_e(o){return o=o|0,(n[(xM()|0)+24>>2]|0)+(o*12|0)|0}function E_e(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),YP(dd[u&31](o)|0)|0}function I_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],C_e(o,u,d,1),I=A}function C_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=kM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=w_e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,B_e(m,A)|0,A),I=d}function kM(){var o=0,l=0;if(s[7680]|0||(yZ(9412),gr(31,9412,U|0)|0,l=7680,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9412)|0)){o=9412,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));yZ(9412)}return 9412}function w_e(o){return o=o|0,0}function B_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=kM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],mZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(v_e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function mZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function v_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=S_e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,D_e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],mZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,b_e(o,k),P_e(k),I=_;return}}function S_e(o){return o=o|0,357913941}function D_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function b_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function P_e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function yZ(o){o=o|0,Q_e(o)}function x_e(o){o=o|0,k_e(o+24|0)}function k_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function Q_e(o){o=o|0;var l=0;l=en()|0,tn(o,2,6,l,EZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function EZ(){return 1200}function T_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=R_e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=F_e(l,A)|0,I=u,l|0}function R_e(o){return o=o|0,(n[(kM()|0)+24>>2]|0)+(o*12|0)|0}function F_e(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),VP(dd[u&31](o)|0)|0}function VP(o){return o=o|0,o|0}function N_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],O_e(o,u,d,0),I=A}function O_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=QM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=L_e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,M_e(m,A)|0,A),I=d}function QM(){var o=0,l=0;if(s[7688]|0||(CZ(9448),gr(32,9448,U|0)|0,l=7688,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9448)|0)){o=9448,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));CZ(9448)}return 9448}function L_e(o){return o=o|0,0}function M_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=QM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],IZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(__e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function IZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function __e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=U_e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,H_e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],IZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,j_e(o,k),q_e(k),I=_;return}}function U_e(o){return o=o|0,357913941}function H_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function j_e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function q_e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function CZ(o){o=o|0,Y_e(o)}function G_e(o){o=o|0,W_e(o+24|0)}function W_e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function Y_e(o){o=o|0;var l=0;l=en()|0,tn(o,2,6,l,wZ()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function wZ(){return 1204}function V_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=K_e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],J_e(l,d,u),I=A}function K_e(o){return o=o|0,(n[(QM()|0)+24>>2]|0)+(o*12|0)|0}function J_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),TM(d,u),d=RM(d,u)|0,ap[A&31](o,d),I=m}function TM(o,l){o=o|0,l=l|0}function RM(o,l){return o=o|0,l=l|0,z_e(l)|0}function z_e(o){return o=o|0,o|0}function Z_e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],X_e(o,u,d,0),I=A}function X_e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=FM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=$_e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,eUe(m,A)|0,A),I=d}function FM(){var o=0,l=0;if(s[7696]|0||(vZ(9484),gr(33,9484,U|0)|0,l=7696,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9484)|0)){o=9484,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));vZ(9484)}return 9484}function $_e(o){return o=o|0,0}function eUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=FM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],BZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(tUe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function BZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function tUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=rUe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,nUe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],BZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,iUe(o,k),sUe(k),I=_;return}}function rUe(o){return o=o|0,357913941}function nUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function iUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function sUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function vZ(o){o=o|0,lUe(o)}function oUe(o){o=o|0,aUe(o+24|0)}function aUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function lUe(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,cUe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function cUe(){return 1212}function uUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=fUe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],AUe(l,m,u,A),I=d}function fUe(o){return o=o|0,(n[(FM()|0)+24>>2]|0)+(o*12|0)|0}function AUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),TM(m,u),m=RM(m,u)|0,ld(B,A),B=cd(B,A)|0,L2[d&15](o,m,B),I=k}function pUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],hUe(o,u,d,1),I=A}function hUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=NM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=gUe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,dUe(m,A)|0,A),I=d}function NM(){var o=0,l=0;if(s[7704]|0||(DZ(9520),gr(34,9520,U|0)|0,l=7704,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9520)|0)){o=9520,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));DZ(9520)}return 9520}function gUe(o){return o=o|0,0}function dUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=NM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],SZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(mUe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function SZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function mUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=yUe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,EUe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],SZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,IUe(o,k),CUe(k),I=_;return}}function yUe(o){return o=o|0,357913941}function EUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function IUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function CUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function DZ(o){o=o|0,vUe(o)}function wUe(o){o=o|0,BUe(o+24|0)}function BUe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function vUe(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,SUe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function SUe(){return 1224}function DUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;return d=I,I=I+16|0,m=d+8|0,B=d,k=bUe(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],A=+PUe(l,m,u),I=d,+A}function bUe(o){return o=o|0,(n[(NM()|0)+24>>2]|0)+(o*12|0)|0}function PUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(d,u),d=ip(d,u)|0,B=+Qf(+B$[A&7](o,d)),I=m,+B}function xUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],kUe(o,u,d,1),I=A}function kUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=OM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=QUe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,TUe(m,A)|0,A),I=d}function OM(){var o=0,l=0;if(s[7712]|0||(PZ(9556),gr(35,9556,U|0)|0,l=7712,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9556)|0)){o=9556,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));PZ(9556)}return 9556}function QUe(o){return o=o|0,0}function TUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=OM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],bZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(RUe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function bZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function RUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=FUe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,NUe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],bZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,OUe(o,k),LUe(k),I=_;return}}function FUe(o){return o=o|0,357913941}function NUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function OUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function LUe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function PZ(o){o=o|0,UUe(o)}function MUe(o){o=o|0,_Ue(o+24|0)}function _Ue(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function UUe(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,HUe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function HUe(){return 1232}function jUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=qUe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=+GUe(l,d),I=A,+u}function qUe(o){return o=o|0,(n[(OM()|0)+24>>2]|0)+(o*12|0)|0}function GUe(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),+ +Qf(+w$[u&15](o))}function WUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],YUe(o,u,d,1),I=A}function YUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=LM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=VUe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,KUe(m,A)|0,A),I=d}function LM(){var o=0,l=0;if(s[7720]|0||(kZ(9592),gr(36,9592,U|0)|0,l=7720,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9592)|0)){o=9592,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));kZ(9592)}return 9592}function VUe(o){return o=o|0,0}function KUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=LM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],xZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(JUe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function xZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function JUe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=zUe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,ZUe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],xZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,XUe(o,k),$Ue(k),I=_;return}}function zUe(o){return o=o|0,357913941}function ZUe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function XUe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function $Ue(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function kZ(o){o=o|0,r4e(o)}function e4e(o){o=o|0,t4e(o+24|0)}function t4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function r4e(o){o=o|0;var l=0;l=en()|0,tn(o,2,7,l,n4e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function n4e(){return 1276}function i4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=s4e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=o4e(l,A)|0,I=u,l|0}function s4e(o){return o=o|0,(n[(LM()|0)+24>>2]|0)+(o*12|0)|0}function o4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;return d=I,I=I+16|0,A=d,u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),ap[u&31](A,o),A=QZ(A)|0,I=d,A|0}function QZ(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=IM(TZ()|0)|0,A?(CM(l,A),wM(u,l),a4e(o,u),o=BM(l)|0):o=l4e(o)|0,I=d,o|0}function TZ(){var o=0;return s[7736]|0||(y4e(9640),gr(25,9640,U|0)|0,o=7736,n[o>>2]=1,n[o+4>>2]=0),9640}function a4e(o,l){o=o|0,l=l|0,A4e(l,o,o+8|0)|0}function l4e(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Fl(8)|0,l=A,k=Jt(16)|0,n[k>>2]=n[o>>2],n[k+4>>2]=n[o+4>>2],n[k+8>>2]=n[o+8>>2],n[k+12>>2]=n[o+12>>2],m=l+4|0,n[m>>2]=k,o=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],MM(o,m,d),n[A>>2]=o,I=u,l|0}function MM(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1244,n[u+12>>2]=l,n[o+4>>2]=u}function c4e(o){o=o|0,$y(o),yt(o)}function u4e(o){o=o|0,o=n[o+12>>2]|0,o|0&&yt(o)}function f4e(o){o=o|0,yt(o)}function A4e(o,l,u){return o=o|0,l=l|0,u=u|0,l=p4e(n[o>>2]|0,l,u)|0,u=o+4|0,n[(n[u>>2]|0)+8>>2]=l,n[(n[u>>2]|0)+8>>2]|0}function p4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return A=I,I=I+16|0,d=A,Nl(d),o=Ls(o)|0,u=h4e(o,n[l>>2]|0,+E[u>>3])|0,Ol(d),I=A,u|0}function h4e(o,l,u){o=o|0,l=l|0,u=+u;var A=0;return A=ma(g4e()|0)|0,l=Yy(l)|0,lu(0,A|0,o|0,l|0,+ +Ja(u))|0}function g4e(){var o=0;return s[7728]|0||(d4e(9628),o=7728,n[o>>2]=1,n[o+4>>2]=0),9628}function d4e(o){o=o|0,Ro(o,m4e()|0,2)}function m4e(){return 1264}function y4e(o){o=o|0,Uh(o)}function E4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],I4e(o,u,d,1),I=A}function I4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=_M()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=C4e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,w4e(m,A)|0,A),I=d}function _M(){var o=0,l=0;if(s[7744]|0||(FZ(9684),gr(37,9684,U|0)|0,l=7744,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9684)|0)){o=9684,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));FZ(9684)}return 9684}function C4e(o){return o=o|0,0}function w4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=_M()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],RZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(B4e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function RZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function B4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=v4e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,S4e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],RZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,D4e(o,k),b4e(k),I=_;return}}function v4e(o){return o=o|0,357913941}function S4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function D4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function b4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function FZ(o){o=o|0,k4e(o)}function P4e(o){o=o|0,x4e(o+24|0)}function x4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function k4e(o){o=o|0;var l=0;l=en()|0,tn(o,2,5,l,Q4e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Q4e(){return 1280}function T4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=R4e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=F4e(l,d,u)|0,I=A,u|0}function R4e(o){return o=o|0,(n[(_M()|0)+24>>2]|0)+(o*12|0)|0}function F4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return B=I,I=I+32|0,d=B,m=B+16|0,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(m,u),m=ip(m,u)|0,L2[A&15](d,o,m),m=QZ(d)|0,I=B,m|0}function N4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],O4e(o,u,d,1),I=A}function O4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=UM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=L4e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,M4e(m,A)|0,A),I=d}function UM(){var o=0,l=0;if(s[7752]|0||(OZ(9720),gr(38,9720,U|0)|0,l=7752,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9720)|0)){o=9720,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));OZ(9720)}return 9720}function L4e(o){return o=o|0,0}function M4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=UM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],NZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(_4e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function NZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function _4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=U4e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,H4e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],NZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,j4e(o,k),q4e(k),I=_;return}}function U4e(o){return o=o|0,357913941}function H4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function j4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function q4e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function OZ(o){o=o|0,Y4e(o)}function G4e(o){o=o|0,W4e(o+24|0)}function W4e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function Y4e(o){o=o|0;var l=0;l=en()|0,tn(o,2,8,l,V4e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function V4e(){return 1288}function K4e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;return u=I,I=I+16|0,A=u+8|0,d=u,m=J4e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],l=z4e(l,A)|0,I=u,l|0}function J4e(o){return o=o|0,(n[(UM()|0)+24>>2]|0)+(o*12|0)|0}function z4e(o,l){o=o|0,l=l|0;var u=0;return u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),od(dd[u&31](o)|0)|0}function Z4e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],X4e(o,u,d,0),I=A}function X4e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=HM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=$4e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,e3e(m,A)|0,A),I=d}function HM(){var o=0,l=0;if(s[7760]|0||(MZ(9756),gr(39,9756,U|0)|0,l=7760,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9756)|0)){o=9756,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));MZ(9756)}return 9756}function $4e(o){return o=o|0,0}function e3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=HM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],LZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(t3e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function LZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function t3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=r3e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,n3e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],LZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,i3e(o,k),s3e(k),I=_;return}}function r3e(o){return o=o|0,357913941}function n3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function i3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function s3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function MZ(o){o=o|0,l3e(o)}function o3e(o){o=o|0,a3e(o+24|0)}function a3e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function l3e(o){o=o|0;var l=0;l=en()|0,tn(o,2,8,l,c3e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function c3e(){return 1292}function u3e(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=f3e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],A3e(l,d,u),I=A}function f3e(o){return o=o|0,(n[(HM()|0)+24>>2]|0)+(o*12|0)|0}function A3e(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),Tf(d,u),u=+Rf(d,u),E$[A&31](o,u),I=m}function p3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],h3e(o,u,d,0),I=A}function h3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=jM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=g3e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,d3e(m,A)|0,A),I=d}function jM(){var o=0,l=0;if(s[7768]|0||(UZ(9792),gr(40,9792,U|0)|0,l=7768,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9792)|0)){o=9792,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));UZ(9792)}return 9792}function g3e(o){return o=o|0,0}function d3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=jM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],_Z(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(m3e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function _Z(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function m3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=y3e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,E3e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],_Z(m,A,u),n[T>>2]=(n[T>>2]|0)+12,I3e(o,k),C3e(k),I=_;return}}function y3e(o){return o=o|0,357913941}function E3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function I3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function C3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function UZ(o){o=o|0,v3e(o)}function w3e(o){o=o|0,B3e(o+24|0)}function B3e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function v3e(o){o=o|0;var l=0;l=en()|0,tn(o,2,1,l,S3e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function S3e(){return 1300}function D3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=b3e(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],P3e(l,m,u,A),I=d}function b3e(o){return o=o|0,(n[(jM()|0)+24>>2]|0)+(o*12|0)|0}function P3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),np(m,u),m=ip(m,u)|0,Tf(B,A),A=+Rf(B,A),b$[d&15](o,m,A),I=k}function x3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],k3e(o,u,d,0),I=A}function k3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=qM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=Q3e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,T3e(m,A)|0,A),I=d}function qM(){var o=0,l=0;if(s[7776]|0||(jZ(9828),gr(41,9828,U|0)|0,l=7776,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9828)|0)){o=9828,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));jZ(9828)}return 9828}function Q3e(o){return o=o|0,0}function T3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=qM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],HZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(R3e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function HZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function R3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=F3e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,N3e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],HZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,O3e(o,k),L3e(k),I=_;return}}function F3e(o){return o=o|0,357913941}function N3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function O3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function L3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function jZ(o){o=o|0,U3e(o)}function M3e(o){o=o|0,_3e(o+24|0)}function _3e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function U3e(o){o=o|0;var l=0;l=en()|0,tn(o,2,7,l,H3e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function H3e(){return 1312}function j3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=q3e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],G3e(l,d,u),I=A}function q3e(o){return o=o|0,(n[(qM()|0)+24>>2]|0)+(o*12|0)|0}function G3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(d,u),d=ip(d,u)|0,ap[A&31](o,d),I=m}function W3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],Y3e(o,u,d,0),I=A}function Y3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=GM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=V3e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,K3e(m,A)|0,A),I=d}function GM(){var o=0,l=0;if(s[7784]|0||(GZ(9864),gr(42,9864,U|0)|0,l=7784,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9864)|0)){o=9864,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));GZ(9864)}return 9864}function V3e(o){return o=o|0,0}function K3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=GM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],qZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(J3e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function qZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function J3e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=z3e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,Z3e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],qZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,X3e(o,k),$3e(k),I=_;return}}function z3e(o){return o=o|0,357913941}function Z3e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function X3e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function $3e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function GZ(o){o=o|0,r8e(o)}function e8e(o){o=o|0,t8e(o+24|0)}function t8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function r8e(o){o=o|0;var l=0;l=en()|0,tn(o,2,8,l,n8e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function n8e(){return 1320}function i8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=s8e(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],o8e(l,d,u),I=A}function s8e(o){return o=o|0,(n[(GM()|0)+24>>2]|0)+(o*12|0)|0}function o8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),a8e(d,u),d=l8e(d,u)|0,ap[A&31](o,d),I=m}function a8e(o,l){o=o|0,l=l|0}function l8e(o,l){return o=o|0,l=l|0,c8e(l)|0}function c8e(o){return o=o|0,o|0}function u8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],f8e(o,u,d,0),I=A}function f8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=WM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=A8e(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,p8e(m,A)|0,A),I=d}function WM(){var o=0,l=0;if(s[7792]|0||(YZ(9900),gr(43,9900,U|0)|0,l=7792,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9900)|0)){o=9900,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));YZ(9900)}return 9900}function A8e(o){return o=o|0,0}function p8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=WM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],WZ(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(h8e(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function WZ(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function h8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=g8e(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,d8e(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],WZ(m,A,u),n[T>>2]=(n[T>>2]|0)+12,m8e(o,k),y8e(k),I=_;return}}function g8e(o){return o=o|0,357913941}function d8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function m8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function y8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function YZ(o){o=o|0,C8e(o)}function E8e(o){o=o|0,I8e(o+24|0)}function I8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function C8e(o){o=o|0;var l=0;l=en()|0,tn(o,2,22,l,w8e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function w8e(){return 1344}function B8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0;u=I,I=I+16|0,A=u+8|0,d=u,m=v8e(o)|0,o=n[m+4>>2]|0,n[d>>2]=n[m>>2],n[d+4>>2]=o,n[A>>2]=n[d>>2],n[A+4>>2]=n[d+4>>2],S8e(l,A),I=u}function v8e(o){return o=o|0,(n[(WM()|0)+24>>2]|0)+(o*12|0)|0}function S8e(o,l){o=o|0,l=l|0;var u=0;u=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(u=n[(n[o>>2]|0)+u>>2]|0),op[u&127](o)}function D8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=YM()|0,o=b8e(u)|0,vn(m,l,d,o,P8e(u,A)|0,A)}function YM(){var o=0,l=0;if(s[7800]|0||(KZ(9936),gr(44,9936,U|0)|0,l=7800,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9936)|0)){o=9936,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));KZ(9936)}return 9936}function b8e(o){return o=o|0,o|0}function P8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=YM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(VZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(x8e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function VZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function x8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=k8e(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,Q8e(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,VZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,T8e(o,d),R8e(d),I=k;return}}function k8e(o){return o=o|0,536870911}function Q8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function T8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function R8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function KZ(o){o=o|0,O8e(o)}function F8e(o){o=o|0,N8e(o+24|0)}function N8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function O8e(o){o=o|0;var l=0;l=en()|0,tn(o,1,23,l,wZ()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function L8e(o,l){o=o|0,l=l|0,_8e(n[(M8e(o)|0)>>2]|0,l)}function M8e(o){return o=o|0,(n[(YM()|0)+24>>2]|0)+(o<<3)|0}function _8e(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,TM(A,l),l=RM(A,l)|0,op[o&127](l),I=u}function U8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=VM()|0,o=H8e(u)|0,vn(m,l,d,o,j8e(u,A)|0,A)}function VM(){var o=0,l=0;if(s[7808]|0||(zZ(9972),gr(45,9972,U|0)|0,l=7808,n[l>>2]=1,n[l+4>>2]=0),!(Ur(9972)|0)){o=9972,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));zZ(9972)}return 9972}function H8e(o){return o=o|0,o|0}function j8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=VM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(JZ(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(q8e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function JZ(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function q8e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=G8e(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,W8e(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,JZ(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,Y8e(o,d),V8e(d),I=k;return}}function G8e(o){return o=o|0,536870911}function W8e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function Y8e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function V8e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function zZ(o){o=o|0,z8e(o)}function K8e(o){o=o|0,J8e(o+24|0)}function J8e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function z8e(o){o=o|0;var l=0;l=en()|0,tn(o,1,9,l,Z8e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Z8e(){return 1348}function X8e(o,l){return o=o|0,l=l|0,eHe(n[($8e(o)|0)>>2]|0,l)|0}function $8e(o){return o=o|0,(n[(VM()|0)+24>>2]|0)+(o<<3)|0}function eHe(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,ZZ(A,l),l=XZ(A,l)|0,l=YP(dd[o&31](l)|0)|0,I=u,l|0}function ZZ(o,l){o=o|0,l=l|0}function XZ(o,l){return o=o|0,l=l|0,tHe(l)|0}function tHe(o){return o=o|0,o|0}function rHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=KM()|0,o=nHe(u)|0,vn(m,l,d,o,iHe(u,A)|0,A)}function KM(){var o=0,l=0;if(s[7816]|0||(eX(10008),gr(46,10008,U|0)|0,l=7816,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10008)|0)){o=10008,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));eX(10008)}return 10008}function nHe(o){return o=o|0,o|0}function iHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=KM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?($Z(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(sHe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function $Z(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function sHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=oHe(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,aHe(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,$Z(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,lHe(o,d),cHe(d),I=k;return}}function oHe(o){return o=o|0,536870911}function aHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function lHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function cHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function eX(o){o=o|0,AHe(o)}function uHe(o){o=o|0,fHe(o+24|0)}function fHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function AHe(o){o=o|0;var l=0;l=en()|0,tn(o,1,15,l,dZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function pHe(o){return o=o|0,gHe(n[(hHe(o)|0)>>2]|0)|0}function hHe(o){return o=o|0,(n[(KM()|0)+24>>2]|0)+(o<<3)|0}function gHe(o){return o=o|0,YP(ax[o&7]()|0)|0}function dHe(){var o=0;return s[7832]|0||(vHe(10052),gr(25,10052,U|0)|0,o=7832,n[o>>2]=1,n[o+4>>2]=0),10052}function mHe(o,l){o=o|0,l=l|0,n[o>>2]=yHe()|0,n[o+4>>2]=EHe()|0,n[o+12>>2]=l,n[o+8>>2]=IHe()|0,n[o+32>>2]=2}function yHe(){return 11709}function EHe(){return 1188}function IHe(){return KP()|0}function CHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(wHe(u),yt(u)):l|0&&(Oy(l),yt(l))}function Gh(o,l){return o=o|0,l=l|0,l&o|0}function wHe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function KP(){var o=0;return s[7824]|0||(n[2511]=BHe()|0,n[2512]=0,o=7824,n[o>>2]=1,n[o+4>>2]=0),10044}function BHe(){return 0}function vHe(o){o=o|0,Uh(o)}function SHe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0;l=I,I=I+32|0,u=l+24|0,m=l+16|0,d=l+8|0,A=l,DHe(o,4827),bHe(o,4834,3)|0,PHe(o,3682,47)|0,n[m>>2]=9,n[m+4>>2]=0,n[u>>2]=n[m>>2],n[u+4>>2]=n[m+4>>2],xHe(o,4841,u)|0,n[d>>2]=1,n[d+4>>2]=0,n[u>>2]=n[d>>2],n[u+4>>2]=n[d+4>>2],kHe(o,4871,u)|0,n[A>>2]=10,n[A+4>>2]=0,n[u>>2]=n[A>>2],n[u+4>>2]=n[A+4>>2],QHe(o,4891,u)|0,I=l}function DHe(o,l){o=o|0,l=l|0;var u=0;u=c6e()|0,n[o>>2]=u,u6e(u,l),Wh(n[o>>2]|0)}function bHe(o,l,u){return o=o|0,l=l|0,u=u|0,Vje(o,Bn(l)|0,u,0),o|0}function PHe(o,l,u){return o=o|0,l=l|0,u=u|0,Tje(o,Bn(l)|0,u,0),o|0}function xHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],pje(o,l,d),I=A,o|0}function kHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],KHe(o,l,d),I=A,o|0}function QHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=n[u+4>>2]|0,n[m>>2]=n[u>>2],n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],THe(o,l,d),I=A,o|0}function THe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],RHe(o,u,d,1),I=A}function RHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=JM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=FHe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,NHe(m,A)|0,A),I=d}function JM(){var o=0,l=0;if(s[7840]|0||(rX(10100),gr(48,10100,U|0)|0,l=7840,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10100)|0)){o=10100,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));rX(10100)}return 10100}function FHe(o){return o=o|0,0}function NHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=JM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],tX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(OHe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function tX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function OHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=LHe(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,MHe(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],tX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,_He(o,k),UHe(k),I=_;return}}function LHe(o){return o=o|0,357913941}function MHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function _He(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function UHe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function rX(o){o=o|0,qHe(o)}function HHe(o){o=o|0,jHe(o+24|0)}function jHe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function qHe(o){o=o|0;var l=0;l=en()|0,tn(o,2,6,l,GHe()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function GHe(){return 1364}function WHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;return A=I,I=I+16|0,d=A+8|0,m=A,B=YHe(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],u=VHe(l,d,u)|0,I=A,u|0}function YHe(o){return o=o|0,(n[(JM()|0)+24>>2]|0)+(o*12|0)|0}function VHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),np(d,u),d=ip(d,u)|0,d=cZ(v_[A&15](o,d)|0)|0,I=m,d|0}function KHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],JHe(o,u,d,0),I=A}function JHe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=zM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=zHe(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,ZHe(m,A)|0,A),I=d}function zM(){var o=0,l=0;if(s[7848]|0||(iX(10136),gr(49,10136,U|0)|0,l=7848,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10136)|0)){o=10136,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));iX(10136)}return 10136}function zHe(o){return o=o|0,0}function ZHe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=zM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],nX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(XHe(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function nX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function XHe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=$He(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,eje(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],nX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,tje(o,k),rje(k),I=_;return}}function $He(o){return o=o|0,357913941}function eje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function tje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function rje(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function iX(o){o=o|0,sje(o)}function nje(o){o=o|0,ije(o+24|0)}function ije(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function sje(o){o=o|0;var l=0;l=en()|0,tn(o,2,9,l,oje()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function oje(){return 1372}function aje(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,d=A+8|0,m=A,B=lje(o)|0,o=n[B+4>>2]|0,n[m>>2]=n[B>>2],n[m+4>>2]=o,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],cje(l,d,u),I=A}function lje(o){return o=o|0,(n[(zM()|0)+24>>2]|0)+(o*12|0)|0}function cje(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=Xe;m=I,I=I+16|0,d=m,A=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(A=n[(n[o>>2]|0)+A>>2]|0),uje(d,u),B=y(fje(d,u)),y$[A&1](o,B),I=m}function uje(o,l){o=o|0,l=+l}function fje(o,l){return o=o|0,l=+l,y(Aje(l))}function Aje(o){return o=+o,y(o)}function pje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,d=A+8|0,m=A,k=n[u>>2]|0,B=n[u+4>>2]|0,u=Bn(l)|0,n[m>>2]=k,n[m+4>>2]=B,n[d>>2]=n[m>>2],n[d+4>>2]=n[m+4>>2],hje(o,u,d,0),I=A}function hje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0,T=0,_=0,M=0;d=I,I=I+32|0,m=d+16|0,M=d+8|0,k=d,_=n[u>>2]|0,T=n[u+4>>2]|0,B=n[o>>2]|0,o=ZM()|0,n[M>>2]=_,n[M+4>>2]=T,n[m>>2]=n[M>>2],n[m+4>>2]=n[M+4>>2],u=gje(m)|0,n[k>>2]=_,n[k+4>>2]=T,n[m>>2]=n[k>>2],n[m+4>>2]=n[k+4>>2],vn(B,l,o,u,dje(m,A)|0,A),I=d}function ZM(){var o=0,l=0;if(s[7856]|0||(oX(10172),gr(50,10172,U|0)|0,l=7856,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10172)|0)){o=10172,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));oX(10172)}return 10172}function gje(o){return o=o|0,0}function dje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0;return M=I,I=I+32|0,d=M+24|0,B=M+16|0,k=M,T=M+8|0,m=n[o>>2]|0,A=n[o+4>>2]|0,n[k>>2]=m,n[k+4>>2]=A,G=ZM()|0,_=G+24|0,o=yr(l,4)|0,n[T>>2]=o,l=G+28|0,u=n[l>>2]|0,u>>>0<(n[G+32>>2]|0)>>>0?(n[B>>2]=m,n[B+4>>2]=A,n[d>>2]=n[B>>2],n[d+4>>2]=n[B+4>>2],sX(u,d,o),o=(n[l>>2]|0)+12|0,n[l>>2]=o):(mje(_,k,T),o=n[l>>2]|0),I=M,((o-(n[_>>2]|0)|0)/12|0)+-1|0}function sX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=n[l+4>>2]|0,n[o>>2]=n[l>>2],n[o+4>>2]=A,n[o+8>>2]=u}function mje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;if(_=I,I=I+48|0,A=_+32|0,B=_+24|0,k=_,T=o+4|0,d=(((n[T>>2]|0)-(n[o>>2]|0)|0)/12|0)+1|0,m=yje(o)|0,m>>>0>>0)sn(o);else{M=n[o>>2]|0,ae=((n[o+8>>2]|0)-M|0)/12|0,G=ae<<1,Eje(k,ae>>>0>>1>>>0?G>>>0>>0?d:G:m,((n[T>>2]|0)-M|0)/12|0,o+8|0),T=k+8|0,m=n[T>>2]|0,d=n[l+4>>2]|0,u=n[u>>2]|0,n[B>>2]=n[l>>2],n[B+4>>2]=d,n[A>>2]=n[B>>2],n[A+4>>2]=n[B+4>>2],sX(m,A,u),n[T>>2]=(n[T>>2]|0)+12,Ije(o,k),Cje(k),I=_;return}}function yje(o){return o=o|0,357913941}function Eje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>357913941)Nt();else{d=Jt(l*12|0)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u*12|0)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l*12|0)}function Ije(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(((d|0)/-12|0)*12|0)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function Cje(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~(((A+-12-l|0)>>>0)/12|0)*12|0)),o=n[o>>2]|0,o|0&&yt(o)}function oX(o){o=o|0,vje(o)}function wje(o){o=o|0,Bje(o+24|0)}function Bje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~(((l+-12-A|0)>>>0)/12|0)*12|0)),yt(u))}function vje(o){o=o|0;var l=0;l=en()|0,tn(o,2,3,l,Sje()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Sje(){return 1380}function Dje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+8|0,B=d,k=bje(o)|0,o=n[k+4>>2]|0,n[B>>2]=n[k>>2],n[B+4>>2]=o,n[m>>2]=n[B>>2],n[m+4>>2]=n[B+4>>2],Pje(l,m,u,A),I=d}function bje(o){return o=o|0,(n[(ZM()|0)+24>>2]|0)+(o*12|0)|0}function Pje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;k=I,I=I+16|0,m=k+1|0,B=k,d=n[l>>2]|0,l=n[l+4>>2]|0,o=o+(l>>1)|0,l&1&&(d=n[(n[o>>2]|0)+d>>2]|0),np(m,u),m=ip(m,u)|0,xje(B,A),B=kje(B,A)|0,L2[d&15](o,m,B),I=k}function xje(o,l){o=o|0,l=l|0}function kje(o,l){return o=o|0,l=l|0,Qje(l)|0}function Qje(o){return o=o|0,(o|0)!=0|0}function Tje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=XM()|0,o=Rje(u)|0,vn(m,l,d,o,Fje(u,A)|0,A)}function XM(){var o=0,l=0;if(s[7864]|0||(lX(10208),gr(51,10208,U|0)|0,l=7864,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10208)|0)){o=10208,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));lX(10208)}return 10208}function Rje(o){return o=o|0,o|0}function Fje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=XM()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(aX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(Nje(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function aX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function Nje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=Oje(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,Lje(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,aX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,Mje(o,d),_je(d),I=k;return}}function Oje(o){return o=o|0,536870911}function Lje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function Mje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _je(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function lX(o){o=o|0,jje(o)}function Uje(o){o=o|0,Hje(o+24|0)}function Hje(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function jje(o){o=o|0;var l=0;l=en()|0,tn(o,1,24,l,qje()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qje(){return 1392}function Gje(o,l){o=o|0,l=l|0,Yje(n[(Wje(o)|0)>>2]|0,l)}function Wje(o){return o=o|0,(n[(XM()|0)+24>>2]|0)+(o<<3)|0}function Yje(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,ZZ(A,l),l=XZ(A,l)|0,op[o&127](l),I=u}function Vje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=$M()|0,o=Kje(u)|0,vn(m,l,d,o,Jje(u,A)|0,A)}function $M(){var o=0,l=0;if(s[7872]|0||(uX(10244),gr(52,10244,U|0)|0,l=7872,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10244)|0)){o=10244,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));uX(10244)}return 10244}function Kje(o){return o=o|0,o|0}function Jje(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=$M()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(cX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(zje(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function cX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function zje(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=Zje(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,Xje(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,cX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,$je(o,d),e6e(d),I=k;return}}function Zje(o){return o=o|0,536870911}function Xje(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function $je(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function e6e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function uX(o){o=o|0,n6e(o)}function t6e(o){o=o|0,r6e(o+24|0)}function r6e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function n6e(o){o=o|0;var l=0;l=en()|0,tn(o,1,16,l,i6e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function i6e(){return 1400}function s6e(o){return o=o|0,a6e(n[(o6e(o)|0)>>2]|0)|0}function o6e(o){return o=o|0,(n[($M()|0)+24>>2]|0)+(o<<3)|0}function a6e(o){return o=o|0,l6e(ax[o&7]()|0)|0}function l6e(o){return o=o|0,o|0}function c6e(){var o=0;return s[7880]|0||(d6e(10280),gr(25,10280,U|0)|0,o=7880,n[o>>2]=1,n[o+4>>2]=0),10280}function u6e(o,l){o=o|0,l=l|0,n[o>>2]=f6e()|0,n[o+4>>2]=A6e()|0,n[o+12>>2]=l,n[o+8>>2]=p6e()|0,n[o+32>>2]=4}function f6e(){return 11711}function A6e(){return 1356}function p6e(){return KP()|0}function h6e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(g6e(u),yt(u)):l|0&&(zg(l),yt(l))}function g6e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function d6e(o){o=o|0,Uh(o)}function m6e(o){o=o|0,y6e(o,4920),E6e(o)|0,I6e(o)|0}function y6e(o,l){o=o|0,l=l|0;var u=0;u=TZ()|0,n[o>>2]=u,H6e(u,l),Wh(n[o>>2]|0)}function E6e(o){o=o|0;var l=0;return l=n[o>>2]|0,ud(l,Q6e()|0),o|0}function I6e(o){o=o|0;var l=0;return l=n[o>>2]|0,ud(l,C6e()|0),o|0}function C6e(){var o=0;return s[7888]|0||(fX(10328),gr(53,10328,U|0)|0,o=7888,n[o>>2]=1,n[o+4>>2]=0),Ur(10328)|0||fX(10328),10328}function ud(o,l){o=o|0,l=l|0,vn(o,0,l,0,0,0)}function fX(o){o=o|0,v6e(o),fd(o,10)}function w6e(o){o=o|0,B6e(o+24|0)}function B6e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function v6e(o){o=o|0;var l=0;l=en()|0,tn(o,5,1,l,P6e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function S6e(o,l,u){o=o|0,l=l|0,u=+u,D6e(o,l,u)}function fd(o,l){o=o|0,l=l|0,n[o+20>>2]=l}function D6e(o,l,u){o=o|0,l=l|0,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+16|0,m=A+8|0,k=A+13|0,d=A,B=A+12|0,np(k,l),n[m>>2]=ip(k,l)|0,Tf(B,u),E[d>>3]=+Rf(B,u),b6e(o,m,d),I=A}function b6e(o,l,u){o=o|0,l=l|0,u=u|0,Rl(o+8|0,n[l>>2]|0,+E[u>>3]),s[o+24>>0]=1}function P6e(){return 1404}function x6e(o,l){return o=o|0,l=+l,k6e(o,l)|0}function k6e(o,l){o=o|0,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,m=A+4|0,B=A+8|0,k=A,d=Fl(8)|0,u=d,T=Jt(16)|0,np(m,o),o=ip(m,o)|0,Tf(B,l),Rl(T,o,+Rf(B,l)),B=u+4|0,n[B>>2]=T,o=Jt(8)|0,B=n[B>>2]|0,n[k>>2]=0,n[m>>2]=n[k>>2],MM(o,B,m),n[d>>2]=o,I=A,u|0}function Q6e(){var o=0;return s[7896]|0||(AX(10364),gr(54,10364,U|0)|0,o=7896,n[o>>2]=1,n[o+4>>2]=0),Ur(10364)|0||AX(10364),10364}function AX(o){o=o|0,F6e(o),fd(o,55)}function T6e(o){o=o|0,R6e(o+24|0)}function R6e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function F6e(o){o=o|0;var l=0;l=en()|0,tn(o,5,4,l,M6e()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function N6e(o){o=o|0,O6e(o)}function O6e(o){o=o|0,L6e(o)}function L6e(o){o=o|0,pX(o+8|0),s[o+24>>0]=1}function pX(o){o=o|0,n[o>>2]=0,E[o+8>>3]=0}function M6e(){return 1424}function _6e(){return U6e()|0}function U6e(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Fl(8)|0,o=u,A=Jt(16)|0,pX(A),m=o+4|0,n[m>>2]=A,A=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],MM(A,m,d),n[u>>2]=A,I=l,o|0}function H6e(o,l){o=o|0,l=l|0,n[o>>2]=j6e()|0,n[o+4>>2]=q6e()|0,n[o+12>>2]=l,n[o+8>>2]=G6e()|0,n[o+32>>2]=5}function j6e(){return 11710}function q6e(){return 1416}function G6e(){return JP()|0}function W6e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(Y6e(u),yt(u)):l|0&&yt(l)}function Y6e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function JP(){var o=0;return s[7904]|0||(n[2600]=V6e()|0,n[2601]=0,o=7904,n[o>>2]=1,n[o+4>>2]=0),10400}function V6e(){return n[357]|0}function K6e(o){o=o|0,J6e(o,4926),z6e(o)|0}function J6e(o,l){o=o|0,l=l|0;var u=0;u=tZ()|0,n[o>>2]=u,aqe(u,l),Wh(n[o>>2]|0)}function z6e(o){o=o|0;var l=0;return l=n[o>>2]|0,ud(l,Z6e()|0),o|0}function Z6e(){var o=0;return s[7912]|0||(hX(10412),gr(56,10412,U|0)|0,o=7912,n[o>>2]=1,n[o+4>>2]=0),Ur(10412)|0||hX(10412),10412}function hX(o){o=o|0,eqe(o),fd(o,57)}function X6e(o){o=o|0,$6e(o+24|0)}function $6e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function eqe(o){o=o|0;var l=0;l=en()|0,tn(o,5,5,l,iqe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function tqe(o){o=o|0,rqe(o)}function rqe(o){o=o|0,nqe(o)}function nqe(o){o=o|0;var l=0,u=0;l=o+8|0,u=l+48|0;do n[l>>2]=0,l=l+4|0;while((l|0)<(u|0));s[o+56>>0]=1}function iqe(){return 1432}function sqe(){return oqe()|0}function oqe(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0;B=I,I=I+16|0,o=B+4|0,l=B,u=Fl(8)|0,A=u,d=Jt(48)|0,m=d,k=m+48|0;do n[m>>2]=0,m=m+4|0;while((m|0)<(k|0));return m=A+4|0,n[m>>2]=d,k=Jt(8)|0,m=n[m>>2]|0,n[l>>2]=0,n[o>>2]=n[l>>2],rZ(k,m,o),n[u>>2]=k,I=B,A|0}function aqe(o,l){o=o|0,l=l|0,n[o>>2]=lqe()|0,n[o+4>>2]=cqe()|0,n[o+12>>2]=l,n[o+8>>2]=uqe()|0,n[o+32>>2]=6}function lqe(){return 11704}function cqe(){return 1436}function uqe(){return JP()|0}function fqe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(Aqe(u),yt(u)):l|0&&yt(l)}function Aqe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function pqe(o){o=o|0,hqe(o,4933),gqe(o)|0,dqe(o)|0}function hqe(o,l){o=o|0,l=l|0;var u=0;u=Uqe()|0,n[o>>2]=u,Hqe(u,l),Wh(n[o>>2]|0)}function gqe(o){o=o|0;var l=0;return l=n[o>>2]|0,ud(l,kqe()|0),o|0}function dqe(o){o=o|0;var l=0;return l=n[o>>2]|0,ud(l,mqe()|0),o|0}function mqe(){var o=0;return s[7920]|0||(gX(10452),gr(58,10452,U|0)|0,o=7920,n[o>>2]=1,n[o+4>>2]=0),Ur(10452)|0||gX(10452),10452}function gX(o){o=o|0,Iqe(o),fd(o,1)}function yqe(o){o=o|0,Eqe(o+24|0)}function Eqe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function Iqe(o){o=o|0;var l=0;l=en()|0,tn(o,5,1,l,vqe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Cqe(o,l,u){o=o|0,l=+l,u=+u,wqe(o,l,u)}function wqe(o,l,u){o=o|0,l=+l,u=+u;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,m=A+8|0,k=A+17|0,d=A,B=A+16|0,Tf(k,l),E[m>>3]=+Rf(k,l),Tf(B,u),E[d>>3]=+Rf(B,u),Bqe(o,m,d),I=A}function Bqe(o,l,u){o=o|0,l=l|0,u=u|0,dX(o+8|0,+E[l>>3],+E[u>>3]),s[o+24>>0]=1}function dX(o,l,u){o=o|0,l=+l,u=+u,E[o>>3]=l,E[o+8>>3]=u}function vqe(){return 1472}function Sqe(o,l){return o=+o,l=+l,Dqe(o,l)|0}function Dqe(o,l){o=+o,l=+l;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+16|0,B=A+4|0,k=A+8|0,T=A,d=Fl(8)|0,u=d,m=Jt(16)|0,Tf(B,o),o=+Rf(B,o),Tf(k,l),dX(m,o,+Rf(k,l)),k=u+4|0,n[k>>2]=m,m=Jt(8)|0,k=n[k>>2]|0,n[T>>2]=0,n[B>>2]=n[T>>2],mX(m,k,B),n[d>>2]=m,I=A,u|0}function mX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1452,n[u+12>>2]=l,n[o+4>>2]=u}function bqe(o){o=o|0,$y(o),yt(o)}function Pqe(o){o=o|0,o=n[o+12>>2]|0,o|0&&yt(o)}function xqe(o){o=o|0,yt(o)}function kqe(){var o=0;return s[7928]|0||(yX(10488),gr(59,10488,U|0)|0,o=7928,n[o>>2]=1,n[o+4>>2]=0),Ur(10488)|0||yX(10488),10488}function yX(o){o=o|0,Rqe(o),fd(o,60)}function Qqe(o){o=o|0,Tqe(o+24|0)}function Tqe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function Rqe(o){o=o|0;var l=0;l=en()|0,tn(o,5,6,l,Lqe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function Fqe(o){o=o|0,Nqe(o)}function Nqe(o){o=o|0,Oqe(o)}function Oqe(o){o=o|0,EX(o+8|0),s[o+24>>0]=1}function EX(o){o=o|0,n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,n[o+12>>2]=0}function Lqe(){return 1492}function Mqe(){return _qe()|0}function _qe(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Fl(8)|0,o=u,A=Jt(16)|0,EX(A),m=o+4|0,n[m>>2]=A,A=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],mX(A,m,d),n[u>>2]=A,I=l,o|0}function Uqe(){var o=0;return s[7936]|0||(Vqe(10524),gr(25,10524,U|0)|0,o=7936,n[o>>2]=1,n[o+4>>2]=0),10524}function Hqe(o,l){o=o|0,l=l|0,n[o>>2]=jqe()|0,n[o+4>>2]=qqe()|0,n[o+12>>2]=l,n[o+8>>2]=Gqe()|0,n[o+32>>2]=7}function jqe(){return 11700}function qqe(){return 1484}function Gqe(){return JP()|0}function Wqe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(Yqe(u),yt(u)):l|0&&yt(l)}function Yqe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function Vqe(o){o=o|0,Uh(o)}function Kqe(o,l,u){o=o|0,l=l|0,u=u|0,o=Bn(l)|0,l=Jqe(u)|0,u=zqe(u,0)|0,DGe(o,l,u,e_()|0,0)}function Jqe(o){return o=o|0,o|0}function zqe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=e_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(CX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(nGe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function e_(){var o=0,l=0;if(s[7944]|0||(IX(10568),gr(61,10568,U|0)|0,l=7944,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10568)|0)){o=10568,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));IX(10568)}return 10568}function IX(o){o=o|0,$qe(o)}function Zqe(o){o=o|0,Xqe(o+24|0)}function Xqe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function $qe(o){o=o|0;var l=0;l=en()|0,tn(o,1,17,l,EZ()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function eGe(o){return o=o|0,rGe(n[(tGe(o)|0)>>2]|0)|0}function tGe(o){return o=o|0,(n[(e_()|0)+24>>2]|0)+(o<<3)|0}function rGe(o){return o=o|0,VP(ax[o&7]()|0)|0}function CX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function nGe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=iGe(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,sGe(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,CX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,oGe(o,d),aGe(d),I=k;return}}function iGe(o){return o=o|0,536870911}function sGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function oGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function aGe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function lGe(){cGe()}function cGe(){uGe(10604)}function uGe(o){o=o|0,fGe(o,4955)}function fGe(o,l){o=o|0,l=l|0;var u=0;u=AGe()|0,n[o>>2]=u,pGe(u,l),Wh(n[o>>2]|0)}function AGe(){var o=0;return s[7952]|0||(wGe(10612),gr(25,10612,U|0)|0,o=7952,n[o>>2]=1,n[o+4>>2]=0),10612}function pGe(o,l){o=o|0,l=l|0,n[o>>2]=mGe()|0,n[o+4>>2]=yGe()|0,n[o+12>>2]=l,n[o+8>>2]=EGe()|0,n[o+32>>2]=8}function Wh(o){o=o|0;var l=0,u=0;l=I,I=I+16|0,u=l,Ky()|0,n[u>>2]=o,hGe(10608,u),I=l}function Ky(){return s[11714]|0||(n[2652]=0,gr(62,10608,U|0)|0,s[11714]=1),10608}function hGe(o,l){o=o|0,l=l|0;var u=0;u=Jt(8)|0,n[u+4>>2]=n[l>>2],n[u>>2]=n[o>>2],n[o>>2]=u}function gGe(o){o=o|0,dGe(o)}function dGe(o){o=o|0;var l=0,u=0;if(l=n[o>>2]|0,l|0)do u=l,l=n[l>>2]|0,yt(u);while(l|0);n[o>>2]=0}function mGe(){return 11715}function yGe(){return 1496}function EGe(){return KP()|0}function IGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(CGe(u),yt(u)):l|0&&yt(l)}function CGe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function wGe(o){o=o|0,Uh(o)}function BGe(o,l){o=o|0,l=l|0;var u=0,A=0;Ky()|0,u=n[2652]|0;e:do if(u|0){for(;A=n[u+4>>2]|0,!(A|0&&!(r$(t_(A)|0,o)|0));)if(u=n[u>>2]|0,!u)break e;vGe(A,l)}while(!1)}function t_(o){return o=o|0,n[o+12>>2]|0}function vGe(o,l){o=o|0,l=l|0;var u=0;o=o+36|0,u=n[o>>2]|0,u|0&&(Df(u),yt(u)),u=Jt(4)|0,UP(u,l),n[o>>2]=u}function r_(){return s[11716]|0||(n[2664]=0,gr(63,10656,U|0)|0,s[11716]=1),10656}function wX(){var o=0;return s[11717]|0?o=n[2665]|0:(SGe(),n[2665]=1504,s[11717]=1,o=1504),o|0}function SGe(){s[11740]|0||(s[11718]=yr(yr(8,0)|0,0)|0,s[11719]=yr(yr(0,0)|0,0)|0,s[11720]=yr(yr(0,16)|0,0)|0,s[11721]=yr(yr(8,0)|0,0)|0,s[11722]=yr(yr(0,0)|0,0)|0,s[11723]=yr(yr(8,0)|0,0)|0,s[11724]=yr(yr(0,0)|0,0)|0,s[11725]=yr(yr(8,0)|0,0)|0,s[11726]=yr(yr(0,0)|0,0)|0,s[11727]=yr(yr(8,0)|0,0)|0,s[11728]=yr(yr(0,0)|0,0)|0,s[11729]=yr(yr(0,0)|0,32)|0,s[11730]=yr(yr(0,0)|0,32)|0,s[11740]=1)}function BX(){return 1572}function DGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0;m=I,I=I+32|0,M=m+16|0,_=m+12|0,T=m+8|0,k=m+4|0,B=m,n[M>>2]=o,n[_>>2]=l,n[T>>2]=u,n[k>>2]=A,n[B>>2]=d,r_()|0,bGe(10656,M,_,T,k,B),I=m}function bGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0;B=Jt(24)|0,Xz(B+4|0,n[l>>2]|0,n[u>>2]|0,n[A>>2]|0,n[d>>2]|0,n[m>>2]|0),n[B>>2]=n[o>>2],n[o>>2]=B}function vX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0;if(ct=I,I=I+32|0,Le=ct+20|0,Qe=ct+8|0,tt=ct+4|0,Ze=ct,l=n[l>>2]|0,l|0){We=Le+4|0,T=Le+8|0,_=Qe+4|0,M=Qe+8|0,G=Qe+8|0,ae=Le+8|0;do{if(B=l+4|0,k=n_(B)|0,k|0){if(d=Q2(k)|0,n[Le>>2]=0,n[We>>2]=0,n[T>>2]=0,A=(T2(k)|0)+1|0,PGe(Le,A),A|0)for(;A=A+-1|0,xu(Qe,n[d>>2]|0),m=n[We>>2]|0,m>>>0<(n[ae>>2]|0)>>>0?(n[m>>2]=n[Qe>>2],n[We>>2]=(n[We>>2]|0)+4):i_(Le,Qe),A;)d=d+4|0;A=R2(k)|0,n[Qe>>2]=0,n[_>>2]=0,n[M>>2]=0;e:do if(n[A>>2]|0)for(d=0,m=0;;){if((d|0)==(m|0)?xGe(Qe,A):(n[d>>2]=n[A>>2],n[_>>2]=(n[_>>2]|0)+4),A=A+4|0,!(n[A>>2]|0))break e;d=n[_>>2]|0,m=n[G>>2]|0}while(!1);n[tt>>2]=zP(B)|0,n[Ze>>2]=Ur(k)|0,kGe(u,o,tt,Ze,Le,Qe),s_(Qe),sp(Le)}l=n[l>>2]|0}while(l|0)}I=ct}function n_(o){return o=o|0,n[o+12>>2]|0}function Q2(o){return o=o|0,n[o+12>>2]|0}function T2(o){return o=o|0,n[o+16>>2]|0}function PGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+32|0,u=d,A=n[o>>2]|0,(n[o+8>>2]|0)-A>>2>>>0>>0&&(TX(u,l,(n[o+4>>2]|0)-A>>2,o+8|0),RX(o,u),FX(u)),I=d}function i_(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=QX(o)|0,m>>>0>>0)sn(o);else{k=n[o>>2]|0,_=(n[o+8>>2]|0)-k|0,T=_>>1,TX(u,_>>2>>>0>>1>>>0?T>>>0>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,RX(o,u),FX(u),I=B;return}}function R2(o){return o=o|0,n[o+8>>2]|0}function xGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;if(B=I,I=I+32|0,u=B,A=o+4|0,d=((n[A>>2]|0)-(n[o>>2]|0)>>2)+1|0,m=kX(o)|0,m>>>0>>0)sn(o);else{k=n[o>>2]|0,_=(n[o+8>>2]|0)-k|0,T=_>>1,JGe(u,_>>2>>>0>>1>>>0?T>>>0>>0?d:T:m,(n[A>>2]|0)-k>>2,o+8|0),m=u+8|0,n[n[m>>2]>>2]=n[l>>2],n[m>>2]=(n[m>>2]|0)+4,zGe(o,u),ZGe(u),I=B;return}}function zP(o){return o=o|0,n[o>>2]|0}function kGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,QGe(o,l,u,A,d,m)}function s_(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),yt(u))}function sp(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-4-A|0)>>>2)<<2)),yt(u))}function QGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0;B=I,I=I+48|0,M=B+40|0,k=B+32|0,G=B+24|0,T=B+12|0,_=B,Nl(k),o=Ls(o)|0,n[G>>2]=n[l>>2],u=n[u>>2]|0,A=n[A>>2]|0,o_(T,d),TGe(_,m),n[M>>2]=n[G>>2],RGe(o,M,u,A,T,_),s_(_),sp(T),Ol(k),I=B}function o_(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(VGe(o,A),KGe(o,n[l>>2]|0,n[u>>2]|0,A))}function TGe(o,l){o=o|0,l=l|0;var u=0,A=0;n[o>>2]=0,n[o+4>>2]=0,n[o+8>>2]=0,u=l+4|0,A=(n[u>>2]|0)-(n[l>>2]|0)>>2,A|0&&(WGe(o,A),YGe(o,n[l>>2]|0,n[u>>2]|0,A))}function RGe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0;B=I,I=I+32|0,M=B+28|0,G=B+24|0,k=B+12|0,T=B,_=ma(FGe()|0)|0,n[G>>2]=n[l>>2],n[M>>2]=n[G>>2],l=Ad(M)|0,u=SX(u)|0,A=a_(A)|0,n[k>>2]=n[d>>2],M=d+4|0,n[k+4>>2]=n[M>>2],G=d+8|0,n[k+8>>2]=n[G>>2],n[G>>2]=0,n[M>>2]=0,n[d>>2]=0,d=l_(k)|0,n[T>>2]=n[m>>2],M=m+4|0,n[T+4>>2]=n[M>>2],G=m+8|0,n[T+8>>2]=n[G>>2],n[G>>2]=0,n[M>>2]=0,n[m>>2]=0,uu(0,_|0,o|0,l|0,u|0,A|0,d|0,NGe(T)|0)|0,s_(T),sp(k),I=B}function FGe(){var o=0;return s[7968]|0||(qGe(10708),o=7968,n[o>>2]=1,n[o+4>>2]=0),10708}function Ad(o){return o=o|0,bX(o)|0}function SX(o){return o=o|0,DX(o)|0}function a_(o){return o=o|0,VP(o)|0}function l_(o){return o=o|0,LGe(o)|0}function NGe(o){return o=o|0,OGe(o)|0}function OGe(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Fl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=DX(n[(n[o>>2]|0)+(l<<2)>>2]|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function DX(o){return o=o|0,o|0}function LGe(o){o=o|0;var l=0,u=0,A=0;if(A=(n[o+4>>2]|0)-(n[o>>2]|0)|0,u=A>>2,A=Fl(A+4|0)|0,n[A>>2]=u,u|0){l=0;do n[A+4+(l<<2)>>2]=bX((n[o>>2]|0)+(l<<2)|0)|0,l=l+1|0;while((l|0)!=(u|0))}return A|0}function bX(o){o=o|0;var l=0,u=0,A=0,d=0;return d=I,I=I+32|0,l=d+12|0,u=d,A=IM(PX()|0)|0,A?(CM(l,A),wM(u,l),EYe(o,u),o=BM(l)|0):o=MGe(o)|0,I=d,o|0}function PX(){var o=0;return s[7960]|0||(jGe(10664),gr(25,10664,U|0)|0,o=7960,n[o>>2]=1,n[o+4>>2]=0),10664}function MGe(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Fl(8)|0,l=A,k=Jt(4)|0,n[k>>2]=n[o>>2],m=l+4|0,n[m>>2]=k,o=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],xX(o,m,d),n[A>>2]=o,I=u,l|0}function xX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1656,n[u+12>>2]=l,n[o+4>>2]=u}function _Ge(o){o=o|0,$y(o),yt(o)}function UGe(o){o=o|0,o=n[o+12>>2]|0,o|0&&yt(o)}function HGe(o){o=o|0,yt(o)}function jGe(o){o=o|0,Uh(o)}function qGe(o){o=o|0,Ro(o,GGe()|0,5)}function GGe(){return 1676}function WGe(o,l){o=o|0,l=l|0;var u=0;if((kX(o)|0)>>>0>>0&&sn(o),l>>>0>1073741823)Nt();else{u=Jt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function YGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function kX(o){return o=o|0,1073741823}function VGe(o,l){o=o|0,l=l|0;var u=0;if((QX(o)|0)>>>0>>0&&sn(o),l>>>0>1073741823)Nt();else{u=Jt(l<<2)|0,n[o+4>>2]=u,n[o>>2]=u,n[o+8>>2]=u+(l<<2);return}}function KGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,A=o+4|0,o=u-l|0,(o|0)>0&&(Qr(n[A>>2]|0,l|0,o|0)|0,n[A>>2]=(n[A>>2]|0)+(o>>>2<<2))}function QX(o){return o=o|0,1073741823}function JGe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Jt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function zGe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function ZGe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&yt(o)}function TX(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>1073741823)Nt();else{d=Jt(l<<2)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<2)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<2)}function RX(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>2)<<2)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function FX(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-4-l|0)>>>2)<<2)),o=n[o>>2]|0,o|0&&yt(o)}function XGe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0;if(Qe=I,I=I+32|0,M=Qe+20|0,G=Qe+12|0,_=Qe+16|0,ae=Qe+4|0,We=Qe,Le=Qe+8|0,k=wX()|0,m=n[k>>2]|0,B=n[m>>2]|0,B|0)for(T=n[k+8>>2]|0,k=n[k+4>>2]|0;xu(M,B),$Ge(o,M,k,T),m=m+4|0,B=n[m>>2]|0,B;)T=T+1|0,k=k+1|0;if(m=BX()|0,B=n[m>>2]|0,B|0)do xu(M,B),n[G>>2]=n[m+4>>2],e5e(l,M,G),m=m+8|0,B=n[m>>2]|0;while(B|0);if(m=n[(Ky()|0)>>2]|0,m|0)do l=n[m+4>>2]|0,xu(M,n[(Jy(l)|0)>>2]|0),n[G>>2]=t_(l)|0,t5e(u,M,G),m=n[m>>2]|0;while(m|0);if(xu(_,0),m=r_()|0,n[M>>2]=n[_>>2],vX(M,m,d),m=n[(Ky()|0)>>2]|0,m|0){o=M+4|0,l=M+8|0,u=M+8|0;do{if(T=n[m+4>>2]|0,xu(G,n[(Jy(T)|0)>>2]|0),r5e(ae,NX(T)|0),B=n[ae>>2]|0,B|0){n[M>>2]=0,n[o>>2]=0,n[l>>2]=0;do xu(We,n[(Jy(n[B+4>>2]|0)|0)>>2]|0),k=n[o>>2]|0,k>>>0<(n[u>>2]|0)>>>0?(n[k>>2]=n[We>>2],n[o>>2]=(n[o>>2]|0)+4):i_(M,We),B=n[B>>2]|0;while(B|0);n5e(A,G,M),sp(M)}n[Le>>2]=n[G>>2],_=OX(T)|0,n[M>>2]=n[Le>>2],vX(M,_,d),iZ(ae),m=n[m>>2]|0}while(m|0)}I=Qe}function $Ge(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,g5e(o,l,u,A)}function e5e(o,l,u){o=o|0,l=l|0,u=u|0,h5e(o,l,u)}function Jy(o){return o=o|0,o|0}function t5e(o,l,u){o=o|0,l=l|0,u=u|0,u5e(o,l,u)}function NX(o){return o=o|0,o+16|0}function r5e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(m=I,I=I+16|0,d=m+8|0,u=m,n[o>>2]=0,A=n[l>>2]|0,n[d>>2]=A,n[u>>2]=o,u=c5e(u)|0,A|0){if(A=Jt(12)|0,B=(LX(d)|0)+4|0,o=n[B+4>>2]|0,l=A+4|0,n[l>>2]=n[B>>2],n[l+4>>2]=o,l=n[n[d>>2]>>2]|0,n[d>>2]=l,!l)o=A;else for(l=A;o=Jt(12)|0,T=(LX(d)|0)+4|0,k=n[T+4>>2]|0,B=o+4|0,n[B>>2]=n[T>>2],n[B+4>>2]=k,n[l>>2]=o,B=n[n[d>>2]>>2]|0,n[d>>2]=B,B;)l=o;n[o>>2]=n[u>>2],n[u>>2]=A}I=m}function n5e(o,l,u){o=o|0,l=l|0,u=u|0,i5e(o,l,u)}function OX(o){return o=o|0,o+24|0}function i5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+24|0,d=A+16|0,k=A+12|0,m=A,Nl(d),o=Ls(o)|0,n[k>>2]=n[l>>2],o_(m,u),n[B>>2]=n[k>>2],s5e(o,B,m),sp(m),Ol(d),I=A}function s5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=I,I=I+32|0,B=A+16|0,k=A+12|0,d=A,m=ma(o5e()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=Ad(B)|0,n[d>>2]=n[u>>2],B=u+4|0,n[d+4>>2]=n[B>>2],k=u+8|0,n[d+8>>2]=n[k>>2],n[k>>2]=0,n[B>>2]=0,n[u>>2]=0,Rs(0,m|0,o|0,l|0,l_(d)|0)|0,sp(d),I=A}function o5e(){var o=0;return s[7976]|0||(a5e(10720),o=7976,n[o>>2]=1,n[o+4>>2]=0),10720}function a5e(o){o=o|0,Ro(o,l5e()|0,2)}function l5e(){return 1732}function c5e(o){return o=o|0,n[o>>2]|0}function LX(o){return o=o|0,n[o>>2]|0}function u5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Nl(d),o=Ls(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],MX(o,m,u),Ol(d),I=A}function MX(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+16|0,m=A+4|0,B=A,d=ma(f5e()|0)|0,n[B>>2]=n[l>>2],n[m>>2]=n[B>>2],l=Ad(m)|0,Rs(0,d|0,o|0,l|0,SX(u)|0)|0,I=A}function f5e(){var o=0;return s[7984]|0||(A5e(10732),o=7984,n[o>>2]=1,n[o+4>>2]=0),10732}function A5e(o){o=o|0,Ro(o,p5e()|0,2)}function p5e(){return 1744}function h5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;A=I,I=I+32|0,m=A+16|0,d=A+8|0,B=A,Nl(d),o=Ls(o)|0,n[B>>2]=n[l>>2],u=n[u>>2]|0,n[m>>2]=n[B>>2],MX(o,m,u),Ol(d),I=A}function g5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Nl(m),o=Ls(o)|0,n[k>>2]=n[l>>2],u=s[u>>0]|0,A=s[A>>0]|0,n[B>>2]=n[k>>2],d5e(o,B,u,A),Ol(m),I=d}function d5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,B=d+4|0,k=d,m=ma(m5e()|0)|0,n[k>>2]=n[l>>2],n[B>>2]=n[k>>2],l=Ad(B)|0,u=zy(u)|0,Oi(0,m|0,o|0,l|0,u|0,zy(A)|0)|0,I=d}function m5e(){var o=0;return s[7992]|0||(E5e(10744),o=7992,n[o>>2]=1,n[o+4>>2]=0),10744}function zy(o){return o=o|0,y5e(o)|0}function y5e(o){return o=o|0,o&255|0}function E5e(o){o=o|0,Ro(o,I5e()|0,3)}function I5e(){return 1756}function C5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;switch(ae=I,I=I+32|0,k=ae+8|0,T=ae+4|0,_=ae+20|0,M=ae,bM(o,0),A=yYe(l)|0,n[k>>2]=0,G=k+4|0,n[G>>2]=0,n[k+8>>2]=0,A<<24>>24){case 0:{s[_>>0]=0,w5e(T,u,_),ZP(o,T)|0,bf(T);break}case 8:{G=h_(l)|0,s[_>>0]=8,xu(M,n[G+4>>2]|0),B5e(T,u,_,M,G+8|0),ZP(o,T)|0,bf(T);break}case 9:{if(m=h_(l)|0,l=n[m+4>>2]|0,l|0)for(B=k+8|0,d=m+12|0;l=l+-1|0,xu(T,n[d>>2]|0),A=n[G>>2]|0,A>>>0<(n[B>>2]|0)>>>0?(n[A>>2]=n[T>>2],n[G>>2]=(n[G>>2]|0)+4):i_(k,T),l;)d=d+4|0;s[_>>0]=9,xu(M,n[m+8>>2]|0),v5e(T,u,_,M,k),ZP(o,T)|0,bf(T);break}default:G=h_(l)|0,s[_>>0]=A,xu(M,n[G+4>>2]|0),S5e(T,u,_,M),ZP(o,T)|0,bf(T)}sp(k),I=ae}function w5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;A=I,I=I+16|0,d=A,Nl(d),l=Ls(l)|0,M5e(o,l,s[u>>0]|0),Ol(d),I=A}function ZP(o,l){o=o|0,l=l|0;var u=0;return u=n[o>>2]|0,u|0&&Oa(u|0),n[o>>2]=n[l>>2],n[l>>2]=0,o|0}function B5e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+32|0,k=m+16|0,B=m+8|0,T=m,Nl(B),l=Ls(l)|0,u=s[u>>0]|0,n[T>>2]=n[A>>2],d=n[d>>2]|0,n[k>>2]=n[T>>2],F5e(o,l,u,k,d),Ol(B),I=m}function v5e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0;m=I,I=I+32|0,T=m+24|0,B=m+16|0,_=m+12|0,k=m,Nl(B),l=Ls(l)|0,u=s[u>>0]|0,n[_>>2]=n[A>>2],o_(k,d),n[T>>2]=n[_>>2],k5e(o,l,u,T,k),sp(k),Ol(B),I=m}function S5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+32|0,B=d+16|0,m=d+8|0,k=d,Nl(m),l=Ls(l)|0,u=s[u>>0]|0,n[k>>2]=n[A>>2],n[B>>2]=n[k>>2],D5e(o,l,u,B),Ol(m),I=d}function D5e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0,B=0,k=0;d=I,I=I+16|0,m=d+4|0,k=d,B=ma(b5e()|0)|0,u=zy(u)|0,n[k>>2]=n[A>>2],n[m>>2]=n[k>>2],XP(o,Rs(0,B|0,l|0,u|0,Ad(m)|0)|0),I=d}function b5e(){var o=0;return s[8e3]|0||(P5e(10756),o=8e3,n[o>>2]=1,n[o+4>>2]=0),10756}function XP(o,l){o=o|0,l=l|0,bM(o,l)}function P5e(o){o=o|0,Ro(o,x5e()|0,2)}function x5e(){return 1772}function k5e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0;m=I,I=I+32|0,T=m+16|0,_=m+12|0,B=m,k=ma(Q5e()|0)|0,u=zy(u)|0,n[_>>2]=n[A>>2],n[T>>2]=n[_>>2],A=Ad(T)|0,n[B>>2]=n[d>>2],T=d+4|0,n[B+4>>2]=n[T>>2],_=d+8|0,n[B+8>>2]=n[_>>2],n[_>>2]=0,n[T>>2]=0,n[d>>2]=0,XP(o,Oi(0,k|0,l|0,u|0,A|0,l_(B)|0)|0),sp(B),I=m}function Q5e(){var o=0;return s[8008]|0||(T5e(10768),o=8008,n[o>>2]=1,n[o+4>>2]=0),10768}function T5e(o){o=o|0,Ro(o,R5e()|0,3)}function R5e(){return 1784}function F5e(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0;m=I,I=I+16|0,k=m+4|0,T=m,B=ma(N5e()|0)|0,u=zy(u)|0,n[T>>2]=n[A>>2],n[k>>2]=n[T>>2],A=Ad(k)|0,XP(o,Oi(0,B|0,l|0,u|0,A|0,a_(d)|0)|0),I=m}function N5e(){var o=0;return s[8016]|0||(O5e(10780),o=8016,n[o>>2]=1,n[o+4>>2]=0),10780}function O5e(o){o=o|0,Ro(o,L5e()|0,3)}function L5e(){return 1800}function M5e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;A=ma(_5e()|0)|0,XP(o,dn(0,A|0,l|0,zy(u)|0)|0)}function _5e(){var o=0;return s[8024]|0||(U5e(10792),o=8024,n[o>>2]=1,n[o+4>>2]=0),10792}function U5e(o){o=o|0,Ro(o,H5e()|0,1)}function H5e(){return 1816}function j5e(){q5e(),G5e(),W5e()}function q5e(){n[2702]=A$(65536)|0}function G5e(){f9e(10856)}function W5e(){Y5e(10816)}function Y5e(o){o=o|0,V5e(o,5044),K5e(o)|0}function V5e(o,l){o=o|0,l=l|0;var u=0;u=PX()|0,n[o>>2]=u,s9e(u,l),Wh(n[o>>2]|0)}function K5e(o){o=o|0;var l=0;return l=n[o>>2]|0,ud(l,J5e()|0),o|0}function J5e(){var o=0;return s[8032]|0||(_X(10820),gr(64,10820,U|0)|0,o=8032,n[o>>2]=1,n[o+4>>2]=0),Ur(10820)|0||_X(10820),10820}function _X(o){o=o|0,X5e(o),fd(o,25)}function z5e(o){o=o|0,Z5e(o+24|0)}function Z5e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function X5e(o){o=o|0;var l=0;l=en()|0,tn(o,5,18,l,r9e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function $5e(o,l){o=o|0,l=l|0,e9e(o,l)}function e9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;u=I,I=I+16|0,A=u,d=u+4|0,ld(d,l),n[A>>2]=cd(d,l)|0,t9e(o,A),I=u}function t9e(o,l){o=o|0,l=l|0,UX(o+4|0,n[l>>2]|0),s[o+8>>0]=1}function UX(o,l){o=o|0,l=l|0,n[o>>2]=l}function r9e(){return 1824}function n9e(o){return o=o|0,i9e(o)|0}function i9e(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0;return u=I,I=I+16|0,d=u+4|0,B=u,A=Fl(8)|0,l=A,k=Jt(4)|0,ld(d,o),UX(k,cd(d,o)|0),m=l+4|0,n[m>>2]=k,o=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],xX(o,m,d),n[A>>2]=o,I=u,l|0}function Fl(o){o=o|0;var l=0,u=0;return o=o+7&-8,o>>>0<=32768&&(l=n[2701]|0,o>>>0<=(65536-l|0)>>>0)?(u=(n[2702]|0)+l|0,n[2701]=l+o,o=u):(o=A$(o+8|0)|0,n[o>>2]=n[2703],n[2703]=o,o=o+8|0),o|0}function s9e(o,l){o=o|0,l=l|0,n[o>>2]=o9e()|0,n[o+4>>2]=a9e()|0,n[o+12>>2]=l,n[o+8>>2]=l9e()|0,n[o+32>>2]=9}function o9e(){return 11744}function a9e(){return 1832}function l9e(){return JP()|0}function c9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(u9e(u),yt(u)):l|0&&yt(l)}function u9e(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function f9e(o){o=o|0,A9e(o,5052),p9e(o)|0,h9e(o,5058,26)|0,g9e(o,5069,1)|0,d9e(o,5077,10)|0,m9e(o,5087,19)|0,y9e(o,5094,27)|0}function A9e(o,l){o=o|0,l=l|0;var u=0;u=uYe()|0,n[o>>2]=u,fYe(u,l),Wh(n[o>>2]|0)}function p9e(o){o=o|0;var l=0;return l=n[o>>2]|0,ud(l,zWe()|0),o|0}function h9e(o,l,u){return o=o|0,l=l|0,u=u|0,TWe(o,Bn(l)|0,u,0),o|0}function g9e(o,l,u){return o=o|0,l=l|0,u=u|0,mWe(o,Bn(l)|0,u,0),o|0}function d9e(o,l,u){return o=o|0,l=l|0,u=u|0,J9e(o,Bn(l)|0,u,0),o|0}function m9e(o,l,u){return o=o|0,l=l|0,u=u|0,F9e(o,Bn(l)|0,u,0),o|0}function HX(o,l){o=o|0,l=l|0;var u=0,A=0;e:for(;;){for(u=n[2703]|0;;){if((u|0)==(l|0))break e;if(A=n[u>>2]|0,n[2703]=A,!u)u=A;else break}yt(u)}n[2701]=o}function y9e(o,l,u){return o=o|0,l=l|0,u=u|0,E9e(o,Bn(l)|0,u,0),o|0}function E9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=c_()|0,o=I9e(u)|0,vn(m,l,d,o,C9e(u,A)|0,A)}function c_(){var o=0,l=0;if(s[8040]|0||(qX(10860),gr(65,10860,U|0)|0,l=8040,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10860)|0)){o=10860,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));qX(10860)}return 10860}function I9e(o){return o=o|0,o|0}function C9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=c_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(jX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(w9e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function jX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function w9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=B9e(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,v9e(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,jX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,S9e(o,d),D9e(d),I=k;return}}function B9e(o){return o=o|0,536870911}function v9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function S9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function D9e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function qX(o){o=o|0,x9e(o)}function b9e(o){o=o|0,P9e(o+24|0)}function P9e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function x9e(o){o=o|0;var l=0;l=en()|0,tn(o,1,11,l,k9e()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function k9e(){return 1840}function Q9e(o,l,u){o=o|0,l=l|0,u=u|0,R9e(n[(T9e(o)|0)>>2]|0,l,u)}function T9e(o){return o=o|0,(n[(c_()|0)+24>>2]|0)+(o<<3)|0}function R9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+1|0,d=A,ld(m,l),l=cd(m,l)|0,ld(d,u),u=cd(d,u)|0,ap[o&31](l,u),I=A}function F9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=u_()|0,o=N9e(u)|0,vn(m,l,d,o,O9e(u,A)|0,A)}function u_(){var o=0,l=0;if(s[8048]|0||(WX(10896),gr(66,10896,U|0)|0,l=8048,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10896)|0)){o=10896,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));WX(10896)}return 10896}function N9e(o){return o=o|0,o|0}function O9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=u_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(GX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(L9e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function GX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function L9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=M9e(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,_9e(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,GX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,U9e(o,d),H9e(d),I=k;return}}function M9e(o){return o=o|0,536870911}function _9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function U9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function H9e(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function WX(o){o=o|0,G9e(o)}function j9e(o){o=o|0,q9e(o+24|0)}function q9e(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function G9e(o){o=o|0;var l=0;l=en()|0,tn(o,1,11,l,W9e()|0,1),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function W9e(){return 1852}function Y9e(o,l){return o=o|0,l=l|0,K9e(n[(V9e(o)|0)>>2]|0,l)|0}function V9e(o){return o=o|0,(n[(u_()|0)+24>>2]|0)+(o<<3)|0}function K9e(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,ld(A,l),l=cd(A,l)|0,l=VP(dd[o&31](l)|0)|0,I=u,l|0}function J9e(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=f_()|0,o=z9e(u)|0,vn(m,l,d,o,Z9e(u,A)|0,A)}function f_(){var o=0,l=0;if(s[8056]|0||(VX(10932),gr(67,10932,U|0)|0,l=8056,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10932)|0)){o=10932,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));VX(10932)}return 10932}function z9e(o){return o=o|0,o|0}function Z9e(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=f_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(YX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(X9e(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function YX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function X9e(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=$9e(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,eWe(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,YX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,tWe(o,d),rWe(d),I=k;return}}function $9e(o){return o=o|0,536870911}function eWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function tWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function rWe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function VX(o){o=o|0,sWe(o)}function nWe(o){o=o|0,iWe(o+24|0)}function iWe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function sWe(o){o=o|0;var l=0;l=en()|0,tn(o,1,7,l,oWe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function oWe(){return 1860}function aWe(o,l,u){return o=o|0,l=l|0,u=u|0,cWe(n[(lWe(o)|0)>>2]|0,l,u)|0}function lWe(o){return o=o|0,(n[(f_()|0)+24>>2]|0)+(o<<3)|0}function cWe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0;return A=I,I=I+32|0,B=A+12|0,m=A+8|0,k=A,T=A+16|0,d=A+4|0,uWe(T,l),fWe(k,T,l),Hh(d,u),u=jh(d,u)|0,n[B>>2]=n[k>>2],L2[o&15](m,B,u),u=AWe(m)|0,bf(m),qh(d),I=A,u|0}function uWe(o,l){o=o|0,l=l|0}function fWe(o,l,u){o=o|0,l=l|0,u=u|0,pWe(o,u)}function AWe(o){return o=o|0,Ls(o)|0}function pWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0;d=I,I=I+16|0,u=d,A=l,A&1?(hWe(u,0),Me(A|0,u|0)|0,gWe(o,u),dWe(u)):n[o>>2]=n[l>>2],I=d}function hWe(o,l){o=o|0,l=l|0,bu(o,l),n[o+4>>2]=0,s[o+8>>0]=0}function gWe(o,l){o=o|0,l=l|0,n[o>>2]=n[l+4>>2]}function dWe(o){o=o|0,s[o+8>>0]=0}function mWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=A_()|0,o=yWe(u)|0,vn(m,l,d,o,EWe(u,A)|0,A)}function A_(){var o=0,l=0;if(s[8064]|0||(JX(10968),gr(68,10968,U|0)|0,l=8064,n[l>>2]=1,n[l+4>>2]=0),!(Ur(10968)|0)){o=10968,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));JX(10968)}return 10968}function yWe(o){return o=o|0,o|0}function EWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=A_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(KX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(IWe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function KX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function IWe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=CWe(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,wWe(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,KX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,BWe(o,d),vWe(d),I=k;return}}function CWe(o){return o=o|0,536870911}function wWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function BWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function vWe(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function JX(o){o=o|0,bWe(o)}function SWe(o){o=o|0,DWe(o+24|0)}function DWe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function bWe(o){o=o|0;var l=0;l=en()|0,tn(o,1,1,l,PWe()|0,5),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function PWe(){return 1872}function xWe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,QWe(n[(kWe(o)|0)>>2]|0,l,u,A,d,m)}function kWe(o){return o=o|0,(n[(A_()|0)+24>>2]|0)+(o<<3)|0}function QWe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0;B=I,I=I+32|0,k=B+16|0,T=B+12|0,_=B+8|0,M=B+4|0,G=B,Hh(k,l),l=jh(k,l)|0,Hh(T,u),u=jh(T,u)|0,Hh(_,A),A=jh(_,A)|0,Hh(M,d),d=jh(M,d)|0,Hh(G,m),m=jh(G,m)|0,m$[o&1](l,u,A,d,m),qh(G),qh(M),qh(_),qh(T),qh(k),I=B}function TWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;m=n[o>>2]|0,d=p_()|0,o=RWe(u)|0,vn(m,l,d,o,FWe(u,A)|0,A)}function p_(){var o=0,l=0;if(s[8072]|0||(ZX(11004),gr(69,11004,U|0)|0,l=8072,n[l>>2]=1,n[l+4>>2]=0),!(Ur(11004)|0)){o=11004,l=o+36|0;do n[o>>2]=0,o=o+4|0;while((o|0)<(l|0));ZX(11004)}return 11004}function RWe(o){return o=o|0,o|0}function FWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0,k=0,T=0;return k=I,I=I+16|0,d=k,m=k+4|0,n[d>>2]=o,T=p_()|0,B=T+24|0,l=yr(l,4)|0,n[m>>2]=l,u=T+28|0,A=n[u>>2]|0,A>>>0<(n[T+32>>2]|0)>>>0?(zX(A,o,l),l=(n[u>>2]|0)+8|0,n[u>>2]=l):(NWe(B,d,m),l=n[u>>2]|0),I=k,(l-(n[B>>2]|0)>>3)+-1|0}function zX(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,n[o+4>>2]=u}function NWe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0;if(k=I,I=I+32|0,d=k,m=o+4|0,B=((n[m>>2]|0)-(n[o>>2]|0)>>3)+1|0,A=OWe(o)|0,A>>>0>>0)sn(o);else{T=n[o>>2]|0,M=(n[o+8>>2]|0)-T|0,_=M>>2,LWe(d,M>>3>>>0>>1>>>0?_>>>0>>0?B:_:A,(n[m>>2]|0)-T>>3,o+8|0),B=d+8|0,zX(n[B>>2]|0,n[l>>2]|0,n[u>>2]|0),n[B>>2]=(n[B>>2]|0)+8,MWe(o,d),_We(d),I=k;return}}function OWe(o){return o=o|0,536870911}function LWe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0;n[o+12>>2]=0,n[o+16>>2]=A;do if(l)if(l>>>0>536870911)Nt();else{d=Jt(l<<3)|0;break}else d=0;while(!1);n[o>>2]=d,A=d+(u<<3)|0,n[o+8>>2]=A,n[o+4>>2]=A,n[o+12>>2]=d+(l<<3)}function MWe(o,l){o=o|0,l=l|0;var u=0,A=0,d=0,m=0,B=0;A=n[o>>2]|0,B=o+4|0,m=l+4|0,d=(n[B>>2]|0)-A|0,u=(n[m>>2]|0)+(0-(d>>3)<<3)|0,n[m>>2]=u,(d|0)>0?(Qr(u|0,A|0,d|0)|0,A=m,u=n[m>>2]|0):A=m,m=n[o>>2]|0,n[o>>2]=u,n[A>>2]=m,m=l+8|0,d=n[B>>2]|0,n[B>>2]=n[m>>2],n[m>>2]=d,m=o+8|0,B=l+12|0,o=n[m>>2]|0,n[m>>2]=n[B>>2],n[B>>2]=o,n[l>>2]=n[A>>2]}function _We(o){o=o|0;var l=0,u=0,A=0;l=n[o+4>>2]|0,u=o+8|0,A=n[u>>2]|0,(A|0)!=(l|0)&&(n[u>>2]=A+(~((A+-8-l|0)>>>3)<<3)),o=n[o>>2]|0,o|0&&yt(o)}function ZX(o){o=o|0,jWe(o)}function UWe(o){o=o|0,HWe(o+24|0)}function HWe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function jWe(o){o=o|0;var l=0;l=en()|0,tn(o,1,12,l,qWe()|0,2),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function qWe(){return 1896}function GWe(o,l,u){o=o|0,l=l|0,u=u|0,YWe(n[(WWe(o)|0)>>2]|0,l,u)}function WWe(o){return o=o|0,(n[(p_()|0)+24>>2]|0)+(o<<3)|0}function YWe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;A=I,I=I+16|0,m=A+4|0,d=A,VWe(m,l),l=KWe(m,l)|0,Hh(d,u),u=jh(d,u)|0,ap[o&31](l,u),qh(d),I=A}function VWe(o,l){o=o|0,l=l|0}function KWe(o,l){return o=o|0,l=l|0,JWe(l)|0}function JWe(o){return o=o|0,o|0}function zWe(){var o=0;return s[8080]|0||(XX(11040),gr(70,11040,U|0)|0,o=8080,n[o>>2]=1,n[o+4>>2]=0),Ur(11040)|0||XX(11040),11040}function XX(o){o=o|0,$We(o),fd(o,71)}function ZWe(o){o=o|0,XWe(o+24|0)}function XWe(o){o=o|0;var l=0,u=0,A=0;u=n[o>>2]|0,A=u,u|0&&(o=o+4|0,l=n[o>>2]|0,(l|0)!=(u|0)&&(n[o>>2]=l+(~((l+-8-A|0)>>>3)<<3)),yt(u))}function $We(o){o=o|0;var l=0;l=en()|0,tn(o,5,7,l,nYe()|0,0),n[o+24>>2]=0,n[o+28>>2]=0,n[o+32>>2]=0}function eYe(o){o=o|0,tYe(o)}function tYe(o){o=o|0,rYe(o)}function rYe(o){o=o|0,s[o+8>>0]=1}function nYe(){return 1936}function iYe(){return sYe()|0}function sYe(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0;return l=I,I=I+16|0,d=l+4|0,B=l,u=Fl(8)|0,o=u,m=o+4|0,n[m>>2]=Jt(1)|0,A=Jt(8)|0,m=n[m>>2]|0,n[B>>2]=0,n[d>>2]=n[B>>2],oYe(A,m,d),n[u>>2]=A,I=l,o|0}function oYe(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]=l,u=Jt(16)|0,n[u+4>>2]=0,n[u+8>>2]=0,n[u>>2]=1916,n[u+12>>2]=l,n[o+4>>2]=u}function aYe(o){o=o|0,$y(o),yt(o)}function lYe(o){o=o|0,o=n[o+12>>2]|0,o|0&&yt(o)}function cYe(o){o=o|0,yt(o)}function uYe(){var o=0;return s[8088]|0||(mYe(11076),gr(25,11076,U|0)|0,o=8088,n[o>>2]=1,n[o+4>>2]=0),11076}function fYe(o,l){o=o|0,l=l|0,n[o>>2]=AYe()|0,n[o+4>>2]=pYe()|0,n[o+12>>2]=l,n[o+8>>2]=hYe()|0,n[o+32>>2]=10}function AYe(){return 11745}function pYe(){return 1940}function hYe(){return KP()|0}function gYe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,(Gh(A,896)|0)==512?u|0&&(dYe(u),yt(u)):l|0&&yt(l)}function dYe(o){o=o|0,o=n[o+4>>2]|0,o|0&&Yh(o)}function mYe(o){o=o|0,Uh(o)}function xu(o,l){o=o|0,l=l|0,n[o>>2]=l}function h_(o){return o=o|0,n[o>>2]|0}function yYe(o){return o=o|0,s[n[o>>2]>>0]|0}function EYe(o,l){o=o|0,l=l|0;var u=0,A=0;u=I,I=I+16|0,A=u,n[A>>2]=n[o>>2],IYe(l,A)|0,I=u}function IYe(o,l){o=o|0,l=l|0;var u=0;return u=CYe(n[o>>2]|0,l)|0,l=o+4|0,n[(n[l>>2]|0)+8>>2]=u,n[(n[l>>2]|0)+8>>2]|0}function CYe(o,l){o=o|0,l=l|0;var u=0,A=0;return u=I,I=I+16|0,A=u,Nl(A),o=Ls(o)|0,l=wYe(o,n[l>>2]|0)|0,Ol(A),I=u,l|0}function Nl(o){o=o|0,n[o>>2]=n[2701],n[o+4>>2]=n[2703]}function wYe(o,l){o=o|0,l=l|0;var u=0;return u=ma(BYe()|0)|0,dn(0,u|0,o|0,a_(l)|0)|0}function Ol(o){o=o|0,HX(n[o>>2]|0,n[o+4>>2]|0)}function BYe(){var o=0;return s[8096]|0||(vYe(11120),o=8096,n[o>>2]=1,n[o+4>>2]=0),11120}function vYe(o){o=o|0,Ro(o,SYe()|0,1)}function SYe(){return 1948}function DYe(){bYe()}function bYe(){var o=0,l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0;if(Le=I,I=I+16|0,M=Le+4|0,G=Le,aa(65536,10804,n[2702]|0,10812),u=wX()|0,l=n[u>>2]|0,o=n[l>>2]|0,o|0)for(A=n[u+8>>2]|0,u=n[u+4>>2]|0;hf(o|0,c[u>>0]|0|0,s[A>>0]|0),l=l+4|0,o=n[l>>2]|0,o;)A=A+1|0,u=u+1|0;if(o=BX()|0,l=n[o>>2]|0,l|0)do LA(l|0,n[o+4>>2]|0),o=o+8|0,l=n[o>>2]|0;while(l|0);LA(PYe()|0,5167),_=Ky()|0,o=n[_>>2]|0;e:do if(o|0){do xYe(n[o+4>>2]|0),o=n[o>>2]|0;while(o|0);if(o=n[_>>2]|0,o|0){T=_;do{for(;d=o,o=n[o>>2]|0,d=n[d+4>>2]|0,!!(kYe(d)|0);)if(n[G>>2]=T,n[M>>2]=n[G>>2],QYe(_,M)|0,!o)break e;if(TYe(d),T=n[T>>2]|0,l=$X(d)|0,m=Ni()|0,B=I,I=I+((1*(l<<2)|0)+15&-16)|0,k=I,I=I+((1*(l<<2)|0)+15&-16)|0,l=n[(NX(d)|0)>>2]|0,l|0)for(u=B,A=k;n[u>>2]=n[(Jy(n[l+4>>2]|0)|0)>>2],n[A>>2]=n[l+8>>2],l=n[l>>2]|0,l;)u=u+4|0,A=A+4|0;Qe=Jy(d)|0,l=RYe(d)|0,u=$X(d)|0,A=FYe(d)|0,ac(Qe|0,l|0,B|0,k|0,u|0,A|0,t_(d)|0),OA(m|0)}while(o|0)}}while(!1);if(o=n[(r_()|0)>>2]|0,o|0)do Qe=o+4|0,_=n_(Qe)|0,d=R2(_)|0,m=Q2(_)|0,B=(T2(_)|0)+1|0,k=$P(_)|0,T=e$(Qe)|0,_=Ur(_)|0,M=zP(Qe)|0,G=g_(Qe)|0,Au(0,d|0,m|0,B|0,k|0,T|0,_|0,M|0,G|0,d_(Qe)|0),o=n[o>>2]|0;while(o|0);o=n[(Ky()|0)>>2]|0;e:do if(o|0){t:for(;;){if(l=n[o+4>>2]|0,l|0&&(ae=n[(Jy(l)|0)>>2]|0,We=n[(OX(l)|0)>>2]|0,We|0)){u=We;do{l=u+4|0,A=n_(l)|0;r:do if(A|0)switch(Ur(A)|0){case 0:break t;case 4:case 3:case 2:{k=R2(A)|0,T=Q2(A)|0,_=(T2(A)|0)+1|0,M=$P(A)|0,G=Ur(A)|0,Qe=zP(l)|0,Au(ae|0,k|0,T|0,_|0,M|0,0,G|0,Qe|0,g_(l)|0,d_(l)|0);break r}case 1:{B=R2(A)|0,k=Q2(A)|0,T=(T2(A)|0)+1|0,_=$P(A)|0,M=e$(l)|0,G=Ur(A)|0,Qe=zP(l)|0,Au(ae|0,B|0,k|0,T|0,_|0,M|0,G|0,Qe|0,g_(l)|0,d_(l)|0);break r}case 5:{_=R2(A)|0,M=Q2(A)|0,G=(T2(A)|0)+1|0,Qe=$P(A)|0,Au(ae|0,_|0,M|0,G|0,Qe|0,NYe(A)|0,Ur(A)|0,0,0,0);break r}default:break r}while(!1);u=n[u>>2]|0}while(u|0)}if(o=n[o>>2]|0,!o)break e}Nt()}while(!1);ve(),I=Le}function PYe(){return 11703}function xYe(o){o=o|0,s[o+40>>0]=0}function kYe(o){return o=o|0,(s[o+40>>0]|0)!=0|0}function QYe(o,l){return o=o|0,l=l|0,l=OYe(l)|0,o=n[l>>2]|0,n[l>>2]=n[o>>2],yt(o),n[l>>2]|0}function TYe(o){o=o|0,s[o+40>>0]=1}function $X(o){return o=o|0,n[o+20>>2]|0}function RYe(o){return o=o|0,n[o+8>>2]|0}function FYe(o){return o=o|0,n[o+32>>2]|0}function $P(o){return o=o|0,n[o+4>>2]|0}function e$(o){return o=o|0,n[o+4>>2]|0}function g_(o){return o=o|0,n[o+8>>2]|0}function d_(o){return o=o|0,n[o+16>>2]|0}function NYe(o){return o=o|0,n[o+20>>2]|0}function OYe(o){return o=o|0,n[o>>2]|0}function ex(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0;Lt=I,I=I+16|0,ae=Lt;do if(o>>>0<245){if(_=o>>>0<11?16:o+11&-8,o=_>>>3,G=n[2783]|0,u=G>>>o,u&3|0)return l=(u&1^1)+o|0,o=11172+(l<<1<<2)|0,u=o+8|0,A=n[u>>2]|0,d=A+8|0,m=n[d>>2]|0,(o|0)==(m|0)?n[2783]=G&~(1<>2]=o,n[u>>2]=m),Ge=l<<3,n[A+4>>2]=Ge|3,Ge=A+Ge+4|0,n[Ge>>2]=n[Ge>>2]|1,Ge=d,I=Lt,Ge|0;if(M=n[2785]|0,_>>>0>M>>>0){if(u|0)return l=2<>>12&16,l=l>>>B,u=l>>>5&8,l=l>>>u,d=l>>>2&4,l=l>>>d,o=l>>>1&2,l=l>>>o,A=l>>>1&1,A=(u|B|d|o|A)+(l>>>A)|0,l=11172+(A<<1<<2)|0,o=l+8|0,d=n[o>>2]|0,B=d+8|0,u=n[B>>2]|0,(l|0)==(u|0)?(o=G&~(1<>2]=l,n[o>>2]=u,o=G),m=(A<<3)-_|0,n[d+4>>2]=_|3,A=d+_|0,n[A+4>>2]=m|1,n[A+m>>2]=m,M|0&&(d=n[2788]|0,l=M>>>3,u=11172+(l<<1<<2)|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=d,n[l+12>>2]=d,n[d+8>>2]=l,n[d+12>>2]=u),n[2785]=m,n[2788]=A,Ge=B,I=Lt,Ge|0;if(k=n[2784]|0,k){if(u=(k&0-k)+-1|0,B=u>>>12&16,u=u>>>B,m=u>>>5&8,u=u>>>m,T=u>>>2&4,u=u>>>T,A=u>>>1&2,u=u>>>A,o=u>>>1&1,o=n[11436+((m|B|T|A|o)+(u>>>o)<<2)>>2]|0,u=(n[o+4>>2]&-8)-_|0,A=n[o+16+(((n[o+16>>2]|0)==0&1)<<2)>>2]|0,!A)T=o,m=u;else{do B=(n[A+4>>2]&-8)-_|0,T=B>>>0>>0,u=T?B:u,o=T?A:o,A=n[A+16+(((n[A+16>>2]|0)==0&1)<<2)>>2]|0;while(A|0);T=o,m=u}if(B=T+_|0,T>>>0>>0){d=n[T+24>>2]|0,l=n[T+12>>2]|0;do if((l|0)==(T|0)){if(o=T+20|0,l=n[o>>2]|0,!l&&(o=T+16|0,l=n[o>>2]|0,!l)){u=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0,u=l}else u=n[T+8>>2]|0,n[u+12>>2]=l,n[l+8>>2]=u,u=l;while(!1);do if(d|0){if(l=n[T+28>>2]|0,o=11436+(l<<2)|0,(T|0)==(n[o>>2]|0)){if(n[o>>2]=u,!u){n[2784]=k&~(1<>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=d,l=n[T+16>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),l=n[T+20>>2]|0,l|0&&(n[u+20>>2]=l,n[l+24>>2]=u)}while(!1);return m>>>0<16?(Ge=m+_|0,n[T+4>>2]=Ge|3,Ge=T+Ge+4|0,n[Ge>>2]=n[Ge>>2]|1):(n[T+4>>2]=_|3,n[B+4>>2]=m|1,n[B+m>>2]=m,M|0&&(A=n[2788]|0,l=M>>>3,u=11172+(l<<1<<2)|0,l=1<>2]|0):(n[2783]=G|l,l=u,o=u+8|0),n[o>>2]=A,n[l+12>>2]=A,n[A+8>>2]=l,n[A+12>>2]=u),n[2785]=m,n[2788]=B),Ge=T+8|0,I=Lt,Ge|0}else G=_}else G=_}else G=_}else if(o>>>0<=4294967231)if(o=o+11|0,_=o&-8,T=n[2784]|0,T){A=0-_|0,o=o>>>8,o?_>>>0>16777215?k=31:(G=(o+1048320|0)>>>16&8,He=o<>>16&4,He=He<>>16&2,k=14-(M|G|k)+(He<>>15)|0,k=_>>>(k+7|0)&1|k<<1):k=0,u=n[11436+(k<<2)>>2]|0;e:do if(!u)u=0,o=0,He=57;else for(o=0,B=_<<((k|0)==31?0:25-(k>>>1)|0),m=0;;){if(d=(n[u+4>>2]&-8)-_|0,d>>>0>>0)if(d)o=u,A=d;else{o=u,A=0,d=u,He=61;break e}if(d=n[u+20>>2]|0,u=n[u+16+(B>>>31<<2)>>2]|0,m=(d|0)==0|(d|0)==(u|0)?m:d,d=(u|0)==0,d){u=m,He=57;break}else B=B<<((d^1)&1)}while(!1);if((He|0)==57){if((u|0)==0&(o|0)==0){if(o=2<>>12&16,G=G>>>B,m=G>>>5&8,G=G>>>m,k=G>>>2&4,G=G>>>k,M=G>>>1&2,G=G>>>M,u=G>>>1&1,o=0,u=n[11436+((m|B|k|M|u)+(G>>>u)<<2)>>2]|0}u?(d=u,He=61):(k=o,B=A)}if((He|0)==61)for(;;)if(He=0,u=(n[d+4>>2]&-8)-_|0,G=u>>>0>>0,u=G?u:A,o=G?d:o,d=n[d+16+(((n[d+16>>2]|0)==0&1)<<2)>>2]|0,d)A=u,He=61;else{k=o,B=u;break}if(k|0&&B>>>0<((n[2785]|0)-_|0)>>>0){if(m=k+_|0,k>>>0>=m>>>0)return Ge=0,I=Lt,Ge|0;d=n[k+24>>2]|0,l=n[k+12>>2]|0;do if((l|0)==(k|0)){if(o=k+20|0,l=n[o>>2]|0,!l&&(o=k+16|0,l=n[o>>2]|0,!l)){l=0;break}for(;;){if(u=l+20|0,A=n[u>>2]|0,A|0){l=A,o=u;continue}if(u=l+16|0,A=n[u>>2]|0,A)l=A,o=u;else break}n[o>>2]=0}else Ge=n[k+8>>2]|0,n[Ge+12>>2]=l,n[l+8>>2]=Ge;while(!1);do if(d){if(o=n[k+28>>2]|0,u=11436+(o<<2)|0,(k|0)==(n[u>>2]|0)){if(n[u>>2]=l,!l){A=T&~(1<>2]|0)!=(k|0)&1)<<2)>>2]=l,!l){A=T;break}n[l+24>>2]=d,o=n[k+16>>2]|0,o|0&&(n[l+16>>2]=o,n[o+24>>2]=l),o=n[k+20>>2]|0,o&&(n[l+20>>2]=o,n[o+24>>2]=l),A=T}else A=T;while(!1);do if(B>>>0>=16){if(n[k+4>>2]=_|3,n[m+4>>2]=B|1,n[m+B>>2]=B,l=B>>>3,B>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=m,n[l+12>>2]=m,n[m+8>>2]=l,n[m+12>>2]=u;break}if(l=B>>>8,l?B>>>0>16777215?l=31:(He=(l+1048320|0)>>>16&8,Ge=l<>>16&4,Ge=Ge<>>16&2,l=14-(ct|He|l)+(Ge<>>15)|0,l=B>>>(l+7|0)&1|l<<1):l=0,u=11436+(l<<2)|0,n[m+28>>2]=l,o=m+16|0,n[o+4>>2]=0,n[o>>2]=0,o=1<>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}for(o=B<<((l|0)==31?0:25-(l>>>1)|0),u=n[u>>2]|0;;){if((n[u+4>>2]&-8|0)==(B|0)){He=97;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=96;break}}if((He|0)==96){n[A>>2]=m,n[m+24>>2]=u,n[m+12>>2]=m,n[m+8>>2]=m;break}else if((He|0)==97){He=u+8|0,Ge=n[He>>2]|0,n[Ge+12>>2]=m,n[He>>2]=m,n[m+8>>2]=Ge,n[m+12>>2]=u,n[m+24>>2]=0;break}}else Ge=B+_|0,n[k+4>>2]=Ge|3,Ge=k+Ge+4|0,n[Ge>>2]=n[Ge>>2]|1;while(!1);return Ge=k+8|0,I=Lt,Ge|0}else G=_}else G=_;else G=-1;while(!1);if(u=n[2785]|0,u>>>0>=G>>>0)return l=u-G|0,o=n[2788]|0,l>>>0>15?(Ge=o+G|0,n[2788]=Ge,n[2785]=l,n[Ge+4>>2]=l|1,n[Ge+l>>2]=l,n[o+4>>2]=G|3):(n[2785]=0,n[2788]=0,n[o+4>>2]=u|3,Ge=o+u+4|0,n[Ge>>2]=n[Ge>>2]|1),Ge=o+8|0,I=Lt,Ge|0;if(B=n[2786]|0,B>>>0>G>>>0)return ct=B-G|0,n[2786]=ct,Ge=n[2789]|0,He=Ge+G|0,n[2789]=He,n[He+4>>2]=ct|1,n[Ge+4>>2]=G|3,Ge=Ge+8|0,I=Lt,Ge|0;if(n[2901]|0?o=n[2903]|0:(n[2903]=4096,n[2902]=4096,n[2904]=-1,n[2905]=-1,n[2906]=0,n[2894]=0,o=ae&-16^1431655768,n[ae>>2]=o,n[2901]=o,o=4096),k=G+48|0,T=G+47|0,m=o+T|0,d=0-o|0,_=m&d,_>>>0<=G>>>0||(o=n[2893]|0,o|0&&(M=n[2891]|0,ae=M+_|0,ae>>>0<=M>>>0|ae>>>0>o>>>0)))return Ge=0,I=Lt,Ge|0;e:do if(n[2894]&4)l=0,He=133;else{u=n[2789]|0;t:do if(u){for(A=11580;o=n[A>>2]|0,!(o>>>0<=u>>>0&&(Qe=A+4|0,(o+(n[Qe>>2]|0)|0)>>>0>u>>>0));)if(o=n[A+8>>2]|0,o)A=o;else{He=118;break t}if(l=m-B&d,l>>>0<2147483647)if(o=Vh(l|0)|0,(o|0)==((n[A>>2]|0)+(n[Qe>>2]|0)|0)){if((o|0)!=-1){B=l,m=o,He=135;break e}}else A=o,He=126;else l=0}else He=118;while(!1);do if((He|0)==118)if(u=Vh(0)|0,(u|0)!=-1&&(l=u,We=n[2902]|0,Le=We+-1|0,l=(Le&l|0?(Le+l&0-We)-l|0:0)+_|0,We=n[2891]|0,Le=l+We|0,l>>>0>G>>>0&l>>>0<2147483647)){if(Qe=n[2893]|0,Qe|0&&Le>>>0<=We>>>0|Le>>>0>Qe>>>0){l=0;break}if(o=Vh(l|0)|0,(o|0)==(u|0)){B=l,m=u,He=135;break e}else A=o,He=126}else l=0;while(!1);do if((He|0)==126){if(u=0-l|0,!(k>>>0>l>>>0&(l>>>0<2147483647&(A|0)!=-1)))if((A|0)==-1){l=0;break}else{B=l,m=A,He=135;break e}if(o=n[2903]|0,o=T-l+o&0-o,o>>>0>=2147483647){B=l,m=A,He=135;break e}if((Vh(o|0)|0)==-1){Vh(u|0)|0,l=0;break}else{B=o+l|0,m=A,He=135;break e}}while(!1);n[2894]=n[2894]|4,He=133}while(!1);if((He|0)==133&&_>>>0<2147483647&&(ct=Vh(_|0)|0,Qe=Vh(0)|0,tt=Qe-ct|0,Ze=tt>>>0>(G+40|0)>>>0,!((ct|0)==-1|Ze^1|ct>>>0>>0&((ct|0)!=-1&(Qe|0)!=-1)^1))&&(B=Ze?tt:l,m=ct,He=135),(He|0)==135){l=(n[2891]|0)+B|0,n[2891]=l,l>>>0>(n[2892]|0)>>>0&&(n[2892]=l),T=n[2789]|0;do if(T){for(l=11580;;){if(o=n[l>>2]|0,u=l+4|0,A=n[u>>2]|0,(m|0)==(o+A|0)){He=145;break}if(d=n[l+8>>2]|0,d)l=d;else break}if((He|0)==145&&!(n[l+12>>2]&8|0)&&T>>>0>>0&T>>>0>=o>>>0){n[u>>2]=A+B,Ge=T+8|0,Ge=Ge&7|0?0-Ge&7:0,He=T+Ge|0,Ge=(n[2786]|0)+(B-Ge)|0,n[2789]=He,n[2786]=Ge,n[He+4>>2]=Ge|1,n[He+Ge+4>>2]=40,n[2790]=n[2905];break}for(m>>>0<(n[2787]|0)>>>0&&(n[2787]=m),u=m+B|0,l=11580;;){if((n[l>>2]|0)==(u|0)){He=153;break}if(o=n[l+8>>2]|0,o)l=o;else break}if((He|0)==153&&!(n[l+12>>2]&8|0)){n[l>>2]=m,M=l+4|0,n[M>>2]=(n[M>>2]|0)+B,M=m+8|0,M=m+(M&7|0?0-M&7:0)|0,l=u+8|0,l=u+(l&7|0?0-l&7:0)|0,_=M+G|0,k=l-M-G|0,n[M+4>>2]=G|3;do if((l|0)!=(T|0)){if((l|0)==(n[2788]|0)){Ge=(n[2785]|0)+k|0,n[2785]=Ge,n[2788]=_,n[_+4>>2]=Ge|1,n[_+Ge>>2]=Ge;break}if(o=n[l+4>>2]|0,(o&3|0)==1){B=o&-8,A=o>>>3;e:do if(o>>>0<256)if(o=n[l+8>>2]|0,u=n[l+12>>2]|0,(u|0)==(o|0)){n[2783]=n[2783]&~(1<>2]=u,n[u+8>>2]=o;break}else{m=n[l+24>>2]|0,o=n[l+12>>2]|0;do if((o|0)==(l|0)){if(A=l+16|0,u=A+4|0,o=n[u>>2]|0,!o)if(o=n[A>>2]|0,o)u=A;else{o=0;break}for(;;){if(A=o+20|0,d=n[A>>2]|0,d|0){o=d,u=A;continue}if(A=o+16|0,d=n[A>>2]|0,d)o=d,u=A;else break}n[u>>2]=0}else Ge=n[l+8>>2]|0,n[Ge+12>>2]=o,n[o+8>>2]=Ge;while(!1);if(!m)break;u=n[l+28>>2]|0,A=11436+(u<<2)|0;do if((l|0)!=(n[A>>2]|0)){if(n[m+16+(((n[m+16>>2]|0)!=(l|0)&1)<<2)>>2]=o,!o)break e}else{if(n[A>>2]=o,o|0)break;n[2784]=n[2784]&~(1<>2]=m,u=l+16|0,A=n[u>>2]|0,A|0&&(n[o+16>>2]=A,n[A+24>>2]=o),u=n[u+4>>2]|0,!u)break;n[o+20>>2]=u,n[u+24>>2]=o}while(!1);l=l+B|0,d=B+k|0}else d=k;if(l=l+4|0,n[l>>2]=n[l>>2]&-2,n[_+4>>2]=d|1,n[_+d>>2]=d,l=d>>>3,d>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=_,n[l+12>>2]=_,n[_+8>>2]=l,n[_+12>>2]=u;break}l=d>>>8;do if(!l)l=0;else{if(d>>>0>16777215){l=31;break}He=(l+1048320|0)>>>16&8,Ge=l<>>16&4,Ge=Ge<>>16&2,l=14-(ct|He|l)+(Ge<>>15)|0,l=d>>>(l+7|0)&1|l<<1}while(!1);if(A=11436+(l<<2)|0,n[_+28>>2]=l,o=_+16|0,n[o+4>>2]=0,n[o>>2]=0,o=n[2784]|0,u=1<>2]=_,n[_+24>>2]=A,n[_+12>>2]=_,n[_+8>>2]=_;break}for(o=d<<((l|0)==31?0:25-(l>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){He=194;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=193;break}}if((He|0)==193){n[A>>2]=_,n[_+24>>2]=u,n[_+12>>2]=_,n[_+8>>2]=_;break}else if((He|0)==194){He=u+8|0,Ge=n[He>>2]|0,n[Ge+12>>2]=_,n[He>>2]=_,n[_+8>>2]=Ge,n[_+12>>2]=u,n[_+24>>2]=0;break}}else Ge=(n[2786]|0)+k|0,n[2786]=Ge,n[2789]=_,n[_+4>>2]=Ge|1;while(!1);return Ge=M+8|0,I=Lt,Ge|0}for(l=11580;o=n[l>>2]|0,!(o>>>0<=T>>>0&&(Ge=o+(n[l+4>>2]|0)|0,Ge>>>0>T>>>0));)l=n[l+8>>2]|0;d=Ge+-47|0,o=d+8|0,o=d+(o&7|0?0-o&7:0)|0,d=T+16|0,o=o>>>0>>0?T:o,l=o+8|0,u=m+8|0,u=u&7|0?0-u&7:0,He=m+u|0,u=B+-40-u|0,n[2789]=He,n[2786]=u,n[He+4>>2]=u|1,n[He+u+4>>2]=40,n[2790]=n[2905],u=o+4|0,n[u>>2]=27,n[l>>2]=n[2895],n[l+4>>2]=n[2896],n[l+8>>2]=n[2897],n[l+12>>2]=n[2898],n[2895]=m,n[2896]=B,n[2898]=0,n[2897]=l,l=o+24|0;do He=l,l=l+4|0,n[l>>2]=7;while((He+8|0)>>>0>>0);if((o|0)!=(T|0)){if(m=o-T|0,n[u>>2]=n[u>>2]&-2,n[T+4>>2]=m|1,n[o>>2]=m,l=m>>>3,m>>>0<256){u=11172+(l<<1<<2)|0,o=n[2783]|0,l=1<>2]|0):(n[2783]=o|l,l=u,o=u+8|0),n[o>>2]=T,n[l+12>>2]=T,n[T+8>>2]=l,n[T+12>>2]=u;break}if(l=m>>>8,l?m>>>0>16777215?u=31:(He=(l+1048320|0)>>>16&8,Ge=l<>>16&4,Ge=Ge<>>16&2,u=14-(ct|He|u)+(Ge<>>15)|0,u=m>>>(u+7|0)&1|u<<1):u=0,A=11436+(u<<2)|0,n[T+28>>2]=u,n[T+20>>2]=0,n[d>>2]=0,l=n[2784]|0,o=1<>2]=T,n[T+24>>2]=A,n[T+12>>2]=T,n[T+8>>2]=T;break}for(o=m<<((u|0)==31?0:25-(u>>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(m|0)){He=216;break}if(A=u+16+(o>>>31<<2)|0,l=n[A>>2]|0,l)o=o<<1,u=l;else{He=215;break}}if((He|0)==215){n[A>>2]=T,n[T+24>>2]=u,n[T+12>>2]=T,n[T+8>>2]=T;break}else if((He|0)==216){He=u+8|0,Ge=n[He>>2]|0,n[Ge+12>>2]=T,n[He>>2]=T,n[T+8>>2]=Ge,n[T+12>>2]=u,n[T+24>>2]=0;break}}}else{Ge=n[2787]|0,(Ge|0)==0|m>>>0>>0&&(n[2787]=m),n[2895]=m,n[2896]=B,n[2898]=0,n[2792]=n[2901],n[2791]=-1,l=0;do Ge=11172+(l<<1<<2)|0,n[Ge+12>>2]=Ge,n[Ge+8>>2]=Ge,l=l+1|0;while((l|0)!=32);Ge=m+8|0,Ge=Ge&7|0?0-Ge&7:0,He=m+Ge|0,Ge=B+-40-Ge|0,n[2789]=He,n[2786]=Ge,n[He+4>>2]=Ge|1,n[He+Ge+4>>2]=40,n[2790]=n[2905]}while(!1);if(l=n[2786]|0,l>>>0>G>>>0)return ct=l-G|0,n[2786]=ct,Ge=n[2789]|0,He=Ge+G|0,n[2789]=He,n[He+4>>2]=ct|1,n[Ge+4>>2]=G|3,Ge=Ge+8|0,I=Lt,Ge|0}return n[(Zy()|0)>>2]=12,Ge=0,I=Lt,Ge|0}function tx(o){o=o|0;var l=0,u=0,A=0,d=0,m=0,B=0,k=0,T=0;if(o){u=o+-8|0,d=n[2787]|0,o=n[o+-4>>2]|0,l=o&-8,T=u+l|0;do if(o&1)k=u,B=u;else{if(A=n[u>>2]|0,!(o&3)||(B=u+(0-A)|0,m=A+l|0,B>>>0>>0))return;if((B|0)==(n[2788]|0)){if(o=T+4|0,l=n[o>>2]|0,(l&3|0)!=3){k=B,l=m;break}n[2785]=m,n[o>>2]=l&-2,n[B+4>>2]=m|1,n[B+m>>2]=m;return}if(u=A>>>3,A>>>0<256)if(o=n[B+8>>2]|0,l=n[B+12>>2]|0,(l|0)==(o|0)){n[2783]=n[2783]&~(1<>2]=l,n[l+8>>2]=o,k=B,l=m;break}d=n[B+24>>2]|0,o=n[B+12>>2]|0;do if((o|0)==(B|0)){if(u=B+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{o=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0}else k=n[B+8>>2]|0,n[k+12>>2]=o,n[o+8>>2]=k;while(!1);if(d){if(l=n[B+28>>2]|0,u=11436+(l<<2)|0,(B|0)==(n[u>>2]|0)){if(n[u>>2]=o,!o){n[2784]=n[2784]&~(1<>2]|0)!=(B|0)&1)<<2)>>2]=o,!o){k=B,l=m;break}n[o+24>>2]=d,l=B+16|0,u=n[l>>2]|0,u|0&&(n[o+16>>2]=u,n[u+24>>2]=o),l=n[l+4>>2]|0,l?(n[o+20>>2]=l,n[l+24>>2]=o,k=B,l=m):(k=B,l=m)}else k=B,l=m}while(!1);if(!(B>>>0>=T>>>0)&&(o=T+4|0,A=n[o>>2]|0,!!(A&1))){if(A&2)n[o>>2]=A&-2,n[k+4>>2]=l|1,n[B+l>>2]=l,d=l;else{if(o=n[2788]|0,(T|0)==(n[2789]|0)){if(T=(n[2786]|0)+l|0,n[2786]=T,n[2789]=k,n[k+4>>2]=T|1,(k|0)!=(o|0))return;n[2788]=0,n[2785]=0;return}if((T|0)==(o|0)){T=(n[2785]|0)+l|0,n[2785]=T,n[2788]=B,n[k+4>>2]=T|1,n[B+T>>2]=T;return}d=(A&-8)+l|0,u=A>>>3;do if(A>>>0<256)if(l=n[T+8>>2]|0,o=n[T+12>>2]|0,(o|0)==(l|0)){n[2783]=n[2783]&~(1<>2]=o,n[o+8>>2]=l;break}else{m=n[T+24>>2]|0,o=n[T+12>>2]|0;do if((o|0)==(T|0)){if(u=T+16|0,l=u+4|0,o=n[l>>2]|0,!o)if(o=n[u>>2]|0,o)l=u;else{u=0;break}for(;;){if(u=o+20|0,A=n[u>>2]|0,A|0){o=A,l=u;continue}if(u=o+16|0,A=n[u>>2]|0,A)o=A,l=u;else break}n[l>>2]=0,u=o}else u=n[T+8>>2]|0,n[u+12>>2]=o,n[o+8>>2]=u,u=o;while(!1);if(m|0){if(o=n[T+28>>2]|0,l=11436+(o<<2)|0,(T|0)==(n[l>>2]|0)){if(n[l>>2]=u,!u){n[2784]=n[2784]&~(1<>2]|0)!=(T|0)&1)<<2)>>2]=u,!u)break;n[u+24>>2]=m,o=T+16|0,l=n[o>>2]|0,l|0&&(n[u+16>>2]=l,n[l+24>>2]=u),o=n[o+4>>2]|0,o|0&&(n[u+20>>2]=o,n[o+24>>2]=u)}}while(!1);if(n[k+4>>2]=d|1,n[B+d>>2]=d,(k|0)==(n[2788]|0)){n[2785]=d;return}}if(o=d>>>3,d>>>0<256){u=11172+(o<<1<<2)|0,l=n[2783]|0,o=1<>2]|0):(n[2783]=l|o,o=u,l=u+8|0),n[l>>2]=k,n[o+12>>2]=k,n[k+8>>2]=o,n[k+12>>2]=u;return}o=d>>>8,o?d>>>0>16777215?o=31:(B=(o+1048320|0)>>>16&8,T=o<>>16&4,T=T<>>16&2,o=14-(m|B|o)+(T<>>15)|0,o=d>>>(o+7|0)&1|o<<1):o=0,A=11436+(o<<2)|0,n[k+28>>2]=o,n[k+20>>2]=0,n[k+16>>2]=0,l=n[2784]|0,u=1<>>1)|0),u=n[A>>2]|0;;){if((n[u+4>>2]&-8|0)==(d|0)){o=73;break}if(A=u+16+(l>>>31<<2)|0,o=n[A>>2]|0,o)l=l<<1,u=o;else{o=72;break}}if((o|0)==72){n[A>>2]=k,n[k+24>>2]=u,n[k+12>>2]=k,n[k+8>>2]=k;break}else if((o|0)==73){B=u+8|0,T=n[B>>2]|0,n[T+12>>2]=k,n[B>>2]=k,n[k+8>>2]=T,n[k+12>>2]=u,n[k+24>>2]=0;break}}else n[2784]=l|u,n[A>>2]=k,n[k+24>>2]=A,n[k+12>>2]=k,n[k+8>>2]=k;while(!1);if(T=(n[2791]|0)+-1|0,n[2791]=T,!T)o=11588;else return;for(;o=n[o>>2]|0,o;)o=o+8|0;n[2791]=-1}}}function LYe(){return 11628}function MYe(o){o=o|0;var l=0,u=0;return l=I,I=I+16|0,u=l,n[u>>2]=HYe(n[o+60>>2]|0)|0,o=rx(hu(6,u|0)|0)|0,I=l,o|0}function t$(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0;G=I,I=I+48|0,_=G+16|0,m=G,d=G+32|0,k=o+28|0,A=n[k>>2]|0,n[d>>2]=A,T=o+20|0,A=(n[T>>2]|0)-A|0,n[d+4>>2]=A,n[d+8>>2]=l,n[d+12>>2]=u,A=A+u|0,B=o+60|0,n[m>>2]=n[B>>2],n[m+4>>2]=d,n[m+8>>2]=2,m=rx(Ma(146,m|0)|0)|0;e:do if((A|0)!=(m|0)){for(l=2;!((m|0)<0);)if(A=A-m|0,We=n[d+4>>2]|0,ae=m>>>0>We>>>0,d=ae?d+8|0:d,l=(ae<<31>>31)+l|0,We=m-(ae?We:0)|0,n[d>>2]=(n[d>>2]|0)+We,ae=d+4|0,n[ae>>2]=(n[ae>>2]|0)-We,n[_>>2]=n[B>>2],n[_+4>>2]=d,n[_+8>>2]=l,m=rx(Ma(146,_|0)|0)|0,(A|0)==(m|0)){M=3;break e}n[o+16>>2]=0,n[k>>2]=0,n[T>>2]=0,n[o>>2]=n[o>>2]|32,(l|0)==2?u=0:u=u-(n[d+4>>2]|0)|0}else M=3;while(!1);return(M|0)==3&&(We=n[o+44>>2]|0,n[o+16>>2]=We+(n[o+48>>2]|0),n[k>>2]=We,n[T>>2]=We),I=G,u|0}function _Ye(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;return d=I,I=I+32|0,m=d,A=d+20|0,n[m>>2]=n[o+60>>2],n[m+4>>2]=0,n[m+8>>2]=l,n[m+12>>2]=A,n[m+16>>2]=u,(rx(La(140,m|0)|0)|0)<0?(n[A>>2]=-1,o=-1):o=n[A>>2]|0,I=d,o|0}function rx(o){return o=o|0,o>>>0>4294963200&&(n[(Zy()|0)>>2]=0-o,o=-1),o|0}function Zy(){return(UYe()|0)+64|0}function UYe(){return m_()|0}function m_(){return 2084}function HYe(o){return o=o|0,o|0}function jYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;return d=I,I=I+32|0,A=d,n[o+36>>2]=1,!(n[o>>2]&64|0)&&(n[A>>2]=n[o+60>>2],n[A+4>>2]=21523,n[A+8>>2]=d+16,io(54,A|0)|0)&&(s[o+75>>0]=-1),A=t$(o,l,u)|0,I=d,A|0}function r$(o,l){o=o|0,l=l|0;var u=0,A=0;if(u=s[o>>0]|0,A=s[l>>0]|0,!(u<<24>>24)||u<<24>>24!=A<<24>>24)o=A;else{do o=o+1|0,l=l+1|0,u=s[o>>0]|0,A=s[l>>0]|0;while(!(!(u<<24>>24)||u<<24>>24!=A<<24>>24));o=A}return(u&255)-(o&255)|0}function qYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0;e:do if(!u)o=0;else{for(;A=s[o>>0]|0,d=s[l>>0]|0,A<<24>>24==d<<24>>24;)if(u=u+-1|0,u)o=o+1|0,l=l+1|0;else{o=0;break e}o=(A&255)-(d&255)|0}while(!1);return o|0}function n$(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0;Qe=I,I=I+224|0,M=Qe+120|0,G=Qe+80|0,We=Qe,Le=Qe+136|0,A=G,d=A+40|0;do n[A>>2]=0,A=A+4|0;while((A|0)<(d|0));return n[M>>2]=n[u>>2],(y_(0,l,M,We,G)|0)<0?u=-1:((n[o+76>>2]|0)>-1?ae=GYe(o)|0:ae=0,u=n[o>>2]|0,_=u&32,(s[o+74>>0]|0)<1&&(n[o>>2]=u&-33),A=o+48|0,n[A>>2]|0?u=y_(o,l,M,We,G)|0:(d=o+44|0,m=n[d>>2]|0,n[d>>2]=Le,B=o+28|0,n[B>>2]=Le,k=o+20|0,n[k>>2]=Le,n[A>>2]=80,T=o+16|0,n[T>>2]=Le+80,u=y_(o,l,M,We,G)|0,m&&(ox[n[o+36>>2]&7](o,0,0)|0,u=n[k>>2]|0?u:-1,n[d>>2]=m,n[A>>2]=0,n[T>>2]=0,n[B>>2]=0,n[k>>2]=0)),A=n[o>>2]|0,n[o>>2]=A|_,ae|0&&WYe(o),u=A&32|0?-1:u),I=Qe,u|0}function y_(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0;cr=I,I=I+64|0,fr=cr+16|0,$t=cr,Lt=cr+24|0,Tr=cr+8|0,Hr=cr+20|0,n[fr>>2]=l,ct=(o|0)!=0,He=Lt+40|0,Ge=He,Lt=Lt+39|0,qr=Tr+4|0,B=0,m=0,M=0;e:for(;;){do if((m|0)>-1)if((B|0)>(2147483647-m|0)){n[(Zy()|0)>>2]=75,m=-1;break}else{m=B+m|0;break}while(!1);if(B=s[l>>0]|0,B<<24>>24)k=l;else{Ze=87;break}t:for(;;){switch(B<<24>>24){case 37:{B=k,Ze=9;break t}case 0:{B=k;break t}default:}tt=k+1|0,n[fr>>2]=tt,B=s[tt>>0]|0,k=tt}t:do if((Ze|0)==9)for(;;){if(Ze=0,(s[k+1>>0]|0)!=37)break t;if(B=B+1|0,k=k+2|0,n[fr>>2]=k,(s[k>>0]|0)==37)Ze=9;else break}while(!1);if(B=B-l|0,ct&&Ss(o,l,B),B|0){l=k;continue}T=k+1|0,B=(s[T>>0]|0)+-48|0,B>>>0<10?(tt=(s[k+2>>0]|0)==36,Qe=tt?B:-1,M=tt?1:M,T=tt?k+3|0:T):Qe=-1,n[fr>>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0;t:do if(k>>>0<32)for(_=0,G=B;;){if(B=1<>2]=T,B=s[T>>0]|0,k=(B<<24>>24)+-32|0,k>>>0>=32)break;G=B}else _=0;while(!1);if(B<<24>>24==42){if(k=T+1|0,B=(s[k>>0]|0)+-48|0,B>>>0<10&&(s[T+2>>0]|0)==36)n[d+(B<<2)>>2]=10,B=n[A+((s[k>>0]|0)+-48<<3)>>2]|0,M=1,T=T+3|0;else{if(M|0){m=-1;break}ct?(M=(n[u>>2]|0)+3&-4,B=n[M>>2]|0,n[u>>2]=M+4,M=0,T=k):(B=0,M=0,T=k)}n[fr>>2]=T,tt=(B|0)<0,B=tt?0-B|0:B,_=tt?_|8192:_}else{if(B=i$(fr)|0,(B|0)<0){m=-1;break}T=n[fr>>2]|0}do if((s[T>>0]|0)==46){if((s[T+1>>0]|0)!=42){n[fr>>2]=T+1,k=i$(fr)|0,T=n[fr>>2]|0;break}if(G=T+2|0,k=(s[G>>0]|0)+-48|0,k>>>0<10&&(s[T+3>>0]|0)==36){n[d+(k<<2)>>2]=10,k=n[A+((s[G>>0]|0)+-48<<3)>>2]|0,T=T+4|0,n[fr>>2]=T;break}if(M|0){m=-1;break e}ct?(tt=(n[u>>2]|0)+3&-4,k=n[tt>>2]|0,n[u>>2]=tt+4):k=0,n[fr>>2]=G,T=G}else k=-1;while(!1);for(Le=0;;){if(((s[T>>0]|0)+-65|0)>>>0>57){m=-1;break e}if(tt=T+1|0,n[fr>>2]=tt,G=s[(s[T>>0]|0)+-65+(5178+(Le*58|0))>>0]|0,ae=G&255,(ae+-1|0)>>>0<8)Le=ae,T=tt;else break}if(!(G<<24>>24)){m=-1;break}We=(Qe|0)>-1;do if(G<<24>>24==19)if(We){m=-1;break e}else Ze=49;else{if(We){n[d+(Qe<<2)>>2]=ae,We=A+(Qe<<3)|0,Qe=n[We+4>>2]|0,Ze=$t,n[Ze>>2]=n[We>>2],n[Ze+4>>2]=Qe,Ze=49;break}if(!ct){m=0;break e}s$($t,ae,u)}while(!1);if((Ze|0)==49&&(Ze=0,!ct)){B=0,l=tt;continue}T=s[T>>0]|0,T=(Le|0)!=0&(T&15|0)==3?T&-33:T,We=_&-65537,Qe=_&8192|0?We:_;t:do switch(T|0){case 110:switch((Le&255)<<24>>24){case 0:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 1:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 2:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}case 3:{a[n[$t>>2]>>1]=m,B=0,l=tt;continue e}case 4:{s[n[$t>>2]>>0]=m,B=0,l=tt;continue e}case 6:{n[n[$t>>2]>>2]=m,B=0,l=tt;continue e}case 7:{B=n[$t>>2]|0,n[B>>2]=m,n[B+4>>2]=((m|0)<0)<<31>>31,B=0,l=tt;continue e}default:{B=0,l=tt;continue e}}case 112:{T=120,k=k>>>0>8?k:8,l=Qe|8,Ze=61;break}case 88:case 120:{l=Qe,Ze=61;break}case 111:{T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,ae=VYe(l,T,He)|0,We=Ge-ae|0,_=0,G=5642,k=(Qe&8|0)==0|(k|0)>(We|0)?k:We+1|0,We=Qe,Ze=67;break}case 105:case 100:if(T=$t,l=n[T>>2]|0,T=n[T+4>>2]|0,(T|0)<0){l=nx(0,0,l|0,T|0)|0,T=Ee,_=$t,n[_>>2]=l,n[_+4>>2]=T,_=1,G=5642,Ze=66;break t}else{_=(Qe&2049|0)!=0&1,G=Qe&2048|0?5643:Qe&1|0?5644:5642,Ze=66;break t}case 117:{T=$t,_=0,G=5642,l=n[T>>2]|0,T=n[T+4>>2]|0,Ze=66;break}case 99:{s[Lt>>0]=n[$t>>2],l=Lt,_=0,G=5642,ae=He,T=1,k=We;break}case 109:{T=KYe(n[(Zy()|0)>>2]|0)|0,Ze=71;break}case 115:{T=n[$t>>2]|0,T=T|0?T:5652,Ze=71;break}case 67:{n[Tr>>2]=n[$t>>2],n[qr>>2]=0,n[$t>>2]=Tr,ae=-1,T=Tr,Ze=75;break}case 83:{l=n[$t>>2]|0,k?(ae=k,T=l,Ze=75):(Ms(o,32,B,0,Qe),l=0,Ze=84);break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{B=zYe(o,+E[$t>>3],B,k,Qe,T)|0,l=tt;continue e}default:_=0,G=5642,ae=He,T=k,k=Qe}while(!1);t:do if((Ze|0)==61)Qe=$t,Le=n[Qe>>2]|0,Qe=n[Qe+4>>2]|0,ae=YYe(Le,Qe,He,T&32)|0,G=(l&8|0)==0|(Le|0)==0&(Qe|0)==0,_=G?0:2,G=G?5642:5642+(T>>4)|0,We=l,l=Le,T=Qe,Ze=67;else if((Ze|0)==66)ae=Xy(l,T,He)|0,We=Qe,Ze=67;else if((Ze|0)==71)Ze=0,Qe=JYe(T,0,k)|0,Le=(Qe|0)==0,l=T,_=0,G=5642,ae=Le?T+k|0:Qe,T=Le?k:Qe-T|0,k=We;else if((Ze|0)==75){for(Ze=0,G=T,l=0,k=0;_=n[G>>2]|0,!(!_||(k=o$(Hr,_)|0,(k|0)<0|k>>>0>(ae-l|0)>>>0));)if(l=k+l|0,ae>>>0>l>>>0)G=G+4|0;else break;if((k|0)<0){m=-1;break e}if(Ms(o,32,B,l,Qe),!l)l=0,Ze=84;else for(_=0;;){if(k=n[T>>2]|0,!k){Ze=84;break t}if(k=o$(Hr,k)|0,_=k+_|0,(_|0)>(l|0)){Ze=84;break t}if(Ss(o,Hr,k),_>>>0>=l>>>0){Ze=84;break}else T=T+4|0}}while(!1);if((Ze|0)==67)Ze=0,T=(l|0)!=0|(T|0)!=0,Qe=(k|0)!=0|T,T=((T^1)&1)+(Ge-ae)|0,l=Qe?ae:He,ae=He,T=Qe?(k|0)>(T|0)?k:T:k,k=(k|0)>-1?We&-65537:We;else if((Ze|0)==84){Ze=0,Ms(o,32,B,l,Qe^8192),B=(B|0)>(l|0)?B:l,l=tt;continue}Le=ae-l|0,We=(T|0)<(Le|0)?Le:T,Qe=We+_|0,B=(B|0)<(Qe|0)?Qe:B,Ms(o,32,B,Qe,k),Ss(o,G,_),Ms(o,48,B,Qe,k^65536),Ms(o,48,We,Le,0),Ss(o,l,Le),Ms(o,32,B,Qe,k^8192),l=tt}e:do if((Ze|0)==87&&!o)if(!M)m=0;else{for(m=1;l=n[d+(m<<2)>>2]|0,!!l;)if(s$(A+(m<<3)|0,l,u),m=m+1|0,(m|0)>=10){m=1;break e}for(;;){if(n[d+(m<<2)>>2]|0){m=-1;break e}if(m=m+1|0,(m|0)>=10){m=1;break}}}while(!1);return I=cr,m|0}function GYe(o){return o=o|0,0}function WYe(o){o=o|0}function Ss(o,l,u){o=o|0,l=l|0,u=u|0,n[o>>2]&32||sVe(l,u,o)|0}function i$(o){o=o|0;var l=0,u=0,A=0;if(u=n[o>>2]|0,A=(s[u>>0]|0)+-48|0,A>>>0<10){l=0;do l=A+(l*10|0)|0,u=u+1|0,n[o>>2]=u,A=(s[u>>0]|0)+-48|0;while(A>>>0<10)}else l=0;return l|0}function s$(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;e:do if(l>>>0<=20)do switch(l|0){case 9:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,n[o>>2]=l;break e}case 10:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=((l|0)<0)<<31>>31;break e}case 11:{A=(n[u>>2]|0)+3&-4,l=n[A>>2]|0,n[u>>2]=A+4,A=o,n[A>>2]=l,n[A+4>>2]=0;break e}case 12:{A=(n[u>>2]|0)+7&-8,l=A,d=n[l>>2]|0,l=n[l+4>>2]|0,n[u>>2]=A+8,A=o,n[A>>2]=d,n[A+4>>2]=l;break e}case 13:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&65535)<<16>>16,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 14:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&65535,n[d+4>>2]=0;break e}case 15:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,A=(A&255)<<24>>24,d=o,n[d>>2]=A,n[d+4>>2]=((A|0)<0)<<31>>31;break e}case 16:{d=(n[u>>2]|0)+3&-4,A=n[d>>2]|0,n[u>>2]=d+4,d=o,n[d>>2]=A&255,n[d+4>>2]=0;break e}case 17:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}case 18:{d=(n[u>>2]|0)+7&-8,m=+E[d>>3],n[u>>2]=d+8,E[o>>3]=m;break e}default:break e}while(!1);while(!1)}function YYe(o,l,u,A){if(o=o|0,l=l|0,u=u|0,A=A|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=c[5694+(o&15)>>0]|0|A,o=ix(o|0,l|0,4)|0,l=Ee;while(!((o|0)==0&(l|0)==0));return u|0}function VYe(o,l,u){if(o=o|0,l=l|0,u=u|0,!((o|0)==0&(l|0)==0))do u=u+-1|0,s[u>>0]=o&7|48,o=ix(o|0,l|0,3)|0,l=Ee;while(!((o|0)==0&(l|0)==0));return u|0}function Xy(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if(l>>>0>0|(l|0)==0&o>>>0>4294967295){for(;A=w_(o|0,l|0,10,0)|0,u=u+-1|0,s[u>>0]=A&255|48,A=o,o=C_(o|0,l|0,10,0)|0,l>>>0>9|(l|0)==9&A>>>0>4294967295;)l=Ee;l=o}else l=o;if(l)for(;u=u+-1|0,s[u>>0]=(l>>>0)%10|0|48,!(l>>>0<10);)l=(l>>>0)/10|0;return u|0}function KYe(o){return o=o|0,tVe(o,n[(eVe()|0)+188>>2]|0)|0}function JYe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;m=l&255,A=(u|0)!=0;e:do if(A&(o&3|0)!=0)for(d=l&255;;){if((s[o>>0]|0)==d<<24>>24){B=6;break e}if(o=o+1|0,u=u+-1|0,A=(u|0)!=0,!(A&(o&3|0)!=0)){B=5;break}}else B=5;while(!1);(B|0)==5&&(A?B=6:u=0);e:do if((B|0)==6&&(d=l&255,(s[o>>0]|0)!=d<<24>>24)){A=_e(m,16843009)|0;t:do if(u>>>0>3){for(;m=n[o>>2]^A,!((m&-2139062144^-2139062144)&m+-16843009|0);)if(o=o+4|0,u=u+-4|0,u>>>0<=3){B=11;break t}}else B=11;while(!1);if((B|0)==11&&!u){u=0;break}for(;;){if((s[o>>0]|0)==d<<24>>24)break e;if(o=o+1|0,u=u+-1|0,!u){u=0;break}}}while(!1);return(u|0?o:0)|0}function Ms(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0;if(B=I,I=I+256|0,m=B,(u|0)>(A|0)&(d&73728|0)==0){if(d=u-A|0,eE(m|0,l|0,(d>>>0<256?d:256)|0)|0,d>>>0>255){l=u-A|0;do Ss(o,m,256),d=d+-256|0;while(d>>>0>255);d=l&255}Ss(o,m,d)}I=B}function o$(o,l){return o=o|0,l=l|0,o?o=XYe(o,l,0)|0:o=0,o|0}function zYe(o,l,u,A,d,m){o=o|0,l=+l,u=u|0,A=A|0,d=d|0,m=m|0;var B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0,Qe=0,tt=0,Ze=0,ct=0,He=0,Ge=0,Lt=0,qr=0,fr=0,$t=0,Tr=0,Hr=0,cr=0,Hn=0;Hn=I,I=I+560|0,T=Hn+8|0,tt=Hn,cr=Hn+524|0,Hr=cr,_=Hn+512|0,n[tt>>2]=0,Tr=_+12|0,a$(l)|0,(Ee|0)<0?(l=-l,fr=1,qr=5659):(fr=(d&2049|0)!=0&1,qr=d&2048|0?5662:d&1|0?5665:5660),a$(l)|0,$t=Ee&2146435072;do if($t>>>0<2146435072|($t|0)==2146435072&!1){if(We=+ZYe(l,tt)*2,B=We!=0,B&&(n[tt>>2]=(n[tt>>2]|0)+-1),ct=m|32,(ct|0)==97){Le=m&32,ae=Le|0?qr+9|0:qr,G=fr|2,B=12-A|0;do if(A>>>0>11|(B|0)==0)l=We;else{l=8;do B=B+-1|0,l=l*16;while(B|0);if((s[ae>>0]|0)==45){l=-(l+(-We-l));break}else{l=We+l-l;break}}while(!1);k=n[tt>>2]|0,B=(k|0)<0?0-k|0:k,B=Xy(B,((B|0)<0)<<31>>31,Tr)|0,(B|0)==(Tr|0)&&(B=_+11|0,s[B>>0]=48),s[B+-1>>0]=(k>>31&2)+43,M=B+-2|0,s[M>>0]=m+15,_=(A|0)<1,T=(d&8|0)==0,B=cr;do $t=~~l,k=B+1|0,s[B>>0]=c[5694+$t>>0]|Le,l=(l-+($t|0))*16,(k-Hr|0)==1&&!(T&(_&l==0))?(s[k>>0]=46,B=B+2|0):B=k;while(l!=0);$t=B-Hr|0,Hr=Tr-M|0,Tr=(A|0)!=0&($t+-2|0)<(A|0)?A+2|0:$t,B=Hr+G+Tr|0,Ms(o,32,u,B,d),Ss(o,ae,G),Ms(o,48,u,B,d^65536),Ss(o,cr,$t),Ms(o,48,Tr-$t|0,0,0),Ss(o,M,Hr),Ms(o,32,u,B,d^8192);break}k=(A|0)<0?6:A,B?(B=(n[tt>>2]|0)+-28|0,n[tt>>2]=B,l=We*268435456):(l=We,B=n[tt>>2]|0),$t=(B|0)<0?T:T+288|0,T=$t;do Ge=~~l>>>0,n[T>>2]=Ge,T=T+4|0,l=(l-+(Ge>>>0))*1e9;while(l!=0);if((B|0)>0)for(_=$t,G=T;;){if(M=(B|0)<29?B:29,B=G+-4|0,B>>>0>=_>>>0){T=0;do He=p$(n[B>>2]|0,0,M|0)|0,He=I_(He|0,Ee|0,T|0,0)|0,Ge=Ee,Ze=w_(He|0,Ge|0,1e9,0)|0,n[B>>2]=Ze,T=C_(He|0,Ge|0,1e9,0)|0,B=B+-4|0;while(B>>>0>=_>>>0);T&&(_=_+-4|0,n[_>>2]=T)}for(T=G;!(T>>>0<=_>>>0);)if(B=T+-4|0,!(n[B>>2]|0))T=B;else break;if(B=(n[tt>>2]|0)-M|0,n[tt>>2]=B,(B|0)>0)G=T;else break}else _=$t;if((B|0)<0){A=((k+25|0)/9|0)+1|0,Qe=(ct|0)==102;do{if(Le=0-B|0,Le=(Le|0)<9?Le:9,_>>>0>>0){M=(1<>>Le,ae=0,B=_;do Ge=n[B>>2]|0,n[B>>2]=(Ge>>>Le)+ae,ae=_e(Ge&M,G)|0,B=B+4|0;while(B>>>0>>0);B=n[_>>2]|0?_:_+4|0,ae?(n[T>>2]=ae,_=B,B=T+4|0):(_=B,B=T)}else _=n[_>>2]|0?_:_+4|0,B=T;T=Qe?$t:_,T=(B-T>>2|0)>(A|0)?T+(A<<2)|0:B,B=(n[tt>>2]|0)+Le|0,n[tt>>2]=B}while((B|0)<0);B=_,A=T}else B=_,A=T;if(Ge=$t,B>>>0>>0){if(T=(Ge-B>>2)*9|0,M=n[B>>2]|0,M>>>0>=10){_=10;do _=_*10|0,T=T+1|0;while(M>>>0>=_>>>0)}}else T=0;if(Qe=(ct|0)==103,Ze=(k|0)!=0,_=k-((ct|0)!=102?T:0)+((Ze&Qe)<<31>>31)|0,(_|0)<(((A-Ge>>2)*9|0)+-9|0)){if(_=_+9216|0,Le=$t+4+(((_|0)/9|0)+-1024<<2)|0,_=((_|0)%9|0)+1|0,(_|0)<9){M=10;do M=M*10|0,_=_+1|0;while((_|0)!=9)}else M=10;if(G=n[Le>>2]|0,ae=(G>>>0)%(M>>>0)|0,_=(Le+4|0)==(A|0),_&(ae|0)==0)_=Le;else if(We=((G>>>0)/(M>>>0)|0)&1|0?9007199254740994:9007199254740992,He=(M|0)/2|0,l=ae>>>0>>0?.5:_&(ae|0)==(He|0)?1:1.5,fr&&(He=(s[qr>>0]|0)==45,l=He?-l:l,We=He?-We:We),_=G-ae|0,n[Le>>2]=_,We+l!=We){if(He=_+M|0,n[Le>>2]=He,He>>>0>999999999)for(T=Le;_=T+-4|0,n[T>>2]=0,_>>>0>>0&&(B=B+-4|0,n[B>>2]=0),He=(n[_>>2]|0)+1|0,n[_>>2]=He,He>>>0>999999999;)T=_;else _=Le;if(T=(Ge-B>>2)*9|0,G=n[B>>2]|0,G>>>0>=10){M=10;do M=M*10|0,T=T+1|0;while(G>>>0>=M>>>0)}}else _=Le;_=_+4|0,_=A>>>0>_>>>0?_:A,He=B}else _=A,He=B;for(ct=_;;){if(ct>>>0<=He>>>0){tt=0;break}if(B=ct+-4|0,!(n[B>>2]|0))ct=B;else{tt=1;break}}A=0-T|0;do if(Qe)if(B=((Ze^1)&1)+k|0,(B|0)>(T|0)&(T|0)>-5?(M=m+-1|0,k=B+-1-T|0):(M=m+-2|0,k=B+-1|0),B=d&8,B)Le=B;else{if(tt&&(Lt=n[ct+-4>>2]|0,(Lt|0)!=0))if((Lt>>>0)%10|0)_=0;else{_=0,B=10;do B=B*10|0,_=_+1|0;while(!((Lt>>>0)%(B>>>0)|0|0))}else _=9;if(B=((ct-Ge>>2)*9|0)+-9|0,(M|32|0)==102){Le=B-_|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}else{Le=B+T-_|0,Le=(Le|0)>0?Le:0,k=(k|0)<(Le|0)?k:Le,Le=0;break}}else M=m,Le=d&8;while(!1);if(Qe=k|Le,G=(Qe|0)!=0&1,ae=(M|32|0)==102,ae)Ze=0,B=(T|0)>0?T:0;else{if(B=(T|0)<0?A:T,B=Xy(B,((B|0)<0)<<31>>31,Tr)|0,_=Tr,(_-B|0)<2)do B=B+-1|0,s[B>>0]=48;while((_-B|0)<2);s[B+-1>>0]=(T>>31&2)+43,B=B+-2|0,s[B>>0]=M,Ze=B,B=_-B|0}if(B=fr+1+k+G+B|0,Ms(o,32,u,B,d),Ss(o,qr,fr),Ms(o,48,u,B,d^65536),ae){M=He>>>0>$t>>>0?$t:He,Le=cr+9|0,G=Le,ae=cr+8|0,_=M;do{if(T=Xy(n[_>>2]|0,0,Le)|0,(_|0)==(M|0))(T|0)==(Le|0)&&(s[ae>>0]=48,T=ae);else if(T>>>0>cr>>>0){eE(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}Ss(o,T,G-T|0),_=_+4|0}while(_>>>0<=$t>>>0);if(Qe|0&&Ss(o,5710,1),_>>>0>>0&(k|0)>0)for(;;){if(T=Xy(n[_>>2]|0,0,Le)|0,T>>>0>cr>>>0){eE(cr|0,48,T-Hr|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}if(Ss(o,T,(k|0)<9?k:9),_=_+4|0,T=k+-9|0,_>>>0>>0&(k|0)>9)k=T;else{k=T;break}}Ms(o,48,k+9|0,9,0)}else{if(Qe=tt?ct:He+4|0,(k|0)>-1){tt=cr+9|0,Le=(Le|0)==0,A=tt,G=0-Hr|0,ae=cr+8|0,M=He;do{T=Xy(n[M>>2]|0,0,tt)|0,(T|0)==(tt|0)&&(s[ae>>0]=48,T=ae);do if((M|0)==(He|0)){if(_=T+1|0,Ss(o,T,1),Le&(k|0)<1){T=_;break}Ss(o,5710,1),T=_}else{if(T>>>0<=cr>>>0)break;eE(cr|0,48,T+G|0)|0;do T=T+-1|0;while(T>>>0>cr>>>0)}while(!1);Hr=A-T|0,Ss(o,T,(k|0)>(Hr|0)?Hr:k),k=k-Hr|0,M=M+4|0}while(M>>>0>>0&(k|0)>-1)}Ms(o,48,k+18|0,18,0),Ss(o,Ze,Tr-Ze|0)}Ms(o,32,u,B,d^8192)}else cr=(m&32|0)!=0,B=fr+3|0,Ms(o,32,u,B,d&-65537),Ss(o,qr,fr),Ss(o,l!=l|!1?cr?5686:5690:cr?5678:5682,3),Ms(o,32,u,B,d^8192);while(!1);return I=Hn,((B|0)<(u|0)?u:B)|0}function a$(o){o=+o;var l=0;return E[S>>3]=o,l=n[S>>2]|0,Ee=n[S+4>>2]|0,l|0}function ZYe(o,l){return o=+o,l=l|0,+ +l$(o,l)}function l$(o,l){o=+o,l=l|0;var u=0,A=0,d=0;switch(E[S>>3]=o,u=n[S>>2]|0,A=n[S+4>>2]|0,d=ix(u|0,A|0,52)|0,d&2047){case 0:{o!=0?(o=+l$(o*18446744073709552e3,l),u=(n[l>>2]|0)+-64|0):u=0,n[l>>2]=u;break}case 2047:break;default:n[l>>2]=(d&2047)+-1022,n[S>>2]=u,n[S+4>>2]=A&-2146435073|1071644672,o=+E[S>>3]}return+o}function XYe(o,l,u){o=o|0,l=l|0,u=u|0;do if(o){if(l>>>0<128){s[o>>0]=l,o=1;break}if(!(n[n[($Ye()|0)+188>>2]>>2]|0))if((l&-128|0)==57216){s[o>>0]=l,o=1;break}else{n[(Zy()|0)>>2]=84,o=-1;break}if(l>>>0<2048){s[o>>0]=l>>>6|192,s[o+1>>0]=l&63|128,o=2;break}if(l>>>0<55296|(l&-8192|0)==57344){s[o>>0]=l>>>12|224,s[o+1>>0]=l>>>6&63|128,s[o+2>>0]=l&63|128,o=3;break}if((l+-65536|0)>>>0<1048576){s[o>>0]=l>>>18|240,s[o+1>>0]=l>>>12&63|128,s[o+2>>0]=l>>>6&63|128,s[o+3>>0]=l&63|128,o=4;break}else{n[(Zy()|0)>>2]=84,o=-1;break}}else o=1;while(!1);return o|0}function $Ye(){return m_()|0}function eVe(){return m_()|0}function tVe(o,l){o=o|0,l=l|0;var u=0,A=0;for(A=0;;){if((c[5712+A>>0]|0)==(o|0)){o=2;break}if(u=A+1|0,(u|0)==87){u=5800,A=87,o=5;break}else A=u}if((o|0)==2&&(A?(u=5800,o=5):u=5800),(o|0)==5)for(;;){do o=u,u=u+1|0;while(s[o>>0]|0);if(A=A+-1|0,A)o=5;else break}return rVe(u,n[l+20>>2]|0)|0}function rVe(o,l){return o=o|0,l=l|0,nVe(o,l)|0}function nVe(o,l){return o=o|0,l=l|0,l?l=iVe(n[l>>2]|0,n[l+4>>2]|0,o)|0:l=0,(l|0?l:o)|0}function iVe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0;ae=(n[o>>2]|0)+1794895138|0,m=pd(n[o+8>>2]|0,ae)|0,A=pd(n[o+12>>2]|0,ae)|0,d=pd(n[o+16>>2]|0,ae)|0;e:do if(m>>>0>>2>>>0&&(G=l-(m<<2)|0,A>>>0>>0&d>>>0>>0)&&!((d|A)&3|0)){for(G=A>>>2,M=d>>>2,_=0;;){if(k=m>>>1,T=_+k|0,B=T<<1,d=B+G|0,A=pd(n[o+(d<<2)>>2]|0,ae)|0,d=pd(n[o+(d+1<<2)>>2]|0,ae)|0,!(d>>>0>>0&A>>>0<(l-d|0)>>>0)){A=0;break e}if(s[o+(d+A)>>0]|0){A=0;break e}if(A=r$(u,o+d|0)|0,!A)break;if(A=(A|0)<0,(m|0)==1){A=0;break e}else _=A?_:T,m=A?k:m-k|0}A=B+M|0,d=pd(n[o+(A<<2)>>2]|0,ae)|0,A=pd(n[o+(A+1<<2)>>2]|0,ae)|0,A>>>0>>0&d>>>0<(l-A|0)>>>0?A=s[o+(A+d)>>0]|0?0:o+A|0:A=0}else A=0;while(!1);return A|0}function pd(o,l){o=o|0,l=l|0;var u=0;return u=d$(o|0)|0,(l|0?u:o)|0}function sVe(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0,k=0;A=u+16|0,d=n[A>>2]|0,d?m=5:oVe(u)|0?A=0:(d=n[A>>2]|0,m=5);e:do if((m|0)==5){if(k=u+20|0,B=n[k>>2]|0,A=B,(d-B|0)>>>0>>0){A=ox[n[u+36>>2]&7](u,o,l)|0;break}t:do if((s[u+75>>0]|0)>-1){for(B=l;;){if(!B){m=0,d=o;break t}if(d=B+-1|0,(s[o+d>>0]|0)==10)break;B=d}if(A=ox[n[u+36>>2]&7](u,o,B)|0,A>>>0>>0)break e;m=B,d=o+B|0,l=l-B|0,A=n[k>>2]|0}else m=0,d=o;while(!1);Qr(A|0,d|0,l|0)|0,n[k>>2]=(n[k>>2]|0)+l,A=m+l|0}while(!1);return A|0}function oVe(o){o=o|0;var l=0,u=0;return l=o+74|0,u=s[l>>0]|0,s[l>>0]=u+255|u,l=n[o>>2]|0,l&8?(n[o>>2]=l|32,o=-1):(n[o+8>>2]=0,n[o+4>>2]=0,u=n[o+44>>2]|0,n[o+28>>2]=u,n[o+20>>2]=u,n[o+16>>2]=u+(n[o+48>>2]|0),o=0),o|0}function $n(o,l){o=y(o),l=y(l);var u=0,A=0;u=c$(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=c$(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?l:o;break}else{o=o>2]=o,n[S>>2]|0|0}function hd(o,l){o=y(o),l=y(l);var u=0,A=0;u=u$(o)|0;do if((u&2147483647)>>>0<=2139095040){if(A=u$(l)|0,(A&2147483647)>>>0<=2139095040)if((A^u|0)<0){o=(u|0)<0?o:l;break}else{o=o>2]=o,n[S>>2]|0|0}function E_(o,l){o=y(o),l=y(l);var u=0,A=0,d=0,m=0,B=0,k=0,T=0,_=0;m=(h[S>>2]=o,n[S>>2]|0),k=(h[S>>2]=l,n[S>>2]|0),u=m>>>23&255,B=k>>>23&255,T=m&-2147483648,d=k<<1;e:do if(d|0&&!((u|0)==255|((aVe(l)|0)&2147483647)>>>0>2139095040)){if(A=m<<1,A>>>0<=d>>>0)return l=y(o*y(0)),y((A|0)==(d|0)?l:o);if(u)A=m&8388607|8388608;else{if(u=m<<9,(u|0)>-1){A=u,u=0;do u=u+-1|0,A=A<<1;while((A|0)>-1)}else u=0;A=m<<1-u}if(B)k=k&8388607|8388608;else{if(m=k<<9,(m|0)>-1){d=0;do d=d+-1|0,m=m<<1;while((m|0)>-1)}else d=0;B=d,k=k<<1-d}d=A-k|0,m=(d|0)>-1;t:do if((u|0)>(B|0)){for(;;){if(m)if(d)A=d;else break;if(A=A<<1,u=u+-1|0,d=A-k|0,m=(d|0)>-1,(u|0)<=(B|0))break t}l=y(o*y(0));break e}while(!1);if(m)if(d)A=d;else{l=y(o*y(0));break}if(A>>>0<8388608)do A=A<<1,u=u+-1|0;while(A>>>0<8388608);(u|0)>0?u=A+-8388608|u<<23:u=A>>>(1-u|0),l=(n[S>>2]=u|T,y(h[S>>2]))}else _=3;while(!1);return(_|0)==3&&(l=y(o*l),l=y(l/l)),y(l)}function aVe(o){return o=y(o),h[S>>2]=o,n[S>>2]|0|0}function lVe(o,l){return o=o|0,l=l|0,n$(n[582]|0,o,l)|0}function sn(o){o=o|0,Nt()}function $y(o){o=o|0}function cVe(o,l){return o=o|0,l=l|0,0}function uVe(o){return o=o|0,(f$(o+4|0)|0)==-1?(op[n[(n[o>>2]|0)+8>>2]&127](o),o=1):o=0,o|0}function f$(o){o=o|0;var l=0;return l=n[o>>2]|0,n[o>>2]=l+-1,l+-1|0}function Yh(o){o=o|0,uVe(o)|0&&fVe(o)}function fVe(o){o=o|0;var l=0;l=o+8|0,n[l>>2]|0&&(f$(l)|0)!=-1||op[n[(n[o>>2]|0)+16>>2]&127](o)}function Jt(o){o=o|0;var l=0;for(l=o|0?o:1;o=ex(l)|0,!(o|0);){if(o=pVe()|0,!o){o=0;break}D$[o&0]()}return o|0}function A$(o){return o=o|0,Jt(o)|0}function yt(o){o=o|0,tx(o)}function AVe(o){o=o|0,(s[o+11>>0]|0)<0&&yt(n[o>>2]|0)}function pVe(){var o=0;return o=n[2923]|0,n[2923]=o+0,o|0}function hVe(){}function nx(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,A=l-A-(u>>>0>o>>>0|0)>>>0,Ee=A,o-u>>>0|0|0}function I_(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,u=o+u>>>0,Ee=l+A+(u>>>0>>0|0)>>>0,u|0|0}function eE(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0,B=0;if(m=o+u|0,l=l&255,(u|0)>=67){for(;o&3;)s[o>>0]=l,o=o+1|0;for(A=m&-4|0,d=A-64|0,B=l|l<<8|l<<16|l<<24;(o|0)<=(d|0);)n[o>>2]=B,n[o+4>>2]=B,n[o+8>>2]=B,n[o+12>>2]=B,n[o+16>>2]=B,n[o+20>>2]=B,n[o+24>>2]=B,n[o+28>>2]=B,n[o+32>>2]=B,n[o+36>>2]=B,n[o+40>>2]=B,n[o+44>>2]=B,n[o+48>>2]=B,n[o+52>>2]=B,n[o+56>>2]=B,n[o+60>>2]=B,o=o+64|0;for(;(o|0)<(A|0);)n[o>>2]=B,o=o+4|0}for(;(o|0)<(m|0);)s[o>>0]=l,o=o+1|0;return m-u|0}function p$(o,l,u){return o=o|0,l=l|0,u=u|0,(u|0)<32?(Ee=l<>>32-u,o<>>u,o>>>u|(l&(1<>>u-32|0)}function Qr(o,l,u){o=o|0,l=l|0,u=u|0;var A=0,d=0,m=0;if((u|0)>=8192)return MA(o|0,l|0,u|0)|0;if(m=o|0,d=o+u|0,(o&3)==(l&3)){for(;o&3;){if(!u)return m|0;s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0,u=u-1|0}for(u=d&-4|0,A=u-64|0;(o|0)<=(A|0);)n[o>>2]=n[l>>2],n[o+4>>2]=n[l+4>>2],n[o+8>>2]=n[l+8>>2],n[o+12>>2]=n[l+12>>2],n[o+16>>2]=n[l+16>>2],n[o+20>>2]=n[l+20>>2],n[o+24>>2]=n[l+24>>2],n[o+28>>2]=n[l+28>>2],n[o+32>>2]=n[l+32>>2],n[o+36>>2]=n[l+36>>2],n[o+40>>2]=n[l+40>>2],n[o+44>>2]=n[l+44>>2],n[o+48>>2]=n[l+48>>2],n[o+52>>2]=n[l+52>>2],n[o+56>>2]=n[l+56>>2],n[o+60>>2]=n[l+60>>2],o=o+64|0,l=l+64|0;for(;(o|0)<(u|0);)n[o>>2]=n[l>>2],o=o+4|0,l=l+4|0}else for(u=d-4|0;(o|0)<(u|0);)s[o>>0]=s[l>>0]|0,s[o+1>>0]=s[l+1>>0]|0,s[o+2>>0]=s[l+2>>0]|0,s[o+3>>0]=s[l+3>>0]|0,o=o+4|0,l=l+4|0;for(;(o|0)<(d|0);)s[o>>0]=s[l>>0]|0,o=o+1|0,l=l+1|0;return m|0}function h$(o){o=o|0;var l=0;return l=s[N+(o&255)>>0]|0,(l|0)<8?l|0:(l=s[N+(o>>8&255)>>0]|0,(l|0)<8?l+8|0:(l=s[N+(o>>16&255)>>0]|0,(l|0)<8?l+16|0:(s[N+(o>>>24)>>0]|0)+24|0))}function g$(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0;var m=0,B=0,k=0,T=0,_=0,M=0,G=0,ae=0,We=0,Le=0;if(M=o,T=l,_=T,B=u,ae=A,k=ae,!_)return m=(d|0)!=0,k?m?(n[d>>2]=o|0,n[d+4>>2]=l&0,ae=0,d=0,Ee=ae,d|0):(ae=0,d=0,Ee=ae,d|0):(m&&(n[d>>2]=(M>>>0)%(B>>>0),n[d+4>>2]=0),ae=0,d=(M>>>0)/(B>>>0)>>>0,Ee=ae,d|0);m=(k|0)==0;do if(B){if(!m){if(m=(b(k|0)|0)-(b(_|0)|0)|0,m>>>0<=31){G=m+1|0,k=31-m|0,l=m-31>>31,B=G,o=M>>>(G>>>0)&l|_<>>(G>>>0)&l,m=0,k=M<>2]=o|0,n[d+4>>2]=T|l&0,ae=0,d=0,Ee=ae,d|0):(ae=0,d=0,Ee=ae,d|0)}if(m=B-1|0,m&B|0){k=(b(B|0)|0)+33-(b(_|0)|0)|0,Le=64-k|0,G=32-k|0,T=G>>31,We=k-32|0,l=We>>31,B=k,o=G-1>>31&_>>>(We>>>0)|(_<>>(k>>>0))&l,l=l&_>>>(k>>>0),m=M<>>(We>>>0))&T|M<>31;break}return d|0&&(n[d>>2]=m&M,n[d+4>>2]=0),(B|0)==1?(We=T|l&0,Le=o|0|0,Ee=We,Le|0):(Le=h$(B|0)|0,We=_>>>(Le>>>0)|0,Le=_<<32-Le|M>>>(Le>>>0)|0,Ee=We,Le|0)}else{if(m)return d|0&&(n[d>>2]=(_>>>0)%(B>>>0),n[d+4>>2]=0),We=0,Le=(_>>>0)/(B>>>0)>>>0,Ee=We,Le|0;if(!M)return d|0&&(n[d>>2]=0,n[d+4>>2]=(_>>>0)%(k>>>0)),We=0,Le=(_>>>0)/(k>>>0)>>>0,Ee=We,Le|0;if(m=k-1|0,!(m&k))return d|0&&(n[d>>2]=o|0,n[d+4>>2]=m&_|l&0),We=0,Le=_>>>((h$(k|0)|0)>>>0),Ee=We,Le|0;if(m=(b(k|0)|0)-(b(_|0)|0)|0,m>>>0<=30){l=m+1|0,k=31-m|0,B=l,o=_<>>(l>>>0),l=_>>>(l>>>0),m=0,k=M<>2]=o|0,n[d+4>>2]=T|l&0,We=0,Le=0,Ee=We,Le|0):(We=0,Le=0,Ee=We,Le|0)}while(!1);if(!B)_=k,T=0,k=0;else{G=u|0|0,M=ae|A&0,_=I_(G|0,M|0,-1,-1)|0,u=Ee,T=k,k=0;do A=T,T=m>>>31|T<<1,m=k|m<<1,A=o<<1|A>>>31|0,ae=o>>>31|l<<1|0,nx(_|0,u|0,A|0,ae|0)|0,Le=Ee,We=Le>>31|((Le|0)<0?-1:0)<<1,k=We&1,o=nx(A|0,ae|0,We&G|0,(((Le|0)<0?-1:0)>>31|((Le|0)<0?-1:0)<<1)&M|0)|0,l=Ee,B=B-1|0;while(B|0);_=T,T=0}return B=0,d|0&&(n[d>>2]=o,n[d+4>>2]=l),We=(m|0)>>>31|(_|B)<<1|(B<<1|m>>>31)&0|T,Le=(m<<1|0)&-2|k,Ee=We,Le|0}function C_(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,g$(o,l,u,A,0)|0}function Vh(o){o=o|0;var l=0,u=0;return u=o+15&-16|0,l=n[C>>2]|0,o=l+u|0,(u|0)>0&(o|0)<(l|0)|(o|0)<0?(oe()|0,pu(12),-1):(n[C>>2]=o,(o|0)>($()|0)&&!(Z()|0)?(n[C>>2]=l,pu(12),-1):l|0)}function F2(o,l,u){o=o|0,l=l|0,u=u|0;var A=0;if((l|0)<(o|0)&(o|0)<(l+u|0)){for(A=o,l=l+u|0,o=o+u|0;(u|0)>0;)o=o-1|0,l=l-1|0,u=u-1|0,s[o>>0]=s[l>>0]|0;o=A}else Qr(o,l,u)|0;return o|0}function w_(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0;var d=0,m=0;return m=I,I=I+16|0,d=m|0,g$(o,l,u,A,d)|0,I=m,Ee=n[d+4>>2]|0,n[d>>2]|0|0}function d$(o){return o=o|0,(o&255)<<24|(o>>8&255)<<16|(o>>16&255)<<8|o>>>24|0}function gVe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,m$[o&1](l|0,u|0,A|0,d|0,m|0)}function dVe(o,l,u){o=o|0,l=l|0,u=y(u),y$[o&1](l|0,y(u))}function mVe(o,l,u){o=o|0,l=l|0,u=+u,E$[o&31](l|0,+u)}function yVe(o,l,u,A){return o=o|0,l=l|0,u=y(u),A=y(A),y(I$[o&0](l|0,y(u),y(A)))}function EVe(o,l){o=o|0,l=l|0,op[o&127](l|0)}function IVe(o,l,u){o=o|0,l=l|0,u=u|0,ap[o&31](l|0,u|0)}function CVe(o,l){return o=o|0,l=l|0,dd[o&31](l|0)|0}function wVe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,C$[o&1](l|0,+u,+A,d|0)}function BVe(o,l,u,A){o=o|0,l=l|0,u=+u,A=+A,n7e[o&1](l|0,+u,+A)}function vVe(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,ox[o&7](l|0,u|0,A|0)|0}function SVe(o,l,u,A){return o=o|0,l=l|0,u=u|0,A=A|0,+i7e[o&1](l|0,u|0,A|0)}function DVe(o,l){return o=o|0,l=l|0,+w$[o&15](l|0)}function bVe(o,l,u){return o=o|0,l=l|0,u=+u,s7e[o&1](l|0,+u)|0}function PVe(o,l,u){return o=o|0,l=l|0,u=u|0,v_[o&15](l|0,u|0)|0}function xVe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=+A,d=+d,m=m|0,o7e[o&1](l|0,u|0,+A,+d,m|0)}function kVe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,B=B|0,a7e[o&1](l|0,u|0,A|0,d|0,m|0,B|0)}function QVe(o,l,u){return o=o|0,l=l|0,u=u|0,+B$[o&7](l|0,u|0)}function TVe(o){return o=o|0,ax[o&7]()|0}function RVe(o,l,u,A,d,m){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,v$[o&1](l|0,u|0,A|0,d|0,m|0)|0}function FVe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=+d,l7e[o&1](l|0,u|0,A|0,+d)}function NVe(o,l,u,A,d,m,B){o=o|0,l=l|0,u=u|0,A=y(A),d=d|0,m=y(m),B=B|0,S$[o&1](l|0,u|0,y(A),d|0,y(m),B|0)}function OVe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,L2[o&15](l|0,u|0,A|0)}function LVe(o){o=o|0,D$[o&0]()}function MVe(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,b$[o&15](l|0,u|0,+A)}function _Ve(o,l,u){return o=o|0,l=+l,u=+u,c7e[o&1](+l,+u)|0}function UVe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,S_[o&15](l|0,u|0,A|0,d|0)}function HVe(o,l,u,A,d){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(0)}function jVe(o,l){o=o|0,l=y(l),F(1)}function Xa(o,l){o=o|0,l=+l,F(2)}function qVe(o,l,u){return o=o|0,l=y(l),u=y(u),F(3),Xe}function wr(o){o=o|0,F(4)}function N2(o,l){o=o|0,l=l|0,F(5)}function Ll(o){return o=o|0,F(6),0}function GVe(o,l,u,A){o=o|0,l=+l,u=+u,A=A|0,F(7)}function WVe(o,l,u){o=o|0,l=+l,u=+u,F(8)}function YVe(o,l,u){return o=o|0,l=l|0,u=u|0,F(9),0}function VVe(o,l,u){return o=o|0,l=l|0,u=u|0,F(10),0}function gd(o){return o=o|0,F(11),0}function KVe(o,l){return o=o|0,l=+l,F(12),0}function O2(o,l){return o=o|0,l=l|0,F(13),0}function JVe(o,l,u,A,d){o=o|0,l=l|0,u=+u,A=+A,d=d|0,F(14)}function zVe(o,l,u,A,d,m){o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,m=m|0,F(15)}function B_(o,l){return o=o|0,l=l|0,F(16),0}function ZVe(){return F(17),0}function XVe(o,l,u,A,d){return o=o|0,l=l|0,u=u|0,A=A|0,d=d|0,F(18),0}function $Ve(o,l,u,A){o=o|0,l=l|0,u=u|0,A=+A,F(19)}function e7e(o,l,u,A,d,m){o=o|0,l=l|0,u=y(u),A=A|0,d=y(d),m=m|0,F(20)}function sx(o,l,u){o=o|0,l=l|0,u=u|0,F(21)}function t7e(){F(22)}function tE(o,l,u){o=o|0,l=l|0,u=+u,F(23)}function r7e(o,l){return o=+o,l=+l,F(24),0}function rE(o,l,u,A){o=o|0,l=l|0,u=u|0,A=A|0,F(25)}var m$=[HVe,XGe],y$=[jVe,Ty],E$=[Xa,$g,Lh,m2,y2,E2,I2,Pf,Uy,C2,xf,ed,td,w2,B2,vu,rd,v2,Hy,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa,Xa],I$=[qVe],op=[wr,$y,TLe,RLe,FLe,c4e,u4e,f4e,bqe,Pqe,xqe,_Ge,UGe,HGe,aYe,lYe,cYe,vl,Xg,p2,sr,gc,qP,GP,wLe,jLe,eMe,yMe,FMe,ZMe,h_e,x_e,G_e,oUe,wUe,MUe,e4e,P4e,G4e,o3e,w3e,M3e,e8e,E8e,F8e,K8e,uHe,kP,HHe,nje,wje,Uje,t6e,w6e,T6e,N6e,X6e,tqe,yqe,Qqe,Fqe,Zqe,gGe,nZ,z5e,b9e,j9e,nWe,SWe,UWe,ZWe,eYe,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr,wr],ap=[N2,Ly,nM,h2,g2,xr,oo,Zi,Os,Bs,_y,Oh,D2,NP,sd,oM,aM,OP,LP,uM,kf,ne,B8e,L8e,Gje,$5e,BGe,HX,N2,N2,N2,N2],dd=[Ll,MYe,Ny,id,qy,da,QP,Mh,S2,sM,RP,Gy,MP,fM,Vy,pHe,s6e,eGe,n9e,Fl,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll,Ll],C$=[GVe,dM],n7e=[WVe,Cqe],ox=[YVe,t$,_Ye,jYe,t_e,T4e,WHe,aWe],i7e=[VVe,DUe],w$=[gd,_h,FP,tp,mM,v,D,Q,H,V,gd,gd,gd,gd,gd,gd],s7e=[KVe,x6e],v_=[O2,cVe,_P,DLe,wMe,m_e,T_e,i4e,K4e,X8e,Ry,Y9e,O2,O2,O2,O2],o7e=[JVe,iMe],a7e=[zVe,xWe],B$=[B_,lM,Se,Ue,At,jUe,B_,B_],ax=[ZVe,Gt,Fy,xP,_6e,sqe,Mqe,iYe],v$=[XVe,Sy],l7e=[$Ve,D3e],S$=[e7e,AM],L2=[sx,To,TP,cM,Du,MMe,V_e,j3e,i8e,rM,C5e,Q9e,GWe,sx,sx,sx],D$=[t7e],b$=[tE,iM,My,ep,d2,Su,jy,nd,u3e,aje,S6e,tE,tE,tE,tE,tE],c7e=[r7e,Sqe],S_=[rE,uUe,CHe,Dje,h6e,W6e,fqe,Wqe,IGe,c9e,gYe,rE,rE,rE,rE,rE];return{_llvm_bswap_i32:d$,dynCall_idd:_Ve,dynCall_i:TVe,_i64Subtract:nx,___udivdi3:C_,dynCall_vif:dVe,setThrew:ua,dynCall_viii:OVe,_bitshift64Lshr:ix,_bitshift64Shl:p$,dynCall_vi:EVe,dynCall_viiddi:xVe,dynCall_diii:SVe,dynCall_iii:PVe,_memset:eE,_sbrk:Vh,_memcpy:Qr,__GLOBAL__sub_I_Yoga_cpp:u2,dynCall_vii:IVe,___uremdi3:w_,dynCall_vid:mVe,stackAlloc:Ha,_nbind_init:DYe,getTempRet0:UA,dynCall_di:DVe,dynCall_iid:bVe,setTempRet0:_A,_i64Add:I_,dynCall_fiff:yVe,dynCall_iiii:vVe,_emscripten_get_global_libc:LYe,dynCall_viid:MVe,dynCall_viiid:FVe,dynCall_viififi:NVe,dynCall_ii:CVe,__GLOBAL__sub_I_Binding_cc:j5e,dynCall_viiii:UVe,dynCall_iiiiii:RVe,stackSave:gf,dynCall_viiiii:gVe,__GLOBAL__sub_I_nbind_cc:vr,dynCall_vidd:BVe,_free:tx,runPostSets:hVe,dynCall_viiiiii:kVe,establishStackSpace:wn,_memmove:F2,stackRestore:cc,_malloc:ex,__GLOBAL__sub_I_common_cc:lGe,dynCall_viddi:wVe,dynCall_dii:QVe,dynCall_v:LVe}}(Module.asmGlobalArg,Module.asmLibraryArg,buffer),_llvm_bswap_i32=Module._llvm_bswap_i32=asm._llvm_bswap_i32,getTempRet0=Module.getTempRet0=asm.getTempRet0,___udivdi3=Module.___udivdi3=asm.___udivdi3,setThrew=Module.setThrew=asm.setThrew,_bitshift64Lshr=Module._bitshift64Lshr=asm._bitshift64Lshr,_bitshift64Shl=Module._bitshift64Shl=asm._bitshift64Shl,_memset=Module._memset=asm._memset,_sbrk=Module._sbrk=asm._sbrk,_memcpy=Module._memcpy=asm._memcpy,stackAlloc=Module.stackAlloc=asm.stackAlloc,___uremdi3=Module.___uremdi3=asm.___uremdi3,_nbind_init=Module._nbind_init=asm._nbind_init,_i64Subtract=Module._i64Subtract=asm._i64Subtract,setTempRet0=Module.setTempRet0=asm.setTempRet0,_i64Add=Module._i64Add=asm._i64Add,_emscripten_get_global_libc=Module._emscripten_get_global_libc=asm._emscripten_get_global_libc,__GLOBAL__sub_I_Yoga_cpp=Module.__GLOBAL__sub_I_Yoga_cpp=asm.__GLOBAL__sub_I_Yoga_cpp,__GLOBAL__sub_I_Binding_cc=Module.__GLOBAL__sub_I_Binding_cc=asm.__GLOBAL__sub_I_Binding_cc,stackSave=Module.stackSave=asm.stackSave,__GLOBAL__sub_I_nbind_cc=Module.__GLOBAL__sub_I_nbind_cc=asm.__GLOBAL__sub_I_nbind_cc,_free=Module._free=asm._free,runPostSets=Module.runPostSets=asm.runPostSets,establishStackSpace=Module.establishStackSpace=asm.establishStackSpace,_memmove=Module._memmove=asm._memmove,stackRestore=Module.stackRestore=asm.stackRestore,_malloc=Module._malloc=asm._malloc,__GLOBAL__sub_I_common_cc=Module.__GLOBAL__sub_I_common_cc=asm.__GLOBAL__sub_I_common_cc,dynCall_viiiii=Module.dynCall_viiiii=asm.dynCall_viiiii,dynCall_vif=Module.dynCall_vif=asm.dynCall_vif,dynCall_vid=Module.dynCall_vid=asm.dynCall_vid,dynCall_fiff=Module.dynCall_fiff=asm.dynCall_fiff,dynCall_vi=Module.dynCall_vi=asm.dynCall_vi,dynCall_vii=Module.dynCall_vii=asm.dynCall_vii,dynCall_ii=Module.dynCall_ii=asm.dynCall_ii,dynCall_viddi=Module.dynCall_viddi=asm.dynCall_viddi,dynCall_vidd=Module.dynCall_vidd=asm.dynCall_vidd,dynCall_iiii=Module.dynCall_iiii=asm.dynCall_iiii,dynCall_diii=Module.dynCall_diii=asm.dynCall_diii,dynCall_di=Module.dynCall_di=asm.dynCall_di,dynCall_iid=Module.dynCall_iid=asm.dynCall_iid,dynCall_iii=Module.dynCall_iii=asm.dynCall_iii,dynCall_viiddi=Module.dynCall_viiddi=asm.dynCall_viiddi,dynCall_viiiiii=Module.dynCall_viiiiii=asm.dynCall_viiiiii,dynCall_dii=Module.dynCall_dii=asm.dynCall_dii,dynCall_i=Module.dynCall_i=asm.dynCall_i,dynCall_iiiiii=Module.dynCall_iiiiii=asm.dynCall_iiiiii,dynCall_viiid=Module.dynCall_viiid=asm.dynCall_viiid,dynCall_viififi=Module.dynCall_viififi=asm.dynCall_viififi,dynCall_viii=Module.dynCall_viii=asm.dynCall_viii,dynCall_v=Module.dynCall_v=asm.dynCall_v,dynCall_viid=Module.dynCall_viid=asm.dynCall_viid,dynCall_idd=Module.dynCall_idd=asm.dynCall_idd,dynCall_viiii=Module.dynCall_viiii=asm.dynCall_viiii;Runtime.stackAlloc=Module.stackAlloc,Runtime.stackSave=Module.stackSave,Runtime.stackRestore=Module.stackRestore,Runtime.establishStackSpace=Module.establishStackSpace,Runtime.setTempRet0=Module.setTempRet0,Runtime.getTempRet0=Module.getTempRet0,Module.asm=asm;function ExitStatus(t){this.name="ExitStatus",this.message="Program terminated with exit("+t+")",this.status=t}ExitStatus.prototype=new Error,ExitStatus.prototype.constructor=ExitStatus;var initialStackTop,preloadStartTime=null,calledMain=!1;dependenciesFulfilled=function t(){Module.calledRun||run(),Module.calledRun||(dependenciesFulfilled=t)},Module.callMain=Module.callMain=function t(e){e=e||[],ensureInitRuntime();var r=e.length+1;function s(){for(var p=0;p<3;p++)a.push(0)}var a=[allocate(intArrayFromString(Module.thisProgram),"i8",ALLOC_NORMAL)];s();for(var n=0;n0||(preRun(),runDependencies>0)||Module.calledRun)return;function e(){Module.calledRun||(Module.calledRun=!0,!ABORT&&(ensureInitRuntime(),preMain(),Module.onRuntimeInitialized&&Module.onRuntimeInitialized(),Module._main&&shouldRunNow&&Module.callMain(t),postRun()))}Module.setStatus?(Module.setStatus("Running..."),setTimeout(function(){setTimeout(function(){Module.setStatus("")},1),e()},1)):e()}Module.run=Module.run=run;function exit(t,e){e&&Module.noExitRuntime||(Module.noExitRuntime||(ABORT=!0,EXITSTATUS=t,STACKTOP=initialStackTop,exitRuntime(),Module.onExit&&Module.onExit(t)),ENVIRONMENT_IS_NODE&&process.exit(t),Module.quit(t,new ExitStatus(t)))}Module.exit=Module.exit=exit;var abortDecorators=[];function abort(t){Module.onAbort&&Module.onAbort(t),t!==void 0?(Module.print(t),Module.printErr(t),t=JSON.stringify(t)):t="",ABORT=!0,EXITSTATUS=1;var e=` +If this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.`,r="abort("+t+") at "+stackTrace()+e;throw abortDecorators&&abortDecorators.forEach(function(s){r=s(r,t)}),r}if(Module.abort=Module.abort=abort,Module.preInit)for(typeof Module.preInit=="function"&&(Module.preInit=[Module.preInit]);Module.preInit.length>0;)Module.preInit.pop()();var shouldRunNow=!0;Module.noInitialRun&&(shouldRunNow=!1),run()})});var Nm=L((Khr,KDe)=>{"use strict";var $Pt=YDe(),ext=VDe(),PW=!1,xW=null;ext({},function(t,e){if(!PW){if(PW=!0,t)throw t;xW=e}});if(!PW)throw new Error("Failed to load the yoga module - it needed to be loaded synchronously, but didn't");KDe.exports=$Pt(xW.bind,xW.lib)});var QW=L((Jhr,kW)=>{"use strict";var JDe=t=>Number.isNaN(t)?!1:t>=4352&&(t<=4447||t===9001||t===9002||11904<=t&&t<=12871&&t!==12351||12880<=t&&t<=19903||19968<=t&&t<=42182||43360<=t&&t<=43388||44032<=t&&t<=55203||63744<=t&&t<=64255||65040<=t&&t<=65049||65072<=t&&t<=65131||65281<=t&&t<=65376||65504<=t&&t<=65510||110592<=t&&t<=110593||127488<=t&&t<=127569||131072<=t&&t<=262141);kW.exports=JDe;kW.exports.default=JDe});var ZDe=L((zhr,zDe)=>{"use strict";zDe.exports=function(){return/\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB-\uDFFD])|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)\uFE0F|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\uD83C\uDFF4\u200D\u2620)\uFE0F|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDF6\uD83C\uDDE6|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDB5\uDDB6\uDDBB\uDDD2-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5\uDEEB\uDEEC\uDEF4-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g}});var iD=L((Zhr,TW)=>{"use strict";var txt=bk(),rxt=QW(),nxt=ZDe(),XDe=t=>{if(typeof t!="string"||t.length===0||(t=txt(t),t.length===0))return 0;t=t.replace(nxt()," ");let e=0;for(let r=0;r=127&&s<=159||s>=768&&s<=879||(s>65535&&r++,e+=rxt(s)?2:1)}return e};TW.exports=XDe;TW.exports.default=XDe});var FW=L((Xhr,RW)=>{"use strict";var ixt=iD(),$De=t=>{let e=0;for(let r of t.split(` +`))e=Math.max(e,ixt(r));return e};RW.exports=$De;RW.exports.default=$De});var ebe=L(sD=>{"use strict";var sxt=sD&&sD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sD,"__esModule",{value:!0});var oxt=sxt(FW()),NW={};sD.default=t=>{if(t.length===0)return{width:0,height:0};if(NW[t])return NW[t];let e=oxt.default(t),r=t.split(` +`).length;return NW[t]={width:e,height:r},{width:e,height:r}}});var tbe=L(oD=>{"use strict";var axt=oD&&oD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(oD,"__esModule",{value:!0});var bn=axt(Nm()),lxt=(t,e)=>{"position"in e&&t.setPositionType(e.position==="absolute"?bn.default.POSITION_TYPE_ABSOLUTE:bn.default.POSITION_TYPE_RELATIVE)},cxt=(t,e)=>{"marginLeft"in e&&t.setMargin(bn.default.EDGE_START,e.marginLeft||0),"marginRight"in e&&t.setMargin(bn.default.EDGE_END,e.marginRight||0),"marginTop"in e&&t.setMargin(bn.default.EDGE_TOP,e.marginTop||0),"marginBottom"in e&&t.setMargin(bn.default.EDGE_BOTTOM,e.marginBottom||0)},uxt=(t,e)=>{"paddingLeft"in e&&t.setPadding(bn.default.EDGE_LEFT,e.paddingLeft||0),"paddingRight"in e&&t.setPadding(bn.default.EDGE_RIGHT,e.paddingRight||0),"paddingTop"in e&&t.setPadding(bn.default.EDGE_TOP,e.paddingTop||0),"paddingBottom"in e&&t.setPadding(bn.default.EDGE_BOTTOM,e.paddingBottom||0)},fxt=(t,e)=>{var r;"flexGrow"in e&&t.setFlexGrow((r=e.flexGrow)!==null&&r!==void 0?r:0),"flexShrink"in e&&t.setFlexShrink(typeof e.flexShrink=="number"?e.flexShrink:1),"flexDirection"in e&&(e.flexDirection==="row"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW),e.flexDirection==="row-reverse"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_ROW_REVERSE),e.flexDirection==="column"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN),e.flexDirection==="column-reverse"&&t.setFlexDirection(bn.default.FLEX_DIRECTION_COLUMN_REVERSE)),"flexBasis"in e&&(typeof e.flexBasis=="number"?t.setFlexBasis(e.flexBasis):typeof e.flexBasis=="string"?t.setFlexBasisPercent(Number.parseInt(e.flexBasis,10)):t.setFlexBasis(NaN)),"alignItems"in e&&((e.alignItems==="stretch"||!e.alignItems)&&t.setAlignItems(bn.default.ALIGN_STRETCH),e.alignItems==="flex-start"&&t.setAlignItems(bn.default.ALIGN_FLEX_START),e.alignItems==="center"&&t.setAlignItems(bn.default.ALIGN_CENTER),e.alignItems==="flex-end"&&t.setAlignItems(bn.default.ALIGN_FLEX_END)),"alignSelf"in e&&((e.alignSelf==="auto"||!e.alignSelf)&&t.setAlignSelf(bn.default.ALIGN_AUTO),e.alignSelf==="flex-start"&&t.setAlignSelf(bn.default.ALIGN_FLEX_START),e.alignSelf==="center"&&t.setAlignSelf(bn.default.ALIGN_CENTER),e.alignSelf==="flex-end"&&t.setAlignSelf(bn.default.ALIGN_FLEX_END)),"justifyContent"in e&&((e.justifyContent==="flex-start"||!e.justifyContent)&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_START),e.justifyContent==="center"&&t.setJustifyContent(bn.default.JUSTIFY_CENTER),e.justifyContent==="flex-end"&&t.setJustifyContent(bn.default.JUSTIFY_FLEX_END),e.justifyContent==="space-between"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_BETWEEN),e.justifyContent==="space-around"&&t.setJustifyContent(bn.default.JUSTIFY_SPACE_AROUND))},Axt=(t,e)=>{var r,s;"width"in e&&(typeof e.width=="number"?t.setWidth(e.width):typeof e.width=="string"?t.setWidthPercent(Number.parseInt(e.width,10)):t.setWidthAuto()),"height"in e&&(typeof e.height=="number"?t.setHeight(e.height):typeof e.height=="string"?t.setHeightPercent(Number.parseInt(e.height,10)):t.setHeightAuto()),"minWidth"in e&&(typeof e.minWidth=="string"?t.setMinWidthPercent(Number.parseInt(e.minWidth,10)):t.setMinWidth((r=e.minWidth)!==null&&r!==void 0?r:0)),"minHeight"in e&&(typeof e.minHeight=="string"?t.setMinHeightPercent(Number.parseInt(e.minHeight,10)):t.setMinHeight((s=e.minHeight)!==null&&s!==void 0?s:0))},pxt=(t,e)=>{"display"in e&&t.setDisplay(e.display==="flex"?bn.default.DISPLAY_FLEX:bn.default.DISPLAY_NONE)},hxt=(t,e)=>{if("borderStyle"in e){let r=typeof e.borderStyle=="string"?1:0;t.setBorder(bn.default.EDGE_TOP,r),t.setBorder(bn.default.EDGE_BOTTOM,r),t.setBorder(bn.default.EDGE_LEFT,r),t.setBorder(bn.default.EDGE_RIGHT,r)}};oD.default=(t,e={})=>{lxt(t,e),cxt(t,e),uxt(t,e),fxt(t,e),Axt(t,e),pxt(t,e),hxt(t,e)}});var ibe=L((t0r,nbe)=>{"use strict";var aD=iD(),gxt=bk(),dxt=IB(),LW=new Set(["\x1B","\x9B"]),mxt=39,rbe=t=>`${LW.values().next().value}[${t}m`,yxt=t=>t.split(" ").map(e=>aD(e)),OW=(t,e,r)=>{let s=[...e],a=!1,n=aD(gxt(t[t.length-1]));for(let[c,f]of s.entries()){let p=aD(f);if(n+p<=r?t[t.length-1]+=f:(t.push(f),n=0),LW.has(f))a=!0;else if(a&&f==="m"){a=!1;continue}a||(n+=p,n===r&&c0&&t.length>1&&(t[t.length-2]+=t.pop())},Ext=t=>{let e=t.split(" "),r=e.length;for(;r>0&&!(aD(e[r-1])>0);)r--;return r===e.length?t:e.slice(0,r).join(" ")+e.slice(r).join("")},Ixt=(t,e,r={})=>{if(r.trim!==!1&&t.trim()==="")return"";let s="",a="",n,c=yxt(t),f=[""];for(let[p,h]of t.split(" ").entries()){r.trim!==!1&&(f[f.length-1]=f[f.length-1].trimLeft());let E=aD(f[f.length-1]);if(p!==0&&(E>=e&&(r.wordWrap===!1||r.trim===!1)&&(f.push(""),E=0),(E>0||r.trim===!1)&&(f[f.length-1]+=" ",E++)),r.hard&&c[p]>e){let C=e-E,S=1+Math.floor((c[p]-C-1)/e);Math.floor((c[p]-1)/e)e&&E>0&&c[p]>0){if(r.wordWrap===!1&&Ee&&r.wordWrap===!1){OW(f,h,e);continue}f[f.length-1]+=h}r.trim!==!1&&(f=f.map(Ext)),s=f.join(` +`);for(let[p,h]of[...s].entries()){if(a+=h,LW.has(h)){let C=parseFloat(/\d[^m]*/.exec(s.slice(p,p+4)));n=C===mxt?null:C}let E=dxt.codes.get(Number(n));n&&E&&(s[p+1]===` +`?a+=rbe(E):h===` +`&&(a+=rbe(n)))}return a};nbe.exports=(t,e,r)=>String(t).normalize().replace(/\r\n/g,` +`).split(` +`).map(s=>Ixt(s,e,r)).join(` +`)});var abe=L((r0r,obe)=>{"use strict";var sbe="[\uD800-\uDBFF][\uDC00-\uDFFF]",Cxt=t=>t&&t.exact?new RegExp(`^${sbe}$`):new RegExp(sbe,"g");obe.exports=Cxt});var MW=L((n0r,fbe)=>{"use strict";var wxt=QW(),Bxt=abe(),lbe=IB(),ube=["\x1B","\x9B"],MF=t=>`${ube[0]}[${t}m`,cbe=(t,e,r)=>{let s=[];t=[...t];for(let a of t){let n=a;a.match(";")&&(a=a.split(";")[0][0]+"0");let c=lbe.codes.get(parseInt(a,10));if(c){let f=t.indexOf(c.toString());f>=0?t.splice(f,1):s.push(MF(e?c:n))}else if(e){s.push(MF(0));break}else s.push(MF(n))}if(e&&(s=s.filter((a,n)=>s.indexOf(a)===n),r!==void 0)){let a=MF(lbe.codes.get(parseInt(r,10)));s=s.reduce((n,c)=>c===a?[c,...n]:[...n,c],[])}return s.join("")};fbe.exports=(t,e,r)=>{let s=[...t.normalize()],a=[];r=typeof r=="number"?r:s.length;let n=!1,c,f=0,p="";for(let[h,E]of s.entries()){let C=!1;if(ube.includes(E)){let S=/\d[^m]*/.exec(t.slice(h,h+18));c=S&&S.length>0?S[0]:void 0,fe&&f<=r)p+=E;else if(f===e&&!n&&c!==void 0)p=cbe(a);else if(f>=r){p+=cbe(a,!0,c);break}}return p}});var pbe=L((i0r,Abe)=>{"use strict";var eg=MW(),vxt=iD();function _F(t,e,r){if(t.charAt(e)===" ")return e;for(let s=1;s<=3;s++)if(r){if(t.charAt(e+s)===" ")return e+s}else if(t.charAt(e-s)===" ")return e-s;return e}Abe.exports=(t,e,r)=>{r={position:"end",preferTruncationOnSpace:!1,...r};let{position:s,space:a,preferTruncationOnSpace:n}=r,c="\u2026",f=1;if(typeof t!="string")throw new TypeError(`Expected \`input\` to be a string, got ${typeof t}`);if(typeof e!="number")throw new TypeError(`Expected \`columns\` to be a number, got ${typeof e}`);if(e<1)return"";if(e===1)return c;let p=vxt(t);if(p<=e)return t;if(s==="start"){if(n){let h=_F(t,p-e+1,!0);return c+eg(t,h,p).trim()}return a===!0&&(c+=" ",f=2),c+eg(t,p-e+f,p)}if(s==="middle"){a===!0&&(c=" "+c+" ",f=3);let h=Math.floor(e/2);if(n){let E=_F(t,h),C=_F(t,p-(e-h)+1,!0);return eg(t,0,E)+c+eg(t,C,p).trim()}return eg(t,0,h)+c+eg(t,p-(e-h)+f,p)}if(s==="end"){if(n){let h=_F(t,e-1);return eg(t,0,h)+c}return a===!0&&(c=" "+c,f=2),eg(t,0,e-f)+c}throw new Error(`Expected \`options.position\` to be either \`start\`, \`middle\` or \`end\`, got ${s}`)}});var UW=L(lD=>{"use strict";var hbe=lD&&lD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(lD,"__esModule",{value:!0});var Sxt=hbe(ibe()),Dxt=hbe(pbe()),_W={};lD.default=(t,e,r)=>{let s=t+String(e)+String(r);if(_W[s])return _W[s];let a=t;if(r==="wrap"&&(a=Sxt.default(t,e,{trim:!1,hard:!0})),r.startsWith("truncate")){let n="end";r==="truncate-middle"&&(n="middle"),r==="truncate-start"&&(n="start"),a=Dxt.default(t,e,{position:n})}return _W[s]=a,a}});var jW=L(HW=>{"use strict";Object.defineProperty(HW,"__esModule",{value:!0});var gbe=t=>{let e="";if(t.childNodes.length>0)for(let r of t.childNodes){let s="";r.nodeName==="#text"?s=r.nodeValue:((r.nodeName==="ink-text"||r.nodeName==="ink-virtual-text")&&(s=gbe(r)),s.length>0&&typeof r.internal_transform=="function"&&(s=r.internal_transform(s))),e+=s}return e};HW.default=gbe});var qW=L(Pi=>{"use strict";var cD=Pi&&Pi.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Pi,"__esModule",{value:!0});Pi.setTextNodeValue=Pi.createTextNode=Pi.setStyle=Pi.setAttribute=Pi.removeChildNode=Pi.insertBeforeNode=Pi.appendChildNode=Pi.createNode=Pi.TEXT_NAME=void 0;var bxt=cD(Nm()),dbe=cD(ebe()),Pxt=cD(tbe()),xxt=cD(UW()),kxt=cD(jW());Pi.TEXT_NAME="#text";Pi.createNode=t=>{var e;let r={nodeName:t,style:{},attributes:{},childNodes:[],parentNode:null,yogaNode:t==="ink-virtual-text"?void 0:bxt.default.Node.create()};return t==="ink-text"&&((e=r.yogaNode)===null||e===void 0||e.setMeasureFunc(Qxt.bind(null,r))),r};Pi.appendChildNode=(t,e)=>{var r;e.parentNode&&Pi.removeChildNode(e.parentNode,e),e.parentNode=t,t.childNodes.push(e),e.yogaNode&&((r=t.yogaNode)===null||r===void 0||r.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&UF(t)};Pi.insertBeforeNode=(t,e,r)=>{var s,a;e.parentNode&&Pi.removeChildNode(e.parentNode,e),e.parentNode=t;let n=t.childNodes.indexOf(r);if(n>=0){t.childNodes.splice(n,0,e),e.yogaNode&&((s=t.yogaNode)===null||s===void 0||s.insertChild(e.yogaNode,n));return}t.childNodes.push(e),e.yogaNode&&((a=t.yogaNode)===null||a===void 0||a.insertChild(e.yogaNode,t.yogaNode.getChildCount())),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&UF(t)};Pi.removeChildNode=(t,e)=>{var r,s;e.yogaNode&&((s=(r=e.parentNode)===null||r===void 0?void 0:r.yogaNode)===null||s===void 0||s.removeChild(e.yogaNode)),e.parentNode=null;let a=t.childNodes.indexOf(e);a>=0&&t.childNodes.splice(a,1),(t.nodeName==="ink-text"||t.nodeName==="ink-virtual-text")&&UF(t)};Pi.setAttribute=(t,e,r)=>{t.attributes[e]=r};Pi.setStyle=(t,e)=>{t.style=e,t.yogaNode&&Pxt.default(t.yogaNode,e)};Pi.createTextNode=t=>{let e={nodeName:"#text",nodeValue:t,yogaNode:void 0,parentNode:null,style:{}};return Pi.setTextNodeValue(e,t),e};var Qxt=function(t,e){var r,s;let a=t.nodeName==="#text"?t.nodeValue:kxt.default(t),n=dbe.default(a);if(n.width<=e||n.width>=1&&e>0&&e<1)return n;let c=(s=(r=t.style)===null||r===void 0?void 0:r.textWrap)!==null&&s!==void 0?s:"wrap",f=xxt.default(a,e,c);return dbe.default(f)},mbe=t=>{var e;if(!(!t||!t.parentNode))return(e=t.yogaNode)!==null&&e!==void 0?e:mbe(t.parentNode)},UF=t=>{let e=mbe(t);e?.markDirty()};Pi.setTextNodeValue=(t,e)=>{typeof e!="string"&&(e=String(e)),t.nodeValue=e,UF(t)}});var wbe=L(uD=>{"use strict";var Cbe=uD&&uD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(uD,"__esModule",{value:!0});var ybe=SW(),Txt=Cbe(UDe()),Ebe=Cbe(Nm()),ta=qW(),Ibe=t=>{t?.unsetMeasureFunc(),t?.freeRecursive()};uD.default=Txt.default({schedulePassiveEffects:ybe.unstable_scheduleCallback,cancelPassiveEffects:ybe.unstable_cancelCallback,now:Date.now,getRootHostContext:()=>({isInsideText:!1}),prepareForCommit:()=>null,preparePortalMount:()=>null,clearContainer:()=>!1,shouldDeprioritizeSubtree:()=>!1,resetAfterCommit:t=>{if(t.isStaticDirty){t.isStaticDirty=!1,typeof t.onImmediateRender=="function"&&t.onImmediateRender();return}typeof t.onRender=="function"&&t.onRender()},getChildHostContext:(t,e)=>{let r=t.isInsideText,s=e==="ink-text"||e==="ink-virtual-text";return r===s?t:{isInsideText:s}},shouldSetTextContent:()=>!1,createInstance:(t,e,r,s)=>{if(s.isInsideText&&t==="ink-box")throw new Error(" can\u2019t be nested inside component");let a=t==="ink-text"&&s.isInsideText?"ink-virtual-text":t,n=ta.createNode(a);for(let[c,f]of Object.entries(e))c!=="children"&&(c==="style"?ta.setStyle(n,f):c==="internal_transform"?n.internal_transform=f:c==="internal_static"?n.internal_static=!0:ta.setAttribute(n,c,f));return n},createTextInstance:(t,e,r)=>{if(!r.isInsideText)throw new Error(`Text string "${t}" must be rendered inside component`);return ta.createTextNode(t)},resetTextContent:()=>{},hideTextInstance:t=>{ta.setTextNodeValue(t,"")},unhideTextInstance:(t,e)=>{ta.setTextNodeValue(t,e)},getPublicInstance:t=>t,hideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(Ebe.default.DISPLAY_NONE)},unhideInstance:t=>{var e;(e=t.yogaNode)===null||e===void 0||e.setDisplay(Ebe.default.DISPLAY_FLEX)},appendInitialChild:ta.appendChildNode,appendChild:ta.appendChildNode,insertBefore:ta.insertBeforeNode,finalizeInitialChildren:(t,e,r,s)=>(t.internal_static&&(s.isStaticDirty=!0,s.staticNode=t),!1),supportsMutation:!0,appendChildToContainer:ta.appendChildNode,insertInContainerBefore:ta.insertBeforeNode,removeChildFromContainer:(t,e)=>{ta.removeChildNode(t,e),Ibe(e.yogaNode)},prepareUpdate:(t,e,r,s,a)=>{t.internal_static&&(a.isStaticDirty=!0);let n={},c=Object.keys(s);for(let f of c)if(s[f]!==r[f]){if(f==="style"&&typeof s.style=="object"&&typeof r.style=="object"){let h=s.style,E=r.style,C=Object.keys(h);for(let S of C){if(S==="borderStyle"||S==="borderColor"){if(typeof n.style!="object"){let P={};n.style=P}n.style.borderStyle=h.borderStyle,n.style.borderColor=h.borderColor}if(h[S]!==E[S]){if(typeof n.style!="object"){let P={};n.style=P}n.style[S]=h[S]}}continue}n[f]=s[f]}return n},commitUpdate:(t,e)=>{for(let[r,s]of Object.entries(e))r!=="children"&&(r==="style"?ta.setStyle(t,s):r==="internal_transform"?t.internal_transform=s:r==="internal_static"?t.internal_static=!0:ta.setAttribute(t,r,s))},commitTextUpdate:(t,e,r)=>{ta.setTextNodeValue(t,r)},removeChild:(t,e)=>{ta.removeChildNode(t,e),Ibe(e.yogaNode)}})});var vbe=L((c0r,Bbe)=>{"use strict";Bbe.exports=(t,e=1,r)=>{if(r={indent:" ",includeEmptyLines:!1,...r},typeof t!="string")throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof t}\``);if(typeof e!="number")throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof e}\``);if(typeof r.indent!="string")throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof r.indent}\``);if(e===0)return t;let s=r.includeEmptyLines?/^/gm:/^(?!\s*$)/gm;return t.replace(s,r.indent.repeat(e))}});var Sbe=L(fD=>{"use strict";var Rxt=fD&&fD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(fD,"__esModule",{value:!0});var HF=Rxt(Nm());fD.default=t=>t.getComputedWidth()-t.getComputedPadding(HF.default.EDGE_LEFT)-t.getComputedPadding(HF.default.EDGE_RIGHT)-t.getComputedBorder(HF.default.EDGE_LEFT)-t.getComputedBorder(HF.default.EDGE_RIGHT)});var Dbe=L((f0r,Fxt)=>{Fxt.exports={single:{topLeft:"\u250C",topRight:"\u2510",bottomRight:"\u2518",bottomLeft:"\u2514",vertical:"\u2502",horizontal:"\u2500"},double:{topLeft:"\u2554",topRight:"\u2557",bottomRight:"\u255D",bottomLeft:"\u255A",vertical:"\u2551",horizontal:"\u2550"},round:{topLeft:"\u256D",topRight:"\u256E",bottomRight:"\u256F",bottomLeft:"\u2570",vertical:"\u2502",horizontal:"\u2500"},bold:{topLeft:"\u250F",topRight:"\u2513",bottomRight:"\u251B",bottomLeft:"\u2517",vertical:"\u2503",horizontal:"\u2501"},singleDouble:{topLeft:"\u2553",topRight:"\u2556",bottomRight:"\u255C",bottomLeft:"\u2559",vertical:"\u2551",horizontal:"\u2500"},doubleSingle:{topLeft:"\u2552",topRight:"\u2555",bottomRight:"\u255B",bottomLeft:"\u2558",vertical:"\u2502",horizontal:"\u2550"},classic:{topLeft:"+",topRight:"+",bottomRight:"+",bottomLeft:"+",vertical:"|",horizontal:"-"}}});var Pbe=L((A0r,GW)=>{"use strict";var bbe=Dbe();GW.exports=bbe;GW.exports.default=bbe});var kbe=L((p0r,xbe)=>{"use strict";var Nxt=(t,e,r)=>{let s=t.indexOf(e);if(s===-1)return t;let a=e.length,n=0,c="";do c+=t.substr(n,s-n)+e+r,n=s+a,s=t.indexOf(e,n);while(s!==-1);return c+=t.substr(n),c},Oxt=(t,e,r,s)=>{let a=0,n="";do{let c=t[s-1]==="\r";n+=t.substr(a,(c?s-1:s)-a)+e+(c?`\r +`:` +`)+r,a=s+1,s=t.indexOf(` +`,a)}while(s!==-1);return n+=t.substr(a),n};xbe.exports={stringReplaceAll:Nxt,stringEncaseCRLFWithFirstIndex:Oxt}});var Nbe=L((h0r,Fbe)=>{"use strict";var Lxt=/(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi,Qbe=/(?:^|\.)(\w+)(?:\(([^)]*)\))?/g,Mxt=/^(['"])((?:\\.|(?!\1)[^\\])*)\1$/,_xt=/\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi,Uxt=new Map([["n",` +`],["r","\r"],["t"," "],["b","\b"],["f","\f"],["v","\v"],["0","\0"],["\\","\\"],["e","\x1B"],["a","\x07"]]);function Rbe(t){let e=t[0]==="u",r=t[1]==="{";return e&&!r&&t.length===5||t[0]==="x"&&t.length===3?String.fromCharCode(parseInt(t.slice(1),16)):e&&r?String.fromCodePoint(parseInt(t.slice(2,-1),16)):Uxt.get(t)||t}function Hxt(t,e){let r=[],s=e.trim().split(/\s*,\s*/g),a;for(let n of s){let c=Number(n);if(!Number.isNaN(c))r.push(c);else if(a=n.match(Mxt))r.push(a[2].replace(_xt,(f,p,h)=>p?Rbe(p):h));else throw new Error(`Invalid Chalk template style argument: ${n} (in style '${t}')`)}return r}function jxt(t){Qbe.lastIndex=0;let e=[],r;for(;(r=Qbe.exec(t))!==null;){let s=r[1];if(r[2]){let a=Hxt(s,r[2]);e.push([s].concat(a))}else e.push([s])}return e}function Tbe(t,e){let r={};for(let a of e)for(let n of a.styles)r[n[0]]=a.inverse?null:n.slice(1);let s=t;for(let[a,n]of Object.entries(r))if(Array.isArray(n)){if(!(a in s))throw new Error(`Unknown Chalk style: ${a}`);s=n.length>0?s[a](...n):s[a]}return s}Fbe.exports=(t,e)=>{let r=[],s=[],a=[];if(e.replace(Lxt,(n,c,f,p,h,E)=>{if(c)a.push(Rbe(c));else if(p){let C=a.join("");a=[],s.push(r.length===0?C:Tbe(t,r)(C)),r.push({inverse:f,styles:jxt(p)})}else if(h){if(r.length===0)throw new Error("Found extraneous } in Chalk template literal");s.push(Tbe(t,r)(a.join(""))),a=[],r.pop()}else a.push(E)}),s.push(a.join("")),r.length>0){let n=`Chalk template literal is missing ${r.length} closing bracket${r.length===1?"":"s"} (\`}\`)`;throw new Error(n)}return s.join("")}});var YF=L((g0r,Hbe)=>{"use strict";var AD=IB(),{stdout:YW,stderr:VW}=c4(),{stringReplaceAll:qxt,stringEncaseCRLFWithFirstIndex:Gxt}=kbe(),{isArray:jF}=Array,Lbe=["ansi","ansi","ansi256","ansi16m"],Iw=Object.create(null),Wxt=(t,e={})=>{if(e.level&&!(Number.isInteger(e.level)&&e.level>=0&&e.level<=3))throw new Error("The `level` option should be an integer from 0 to 3");let r=YW?YW.level:0;t.level=e.level===void 0?r:e.level},KW=class{constructor(e){return Mbe(e)}},Mbe=t=>{let e={};return Wxt(e,t),e.template=(...r)=>Ube(e.template,...r),Object.setPrototypeOf(e,qF.prototype),Object.setPrototypeOf(e.template,e),e.template.constructor=()=>{throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.")},e.template.Instance=KW,e.template};function qF(t){return Mbe(t)}for(let[t,e]of Object.entries(AD))Iw[t]={get(){let r=GF(this,JW(e.open,e.close,this._styler),this._isEmpty);return Object.defineProperty(this,t,{value:r}),r}};Iw.visible={get(){let t=GF(this,this._styler,!0);return Object.defineProperty(this,"visible",{value:t}),t}};var _be=["rgb","hex","keyword","hsl","hsv","hwb","ansi","ansi256"];for(let t of _be)Iw[t]={get(){let{level:e}=this;return function(...r){let s=JW(AD.color[Lbe[e]][t](...r),AD.color.close,this._styler);return GF(this,s,this._isEmpty)}}};for(let t of _be){let e="bg"+t[0].toUpperCase()+t.slice(1);Iw[e]={get(){let{level:r}=this;return function(...s){let a=JW(AD.bgColor[Lbe[r]][t](...s),AD.bgColor.close,this._styler);return GF(this,a,this._isEmpty)}}}}var Yxt=Object.defineProperties(()=>{},{...Iw,level:{enumerable:!0,get(){return this._generator.level},set(t){this._generator.level=t}}}),JW=(t,e,r)=>{let s,a;return r===void 0?(s=t,a=e):(s=r.openAll+t,a=e+r.closeAll),{open:t,close:e,openAll:s,closeAll:a,parent:r}},GF=(t,e,r)=>{let s=(...a)=>jF(a[0])&&jF(a[0].raw)?Obe(s,Ube(s,...a)):Obe(s,a.length===1?""+a[0]:a.join(" "));return Object.setPrototypeOf(s,Yxt),s._generator=t,s._styler=e,s._isEmpty=r,s},Obe=(t,e)=>{if(t.level<=0||!e)return t._isEmpty?"":e;let r=t._styler;if(r===void 0)return e;let{openAll:s,closeAll:a}=r;if(e.indexOf("\x1B")!==-1)for(;r!==void 0;)e=qxt(e,r.close,r.open),r=r.parent;let n=e.indexOf(` +`);return n!==-1&&(e=Gxt(e,a,s,n)),s+e+a},WW,Ube=(t,...e)=>{let[r]=e;if(!jF(r)||!jF(r.raw))return e.join(" ");let s=e.slice(1),a=[r.raw[0]];for(let n=1;n{"use strict";var Vxt=hD&&hD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(hD,"__esModule",{value:!0});var pD=Vxt(YF()),Kxt=/^(rgb|hsl|hsv|hwb)\(\s?(\d+),\s?(\d+),\s?(\d+)\s?\)$/,Jxt=/^(ansi|ansi256)\(\s?(\d+)\s?\)$/,VF=(t,e)=>e==="foreground"?t:"bg"+t[0].toUpperCase()+t.slice(1);hD.default=(t,e,r)=>{if(!e)return t;if(e in pD.default){let a=VF(e,r);return pD.default[a](t)}if(e.startsWith("#")){let a=VF("hex",r);return pD.default[a](e)(t)}if(e.startsWith("ansi")){let a=Jxt.exec(e);if(!a)return t;let n=VF(a[1],r),c=Number(a[2]);return pD.default[n](c)(t)}if(e.startsWith("rgb")||e.startsWith("hsl")||e.startsWith("hsv")||e.startsWith("hwb")){let a=Kxt.exec(e);if(!a)return t;let n=VF(a[1],r),c=Number(a[2]),f=Number(a[3]),p=Number(a[4]);return pD.default[n](c,f,p)(t)}return t}});var qbe=L(gD=>{"use strict";var jbe=gD&&gD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(gD,"__esModule",{value:!0});var zxt=jbe(Pbe()),ZW=jbe(zW());gD.default=(t,e,r,s)=>{if(typeof r.style.borderStyle=="string"){let a=r.yogaNode.getComputedWidth(),n=r.yogaNode.getComputedHeight(),c=r.style.borderColor,f=zxt.default[r.style.borderStyle],p=ZW.default(f.topLeft+f.horizontal.repeat(a-2)+f.topRight,c,"foreground"),h=(ZW.default(f.vertical,c,"foreground")+` +`).repeat(n-2),E=ZW.default(f.bottomLeft+f.horizontal.repeat(a-2)+f.bottomRight,c,"foreground");s.write(t,e,p,{transformers:[]}),s.write(t,e+1,h,{transformers:[]}),s.write(t+a-1,e+1,h,{transformers:[]}),s.write(t,e+n-1,E,{transformers:[]})}}});var Wbe=L(dD=>{"use strict";var Om=dD&&dD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(dD,"__esModule",{value:!0});var Zxt=Om(Nm()),Xxt=Om(FW()),$xt=Om(vbe()),ekt=Om(UW()),tkt=Om(Sbe()),rkt=Om(jW()),nkt=Om(qbe()),ikt=(t,e)=>{var r;let s=(r=t.childNodes[0])===null||r===void 0?void 0:r.yogaNode;if(s){let a=s.getComputedLeft(),n=s.getComputedTop();e=` +`.repeat(n)+$xt.default(e,a)}return e},Gbe=(t,e,r)=>{var s;let{offsetX:a=0,offsetY:n=0,transformers:c=[],skipStaticElements:f}=r;if(f&&t.internal_static)return;let{yogaNode:p}=t;if(p){if(p.getDisplay()===Zxt.default.DISPLAY_NONE)return;let h=a+p.getComputedLeft(),E=n+p.getComputedTop(),C=c;if(typeof t.internal_transform=="function"&&(C=[t.internal_transform,...c]),t.nodeName==="ink-text"){let S=rkt.default(t);if(S.length>0){let P=Xxt.default(S),I=tkt.default(p);if(P>I){let R=(s=t.style.textWrap)!==null&&s!==void 0?s:"wrap";S=ekt.default(S,I,R)}S=ikt(t,S),e.write(h,E,S,{transformers:C})}return}if(t.nodeName==="ink-box"&&nkt.default(h,E,t,e),t.nodeName==="ink-root"||t.nodeName==="ink-box")for(let S of t.childNodes)Gbe(S,e,{offsetX:h,offsetY:E,transformers:C,skipStaticElements:f})}};dD.default=Gbe});var Kbe=L(mD=>{"use strict";var Vbe=mD&&mD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(mD,"__esModule",{value:!0});var Ybe=Vbe(MW()),skt=Vbe(iD()),XW=class{constructor(e){this.writes=[];let{width:r,height:s}=e;this.width=r,this.height=s}write(e,r,s,a){let{transformers:n}=a;s&&this.writes.push({x:e,y:r,text:s,transformers:n})}get(){let e=[];for(let s=0;ss.trimRight()).join(` +`),height:e.length}}};mD.default=XW});var Zbe=L(yD=>{"use strict";var $W=yD&&yD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(yD,"__esModule",{value:!0});var okt=$W(Nm()),Jbe=$W(Wbe()),zbe=$W(Kbe());yD.default=(t,e)=>{var r;if(t.yogaNode.setWidth(e),t.yogaNode){t.yogaNode.calculateLayout(void 0,void 0,okt.default.DIRECTION_LTR);let s=new zbe.default({width:t.yogaNode.getComputedWidth(),height:t.yogaNode.getComputedHeight()});Jbe.default(t,s,{skipStaticElements:!0});let a;!((r=t.staticNode)===null||r===void 0)&&r.yogaNode&&(a=new zbe.default({width:t.staticNode.yogaNode.getComputedWidth(),height:t.staticNode.yogaNode.getComputedHeight()}),Jbe.default(t.staticNode,a,{skipStaticElements:!1}));let{output:n,height:c}=s.get();return{output:n,outputHeight:c,staticOutput:a?`${a.get().output} +`:""}}return{output:"",outputHeight:0,staticOutput:""}}});var tPe=L((C0r,ePe)=>{"use strict";var Xbe=ye("stream"),$be=["assert","count","countReset","debug","dir","dirxml","error","group","groupCollapsed","groupEnd","info","log","table","time","timeEnd","timeLog","trace","warn"],eY={},akt=t=>{let e=new Xbe.PassThrough,r=new Xbe.PassThrough;e.write=a=>t("stdout",a),r.write=a=>t("stderr",a);let s=new console.Console(e,r);for(let a of $be)eY[a]=console[a],console[a]=s[a];return()=>{for(let a of $be)console[a]=eY[a];eY={}}};ePe.exports=akt});var rY=L(tY=>{"use strict";Object.defineProperty(tY,"__esModule",{value:!0});tY.default=new WeakMap});var iY=L(nY=>{"use strict";Object.defineProperty(nY,"__esModule",{value:!0});var lkt=hn(),rPe=lkt.createContext({exit:()=>{}});rPe.displayName="InternalAppContext";nY.default=rPe});var oY=L(sY=>{"use strict";Object.defineProperty(sY,"__esModule",{value:!0});var ckt=hn(),nPe=ckt.createContext({stdin:void 0,setRawMode:()=>{},isRawModeSupported:!1,internal_exitOnCtrlC:!0});nPe.displayName="InternalStdinContext";sY.default=nPe});var lY=L(aY=>{"use strict";Object.defineProperty(aY,"__esModule",{value:!0});var ukt=hn(),iPe=ukt.createContext({stdout:void 0,write:()=>{}});iPe.displayName="InternalStdoutContext";aY.default=iPe});var uY=L(cY=>{"use strict";Object.defineProperty(cY,"__esModule",{value:!0});var fkt=hn(),sPe=fkt.createContext({stderr:void 0,write:()=>{}});sPe.displayName="InternalStderrContext";cY.default=sPe});var KF=L(fY=>{"use strict";Object.defineProperty(fY,"__esModule",{value:!0});var Akt=hn(),oPe=Akt.createContext({activeId:void 0,add:()=>{},remove:()=>{},activate:()=>{},deactivate:()=>{},enableFocus:()=>{},disableFocus:()=>{},focusNext:()=>{},focusPrevious:()=>{},focus:()=>{}});oPe.displayName="InternalFocusContext";fY.default=oPe});var lPe=L((P0r,aPe)=>{"use strict";var pkt=/[|\\{}()[\]^$+*?.-]/g;aPe.exports=t=>{if(typeof t!="string")throw new TypeError("Expected a string");return t.replace(pkt,"\\$&")}});var APe=L((x0r,fPe)=>{"use strict";var hkt=lPe(),gkt=typeof process=="object"&&process&&typeof process.cwd=="function"?process.cwd():".",uPe=[].concat(ye("module").builtinModules,"bootstrap_node","node").map(t=>new RegExp(`(?:\\((?:node:)?${t}(?:\\.js)?:\\d+:\\d+\\)$|^\\s*at (?:node:)?${t}(?:\\.js)?:\\d+:\\d+$)`));uPe.push(/\((?:node:)?internal\/[^:]+:\d+:\d+\)$/,/\s*at (?:node:)?internal\/[^:]+:\d+:\d+$/,/\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/);var AY=class t{constructor(e){e={ignoredPackages:[],...e},"internals"in e||(e.internals=t.nodeInternals()),"cwd"in e||(e.cwd=gkt),this._cwd=e.cwd.replace(/\\/g,"/"),this._internals=[].concat(e.internals,dkt(e.ignoredPackages)),this._wrapCallSite=e.wrapCallSite||!1}static nodeInternals(){return[...uPe]}clean(e,r=0){r=" ".repeat(r),Array.isArray(e)||(e=e.split(` +`)),!/^\s*at /.test(e[0])&&/^\s*at /.test(e[1])&&(e=e.slice(1));let s=!1,a=null,n=[];return e.forEach(c=>{if(c=c.replace(/\\/g,"/"),this._internals.some(p=>p.test(c)))return;let f=/^\s*at /.test(c);s?c=c.trimEnd().replace(/^(\s+)at /,"$1"):(c=c.trim(),f&&(c=c.slice(3))),c=c.replace(`${this._cwd}/`,""),c&&(f?(a&&(n.push(a),a=null),n.push(c)):(s=!0,a=c))}),n.map(c=>`${r}${c} +`).join("")}captureString(e,r=this.captureString){typeof e=="function"&&(r=e,e=1/0);let{stackTraceLimit:s}=Error;e&&(Error.stackTraceLimit=e);let a={};Error.captureStackTrace(a,r);let{stack:n}=a;return Error.stackTraceLimit=s,this.clean(n)}capture(e,r=this.capture){typeof e=="function"&&(r=e,e=1/0);let{prepareStackTrace:s,stackTraceLimit:a}=Error;Error.prepareStackTrace=(f,p)=>this._wrapCallSite?p.map(this._wrapCallSite):p,e&&(Error.stackTraceLimit=e);let n={};Error.captureStackTrace(n,r);let{stack:c}=n;return Object.assign(Error,{prepareStackTrace:s,stackTraceLimit:a}),c}at(e=this.at){let[r]=this.capture(1,e);if(!r)return{};let s={line:r.getLineNumber(),column:r.getColumnNumber()};cPe(s,r.getFileName(),this._cwd),r.isConstructor()&&(s.constructor=!0),r.isEval()&&(s.evalOrigin=r.getEvalOrigin()),r.isNative()&&(s.native=!0);let a;try{a=r.getTypeName()}catch{}a&&a!=="Object"&&a!=="[object Object]"&&(s.type=a);let n=r.getFunctionName();n&&(s.function=n);let c=r.getMethodName();return c&&n!==c&&(s.method=c),s}parseLine(e){let r=e&&e.match(mkt);if(!r)return null;let s=r[1]==="new",a=r[2],n=r[3],c=r[4],f=Number(r[5]),p=Number(r[6]),h=r[7],E=r[8],C=r[9],S=r[10]==="native",P=r[11]===")",I,R={};if(E&&(R.line=Number(E)),C&&(R.column=Number(C)),P&&h){let N=0;for(let U=h.length-1;U>0;U--)if(h.charAt(U)===")")N++;else if(h.charAt(U)==="("&&h.charAt(U-1)===" "&&(N--,N===-1&&h.charAt(U-1)===" ")){let W=h.slice(0,U-1);h=h.slice(U+1),a+=` (${W}`;break}}if(a){let N=a.match(ykt);N&&(a=N[1],I=N[2])}return cPe(R,h,this._cwd),s&&(R.constructor=!0),n&&(R.evalOrigin=n,R.evalLine=f,R.evalColumn=p,R.evalFile=c&&c.replace(/\\/g,"/")),S&&(R.native=!0),a&&(R.function=a),I&&a!==I&&(R.method=I),R}};function cPe(t,e,r){e&&(e=e.replace(/\\/g,"/"),e.startsWith(`${r}/`)&&(e=e.slice(r.length+1)),t.file=e)}function dkt(t){if(t.length===0)return[];let e=t.map(r=>hkt(r));return new RegExp(`[/\\\\]node_modules[/\\\\](?:${e.join("|")})[/\\\\][^:]+:\\d+:\\d+`)}var mkt=new RegExp("^(?:\\s*at )?(?:(new) )?(?:(.*?) \\()?(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?(?:(.+?):(\\d+):(\\d+)|(native))(\\)?)$"),ykt=/^(.*?) \[as (.*?)\]$/;fPe.exports=AY});var hPe=L((k0r,pPe)=>{"use strict";pPe.exports=(t,e)=>t.replace(/^\t+/gm,r=>" ".repeat(r.length*(e||2)))});var dPe=L((Q0r,gPe)=>{"use strict";var Ekt=hPe(),Ikt=(t,e)=>{let r=[],s=t-e,a=t+e;for(let n=s;n<=a;n++)r.push(n);return r};gPe.exports=(t,e,r)=>{if(typeof t!="string")throw new TypeError("Source code is missing.");if(!e||e<1)throw new TypeError("Line number must start from `1`.");if(t=Ekt(t).split(/\r?\n/),!(e>t.length))return r={around:3,...r},Ikt(e,r.around).filter(s=>t[s-1]!==void 0).map(s=>({line:s,value:t[s-1]}))}});var JF=L(nf=>{"use strict";var Ckt=nf&&nf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),wkt=nf&&nf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Bkt=nf&&nf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Ckt(e,t,r);return wkt(e,t),e},vkt=nf&&nf.__rest||function(t,e){var r={};for(var s in t)Object.prototype.hasOwnProperty.call(t,s)&&e.indexOf(s)<0&&(r[s]=t[s]);if(t!=null&&typeof Object.getOwnPropertySymbols=="function")for(var a=0,s=Object.getOwnPropertySymbols(t);a{var{children:r}=t,s=vkt(t,["children"]);let a=Object.assign(Object.assign({},s),{marginLeft:s.marginLeft||s.marginX||s.margin||0,marginRight:s.marginRight||s.marginX||s.margin||0,marginTop:s.marginTop||s.marginY||s.margin||0,marginBottom:s.marginBottom||s.marginY||s.margin||0,paddingLeft:s.paddingLeft||s.paddingX||s.padding||0,paddingRight:s.paddingRight||s.paddingX||s.padding||0,paddingTop:s.paddingTop||s.paddingY||s.padding||0,paddingBottom:s.paddingBottom||s.paddingY||s.padding||0});return mPe.default.createElement("ink-box",{ref:e,style:a},r)});pY.displayName="Box";pY.defaultProps={flexDirection:"row",flexGrow:0,flexShrink:1};nf.default=pY});var dY=L(ED=>{"use strict";var hY=ED&&ED.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ED,"__esModule",{value:!0});var Skt=hY(hn()),Cw=hY(YF()),yPe=hY(zW()),gY=({color:t,backgroundColor:e,dimColor:r,bold:s,italic:a,underline:n,strikethrough:c,inverse:f,wrap:p,children:h})=>{if(h==null)return null;let E=C=>(r&&(C=Cw.default.dim(C)),t&&(C=yPe.default(C,t,"foreground")),e&&(C=yPe.default(C,e,"background")),s&&(C=Cw.default.bold(C)),a&&(C=Cw.default.italic(C)),n&&(C=Cw.default.underline(C)),c&&(C=Cw.default.strikethrough(C)),f&&(C=Cw.default.inverse(C)),C);return Skt.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row",textWrap:p},internal_transform:E},h)};gY.displayName="Text";gY.defaultProps={dimColor:!1,bold:!1,italic:!1,underline:!1,strikethrough:!1,wrap:"wrap"};ED.default=gY});var wPe=L(sf=>{"use strict";var Dkt=sf&&sf.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),bkt=sf&&sf.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Pkt=sf&&sf.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Dkt(e,t,r);return bkt(e,t),e},ID=sf&&sf.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(sf,"__esModule",{value:!0});var EPe=Pkt(ye("fs")),Ts=ID(hn()),IPe=ID(APe()),xkt=ID(dPe()),rh=ID(JF()),hA=ID(dY()),CPe=new IPe.default({cwd:process.cwd(),internals:IPe.default.nodeInternals()}),kkt=({error:t})=>{let e=t.stack?t.stack.split(` +`).slice(1):void 0,r=e?CPe.parseLine(e[0]):void 0,s,a=0;if(r?.file&&r?.line&&EPe.existsSync(r.file)){let n=EPe.readFileSync(r.file,"utf8");if(s=xkt.default(n,r.line),s)for(let{line:c}of s)a=Math.max(a,String(c).length)}return Ts.default.createElement(rh.default,{flexDirection:"column",padding:1},Ts.default.createElement(rh.default,null,Ts.default.createElement(hA.default,{backgroundColor:"red",color:"white"}," ","ERROR"," "),Ts.default.createElement(hA.default,null," ",t.message)),r&&Ts.default.createElement(rh.default,{marginTop:1},Ts.default.createElement(hA.default,{dimColor:!0},r.file,":",r.line,":",r.column)),r&&s&&Ts.default.createElement(rh.default,{marginTop:1,flexDirection:"column"},s.map(({line:n,value:c})=>Ts.default.createElement(rh.default,{key:n},Ts.default.createElement(rh.default,{width:a+1},Ts.default.createElement(hA.default,{dimColor:n!==r.line,backgroundColor:n===r.line?"red":void 0,color:n===r.line?"white":void 0},String(n).padStart(a," "),":")),Ts.default.createElement(hA.default,{key:n,backgroundColor:n===r.line?"red":void 0,color:n===r.line?"white":void 0}," "+c)))),t.stack&&Ts.default.createElement(rh.default,{marginTop:1,flexDirection:"column"},t.stack.split(` +`).slice(1).map(n=>{let c=CPe.parseLine(n);return c?Ts.default.createElement(rh.default,{key:n},Ts.default.createElement(hA.default,{dimColor:!0},"- "),Ts.default.createElement(hA.default,{dimColor:!0,bold:!0},c.function),Ts.default.createElement(hA.default,{dimColor:!0,color:"gray"}," ","(",c.file,":",c.line,":",c.column,")")):Ts.default.createElement(rh.default,{key:n},Ts.default.createElement(hA.default,{dimColor:!0},"- "),Ts.default.createElement(hA.default,{dimColor:!0,bold:!0},n))})))};sf.default=kkt});var vPe=L(of=>{"use strict";var Qkt=of&&of.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Tkt=of&&of.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Rkt=of&&of.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&Qkt(e,t,r);return Tkt(e,t),e},Mm=of&&of.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(of,"__esModule",{value:!0});var Lm=Rkt(hn()),BPe=Mm(pW()),Fkt=Mm(iY()),Nkt=Mm(oY()),Okt=Mm(lY()),Lkt=Mm(uY()),Mkt=Mm(KF()),_kt=Mm(wPe()),Ukt=" ",Hkt="\x1B[Z",jkt="\x1B",zF=class extends Lm.PureComponent{constructor(){super(...arguments),this.state={isFocusEnabled:!0,activeFocusId:void 0,focusables:[],error:void 0},this.rawModeEnabledCount=0,this.handleSetRawMode=e=>{let{stdin:r}=this.props;if(!this.isRawModeSupported())throw r===process.stdin?new Error(`Raw mode is not supported on the current process.stdin, which Ink uses as input stream by default. +Read about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`):new Error(`Raw mode is not supported on the stdin provided to Ink. +Read about how to prevent this error on https://github.com/vadimdemedes/ink/#israwmodesupported`);if(r.setEncoding("utf8"),e){this.rawModeEnabledCount===0&&(r.addListener("data",this.handleInput),r.resume(),r.setRawMode(!0)),this.rawModeEnabledCount++;return}--this.rawModeEnabledCount===0&&(r.setRawMode(!1),r.removeListener("data",this.handleInput),r.pause())},this.handleInput=e=>{e===""&&this.props.exitOnCtrlC&&this.handleExit(),e===jkt&&this.state.activeFocusId&&this.setState({activeFocusId:void 0}),this.state.isFocusEnabled&&this.state.focusables.length>0&&(e===Ukt&&this.focusNext(),e===Hkt&&this.focusPrevious())},this.handleExit=e=>{this.isRawModeSupported()&&this.handleSetRawMode(!1),this.props.onExit(e)},this.enableFocus=()=>{this.setState({isFocusEnabled:!0})},this.disableFocus=()=>{this.setState({isFocusEnabled:!1})},this.focus=e=>{this.setState(r=>r.focusables.some(a=>a?.id===e)?{activeFocusId:e}:r)},this.focusNext=()=>{this.setState(e=>{var r;let s=(r=e.focusables[0])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findNextFocusable(e)||s}})},this.focusPrevious=()=>{this.setState(e=>{var r;let s=(r=e.focusables[e.focusables.length-1])===null||r===void 0?void 0:r.id;return{activeFocusId:this.findPreviousFocusable(e)||s}})},this.addFocusable=(e,{autoFocus:r})=>{this.setState(s=>{let a=s.activeFocusId;return!a&&r&&(a=e),{activeFocusId:a,focusables:[...s.focusables,{id:e,isActive:!0}]}})},this.removeFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.filter(s=>s.id!==e)}))},this.activateFocusable=e=>{this.setState(r=>({focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!0})}))},this.deactivateFocusable=e=>{this.setState(r=>({activeFocusId:r.activeFocusId===e?void 0:r.activeFocusId,focusables:r.focusables.map(s=>s.id!==e?s:{id:e,isActive:!1})}))},this.findNextFocusable=e=>{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s+1;a{var r;let s=e.focusables.findIndex(a=>a.id===e.activeFocusId);for(let a=s-1;a>=0;a--)if(!((r=e.focusables[a])===null||r===void 0)&&r.isActive)return e.focusables[a].id}}static getDerivedStateFromError(e){return{error:e}}isRawModeSupported(){return this.props.stdin.isTTY}render(){return Lm.default.createElement(Fkt.default.Provider,{value:{exit:this.handleExit}},Lm.default.createElement(Nkt.default.Provider,{value:{stdin:this.props.stdin,setRawMode:this.handleSetRawMode,isRawModeSupported:this.isRawModeSupported(),internal_exitOnCtrlC:this.props.exitOnCtrlC}},Lm.default.createElement(Okt.default.Provider,{value:{stdout:this.props.stdout,write:this.props.writeToStdout}},Lm.default.createElement(Lkt.default.Provider,{value:{stderr:this.props.stderr,write:this.props.writeToStderr}},Lm.default.createElement(Mkt.default.Provider,{value:{activeId:this.state.activeFocusId,add:this.addFocusable,remove:this.removeFocusable,activate:this.activateFocusable,deactivate:this.deactivateFocusable,enableFocus:this.enableFocus,disableFocus:this.disableFocus,focusNext:this.focusNext,focusPrevious:this.focusPrevious,focus:this.focus}},this.state.error?Lm.default.createElement(_kt.default,{error:this.state.error}):this.props.children)))))}componentDidMount(){BPe.default.hide(this.props.stdout)}componentWillUnmount(){BPe.default.show(this.props.stdout),this.isRawModeSupported()&&this.handleSetRawMode(!1)}componentDidCatch(e){this.handleExit(e)}};of.default=zF;zF.displayName="InternalApp"});var bPe=L(af=>{"use strict";var qkt=af&&af.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Gkt=af&&af.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),Wkt=af&&af.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&qkt(e,t,r);return Gkt(e,t),e},lf=af&&af.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(af,"__esModule",{value:!0});var Ykt=lf(hn()),SPe=rH(),Vkt=lf(wDe()),Kkt=lf(lW()),Jkt=lf(PDe()),zkt=lf(kDe()),mY=lf(wbe()),Zkt=lf(Zbe()),Xkt=lf(AW()),$kt=lf(tPe()),eQt=Wkt(qW()),tQt=lf(rY()),rQt=lf(vPe()),ww=process.env.CI==="false"?!1:Jkt.default,DPe=()=>{},yY=class{constructor(e){this.resolveExitPromise=()=>{},this.rejectExitPromise=()=>{},this.unsubscribeExit=()=>{},this.onRender=()=>{if(this.isUnmounted)return;let{output:r,outputHeight:s,staticOutput:a}=Zkt.default(this.rootNode,this.options.stdout.columns||80),n=a&&a!==` +`;if(this.options.debug){n&&(this.fullStaticOutput+=a),this.options.stdout.write(this.fullStaticOutput+r);return}if(ww){n&&this.options.stdout.write(a),this.lastOutput=r;return}if(n&&(this.fullStaticOutput+=a),s>=this.options.stdout.rows){this.options.stdout.write(Kkt.default.clearTerminal+this.fullStaticOutput+r),this.lastOutput=r;return}n&&(this.log.clear(),this.options.stdout.write(a),this.log(r)),!n&&r!==this.lastOutput&&this.throttledLog(r),this.lastOutput=r},zkt.default(this),this.options=e,this.rootNode=eQt.createNode("ink-root"),this.rootNode.onRender=e.debug?this.onRender:SPe(this.onRender,32,{leading:!0,trailing:!0}),this.rootNode.onImmediateRender=this.onRender,this.log=Vkt.default.create(e.stdout),this.throttledLog=e.debug?this.log:SPe(this.log,void 0,{leading:!0,trailing:!0}),this.isUnmounted=!1,this.lastOutput="",this.fullStaticOutput="",this.container=mY.default.createContainer(this.rootNode,0,!1,null),this.unsubscribeExit=Xkt.default(this.unmount,{alwaysLast:!1}),e.patchConsole&&this.patchConsole(),ww||(e.stdout.on("resize",this.onRender),this.unsubscribeResize=()=>{e.stdout.off("resize",this.onRender)})}render(e){let r=Ykt.default.createElement(rQt.default,{stdin:this.options.stdin,stdout:this.options.stdout,stderr:this.options.stderr,writeToStdout:this.writeToStdout,writeToStderr:this.writeToStderr,exitOnCtrlC:this.options.exitOnCtrlC,onExit:this.unmount},e);mY.default.updateContainer(r,this.container,null,DPe)}writeToStdout(e){if(!this.isUnmounted){if(this.options.debug){this.options.stdout.write(e+this.fullStaticOutput+this.lastOutput);return}if(ww){this.options.stdout.write(e);return}this.log.clear(),this.options.stdout.write(e),this.log(this.lastOutput)}}writeToStderr(e){if(!this.isUnmounted){if(this.options.debug){this.options.stderr.write(e),this.options.stdout.write(this.fullStaticOutput+this.lastOutput);return}if(ww){this.options.stderr.write(e);return}this.log.clear(),this.options.stderr.write(e),this.log(this.lastOutput)}}unmount(e){this.isUnmounted||(this.onRender(),this.unsubscribeExit(),typeof this.restoreConsole=="function"&&this.restoreConsole(),typeof this.unsubscribeResize=="function"&&this.unsubscribeResize(),ww?this.options.stdout.write(this.lastOutput+` +`):this.options.debug||this.log.done(),this.isUnmounted=!0,mY.default.updateContainer(null,this.container,null,DPe),tQt.default.delete(this.options.stdout),e instanceof Error?this.rejectExitPromise(e):this.resolveExitPromise())}waitUntilExit(){return this.exitPromise||(this.exitPromise=new Promise((e,r)=>{this.resolveExitPromise=e,this.rejectExitPromise=r})),this.exitPromise}clear(){!ww&&!this.options.debug&&this.log.clear()}patchConsole(){this.options.debug||(this.restoreConsole=$kt.default((e,r)=>{e==="stdout"&&this.writeToStdout(r),e==="stderr"&&(r.startsWith("The above error occurred")||this.writeToStderr(r))}))}};af.default=yY});var xPe=L(CD=>{"use strict";var PPe=CD&&CD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(CD,"__esModule",{value:!0});var nQt=PPe(bPe()),ZF=PPe(rY()),iQt=ye("stream"),sQt=(t,e)=>{let r=Object.assign({stdout:process.stdout,stdin:process.stdin,stderr:process.stderr,debug:!1,exitOnCtrlC:!0,patchConsole:!0},oQt(e)),s=aQt(r.stdout,()=>new nQt.default(r));return s.render(t),{rerender:s.render,unmount:()=>s.unmount(),waitUntilExit:s.waitUntilExit,cleanup:()=>ZF.default.delete(r.stdout),clear:s.clear}};CD.default=sQt;var oQt=(t={})=>t instanceof iQt.Stream?{stdout:t,stdin:process.stdin}:t,aQt=(t,e)=>{let r;return ZF.default.has(t)?r=ZF.default.get(t):(r=e(),ZF.default.set(t,r)),r}});var QPe=L(nh=>{"use strict";var lQt=nh&&nh.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r),Object.defineProperty(t,s,{enumerable:!0,get:function(){return e[r]}})}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),cQt=nh&&nh.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),uQt=nh&&nh.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.hasOwnProperty.call(t,r)&&lQt(e,t,r);return cQt(e,t),e};Object.defineProperty(nh,"__esModule",{value:!0});var wD=uQt(hn()),kPe=t=>{let{items:e,children:r,style:s}=t,[a,n]=wD.useState(0),c=wD.useMemo(()=>e.slice(a),[e,a]);wD.useLayoutEffect(()=>{n(e.length)},[e.length]);let f=c.map((h,E)=>r(h,a+E)),p=wD.useMemo(()=>Object.assign({position:"absolute",flexDirection:"column"},s),[s]);return wD.default.createElement("ink-box",{internal_static:!0,style:p},f)};kPe.displayName="Static";nh.default=kPe});var RPe=L(BD=>{"use strict";var fQt=BD&&BD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(BD,"__esModule",{value:!0});var AQt=fQt(hn()),TPe=({children:t,transform:e})=>t==null?null:AQt.default.createElement("ink-text",{style:{flexGrow:0,flexShrink:1,flexDirection:"row"},internal_transform:e},t);TPe.displayName="Transform";BD.default=TPe});var NPe=L(vD=>{"use strict";var pQt=vD&&vD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(vD,"__esModule",{value:!0});var hQt=pQt(hn()),FPe=({count:t=1})=>hQt.default.createElement("ink-text",null,` +`.repeat(t));FPe.displayName="Newline";vD.default=FPe});var MPe=L(SD=>{"use strict";var OPe=SD&&SD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(SD,"__esModule",{value:!0});var gQt=OPe(hn()),dQt=OPe(JF()),LPe=()=>gQt.default.createElement(dQt.default,{flexGrow:1});LPe.displayName="Spacer";SD.default=LPe});var XF=L(DD=>{"use strict";var mQt=DD&&DD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(DD,"__esModule",{value:!0});var yQt=hn(),EQt=mQt(oY()),IQt=()=>yQt.useContext(EQt.default);DD.default=IQt});var UPe=L(bD=>{"use strict";var CQt=bD&&bD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(bD,"__esModule",{value:!0});var _Pe=hn(),wQt=CQt(XF()),BQt=(t,e={})=>{let{stdin:r,setRawMode:s,internal_exitOnCtrlC:a}=wQt.default();_Pe.useEffect(()=>{if(e.isActive!==!1)return s(!0),()=>{s(!1)}},[e.isActive,s]),_Pe.useEffect(()=>{if(e.isActive===!1)return;let n=c=>{let f=String(c),p={upArrow:f==="\x1B[A",downArrow:f==="\x1B[B",leftArrow:f==="\x1B[D",rightArrow:f==="\x1B[C",pageDown:f==="\x1B[6~",pageUp:f==="\x1B[5~",return:f==="\r",escape:f==="\x1B",ctrl:!1,shift:!1,tab:f===" "||f==="\x1B[Z",backspace:f==="\b",delete:f==="\x7F"||f==="\x1B[3~",meta:!1};f<=""&&!p.return&&(f=String.fromCharCode(f.charCodeAt(0)+97-1),p.ctrl=!0),f.startsWith("\x1B")&&(f=f.slice(1),p.meta=!0);let h=f>="A"&&f<="Z",E=f>="\u0410"&&f<="\u042F";f.length===1&&(h||E)&&(p.shift=!0),p.tab&&f==="[Z"&&(p.shift=!0),(p.tab||p.backspace||p.delete)&&(f=""),(!(f==="c"&&p.ctrl)||!a)&&t(f,p)};return r?.on("data",n),()=>{r?.off("data",n)}},[e.isActive,r,a,t])};bD.default=BQt});var HPe=L(PD=>{"use strict";var vQt=PD&&PD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(PD,"__esModule",{value:!0});var SQt=hn(),DQt=vQt(iY()),bQt=()=>SQt.useContext(DQt.default);PD.default=bQt});var jPe=L(xD=>{"use strict";var PQt=xD&&xD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(xD,"__esModule",{value:!0});var xQt=hn(),kQt=PQt(lY()),QQt=()=>xQt.useContext(kQt.default);xD.default=QQt});var qPe=L(kD=>{"use strict";var TQt=kD&&kD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(kD,"__esModule",{value:!0});var RQt=hn(),FQt=TQt(uY()),NQt=()=>RQt.useContext(FQt.default);kD.default=NQt});var WPe=L(TD=>{"use strict";var GPe=TD&&TD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(TD,"__esModule",{value:!0});var QD=hn(),OQt=GPe(KF()),LQt=GPe(XF()),MQt=({isActive:t=!0,autoFocus:e=!1,id:r}={})=>{let{isRawModeSupported:s,setRawMode:a}=LQt.default(),{activeId:n,add:c,remove:f,activate:p,deactivate:h,focus:E}=QD.useContext(OQt.default),C=QD.useMemo(()=>r??Math.random().toString().slice(2,7),[r]);return QD.useEffect(()=>(c(C,{autoFocus:e}),()=>{f(C)}),[C,e]),QD.useEffect(()=>{t?p(C):h(C)},[t,C]),QD.useEffect(()=>{if(!(!s||!t))return a(!0),()=>{a(!1)}},[t]),{isFocused:!!C&&n===C,focus:E}};TD.default=MQt});var YPe=L(RD=>{"use strict";var _Qt=RD&&RD.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(RD,"__esModule",{value:!0});var UQt=hn(),HQt=_Qt(KF()),jQt=()=>{let t=UQt.useContext(HQt.default);return{enableFocus:t.enableFocus,disableFocus:t.disableFocus,focusNext:t.focusNext,focusPrevious:t.focusPrevious,focus:t.focus}};RD.default=jQt});var VPe=L(EY=>{"use strict";Object.defineProperty(EY,"__esModule",{value:!0});EY.default=t=>{var e,r,s,a;return{width:(r=(e=t.yogaNode)===null||e===void 0?void 0:e.getComputedWidth())!==null&&r!==void 0?r:0,height:(a=(s=t.yogaNode)===null||s===void 0?void 0:s.getComputedHeight())!==null&&a!==void 0?a:0}}});var Vc=L(Eo=>{"use strict";Object.defineProperty(Eo,"__esModule",{value:!0});var qQt=xPe();Object.defineProperty(Eo,"render",{enumerable:!0,get:function(){return qQt.default}});var GQt=JF();Object.defineProperty(Eo,"Box",{enumerable:!0,get:function(){return GQt.default}});var WQt=dY();Object.defineProperty(Eo,"Text",{enumerable:!0,get:function(){return WQt.default}});var YQt=QPe();Object.defineProperty(Eo,"Static",{enumerable:!0,get:function(){return YQt.default}});var VQt=RPe();Object.defineProperty(Eo,"Transform",{enumerable:!0,get:function(){return VQt.default}});var KQt=NPe();Object.defineProperty(Eo,"Newline",{enumerable:!0,get:function(){return KQt.default}});var JQt=MPe();Object.defineProperty(Eo,"Spacer",{enumerable:!0,get:function(){return JQt.default}});var zQt=UPe();Object.defineProperty(Eo,"useInput",{enumerable:!0,get:function(){return zQt.default}});var ZQt=HPe();Object.defineProperty(Eo,"useApp",{enumerable:!0,get:function(){return ZQt.default}});var XQt=XF();Object.defineProperty(Eo,"useStdin",{enumerable:!0,get:function(){return XQt.default}});var $Qt=jPe();Object.defineProperty(Eo,"useStdout",{enumerable:!0,get:function(){return $Qt.default}});var eTt=qPe();Object.defineProperty(Eo,"useStderr",{enumerable:!0,get:function(){return eTt.default}});var tTt=WPe();Object.defineProperty(Eo,"useFocus",{enumerable:!0,get:function(){return tTt.default}});var rTt=YPe();Object.defineProperty(Eo,"useFocusManager",{enumerable:!0,get:function(){return rTt.default}});var nTt=VPe();Object.defineProperty(Eo,"measureElement",{enumerable:!0,get:function(){return nTt.default}})});var CY={};Vt(CY,{Gem:()=>IY});var KPe,_m,IY,$F=It(()=>{KPe=et(Vc()),_m=et(hn()),IY=(0,_m.memo)(({active:t})=>{let e=(0,_m.useMemo)(()=>t?"\u25C9":"\u25EF",[t]),r=(0,_m.useMemo)(()=>t?"green":"yellow",[t]);return _m.default.createElement(KPe.Text,{color:r},e)})});var zPe={};Vt(zPe,{useKeypress:()=>Um});function Um({active:t},e,r){let{stdin:s}=(0,JPe.useStdin)(),a=(0,eN.useCallback)((n,c)=>e(n,c),r);(0,eN.useEffect)(()=>{if(!(!t||!s))return s.on("keypress",a),()=>{s.off("keypress",a)}},[t,a,s])}var JPe,eN,FD=It(()=>{JPe=et(Vc()),eN=et(hn())});var XPe={};Vt(XPe,{FocusRequest:()=>ZPe,useFocusRequest:()=>wY});var ZPe,wY,BY=It(()=>{FD();ZPe=(r=>(r.BEFORE="before",r.AFTER="after",r))(ZPe||{}),wY=function({active:t},e,r){Um({active:t},(s,a)=>{a.name==="tab"&&(a.shift?e("before"):e("after"))},r)}});var $Pe={};Vt($Pe,{useListInput:()=>ND});var ND,tN=It(()=>{FD();ND=function(t,e,{active:r,minus:s,plus:a,set:n,loop:c=!0}){Um({active:r},(f,p)=>{let h=e.indexOf(t);switch(p.name){case s:{let E=h-1;if(c){n(e[(e.length+E)%e.length]);return}if(E<0)return;n(e[E])}break;case a:{let E=h+1;if(c){n(e[E%e.length]);return}if(E>=e.length)return;n(e[E])}break}},[e,t,a,n,c])}});var rN={};Vt(rN,{ScrollableItems:()=>iTt});var tg,ml,iTt,nN=It(()=>{tg=et(Vc()),ml=et(hn());BY();tN();iTt=({active:t=!0,children:e=[],radius:r=10,size:s=1,loop:a=!0,onFocusRequest:n,willReachEnd:c})=>{let f=N=>{if(N.key===null)throw new Error("Expected all children to have a key");return N.key},p=ml.default.Children.map(e,N=>f(N)),h=p[0],[E,C]=(0,ml.useState)(h),S=p.indexOf(E);(0,ml.useEffect)(()=>{p.includes(E)||C(h)},[e]),(0,ml.useEffect)(()=>{c&&S>=p.length-2&&c()},[S]),wY({active:t&&!!n},N=>{n?.(N)},[n]),ND(E,p,{active:t,minus:"up",plus:"down",set:C,loop:a});let P=S-r,I=S+r;I>p.length&&(P-=I-p.length,I=p.length),P<0&&(I+=-P,P=0),I>=p.length&&(I=p.length-1);let R=[];for(let N=P;N<=I;++N){let U=p[N],W=t&&U===E;R.push(ml.default.createElement(tg.Box,{key:U,height:s},ml.default.createElement(tg.Box,{marginLeft:1,marginRight:1},ml.default.createElement(tg.Text,null,W?ml.default.createElement(tg.Text,{color:"cyan",bold:!0},">"):" ")),ml.default.createElement(tg.Box,null,ml.default.cloneElement(e[N],{active:W}))))}return ml.default.createElement(tg.Box,{flexDirection:"column",width:"100%"},R)}});var exe,ih,txe,vY,rxe,SY=It(()=>{exe=et(Vc()),ih=et(hn()),txe=ye("readline"),vY=ih.default.createContext(null),rxe=({children:t})=>{let{stdin:e,setRawMode:r}=(0,exe.useStdin)();(0,ih.useEffect)(()=>{r&&r(!0),e&&(0,txe.emitKeypressEvents)(e)},[e,r]);let[s,a]=(0,ih.useState)(new Map),n=(0,ih.useMemo)(()=>({getAll:()=>s,get:c=>s.get(c),set:(c,f)=>a(new Map([...s,[c,f]]))}),[s,a]);return ih.default.createElement(vY.Provider,{value:n,children:t})}});var DY={};Vt(DY,{useMinistore:()=>sTt});function sTt(t,e){let r=(0,iN.useContext)(vY);if(r===null)throw new Error("Expected this hook to run with a ministore context attached");if(typeof t>"u")return r.getAll();let s=(0,iN.useCallback)(n=>{r.set(t,n)},[t,r.set]),a=r.get(t);return typeof a>"u"&&(a=e),[a,s]}var iN,bY=It(()=>{iN=et(hn());SY()});var oN={};Vt(oN,{renderForm:()=>oTt});async function oTt(t,e,{stdin:r,stdout:s,stderr:a}){let n,c=p=>{let{exit:h}=(0,sN.useApp)();Um({active:!0},(E,C)=>{C.name==="return"&&(n=p,h())},[h,p])},{waitUntilExit:f}=(0,sN.render)(PY.default.createElement(rxe,null,PY.default.createElement(t,{...e,useSubmit:c})),{stdin:r,stdout:s,stderr:a});return await f(),n}var sN,PY,aN=It(()=>{sN=et(Vc()),PY=et(hn());SY();FD()});var oxe=L(OD=>{"use strict";Object.defineProperty(OD,"__esModule",{value:!0});OD.UncontrolledTextInput=void 0;var ixe=hn(),xY=hn(),nxe=Vc(),Hm=YF(),sxe=({value:t,placeholder:e="",focus:r=!0,mask:s,highlightPastedText:a=!1,showCursor:n=!0,onChange:c,onSubmit:f})=>{let[{cursorOffset:p,cursorWidth:h},E]=xY.useState({cursorOffset:(t||"").length,cursorWidth:0});xY.useEffect(()=>{E(R=>{if(!r||!n)return R;let N=t||"";return R.cursorOffset>N.length-1?{cursorOffset:N.length,cursorWidth:0}:R})},[t,r,n]);let C=a?h:0,S=s?s.repeat(t.length):t,P=S,I=e?Hm.grey(e):void 0;if(n&&r){I=e.length>0?Hm.inverse(e[0])+Hm.grey(e.slice(1)):Hm.inverse(" "),P=S.length>0?"":Hm.inverse(" ");let R=0;for(let N of S)R>=p-C&&R<=p?P+=Hm.inverse(N):P+=N,R++;S.length>0&&p===S.length&&(P+=Hm.inverse(" "))}return nxe.useInput((R,N)=>{if(N.upArrow||N.downArrow||N.ctrl&&R==="c"||N.tab||N.shift&&N.tab)return;if(N.return){f&&f(t);return}let U=p,W=t,te=0;N.leftArrow?n&&U--:N.rightArrow?n&&U++:N.backspace||N.delete?p>0&&(W=t.slice(0,p-1)+t.slice(p,t.length),U--):(W=t.slice(0,p)+R+t.slice(p,t.length),U+=R.length,R.length>1&&(te=R.length)),p<0&&(U=0),p>t.length&&(U=t.length),E({cursorOffset:U,cursorWidth:te}),W!==t&&c(W)},{isActive:r}),ixe.createElement(nxe.Text,null,e?S.length>0?P:I:P)};OD.default=sxe;OD.UncontrolledTextInput=({initialValue:t="",...e})=>{let[r,s]=xY.useState(t);return ixe.createElement(sxe,Object.assign({},e,{value:r,onChange:s}))}});var cxe={};Vt(cxe,{Pad:()=>kY});var axe,lxe,kY,QY=It(()=>{axe=et(Vc()),lxe=et(hn()),kY=({length:t,active:e})=>{if(t===0)return null;let r=t>1?` ${"-".repeat(t-1)}`:" ";return lxe.default.createElement(axe.Text,{dimColor:!e},r)}});var uxe={};Vt(uxe,{ItemOptions:()=>aTt});var MD,rg,aTt,fxe=It(()=>{MD=et(Vc()),rg=et(hn());tN();$F();QY();aTt=function({active:t,skewer:e,options:r,value:s,onChange:a,sizes:n=[]}){let c=r.filter(({label:p})=>!!p).map(({value:p})=>p),f=r.findIndex(p=>p.value===s&&p.label!="");return ND(s,c,{active:t,minus:"left",plus:"right",set:a}),rg.default.createElement(rg.default.Fragment,null,r.map(({label:p},h)=>{let E=h===f,C=n[h]-1||0,S=p.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,""),P=Math.max(0,C-S.length-2);return p?rg.default.createElement(MD.Box,{key:p,width:C,marginLeft:1},rg.default.createElement(MD.Text,{wrap:"truncate"},rg.default.createElement(IY,{active:E})," ",p),e?rg.default.createElement(kY,{active:t,length:P}):null):rg.default.createElement(MD.Box,{key:`spacer-${h}`,width:C,marginLeft:1})}))}});var Dxe=L((Ndr,Sxe)=>{var jY;Sxe.exports=()=>(typeof jY>"u"&&(jY=ye("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),jY)});var Vxe=L((omr,Yxe)=>{var ZY=Symbol("arg flag"),Kc=class t extends Error{constructor(e,r){super(e),this.name="ArgError",this.code=r,Object.setPrototypeOf(this,t.prototype)}};function tb(t,{argv:e=process.argv.slice(2),permissive:r=!1,stopAtPositional:s=!1}={}){if(!t)throw new Kc("argument specification object is required","ARG_CONFIG_NO_SPEC");let a={_:[]},n={},c={};for(let f of Object.keys(t)){if(!f)throw new Kc("argument key cannot be an empty string","ARG_CONFIG_EMPTY_KEY");if(f[0]!=="-")throw new Kc(`argument key must start with '-' but found: '${f}'`,"ARG_CONFIG_NONOPT_KEY");if(f.length===1)throw new Kc(`argument key must have a name; singular '-' keys are not allowed: ${f}`,"ARG_CONFIG_NONAME_KEY");if(typeof t[f]=="string"){n[f]=t[f];continue}let p=t[f],h=!1;if(Array.isArray(p)&&p.length===1&&typeof p[0]=="function"){let[E]=p;p=(C,S,P=[])=>(P.push(E(C,S,P[P.length-1])),P),h=E===Boolean||E[ZY]===!0}else if(typeof p=="function")h=p===Boolean||p[ZY]===!0;else throw new Kc(`type missing or not a function or valid array type: ${f}`,"ARG_CONFIG_VAD_TYPE");if(f[1]!=="-"&&f.length>2)throw new Kc(`short argument keys (with a single hyphen) must have only one character: ${f}`,"ARG_CONFIG_SHORTOPT_TOOLONG");c[f]=[p,h]}for(let f=0,p=e.length;f0){a._=a._.concat(e.slice(f));break}if(h==="--"){a._=a._.concat(e.slice(f+1));break}if(h.length>1&&h[0]==="-"){let E=h[1]==="-"||h.length===2?[h]:h.slice(1).split("").map(C=>`-${C}`);for(let C=0;C1&&e[f+1][0]==="-"&&!(e[f+1].match(/^-?\d*(\.(?=\d))?\d*$/)&&(N===Number||typeof BigInt<"u"&&N===BigInt))){let W=P===R?"":` (alias for ${R})`;throw new Kc(`option requires argument: ${P}${W}`,"ARG_MISSING_REQUIRED_LONGARG")}a[R]=N(e[f+1],R,a[R]),++f}else a[R]=N(I,R,a[R])}}else a._.push(h)}return a}tb.flag=t=>(t[ZY]=!0,t);tb.COUNT=tb.flag((t,e,r)=>(r||0)+1);tb.ArgError=Kc;Yxe.exports=tb});var tke=L((Omr,eke)=>{var tV;eke.exports=()=>(typeof tV>"u"&&(tV=ye("zlib").brotliDecompressSync(Buffer.from("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","base64")).toString()),tV)});var oke=L((aV,lV)=>{(function(t){aV&&typeof aV=="object"&&typeof lV<"u"?lV.exports=t():typeof define=="function"&&define.amd?define([],t):typeof window<"u"?window.isWindows=t():typeof global<"u"?global.isWindows=t():typeof self<"u"?self.isWindows=t():this.isWindows=t()})(function(){"use strict";return function(){return process&&(process.platform==="win32"||/^(msys|cygwin)$/.test(process.env.OSTYPE))}})});var uke=L((Fyr,cke)=>{"use strict";cV.ifExists=lRt;var xw=ye("util"),Jc=ye("path"),ake=oke(),sRt=/^#!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+)(.*)$/,oRt={createPwshFile:!0,createCmdFile:ake(),fs:ye("fs")},aRt=new Map([[".js","node"],[".cjs","node"],[".mjs","node"],[".cmd","cmd"],[".bat","cmd"],[".ps1","pwsh"],[".sh","sh"]]);function lke(t){let e={...oRt,...t},r=e.fs;return e.fs_={chmod:r.chmod?xw.promisify(r.chmod):async()=>{},mkdir:xw.promisify(r.mkdir),readFile:xw.promisify(r.readFile),stat:xw.promisify(r.stat),unlink:xw.promisify(r.unlink),writeFile:xw.promisify(r.writeFile)},e}async function cV(t,e,r){let s=lke(r);await s.fs_.stat(t),await uRt(t,e,s)}function lRt(t,e,r){return cV(t,e,r).catch(()=>{})}function cRt(t,e){return e.fs_.unlink(t).catch(()=>{})}async function uRt(t,e,r){let s=await gRt(t,r);return await fRt(e,r),ARt(t,e,s,r)}function fRt(t,e){return e.fs_.mkdir(Jc.dirname(t),{recursive:!0})}function ARt(t,e,r,s){let a=lke(s),n=[{generator:yRt,extension:""}];return a.createCmdFile&&n.push({generator:mRt,extension:".cmd"}),a.createPwshFile&&n.push({generator:ERt,extension:".ps1"}),Promise.all(n.map(c=>dRt(t,e+c.extension,r,c.generator,a)))}function pRt(t,e){return cRt(t,e)}function hRt(t,e){return IRt(t,e)}async function gRt(t,e){let a=(await e.fs_.readFile(t,"utf8")).trim().split(/\r*\n/)[0].match(sRt);if(!a){let n=Jc.extname(t).toLowerCase();return{program:aRt.get(n)||null,additionalArgs:""}}return{program:a[1],additionalArgs:a[2]}}async function dRt(t,e,r,s,a){let n=a.preserveSymlinks?"--preserve-symlinks":"",c=[r.additionalArgs,n].filter(f=>f).join(" ");return a=Object.assign({},a,{prog:r.program,args:c}),await pRt(e,a),await a.fs_.writeFile(e,s(t,e,a),"utf8"),hRt(e,a)}function mRt(t,e,r){let a=Jc.relative(Jc.dirname(e),t).split("/").join("\\"),n=Jc.isAbsolute(a)?`"${a}"`:`"%~dp0\\${a}"`,c,f=r.prog,p=r.args||"",h=uV(r.nodePath).win32;f?(c=`"%~dp0\\${f}.exe"`,a=n):(f=n,p="",a="");let E=r.progArgs?`${r.progArgs.join(" ")} `:"",C=h?`@SET NODE_PATH=${h}\r +`:"";return c?C+=`@IF EXIST ${c} (\r + ${c} ${p} ${a} ${E}%*\r +) ELSE (\r + @SETLOCAL\r + @SET PATHEXT=%PATHEXT:;.JS;=;%\r + ${f} ${p} ${a} ${E}%*\r +)\r +`:C+=`@${f} ${p} ${a} ${E}%*\r +`,C}function yRt(t,e,r){let s=Jc.relative(Jc.dirname(e),t),a=r.prog&&r.prog.split("\\").join("/"),n;s=s.split("\\").join("/");let c=Jc.isAbsolute(s)?`"${s}"`:`"$basedir/${s}"`,f=r.args||"",p=uV(r.nodePath).posix;a?(n=`"$basedir/${r.prog}"`,s=c):(a=c,f="",s="");let h=r.progArgs?`${r.progArgs.join(" ")} `:"",E=`#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')") + +case \`uname\` in + *CYGWIN*) basedir=\`cygpath -w "$basedir"\`;; +esac + +`,C=r.nodePath?`export NODE_PATH="${p}" +`:"";return n?E+=`${C}if [ -x ${n} ]; then + exec ${n} ${f} ${s} ${h}"$@" +else + exec ${a} ${f} ${s} ${h}"$@" +fi +`:E+=`${C}${a} ${f} ${s} ${h}"$@" +exit $? +`,E}function ERt(t,e,r){let s=Jc.relative(Jc.dirname(e),t),a=r.prog&&r.prog.split("\\").join("/"),n=a&&`"${a}$exe"`,c;s=s.split("\\").join("/");let f=Jc.isAbsolute(s)?`"${s}"`:`"$basedir/${s}"`,p=r.args||"",h=uV(r.nodePath),E=h.win32,C=h.posix;n?(c=`"$basedir/${r.prog}$exe"`,s=f):(n=f,p="",s="");let S=r.progArgs?`${r.progArgs.join(" ")} `:"",P=`#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +${r.nodePath?`$env_node_path=$env:NODE_PATH +$env:NODE_PATH="${E}" +`:""}if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +}`;return r.nodePath&&(P+=` else { + $env:NODE_PATH="${C}" +}`),c?P+=` +$ret=0 +if (Test-Path ${c}) { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & ${c} ${p} ${s} ${S}$args + } else { + & ${c} ${p} ${s} ${S}$args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & ${n} ${p} ${s} ${S}$args + } else { + & ${n} ${p} ${s} ${S}$args + } + $ret=$LASTEXITCODE +} +${r.nodePath?`$env:NODE_PATH=$env_node_path +`:""}exit $ret +`:P+=` +# Support pipeline input +if ($MyInvocation.ExpectingInput) { + $input | & ${n} ${p} ${s} ${S}$args +} else { + & ${n} ${p} ${s} ${S}$args +} +${r.nodePath?`$env:NODE_PATH=$env_node_path +`:""}exit $LASTEXITCODE +`,P}function IRt(t,e){return e.fs_.chmod(t,493)}function uV(t){if(!t)return{win32:"",posix:""};let e=typeof t=="string"?t.split(Jc.delimiter):Array.from(t),r={};for(let s=0;s`/mnt/${f.toLowerCase()}`):e[s];r.win32=r.win32?`${r.win32};${a}`:a,r.posix=r.posix?`${r.posix}:${n}`:n,r[s]={win32:a,posix:n}}return r}cke.exports=cV});var vV=L((tIr,kke)=>{kke.exports=ye("stream")});var Fke=L((rIr,Rke)=>{"use strict";function Qke(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function JRt(t){for(var e=1;e0?this.tail.next=s:this.head=s,this.tail=s,++this.length}},{key:"unshift",value:function(r){var s={data:r,next:this.head};this.length===0&&(this.tail=s),this.head=s,++this.length}},{key:"shift",value:function(){if(this.length!==0){var r=this.head.data;return this.length===1?this.head=this.tail=null:this.head=this.head.next,--this.length,r}}},{key:"clear",value:function(){this.head=this.tail=null,this.length=0}},{key:"join",value:function(r){if(this.length===0)return"";for(var s=this.head,a=""+s.data;s=s.next;)a+=r+s.data;return a}},{key:"concat",value:function(r){if(this.length===0)return CN.alloc(0);for(var s=CN.allocUnsafe(r>>>0),a=this.head,n=0;a;)rFt(a.data,s,n),n+=a.data.length,a=a.next;return s}},{key:"consume",value:function(r,s){var a;return rc.length?c.length:r;if(f===c.length?n+=c:n+=c.slice(0,r),r-=f,r===0){f===c.length?(++a,s.next?this.head=s.next:this.head=this.tail=null):(this.head=s,s.data=c.slice(f));break}++a}return this.length-=a,n}},{key:"_getBuffer",value:function(r){var s=CN.allocUnsafe(r),a=this.head,n=1;for(a.data.copy(s),r-=a.data.length;a=a.next;){var c=a.data,f=r>c.length?c.length:r;if(c.copy(s,s.length-r,0,f),r-=f,r===0){f===c.length?(++n,a.next?this.head=a.next:this.head=this.tail=null):(this.head=a,a.data=c.slice(f));break}++n}return this.length-=n,s}},{key:tFt,value:function(r,s){return SV(this,JRt({},s,{depth:0,customInspect:!1}))}}]),t}()});var bV=L((nIr,Oke)=>{"use strict";function nFt(t,e){var r=this,s=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return s||a?(e?e(t):t&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,process.nextTick(DV,this,t)):process.nextTick(DV,this,t)),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(t||null,function(n){!e&&n?r._writableState?r._writableState.errorEmitted?process.nextTick(wN,r):(r._writableState.errorEmitted=!0,process.nextTick(Nke,r,n)):process.nextTick(Nke,r,n):e?(process.nextTick(wN,r),e(n)):process.nextTick(wN,r)}),this)}function Nke(t,e){DV(t,e),wN(t)}function wN(t){t._writableState&&!t._writableState.emitClose||t._readableState&&!t._readableState.emitClose||t.emit("close")}function iFt(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}function DV(t,e){t.emit("error",e)}function sFt(t,e){var r=t._readableState,s=t._writableState;r&&r.autoDestroy||s&&s.autoDestroy?t.destroy(e):t.emit("error",e)}Oke.exports={destroy:nFt,undestroy:iFt,errorOrDestroy:sFt}});var cg=L((iIr,_ke)=>{"use strict";var Mke={};function Zc(t,e,r){r||(r=Error);function s(n,c,f){return typeof e=="string"?e:e(n,c,f)}class a extends r{constructor(c,f,p){super(s(c,f,p))}}a.prototype.name=r.name,a.prototype.code=t,Mke[t]=a}function Lke(t,e){if(Array.isArray(t)){let r=t.length;return t=t.map(s=>String(s)),r>2?`one of ${e} ${t.slice(0,r-1).join(", ")}, or `+t[r-1]:r===2?`one of ${e} ${t[0]} or ${t[1]}`:`of ${e} ${t[0]}`}else return`of ${e} ${String(t)}`}function oFt(t,e,r){return t.substr(!r||r<0?0:+r,e.length)===e}function aFt(t,e,r){return(r===void 0||r>t.length)&&(r=t.length),t.substring(r-e.length,r)===e}function lFt(t,e,r){return typeof r!="number"&&(r=0),r+e.length>t.length?!1:t.indexOf(e,r)!==-1}Zc("ERR_INVALID_OPT_VALUE",function(t,e){return'The value "'+e+'" is invalid for option "'+t+'"'},TypeError);Zc("ERR_INVALID_ARG_TYPE",function(t,e,r){let s;typeof e=="string"&&oFt(e,"not ")?(s="must not be",e=e.replace(/^not /,"")):s="must be";let a;if(aFt(t," argument"))a=`The ${t} ${s} ${Lke(e,"type")}`;else{let n=lFt(t,".")?"property":"argument";a=`The "${t}" ${n} ${s} ${Lke(e,"type")}`}return a+=`. Received type ${typeof r}`,a},TypeError);Zc("ERR_STREAM_PUSH_AFTER_EOF","stream.push() after EOF");Zc("ERR_METHOD_NOT_IMPLEMENTED",function(t){return"The "+t+" method is not implemented"});Zc("ERR_STREAM_PREMATURE_CLOSE","Premature close");Zc("ERR_STREAM_DESTROYED",function(t){return"Cannot call "+t+" after a stream was destroyed"});Zc("ERR_MULTIPLE_CALLBACK","Callback called multiple times");Zc("ERR_STREAM_CANNOT_PIPE","Cannot pipe, not readable");Zc("ERR_STREAM_WRITE_AFTER_END","write after end");Zc("ERR_STREAM_NULL_VALUES","May not write null values to stream",TypeError);Zc("ERR_UNKNOWN_ENCODING",function(t){return"Unknown encoding: "+t},TypeError);Zc("ERR_STREAM_UNSHIFT_AFTER_END_EVENT","stream.unshift() after end event");_ke.exports.codes=Mke});var PV=L((sIr,Uke)=>{"use strict";var cFt=cg().codes.ERR_INVALID_OPT_VALUE;function uFt(t,e,r){return t.highWaterMark!=null?t.highWaterMark:e?t[r]:null}function fFt(t,e,r,s){var a=uFt(e,s,r);if(a!=null){if(!(isFinite(a)&&Math.floor(a)===a)||a<0){var n=s?r:"highWaterMark";throw new cFt(n,a)}return Math.floor(a)}return t.objectMode?16:16*1024}Uke.exports={getHighWaterMark:fFt}});var Hke=L((oIr,xV)=>{typeof Object.create=="function"?xV.exports=function(e,r){r&&(e.super_=r,e.prototype=Object.create(r.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}))}:xV.exports=function(e,r){if(r){e.super_=r;var s=function(){};s.prototype=r.prototype,e.prototype=new s,e.prototype.constructor=e}}});var ug=L((aIr,QV)=>{try{if(kV=ye("util"),typeof kV.inherits!="function")throw"";QV.exports=kV.inherits}catch{QV.exports=Hke()}var kV});var qke=L((lIr,jke)=>{jke.exports=ye("util").deprecate});var FV=L((cIr,Jke)=>{"use strict";Jke.exports=Vi;function Wke(t){var e=this;this.next=null,this.entry=null,this.finish=function(){MFt(e,t)}}var Fw;Vi.WritableState=hb;var AFt={deprecate:qke()},Yke=vV(),vN=ye("buffer").Buffer,pFt=global.Uint8Array||function(){};function hFt(t){return vN.from(t)}function gFt(t){return vN.isBuffer(t)||t instanceof pFt}var RV=bV(),dFt=PV(),mFt=dFt.getHighWaterMark,fg=cg().codes,yFt=fg.ERR_INVALID_ARG_TYPE,EFt=fg.ERR_METHOD_NOT_IMPLEMENTED,IFt=fg.ERR_MULTIPLE_CALLBACK,CFt=fg.ERR_STREAM_CANNOT_PIPE,wFt=fg.ERR_STREAM_DESTROYED,BFt=fg.ERR_STREAM_NULL_VALUES,vFt=fg.ERR_STREAM_WRITE_AFTER_END,SFt=fg.ERR_UNKNOWN_ENCODING,Nw=RV.errorOrDestroy;ug()(Vi,Yke);function DFt(){}function hb(t,e,r){Fw=Fw||Vm(),t=t||{},typeof r!="boolean"&&(r=e instanceof Fw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.writableObjectMode),this.highWaterMark=mFt(this,t,"writableHighWaterMark",r),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var s=t.decodeStrings===!1;this.decodeStrings=!s,this.defaultEncoding=t.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(a){RFt(e,a)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.bufferedRequestCount=0,this.corkedRequestsFree=new Wke(this)}hb.prototype.getBuffer=function(){for(var e=this.bufferedRequest,r=[];e;)r.push(e),e=e.next;return r};(function(){try{Object.defineProperty(hb.prototype,"buffer",{get:AFt.deprecate(function(){return this.getBuffer()},"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch{}})();var BN;typeof Symbol=="function"&&Symbol.hasInstance&&typeof Function.prototype[Symbol.hasInstance]=="function"?(BN=Function.prototype[Symbol.hasInstance],Object.defineProperty(Vi,Symbol.hasInstance,{value:function(e){return BN.call(this,e)?!0:this!==Vi?!1:e&&e._writableState instanceof hb}})):BN=function(e){return e instanceof this};function Vi(t){Fw=Fw||Vm();var e=this instanceof Fw;if(!e&&!BN.call(Vi,this))return new Vi(t);this._writableState=new hb(t,this,e),this.writable=!0,t&&(typeof t.write=="function"&&(this._write=t.write),typeof t.writev=="function"&&(this._writev=t.writev),typeof t.destroy=="function"&&(this._destroy=t.destroy),typeof t.final=="function"&&(this._final=t.final)),Yke.call(this)}Vi.prototype.pipe=function(){Nw(this,new CFt)};function bFt(t,e){var r=new vFt;Nw(t,r),process.nextTick(e,r)}function PFt(t,e,r,s){var a;return r===null?a=new BFt:typeof r!="string"&&!e.objectMode&&(a=new yFt("chunk",["string","Buffer"],r)),a?(Nw(t,a),process.nextTick(s,a),!1):!0}Vi.prototype.write=function(t,e,r){var s=this._writableState,a=!1,n=!s.objectMode&&gFt(t);return n&&!vN.isBuffer(t)&&(t=hFt(t)),typeof e=="function"&&(r=e,e=null),n?e="buffer":e||(e=s.defaultEncoding),typeof r!="function"&&(r=DFt),s.ending?bFt(this,r):(n||PFt(this,s,t,r))&&(s.pendingcb++,a=kFt(this,s,n,t,e,r)),a};Vi.prototype.cork=function(){this._writableState.corked++};Vi.prototype.uncork=function(){var t=this._writableState;t.corked&&(t.corked--,!t.writing&&!t.corked&&!t.bufferProcessing&&t.bufferedRequest&&Vke(this,t))};Vi.prototype.setDefaultEncoding=function(e){if(typeof e=="string"&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new SFt(e);return this._writableState.defaultEncoding=e,this};Object.defineProperty(Vi.prototype,"writableBuffer",{enumerable:!1,get:function(){return this._writableState&&this._writableState.getBuffer()}});function xFt(t,e,r){return!t.objectMode&&t.decodeStrings!==!1&&typeof e=="string"&&(e=vN.from(e,r)),e}Object.defineProperty(Vi.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}});function kFt(t,e,r,s,a,n){if(!r){var c=xFt(e,s,a);s!==c&&(r=!0,a="buffer",s=c)}var f=e.objectMode?1:s.length;e.length+=f;var p=e.length{"use strict";var _Ft=Object.keys||function(t){var e=[];for(var r in t)e.push(r);return e};Zke.exports=yA;var zke=LV(),OV=FV();ug()(yA,zke);for(NV=_Ft(OV.prototype),SN=0;SN{var bN=ye("buffer"),uh=bN.Buffer;function Xke(t,e){for(var r in t)e[r]=t[r]}uh.from&&uh.alloc&&uh.allocUnsafe&&uh.allocUnsafeSlow?$ke.exports=bN:(Xke(bN,MV),MV.Buffer=Ow);function Ow(t,e,r){return uh(t,e,r)}Xke(uh,Ow);Ow.from=function(t,e,r){if(typeof t=="number")throw new TypeError("Argument must not be a number");return uh(t,e,r)};Ow.alloc=function(t,e,r){if(typeof t!="number")throw new TypeError("Argument must be a number");var s=uh(t);return e!==void 0?typeof r=="string"?s.fill(e,r):s.fill(e):s.fill(0),s};Ow.allocUnsafe=function(t){if(typeof t!="number")throw new TypeError("Argument must be a number");return uh(t)};Ow.allocUnsafeSlow=function(t){if(typeof t!="number")throw new TypeError("Argument must be a number");return bN.SlowBuffer(t)}});var HV=L(rQe=>{"use strict";var UV=eQe().Buffer,tQe=UV.isEncoding||function(t){switch(t=""+t,t&&t.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function jFt(t){if(!t)return"utf8";for(var e;;)switch(t){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return t;default:if(e)return;t=(""+t).toLowerCase(),e=!0}}function qFt(t){var e=jFt(t);if(typeof e!="string"&&(UV.isEncoding===tQe||!tQe(t)))throw new Error("Unknown encoding: "+t);return e||t}rQe.StringDecoder=gb;function gb(t){this.encoding=qFt(t);var e;switch(this.encoding){case"utf16le":this.text=JFt,this.end=zFt,e=4;break;case"utf8":this.fillLast=YFt,e=4;break;case"base64":this.text=ZFt,this.end=XFt,e=3;break;default:this.write=$Ft,this.end=eNt;return}this.lastNeed=0,this.lastTotal=0,this.lastChar=UV.allocUnsafe(e)}gb.prototype.write=function(t){if(t.length===0)return"";var e,r;if(this.lastNeed){if(e=this.fillLast(t),e===void 0)return"";r=this.lastNeed,this.lastNeed=0}else r=0;return r>5===6?2:t>>4===14?3:t>>3===30?4:t>>6===2?-1:-2}function GFt(t,e,r){var s=e.length-1;if(s=0?(a>0&&(t.lastNeed=a-1),a):--s=0?(a>0&&(t.lastNeed=a-2),a):--s=0?(a>0&&(a===2?a=0:t.lastNeed=a-3),a):0))}function WFt(t,e,r){if((e[0]&192)!==128)return t.lastNeed=0,"\uFFFD";if(t.lastNeed>1&&e.length>1){if((e[1]&192)!==128)return t.lastNeed=1,"\uFFFD";if(t.lastNeed>2&&e.length>2&&(e[2]&192)!==128)return t.lastNeed=2,"\uFFFD"}}function YFt(t){var e=this.lastTotal-this.lastNeed,r=WFt(this,t,e);if(r!==void 0)return r;if(this.lastNeed<=t.length)return t.copy(this.lastChar,e,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);t.copy(this.lastChar,e,0,t.length),this.lastNeed-=t.length}function VFt(t,e){var r=GFt(this,t,e);if(!this.lastNeed)return t.toString("utf8",e);this.lastTotal=r;var s=t.length-(r-this.lastNeed);return t.copy(this.lastChar,0,s),t.toString("utf8",e,s)}function KFt(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+"\uFFFD":e}function JFt(t,e){if((t.length-e)%2===0){var r=t.toString("utf16le",e);if(r){var s=r.charCodeAt(r.length-1);if(s>=55296&&s<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1],r.slice(0,-1)}return r}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=t[t.length-1],t.toString("utf16le",e,t.length-1)}function zFt(t){var e=t&&t.length?this.write(t):"";if(this.lastNeed){var r=this.lastTotal-this.lastNeed;return e+this.lastChar.toString("utf16le",0,r)}return e}function ZFt(t,e){var r=(t.length-e)%3;return r===0?t.toString("base64",e):(this.lastNeed=3-r,this.lastTotal=3,r===1?this.lastChar[0]=t[t.length-1]:(this.lastChar[0]=t[t.length-2],this.lastChar[1]=t[t.length-1]),t.toString("base64",e,t.length-r))}function XFt(t){var e=t&&t.length?this.write(t):"";return this.lastNeed?e+this.lastChar.toString("base64",0,3-this.lastNeed):e}function $Ft(t){return t.toString(this.encoding)}function eNt(t){return t&&t.length?this.write(t):""}});var PN=L((AIr,sQe)=>{"use strict";var nQe=cg().codes.ERR_STREAM_PREMATURE_CLOSE;function tNt(t){var e=!1;return function(){if(!e){e=!0;for(var r=arguments.length,s=new Array(r),a=0;a{"use strict";var xN;function Ag(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}var iNt=PN(),pg=Symbol("lastResolve"),Km=Symbol("lastReject"),db=Symbol("error"),kN=Symbol("ended"),Jm=Symbol("lastPromise"),jV=Symbol("handlePromise"),zm=Symbol("stream");function hg(t,e){return{value:t,done:e}}function sNt(t){var e=t[pg];if(e!==null){var r=t[zm].read();r!==null&&(t[Jm]=null,t[pg]=null,t[Km]=null,e(hg(r,!1)))}}function oNt(t){process.nextTick(sNt,t)}function aNt(t,e){return function(r,s){t.then(function(){if(e[kN]){r(hg(void 0,!0));return}e[jV](r,s)},s)}}var lNt=Object.getPrototypeOf(function(){}),cNt=Object.setPrototypeOf((xN={get stream(){return this[zm]},next:function(){var e=this,r=this[db];if(r!==null)return Promise.reject(r);if(this[kN])return Promise.resolve(hg(void 0,!0));if(this[zm].destroyed)return new Promise(function(c,f){process.nextTick(function(){e[db]?f(e[db]):c(hg(void 0,!0))})});var s=this[Jm],a;if(s)a=new Promise(aNt(s,this));else{var n=this[zm].read();if(n!==null)return Promise.resolve(hg(n,!1));a=new Promise(this[jV])}return this[Jm]=a,a}},Ag(xN,Symbol.asyncIterator,function(){return this}),Ag(xN,"return",function(){var e=this;return new Promise(function(r,s){e[zm].destroy(null,function(a){if(a){s(a);return}r(hg(void 0,!0))})})}),xN),lNt),uNt=function(e){var r,s=Object.create(cNt,(r={},Ag(r,zm,{value:e,writable:!0}),Ag(r,pg,{value:null,writable:!0}),Ag(r,Km,{value:null,writable:!0}),Ag(r,db,{value:null,writable:!0}),Ag(r,kN,{value:e._readableState.endEmitted,writable:!0}),Ag(r,jV,{value:function(n,c){var f=s[zm].read();f?(s[Jm]=null,s[pg]=null,s[Km]=null,n(hg(f,!1))):(s[pg]=n,s[Km]=c)},writable:!0}),r));return s[Jm]=null,iNt(e,function(a){if(a&&a.code!=="ERR_STREAM_PREMATURE_CLOSE"){var n=s[Km];n!==null&&(s[Jm]=null,s[pg]=null,s[Km]=null,n(a)),s[db]=a;return}var c=s[pg];c!==null&&(s[Jm]=null,s[pg]=null,s[Km]=null,c(hg(void 0,!0))),s[kN]=!0}),e.on("readable",oNt.bind(null,s)),s};oQe.exports=uNt});var fQe=L((hIr,uQe)=>{"use strict";function lQe(t,e,r,s,a,n,c){try{var f=t[n](c),p=f.value}catch(h){r(h);return}f.done?e(p):Promise.resolve(p).then(s,a)}function fNt(t){return function(){var e=this,r=arguments;return new Promise(function(s,a){var n=t.apply(e,r);function c(p){lQe(n,s,a,c,f,"next",p)}function f(p){lQe(n,s,a,c,f,"throw",p)}c(void 0)})}}function cQe(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(t);e&&(s=s.filter(function(a){return Object.getOwnPropertyDescriptor(t,a).enumerable})),r.push.apply(r,s)}return r}function ANt(t){for(var e=1;e{"use strict";CQe.exports=Pn;var Lw;Pn.ReadableState=gQe;var gIr=ye("events").EventEmitter,hQe=function(e,r){return e.listeners(r).length},yb=vV(),QN=ye("buffer").Buffer,dNt=global.Uint8Array||function(){};function mNt(t){return QN.from(t)}function yNt(t){return QN.isBuffer(t)||t instanceof dNt}var qV=ye("util"),ln;qV&&qV.debuglog?ln=qV.debuglog("stream"):ln=function(){};var ENt=Fke(),zV=bV(),INt=PV(),CNt=INt.getHighWaterMark,TN=cg().codes,wNt=TN.ERR_INVALID_ARG_TYPE,BNt=TN.ERR_STREAM_PUSH_AFTER_EOF,vNt=TN.ERR_METHOD_NOT_IMPLEMENTED,SNt=TN.ERR_STREAM_UNSHIFT_AFTER_END_EVENT,Mw,GV,WV;ug()(Pn,yb);var mb=zV.errorOrDestroy,YV=["error","close","destroy","pause","resume"];function DNt(t,e,r){if(typeof t.prependListener=="function")return t.prependListener(e,r);!t._events||!t._events[e]?t.on(e,r):Array.isArray(t._events[e])?t._events[e].unshift(r):t._events[e]=[r,t._events[e]]}function gQe(t,e,r){Lw=Lw||Vm(),t=t||{},typeof r!="boolean"&&(r=e instanceof Lw),this.objectMode=!!t.objectMode,r&&(this.objectMode=this.objectMode||!!t.readableObjectMode),this.highWaterMark=CNt(this,t,"readableHighWaterMark",r),this.buffer=new ENt,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.paused=!0,this.emitClose=t.emitClose!==!1,this.autoDestroy=!!t.autoDestroy,this.destroyed=!1,this.defaultEncoding=t.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,t.encoding&&(Mw||(Mw=HV().StringDecoder),this.decoder=new Mw(t.encoding),this.encoding=t.encoding)}function Pn(t){if(Lw=Lw||Vm(),!(this instanceof Pn))return new Pn(t);var e=this instanceof Lw;this._readableState=new gQe(t,this,e),this.readable=!0,t&&(typeof t.read=="function"&&(this._read=t.read),typeof t.destroy=="function"&&(this._destroy=t.destroy)),yb.call(this)}Object.defineProperty(Pn.prototype,"destroyed",{enumerable:!1,get:function(){return this._readableState===void 0?!1:this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}});Pn.prototype.destroy=zV.destroy;Pn.prototype._undestroy=zV.undestroy;Pn.prototype._destroy=function(t,e){e(t)};Pn.prototype.push=function(t,e){var r=this._readableState,s;return r.objectMode?s=!0:typeof t=="string"&&(e=e||r.defaultEncoding,e!==r.encoding&&(t=QN.from(t,e),e=""),s=!0),dQe(this,t,e,!1,s)};Pn.prototype.unshift=function(t){return dQe(this,t,null,!0,!1)};function dQe(t,e,r,s,a){ln("readableAddChunk",e);var n=t._readableState;if(e===null)n.reading=!1,xNt(t,n);else{var c;if(a||(c=bNt(n,e)),c)mb(t,c);else if(n.objectMode||e&&e.length>0)if(typeof e!="string"&&!n.objectMode&&Object.getPrototypeOf(e)!==QN.prototype&&(e=mNt(e)),s)n.endEmitted?mb(t,new SNt):VV(t,n,e,!0);else if(n.ended)mb(t,new BNt);else{if(n.destroyed)return!1;n.reading=!1,n.decoder&&!r?(e=n.decoder.write(e),n.objectMode||e.length!==0?VV(t,n,e,!1):JV(t,n)):VV(t,n,e,!1)}else s||(n.reading=!1,JV(t,n))}return!n.ended&&(n.length=AQe?t=AQe:(t--,t|=t>>>1,t|=t>>>2,t|=t>>>4,t|=t>>>8,t|=t>>>16,t++),t}function pQe(t,e){return t<=0||e.length===0&&e.ended?0:e.objectMode?1:t!==t?e.flowing&&e.length?e.buffer.head.data.length:e.length:(t>e.highWaterMark&&(e.highWaterMark=PNt(t)),t<=e.length?t:e.ended?e.length:(e.needReadable=!0,0))}Pn.prototype.read=function(t){ln("read",t),t=parseInt(t,10);var e=this._readableState,r=t;if(t!==0&&(e.emittedReadable=!1),t===0&&e.needReadable&&((e.highWaterMark!==0?e.length>=e.highWaterMark:e.length>0)||e.ended))return ln("read: emitReadable",e.length,e.ended),e.length===0&&e.ended?KV(this):RN(this),null;if(t=pQe(t,e),t===0&&e.ended)return e.length===0&&KV(this),null;var s=e.needReadable;ln("need readable",s),(e.length===0||e.length-t0?a=EQe(t,e):a=null,a===null?(e.needReadable=e.length<=e.highWaterMark,t=0):(e.length-=t,e.awaitDrain=0),e.length===0&&(e.ended||(e.needReadable=!0),r!==t&&e.ended&&KV(this)),a!==null&&this.emit("data",a),a};function xNt(t,e){if(ln("onEofChunk"),!e.ended){if(e.decoder){var r=e.decoder.end();r&&r.length&&(e.buffer.push(r),e.length+=e.objectMode?1:r.length)}e.ended=!0,e.sync?RN(t):(e.needReadable=!1,e.emittedReadable||(e.emittedReadable=!0,mQe(t)))}}function RN(t){var e=t._readableState;ln("emitReadable",e.needReadable,e.emittedReadable),e.needReadable=!1,e.emittedReadable||(ln("emitReadable",e.flowing),e.emittedReadable=!0,process.nextTick(mQe,t))}function mQe(t){var e=t._readableState;ln("emitReadable_",e.destroyed,e.length,e.ended),!e.destroyed&&(e.length||e.ended)&&(t.emit("readable"),e.emittedReadable=!1),e.needReadable=!e.flowing&&!e.ended&&e.length<=e.highWaterMark,ZV(t)}function JV(t,e){e.readingMore||(e.readingMore=!0,process.nextTick(kNt,t,e))}function kNt(t,e){for(;!e.reading&&!e.ended&&(e.length1&&IQe(s.pipes,t)!==-1)&&!h&&(ln("false write response, pause",s.awaitDrain),s.awaitDrain++),r.pause())}function S(N){ln("onerror",N),R(),t.removeListener("error",S),hQe(t,"error")===0&&mb(t,N)}DNt(t,"error",S);function P(){t.removeListener("finish",I),R()}t.once("close",P);function I(){ln("onfinish"),t.removeListener("close",P),R()}t.once("finish",I);function R(){ln("unpipe"),r.unpipe(t)}return t.emit("pipe",r),s.flowing||(ln("pipe resume"),r.resume()),t};function QNt(t){return function(){var r=t._readableState;ln("pipeOnDrain",r.awaitDrain),r.awaitDrain&&r.awaitDrain--,r.awaitDrain===0&&hQe(t,"data")&&(r.flowing=!0,ZV(t))}}Pn.prototype.unpipe=function(t){var e=this._readableState,r={hasUnpiped:!1};if(e.pipesCount===0)return this;if(e.pipesCount===1)return t&&t!==e.pipes?this:(t||(t=e.pipes),e.pipes=null,e.pipesCount=0,e.flowing=!1,t&&t.emit("unpipe",this,r),this);if(!t){var s=e.pipes,a=e.pipesCount;e.pipes=null,e.pipesCount=0,e.flowing=!1;for(var n=0;n0,s.flowing!==!1&&this.resume()):t==="readable"&&!s.endEmitted&&!s.readableListening&&(s.readableListening=s.needReadable=!0,s.flowing=!1,s.emittedReadable=!1,ln("on readable",s.length,s.reading),s.length?RN(this):s.reading||process.nextTick(TNt,this)),r};Pn.prototype.addListener=Pn.prototype.on;Pn.prototype.removeListener=function(t,e){var r=yb.prototype.removeListener.call(this,t,e);return t==="readable"&&process.nextTick(yQe,this),r};Pn.prototype.removeAllListeners=function(t){var e=yb.prototype.removeAllListeners.apply(this,arguments);return(t==="readable"||t===void 0)&&process.nextTick(yQe,this),e};function yQe(t){var e=t._readableState;e.readableListening=t.listenerCount("readable")>0,e.resumeScheduled&&!e.paused?e.flowing=!0:t.listenerCount("data")>0&&t.resume()}function TNt(t){ln("readable nexttick read 0"),t.read(0)}Pn.prototype.resume=function(){var t=this._readableState;return t.flowing||(ln("resume"),t.flowing=!t.readableListening,RNt(this,t)),t.paused=!1,this};function RNt(t,e){e.resumeScheduled||(e.resumeScheduled=!0,process.nextTick(FNt,t,e))}function FNt(t,e){ln("resume",e.reading),e.reading||t.read(0),e.resumeScheduled=!1,t.emit("resume"),ZV(t),e.flowing&&!e.reading&&t.read(0)}Pn.prototype.pause=function(){return ln("call pause flowing=%j",this._readableState.flowing),this._readableState.flowing!==!1&&(ln("pause"),this._readableState.flowing=!1,this.emit("pause")),this._readableState.paused=!0,this};function ZV(t){var e=t._readableState;for(ln("flow",e.flowing);e.flowing&&t.read()!==null;);}Pn.prototype.wrap=function(t){var e=this,r=this._readableState,s=!1;t.on("end",function(){if(ln("wrapped end"),r.decoder&&!r.ended){var c=r.decoder.end();c&&c.length&&e.push(c)}e.push(null)}),t.on("data",function(c){if(ln("wrapped data"),r.decoder&&(c=r.decoder.write(c)),!(r.objectMode&&c==null)&&!(!r.objectMode&&(!c||!c.length))){var f=e.push(c);f||(s=!0,t.pause())}});for(var a in t)this[a]===void 0&&typeof t[a]=="function"&&(this[a]=function(f){return function(){return t[f].apply(t,arguments)}}(a));for(var n=0;n=e.length?(e.decoder?r=e.buffer.join(""):e.buffer.length===1?r=e.buffer.first():r=e.buffer.concat(e.length),e.buffer.clear()):r=e.buffer.consume(t,e.decoder),r}function KV(t){var e=t._readableState;ln("endReadable",e.endEmitted),e.endEmitted||(e.ended=!0,process.nextTick(NNt,e,t))}function NNt(t,e){if(ln("endReadableNT",t.endEmitted,t.length),!t.endEmitted&&t.length===0&&(t.endEmitted=!0,e.readable=!1,e.emit("end"),t.autoDestroy)){var r=e._writableState;(!r||r.autoDestroy&&r.finished)&&e.destroy()}}typeof Symbol=="function"&&(Pn.from=function(t,e){return WV===void 0&&(WV=fQe()),WV(Pn,t,e)});function IQe(t,e){for(var r=0,s=t.length;r{"use strict";BQe.exports=fh;var FN=cg().codes,ONt=FN.ERR_METHOD_NOT_IMPLEMENTED,LNt=FN.ERR_MULTIPLE_CALLBACK,MNt=FN.ERR_TRANSFORM_ALREADY_TRANSFORMING,_Nt=FN.ERR_TRANSFORM_WITH_LENGTH_0,NN=Vm();ug()(fh,NN);function UNt(t,e){var r=this._transformState;r.transforming=!1;var s=r.writecb;if(s===null)return this.emit("error",new LNt);r.writechunk=null,r.writecb=null,e!=null&&this.push(e),s(t);var a=this._readableState;a.reading=!1,(a.needReadable||a.length{"use strict";SQe.exports=Eb;var vQe=XV();ug()(Eb,vQe);function Eb(t){if(!(this instanceof Eb))return new Eb(t);vQe.call(this,t)}Eb.prototype._transform=function(t,e,r){r(null,t)}});var QQe=L((EIr,kQe)=>{"use strict";var $V;function jNt(t){var e=!1;return function(){e||(e=!0,t.apply(void 0,arguments))}}var xQe=cg().codes,qNt=xQe.ERR_MISSING_ARGS,GNt=xQe.ERR_STREAM_DESTROYED;function bQe(t){if(t)throw t}function WNt(t){return t.setHeader&&typeof t.abort=="function"}function YNt(t,e,r,s){s=jNt(s);var a=!1;t.on("close",function(){a=!0}),$V===void 0&&($V=PN()),$V(t,{readable:e,writable:r},function(c){if(c)return s(c);a=!0,s()});var n=!1;return function(c){if(!a&&!n){if(n=!0,WNt(t))return t.abort();if(typeof t.destroy=="function")return t.destroy();s(c||new GNt("pipe"))}}}function PQe(t){t()}function VNt(t,e){return t.pipe(e)}function KNt(t){return!t.length||typeof t[t.length-1]!="function"?bQe:t.pop()}function JNt(){for(var t=arguments.length,e=new Array(t),r=0;r0;return YNt(c,p,h,function(E){a||(a=E),E&&n.forEach(PQe),!p&&(n.forEach(PQe),s(a))})});return e.reduce(VNt)}kQe.exports=JNt});var _w=L((Xc,Cb)=>{var Ib=ye("stream");process.env.READABLE_STREAM==="disable"&&Ib?(Cb.exports=Ib.Readable,Object.assign(Cb.exports,Ib),Cb.exports.Stream=Ib):(Xc=Cb.exports=LV(),Xc.Stream=Ib||Xc,Xc.Readable=Xc,Xc.Writable=FV(),Xc.Duplex=Vm(),Xc.Transform=XV(),Xc.PassThrough=DQe(),Xc.finished=PN(),Xc.pipeline=QQe())});var FQe=L((IIr,RQe)=>{"use strict";var{Buffer:uf}=ye("buffer"),TQe=Symbol.for("BufferList");function Ci(t){if(!(this instanceof Ci))return new Ci(t);Ci._init.call(this,t)}Ci._init=function(e){Object.defineProperty(this,TQe,{value:!0}),this._bufs=[],this.length=0,e&&this.append(e)};Ci.prototype._new=function(e){return new Ci(e)};Ci.prototype._offset=function(e){if(e===0)return[0,0];let r=0;for(let s=0;sthis.length||e<0)return;let r=this._offset(e);return this._bufs[r[0]][r[1]]};Ci.prototype.slice=function(e,r){return typeof e=="number"&&e<0&&(e+=this.length),typeof r=="number"&&r<0&&(r+=this.length),this.copy(null,0,e,r)};Ci.prototype.copy=function(e,r,s,a){if((typeof s!="number"||s<0)&&(s=0),(typeof a!="number"||a>this.length)&&(a=this.length),s>=this.length||a<=0)return e||uf.alloc(0);let n=!!e,c=this._offset(s),f=a-s,p=f,h=n&&r||0,E=c[1];if(s===0&&a===this.length){if(!n)return this._bufs.length===1?this._bufs[0]:uf.concat(this._bufs,this.length);for(let C=0;CS)this._bufs[C].copy(e,h,E),h+=S;else{this._bufs[C].copy(e,h,E,E+p),h+=S;break}p-=S,E&&(E=0)}return e.length>h?e.slice(0,h):e};Ci.prototype.shallowSlice=function(e,r){if(e=e||0,r=typeof r!="number"?this.length:r,e<0&&(e+=this.length),r<0&&(r+=this.length),e===r)return this._new();let s=this._offset(e),a=this._offset(r),n=this._bufs.slice(s[0],a[0]+1);return a[1]===0?n.pop():n[n.length-1]=n[n.length-1].slice(0,a[1]),s[1]!==0&&(n[0]=n[0].slice(s[1])),this._new(n)};Ci.prototype.toString=function(e,r,s){return this.slice(r,s).toString(e)};Ci.prototype.consume=function(e){if(e=Math.trunc(e),Number.isNaN(e)||e<=0)return this;for(;this._bufs.length;)if(e>=this._bufs[0].length)e-=this._bufs[0].length,this.length-=this._bufs[0].length,this._bufs.shift();else{this._bufs[0]=this._bufs[0].slice(e),this.length-=e;break}return this};Ci.prototype.duplicate=function(){let e=this._new();for(let r=0;rthis.length?this.length:e;let s=this._offset(e),a=s[0],n=s[1];for(;a=t.length){let p=c.indexOf(t,n);if(p!==-1)return this._reverseOffset([a,p]);n=c.length-t.length+1}else{let p=this._reverseOffset([a,n]);if(this._match(p,t))return p;n++}n=0}return-1};Ci.prototype._match=function(t,e){if(this.length-t{"use strict";var e7=_w().Duplex,zNt=ug(),wb=FQe();function na(t){if(!(this instanceof na))return new na(t);if(typeof t=="function"){this._callback=t;let e=function(s){this._callback&&(this._callback(s),this._callback=null)}.bind(this);this.on("pipe",function(s){s.on("error",e)}),this.on("unpipe",function(s){s.removeListener("error",e)}),t=null}wb._init.call(this,t),e7.call(this)}zNt(na,e7);Object.assign(na.prototype,wb.prototype);na.prototype._new=function(e){return new na(e)};na.prototype._write=function(e,r,s){this._appendBuffer(e),typeof s=="function"&&s()};na.prototype._read=function(e){if(!this.length)return this.push(null);e=Math.min(e,this.length),this.push(this.slice(0,e)),this.consume(e)};na.prototype.end=function(e){e7.prototype.end.call(this,e),this._callback&&(this._callback(null,this.slice()),this._callback=null)};na.prototype._destroy=function(e,r){this._bufs.length=0,this.length=0,r(e)};na.prototype._isBufferList=function(e){return e instanceof na||e instanceof wb||na.isBufferList(e)};na.isBufferList=wb.isBufferList;ON.exports=na;ON.exports.BufferListStream=na;ON.exports.BufferList=wb});var n7=L(Hw=>{var ZNt=Buffer.alloc,XNt="0000000000000000000",$Nt="7777777777777777777",OQe=48,LQe=Buffer.from("ustar\0","binary"),eOt=Buffer.from("00","binary"),tOt=Buffer.from("ustar ","binary"),rOt=Buffer.from(" \0","binary"),nOt=parseInt("7777",8),Bb=257,r7=263,iOt=function(t,e,r){return typeof t!="number"?r:(t=~~t,t>=e?e:t>=0||(t+=e,t>=0)?t:0)},sOt=function(t){switch(t){case 0:return"file";case 1:return"link";case 2:return"symlink";case 3:return"character-device";case 4:return"block-device";case 5:return"directory";case 6:return"fifo";case 7:return"contiguous-file";case 72:return"pax-header";case 55:return"pax-global-header";case 27:return"gnu-long-link-path";case 28:case 30:return"gnu-long-path"}return null},oOt=function(t){switch(t){case"file":return 0;case"link":return 1;case"symlink":return 2;case"character-device":return 3;case"block-device":return 4;case"directory":return 5;case"fifo":return 6;case"contiguous-file":return 7;case"pax-header":return 72}return 0},MQe=function(t,e,r,s){for(;re?$Nt.slice(0,e)+" ":XNt.slice(0,e-t.length)+t+" "};function aOt(t){var e;if(t[0]===128)e=!0;else if(t[0]===255)e=!1;else return null;for(var r=[],s=t.length-1;s>0;s--){var a=t[s];e?r.push(a):r.push(255-a)}var n=0,c=r.length;for(s=0;s=Math.pow(10,r)&&r++,e+r+t};Hw.decodeLongPath=function(t,e){return Uw(t,0,t.length,e)};Hw.encodePax=function(t){var e="";t.name&&(e+=t7(" path="+t.name+` +`)),t.linkname&&(e+=t7(" linkpath="+t.linkname+` +`));var r=t.pax;if(r)for(var s in r)e+=t7(" "+s+"="+r[s]+` +`);return Buffer.from(e)};Hw.decodePax=function(t){for(var e={};t.length;){for(var r=0;r100;){var a=r.indexOf("/");if(a===-1)return null;s+=s?"/"+r.slice(0,a):r.slice(0,a),r=r.slice(a+1)}return Buffer.byteLength(r)>100||Buffer.byteLength(s)>155||t.linkname&&Buffer.byteLength(t.linkname)>100?null:(e.write(r),e.write(gg(t.mode&nOt,6),100),e.write(gg(t.uid,6),108),e.write(gg(t.gid,6),116),e.write(gg(t.size,11),124),e.write(gg(t.mtime.getTime()/1e3|0,11),136),e[156]=OQe+oOt(t.type),t.linkname&&e.write(t.linkname,157),LQe.copy(e,Bb),eOt.copy(e,r7),t.uname&&e.write(t.uname,265),t.gname&&e.write(t.gname,297),e.write(gg(t.devmajor||0,6),329),e.write(gg(t.devminor||0,6),337),s&&e.write(s,345),e.write(gg(_Qe(e),6),148),e)};Hw.decode=function(t,e,r){var s=t[156]===0?0:t[156]-OQe,a=Uw(t,0,100,e),n=dg(t,100,8),c=dg(t,108,8),f=dg(t,116,8),p=dg(t,124,12),h=dg(t,136,12),E=sOt(s),C=t[157]===0?null:Uw(t,157,100,e),S=Uw(t,265,32),P=Uw(t,297,32),I=dg(t,329,8),R=dg(t,337,8),N=_Qe(t);if(N===8*32)return null;if(N!==dg(t,148,8))throw new Error("Invalid tar header. Maybe the tar is corrupted or it needs to be gunzipped?");if(LQe.compare(t,Bb,Bb+6)===0)t[345]&&(a=Uw(t,345,155,e)+"/"+a);else if(!(tOt.compare(t,Bb,Bb+6)===0&&rOt.compare(t,r7,r7+2)===0)){if(!r)throw new Error("Invalid tar header: unknown format.")}return s===0&&a&&a[a.length-1]==="/"&&(s=5),{name:a,mode:n,uid:c,gid:f,size:p,mtime:new Date(1e3*h),type:E,linkname:C,uname:S,gname:P,devmajor:I,devminor:R}}});var YQe=L((BIr,WQe)=>{var HQe=ye("util"),lOt=NQe(),vb=n7(),jQe=_w().Writable,qQe=_w().PassThrough,GQe=function(){},UQe=function(t){return t&=511,t&&512-t},cOt=function(t,e){var r=new LN(t,e);return r.end(),r},uOt=function(t,e){return e.path&&(t.name=e.path),e.linkpath&&(t.linkname=e.linkpath),e.size&&(t.size=parseInt(e.size,10)),t.pax=e,t},LN=function(t,e){this._parent=t,this.offset=e,qQe.call(this,{autoDestroy:!1})};HQe.inherits(LN,qQe);LN.prototype.destroy=function(t){this._parent.destroy(t)};var Ah=function(t){if(!(this instanceof Ah))return new Ah(t);jQe.call(this,t),t=t||{},this._offset=0,this._buffer=lOt(),this._missing=0,this._partial=!1,this._onparse=GQe,this._header=null,this._stream=null,this._overflow=null,this._cb=null,this._locked=!1,this._destroyed=!1,this._pax=null,this._paxGlobal=null,this._gnuLongPath=null,this._gnuLongLinkPath=null;var e=this,r=e._buffer,s=function(){e._continue()},a=function(S){if(e._locked=!1,S)return e.destroy(S);e._stream||s()},n=function(){e._stream=null;var S=UQe(e._header.size);S?e._parse(S,c):e._parse(512,C),e._locked||s()},c=function(){e._buffer.consume(UQe(e._header.size)),e._parse(512,C),s()},f=function(){var S=e._header.size;e._paxGlobal=vb.decodePax(r.slice(0,S)),r.consume(S),n()},p=function(){var S=e._header.size;e._pax=vb.decodePax(r.slice(0,S)),e._paxGlobal&&(e._pax=Object.assign({},e._paxGlobal,e._pax)),r.consume(S),n()},h=function(){var S=e._header.size;this._gnuLongPath=vb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},E=function(){var S=e._header.size;this._gnuLongLinkPath=vb.decodeLongPath(r.slice(0,S),t.filenameEncoding),r.consume(S),n()},C=function(){var S=e._offset,P;try{P=e._header=vb.decode(r.slice(0,512),t.filenameEncoding,t.allowUnknownFormat)}catch(I){e.emit("error",I)}if(r.consume(512),!P){e._parse(512,C),s();return}if(P.type==="gnu-long-path"){e._parse(P.size,h),s();return}if(P.type==="gnu-long-link-path"){e._parse(P.size,E),s();return}if(P.type==="pax-global-header"){e._parse(P.size,f),s();return}if(P.type==="pax-header"){e._parse(P.size,p),s();return}if(e._gnuLongPath&&(P.name=e._gnuLongPath,e._gnuLongPath=null),e._gnuLongLinkPath&&(P.linkname=e._gnuLongLinkPath,e._gnuLongLinkPath=null),e._pax&&(e._header=P=uOt(P,e._pax),e._pax=null),e._locked=!0,!P.size||P.type==="directory"){e._parse(512,C),e.emit("entry",P,cOt(e,S),a);return}e._stream=new LN(e,S),e.emit("entry",P,e._stream,a),e._parse(P.size,n),s()};this._onheader=C,this._parse(512,C)};HQe.inherits(Ah,jQe);Ah.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit("error",t),this.emit("close"),this._stream&&this._stream.emit("close"))};Ah.prototype._parse=function(t,e){this._destroyed||(this._offset+=t,this._missing=t,e===this._onheader&&(this._partial=!1),this._onparse=e)};Ah.prototype._continue=function(){if(!this._destroyed){var t=this._cb;this._cb=GQe,this._overflow?this._write(this._overflow,void 0,t):t()}};Ah.prototype._write=function(t,e,r){if(!this._destroyed){var s=this._stream,a=this._buffer,n=this._missing;if(t.length&&(this._partial=!0),t.lengthn&&(c=t.slice(n),t=t.slice(0,n)),s?s.end(t):a.append(t),this._overflow=c,this._onparse()}};Ah.prototype._final=function(t){if(this._partial)return this.destroy(new Error("Unexpected end of data"));t()};WQe.exports=Ah});var KQe=L((vIr,VQe)=>{VQe.exports=ye("fs").constants||ye("constants")});var $Qe=L((SIr,XQe)=>{var jw=KQe(),JQe=kH(),_N=ug(),fOt=Buffer.alloc,zQe=_w().Readable,qw=_w().Writable,AOt=ye("string_decoder").StringDecoder,MN=n7(),pOt=parseInt("755",8),hOt=parseInt("644",8),ZQe=fOt(1024),s7=function(){},i7=function(t,e){e&=511,e&&t.push(ZQe.slice(0,512-e))};function gOt(t){switch(t&jw.S_IFMT){case jw.S_IFBLK:return"block-device";case jw.S_IFCHR:return"character-device";case jw.S_IFDIR:return"directory";case jw.S_IFIFO:return"fifo";case jw.S_IFLNK:return"symlink"}return"file"}var UN=function(t){qw.call(this),this.written=0,this._to=t,this._destroyed=!1};_N(UN,qw);UN.prototype._write=function(t,e,r){if(this.written+=t.length,this._to.push(t))return r();this._to._drain=r};UN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var HN=function(){qw.call(this),this.linkname="",this._decoder=new AOt("utf-8"),this._destroyed=!1};_N(HN,qw);HN.prototype._write=function(t,e,r){this.linkname+=this._decoder.write(t),r()};HN.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var Sb=function(){qw.call(this),this._destroyed=!1};_N(Sb,qw);Sb.prototype._write=function(t,e,r){r(new Error("No body allowed for this entry"))};Sb.prototype.destroy=function(){this._destroyed||(this._destroyed=!0,this.emit("close"))};var EA=function(t){if(!(this instanceof EA))return new EA(t);zQe.call(this,t),this._drain=s7,this._finalized=!1,this._finalizing=!1,this._destroyed=!1,this._stream=null};_N(EA,zQe);EA.prototype.entry=function(t,e,r){if(this._stream)throw new Error("already piping an entry");if(!(this._finalized||this._destroyed)){typeof e=="function"&&(r=e,e=null),r||(r=s7);var s=this;if((!t.size||t.type==="symlink")&&(t.size=0),t.type||(t.type=gOt(t.mode)),t.mode||(t.mode=t.type==="directory"?pOt:hOt),t.uid||(t.uid=0),t.gid||(t.gid=0),t.mtime||(t.mtime=new Date),typeof e=="string"&&(e=Buffer.from(e)),Buffer.isBuffer(e)){t.size=e.length,this._encode(t);var a=this.push(e);return i7(s,t.size),a?process.nextTick(r):this._drain=r,new Sb}if(t.type==="symlink"&&!t.linkname){var n=new HN;return JQe(n,function(f){if(f)return s.destroy(),r(f);t.linkname=n.linkname,s._encode(t),r()}),n}if(this._encode(t),t.type!=="file"&&t.type!=="contiguous-file")return process.nextTick(r),new Sb;var c=new UN(this);return this._stream=c,JQe(c,function(f){if(s._stream=null,f)return s.destroy(),r(f);if(c.written!==t.size)return s.destroy(),r(new Error("size mismatch"));i7(s,t.size),s._finalizing&&s.finalize(),r()}),c}};EA.prototype.finalize=function(){if(this._stream){this._finalizing=!0;return}this._finalized||(this._finalized=!0,this.push(ZQe),this.push(null))};EA.prototype.destroy=function(t){this._destroyed||(this._destroyed=!0,t&&this.emit("error",t),this.emit("close"),this._stream&&this._stream.destroy&&this._stream.destroy())};EA.prototype._encode=function(t){if(!t.pax){var e=MN.encode(t);if(e){this.push(e);return}}this._encodePax(t)};EA.prototype._encodePax=function(t){var e=MN.encodePax({name:t.name,linkname:t.linkname,pax:t.pax}),r={name:"PaxHeader",mode:t.mode,uid:t.uid,gid:t.gid,size:e.length,mtime:t.mtime,type:"pax-header",linkname:t.linkname&&"PaxHeader",uname:t.uname,gname:t.gname,devmajor:t.devmajor,devminor:t.devminor};this.push(MN.encode(r)),this.push(e),i7(this,e.length),r.size=t.size,r.type=t.type,this.push(MN.encode(r))};EA.prototype._read=function(t){var e=this._drain;this._drain=s7,e()};XQe.exports=EA});var eTe=L(o7=>{o7.extract=YQe();o7.pack=$Qe()});var pTe=L(Ra=>{"use strict";var POt=Ra&&Ra.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Ra,"__esModule",{value:!0});Ra.Minipass=Ra.isWritable=Ra.isReadable=Ra.isStream=void 0;var lTe=typeof process=="object"&&process?process:{stdout:null,stderr:null},y7=ye("node:events"),ATe=POt(ye("node:stream")),xOt=ye("node:string_decoder"),kOt=t=>!!t&&typeof t=="object"&&(t instanceof zN||t instanceof ATe.default||(0,Ra.isReadable)(t)||(0,Ra.isWritable)(t));Ra.isStream=kOt;var QOt=t=>!!t&&typeof t=="object"&&t instanceof y7.EventEmitter&&typeof t.pipe=="function"&&t.pipe!==ATe.default.Writable.prototype.pipe;Ra.isReadable=QOt;var TOt=t=>!!t&&typeof t=="object"&&t instanceof y7.EventEmitter&&typeof t.write=="function"&&typeof t.end=="function";Ra.isWritable=TOt;var ph=Symbol("EOF"),hh=Symbol("maybeEmitEnd"),mg=Symbol("emittedEnd"),GN=Symbol("emittingEnd"),Db=Symbol("emittedError"),WN=Symbol("closed"),cTe=Symbol("read"),YN=Symbol("flush"),uTe=Symbol("flushChunk"),ff=Symbol("encoding"),Ww=Symbol("decoder"),zs=Symbol("flowing"),bb=Symbol("paused"),Yw=Symbol("resume"),Zs=Symbol("buffer"),Ta=Symbol("pipes"),Xs=Symbol("bufferLength"),A7=Symbol("bufferPush"),VN=Symbol("bufferShift"),ia=Symbol("objectMode"),ts=Symbol("destroyed"),p7=Symbol("error"),h7=Symbol("emitData"),fTe=Symbol("emitEnd"),g7=Symbol("emitEnd2"),CA=Symbol("async"),d7=Symbol("abort"),KN=Symbol("aborted"),Pb=Symbol("signal"),Zm=Symbol("dataListeners"),nc=Symbol("discarded"),xb=t=>Promise.resolve().then(t),ROt=t=>t(),FOt=t=>t==="end"||t==="finish"||t==="prefinish",NOt=t=>t instanceof ArrayBuffer||!!t&&typeof t=="object"&&t.constructor&&t.constructor.name==="ArrayBuffer"&&t.byteLength>=0,OOt=t=>!Buffer.isBuffer(t)&&ArrayBuffer.isView(t),JN=class{src;dest;opts;ondrain;constructor(e,r,s){this.src=e,this.dest=r,this.opts=s,this.ondrain=()=>e[Yw](),this.dest.on("drain",this.ondrain)}unpipe(){this.dest.removeListener("drain",this.ondrain)}proxyErrors(e){}end(){this.unpipe(),this.opts.end&&this.dest.end()}},m7=class extends JN{unpipe(){this.src.removeListener("error",this.proxyErrors),super.unpipe()}constructor(e,r,s){super(e,r,s),this.proxyErrors=a=>r.emit("error",a),e.on("error",this.proxyErrors)}},LOt=t=>!!t.objectMode,MOt=t=>!t.objectMode&&!!t.encoding&&t.encoding!=="buffer",zN=class extends y7.EventEmitter{[zs]=!1;[bb]=!1;[Ta]=[];[Zs]=[];[ia];[ff];[CA];[Ww];[ph]=!1;[mg]=!1;[GN]=!1;[WN]=!1;[Db]=null;[Xs]=0;[ts]=!1;[Pb];[KN]=!1;[Zm]=0;[nc]=!1;writable=!0;readable=!0;constructor(...e){let r=e[0]||{};if(super(),r.objectMode&&typeof r.encoding=="string")throw new TypeError("Encoding and objectMode may not be used together");LOt(r)?(this[ia]=!0,this[ff]=null):MOt(r)?(this[ff]=r.encoding,this[ia]=!1):(this[ia]=!1,this[ff]=null),this[CA]=!!r.async,this[Ww]=this[ff]?new xOt.StringDecoder(this[ff]):null,r&&r.debugExposeBuffer===!0&&Object.defineProperty(this,"buffer",{get:()=>this[Zs]}),r&&r.debugExposePipes===!0&&Object.defineProperty(this,"pipes",{get:()=>this[Ta]});let{signal:s}=r;s&&(this[Pb]=s,s.aborted?this[d7]():s.addEventListener("abort",()=>this[d7]()))}get bufferLength(){return this[Xs]}get encoding(){return this[ff]}set encoding(e){throw new Error("Encoding must be set at instantiation time")}setEncoding(e){throw new Error("Encoding must be set at instantiation time")}get objectMode(){return this[ia]}set objectMode(e){throw new Error("objectMode must be set at instantiation time")}get async(){return this[CA]}set async(e){this[CA]=this[CA]||!!e}[d7](){this[KN]=!0,this.emit("abort",this[Pb]?.reason),this.destroy(this[Pb]?.reason)}get aborted(){return this[KN]}set aborted(e){}write(e,r,s){if(this[KN])return!1;if(this[ph])throw new Error("write after end");if(this[ts])return this.emit("error",Object.assign(new Error("Cannot call write after a stream was destroyed"),{code:"ERR_STREAM_DESTROYED"})),!0;typeof r=="function"&&(s=r,r="utf8"),r||(r="utf8");let a=this[CA]?xb:ROt;if(!this[ia]&&!Buffer.isBuffer(e)){if(OOt(e))e=Buffer.from(e.buffer,e.byteOffset,e.byteLength);else if(NOt(e))e=Buffer.from(e);else if(typeof e!="string")throw new Error("Non-contiguous data written to non-objectMode stream")}return this[ia]?(this[zs]&&this[Xs]!==0&&this[YN](!0),this[zs]?this.emit("data",e):this[A7](e),this[Xs]!==0&&this.emit("readable"),s&&a(s),this[zs]):e.length?(typeof e=="string"&&!(r===this[ff]&&!this[Ww]?.lastNeed)&&(e=Buffer.from(e,r)),Buffer.isBuffer(e)&&this[ff]&&(e=this[Ww].write(e)),this[zs]&&this[Xs]!==0&&this[YN](!0),this[zs]?this.emit("data",e):this[A7](e),this[Xs]!==0&&this.emit("readable"),s&&a(s),this[zs]):(this[Xs]!==0&&this.emit("readable"),s&&a(s),this[zs])}read(e){if(this[ts])return null;if(this[nc]=!1,this[Xs]===0||e===0||e&&e>this[Xs])return this[hh](),null;this[ia]&&(e=null),this[Zs].length>1&&!this[ia]&&(this[Zs]=[this[ff]?this[Zs].join(""):Buffer.concat(this[Zs],this[Xs])]);let r=this[cTe](e||null,this[Zs][0]);return this[hh](),r}[cTe](e,r){if(this[ia])this[VN]();else{let s=r;e===s.length||e===null?this[VN]():typeof s=="string"?(this[Zs][0]=s.slice(e),r=s.slice(0,e),this[Xs]-=e):(this[Zs][0]=s.subarray(e),r=s.subarray(0,e),this[Xs]-=e)}return this.emit("data",r),!this[Zs].length&&!this[ph]&&this.emit("drain"),r}end(e,r,s){return typeof e=="function"&&(s=e,e=void 0),typeof r=="function"&&(s=r,r="utf8"),e!==void 0&&this.write(e,r),s&&this.once("end",s),this[ph]=!0,this.writable=!1,(this[zs]||!this[bb])&&this[hh](),this}[Yw](){this[ts]||(!this[Zm]&&!this[Ta].length&&(this[nc]=!0),this[bb]=!1,this[zs]=!0,this.emit("resume"),this[Zs].length?this[YN]():this[ph]?this[hh]():this.emit("drain"))}resume(){return this[Yw]()}pause(){this[zs]=!1,this[bb]=!0,this[nc]=!1}get destroyed(){return this[ts]}get flowing(){return this[zs]}get paused(){return this[bb]}[A7](e){this[ia]?this[Xs]+=1:this[Xs]+=e.length,this[Zs].push(e)}[VN](){return this[ia]?this[Xs]-=1:this[Xs]-=this[Zs][0].length,this[Zs].shift()}[YN](e=!1){do;while(this[uTe](this[VN]())&&this[Zs].length);!e&&!this[Zs].length&&!this[ph]&&this.emit("drain")}[uTe](e){return this.emit("data",e),this[zs]}pipe(e,r){if(this[ts])return e;this[nc]=!1;let s=this[mg];return r=r||{},e===lTe.stdout||e===lTe.stderr?r.end=!1:r.end=r.end!==!1,r.proxyErrors=!!r.proxyErrors,s?r.end&&e.end():(this[Ta].push(r.proxyErrors?new m7(this,e,r):new JN(this,e,r)),this[CA]?xb(()=>this[Yw]()):this[Yw]()),e}unpipe(e){let r=this[Ta].find(s=>s.dest===e);r&&(this[Ta].length===1?(this[zs]&&this[Zm]===0&&(this[zs]=!1),this[Ta]=[]):this[Ta].splice(this[Ta].indexOf(r),1),r.unpipe())}addListener(e,r){return this.on(e,r)}on(e,r){let s=super.on(e,r);if(e==="data")this[nc]=!1,this[Zm]++,!this[Ta].length&&!this[zs]&&this[Yw]();else if(e==="readable"&&this[Xs]!==0)super.emit("readable");else if(FOt(e)&&this[mg])super.emit(e),this.removeAllListeners(e);else if(e==="error"&&this[Db]){let a=r;this[CA]?xb(()=>a.call(this,this[Db])):a.call(this,this[Db])}return s}removeListener(e,r){return this.off(e,r)}off(e,r){let s=super.off(e,r);return e==="data"&&(this[Zm]=this.listeners("data").length,this[Zm]===0&&!this[nc]&&!this[Ta].length&&(this[zs]=!1)),s}removeAllListeners(e){let r=super.removeAllListeners(e);return(e==="data"||e===void 0)&&(this[Zm]=0,!this[nc]&&!this[Ta].length&&(this[zs]=!1)),r}get emittedEnd(){return this[mg]}[hh](){!this[GN]&&!this[mg]&&!this[ts]&&this[Zs].length===0&&this[ph]&&(this[GN]=!0,this.emit("end"),this.emit("prefinish"),this.emit("finish"),this[WN]&&this.emit("close"),this[GN]=!1)}emit(e,...r){let s=r[0];if(e!=="error"&&e!=="close"&&e!==ts&&this[ts])return!1;if(e==="data")return!this[ia]&&!s?!1:this[CA]?(xb(()=>this[h7](s)),!0):this[h7](s);if(e==="end")return this[fTe]();if(e==="close"){if(this[WN]=!0,!this[mg]&&!this[ts])return!1;let n=super.emit("close");return this.removeAllListeners("close"),n}else if(e==="error"){this[Db]=s,super.emit(p7,s);let n=!this[Pb]||this.listeners("error").length?super.emit("error",s):!1;return this[hh](),n}else if(e==="resume"){let n=super.emit("resume");return this[hh](),n}else if(e==="finish"||e==="prefinish"){let n=super.emit(e);return this.removeAllListeners(e),n}let a=super.emit(e,...r);return this[hh](),a}[h7](e){for(let s of this[Ta])s.dest.write(e)===!1&&this.pause();let r=this[nc]?!1:super.emit("data",e);return this[hh](),r}[fTe](){return this[mg]?!1:(this[mg]=!0,this.readable=!1,this[CA]?(xb(()=>this[g7]()),!0):this[g7]())}[g7](){if(this[Ww]){let r=this[Ww].end();if(r){for(let s of this[Ta])s.dest.write(r);this[nc]||super.emit("data",r)}}for(let r of this[Ta])r.end();let e=super.emit("end");return this.removeAllListeners("end"),e}async collect(){let e=Object.assign([],{dataLength:0});this[ia]||(e.dataLength=0);let r=this.promise();return this.on("data",s=>{e.push(s),this[ia]||(e.dataLength+=s.length)}),await r,e}async concat(){if(this[ia])throw new Error("cannot concat in objectMode");let e=await this.collect();return this[ff]?e.join(""):Buffer.concat(e,e.dataLength)}async promise(){return new Promise((e,r)=>{this.on(ts,()=>r(new Error("stream destroyed"))),this.on("error",s=>r(s)),this.on("end",()=>e())})}[Symbol.asyncIterator](){this[nc]=!1;let e=!1,r=async()=>(this.pause(),e=!0,{value:void 0,done:!0});return{next:()=>{if(e)return r();let a=this.read();if(a!==null)return Promise.resolve({done:!1,value:a});if(this[ph])return r();let n,c,f=C=>{this.off("data",p),this.off("end",h),this.off(ts,E),r(),c(C)},p=C=>{this.off("error",f),this.off("end",h),this.off(ts,E),this.pause(),n({value:C,done:!!this[ph]})},h=()=>{this.off("error",f),this.off("data",p),this.off(ts,E),r(),n({done:!0,value:void 0})},E=()=>f(new Error("stream destroyed"));return new Promise((C,S)=>{c=S,n=C,this.once(ts,E),this.once("error",f),this.once("end",h),this.once("data",p)})},throw:r,return:r,[Symbol.asyncIterator](){return this}}}[Symbol.iterator](){this[nc]=!1;let e=!1,r=()=>(this.pause(),this.off(p7,r),this.off(ts,r),this.off("end",r),e=!0,{done:!0,value:void 0}),s=()=>{if(e)return r();let a=this.read();return a===null?r():{done:!1,value:a}};return this.once("end",r),this.once(p7,r),this.once(ts,r),{next:s,throw:r,return:r,[Symbol.iterator](){return this}}}destroy(e){if(this[ts])return e?this.emit("error",e):this.emit(ts),this;this[ts]=!0,this[nc]=!0,this[Zs].length=0,this[Xs]=0;let r=this;return typeof r.close=="function"&&!this[WN]&&r.close(),e?this.emit("error",e):this.emit(ts),this}static get isStream(){return Ra.isStream}};Ra.Minipass=zN});var dTe=L((YIr,wA)=>{"use strict";var Qb=ye("crypto"),{Minipass:_Ot}=pTe(),I7=["sha512","sha384","sha256"],w7=["sha512"],UOt=/^[a-z0-9+/]+(?:=?=?)$/i,HOt=/^([a-z0-9]+)-([^?]+)([?\S*]*)$/,jOt=/^([a-z0-9]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)?$/,qOt=/^[\x21-\x7E]+$/,Tb=t=>t?.length?`?${t.join("?")}`:"",C7=class extends _Ot{#t;#r;#i;constructor(e){super(),this.size=0,this.opts=e,this.#e(),e?.algorithms?this.algorithms=[...e.algorithms]:this.algorithms=[...w7],this.algorithm!==null&&!this.algorithms.includes(this.algorithm)&&this.algorithms.push(this.algorithm),this.hashes=this.algorithms.map(Qb.createHash)}#e(){this.sri=this.opts?.integrity?ic(this.opts?.integrity,this.opts):null,this.expectedSize=this.opts?.size,this.sri?this.sri.isHash?(this.goodSri=!0,this.algorithm=this.sri.algorithm):(this.goodSri=!this.sri.isEmpty(),this.algorithm=this.sri.pickAlgorithm(this.opts)):this.algorithm=null,this.digests=this.goodSri?this.sri[this.algorithm]:null,this.optString=Tb(this.opts?.options)}on(e,r){return e==="size"&&this.#r?r(this.#r):e==="integrity"&&this.#t?r(this.#t):e==="verified"&&this.#i?r(this.#i):super.on(e,r)}emit(e,r){return e==="end"&&this.#n(),super.emit(e,r)}write(e){return this.size+=e.length,this.hashes.forEach(r=>r.update(e)),super.write(e)}#n(){this.goodSri||this.#e();let e=ic(this.hashes.map((s,a)=>`${this.algorithms[a]}-${s.digest("base64")}${this.optString}`).join(" "),this.opts),r=this.goodSri&&e.match(this.sri,this.opts);if(typeof this.expectedSize=="number"&&this.size!==this.expectedSize){let s=new Error(`stream size mismatch when checking ${this.sri}. + Wanted: ${this.expectedSize} + Found: ${this.size}`);s.code="EBADSIZE",s.found=this.size,s.expected=this.expectedSize,s.sri=this.sri,this.emit("error",s)}else if(this.sri&&!r){let s=new Error(`${this.sri} integrity checksum failed when using ${this.algorithm}: wanted ${this.digests} but got ${e}. (${this.size} bytes)`);s.code="EINTEGRITY",s.found=e,s.expected=this.digests,s.algorithm=this.algorithm,s.sri=this.sri,this.emit("error",s)}else this.#r=this.size,this.emit("size",this.size),this.#t=e,this.emit("integrity",e),r&&(this.#i=r,this.emit("verified",r))}},gh=class{get isHash(){return!0}constructor(e,r){let s=r?.strict;this.source=e.trim(),this.digest="",this.algorithm="",this.options=[];let a=this.source.match(s?jOt:HOt);if(!a||s&&!I7.includes(a[1]))return;this.algorithm=a[1],this.digest=a[2];let n=a[3];n&&(this.options=n.slice(1).split("?"))}hexDigest(){return this.digest&&Buffer.from(this.digest,"base64").toString("hex")}toJSON(){return this.toString()}match(e,r){let s=ic(e,r);if(!s)return!1;if(s.isIntegrity){let a=s.pickAlgorithm(r,[this.algorithm]);if(!a)return!1;let n=s[a].find(c=>c.digest===this.digest);return n||!1}return s.digest===this.digest?s:!1}toString(e){return e?.strict&&!(I7.includes(this.algorithm)&&this.digest.match(UOt)&&this.options.every(r=>r.match(qOt)))?"":`${this.algorithm}-${this.digest}${Tb(this.options)}`}};function hTe(t,e,r,s){let a=t!=="",n=!1,c="",f=s.length-1;for(let h=0;hs[a].find(c=>n.digest===c.digest)))throw new Error("hashes do not match, cannot update integrity")}else this[a]=s[a]}match(e,r){let s=ic(e,r);if(!s)return!1;let a=s.pickAlgorithm(r,Object.keys(this));return!!a&&this[a]&&s[a]&&this[a].find(n=>s[a].find(c=>n.digest===c.digest))||!1}pickAlgorithm(e,r){let s=e?.pickAlgorithm||ZOt,a=Object.keys(this).filter(n=>r?.length?r.includes(n):!0);return a.length?a.reduce((n,c)=>s(n,c)||n):null}};wA.exports.parse=ic;function ic(t,e){if(!t)return null;if(typeof t=="string")return E7(t,e);if(t.algorithm&&t.digest){let r=new Xm;return r[t.algorithm]=[t],E7(kb(r,e),e)}else return E7(kb(t,e),e)}function E7(t,e){if(e?.single)return new gh(t,e);let r=t.trim().split(/\s+/).reduce((s,a)=>{let n=new gh(a,e);if(n.algorithm&&n.digest){let c=n.algorithm;s[c]||(s[c]=[]),s[c].push(n)}return s},new Xm);return r.isEmpty()?null:r}wA.exports.stringify=kb;function kb(t,e){return t.algorithm&&t.digest?gh.prototype.toString.call(t,e):typeof t=="string"?kb(ic(t,e),e):Xm.prototype.toString.call(t,e)}wA.exports.fromHex=GOt;function GOt(t,e,r){let s=Tb(r?.options);return ic(`${e}-${Buffer.from(t,"hex").toString("base64")}${s}`,r)}wA.exports.fromData=WOt;function WOt(t,e){let r=e?.algorithms||[...w7],s=Tb(e?.options);return r.reduce((a,n)=>{let c=Qb.createHash(n).update(t).digest("base64"),f=new gh(`${n}-${c}${s}`,e);if(f.algorithm&&f.digest){let p=f.algorithm;a[p]||(a[p]=[]),a[p].push(f)}return a},new Xm)}wA.exports.fromStream=YOt;function YOt(t,e){let r=B7(e);return new Promise((s,a)=>{t.pipe(r),t.on("error",a),r.on("error",a);let n;r.on("integrity",c=>{n=c}),r.on("end",()=>s(n)),r.resume()})}wA.exports.checkData=VOt;function VOt(t,e,r){if(e=ic(e,r),!e||!Object.keys(e).length){if(r?.error)throw Object.assign(new Error("No valid integrity hashes to check against"),{code:"EINTEGRITY"});return!1}let s=e.pickAlgorithm(r),a=Qb.createHash(s).update(t).digest("base64"),n=ic({algorithm:s,digest:a}),c=n.match(e,r);if(r=r||{},c||!r.error)return c;if(typeof r.size=="number"&&t.length!==r.size){let f=new Error(`data size mismatch when checking ${e}. + Wanted: ${r.size} + Found: ${t.length}`);throw f.code="EBADSIZE",f.found=t.length,f.expected=r.size,f.sri=e,f}else{let f=new Error(`Integrity checksum failed when using ${s}: Wanted ${e}, but got ${n}. (${t.length} bytes)`);throw f.code="EINTEGRITY",f.found=n,f.expected=e,f.algorithm=s,f.sri=e,f}}wA.exports.checkStream=KOt;function KOt(t,e,r){if(r=r||Object.create(null),r.integrity=e,e=ic(e,r),!e||!Object.keys(e).length)return Promise.reject(Object.assign(new Error("No valid integrity hashes to check against"),{code:"EINTEGRITY"}));let s=B7(r);return new Promise((a,n)=>{t.pipe(s),t.on("error",n),s.on("error",n);let c;s.on("verified",f=>{c=f}),s.on("end",()=>a(c)),s.resume()})}wA.exports.integrityStream=B7;function B7(t=Object.create(null)){return new C7(t)}wA.exports.create=JOt;function JOt(t){let e=t?.algorithms||[...w7],r=Tb(t?.options),s=e.map(Qb.createHash);return{update:function(a,n){return s.forEach(c=>c.update(a,n)),this},digest:function(){return e.reduce((n,c)=>{let f=s.shift().digest("base64"),p=new gh(`${c}-${f}${r}`,t);if(p.algorithm&&p.digest){let h=p.algorithm;n[h]||(n[h]=[]),n[h].push(p)}return n},new Xm)}}}var zOt=Qb.getHashes(),gTe=["md5","whirlpool","sha1","sha224","sha256","sha384","sha512","sha3","sha3-256","sha3-384","sha3-512","sha3_256","sha3_384","sha3_512"].filter(t=>zOt.includes(t));function ZOt(t,e){return gTe.indexOf(t.toLowerCase())>=gTe.indexOf(e.toLowerCase())?t:e}});var v7=L(yg=>{"use strict";Object.defineProperty(yg,"__esModule",{value:!0});yg.Signature=yg.Envelope=void 0;yg.Envelope={fromJSON(t){return{payload:ZN(t.payload)?Buffer.from(mTe(t.payload)):Buffer.alloc(0),payloadType:ZN(t.payloadType)?globalThis.String(t.payloadType):"",signatures:globalThis.Array.isArray(t?.signatures)?t.signatures.map(e=>yg.Signature.fromJSON(e)):[]}},toJSON(t){let e={};return t.payload.length!==0&&(e.payload=yTe(t.payload)),t.payloadType!==""&&(e.payloadType=t.payloadType),t.signatures?.length&&(e.signatures=t.signatures.map(r=>yg.Signature.toJSON(r))),e}};yg.Signature={fromJSON(t){return{sig:ZN(t.sig)?Buffer.from(mTe(t.sig)):Buffer.alloc(0),keyid:ZN(t.keyid)?globalThis.String(t.keyid):""}},toJSON(t){let e={};return t.sig.length!==0&&(e.sig=yTe(t.sig)),t.keyid!==""&&(e.keyid=t.keyid),e}};function mTe(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function yTe(t){return globalThis.Buffer.from(t).toString("base64")}function ZN(t){return t!=null}});var ITe=L(XN=>{"use strict";Object.defineProperty(XN,"__esModule",{value:!0});XN.Timestamp=void 0;XN.Timestamp={fromJSON(t){return{seconds:ETe(t.seconds)?globalThis.String(t.seconds):"0",nanos:ETe(t.nanos)?globalThis.Number(t.nanos):0}},toJSON(t){let e={};return t.seconds!=="0"&&(e.seconds=t.seconds),t.nanos!==0&&(e.nanos=Math.round(t.nanos)),e}};function ETe(t){return t!=null}});var Vw=L(_r=>{"use strict";Object.defineProperty(_r,"__esModule",{value:!0});_r.TimeRange=_r.X509CertificateChain=_r.SubjectAlternativeName=_r.X509Certificate=_r.DistinguishedName=_r.ObjectIdentifierValuePair=_r.ObjectIdentifier=_r.PublicKeyIdentifier=_r.PublicKey=_r.RFC3161SignedTimestamp=_r.LogId=_r.MessageSignature=_r.HashOutput=_r.SubjectAlternativeNameType=_r.PublicKeyDetails=_r.HashAlgorithm=void 0;_r.hashAlgorithmFromJSON=wTe;_r.hashAlgorithmToJSON=BTe;_r.publicKeyDetailsFromJSON=vTe;_r.publicKeyDetailsToJSON=STe;_r.subjectAlternativeNameTypeFromJSON=DTe;_r.subjectAlternativeNameTypeToJSON=bTe;var XOt=ITe(),El;(function(t){t[t.HASH_ALGORITHM_UNSPECIFIED=0]="HASH_ALGORITHM_UNSPECIFIED",t[t.SHA2_256=1]="SHA2_256",t[t.SHA2_384=2]="SHA2_384",t[t.SHA2_512=3]="SHA2_512",t[t.SHA3_256=4]="SHA3_256",t[t.SHA3_384=5]="SHA3_384"})(El||(_r.HashAlgorithm=El={}));function wTe(t){switch(t){case 0:case"HASH_ALGORITHM_UNSPECIFIED":return El.HASH_ALGORITHM_UNSPECIFIED;case 1:case"SHA2_256":return El.SHA2_256;case 2:case"SHA2_384":return El.SHA2_384;case 3:case"SHA2_512":return El.SHA2_512;case 4:case"SHA3_256":return El.SHA3_256;case 5:case"SHA3_384":return El.SHA3_384;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum HashAlgorithm")}}function BTe(t){switch(t){case El.HASH_ALGORITHM_UNSPECIFIED:return"HASH_ALGORITHM_UNSPECIFIED";case El.SHA2_256:return"SHA2_256";case El.SHA2_384:return"SHA2_384";case El.SHA2_512:return"SHA2_512";case El.SHA3_256:return"SHA3_256";case El.SHA3_384:return"SHA3_384";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum HashAlgorithm")}}var rn;(function(t){t[t.PUBLIC_KEY_DETAILS_UNSPECIFIED=0]="PUBLIC_KEY_DETAILS_UNSPECIFIED",t[t.PKCS1_RSA_PKCS1V5=1]="PKCS1_RSA_PKCS1V5",t[t.PKCS1_RSA_PSS=2]="PKCS1_RSA_PSS",t[t.PKIX_RSA_PKCS1V5=3]="PKIX_RSA_PKCS1V5",t[t.PKIX_RSA_PSS=4]="PKIX_RSA_PSS",t[t.PKIX_RSA_PKCS1V15_2048_SHA256=9]="PKIX_RSA_PKCS1V15_2048_SHA256",t[t.PKIX_RSA_PKCS1V15_3072_SHA256=10]="PKIX_RSA_PKCS1V15_3072_SHA256",t[t.PKIX_RSA_PKCS1V15_4096_SHA256=11]="PKIX_RSA_PKCS1V15_4096_SHA256",t[t.PKIX_RSA_PSS_2048_SHA256=16]="PKIX_RSA_PSS_2048_SHA256",t[t.PKIX_RSA_PSS_3072_SHA256=17]="PKIX_RSA_PSS_3072_SHA256",t[t.PKIX_RSA_PSS_4096_SHA256=18]="PKIX_RSA_PSS_4096_SHA256",t[t.PKIX_ECDSA_P256_HMAC_SHA_256=6]="PKIX_ECDSA_P256_HMAC_SHA_256",t[t.PKIX_ECDSA_P256_SHA_256=5]="PKIX_ECDSA_P256_SHA_256",t[t.PKIX_ECDSA_P384_SHA_384=12]="PKIX_ECDSA_P384_SHA_384",t[t.PKIX_ECDSA_P521_SHA_512=13]="PKIX_ECDSA_P521_SHA_512",t[t.PKIX_ED25519=7]="PKIX_ED25519",t[t.PKIX_ED25519_PH=8]="PKIX_ED25519_PH",t[t.LMS_SHA256=14]="LMS_SHA256",t[t.LMOTS_SHA256=15]="LMOTS_SHA256"})(rn||(_r.PublicKeyDetails=rn={}));function vTe(t){switch(t){case 0:case"PUBLIC_KEY_DETAILS_UNSPECIFIED":return rn.PUBLIC_KEY_DETAILS_UNSPECIFIED;case 1:case"PKCS1_RSA_PKCS1V5":return rn.PKCS1_RSA_PKCS1V5;case 2:case"PKCS1_RSA_PSS":return rn.PKCS1_RSA_PSS;case 3:case"PKIX_RSA_PKCS1V5":return rn.PKIX_RSA_PKCS1V5;case 4:case"PKIX_RSA_PSS":return rn.PKIX_RSA_PSS;case 9:case"PKIX_RSA_PKCS1V15_2048_SHA256":return rn.PKIX_RSA_PKCS1V15_2048_SHA256;case 10:case"PKIX_RSA_PKCS1V15_3072_SHA256":return rn.PKIX_RSA_PKCS1V15_3072_SHA256;case 11:case"PKIX_RSA_PKCS1V15_4096_SHA256":return rn.PKIX_RSA_PKCS1V15_4096_SHA256;case 16:case"PKIX_RSA_PSS_2048_SHA256":return rn.PKIX_RSA_PSS_2048_SHA256;case 17:case"PKIX_RSA_PSS_3072_SHA256":return rn.PKIX_RSA_PSS_3072_SHA256;case 18:case"PKIX_RSA_PSS_4096_SHA256":return rn.PKIX_RSA_PSS_4096_SHA256;case 6:case"PKIX_ECDSA_P256_HMAC_SHA_256":return rn.PKIX_ECDSA_P256_HMAC_SHA_256;case 5:case"PKIX_ECDSA_P256_SHA_256":return rn.PKIX_ECDSA_P256_SHA_256;case 12:case"PKIX_ECDSA_P384_SHA_384":return rn.PKIX_ECDSA_P384_SHA_384;case 13:case"PKIX_ECDSA_P521_SHA_512":return rn.PKIX_ECDSA_P521_SHA_512;case 7:case"PKIX_ED25519":return rn.PKIX_ED25519;case 8:case"PKIX_ED25519_PH":return rn.PKIX_ED25519_PH;case 14:case"LMS_SHA256":return rn.LMS_SHA256;case 15:case"LMOTS_SHA256":return rn.LMOTS_SHA256;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum PublicKeyDetails")}}function STe(t){switch(t){case rn.PUBLIC_KEY_DETAILS_UNSPECIFIED:return"PUBLIC_KEY_DETAILS_UNSPECIFIED";case rn.PKCS1_RSA_PKCS1V5:return"PKCS1_RSA_PKCS1V5";case rn.PKCS1_RSA_PSS:return"PKCS1_RSA_PSS";case rn.PKIX_RSA_PKCS1V5:return"PKIX_RSA_PKCS1V5";case rn.PKIX_RSA_PSS:return"PKIX_RSA_PSS";case rn.PKIX_RSA_PKCS1V15_2048_SHA256:return"PKIX_RSA_PKCS1V15_2048_SHA256";case rn.PKIX_RSA_PKCS1V15_3072_SHA256:return"PKIX_RSA_PKCS1V15_3072_SHA256";case rn.PKIX_RSA_PKCS1V15_4096_SHA256:return"PKIX_RSA_PKCS1V15_4096_SHA256";case rn.PKIX_RSA_PSS_2048_SHA256:return"PKIX_RSA_PSS_2048_SHA256";case rn.PKIX_RSA_PSS_3072_SHA256:return"PKIX_RSA_PSS_3072_SHA256";case rn.PKIX_RSA_PSS_4096_SHA256:return"PKIX_RSA_PSS_4096_SHA256";case rn.PKIX_ECDSA_P256_HMAC_SHA_256:return"PKIX_ECDSA_P256_HMAC_SHA_256";case rn.PKIX_ECDSA_P256_SHA_256:return"PKIX_ECDSA_P256_SHA_256";case rn.PKIX_ECDSA_P384_SHA_384:return"PKIX_ECDSA_P384_SHA_384";case rn.PKIX_ECDSA_P521_SHA_512:return"PKIX_ECDSA_P521_SHA_512";case rn.PKIX_ED25519:return"PKIX_ED25519";case rn.PKIX_ED25519_PH:return"PKIX_ED25519_PH";case rn.LMS_SHA256:return"LMS_SHA256";case rn.LMOTS_SHA256:return"LMOTS_SHA256";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum PublicKeyDetails")}}var BA;(function(t){t[t.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED=0]="SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED",t[t.EMAIL=1]="EMAIL",t[t.URI=2]="URI",t[t.OTHER_NAME=3]="OTHER_NAME"})(BA||(_r.SubjectAlternativeNameType=BA={}));function DTe(t){switch(t){case 0:case"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED":return BA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED;case 1:case"EMAIL":return BA.EMAIL;case 2:case"URI":return BA.URI;case 3:case"OTHER_NAME":return BA.OTHER_NAME;default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum SubjectAlternativeNameType")}}function bTe(t){switch(t){case BA.SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED:return"SUBJECT_ALTERNATIVE_NAME_TYPE_UNSPECIFIED";case BA.EMAIL:return"EMAIL";case BA.URI:return"URI";case BA.OTHER_NAME:return"OTHER_NAME";default:throw new globalThis.Error("Unrecognized enum value "+t+" for enum SubjectAlternativeNameType")}}_r.HashOutput={fromJSON(t){return{algorithm:ms(t.algorithm)?wTe(t.algorithm):0,digest:ms(t.digest)?Buffer.from($m(t.digest)):Buffer.alloc(0)}},toJSON(t){let e={};return t.algorithm!==0&&(e.algorithm=BTe(t.algorithm)),t.digest.length!==0&&(e.digest=ey(t.digest)),e}};_r.MessageSignature={fromJSON(t){return{messageDigest:ms(t.messageDigest)?_r.HashOutput.fromJSON(t.messageDigest):void 0,signature:ms(t.signature)?Buffer.from($m(t.signature)):Buffer.alloc(0)}},toJSON(t){let e={};return t.messageDigest!==void 0&&(e.messageDigest=_r.HashOutput.toJSON(t.messageDigest)),t.signature.length!==0&&(e.signature=ey(t.signature)),e}};_r.LogId={fromJSON(t){return{keyId:ms(t.keyId)?Buffer.from($m(t.keyId)):Buffer.alloc(0)}},toJSON(t){let e={};return t.keyId.length!==0&&(e.keyId=ey(t.keyId)),e}};_r.RFC3161SignedTimestamp={fromJSON(t){return{signedTimestamp:ms(t.signedTimestamp)?Buffer.from($m(t.signedTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedTimestamp.length!==0&&(e.signedTimestamp=ey(t.signedTimestamp)),e}};_r.PublicKey={fromJSON(t){return{rawBytes:ms(t.rawBytes)?Buffer.from($m(t.rawBytes)):void 0,keyDetails:ms(t.keyDetails)?vTe(t.keyDetails):0,validFor:ms(t.validFor)?_r.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.rawBytes!==void 0&&(e.rawBytes=ey(t.rawBytes)),t.keyDetails!==0&&(e.keyDetails=STe(t.keyDetails)),t.validFor!==void 0&&(e.validFor=_r.TimeRange.toJSON(t.validFor)),e}};_r.PublicKeyIdentifier={fromJSON(t){return{hint:ms(t.hint)?globalThis.String(t.hint):""}},toJSON(t){let e={};return t.hint!==""&&(e.hint=t.hint),e}};_r.ObjectIdentifier={fromJSON(t){return{id:globalThis.Array.isArray(t?.id)?t.id.map(e=>globalThis.Number(e)):[]}},toJSON(t){let e={};return t.id?.length&&(e.id=t.id.map(r=>Math.round(r))),e}};_r.ObjectIdentifierValuePair={fromJSON(t){return{oid:ms(t.oid)?_r.ObjectIdentifier.fromJSON(t.oid):void 0,value:ms(t.value)?Buffer.from($m(t.value)):Buffer.alloc(0)}},toJSON(t){let e={};return t.oid!==void 0&&(e.oid=_r.ObjectIdentifier.toJSON(t.oid)),t.value.length!==0&&(e.value=ey(t.value)),e}};_r.DistinguishedName={fromJSON(t){return{organization:ms(t.organization)?globalThis.String(t.organization):"",commonName:ms(t.commonName)?globalThis.String(t.commonName):""}},toJSON(t){let e={};return t.organization!==""&&(e.organization=t.organization),t.commonName!==""&&(e.commonName=t.commonName),e}};_r.X509Certificate={fromJSON(t){return{rawBytes:ms(t.rawBytes)?Buffer.from($m(t.rawBytes)):Buffer.alloc(0)}},toJSON(t){let e={};return t.rawBytes.length!==0&&(e.rawBytes=ey(t.rawBytes)),e}};_r.SubjectAlternativeName={fromJSON(t){return{type:ms(t.type)?DTe(t.type):0,identity:ms(t.regexp)?{$case:"regexp",regexp:globalThis.String(t.regexp)}:ms(t.value)?{$case:"value",value:globalThis.String(t.value)}:void 0}},toJSON(t){let e={};return t.type!==0&&(e.type=bTe(t.type)),t.identity?.$case==="regexp"?e.regexp=t.identity.regexp:t.identity?.$case==="value"&&(e.value=t.identity.value),e}};_r.X509CertificateChain={fromJSON(t){return{certificates:globalThis.Array.isArray(t?.certificates)?t.certificates.map(e=>_r.X509Certificate.fromJSON(e)):[]}},toJSON(t){let e={};return t.certificates?.length&&(e.certificates=t.certificates.map(r=>_r.X509Certificate.toJSON(r))),e}};_r.TimeRange={fromJSON(t){return{start:ms(t.start)?CTe(t.start):void 0,end:ms(t.end)?CTe(t.end):void 0}},toJSON(t){let e={};return t.start!==void 0&&(e.start=t.start.toISOString()),t.end!==void 0&&(e.end=t.end.toISOString()),e}};function $m(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function ey(t){return globalThis.Buffer.from(t).toString("base64")}function $Ot(t){let e=(globalThis.Number(t.seconds)||0)*1e3;return e+=(t.nanos||0)/1e6,new globalThis.Date(e)}function CTe(t){return t instanceof globalThis.Date?t:typeof t=="string"?new globalThis.Date(t):$Ot(XOt.Timestamp.fromJSON(t))}function ms(t){return t!=null}});var S7=L(ys=>{"use strict";Object.defineProperty(ys,"__esModule",{value:!0});ys.TransparencyLogEntry=ys.InclusionPromise=ys.InclusionProof=ys.Checkpoint=ys.KindVersion=void 0;var PTe=Vw();ys.KindVersion={fromJSON(t){return{kind:Fa(t.kind)?globalThis.String(t.kind):"",version:Fa(t.version)?globalThis.String(t.version):""}},toJSON(t){let e={};return t.kind!==""&&(e.kind=t.kind),t.version!==""&&(e.version=t.version),e}};ys.Checkpoint={fromJSON(t){return{envelope:Fa(t.envelope)?globalThis.String(t.envelope):""}},toJSON(t){let e={};return t.envelope!==""&&(e.envelope=t.envelope),e}};ys.InclusionProof={fromJSON(t){return{logIndex:Fa(t.logIndex)?globalThis.String(t.logIndex):"0",rootHash:Fa(t.rootHash)?Buffer.from($N(t.rootHash)):Buffer.alloc(0),treeSize:Fa(t.treeSize)?globalThis.String(t.treeSize):"0",hashes:globalThis.Array.isArray(t?.hashes)?t.hashes.map(e=>Buffer.from($N(e))):[],checkpoint:Fa(t.checkpoint)?ys.Checkpoint.fromJSON(t.checkpoint):void 0}},toJSON(t){let e={};return t.logIndex!=="0"&&(e.logIndex=t.logIndex),t.rootHash.length!==0&&(e.rootHash=eO(t.rootHash)),t.treeSize!=="0"&&(e.treeSize=t.treeSize),t.hashes?.length&&(e.hashes=t.hashes.map(r=>eO(r))),t.checkpoint!==void 0&&(e.checkpoint=ys.Checkpoint.toJSON(t.checkpoint)),e}};ys.InclusionPromise={fromJSON(t){return{signedEntryTimestamp:Fa(t.signedEntryTimestamp)?Buffer.from($N(t.signedEntryTimestamp)):Buffer.alloc(0)}},toJSON(t){let e={};return t.signedEntryTimestamp.length!==0&&(e.signedEntryTimestamp=eO(t.signedEntryTimestamp)),e}};ys.TransparencyLogEntry={fromJSON(t){return{logIndex:Fa(t.logIndex)?globalThis.String(t.logIndex):"0",logId:Fa(t.logId)?PTe.LogId.fromJSON(t.logId):void 0,kindVersion:Fa(t.kindVersion)?ys.KindVersion.fromJSON(t.kindVersion):void 0,integratedTime:Fa(t.integratedTime)?globalThis.String(t.integratedTime):"0",inclusionPromise:Fa(t.inclusionPromise)?ys.InclusionPromise.fromJSON(t.inclusionPromise):void 0,inclusionProof:Fa(t.inclusionProof)?ys.InclusionProof.fromJSON(t.inclusionProof):void 0,canonicalizedBody:Fa(t.canonicalizedBody)?Buffer.from($N(t.canonicalizedBody)):Buffer.alloc(0)}},toJSON(t){let e={};return t.logIndex!=="0"&&(e.logIndex=t.logIndex),t.logId!==void 0&&(e.logId=PTe.LogId.toJSON(t.logId)),t.kindVersion!==void 0&&(e.kindVersion=ys.KindVersion.toJSON(t.kindVersion)),t.integratedTime!=="0"&&(e.integratedTime=t.integratedTime),t.inclusionPromise!==void 0&&(e.inclusionPromise=ys.InclusionPromise.toJSON(t.inclusionPromise)),t.inclusionProof!==void 0&&(e.inclusionProof=ys.InclusionProof.toJSON(t.inclusionProof)),t.canonicalizedBody.length!==0&&(e.canonicalizedBody=eO(t.canonicalizedBody)),e}};function $N(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function eO(t){return globalThis.Buffer.from(t).toString("base64")}function Fa(t){return t!=null}});var D7=L($c=>{"use strict";Object.defineProperty($c,"__esModule",{value:!0});$c.Bundle=$c.VerificationMaterial=$c.TimestampVerificationData=void 0;var xTe=v7(),vA=Vw(),kTe=S7();$c.TimestampVerificationData={fromJSON(t){return{rfc3161Timestamps:globalThis.Array.isArray(t?.rfc3161Timestamps)?t.rfc3161Timestamps.map(e=>vA.RFC3161SignedTimestamp.fromJSON(e)):[]}},toJSON(t){let e={};return t.rfc3161Timestamps?.length&&(e.rfc3161Timestamps=t.rfc3161Timestamps.map(r=>vA.RFC3161SignedTimestamp.toJSON(r))),e}};$c.VerificationMaterial={fromJSON(t){return{content:Eg(t.publicKey)?{$case:"publicKey",publicKey:vA.PublicKeyIdentifier.fromJSON(t.publicKey)}:Eg(t.x509CertificateChain)?{$case:"x509CertificateChain",x509CertificateChain:vA.X509CertificateChain.fromJSON(t.x509CertificateChain)}:Eg(t.certificate)?{$case:"certificate",certificate:vA.X509Certificate.fromJSON(t.certificate)}:void 0,tlogEntries:globalThis.Array.isArray(t?.tlogEntries)?t.tlogEntries.map(e=>kTe.TransparencyLogEntry.fromJSON(e)):[],timestampVerificationData:Eg(t.timestampVerificationData)?$c.TimestampVerificationData.fromJSON(t.timestampVerificationData):void 0}},toJSON(t){let e={};return t.content?.$case==="publicKey"?e.publicKey=vA.PublicKeyIdentifier.toJSON(t.content.publicKey):t.content?.$case==="x509CertificateChain"?e.x509CertificateChain=vA.X509CertificateChain.toJSON(t.content.x509CertificateChain):t.content?.$case==="certificate"&&(e.certificate=vA.X509Certificate.toJSON(t.content.certificate)),t.tlogEntries?.length&&(e.tlogEntries=t.tlogEntries.map(r=>kTe.TransparencyLogEntry.toJSON(r))),t.timestampVerificationData!==void 0&&(e.timestampVerificationData=$c.TimestampVerificationData.toJSON(t.timestampVerificationData)),e}};$c.Bundle={fromJSON(t){return{mediaType:Eg(t.mediaType)?globalThis.String(t.mediaType):"",verificationMaterial:Eg(t.verificationMaterial)?$c.VerificationMaterial.fromJSON(t.verificationMaterial):void 0,content:Eg(t.messageSignature)?{$case:"messageSignature",messageSignature:vA.MessageSignature.fromJSON(t.messageSignature)}:Eg(t.dsseEnvelope)?{$case:"dsseEnvelope",dsseEnvelope:xTe.Envelope.fromJSON(t.dsseEnvelope)}:void 0}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.verificationMaterial!==void 0&&(e.verificationMaterial=$c.VerificationMaterial.toJSON(t.verificationMaterial)),t.content?.$case==="messageSignature"?e.messageSignature=vA.MessageSignature.toJSON(t.content.messageSignature):t.content?.$case==="dsseEnvelope"&&(e.dsseEnvelope=xTe.Envelope.toJSON(t.content.dsseEnvelope)),e}};function Eg(t){return t!=null}});var b7=L(Ti=>{"use strict";Object.defineProperty(Ti,"__esModule",{value:!0});Ti.ClientTrustConfig=Ti.SigningConfig=Ti.TrustedRoot=Ti.CertificateAuthority=Ti.TransparencyLogInstance=void 0;var Il=Vw();Ti.TransparencyLogInstance={fromJSON(t){return{baseUrl:sa(t.baseUrl)?globalThis.String(t.baseUrl):"",hashAlgorithm:sa(t.hashAlgorithm)?(0,Il.hashAlgorithmFromJSON)(t.hashAlgorithm):0,publicKey:sa(t.publicKey)?Il.PublicKey.fromJSON(t.publicKey):void 0,logId:sa(t.logId)?Il.LogId.fromJSON(t.logId):void 0,checkpointKeyId:sa(t.checkpointKeyId)?Il.LogId.fromJSON(t.checkpointKeyId):void 0}},toJSON(t){let e={};return t.baseUrl!==""&&(e.baseUrl=t.baseUrl),t.hashAlgorithm!==0&&(e.hashAlgorithm=(0,Il.hashAlgorithmToJSON)(t.hashAlgorithm)),t.publicKey!==void 0&&(e.publicKey=Il.PublicKey.toJSON(t.publicKey)),t.logId!==void 0&&(e.logId=Il.LogId.toJSON(t.logId)),t.checkpointKeyId!==void 0&&(e.checkpointKeyId=Il.LogId.toJSON(t.checkpointKeyId)),e}};Ti.CertificateAuthority={fromJSON(t){return{subject:sa(t.subject)?Il.DistinguishedName.fromJSON(t.subject):void 0,uri:sa(t.uri)?globalThis.String(t.uri):"",certChain:sa(t.certChain)?Il.X509CertificateChain.fromJSON(t.certChain):void 0,validFor:sa(t.validFor)?Il.TimeRange.fromJSON(t.validFor):void 0}},toJSON(t){let e={};return t.subject!==void 0&&(e.subject=Il.DistinguishedName.toJSON(t.subject)),t.uri!==""&&(e.uri=t.uri),t.certChain!==void 0&&(e.certChain=Il.X509CertificateChain.toJSON(t.certChain)),t.validFor!==void 0&&(e.validFor=Il.TimeRange.toJSON(t.validFor)),e}};Ti.TrustedRoot={fromJSON(t){return{mediaType:sa(t.mediaType)?globalThis.String(t.mediaType):"",tlogs:globalThis.Array.isArray(t?.tlogs)?t.tlogs.map(e=>Ti.TransparencyLogInstance.fromJSON(e)):[],certificateAuthorities:globalThis.Array.isArray(t?.certificateAuthorities)?t.certificateAuthorities.map(e=>Ti.CertificateAuthority.fromJSON(e)):[],ctlogs:globalThis.Array.isArray(t?.ctlogs)?t.ctlogs.map(e=>Ti.TransparencyLogInstance.fromJSON(e)):[],timestampAuthorities:globalThis.Array.isArray(t?.timestampAuthorities)?t.timestampAuthorities.map(e=>Ti.CertificateAuthority.fromJSON(e)):[]}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.tlogs?.length&&(e.tlogs=t.tlogs.map(r=>Ti.TransparencyLogInstance.toJSON(r))),t.certificateAuthorities?.length&&(e.certificateAuthorities=t.certificateAuthorities.map(r=>Ti.CertificateAuthority.toJSON(r))),t.ctlogs?.length&&(e.ctlogs=t.ctlogs.map(r=>Ti.TransparencyLogInstance.toJSON(r))),t.timestampAuthorities?.length&&(e.timestampAuthorities=t.timestampAuthorities.map(r=>Ti.CertificateAuthority.toJSON(r))),e}};Ti.SigningConfig={fromJSON(t){return{mediaType:sa(t.mediaType)?globalThis.String(t.mediaType):"",caUrl:sa(t.caUrl)?globalThis.String(t.caUrl):"",oidcUrl:sa(t.oidcUrl)?globalThis.String(t.oidcUrl):"",tlogUrls:globalThis.Array.isArray(t?.tlogUrls)?t.tlogUrls.map(e=>globalThis.String(e)):[],tsaUrls:globalThis.Array.isArray(t?.tsaUrls)?t.tsaUrls.map(e=>globalThis.String(e)):[]}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.caUrl!==""&&(e.caUrl=t.caUrl),t.oidcUrl!==""&&(e.oidcUrl=t.oidcUrl),t.tlogUrls?.length&&(e.tlogUrls=t.tlogUrls),t.tsaUrls?.length&&(e.tsaUrls=t.tsaUrls),e}};Ti.ClientTrustConfig={fromJSON(t){return{mediaType:sa(t.mediaType)?globalThis.String(t.mediaType):"",trustedRoot:sa(t.trustedRoot)?Ti.TrustedRoot.fromJSON(t.trustedRoot):void 0,signingConfig:sa(t.signingConfig)?Ti.SigningConfig.fromJSON(t.signingConfig):void 0}},toJSON(t){let e={};return t.mediaType!==""&&(e.mediaType=t.mediaType),t.trustedRoot!==void 0&&(e.trustedRoot=Ti.TrustedRoot.toJSON(t.trustedRoot)),t.signingConfig!==void 0&&(e.signingConfig=Ti.SigningConfig.toJSON(t.signingConfig)),e}};function sa(t){return t!=null}});var RTe=L(Vr=>{"use strict";Object.defineProperty(Vr,"__esModule",{value:!0});Vr.Input=Vr.Artifact=Vr.ArtifactVerificationOptions_ObserverTimestampOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions=Vr.ArtifactVerificationOptions_CtlogOptions=Vr.ArtifactVerificationOptions_TlogOptions=Vr.ArtifactVerificationOptions=Vr.PublicKeyIdentities=Vr.CertificateIdentities=Vr.CertificateIdentity=void 0;var QTe=D7(),Ig=Vw(),TTe=b7();Vr.CertificateIdentity={fromJSON(t){return{issuer:hi(t.issuer)?globalThis.String(t.issuer):"",san:hi(t.san)?Ig.SubjectAlternativeName.fromJSON(t.san):void 0,oids:globalThis.Array.isArray(t?.oids)?t.oids.map(e=>Ig.ObjectIdentifierValuePair.fromJSON(e)):[]}},toJSON(t){let e={};return t.issuer!==""&&(e.issuer=t.issuer),t.san!==void 0&&(e.san=Ig.SubjectAlternativeName.toJSON(t.san)),t.oids?.length&&(e.oids=t.oids.map(r=>Ig.ObjectIdentifierValuePair.toJSON(r))),e}};Vr.CertificateIdentities={fromJSON(t){return{identities:globalThis.Array.isArray(t?.identities)?t.identities.map(e=>Vr.CertificateIdentity.fromJSON(e)):[]}},toJSON(t){let e={};return t.identities?.length&&(e.identities=t.identities.map(r=>Vr.CertificateIdentity.toJSON(r))),e}};Vr.PublicKeyIdentities={fromJSON(t){return{publicKeys:globalThis.Array.isArray(t?.publicKeys)?t.publicKeys.map(e=>Ig.PublicKey.fromJSON(e)):[]}},toJSON(t){let e={};return t.publicKeys?.length&&(e.publicKeys=t.publicKeys.map(r=>Ig.PublicKey.toJSON(r))),e}};Vr.ArtifactVerificationOptions={fromJSON(t){return{signers:hi(t.certificateIdentities)?{$case:"certificateIdentities",certificateIdentities:Vr.CertificateIdentities.fromJSON(t.certificateIdentities)}:hi(t.publicKeys)?{$case:"publicKeys",publicKeys:Vr.PublicKeyIdentities.fromJSON(t.publicKeys)}:void 0,tlogOptions:hi(t.tlogOptions)?Vr.ArtifactVerificationOptions_TlogOptions.fromJSON(t.tlogOptions):void 0,ctlogOptions:hi(t.ctlogOptions)?Vr.ArtifactVerificationOptions_CtlogOptions.fromJSON(t.ctlogOptions):void 0,tsaOptions:hi(t.tsaOptions)?Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.fromJSON(t.tsaOptions):void 0,integratedTsOptions:hi(t.integratedTsOptions)?Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.fromJSON(t.integratedTsOptions):void 0,observerOptions:hi(t.observerOptions)?Vr.ArtifactVerificationOptions_ObserverTimestampOptions.fromJSON(t.observerOptions):void 0}},toJSON(t){let e={};return t.signers?.$case==="certificateIdentities"?e.certificateIdentities=Vr.CertificateIdentities.toJSON(t.signers.certificateIdentities):t.signers?.$case==="publicKeys"&&(e.publicKeys=Vr.PublicKeyIdentities.toJSON(t.signers.publicKeys)),t.tlogOptions!==void 0&&(e.tlogOptions=Vr.ArtifactVerificationOptions_TlogOptions.toJSON(t.tlogOptions)),t.ctlogOptions!==void 0&&(e.ctlogOptions=Vr.ArtifactVerificationOptions_CtlogOptions.toJSON(t.ctlogOptions)),t.tsaOptions!==void 0&&(e.tsaOptions=Vr.ArtifactVerificationOptions_TimestampAuthorityOptions.toJSON(t.tsaOptions)),t.integratedTsOptions!==void 0&&(e.integratedTsOptions=Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions.toJSON(t.integratedTsOptions)),t.observerOptions!==void 0&&(e.observerOptions=Vr.ArtifactVerificationOptions_ObserverTimestampOptions.toJSON(t.observerOptions)),e}};Vr.ArtifactVerificationOptions_TlogOptions={fromJSON(t){return{threshold:hi(t.threshold)?globalThis.Number(t.threshold):0,performOnlineVerification:hi(t.performOnlineVerification)?globalThis.Boolean(t.performOnlineVerification):!1,disable:hi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.performOnlineVerification!==!1&&(e.performOnlineVerification=t.performOnlineVerification),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_CtlogOptions={fromJSON(t){return{threshold:hi(t.threshold)?globalThis.Number(t.threshold):0,disable:hi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TimestampAuthorityOptions={fromJSON(t){return{threshold:hi(t.threshold)?globalThis.Number(t.threshold):0,disable:hi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_TlogIntegratedTimestampOptions={fromJSON(t){return{threshold:hi(t.threshold)?globalThis.Number(t.threshold):0,disable:hi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.ArtifactVerificationOptions_ObserverTimestampOptions={fromJSON(t){return{threshold:hi(t.threshold)?globalThis.Number(t.threshold):0,disable:hi(t.disable)?globalThis.Boolean(t.disable):!1}},toJSON(t){let e={};return t.threshold!==0&&(e.threshold=Math.round(t.threshold)),t.disable!==!1&&(e.disable=t.disable),e}};Vr.Artifact={fromJSON(t){return{data:hi(t.artifactUri)?{$case:"artifactUri",artifactUri:globalThis.String(t.artifactUri)}:hi(t.artifact)?{$case:"artifact",artifact:Buffer.from(eLt(t.artifact))}:hi(t.artifactDigest)?{$case:"artifactDigest",artifactDigest:Ig.HashOutput.fromJSON(t.artifactDigest)}:void 0}},toJSON(t){let e={};return t.data?.$case==="artifactUri"?e.artifactUri=t.data.artifactUri:t.data?.$case==="artifact"?e.artifact=tLt(t.data.artifact):t.data?.$case==="artifactDigest"&&(e.artifactDigest=Ig.HashOutput.toJSON(t.data.artifactDigest)),e}};Vr.Input={fromJSON(t){return{artifactTrustRoot:hi(t.artifactTrustRoot)?TTe.TrustedRoot.fromJSON(t.artifactTrustRoot):void 0,artifactVerificationOptions:hi(t.artifactVerificationOptions)?Vr.ArtifactVerificationOptions.fromJSON(t.artifactVerificationOptions):void 0,bundle:hi(t.bundle)?QTe.Bundle.fromJSON(t.bundle):void 0,artifact:hi(t.artifact)?Vr.Artifact.fromJSON(t.artifact):void 0}},toJSON(t){let e={};return t.artifactTrustRoot!==void 0&&(e.artifactTrustRoot=TTe.TrustedRoot.toJSON(t.artifactTrustRoot)),t.artifactVerificationOptions!==void 0&&(e.artifactVerificationOptions=Vr.ArtifactVerificationOptions.toJSON(t.artifactVerificationOptions)),t.bundle!==void 0&&(e.bundle=QTe.Bundle.toJSON(t.bundle)),t.artifact!==void 0&&(e.artifact=Vr.Artifact.toJSON(t.artifact)),e}};function eLt(t){return Uint8Array.from(globalThis.Buffer.from(t,"base64"))}function tLt(t){return globalThis.Buffer.from(t).toString("base64")}function hi(t){return t!=null}});var Rb=L(eu=>{"use strict";var rLt=eu&&eu.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),Kw=eu&&eu.__exportStar||function(t,e){for(var r in t)r!=="default"&&!Object.prototype.hasOwnProperty.call(e,r)&&rLt(e,t,r)};Object.defineProperty(eu,"__esModule",{value:!0});Kw(v7(),eu);Kw(D7(),eu);Kw(Vw(),eu);Kw(S7(),eu);Kw(b7(),eu);Kw(RTe(),eu)});var tO=L(Cl=>{"use strict";Object.defineProperty(Cl,"__esModule",{value:!0});Cl.BUNDLE_V03_MEDIA_TYPE=Cl.BUNDLE_V03_LEGACY_MEDIA_TYPE=Cl.BUNDLE_V02_MEDIA_TYPE=Cl.BUNDLE_V01_MEDIA_TYPE=void 0;Cl.isBundleWithCertificateChain=nLt;Cl.isBundleWithPublicKey=iLt;Cl.isBundleWithMessageSignature=sLt;Cl.isBundleWithDsseEnvelope=oLt;Cl.BUNDLE_V01_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.1";Cl.BUNDLE_V02_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.2";Cl.BUNDLE_V03_LEGACY_MEDIA_TYPE="application/vnd.dev.sigstore.bundle+json;version=0.3";Cl.BUNDLE_V03_MEDIA_TYPE="application/vnd.dev.sigstore.bundle.v0.3+json";function nLt(t){return t.verificationMaterial.content.$case==="x509CertificateChain"}function iLt(t){return t.verificationMaterial.content.$case==="publicKey"}function sLt(t){return t.content.$case==="messageSignature"}function oLt(t){return t.content.$case==="dsseEnvelope"}});var NTe=L(nO=>{"use strict";Object.defineProperty(nO,"__esModule",{value:!0});nO.toMessageSignatureBundle=lLt;nO.toDSSEBundle=cLt;var aLt=Rb(),rO=tO();function lLt(t){return{mediaType:t.certificateChain?rO.BUNDLE_V02_MEDIA_TYPE:rO.BUNDLE_V03_MEDIA_TYPE,content:{$case:"messageSignature",messageSignature:{messageDigest:{algorithm:aLt.HashAlgorithm.SHA2_256,digest:t.digest},signature:t.signature}},verificationMaterial:FTe(t)}}function cLt(t){return{mediaType:t.certificateChain?rO.BUNDLE_V02_MEDIA_TYPE:rO.BUNDLE_V03_MEDIA_TYPE,content:{$case:"dsseEnvelope",dsseEnvelope:uLt(t)},verificationMaterial:FTe(t)}}function uLt(t){return{payloadType:t.artifactType,payload:t.artifact,signatures:[fLt(t)]}}function fLt(t){return{keyid:t.keyHint||"",sig:t.signature}}function FTe(t){return{content:ALt(t),tlogEntries:[],timestampVerificationData:{rfc3161Timestamps:[]}}}function ALt(t){return t.certificate?t.certificateChain?{$case:"x509CertificateChain",x509CertificateChain:{certificates:[{rawBytes:t.certificate}]}}:{$case:"certificate",certificate:{rawBytes:t.certificate}}:{$case:"publicKey",publicKey:{hint:t.keyHint||""}}}});var x7=L(iO=>{"use strict";Object.defineProperty(iO,"__esModule",{value:!0});iO.ValidationError=void 0;var P7=class extends Error{constructor(e,r){super(e),this.fields=r}};iO.ValidationError=P7});var k7=L(ty=>{"use strict";Object.defineProperty(ty,"__esModule",{value:!0});ty.assertBundle=pLt;ty.assertBundleV01=OTe;ty.isBundleV01=hLt;ty.assertBundleV02=gLt;ty.assertBundleLatest=dLt;var sO=x7();function pLt(t){let e=oO(t);if(e.length>0)throw new sO.ValidationError("invalid bundle",e)}function OTe(t){let e=[];if(e.push(...oO(t)),e.push(...mLt(t)),e.length>0)throw new sO.ValidationError("invalid v0.1 bundle",e)}function hLt(t){try{return OTe(t),!0}catch{return!1}}function gLt(t){let e=[];if(e.push(...oO(t)),e.push(...LTe(t)),e.length>0)throw new sO.ValidationError("invalid v0.2 bundle",e)}function dLt(t){let e=[];if(e.push(...oO(t)),e.push(...LTe(t)),e.push(...yLt(t)),e.length>0)throw new sO.ValidationError("invalid bundle",e)}function oO(t){let e=[];if((t.mediaType===void 0||!t.mediaType.match(/^application\/vnd\.dev\.sigstore\.bundle\+json;version=\d\.\d/)&&!t.mediaType.match(/^application\/vnd\.dev\.sigstore\.bundle\.v\d\.\d\+json/))&&e.push("mediaType"),t.content===void 0)e.push("content");else switch(t.content.$case){case"messageSignature":t.content.messageSignature.messageDigest===void 0?e.push("content.messageSignature.messageDigest"):t.content.messageSignature.messageDigest.digest.length===0&&e.push("content.messageSignature.messageDigest.digest"),t.content.messageSignature.signature.length===0&&e.push("content.messageSignature.signature");break;case"dsseEnvelope":t.content.dsseEnvelope.payload.length===0&&e.push("content.dsseEnvelope.payload"),t.content.dsseEnvelope.signatures.length!==1?e.push("content.dsseEnvelope.signatures"):t.content.dsseEnvelope.signatures[0].sig.length===0&&e.push("content.dsseEnvelope.signatures[0].sig");break}if(t.verificationMaterial===void 0)e.push("verificationMaterial");else{if(t.verificationMaterial.content===void 0)e.push("verificationMaterial.content");else switch(t.verificationMaterial.content.$case){case"x509CertificateChain":t.verificationMaterial.content.x509CertificateChain.certificates.length===0&&e.push("verificationMaterial.content.x509CertificateChain.certificates"),t.verificationMaterial.content.x509CertificateChain.certificates.forEach((r,s)=>{r.rawBytes.length===0&&e.push(`verificationMaterial.content.x509CertificateChain.certificates[${s}].rawBytes`)});break;case"certificate":t.verificationMaterial.content.certificate.rawBytes.length===0&&e.push("verificationMaterial.content.certificate.rawBytes");break}t.verificationMaterial.tlogEntries===void 0?e.push("verificationMaterial.tlogEntries"):t.verificationMaterial.tlogEntries.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.logId===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].logId`),r.kindVersion===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].kindVersion`)})}return e}function mLt(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionPromise===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionPromise`)}),e}function LTe(t){let e=[];return t.verificationMaterial&&t.verificationMaterial.tlogEntries?.length>0&&t.verificationMaterial.tlogEntries.forEach((r,s)=>{r.inclusionProof===void 0?e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof`):r.inclusionProof.checkpoint===void 0&&e.push(`verificationMaterial.tlogEntries[${s}].inclusionProof.checkpoint`)}),e}function yLt(t){let e=[];return t.verificationMaterial?.content?.$case==="x509CertificateChain"&&e.push("verificationMaterial.content.$case"),e}});var _Te=L(SA=>{"use strict";Object.defineProperty(SA,"__esModule",{value:!0});SA.envelopeToJSON=SA.envelopeFromJSON=SA.bundleToJSON=SA.bundleFromJSON=void 0;var aO=Rb(),MTe=tO(),Q7=k7(),ELt=t=>{let e=aO.Bundle.fromJSON(t);switch(e.mediaType){case MTe.BUNDLE_V01_MEDIA_TYPE:(0,Q7.assertBundleV01)(e);break;case MTe.BUNDLE_V02_MEDIA_TYPE:(0,Q7.assertBundleV02)(e);break;default:(0,Q7.assertBundleLatest)(e);break}return e};SA.bundleFromJSON=ELt;var ILt=t=>aO.Bundle.toJSON(t);SA.bundleToJSON=ILt;var CLt=t=>aO.Envelope.fromJSON(t);SA.envelopeFromJSON=CLt;var wLt=t=>aO.Envelope.toJSON(t);SA.envelopeToJSON=wLt});var Nb=L(Zr=>{"use strict";Object.defineProperty(Zr,"__esModule",{value:!0});Zr.isBundleV01=Zr.assertBundleV02=Zr.assertBundleV01=Zr.assertBundleLatest=Zr.assertBundle=Zr.envelopeToJSON=Zr.envelopeFromJSON=Zr.bundleToJSON=Zr.bundleFromJSON=Zr.ValidationError=Zr.isBundleWithPublicKey=Zr.isBundleWithMessageSignature=Zr.isBundleWithDsseEnvelope=Zr.isBundleWithCertificateChain=Zr.BUNDLE_V03_MEDIA_TYPE=Zr.BUNDLE_V03_LEGACY_MEDIA_TYPE=Zr.BUNDLE_V02_MEDIA_TYPE=Zr.BUNDLE_V01_MEDIA_TYPE=Zr.toMessageSignatureBundle=Zr.toDSSEBundle=void 0;var UTe=NTe();Object.defineProperty(Zr,"toDSSEBundle",{enumerable:!0,get:function(){return UTe.toDSSEBundle}});Object.defineProperty(Zr,"toMessageSignatureBundle",{enumerable:!0,get:function(){return UTe.toMessageSignatureBundle}});var Cg=tO();Object.defineProperty(Zr,"BUNDLE_V01_MEDIA_TYPE",{enumerable:!0,get:function(){return Cg.BUNDLE_V01_MEDIA_TYPE}});Object.defineProperty(Zr,"BUNDLE_V02_MEDIA_TYPE",{enumerable:!0,get:function(){return Cg.BUNDLE_V02_MEDIA_TYPE}});Object.defineProperty(Zr,"BUNDLE_V03_LEGACY_MEDIA_TYPE",{enumerable:!0,get:function(){return Cg.BUNDLE_V03_LEGACY_MEDIA_TYPE}});Object.defineProperty(Zr,"BUNDLE_V03_MEDIA_TYPE",{enumerable:!0,get:function(){return Cg.BUNDLE_V03_MEDIA_TYPE}});Object.defineProperty(Zr,"isBundleWithCertificateChain",{enumerable:!0,get:function(){return Cg.isBundleWithCertificateChain}});Object.defineProperty(Zr,"isBundleWithDsseEnvelope",{enumerable:!0,get:function(){return Cg.isBundleWithDsseEnvelope}});Object.defineProperty(Zr,"isBundleWithMessageSignature",{enumerable:!0,get:function(){return Cg.isBundleWithMessageSignature}});Object.defineProperty(Zr,"isBundleWithPublicKey",{enumerable:!0,get:function(){return Cg.isBundleWithPublicKey}});var BLt=x7();Object.defineProperty(Zr,"ValidationError",{enumerable:!0,get:function(){return BLt.ValidationError}});var lO=_Te();Object.defineProperty(Zr,"bundleFromJSON",{enumerable:!0,get:function(){return lO.bundleFromJSON}});Object.defineProperty(Zr,"bundleToJSON",{enumerable:!0,get:function(){return lO.bundleToJSON}});Object.defineProperty(Zr,"envelopeFromJSON",{enumerable:!0,get:function(){return lO.envelopeFromJSON}});Object.defineProperty(Zr,"envelopeToJSON",{enumerable:!0,get:function(){return lO.envelopeToJSON}});var Fb=k7();Object.defineProperty(Zr,"assertBundle",{enumerable:!0,get:function(){return Fb.assertBundle}});Object.defineProperty(Zr,"assertBundleLatest",{enumerable:!0,get:function(){return Fb.assertBundleLatest}});Object.defineProperty(Zr,"assertBundleV01",{enumerable:!0,get:function(){return Fb.assertBundleV01}});Object.defineProperty(Zr,"assertBundleV02",{enumerable:!0,get:function(){return Fb.assertBundleV02}});Object.defineProperty(Zr,"isBundleV01",{enumerable:!0,get:function(){return Fb.isBundleV01}})});var Ob=L(uO=>{"use strict";Object.defineProperty(uO,"__esModule",{value:!0});uO.ByteStream=void 0;var T7=class extends Error{},cO=class t{constructor(e){this.start=0,e?(this.buf=e,this.view=Buffer.from(e)):(this.buf=new ArrayBuffer(0),this.view=Buffer.from(this.buf))}get buffer(){return this.view.subarray(0,this.start)}get length(){return this.view.byteLength}get position(){return this.start}seek(e){this.start=e}slice(e,r){let s=e+r;if(s>this.length)throw new T7("request past end of buffer");return this.view.subarray(e,s)}appendChar(e){this.ensureCapacity(1),this.view[this.start]=e,this.start+=1}appendUint16(e){this.ensureCapacity(2);let r=new Uint16Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[1],this.view[this.start+1]=s[0],this.start+=2}appendUint24(e){this.ensureCapacity(3);let r=new Uint32Array([e]),s=new Uint8Array(r.buffer);this.view[this.start]=s[2],this.view[this.start+1]=s[1],this.view[this.start+2]=s[0],this.start+=3}appendView(e){this.ensureCapacity(e.length),this.view.set(e,this.start),this.start+=e.length}getBlock(e){if(e<=0)return Buffer.alloc(0);if(this.start+e>this.view.length)throw new Error("request past end of buffer");let r=this.view.subarray(this.start,this.start+e);return this.start+=e,r}getUint8(){return this.getBlock(1)[0]}getUint16(){let e=this.getBlock(2);return e[0]<<8|e[1]}ensureCapacity(e){if(this.start+e>this.view.byteLength){let r=t.BLOCK_SIZE+(e>t.BLOCK_SIZE?e:0);this.realloc(this.view.byteLength+r)}}realloc(e){let r=new ArrayBuffer(e),s=Buffer.from(r);s.set(this.view),this.buf=r,this.view=s}};uO.ByteStream=cO;cO.BLOCK_SIZE=1024});var fO=L(Jw=>{"use strict";Object.defineProperty(Jw,"__esModule",{value:!0});Jw.ASN1TypeError=Jw.ASN1ParseError=void 0;var R7=class extends Error{};Jw.ASN1ParseError=R7;var F7=class extends Error{};Jw.ASN1TypeError=F7});var jTe=L(AO=>{"use strict";Object.defineProperty(AO,"__esModule",{value:!0});AO.decodeLength=vLt;AO.encodeLength=SLt;var HTe=fO();function vLt(t){let e=t.getUint8();if(!(e&128))return e;let r=e&127;if(r>6)throw new HTe.ASN1ParseError("length exceeds 6 byte limit");let s=0;for(let a=0;a0n;)r.unshift(Number(e&255n)),e=e>>8n;return Buffer.from([128|r.length,...r])}});var GTe=L(wg=>{"use strict";Object.defineProperty(wg,"__esModule",{value:!0});wg.parseInteger=PLt;wg.parseStringASCII=qTe;wg.parseTime=xLt;wg.parseOID=kLt;wg.parseBoolean=QLt;wg.parseBitString=TLt;var DLt=/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/,bLt=/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/;function PLt(t){let e=0,r=t.length,s=t[e],a=s>127,n=a?255:0;for(;s==n&&++e=50?1900:2e3,s[1]=a.toString()}return new Date(`${s[1]}-${s[2]}-${s[3]}T${s[4]}:${s[5]}:${s[6]}Z`)}function kLt(t){let e=0,r=t.length,s=t[e++],a=Math.floor(s/40),n=s%40,c=`${a}.${n}`,f=0;for(;e=f;--p)a.push(c>>p&1)}return a}});var YTe=L(pO=>{"use strict";Object.defineProperty(pO,"__esModule",{value:!0});pO.ASN1Tag=void 0;var WTe=fO(),ry={BOOLEAN:1,INTEGER:2,BIT_STRING:3,OCTET_STRING:4,OBJECT_IDENTIFIER:6,SEQUENCE:16,SET:17,PRINTABLE_STRING:19,UTC_TIME:23,GENERALIZED_TIME:24},N7={UNIVERSAL:0,APPLICATION:1,CONTEXT_SPECIFIC:2,PRIVATE:3},O7=class{constructor(e){if(this.number=e&31,this.constructed=(e&32)===32,this.class=e>>6,this.number===31)throw new WTe.ASN1ParseError("long form tags not supported");if(this.class===N7.UNIVERSAL&&this.number===0)throw new WTe.ASN1ParseError("unsupported tag 0x00")}isUniversal(){return this.class===N7.UNIVERSAL}isContextSpecific(e){let r=this.class===N7.CONTEXT_SPECIFIC;return e!==void 0?r&&this.number===e:r}isBoolean(){return this.isUniversal()&&this.number===ry.BOOLEAN}isInteger(){return this.isUniversal()&&this.number===ry.INTEGER}isBitString(){return this.isUniversal()&&this.number===ry.BIT_STRING}isOctetString(){return this.isUniversal()&&this.number===ry.OCTET_STRING}isOID(){return this.isUniversal()&&this.number===ry.OBJECT_IDENTIFIER}isUTCTime(){return this.isUniversal()&&this.number===ry.UTC_TIME}isGeneralizedTime(){return this.isUniversal()&&this.number===ry.GENERALIZED_TIME}toDER(){return this.number|(this.constructed?32:0)|this.class<<6}};pO.ASN1Tag=O7});var zTe=L(gO=>{"use strict";Object.defineProperty(gO,"__esModule",{value:!0});gO.ASN1Obj=void 0;var L7=Ob(),ny=fO(),KTe=jTe(),zw=GTe(),RLt=YTe(),hO=class{constructor(e,r,s){this.tag=e,this.value=r,this.subs=s}static parseBuffer(e){return JTe(new L7.ByteStream(e))}toDER(){let e=new L7.ByteStream;if(this.subs.length>0)for(let a of this.subs)e.appendView(a.toDER());else e.appendView(this.value);let r=e.buffer,s=new L7.ByteStream;return s.appendChar(this.tag.toDER()),s.appendView((0,KTe.encodeLength)(r.length)),s.appendView(r),s.buffer}toBoolean(){if(!this.tag.isBoolean())throw new ny.ASN1TypeError("not a boolean");return(0,zw.parseBoolean)(this.value)}toInteger(){if(!this.tag.isInteger())throw new ny.ASN1TypeError("not an integer");return(0,zw.parseInteger)(this.value)}toOID(){if(!this.tag.isOID())throw new ny.ASN1TypeError("not an OID");return(0,zw.parseOID)(this.value)}toDate(){switch(!0){case this.tag.isUTCTime():return(0,zw.parseTime)(this.value,!0);case this.tag.isGeneralizedTime():return(0,zw.parseTime)(this.value,!1);default:throw new ny.ASN1TypeError("not a date")}}toBitString(){if(!this.tag.isBitString())throw new ny.ASN1TypeError("not a bit string");return(0,zw.parseBitString)(this.value)}};gO.ASN1Obj=hO;function JTe(t){let e=new RLt.ASN1Tag(t.getUint8()),r=(0,KTe.decodeLength)(t),s=t.slice(t.position,r),a=t.position,n=[];if(e.constructed)n=VTe(t,r);else if(e.isOctetString())try{n=VTe(t,r)}catch{}return n.length===0&&t.seek(a+r),new hO(e,s,n)}function VTe(t,e){let r=t.position+e;if(r>t.length)throw new ny.ASN1ParseError("invalid length");let s=[];for(;t.position{"use strict";Object.defineProperty(dO,"__esModule",{value:!0});dO.ASN1Obj=void 0;var FLt=zTe();Object.defineProperty(dO,"ASN1Obj",{enumerable:!0,get:function(){return FLt.ASN1Obj}})});var Zw=L(Bg=>{"use strict";var NLt=Bg&&Bg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Bg,"__esModule",{value:!0});Bg.createPublicKey=OLt;Bg.digest=LLt;Bg.verify=MLt;Bg.bufferEqual=_Lt;var Lb=NLt(ye("crypto"));function OLt(t,e="spki"){return typeof t=="string"?Lb.default.createPublicKey(t):Lb.default.createPublicKey({key:t,format:"der",type:e})}function LLt(t,...e){let r=Lb.default.createHash(t);for(let s of e)r.update(s);return r.digest()}function MLt(t,e,r,s){try{return Lb.default.verify(s,t,e,r)}catch{return!1}}function _Lt(t,e){try{return Lb.default.timingSafeEqual(t,e)}catch{return!1}}});var ZTe=L(M7=>{"use strict";Object.defineProperty(M7,"__esModule",{value:!0});M7.preAuthEncoding=HLt;var ULt="DSSEv1";function HLt(t,e){let r=[ULt,t.length,t,e.length,""].join(" ");return Buffer.concat([Buffer.from(r,"ascii"),e])}});var eRe=L(yO=>{"use strict";Object.defineProperty(yO,"__esModule",{value:!0});yO.base64Encode=jLt;yO.base64Decode=qLt;var XTe="base64",$Te="utf-8";function jLt(t){return Buffer.from(t,$Te).toString(XTe)}function qLt(t){return Buffer.from(t,XTe).toString($Te)}});var tRe=L(U7=>{"use strict";Object.defineProperty(U7,"__esModule",{value:!0});U7.canonicalize=_7;function _7(t){let e="";if(t===null||typeof t!="object"||t.toJSON!=null)e+=JSON.stringify(t);else if(Array.isArray(t)){e+="[";let r=!0;t.forEach(s=>{r||(e+=","),r=!1,e+=_7(s)}),e+="]"}else{e+="{";let r=!0;Object.keys(t).sort().forEach(s=>{r||(e+=","),r=!1,e+=JSON.stringify(s),e+=":",e+=_7(t[s])}),e+="}"}return e}});var H7=L(EO=>{"use strict";Object.defineProperty(EO,"__esModule",{value:!0});EO.toDER=YLt;EO.fromDER=VLt;var GLt=/-----BEGIN (.*)-----/,WLt=/-----END (.*)-----/;function YLt(t){let e="";return t.split(` +`).forEach(r=>{r.match(GLt)||r.match(WLt)||(e+=r)}),Buffer.from(e,"base64")}function VLt(t,e="CERTIFICATE"){let s=t.toString("base64").match(/.{1,64}/g)||"";return[`-----BEGIN ${e}-----`,...s,`-----END ${e}-----`].join(` +`).concat(` +`)}});var IO=L(Xw=>{"use strict";Object.defineProperty(Xw,"__esModule",{value:!0});Xw.SHA2_HASH_ALGOS=Xw.ECDSA_SIGNATURE_ALGOS=void 0;Xw.ECDSA_SIGNATURE_ALGOS={"1.2.840.10045.4.3.1":"sha224","1.2.840.10045.4.3.2":"sha256","1.2.840.10045.4.3.3":"sha384","1.2.840.10045.4.3.4":"sha512"};Xw.SHA2_HASH_ALGOS={"2.16.840.1.101.3.4.2.1":"sha256","2.16.840.1.101.3.4.2.2":"sha384","2.16.840.1.101.3.4.2.3":"sha512"}});var q7=L(CO=>{"use strict";Object.defineProperty(CO,"__esModule",{value:!0});CO.RFC3161TimestampVerificationError=void 0;var j7=class extends Error{};CO.RFC3161TimestampVerificationError=j7});var nRe=L(DA=>{"use strict";var KLt=DA&&DA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),JLt=DA&&DA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),zLt=DA&&DA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&KLt(e,t,r);return JLt(e,t),e};Object.defineProperty(DA,"__esModule",{value:!0});DA.TSTInfo=void 0;var rRe=zLt(Zw()),ZLt=IO(),XLt=q7(),G7=class{constructor(e){this.root=e}get version(){return this.root.subs[0].toInteger()}get genTime(){return this.root.subs[4].toDate()}get messageImprintHashAlgorithm(){let e=this.messageImprintObj.subs[0].subs[0].toOID();return ZLt.SHA2_HASH_ALGOS[e]}get messageImprintHashedMessage(){return this.messageImprintObj.subs[1].value}get raw(){return this.root.toDER()}verify(e){let r=rRe.digest(this.messageImprintHashAlgorithm,e);if(!rRe.bufferEqual(r,this.messageImprintHashedMessage))throw new XLt.RFC3161TimestampVerificationError("message imprint does not match artifact")}get messageImprintObj(){return this.root.subs[2]}};DA.TSTInfo=G7});var sRe=L(bA=>{"use strict";var $Lt=bA&&bA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),eMt=bA&&bA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),tMt=bA&&bA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&$Lt(e,t,r);return eMt(e,t),e};Object.defineProperty(bA,"__esModule",{value:!0});bA.RFC3161Timestamp=void 0;var rMt=mO(),W7=tMt(Zw()),iRe=IO(),Mb=q7(),nMt=nRe(),iMt="1.2.840.113549.1.7.2",sMt="1.2.840.113549.1.9.16.1.4",oMt="1.2.840.113549.1.9.4",Y7=class t{constructor(e){this.root=e}static parse(e){let r=rMt.ASN1Obj.parseBuffer(e);return new t(r)}get status(){return this.pkiStatusInfoObj.subs[0].toInteger()}get contentType(){return this.contentTypeObj.toOID()}get eContentType(){return this.eContentTypeObj.toOID()}get signingTime(){return this.tstInfo.genTime}get signerIssuer(){return this.signerSidObj.subs[0].value}get signerSerialNumber(){return this.signerSidObj.subs[1].value}get signerDigestAlgorithm(){let e=this.signerDigestAlgorithmObj.subs[0].toOID();return iRe.SHA2_HASH_ALGOS[e]}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return iRe.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value}get tstInfo(){return new nMt.TSTInfo(this.eContentObj.subs[0].subs[0])}verify(e,r){if(!this.timeStampTokenObj)throw new Mb.RFC3161TimestampVerificationError("timeStampToken is missing");if(this.contentType!==iMt)throw new Mb.RFC3161TimestampVerificationError(`incorrect content type: ${this.contentType}`);if(this.eContentType!==sMt)throw new Mb.RFC3161TimestampVerificationError(`incorrect encapsulated content type: ${this.eContentType}`);this.tstInfo.verify(e),this.verifyMessageDigest(),this.verifySignature(r)}verifyMessageDigest(){let e=W7.digest(this.signerDigestAlgorithm,this.tstInfo.raw),r=this.messageDigestAttributeObj.subs[1].subs[0].value;if(!W7.bufferEqual(e,r))throw new Mb.RFC3161TimestampVerificationError("signed data does not match tstInfo")}verifySignature(e){let r=this.signedAttrsObj.toDER();if(r[0]=49,!W7.verify(r,e,this.signatureValue,this.signatureAlgorithm))throw new Mb.RFC3161TimestampVerificationError("signature verification failed")}get pkiStatusInfoObj(){return this.root.subs[0]}get timeStampTokenObj(){return this.root.subs[1]}get contentTypeObj(){return this.timeStampTokenObj.subs[0]}get signedDataObj(){return this.timeStampTokenObj.subs.find(r=>r.tag.isContextSpecific(0)).subs[0]}get encapContentInfoObj(){return this.signedDataObj.subs[2]}get signerInfosObj(){let e=this.signedDataObj;return e.subs[e.subs.length-1]}get signerInfoObj(){return this.signerInfosObj.subs[0]}get eContentTypeObj(){return this.encapContentInfoObj.subs[0]}get eContentObj(){return this.encapContentInfoObj.subs[1]}get signedAttrsObj(){return this.signerInfoObj.subs.find(r=>r.tag.isContextSpecific(0))}get messageDigestAttributeObj(){return this.signedAttrsObj.subs.find(r=>r.subs[0].tag.isOID()&&r.subs[0].toOID()===oMt)}get signerSidObj(){return this.signerInfoObj.subs[1]}get signerDigestAlgorithmObj(){return this.signerInfoObj.subs[2]}get signatureAlgorithmObj(){return this.signerInfoObj.subs[4]}get signatureValueObj(){return this.signerInfoObj.subs[5]}};bA.RFC3161Timestamp=Y7});var oRe=L(wO=>{"use strict";Object.defineProperty(wO,"__esModule",{value:!0});wO.RFC3161Timestamp=void 0;var aMt=sRe();Object.defineProperty(wO,"RFC3161Timestamp",{enumerable:!0,get:function(){return aMt.RFC3161Timestamp}})});var lRe=L(PA=>{"use strict";var lMt=PA&&PA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),cMt=PA&&PA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),uMt=PA&&PA.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&lMt(e,t,r);return cMt(e,t),e};Object.defineProperty(PA,"__esModule",{value:!0});PA.SignedCertificateTimestamp=void 0;var fMt=uMt(Zw()),aRe=Ob(),V7=class t{constructor(e){this.version=e.version,this.logID=e.logID,this.timestamp=e.timestamp,this.extensions=e.extensions,this.hashAlgorithm=e.hashAlgorithm,this.signatureAlgorithm=e.signatureAlgorithm,this.signature=e.signature}get datetime(){return new Date(Number(this.timestamp.readBigInt64BE()))}get algorithm(){switch(this.hashAlgorithm){case 0:return"none";case 1:return"md5";case 2:return"sha1";case 3:return"sha224";case 4:return"sha256";case 5:return"sha384";case 6:return"sha512";default:return"unknown"}}verify(e,r){let s=new aRe.ByteStream;return s.appendChar(this.version),s.appendChar(0),s.appendView(this.timestamp),s.appendUint16(1),s.appendView(e),s.appendUint16(this.extensions.byteLength),this.extensions.byteLength>0&&s.appendView(this.extensions),fMt.verify(s.buffer,r,this.signature,this.algorithm)}static parse(e){let r=new aRe.ByteStream(e),s=r.getUint8(),a=r.getBlock(32),n=r.getBlock(8),c=r.getUint16(),f=r.getBlock(c),p=r.getUint8(),h=r.getUint8(),E=r.getUint16(),C=r.getBlock(E);if(r.position!==e.length)throw new Error("SCT buffer length mismatch");return new t({version:s,logID:a,timestamp:n,extensions:f,hashAlgorithm:p,signatureAlgorithm:h,signature:C})}};PA.SignedCertificateTimestamp=V7});var eK=L(oa=>{"use strict";Object.defineProperty(oa,"__esModule",{value:!0});oa.X509SCTExtension=oa.X509SubjectKeyIDExtension=oa.X509AuthorityKeyIDExtension=oa.X509SubjectAlternativeNameExtension=oa.X509KeyUsageExtension=oa.X509BasicConstraintsExtension=oa.X509Extension=void 0;var AMt=Ob(),pMt=lRe(),dh=class{constructor(e){this.root=e}get oid(){return this.root.subs[0].toOID()}get critical(){return this.root.subs.length===3?this.root.subs[1].toBoolean():!1}get value(){return this.extnValueObj.value}get valueObj(){return this.extnValueObj}get extnValueObj(){return this.root.subs[this.root.subs.length-1]}};oa.X509Extension=dh;var K7=class extends dh{get isCA(){return this.sequence.subs[0]?.toBoolean()??!1}get pathLenConstraint(){return this.sequence.subs.length>1?this.sequence.subs[1].toInteger():void 0}get sequence(){return this.extnValueObj.subs[0]}};oa.X509BasicConstraintsExtension=K7;var J7=class extends dh{get digitalSignature(){return this.bitString[0]===1}get keyCertSign(){return this.bitString[5]===1}get crlSign(){return this.bitString[6]===1}get bitString(){return this.extnValueObj.subs[0].toBitString()}};oa.X509KeyUsageExtension=J7;var z7=class extends dh{get rfc822Name(){return this.findGeneralName(1)?.value.toString("ascii")}get uri(){return this.findGeneralName(6)?.value.toString("ascii")}otherName(e){let r=this.findGeneralName(0);return r===void 0||r.subs[0].toOID()!==e?void 0:r.subs[1].subs[0].value.toString("ascii")}findGeneralName(e){return this.generalNames.find(r=>r.tag.isContextSpecific(e))}get generalNames(){return this.extnValueObj.subs[0].subs}};oa.X509SubjectAlternativeNameExtension=z7;var Z7=class extends dh{get keyIdentifier(){return this.findSequenceMember(0)?.value}findSequenceMember(e){return this.sequence.subs.find(r=>r.tag.isContextSpecific(e))}get sequence(){return this.extnValueObj.subs[0]}};oa.X509AuthorityKeyIDExtension=Z7;var X7=class extends dh{get keyIdentifier(){return this.extnValueObj.subs[0].value}};oa.X509SubjectKeyIDExtension=X7;var $7=class extends dh{constructor(e){super(e)}get signedCertificateTimestamps(){let e=this.extnValueObj.subs[0].value,r=new AMt.ByteStream(e),s=r.getUint16()+2,a=[];for(;r.position{"use strict";var hMt=sc&&sc.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),gMt=sc&&sc.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),uRe=sc&&sc.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&hMt(e,t,r);return gMt(e,t),e};Object.defineProperty(sc,"__esModule",{value:!0});sc.X509Certificate=sc.EXTENSION_OID_SCT=void 0;var dMt=mO(),cRe=uRe(Zw()),mMt=IO(),yMt=uRe(H7()),iy=eK(),EMt="2.5.29.14",IMt="2.5.29.15",CMt="2.5.29.17",wMt="2.5.29.19",BMt="2.5.29.35";sc.EXTENSION_OID_SCT="1.3.6.1.4.1.11129.2.4.2";var tK=class t{constructor(e){this.root=e}static parse(e){let r=typeof e=="string"?yMt.toDER(e):e,s=dMt.ASN1Obj.parseBuffer(r);return new t(s)}get tbsCertificate(){return this.tbsCertificateObj}get version(){return`v${(this.versionObj.subs[0].toInteger()+BigInt(1)).toString()}`}get serialNumber(){return this.serialNumberObj.value}get notBefore(){return this.validityObj.subs[0].toDate()}get notAfter(){return this.validityObj.subs[1].toDate()}get issuer(){return this.issuerObj.value}get subject(){return this.subjectObj.value}get publicKey(){return this.subjectPublicKeyInfoObj.toDER()}get signatureAlgorithm(){let e=this.signatureAlgorithmObj.subs[0].toOID();return mMt.ECDSA_SIGNATURE_ALGOS[e]}get signatureValue(){return this.signatureValueObj.value.subarray(1)}get subjectAltName(){let e=this.extSubjectAltName;return e?.uri||e?.rfc822Name}get extensions(){return this.extensionsObj?.subs[0]?.subs||[]}get extKeyUsage(){let e=this.findExtension(IMt);return e?new iy.X509KeyUsageExtension(e):void 0}get extBasicConstraints(){let e=this.findExtension(wMt);return e?new iy.X509BasicConstraintsExtension(e):void 0}get extSubjectAltName(){let e=this.findExtension(CMt);return e?new iy.X509SubjectAlternativeNameExtension(e):void 0}get extAuthorityKeyID(){let e=this.findExtension(BMt);return e?new iy.X509AuthorityKeyIDExtension(e):void 0}get extSubjectKeyID(){let e=this.findExtension(EMt);return e?new iy.X509SubjectKeyIDExtension(e):void 0}get extSCT(){let e=this.findExtension(sc.EXTENSION_OID_SCT);return e?new iy.X509SCTExtension(e):void 0}get isCA(){let e=this.extBasicConstraints?.isCA||!1;return this.extKeyUsage?e&&this.extKeyUsage.keyCertSign:e}extension(e){let r=this.findExtension(e);return r?new iy.X509Extension(r):void 0}verify(e){let r=e?.publicKey||this.publicKey,s=cRe.createPublicKey(r);return cRe.verify(this.tbsCertificate.toDER(),s,this.signatureValue,this.signatureAlgorithm)}validForDate(e){return this.notBefore<=e&&e<=this.notAfter}equals(e){return this.root.toDER().equals(e.root.toDER())}clone(){let e=this.root.toDER(),r=Buffer.alloc(e.length);return e.copy(r),t.parse(r)}findExtension(e){return this.extensions.find(r=>r.subs[0].toOID()===e)}get tbsCertificateObj(){return this.root.subs[0]}get signatureAlgorithmObj(){return this.root.subs[1]}get signatureValueObj(){return this.root.subs[2]}get versionObj(){return this.tbsCertificateObj.subs[0]}get serialNumberObj(){return this.tbsCertificateObj.subs[1]}get issuerObj(){return this.tbsCertificateObj.subs[3]}get validityObj(){return this.tbsCertificateObj.subs[4]}get subjectObj(){return this.tbsCertificateObj.subs[5]}get subjectPublicKeyInfoObj(){return this.tbsCertificateObj.subs[6]}get extensionsObj(){return this.tbsCertificateObj.subs.find(e=>e.tag.isContextSpecific(3))}};sc.X509Certificate=tK});var pRe=L(vg=>{"use strict";Object.defineProperty(vg,"__esModule",{value:!0});vg.X509SCTExtension=vg.X509Certificate=vg.EXTENSION_OID_SCT=void 0;var ARe=fRe();Object.defineProperty(vg,"EXTENSION_OID_SCT",{enumerable:!0,get:function(){return ARe.EXTENSION_OID_SCT}});Object.defineProperty(vg,"X509Certificate",{enumerable:!0,get:function(){return ARe.X509Certificate}});var vMt=eK();Object.defineProperty(vg,"X509SCTExtension",{enumerable:!0,get:function(){return vMt.X509SCTExtension}})});var wl=L(Kn=>{"use strict";var SMt=Kn&&Kn.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),DMt=Kn&&Kn.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),_b=Kn&&Kn.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&SMt(e,t,r);return DMt(e,t),e};Object.defineProperty(Kn,"__esModule",{value:!0});Kn.X509SCTExtension=Kn.X509Certificate=Kn.EXTENSION_OID_SCT=Kn.ByteStream=Kn.RFC3161Timestamp=Kn.pem=Kn.json=Kn.encoding=Kn.dsse=Kn.crypto=Kn.ASN1Obj=void 0;var bMt=mO();Object.defineProperty(Kn,"ASN1Obj",{enumerable:!0,get:function(){return bMt.ASN1Obj}});Kn.crypto=_b(Zw());Kn.dsse=_b(ZTe());Kn.encoding=_b(eRe());Kn.json=_b(tRe());Kn.pem=_b(H7());var PMt=oRe();Object.defineProperty(Kn,"RFC3161Timestamp",{enumerable:!0,get:function(){return PMt.RFC3161Timestamp}});var xMt=Ob();Object.defineProperty(Kn,"ByteStream",{enumerable:!0,get:function(){return xMt.ByteStream}});var rK=pRe();Object.defineProperty(Kn,"EXTENSION_OID_SCT",{enumerable:!0,get:function(){return rK.EXTENSION_OID_SCT}});Object.defineProperty(Kn,"X509Certificate",{enumerable:!0,get:function(){return rK.X509Certificate}});Object.defineProperty(Kn,"X509SCTExtension",{enumerable:!0,get:function(){return rK.X509SCTExtension}})});var hRe=L(nK=>{"use strict";Object.defineProperty(nK,"__esModule",{value:!0});nK.extractJWTSubject=QMt;var kMt=wl();function QMt(t){let e=t.split(".",3),r=JSON.parse(kMt.encoding.base64Decode(e[1]));switch(r.iss){case"https://accounts.google.com":case"https://oauth2.sigstore.dev/auth":return r.email;default:return r.sub}}});var gRe=L((kCr,TMt)=>{TMt.exports={name:"@sigstore/sign",version:"3.1.0",description:"Sigstore signing library",main:"dist/index.js",types:"dist/index.d.ts",scripts:{clean:"shx rm -rf dist *.tsbuildinfo",build:"tsc --build",test:"jest"},files:["dist"],author:"bdehamer@github.com",license:"Apache-2.0",repository:{type:"git",url:"git+https://github.com/sigstore/sigstore-js.git"},bugs:{url:"https://github.com/sigstore/sigstore-js/issues"},homepage:"https://github.com/sigstore/sigstore-js/tree/main/packages/sign#readme",publishConfig:{provenance:!0},devDependencies:{"@sigstore/jest":"^0.0.0","@sigstore/mock":"^0.10.0","@sigstore/rekor-types":"^3.0.0","@types/make-fetch-happen":"^10.0.4","@types/promise-retry":"^1.1.6"},dependencies:{"@sigstore/bundle":"^3.1.0","@sigstore/core":"^2.0.0","@sigstore/protobuf-specs":"^0.4.0","make-fetch-happen":"^14.0.2","proc-log":"^5.0.0","promise-retry":"^2.0.1"},engines:{node:"^18.17.0 || >=20.5.0"}}});var mRe=L($w=>{"use strict";var RMt=$w&&$w.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty($w,"__esModule",{value:!0});$w.getUserAgent=void 0;var dRe=RMt(ye("os")),FMt=()=>{let t=gRe().version,e=process.version,r=dRe.default.platform(),s=dRe.default.arch();return`sigstore-js/${t} (Node ${e}) (${r}/${s})`};$w.getUserAgent=FMt});var Sg=L(Ki=>{"use strict";var NMt=Ki&&Ki.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),OMt=Ki&&Ki.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),yRe=Ki&&Ki.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a{"use strict";Object.defineProperty(BO,"__esModule",{value:!0});BO.BaseBundleBuilder=void 0;var iK=class{constructor(e){this.signer=e.signer,this.witnesses=e.witnesses}async create(e){let r=await this.prepare(e).then(f=>this.signer.sign(f)),s=await this.package(e,r),a=await Promise.all(this.witnesses.map(f=>f.testify(s.content,LMt(r.key)))),n=[],c=[];return a.forEach(({tlogEntries:f,rfc3161Timestamps:p})=>{n.push(...f??[]),c.push(...p??[])}),s.verificationMaterial.tlogEntries=n,s.verificationMaterial.timestampVerificationData={rfc3161Timestamps:c},s}async prepare(e){return e.data}};BO.BaseBundleBuilder=iK;function LMt(t){switch(t.$case){case"publicKey":return t.publicKey;case"x509Certificate":return t.certificate}}});var aK=L(xA=>{"use strict";var MMt=xA&&xA.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),_Mt=xA&&xA.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),UMt=xA&&xA.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;a{"use strict";Object.defineProperty(vO,"__esModule",{value:!0});vO.DSSEBundleBuilder=void 0;var qMt=Sg(),GMt=sK(),WMt=aK(),lK=class extends GMt.BaseBundleBuilder{constructor(e){super(e),this.certificateChain=e.certificateChain??!1}async prepare(e){let r=IRe(e);return qMt.dsse.preAuthEncoding(r.type,r.data)}async package(e,r){return(0,WMt.toDSSEBundle)(IRe(e),r,this.certificateChain)}};vO.DSSEBundleBuilder=lK;function IRe(t){return{...t,type:t.type??""}}});var wRe=L(SO=>{"use strict";Object.defineProperty(SO,"__esModule",{value:!0});SO.MessageSignatureBundleBuilder=void 0;var YMt=sK(),VMt=aK(),cK=class extends YMt.BaseBundleBuilder{constructor(e){super(e)}async package(e,r){return(0,VMt.toMessageSignatureBundle)(e,r)}};SO.MessageSignatureBundleBuilder=cK});var BRe=L(e1=>{"use strict";Object.defineProperty(e1,"__esModule",{value:!0});e1.MessageSignatureBundleBuilder=e1.DSSEBundleBuilder=void 0;var KMt=CRe();Object.defineProperty(e1,"DSSEBundleBuilder",{enumerable:!0,get:function(){return KMt.DSSEBundleBuilder}});var JMt=wRe();Object.defineProperty(e1,"MessageSignatureBundleBuilder",{enumerable:!0,get:function(){return JMt.MessageSignatureBundleBuilder}})});var bO=L(DO=>{"use strict";Object.defineProperty(DO,"__esModule",{value:!0});DO.HTTPError=void 0;var uK=class extends Error{constructor({status:e,message:r,location:s}){super(`(${e}) ${r}`),this.statusCode=e,this.location=s}};DO.HTTPError=uK});var t1=L(Hb=>{"use strict";Object.defineProperty(Hb,"__esModule",{value:!0});Hb.InternalError=void 0;Hb.internalError=ZMt;var zMt=bO(),PO=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.name=this.constructor.name,this.cause=s,this.code=e}};Hb.InternalError=PO;function ZMt(t,e,r){throw t instanceof zMt.HTTPError&&(r+=` - ${t.message}`),new PO({code:e,message:r,cause:t})}});var xO=L((UCr,vRe)=>{vRe.exports=fetch});var SRe=L(r1=>{"use strict";var XMt=r1&&r1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(r1,"__esModule",{value:!0});r1.CIContextProvider=void 0;var $Mt=XMt(xO()),e_t=[t_t,r_t],fK=class{constructor(e="sigstore"){this.audience=e}async getToken(){return Promise.any(e_t.map(e=>e(this.audience))).catch(()=>Promise.reject("CI: no tokens available"))}};r1.CIContextProvider=fK;async function t_t(t){if(!process.env.ACTIONS_ID_TOKEN_REQUEST_URL||!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN)return Promise.reject("no token available");let e=new URL(process.env.ACTIONS_ID_TOKEN_REQUEST_URL);return e.searchParams.append("audience",t),(await(0,$Mt.default)(e.href,{retry:2,headers:{Accept:"application/json",Authorization:`Bearer ${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN}`}})).json().then(s=>s.value)}async function r_t(){return process.env.SIGSTORE_ID_TOKEN?process.env.SIGSTORE_ID_TOKEN:Promise.reject("no token available")}});var DRe=L(kO=>{"use strict";Object.defineProperty(kO,"__esModule",{value:!0});kO.CIContextProvider=void 0;var n_t=SRe();Object.defineProperty(kO,"CIContextProvider",{enumerable:!0,get:function(){return n_t.CIContextProvider}})});var PRe=L((qCr,bRe)=>{var i_t=Symbol("proc-log.meta");bRe.exports={META:i_t,output:{LEVELS:["standard","error","buffer","flush"],KEYS:{standard:"standard",error:"error",buffer:"buffer",flush:"flush"},standard:function(...t){return process.emit("output","standard",...t)},error:function(...t){return process.emit("output","error",...t)},buffer:function(...t){return process.emit("output","buffer",...t)},flush:function(...t){return process.emit("output","flush",...t)}},log:{LEVELS:["notice","error","warn","info","verbose","http","silly","timing","pause","resume"],KEYS:{notice:"notice",error:"error",warn:"warn",info:"info",verbose:"verbose",http:"http",silly:"silly",timing:"timing",pause:"pause",resume:"resume"},error:function(...t){return process.emit("log","error",...t)},notice:function(...t){return process.emit("log","notice",...t)},warn:function(...t){return process.emit("log","warn",...t)},info:function(...t){return process.emit("log","info",...t)},verbose:function(...t){return process.emit("log","verbose",...t)},http:function(...t){return process.emit("log","http",...t)},silly:function(...t){return process.emit("log","silly",...t)},timing:function(...t){return process.emit("log","timing",...t)},pause:function(){return process.emit("log","pause")},resume:function(){return process.emit("log","resume")}},time:{LEVELS:["start","end"],KEYS:{start:"start",end:"end"},start:function(t,e){process.emit("time","start",t);function r(){return process.emit("time","end",t)}if(typeof e=="function"){let s=e();return s&&s.finally?s.finally(r):(r(),s)}return r},end:function(t){return process.emit("time","end",t)}},input:{LEVELS:["start","end","read"],KEYS:{start:"start",end:"end",read:"read"},start:function(t){process.emit("input","start");function e(){return process.emit("input","end")}if(typeof t=="function"){let r=t();return r&&r.finally?r.finally(e):(e(),r)}return e},end:function(){return process.emit("input","end")},read:function(...t){let e,r,s=new Promise((a,n)=>{e=a,r=n});return process.emit("input","read",e,r,...t),s}}}});var QRe=L((GCr,kRe)=>{"use strict";function xRe(t,e){for(let r in e)Object.defineProperty(t,r,{value:e[r],enumerable:!0,configurable:!0});return t}function s_t(t,e,r){if(!t||typeof t=="string")throw new TypeError("Please pass an Error to err-code");r||(r={}),typeof e=="object"&&(r=e,e=void 0),e!=null&&(r.code=e);try{return xRe(t,r)}catch{r.message=t.message,r.stack=t.stack;let a=function(){};return a.prototype=Object.create(Object.getPrototypeOf(t)),xRe(new a,r)}}kRe.exports=s_t});var RRe=L((WCr,TRe)=>{function tu(t,e){typeof e=="boolean"&&(e={forever:e}),this._originalTimeouts=JSON.parse(JSON.stringify(t)),this._timeouts=t,this._options=e||{},this._maxRetryTime=e&&e.maxRetryTime||1/0,this._fn=null,this._errors=[],this._attempts=1,this._operationTimeout=null,this._operationTimeoutCb=null,this._timeout=null,this._operationStart=null,this._options.forever&&(this._cachedTimeouts=this._timeouts.slice(0))}TRe.exports=tu;tu.prototype.reset=function(){this._attempts=1,this._timeouts=this._originalTimeouts};tu.prototype.stop=function(){this._timeout&&clearTimeout(this._timeout),this._timeouts=[],this._cachedTimeouts=null};tu.prototype.retry=function(t){if(this._timeout&&clearTimeout(this._timeout),!t)return!1;var e=new Date().getTime();if(t&&e-this._operationStart>=this._maxRetryTime)return this._errors.unshift(new Error("RetryOperation timeout occurred")),!1;this._errors.push(t);var r=this._timeouts.shift();if(r===void 0)if(this._cachedTimeouts)this._errors.splice(this._errors.length-1,this._errors.length),this._timeouts=this._cachedTimeouts.slice(0),r=this._timeouts.shift();else return!1;var s=this,a=setTimeout(function(){s._attempts++,s._operationTimeoutCb&&(s._timeout=setTimeout(function(){s._operationTimeoutCb(s._attempts)},s._operationTimeout),s._options.unref&&s._timeout.unref()),s._fn(s._attempts)},r);return this._options.unref&&a.unref(),!0};tu.prototype.attempt=function(t,e){this._fn=t,e&&(e.timeout&&(this._operationTimeout=e.timeout),e.cb&&(this._operationTimeoutCb=e.cb));var r=this;this._operationTimeoutCb&&(this._timeout=setTimeout(function(){r._operationTimeoutCb()},r._operationTimeout)),this._operationStart=new Date().getTime(),this._fn(this._attempts)};tu.prototype.try=function(t){console.log("Using RetryOperation.try() is deprecated"),this.attempt(t)};tu.prototype.start=function(t){console.log("Using RetryOperation.start() is deprecated"),this.attempt(t)};tu.prototype.start=tu.prototype.try;tu.prototype.errors=function(){return this._errors};tu.prototype.attempts=function(){return this._attempts};tu.prototype.mainError=function(){if(this._errors.length===0)return null;for(var t={},e=null,r=0,s=0;s=r&&(e=a,r=c)}return e}});var FRe=L(sy=>{var o_t=RRe();sy.operation=function(t){var e=sy.timeouts(t);return new o_t(e,{forever:t&&t.forever,unref:t&&t.unref,maxRetryTime:t&&t.maxRetryTime})};sy.timeouts=function(t){if(t instanceof Array)return[].concat(t);var e={retries:10,factor:2,minTimeout:1*1e3,maxTimeout:1/0,randomize:!1};for(var r in t)e[r]=t[r];if(e.minTimeout>e.maxTimeout)throw new Error("minTimeout is greater than maxTimeout");for(var s=[],a=0;a{NRe.exports=FRe()});var _Re=L((KCr,MRe)=>{"use strict";var a_t=QRe(),l_t=ORe(),c_t=Object.prototype.hasOwnProperty;function LRe(t){return t&&t.code==="EPROMISERETRY"&&c_t.call(t,"retried")}function u_t(t,e){var r,s;return typeof t=="object"&&typeof e=="function"&&(r=e,e=t,t=r),s=l_t.operation(e),new Promise(function(a,n){s.attempt(function(c){Promise.resolve().then(function(){return t(function(f){throw LRe(f)&&(f=f.retried),a_t(new Error("Retrying"),"EPROMISERETRY",{retried:f})},c)}).then(a,function(f){LRe(f)&&(f=f.retried,s.retry(f||new Error))||n(f)})})})}MRe.exports=u_t});var QO=L(jb=>{"use strict";var HRe=jb&&jb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(jb,"__esModule",{value:!0});jb.fetchWithRetry=w_t;var f_t=ye("http2"),A_t=HRe(xO()),URe=PRe(),p_t=HRe(_Re()),h_t=Sg(),g_t=bO(),{HTTP2_HEADER_LOCATION:d_t,HTTP2_HEADER_CONTENT_TYPE:m_t,HTTP2_HEADER_USER_AGENT:y_t,HTTP_STATUS_INTERNAL_SERVER_ERROR:E_t,HTTP_STATUS_TOO_MANY_REQUESTS:I_t,HTTP_STATUS_REQUEST_TIMEOUT:C_t}=f_t.constants;async function w_t(t,e){return(0,p_t.default)(async(r,s)=>{let a=e.method||"POST",n={[y_t]:h_t.ua.getUserAgent(),...e.headers},c=await(0,A_t.default)(t,{method:a,headers:n,body:e.body,timeout:e.timeout,retry:!1}).catch(f=>(URe.log.http("fetch",`${a} ${t} attempt ${s} failed with ${f}`),r(f)));if(c.ok)return c;{let f=await B_t(c);if(URe.log.http("fetch",`${a} ${t} attempt ${s} failed with ${c.status}`),v_t(c.status))return r(f);throw f}},S_t(e.retry))}var B_t=async t=>{let e=t.statusText,r=t.headers.get(d_t)||void 0;if(t.headers.get(m_t)?.includes("application/json"))try{e=(await t.json()).message||e}catch{}return new g_t.HTTPError({status:t.status,message:e,location:r})},v_t=t=>[C_t,I_t].includes(t)||t>=E_t,S_t=t=>typeof t=="boolean"?{retries:t?1:0}:typeof t=="number"?{retries:t}:{retries:0,...t}});var jRe=L(TO=>{"use strict";Object.defineProperty(TO,"__esModule",{value:!0});TO.Fulcio=void 0;var D_t=QO(),AK=class{constructor(e){this.options=e}async createSigningCertificate(e){let{baseURL:r,retry:s,timeout:a}=this.options,n=`${r}/api/v2/signingCert`;return(await(0,D_t.fetchWithRetry)(n,{headers:{"Content-Type":"application/json"},body:JSON.stringify(e),timeout:a,retry:s})).json()}};TO.Fulcio=AK});var qRe=L(RO=>{"use strict";Object.defineProperty(RO,"__esModule",{value:!0});RO.CAClient=void 0;var b_t=t1(),P_t=jRe(),pK=class{constructor(e){this.fulcio=new P_t.Fulcio({baseURL:e.fulcioBaseURL,retry:e.retry,timeout:e.timeout})}async createSigningCertificate(e,r,s){let a=x_t(e,r,s);try{let n=await this.fulcio.createSigningCertificate(a);return(n.signedCertificateEmbeddedSct?n.signedCertificateEmbeddedSct:n.signedCertificateDetachedSct).chain.certificates}catch(n){(0,b_t.internalError)(n,"CA_CREATE_SIGNING_CERTIFICATE_ERROR","error creating signing certificate")}}};RO.CAClient=pK;function x_t(t,e,r){return{credentials:{oidcIdentityToken:t},publicKeyRequest:{publicKey:{algorithm:"ECDSA",content:e},proofOfPossession:r.toString("base64")}}}});var WRe=L(n1=>{"use strict";var k_t=n1&&n1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(n1,"__esModule",{value:!0});n1.EphemeralSigner=void 0;var GRe=k_t(ye("crypto")),Q_t="ec",T_t="P-256",hK=class{constructor(){this.keypair=GRe.default.generateKeyPairSync(Q_t,{namedCurve:T_t})}async sign(e){let r=GRe.default.sign(null,e,this.keypair.privateKey),s=this.keypair.publicKey.export({format:"pem",type:"spki"}).toString("ascii");return{signature:r,key:{$case:"publicKey",publicKey:s}}}};n1.EphemeralSigner=hK});var YRe=L(oy=>{"use strict";Object.defineProperty(oy,"__esModule",{value:!0});oy.FulcioSigner=oy.DEFAULT_FULCIO_URL=void 0;var gK=t1(),R_t=Sg(),F_t=qRe(),N_t=WRe();oy.DEFAULT_FULCIO_URL="https://fulcio.sigstore.dev";var dK=class{constructor(e){this.ca=new F_t.CAClient({...e,fulcioBaseURL:e.fulcioBaseURL||oy.DEFAULT_FULCIO_URL}),this.identityProvider=e.identityProvider,this.keyHolder=e.keyHolder||new N_t.EphemeralSigner}async sign(e){let r=await this.getIdentityToken(),s;try{s=R_t.oidc.extractJWTSubject(r)}catch(f){throw new gK.InternalError({code:"IDENTITY_TOKEN_PARSE_ERROR",message:`invalid identity token: ${r}`,cause:f})}let a=await this.keyHolder.sign(Buffer.from(s));if(a.key.$case!=="publicKey")throw new gK.InternalError({code:"CA_CREATE_SIGNING_CERTIFICATE_ERROR",message:"unexpected format for signing key"});let n=await this.ca.createSigningCertificate(r,a.key.publicKey,a.signature);return{signature:(await this.keyHolder.sign(e)).signature,key:{$case:"x509Certificate",certificate:n[0]}}}async getIdentityToken(){try{return await this.identityProvider.getToken()}catch(e){throw new gK.InternalError({code:"IDENTITY_TOKEN_READ_ERROR",message:"error retrieving identity token",cause:e})}}};oy.FulcioSigner=dK});var KRe=L(i1=>{"use strict";Object.defineProperty(i1,"__esModule",{value:!0});i1.FulcioSigner=i1.DEFAULT_FULCIO_URL=void 0;var VRe=YRe();Object.defineProperty(i1,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return VRe.DEFAULT_FULCIO_URL}});Object.defineProperty(i1,"FulcioSigner",{enumerable:!0,get:function(){return VRe.FulcioSigner}})});var ZRe=L(FO=>{"use strict";Object.defineProperty(FO,"__esModule",{value:!0});FO.Rekor=void 0;var JRe=QO(),mK=class{constructor(e){this.options=e}async createEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries`,f=await(await(0,JRe.fetchWithRetry)(n,{headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(e),timeout:s,retry:a})).json();return zRe(f)}async getEntry(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/log/entries/${e}`,f=await(await(0,JRe.fetchWithRetry)(n,{method:"GET",headers:{Accept:"application/json"},timeout:s,retry:a})).json();return zRe(f)}};FO.Rekor=mK;function zRe(t){let e=Object.entries(t);if(e.length!=1)throw new Error("Received multiple entries in Rekor response");let[r,s]=e[0];return{...s,uuid:r}}});var $Re=L(NO=>{"use strict";Object.defineProperty(NO,"__esModule",{value:!0});NO.TLogClient=void 0;var XRe=t1(),O_t=bO(),L_t=ZRe(),yK=class{constructor(e){this.fetchOnConflict=e.fetchOnConflict??!1,this.rekor=new L_t.Rekor({baseURL:e.rekorBaseURL,retry:e.retry,timeout:e.timeout})}async createEntry(e){let r;try{r=await this.rekor.createEntry(e)}catch(s){if(M_t(s)&&this.fetchOnConflict){let a=s.location.split("/").pop()||"";try{r=await this.rekor.getEntry(a)}catch(n){(0,XRe.internalError)(n,"TLOG_FETCH_ENTRY_ERROR","error fetching tlog entry")}}else(0,XRe.internalError)(s,"TLOG_CREATE_ENTRY_ERROR","error creating tlog entry")}return r}};NO.TLogClient=yK;function M_t(t){return t instanceof O_t.HTTPError&&t.statusCode===409&&t.location!==void 0}});var eFe=L(EK=>{"use strict";Object.defineProperty(EK,"__esModule",{value:!0});EK.toProposedEntry=U_t;var __t=Nb(),Dg=Sg(),qb="sha256";function U_t(t,e,r="dsse"){switch(t.$case){case"dsseEnvelope":return r==="intoto"?q_t(t.dsseEnvelope,e):j_t(t.dsseEnvelope,e);case"messageSignature":return H_t(t.messageSignature,e)}}function H_t(t,e){let r=t.messageDigest.digest.toString("hex"),s=t.signature.toString("base64"),a=Dg.encoding.base64Encode(e);return{apiVersion:"0.0.1",kind:"hashedrekord",spec:{data:{hash:{algorithm:qb,value:r}},signature:{content:s,publicKey:{content:a}}}}}function j_t(t,e){let r=JSON.stringify((0,__t.envelopeToJSON)(t)),s=Dg.encoding.base64Encode(e);return{apiVersion:"0.0.1",kind:"dsse",spec:{proposedContent:{envelope:r,verifiers:[s]}}}}function q_t(t,e){let r=Dg.crypto.digest(qb,t.payload).toString("hex"),s=G_t(t,e),a=Dg.encoding.base64Encode(t.payload.toString("base64")),n=Dg.encoding.base64Encode(t.signatures[0].sig.toString("base64")),c=t.signatures[0].keyid,f=Dg.encoding.base64Encode(e),p={payloadType:t.payloadType,payload:a,signatures:[{sig:n,publicKey:f}]};return c.length>0&&(p.signatures[0].keyid=c),{apiVersion:"0.0.2",kind:"intoto",spec:{content:{envelope:p,hash:{algorithm:qb,value:s},payloadHash:{algorithm:qb,value:r}}}}}function G_t(t,e){let r={payloadType:t.payloadType,payload:t.payload.toString("base64"),signatures:[{sig:t.signatures[0].sig.toString("base64"),publicKey:e}]};return t.signatures[0].keyid.length>0&&(r.signatures[0].keyid=t.signatures[0].keyid),Dg.crypto.digest(qb,Dg.json.canonicalize(r)).toString("hex")}});var tFe=L(ay=>{"use strict";Object.defineProperty(ay,"__esModule",{value:!0});ay.RekorWitness=ay.DEFAULT_REKOR_URL=void 0;var W_t=Sg(),Y_t=$Re(),V_t=eFe();ay.DEFAULT_REKOR_URL="https://rekor.sigstore.dev";var IK=class{constructor(e){this.entryType=e.entryType,this.tlog=new Y_t.TLogClient({...e,rekorBaseURL:e.rekorBaseURL||ay.DEFAULT_REKOR_URL})}async testify(e,r){let s=(0,V_t.toProposedEntry)(e,r,this.entryType),a=await this.tlog.createEntry(s);return K_t(a)}};ay.RekorWitness=IK;function K_t(t){let e=Buffer.from(t.logID,"hex"),r=W_t.encoding.base64Decode(t.body),s=JSON.parse(r),a=t?.verification?.signedEntryTimestamp?J_t(t.verification.signedEntryTimestamp):void 0,n=t?.verification?.inclusionProof?z_t(t.verification.inclusionProof):void 0;return{tlogEntries:[{logIndex:t.logIndex.toString(),logId:{keyId:e},integratedTime:t.integratedTime.toString(),kindVersion:{kind:s.kind,version:s.apiVersion},inclusionPromise:a,inclusionProof:n,canonicalizedBody:Buffer.from(t.body,"base64")}]}}function J_t(t){return{signedEntryTimestamp:Buffer.from(t,"base64")}}function z_t(t){return{logIndex:t.logIndex.toString(),treeSize:t.treeSize.toString(),rootHash:Buffer.from(t.rootHash,"hex"),hashes:t.hashes.map(e=>Buffer.from(e,"hex")),checkpoint:{envelope:t.checkpoint}}}});var rFe=L(OO=>{"use strict";Object.defineProperty(OO,"__esModule",{value:!0});OO.TimestampAuthority=void 0;var Z_t=QO(),CK=class{constructor(e){this.options=e}async createTimestamp(e){let{baseURL:r,timeout:s,retry:a}=this.options,n=`${r}/api/v1/timestamp`;return(await(0,Z_t.fetchWithRetry)(n,{headers:{"Content-Type":"application/json"},body:JSON.stringify(e),timeout:s,retry:a})).buffer()}};OO.TimestampAuthority=CK});var iFe=L(LO=>{"use strict";Object.defineProperty(LO,"__esModule",{value:!0});LO.TSAClient=void 0;var X_t=t1(),$_t=rFe(),eUt=Sg(),nFe="sha256",wK=class{constructor(e){this.tsa=new $_t.TimestampAuthority({baseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async createTimestamp(e){let r={artifactHash:eUt.crypto.digest(nFe,e).toString("base64"),hashAlgorithm:nFe};try{return await this.tsa.createTimestamp(r)}catch(s){(0,X_t.internalError)(s,"TSA_CREATE_TIMESTAMP_ERROR","error creating timestamp")}}};LO.TSAClient=wK});var sFe=L(MO=>{"use strict";Object.defineProperty(MO,"__esModule",{value:!0});MO.TSAWitness=void 0;var tUt=iFe(),BK=class{constructor(e){this.tsa=new tUt.TSAClient({tsaBaseURL:e.tsaBaseURL,retry:e.retry,timeout:e.timeout})}async testify(e){let r=rUt(e);return{rfc3161Timestamps:[{signedTimestamp:await this.tsa.createTimestamp(r)}]}}};MO.TSAWitness=BK;function rUt(t){switch(t.$case){case"dsseEnvelope":return t.dsseEnvelope.signatures[0].sig;case"messageSignature":return t.messageSignature.signature}}});var aFe=L(bg=>{"use strict";Object.defineProperty(bg,"__esModule",{value:!0});bg.TSAWitness=bg.RekorWitness=bg.DEFAULT_REKOR_URL=void 0;var oFe=tFe();Object.defineProperty(bg,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return oFe.DEFAULT_REKOR_URL}});Object.defineProperty(bg,"RekorWitness",{enumerable:!0,get:function(){return oFe.RekorWitness}});var nUt=sFe();Object.defineProperty(bg,"TSAWitness",{enumerable:!0,get:function(){return nUt.TSAWitness}})});var SK=L(Es=>{"use strict";Object.defineProperty(Es,"__esModule",{value:!0});Es.TSAWitness=Es.RekorWitness=Es.DEFAULT_REKOR_URL=Es.FulcioSigner=Es.DEFAULT_FULCIO_URL=Es.CIContextProvider=Es.InternalError=Es.MessageSignatureBundleBuilder=Es.DSSEBundleBuilder=void 0;var lFe=BRe();Object.defineProperty(Es,"DSSEBundleBuilder",{enumerable:!0,get:function(){return lFe.DSSEBundleBuilder}});Object.defineProperty(Es,"MessageSignatureBundleBuilder",{enumerable:!0,get:function(){return lFe.MessageSignatureBundleBuilder}});var iUt=t1();Object.defineProperty(Es,"InternalError",{enumerable:!0,get:function(){return iUt.InternalError}});var sUt=DRe();Object.defineProperty(Es,"CIContextProvider",{enumerable:!0,get:function(){return sUt.CIContextProvider}});var cFe=KRe();Object.defineProperty(Es,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return cFe.DEFAULT_FULCIO_URL}});Object.defineProperty(Es,"FulcioSigner",{enumerable:!0,get:function(){return cFe.FulcioSigner}});var vK=aFe();Object.defineProperty(Es,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return vK.DEFAULT_REKOR_URL}});Object.defineProperty(Es,"RekorWitness",{enumerable:!0,get:function(){return vK.RekorWitness}});Object.defineProperty(Es,"TSAWitness",{enumerable:!0,get:function(){return vK.TSAWitness}})});var fFe=L(Gb=>{"use strict";var uFe=Gb&&Gb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Gb,"__esModule",{value:!0});Gb.appDataPath=aUt;var oUt=uFe(ye("os")),s1=uFe(ye("path"));function aUt(t){let e=oUt.default.homedir();switch(process.platform){case"darwin":{let r=s1.default.join(e,"Library","Application Support");return s1.default.join(r,t)}case"win32":{let r=process.env.LOCALAPPDATA||s1.default.join(e,"AppData","Local");return s1.default.join(r,t,"Data")}default:{let r=process.env.XDG_DATA_HOME||s1.default.join(e,".local","share");return s1.default.join(r,t)}}}});var kA=L(Bl=>{"use strict";Object.defineProperty(Bl,"__esModule",{value:!0});Bl.UnsupportedAlgorithmError=Bl.CryptoError=Bl.LengthOrHashMismatchError=Bl.UnsignedMetadataError=Bl.RepositoryError=Bl.ValueError=void 0;var DK=class extends Error{};Bl.ValueError=DK;var Wb=class extends Error{};Bl.RepositoryError=Wb;var bK=class extends Wb{};Bl.UnsignedMetadataError=bK;var PK=class extends Wb{};Bl.LengthOrHashMismatchError=PK;var _O=class extends Error{};Bl.CryptoError=_O;var xK=class extends _O{};Bl.UnsupportedAlgorithmError=xK});var pFe=L(Pg=>{"use strict";Object.defineProperty(Pg,"__esModule",{value:!0});Pg.isDefined=lUt;Pg.isObject=AFe;Pg.isStringArray=cUt;Pg.isObjectArray=uUt;Pg.isStringRecord=fUt;Pg.isObjectRecord=AUt;function lUt(t){return t!==void 0}function AFe(t){return typeof t=="object"&&t!==null}function cUt(t){return Array.isArray(t)&&t.every(e=>typeof e=="string")}function uUt(t){return Array.isArray(t)&&t.every(AFe)}function fUt(t){return typeof t=="object"&&t!==null&&Object.keys(t).every(e=>typeof e=="string")&&Object.values(t).every(e=>typeof e=="string")}function AUt(t){return typeof t=="object"&&t!==null&&Object.keys(t).every(e=>typeof e=="string")&&Object.values(t).every(e=>typeof e=="object"&&e!==null)}});var QK=L((pwr,dFe)=>{var hFe=",",pUt=":",hUt="[",gUt="]",dUt="{",mUt="}";function kK(t){let e=[];if(typeof t=="string")e.push(gFe(t));else if(typeof t=="boolean")e.push(JSON.stringify(t));else if(Number.isInteger(t))e.push(JSON.stringify(t));else if(t===null)e.push(JSON.stringify(t));else if(Array.isArray(t)){e.push(hUt);let r=!0;t.forEach(s=>{r||e.push(hFe),r=!1,e.push(kK(s))}),e.push(gUt)}else if(typeof t=="object"){e.push(dUt);let r=!0;Object.keys(t).sort().forEach(s=>{r||e.push(hFe),r=!1,e.push(gFe(s)),e.push(pUt),e.push(kK(t[s]))}),e.push(mUt)}else throw new TypeError("cannot encode "+t.toString());return e.join("")}function gFe(t){return'"'+t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')+'"'}dFe.exports={canonicalize:kK}});var mFe=L(o1=>{"use strict";var yUt=o1&&o1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(o1,"__esModule",{value:!0});o1.verifySignature=void 0;var EUt=QK(),IUt=yUt(ye("crypto")),CUt=(t,e,r)=>{let s=Buffer.from((0,EUt.canonicalize)(t));return IUt.default.verify(void 0,s,e,Buffer.from(r,"hex"))};o1.verifySignature=CUt});var Af=L(ru=>{"use strict";var wUt=ru&&ru.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),BUt=ru&&ru.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),yFe=ru&&ru.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&wUt(e,t,r);return BUt(e,t),e};Object.defineProperty(ru,"__esModule",{value:!0});ru.crypto=ru.guard=void 0;ru.guard=yFe(pFe());ru.crypto=yFe(mFe())});var ly=L(mh=>{"use strict";var vUt=mh&&mh.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(mh,"__esModule",{value:!0});mh.Signed=mh.MetadataKind=void 0;mh.isMetadataKind=DUt;var SUt=vUt(ye("util")),Yb=kA(),TK=Af(),EFe=["1","0","31"],RK;(function(t){t.Root="root",t.Timestamp="timestamp",t.Snapshot="snapshot",t.Targets="targets"})(RK||(mh.MetadataKind=RK={}));function DUt(t){return typeof t=="string"&&Object.values(RK).includes(t)}var FK=class t{constructor(e){this.specVersion=e.specVersion||EFe.join(".");let r=this.specVersion.split(".");if(!(r.length===2||r.length===3)||!r.every(s=>bUt(s)))throw new Yb.ValueError("Failed to parse specVersion");if(r[0]!=EFe[0])throw new Yb.ValueError("Unsupported specVersion");this.expires=e.expires,this.version=e.version,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.specVersion===e.specVersion&&this.expires===e.expires&&this.version===e.version&&SUt.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}isExpired(e){return e||(e=new Date),e>=new Date(this.expires)}static commonFieldsFromJSON(e){let{spec_version:r,expires:s,version:a,...n}=e;if(TK.guard.isDefined(r)){if(typeof r!="string")throw new TypeError("spec_version must be a string")}else throw new Yb.ValueError("spec_version is not defined");if(TK.guard.isDefined(s)){if(typeof s!="string")throw new TypeError("expires must be a string")}else throw new Yb.ValueError("expires is not defined");if(TK.guard.isDefined(a)){if(typeof a!="number")throw new TypeError("version must be a number")}else throw new Yb.ValueError("version is not defined");return{specVersion:r,expires:s,version:a,unrecognizedFields:n}}};mh.Signed=FK;function bUt(t){return!isNaN(Number(t))}});var Vb=L(kg=>{"use strict";var IFe=kg&&kg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(kg,"__esModule",{value:!0});kg.TargetFile=kg.MetaFile=void 0;var CFe=IFe(ye("crypto")),HO=IFe(ye("util")),xg=kA(),UO=Af(),NK=class t{constructor(e){if(e.version<=0)throw new xg.ValueError("Metafile version must be at least 1");e.length!==void 0&&wFe(e.length),this.version=e.version,this.length=e.length,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}equals(e){return e instanceof t?this.version===e.version&&this.length===e.length&&HO.default.isDeepStrictEqual(this.hashes,e.hashes)&&HO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}verify(e){if(this.length!==void 0&&e.length!==this.length)throw new xg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${e.length}`);this.hashes&&Object.entries(this.hashes).forEach(([r,s])=>{let a;try{a=CFe.default.createHash(r)}catch{throw new xg.LengthOrHashMismatchError(`Hash algorithm ${r} not supported`)}let n=a.update(e).digest("hex");if(n!==s)throw new xg.LengthOrHashMismatchError(`Expected hash ${s} but got ${n}`)})}toJSON(){let e={version:this.version,...this.unrecognizedFields};return this.length!==void 0&&(e.length=this.length),this.hashes&&(e.hashes=this.hashes),e}static fromJSON(e){let{version:r,length:s,hashes:a,...n}=e;if(typeof r!="number")throw new TypeError("version must be a number");if(UO.guard.isDefined(s)&&typeof s!="number")throw new TypeError("length must be a number");if(UO.guard.isDefined(a)&&!UO.guard.isStringRecord(a))throw new TypeError("hashes must be string keys and values");return new t({version:r,length:s,hashes:a,unrecognizedFields:n})}};kg.MetaFile=NK;var OK=class t{constructor(e){wFe(e.length),this.length=e.length,this.path=e.path,this.hashes=e.hashes,this.unrecognizedFields=e.unrecognizedFields||{}}get custom(){let e=this.unrecognizedFields.custom;return!e||Array.isArray(e)||typeof e!="object"?{}:e}equals(e){return e instanceof t?this.length===e.length&&this.path===e.path&&HO.default.isDeepStrictEqual(this.hashes,e.hashes)&&HO.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}async verify(e){let r=0,s=Object.keys(this.hashes).reduce((a,n)=>{try{a[n]=CFe.default.createHash(n)}catch{throw new xg.LengthOrHashMismatchError(`Hash algorithm ${n} not supported`)}return a},{});for await(let a of e)r+=a.length,Object.values(s).forEach(n=>{n.update(a)});if(r!==this.length)throw new xg.LengthOrHashMismatchError(`Expected length ${this.length} but got ${r}`);Object.entries(s).forEach(([a,n])=>{let c=this.hashes[a],f=n.digest("hex");if(f!==c)throw new xg.LengthOrHashMismatchError(`Expected hash ${c} but got ${f}`)})}toJSON(){return{length:this.length,hashes:this.hashes,...this.unrecognizedFields}}static fromJSON(e,r){let{length:s,hashes:a,...n}=r;if(typeof s!="number")throw new TypeError("length must be a number");if(!UO.guard.isStringRecord(a))throw new TypeError("hashes must have string keys and values");return new t({length:s,path:e,hashes:a,unrecognizedFields:n})}};kg.TargetFile=OK;function wFe(t){if(t<0)throw new xg.ValueError("Length must be at least 0")}});var BFe=L(LK=>{"use strict";Object.defineProperty(LK,"__esModule",{value:!0});LK.encodeOIDString=xUt;var PUt=6;function xUt(t){let e=t.split("."),r=parseInt(e[0],10)*40+parseInt(e[1],10),s=[];e.slice(2).forEach(n=>{let c=kUt(parseInt(n,10));s.push(...c)});let a=Buffer.from([r,...s]);return Buffer.from([PUt,a.length,...a])}function kUt(t){let e=[],r=0;for(;t>0;)e.unshift(t&127|r),t>>=7,r=128;return e}});var bFe=L(Jb=>{"use strict";var QUt=Jb&&Jb.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Jb,"__esModule",{value:!0});Jb.getPublicKey=NUt;var a1=QUt(ye("crypto")),Kb=kA(),MK=BFe(),jO=48,vFe=3,SFe=0,TUt="1.3.101.112",RUt="1.2.840.10045.2.1",FUt="1.2.840.10045.3.1.7",_K="-----BEGIN PUBLIC KEY-----";function NUt(t){switch(t.keyType){case"rsa":return OUt(t);case"ed25519":return LUt(t);case"ecdsa":case"ecdsa-sha2-nistp256":case"ecdsa-sha2-nistp384":return MUt(t);default:throw new Kb.UnsupportedAlgorithmError(`Unsupported key type: ${t.keyType}`)}}function OUt(t){if(!t.keyVal.startsWith(_K))throw new Kb.CryptoError("Invalid key format");let e=a1.default.createPublicKey(t.keyVal);switch(t.scheme){case"rsassa-pss-sha256":return{key:e,padding:a1.default.constants.RSA_PKCS1_PSS_PADDING};default:throw new Kb.UnsupportedAlgorithmError(`Unsupported RSA scheme: ${t.scheme}`)}}function LUt(t){let e;if(t.keyVal.startsWith(_K))e=a1.default.createPublicKey(t.keyVal);else{if(!DFe(t.keyVal))throw new Kb.CryptoError("Invalid key format");e=a1.default.createPublicKey({key:_Ut.hexToDER(t.keyVal),format:"der",type:"spki"})}return{key:e}}function MUt(t){let e;if(t.keyVal.startsWith(_K))e=a1.default.createPublicKey(t.keyVal);else{if(!DFe(t.keyVal))throw new Kb.CryptoError("Invalid key format");e=a1.default.createPublicKey({key:UUt.hexToDER(t.keyVal),format:"der",type:"spki"})}return{key:e}}var _Ut={hexToDER:t=>{let e=Buffer.from(t,"hex"),r=(0,MK.encodeOIDString)(TUt),s=Buffer.concat([Buffer.concat([Buffer.from([jO]),Buffer.from([r.length]),r]),Buffer.concat([Buffer.from([vFe]),Buffer.from([e.length+1]),Buffer.from([SFe]),e])]);return Buffer.concat([Buffer.from([jO]),Buffer.from([s.length]),s])}},UUt={hexToDER:t=>{let e=Buffer.from(t,"hex"),r=Buffer.concat([Buffer.from([vFe]),Buffer.from([e.length+1]),Buffer.from([SFe]),e]),s=Buffer.concat([(0,MK.encodeOIDString)(RUt),(0,MK.encodeOIDString)(FUt)]),a=Buffer.concat([Buffer.from([jO]),Buffer.from([s.length]),s]);return Buffer.concat([Buffer.from([jO]),Buffer.from([a.length+r.length]),a,r])}},DFe=t=>/^[0-9a-fA-F]+$/.test(t)});var qO=L(l1=>{"use strict";var HUt=l1&&l1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(l1,"__esModule",{value:!0});l1.Key=void 0;var PFe=HUt(ye("util")),zb=kA(),xFe=Af(),jUt=bFe(),UK=class t{constructor(e){let{keyID:r,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c}=e;this.keyID=r,this.keyType=s,this.scheme=a,this.keyVal=n,this.unrecognizedFields=c||{}}verifySignature(e){let r=e.signatures[this.keyID];if(!r)throw new zb.UnsignedMetadataError("no signature for key found in metadata");if(!this.keyVal.public)throw new zb.UnsignedMetadataError("no public key found");let s=(0,jUt.getPublicKey)({keyType:this.keyType,scheme:this.scheme,keyVal:this.keyVal.public}),a=e.signed.toJSON();try{if(!xFe.crypto.verifySignature(a,s,r.sig))throw new zb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}catch(n){throw n instanceof zb.UnsignedMetadataError?n:new zb.UnsignedMetadataError(`failed to verify ${this.keyID} signature`)}}equals(e){return e instanceof t?this.keyID===e.keyID&&this.keyType===e.keyType&&this.scheme===e.scheme&&PFe.default.isDeepStrictEqual(this.keyVal,e.keyVal)&&PFe.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keytype:this.keyType,scheme:this.scheme,keyval:this.keyVal,...this.unrecognizedFields}}static fromJSON(e,r){let{keytype:s,scheme:a,keyval:n,...c}=r;if(typeof s!="string")throw new TypeError("keytype must be a string");if(typeof a!="string")throw new TypeError("scheme must be a string");if(!xFe.guard.isStringRecord(n))throw new TypeError("keyval must be a string record");return new t({keyID:e,keyType:s,scheme:a,keyVal:n,unrecognizedFields:c})}};l1.Key=UK});var FFe=L((Cwr,RFe)=>{"use strict";RFe.exports=QFe;function QFe(t,e,r){t instanceof RegExp&&(t=kFe(t,r)),e instanceof RegExp&&(e=kFe(e,r));var s=TFe(t,e,r);return s&&{start:s[0],end:s[1],pre:r.slice(0,s[0]),body:r.slice(s[0]+t.length,s[1]),post:r.slice(s[1]+e.length)}}function kFe(t,e){var r=e.match(t);return r?r[0]:null}QFe.range=TFe;function TFe(t,e,r){var s,a,n,c,f,p=r.indexOf(t),h=r.indexOf(e,p+1),E=p;if(p>=0&&h>0){for(s=[],n=r.length;E>=0&&!f;)E==p?(s.push(E),p=r.indexOf(t,E+1)):s.length==1?f=[s.pop(),h]:(a=s.pop(),a=0?p:h;s.length&&(f=[n,c])}return f}});var jFe=L((wwr,HFe)=>{var NFe=FFe();HFe.exports=WUt;var OFe="\0SLASH"+Math.random()+"\0",LFe="\0OPEN"+Math.random()+"\0",jK="\0CLOSE"+Math.random()+"\0",MFe="\0COMMA"+Math.random()+"\0",_Fe="\0PERIOD"+Math.random()+"\0";function HK(t){return parseInt(t,10)==t?parseInt(t,10):t.charCodeAt(0)}function qUt(t){return t.split("\\\\").join(OFe).split("\\{").join(LFe).split("\\}").join(jK).split("\\,").join(MFe).split("\\.").join(_Fe)}function GUt(t){return t.split(OFe).join("\\").split(LFe).join("{").split(jK).join("}").split(MFe).join(",").split(_Fe).join(".")}function UFe(t){if(!t)return[""];var e=[],r=NFe("{","}",t);if(!r)return t.split(",");var s=r.pre,a=r.body,n=r.post,c=s.split(",");c[c.length-1]+="{"+a+"}";var f=UFe(n);return n.length&&(c[c.length-1]+=f.shift(),c.push.apply(c,f)),e.push.apply(e,c),e}function WUt(t){return t?(t.substr(0,2)==="{}"&&(t="\\{\\}"+t.substr(2)),Zb(qUt(t),!0).map(GUt)):[]}function YUt(t){return"{"+t+"}"}function VUt(t){return/^-?0\d/.test(t)}function KUt(t,e){return t<=e}function JUt(t,e){return t>=e}function Zb(t,e){var r=[],s=NFe("{","}",t);if(!s)return[t];var a=s.pre,n=s.post.length?Zb(s.post,!1):[""];if(/\$$/.test(s.pre))for(var c=0;c=0;if(!E&&!C)return s.post.match(/,.*\}/)?(t=s.pre+"{"+s.body+jK+s.post,Zb(t)):[t];var S;if(E)S=s.body.split(/\.\./);else if(S=UFe(s.body),S.length===1&&(S=Zb(S[0],!1).map(YUt),S.length===1))return n.map(function(Ce){return s.pre+S[0]+Ce});var P;if(E){var I=HK(S[0]),R=HK(S[1]),N=Math.max(S[0].length,S[1].length),U=S.length==3?Math.abs(HK(S[2])):1,W=KUt,te=R0){var pe=new Array(me+1).join("0");Ae<0?ce="-"+pe+ce.slice(1):ce=pe+ce}}P.push(ce)}}else{P=[];for(var Be=0;Be{"use strict";Object.defineProperty(GO,"__esModule",{value:!0});GO.assertValidPattern=void 0;var zUt=1024*64,ZUt=t=>{if(typeof t!="string")throw new TypeError("invalid pattern");if(t.length>zUt)throw new TypeError("pattern is too long")};GO.assertValidPattern=ZUt});var WFe=L(WO=>{"use strict";Object.defineProperty(WO,"__esModule",{value:!0});WO.parseClass=void 0;var XUt={"[:alnum:]":["\\p{L}\\p{Nl}\\p{Nd}",!0],"[:alpha:]":["\\p{L}\\p{Nl}",!0],"[:ascii:]":["\\x00-\\x7f",!1],"[:blank:]":["\\p{Zs}\\t",!0],"[:cntrl:]":["\\p{Cc}",!0],"[:digit:]":["\\p{Nd}",!0],"[:graph:]":["\\p{Z}\\p{C}",!0,!0],"[:lower:]":["\\p{Ll}",!0],"[:print:]":["\\p{C}",!0],"[:punct:]":["\\p{P}",!0],"[:space:]":["\\p{Z}\\t\\r\\n\\v\\f",!0],"[:upper:]":["\\p{Lu}",!0],"[:word:]":["\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}",!0],"[:xdigit:]":["A-Fa-f0-9",!1]},Xb=t=>t.replace(/[[\]\\-]/g,"\\$&"),$Ut=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),GFe=t=>t.join(""),e4t=(t,e)=>{let r=e;if(t.charAt(r)!=="[")throw new Error("not in a brace expression");let s=[],a=[],n=r+1,c=!1,f=!1,p=!1,h=!1,E=r,C="";e:for(;nC?s.push(Xb(C)+"-"+Xb(R)):R===C&&s.push(Xb(R)),C="",n++;continue}if(t.startsWith("-]",n+1)){s.push(Xb(R+"-")),n+=2;continue}if(t.startsWith("-",n+1)){C=R,n+=2;continue}s.push(Xb(R)),n++}if(E{"use strict";Object.defineProperty(YO,"__esModule",{value:!0});YO.unescape=void 0;var t4t=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/\[([^\/\\])\]/g,"$1"):t.replace(/((?!\\).|^)\[([^\/\\])\]/g,"$1$2").replace(/\\([^\/])/g,"$1");YO.unescape=t4t});var WK=L(zO=>{"use strict";Object.defineProperty(zO,"__esModule",{value:!0});zO.AST=void 0;var r4t=WFe(),KO=VO(),n4t=new Set(["!","?","+","*","@"]),YFe=t=>n4t.has(t),i4t="(?!(?:^|/)\\.\\.?(?:$|/))",JO="(?!\\.)",s4t=new Set(["[","."]),o4t=new Set(["..","."]),a4t=new Set("().*{}+?[]^$\\!"),l4t=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),GK="[^/]",VFe=GK+"*?",KFe=GK+"+?",qK=class t{type;#t;#r;#i=!1;#e=[];#n;#o;#l;#a=!1;#s;#c;#f=!1;constructor(e,r,s={}){this.type=e,e&&(this.#r=!0),this.#n=r,this.#t=this.#n?this.#n.#t:this,this.#s=this.#t===this?s:this.#t.#s,this.#l=this.#t===this?[]:this.#t.#l,e==="!"&&!this.#t.#a&&this.#l.push(this),this.#o=this.#n?this.#n.#e.length:0}get hasMagic(){if(this.#r!==void 0)return this.#r;for(let e of this.#e)if(typeof e!="string"&&(e.type||e.hasMagic))return this.#r=!0;return this.#r}toString(){return this.#c!==void 0?this.#c:this.type?this.#c=this.type+"("+this.#e.map(e=>String(e)).join("|")+")":this.#c=this.#e.map(e=>String(e)).join("")}#p(){if(this!==this.#t)throw new Error("should only call on root");if(this.#a)return this;this.toString(),this.#a=!0;let e;for(;e=this.#l.pop();){if(e.type!=="!")continue;let r=e,s=r.#n;for(;s;){for(let a=r.#o+1;!s.type&&atypeof r=="string"?r:r.toJSON()):[this.type,...this.#e.map(r=>r.toJSON())];return this.isStart()&&!this.type&&e.unshift([]),this.isEnd()&&(this===this.#t||this.#t.#a&&this.#n?.type==="!")&&e.push({}),e}isStart(){if(this.#t===this)return!0;if(!this.#n?.isStart())return!1;if(this.#o===0)return!0;let e=this.#n;for(let r=0;r{let[I,R,N,U]=typeof P=="string"?t.#h(P,this.#r,p):P.toRegExpSource(e);return this.#r=this.#r||N,this.#i=this.#i||U,I}).join(""),E="";if(this.isStart()&&typeof this.#e[0]=="string"&&!(this.#e.length===1&&o4t.has(this.#e[0]))){let I=s4t,R=r&&I.has(h.charAt(0))||h.startsWith("\\.")&&I.has(h.charAt(2))||h.startsWith("\\.\\.")&&I.has(h.charAt(4)),N=!r&&!e&&I.has(h.charAt(0));E=R?i4t:N?JO:""}let C="";return this.isEnd()&&this.#t.#a&&this.#n?.type==="!"&&(C="(?:$|\\/)"),[E+h+C,(0,KO.unescape)(h),this.#r=!!this.#r,this.#i]}let s=this.type==="*"||this.type==="+",a=this.type==="!"?"(?:(?!(?:":"(?:",n=this.#A(r);if(this.isStart()&&this.isEnd()&&!n&&this.type!=="!"){let p=this.toString();return this.#e=[p],this.type=null,this.#r=void 0,[p,(0,KO.unescape)(this.toString()),!1,!1]}let c=!s||e||r||!JO?"":this.#A(!0);c===n&&(c=""),c&&(n=`(?:${n})(?:${c})*?`);let f="";if(this.type==="!"&&this.#f)f=(this.isStart()&&!r?JO:"")+KFe;else{let p=this.type==="!"?"))"+(this.isStart()&&!r&&!e?JO:"")+VFe+")":this.type==="@"?")":this.type==="?"?")?":this.type==="+"&&c?")":this.type==="*"&&c?")?":`)${this.type}`;f=a+n+p}return[f,(0,KO.unescape)(n),this.#r=!!this.#r,this.#i]}#A(e){return this.#e.map(r=>{if(typeof r=="string")throw new Error("string type in extglob ast??");let[s,a,n,c]=r.toRegExpSource(e);return this.#i=this.#i||c,s}).filter(r=>!(this.isStart()&&this.isEnd())||!!r).join("|")}static#h(e,r,s=!1){let a=!1,n="",c=!1;for(let f=0;f{"use strict";Object.defineProperty(ZO,"__esModule",{value:!0});ZO.escape=void 0;var c4t=(t,{windowsPathsNoEscape:e=!1}={})=>e?t.replace(/[?*()[\]]/g,"[$&]"):t.replace(/[?*()[\]\\]/g,"\\$&");ZO.escape=c4t});var tNe=L(pr=>{"use strict";var u4t=pr&&pr.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(pr,"__esModule",{value:!0});pr.unescape=pr.escape=pr.AST=pr.Minimatch=pr.match=pr.makeRe=pr.braceExpand=pr.defaults=pr.filter=pr.GLOBSTAR=pr.sep=pr.minimatch=void 0;var f4t=u4t(jFe()),XO=qFe(),ZFe=WK(),A4t=YK(),p4t=VO(),h4t=(t,e,r={})=>((0,XO.assertValidPattern)(e),!r.nocomment&&e.charAt(0)==="#"?!1:new cy(e,r).match(t));pr.minimatch=h4t;var g4t=/^\*+([^+@!?\*\[\(]*)$/,d4t=t=>e=>!e.startsWith(".")&&e.endsWith(t),m4t=t=>e=>e.endsWith(t),y4t=t=>(t=t.toLowerCase(),e=>!e.startsWith(".")&&e.toLowerCase().endsWith(t)),E4t=t=>(t=t.toLowerCase(),e=>e.toLowerCase().endsWith(t)),I4t=/^\*+\.\*+$/,C4t=t=>!t.startsWith(".")&&t.includes("."),w4t=t=>t!=="."&&t!==".."&&t.includes("."),B4t=/^\.\*+$/,v4t=t=>t!=="."&&t!==".."&&t.startsWith("."),S4t=/^\*+$/,D4t=t=>t.length!==0&&!t.startsWith("."),b4t=t=>t.length!==0&&t!=="."&&t!=="..",P4t=/^\?+([^+@!?\*\[\(]*)?$/,x4t=([t,e=""])=>{let r=XFe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},k4t=([t,e=""])=>{let r=$Fe([t]);return e?(e=e.toLowerCase(),s=>r(s)&&s.toLowerCase().endsWith(e)):r},Q4t=([t,e=""])=>{let r=$Fe([t]);return e?s=>r(s)&&s.endsWith(e):r},T4t=([t,e=""])=>{let r=XFe([t]);return e?s=>r(s)&&s.endsWith(e):r},XFe=([t])=>{let e=t.length;return r=>r.length===e&&!r.startsWith(".")},$Fe=([t])=>{let e=t.length;return r=>r.length===e&&r!=="."&&r!==".."},eNe=typeof process=="object"&&process?typeof process.env=="object"&&process.env&&process.env.__MINIMATCH_TESTING_PLATFORM__||process.platform:"posix",JFe={win32:{sep:"\\"},posix:{sep:"/"}};pr.sep=eNe==="win32"?JFe.win32.sep:JFe.posix.sep;pr.minimatch.sep=pr.sep;pr.GLOBSTAR=Symbol("globstar **");pr.minimatch.GLOBSTAR=pr.GLOBSTAR;var R4t="[^/]",F4t=R4t+"*?",N4t="(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?",O4t="(?:(?!(?:\\/|^)\\.).)*?",L4t=(t,e={})=>r=>(0,pr.minimatch)(r,t,e);pr.filter=L4t;pr.minimatch.filter=pr.filter;var nu=(t,e={})=>Object.assign({},t,e),M4t=t=>{if(!t||typeof t!="object"||!Object.keys(t).length)return pr.minimatch;let e=pr.minimatch;return Object.assign((s,a,n={})=>e(s,a,nu(t,n)),{Minimatch:class extends e.Minimatch{constructor(a,n={}){super(a,nu(t,n))}static defaults(a){return e.defaults(nu(t,a)).Minimatch}},AST:class extends e.AST{constructor(a,n,c={}){super(a,n,nu(t,c))}static fromGlob(a,n={}){return e.AST.fromGlob(a,nu(t,n))}},unescape:(s,a={})=>e.unescape(s,nu(t,a)),escape:(s,a={})=>e.escape(s,nu(t,a)),filter:(s,a={})=>e.filter(s,nu(t,a)),defaults:s=>e.defaults(nu(t,s)),makeRe:(s,a={})=>e.makeRe(s,nu(t,a)),braceExpand:(s,a={})=>e.braceExpand(s,nu(t,a)),match:(s,a,n={})=>e.match(s,a,nu(t,n)),sep:e.sep,GLOBSTAR:pr.GLOBSTAR})};pr.defaults=M4t;pr.minimatch.defaults=pr.defaults;var _4t=(t,e={})=>((0,XO.assertValidPattern)(t),e.nobrace||!/\{(?:(?!\{).)*\}/.test(t)?[t]:(0,f4t.default)(t));pr.braceExpand=_4t;pr.minimatch.braceExpand=pr.braceExpand;var U4t=(t,e={})=>new cy(t,e).makeRe();pr.makeRe=U4t;pr.minimatch.makeRe=pr.makeRe;var H4t=(t,e,r={})=>{let s=new cy(e,r);return t=t.filter(a=>s.match(a)),s.options.nonull&&!t.length&&t.push(e),t};pr.match=H4t;pr.minimatch.match=pr.match;var zFe=/[?*]|[+@!]\(.*?\)|\[|\]/,j4t=t=>t.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),cy=class{options;set;pattern;windowsPathsNoEscape;nonegate;negate;comment;empty;preserveMultipleSlashes;partial;globSet;globParts;nocase;isWindows;platform;windowsNoMagicRoot;regexp;constructor(e,r={}){(0,XO.assertValidPattern)(e),r=r||{},this.options=r,this.pattern=e,this.platform=r.platform||eNe,this.isWindows=this.platform==="win32",this.windowsPathsNoEscape=!!r.windowsPathsNoEscape||r.allowWindowsEscape===!1,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\/g,"/")),this.preserveMultipleSlashes=!!r.preserveMultipleSlashes,this.regexp=null,this.negate=!1,this.nonegate=!!r.nonegate,this.comment=!1,this.empty=!1,this.partial=!!r.partial,this.nocase=!!this.options.nocase,this.windowsNoMagicRoot=r.windowsNoMagicRoot!==void 0?r.windowsNoMagicRoot:!!(this.isWindows&&this.nocase),this.globSet=[],this.globParts=[],this.set=[],this.make()}hasMagic(){if(this.options.magicalBraces&&this.set.length>1)return!0;for(let e of this.set)for(let r of e)if(typeof r!="string")return!0;return!1}debug(...e){}make(){let e=this.pattern,r=this.options;if(!r.nocomment&&e.charAt(0)==="#"){this.comment=!0;return}if(!e){this.empty=!0;return}this.parseNegate(),this.globSet=[...new Set(this.braceExpand())],r.debug&&(this.debug=(...n)=>console.error(...n)),this.debug(this.pattern,this.globSet);let s=this.globSet.map(n=>this.slashSplit(n));this.globParts=this.preprocess(s),this.debug(this.pattern,this.globParts);let a=this.globParts.map((n,c,f)=>{if(this.isWindows&&this.windowsNoMagicRoot){let p=n[0]===""&&n[1]===""&&(n[2]==="?"||!zFe.test(n[2]))&&!zFe.test(n[3]),h=/^[a-z]:/i.test(n[0]);if(p)return[...n.slice(0,4),...n.slice(4).map(E=>this.parse(E))];if(h)return[n[0],...n.slice(1).map(E=>this.parse(E))]}return n.map(p=>this.parse(p))});if(this.debug(this.pattern,a),this.set=a.filter(n=>n.indexOf(!1)===-1),this.isWindows)for(let n=0;n=2?(e=this.firstPhasePreProcess(e),e=this.secondPhasePreProcess(e)):r>=1?e=this.levelOneOptimize(e):e=this.adjascentGlobstarOptimize(e),e}adjascentGlobstarOptimize(e){return e.map(r=>{let s=-1;for(;(s=r.indexOf("**",s+1))!==-1;){let a=s;for(;r[a+1]==="**";)a++;a!==s&&r.splice(s,a-s)}return r})}levelOneOptimize(e){return e.map(r=>(r=r.reduce((s,a)=>{let n=s[s.length-1];return a==="**"&&n==="**"?s:a===".."&&n&&n!==".."&&n!=="."&&n!=="**"?(s.pop(),s):(s.push(a),s)},[]),r.length===0?[""]:r))}levelTwoFileOptimize(e){Array.isArray(e)||(e=this.slashSplit(e));let r=!1;do{if(r=!1,!this.preserveMultipleSlashes){for(let a=1;aa&&s.splice(a+1,c-a);let f=s[a+1],p=s[a+2],h=s[a+3];if(f!==".."||!p||p==="."||p===".."||!h||h==="."||h==="..")continue;r=!0,s.splice(a,1);let E=s.slice(0);E[a]="**",e.push(E),a--}if(!this.preserveMultipleSlashes){for(let c=1;cr.length)}partsMatch(e,r,s=!1){let a=0,n=0,c=[],f="";for(;ate?r=r.slice(ie):te>ie&&(e=e.slice(te)))}}let{optimizationLevel:n=1}=this.options;n>=2&&(e=this.levelTwoFileOptimize(e)),this.debug("matchOne",this,{file:e,pattern:r}),this.debug("matchOne",e.length,r.length);for(var c=0,f=0,p=e.length,h=r.length;c>> no match, partial?`,e,S,r,P),S===p))}let R;if(typeof E=="string"?(R=C===E,this.debug("string match",E,C,R)):(R=E.test(C),this.debug("pattern match",E,C,R)),!R)return!1}if(c===p&&f===h)return!0;if(c===p)return s;if(f===h)return c===p-1&&e[c]==="";throw new Error("wtf?")}braceExpand(){return(0,pr.braceExpand)(this.pattern,this.options)}parse(e){(0,XO.assertValidPattern)(e);let r=this.options;if(e==="**")return pr.GLOBSTAR;if(e==="")return"";let s,a=null;(s=e.match(S4t))?a=r.dot?b4t:D4t:(s=e.match(g4t))?a=(r.nocase?r.dot?E4t:y4t:r.dot?m4t:d4t)(s[1]):(s=e.match(P4t))?a=(r.nocase?r.dot?k4t:x4t:r.dot?Q4t:T4t)(s):(s=e.match(I4t))?a=r.dot?w4t:C4t:(s=e.match(B4t))&&(a=v4t);let n=ZFe.AST.fromGlob(e,this.options).toMMPattern();return a&&typeof n=="object"&&Reflect.defineProperty(n,"test",{value:a}),n}makeRe(){if(this.regexp||this.regexp===!1)return this.regexp;let e=this.set;if(!e.length)return this.regexp=!1,this.regexp;let r=this.options,s=r.noglobstar?F4t:r.dot?N4t:O4t,a=new Set(r.nocase?["i"]:[]),n=e.map(p=>{let h=p.map(E=>{if(E instanceof RegExp)for(let C of E.flags.split(""))a.add(C);return typeof E=="string"?j4t(E):E===pr.GLOBSTAR?pr.GLOBSTAR:E._src});return h.forEach((E,C)=>{let S=h[C+1],P=h[C-1];E!==pr.GLOBSTAR||P===pr.GLOBSTAR||(P===void 0?S!==void 0&&S!==pr.GLOBSTAR?h[C+1]="(?:\\/|"+s+"\\/)?"+S:h[C]=s:S===void 0?h[C-1]=P+"(?:\\/|"+s+")?":S!==pr.GLOBSTAR&&(h[C-1]=P+"(?:\\/|\\/"+s+"\\/)"+S,h[C+1]=pr.GLOBSTAR))}),h.filter(E=>E!==pr.GLOBSTAR).join("/")}).join("|"),[c,f]=e.length>1?["(?:",")"]:["",""];n="^"+c+n+f+"$",this.negate&&(n="^(?!"+n+").+$");try{this.regexp=new RegExp(n,[...a].join(""))}catch{this.regexp=!1}return this.regexp}slashSplit(e){return this.preserveMultipleSlashes?e.split("/"):this.isWindows&&/^\/\/[^\/]+/.test(e)?["",...e.split(/\/+/)]:e.split(/\/+/)}match(e,r=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return e==="";if(e==="/"&&r)return!0;let s=this.options;this.isWindows&&(e=e.split("\\").join("/"));let a=this.slashSplit(e);this.debug(this.pattern,"split",a);let n=this.set;this.debug(this.pattern,"set",n);let c=a[a.length-1];if(!c)for(let f=a.length-2;!c&&f>=0;f--)c=a[f];for(let f=0;f{"use strict";var rNe=iu&&iu.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(iu,"__esModule",{value:!0});iu.SuccinctRoles=iu.DelegatedRole=iu.Role=iu.TOP_LEVEL_ROLE_NAMES=void 0;var nNe=rNe(ye("crypto")),Y4t=tNe(),$O=rNe(ye("util")),eL=kA(),uy=Af();iu.TOP_LEVEL_ROLE_NAMES=["root","targets","snapshot","timestamp"];var $b=class t{constructor(e){let{keyIDs:r,threshold:s,unrecognizedFields:a}=e;if(V4t(r))throw new eL.ValueError("duplicate key IDs found");if(s<1)throw new eL.ValueError("threshold must be at least 1");this.keyIDs=r,this.threshold=s,this.unrecognizedFields=a||{}}equals(e){return e instanceof t?this.threshold===e.threshold&&$O.default.isDeepStrictEqual(this.keyIDs,e.keyIDs)&&$O.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields):!1}toJSON(){return{keyids:this.keyIDs,threshold:this.threshold,...this.unrecognizedFields}}static fromJSON(e){let{keyids:r,threshold:s,...a}=e;if(!uy.guard.isStringArray(r))throw new TypeError("keyids must be an array");if(typeof s!="number")throw new TypeError("threshold must be a number");return new t({keyIDs:r,threshold:s,unrecognizedFields:a})}};iu.Role=$b;function V4t(t){return new Set(t).size!==t.length}var VK=class t extends $b{constructor(e){super(e);let{name:r,terminating:s,paths:a,pathHashPrefixes:n}=e;if(this.name=r,this.terminating=s,e.paths&&e.pathHashPrefixes)throw new eL.ValueError("paths and pathHashPrefixes are mutually exclusive");this.paths=a,this.pathHashPrefixes=n}equals(e){return e instanceof t?super.equals(e)&&this.name===e.name&&this.terminating===e.terminating&&$O.default.isDeepStrictEqual(this.paths,e.paths)&&$O.default.isDeepStrictEqual(this.pathHashPrefixes,e.pathHashPrefixes):!1}isDelegatedPath(e){if(this.paths)return this.paths.some(r=>J4t(e,r));if(this.pathHashPrefixes){let s=nNe.default.createHash("sha256").update(e).digest("hex");return this.pathHashPrefixes.some(a=>s.startsWith(a))}return!1}toJSON(){let e={...super.toJSON(),name:this.name,terminating:this.terminating};return this.paths&&(e.paths=this.paths),this.pathHashPrefixes&&(e.path_hash_prefixes=this.pathHashPrefixes),e}static fromJSON(e){let{keyids:r,threshold:s,name:a,terminating:n,paths:c,path_hash_prefixes:f,...p}=e;if(!uy.guard.isStringArray(r))throw new TypeError("keyids must be an array of strings");if(typeof s!="number")throw new TypeError("threshold must be a number");if(typeof a!="string")throw new TypeError("name must be a string");if(typeof n!="boolean")throw new TypeError("terminating must be a boolean");if(uy.guard.isDefined(c)&&!uy.guard.isStringArray(c))throw new TypeError("paths must be an array of strings");if(uy.guard.isDefined(f)&&!uy.guard.isStringArray(f))throw new TypeError("path_hash_prefixes must be an array of strings");return new t({keyIDs:r,threshold:s,name:a,terminating:n,paths:c,pathHashPrefixes:f,unrecognizedFields:p})}};iu.DelegatedRole=VK;var K4t=(t,e)=>t.map((r,s)=>[r,e[s]]);function J4t(t,e){let r=t.split("/"),s=e.split("/");return s.length!=r.length?!1:K4t(r,s).every(([a,n])=>(0,Y4t.minimatch)(a,n))}var KK=class t extends $b{constructor(e){super(e);let{bitLength:r,namePrefix:s}=e;if(r<=0||r>32)throw new eL.ValueError("bitLength must be between 1 and 32");this.bitLength=r,this.namePrefix=s,this.numberOfBins=Math.pow(2,r),this.suffixLen=(this.numberOfBins-1).toString(16).length}equals(e){return e instanceof t?super.equals(e)&&this.bitLength===e.bitLength&&this.namePrefix===e.namePrefix:!1}getRoleForTarget(e){let a=nNe.default.createHash("sha256").update(e).digest().subarray(0,4),n=32-this.bitLength,f=(a.readUInt32BE()>>>n).toString(16).padStart(this.suffixLen,"0");return`${this.namePrefix}-${f}`}*getRoles(){for(let e=0;e{"use strict";var z4t=c1&&c1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(c1,"__esModule",{value:!0});c1.Root=void 0;var iNe=z4t(ye("util")),zK=ly(),sNe=kA(),Z4t=qO(),tL=JK(),rL=Af(),ZK=class t extends zK.Signed{constructor(e){if(super(e),this.type=zK.MetadataKind.Root,this.keys=e.keys||{},this.consistentSnapshot=e.consistentSnapshot??!0,!e.roles)this.roles=tL.TOP_LEVEL_ROLE_NAMES.reduce((r,s)=>({...r,[s]:new tL.Role({keyIDs:[],threshold:1})}),{});else{let r=new Set(Object.keys(e.roles));if(!tL.TOP_LEVEL_ROLE_NAMES.every(s=>r.has(s)))throw new sNe.ValueError("missing top-level role");this.roles=e.roles}}addKey(e,r){if(!this.roles[r])throw new sNe.ValueError(`role ${r} does not exist`);this.roles[r].keyIDs.includes(e.keyID)||this.roles[r].keyIDs.push(e.keyID),this.keys[e.keyID]=e}equals(e){return e instanceof t?super.equals(e)&&this.consistentSnapshot===e.consistentSnapshot&&iNe.default.isDeepStrictEqual(this.keys,e.keys)&&iNe.default.isDeepStrictEqual(this.roles,e.roles):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,keys:X4t(this.keys),roles:$4t(this.roles),consistent_snapshot:this.consistentSnapshot,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=zK.Signed.commonFieldsFromJSON(e),{keys:a,roles:n,consistent_snapshot:c,...f}=r;if(typeof c!="boolean")throw new TypeError("consistent_snapshot must be a boolean");return new t({...s,keys:e3t(a),roles:t3t(n),consistentSnapshot:c,unrecognizedFields:f})}};c1.Root=ZK;function X4t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function $4t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function e3t(t){let e;if(rL.guard.isDefined(t)){if(!rL.guard.isObjectRecord(t))throw new TypeError("keys must be an object");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:Z4t.Key.fromJSON(s,a)}),{})}return e}function t3t(t){let e;if(rL.guard.isDefined(t)){if(!rL.guard.isObjectRecord(t))throw new TypeError("roles must be an object");e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:tL.Role.fromJSON(a)}),{})}return e}});var eJ=L(nL=>{"use strict";Object.defineProperty(nL,"__esModule",{value:!0});nL.Signature=void 0;var $K=class t{constructor(e){let{keyID:r,sig:s}=e;this.keyID=r,this.sig=s}toJSON(){return{keyid:this.keyID,sig:this.sig}}static fromJSON(e){let{keyid:r,sig:s}=e;if(typeof r!="string")throw new TypeError("keyid must be a string");if(typeof s!="string")throw new TypeError("sig must be a string");return new t({keyID:r,sig:s})}};nL.Signature=$K});var nJ=L(u1=>{"use strict";var r3t=u1&&u1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(u1,"__esModule",{value:!0});u1.Snapshot=void 0;var n3t=r3t(ye("util")),tJ=ly(),aNe=Vb(),oNe=Af(),rJ=class t extends tJ.Signed{constructor(e){super(e),this.type=tJ.MetadataKind.Snapshot,this.meta=e.meta||{"targets.json":new aNe.MetaFile({version:1})}}equals(e){return e instanceof t?super.equals(e)&&n3t.default.isDeepStrictEqual(this.meta,e.meta):!1}toJSON(){return{_type:this.type,meta:i3t(this.meta),spec_version:this.specVersion,version:this.version,expires:this.expires,...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=tJ.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,meta:s3t(a),unrecognizedFields:n})}};u1.Snapshot=rJ;function i3t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function s3t(t){let e;if(oNe.guard.isDefined(t))if(oNe.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:aNe.MetaFile.fromJSON(a)}),{});else throw new TypeError("meta field is malformed");return e}});var lNe=L(f1=>{"use strict";var o3t=f1&&f1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(f1,"__esModule",{value:!0});f1.Delegations=void 0;var iL=o3t(ye("util")),a3t=kA(),l3t=qO(),iJ=JK(),sL=Af(),sJ=class t{constructor(e){if(this.keys=e.keys,this.unrecognizedFields=e.unrecognizedFields||{},e.roles&&Object.keys(e.roles).some(r=>iJ.TOP_LEVEL_ROLE_NAMES.includes(r)))throw new a3t.ValueError("Delegated role name conflicts with top-level role name");this.succinctRoles=e.succinctRoles,this.roles=e.roles}equals(e){return e instanceof t?iL.default.isDeepStrictEqual(this.keys,e.keys)&&iL.default.isDeepStrictEqual(this.roles,e.roles)&&iL.default.isDeepStrictEqual(this.unrecognizedFields,e.unrecognizedFields)&&iL.default.isDeepStrictEqual(this.succinctRoles,e.succinctRoles):!1}*rolesForTarget(e){if(this.roles)for(let r of Object.values(this.roles))r.isDelegatedPath(e)&&(yield{role:r.name,terminating:r.terminating});else this.succinctRoles&&(yield{role:this.succinctRoles.getRoleForTarget(e),terminating:!0})}toJSON(){let e={keys:c3t(this.keys),...this.unrecognizedFields};return this.roles?e.roles=u3t(this.roles):this.succinctRoles&&(e.succinct_roles=this.succinctRoles.toJSON()),e}static fromJSON(e){let{keys:r,roles:s,succinct_roles:a,...n}=e,c;return sL.guard.isObject(a)&&(c=iJ.SuccinctRoles.fromJSON(a)),new t({keys:f3t(r),roles:A3t(s),unrecognizedFields:n,succinctRoles:c})}};f1.Delegations=sJ;function c3t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function u3t(t){return Object.values(t).map(e=>e.toJSON())}function f3t(t){if(!sL.guard.isObjectRecord(t))throw new TypeError("keys is malformed");return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:l3t.Key.fromJSON(r,s)}),{})}function A3t(t){let e;if(sL.guard.isDefined(t)){if(!sL.guard.isObjectArray(t))throw new TypeError("roles is malformed");e=t.reduce((r,s)=>{let a=iJ.DelegatedRole.fromJSON(s);return{...r,[a.name]:a}},{})}return e}});var lJ=L(A1=>{"use strict";var p3t=A1&&A1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(A1,"__esModule",{value:!0});A1.Targets=void 0;var cNe=p3t(ye("util")),oJ=ly(),h3t=lNe(),g3t=Vb(),oL=Af(),aJ=class t extends oJ.Signed{constructor(e){super(e),this.type=oJ.MetadataKind.Targets,this.targets=e.targets||{},this.delegations=e.delegations}addTarget(e){this.targets[e.path]=e}equals(e){return e instanceof t?super.equals(e)&&cNe.default.isDeepStrictEqual(this.targets,e.targets)&&cNe.default.isDeepStrictEqual(this.delegations,e.delegations):!1}toJSON(){let e={_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,targets:d3t(this.targets),...this.unrecognizedFields};return this.delegations&&(e.delegations=this.delegations.toJSON()),e}static fromJSON(e){let{unrecognizedFields:r,...s}=oJ.Signed.commonFieldsFromJSON(e),{targets:a,delegations:n,...c}=r;return new t({...s,targets:m3t(a),delegations:y3t(n),unrecognizedFields:c})}};A1.Targets=aJ;function d3t(t){return Object.entries(t).reduce((e,[r,s])=>({...e,[r]:s.toJSON()}),{})}function m3t(t){let e;if(oL.guard.isDefined(t))if(oL.guard.isObjectRecord(t))e=Object.entries(t).reduce((r,[s,a])=>({...r,[s]:g3t.TargetFile.fromJSON(s,a)}),{});else throw new TypeError("targets must be an object");return e}function y3t(t){let e;if(oL.guard.isDefined(t))if(oL.guard.isObject(t))e=h3t.Delegations.fromJSON(t);else throw new TypeError("delegations must be an object");return e}});var AJ=L(aL=>{"use strict";Object.defineProperty(aL,"__esModule",{value:!0});aL.Timestamp=void 0;var cJ=ly(),uNe=Vb(),uJ=Af(),fJ=class t extends cJ.Signed{constructor(e){super(e),this.type=cJ.MetadataKind.Timestamp,this.snapshotMeta=e.snapshotMeta||new uNe.MetaFile({version:1})}equals(e){return e instanceof t?super.equals(e)&&this.snapshotMeta.equals(e.snapshotMeta):!1}toJSON(){return{_type:this.type,spec_version:this.specVersion,version:this.version,expires:this.expires,meta:{"snapshot.json":this.snapshotMeta.toJSON()},...this.unrecognizedFields}}static fromJSON(e){let{unrecognizedFields:r,...s}=cJ.Signed.commonFieldsFromJSON(e),{meta:a,...n}=r;return new t({...s,snapshotMeta:E3t(a),unrecognizedFields:n})}};aL.Timestamp=fJ;function E3t(t){let e;if(uJ.guard.isDefined(t)){let r=t["snapshot.json"];if(!uJ.guard.isDefined(r)||!uJ.guard.isObject(r))throw new TypeError("missing snapshot.json in meta");e=uNe.MetaFile.fromJSON(r)}return e}});var ANe=L(h1=>{"use strict";var I3t=h1&&h1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(h1,"__esModule",{value:!0});h1.Metadata=void 0;var C3t=QK(),fNe=I3t(ye("util")),p1=ly(),eP=kA(),w3t=XK(),B3t=eJ(),v3t=nJ(),S3t=lJ(),D3t=AJ(),pJ=Af(),hJ=class t{constructor(e,r,s){this.signed=e,this.signatures=r||{},this.unrecognizedFields=s||{}}sign(e,r=!0){let s=Buffer.from((0,C3t.canonicalize)(this.signed.toJSON())),a=e(s);r||(this.signatures={}),this.signatures[a.keyID]=a}verifyDelegate(e,r){let s,a={};switch(this.signed.type){case p1.MetadataKind.Root:a=this.signed.keys,s=this.signed.roles[e];break;case p1.MetadataKind.Targets:if(!this.signed.delegations)throw new eP.ValueError(`No delegations found for ${e}`);a=this.signed.delegations.keys,this.signed.delegations.roles?s=this.signed.delegations.roles[e]:this.signed.delegations.succinctRoles&&this.signed.delegations.succinctRoles.isDelegatedRole(e)&&(s=this.signed.delegations.succinctRoles);break;default:throw new TypeError("invalid metadata type")}if(!s)throw new eP.ValueError(`no delegation found for ${e}`);let n=new Set;if(s.keyIDs.forEach(c=>{let f=a[c];if(f)try{f.verifySignature(r),n.add(f.keyID)}catch{}}),n.sizer.toJSON()),signed:this.signed.toJSON(),...this.unrecognizedFields}}static fromJSON(e,r){let{signed:s,signatures:a,...n}=r;if(!pJ.guard.isDefined(s)||!pJ.guard.isObject(s))throw new TypeError("signed is not defined");if(e!==s._type)throw new eP.ValueError(`expected '${e}', got ${s._type}`);if(!pJ.guard.isObjectArray(a))throw new TypeError("signatures is not an array");let c;switch(e){case p1.MetadataKind.Root:c=w3t.Root.fromJSON(s);break;case p1.MetadataKind.Timestamp:c=D3t.Timestamp.fromJSON(s);break;case p1.MetadataKind.Snapshot:c=v3t.Snapshot.fromJSON(s);break;case p1.MetadataKind.Targets:c=S3t.Targets.fromJSON(s);break;default:throw new TypeError("invalid metadata type")}let f={};return a.forEach(p=>{let h=B3t.Signature.fromJSON(p);if(f[h.keyID])throw new eP.ValueError(`multiple signatures found for keyid: ${h.keyID}`);f[h.keyID]=h}),new t(c,f,n)}};h1.Metadata=hJ});var lL=L(Ri=>{"use strict";Object.defineProperty(Ri,"__esModule",{value:!0});Ri.Timestamp=Ri.Targets=Ri.Snapshot=Ri.Signature=Ri.Root=Ri.Metadata=Ri.Key=Ri.TargetFile=Ri.MetaFile=Ri.ValueError=Ri.MetadataKind=void 0;var b3t=ly();Object.defineProperty(Ri,"MetadataKind",{enumerable:!0,get:function(){return b3t.MetadataKind}});var P3t=kA();Object.defineProperty(Ri,"ValueError",{enumerable:!0,get:function(){return P3t.ValueError}});var pNe=Vb();Object.defineProperty(Ri,"MetaFile",{enumerable:!0,get:function(){return pNe.MetaFile}});Object.defineProperty(Ri,"TargetFile",{enumerable:!0,get:function(){return pNe.TargetFile}});var x3t=qO();Object.defineProperty(Ri,"Key",{enumerable:!0,get:function(){return x3t.Key}});var k3t=ANe();Object.defineProperty(Ri,"Metadata",{enumerable:!0,get:function(){return k3t.Metadata}});var Q3t=XK();Object.defineProperty(Ri,"Root",{enumerable:!0,get:function(){return Q3t.Root}});var T3t=eJ();Object.defineProperty(Ri,"Signature",{enumerable:!0,get:function(){return T3t.Signature}});var R3t=nJ();Object.defineProperty(Ri,"Snapshot",{enumerable:!0,get:function(){return R3t.Snapshot}});var F3t=lJ();Object.defineProperty(Ri,"Targets",{enumerable:!0,get:function(){return F3t.Targets}});var N3t=AJ();Object.defineProperty(Ri,"Timestamp",{enumerable:!0,get:function(){return N3t.Timestamp}})});var gNe=L((Uwr,hNe)=>{var g1=1e3,d1=g1*60,m1=d1*60,fy=m1*24,O3t=fy*7,L3t=fy*365.25;hNe.exports=function(t,e){e=e||{};var r=typeof t;if(r==="string"&&t.length>0)return M3t(t);if(r==="number"&&isFinite(t))return e.long?U3t(t):_3t(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))};function M3t(t){if(t=String(t),!(t.length>100)){var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var r=parseFloat(e[1]),s=(e[2]||"ms").toLowerCase();switch(s){case"years":case"year":case"yrs":case"yr":case"y":return r*L3t;case"weeks":case"week":case"w":return r*O3t;case"days":case"day":case"d":return r*fy;case"hours":case"hour":case"hrs":case"hr":case"h":return r*m1;case"minutes":case"minute":case"mins":case"min":case"m":return r*d1;case"seconds":case"second":case"secs":case"sec":case"s":return r*g1;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}}}function _3t(t){var e=Math.abs(t);return e>=fy?Math.round(t/fy)+"d":e>=m1?Math.round(t/m1)+"h":e>=d1?Math.round(t/d1)+"m":e>=g1?Math.round(t/g1)+"s":t+"ms"}function U3t(t){var e=Math.abs(t);return e>=fy?cL(t,e,fy,"day"):e>=m1?cL(t,e,m1,"hour"):e>=d1?cL(t,e,d1,"minute"):e>=g1?cL(t,e,g1,"second"):t+" ms"}function cL(t,e,r,s){var a=e>=r*1.5;return Math.round(t/r)+" "+s+(a?"s":"")}});var gJ=L((Hwr,dNe)=>{function H3t(t){r.debug=r,r.default=r,r.coerce=p,r.disable=c,r.enable=a,r.enabled=f,r.humanize=gNe(),r.destroy=h,Object.keys(t).forEach(E=>{r[E]=t[E]}),r.names=[],r.skips=[],r.formatters={};function e(E){let C=0;for(let S=0;S{if(ce==="%%")return"%";ie++;let pe=r.formatters[me];if(typeof pe=="function"){let Be=N[ie];ce=pe.call(U,Be),N.splice(ie,1),ie--}return ce}),r.formatArgs.call(U,N),(U.log||r.log).apply(U,N)}return R.namespace=E,R.useColors=r.useColors(),R.color=r.selectColor(E),R.extend=s,R.destroy=r.destroy,Object.defineProperty(R,"enabled",{enumerable:!0,configurable:!1,get:()=>S!==null?S:(P!==r.namespaces&&(P=r.namespaces,I=r.enabled(E)),I),set:N=>{S=N}}),typeof r.init=="function"&&r.init(R),R}function s(E,C){let S=r(this.namespace+(typeof C>"u"?":":C)+E);return S.log=this.log,S}function a(E){r.save(E),r.namespaces=E,r.names=[],r.skips=[];let C=(typeof E=="string"?E:"").trim().replace(" ",",").split(",").filter(Boolean);for(let S of C)S[0]==="-"?r.skips.push(S.slice(1)):r.names.push(S)}function n(E,C){let S=0,P=0,I=-1,R=0;for(;S"-"+C)].join(",");return r.enable(""),E}function f(E){for(let C of r.skips)if(n(E,C))return!1;for(let C of r.names)if(n(E,C))return!0;return!1}function p(E){return E instanceof Error?E.stack||E.message:E}function h(){console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.")}return r.enable(r.load()),r}dNe.exports=H3t});var mNe=L((oc,uL)=>{oc.formatArgs=q3t;oc.save=G3t;oc.load=W3t;oc.useColors=j3t;oc.storage=Y3t();oc.destroy=(()=>{let t=!1;return()=>{t||(t=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})();oc.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function j3t(){if(typeof window<"u"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs))return!0;if(typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))return!1;let t;return typeof document<"u"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<"u"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<"u"&&navigator.userAgent&&(t=navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/))&&parseInt(t[1],10)>=31||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function q3t(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+uL.exports.humanize(this.diff),!this.useColors)return;let e="color: "+this.color;t.splice(1,0,e,"color: inherit");let r=0,s=0;t[0].replace(/%[a-zA-Z%]/g,a=>{a!=="%%"&&(r++,a==="%c"&&(s=r))}),t.splice(s,0,e)}oc.log=console.debug||console.log||(()=>{});function G3t(t){try{t?oc.storage.setItem("debug",t):oc.storage.removeItem("debug")}catch{}}function W3t(){let t;try{t=oc.storage.getItem("debug")}catch{}return!t&&typeof process<"u"&&"env"in process&&(t=process.env.DEBUG),t}function Y3t(){try{return localStorage}catch{}}uL.exports=gJ()(oc);var{formatters:V3t}=uL.exports;V3t.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}});var ENe=L(($s,AL)=>{var K3t=ye("tty"),fL=ye("util");$s.init=t8t;$s.log=X3t;$s.formatArgs=z3t;$s.save=$3t;$s.load=e8t;$s.useColors=J3t;$s.destroy=fL.deprecate(()=>{},"Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");$s.colors=[6,2,3,4,5,1];try{let t=ye("supports-color");t&&(t.stderr||t).level>=2&&($s.colors=[20,21,26,27,32,33,38,39,40,41,42,43,44,45,56,57,62,63,68,69,74,75,76,77,78,79,80,81,92,93,98,99,112,113,128,129,134,135,148,149,160,161,162,163,164,165,166,167,168,169,170,171,172,173,178,179,184,185,196,197,198,199,200,201,202,203,204,205,206,207,208,209,214,215,220,221])}catch{}$s.inspectOpts=Object.keys(process.env).filter(t=>/^debug_/i.test(t)).reduce((t,e)=>{let r=e.substring(6).toLowerCase().replace(/_([a-z])/g,(a,n)=>n.toUpperCase()),s=process.env[e];return/^(yes|on|true|enabled)$/i.test(s)?s=!0:/^(no|off|false|disabled)$/i.test(s)?s=!1:s==="null"?s=null:s=Number(s),t[r]=s,t},{});function J3t(){return"colors"in $s.inspectOpts?!!$s.inspectOpts.colors:K3t.isatty(process.stderr.fd)}function z3t(t){let{namespace:e,useColors:r}=this;if(r){let s=this.color,a="\x1B[3"+(s<8?s:"8;5;"+s),n=` ${a};1m${e} \x1B[0m`;t[0]=n+t[0].split(` +`).join(` +`+n),t.push(a+"m+"+AL.exports.humanize(this.diff)+"\x1B[0m")}else t[0]=Z3t()+e+" "+t[0]}function Z3t(){return $s.inspectOpts.hideDate?"":new Date().toISOString()+" "}function X3t(...t){return process.stderr.write(fL.formatWithOptions($s.inspectOpts,...t)+` +`)}function $3t(t){t?process.env.DEBUG=t:delete process.env.DEBUG}function e8t(){return process.env.DEBUG}function t8t(t){t.inspectOpts={};let e=Object.keys($s.inspectOpts);for(let r=0;re.trim()).join(" ")};yNe.O=function(t){return this.inspectOpts.colors=this.useColors,fL.inspect(t,this.inspectOpts)}});var mJ=L((jwr,dJ)=>{typeof process>"u"||process.type==="renderer"||process.browser===!0||process.__nwjs?dJ.exports=mNe():dJ.exports=ENe()});var hL=L(Ji=>{"use strict";Object.defineProperty(Ji,"__esModule",{value:!0});Ji.DownloadHTTPError=Ji.DownloadLengthMismatchError=Ji.DownloadError=Ji.ExpiredMetadataError=Ji.EqualVersionError=Ji.BadVersionError=Ji.RepositoryError=Ji.PersistError=Ji.RuntimeError=Ji.ValueError=void 0;var yJ=class extends Error{};Ji.ValueError=yJ;var EJ=class extends Error{};Ji.RuntimeError=EJ;var IJ=class extends Error{};Ji.PersistError=IJ;var tP=class extends Error{};Ji.RepositoryError=tP;var pL=class extends tP{};Ji.BadVersionError=pL;var CJ=class extends pL{};Ji.EqualVersionError=CJ;var wJ=class extends tP{};Ji.ExpiredMetadataError=wJ;var rP=class extends Error{};Ji.DownloadError=rP;var BJ=class extends rP{};Ji.DownloadLengthMismatchError=BJ;var vJ=class extends rP{constructor(e,r){super(e),this.statusCode=r}};Ji.DownloadHTTPError=vJ});var CNe=L(y1=>{"use strict";var DJ=y1&&y1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(y1,"__esModule",{value:!0});y1.withTempFile=void 0;var SJ=DJ(ye("fs/promises")),r8t=DJ(ye("os")),INe=DJ(ye("path")),n8t=async t=>i8t(async e=>t(INe.default.join(e,"tempfile")));y1.withTempFile=n8t;var i8t=async t=>{let e=await SJ.default.realpath(r8t.default.tmpdir()),r=await SJ.default.mkdtemp(e+INe.default.sep);try{return await t(r)}finally{await SJ.default.rm(r,{force:!0,recursive:!0,maxRetries:3})}}});var PJ=L(Qg=>{"use strict";var dL=Qg&&Qg.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Qg,"__esModule",{value:!0});Qg.DefaultFetcher=Qg.BaseFetcher=void 0;var s8t=dL(mJ()),wNe=dL(ye("fs")),o8t=dL(xO()),a8t=dL(ye("util")),BNe=hL(),l8t=CNe(),c8t=(0,s8t.default)("tuf:fetch"),gL=class{async downloadFile(e,r,s){return(0,l8t.withTempFile)(async a=>{let n=await this.fetch(e),c=0,f=wNe.default.createWriteStream(a);try{for await(let p of n){let h=Buffer.from(p);if(c+=h.length,c>r)throw new BNe.DownloadLengthMismatchError("Max length reached");await u8t(f,h)}}finally{await a8t.default.promisify(f.close).bind(f)()}return s(a)})}async downloadBytes(e,r){return this.downloadFile(e,r,async s=>{let a=wNe.default.createReadStream(s),n=[];for await(let c of a)n.push(c);return Buffer.concat(n)})}};Qg.BaseFetcher=gL;var bJ=class extends gL{constructor(e={}){super(),this.timeout=e.timeout,this.retry=e.retry}async fetch(e){c8t("GET %s",e);let r=await(0,o8t.default)(e,{timeout:this.timeout,retry:this.retry});if(!r.ok||!r?.body)throw new BNe.DownloadHTTPError("Failed to download",r.status);return r.body}};Qg.DefaultFetcher=bJ;var u8t=async(t,e)=>new Promise((r,s)=>{t.write(e,a=>{a&&s(a),r(!0)})})});var vNe=L(mL=>{"use strict";Object.defineProperty(mL,"__esModule",{value:!0});mL.defaultConfig=void 0;mL.defaultConfig={maxRootRotations:256,maxDelegations:32,rootMaxLength:512e3,timestampMaxLength:16384,snapshotMaxLength:2e6,targetsMaxLength:5e6,prefixTargetsWithHash:!0,fetchTimeout:1e5,fetchRetries:void 0,fetchRetry:2}});var SNe=L(yL=>{"use strict";Object.defineProperty(yL,"__esModule",{value:!0});yL.TrustedMetadataStore=void 0;var Is=lL(),Ui=hL(),xJ=class{constructor(e){this.trustedSet={},this.referenceTime=new Date,this.loadTrustedRoot(e)}get root(){if(!this.trustedSet.root)throw new ReferenceError("No trusted root metadata");return this.trustedSet.root}get timestamp(){return this.trustedSet.timestamp}get snapshot(){return this.trustedSet.snapshot}get targets(){return this.trustedSet.targets}getRole(e){return this.trustedSet[e]}updateRoot(e){let r=JSON.parse(e.toString("utf8")),s=Is.Metadata.fromJSON(Is.MetadataKind.Root,r);if(s.signed.type!=Is.MetadataKind.Root)throw new Ui.RepositoryError(`Expected 'root', got ${s.signed.type}`);if(this.root.verifyDelegate(Is.MetadataKind.Root,s),s.signed.version!=this.root.signed.version+1)throw new Ui.BadVersionError(`Expected version ${this.root.signed.version+1}, got ${s.signed.version}`);return s.verifyDelegate(Is.MetadataKind.Root,s),this.trustedSet.root=s,s}updateTimestamp(e){if(this.snapshot)throw new Ui.RuntimeError("Cannot update timestamp after snapshot");if(this.root.signed.isExpired(this.referenceTime))throw new Ui.ExpiredMetadataError("Final root.json is expired");let r=JSON.parse(e.toString("utf8")),s=Is.Metadata.fromJSON(Is.MetadataKind.Timestamp,r);if(s.signed.type!=Is.MetadataKind.Timestamp)throw new Ui.RepositoryError(`Expected 'timestamp', got ${s.signed.type}`);if(this.root.verifyDelegate(Is.MetadataKind.Timestamp,s),this.timestamp){if(s.signed.version{let p=n.signed.meta[c];if(!p)throw new Ui.RepositoryError(`Missing file ${c} in new snapshot`);if(p.version{"use strict";Object.defineProperty(kJ,"__esModule",{value:!0});kJ.join=A8t;var f8t=ye("url");function A8t(t,e){return new f8t.URL(p8t(t)+h8t(e)).toString()}function p8t(t){return t.endsWith("/")?t:t+"/"}function h8t(t){return t.startsWith("/")?t.slice(1):t}});var bNe=L(su=>{"use strict";var g8t=su&&su.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),d8t=su&&su.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),RJ=su&&su.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(t!=null)for(var r in t)r!=="default"&&Object.prototype.hasOwnProperty.call(t,r)&&g8t(e,t,r);return d8t(e,t),e},m8t=su&&su.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(su,"__esModule",{value:!0});su.Updater=void 0;var QA=lL(),y8t=m8t(mJ()),E1=RJ(ye("fs")),EL=RJ(ye("path")),E8t=vNe(),Ay=hL(),I8t=PJ(),C8t=SNe(),nP=RJ(DNe()),QJ=(0,y8t.default)("tuf:cache"),TJ=class{constructor(e){let{metadataDir:r,metadataBaseUrl:s,targetDir:a,targetBaseUrl:n,fetcher:c,config:f}=e;this.dir=r,this.metadataBaseUrl=s,this.targetDir=a,this.targetBaseUrl=n,this.forceCache=e.forceCache??!1;let p=this.loadLocalMetadata(QA.MetadataKind.Root);this.trustedSet=new C8t.TrustedMetadataStore(p),this.config={...E8t.defaultConfig,...f},this.fetcher=c||new I8t.DefaultFetcher({timeout:this.config.fetchTimeout,retry:this.config.fetchRetries??this.config.fetchRetry})}async refresh(){if(this.forceCache)try{await this.loadTimestamp({checkRemote:!1})}catch{await this.loadRoot(),await this.loadTimestamp()}else await this.loadRoot(),await this.loadTimestamp();await this.loadSnapshot(),await this.loadTargets(QA.MetadataKind.Targets,QA.MetadataKind.Root)}async getTargetInfo(e){return this.trustedSet.targets||await this.refresh(),this.preorderDepthFirstWalk(e)}async downloadTarget(e,r,s){let a=r||this.generateTargetPath(e);if(!s){if(!this.targetBaseUrl)throw new Ay.ValueError("Target base URL not set");s=this.targetBaseUrl}let n=e.path;if(this.trustedSet.root.signed.consistentSnapshot&&this.config.prefixTargetsWithHash){let p=Object.values(e.hashes),{dir:h,base:E}=EL.parse(n),C=`${p[0]}.${E}`;n=h?`${h}/${C}`:C}let f=nP.join(s,n);return await this.fetcher.downloadFile(f,e.length,async p=>{await e.verify(E1.createReadStream(p)),QJ("WRITE %s",a),E1.copyFileSync(p,a)}),a}async findCachedTarget(e,r){r||(r=this.generateTargetPath(e));try{if(E1.existsSync(r))return await e.verify(E1.createReadStream(r)),r}catch{return}}loadLocalMetadata(e){let r=EL.join(this.dir,`${e}.json`);return QJ("READ %s",r),E1.readFileSync(r)}async loadRoot(){let r=this.trustedSet.root.signed.version+1,s=r+this.config.maxRootRotations;for(let a=r;a0;){let{roleName:a,parentRoleName:n}=r.pop();if(s.has(a))continue;let c=(await this.loadTargets(a,n))?.signed;if(!c)continue;let f=c.targets?.[e];if(f)return f;if(s.add(a),c.delegations){let p=[],h=c.delegations.rolesForTarget(e);for(let{role:E,terminating:C}of h)if(p.push({roleName:E,parentRoleName:a}),C){r.splice(0);break}p.reverse(),r.push(...p)}}}generateTargetPath(e){if(!this.targetDir)throw new Ay.ValueError("Target directory not set");let r=encodeURIComponent(e.path);return EL.join(this.targetDir,r)}persistMetadata(e,r){let s=encodeURIComponent(e);try{let a=EL.join(this.dir,`${s}.json`);QJ("WRITE %s",a),E1.writeFileSync(a,r.toString("utf8"))}catch(a){throw new Ay.PersistError(`Failed to persist metadata ${s} error: ${a}`)}}};su.Updater=TJ});var PNe=L(Tg=>{"use strict";Object.defineProperty(Tg,"__esModule",{value:!0});Tg.Updater=Tg.BaseFetcher=Tg.TargetFile=void 0;var w8t=lL();Object.defineProperty(Tg,"TargetFile",{enumerable:!0,get:function(){return w8t.TargetFile}});var B8t=PJ();Object.defineProperty(Tg,"BaseFetcher",{enumerable:!0,get:function(){return B8t.BaseFetcher}});var v8t=bNe();Object.defineProperty(Tg,"Updater",{enumerable:!0,get:function(){return v8t.Updater}})});var NJ=L(IL=>{"use strict";Object.defineProperty(IL,"__esModule",{value:!0});IL.TUFError=void 0;var FJ=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}};IL.TUFError=FJ});var xNe=L(iP=>{"use strict";var S8t=iP&&iP.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(iP,"__esModule",{value:!0});iP.readTarget=b8t;var D8t=S8t(ye("fs")),CL=NJ();async function b8t(t,e){let r=await P8t(t,e);return new Promise((s,a)=>{D8t.default.readFile(r,"utf-8",(n,c)=>{n?a(new CL.TUFError({code:"TUF_READ_TARGET_ERROR",message:`error reading target ${r}`,cause:n})):s(c)})})}async function P8t(t,e){let r;try{r=await t.getTargetInfo(e)}catch(a){throw new CL.TUFError({code:"TUF_REFRESH_METADATA_ERROR",message:"error refreshing TUF metadata",cause:a})}if(!r)throw new CL.TUFError({code:"TUF_FIND_TARGET_ERROR",message:`target ${e} not found`});let s=await t.findCachedTarget(r);if(!s)try{s=await t.downloadTarget(r)}catch(a){throw new CL.TUFError({code:"TUF_DOWNLOAD_TARGET_ERROR",message:`error downloading target ${s}`,cause:a})}return s}});var kNe=L(($wr,x8t)=>{x8t.exports={"https://tuf-repo-cdn.sigstore.dev":{"root.json":"ewogInNpZ25hdHVyZXMiOiBbCiAgewogICAia2V5aWQiOiAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICJzaWciOiAiMzA0NjAyMjEwMDhhYjFmNmYxN2Q0ZjllNmQ3ZGNmMWM4ODkxMmI2YjUzY2MxMDM4ODY0NGFlMWYwOWJjMzdhMDgyY2QwNjAwM2UwMjIxMDBlMTQ1ZWY0YzdiNzgyZDRlODEwN2I1MzQzN2U2NjlkMDQ3Njg5MmNlOTk5OTAzYWUzM2QxNDQ0ODM2Njk5NmU3IgogIH0sCiAgewogICAia2V5aWQiOiAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICJzaWciOiAiMzA0NTAyMjEwMGM3NjhiMmY4NmRhOTk1NjkwMTljMTYwYTA4MWRhNTRhZTM2YzM0YzBhMzEyMGQzY2I2OWI1M2I3ZDExMzc1OGUwMjIwNGY2NzE1MThmNjE3YjIwZDQ2NTM3ZmFlNmMzYjYzYmFlODkxM2Y0ZjE5NjIxNTYxMDVjYzRmMDE5YWMzNWM2YSIKICB9LAogIHsKICAgImtleWlkIjogIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAic2lnIjogIjMwNDUwMjIxMDBiNDQzNGU2OTk1ZDM2OGQyM2U3NDc1OWFjZDBjYjkwMTNjODNhNWQzNTExZjBmOTk3ZWM1NGM0NTZhZTQzNTBhMDIyMDE1YjBlMjY1ZDE4MmQyYjYxZGM3NGUxNTVkOThiM2MzZmJlNTY0YmEwNTI4NmFhMTRjOGRmMDJjOWI3NTY1MTYiCiAgfSwKICB7CiAgICJrZXlpZCI6ICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgInNpZyI6ICIzMDQ1MDIyMTAwODJjNTg0MTFkOTg5ZWI5Zjg2MTQxMDg1N2Q0MjM4MTU5MGVjOTQyNGRiZGFhNTFlNzhlZDEzNTE1NDMxOTA0ZTAyMjAxMTgxODVkYTZhNmMyOTQ3MTMxYzE3Nzk3ZTJiYjc2MjBjZTI2ZTVmMzAxZDFjZWFjNWYyYTdlNThmOWRjZjJlIgogIH0sCiAgewogICAia2V5aWQiOiAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIsCiAgICJzaWciOiAiMzA0NjAyMjEwMGM3ODUxMzg1NGNhZTljMzJlYWE2Yjg4ZTE4OTEyZjQ4MDA2YzI3NTdhMjU4ZjkxNzMxMmNhYmE3NTk0OGViOWUwMjIxMDBkOWUxYjRjZTBhZGZlOWZkMmUyMTQ4ZDdmYTI3YTJmNDBiYTExMjJiZDY5ZGE3NjEyZDhkMTc3NmIwMTNjOTFkIgogIH0sCiAgewogICAia2V5aWQiOiAiZmRmYTgzYTA3YjVhODM1ODliODdkZWQ0MWY3N2YzOWQyMzJhZDkxZjdjY2U1Mjg2OGRhY2QwNmJhMDg5ODQ5ZiIsCiAgICJzaWciOiAiMzA0NTAyMjA1NjQ4M2EyZDVkOWVhOWNlYzZlMTFlYWRmYjMzYzQ4NGI2MTQyOThmYWNhMTVhY2YxYzQzMWIxMWVkN2Y3MzRjMDIyMTAwZDBjMWQ3MjZhZjkyYTg3ZTRlNjY0NTljYTVhZGYzOGEwNWI0NGUxZjk0MzE4NDIzZjk1NGJhZThiY2E1YmIyZSIKICB9LAogIHsKICAgImtleWlkIjogImUyZjU5YWNiOTQ4ODUxOTQwN2UxOGNiZmM5MzI5NTEwYmUwM2MwNGFjYTk5MjlkMmYwMzAxMzQzZmVjODU1MjMiLAogICAic2lnIjogIjMwNDYwMjIxMDBkMDA0ZGU4ODAyNGMzMmRjNTY1M2E5ZjQ4NDNjZmM1MjE1NDI3MDQ4YWQ5NjAwZDJjZjljOTY5ZTZlZGZmM2QyMDIyMTAwZDllYmI3OThmNWZjNjZhZjEwODk5ZGVjZTAxNGE4NjI4Y2NmM2M1NDAyY2Q0YTQyNzAyMDc0NzJmOGY2ZTcxMiIKICB9LAogIHsKICAgImtleWlkIjogIjNjMzQ0YWEwNjhmZDRjYzRlODdkYzUwYjYxMmMwMjQzMWZiYzc3MWU5NTAwMzk5MzY4M2EyYjBiZjI2MGNmMGUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiN2IwOTk5NmM0NWNhMmQ0YjA1NjAzZTU2YmFlZmEyOTcxOGEwYjcxMTQ3Y2Y4YzZlNjYzNDliYWE2MTQ3N2RmMDIyMTAwYzRkYTgwYzcxN2I0ZmE3YmJhMGZkNWM3MmRhOGEwNDk5MzU4YjAxMzU4YjIzMDlmNDFkMTQ1NmVhMWU3ZTFkOSIKICB9LAogIHsKICAgImtleWlkIjogImVjODE2Njk3MzRlMDE3OTk2YzViODVmM2QwMmMzZGUxZGQ0NjM3YTE1MjAxOWZlMWFmMTI1ZDJmOTM2OGI5NWUiLAogICAic2lnIjogIjMwNDYwMjIxMDBiZTk3ODJjMzA3NDRlNDExYTgyZmE4NWI1MTM4ZDYwMWNlMTQ4YmMxOTI1OGFlYzY0ZTdlYzI0NDc4ZjM4ODEyMDIyMTAwY2FlZjYzZGNhZjFhNGI5YTUwMGQzYmQwZTNmMTY0ZWMxOGYxYjYzZDdhOTQ2MGQ5YWNhYjEwNjZkYjBmMDE2ZCIKICB9LAogIHsKICAgImtleWlkIjogIjFlMWQ2NWNlOThiMTBhZGRhZDQ3NjRmZWJmN2RkYTJkMDQzNmIzZDNhMzg5MzU3OWMwZGRkYWVhMjBlNTQ4NDkiLAogICAic2lnIjogIjMwNDUwMjIwNzQ2ZWMzZjg1MzRjZTU1NTMxZDBkMDFmZjY0OTY0ZWY0NDBkMWU3ZDJjNGMxNDI0MDliOGU5NzY5ZjFhZGE2ZjAyMjEwMGUzYjkyOWZjZDkzZWExOGZlYWEwODI1ODg3YTcyMTA0ODk4NzlhNjY3ODBjMDdhODNmNGJkNDZlMmYwOWFiM2IiCiAgfQogXSwKICJzaWduZWQiOiB7CiAgIl90eXBlIjogInJvb3QiLAogICJjb25zaXN0ZW50X3NuYXBzaG90IjogdHJ1ZSwKICAiZXhwaXJlcyI6ICIyMDI1LTAyLTE5VDA4OjA0OjMyWiIsCiAgImtleXMiOiB7CiAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFekJ6Vk9tSENQb2pNVkxTSTM2NFdpaVY4TlByRFxuNklnUnhWbGlza3ovdit5M0pFUjVtY1ZHY09ObGlEY1dNQzVKMmxmSG1qUE5QaGI0SDd4bThMemZTQT09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBzYW50aWFnb3RvcnJlcyIKICAgfSwKICAgIjYxNjQzODM4MTI1YjQ0MGI0MGRiNjk0MmY1Y2I1YTMxYzBkYzA0MzY4MzE2ZWIyYWFhNThiOTU5MDRhNTgyMjIiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVpbmlrU3NBUW1Za05lSDVlWXEvQ25JekxhYWNPXG54bFNhYXdRRE93cUt5L3RDcXhxNXh4UFNKYzIxSzRXSWhzOUd5T2tLZnp1ZVkzR0lMemNNSlo0Y1d3PT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2kta2V5b3duZXIiOiAiQGJvYmNhbGxhd2F5IgogICB9LAogICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXk4WEtzbWhCWURJOEpjMEd3ekJ4ZUtheDBjbTVcblNUS0VVNjVIUEZ1blVuNDFzVDhwaTBGak00SWtIei9ZVW13bUxVTzBXdDdseGhqNkJrTElLNHFZQXc9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAZGxvcmVuYyIKICAgfSwKICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiOiB7CiAgICAia2V5aWRfaGFzaF9hbGdvcml0aG1zIjogWwogICAgICJzaGEyNTYiLAogICAgICJzaGE1MTIiCiAgICBdLAogICAgImtleXR5cGUiOiAiZWNkc2EiLAogICAgImtleXZhbCI6IHsKICAgICAicHVibGljIjogIi0tLS0tQkVHSU4gUFVCTElDIEtFWS0tLS0tXG5NRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVXUmlHcjUraiszSjVTc0grWnRyNW5FMkgyd083XG5CVituTzNzOTNnTGNhMThxVE96SFkxb1d5QUdEeWtNU3NHVFVCU3Q5RCtBbjBLZktzRDJtZlNNNDJRPT1cbi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLVxuIgogICAgfSwKICAgICJzY2hlbWUiOiAiZWNkc2Etc2hhMi1uaXN0cDI1NiIsCiAgICAieC10dWYtb24tY2ktb25saW5lLXVyaSI6ICJnY3BrbXM6Ly9wcm9qZWN0cy9zaWdzdG9yZS1yb290LXNpZ25pbmcvbG9jYXRpb25zL2dsb2JhbC9rZXlSaW5ncy9yb290L2NyeXB0b0tleXMvdGltZXN0YW1wIgogICB9LAogICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCI6IHsKICAgICJrZXlpZF9oYXNoX2FsZ29yaXRobXMiOiBbCiAgICAgInNoYTI1NiIsCiAgICAgInNoYTUxMiIKICAgIF0sCiAgICAia2V5dHlwZSI6ICJlY2RzYSIsCiAgICAia2V5dmFsIjogewogICAgICJwdWJsaWMiOiAiLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS1cbk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTBnaHJoOTJMdzFZcjNpZEdWNVdxQ3RNREI4Q3hcbitEOGhkQzR3MlpMTklwbFZSb1ZHTHNrWWEzZ2hlTXlPamlKOGtQaTE1YVEyLy83UCtvajdVdkpQR3c9PVxuLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tXG4iCiAgICB9LAogICAgInNjaGVtZSI6ICJlY2RzYS1zaGEyLW5pc3RwMjU2IiwKICAgICJ4LXR1Zi1vbi1jaS1rZXlvd25lciI6ICJAam9zaHVhZ2wiCiAgIH0sCiAgICJlNzFhNTRkNTQzODM1YmE4NmFkYWQ5NDYwMzc5Yzc2NDFmYjg3MjZkMTY0ZWE3NjY4MDFhMWM1MjJhYmE3ZWEyIjogewogICAgImtleWlkX2hhc2hfYWxnb3JpdGhtcyI6IFsKICAgICAic2hhMjU2IiwKICAgICAic2hhNTEyIgogICAgXSwKICAgICJrZXl0eXBlIjogImVjZHNhIiwKICAgICJrZXl2YWwiOiB7CiAgICAgInB1YmxpYyI6ICItLS0tLUJFR0lOIFBVQkxJQyBLRVktLS0tLVxuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFRVhzejNTWlhGYjhqTVY0Mmo2cEpseWpialI4S1xuTjNCd29jZXhxNkxNSWI1cXNXS09RdkxOMTZOVWVmTGM0SHN3T291bVJzVlZhYWpTcFFTNmZvYmtSdz09XG4tLS0tLUVORCBQVUJMSUMgS0VZLS0tLS1cbiIKICAgIH0sCiAgICAic2NoZW1lIjogImVjZHNhLXNoYTItbmlzdHAyNTYiLAogICAgIngtdHVmLW9uLWNpLWtleW93bmVyIjogIkBtbm02NzgiCiAgIH0KICB9LAogICJyb2xlcyI6IHsKICAgInJvb3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI2ZjI2MDA4OWQ1OTIzZGFmMjAxNjZjYTY1N2M1NDNhZjYxODM0NmFiOTcxODg0YTk5OTYyYjAxOTg4YmJlMGMzIiwKICAgICAiZTcxYTU0ZDU0MzgzNWJhODZhZGFkOTQ2MDM3OWM3NjQxZmI4NzI2ZDE2NGVhNzY2ODAxYTFjNTIyYWJhN2VhMiIsCiAgICAgIjIyZjRjYWVjNmQ4ZTZmOTU1NWFmNjZiM2Q0YzNjYjA2YTNiYjIzZmRjN2UzOWM5MTZjNjFmNDYyZTZmNTJiMDYiLAogICAgICI2MTY0MzgzODEyNWI0NDBiNDBkYjY5NDJmNWNiNWEzMWMwZGMwNDM2ODMxNmViMmFhYTU4Yjk1OTA0YTU4MjIyIiwKICAgICAiYTY4N2U1YmY0ZmFiODJiMGVlNThkNDZlMDVjOTUzNTE0NWEyYzlhZmI0NThmNDNkNDJiNDVjYTBmZGNlMmE3MCIKICAgIF0sCiAgICAidGhyZXNob2xkIjogMwogICB9LAogICAic25hcHNob3QiOiB7CiAgICAia2V5aWRzIjogWwogICAgICI3MjQ3ZjBkYmFkODViMTQ3ZTE4NjNiYWRlNzYxMjQzY2M3ODVkY2I3YWE0MTBlNzEwNWRkM2QyYjYxYTM2ZDJjIgogICAgXSwKICAgICJ0aHJlc2hvbGQiOiAxLAogICAgIngtdHVmLW9uLWNpLWV4cGlyeS1wZXJpb2QiOiAzNjUwLAogICAgIngtdHVmLW9uLWNpLXNpZ25pbmctcGVyaW9kIjogMzY1CiAgIH0sCiAgICJ0YXJnZXRzIjogewogICAgImtleWlkcyI6IFsKICAgICAiNmYyNjAwODlkNTkyM2RhZjIwMTY2Y2E2NTdjNTQzYWY2MTgzNDZhYjk3MTg4NGE5OTk2MmIwMTk4OGJiZTBjMyIsCiAgICAgImU3MWE1NGQ1NDM4MzViYTg2YWRhZDk0NjAzNzljNzY0MWZiODcyNmQxNjRlYTc2NjgwMWExYzUyMmFiYTdlYTIiLAogICAgICIyMmY0Y2FlYzZkOGU2Zjk1NTVhZjY2YjNkNGMzY2IwNmEzYmIyM2ZkYzdlMzljOTE2YzYxZjQ2MmU2ZjUyYjA2IiwKICAgICAiNjE2NDM4MzgxMjViNDQwYjQwZGI2OTQyZjVjYjVhMzFjMGRjMDQzNjgzMTZlYjJhYWE1OGI5NTkwNGE1ODIyMiIsCiAgICAgImE2ODdlNWJmNGZhYjgyYjBlZTU4ZDQ2ZTA1Yzk1MzUxNDVhMmM5YWZiNDU4ZjQzZDQyYjQ1Y2EwZmRjZTJhNzAiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDMKICAgfSwKICAgInRpbWVzdGFtcCI6IHsKICAgICJrZXlpZHMiOiBbCiAgICAgIjcyNDdmMGRiYWQ4NWIxNDdlMTg2M2JhZGU3NjEyNDNjYzc4NWRjYjdhYTQxMGU3MTA1ZGQzZDJiNjFhMzZkMmMiCiAgICBdLAogICAgInRocmVzaG9sZCI6IDEsCiAgICAieC10dWYtb24tY2ktZXhwaXJ5LXBlcmlvZCI6IDcsCiAgICAieC10dWYtb24tY2ktc2lnbmluZy1wZXJpb2QiOiA0CiAgIH0KICB9LAogICJzcGVjX3ZlcnNpb24iOiAiMS4wIiwKICAidmVyc2lvbiI6IDEwLAogICJ4LXR1Zi1vbi1jaS1leHBpcnktcGVyaW9kIjogMTgyLAogICJ4LXR1Zi1vbi1jaS1zaWduaW5nLXBlcmlvZCI6IDMxCiB9Cn0=",targets:{"trusted_root.json":"ewogICJtZWRpYVR5cGUiOiAiYXBwbGljYXRpb24vdm5kLmRldi5zaWdzdG9yZS50cnVzdGVkcm9vdCtqc29uO3ZlcnNpb249MC4xIiwKICAidGxvZ3MiOiBbCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vcmVrb3Iuc2lnc3RvcmUuZGV2IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUyRzJZKzJ0YWJkVFY1QmNHaUJJeDBhOWZBRndya0JibUxTR3RrczRMM3FYNnlZWTB6dWZCbmhDOFVyL2l5NTVHaFdQLzlBL2JZMkxoQzMwTTkrUll0dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDEtMTJUMTE6NTM6MjcuMDAwWiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAid05JOWF0UUdseitWV2ZPNkxSeWdINFFVZlkvOFc0UkZ3aVQ1aTVXUmdCMD0iCiAgICAgIH0KICAgIH0KICBdLAogICJjZXJ0aWZpY2F0ZUF1dGhvcml0aWVzIjogWwogICAgewogICAgICAic3ViamVjdCI6IHsKICAgICAgICAib3JnYW5pemF0aW9uIjogInNpZ3N0b3JlLmRldiIsCiAgICAgICAgImNvbW1vbk5hbWUiOiAic2lnc3RvcmUiCiAgICAgIH0sCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly9mdWxjaW8uc2lnc3RvcmUuZGV2IiwKICAgICAgImNlcnRDaGFpbiI6IHsKICAgICAgICAiY2VydGlmaWNhdGVzIjogWwogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQitEQ0NBWDZnQXdJQkFnSVROVmtEWm9DaW9mUERzeTdkZm02Z2VMYnVoekFLQmdncWhrak9QUVFEQXpBcU1SVXdFd1lEVlFRS0V3eHphV2R6ZEc5eVpTNWtaWFl4RVRBUEJnTlZCQU1UQ0hOcFozTjBiM0psTUI0WERUSXhNRE13TnpBek1qQXlPVm9YRFRNeE1ESXlNekF6TWpBeU9Wb3dLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkxTeUE3SWk1aytwTk84WkVXWTB5bGVtV0Rvd09rTmEza0wrR1pFNVo1R1dlaEw5L0E5YlJOQTNSYnJzWjVpMEpjYXN0YVJMN1NwNWZwL2pENWR4cWMvVWRUVm5sdlMxNmFuKzJZZnN3ZS9RdUxvbFJVQ3JjT0UyKzJpQTUrdHpkNk5tTUdRd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFFd0hRWURWUjBPQkJZRUZNakZIUUJCbWlRcE1sRWs2dzJ1U3UxS0J0UHNNQjhHQTFVZEl3UVlNQmFBRk1qRkhRQkJtaVFwTWxFazZ3MnVTdTFLQnRQc01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01IOGxpV0pmTXVpNnZYWEJoakRnWTRNd3NsbU4vVEp4VmUvODNXckZvbXdtTmYwNTZ5MVg0OEY5YzRtM2Ezb3pYQUl4QUtqUmF5NS9hai9qc0tLR0lrbVFhdGpJOHV1cEhyLytDeEZ2YUpXbXBZcU5rTERHUlUrOW9yemg1aEkyUnJjdWFRPT0iCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMDdUMDM6MjA6MjkuMDAwWiIsCiAgICAgICAgImVuZCI6ICIyMDIyLTEyLTMxVDIzOjU5OjU5Ljk5OVoiCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAic2lnc3RvcmUuZGV2IiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJzaWdzdG9yZSIKICAgICAgfSwKICAgICAgInVyaSI6ICJodHRwczovL2Z1bGNpby5zaWdzdG9yZS5kZXYiLAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlDR2pDQ0FhR2dBd0lCQWdJVUFMblZpVmZuVTBickphc21Sa0hybi9VbmZhUXdDZ1lJS29aSXpqMEVBd013S2pFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUkV3RHdZRFZRUURFd2h6YVdkemRHOXlaVEFlRncweU1qQTBNVE15TURBMk1UVmFGdzB6TVRFd01EVXhNelUyTlRoYU1EY3hGVEFUQmdOVkJBb1RESE5wWjNOMGIzSmxMbVJsZGpFZU1Cd0dBMVVFQXhNVmMybG5jM1J2Y21VdGFXNTBaWEp0WldScFlYUmxNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRThSVlMveXNIK05PdnVEWnlQSVp0aWxnVUY5TmxhcllwQWQ5SFAxdkJCSDFVNUNWNzdMU1M3czBaaUg0bkU3SHY3cHRTNkx2dlIvU1RrNzk4TFZnTXpMbEo0SGVJZkYzdEhTYWV4TGNZcFNBU3Ixa1MwTi9SZ0JKei85aldDaVhubzNzd2VUQU9CZ05WSFE4QkFmOEVCQU1DQVFZd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFkQmdOVkhRNEVGZ1FVMzlQcHoxWWtFWmI1cU5qcEtGV2l4aTRZWkQ4d0h3WURWUjBqQkJnd0ZvQVVXTUFlWDVGRnBXYXBlc3lRb1pNaTBDckZ4Zm93Q2dZSUtvWkl6ajBFQXdNRFp3QXdaQUl3UENzUUs0RFlpWllEUElhRGk1SEZLbmZ4WHg2QVNTVm1FUmZzeW5ZQmlYMlg2U0pSblpVODQvOURaZG5GdnZ4bUFqQk90NlFwQmxjNEovMER4dmtUQ3FwY2x2emlMNkJDQ1BuamRsSUIzUHUzQnhzUG15Z1VZN0lpMnpiZENkbGlpb3c9IgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUI5ekNDQVh5Z0F3SUJBZ0lVQUxaTkFQRmR4SFB3amVEbG9Ed3lZQ2hBTy80d0NnWUlLb1pJemowRUF3TXdLakVWTUJNR0ExVUVDaE1NYzJsbmMzUnZjbVV1WkdWMk1SRXdEd1lEVlFRREV3aHphV2R6ZEc5eVpUQWVGdzB5TVRFd01EY3hNelUyTlRsYUZ3MHpNVEV3TURVeE16VTJOVGhhTUNveEZUQVRCZ05WQkFvVERITnBaM04wYjNKbExtUmxkakVSTUE4R0ExVUVBeE1JYzJsbmMzUnZjbVV3ZGpBUUJnY3Foa2pPUFFJQkJnVXJnUVFBSWdOaUFBVDdYZUZUNHJiM1BRR3dTNElhanRMazMvT2xucGdhbmdhQmNsWXBzWUJyNWkrNHluQjA3Y2ViM0xQME9JT1pkeGV4WDY5YzVpVnV5SlJRK0h6MDV5aStVRjN1QldBbEhwaVM1c2gwK0gyR0hFN1NYcmsxRUM1bTFUcjE5TDlnZzkyall6QmhNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01CMEdBMVVkRGdRV0JCUll3QjVma1VXbFpxbDZ6SkNoa3lMUUtzWEYrakFmQmdOVkhTTUVHREFXZ0JSWXdCNWZrVVdsWnFsNnpKQ2hreUxRS3NYRitqQUtCZ2dxaGtqT1BRUURBd05wQURCbUFqRUFqMW5IZVhacCsxM05XQk5hK0VEc0RQOEcxV1dnMXRDTVdQL1dIUHFwYVZvMGpoc3dlTkZaZ1NzMGVFN3dZSTRxQWpFQTJXQjlvdDk4c0lrb0YzdlpZZGQzL1Z0V0I1YjlUTk1lYTdJeC9zdEo1VGZjTExlQUJMRTRCTkpPc1E0dm5CSEoiCiAgICAgICAgICB9CiAgICAgICAgXQogICAgICB9LAogICAgICAidmFsaWRGb3IiOiB7CiAgICAgICAgInN0YXJ0IjogIjIwMjItMDQtMTNUMjA6MDY6MTUuMDAwWiIKICAgICAgfQogICAgfQogIF0sCiAgImN0bG9ncyI6IFsKICAgIHsKICAgICAgImJhc2VVcmwiOiAiaHR0cHM6Ly9jdGZlLnNpZ3N0b3JlLmRldi90ZXN0IiwKICAgICAgImhhc2hBbGdvcml0aG0iOiAiU0hBMl8yNTYiLAogICAgICAicHVibGljS2V5IjogewogICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUViZndSK1JKdWRYc2NnUkJScEtYMVhGRHkzUHl1ZER4ei9TZm5SaTFmVDhla3BmQmQyTzF1b3o3anIzWjhuS3p4QTY5RVVRK2VGQ0ZJM3pldWJQV1U3dz09IiwKICAgICAgICAia2V5RGV0YWlscyI6ICJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsCiAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgInN0YXJ0IjogIjIwMjEtMDMtMTRUMDA6MDA6MDAuMDAwWiIsCiAgICAgICAgICAiZW5kIjogIjIwMjItMTAtMzFUMjM6NTk6NTkuOTk5WiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJsb2dJZCI6IHsKICAgICAgICAia2V5SWQiOiAiQ0dDUzhDaFMvMmhGMGRGcko0U2NSV2NZckJZOXd6alNiZWE4SWdZMmIzST0iCiAgICAgIH0KICAgIH0sCiAgICB7CiAgICAgICJiYXNlVXJsIjogImh0dHBzOi8vY3RmZS5zaWdzdG9yZS5kZXYvMjAyMiIsCiAgICAgICJoYXNoQWxnb3JpdGhtIjogIlNIQTJfMjU2IiwKICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAicmF3Qnl0ZXMiOiAiTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFaVBTbEZpMENtRlRmRWpDVXFGOUh1Q0VjWVhOS0FhWWFsSUptQlo4eXllelBqVHFoeHJLQnBNbmFvY1Z0TEpCSTFlTTN1WG5RelFHQUpkSjRnczlGeXc9PSIsCiAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICJzdGFydCI6ICIyMDIyLTEwLTIwVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgfQogICAgICB9LAogICAgICAibG9nSWQiOiB7CiAgICAgICAgImtleUlkIjogIjNUMHdhc2JIRVRKakdSNGNtV2MzQXFKS1hyamVQSzMvaDRweWdDOHA3bzQ9IgogICAgICB9CiAgICB9CiAgXSwKICAidGltZXN0YW1wQXV0aG9yaXRpZXMiOiBbCiAgICB7CiAgICAgICJzdWJqZWN0IjogewogICAgICAgICJvcmdhbml6YXRpb24iOiAiR2l0SHViLCBJbmMuIiwKICAgICAgICAiY29tbW9uTmFtZSI6ICJJbnRlcm5hbCBTZXJ2aWNlcyBSb290IgogICAgICB9LAogICAgICAiY2VydENoYWluIjogewogICAgICAgICJjZXJ0aWZpY2F0ZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNSUlCM0RDQ0FXS2dBd0lCQWdJVWNoa05zSDM2WGEwNGIxTHFJYytxcjlEVmVjTXdDZ1lJS29aSXpqMEVBd013TWpFVk1CTUdBMVVFQ2hNTVIybDBTSFZpTENCSmJtTXVNUmt3RndZRFZRUURFeEJVVTBFZ2FXNTBaWEp0WldScFlYUmxNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVEkwTURReE16QXdNREF3TUZvd01qRVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVJrd0Z3WURWUVFERXhCVVUwRWdWR2x0WlhOMFlXMXdhVzVuTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFVUQ1Wk5iU3FZTWQ2cjhxcE9PRVg5aWJHblpUOUdzdVhPaHIvZjhVOUZKdWdCR0V4S1lwNDBPVUxTMGVyalpXN3hWOXhWNTJObkpmNU9lRHE0ZTVaS3FOV01GUXdEZ1lEVlIwUEFRSC9CQVFEQWdlQU1CTUdBMVVkSlFRTU1Bb0dDQ3NHQVFVRkJ3TUlNQXdHQTFVZEV3RUIvd1FDTUFBd0h3WURWUjBqQkJnd0ZvQVVhVzFSdWRPZ1Z0MGxlcVkwV0tZYnVQcjQ3d0F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl3YlVIOUh2RDRlakNaSk9XUW5xQWxrcVVSbGx2dTlNOCtWcUxiaVJLK3pTZlpDWndzaWxqUm44TVFRUlNrWEVFNUFqRUFnK1Z4cXRvamZWZnU4RGh6emhDeDlHS0VUYkpIYjE5aVY3Mm1NS1ViREFGbXpaNmJROGI1NFpiOHRpZHk1YVdlIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgInJhd0J5dGVzIjogIk1JSUNFRENDQVpXZ0F3SUJBZ0lVWDhaTzVRWFA3dk40ZE1RNWU5c1UzbnViOE9nd0NnWUlLb1pJemowRUF3TXdPREVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1SOHdIUVlEVlFRREV4WkpiblJsY201aGJDQlRaWEoyYVdObGN5QlNiMjkwTUI0WERUSXpNRFF4TkRBd01EQXdNRm9YRFRJNE1EUXhNakF3TURBd01Gb3dNakVWTUJNR0ExVUVDaE1NUjJsMFNIVmlMQ0JKYm1NdU1Sa3dGd1lEVlFRREV4QlVVMEVnYVc1MFpYSnRaV1JwWVhSbE1IWXdFQVlIS29aSXpqMENBUVlGSzRFRUFDSURZZ0FFdk1MWS9kVFZidklKWUFOQXVzekV3Sm5RRTFsbGZ0eW55TUtJTWhoNDhIbXFiVnI1eWd5YnpzTFJMVktiQldPZFoyMWFlSnorZ1ppeXRaZXRxY3lGOVdsRVI1TkVNZjZKVjdaTm9qUXB4SHE0UkhHb0dTY2VRdi9xdlRpWnhFREtvMll3WkRBT0JnTlZIUThCQWY4RUJBTUNBUVl3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBZEJnTlZIUTRFRmdRVWFXMVJ1ZE9nVnQwbGVxWTBXS1lidVByNDd3QXdId1lEVlIwakJCZ3dGb0FVOU5ZWWxvYm5BRzRjMC9xanh5SC9scS93eitRd0NnWUlLb1pJemowRUF3TURhUUF3WmdJeEFLMUIxODV5Z0NySVlGbElzM0dqc3dqbndTTUc2TFk4d29MVmRha0tEWnhWYThmOGNxTXMxRGhjeEowKzA5dzk1UUl4QU8rdEJ6Wms3dmpVSjlpSmdENFI2WldUeFFXS3FObTc0ak85OW8rbzlzdjRGSS9TWlRaVEZ5TW4wSUpFSGRObXlBPT0iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAicmF3Qnl0ZXMiOiAiTUlJQjlEQ0NBWHFnQXdJQkFnSVVhL0pBa2RVaks0SlV3c3F0YWlSSkdXaHFMU293Q2dZSUtvWkl6ajBFQXdNd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNQjRYRFRJek1EUXhOREF3TURBd01Gb1hEVE16TURReE1UQXdNREF3TUZvd09ERVZNQk1HQTFVRUNoTU1SMmwwU0hWaUxDQkpibU11TVI4d0hRWURWUVFERXhaSmJuUmxjbTVoYkNCVFpYSjJhV05sY3lCU2IyOTBNSFl3RUFZSEtvWkl6ajBDQVFZRks0RUVBQ0lEWWdBRWY5akZBWHh6NGt4NjhBSFJNT2tGQmhmbERjTVR2emFYejR4L0ZDY1hqSi8xcUVLb24vcVBJR25hVVJza0R0eU5iTkRPcGVKVERERnF0NDhpTVBybnpweDZJWndxZW1mVUpONHhCRVpmemErcFl0L2l5b2QrOXRacjIwUlJXU3YvbzBVd1F6QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0VnWURWUjBUQVFIL0JBZ3dCZ0VCL3dJQkFqQWRCZ05WSFE0RUZnUVU5TllZbG9ibkFHNGMwL3FqeHlIL2xxL3d6K1F3Q2dZSUtvWkl6ajBFQXdNRGFBQXdaUUl4QUxaTFo4QmdSWHpLeExNTU45VklsTytlNGhyQm5OQmdGN3R6N0hucm93djJOZXRaRXJJQUNLRnltQmx2V0R2dE1BSXdaTytraTZzc1ExYnNabzk4TzhtRUFmMk5aN2lpQ2dERFUwVndqZWNvNnp5ZWgwekJUczkvN2dWNkFITlE1M3hEIgogICAgICAgICAgfQogICAgICAgIF0KICAgICAgfSwKICAgICAgInZhbGlkRm9yIjogewogICAgICAgICJzdGFydCI6ICIyMDIzLTA0LTE0VDAwOjAwOjAwLjAwMFoiCiAgICAgIH0KICAgIH0KICBdCn0K","registry.npmjs.org%2Fkeys.json":"ewogICAgImtleXMiOiBbCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OmpsM2J3c3d1ODBQampva0NnaDBvMnc1YzJVNExoUUFFNTdnajljejFrekEiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTFPbGIzek1BRkZ4WEtIaUlrUU81Y0ozWWhsNWk2VVBwK0lodXRlQkpidUhjQTVVb2dLbzBFV3RsV3dXNktTYUtvVE5FWUw3SmxDUWlWbmtoQmt0VWdnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIxOTk5LTAxLTAxVDAwOjAwOjAwLjAwMFoiLAogICAgICAgICAgICAgICAgICAgICJlbmQiOiAiMjAyNS0wMS0yOVQwMDowMDowMC4wMDBaIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICB7CiAgICAgICAgICAgICJrZXlJZCI6ICJTSEEyNTY6amwzYndzd3U4MFBqam9rQ2doMG8ydzVjMlU0TGhRQUU1N2dqOWN6MWt6QSIsCiAgICAgICAgICAgICJrZXlVc2FnZSI6ICJucG06YXR0ZXN0YXRpb25zIiwKICAgICAgICAgICAgInB1YmxpY0tleSI6IHsKICAgICAgICAgICAgICAgICJyYXdCeXRlcyI6ICJNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUUxT2xiM3pNQUZGeFhLSGlJa1FPNWNKM1lobDVpNlVQcCtJaHV0ZUJKYnVIY0E1VW9nS28wRVd0bFd3VzZLU2FLb1RORVlMN0psQ1FpVm5raEJrdFVnZz09IiwKICAgICAgICAgICAgICAgICJrZXlEZXRhaWxzIjogIlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwKICAgICAgICAgICAgICAgICJ2YWxpZEZvciI6IHsKICAgICAgICAgICAgICAgICAgICAic3RhcnQiOiAiMjAyMi0xMi0wMVQwMDowMDowMC4wMDBaIiwKICAgICAgICAgICAgICAgICAgICAiZW5kIjogIjIwMjUtMDEtMjlUMDA6MDA6MDAuMDAwWiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgewogICAgICAgICAgICAia2V5SWQiOiAiU0hBMjU2OkRoUTh3UjVBUEJ2RkhMRi8rVGMrQVl2UE9kVHBjSURxT2h4c0JIUndDN1UiLAogICAgICAgICAgICAia2V5VXNhZ2UiOiAibnBtOnNpZ25hdHVyZXMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgIHsKICAgICAgICAgICAgImtleUlkIjogIlNIQTI1NjpEaFE4d1I1QVBCdkZITEYvK1RjK0FZdlBPZFRwY0lEcU9oeHNCSFJ3QzdVIiwKICAgICAgICAgICAgImtleVVzYWdlIjogIm5wbTphdHRlc3RhdGlvbnMiLAogICAgICAgICAgICAicHVibGljS2V5IjogewogICAgICAgICAgICAgICAgInJhd0J5dGVzIjogIk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVk2WWE3VysrN2FVUHp2TVRyZXpINlljeDNjK0hPS1lDY05HeWJKWlNDSnEvZmQ3UWE4dXVBS3RkSWtVUXRRaUVLRVJoQW1FNWxNTUpoUDhPa0RPYTJnPT0iLAogICAgICAgICAgICAgICAgImtleURldGFpbHMiOiAiUEtJWF9FQ0RTQV9QMjU2X1NIQV8yNTYiLAogICAgICAgICAgICAgICAgInZhbGlkRm9yIjogewogICAgICAgICAgICAgICAgICAgICJzdGFydCI6ICIyMDI1LTAxLTEzVDAwOjAwOjAwLjAwMFoiCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICBdCn0K"}}}});var TNe=L(I1=>{"use strict";var QNe=I1&&I1.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(I1,"__esModule",{value:!0});I1.TUFClient=void 0;var Rg=QNe(ye("fs")),sP=QNe(ye("path")),k8t=PNe(),Q8t=wL(),T8t=xNe(),LJ="targets",OJ=class{constructor(e){let r=new URL(e.mirrorURL),s=encodeURIComponent(r.host+r.pathname.replace(/\/$/,"")),a=sP.default.join(e.cachePath,s);R8t(a),F8t({cachePath:a,mirrorURL:e.mirrorURL,tufRootPath:e.rootPath,forceInit:e.forceInit}),this.updater=N8t({mirrorURL:e.mirrorURL,cachePath:a,forceCache:e.forceCache,retry:e.retry,timeout:e.timeout})}async refresh(){return this.updater.refresh()}getTarget(e){return(0,T8t.readTarget)(this.updater,e)}};I1.TUFClient=OJ;function R8t(t){let e=sP.default.join(t,LJ);Rg.default.existsSync(t)||Rg.default.mkdirSync(t,{recursive:!0}),Rg.default.existsSync(e)||Rg.default.mkdirSync(e)}function F8t({cachePath:t,mirrorURL:e,tufRootPath:r,forceInit:s}){let a=sP.default.join(t,"root.json");if(!Rg.default.existsSync(a)||s)if(r)Rg.default.copyFileSync(r,a);else{let c=kNe()[e];if(!c)throw new Q8t.TUFError({code:"TUF_INIT_CACHE_ERROR",message:`No root.json found for mirror: ${e}`});Rg.default.writeFileSync(a,Buffer.from(c["root.json"],"base64")),Object.entries(c.targets).forEach(([f,p])=>{Rg.default.writeFileSync(sP.default.join(t,LJ,f),Buffer.from(p,"base64"))})}}function N8t(t){let e={fetchTimeout:t.timeout,fetchRetry:t.retry};return new k8t.Updater({metadataBaseUrl:t.mirrorURL,targetBaseUrl:`${t.mirrorURL}/targets`,metadataDir:t.cachePath,targetDir:sP.default.join(t.cachePath,LJ),forceCache:t.forceCache,config:e})}});var wL=L(yh=>{"use strict";Object.defineProperty(yh,"__esModule",{value:!0});yh.TUFError=yh.DEFAULT_MIRROR_URL=void 0;yh.getTrustedRoot=q8t;yh.initTUF=G8t;var O8t=Rb(),L8t=fFe(),M8t=TNe();yh.DEFAULT_MIRROR_URL="https://tuf-repo-cdn.sigstore.dev";var _8t="sigstore-js",U8t={retries:2},H8t=5e3,j8t="trusted_root.json";async function q8t(t={}){let r=await RNe(t).getTarget(j8t);return O8t.TrustedRoot.fromJSON(JSON.parse(r))}async function G8t(t={}){let e=RNe(t);return e.refresh().then(()=>e)}function RNe(t){return new M8t.TUFClient({cachePath:t.cachePath||(0,L8t.appDataPath)(_8t),rootPath:t.rootPath,mirrorURL:t.mirrorURL||yh.DEFAULT_MIRROR_URL,retry:t.retry??U8t,timeout:t.timeout??H8t,forceCache:t.forceCache??!1,forceInit:t.forceInit??t.force??!1})}var W8t=NJ();Object.defineProperty(yh,"TUFError",{enumerable:!0,get:function(){return W8t.TUFError}})});var FNe=L(BL=>{"use strict";Object.defineProperty(BL,"__esModule",{value:!0});BL.DSSESignatureContent=void 0;var oP=wl(),MJ=class{constructor(e){this.env=e}compareDigest(e){return oP.crypto.bufferEqual(e,oP.crypto.digest("sha256",this.env.payload))}compareSignature(e){return oP.crypto.bufferEqual(e,this.signature)}verifySignature(e){return oP.crypto.verify(this.preAuthEncoding,e,this.signature)}get signature(){return this.env.signatures.length>0?this.env.signatures[0].sig:Buffer.from("")}get preAuthEncoding(){return oP.dsse.preAuthEncoding(this.env.payloadType,this.env.payload)}};BL.DSSESignatureContent=MJ});var NNe=L(vL=>{"use strict";Object.defineProperty(vL,"__esModule",{value:!0});vL.MessageSignatureContent=void 0;var _J=wl(),UJ=class{constructor(e,r){this.signature=e.signature,this.messageDigest=e.messageDigest.digest,this.artifact=r}compareSignature(e){return _J.crypto.bufferEqual(e,this.signature)}compareDigest(e){return _J.crypto.bufferEqual(e,this.messageDigest)}verifySignature(e){return _J.crypto.verify(this.artifact,e,this.signature)}};vL.MessageSignatureContent=UJ});var LNe=L(SL=>{"use strict";Object.defineProperty(SL,"__esModule",{value:!0});SL.toSignedEntity=K8t;SL.signatureContent=ONe;var HJ=wl(),Y8t=FNe(),V8t=NNe();function K8t(t,e){let{tlogEntries:r,timestampVerificationData:s}=t.verificationMaterial,a=[];for(let n of r)a.push({$case:"transparency-log",tlogEntry:n});for(let n of s?.rfc3161Timestamps??[])a.push({$case:"timestamp-authority",timestamp:HJ.RFC3161Timestamp.parse(n.signedTimestamp)});return{signature:ONe(t,e),key:J8t(t),tlogEntries:r,timestamps:a}}function ONe(t,e){switch(t.content.$case){case"dsseEnvelope":return new Y8t.DSSESignatureContent(t.content.dsseEnvelope);case"messageSignature":return new V8t.MessageSignatureContent(t.content.messageSignature,e)}}function J8t(t){switch(t.verificationMaterial.content.$case){case"publicKey":return{$case:"public-key",hint:t.verificationMaterial.content.publicKey.hint};case"x509CertificateChain":return{$case:"certificate",certificate:HJ.X509Certificate.parse(t.verificationMaterial.content.x509CertificateChain.certificates[0].rawBytes)};case"certificate":return{$case:"certificate",certificate:HJ.X509Certificate.parse(t.verificationMaterial.content.certificate.rawBytes)}}}});var Co=L(C1=>{"use strict";Object.defineProperty(C1,"__esModule",{value:!0});C1.PolicyError=C1.VerificationError=void 0;var DL=class extends Error{constructor({code:e,message:r,cause:s}){super(r),this.code=e,this.cause=s,this.name=this.constructor.name}},jJ=class extends DL{};C1.VerificationError=jJ;var qJ=class extends DL{};C1.PolicyError=qJ});var MNe=L(bL=>{"use strict";Object.defineProperty(bL,"__esModule",{value:!0});bL.filterCertAuthorities=z8t;bL.filterTLogAuthorities=Z8t;function z8t(t,e){return t.filter(r=>r.validFor.start<=e.start&&r.validFor.end>=e.end)}function Z8t(t,e){return t.filter(r=>e.logID&&!r.logID.equals(e.logID)?!1:r.validFor.start<=e.targetDate&&e.targetDate<=r.validFor.end)}});var hy=L(py=>{"use strict";Object.defineProperty(py,"__esModule",{value:!0});py.filterTLogAuthorities=py.filterCertAuthorities=void 0;py.toTrustMaterial=$8t;var GJ=wl(),aP=Rb(),X8t=Co(),WJ=new Date(0),YJ=new Date(864e13),HNe=MNe();Object.defineProperty(py,"filterCertAuthorities",{enumerable:!0,get:function(){return HNe.filterCertAuthorities}});Object.defineProperty(py,"filterTLogAuthorities",{enumerable:!0,get:function(){return HNe.filterTLogAuthorities}});function $8t(t,e){let r=typeof e=="function"?e:eHt(e);return{certificateAuthorities:t.certificateAuthorities.map(UNe),timestampAuthorities:t.timestampAuthorities.map(UNe),tlogs:t.tlogs.map(_Ne),ctlogs:t.ctlogs.map(_Ne),publicKey:r}}function _Ne(t){let e=t.publicKey.keyDetails,r=e===aP.PublicKeyDetails.PKCS1_RSA_PKCS1V5||e===aP.PublicKeyDetails.PKIX_RSA_PKCS1V5||e===aP.PublicKeyDetails.PKIX_RSA_PKCS1V15_2048_SHA256||e===aP.PublicKeyDetails.PKIX_RSA_PKCS1V15_3072_SHA256||e===aP.PublicKeyDetails.PKIX_RSA_PKCS1V15_4096_SHA256?"pkcs1":"spki";return{logID:t.logId.keyId,publicKey:GJ.crypto.createPublicKey(t.publicKey.rawBytes,r),validFor:{start:t.publicKey.validFor?.start||WJ,end:t.publicKey.validFor?.end||YJ}}}function UNe(t){return{certChain:t.certChain.certificates.map(e=>GJ.X509Certificate.parse(e.rawBytes)),validFor:{start:t.validFor?.start||WJ,end:t.validFor?.end||YJ}}}function eHt(t){return e=>{let r=(t||{})[e];if(!r)throw new X8t.VerificationError({code:"PUBLIC_KEY_ERROR",message:`key not found: ${e}`});return{publicKey:GJ.crypto.createPublicKey(r.rawBytes),validFor:s=>(r.validFor?.start||WJ)<=s&&(r.validFor?.end||YJ)>=s}}}});var VJ=L(lP=>{"use strict";Object.defineProperty(lP,"__esModule",{value:!0});lP.CertificateChainVerifier=void 0;lP.verifyCertificateChain=rHt;var gy=Co(),tHt=hy();function rHt(t,e){let r=(0,tHt.filterCertAuthorities)(e,{start:t.notBefore,end:t.notAfter}),s;for(let a of r)try{return new PL({trustedCerts:a.certChain,untrustedCert:t}).verify()}catch(n){s=n}throw new gy.VerificationError({code:"CERTIFICATE_ERROR",message:"Failed to verify certificate chain",cause:s})}var PL=class{constructor(e){this.untrustedCert=e.untrustedCert,this.trustedCerts=e.trustedCerts,this.localCerts=nHt([...e.trustedCerts,e.untrustedCert])}verify(){let e=this.sort();return this.checkPath(e),e}sort(){let e=this.untrustedCert,r=this.buildPaths(e);if(r=r.filter(a=>a.some(n=>this.trustedCerts.includes(n))),r.length===0)throw new gy.VerificationError({code:"CERTIFICATE_ERROR",message:"no trusted certificate path found"});let s=r.reduce((a,n)=>a.length{if(s&&a.extSubjectKeyID){a.extSubjectKeyID.keyIdentifier.equals(s)&&r.push(a);return}a.subject.equals(e.issuer)&&r.push(a)}),r=r.filter(a=>{try{return e.verify(a)}catch{return!1}}),r)}checkPath(e){if(e.length<1)throw new gy.VerificationError({code:"CERTIFICATE_ERROR",message:"certificate chain must contain at least one certificate"});if(!e.slice(1).every(s=>s.isCA))throw new gy.VerificationError({code:"CERTIFICATE_ERROR",message:"intermediate certificate is not a CA"});for(let s=e.length-2;s>=0;s--)if(!e[s].issuer.equals(e[s+1].subject))throw new gy.VerificationError({code:"CERTIFICATE_ERROR",message:"incorrect certificate name chaining"});for(let s=0;s{"use strict";Object.defineProperty(KJ,"__esModule",{value:!0});KJ.verifySCTs=oHt;var xL=wl(),iHt=Co(),sHt=hy();function oHt(t,e,r){let s,a=t.clone();for(let p=0;p{if(!(0,sHt.filterTLogAuthorities)(r,{logID:p.logID,targetDate:p.datetime}).some(C=>p.verify(n.buffer,C.publicKey)))throw new iHt.VerificationError({code:"CERTIFICATE_ERROR",message:"SCT verification failed"});return p.logID})}});var GNe=L(kL=>{"use strict";Object.defineProperty(kL,"__esModule",{value:!0});kL.verifyPublicKey=AHt;kL.verifyCertificate=pHt;var aHt=wl(),qNe=Co(),lHt=VJ(),cHt=jNe(),uHt="1.3.6.1.4.1.57264.1.1",fHt="1.3.6.1.4.1.57264.1.8";function AHt(t,e,r){let s=r.publicKey(t);return e.forEach(a=>{if(!s.validFor(a))throw new qNe.VerificationError({code:"PUBLIC_KEY_ERROR",message:`Public key is not valid for timestamp: ${a.toISOString()}`})}),{key:s.publicKey}}function pHt(t,e,r){let s=(0,lHt.verifyCertificateChain)(t,r.certificateAuthorities);if(!e.every(n=>s.every(c=>c.validForDate(n))))throw new qNe.VerificationError({code:"CERTIFICATE_ERROR",message:"certificate is not valid or expired at the specified date"});return{scts:(0,cHt.verifySCTs)(s[0],s[1],r.ctlogs),signer:hHt(s[0])}}function hHt(t){let e,r=t.extension(fHt);r?e=r.valueObj.subs?.[0]?.value.toString("ascii"):e=t.extension(uHt)?.value.toString("ascii");let s={extensions:{issuer:e},subjectAlternativeName:t.subjectAltName};return{key:aHt.crypto.createPublicKey(t.publicKey),identity:s}}});var YNe=L(QL=>{"use strict";Object.defineProperty(QL,"__esModule",{value:!0});QL.verifySubjectAlternativeName=gHt;QL.verifyExtensions=dHt;var WNe=Co();function gHt(t,e){if(e===void 0||!e.match(t))throw new WNe.PolicyError({code:"UNTRUSTED_SIGNER_ERROR",message:`certificate identity error - expected ${t}, got ${e}`})}function dHt(t,e={}){let r;for(r in t)if(e[r]!==t[r])throw new WNe.PolicyError({code:"UNTRUSTED_SIGNER_ERROR",message:`invalid certificate extension - expected ${r}=${t[r]}, got ${r}=${e[r]}`})}});var VNe=L($J=>{"use strict";Object.defineProperty($J,"__esModule",{value:!0});$J.verifyCheckpoint=EHt;var zJ=wl(),w1=Co(),mHt=hy(),JJ=` + +`,yHt=/\u2014 (\S+) (\S+)\n/g;function EHt(t,e){let r=(0,mHt.filterTLogAuthorities)(e,{targetDate:new Date(Number(t.integratedTime)*1e3)}),s=t.inclusionProof,a=ZJ.fromString(s.checkpoint.envelope),n=XJ.fromString(a.note);if(!IHt(a,r))throw new w1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"invalid checkpoint signature"});if(!zJ.crypto.bufferEqual(n.logHash,s.rootHash))throw new w1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"root hash mismatch"})}function IHt(t,e){let r=Buffer.from(t.note,"utf-8");return t.signatures.every(s=>{let a=e.find(n=>zJ.crypto.bufferEqual(n.logID.subarray(0,4),s.keyHint));return a?zJ.crypto.verify(r,a.publicKey,s.signature):!1})}var ZJ=class t{constructor(e,r){this.note=e,this.signatures=r}static fromString(e){if(!e.includes(JJ))throw new w1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"missing checkpoint separator"});let r=e.indexOf(JJ),s=e.slice(0,r+1),n=e.slice(r+JJ.length).matchAll(yHt),c=Array.from(n,f=>{let[,p,h]=f,E=Buffer.from(h,"base64");if(E.length<5)throw new w1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"malformed checkpoint signature"});return{name:p,keyHint:E.subarray(0,4),signature:E.subarray(4)}});if(c.length===0)throw new w1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"no signatures found in checkpoint"});return new t(s,c)}},XJ=class t{constructor(e,r,s,a){this.origin=e,this.logSize=r,this.logHash=s,this.rest=a}static fromString(e){let r=e.trimEnd().split(` +`);if(r.length<3)throw new w1.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"too few lines in checkpoint header"});let s=r[0],a=BigInt(r[1]),n=Buffer.from(r[2],"base64"),c=r.slice(3);return new t(s,a,n,c)}}});var KNe=L(nz=>{"use strict";Object.defineProperty(nz,"__esModule",{value:!0});nz.verifyMerkleInclusion=BHt;var rz=wl(),ez=Co(),CHt=Buffer.from([0]),wHt=Buffer.from([1]);function BHt(t){let e=t.inclusionProof,r=BigInt(e.logIndex),s=BigInt(e.treeSize);if(r<0n||r>=s)throw new ez.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:`invalid index: ${r}`});let{inner:a,border:n}=vHt(r,s);if(e.hashes.length!==a+n)throw new ez.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"invalid hash count"});let c=e.hashes.slice(0,a),f=e.hashes.slice(a),p=kHt(t.canonicalizedBody),h=DHt(SHt(p,c,r),f);if(!rz.crypto.bufferEqual(h,e.rootHash))throw new ez.VerificationError({code:"TLOG_INCLUSION_PROOF_ERROR",message:"calculated root hash does not match inclusion proof"})}function vHt(t,e){let r=bHt(t,e),s=PHt(t>>BigInt(r));return{inner:r,border:s}}function SHt(t,e,r){return e.reduce((s,a,n)=>r>>BigInt(n)&BigInt(1)?tz(a,s):tz(s,a),t)}function DHt(t,e){return e.reduce((r,s)=>tz(s,r),t)}function bHt(t,e){return xHt(t^e-BigInt(1))}function PHt(t){return t.toString(2).split("1").length-1}function xHt(t){return t===0n?0:t.toString(2).length}function tz(t,e){return rz.crypto.digest("sha256",wHt,t,e)}function kHt(t){return rz.crypto.digest("sha256",CHt,t)}});var zNe=L(iz=>{"use strict";Object.defineProperty(iz,"__esModule",{value:!0});iz.verifyTLogSET=RHt;var JNe=wl(),QHt=Co(),THt=hy();function RHt(t,e){if(!(0,THt.filterTLogAuthorities)(e,{logID:t.logId.keyId,targetDate:new Date(Number(t.integratedTime)*1e3)}).some(a=>{let n=FHt(t),c=Buffer.from(JNe.json.canonicalize(n),"utf8"),f=t.inclusionPromise.signedEntryTimestamp;return JNe.crypto.verify(c,a.publicKey,f)}))throw new QHt.VerificationError({code:"TLOG_INCLUSION_PROMISE_ERROR",message:"inclusion promise could not be verified"})}function FHt(t){let{integratedTime:e,logIndex:r,logId:s,canonicalizedBody:a}=t;return{body:a.toString("base64"),integratedTime:Number(e),logIndex:Number(r),logID:s.keyId.toString("hex")}}});var ZNe=L(az=>{"use strict";Object.defineProperty(az,"__esModule",{value:!0});az.verifyRFC3161Timestamp=LHt;var sz=wl(),oz=Co(),NHt=VJ(),OHt=hy();function LHt(t,e,r){let s=t.signingTime;if(r=(0,OHt.filterCertAuthorities)(r,{start:s,end:s}),r=_Ht(r,{serialNumber:t.signerSerialNumber,issuer:t.signerIssuer}),!r.some(n=>{try{return MHt(t,e,n),!0}catch{return!1}}))throw new oz.VerificationError({code:"TIMESTAMP_ERROR",message:"timestamp could not be verified"})}function MHt(t,e,r){let[s,...a]=r.certChain,n=sz.crypto.createPublicKey(s.publicKey),c=t.signingTime;try{new NHt.CertificateChainVerifier({untrustedCert:s,trustedCerts:a}).verify()}catch{throw new oz.VerificationError({code:"TIMESTAMP_ERROR",message:"invalid certificate chain"})}if(!r.certChain.every(p=>p.validForDate(c)))throw new oz.VerificationError({code:"TIMESTAMP_ERROR",message:"timestamp was signed with an expired certificate"});t.verify(e,n)}function _Ht(t,e){return t.filter(r=>r.certChain.length>0&&sz.crypto.bufferEqual(r.certChain[0].serialNumber,e.serialNumber)&&sz.crypto.bufferEqual(r.certChain[0].issuer,e.issuer))}});var XNe=L(TL=>{"use strict";Object.defineProperty(TL,"__esModule",{value:!0});TL.verifyTSATimestamp=WHt;TL.verifyTLogTimestamp=YHt;var UHt=Co(),HHt=VNe(),jHt=KNe(),qHt=zNe(),GHt=ZNe();function WHt(t,e,r){return(0,GHt.verifyRFC3161Timestamp)(t,e,r),{type:"timestamp-authority",logID:t.signerSerialNumber,timestamp:t.signingTime}}function YHt(t,e){let r=!1;if(VHt(t)&&((0,qHt.verifyTLogSET)(t,e),r=!0),KHt(t)&&((0,jHt.verifyMerkleInclusion)(t),(0,HHt.verifyCheckpoint)(t,e),r=!0),!r)throw new UHt.VerificationError({code:"TLOG_MISSING_INCLUSION_ERROR",message:"inclusion could not be verified"});return{type:"transparency-log",logID:t.logId.keyId,timestamp:new Date(Number(t.integratedTime)*1e3)}}function VHt(t){return t.inclusionPromise!==void 0}function KHt(t){return t.inclusionProof!==void 0}});var $Ne=L(lz=>{"use strict";Object.defineProperty(lz,"__esModule",{value:!0});lz.verifyDSSETLogBody=JHt;var RL=Co();function JHt(t,e){switch(t.apiVersion){case"0.0.1":return zHt(t,e);default:throw new RL.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported dsse version: ${t.apiVersion}`})}}function zHt(t,e){if(t.spec.signatures?.length!==1)throw new RL.VerificationError({code:"TLOG_BODY_ERROR",message:"signature count mismatch"});let r=t.spec.signatures[0].signature;if(!e.compareSignature(Buffer.from(r,"base64")))throw new RL.VerificationError({code:"TLOG_BODY_ERROR",message:"tlog entry signature mismatch"});let s=t.spec.payloadHash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new RL.VerificationError({code:"TLOG_BODY_ERROR",message:"DSSE payload hash mismatch"})}});var eOe=L(uz=>{"use strict";Object.defineProperty(uz,"__esModule",{value:!0});uz.verifyHashedRekordTLogBody=ZHt;var cz=Co();function ZHt(t,e){switch(t.apiVersion){case"0.0.1":return XHt(t,e);default:throw new cz.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported hashedrekord version: ${t.apiVersion}`})}}function XHt(t,e){let r=t.spec.signature.content||"";if(!e.compareSignature(Buffer.from(r,"base64")))throw new cz.VerificationError({code:"TLOG_BODY_ERROR",message:"signature mismatch"});let s=t.spec.data.hash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new cz.VerificationError({code:"TLOG_BODY_ERROR",message:"digest mismatch"})}});var tOe=L(fz=>{"use strict";Object.defineProperty(fz,"__esModule",{value:!0});fz.verifyIntotoTLogBody=$Ht;var FL=Co();function $Ht(t,e){switch(t.apiVersion){case"0.0.2":return ejt(t,e);default:throw new FL.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported intoto version: ${t.apiVersion}`})}}function ejt(t,e){if(t.spec.content.envelope.signatures?.length!==1)throw new FL.VerificationError({code:"TLOG_BODY_ERROR",message:"signature count mismatch"});let r=tjt(t.spec.content.envelope.signatures[0].sig);if(!e.compareSignature(Buffer.from(r,"base64")))throw new FL.VerificationError({code:"TLOG_BODY_ERROR",message:"tlog entry signature mismatch"});let s=t.spec.content.payloadHash?.value||"";if(!e.compareDigest(Buffer.from(s,"hex")))throw new FL.VerificationError({code:"TLOG_BODY_ERROR",message:"DSSE payload hash mismatch"})}function tjt(t){return Buffer.from(t,"base64").toString("utf-8")}});var nOe=L(Az=>{"use strict";Object.defineProperty(Az,"__esModule",{value:!0});Az.verifyTLogBody=sjt;var rOe=Co(),rjt=$Ne(),njt=eOe(),ijt=tOe();function sjt(t,e){let{kind:r,version:s}=t.kindVersion,a=JSON.parse(t.canonicalizedBody.toString("utf8"));if(r!==a.kind||s!==a.apiVersion)throw new rOe.VerificationError({code:"TLOG_BODY_ERROR",message:`kind/version mismatch - expected: ${r}/${s}, received: ${a.kind}/${a.apiVersion}`});switch(a.kind){case"dsse":return(0,rjt.verifyDSSETLogBody)(a,e);case"intoto":return(0,ijt.verifyIntotoTLogBody)(a,e);case"hashedrekord":return(0,njt.verifyHashedRekordTLogBody)(a,e);default:throw new rOe.VerificationError({code:"TLOG_BODY_ERROR",message:`unsupported kind: ${r}`})}}});var lOe=L(NL=>{"use strict";Object.defineProperty(NL,"__esModule",{value:!0});NL.Verifier=void 0;var ojt=ye("util"),B1=Co(),iOe=GNe(),sOe=YNe(),oOe=XNe(),ajt=nOe(),pz=class{constructor(e,r={}){this.trustMaterial=e,this.options={ctlogThreshold:r.ctlogThreshold??1,tlogThreshold:r.tlogThreshold??1,tsaThreshold:r.tsaThreshold??0}}verify(e,r){let s=this.verifyTimestamps(e),a=this.verifySigningKey(e,s);return this.verifyTLogs(e),this.verifySignature(e,a),r&&this.verifyPolicy(r,a.identity||{}),a}verifyTimestamps(e){let r=0,s=0,a=e.timestamps.map(n=>{switch(n.$case){case"timestamp-authority":return s++,(0,oOe.verifyTSATimestamp)(n.timestamp,e.signature.signature,this.trustMaterial.timestampAuthorities);case"transparency-log":return r++,(0,oOe.verifyTLogTimestamp)(n.tlogEntry,this.trustMaterial.tlogs)}});if(aOe(a))throw new B1.VerificationError({code:"TIMESTAMP_ERROR",message:"duplicate timestamp"});if(rn.timestamp)}verifySigningKey({key:e},r){switch(e.$case){case"public-key":return(0,iOe.verifyPublicKey)(e.hint,r,this.trustMaterial);case"certificate":{let s=(0,iOe.verifyCertificate)(e.certificate,r,this.trustMaterial);if(aOe(s.scts))throw new B1.VerificationError({code:"CERTIFICATE_ERROR",message:"duplicate SCT"});if(s.scts.length(0,ajt.verifyTLogBody)(s,e))}verifySignature(e,r){if(!e.signature.verifySignature(r.key))throw new B1.VerificationError({code:"SIGNATURE_ERROR",message:"signature verification failed"})}verifyPolicy(e,r){e.subjectAlternativeName&&(0,sOe.verifySubjectAlternativeName)(e.subjectAlternativeName,r.subjectAlternativeName),e.extensions&&(0,sOe.verifyExtensions)(e.extensions,r.extensions)}};NL.Verifier=pz;function aOe(t){for(let e=0;e{"use strict";Object.defineProperty(ou,"__esModule",{value:!0});ou.Verifier=ou.toTrustMaterial=ou.VerificationError=ou.PolicyError=ou.toSignedEntity=void 0;var ljt=LNe();Object.defineProperty(ou,"toSignedEntity",{enumerable:!0,get:function(){return ljt.toSignedEntity}});var cOe=Co();Object.defineProperty(ou,"PolicyError",{enumerable:!0,get:function(){return cOe.PolicyError}});Object.defineProperty(ou,"VerificationError",{enumerable:!0,get:function(){return cOe.VerificationError}});var cjt=hy();Object.defineProperty(ou,"toTrustMaterial",{enumerable:!0,get:function(){return cjt.toTrustMaterial}});var ujt=lOe();Object.defineProperty(ou,"Verifier",{enumerable:!0,get:function(){return ujt.Verifier}})});var uOe=L(Na=>{"use strict";Object.defineProperty(Na,"__esModule",{value:!0});Na.DEFAULT_TIMEOUT=Na.DEFAULT_RETRY=void 0;Na.createBundleBuilder=pjt;Na.createKeyFinder=hjt;Na.createVerificationPolicy=gjt;var fjt=wl(),v1=SK(),Ajt=OL();Na.DEFAULT_RETRY={retries:2};Na.DEFAULT_TIMEOUT=5e3;function pjt(t,e){let r={signer:djt(e),witnesses:yjt(e)};switch(t){case"messageSignature":return new v1.MessageSignatureBundleBuilder(r);case"dsseEnvelope":return new v1.DSSEBundleBuilder({...r,certificateChain:e.legacyCompatibility})}}function hjt(t){return e=>{let r=t(e);if(!r)throw new Ajt.VerificationError({code:"PUBLIC_KEY_ERROR",message:`key not found: ${e}`});return{publicKey:fjt.crypto.createPublicKey(r),validFor:()=>!0}}}function gjt(t){let e={},r=t.certificateIdentityEmail||t.certificateIdentityURI;return r&&(e.subjectAlternativeName=r),t.certificateIssuer&&(e.extensions={issuer:t.certificateIssuer}),e}function djt(t){return new v1.FulcioSigner({fulcioBaseURL:t.fulcioURL,identityProvider:t.identityProvider||mjt(t),retry:t.retry??Na.DEFAULT_RETRY,timeout:t.timeout??Na.DEFAULT_TIMEOUT})}function mjt(t){let e=t.identityToken;return e?{getToken:()=>Promise.resolve(e)}:new v1.CIContextProvider("sigstore")}function yjt(t){let e=[];return Ejt(t)&&e.push(new v1.RekorWitness({rekorBaseURL:t.rekorURL,entryType:t.legacyCompatibility?"intoto":"dsse",fetchOnConflict:!1,retry:t.retry??Na.DEFAULT_RETRY,timeout:t.timeout??Na.DEFAULT_TIMEOUT})),Ijt(t)&&e.push(new v1.TSAWitness({tsaBaseURL:t.tsaServerURL,retry:t.retry??Na.DEFAULT_RETRY,timeout:t.timeout??Na.DEFAULT_TIMEOUT})),e}function Ejt(t){return t.tlogUpload!==!1}function Ijt(t){return t.tsaServerURL!==void 0}});var pOe=L(au=>{"use strict";var Cjt=au&&au.__createBinding||(Object.create?function(t,e,r,s){s===void 0&&(s=r);var a=Object.getOwnPropertyDescriptor(e,r);(!a||("get"in a?!e.__esModule:a.writable||a.configurable))&&(a={enumerable:!0,get:function(){return e[r]}}),Object.defineProperty(t,s,a)}:function(t,e,r,s){s===void 0&&(s=r),t[s]=e[r]}),wjt=au&&au.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),fOe=au&&au.__importStar||function(){var t=function(e){return t=Object.getOwnPropertyNames||function(r){var s=[];for(var a in r)Object.prototype.hasOwnProperty.call(r,a)&&(s[s.length]=a);return s},t(e)};return function(e){if(e&&e.__esModule)return e;var r={};if(e!=null)for(var s=t(e),a=0;aa.verify(t,s))}async function AOe(t={}){let e=await Bjt.getTrustedRoot({mirrorURL:t.tufMirrorURL,rootPath:t.tufRootPath,cachePath:t.tufCachePath,forceCache:t.tufForceCache,retry:t.retry??S1.DEFAULT_RETRY,timeout:t.timeout??S1.DEFAULT_TIMEOUT}),r=t.keySelector?S1.createKeyFinder(t.keySelector):void 0,s=(0,hz.toTrustMaterial)(e,r),a={ctlogThreshold:t.ctLogThreshold,tlogThreshold:t.tlogThreshold},n=new hz.Verifier(s,a),c=S1.createVerificationPolicy(t);return{verify:(f,p)=>{let h=(0,gz.bundleFromJSON)(f),E=(0,hz.toSignedEntity)(h,p);n.verify(E,c)}}}});var gOe=L(Fi=>{"use strict";Object.defineProperty(Fi,"__esModule",{value:!0});Fi.verify=Fi.sign=Fi.createVerifier=Fi.attest=Fi.VerificationError=Fi.PolicyError=Fi.TUFError=Fi.InternalError=Fi.DEFAULT_REKOR_URL=Fi.DEFAULT_FULCIO_URL=Fi.ValidationError=void 0;var bjt=Nb();Object.defineProperty(Fi,"ValidationError",{enumerable:!0,get:function(){return bjt.ValidationError}});var dz=SK();Object.defineProperty(Fi,"DEFAULT_FULCIO_URL",{enumerable:!0,get:function(){return dz.DEFAULT_FULCIO_URL}});Object.defineProperty(Fi,"DEFAULT_REKOR_URL",{enumerable:!0,get:function(){return dz.DEFAULT_REKOR_URL}});Object.defineProperty(Fi,"InternalError",{enumerable:!0,get:function(){return dz.InternalError}});var Pjt=wL();Object.defineProperty(Fi,"TUFError",{enumerable:!0,get:function(){return Pjt.TUFError}});var hOe=OL();Object.defineProperty(Fi,"PolicyError",{enumerable:!0,get:function(){return hOe.PolicyError}});Object.defineProperty(Fi,"VerificationError",{enumerable:!0,get:function(){return hOe.VerificationError}});var LL=pOe();Object.defineProperty(Fi,"attest",{enumerable:!0,get:function(){return LL.attest}});Object.defineProperty(Fi,"createVerifier",{enumerable:!0,get:function(){return LL.createVerifier}});Object.defineProperty(Fi,"sign",{enumerable:!0,get:function(){return LL.sign}});Object.defineProperty(Fi,"verify",{enumerable:!0,get:function(){return LL.verify}})});var GOe=L((xSr,qOe)=>{var M6t=t3();function _6t(t){return M6t(t)?void 0:t}qOe.exports=_6t});var YOe=L((kSr,WOe)=>{var U6t=RT(),H6t=x5(),j6t=R5(),q6t=wm(),G6t=Jd(),W6t=GOe(),Y6t=BG(),V6t=P5(),K6t=1,J6t=2,z6t=4,Z6t=Y6t(function(t,e){var r={};if(t==null)return r;var s=!1;e=U6t(e,function(n){return n=q6t(n,t),s||(s=n.length>1),n}),G6t(t,V6t(t),r),s&&(r=H6t(r,K6t|J6t|z6t,W6t));for(var a=e.length;a--;)j6t(r,e[a]);return r});WOe.exports=Z6t});bt();Ve();bt();var ZOe=ye("child_process"),XOe=et(Nd());Wt();var tC=new Map([]);var iS={};Vt(iS,{BaseCommand:()=>ut,WorkspaceRequiredError:()=>ar,getCli:()=>cwe,getDynamicLibs:()=>lwe,getPluginConfiguration:()=>nC,openWorkspace:()=>rC,pluginCommands:()=>tC,runExit:()=>zR});Wt();var ut=class extends ot{constructor(){super(...arguments);this.cwd=ge.String("--cwd",{hidden:!0})}validateAndExecute(){if(typeof this.cwd<"u")throw new nt("The --cwd option is ambiguous when used anywhere else than the very first parameter provided in the command line, before even the command path");return super.validateAndExecute()}};Ve();bt();Wt();var ar=class extends nt{constructor(e,r){let s=K.relative(e,r),a=K.join(e,Ht.fileName);super(`This command can only be run from within a workspace of your project (${s} isn't a workspace of ${a}).`)}};Ve();bt();rA();Bc();bv();Wt();var zwt=et(fi());Ul();var lwe=()=>new Map([["@yarnpkg/cli",iS],["@yarnpkg/core",nS],["@yarnpkg/fslib",q2],["@yarnpkg/libzip",Sv],["@yarnpkg/parsers",Z2],["@yarnpkg/shell",Qv],["clipanion",cB],["semver",zwt],["typanion",Ia]]);Ve();async function rC(t,e){let{project:r,workspace:s}=await Tt.find(t,e);if(!s)throw new ar(r.cwd,e);return s}Ve();bt();rA();Bc();bv();Wt();var oqt=et(fi());Ul();var $5={};Vt($5,{AddCommand:()=>aC,BinCommand:()=>lC,CacheCleanCommand:()=>cC,ClipanionCommand:()=>gC,ConfigCommand:()=>pC,ConfigGetCommand:()=>uC,ConfigSetCommand:()=>fC,ConfigUnsetCommand:()=>AC,DedupeCommand:()=>hC,EntryCommand:()=>mC,ExecCommand:()=>EC,ExplainCommand:()=>wC,ExplainPeerRequirementsCommand:()=>IC,HelpCommand:()=>dC,InfoCommand:()=>BC,LinkCommand:()=>SC,NodeCommand:()=>DC,PluginCheckCommand:()=>bC,PluginImportCommand:()=>kC,PluginImportSourcesCommand:()=>QC,PluginListCommand:()=>PC,PluginRemoveCommand:()=>TC,PluginRuntimeCommand:()=>RC,RebuildCommand:()=>FC,RemoveCommand:()=>NC,RunCommand:()=>LC,RunIndexCommand:()=>OC,SetResolutionCommand:()=>MC,SetVersionCommand:()=>CC,SetVersionSourcesCommand:()=>xC,UnlinkCommand:()=>_C,UpCommand:()=>UC,VersionCommand:()=>yC,WhyCommand:()=>HC,WorkspaceCommand:()=>YC,WorkspacesListCommand:()=>WC,YarnCommand:()=>vC,dedupeUtils:()=>oF,default:()=>WSt,suggestUtils:()=>Xu});var UBe=et(Nd());Ve();Ve();Ve();Wt();var z1e=et(lS());Ul();var Xu={};Vt(Xu,{Modifier:()=>B5,Strategy:()=>nF,Target:()=>cS,WorkspaceModifier:()=>W1e,applyModifier:()=>d2t,extractDescriptorFromPath:()=>v5,extractRangeModifier:()=>Y1e,fetchDescriptorFrom:()=>S5,findProjectDescriptors:()=>J1e,getModifier:()=>uS,getSuggestedDescriptors:()=>fS,makeWorkspaceDescriptor:()=>K1e,toWorkspaceModifier:()=>V1e});Ve();Ve();bt();var w5=et(fi()),h2t="workspace:",cS=(s=>(s.REGULAR="dependencies",s.DEVELOPMENT="devDependencies",s.PEER="peerDependencies",s))(cS||{}),B5=(s=>(s.CARET="^",s.TILDE="~",s.EXACT="",s))(B5||{}),W1e=(s=>(s.CARET="^",s.TILDE="~",s.EXACT="*",s))(W1e||{}),nF=(n=>(n.KEEP="keep",n.REUSE="reuse",n.PROJECT="project",n.LATEST="latest",n.CACHE="cache",n))(nF||{});function uS(t,e){return t.exact?"":t.caret?"^":t.tilde?"~":e.configuration.get("defaultSemverRangePrefix")}var g2t=/^([\^~]?)[0-9]+(?:\.[0-9]+){0,2}(?:-\S+)?$/;function Y1e(t,{project:e}){let r=t.match(g2t);return r?r[1]:e.configuration.get("defaultSemverRangePrefix")}function d2t(t,e){let{protocol:r,source:s,params:a,selector:n}=q.parseRange(t.range);return w5.default.valid(n)&&(n=`${e}${t.range}`),q.makeDescriptor(t,q.makeRange({protocol:r,source:s,params:a,selector:n}))}function V1e(t){switch(t){case"^":return"^";case"~":return"~";case"":return"*";default:throw new Error(`Assertion failed: Unknown modifier: "${t}"`)}}function K1e(t,e){return q.makeDescriptor(t.anchoredDescriptor,`${h2t}${V1e(e)}`)}async function J1e(t,{project:e,target:r}){let s=new Map,a=n=>{let c=s.get(n.descriptorHash);return c||s.set(n.descriptorHash,c={descriptor:n,locators:[]}),c};for(let n of e.workspaces)if(r==="peerDependencies"){let c=n.manifest.peerDependencies.get(t.identHash);c!==void 0&&a(c).locators.push(n.anchoredLocator)}else{let c=n.manifest.dependencies.get(t.identHash),f=n.manifest.devDependencies.get(t.identHash);r==="devDependencies"?f!==void 0?a(f).locators.push(n.anchoredLocator):c!==void 0&&a(c).locators.push(n.anchoredLocator):c!==void 0?a(c).locators.push(n.anchoredLocator):f!==void 0&&a(f).locators.push(n.anchoredLocator)}return s}async function v5(t,{cwd:e,workspace:r}){return await y2t(async s=>{K.isAbsolute(t)||(t=K.relative(r.cwd,K.resolve(e,t)),t.match(/^\.{0,2}\//)||(t=`./${t}`));let{project:a}=r,n=await S5(q.makeIdent(null,"archive"),t,{project:r.project,cache:s,workspace:r});if(!n)throw new Error("Assertion failed: The descriptor should have been found");let c=new Wi,f=a.configuration.makeResolver(),p=a.configuration.makeFetcher(),h={checksums:a.storedChecksums,project:a,cache:s,fetcher:p,report:c,resolver:f},E=f.bindDescriptor(n,r.anchoredLocator,h),C=q.convertDescriptorToLocator(E),S=await p.fetch(C,h),P=await Ht.find(S.prefixPath,{baseFs:S.packageFs});if(!P.name)throw new Error("Target path doesn't have a name");return q.makeDescriptor(P.name,t)})}function m2t(t){if(t.range==="unknown")return{type:"resolve",range:"latest"};if(Or.validRange(t.range))return{type:"fixed",range:t.range};if(Hp.test(t.range))return{type:"resolve",range:t.range};let e=t.range.match(/^(?:jsr:|npm:)(.*)/);if(!e)return{type:"fixed",range:t.range};let[,r]=e,s=`${q.stringifyIdent(t)}@`;return r.startsWith(s)&&(r=r.slice(s.length)),Or.validRange(r)?{type:"fixed",range:t.range}:Hp.test(r)?{type:"resolve",range:t.range}:{type:"fixed",range:t.range}}async function fS(t,{project:e,workspace:r,cache:s,target:a,fixed:n,modifier:c,strategies:f,maxResults:p=1/0}){if(!(p>=0))throw new Error(`Invalid maxResults (${p})`);let h=!n||t.range==="unknown"?m2t(t):{type:"fixed",range:t.range};if(h.type==="fixed")return{suggestions:[{descriptor:t,name:`Use ${q.prettyDescriptor(e.configuration,t)}`,reason:"(unambiguous explicit request)"}],rejections:[]};let E=typeof r<"u"&&r!==null&&r.manifest[a].get(t.identHash)||null,C=[],S=[],P=async I=>{try{await I()}catch(R){S.push(R)}};for(let I of f){if(C.length>=p)break;switch(I){case"keep":await P(async()=>{E&&C.push({descriptor:E,name:`Keep ${q.prettyDescriptor(e.configuration,E)}`,reason:"(no changes)"})});break;case"reuse":await P(async()=>{for(let{descriptor:R,locators:N}of(await J1e(t,{project:e,target:a})).values()){if(N.length===1&&N[0].locatorHash===r.anchoredLocator.locatorHash&&f.includes("keep"))continue;let U=`(originally used by ${q.prettyLocator(e.configuration,N[0])}`;U+=N.length>1?` and ${N.length-1} other${N.length>2?"s":""})`:")",C.push({descriptor:R,name:`Reuse ${q.prettyDescriptor(e.configuration,R)}`,reason:U})}});break;case"cache":await P(async()=>{for(let R of e.storedDescriptors.values())R.identHash===t.identHash&&C.push({descriptor:R,name:`Reuse ${q.prettyDescriptor(e.configuration,R)}`,reason:"(already used somewhere in the lockfile)"})});break;case"project":await P(async()=>{if(r.manifest.name!==null&&t.identHash===r.manifest.name.identHash)return;let R=e.tryWorkspaceByIdent(t);if(R===null)return;let N=K1e(R,c);C.push({descriptor:N,name:`Attach ${q.prettyDescriptor(e.configuration,N)}`,reason:`(local workspace at ${he.pretty(e.configuration,R.relativeCwd,he.Type.PATH)})`})});break;case"latest":{let R=e.configuration.get("enableNetwork"),N=e.configuration.get("enableOfflineMode");await P(async()=>{if(a==="peerDependencies")C.push({descriptor:q.makeDescriptor(t,"*"),name:"Use *",reason:"(catch-all peer dependency pattern)"});else if(!R&&!N)C.push({descriptor:null,name:"Resolve from latest",reason:he.pretty(e.configuration,"(unavailable because enableNetwork is toggled off)","grey")});else{let U=await S5(t,h.range,{project:e,cache:s,workspace:r,modifier:c});U&&C.push({descriptor:U,name:`Use ${q.prettyDescriptor(e.configuration,U)}`,reason:`(resolved from ${N?"the cache":"latest"})`})}})}break}}return{suggestions:C.slice(0,p),rejections:S.slice(0,p)}}async function S5(t,e,{project:r,cache:s,workspace:a,preserveModifier:n=!0,modifier:c}){let f=r.configuration.normalizeDependency(q.makeDescriptor(t,e)),p=new Wi,h=r.configuration.makeFetcher(),E=r.configuration.makeResolver(),C={project:r,fetcher:h,cache:s,checksums:r.storedChecksums,report:p,cacheOptions:{skipIntegrityCheck:!0}},S={...C,resolver:E,fetchOptions:C},P=E.bindDescriptor(f,a.anchoredLocator,S),I=await E.getCandidates(P,{},S);if(I.length===0)return null;let R=I[0],{protocol:N,source:U,params:W,selector:te}=q.parseRange(q.convertToManifestRange(R.reference));if(N===r.configuration.get("defaultProtocol")&&(N=null),w5.default.valid(te)){let ie=te;if(typeof c<"u")te=c+te;else if(n!==!1){let me=typeof n=="string"?n:f.range;te=Y1e(me,{project:r})+te}let Ae=q.makeDescriptor(R,q.makeRange({protocol:N,source:U,params:W,selector:te}));(await E.getCandidates(r.configuration.normalizeDependency(Ae),{},S)).length!==1&&(te=ie)}return q.makeDescriptor(R,q.makeRange({protocol:N,source:U,params:W,selector:te}))}async function y2t(t){return await le.mktempPromise(async e=>{let r=ze.create(e);return r.useWithSource(e,{enableMirror:!1,compressionLevel:0},e,{overwrite:!0}),await t(new Jr(e,{configuration:r,check:!1,immutable:!1}))})}var aC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.fixed=ge.Boolean("-F,--fixed",!1,{description:"Store dependency tags as-is instead of resolving them"});this.exact=ge.Boolean("-E,--exact",!1,{description:"Don't use any semver modifier on the resolved range"});this.tilde=ge.Boolean("-T,--tilde",!1,{description:"Use the `~` semver modifier on the resolved range"});this.caret=ge.Boolean("-C,--caret",!1,{description:"Use the `^` semver modifier on the resolved range"});this.dev=ge.Boolean("-D,--dev",!1,{description:"Add a package as a dev dependency"});this.peer=ge.Boolean("-P,--peer",!1,{description:"Add a package as a peer dependency"});this.optional=ge.Boolean("-O,--optional",!1,{description:"Add / upgrade a package to an optional regular / peer dependency"});this.preferDev=ge.Boolean("--prefer-dev",!1,{description:"Add / upgrade a package to a dev dependency"});this.interactive=ge.Boolean("-i,--interactive",{description:"Reuse the specified package from other workspaces in the project"});this.cached=ge.Boolean("--cached",!1,{description:"Reuse the highest version already used somewhere within the project"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:Ao(ec)});this.silent=ge.Boolean("--silent",{hidden:!0});this.packages=ge.Rest()}static{this.paths=[["add"]]}static{this.usage=ot.Usage({description:"add dependencies to the project",details:"\n This command adds a package to the package.json for the nearest workspace.\n\n - If it didn't exist before, the package will by default be added to the regular `dependencies` field, but this behavior can be overriden thanks to the `-D,--dev` flag (which will cause the dependency to be added to the `devDependencies` field instead) and the `-P,--peer` flag (which will do the same but for `peerDependencies`).\n\n - If the package was already listed in your dependencies, it will by default be upgraded whether it's part of your `dependencies` or `devDependencies` (it won't ever update `peerDependencies`, though).\n\n - If set, the `--prefer-dev` flag will operate as a more flexible `-D,--dev` in that it will add the package to your `devDependencies` if it isn't already listed in either `dependencies` or `devDependencies`, but it will also happily upgrade your `dependencies` if that's what you already use (whereas `-D,--dev` would throw an exception).\n\n - If set, the `-O,--optional` flag will add the package to the `optionalDependencies` field and, in combination with the `-P,--peer` flag, it will add the package as an optional peer dependency. If the package was already listed in your `dependencies`, it will be upgraded to `optionalDependencies`. If the package was already listed in your `peerDependencies`, in combination with the `-P,--peer` flag, it will be upgraded to an optional peer dependency: `\"peerDependenciesMeta\": { \"\": { \"optional\": true } }`\n\n - If the added package doesn't specify a range at all its `latest` tag will be resolved and the returned version will be used to generate a new semver range (using the `^` modifier by default unless otherwise configured via the `defaultSemverRangePrefix` configuration, or the `~` modifier if `-T,--tilde` is specified, or no modifier at all if `-E,--exact` is specified). Two exceptions to this rule: the first one is that if the package is a workspace then its local version will be used, and the second one is that if you use `-P,--peer` the default range will be `*` and won't be resolved at all.\n\n - If the added package specifies a range (such as `^1.0.0`, `latest`, or `rc`), Yarn will add this range as-is in the resulting package.json entry (in particular, tags such as `rc` will be encoded as-is rather than being converted into a semver range).\n\n If the `--cached` option is used, Yarn will preferably reuse the highest version already used somewhere within the project, even if through a transitive dependency.\n\n If the `-i,--interactive` option is used (or if the `preferInteractive` settings is toggled on) the command will first try to check whether other workspaces in the project use the specified package and, if so, will offer to reuse them.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n For a compilation of all the supported protocols, please consult the dedicated page from our website: https://yarnpkg.com/protocols.\n ",examples:[["Add a regular package to the current workspace","$0 add lodash"],["Add a specific version for a package to the current workspace","$0 add lodash@1.2.3"],["Add a package from a GitHub repository (the master branch) to the current workspace using a URL","$0 add lodash@https://github.com/lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol","$0 add lodash@github:lodash/lodash"],["Add a package from a GitHub repository (the master branch) to the current workspace using the GitHub protocol (shorthand)","$0 add lodash@lodash/lodash"],["Add a package from a specific branch of a GitHub repository to the current workspace using the GitHub protocol (shorthand)","$0 add lodash-es@lodash/lodash#es"],["Add a local package (gzipped tarball format) to the current workspace","$0 add local-package-name@file:../path/to/local-package-name-v0.1.2.tgz"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=f||r.get("preferReuse"),h=uS(this,s),E=[p?"reuse":void 0,"project",this.cached?"cache":void 0,"latest"].filter(W=>typeof W<"u"),C=f?1/0:1,S=W=>{let te=q.tryParseDescriptor(W.slice(4));return te?te.range==="unknown"?q.makeDescriptor(te,`jsr:${q.stringifyIdent(te)}@latest`):q.makeDescriptor(te,`jsr:${te.range}`):null},P=await Promise.all(this.packages.map(async W=>{let te=W.match(/^\.{0,2}\//)?await v5(W,{cwd:this.context.cwd,workspace:a}):W.startsWith("jsr:")?S(W):q.tryParseDescriptor(W),ie=W.match(/^(https?:|git@github)/);if(ie)throw new nt(`It seems you are trying to add a package using a ${he.pretty(r,`${ie[0]}...`,he.Type.RANGE)} url; we now require package names to be explicitly specified. +Try running the command again with the package name prefixed: ${he.pretty(r,"yarn add",he.Type.CODE)} ${he.pretty(r,q.makeDescriptor(q.makeIdent(null,"my-package"),`${ie[0]}...`),he.Type.DESCRIPTOR)}`);if(!te)throw new nt(`The ${he.pretty(r,W,he.Type.CODE)} string didn't match the required format (package-name@range). Did you perhaps forget to explicitly reference the package name?`);let Ae=E2t(a,te,{dev:this.dev,peer:this.peer,preferDev:this.preferDev,optional:this.optional});return await Promise.all(Ae.map(async me=>{let pe=await fS(te,{project:s,workspace:a,cache:n,fixed:c,target:me,modifier:h,strategies:E,maxResults:C});return{request:te,suggestedDescriptors:pe,target:me}}))})).then(W=>W.flat()),I=await uA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async W=>{for(let{request:te,suggestedDescriptors:{suggestions:ie,rejections:Ae}}of P)if(ie.filter(me=>me.descriptor!==null).length===0){let[me]=Ae;if(typeof me>"u")throw new Error("Assertion failed: Expected an error to have been set");s.configuration.get("enableNetwork")?W.reportError(27,`${q.prettyDescriptor(r,te)} can't be resolved to a satisfying range`):W.reportError(27,`${q.prettyDescriptor(r,te)} can't be resolved to a satisfying range (note: network resolution has been disabled)`),W.reportSeparator(),W.reportExceptionOnce(me)}});if(I.hasErrors())return I.exitCode();let R=!1,N=[],U=[];for(let{suggestedDescriptors:{suggestions:W},target:te}of P){let ie,Ae=W.filter(Be=>Be.descriptor!==null),ce=Ae[0].descriptor,me=Ae.every(Be=>q.areDescriptorsEqual(Be.descriptor,ce));Ae.length===1||me?ie=ce:(R=!0,{answer:ie}=await(0,z1e.prompt)({type:"select",name:"answer",message:"Which range do you want to use?",choices:W.map(({descriptor:Be,name:Ce,reason:g})=>Be?{name:Ce,hint:g,descriptor:Be}:{name:Ce,hint:g,disabled:!0}),onCancel:()=>process.exit(130),result(Be){return this.find(Be,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout}));let pe=a.manifest[te].get(ie.identHash);(typeof pe>"u"||pe.descriptorHash!==ie.descriptorHash)&&(a.manifest[te].set(ie.identHash,ie),this.optional&&(te==="dependencies"?a.manifest.ensureDependencyMeta({...ie,range:"unknown"}).optional=!0:te==="peerDependencies"&&(a.manifest.ensurePeerDependencyMeta({...ie,range:"unknown"}).optional=!0)),typeof pe>"u"?N.push([a,te,ie,E]):U.push([a,te,pe,ie]))}return await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyAddition,N),await r.triggerMultipleHooks(W=>W.afterWorkspaceDependencyReplacement,U),R&&this.context.stdout.write(` +`),await s.installWithNewReport({json:this.json,stdout:this.context.stdout,quiet:this.context.quiet},{cache:n,mode:this.mode})}};function E2t(t,e,{dev:r,peer:s,preferDev:a,optional:n}){let c=t.manifest.dependencies.has(e.identHash),f=t.manifest.devDependencies.has(e.identHash),p=t.manifest.peerDependencies.has(e.identHash);if((r||s)&&c)throw new nt(`Package "${q.prettyIdent(t.project.configuration,e)}" is already listed as a regular dependency - remove the -D,-P flags or remove it from your dependencies first`);if(!r&&!s&&p)throw new nt(`Package "${q.prettyIdent(t.project.configuration,e)}" is already listed as a peer dependency - use either of -D or -P, or remove it from your peer dependencies first`);if(n&&f)throw new nt(`Package "${q.prettyIdent(t.project.configuration,e)}" is already listed as a dev dependency - remove the -O flag or remove it from your dev dependencies first`);if(n&&!s&&p)throw new nt(`Package "${q.prettyIdent(t.project.configuration,e)}" is already listed as a peer dependency - remove the -O flag or add the -P flag or remove it from your peer dependencies first`);if((r||a)&&n)throw new nt(`Package "${q.prettyIdent(t.project.configuration,e)}" cannot simultaneously be a dev dependency and an optional dependency`);let h=[];return s&&h.push("peerDependencies"),(r||a)&&h.push("devDependencies"),n&&h.push("dependencies"),h.length>0?h:f?["devDependencies"]:p?["peerDependencies"]:["dependencies"]}Ve();Ve();Wt();var lC=class extends ut{constructor(){super(...arguments);this.verbose=ge.Boolean("-v,--verbose",!1,{description:"Print both the binary name and the locator of the package that provides the binary"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.name=ge.String({required:!1})}static{this.paths=[["bin"]]}static{this.usage=ot.Usage({description:"get the path to a binary script",details:` + When used without arguments, this command will print the list of all the binaries available in the current workspace. Adding the \`-v,--verbose\` flag will cause the output to contain both the binary name and the locator of the package that provides the binary. + + When an argument is specified, this command will just print the path to the binary on the standard output and exit. Note that the reported path may be stored within a zip archive. + `,examples:[["List all the available binaries","$0 bin"],["Print the path to a specific binary","$0 bin eslint"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);if(await s.restoreInstallState(),this.name){let f=(await In.getPackageAccessibleBinaries(a,{project:s})).get(this.name);if(!f)throw new nt(`Couldn't find a binary named "${this.name}" for package "${q.prettyLocator(r,a)}"`);let[,p]=f;return this.context.stdout.write(`${p} +`),0}return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async c=>{let f=await In.getPackageAccessibleBinaries(a,{project:s}),h=Array.from(f.keys()).reduce((E,C)=>Math.max(E,C.length),0);for(let[E,[C,S]]of f)c.reportJson({name:E,source:q.stringifyIdent(C),path:S});if(this.verbose)for(let[E,[C]]of f)c.reportInfo(null,`${E.padEnd(h," ")} ${q.prettyLocator(r,C)}`);else for(let E of f.keys())c.reportInfo(null,E)})).exitCode()}};Ve();bt();Wt();var cC=class extends ut{constructor(){super(...arguments);this.mirror=ge.Boolean("--mirror",!1,{description:"Remove the global cache files instead of the local cache files"});this.all=ge.Boolean("--all",!1,{description:"Remove both the global cache files and the local cache files of the current project"})}static{this.paths=[["cache","clean"],["cache","clear"]]}static{this.usage=ot.Usage({description:"remove the shared cache files",details:` + This command will remove all the files from the cache. + `,examples:[["Remove all the local archives","$0 cache clean"],["Remove all the archives stored in the ~/.yarn directory","$0 cache clean --mirror"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(!r.get("enableCacheClean"))throw new nt("Cache cleaning is currently disabled. To enable it, set `enableCacheClean: true` in your configuration file. Note: Cache cleaning is typically not required and should be avoided when using Zero-Installs.");let s=await Jr.find(r);return(await Ot.start({configuration:r,stdout:this.context.stdout},async()=>{let n=(this.all||this.mirror)&&s.mirrorCwd!==null,c=!this.mirror;n&&(await le.removePromise(s.mirrorCwd),await r.triggerHook(f=>f.cleanGlobalArtifacts,r)),c&&await le.removePromise(s.cwd)})).exitCode()}};Ve();Wt();var X1e=et(AS()),D5=ye("util"),uC=class extends ut{constructor(){super(...arguments);this.why=ge.Boolean("--why",!1,{description:"Print the explanation for why a setting has its value"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.unsafe=ge.Boolean("--no-redacted",!1,{description:"Don't redact secrets (such as tokens) from the output"});this.name=ge.String()}static{this.paths=[["config","get"]]}static{this.usage=ot.Usage({description:"read a configuration settings",details:` + This command will print a configuration setting. + + Secrets (such as tokens) will be redacted from the output by default. If this behavior isn't desired, set the \`--no-redacted\` to get the untransformed value. + `,examples:[["Print a simple configuration setting","yarn config get yarnPath"],["Print a complex configuration setting","yarn config get packageExtensions"],["Print a nested field from the configuration",`yarn config get 'npmScopes["my-company"].npmRegistryServer'`],["Print a token from the configuration","yarn config get npmAuthToken --no-redacted"],["Print a configuration setting as JSON","yarn config get packageExtensions --json"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=this.name.replace(/[.[].*$/,""),a=this.name.replace(/^[^.[]*/,"");if(typeof r.settings.get(s)>"u")throw new nt(`Couldn't find a configuration settings named "${s}"`);let c=r.getSpecial(s,{hideSecrets:!this.unsafe,getNativePaths:!0}),f=je.convertMapsToIndexableObjects(c),p=a?(0,X1e.default)(f,a):f,h=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async E=>{E.reportJson(p)});if(!this.json){if(typeof p=="string")return this.context.stdout.write(`${p} +`),h.exitCode();D5.inspect.styles.name="cyan",this.context.stdout.write(`${(0,D5.inspect)(p,{depth:1/0,colors:r.get("enableColors"),compact:!1})} +`)}return h.exitCode()}};Ve();Wt();var j2e=et(k5()),q2e=et(AS()),G2e=et(Q5()),T5=ye("util"),fC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Set complex configuration settings to JSON values"});this.home=ge.Boolean("-H,--home",!1,{description:"Update the home configuration instead of the project configuration"});this.name=ge.String();this.value=ge.String()}static{this.paths=[["config","set"]]}static{this.usage=ot.Usage({description:"change a configuration settings",details:` + This command will set a configuration setting. + + When used without the \`--json\` flag, it can only set a simple configuration setting (a string, a number, or a boolean). + + When used with the \`--json\` flag, it can set both simple and complex configuration settings, including Arrays and Objects. + `,examples:[["Set a simple configuration setting (a string, a number, or a boolean)","yarn config set initScope myScope"],["Set a simple configuration setting (a string, a number, or a boolean) using the `--json` flag",'yarn config set initScope --json \\"myScope\\"'],["Set a complex configuration setting (an Array) using the `--json` flag",`yarn config set unsafeHttpWhitelist --json '["*.example.com", "example.com"]'`],["Set a complex configuration setting (an Object) using the `--json` flag",`yarn config set packageExtensions --json '{ "@babel/parser@*": { "dependencies": { "@babel/types": "*" } } }'`],["Set a nested configuration setting",'yarn config set npmScopes.company.npmRegistryServer "https://npm.example.com"'],["Set a nested configuration setting using indexed access for non-simple keys",`yarn config set 'npmRegistries["//npm.example.com"].npmAuthToken' "ffffffff-ffff-ffff-ffff-ffffffffffff"`]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt("This command must be run from within a project folder");return r.projectCwd},a=this.name.replace(/[.[].*$/,""),n=this.name.replace(/^[^.[]*\.?/,"");if(typeof r.settings.get(a)>"u")throw new nt(`Couldn't find a configuration settings named "${a}"`);if(a==="enableStrictSettings")throw new nt("This setting only affects the file it's in, and thus cannot be set from the CLI");let f=this.json?JSON.parse(this.value):this.value;await(this.home?I=>ze.updateHomeConfiguration(I):I=>ze.updateConfiguration(s(),I))(I=>{if(n){let R=(0,j2e.default)(I);return(0,G2e.default)(R,this.name,f),R}else return{...I,[a]:f}});let E=(await ze.find(this.context.cwd,this.context.plugins)).getSpecial(a,{hideSecrets:!0,getNativePaths:!0}),C=je.convertMapsToIndexableObjects(E),S=n?(0,q2e.default)(C,n):C;return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async I=>{T5.inspect.styles.name="cyan",I.reportInfo(0,`Successfully set ${this.name} to ${(0,T5.inspect)(S,{depth:1/0,colors:r.get("enableColors"),compact:!1})}`)})).exitCode()}};Ve();Wt();var tBe=et(k5()),rBe=et(K2e()),nBe=et(F5()),AC=class extends ut{constructor(){super(...arguments);this.home=ge.Boolean("-H,--home",!1,{description:"Update the home configuration instead of the project configuration"});this.name=ge.String()}static{this.paths=[["config","unset"]]}static{this.usage=ot.Usage({description:"unset a configuration setting",details:` + This command will unset a configuration setting. + `,examples:[["Unset a simple configuration setting","yarn config unset initScope"],["Unset a complex configuration setting","yarn config unset packageExtensions"],["Unset a nested configuration setting","yarn config unset npmScopes.company.npmRegistryServer"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=()=>{if(!r.projectCwd)throw new nt("This command must be run from within a project folder");return r.projectCwd},a=this.name.replace(/[.[].*$/,""),n=this.name.replace(/^[^.[]*\.?/,"");if(typeof r.settings.get(a)>"u")throw new nt(`Couldn't find a configuration settings named "${a}"`);let f=this.home?h=>ze.updateHomeConfiguration(h):h=>ze.updateConfiguration(s(),h);return(await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout},async h=>{let E=!1;await f(C=>{if(!(0,rBe.default)(C,this.name))return h.reportWarning(0,`Configuration doesn't contain setting ${this.name}; there is nothing to unset`),E=!0,C;let S=n?(0,tBe.default)(C):{...C};return(0,nBe.default)(S,this.name),S}),E||h.reportInfo(0,`Successfully unset ${this.name}`)})).exitCode()}};Ve();bt();Wt();var sF=ye("util"),pC=class extends ut{constructor(){super(...arguments);this.noDefaults=ge.Boolean("--no-defaults",!1,{description:"Omit the default values from the display"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.verbose=ge.Boolean("-v,--verbose",{hidden:!0});this.why=ge.Boolean("--why",{hidden:!0});this.names=ge.Rest()}static{this.paths=[["config"]]}static{this.usage=ot.Usage({description:"display the current configuration",details:` + This command prints the current active configuration settings. + `,examples:[["Print the active configuration settings","$0 config"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins,{strict:!1}),s=await DI({configuration:r,stdout:this.context.stdout,forceError:this.json},[{option:this.verbose,message:"The --verbose option is deprecated, the settings' descriptions are now always displayed"},{option:this.why,message:"The --why option is deprecated, the settings' sources are now always displayed"}]);if(s!==null)return s;let a=this.names.length>0?[...new Set(this.names)].sort():[...r.settings.keys()].sort(),n,c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async f=>{if(r.invalid.size>0&&!this.json){for(let[p,h]of r.invalid)f.reportError(34,`Invalid configuration key "${p}" in ${h}`);f.reportSeparator()}if(this.json)for(let p of a){if(this.noDefaults&&!r.sources.has(p))continue;let h=r.settings.get(p);typeof h>"u"&&f.reportError(34,`No configuration key named "${p}"`);let E=r.getSpecial(p,{hideSecrets:!0,getNativePaths:!0}),C=r.sources.get(p)??"",S=C&&C[0]!=="<"?ue.fromPortablePath(C):C;f.reportJson({key:p,effective:E,source:S,...h})}else{let p={breakLength:1/0,colors:r.get("enableColors"),maxArrayLength:2},h={},E={children:h};for(let C of a){if(this.noDefaults&&!r.sources.has(C))continue;let S=r.settings.get(C),P=r.sources.get(C)??"",I=r.getSpecial(C,{hideSecrets:!0,getNativePaths:!0}),R={Description:{label:"Description",value:he.tuple(he.Type.MARKDOWN,{text:S.description,format:this.cli.format(),paragraphs:!1})},Source:{label:"Source",value:he.tuple(P[0]==="<"?he.Type.CODE:he.Type.PATH,P)}};h[C]={value:he.tuple(he.Type.CODE,C),children:R};let N=(U,W)=>{for(let[te,ie]of W)if(ie instanceof Map){let Ae={};U[te]={children:Ae},N(Ae,ie)}else U[te]={label:te,value:he.tuple(he.Type.NO_HINT,(0,sF.inspect)(ie,p))}};I instanceof Map?N(R,I):R.Value={label:"Value",value:he.tuple(he.Type.NO_HINT,(0,sF.inspect)(I,p))}}a.length!==1&&(n=void 0),ks.emitTree(E,{configuration:r,json:this.json,stdout:this.context.stdout,separators:2})}});if(!this.json&&typeof n<"u"){let f=a[0],p=(0,sF.inspect)(r.getSpecial(f,{hideSecrets:!0,getNativePaths:!0}),{colors:r.get("enableColors")});this.context.stdout.write(` +`),this.context.stdout.write(`${p} +`)}return c.exitCode()}};Ve();Wt();Ul();var oF={};Vt(oF,{Strategy:()=>pS,acceptedStrategies:()=>tSt,dedupe:()=>N5});Ve();Ve();var iBe=et(Sa()),pS=(e=>(e.HIGHEST="highest",e))(pS||{}),tSt=new Set(Object.values(pS)),rSt={highest:async(t,e,{resolver:r,fetcher:s,resolveOptions:a,fetchOptions:n})=>{let c=new Map;for(let[p,h]of t.storedResolutions){let E=t.storedDescriptors.get(p);if(typeof E>"u")throw new Error(`Assertion failed: The descriptor (${p}) should have been registered`);je.getSetWithDefault(c,E.identHash).add(h)}let f=new Map(je.mapAndFilter(t.storedDescriptors.values(),p=>q.isVirtualDescriptor(p)?je.mapAndFilter.skip:[p.descriptorHash,je.makeDeferred()]));for(let p of t.storedDescriptors.values()){let h=f.get(p.descriptorHash);if(typeof h>"u")throw new Error(`Assertion failed: The descriptor (${p.descriptorHash}) should have been registered`);let E=t.storedResolutions.get(p.descriptorHash);if(typeof E>"u")throw new Error(`Assertion failed: The resolution (${p.descriptorHash}) should have been registered`);let C=t.originalPackages.get(E);if(typeof C>"u")throw new Error(`Assertion failed: The package (${E}) should have been registered`);Promise.resolve().then(async()=>{let S=r.getResolutionDependencies(p,a),P=Object.fromEntries(await je.allSettledSafe(Object.entries(S).map(async([te,ie])=>{let Ae=f.get(ie.descriptorHash);if(typeof Ae>"u")throw new Error(`Assertion failed: The descriptor (${ie.descriptorHash}) should have been registered`);let ce=await Ae.promise;if(!ce)throw new Error("Assertion failed: Expected the dependency to have been through the dedupe process itself");return[te,ce.updatedPackage]})));if(e.length&&!iBe.default.isMatch(q.stringifyIdent(p),e)||!r.shouldPersistResolution(C,a))return C;let I=c.get(p.identHash);if(typeof I>"u")throw new Error(`Assertion failed: The resolutions (${p.identHash}) should have been registered`);if(I.size===1)return C;let R=[...I].map(te=>{let ie=t.originalPackages.get(te);if(typeof ie>"u")throw new Error(`Assertion failed: The package (${te}) should have been registered`);return ie}),N=await r.getSatisfying(p,P,R,a),U=N.locators?.[0];if(typeof U>"u"||!N.sorted)return C;let W=t.originalPackages.get(U.locatorHash);if(typeof W>"u")throw new Error(`Assertion failed: The package (${U.locatorHash}) should have been registered`);return W}).then(async S=>{let P=await t.preparePackage(S,{resolver:r,resolveOptions:a});h.resolve({descriptor:p,currentPackage:C,updatedPackage:S,resolvedPackage:P})}).catch(S=>{h.reject(S)})}return[...f.values()].map(p=>p.promise)}};async function N5(t,{strategy:e,patterns:r,cache:s,report:a}){let{configuration:n}=t,c=new Wi,f=n.makeResolver(),p=n.makeFetcher(),h={cache:s,checksums:t.storedChecksums,fetcher:p,project:t,report:c,cacheOptions:{skipIntegrityCheck:!0}},E={project:t,resolver:f,report:c,fetchOptions:h};return await a.startTimerPromise("Deduplication step",async()=>{let C=rSt[e],S=await C(t,r,{resolver:f,resolveOptions:E,fetcher:p,fetchOptions:h}),P=ho.progressViaCounter(S.length);await a.reportProgress(P);let I=0;await Promise.all(S.map(U=>U.then(W=>{if(W===null||W.currentPackage.locatorHash===W.updatedPackage.locatorHash)return;I++;let{descriptor:te,currentPackage:ie,updatedPackage:Ae}=W;a.reportInfo(0,`${q.prettyDescriptor(n,te)} can be deduped from ${q.prettyLocator(n,ie)} to ${q.prettyLocator(n,Ae)}`),a.reportJson({descriptor:q.stringifyDescriptor(te),currentResolution:q.stringifyLocator(ie),updatedResolution:q.stringifyLocator(Ae)}),t.storedResolutions.set(te.descriptorHash,Ae.locatorHash)}).finally(()=>P.tick())));let R;switch(I){case 0:R="No packages";break;case 1:R="One package";break;default:R=`${I} packages`}let N=he.pretty(n,e,he.Type.CODE);return a.reportInfo(0,`${R} can be deduped using the ${N} strategy`),I})}var hC=class extends ut{constructor(){super(...arguments);this.strategy=ge.String("-s,--strategy","highest",{description:"The strategy to use when deduping dependencies",validator:Ao(pS)});this.check=ge.Boolean("-c,--check",!1,{description:"Exit with exit code 1 when duplicates are found, without persisting the dependency tree"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:Ao(ec)});this.patterns=ge.Rest()}static{this.paths=[["dedupe"]]}static{this.usage=ot.Usage({description:"deduplicate dependencies with overlapping ranges",details:"\n Duplicates are defined as descriptors with overlapping ranges being resolved and locked to different locators. They are a natural consequence of Yarn's deterministic installs, but they can sometimes pile up and unnecessarily increase the size of your project.\n\n This command dedupes dependencies in the current project using different strategies (only one is implemented at the moment):\n\n - `highest`: Reuses (where possible) the locators with the highest versions. This means that dependencies can only be upgraded, never downgraded. It's also guaranteed that it never takes more than a single pass to dedupe the entire dependency tree.\n\n **Note:** Even though it never produces a wrong dependency tree, this command should be used with caution, as it modifies the dependency tree, which can sometimes cause problems when packages don't strictly follow semver recommendations. Because of this, it is recommended to also review the changes manually.\n\n If set, the `-c,--check` flag will only report the found duplicates, without persisting the modified dependency tree. If changes are found, the command will exit with a non-zero exit code, making it suitable for CI purposes.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n ### In-depth explanation:\n\n Yarn doesn't deduplicate dependencies by default, otherwise installs wouldn't be deterministic and the lockfile would be useless. What it actually does is that it tries to not duplicate dependencies in the first place.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@*`will cause Yarn to reuse `foo@2.3.4`, even if the latest `foo` is actually `foo@2.10.14`, thus preventing unnecessary duplication.\n\n Duplication happens when Yarn can't unlock dependencies that have already been locked inside the lockfile.\n\n **Example:** If `foo@^2.3.4` (a dependency of a dependency) has already been resolved to `foo@2.3.4`, running `yarn add foo@2.10.14` will cause Yarn to install `foo@2.10.14` because the existing resolution doesn't satisfy the range `2.10.14`. This behavior can lead to (sometimes) unwanted duplication, since now the lockfile contains 2 separate resolutions for the 2 `foo` descriptors, even though they have overlapping ranges, which means that the lockfile can be simplified so that both descriptors resolve to `foo@2.10.14`.\n ",examples:[["Dedupe all packages","$0 dedupe"],["Dedupe all packages using a specific strategy","$0 dedupe --strategy highest"],["Dedupe a specific package","$0 dedupe lodash"],["Dedupe all packages with the `@babel/*` scope","$0 dedupe '@babel/*'"],["Check for duplicates (can be used as a CI step)","$0 dedupe --check"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=await Jr.find(r);await s.restoreInstallState({restoreResolutions:!1});let n=0,c=await Ot.start({configuration:r,includeFooter:!1,stdout:this.context.stdout,json:this.json},async f=>{n=await N5(s,{strategy:this.strategy,patterns:this.patterns,cache:a,report:f})});return c.hasErrors()?c.exitCode():this.check?n?1:0:await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:a,mode:this.mode})}};Ve();Wt();var gC=class extends ut{static{this.paths=[["--clipanion=definitions"]]}async execute(){let{plugins:e}=await ze.find(this.context.cwd,this.context.plugins),r=[];for(let c of e){let{commands:f}=c[1];if(f){let h=wa.from(f).definitions();r.push([c[0],h])}}let s=this.cli.definitions(),a=(c,f)=>c.split(" ").slice(1).join()===f.split(" ").slice(1).join(),n=sBe()["@yarnpkg/builder"].bundles.standard;for(let c of r){let f=c[1];for(let p of f)s.find(h=>a(h.path,p.path)).plugin={name:c[0],isDefault:n.includes(c[0])}}this.context.stdout.write(`${JSON.stringify(s,null,2)} +`)}};var dC=class extends ut{static{this.paths=[["help"],["--help"],["-h"]]}async execute(){this.context.stdout.write(this.cli.usage(null))}};Ve();bt();Wt();var mC=class extends ut{constructor(){super(...arguments);this.leadingArgument=ge.String();this.args=ge.Proxy()}async execute(){if(this.leadingArgument.match(/[\\/]/)&&!q.tryParseIdent(this.leadingArgument)){let r=K.resolve(this.context.cwd,ue.toPortablePath(this.leadingArgument));return await this.cli.run(this.args,{cwd:r})}else return await this.cli.run(["run",this.leadingArgument,...this.args])}};Ve();var yC=class extends ut{static{this.paths=[["-v"],["--version"]]}async execute(){this.context.stdout.write(`${un||""} +`)}};Ve();Ve();Wt();var EC=class extends ut{constructor(){super(...arguments);this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["exec"]]}static{this.usage=ot.Usage({description:"execute a shell script",details:` + This command simply executes a shell script within the context of the root directory of the active workspace using the portable shell. + + It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment). + `,examples:[["Execute a single shell command","$0 exec echo Hello World"],["Execute a shell script",'$0 exec "tsc & babel src --out-dir lib"']]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,locator:a}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState(),await In.executePackageShellcode(a,this.commandName,this.args,{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,project:s})}};Ve();Wt();Ul();var IC=class extends ut{constructor(){super(...arguments);this.hash=ge.String({required:!1,validator:Jx(wE(),[tB(/^p[0-9a-f]{5}$/)])})}static{this.paths=[["explain","peer-requirements"]]}static{this.usage=ot.Usage({description:"explain a set of peer requirements",details:` + A peer requirement represents all peer requests that a subject must satisfy when providing a requested package to requesters. + + When the hash argument is specified, this command prints a detailed explanation of the peer requirement corresponding to the hash and whether it is satisfied or not. + + When used without arguments, this command lists all peer requirements and the corresponding hash that can be used to get detailed information about a given requirement. + + **Note:** A hash is a six-letter p-prefixed code that can be obtained from peer dependency warnings or from the list of all peer requirements (\`yarn explain peer-requirements\`). + `,examples:[["Explain the corresponding peer requirement for a hash","$0 explain peer-requirements p1a4ed"],["List all peer requirements","$0 explain peer-requirements"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return await s.restoreInstallState({restoreResolutions:!1}),await s.applyLightResolution(),typeof this.hash<"u"?await iSt(this.hash,s,{stdout:this.context.stdout}):await sSt(s,{stdout:this.context.stdout})}};async function iSt(t,e,r){let s=e.peerRequirementNodes.get(t);if(typeof s>"u")throw new Error(`No peerDependency requirements found for hash: "${t}"`);let a=new Set,n=p=>a.has(p.requester.locatorHash)?{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:p.children.size>0?[{value:he.tuple(he.Type.NO_HINT,"...")}]:[]}:(a.add(p.requester.locatorHash),{value:he.tuple(he.Type.DEPENDENT,{locator:p.requester,descriptor:p.descriptor}),children:Object.fromEntries(Array.from(p.children.values(),h=>[q.stringifyLocator(h.requester),n(h)]))}),c=e.peerWarnings.find(p=>p.hash===t);return(await Ot.start({configuration:e.configuration,stdout:r.stdout,includeFooter:!1,includePrefix:!1},async p=>{let h=he.mark(e.configuration),E=c?h.Cross:h.Check;if(p.reportInfo(0,`Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} is requested to provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} by its descendants`),p.reportSeparator(),p.reportInfo(0,he.pretty(e.configuration,s.subject,he.Type.LOCATOR)),ks.emitTree({children:Object.fromEntries(Array.from(s.requests.values(),C=>[q.stringifyLocator(C.requester),n(C)]))},{configuration:e.configuration,stdout:r.stdout,json:!1}),p.reportSeparator(),s.provided.range==="missing:"){let C=c?"":" , but all peer requests are optional";p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} does not provide ${he.pretty(e.configuration,s.ident,he.Type.IDENT)}${C}.`)}else{let C=e.storedResolutions.get(s.provided.descriptorHash);if(!C)throw new Error("Assertion failed: Expected the descriptor to be registered");let S=e.storedPackages.get(C);if(!S)throw new Error("Assertion failed: Expected the package to be registered");p.reportInfo(0,`${E} Package ${he.pretty(e.configuration,s.subject,he.Type.LOCATOR)} provides ${he.pretty(e.configuration,s.ident,he.Type.IDENT)} with version ${q.prettyReference(e.configuration,S.version??"0.0.0")}, ${c?"which does not satisfy all requests.":"which satisfies all requests"}`),c?.type===3&&(c.range?p.reportInfo(0,` The combined requested range is ${he.pretty(e.configuration,c.range,he.Type.RANGE)}`):p.reportInfo(0," Unfortunately, the requested ranges have no overlap"))}})).exitCode()}async function sSt(t,e){return(await Ot.start({configuration:t.configuration,stdout:e.stdout,includeFooter:!1,includePrefix:!1},async s=>{let a=he.mark(t.configuration),n=je.sortMap(t.peerRequirementNodes,[([,c])=>q.stringifyLocator(c.subject),([,c])=>q.stringifyIdent(c.ident)]);for(let[,c]of n.values()){if(!c.root)continue;let f=t.peerWarnings.find(E=>E.hash===c.hash),p=[...q.allPeerRequests(c)],h;if(p.length>2?h=` and ${p.length-1} other dependencies`:p.length===2?h=" and 1 other dependency":h="",c.provided.range!=="missing:"){let E=t.storedResolutions.get(c.provided.descriptorHash);if(!E)throw new Error("Assertion failed: Expected the resolution to have been registered");let C=t.storedPackages.get(E);if(!C)throw new Error("Assertion failed: Expected the provided package to have been registered");let S=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \u2192 ${f?a.Cross:a.Check} ${q.prettyLocator(t.configuration,c.subject)} provides ${q.prettyLocator(t.configuration,C)} to ${q.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,S):s.reportInfo(0,S)}else{let E=`${he.pretty(t.configuration,c.hash,he.Type.CODE)} \u2192 ${f?a.Cross:a.Check} ${q.prettyLocator(t.configuration,c.subject)} doesn't provide ${q.prettyIdent(t.configuration,c.ident)} to ${q.prettyLocator(t.configuration,p[0].requester)}${h}`;f?s.reportWarning(0,E):s.reportInfo(0,E)}}})).exitCode()}Ve();Wt();Ul();Ve();Ve();bt();Wt();var oBe=et(fi()),CC=class extends ut{constructor(){super(...arguments);this.useYarnPath=ge.Boolean("--yarn-path",{description:"Set the yarnPath setting even if the version can be accessed by Corepack"});this.onlyIfNeeded=ge.Boolean("--only-if-needed",!1,{description:"Only lock the Yarn version if it isn't already locked"});this.version=ge.String()}static{this.paths=[["set","version"]]}static{this.usage=ot.Usage({description:"lock the Yarn version used by the project",details:"\n This command will set a specific release of Yarn to be used by Corepack: https://nodejs.org/api/corepack.html.\n\n By default it only will set the `packageManager` field at the root of your project, but if the referenced release cannot be represented this way, if you already have `yarnPath` configured, or if you set the `--yarn-path` command line flag, then the release will also be downloaded from the Yarn GitHub repository, stored inside your project, and referenced via the `yarnPath` settings from your project `.yarnrc.yml` file.\n\n A very good use case for this command is to enforce the version of Yarn used by any single member of your team inside the same project - by doing this you ensure that you have control over Yarn upgrades and downgrades (including on your deployment servers), and get rid of most of the headaches related to someone using a slightly different version and getting different behavior.\n\n The version specifier can be:\n\n - a tag:\n - `latest` / `berry` / `stable` -> the most recent stable berry (`>=2.0.0`) release\n - `canary` -> the most recent canary (release candidate) berry (`>=2.0.0`) release\n - `classic` -> the most recent classic (`^0.x || ^1.x`) release\n\n - a semver range (e.g. `2.x`) -> the most recent version satisfying the range (limited to berry releases)\n\n - a semver version (e.g. `2.4.1`, `1.22.1`)\n\n - a local file referenced through either a relative or absolute path\n\n - `self` -> the version used to invoke the command\n ",examples:[["Download the latest release from the Yarn repository","$0 set version latest"],["Download the latest canary release from the Yarn repository","$0 set version canary"],["Download the latest classic release from the Yarn repository","$0 set version classic"],["Download the most recent Yarn 3 build","$0 set version 3.x"],["Download a specific Yarn 2 build","$0 set version 2.0.0-rc.30"],["Switch back to a specific Yarn 1 release","$0 set version 1.22.1"],["Use a release from the local filesystem","$0 set version ./yarn.cjs"],["Use a release from a URL","$0 set version https://repo.yarnpkg.com/3.1.0/packages/yarnpkg-cli/bin/yarn.js"],["Download the version used to invoke the command","$0 set version self"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(this.onlyIfNeeded&&r.get("yarnPath")){let f=r.sources.get("yarnPath");if(!f)throw new Error("Assertion failed: Expected 'yarnPath' to have a source");let p=r.projectCwd??r.startingCwd;if(K.contains(p,f))return 0}let s=()=>{if(typeof un>"u")throw new nt("The --install flag can only be used without explicit version specifier from the Yarn CLI");return`file://${process.argv[1]}`},a,n=(f,p)=>({version:p,url:f.replace(/\{\}/g,p)});if(this.version==="self")a={url:s(),version:un??"self"};else if(this.version==="latest"||this.version==="berry"||this.version==="stable")a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await hS(r,"stable"));else if(this.version==="canary")a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await hS(r,"canary"));else if(this.version==="classic")a={url:"https://classic.yarnpkg.com/latest.js",version:"classic"};else if(this.version.match(/^https?:/))a={url:this.version,version:"remote"};else if(this.version.match(/^\.{0,2}[\\/]/)||ue.isAbsolute(this.version))a={url:`file://${K.resolve(ue.toPortablePath(this.version))}`,version:"file"};else if(Or.satisfiesWithPrereleases(this.version,">=2.0.0"))a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",this.version);else if(Or.satisfiesWithPrereleases(this.version,"^0.x || ^1.x"))a=n("https://github.com/yarnpkg/yarn/releases/download/v{}/yarn-{}.js",this.version);else if(Or.validRange(this.version))a=n("https://repo.yarnpkg.com/{}/packages/yarnpkg-cli/bin/yarn.js",await oSt(r,this.version));else throw new nt(`Invalid version descriptor "${this.version}"`);return(await Ot.start({configuration:r,stdout:this.context.stdout,includeLogs:!this.context.quiet},async f=>{let p=async()=>{let h="file://";return a.url.startsWith(h)?(f.reportInfo(0,`Retrieving ${he.pretty(r,a.url,he.Type.PATH)}`),await le.readFilePromise(a.url.slice(h.length))):(f.reportInfo(0,`Downloading ${he.pretty(r,a.url,he.Type.URL)}`),await An.get(a.url,{configuration:r}))};await O5(r,a.version,p,{report:f,useYarnPath:this.useYarnPath})})).exitCode()}};async function oSt(t,e){let s=(await An.get("https://repo.yarnpkg.com/tags",{configuration:t,jsonResponse:!0})).tags.filter(a=>Or.satisfiesWithPrereleases(a,e));if(s.length===0)throw new nt(`No matching release found for range ${he.pretty(t,e,he.Type.RANGE)}.`);return s[0]}async function hS(t,e){let r=await An.get("https://repo.yarnpkg.com/tags",{configuration:t,jsonResponse:!0});if(!r.latest[e])throw new nt(`Tag ${he.pretty(t,e,he.Type.RANGE)} not found`);return r.latest[e]}async function O5(t,e,r,{report:s,useYarnPath:a}){let n,c=async()=>(typeof n>"u"&&(n=await r()),n);if(e===null){let te=await c();await le.mktempPromise(async ie=>{let Ae=K.join(ie,"yarn.cjs");await le.writeFilePromise(Ae,te);let{stdout:ce}=await Gr.execvp(process.execPath,[ue.fromPortablePath(Ae),"--version"],{cwd:ie,env:{...t.env,YARN_IGNORE_PATH:"1"}});if(e=ce.trim(),!oBe.default.valid(e))throw new Error(`Invalid semver version. ${he.pretty(t,"yarn --version",he.Type.CODE)} returned: +${e}`)})}let f=t.projectCwd??t.startingCwd,p=K.resolve(f,".yarn/releases"),h=K.resolve(p,`yarn-${e}.cjs`),E=K.relative(t.startingCwd,h),C=je.isTaggedYarnVersion(e),S=t.get("yarnPath"),P=!C,I=P||!!S||!!a;if(a===!1){if(P)throw new Yt(0,"You explicitly opted out of yarnPath usage in your command line, but the version you specified cannot be represented by Corepack");I=!1}else!I&&!process.env.COREPACK_ROOT&&(s.reportWarning(0,`You don't seem to have ${he.applyHyperlink(t,"Corepack","https://nodejs.org/api/corepack.html")} enabled; we'll have to rely on ${he.applyHyperlink(t,"yarnPath","https://yarnpkg.com/configuration/yarnrc#yarnPath")} instead`),I=!0);if(I){let te=await c();s.reportInfo(0,`Saving the new release in ${he.pretty(t,E,"magenta")}`),await le.removePromise(K.dirname(h)),await le.mkdirPromise(K.dirname(h),{recursive:!0}),await le.writeFilePromise(h,te,{mode:493}),await ze.updateConfiguration(f,{yarnPath:K.relative(f,h)})}else await le.removePromise(K.dirname(h)),await ze.updateConfiguration(f,{yarnPath:ze.deleteProperty});let R=await Ht.tryFind(f)||new Ht;R.packageManager=`yarn@${C?e:await hS(t,"stable")}`;let N={};R.exportTo(N);let U=K.join(f,Ht.fileName),W=`${JSON.stringify(N,null,R.indent)} +`;return await le.changeFilePromise(U,W,{automaticNewlines:!0}),{bundleVersion:e}}function aBe(t){return Dr[rk(t)]}var aSt=/## (?YN[0-9]{4}) - `(?[A-Z_]+)`\n\n(?
(?:.(?!##))+)/gs;async function lSt(t){let r=`https://repo.yarnpkg.com/${je.isTaggedYarnVersion(un)?un:await hS(t,"canary")}/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx`,s=await An.get(r,{configuration:t});return new Map(Array.from(s.toString().matchAll(aSt),({groups:a})=>{if(!a)throw new Error("Assertion failed: Expected the match to have been successful");let n=aBe(a.code);if(a.name!==n)throw new Error(`Assertion failed: Invalid error code data: Expected "${a.name}" to be named "${n}"`);return[a.code,a.details]}))}var wC=class extends ut{constructor(){super(...arguments);this.code=ge.String({required:!1,validator:rB(wE(),[tB(/^YN[0-9]{4}$/)])});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["explain"]]}static{this.usage=ot.Usage({description:"explain an error code",details:` + When the code argument is specified, this command prints its name and its details. + + When used without arguments, this command lists all error codes and their names. + `,examples:[["Explain an error code","$0 explain YN0006"],["List all error codes","$0 explain"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);if(typeof this.code<"u"){let s=aBe(this.code),a=he.pretty(r,s,he.Type.CODE),n=this.cli.format().header(`${this.code} - ${a}`),f=(await lSt(r)).get(this.code),p=typeof f<"u"?he.jsonOrPretty(this.json,r,he.tuple(he.Type.MARKDOWN,{text:f,format:this.cli.format(),paragraphs:!0})):`This error code does not have a description. + +You can help us by editing this page on GitHub \u{1F642}: +${he.jsonOrPretty(this.json,r,he.tuple(he.Type.URL,"https://github.com/yarnpkg/berry/blob/master/packages/docusaurus/docs/advanced/01-general-reference/error-codes.mdx"))} +`;this.json?this.context.stdout.write(`${JSON.stringify({code:this.code,name:s,details:p})} +`):this.context.stdout.write(`${n} + +${p} +`)}else{let s={children:je.mapAndFilter(Object.entries(Dr),([a,n])=>Number.isNaN(Number(a))?je.mapAndFilter.skip:{label:Vf(Number(a)),value:he.tuple(he.Type.CODE,n)})};ks.emitTree(s,{configuration:r,stdout:this.context.stdout,json:this.json})}}};Ve();bt();Wt();var lBe=et(Sa()),BC=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Print versions of a package from the whole project"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Print information for all packages, including transitive dependencies"});this.extra=ge.Array("-X,--extra",[],{description:"An array of requests of extra data provided by plugins"});this.cache=ge.Boolean("--cache",!1,{description:"Print information about the cache entry of a package (path, size, checksum)"});this.dependents=ge.Boolean("--dependents",!1,{description:"Print all dependents for each matching package"});this.manifest=ge.Boolean("--manifest",!1,{description:"Print data obtained by looking at the package archive (license, homepage, ...)"});this.nameOnly=ge.Boolean("--name-only",!1,{description:"Only print the name for the matching packages"});this.virtuals=ge.Boolean("--virtuals",!1,{description:"Print each instance of the virtual packages"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.patterns=ge.Rest()}static{this.paths=[["info"]]}static{this.usage=ot.Usage({description:"see information related to packages",details:"\n This command prints various information related to the specified packages, accepting glob patterns.\n\n By default, if the locator reference is missing, Yarn will default to print the information about all the matching direct dependencies of the package for the active workspace. To instead print all versions of the package that are direct dependencies of any of your workspaces, use the `-A,--all` flag. Adding the `-R,--recursive` flag will also report transitive dependencies.\n\n Some fields will be hidden by default in order to keep the output readable, but can be selectively displayed by using additional options (`--dependents`, `--manifest`, `--virtuals`, ...) described in the option descriptions.\n\n Note that this command will only print the information directly related to the selected packages - if you wish to know why the package is there in the first place, use `yarn why` which will do just that (it also provides a `-R,--recursive` flag that may be of some help).\n ",examples:[["Show information about Lodash","$0 info lodash"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a&&!this.all)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=new Set(this.extra);this.cache&&c.add("cache"),this.dependents&&c.add("dependents"),this.manifest&&c.add("manifest");let f=(ie,{recursive:Ae})=>{let ce=ie.anchoredLocator.locatorHash,me=new Map,pe=[ce];for(;pe.length>0;){let Be=pe.shift();if(me.has(Be))continue;let Ce=s.storedPackages.get(Be);if(typeof Ce>"u")throw new Error("Assertion failed: Expected the package to be registered");if(me.set(Be,Ce),q.isVirtualLocator(Ce)&&pe.push(q.devirtualizeLocator(Ce).locatorHash),!(!Ae&&Be!==ce))for(let g of Ce.dependencies.values()){let we=s.storedResolutions.get(g.descriptorHash);if(typeof we>"u")throw new Error("Assertion failed: Expected the resolution to be registered");pe.push(we)}}return me.values()},p=({recursive:ie})=>{let Ae=new Map;for(let ce of s.workspaces)for(let me of f(ce,{recursive:ie}))Ae.set(me.locatorHash,me);return Ae.values()},h=({all:ie,recursive:Ae})=>ie&&Ae?s.storedPackages.values():ie?p({recursive:Ae}):f(a,{recursive:Ae}),E=({all:ie,recursive:Ae})=>{let ce=h({all:ie,recursive:Ae}),me=this.patterns.map(Ce=>{let g=q.parseLocator(Ce),we=lBe.default.makeRe(q.stringifyIdent(g)),Ee=q.isVirtualLocator(g),fe=Ee?q.devirtualizeLocator(g):g;return se=>{let X=q.stringifyIdent(se);if(!we.test(X))return!1;if(g.reference==="unknown")return!0;let De=q.isVirtualLocator(se),Re=De?q.devirtualizeLocator(se):se;return!(Ee&&De&&g.reference!==se.reference||fe.reference!==Re.reference)}}),pe=je.sortMap([...ce],Ce=>q.stringifyLocator(Ce));return{selection:pe.filter(Ce=>me.length===0||me.some(g=>g(Ce))),sortedLookup:pe}},{selection:C,sortedLookup:S}=E({all:this.all,recursive:this.recursive});if(C.length===0)throw new nt("No package matched your request");let P=new Map;if(this.dependents)for(let ie of S)for(let Ae of ie.dependencies.values()){let ce=s.storedResolutions.get(Ae.descriptorHash);if(typeof ce>"u")throw new Error("Assertion failed: Expected the resolution to be registered");je.getArrayWithDefault(P,ce).push(ie)}let I=new Map;for(let ie of S){if(!q.isVirtualLocator(ie))continue;let Ae=q.devirtualizeLocator(ie);je.getArrayWithDefault(I,Ae.locatorHash).push(ie)}let R={},N={children:R},U=r.makeFetcher(),W={project:s,fetcher:U,cache:n,checksums:s.storedChecksums,report:new Wi,cacheOptions:{skipIntegrityCheck:!0}},te=[async(ie,Ae,ce)=>{if(!Ae.has("manifest"))return;let me=await U.fetch(ie,W),pe;try{pe=await Ht.find(me.prefixPath,{baseFs:me.packageFs})}finally{me.releaseFs?.()}ce("Manifest",{License:he.tuple(he.Type.NO_HINT,pe.license),Homepage:he.tuple(he.Type.URL,pe.raw.homepage??null)})},async(ie,Ae,ce)=>{if(!Ae.has("cache"))return;let me=s.storedChecksums.get(ie.locatorHash)??null,pe=n.getLocatorPath(ie,me),Be;if(pe!==null)try{Be=await le.statPromise(pe)}catch{}let Ce=typeof Be<"u"?[Be.size,he.Type.SIZE]:void 0;ce("Cache",{Checksum:he.tuple(he.Type.NO_HINT,me),Path:he.tuple(he.Type.PATH,pe),Size:Ce})}];for(let ie of C){let Ae=q.isVirtualLocator(ie);if(!this.virtuals&&Ae)continue;let ce={},me={value:[ie,he.Type.LOCATOR],children:ce};if(R[q.stringifyLocator(ie)]=me,this.nameOnly){delete me.children;continue}let pe=I.get(ie.locatorHash);typeof pe<"u"&&(ce.Instances={label:"Instances",value:he.tuple(he.Type.NUMBER,pe.length)}),ce.Version={label:"Version",value:he.tuple(he.Type.NO_HINT,ie.version)};let Be=(g,we)=>{let Ee={};if(ce[g]=Ee,Array.isArray(we))Ee.children=we.map(fe=>({value:fe}));else{let fe={};Ee.children=fe;for(let[se,X]of Object.entries(we))typeof X>"u"||(fe[se]={label:se,value:X})}};if(!Ae){for(let g of te)await g(ie,c,Be);await r.triggerHook(g=>g.fetchPackageInfo,ie,c,Be)}ie.bin.size>0&&!Ae&&Be("Exported Binaries",[...ie.bin.keys()].map(g=>he.tuple(he.Type.PATH,g)));let Ce=P.get(ie.locatorHash);typeof Ce<"u"&&Ce.length>0&&Be("Dependents",Ce.map(g=>he.tuple(he.Type.LOCATOR,g))),ie.dependencies.size>0&&!Ae&&Be("Dependencies",[...ie.dependencies.values()].map(g=>{let we=s.storedResolutions.get(g.descriptorHash),Ee=typeof we<"u"?s.storedPackages.get(we)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:Ee})})),ie.peerDependencies.size>0&&Ae&&Be("Peer dependencies",[...ie.peerDependencies.values()].map(g=>{let we=ie.dependencies.get(g.identHash),Ee=typeof we<"u"?s.storedResolutions.get(we.descriptorHash)??null:null,fe=Ee!==null?s.storedPackages.get(Ee)??null:null;return he.tuple(he.Type.RESOLUTION,{descriptor:g,locator:fe})}))}ks.emitTree(N,{configuration:r,json:this.json,stdout:this.context.stdout,separators:this.nameOnly?0:2})}};Ve();bt();Bc();var aF=et(Nd());Wt();var L5=et(fi());Ul();var cSt=[{selector:t=>t===-1,name:"nodeLinker",value:"node-modules"},{selector:t=>t!==-1&&t<8,name:"enableGlobalCache",value:!1},{selector:t=>t!==-1&&t<8,name:"compressionLevel",value:"mixed"}],vC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.immutable=ge.Boolean("--immutable",{description:"Abort with an error exit code if the lockfile was to be modified"});this.immutableCache=ge.Boolean("--immutable-cache",{description:"Abort with an error exit code if the cache folder was to be modified"});this.refreshLockfile=ge.Boolean("--refresh-lockfile",{description:"Refresh the package metadata stored in the lockfile"});this.checkCache=ge.Boolean("--check-cache",{description:"Always refetch the packages and ensure that their checksums are consistent"});this.checkResolutions=ge.Boolean("--check-resolutions",{description:"Validates that the package resolutions are coherent"});this.inlineBuilds=ge.Boolean("--inline-builds",{description:"Verbosely print the output of the build steps of dependencies"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:Ao(ec)});this.cacheFolder=ge.String("--cache-folder",{hidden:!0});this.frozenLockfile=ge.Boolean("--frozen-lockfile",{hidden:!0});this.ignoreEngines=ge.Boolean("--ignore-engines",{hidden:!0});this.nonInteractive=ge.Boolean("--non-interactive",{hidden:!0});this.preferOffline=ge.Boolean("--prefer-offline",{hidden:!0});this.production=ge.Boolean("--production",{hidden:!0});this.registry=ge.String("--registry",{hidden:!0});this.silent=ge.Boolean("--silent",{hidden:!0});this.networkTimeout=ge.String("--network-timeout",{hidden:!0})}static{this.paths=[["install"],ot.Default]}static{this.usage=ot.Usage({description:"install the project dependencies",details:"\n This command sets up your project if needed. The installation is split into four different steps that each have their own characteristics:\n\n - **Resolution:** First the package manager will resolve your dependencies. The exact way a dependency version is privileged over another isn't standardized outside of the regular semver guarantees. If a package doesn't resolve to what you would expect, check that all dependencies are correctly declared (also check our website for more information: ).\n\n - **Fetch:** Then we download all the dependencies if needed, and make sure that they're all stored within our cache (check the value of `cacheFolder` in `yarn config` to see where the cache files are stored).\n\n - **Link:** Then we send the dependency tree information to internal plugins tasked with writing them on the disk in some form (for example by generating the `.pnp.cjs` file you might know).\n\n - **Build:** Once the dependency tree has been written on the disk, the package manager will now be free to run the build scripts for all packages that might need it, in a topological order compatible with the way they depend on one another. See https://yarnpkg.com/advanced/lifecycle-scripts for detail.\n\n Note that running this command is not part of the recommended workflow. Yarn supports zero-installs, which means that as long as you store your cache and your `.pnp.cjs` file inside your repository, everything will work without requiring any install right after cloning your repository or switching branches.\n\n If the `--immutable` option is set (defaults to true on CI), Yarn will abort with an error exit code if the lockfile was to be modified (other paths can be added using the `immutablePatterns` configuration setting). For backward compatibility we offer an alias under the name of `--frozen-lockfile`, but it will be removed in a later release.\n\n If the `--immutable-cache` option is set, Yarn will abort with an error exit code if the cache folder was to be modified (either because files would be added, or because they'd be removed).\n\n If the `--refresh-lockfile` option is set, Yarn will keep the same resolution for the packages currently in the lockfile but will refresh their metadata. If used together with `--immutable`, it can validate that the lockfile information are consistent. This flag is enabled by default when Yarn detects it runs within a pull request context.\n\n If the `--check-cache` option is set, Yarn will always refetch the packages and will ensure that their checksum matches what's 1/ described in the lockfile 2/ inside the existing cache files (if present). This is recommended as part of your CI workflow if you're both following the Zero-Installs model and accepting PRs from third-parties, as they'd otherwise have the ability to alter the checked-in packages before submitting them.\n\n If the `--inline-builds` option is set, Yarn will verbosely print the output of the build steps of your dependencies (instead of writing them into individual files). This is likely useful mostly for debug purposes only when using Docker-like environments.\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n ",examples:[["Install the project","$0 install"],["Validate a project when using Zero-Installs","$0 install --immutable --immutable-cache"],["Validate a project when using Zero-Installs (slightly safer if you accept external PRs)","$0 install --immutable --immutable-cache --check-cache"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);typeof this.inlineBuilds<"u"&&r.useWithSource("",{enableInlineBuilds:this.inlineBuilds},r.startingCwd,{overwrite:!0});let s=!!process.env.FUNCTION_TARGET||!!process.env.GOOGLE_RUNTIME,a=await DI({configuration:r,stdout:this.context.stdout},[{option:this.ignoreEngines,message:"The --ignore-engines option is deprecated; engine checking isn't a core feature anymore",error:!aF.default.VERCEL},{option:this.registry,message:"The --registry option is deprecated; prefer setting npmRegistryServer in your .yarnrc.yml file"},{option:this.preferOffline,message:"The --prefer-offline flag is deprecated; use the --cached flag with 'yarn add' instead",error:!aF.default.VERCEL},{option:this.production,message:"The --production option is deprecated on 'install'; use 'yarn workspaces focus' instead",error:!0},{option:this.nonInteractive,message:"The --non-interactive option is deprecated",error:!s},{option:this.frozenLockfile,message:"The --frozen-lockfile option is deprecated; use --immutable and/or --immutable-cache instead",callback:()=>this.immutable=this.frozenLockfile},{option:this.cacheFolder,message:"The cache-folder option has been deprecated; use rc settings instead",error:!aF.default.NETLIFY}]);if(a!==null)return a;let n=this.mode==="update-lockfile";if(n&&(this.immutable||this.immutableCache))throw new nt(`${he.pretty(r,"--immutable",he.Type.CODE)} and ${he.pretty(r,"--immutable-cache",he.Type.CODE)} cannot be used with ${he.pretty(r,"--mode=update-lockfile",he.Type.CODE)}`);let c=(this.immutable??r.get("enableImmutableInstalls"))&&!n,f=this.immutableCache&&!n;if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U=!1;await ASt(r,c)&&(N.reportInfo(48,"Automatically removed core plugins that are now builtins \u{1F44D}"),U=!0),await fSt(r,c)&&(N.reportInfo(48,"Automatically fixed merge conflicts \u{1F44D}"),U=!0),U&&N.reportSeparator()});if(R.hasErrors())return R.exitCode()}if(r.projectCwd!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{if(ze.telemetry?.isNew)ze.telemetry.commitTips(),N.reportInfo(65,"Yarn will periodically gather anonymous telemetry: https://yarnpkg.com/advanced/telemetry"),N.reportInfo(65,`Run ${he.pretty(r,"yarn config set --home enableTelemetry 0",he.Type.CODE)} to disable`),N.reportSeparator();else if(ze.telemetry?.shouldShowTips){let U=await An.get("https://repo.yarnpkg.com/tags",{configuration:r,jsonResponse:!0}).catch(()=>null);if(U!==null){let W=null;if(un!==null){let ie=L5.default.prerelease(un)?"canary":"stable",Ae=U.latest[ie];L5.default.gt(Ae,un)&&(W=[ie,Ae])}if(W)ze.telemetry.commitTips(),N.reportInfo(88,`${he.applyStyle(r,`A new ${W[0]} version of Yarn is available:`,he.Style.BOLD)} ${q.prettyReference(r,W[1])}!`),N.reportInfo(88,`Upgrade now by running ${he.pretty(r,`yarn set version ${W[1]}`,he.Type.CODE)}`),N.reportSeparator();else{let te=ze.telemetry.selectTip(U.tips);te&&(N.reportInfo(89,he.pretty(r,te.message,he.Type.MARKDOWN_INLINE)),te.url&&N.reportInfo(89,`Learn more at ${te.url}`),N.reportSeparator())}}}});if(R.hasErrors())return R.exitCode()}let{project:p,workspace:h}=await Tt.find(r,this.context.cwd),E=p.lockfileLastVersion;if(E!==null){let R=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async N=>{let U={};for(let W of cSt)W.selector(E)&&typeof r.sources.get(W.name)>"u"&&(r.use("",{[W.name]:W.value},p.cwd,{overwrite:!0}),U[W.name]=W.value);Object.keys(U).length>0&&(await ze.updateConfiguration(p.cwd,U),N.reportInfo(87,"Migrated your project to the latest Yarn version \u{1F680}"),N.reportSeparator())});if(R.hasErrors())return R.exitCode()}let C=await Jr.find(r,{immutable:f,check:this.checkCache});if(!h)throw new ar(p.cwd,this.context.cwd);await p.restoreInstallState({restoreResolutions:!1});let S=r.get("enableHardenedMode");S&&typeof r.sources.get("enableHardenedMode")>"u"&&await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,includeFooter:!1},async R=>{R.reportWarning(0,"Yarn detected that the current workflow is executed from a public pull request. For safety the hardened mode has been enabled."),R.reportWarning(0,`It will prevent malicious lockfile manipulations, in exchange for a slower install time. You can opt-out if necessary; check our ${he.applyHyperlink(r,"documentation","https://yarnpkg.com/features/security#hardened-mode")} for more details.`),R.reportSeparator()}),(this.refreshLockfile??S)&&(p.lockfileNeedsRefresh=!0);let P=this.checkResolutions??S;return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout,forceSectionAlignment:!0,includeLogs:!0,includeVersion:!0},async R=>{await p.install({cache:C,report:R,immutable:c,checkResolutions:P,mode:this.mode})})).exitCode()}},uSt="<<<<<<<";async function fSt(t,e){if(!t.projectCwd)return!1;let r=K.join(t.projectCwd,Er.lockfile);if(!await le.existsPromise(r)||!(await le.readFilePromise(r,"utf8")).includes(uSt))return!1;if(e)throw new Yt(47,"Cannot autofix a lockfile when running an immutable install");let a=await Gr.execvp("git",["rev-parse","MERGE_HEAD","HEAD"],{cwd:t.projectCwd});if(a.code!==0&&(a=await Gr.execvp("git",["rev-parse","REBASE_HEAD","HEAD"],{cwd:t.projectCwd})),a.code!==0&&(a=await Gr.execvp("git",["rev-parse","CHERRY_PICK_HEAD","HEAD"],{cwd:t.projectCwd})),a.code!==0)throw new Yt(83,"Git returned an error when trying to find the commits pertaining to the conflict");let n=await Promise.all(a.stdout.trim().split(/\n/).map(async f=>{let p=await Gr.execvp("git",["show",`${f}:./${Er.lockfile}`],{cwd:t.projectCwd});if(p.code!==0)throw new Yt(83,`Git returned an error when trying to access the lockfile content in ${f}`);try{return ls(p.stdout)}catch{throw new Yt(46,"A variant of the conflicting lockfile failed to parse")}}));n=n.filter(f=>!!f.__metadata);for(let f of n){if(f.__metadata.version<7)for(let p of Object.keys(f)){if(p==="__metadata")continue;let h=q.parseDescriptor(p,!0),E=t.normalizeDependency(h),C=q.stringifyDescriptor(E);C!==p&&(f[C]=f[p],delete f[p])}for(let p of Object.keys(f)){if(p==="__metadata")continue;let h=f[p].checksum;typeof h>"u"||h.includes("/")||(f[p].checksum=`${f.__metadata.cacheKey}/${h}`)}}let c=Object.assign({},...n);c.__metadata.version=`${Math.min(...n.map(f=>parseInt(f.__metadata.version??0)))}`,c.__metadata.cacheKey="merged";for(let[f,p]of Object.entries(c))typeof p=="string"&&delete c[f];return await le.changeFilePromise(r,il(c),{automaticNewlines:!0}),!0}async function ASt(t,e){if(!t.projectCwd)return!1;let r=[],s=K.join(t.projectCwd,".yarn/plugins/@yarnpkg");return await ze.updateConfiguration(t.projectCwd,{plugins:n=>{if(!Array.isArray(n))return n;let c=n.filter(f=>{if(!f.path)return!0;let p=K.resolve(t.projectCwd,f.path),h=Ev.has(f.spec)&&K.contains(s,p);return h&&r.push(p),!h});return c.length===0?ze.deleteProperty:c.length===n.length?n:c}},{immutable:e})?(await Promise.all(r.map(async n=>{await le.removePromise(n)})),!0):!1}Ve();bt();Wt();var SC=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Link all workspaces belonging to the target projects to the current one"});this.private=ge.Boolean("-p,--private",!1,{description:"Also link private workspaces belonging to the target projects to the current one"});this.relative=ge.Boolean("-r,--relative",!1,{description:"Link workspaces using relative paths instead of absolute paths"});this.destinations=ge.Rest()}static{this.paths=[["link"]]}static{this.usage=ot.Usage({description:"connect the local project to another one",details:"\n This command will set a new `resolutions` field in the project-level manifest and point it to the workspace at the specified location (even if part of another project).\n ",examples:[["Register one or more remote workspaces for use in the current project","$0 link ~/ts-loader ~/jest"],["Register all workspaces from a remote project for use in the current project","$0 link ~/jest --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=s.topLevelWorkspace,f=[];for(let p of this.destinations){let h=K.resolve(this.context.cwd,ue.toPortablePath(p)),E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(s.cwd===C.cwd)throw new nt(`Invalid destination '${p}'; Can't link the project to itself`);if(!S)throw new ar(C.cwd,h);if(this.all){let P=!1;for(let I of C.workspaces)I.manifest.name&&(!I.manifest.private||this.private)&&(f.push(I),P=!0);if(!P)throw new nt(`No workspace found to be linked in the target project: ${p}`)}else{if(!S.manifest.name)throw new nt(`The target workspace at '${p}' doesn't have a name and thus cannot be linked`);if(S.manifest.private&&!this.private)throw new nt(`The target workspace at '${p}' is marked private - use the --private flag to link it anyway`);f.push(S)}}for(let p of f){let h=q.stringifyIdent(p.anchoredLocator),E=this.relative?K.relative(s.cwd,p.cwd):p.cwd;c.manifest.resolutions.push({pattern:{descriptor:{fullName:h}},reference:`portal:${E}`})}return await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Wt();var DC=class extends ut{constructor(){super(...arguments);this.args=ge.Proxy()}static{this.paths=[["node"]]}static{this.usage=ot.Usage({description:"run node with the hook already setup",details:` + This command simply runs Node. It also makes sure to call it in a way that's compatible with the current project (for example, on PnP projects the environment will be setup in such a way that PnP will be correctly injected into the environment). + + The Node process will use the exact same version of Node as the one used to run Yarn itself, which might be a good way to ensure that your commands always use a consistent Node version. + `,examples:[["Run a Node script","$0 node ./my-script.js"]]})}async execute(){return this.cli.run(["exec","node",...this.args])}};Ve();Wt();var bC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","check"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"find all third-party plugins that differ from their own spec",details:` + Check only the plugins from https. + + If this command detects any plugin differences in the CI environment, it will throw an error. + `,examples:[["find all third-party plugins that differ from their own spec","$0 plugin check"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await ze.findRcFiles(this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{for(let c of s)if(c.data?.plugins)for(let f of c.data.plugins){if(!f.checksum||!f.spec.match(/^https?:/))continue;let p=await An.get(f.spec,{configuration:r}),h=Nn.makeHash(p);if(f.checksum===h)continue;let E=he.pretty(r,f.path,he.Type.PATH),C=he.pretty(r,f.spec,he.Type.URL),S=`${E} is different from the file provided by ${C}`;n.reportJson({...f,newChecksum:h}),n.reportError(0,S)}})).exitCode()}};Ve();Ve();bt();Wt();var pBe=ye("os");Ve();bt();Wt();var cBe=ye("os");Ve();Bc();Wt();var pSt="https://raw.githubusercontent.com/yarnpkg/berry/master/plugins.yml";async function Dm(t,e){let r=await An.get(pSt,{configuration:t}),s=ls(r.toString());return Object.fromEntries(Object.entries(s).filter(([a,n])=>!e||Or.satisfiesWithPrereleases(e,n.range??"<4.0.0-rc.1")))}var PC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","list"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"list the available official plugins",details:"\n This command prints the plugins available directly from the Yarn repository. Only those plugins can be referenced by name in `yarn plugin import`.\n ",examples:[["List the official plugins","$0 plugin list"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{let n=await Dm(r,un);for(let[c,{experimental:f,...p}]of Object.entries(n)){let h=c;f&&(h+=" [experimental]"),a.reportJson({name:c,experimental:f,...p}),a.reportInfo(null,h)}})).exitCode()}};var hSt=/^[0-9]+$/,gSt=process.platform==="win32";function uBe(t){return hSt.test(t)?`pull/${t}/head`:t}var dSt=({repository:t,branch:e},r)=>[["git","init",ue.fromPortablePath(r)],["git","remote","add","origin",t],["git","fetch","origin","--depth=1",uBe(e)],["git","reset","--hard","FETCH_HEAD"]],mSt=({branch:t})=>[["git","fetch","origin","--depth=1",uBe(t),"--force"],["git","reset","--hard","FETCH_HEAD"],["git","clean","-dfx","-e","packages/yarnpkg-cli/bundles"]],ySt=({plugins:t,noMinify:e},r,s)=>[["yarn","build:cli",...new Array().concat(...t.map(a=>["--plugin",K.resolve(s,a)])),...e?["--no-minify"]:[],"|"],[gSt?"move":"mv","packages/yarnpkg-cli/bundles/yarn.js",ue.fromPortablePath(r),"|"]],xC=class extends ut{constructor(){super(...arguments);this.installPath=ge.String("--path",{description:"The path where the repository should be cloned to"});this.repository=ge.String("--repository","https://github.com/yarnpkg/berry.git",{description:"The repository that should be cloned"});this.branch=ge.String("--branch","master",{description:"The branch of the repository that should be cloned"});this.plugins=ge.Array("--plugin",[],{description:"An array of additional plugins that should be included in the bundle"});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"If set, the bundle will be built but not added to the project"});this.noMinify=ge.Boolean("--no-minify",!1,{description:"Build a bundle for development (debugging) - non-minified and non-mangled"});this.force=ge.Boolean("-f,--force",!1,{description:"Always clone the repository instead of trying to fetch the latest commits"});this.skipPlugins=ge.Boolean("--skip-plugins",!1,{description:"Skip updating the contrib plugins"})}static{this.paths=[["set","version","from","sources"]]}static{this.usage=ot.Usage({description:"build Yarn from master",details:` + This command will clone the Yarn repository into a temporary folder, then build it. The resulting bundle will then be copied into the local project. + + By default, it also updates all contrib plugins to the same commit the bundle is built from. This behavior can be disabled by using the \`--skip-plugins\` flag. + `,examples:[["Build Yarn from master","$0 set version from sources"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.installPath<"u"?K.resolve(this.context.cwd,ue.toPortablePath(this.installPath)):K.resolve(ue.toPortablePath((0,cBe.tmpdir)()),"yarnpkg-sources",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{await M5(this,{configuration:r,report:c,target:a}),c.reportSeparator(),c.reportInfo(0,"Building a fresh bundle"),c.reportSeparator();let f=await Gr.execvp("git",["rev-parse","--short","HEAD"],{cwd:a,strict:!0}),p=K.join(a,`packages/yarnpkg-cli/bundles/yarn-${f.stdout.trim()}.js`);le.existsSync(p)||(await gS(ySt(this,p,a),{configuration:r,context:this.context,target:a}),c.reportSeparator());let h=await le.readFilePromise(p);if(!this.dryRun){let{bundleVersion:E}=await O5(r,null,async()=>h,{report:c});this.skipPlugins||await ESt(this,E,{project:s,report:c,target:a})}})).exitCode()}};async function gS(t,{configuration:e,context:r,target:s}){for(let[a,...n]of t){let c=n[n.length-1]==="|";if(c&&n.pop(),c)await Gr.pipevp(a,n,{cwd:s,stdin:r.stdin,stdout:r.stdout,stderr:r.stderr,strict:!0});else{r.stdout.write(`${he.pretty(e,` $ ${[a,...n].join(" ")}`,"grey")} +`);try{await Gr.execvp(a,n,{cwd:s,strict:!0})}catch(f){throw r.stdout.write(f.stdout||f.stack),f}}}}async function M5(t,{configuration:e,report:r,target:s}){let a=!1;if(!t.force&&le.existsSync(K.join(s,".git"))){r.reportInfo(0,"Fetching the latest commits"),r.reportSeparator();try{await gS(mSt(t),{configuration:e,context:t.context,target:s}),a=!0}catch{r.reportSeparator(),r.reportWarning(0,"Repository update failed; we'll try to regenerate it")}}a||(r.reportInfo(0,"Cloning the remote repository"),r.reportSeparator(),await le.removePromise(s),await le.mkdirPromise(s,{recursive:!0}),await gS(dSt(t,s),{configuration:e,context:t.context,target:s}))}async function ESt(t,e,{project:r,report:s,target:a}){let n=await Dm(r.configuration,e),c=new Set(Object.keys(n));for(let f of r.configuration.plugins.keys())c.has(f)&&await _5(f,t,{project:r,report:s,target:a})}Ve();Ve();bt();Wt();var fBe=et(fi()),ABe=ye("vm");var kC=class extends ut{constructor(){super(...arguments);this.name=ge.String();this.checksum=ge.Boolean("--checksum",!0,{description:"Whether to care if this plugin is modified"})}static{this.paths=[["plugin","import"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"download a plugin",details:` + This command downloads the specified plugin from its remote location and updates the configuration to reference it in further CLI invocations. + + Three types of plugin references are accepted: + + - If the plugin is stored within the Yarn repository, it can be referenced by name. + - Third-party plugins can be referenced directly through their public urls. + - Local plugins can be referenced by their path on the disk. + + If the \`--no-checksum\` option is set, Yarn will no longer care if the plugin is modified. + + Plugins cannot be downloaded from the npm registry, and aren't allowed to have dependencies (they need to be bundled into a single file, possibly thanks to the \`@yarnpkg/builder\` package). + `,examples:[['Download and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import @yarnpkg/plugin-exec"],['Download and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import exec"],["Download and activate a community plugin","$0 plugin import https://example.org/path/to/plugin.js"],["Activate a local plugin","$0 plugin import ./path/to/plugin.js"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,stdout:this.context.stdout},async a=>{let{project:n}=await Tt.find(r,this.context.cwd),c,f;if(this.name.match(/^\.{0,2}[\\/]/)||ue.isAbsolute(this.name)){let p=K.resolve(this.context.cwd,ue.toPortablePath(this.name));a.reportInfo(0,`Reading ${he.pretty(r,p,he.Type.PATH)}`),c=K.relative(n.cwd,p),f=await le.readFilePromise(p)}else{let p;if(this.name.match(/^https?:/)){try{new URL(this.name)}catch{throw new Yt(52,`Plugin specifier "${this.name}" is neither a plugin name nor a valid url`)}c=this.name,p=this.name}else{let h=q.parseLocator(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-"));if(h.reference!=="unknown"&&!fBe.default.valid(h.reference))throw new Yt(0,"Official plugins only accept strict version references. Use an explicit URL if you wish to download them from another location.");let E=q.stringifyIdent(h),C=await Dm(r,un);if(!Object.hasOwn(C,E)){let S=`Couldn't find a plugin named ${q.prettyIdent(r,h)} on the remote registry. +`;throw r.plugins.has(E)?S+=`A plugin named ${q.prettyIdent(r,h)} is already installed; possibly attempting to import a built-in plugin.`:S+=`Note that only the plugins referenced on our website (${he.pretty(r,"https://github.com/yarnpkg/berry/blob/master/plugins.yml",he.Type.URL)}) can be referenced by their name; any other plugin will have to be referenced through its public url (for example ${he.pretty(r,"https://github.com/yarnpkg/berry/raw/master/packages/plugin-typescript/bin/%40yarnpkg/plugin-typescript.js",he.Type.URL)}).`,new Yt(51,S)}c=E,p=C[E].url,h.reference!=="unknown"?p=p.replace(/\/master\//,`/${E}/${h.reference}/`):un!==null&&(p=p.replace(/\/master\//,`/@yarnpkg/cli/${un}/`))}a.reportInfo(0,`Downloading ${he.pretty(r,p,"green")}`),f=await An.get(p,{configuration:r})}await U5(c,f,{checksum:this.checksum,project:n,report:a})})).exitCode()}};async function U5(t,e,{checksum:r=!0,project:s,report:a}){let{configuration:n}=s,c={},f={exports:c};(0,ABe.runInNewContext)(e.toString(),{module:f,exports:c});let h=`.yarn/plugins/${f.exports.name}.cjs`,E=K.resolve(s.cwd,h);a.reportInfo(0,`Saving the new plugin in ${he.pretty(n,h,"magenta")}`),await le.mkdirPromise(K.dirname(E),{recursive:!0}),await le.writeFilePromise(E,e);let C={path:h,spec:t};r&&(C.checksum=Nn.makeHash(e)),await ze.addPlugin(s.cwd,[C])}var ISt=({pluginName:t,noMinify:e},r)=>[["yarn",`build:${t}`,...e?["--no-minify"]:[],"|"]],QC=class extends ut{constructor(){super(...arguments);this.installPath=ge.String("--path",{description:"The path where the repository should be cloned to"});this.repository=ge.String("--repository","https://github.com/yarnpkg/berry.git",{description:"The repository that should be cloned"});this.branch=ge.String("--branch","master",{description:"The branch of the repository that should be cloned"});this.noMinify=ge.Boolean("--no-minify",!1,{description:"Build a plugin for development (debugging) - non-minified and non-mangled"});this.force=ge.Boolean("-f,--force",!1,{description:"Always clone the repository instead of trying to fetch the latest commits"});this.name=ge.String()}static{this.paths=[["plugin","import","from","sources"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"build a plugin from sources",details:` + This command clones the Yarn repository into a temporary folder, builds the specified contrib plugin and updates the configuration to reference it in further CLI invocations. + + The plugins can be referenced by their short name if sourced from the official Yarn repository. + `,examples:[['Build and activate the "@yarnpkg/plugin-exec" plugin',"$0 plugin import from sources @yarnpkg/plugin-exec"],['Build and activate the "@yarnpkg/plugin-exec" plugin (shorthand)',"$0 plugin import from sources exec"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.installPath<"u"?K.resolve(this.context.cwd,ue.toPortablePath(this.installPath)):K.resolve(ue.toPortablePath((0,pBe.tmpdir)()),"yarnpkg-sources",Nn.makeHash(this.repository).slice(0,6));return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let{project:c}=await Tt.find(r,this.context.cwd),f=q.parseIdent(this.name.replace(/^((@yarnpkg\/)?plugin-)?/,"@yarnpkg/plugin-")),p=q.stringifyIdent(f),h=await Dm(r,un);if(!Object.hasOwn(h,p))throw new Yt(51,`Couldn't find a plugin named "${p}" on the remote registry. Note that only the plugins referenced on our website (https://github.com/yarnpkg/berry/blob/master/plugins.yml) can be built and imported from sources.`);let E=p;await M5(this,{configuration:r,report:n,target:s}),await _5(E,this,{project:c,report:n,target:s})})).exitCode()}};async function _5(t,{context:e,noMinify:r},{project:s,report:a,target:n}){let c=t.replace(/@yarnpkg\//,""),{configuration:f}=s;a.reportSeparator(),a.reportInfo(0,`Building a fresh ${c}`),a.reportSeparator(),await gS(ISt({pluginName:c,noMinify:r},n),{configuration:f,context:e,target:n}),a.reportSeparator();let p=K.resolve(n,`packages/${c}/bundles/${t}.js`),h=await le.readFilePromise(p);await U5(t,h,{project:s,report:a})}Ve();bt();Wt();var TC=class extends ut{constructor(){super(...arguments);this.name=ge.String()}static{this.paths=[["plugin","remove"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"remove a plugin",details:` + This command deletes the specified plugin from the .yarn/plugins folder and removes it from the configuration. + + **Note:** The plugins have to be referenced by their name property, which can be obtained using the \`yarn plugin runtime\` command. Shorthands are not allowed. + `,examples:[["Remove a plugin imported from the Yarn repository","$0 plugin remove @yarnpkg/plugin-typescript"],["Remove a plugin imported from a local file","$0 plugin remove my-local-plugin"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c=this.name,f=q.parseIdent(c);if(!r.plugins.has(c))throw new nt(`${q.prettyIdent(r,f)} isn't referenced by the current configuration`);let p=`.yarn/plugins/${c}.cjs`,h=K.resolve(s.cwd,p);le.existsSync(h)&&(n.reportInfo(0,`Removing ${he.pretty(r,p,he.Type.PATH)}...`),await le.removePromise(h)),n.reportInfo(0,"Updating the configuration..."),await ze.updateConfiguration(s.cwd,{plugins:E=>{if(!Array.isArray(E))return E;let C=E.filter(S=>S.path!==p);return C.length===0?ze.deleteProperty:C.length===E.length?E:C}})})).exitCode()}};Ve();Wt();var RC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["plugin","runtime"]]}static{this.usage=ot.Usage({category:"Plugin-related commands",description:"list the active plugins",details:` + This command prints the currently active plugins. Will be displayed both builtin plugins and external plugins. + `,examples:[["List the currently active plugins","$0 plugin runtime"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async a=>{for(let n of r.plugins.keys()){let c=this.context.plugins.plugins.has(n),f=n;c&&(f+=" [builtin]"),a.reportJson({name:n,builtin:c}),a.reportInfo(null,`${f}`)}})).exitCode()}};Ve();Ve();Wt();var FC=class extends ut{constructor(){super(...arguments);this.idents=ge.Rest()}static{this.paths=[["rebuild"]]}static{this.usage=ot.Usage({description:"rebuild the project's native packages",details:` + This command will automatically cause Yarn to forget about previous compilations of the given packages and to run them again. + + Note that while Yarn forgets the compilation, the previous artifacts aren't erased from the filesystem and may affect the next builds (in good or bad). To avoid this, you may remove the .yarn/unplugged folder, or any other relevant location where packages might have been stored (Yarn may offer a way to do that automatically in the future). + + By default all packages will be rebuilt, but you can filter the list by specifying the names of the packages you want to clear from memory. + `,examples:[["Rebuild all packages","$0 rebuild"],["Rebuild fsevents only","$0 rebuild fsevents"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=new Set;for(let f of this.idents)c.add(q.parseIdent(f).identHash);if(await s.restoreInstallState({restoreResolutions:!1}),await s.resolveEverything({cache:n,report:new Wi}),c.size>0)for(let f of s.storedPackages.values())c.has(f.identHash)&&(s.storedBuildState.delete(f.locatorHash),s.skippedBuilds.delete(f.locatorHash));else s.storedBuildState.clear(),s.skippedBuilds.clear();return await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ve();Ve();Ve();Wt();var H5=et(Sa());Ul();var NC=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Apply the operation to all workspaces from the current project"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:Ao(ec)});this.patterns=ge.Rest()}static{this.paths=[["remove"]]}static{this.usage=ot.Usage({description:"remove dependencies from the project",details:` + This command will remove the packages matching the specified patterns from the current workspace. + + If the \`--mode=\` option is set, Yarn will change which artifacts are generated. The modes currently supported are: + + - \`skip-build\` will not run the build scripts at all. Note that this is different from setting \`enableScripts\` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run. + + - \`update-lockfile\` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost. + + This command accepts glob patterns as arguments (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them. + `,examples:[["Remove a dependency from the current project","$0 remove lodash"],["Remove a dependency from all workspaces at once","$0 remove lodash --all"],["Remove all dependencies starting with `eslint-`","$0 remove 'eslint-*'"],["Remove all dependencies with the `@babel` scope","$0 remove '@babel/*'"],["Remove all dependencies matching `react-dom` or `react-helmet`","$0 remove 'react-{dom,helmet}'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.all?s.workspaces:[a],f=["dependencies","devDependencies","peerDependencies"],p=[],h=!1,E=[];for(let I of this.patterns){let R=!1,N=q.parseIdent(I);for(let U of c){let W=[...U.manifest.peerDependenciesMeta.keys()];for(let te of(0,H5.default)(W,I))U.manifest.peerDependenciesMeta.delete(te),h=!0,R=!0;for(let te of f){let ie=U.manifest.getForScope(te),Ae=[...ie.values()].map(ce=>q.stringifyIdent(ce));for(let ce of(0,H5.default)(Ae,q.stringifyIdent(N))){let{identHash:me}=q.parseIdent(ce),pe=ie.get(me);if(typeof pe>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");U.manifest[te].delete(me),E.push([U,te,pe]),h=!0,R=!0}}}R||p.push(I)}let C=p.length>1?"Patterns":"Pattern",S=p.length>1?"don't":"doesn't",P=this.all?"any":"this";if(p.length>0)throw new nt(`${C} ${he.prettyList(r,p,he.Type.CODE)} ${S} match any packages referenced by ${P} workspace`);return h?(await r.triggerMultipleHooks(I=>I.afterWorkspaceDependencyRemoval,E),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})):0}};Ve();Ve();Wt();var hBe=ye("util"),OC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["run"]]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return(await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async c=>{let f=a.manifest.scripts,p=je.sortMap(f.keys(),C=>C),h={breakLength:1/0,colors:r.get("enableColors"),maxArrayLength:2},E=p.reduce((C,S)=>Math.max(C,S.length),0);for(let[C,S]of f.entries())c.reportInfo(null,`${C.padEnd(E," ")} ${(0,hBe.inspect)(S,h)}`),c.reportJson({name:C,script:S})})).exitCode()}};Ve();Ve();Wt();var LC=class extends ut{constructor(){super(...arguments);this.inspect=ge.String("--inspect",!1,{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"});this.inspectBrk=ge.String("--inspect-brk",!1,{tolerateBoolean:!0,description:"Forwarded to the underlying Node process when executing a binary"});this.topLevel=ge.Boolean("-T,--top-level",!1,{description:"Check the root workspace for scripts and/or binaries instead of the current one"});this.binariesOnly=ge.Boolean("-B,--binaries-only",!1,{description:"Ignore any user defined scripts and only check for binaries"});this.require=ge.String("--require",{description:"Forwarded to the underlying Node process when executing a binary"});this.silent=ge.Boolean("--silent",{hidden:!0});this.scriptName=ge.String();this.args=ge.Proxy()}static{this.paths=[["run"]]}static{this.usage=ot.Usage({description:"run a script defined in the package.json",details:` + This command will run a tool. The exact tool that will be executed will depend on the current state of your workspace: + + - If the \`scripts\` field from your local package.json contains a matching script name, its definition will get executed. + + - Otherwise, if one of the local workspace's dependencies exposes a binary with a matching name, this binary will get executed. + + - Otherwise, if the specified name contains a colon character and if one of the workspaces in the project contains exactly one script with a matching name, then this script will get executed. + + Whatever happens, the cwd of the spawned process will be the workspace that declares the script (which makes it possible to call commands cross-workspaces using the third syntax). + `,examples:[["Run the tests from the local workspace","$0 run test"],['Same thing, but without the "run" keyword',"$0 test"],["Inspect Webpack while running","$0 run --inspect-brk webpack"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a,locator:n}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let c=this.topLevel?s.topLevelWorkspace.anchoredLocator:n;if(!this.binariesOnly&&await In.hasPackageScript(c,this.scriptName,{project:s}))return await In.executePackageScript(c,this.scriptName,this.args,{project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});let f=await In.getPackageAccessibleBinaries(c,{project:s});if(f.get(this.scriptName)){let h=[];return this.inspect&&(typeof this.inspect=="string"?h.push(`--inspect=${this.inspect}`):h.push("--inspect")),this.inspectBrk&&(typeof this.inspectBrk=="string"?h.push(`--inspect-brk=${this.inspectBrk}`):h.push("--inspect-brk")),this.require&&h.push(`--require=${this.require}`),await In.executePackageAccessibleBinary(c,this.scriptName,this.args,{cwd:this.context.cwd,project:s,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,nodeArgs:h,packageAccessibleBinaries:f})}if(!this.topLevel&&!this.binariesOnly&&a&&this.scriptName.includes(":")){let E=(await Promise.all(s.workspaces.map(async C=>C.manifest.scripts.has(this.scriptName)?C:null))).filter(C=>C!==null);if(E.length===1)return await In.executeWorkspaceScript(E[0],this.scriptName,this.args,{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})}if(this.topLevel)throw this.scriptName==="node-gyp"?new nt(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${q.prettyLocator(r,n)}). This typically happens because some package depends on "node-gyp" to build itself, but didn't list it in their dependencies. To fix that, please run "yarn add node-gyp" into your top-level workspace. You also can open an issue on the repository of the specified package to suggest them to use an optional peer dependency.`):new nt(`Couldn't find a script name "${this.scriptName}" in the top-level (used by ${q.prettyLocator(r,n)}).`);{if(this.scriptName==="global")throw new nt("The 'yarn global' commands have been removed in 2.x - consider using 'yarn dlx' or a third-party plugin instead");let h=[this.scriptName].concat(this.args);for(let[E,C]of tC)for(let S of C)if(h.length>=S.length&&JSON.stringify(h.slice(0,S.length))===JSON.stringify(S))throw new nt(`Couldn't find a script named "${this.scriptName}", but a matching command can be found in the ${E} plugin. You can install it with "yarn plugin import ${E}".`);throw new nt(`Couldn't find a script named "${this.scriptName}".`)}}};Ve();Ve();Wt();var MC=class extends ut{constructor(){super(...arguments);this.descriptor=ge.String();this.resolution=ge.String()}static{this.paths=[["set","resolution"]]}static{this.usage=ot.Usage({description:"enforce a package resolution",details:'\n This command updates the resolution table so that `descriptor` is resolved by `resolution`.\n\n Note that by default this command only affect the current resolution table - meaning that this "manual override" will disappear if you remove the lockfile, or if the package disappear from the table. If you wish to make the enforced resolution persist whatever happens, edit the `resolutions` field in your top-level manifest.\n\n Note that no attempt is made at validating that `resolution` is a valid resolution entry for `descriptor`.\n ',examples:[["Force all instances of lodash@npm:^1.2.3 to resolve to 1.5.0","$0 set resolution lodash@npm:^1.2.3 npm:1.5.0"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(await s.restoreInstallState({restoreResolutions:!1}),!a)throw new ar(s.cwd,this.context.cwd);let c=q.parseDescriptor(this.descriptor,!0),f=q.makeDescriptor(c,this.resolution);return s.storedDescriptors.set(c.descriptorHash,c),s.storedDescriptors.set(f.descriptorHash,f),s.resolutionAliases.set(c.descriptorHash,f.descriptorHash),await s.installWithNewReport({stdout:this.context.stdout},{cache:n})}};Ve();bt();Wt();var gBe=et(Sa()),_C=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Unlink all workspaces belonging to the target project from the current one"});this.leadingArguments=ge.Rest()}static{this.paths=[["unlink"]]}static{this.usage=ot.Usage({description:"disconnect the local project from another one",details:` + This command will remove any resolutions in the project-level manifest that would have been added via a yarn link with similar arguments. + `,examples:[["Unregister a remote workspace in the current project","$0 unlink ~/ts-loader"],["Unregister all workspaces from a remote project in the current project","$0 unlink ~/jest --all"],["Unregister all previously linked workspaces","$0 unlink --all"],["Unregister all workspaces matching a glob","$0 unlink '@babel/*' 'pkg-{a,b}'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);let c=s.topLevelWorkspace,f=new Set;if(this.leadingArguments.length===0&&this.all)for(let{pattern:p,reference:h}of c.manifest.resolutions)h.startsWith("portal:")&&f.add(p.descriptor.fullName);if(this.leadingArguments.length>0)for(let p of this.leadingArguments){let h=K.resolve(this.context.cwd,ue.toPortablePath(p));if(je.isPathLike(p)){let E=await ze.find(h,this.context.plugins,{useRc:!1,strict:!1}),{project:C,workspace:S}=await Tt.find(E,h);if(!S)throw new ar(C.cwd,h);if(this.all){for(let P of C.workspaces)P.manifest.name&&f.add(q.stringifyIdent(P.anchoredLocator));if(f.size===0)throw new nt("No workspace found to be unlinked in the target project")}else{if(!S.manifest.name)throw new nt("The target workspace doesn't have a name and thus cannot be unlinked");f.add(q.stringifyIdent(S.anchoredLocator))}}else{let E=[...c.manifest.resolutions.map(({pattern:C})=>C.descriptor.fullName)];for(let C of(0,gBe.default)(E,p))f.add(C)}}return c.manifest.resolutions=c.manifest.resolutions.filter(({pattern:p})=>!f.has(p.descriptor.fullName)),await s.installWithNewReport({stdout:this.context.stdout,quiet:this.context.quiet},{cache:n})}};Ve();Ve();Ve();Wt();var dBe=et(lS()),j5=et(Sa());Ul();var UC=class extends ut{constructor(){super(...arguments);this.interactive=ge.Boolean("-i,--interactive",{description:"Offer various choices, depending on the detected upgrade paths"});this.fixed=ge.Boolean("-F,--fixed",!1,{description:"Store dependency tags as-is instead of resolving them"});this.exact=ge.Boolean("-E,--exact",!1,{description:"Don't use any semver modifier on the resolved range"});this.tilde=ge.Boolean("-T,--tilde",!1,{description:"Use the `~` semver modifier on the resolved range"});this.caret=ge.Boolean("-C,--caret",!1,{description:"Use the `^` semver modifier on the resolved range"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Resolve again ALL resolutions for those packages"});this.mode=ge.String("--mode",{description:"Change what artifacts installs generate",validator:Ao(ec)});this.patterns=ge.Rest()}static{this.paths=[["up"]]}static{this.usage=ot.Usage({description:"upgrade dependencies across the project",details:"\n This command upgrades the packages matching the list of specified patterns to their latest available version across the whole project (regardless of whether they're part of `dependencies` or `devDependencies` - `peerDependencies` won't be affected). This is a project-wide command: all workspaces will be upgraded in the process.\n\n If `-R,--recursive` is set the command will change behavior and no other switch will be allowed. When operating under this mode `yarn up` will force all ranges matching the selected packages to be resolved again (often to the highest available versions) before being stored in the lockfile. It however won't touch your manifests anymore, so depending on your needs you might want to run both `yarn up` and `yarn up -R` to cover all bases.\n\n If `-i,--interactive` is set (or if the `preferInteractive` settings is toggled on) the command will offer various choices, depending on the detected upgrade paths. Some upgrades require this flag in order to resolve ambiguities.\n\n The, `-C,--caret`, `-E,--exact` and `-T,--tilde` options have the same meaning as in the `add` command (they change the modifier used when the range is missing or a tag, and are ignored when the range is explicitly set).\n\n If the `--mode=` option is set, Yarn will change which artifacts are generated. The modes currently supported are:\n\n - `skip-build` will not run the build scripts at all. Note that this is different from setting `enableScripts` to false because the latter will disable build scripts, and thus affect the content of the artifacts generated on disk, whereas the former will just disable the build step - but not the scripts themselves, which just won't run.\n\n - `update-lockfile` will skip the link step altogether, and only fetch packages that are missing from the lockfile (or that have no associated checksums). This mode is typically used by tools like Renovate or Dependabot to keep a lockfile up-to-date without incurring the full install cost.\n\n Generally you can see `yarn up` as a counterpart to what was `yarn upgrade --latest` in Yarn 1 (ie it ignores the ranges previously listed in your manifests), but unlike `yarn upgrade` which only upgraded dependencies in the current workspace, `yarn up` will upgrade all workspaces at the same time.\n\n This command accepts glob patterns as arguments (if valid Descriptors and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n **Note:** The ranges have to be static, only the package scopes and names can contain glob patterns.\n ",examples:[["Upgrade all instances of lodash to the latest release","$0 up lodash"],["Upgrade all instances of lodash to the latest release, but ask confirmation for each","$0 up lodash -i"],["Upgrade all instances of lodash to 1.2.3","$0 up lodash@1.2.3"],["Upgrade all instances of packages with the `@babel` scope to the latest release","$0 up '@babel/*'"],["Upgrade all instances of packages containing the word `jest` to the latest release","$0 up '*jest*'"],["Upgrade all instances of packages with the `@babel` scope to 7.0.0","$0 up '@babel/*@7.0.0'"]]})}static{this.schema=[iB("recursive",Wf.Forbids,["interactive","exact","tilde","caret"],{ignore:[void 0,!1]})]}async execute(){return this.recursive?await this.executeUpRecursive():await this.executeUpClassic()}async executeUpRecursive(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=[...s.storedDescriptors.values()],f=c.map(E=>q.stringifyIdent(E)),p=new Set;for(let E of this.patterns){if(q.parseDescriptor(E).range!=="unknown")throw new nt("Ranges aren't allowed when using --recursive");for(let C of(0,j5.default)(f,E)){let S=q.parseIdent(C);p.add(S.identHash)}}let h=c.filter(E=>p.has(E.identHash));for(let E of h)s.storedDescriptors.delete(E.descriptorHash),s.storedResolutions.delete(E.descriptorHash);return await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}async executeUpClassic(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=this.fixed,f=r.isInteractive({interactive:this.interactive,stdout:this.context.stdout}),p=uS(this,s),h=f?["keep","reuse","project","latest"]:["project","latest"],E=[],C=[];for(let N of this.patterns){let U=!1,W=q.parseDescriptor(N),te=q.stringifyIdent(W);for(let ie of s.workspaces)for(let Ae of["dependencies","devDependencies"]){let me=[...ie.manifest.getForScope(Ae).values()].map(Be=>q.stringifyIdent(Be)),pe=te==="*"?me:(0,j5.default)(me,te);for(let Be of pe){let Ce=q.parseIdent(Be),g=ie.manifest[Ae].get(Ce.identHash);if(typeof g>"u")throw new Error("Assertion failed: Expected the descriptor to be registered");let we=q.makeDescriptor(Ce,W.range);E.push(Promise.resolve().then(async()=>[ie,Ae,g,await fS(we,{project:s,workspace:ie,cache:n,target:Ae,fixed:c,modifier:p,strategies:h})])),U=!0}}U||C.push(N)}if(C.length>1)throw new nt(`Patterns ${he.prettyList(r,C,he.Type.CODE)} don't match any packages referenced by any workspace`);if(C.length>0)throw new nt(`Pattern ${he.prettyList(r,C,he.Type.CODE)} doesn't match any packages referenced by any workspace`);let S=await Promise.all(E),P=await uA.start({configuration:r,stdout:this.context.stdout,suggestInstall:!1},async N=>{for(let[,,U,{suggestions:W,rejections:te}]of S){let ie=W.filter(Ae=>Ae.descriptor!==null);if(ie.length===0){let[Ae]=te;if(typeof Ae>"u")throw new Error("Assertion failed: Expected an error to have been set");let ce=this.cli.error(Ae);s.configuration.get("enableNetwork")?N.reportError(27,`${q.prettyDescriptor(r,U)} can't be resolved to a satisfying range + +${ce}`):N.reportError(27,`${q.prettyDescriptor(r,U)} can't be resolved to a satisfying range (note: network resolution has been disabled) + +${ce}`)}else ie.length>1&&!f&&N.reportError(27,`${q.prettyDescriptor(r,U)} has multiple possible upgrade strategies; use -i to disambiguate manually`)}});if(P.hasErrors())return P.exitCode();let I=!1,R=[];for(let[N,U,,{suggestions:W}]of S){let te,ie=W.filter(pe=>pe.descriptor!==null),Ae=ie[0].descriptor,ce=ie.every(pe=>q.areDescriptorsEqual(pe.descriptor,Ae));ie.length===1||ce?te=Ae:(I=!0,{answer:te}=await(0,dBe.prompt)({type:"select",name:"answer",message:`Which range do you want to use in ${q.prettyWorkspace(r,N)} \u276F ${U}?`,choices:W.map(({descriptor:pe,name:Be,reason:Ce})=>pe?{name:Be,hint:Ce,descriptor:pe}:{name:Be,hint:Ce,disabled:!0}),onCancel:()=>process.exit(130),result(pe){return this.find(pe,"descriptor")},stdin:this.context.stdin,stdout:this.context.stdout}));let me=N.manifest[U].get(te.identHash);if(typeof me>"u")throw new Error("Assertion failed: This descriptor should have a matching entry");if(me.descriptorHash!==te.descriptorHash)N.manifest[U].set(te.identHash,te),R.push([N,U,me,te]);else{let pe=r.makeResolver(),Be={project:s,resolver:pe},Ce=r.normalizeDependency(me),g=pe.bindDescriptor(Ce,N.anchoredLocator,Be);s.forgetResolution(g)}}return await r.triggerMultipleHooks(N=>N.afterWorkspaceDependencyReplacement,R),I&&this.context.stdout.write(` +`),await s.installWithNewReport({stdout:this.context.stdout},{cache:n,mode:this.mode})}};Ve();Ve();Ve();Wt();var HC=class extends ut{constructor(){super(...arguments);this.recursive=ge.Boolean("-R,--recursive",!1,{description:"List, for each workspace, what are all the paths that lead to the dependency"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.peers=ge.Boolean("--peers",!1,{description:"Also print the peer dependencies that match the specified name"});this.package=ge.String()}static{this.paths=[["why"]]}static{this.usage=ot.Usage({description:"display the reason why a package is needed",details:` + This command prints the exact reasons why a package appears in the dependency tree. + + If \`-R,--recursive\` is set, the listing will go in depth and will list, for each workspaces, what are all the paths that lead to the dependency. Note that the display is somewhat optimized in that it will not print the package listing twice for a single package, so if you see a leaf named "Foo" when looking for "Bar", it means that "Foo" already got printed higher in the tree. + `,examples:[["Explain why lodash is used in your project","$0 why lodash"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=q.parseIdent(this.package).identHash,c=this.recursive?wSt(s,n,{configuration:r,peers:this.peers}):CSt(s,n,{configuration:r,peers:this.peers});ks.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1})}};function CSt(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.storedPackages.values(),f=>q.stringifyLocator(f)),n={},c={children:n};for(let f of a){let p={};for(let E of f.dependencies.values()){if(!s&&f.peerDependencies.has(E.identHash))continue;let C=t.storedResolutions.get(E.descriptorHash);if(!C)throw new Error("Assertion failed: The resolution should have been registered");let S=t.storedPackages.get(C);if(!S)throw new Error("Assertion failed: The package should have been registered");if(S.identHash!==e)continue;{let I=q.stringifyLocator(f);n[I]={value:[f,he.Type.LOCATOR],children:p}}let P=q.stringifyLocator(S);p[P]={value:[{descriptor:E,locator:S},he.Type.DEPENDENT]}}}return c}function wSt(t,e,{configuration:r,peers:s}){let a=je.sortMap(t.workspaces,S=>q.stringifyLocator(S.anchoredLocator)),n=new Set,c=new Set,f=S=>{if(n.has(S.locatorHash))return c.has(S.locatorHash);if(n.add(S.locatorHash),S.identHash===e)return c.add(S.locatorHash),!0;let P=!1;S.identHash===e&&(P=!0);for(let I of S.dependencies.values()){if(!s&&S.peerDependencies.has(I.identHash))continue;let R=t.storedResolutions.get(I.descriptorHash);if(!R)throw new Error("Assertion failed: The resolution should have been registered");let N=t.storedPackages.get(R);if(!N)throw new Error("Assertion failed: The package should have been registered");f(N)&&(P=!0)}return P&&c.add(S.locatorHash),P};for(let S of a)f(S.anchoredPackage);let p=new Set,h={},E={children:h},C=(S,P,I)=>{if(!c.has(S.locatorHash))return;let R=I!==null?he.tuple(he.Type.DEPENDENT,{locator:S,descriptor:I}):he.tuple(he.Type.LOCATOR,S),N={},U={value:R,children:N},W=q.stringifyLocator(S);if(P[W]=U,!(I!==null&&t.tryWorkspaceByLocator(S))&&!p.has(S.locatorHash)){p.add(S.locatorHash);for(let te of S.dependencies.values()){if(!s&&S.peerDependencies.has(te.identHash))continue;let ie=t.storedResolutions.get(te.descriptorHash);if(!ie)throw new Error("Assertion failed: The resolution should have been registered");let Ae=t.storedPackages.get(ie);if(!Ae)throw new Error("Assertion failed: The package should have been registered");C(Ae,N,te)}}};for(let S of a)C(S.anchoredPackage,h,null);return E}Ve();var X5={};Vt(X5,{GitFetcher:()=>mS,GitResolver:()=>yS,default:()=>qSt,gitUtils:()=>Qa});Ve();bt();var Qa={};Vt(Qa,{TreeishProtocols:()=>dS,clone:()=>Z5,fetchBase:()=>MBe,fetchChangedFiles:()=>_Be,fetchChangedWorkspaces:()=>HSt,fetchRoot:()=>LBe,isGitUrl:()=>GC,lsRemote:()=>OBe,normalizeLocator:()=>USt,normalizeRepoUrl:()=>jC,resolveUrl:()=>z5,splitRepoUrl:()=>Y0,validateRepoUrl:()=>J5});Ve();bt();Wt();var RBe=et(kBe()),FBe=et(d6()),qC=et(ye("querystring")),V5=et(fi());function Y5(t,e,r){let s=t.indexOf(r);return t.lastIndexOf(e,s>-1?s:1/0)}function QBe(t){try{return new URL(t)}catch{return}}function MSt(t){let e=Y5(t,"@","#"),r=Y5(t,":","#");return r>e&&(t=`${t.slice(0,r)}/${t.slice(r+1)}`),Y5(t,":","#")===-1&&t.indexOf("//")===-1&&(t=`ssh://${t}`),t}function TBe(t){return QBe(t)||QBe(MSt(t))}function jC(t,{git:e=!1}={}){if(t=t.replace(/^git\+https:/,"https:"),t=t.replace(/^(?:github:|https:\/\/github\.com\/|git:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)(?:\.git)?(#.*)?$/,"https://github.com/$1/$2.git$3"),t=t.replace(/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/,"https://github.com/$1/$2.git#$3"),e){let r=TBe(t);r&&(t=r.href),t=t.replace(/^git\+([^:]+):/,"$1:")}return t}function NBe(){return{...process.env,GIT_SSH_COMMAND:process.env.GIT_SSH_COMMAND||`${process.env.GIT_SSH||"ssh"} -o BatchMode=yes`}}var _St=[/^ssh:/,/^git(?:\+[^:]+)?:/,/^(?:git\+)?https?:[^#]+\/[^#]+(?:\.git)(?:#.*)?$/,/^git@[^#]+\/[^#]+\.git(?:#.*)?$/,/^(?:github:|https:\/\/github\.com\/)?(?!\.{1,2}\/)([a-zA-Z._0-9-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z._0-9-]+?)(?:\.git)?(?:#.*)?$/,/^https:\/\/github\.com\/(?!\.{1,2}\/)([a-zA-Z0-9._-]+)\/(?!\.{1,2}(?:#|$))([a-zA-Z0-9._-]+?)\/tarball\/(.+)?$/],dS=(a=>(a.Commit="commit",a.Head="head",a.Tag="tag",a.Semver="semver",a))(dS||{});function GC(t){return t?_St.some(e=>!!t.match(e)):!1}function Y0(t){t=jC(t);let e=t.indexOf("#");if(e===-1)return{repo:t,treeish:{protocol:"head",request:"HEAD"},extra:{}};let r=t.slice(0,e),s=t.slice(e+1);if(s.match(/^[a-z]+=/)){let a=qC.default.parse(s);for(let[p,h]of Object.entries(a))if(typeof h!="string")throw new Error(`Assertion failed: The ${p} parameter must be a literal string`);let n=Object.values(dS).find(p=>Object.hasOwn(a,p)),[c,f]=typeof n<"u"?[n,a[n]]:["head","HEAD"];for(let p of Object.values(dS))delete a[p];return{repo:r,treeish:{protocol:c,request:f},extra:a}}else{let a=s.indexOf(":"),[n,c]=a===-1?[null,s]:[s.slice(0,a),s.slice(a+1)];return{repo:r,treeish:{protocol:n,request:c},extra:{}}}}function USt(t){return q.makeLocator(t,jC(t.reference))}function J5(t,{configuration:e}){let r=jC(t,{git:!0});if(!An.getNetworkSettings(`https://${(0,RBe.default)(r).resource}`,{configuration:e}).enableNetwork)throw new Yt(80,`Request to '${r}' has been blocked because of your configuration settings`);return r}async function OBe(t,e){let r=J5(t,{configuration:e}),s=await K5("listing refs",["ls-remote",r],{cwd:e.startingCwd,env:NBe()},{configuration:e,normalizedRepoUrl:r}),a=new Map,n=/^([a-f0-9]{40})\t([^\n]+)/gm,c;for(;(c=n.exec(s.stdout))!==null;)a.set(c[2],c[1]);return a}async function z5(t,e){let{repo:r,treeish:{protocol:s,request:a},extra:n}=Y0(t),c=await OBe(r,e),f=(h,E)=>{switch(h){case"commit":{if(!E.match(/^[a-f0-9]{40}$/))throw new Error("Invalid commit hash");return qC.default.stringify({...n,commit:E})}case"head":{let C=c.get(E==="HEAD"?E:`refs/heads/${E}`);if(typeof C>"u")throw new Error(`Unknown head ("${E}")`);return qC.default.stringify({...n,commit:C})}case"tag":{let C=c.get(`refs/tags/${E}`);if(typeof C>"u")throw new Error(`Unknown tag ("${E}")`);return qC.default.stringify({...n,commit:C})}case"semver":{let C=Or.validRange(E);if(!C)throw new Error(`Invalid range ("${E}")`);let S=new Map([...c.entries()].filter(([I])=>I.startsWith("refs/tags/")).map(([I,R])=>[V5.default.parse(I.slice(10)),R]).filter(I=>I[0]!==null)),P=V5.default.maxSatisfying([...S.keys()],C);if(P===null)throw new Error(`No matching range ("${E}")`);return qC.default.stringify({...n,commit:S.get(P)})}case null:{let C;if((C=p("commit",E))!==null||(C=p("tag",E))!==null||(C=p("head",E))!==null)return C;throw E.match(/^[a-f0-9]+$/)?new Error(`Couldn't resolve "${E}" as either a commit, a tag, or a head - if a commit, use the 40-characters commit hash`):new Error(`Couldn't resolve "${E}" as either a commit, a tag, or a head`)}default:throw new Error(`Invalid Git resolution protocol ("${h}")`)}},p=(h,E)=>{try{return f(h,E)}catch{return null}};return jC(`${r}#${f(s,a)}`)}async function Z5(t,e){return await e.getLimit("cloneConcurrency")(async()=>{let{repo:r,treeish:{protocol:s,request:a}}=Y0(t);if(s!=="commit")throw new Error("Invalid treeish protocol when cloning");let n=J5(r,{configuration:e}),c=await le.mktempPromise(),f={cwd:c,env:NBe()};return await K5("cloning the repository",["clone","-c core.autocrlf=false",n,ue.fromPortablePath(c)],f,{configuration:e,normalizedRepoUrl:n}),await K5("switching branch",["checkout",`${a}`],f,{configuration:e,normalizedRepoUrl:n}),c})}async function LBe(t){let e,r=t;do{if(e=r,await le.existsPromise(K.join(e,".git")))return e;r=K.dirname(e)}while(r!==e);return null}async function MBe(t,{baseRefs:e}){if(e.length===0)throw new nt("Can't run this command with zero base refs specified.");let r=[];for(let f of e){let{code:p}=await Gr.execvp("git",["merge-base",f,"HEAD"],{cwd:t});p===0&&r.push(f)}if(r.length===0)throw new nt(`No ancestor could be found between any of HEAD and ${e.join(", ")}`);let{stdout:s}=await Gr.execvp("git",["merge-base","HEAD",...r],{cwd:t,strict:!0}),a=s.trim(),{stdout:n}=await Gr.execvp("git",["show","--quiet","--pretty=format:%s",a],{cwd:t,strict:!0}),c=n.trim();return{hash:a,title:c}}async function _Be(t,{base:e,project:r}){let s=je.buildIgnorePattern(r.configuration.get("changesetIgnorePatterns")),{stdout:a}=await Gr.execvp("git",["diff","--name-only",`${e}`],{cwd:t,strict:!0}),n=a.split(/\r\n|\r|\n/).filter(h=>h.length>0).map(h=>K.resolve(t,ue.toPortablePath(h))),{stdout:c}=await Gr.execvp("git",["ls-files","--others","--exclude-standard"],{cwd:t,strict:!0}),f=c.split(/\r\n|\r|\n/).filter(h=>h.length>0).map(h=>K.resolve(t,ue.toPortablePath(h))),p=[...new Set([...n,...f].sort())];return s?p.filter(h=>!K.relative(r.cwd,h).match(s)):p}async function HSt({ref:t,project:e}){if(e.configuration.projectCwd===null)throw new nt("This command can only be run from within a Yarn project");let r=[K.resolve(e.cwd,Er.lockfile),K.resolve(e.cwd,e.configuration.get("cacheFolder")),K.resolve(e.cwd,e.configuration.get("installStatePath")),K.resolve(e.cwd,e.configuration.get("virtualFolder"))];await e.configuration.triggerHook(c=>c.populateYarnPaths,e,c=>{c!=null&&r.push(c)});let s=await LBe(e.configuration.projectCwd);if(s==null)throw new nt("This command can only be run on Git repositories");let a=await MBe(s,{baseRefs:typeof t=="string"?[t]:e.configuration.get("changesetBaseRefs")}),n=await _Be(s,{base:a.hash,project:e});return new Set(je.mapAndFilter(n,c=>{let f=e.tryWorkspaceByFilePath(c);return f===null?je.mapAndFilter.skip:r.some(p=>c.startsWith(p))?je.mapAndFilter.skip:f}))}async function K5(t,e,r,{configuration:s,normalizedRepoUrl:a}){try{return await Gr.execvp("git",e,{...r,strict:!0})}catch(n){if(!(n instanceof Gr.ExecError))throw n;let c=n.reportExtra,f=n.stderr.toString();throw new Yt(1,`Failed ${t}`,p=>{p.reportError(1,` ${he.prettyField(s,{label:"Repository URL",value:he.tuple(he.Type.URL,a)})}`);for(let h of f.matchAll(/^(.+?): (.*)$/gm)){let[,E,C]=h;E=E.toLowerCase();let S=E==="error"?"Error":`${(0,FBe.default)(E)} Error`;p.reportError(1,` ${he.prettyField(s,{label:S,value:he.tuple(he.Type.NO_HINT,C)})}`)}c?.(p)})}}var mS=class{supports(e,r){return GC(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,a=new Map(r.checksums);a.set(e.locatorHash,s);let n={...r,checksums:a},c=await this.downloadHosted(e,n);if(c!==null)return c;let[f,p,h]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote repository`),loader:()=>this.cloneFromRemote(e,n),...r.cacheOptions});return{packageFs:f,releaseFs:p,prefixPath:q.getIdentVendorPath(e),checksum:h}}async downloadHosted(e,r){return r.project.configuration.reduceHook(s=>s.fetchHostedRepository,null,e,r)}async cloneFromRemote(e,r){let s=Y0(e.reference),a=await Z5(e.reference,r.project.configuration),n=K.resolve(a,s.extra.cwd??vt.dot),c=K.join(n,"package.tgz");await In.prepareExternalProject(n,c,{configuration:r.project.configuration,report:r.report,workspace:s.extra.workspace,locator:e});let f=await le.readFilePromise(c);return await je.releaseAfterUseAsync(async()=>await hs.convertToZip(f,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1}))}};Ve();Ve();var yS=class{supportsDescriptor(e,r){return GC(e.range)}supportsLocator(e,r){return GC(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=await z5(e.range,s.project.configuration);return[q.makeLocator(e,a)]}async getSatisfying(e,r,s,a){let n=Y0(e.range);return{locators:s.filter(f=>{if(f.identHash!==e.identHash)return!1;let p=Y0(f.reference);return!(n.repo!==p.repo||n.treeish.protocol==="commit"&&n.treeish.request!==p.treeish.request)}),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var jSt={configuration:{changesetBaseRefs:{description:"The base git refs that the current HEAD is compared against when detecting changes. Supports git branches, tags, and commits.",type:"STRING",isArray:!0,isNullable:!1,default:["master","origin/master","upstream/master","main","origin/main","upstream/main"]},changesetIgnorePatterns:{description:"Array of glob patterns; files matching them will be ignored when fetching the changed files",type:"STRING",default:[],isArray:!0},cloneConcurrency:{description:"Maximal number of concurrent clones",type:"NUMBER",default:2}},fetchers:[mS],resolvers:[yS]};var qSt=jSt;Wt();var WC=class extends ut{constructor(){super(...arguments);this.since=ge.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.noPrivate=ge.Boolean("--no-private",{description:"Exclude workspaces that have the private field set to true"});this.verbose=ge.Boolean("-v,--verbose",!1,{description:"Also return the cross-dependencies between workspaces"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["workspaces","list"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"list all available workspaces",details:"\n This command will print the list of all workspaces in the project.\n\n - If `--since` is set, Yarn will only list workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--no-private` is set, Yarn will not list any workspaces that have the `private` field set to `true`.\n\n - If both the `-v,--verbose` and `--json` options are set, Yarn will also return the cross-dependencies between each workspaces (useful when you wish to automatically generate Buck / Bazel rules).\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);return(await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async n=>{let c=this.since?await Qa.fetchChangedWorkspaces({ref:this.since,project:s}):s.workspaces,f=new Set(c);if(this.recursive)for(let p of[...c].map(h=>h.getRecursiveWorkspaceDependents()))for(let h of p)f.add(h);for(let p of f){let{manifest:h}=p;if(h.private&&this.noPrivate)continue;let E;if(this.verbose){let C=new Set,S=new Set;for(let P of Ht.hardDependencies)for(let[I,R]of h.getForScope(P)){let N=s.tryWorkspaceByDescriptor(R);N===null?s.workspacesByIdent.has(I)&&S.add(R):C.add(N)}E={workspaceDependencies:Array.from(C).map(P=>P.relativeCwd),mismatchedWorkspaceDependencies:Array.from(S).map(P=>q.stringifyDescriptor(P))}}n.reportInfo(null,`${p.relativeCwd}`),n.reportJson({location:p.relativeCwd,name:h.name?q.stringifyIdent(h.name):null,...E})}})).exitCode()}};Ve();Ve();Wt();var YC=class extends ut{constructor(){super(...arguments);this.workspaceName=ge.String();this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["workspace"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"run a command within the specified workspace",details:` + This command will run a given sub-command on a single workspace. + `,examples:[["Add a package to a single workspace","yarn workspace components add -D react"],["Run build script on a single workspace","yarn workspace components run build"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=s.workspaces,c=new Map(n.map(p=>[q.stringifyIdent(p.anchoredLocator),p])),f=c.get(this.workspaceName);if(f===void 0){let p=Array.from(c.keys()).sort();throw new nt(`Workspace '${this.workspaceName}' not found. Did you mean any of the following: + - ${p.join(` + - `)}?`)}return this.cli.run([this.commandName,...this.args],{cwd:f.cwd})}};var GSt={configuration:{enableImmutableInstalls:{description:"If true (the default on CI), prevents the install command from modifying the lockfile",type:"BOOLEAN",default:UBe.isCI},defaultSemverRangePrefix:{description:"The default save prefix: '^', '~' or ''",type:"STRING",values:["^","~",""],default:"^"},preferReuse:{description:"If true, `yarn add` will attempt to reuse the most common dependency range in other workspaces.",type:"BOOLEAN",default:!1}},commands:[cC,uC,fC,AC,MC,xC,CC,WC,gC,dC,mC,yC,aC,lC,pC,hC,EC,IC,wC,BC,vC,SC,_C,DC,bC,QC,kC,TC,PC,RC,FC,NC,OC,LC,UC,HC,YC]},WSt=GSt;var i9={};Vt(i9,{default:()=>VSt});Ve();var Qt={optional:!0},e9=[["@tailwindcss/aspect-ratio@<0.2.1",{peerDependencies:{tailwindcss:"^2.0.2"}}],["@tailwindcss/line-clamp@<0.2.1",{peerDependencies:{tailwindcss:"^2.0.2"}}],["@fullhuman/postcss-purgecss@3.1.3 || 3.1.3-alpha.0",{peerDependencies:{postcss:"^8.0.0"}}],["@samverschueren/stream-to-observable@<0.3.1",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],["any-observable@<0.5.1",{peerDependenciesMeta:{rxjs:Qt,zenObservable:Qt}}],["@pm2/agent@<1.0.4",{dependencies:{debug:"*"}}],["debug@<4.2.0",{peerDependenciesMeta:{"supports-color":Qt}}],["got@<11",{dependencies:{"@types/responselike":"^1.0.0","@types/keyv":"^3.1.1"}}],["cacheable-lookup@<4.1.2",{dependencies:{"@types/keyv":"^3.1.1"}}],["http-link-dataloader@*",{peerDependencies:{graphql:"^0.13.1 || ^14.0.0"}}],["typescript-language-server@*",{dependencies:{"vscode-jsonrpc":"^5.0.1","vscode-languageserver-protocol":"^3.15.0"}}],["postcss-syntax@*",{peerDependenciesMeta:{"postcss-html":Qt,"postcss-jsx":Qt,"postcss-less":Qt,"postcss-markdown":Qt,"postcss-scss":Qt}}],["jss-plugin-rule-value-function@<=10.1.1",{dependencies:{"tiny-warning":"^1.0.2"}}],["ink-select-input@<4.1.0",{peerDependencies:{react:"^16.8.2"}}],["license-webpack-plugin@<2.3.18",{peerDependenciesMeta:{webpack:Qt}}],["snowpack@>=3.3.0",{dependencies:{"node-gyp":"^7.1.0"}}],["promise-inflight@*",{peerDependenciesMeta:{bluebird:Qt}}],["reactcss@*",{peerDependencies:{react:"*"}}],["react-color@<=2.19.0",{peerDependencies:{react:"*"}}],["gatsby-plugin-i18n@*",{dependencies:{ramda:"^0.24.1"}}],["useragent@^2.0.0",{dependencies:{request:"^2.88.0",yamlparser:"0.0.x",semver:"5.5.x"}}],["@apollographql/apollo-tools@<=0.5.2",{peerDependencies:{graphql:"^14.2.1 || ^15.0.0"}}],["material-table@^2.0.0",{dependencies:{"@babel/runtime":"^7.11.2"}}],["@babel/parser@*",{dependencies:{"@babel/types":"^7.8.3"}}],["fork-ts-checker-webpack-plugin@<=6.3.4",{peerDependencies:{eslint:">= 6",typescript:">= 2.7",webpack:">= 4","vue-template-compiler":"*"},peerDependenciesMeta:{eslint:Qt,"vue-template-compiler":Qt}}],["rc-animate@<=3.1.1",{peerDependencies:{react:">=16.9.0","react-dom":">=16.9.0"}}],["react-bootstrap-table2-paginator@*",{dependencies:{classnames:"^2.2.6"}}],["react-draggable@<=4.4.3",{peerDependencies:{react:">= 16.3.0","react-dom":">= 16.3.0"}}],["apollo-upload-client@<14",{peerDependencies:{graphql:"14 - 15"}}],["react-instantsearch-core@<=6.7.0",{peerDependencies:{algoliasearch:">= 3.1 < 5"}}],["react-instantsearch-dom@<=6.7.0",{dependencies:{"react-fast-compare":"^3.0.0"}}],["ws@<7.2.1",{peerDependencies:{bufferutil:"^4.0.1","utf-8-validate":"^5.0.2"},peerDependenciesMeta:{bufferutil:Qt,"utf-8-validate":Qt}}],["react-portal@<4.2.2",{peerDependencies:{"react-dom":"^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0"}}],["react-scripts@<=4.0.1",{peerDependencies:{react:"*"}}],["testcafe@<=1.10.1",{dependencies:{"@babel/plugin-transform-for-of":"^7.12.1","@babel/runtime":"^7.12.5"}}],["testcafe-legacy-api@<=4.2.0",{dependencies:{"testcafe-hammerhead":"^17.0.1","read-file-relative":"^1.2.0"}}],["@google-cloud/firestore@<=4.9.3",{dependencies:{protobufjs:"^6.8.6"}}],["gatsby-source-apiserver@*",{dependencies:{"babel-polyfill":"^6.26.0"}}],["@webpack-cli/package-utils@<=1.0.1-alpha.4",{dependencies:{"cross-spawn":"^7.0.3"}}],["gatsby-remark-prismjs@<3.3.28",{dependencies:{lodash:"^4"}}],["gatsby-plugin-favicon@*",{peerDependencies:{webpack:"*"}}],["gatsby-plugin-sharp@<=4.6.0-next.3",{dependencies:{debug:"^4.3.1"}}],["gatsby-react-router-scroll@<=5.6.0-next.0",{dependencies:{"prop-types":"^15.7.2"}}],["@rebass/forms@*",{dependencies:{"@styled-system/should-forward-prop":"^5.0.0"},peerDependencies:{react:"^16.8.6"}}],["rebass@*",{peerDependencies:{react:"^16.8.6"}}],["@ant-design/react-slick@<=0.28.3",{peerDependencies:{react:">=16.0.0"}}],["mqtt@<4.2.7",{dependencies:{duplexify:"^4.1.1"}}],["vue-cli-plugin-vuetify@<=2.0.3",{dependencies:{semver:"^6.3.0"},peerDependenciesMeta:{"sass-loader":Qt,"vuetify-loader":Qt}}],["vue-cli-plugin-vuetify@<=2.0.4",{dependencies:{"null-loader":"^3.0.0"}}],["vue-cli-plugin-vuetify@>=2.4.3",{peerDependencies:{vue:"*"}}],["@vuetify/cli-plugin-utils@<=0.0.4",{dependencies:{semver:"^6.3.0"},peerDependenciesMeta:{"sass-loader":Qt}}],["@vue/cli-plugin-typescript@<=5.0.0-alpha.0",{dependencies:{"babel-loader":"^8.1.0"}}],["@vue/cli-plugin-typescript@<=5.0.0-beta.0",{dependencies:{"@babel/core":"^7.12.16"},peerDependencies:{"vue-template-compiler":"^2.0.0"},peerDependenciesMeta:{"vue-template-compiler":Qt}}],["cordova-ios@<=6.3.0",{dependencies:{underscore:"^1.9.2"}}],["cordova-lib@<=10.0.1",{dependencies:{underscore:"^1.9.2"}}],["git-node-fs@*",{peerDependencies:{"js-git":"^0.7.8"},peerDependenciesMeta:{"js-git":Qt}}],["consolidate@<0.16.0",{peerDependencies:{mustache:"^3.0.0"},peerDependenciesMeta:{mustache:Qt}}],["consolidate@<=0.16.0",{peerDependencies:{velocityjs:"^2.0.1",tinyliquid:"^0.2.34","liquid-node":"^3.0.1",jade:"^1.11.0","then-jade":"*",dust:"^0.3.0","dustjs-helpers":"^1.7.4","dustjs-linkedin":"^2.7.5",swig:"^1.4.2","swig-templates":"^2.0.3","razor-tmpl":"^1.3.1",atpl:">=0.7.6",liquor:"^0.0.5",twig:"^1.15.2",ejs:"^3.1.5",eco:"^1.1.0-rc-3",jazz:"^0.0.18",jqtpl:"~1.1.0",hamljs:"^0.6.2",hamlet:"^0.3.3",whiskers:"^0.4.0","haml-coffee":"^1.14.1","hogan.js":"^3.0.2",templayed:">=0.2.3",handlebars:"^4.7.6",underscore:"^1.11.0",lodash:"^4.17.20",pug:"^3.0.0","then-pug":"*",qejs:"^3.0.5",walrus:"^0.10.1",mustache:"^4.0.1",just:"^0.1.8",ect:"^0.5.9",mote:"^0.2.0",toffee:"^0.3.6",dot:"^1.1.3","bracket-template":"^1.1.5",ractive:"^1.3.12",nunjucks:"^3.2.2",htmling:"^0.0.8","babel-core":"^6.26.3",plates:"~0.4.11","react-dom":"^16.13.1",react:"^16.13.1","arc-templates":"^0.5.3",vash:"^0.13.0",slm:"^2.0.0",marko:"^3.14.4",teacup:"^2.0.0","coffee-script":"^1.12.7",squirrelly:"^5.1.0",twing:"^5.0.2"},peerDependenciesMeta:{velocityjs:Qt,tinyliquid:Qt,"liquid-node":Qt,jade:Qt,"then-jade":Qt,dust:Qt,"dustjs-helpers":Qt,"dustjs-linkedin":Qt,swig:Qt,"swig-templates":Qt,"razor-tmpl":Qt,atpl:Qt,liquor:Qt,twig:Qt,ejs:Qt,eco:Qt,jazz:Qt,jqtpl:Qt,hamljs:Qt,hamlet:Qt,whiskers:Qt,"haml-coffee":Qt,"hogan.js":Qt,templayed:Qt,handlebars:Qt,underscore:Qt,lodash:Qt,pug:Qt,"then-pug":Qt,qejs:Qt,walrus:Qt,mustache:Qt,just:Qt,ect:Qt,mote:Qt,toffee:Qt,dot:Qt,"bracket-template":Qt,ractive:Qt,nunjucks:Qt,htmling:Qt,"babel-core":Qt,plates:Qt,"react-dom":Qt,react:Qt,"arc-templates":Qt,vash:Qt,slm:Qt,marko:Qt,teacup:Qt,"coffee-script":Qt,squirrelly:Qt,twing:Qt}}],["vue-loader@<=16.3.3",{peerDependencies:{"@vue/compiler-sfc":"^3.0.8",webpack:"^4.1.0 || ^5.0.0-0"},peerDependenciesMeta:{"@vue/compiler-sfc":Qt}}],["vue-loader@^16.7.0",{peerDependencies:{"@vue/compiler-sfc":"^3.0.8",vue:"^3.2.13"},peerDependenciesMeta:{"@vue/compiler-sfc":Qt,vue:Qt}}],["scss-parser@<=1.0.5",{dependencies:{lodash:"^4.17.21"}}],["query-ast@<1.0.5",{dependencies:{lodash:"^4.17.21"}}],["redux-thunk@<=2.3.0",{peerDependencies:{redux:"^4.0.0"}}],["skypack@<=0.3.2",{dependencies:{tar:"^6.1.0"}}],["@npmcli/metavuln-calculator@<2.0.0",{dependencies:{"json-parse-even-better-errors":"^2.3.1"}}],["bin-links@<2.3.0",{dependencies:{"mkdirp-infer-owner":"^1.0.2"}}],["rollup-plugin-polyfill-node@<=0.8.0",{peerDependencies:{rollup:"^1.20.0 || ^2.0.0"}}],["snowpack@<3.8.6",{dependencies:{"magic-string":"^0.25.7"}}],["elm-webpack-loader@*",{dependencies:{temp:"^0.9.4"}}],["winston-transport@<=4.4.0",{dependencies:{logform:"^2.2.0"}}],["jest-vue-preprocessor@*",{dependencies:{"@babel/core":"7.8.7","@babel/template":"7.8.6"},peerDependencies:{pug:"^2.0.4"},peerDependenciesMeta:{pug:Qt}}],["redux-persist@*",{peerDependencies:{react:">=16"},peerDependenciesMeta:{react:Qt}}],["sodium@>=3",{dependencies:{"node-gyp":"^3.8.0"}}],["babel-plugin-graphql-tag@<=3.1.0",{peerDependencies:{graphql:"^14.0.0 || ^15.0.0"}}],["@playwright/test@<=1.14.1",{dependencies:{"jest-matcher-utils":"^26.4.2"}}],...["babel-plugin-remove-graphql-queries@<3.14.0-next.1","babel-preset-gatsby-package@<1.14.0-next.1","create-gatsby@<1.14.0-next.1","gatsby-admin@<0.24.0-next.1","gatsby-cli@<3.14.0-next.1","gatsby-core-utils@<2.14.0-next.1","gatsby-design-tokens@<3.14.0-next.1","gatsby-legacy-polyfills@<1.14.0-next.1","gatsby-plugin-benchmark-reporting@<1.14.0-next.1","gatsby-plugin-graphql-config@<0.23.0-next.1","gatsby-plugin-image@<1.14.0-next.1","gatsby-plugin-mdx@<2.14.0-next.1","gatsby-plugin-netlify-cms@<5.14.0-next.1","gatsby-plugin-no-sourcemaps@<3.14.0-next.1","gatsby-plugin-page-creator@<3.14.0-next.1","gatsby-plugin-preact@<5.14.0-next.1","gatsby-plugin-preload-fonts@<2.14.0-next.1","gatsby-plugin-schema-snapshot@<2.14.0-next.1","gatsby-plugin-styletron@<6.14.0-next.1","gatsby-plugin-subfont@<3.14.0-next.1","gatsby-plugin-utils@<1.14.0-next.1","gatsby-recipes@<0.25.0-next.1","gatsby-source-shopify@<5.6.0-next.1","gatsby-source-wikipedia@<3.14.0-next.1","gatsby-transformer-screenshot@<3.14.0-next.1","gatsby-worker@<0.5.0-next.1"].map(t=>[t,{dependencies:{"@babel/runtime":"^7.14.8"}}]),["gatsby-core-utils@<2.14.0-next.1",{dependencies:{got:"8.3.2"}}],["gatsby-plugin-gatsby-cloud@<=3.1.0-next.0",{dependencies:{"gatsby-core-utils":"^2.13.0-next.0"}}],["gatsby-plugin-gatsby-cloud@<=3.2.0-next.1",{peerDependencies:{webpack:"*"}}],["babel-plugin-remove-graphql-queries@<=3.14.0-next.1",{dependencies:{"gatsby-core-utils":"^2.8.0-next.1"}}],["gatsby-plugin-netlify@3.13.0-next.1",{dependencies:{"gatsby-core-utils":"^2.13.0-next.0"}}],["clipanion-v3-codemod@<=0.2.0",{peerDependencies:{jscodeshift:"^0.11.0"}}],["react-live@*",{peerDependencies:{"react-dom":"*",react:"*"}}],["webpack@<4.44.1",{peerDependenciesMeta:{"webpack-cli":Qt,"webpack-command":Qt}}],["webpack@<5.0.0-beta.23",{peerDependenciesMeta:{"webpack-cli":Qt}}],["webpack-dev-server@<3.10.2",{peerDependenciesMeta:{"webpack-cli":Qt}}],["@docusaurus/responsive-loader@<1.5.0",{peerDependenciesMeta:{sharp:Qt,jimp:Qt}}],["eslint-module-utils@*",{peerDependenciesMeta:{"eslint-import-resolver-node":Qt,"eslint-import-resolver-typescript":Qt,"eslint-import-resolver-webpack":Qt,"@typescript-eslint/parser":Qt}}],["eslint-plugin-import@*",{peerDependenciesMeta:{"@typescript-eslint/parser":Qt}}],["critters-webpack-plugin@<3.0.2",{peerDependenciesMeta:{"html-webpack-plugin":Qt}}],["terser@<=5.10.0",{dependencies:{acorn:"^8.5.0"}}],["babel-preset-react-app@10.0.x <10.0.2",{dependencies:{"@babel/plugin-proposal-private-property-in-object":"^7.16.7"}}],["eslint-config-react-app@*",{peerDependenciesMeta:{typescript:Qt}}],["@vue/eslint-config-typescript@<11.0.0",{peerDependenciesMeta:{typescript:Qt}}],["unplugin-vue2-script-setup@<0.9.1",{peerDependencies:{"@vue/composition-api":"^1.4.3","@vue/runtime-dom":"^3.2.26"}}],["@cypress/snapshot@*",{dependencies:{debug:"^3.2.7"}}],["auto-relay@<=0.14.0",{peerDependencies:{"reflect-metadata":"^0.1.13"}}],["vue-template-babel-compiler@<1.2.0",{peerDependencies:{"vue-template-compiler":"^2.6.0"}}],["@parcel/transformer-image@<2.5.0",{peerDependencies:{"@parcel/core":"*"}}],["@parcel/transformer-js@<2.5.0",{peerDependencies:{"@parcel/core":"*"}}],["parcel@*",{peerDependenciesMeta:{"@parcel/core":Qt}}],["react-scripts@*",{peerDependencies:{eslint:"*"}}],["focus-trap-react@^8.0.0",{dependencies:{tabbable:"^5.3.2"}}],["react-rnd@<10.3.7",{peerDependencies:{react:">=16.3.0","react-dom":">=16.3.0"}}],["connect-mongo@<5.0.0",{peerDependencies:{"express-session":"^1.17.1"}}],["vue-i18n@<9",{peerDependencies:{vue:"^2"}}],["vue-router@<4",{peerDependencies:{vue:"^2"}}],["unified@<10",{dependencies:{"@types/unist":"^2.0.0"}}],["react-github-btn@<=1.3.0",{peerDependencies:{react:">=16.3.0"}}],["react-dev-utils@*",{peerDependencies:{typescript:">=2.7",webpack:">=4"},peerDependenciesMeta:{typescript:Qt}}],["@asyncapi/react-component@<=1.0.0-next.39",{peerDependencies:{react:">=16.8.0","react-dom":">=16.8.0"}}],["xo@*",{peerDependencies:{webpack:">=1.11.0"},peerDependenciesMeta:{webpack:Qt}}],["babel-plugin-remove-graphql-queries@<=4.20.0-next.0",{dependencies:{"@babel/types":"^7.15.4"}}],["gatsby-plugin-page-creator@<=4.20.0-next.1",{dependencies:{"fs-extra":"^10.1.0"}}],["gatsby-plugin-utils@<=3.14.0-next.1",{dependencies:{fastq:"^1.13.0"},peerDependencies:{graphql:"^15.0.0"}}],["gatsby-plugin-mdx@<3.1.0-next.1",{dependencies:{mkdirp:"^1.0.4"}}],["gatsby-plugin-mdx@^2",{peerDependencies:{gatsby:"^3.0.0-next"}}],["fdir@<=5.2.0",{peerDependencies:{picomatch:"2.x"},peerDependenciesMeta:{picomatch:Qt}}],["babel-plugin-transform-typescript-metadata@<=0.3.2",{peerDependencies:{"@babel/core":"^7","@babel/traverse":"^7"},peerDependenciesMeta:{"@babel/traverse":Qt}}],["graphql-compose@>=9.0.10",{peerDependencies:{graphql:"^14.2.0 || ^15.0.0 || ^16.0.0"}}],["vite-plugin-vuetify@<=1.0.2",{peerDependencies:{vue:"^3.0.0"}}],["webpack-plugin-vuetify@<=2.0.1",{peerDependencies:{vue:"^3.2.6"}}],["eslint-import-resolver-vite@<2.0.1",{dependencies:{debug:"^4.3.4",resolve:"^1.22.8"}}],["notistack@^3.0.0",{dependencies:{csstype:"^3.0.10"}}]];var t9;function HBe(){return typeof t9>"u"&&(t9=ye("zlib").brotliDecompressSync(Buffer.from("G7weAByFTVk3Vs7UfHhq4yykgEM7pbW7TI43SG2S5tvGrwHBAzdz+s/npQ6tgEvobvxisrPIadkXeUAJotBn5bDZ5kAhcRqsIHe3F75Walet5hNalwgFDtxb0BiDUjiUQkjG0yW2hto9HPgiCkm316d6bC0kST72YN7D7rfkhCE9x4J0XwB0yavalxpUu2t9xszHrmtwalOxT7VslsxWcB1qpqZwERUra4psWhTV8BgwWeizurec82Caf1ABL11YMfbf8FJ9JBceZOkgmvrQPbC9DUldX/yMbmX06UQluCEjSwUoyO+EZPIjofr+/oAZUck2enraRD+oWLlnlYnj8xB+gwSo9lmmks4fXv574qSqcWA6z21uYkzMu3EWj+K23RxeQlLqiE35/rC8GcS4CGkKHKKq+zAIQwD9iRDNfiAqueLLpicFFrNsAI4zeTD/eO9MHcnRa5m8UT+M2+V+AkFST4BlKneiAQRSdST8KEAIyFlULt6wa9EBd0Ds28VmpaxquJdVt+nwdEs5xUskI13OVtFyY0UrQIRAlCuvvWivvlSKQfTO+2Q8OyUR1W5RvetaPz4jD27hdtwHFFA1Ptx6Ee/t2cY2rg2G46M1pNDRf2pWhvpy8pqMnuI3++4OF3+7OFIWXGjh+o7Nr2jNvbiYcQdQS1h903/jVFgOpA0yJ78z+x759bFA0rq+6aY5qPB4FzS3oYoLupDUhD9nDz6F6H7hpnlMf18KNKDu4IKjTWwrAnY6MFQw1W6ymOALHlFyCZmQhldg1MQHaMVVQTVgDC60TfaBqG++Y8PEoFhN/PBTZT175KNP/BlHDYGOOBmnBdzqJKplZ/ljiVG0ZBzfqeBRrrUkn6rA54462SgiliKoYVnbeptMdXNfAuaupIEi0bApF10TlgHfmEJAPUVidRVFyDupSem5po5vErPqWKhKbUIp0LozpYsIKK57dM/HKr+nguF+7924IIWMICkQ8JUigs9D+W+c4LnNoRtPPKNRUiCYmP+Jfo2lfKCKw8qpraEeWU3uiNRO6zcyKQoXPR5htmzzLznke7b4YbXW3I1lIRzmgG02Udb58U+7TpwyN7XymCgH+wuPDthZVQvRZuEP+SnLtMicz9m5zASWOBiAcLmkuFlTKuHspSIhCBD0yUPKcxu81A+4YD78rA2vtwsUEday9WNyrShyrl60rWmA+SmbYZkQOwFJWArxRYYc5jGhA5ikxYw1rx3ei4NmeX/lKiwpZ9Ln1tV2Ae7sArvxuVLbJjqJRjW1vFXAyHpvLG+8MJ6T2Ubx5M2KDa2SN6vuIGxJ9WQM9Mk3Q7aCNiZONXllhqq24DmoLbQfW2rYWsOgHWjtOmIQMyMKdiHZDjoyIq5+U700nZ6odJAoYXPQBvFNiQ78d5jaXliBqLTJEqUCwi+LiH2mx92EmNKDsJL74Z613+3lf20pxkV1+erOrjj8pW00vsPaahKUM+05ssd5uwM7K482KWEf3TCwlg/o3e5ngto7qSMz7YteIgCsF1UOcsLk7F7MxWbvrPMY473ew0G+noVL8EPbkmEMftMSeL6HFub/zy+2JQ==","base64")).toString()),t9}var r9;function jBe(){return typeof r9>"u"&&(r9=ye("zlib").brotliDecompressSync(Buffer.from("G8MSIIzURnVBnObTcvb3XE6v2S9Qgc2K801Oa5otNKEtK8BINZNcaQHy+9/vf/WXBimwutXC33P2DPc64pps5rz7NGGWaOKNSPL4Y2KRE8twut2lFOIN+OXPtRmPMRhMTILib2bEQx43az2I5d3YS8Roa5UZpF/ujHb3Djd3GDvYUfvFYSUQ39vb2cmifp/rgB4J/65JK3wRBTvMBoNBmn3mbXC63/gbBkW/2IRPri0O8bcsRBsmarF328pAln04nyJFkwUAvNu934supAqLtyerZZpJ8I8suJHhf/ocMV+scKwa8NOiDKIPXw6Ex/EEZD6TEGaW8N5zvNHYF10l6Lfooj7D5W2k3dgvQSbp2Wv8TGOayS978gxlOLVjTGXs66ozewbrjwElLtyrYNnWTfzzdEutgROUFPVMhnMoy8EjJLLlWwIEoySxliim9kYW30JUHiPVyjt0iAw/ZpPmCbUCltYPnq6ZNblIKhTNhqS/oqC9iya5sGKZTOVsTEg34n92uZTf2iPpcZih8rPW8CzA+adIGmyCPcKdLMsBLShd+zuEbTrqpwuh+DLmracZcjPC5Sdf5odDAhKpFuOsQS67RT+1VgWWygSv3YwxDnylc04/PYuaMeIzhBkLrvs7e/OUzRTF56MmfY6rI63QtEjEQzq637zQqJ39nNhu3NmoRRhW/086bHGBUtx0PE0j3aEGvkdh9WJC8y8j8mqqke9/dQ5la+Q3ba4RlhvTbnfQhPDDab3tUifkjKuOsp13mXEmO00Mu88F/M67R7LXfoFDFLNtgCSWjWX+3Jn1371pJTK9xPBiMJafvDjtFyAzu8rxeQ0TKMQXNPs5xxiBOd+BRJP8KP88XPtJIbZKh/cdW8KvBUkpqKpGoiIaA32c3/JnQr4efXt85mXvidOvn/eU3Pase1typLYBalJ14mCso9h79nuMOuCa/kZAOkJHmTjP5RM2WNoPasZUAnT1TAE/NH25hUxcQv6hQWR/m1PKk4ooXMcM4SR1iYU3fUohvqk4RY2hbmTVVIXv6TvqO+0doOjgeVFAcom+RlwJQmOVH7pr1Q9LoJT6n1DeQEB+NHygsATbIwTcOKZlJsY8G4+suX1uQLjUWwLjjs0mvSvZcLTpIGAekeR7GCgl8eo3ndAqEe2XCav4huliHjdbIPBsGJuPX7lrO9HX1UbXRH5opOe1x6JsOSgHZR+EaxuXVhpLLxm6jk1LJtZfHSc6BKPun3CpYYVMJGwEUyk8MTGG0XL5MfEwaXpnc9TKnBmlGn6nHiGREc3ysn47XIBDzA+YvFdjZzVIEDcKGpS6PbUJehFRjEne8D0lVU1XuRtlgszq6pTNlQ/3MzNOEgCWPyTct22V2mEi2krizn5VDo9B19/X2DB3hCGRMM7ONbtnAcIx/OWB1u5uPbW1gsH8irXxT/IzG0PoXWYjhbMsH3KTuoOl5o17PulcgvsfTSnKFM354GWI8luqZnrswWjiXy3G+Vbyo1KMopFmmvBwNELgaS8z8dNZchx/Cl/xjddxhMcyqtzFyONb2Zdu90NkI8pAeufe7YlXrp53v8Dj/l8vWeVspRKBGXScBBPI/HinSTGmLDOGGOCIyH0JFdOZx0gWsacNlQLJMIrBhqRxXxHF/5pseWwejlAAvZ3klZSDSYY8mkToaWejXhgNomeGtx1DTLEUFMRkgF5yFB22WYdJnaWN14r1YJj81hGi45+jrADS5nYRhCiSlCJJ1nL8pYX+HDSMhdTEWyRcgHVp/IsUIZYMfT+YYncUQPgcxNGCHfZ88vDdrcUuaGIl6zhAsiaq7R5dfqrqXH/JcBhfjT8D0azayIyEz75Nxp6YkcyDxlJq3EXnJUpqDohJJOysL1t1uNiHESlvsxPb5cpbW0+ICZqJmUZus1BMW0F5IVBODLIo2zHHjA0=","base64")).toString()),r9}var n9;function qBe(){return typeof n9>"u"&&(n9=ye("zlib").brotliDecompressSync(Buffer.from("m9XmPqMRsZ7bFo1U5CxexdgYepcdMsrcAbbqv7/rCXGM7SZhmJ2jPScITf1tA+qxuDFE8KC9mQaCs84ftss/pB0UrlDfSS52Q7rXyYIcHbrGG2egYMqC8FFfnNfZVLU+4ZieJEVLu1qxY0MYkbD8opX7TYstjKzqxwBObq8HUIQwogljOgs72xyCrxj0q79cf/hN2Ys/0fU6gkRgxFedikACuQLS4lvO/N5NpZ85m+BdO3c5VplDLMcfEDt6umRCbfM16uxnqUKPvPFg/qtuzzId3SjAxZFoZRqK3pdtWt/C+VU6+zuX09NsoBs3MwobpU1yyoXZnzA1EmiMRS5GfJeLxV51/jSXrfgTWr1af9hwKvqCfSVHiQuk+uO/N16Cror2c1QlthM7WkS/86azhK3b47PG6f5TAJVtrK7g+zlR2boyKBV+QkdOXcfBDrI8yCciS3LktLb+d3gopE3R1QYFN1QWdQtrso2qK3+OTVYpTdPAfICTe9//3y/1+6mixIob4kfOI1WT3DxyD2ZuR06a6RPOPlftc/bZeqWqUtoqSetJlgP0AOBsOOeWqkpKJDtgP25CmIz+ZAo8+zwb3wI5ZD/0a7Qb7Q8Ag8HkWzhVQqzLFksA/nKSsR6hEu4tymzAQcZUDV4D2f17NbNSreHMVG0D1Knfa5n//prG6IzFVH7GSdEZn+1eEohVH5hmz6wxnj0biDxnMlq0fHQ2v7ogu8tEBnHaJICmVgLINf+jr4b/AVtDfPSZWelMen+u+pT60nu+9LrK0z0L/oyvC+kDtsi13AdC/i6pd29uB/1alOsA0Kc6N0wICwzbHkBQGJ94pBZ5TyKj7lzzUQ5CYn3Xp/cLhrJ2GpBakWmkymfeKcX2Vy2QEDcIxnju2369rf+l+H7E96GzyVs0gyDzUD0ipfKdmd7LN80sxjSiau/0PX2e7EMt4hNqThHEad9B1L44EDU1ZyFL+QJ0n1v7McxqupfO9zYGEBGJ0XxHdZmWuNKcV+0WJmzGd4y1qu3RfbunEBAQgZyBUWwjoXAwxk2XVRjBAy1jWcGsnb/Tu2oRKUbqGxHjFxUihoreyXW2M2ZnxkQYPfCorcVYq7rnrfuUV1ZYBNakboTPj+b+PLaIyFVsA5nmcP8ZS23WpTvTnSog5wfhixjwbRCqUZs5CmhOL9EgGmgj/26ysZ0jCMvtwDK2F7UktN2QnwoB1S1oLmpPmOrFf/CT8ITb/UkMLLqMjdVY/y/EH/MtrH9VkMaxM7mf8v/TkuD1ov5CqEgw9xvc/+8UXQ/+Idb2isH35w98+skf/i3b72L4ElozP8Dyc9wbdJcY70N/9F9PVz4uSI/nhcrSt21q/fpyf6UbWyso4Ds08/rSPGAcAJs8sBMCYualxyZxlLqfQnp9jYxdy/TQVs6vYmnTgEERAfmtB2No5xf8eqN4yCWgmnR91NQZQ4CmYCqijiU983mMTgUPedf8L8/XiCu9jbsDMIARuL0a0MZlq7lU2nxB8T+N/F7EFutvEuWhxf3XFlS0KcKMiAbpPy3gv/6r+NIQcVkdlqicBgiYOnzr6FjwJVz+QQxpM+uMAIW4F13oWQzNh95KZlI9LOFocgrLUo8g+i+ZNTor6ypk+7O/PlsJ9WsFhRgnLuNv5P2Isk25gqT6i2tMopOL1+RQcnRBuKZ06E8Ri4/BOrY/bQ4GAZPE+LXKsS5jTYjEl5jHNgnm+kjV9trqJ4C9pcDVxTWux8uovsXQUEYh9BP+NR07OqmcjOsakIEI/xofJioScCLW09tzJAVwZwgbQtVnkX3x8H1sI2y8Hs4AiQYfXRNklTmb9mn9RgbJl2yf19aSzCGZqFq79dXW791Na6an1ydMUb/LNp5HdEZkkmTAdP7EPMC563MSh6zxa+Bz5hMDuNq43JYIRJRIWCuNWvM1xTjf8XaHnVPKElBLyFDMJyWiSAElJ0FJVA++8CIBc8ItAWrxhecW+tOoGq4yReF6Dcz615ifhRWLpIOaf8WTs3zUcjEBS1JEXbIByQhm6+oAoTb3QPkok35qz9L2c/mp5WEuCJgerL5QCxMXUWHBJ80t+LevvZ65pBkFa72ITFw4oGQ05TynQJyDjU1AqBylBAdTE9uIflWo0b+xSUCJ9Ty3GlCggfasdT0PX/ue3w16GUfU+QVQddTm9XiY2Bckz2tKt2il7oUIGBRa7Ft5qJfrRIK3mVs9QsDo9higyTz0N9jmILeRhROdecjV44DDZzYnJNryISvfdIq2x4c2/8e2UXrlRm303TE6kxkQ/0kylxgtsQimZ/nb6jUaggIXXN+F2vyIqMGIuJXQR8yzdFIHknqeWFDgsdvcftmkZyWojcZc+ZFY4rua8nU3XuMNchfTDpBbrjMXsJGonJ+vKX0sZbNcoakrr9c9i+bj6uf6f4yNDdaiXLRhJrlh5zmfbkOGQkosfTqWYgpEKdYx2Kxfb+ZDz4Ufteybj63LzVc7oklSvXHh5Nab4+b8DeoXZihVLRZRCBJuj0J6zk3PtbkjaEH3sD3j6hHhwmufk+pBoGYd9qCJEFL21AmLzzHHktN9jW7GSpe1p91X10Bm5/Dhxo3BNex+EtiAFD3dTK0NcvT58F0IFIQIhgLP6s1MX8wofvtnPX1PQ/bLAwNP+ulKiokjXruRYKzTErNjFrvX5n6QD7oiRbOs3OQUswDgOxzcd+WwGZH1ONZJLEKk2T4VGPrrdkN9ncxP/oQ8UFvRbI7zGVrpNjlniCHT6nYmp7SlDcZ1XmS7tm9CXTMumh89LnaNuF3/wPVa/NLSE195Ntstwz1V2ZLc/sULMGaL4gdF3src9sR1Fh33/xiS3qOrJQlLpy2luR0/y+0q0RnVBBBe4yi4ueiNOdNAq/pR8JehYiEiu7YVJJcGBNBHlCOREQviO39dwxTxdulwW+UOO+OrXOskQ/csaLPIKxUOUHktlUtch/SkuaV5QD2G4vweAaCoSxMZ8k9jagIRR/irArsMUBBkvwQBZj1NYclQ1WtdeoYsd38CObL/DJksETohDEy6ZCixViSEPvNKiV1SSCwIiVk0dPGwTZxeNwPoA0BDhYNc4tIkej3DcTHVTS8W1vYFlURRUS4k2naQ5xI0fseTRBHJQ3WJ6Tn45afc9k9VffnLeTH+Kdd9X9Rnont4E39i8pr21YM+umrbIBTB8Ex2jNapeDYMPaeXACP6jpZnFy8NEyG2AF+Ega5vkvKIWjidXnkItArCkmeU63Fx+eg8KiP95JfLbUQus2hJTKPeGTz9b9A0TJtnTVcdJW15L/+3ZIOQ3jeoFsEuB9IGzxFY52ntO1vJvNdPQMJhXkvTNcRYz7Qz6l09rNUNGbfVNOW7tQgzdp42/0sZtnFW0+64nFJ127Niq3QLT8vwHYw3kOplK43u3yllVjU+RYv76vu3JMghXWGsSB0u3ESlir8CjF5ZIflzQoMn0xbP3qWknhPYHTAfu11TcndM/gV+npAK5/yKkwjnzWs5UXGXJHwAFo1FU99jtfiDBlqk9Xmq1YKsy7YkB5nOmw6dy9mjCqYT72Nz9S4+BsTCObdH/e/YZR3MzUt/j/sjQMujqJNOqABq9wAJCDwn/vwSbELgikVGYviA89VqCQjLBkWsMBf7qNjRT3hPXMbT+DM+fsTUEgPlFV5oq2qzdgZ6uAb0yK/szd/zKqTdSC0GlgQ//otU9TAFEtm4moY7QTBAIb2YdPBQAqhW1LevpeqAvf9tku0fT+IfpA8fDsqAOAQxGbPa0YLgAOIZRFlh3WHrFyBDcFLdrSJP+9Ikfv1V16ukcQt9i8sBbU/+m0SAUsjdTq6mtQfoeI7xPWpsP+1vTo73Rz8VnYLmgxaDWgOuNmD8+vxzpyCIC1upRk0+Wd7Z0smljU7G9IdJYlY5vyGTyzRkkN88RMEm9OKFJ4IHwBxzcQtMNeMUwwUATphdaafYwiPK8NptzFLY0dUIAFj2UVoHzUBmmTP1mWCmKvvesqnrG3hj+FHkfjO3nN+MaWXgorgAAA6K9IXTUD1+uwaqHXsEALRgD82K6GVuzjQznaC89QI2B34wNf1dPIwydDO38xCsAKCdf19/ePn1xejxPZgLmzLlTLvloYWMde1luC66/CFwUdwGF5iJ4QIAM5jvbl94r6EYr52H2W12SlcjAHBSzoVjusrp7UZh18Z/J+vwjQccSS/JBNE2b1adygAAyNgJ5P+bqz5+CPu24bqx6Gjcz84IAtVx2VEyBJTqrocOCI9I7r4vD7cz9L3AGZ6DBzEu36w6fQsAkN2IsmzCZWMxqbMTE75ymnyFiK09l327D2K9sywTANigkEkmLwTn4RqDiPxpy5HKA4aeYqbSoi0AUAKsGA5go3ZXjR0qpUsAoMWolyNxzyiIPZ+qsEM7QDgbHW9WJWwBADq5800tDEPPiPa6ialFj0uNAEDJEC4am4A/oPGPxmDmXdikl4cLKa8CgG7265rxY/wjtmbutfwJ6M9Mer8dKHyeZkalbAEA49jkE8MATNz+qKwsMOlGAEC+lkvGJh0ds/j5uNtg3tilTY+NTe/JnqF4N6uSDACAHKQP1Lht8vSzU7iEyzPjut2EPs/Y38IspIepXm+8s+bS2w8QPd+8ONuavlmV3gIAJLA8T+O2x6fBKOJyYweNq/YsVtd2SjETADgxiwkX4POo7fsmuHnc8rCP05hqlnABgBq023MivCisNnZRtK+sru0oXAIAK+fRHim5pkf85kL/YfPLQ/xReQkXAChjtR0XhfDJaiOHaB9ZXctR2AQARsyesDkUv0deoTWmffvT4f6SYAUA6+xXzrX3Smi6X8zthH22b/w19LM0XlWqr0rjAgAWs1Wq4T6AhPsAVGoEAAa5PpwVKjiHWlfJ2TZJf63FjF8SUG6KBOOL9A4PW3qOHE295pQyfVPIvxcJeU+CKduBk6Q+a2BAVtKhf4QnHrHLFpj6sNDUDvhCfNPmtn4pdDSUkHE1wPPrF1UvkQS/L1S52Zv0Sb/r9YK+jx51oWU+i39Owb1p4MDw3LcwvjpMvtDXPEWBlLcw4DNpOOC8f11nKez61/hc4txssbudIo5lL+aszAI1EiiSfkCetqOyBs4trCbou3jqJZ4diL4zvDnDBRgP+086X66Tvj3JOY1rJwmj/sJrubDrVb32PWhOs6BN+sJXQ+6nOZJTgPRg4PWz8sp/wWI3wsGBQoSU6tr0dWOkrwhDNCN5mfGAM5vfnawcoCdm2CdzIN0r72XbbDWqjom1cMjYh229sPnvzWLZAaSiQR3bSL1XjCwFH1wa4ZmmLeiaD4xutxAZfzu0FwMUkXTsvb7SX7TLM4zwjGg+HbjiaRWI92lgwaxTyKgiXbnThL9j7uBDihzuMULvXXes0e9x7PwRK+6mBLGD9z7PAt7b7va1J2EHu/zZfZ6JPoQVd849MZCk3RJOxd5Nsxi+O0lUD4Pochlk5+4naG1j6yiVRKBPobLOad//hDECeD1ORiB9M37JsSxMC6yAkKEdy7S1aRmXRGrLECneqByM8iQ8x6d71F1uhkYUi3WEjh/A9Yw//HCidh7pl7XD8vEkuN/f7XQ3+fhmSfR/9fHkNcRp4qCD13IGIBIAsQXtoDUnASJc+5H5f7YWufNDdZ3SiHJqVvKw8K1RNB/4mJi3YzQP47nmN2cw2BH4yKk+zk7wcLx2bVzeS773YW/7nMg8DMlWZGeYPJ8lYLzOnN4o/0fk9Fb9upq1yXbRyN7iDSRnOnj+kn3vLjHbn3NmA2tRwcfVd/KHGxPybUwcg9e742hY/XBtEgCQYe9Qh8t8fte6aEo1Lt7a9rryutsDxLxo0o9/lhdL/GMs9n3cCxZiuv3as0lchJm9dQGckDBOT/R+y2ft/W/eswB4NFnsqcrBTerQmx0BTPclttiZPF+ctHerFc2RW9MJzpuGOShqyTLCNsCjhPV3EtMF8nVQf2TL6GzI6EphQEjQgG6JrtMu/0zWg2e97o/uoTIf4ipUvVVM0KYey+VkMCWrFynVZh/hpTTXcm3+EV7yX7W6Ehrz8KON4P9MrENJx2msYomlnUT80OrH6Y1+KEfOWn8KyenbZuHQkjBZcDAx5+J64Aj6TSooLJw3anwLeZGOQeSSPXLe6dVY7MF7HhAl2HU9fwES3l2dLETAm5btht91AwjpdUoQghLn7RhAIRWFRVWJa2Jtc0Tm+dHRGiAvx6wG/OCGa7BsWuJ6U3LwfOzSY5qNsj3Qpt6+JyEhflEfl2YZ7jhjJ3y+3ehNh4IBG4eEmVuhYdlx/EQQvnVDqC5Lodj7NWEXjMFyT14tjF768alhticUJrdl3w6P7cKsF4rhxIKWxOSELDHpzaBPR0EgNZlKdZrSiJfPGaWK++nvRxwoo0gt4maZU1CAx33oq3e+NirCq8K514FHpLc0jbti5KzNlr3ttdqoSeYKrOsq+jS0w4q5Z2AMeYnbAgCra8oCHFF0wJ/PTdXUMVyIdTRhS8cJZVr5dTMliVhKm9/TZduaYLTA346l+ILCTo1es+CVq/f+2MU+XuX47AuupenBsoFCNMV/2ywHjCr2flEAWipfnI46tqmjq81ytF7IWoydKyHCSI4ew+k4+ATvUzq2buldaR6SAI4VKAMyMT7zkBkAMB00NLbwmtJqj2k7NAGAqHKufA41DAksWEk7A33esJTuBprShiAOZCMOdd72+E7b1umdzQCSOsdaB3BxZgCAIhUUSdbxYbW7MfnSRjQBAOeidlz5FgodFOhlNAn2jcFu6KmERUygbnHGMpnfdLZ+KTEVgF9WExaIcJy8hr/tp7Y+ofIvp0nKjrUMZqLMAMAsmaCWuxWW9dpVpoxoAgBXKtOVhyhPGCAhWFJty3Ija39F5udrAvbBC+QD+d2Qpx5Dhfh+FqLgzUW10AwAWChUQzuhruPOnJ3rUZXMdgmhZDvzdRCfX1UCN4/l/wPrk1X0qHN3KbpjTKBihdxy04nZgZFKr7EcDqvvSSpivzg7QGxmssgfLo5KZRV1TZtdbR+k3S/kYjTNfDUZyWrcFtxkiVhetaWfvcxumYBgVeSozNkvIgSbt+L/2Cl6TuiPToNFUi3gzvnWRxo0ES1a/Wjq0Zc47dikmBBXXE4/cj/BEnTUGU8vsXsssBsmrEbCzB27QqDQGPdcgFpmIb3VQSk9zfTyXFlADILp0V5qUnuHn2SAu8QszfXheW/UnD34sJXHTECWUYQhLc5QozwqlP1qnYO/j2pQmGU03C06s3d2EjlIdLNuy+Z0X9GIUUWCXDpwtAPYI/zXrF26ADyEpyyj5o5bn4GKoyNdkhskDGYenTTQ+fRqo0EL0yIqcAfyVOvo2jq3CjCRKOLgRzv8NZ30rd0sMLzpKrIwt866C8KrAes6AeYvDWFOdG2WjV8dNiG2wUyaYIU3T/cDo3COPFw8EPEFcIZAcCNE6BpH0CBPxefguDvpbTKPZF5TYE+uaLtxvaIUB3bIQI6/yK34JNzrQt1az5ucZEtXCMlBED4lW3rAfndm6l/kCGLzwMc1jaGqJo9VNR0VIO4dMQMAo+m4cpFwrKQXPzW3czk7Vehrc4bS6j+UCQBQhrljlDaOxR/+L+5R2jt6Tz+GWNGIJbKP1cd9mk9gzEk9hjdUxnNNvHTW4dOvtRS4MRoQDFpUwYuR+pe67JmTNfNtDqx7LG4zNLjh8a/7i6F+adgW4ci+DW1Ilf9ok+1zg/3+lfN6pK5X6QelSexeWGj2JnH1ym6sQa173zvfno297vUcHC6hAoTC/3enX+ej+9JNHu5RQubQD4++jHOK2fiK8Df3A4QC1LZSDmK46S0VdPvZ8VSJnWHbWlJDsshRGb3dyRkMr3d8VnqqBEcrMSKUyBqMsk6yUayfov2tM+rgwqxlrsiFu4pvawUNfFtcuWrc8FmGXzmz8Vn5LxfzeQoLfUX/JWNR9xC9tZZamjtBesX5eUAqtw7rpFfDcdbgXsMcsICLg6iqrNnoDTf4umgefPn5ZdXLAEaKmKr9K2jWq3EjfHsxMwBg48Ul4dwopQnV1GzvwQsXaQIAGfxz3b1L+LfNKAGAuxiMqmZyB+AYNU1XTRJXly88AYU39jt8cP2yet2jRRzcU6scgDEiEryUmuE0/9XcsZcfId18ZowZMT1Pn3IAxpBI9rrhhqfOkyl7L398ZNuIPH7ElH1o1LGcrV7PCOR1IzMAwAuoc0mYU0VR8SZmewtvuEATAGjx8Jyr7ndZRRabBAAakrqa1eFyutex5al/HR9+Pg/51BPSD406ljMQA8pRvJ9nBgCMQyre6J1RTDLuzPw1pAsbjcEeOqQ1rdTmu87PE3XTX6L5Gyznwp9PhH9fPkpGQ8UNREgtj619rgZb/3wPFNQVbHc/a4jvwl/8oBKYjqAA6N6ujHBoGb4ATrvhNBnDILjc0CJKnveWTCZsDPoCAtX87ot1zaqQIOzniFoY5+YhQw5B2c/phhnSAZA9ApFkx0IJ7sCLThlPpxnHyv9oR13WpgPR4gUqXIl2N4nXnTkJrp58Eu4njBlKzTOEZg8IxnUq8+sqOnQo9N2SE6jdRZ1z/fsQ3CJqNvCck7DRQdc3RveF/dc5mlOPI8T4uL+oz+Z8sJ9wZo/NELlDNct9N677yFvr2oYCQ3/83EfWnj06lnR27o268AYQhVTPo3RYYPpkhgyVUD50TQGcbIPBCGxagjGtFBjceJbYSX958r3v5q3JbgoA8LXamYl9ce+UOusgjorz1/LGw/LsWuxIqVZLUflBNNzqe8wfBnngUekITgge65Xj6xD8Ero1H/HAEgzxiww6j8ZB7I9hA4PQLxy2xTCSF3tJ/60ye1nRAiEhHZjEwgdaaD7HdmaDiTG4HD0ArtUhToud4pjcKlanIcEUD7j13JTtBA9u040VgeqfcMoXejWyk7YDcHR0TNJsYM2cyGylQEg654jKROckKeaXtByXo7DqAQhhd+e41CpRPIm6zoUBBU30L6veKGoHUvVujt12wrswKY0GCX7BAJ1ePs85euedVbtDdCFD6u6HVpjhIAJuyalS4D2EoUBc+OfKne64AHj8o92ql+v1XqI15bZv54pNU+xgh2zxoFup3vOQ40Jgk6wnrxfKqgVYJ8SCL5iRzYqxfYJEKQ6I4V7umobUg1tBdDZCI6wYso5GIsPj5aztuwBIib7SFoG3neHuUIkB0omw3HgYMqAVKWPKX3j0zEOeXOXa53uihs/cCwK2zTUdWfmdaBXGvP2ca3oubeEUEhTjUTjLD469sBTbSoNat4Q6NAHDoLn1d7TVHjJAmwfrggxygS3ojqv4siKiccTvzqizQ/sT37uxiPOJBH54kEryjipahqC4WYQ3Ztrduw39FZkaL80/Kl1M7mFa0VRxRoxS2hASYUpIdRLxT54CSsaACskZURcD6T7DueOjXevevtHYqtG2ZT+lHHVdNiMYIjJ4fu/nmbJp1zaOCONKPSKaP8J95Ije8V4Dnzyb3018HkdmaFbKBJDZMrXEB/VBy2mXVnq8WJSTK8CQuWPax3x8N3IdHtP+nKkRuXSj644Hnl38rAj9tk+2VVRuWRjNa1nsrvymeydN2VmUP4vo65rVvUozV8g+vFK0Pl3TTFjraGzjnpqnYj8fEn7y8xRGCb8o0PpJFDvkn5OOcISVLmQL98k0v89Y4snCvN8eEeM3lT34MjVzW2tBDx823AnRhLHF+wMcfn1USCfNH/y2+Nkmud//9f0xIbj11Zu5Zj4+4VjnVY/3brOKzwL+ejBmAOA47WPUljHF/2vcrorTjC9qauGcdjWqnl4Xqn61TABAfHiRvtpVT/BXt6udWv7G98iwegCujaC1eL1yhl59ATcUPRL3AaIOA+I5uupJcT1P8HWp2/hzT0Sgulz3jhhpRAGwRce+/k0LmNKMTfgx0HDnnYCoD4hwwcoVOwxDBCUhRKsQoCSRhCue2/9c9F4/djN/iU8vqQQAu2W7NleXuELigy7hrrH0ugYBzkBDFOm6hLH5gmTFDrY922J2jrjyFiDRWEKvovHJtvocMB+GdcfEc26nXAIxds31Zvyjgg9jDEkcu356cP45FQyWQ/2Xr9D3uuWTcP5rnCe2ZJ0E+rAzmSuB7q8l5kKexhJKIEgrqufzwt4z0Ma+6Z2Tc87Mxal5/108FsEkt5OMAUkkyPVYQvnEFI//BZi8mLGfYTCJKmKnPSOjj6PKKtrk9r4yTzXtIoLNfgCFXbO64O3y2dHOc0mB/cn4z5fkuA4VivPPReLcHVz8e0Cn05dLt14MyJdAU5yPV1oQSPcU194ylCH1I3Xt+oTMx7XGZgDuxpWddWvXNDuvgrl5OdL1SFnrVEM9U/0qfyz+6vo/VODmhzpDG/dFXZtJ7jTriHeSCKPhhLO5/uYBuSfw1POp6E8u60XdpKOROkyUcoWjqimnNyHhPDDdV1/7ND2Bh/7aiuxpFbYlYhwZNrk3v2ylTvyNsFmfuRontBwiqKx329Zob7jLYDIb9PrG+AWk4nN4QAF3naK32CroJjFK0dzBGBdbhqGvOwlO4Bqc2B+K8vMn9SgTYKOTXQpGthMF0aJQHsdrTiN+fG+eK6bKky6CiukeqBgoB0KYhl0ngc3MWhYQhR6ULDmmmrqvURCguRGH+xUW59GyJPI78e38CbKxEQpOnYlmZUheRl8+5Orw0KnDEZXpMdVzYEcr8V95gf54U3cS7adnQVQm9yAR5pkyblumE52RaVLbIouY4WxcNzoLJraAqsbN7CUaEyQRtqm83YVxgTXFBNPk2z9SfS/2mTSulgEfWUOYmQEfiAaWnX+P0ezKFz1BzO/T9SX4B8Sm7NUmDnbHI74izpe3Dq/k2jqvsxNBX7keI1eux798aA+Ee3pag6xpPDa7uIun6dXBDb9xrdpAFa1TYvlj/3iacVrXUYInG3OQv5lASKQr6Ok3CWTOFrkE3Ab4lFR8hbY0DZsgpiXw3Ic8YccFXomJeuZ+zNjq4CmlxYhcXQnrgtpWb2S+JXEp5JHh9APA4IjKN4hdm0qnHRzhSFfJCcOkg/RinGMzwtgNDahb4H/uNWjrIexsVRC9uYlMT3CCWCLeq12rSi3BlAQrnIAdFhL2INatBUy7ruc1TE+6eZ2XkZ/C6d6+CJrwouvF0ghjWDogxPbgxotmr56iGJoKnuwNF/VWHb037trPU+K8a9PCmGGWrqdiVkSOISAAc7D91xXG8Svq43DBvltxo/jeFylAbMWcCDXDm0rM6DbyRvFtLzAazwd/SPi1x5/NHyxHgX5VESDDn1tRHXzSlbjz2ulMvtv9Dp+Ic6KQZ3edNwa+9iZsx7kIwYF4aRfPuiAwhoYbkgvhVzlgwfF3Z5tX5KgmwkDs6AQdqyuZv1U3sFzdM7UxaJQ6JM5ELO+d+/k6PEylnYrwSOBlurpS2rECSHSp8S5Sbrm9jweZ44BxmkOBY4P5BmhH1PRRkCRcXYG91K0JRzOD/B1vQCcHf//8atBI/HuWuilLAbut+HwOMwBwqaIhe73RUkx4vCmUs4j6ALwz2cUa21NgLwszAYDj7hk5AvfEbG4HnKsavV0z2HZTPwBwNCiFQ3kIus/yxQ2assWZAi2zvyzAEU2C3XdnMwLHq7+vztaFd9UtqeZAqkKXkjoBs2vNdgByZS2cA1XNs70DCmO/0wQp1xWZZFWF8W3oy6uDaQnLF/YRxHk4rtJAAui5f4zymPhhpt+bgyGzSZdePfx3cSoXJIAuErW2pSJav7eSO0FL2bOd0eNgTenDatV0qcMQm4q085gBgJZgp6OlHCwNuT4pJjv46ZFji8t1ho8XaAIABIPsmTYL/HWV3harXQv7AQAWvtqIyuK3dJ+Cj9PGMb7K/JvB5xoGYzzTeucCQeXKMYa5Jh9EzhnyD3aGdQvU/FS1qMnjkPpyqtBQbX+HZgCANU1TteXcz9EMPZ0a78Xu1gxoX41fMf9Gx5SxOfgyF43WlePpTPS7KysCZeKjhxfH8OR2QZTGU8btjQNsDjEviJ5zZ659N/5Cs3tCTKjmg9XhwU2AieBC2CpJAc9MszqjvkvHbiHW4L7rMM9qMRXNBirYkwJvjoctYaKk80gNWxIUK2xDd1rykGGMhRq2glXBCIanrVbE4ctMSCncz7rDmN8J8+7xEr+37HpwPbbLV7DuIoUNODXiuNOYAYAdqqXg3NFSErZEqkops7NsF4dEt0pzJgBg3t6nyOT+ujWUO3o/HWboODheW/ZPjzH7Y2vJl5Vf1yz6cJxee134g1HHKtqNR06Yb1afnVoMAHh1fMz7KJmMuovLqpY/VRzDP+iqbrVar9VPSZxLCflzMZyzGDZ8juE3iuEfdIFWywg4UAxhvkt7H3Vz2Nmijfg10C3pDCGbW5HkGR033VTgXud+mVEqiPa0FRwBokdONicFMVWtN2cDyUBXkaaL5B06Dqt35stna5O88Hr68+Z+0vHQeOL7mZXCPby/RztHkz1eoTOcHLwcfGzDjP9lqtKlou5FzABAt+Kmy07cqDp8+QpF+lRyz702fCBvwQM5RRMAiMkiog3HhpH3/YCarpVzwsDVzQUBQNA83tWEAQVHZpGCKOs9UgWB0sS0CoJt+jEqKJxR4KigJF3udZC6mslAYLpqlIKwZZRLawYKHLe1OAacLM8+C5yT/b4tcDp1RVdidcVxOsa8Vfh2fiRZ4tPLrNuhQJAAyu8f42gdo2Z48/uSo/P29+J71n4oGiSAghLF0zoExPPe086JT6uNadoIQf+UfWOXtuWPNasWv/o8ZgCguhluxCuXg+UWd3uW2hGf5Yq3s0gTAMDia0wbFX5SKZfmYVwWGgQAHXyMEWXhV+k+Ar+tjd34iPkX4kOGQRqfp70XJHXkjm/sJ/ruOb4mSeuYnTfjCWFvoEcG4BwfnEtpFvRelrlGIum4+DYYBA7AtEQyHmxHxTHP/CVxmr/Sp7QXobUx4qP+rGJRXehvjg/uZD3fs2M5+cf7E5+fOPC8KOzGyYE0ZYwhuF0MBVh+MePAVk05a3djJn7kqrUyvLsOroqbM46Z+nM6JvdaGsEjVfwqoN2SfHc135EyJUq88XZEIX8I5nbsDEklYj4fVQqmNM/LjlmbbOv7O+qij/N1bqYrmUIugDHNlrEKYJjRKVYXlHSPdfyGYRC+RPqs64u/jo2ougiKUNbbpI+Db/x2xXsz0rs6VPAcqFgWBi/RYfXDhM5Ens0FyhIjELEM6DiViir7E6DJ9dNP4HqWVSnodz119e7ebZ8KbVAEGh++0g/ApiYn5VRNSkMFBkNiOgyUXPxXrPkCEEh32BdBNi3O8TCdjh1Kx36Mgtx2wdrve3T5Tblwg3Dy+gFH1Y8bEJ4Y8CpF3f2ifCSfFN4eSp3qgkZwRVzRWFGKT6KmfJbumRyGcIXhjcutiG3UCPipFIo5tES/QJQ4o5fA1zjdnptOZ6UTfGNOqVAk55iL3/7V9vAJgEzoLJTAOcpesyuSLJ9+IW+7q3ToWSR3w5Y1jIGVKSSunuyIIgcV81NlP/hsnTQRh8qFuSJCUR//D4NH89aIdvtqj5KNjOeCsW9jtsu+p9no9a8geJI1GJXPffb0anRpeUfz4mHRTMBWKl2PDpgKGxjEFyPzEZovmYVbBJqzI/RTaIuAbGwW7lIsDnvF2tLp7Hu1b3qfcsk+/G3PLnDBtaF3JHFxcZZjXgxceGu9ILgKdVl711k70N7xjW3vWAcAGE3Dl1+jmMZYWowjir3aY4c8NRZirPY0Ev1+E7PCsPpUUrFDWx5UL3Rodd/wKDQrtaeR5aVhbA3ILyE3ZJhjvRLYnEuAOyGwKzeB1SZsOJCWaGuT/p5rkM+b8QSzB+lVCEqxH0kxZyEM08yz5OVyjGpfkg0zhcnqroQ1mRg3mTReLxNIU9elAcNGtsPJ5lXSDFeEIunTdwmY2MhZ8LoROcH35TLh3OplkQ6JJnwA1CB9d6SN0ThG3scVgT6N+LHBf3cmMBRjqZn7XbXIGemgb/Xk8bt/mx5VZe42eAID680ptynUQBNR9Rf8HbSWhuPaSJA7qG83SvHE4ZU8OEZqIpGXZ2GlaMKbIbq4uiDYovInRvGODQYcpAO4zgeB4dnzqV7jSqHt230tB5CUBEsE9/4cJkpF0SBAh3k35zXTHvCenvz1Ud2TezFEu6rBNFZnsbQrAZqU7ErkypRSf6XKqPZigpk+a+0vsVaED2D3JhRNwxIY2pE+dvJNX6SJNv8AiFzDxFryAUsX4o48r+31f43Yzj4WI6eSDCeJu+GPFvJDu133wd1RnUutlzOH90ntQT/X7R/amKrLW7A0s7jEKi1VMJ5La3AvXzgwxMrp+bww7wFh1HKN3Xhvv+lKLFWQ4sUEOD0zd8CG7eucPfHjJI21YN1vyB1iSH3wVqtyGD321FZKYMEewOQgYKGh26SN3RxAK4uhux5ehCjaQ3GjyCMS4cIeECSG9Ami/Bv5lzzDc4SKixDRO7muxtyUi7xbSGtZIACJ1BYtKuVj8nKICZEkv6tAB0p5TtJpK/9/XVrKVqIC5Gn5Gl+0A2Rp6qk+LbeXn8lN20x2VCwnMxjORdqIQiITNmlKN5I4thKV3Ze3OPhGP46gumAIlPrjldf1dBKZVqhtblr7/oNQt+T9uE7exCNrEZu9oghu1pbzbmo/SpgGJQZbzXpocaLCH1LDy+GH68PkYGdP4CubBJyQ1g6E90ERC3NTSp0QBu/GHRqDgqyK3V2j9dxCEcVLFpXzSIB7on3SnT1kN8WtZr7ekIrjZi5f0VjZ7TRFA2LXcUfw+v714j3uPV07vb6V+Guqzup7wTfa5UOr6bDQ1T3NbY5CGPvUfib/szeX2BjA7h6u+ioHp1/cw2IrfMVok9S9Z7yhpsnxkOmq8Xo0MV1RmRf8bpBvDNH6cgLW961Vv5SeD4Jpn5HEoPWpbBq9Bpna680qtL7lTEt5D8J1k+uhkho8aCcB6XQ2X8v3eZNlMhvyPqR7PLF2hJCMfG8uj+rFeMWAK3akFPtO/o/VbnP2iGtkR7/rWe7ck92lDvk8q6oXiA3cZktHYFYSaLq/Wd2Evot7Yw3RHQToOu7B9UKkrATgIggmR6iaaXml2a1gHX2n548XA7GA0NQHEl1jZVE8ujv65YK5p+tg0LLvdzacpN/toxn+ebxUhZ9WrxYP/6fr9Dd/3jKT9qPcwb0ZHjwa/vmHOeZ72aED+8NvjT7aj4YMnL9DKEMLCLsQsf5EarQaDzcmTWgys8xKOyFBrbcOon9JCV+wNpa53kzxvzJ5O7bVGIgO402v5IAgHbO+6RUbSNbEWEGK5hXuh+Ctu9QahUtfNk/FnItXny1lltmcqOehqOIVT1blWCfzlpMrYeA2qZwB3KGKD+QmDdOALt20yVYVTB5tTj2+GmMDy7xkk08/ezZRHkiu8F0SYN6kOz01gIVGhx4PnxMBNNZ19oSmZ0G7FbhqlOWIIN2tq4hR3nQRsLN+eWFM6eCpGpYrQ5lDB1p4wKcLgCNRIbYX1syQAvEl1a7llGiQmb6ECq/7/nV3Xt89iAoMLWoQN9mTtC42bTObuALCdRI0FV310Ea36gJCuyQ4X4E50iOCXlEIKYZ45eU7UrnNCS17WqO8MCAmY/Yand6v9O4d4kmT7ZC6qk2ekv8GIkgTdUVpWwTWFjLkaZ6q9fkiCDJsYM825A3DCEUh5hZUZGJFNwjUOTlKo3HuGa4aRV7sQlx3cjhkPGRIchPPtePHjmm8Ip2DZR/q5o86FVBaF5Sk9XumrXpwRZPTIQ8bJxNId0kTDy1nEIPjmvYo3kUVH3D7CVqAmawsvm8JH2Z8KLO8/ycLE/DBQ4WvxhWo0Pph5K98UQLfVWZ/UytitHvuWl11gNnpSwBMZijoDMvuarjMIyi2buz2w3nFt2lpdsU17X3m7DfPdSAU9ozBqxNBx8mWf4WzrW5IfaqvHR+vH+6YsTi6rz0tLf4aYgt3gu05+/SiYYq5pqhILfws18fN2XL7xjVL8jw9EWjAFXcAuix8blRIvBCOgrr//dB0izhF6Q4oWfD+aK30NB7cqT/Opn3kXl2QFB4JyrpPrPt0JPzeIdIfbzbr/hE9plcxZZnOkVdFV/zSp8FxdslyWpjEPNJJXZ1ePgtW8Q+fbzcSjnd79KdsHHypr2ZwICYguSrAJJFHlydIA6Ttjc067yPgP6S3LV3rdJuwzy3VURPPHcEuBE9RKTDdFVjDOea4iMrycYG+WNjo2W4TIQg4t+3bQ0kjB2yZ4EE1MQaEyWQTd7kBeL8RFGoyLWXUR5C3g+NeYxfCxVsIvZVoBp9HFHTUJCbXacDeU4pAR7s52EfaGGusTdyg4bF2zu/jkG6jO2B4phg6J6GFn4PPaNgei5xBroUV92Oj5wuQfwYpJO3/plgv5Y0r80XSsnGEXuAWiWmZmY1lsQ8US4K1dYzPRcTy5Jlxw4fYlmKuVWTRbRMYKmuw1I33DmDEq1P8VP92Od4QKQnw9hFYWJPYbHR0xKSftb2WMjZ8tBAxQRPsko2tgFd8fyI6MCWnUbiNYeCpRs+YHAIoP5A+IMw7ilfD67stGzBQbPe0rkPkdzvafekGuhsTZkCc1If+8DSkV43eb9zvJrl1ePyIq5kn1iSK48mmVI5s6WKnHAb87PJYKWmHAK/LiVmO1GT1IDxFSZpp6kLIrQ7z8uqWdiM1+HzjCOwrqHqwKVQCrrOeaQZV3Cn2NWhvzqwXdibTusuLztkgAGUlBxHXhPHbYl7s4t/uGwwBytV2qw66lXlF+tFiQG8sAr/l2+r8X+oPmPxVda9IVEtMFPehuoD+szcvsVuBjanjPfYXvZ1sY08gp19W6SxEGa5MH9kyBEfRetwvbGSqFojHD2jSJn5jmQ3OFTtWNPaj6WgL4LGDmfRvLGMwm5o3lTJkx2kAkCf27T4iS0PfW7p0PeQeHjoPZ90eKsPWr9dxgOSg7PKMbAB5+v0/X3SUGA8BZjFKz+g1kLfK4vgHtHa9G7ODeBAEKJ7NZ+pZtitnlTsDdSbUu3PeQvYjt8EhRO0QBPg22kUkFv+JRStiXAXYTTqYAjjf+cCyqr7UJcxbMM371xP4jigI4Kub0l4rz7G2iqZkzSvv47XPVqmV/l/qyRaVUsyrWGaB8Foer1e7OepmcSpQxfAbod3dnOIX4z27UQXtQgJobSIkWYTYZkjCAP37uo9WcCNqL9w4NRW40ADhRMYBmRub96mtPmEO9KOezoayE3UFzDVvk8YxLZha/Bzt9LXEfY5sF/FVyV4e+iHBKpbaCoIB/I7Ntfnf+qFO6ZQlYjH5ecDmKYSk61/ngM7IN9BaZKepxqwDSNsMK7eQ/gnoyGTVPFcPQgoPz7GMBocsvBftsYYjogrg5iLJtK+2TCKSnAt8VEF6h8ypqi4A7HaAjqhK8eQZOfi9fjaw35vff2n6/3Hy5fs4iRuaT43Vwu+NN/BLTk6tyTyTsd6o3OFwet5g6ojRzhtMnS3peiBHGEcGtg2GVTrJWp2gIFIs5KPyrAophV8Onw+qo/HH+YrmB6vkPieGt7VPry2xQCKnJ+lVCQrgZd0AQMCqvBgQp+mYcCLJzoVtart15zDIVzi0momismLW61a7tTrqbvnlGgR2GxHMECE3111MlUkwFXYtx1vcYe3fbYFXXPoPAKAoMCf2s2xwctbtusDZ1cPHEXsrhg3/zviTN7gbp4AtQqyGI8COwAUt782BS/OxOwDrfsN2AABVtfQvvN+Hai79m45zarWdRnmo7b48HqADqqPphAJOcVWmE6TrpjEPAGAPOIiNuy1QkZ2ZPlALnj0c0LW8YUJQOzVQI7Hs7nij+oX37OGikkz/Wu24Xl39/yx0G2C/WP7edwTWwENB1ZgUIXWF4/F+Hr/JnytTZk0+iu+3VNsAqsF0OLj5/sh79nCxF2bkfPhkWvtMijpO7Xf5R9kf4nyPCXtlFsb3H7YCf10Rc171fYX4MvixfNsA9tosnsxd4BIi9GaGT9iv+W53tfpIK2XugXoVRKRQcdx53QCAj68BNFTUdcqnmZ0LqS3ukg5q5isckmNHUVkxdEhOiVRJXISuGBHtETFhrrvIs0ngCmrX4y0mW/s3YzC3S/8BgF4cqD32EwR0ZN2mDHppiwcL+sT+RgXMwSnAcSFsTduP80FQBb4rDv49Ge9DKs6aW2psI90rV4gcAt7Eced1AQDnKIrYj0f8uwKmfu8wMr+ex/at+DweCrbC59l7ZD2HUL4oysJnurkIaug40ygE01hSAAAwASJFtvhpiPUHId5mMwgZ6lpROiDZvVwHAFBCCGOLuZhnvWQqIkz3JdKaxm5xUzevRXZkZY2929k7imOvtveTwVj3lH3OvBEvfIB4tw9/pcogEIS51MV2nLx6pta2ufndi5N/XyuzHOp4tX07VU0OQJPa84WmSZDrrfWbtTcfv/T39LPko+c1rF7YEz9rM6U1rF96M59g9cktVllRpsCqYhx3PjcAsAqrGUXBMKXcZPANOTGTJeUMraxbO2swl+LlKxzaRURxdsUEzquwS5GzJE5olHIeIgAQaVnLCVY9BRMda0k5d/1pC0gNvOwfANA6kA2xHyfxZ0FOob30iIXKxTmcqD8XxRNkr+jI0nuOA5Q5l/Jq2URemRf4ru8IkTdlT1JNaolgiwm6GXecj6Cx55gVt7BVgStP9CpJzZzxZDKMpraMBPF149VfuDk5W+JGpq7KhshgFoHBMTY8t4SruiUqOBuCgtuPmODsnl5BFd3SdTQ73pZ8fnYEBJfWAo1wYJhoYDrBwFRigU2n1YOJBAYIBC6Vl740850tyXxjgoDL/nFsp8JEAHMIANYhIQCe+XZ6Ki4wtj9z4s37J596qh8oJuSRpUTYdqvLqsl1IUNgMbGRMMVQqerjwIoOBIvhvCkAwLkOnN3usRMeBy7stGOP+bpL3ptAVFwl49CpoGt7WR4AcBwjboIWbqo65luDaW/ux0yvmj+YTumfhIntczgdVuwSmAxrg0FquqAGm9CpGElDj+MzoaBJj1s1e8vq2PD8Ub2HA5/0xTXL6K5pu/r9MM/tLnWJod96/hO400WAK2z3904HZ8b1HBMZXTWZkKNVzTR4IrD65o26AQALhQp4AbG8mTGwc8Xd5VXAeQsBSI0FsgDUVRK44G+FVjUhAgAtQ+sCJ9jUbPh1vDfcvcq/u15rNNB14z8A4DLk6XV+vLY4F6t5HHCxBfFN67IRXJ6mvw0U11QrpXisIL3DrfdWpyz1CcoU42Cq6+fWA06z7mHXSHJldz1Bkhc25j3eTjWa2gGAlJE0ZPmG5u00UW83EtQFOSsNCaSuMQ8AcA48R8Oh45ZVgdmyMih2uCIF5pZlo6wCC7EG1KjAVndAsbwg4+KWFd314aQ4TlpwPkNrbKkHhuodKaKYFRv6GbIfc/DTIS/9MrZTgbEBVOVonNhbndOIfBT6ofxW+ho/Rk89QuxZWDnKVkL8bABfj2PvaSj90uinomMD2POweJQ+Be/a1Cs42xFUIjL6yvFiE2NViUHkDnHced0AwLTOPzTImzsFZKTtprPxkryFUOjqikroqCpQTJVErdB9TYgAQEPQ4oYTrGru8jzeG2ZV+zfX4LSW/gMAWhl0k/3EBfraag4BBtTFkzBTRYeW3rOkWslLmQW+pPdhq706C5QyfZhgboceEvIzWO9lEqQ/ZO9xT/HNeinsY643vp+BGEBexdfzbQAABp/qaNw2vRWCquO3vPmnlM4CUVXQ3ZaB1pHCzA0IZ/H5u0IIma4MsYIQth1nEYuQ0CoWEwAA0w7bVYgUzJcJKp0cm5hka1dmMgCz4uQadgCA2UKsWExpLWFdNnMDYE1LvDGwFmySEogbcIxKHHj06/lwe8wpUMf+TymTqZT6cQlfVbGD4QS7nmACn+6OoP3enWfJG24ruwwvWxvb68HL+c16gt2TNasMXmaRIQBw0wgS+ynUJluos5PourUM3SwnJ0+i6Jh8vnMBH/+0qCq7K1ACAtXukEDFAHoaEAEAAARd7lPLiAJJU3vVf9PRNLE6vfgfABhAc5D5sxXKqv6W3tzG39LG2/hb36bb5EtKrTsBavpEC4MXLK+L+eAi1n/VrN8H+SC7f/79K/05bxVuEMRc/u+Ca6A8krSyN+q8ZhSj3vrcZL3BMXZZjEh+4pkDr12cFHsL/559wPd/sIUbHivH/4Z5/tj48SgOcLjTe8v3zOSy2/2M/gD9GkMWsVtTdyTVvg+3W6uwXhxk1FmId6QMP/uZeku8OJb5sRrrttOGRRDG+lpD88P7L10woNhld50dJssC2L3OGDzF47ApDuFpTp8CAII2lRzF8nnl43Csejuv2TTXrZuiCoipt3LVOC0PABikV4MhsqosnJsXcqNaGTOB3Fwn21xB7shpsLqgtLcrKqoQbBdOMXxwF9rGKrzKaemo3h+DlyEn+EL3F9zk7rf19d/HjKBNRb3EHooiBcy33plc/Tq+s+a6zu92p3tcZQgAjDX4ErKRamcBDryZOGA15vzu1LqhQJ9MYfDu3aUOAXV1EvABnDIihDlXeK67OE1OtL0glpV/vEGwZDDsxn8AYCRou9f8WQRwqr+tN5f4C228xF9cW+ZKN5RiEvjuRGUEldYn6Vt6kYQpp0tCIGG2M1CioNRuuxtMQ+kqZyxYIdOdZe0AQFgFBdiWL2IhA6bbLuIhJbK0klBFVWCVpjwAgOXhVVVBBTZuakC27IxTIAme7VmQXt6QEkijCio1Ltwj4zaUKHzkPcM5RXxjvU0t/cBQqSFFqKKiiIIb/jhTMe8lrqmdy2oNoAJD4wToKYbsWyW9Ofg7we/ImDz9CLE/XaFI8Oi10pejA7vfHCY/l9oawP52tWFpigZrOPMgp/nE2huTszl7klaVCKxzoloEDgCk2x8faoc3NwRE0HbZXL8sZyH17dVYFBuoUp1EWUDHRgR6xv+f6y66tlSUkduLpmZr/6Z3ZEMdTFfjPwAwIDTXNH+2QtTUn9Ob2/hb2ngbf+vadq70glDzAu6AcGy/akkqsE1/TKEItTbUb1F8oT/nBx9PzPQmWmTCtfG1dm8LcVdwF5g4UxQft+VK5Nvoj208DiQ8dQu3/atIawDmRPJ43jNDVrWAFTJ0OAJEYJGQzpeDGKkybTYd5mukPmldavVcjb4/dyfi/gLd/Ozoq0tIKBWjJy2eLim1ITyuoX2Edm7GMqOichceVrfRhypP98e5uOAaIt1SMlMZ2IhIq6e3SphC+I/h0nbG27Ai2dMU2mYYBoNsoANzwdjT0gvkUj0hNRpsDGuJBYmO1C7D5OPki6qP4mLe/obk8oiOTLSuUWjYBtLtYyCHeyA5Tw3tYSJItv1hitwsHaSGHT2dNhvkLxqYUw9Hu7C9CIQD18omTNkPwc1IQXEGbuS07nkzR6JsqXjCoNSB/tnqWkLsaDcUAmA8z86JiEM/Ni+SODFvBxi1gEAWZHLIlnoB1VkBkOBrf239cXXlpVD8c2NFej6ddl8uARiyiGrmQ9Hka+APe1xY9NRUTfwzLfv6FcD5A6WEtXxtbID+ymrVY9/J4iwNREZjukGdhjkX8hGsswGUWk7vnC9l7ibCX6ASP04eueRlIMD4qCzdpyeVoe+2oS3Uyi7xW4CtNYNLneV35GHLjDUvqWAwFviZPsYXKd3Uqh3A9GlyAfPGM0WbZ5+eTm8XiG9bTN+ULlK8BXWhTt9eX0xw6fmhzbNPz7XywsmFvyOUfKx3j5Wv9QMd33Kp0ouJJv36ePfA/bGqXGotwjghbiLn9s4bFtrzcNYh5vdx9wS8PmsHjblJ8rX0ORBx4SCS1KvrdExAQ9xPWeNmlEJnwqBsif2jfm+PyTxBNaN3rYpFkTQK+0rrGNAOxWV/wBCJ0kwgxiXHwLVoG8NTIrrxMiIcUDX6olm6hzE3XbRZFf1Psjqff6ujR29sTcPei1pgfGRzvgAqIHDToyngNbDbYTzaHmDsZMwrhVALcC6VHdMmJNirZ+h4+Aqx1qof3sHNn848n6ekkUKtk4gQdIA2AD2rUSVwMTGA95YBHeotFyOYhipzN3srWpDN6Iflf14z5Ob9ObbbRt2rWegh7JrzO+k0WiiO3AYhqgJrXDZ2t8iMcJNlDZRCMV8DndlBfACGGHAiLJcZtnQk7PVJE6jP8ceelv9dOzC53kfXG+wBAH1T9CXY8UBfmYmhWLzTo5rAMblPkTRKEaBgtZkotQhQ7LLEKNFqfgwbPtog3XsLUMN2ClDrVbGAADVaNwDlEhNsrXS6Fh2BW9tuLbBiz44n5lsQyCo5cbubMgQ5d85YKiOkr0f5k9PV5zqcONcoRMnJkGJoUL1q4RSvmp3aVQeS0lXTQxLDB3tHSL1gYmoFOfhhlYFVoBnIPzXLs4M6sfAJNaRCERBjfr4x17J5b7xCQllj2FP/auE0VrHLhG4qKin4El9AiQ9IcW4M8pntZMUtXK5iTkRlzvjn7m0nwtCCXVkoqCIlK6MULVW0ja07CkDffd/ZVrm6DRDZeDQv+PL2Pp6XH5qd5BLchhHXRrowk70ZsWolmlycHZeoRNFvkmOKUHKbe+0bYAslGi3kgZycD86ZfTZmRG4vKBRMphUh1Fh9Fyxz3n5RsXa4Fg9wYMTpDx4t5qxHiwKc9GSKY51QEz8zu/ENXOaQh+f8YjWU34kzjdUuErVYbcqaQkD6BQqcfSpwev9ejYSyePgOtL5aFtgex6x8BCSSdarUMGq9tUM+h7pXYPAnPvxK/trfumJ1bVjGnipf9E19v5hwCkD6GkwAgIDA0KbHTMcJyqIElfmfNAhW0nXG7kKw5twCNhvBunaR2DIAlxHBWm6unYoAAIgDcKLFgUb0ddjaX3MDHDhqAAgAcgPyiv0YByqrMdO9MjKCLhXFyfWXFHSblSYEBzYKdrKXAAVHZQbsqWAE3rVVYFw1hFuLXOXsbizkapuNJcPbVzcNEAFAlmDqdN/2OGovNz01d7tgMgPJVU6FTCfNhAAAF8As2rgpAgylZ3bHfVXaGDx7r5hsZmUQhwMzqBE7mFVjglV1DsU4rHmlNPXnfG4FjY7fKtQNoFpGYwS66swnSb8lOekLqzlu++bV36rWDWBfvdqocZ33hBvhXyZ3r8G/Gvvp1d8mlzydVnUtBMW2bB4ObwAT5g2gVoMJAKBewCzTwzOGq2ZRAqr4HwQm2HQoY1SflfFGpgGCtzGSVHhyqa2mhdv52no9+aJxO0zx0cU1B1GL+QH6viaAAEAH/LX5A+GHWrPCAHcFsZJY9ojfZZZ68VGlgozuYRGP1v5ZE1vnlIRkfUa71ybJ9dO1uT3X5/5+4usJ2R6uGEEGCTDhlSIelpNdDXBgDfkhCBXLMqgScP45B8E35l8YsGcK4Fw7QxJghRXQANhjyxkDshs+AACXENSWw0JPISL192ZMEJPWDZvfcaNoUgUWr8my5pPkuicgZwfXzWjenE2FgLkUZ0UjcwqkCxvDOpLUmfI84zmoYq4lrtJtYlvE0Rg2OJGLBAwb6zDa3AKN0xtp9MFLGD3+0V35Odcp3O5aBh7+rXbNUcL9weBlnWkPdwtovF19Mk3c9umJgmBvNLbXy/I4RKcX1VEid0n29ti6Wru6riQeoFgn7W2ZsDdAig0mAEBqgOnh6eMB1GUAyrXvEuyg9owogT3MgADAXpZECI9aJAoAqCAKw4hoGqCovAslO1ssU2z+xIvrKK6WagMAKHdsYcxmqYUBGtQ1dLmFHLASXdRstJktG2pqLXHrVu9Km2j6dKTaNSRecmGA9qR1RQ8ybuAEjYHGvy5OlEYDp5devkvTF9419AjUSoOS5RqG+RsheEFXiOU99MAgRldcPnYA8spa/hAAHFTSddLyHYfI69FHjjvfTtr1GStXaUzA5sw2rd/bwkxqm3uXVrj2bTNHsIXt+zFbJgi2cKeKY9tlsEVYYQ+eGGyzT6kR88DR5/KUvrhw0VS4vVLkuHwZmhvWJcb9+vDTWxjn+VWHK/kX/SoUq3XqR0HBGTPh2QLmpsEEANhq4LoN9XPvOoKU+F8UBOnUn1Glx5gGAh7XSBLxrEWiAIAPYtCMiINxvTWehk9Wqi4xuspxDTzbEA8ATDcorOHi3J3Pg4quWM3oQAuaOJv+nCho05SaGjfypyDOlHa9bu2tZMVZa/9jA26ti1vDuy4Gt11HeEMwHM276IdGeBEfuyWDSxogAoBbgzdj++6Wwc3W3N0ddJriKpdNi1hptqqGbxb5nHT+/YIBNdzO2JKvoMZaZqCCOhrZIxV0H4OYKdDNGrFJoAbFpivYPtPh8zIXnWTb4NoMHX9Ry20AdRga5LxjHugH46M3mZujv7QGO7LVx3JrfbcB7NhWfIaTEPDHbemR6f1aLg16p7axgc96WnvDbFfX3mDZOmlPyYQ9BnxoMAEAfAGmwtNHAXhn/kkD4OGGbFt7xj6AHWZANMAelkQQj1wkCgDwIKrDiGiM3q4BivTrJaIktTL/gMNFewCAKzU3zCRFgIYLM84tHjj8KvxqvSnhc7TxCk/L23TBjwvXHiotEtbfKvw5+lkkFSKsNf9Thf0xxbdyL0dmfhsdeZV96q/qm31cL/cESbWfcYgVSXcZmWQwLWX/OcrSNJ3jpCS+0D1+A3c9q/MHX0J4ghoN41Frez4G87xwUEUa3SS4QtPiGQjKX3b3V3oW8PrArxQTyNmt9IIQV8IZNPPN+xiDR7jOYBlumI9m+ndavwQK8ml2TBDE7KrwJRJLIrn933ZRANS++RXGPp5aMdhSrynKLZVl246VVuF28T/3Hn5NBXZYO3PdwK5YwbGAq7bkp0NM8ZZ8AABTuwjFcFc0An8wqrLx71lPM8Nb7ER+vOdplI0sAMBin1K76Ch1eqH2yGZ2Lu3EDKrTZYurZ3nk8Y3q4OOG8SVdqLdVwHYO1puo1IsrUjqt6k1Phhu+CwaMh00+Km9c85JuEr71c6VVc6coTDYFApkwkL5KBMBGkf7cdn4lfi756Ou6Iy5S8+ndlkiwa9w/tg7BPXed8XgIXq2t5KXgpeNnDGFXYCAtFKodFqHWisX+NAQAQNKCjEjHjDI6QG/rdRLRB9bgS/YaTXsAQN9mECdZpIQpcB+s8gqBTWC2tJk4uAlsR0uMy9xNswksRi6FG5OXWJJ+ZU+6uIlKLJ8pQMyjuLRZO127IrQ5dg/uumPEImCZvK/Lml4CluX7+axh4z38jDODyjDNmCHlRwt7m+xaULzsS+/TFP+b2XbHspvwWjdkEDxXhn/+BvDZ6YmXQQ6sjdKFuQiUIcsugueudKltySz0EOPMn0RzN0l5hU0iIj7H5H1Gz+NIo14fqzygBDhyqr6EhzVel9pnCR4A5ye8oyUn4drLXgFM3DSeijXfhN5+ndLoizM2fjpdAmKqvn+Snqv+DW0Rk5GiKkcF03T2GfKlFk7koDmkTRmuCo6N/+zDxA9a0gLghsGHa3f7GzHXnwufk7RCTgAGCjS113fL3VyubGSz8C9VH+J/TK/wlYbHe0XiOoCssAqQhVkOS85pjRk2/zek1zm94jq4saDT5fWk/ic7uyhNxQaIu7LyxeJbA2YtXN1P8V+fA+oqF+5lf1IrZOQoEtY1WkB4fxbUSPoEY/6uc8T/1/ZhckpcKWjvprk6wVs6sg3IUODu0ZONHFcd5ZLmswfUJMfvlsiykJf3jDY0f+sAYIYjjho0sQ2dX8JZIXw89IAQsCMyZnx3zb0lYgpPOEjADm2GTHmEMGSyRfXChbWO2QPb1UZmJNavM3IH52+cZz5oByzl+TwmeeBoGVT4zh2AHcEd2CTOq5zP2JnU9ZIhEU3pEacXOubXNmPYT9Iyrz2PkZDbaY4WD/ht8sKMY9q9r4QvYas9aWviMNFJ7+q9aTPy/dt0kK9cnAfMlygmIvIQnsU/inaR6Tqd2tTz6bImJEJrFGYCwef/j8G584jsg7cSkZ1JF7UcWR22TCVpWf993SKBcqVNaP6vE2h0aYGTARq0Jjksjoe12bjEw032fDSJyPo4Bj9xi9L9O1yaT3PfAikuJrNzdXzglixr6TVyW9QzWhZk588b3VhVCbcC4xJTFxmnmDpX3GLqAY5jTDVTGFTkj1k0gaF7sdGOfOKJtC34HbEThv/ggIetpwlCFx6rmTp37GbqgujyqYuM7QyKgtJjP1OXKRb0zm/d6pY/XjR1aeJHUxcST5o6pzcy2PGmqQ5+/GnqIRKPmmph8ampSxavyhWCsQWKjmflDxIyLTn48a5yuvCMFxofIbGbU486JeA8t6yE1FZkNQufzUtrjxxFUZqkrRb2bTiFNhiUFOkCkzvjRVs3+aQn9s+dK3UXPLHo6UEST47bcLYJGx5JyYXpCWpTCk4rYnqgJwpNKUPiECRAmoNrbKSqfJtl4GbRdC1ZtfiNNVsnc5QVV2ZQiC+Z7KDjcoTZG7RxejediCl9yz/pDuqIWIO7v8c6o26FgDWcOKdW2qUNpk5wVqZ7ptFicadaSggAbPUME2/Blh11ariFwULd92UWmY1TY4TgZCMXELL7gAFASrd5nTm20qrowm2O0CZ0+fa8hEMp+VDfYeNfM73HtRrCU936vdKrvZ2nniDHEYbSlRIGzTajAABaAClphug+jeeCBFabf1QPM439WLly2aO58otQF1wCtUUMYVdgIk0EbBsR5Jmiu9MQAADJ1WMSuftRfQBU7eskAt2jRClNewAAeuaMqUxS2Iv5w5rVDXyc3mTjs7QxG59lTLGZgghu8cozqD3JijALFJ0U7Ukv0uFieJ16c5d/rCI8scluSbvbRFbhssluR6vflGlG6h44PE0v1L1aehIANKeQjcJSuwGgBUFNleVrp+PcBWxq45x6tt0YTNtUh6kya7DVlNJMCAAwAcZVyHWi8K1gynpm50IIyLOxByE6BoFriBHrxHhNcgY6eZNjNMYb9XN/jvYv8QwfriF/EQKegg4B6o66JycYhQ3/gt8TNnbp1ww6pQJB/iMzP1UdAlQoyG9/mDg3Ka+NJbtD+ZDoVVWZIP+3VeaOqpnlsf2PBdz2cZHwYETZAuOijAIAzNGsbHlXe4jpul6Isq3L6V9z+S53FV57s2dYur2pDXToHok04xKlpSclUQCAWtQQRD3ZgTpUnE1s0KhLewDAZF57QdJ1rqUPcxgOh3Kc2TpUDsTnTYZ6SZ26LYJIdt3145JnScv+tSRc8pb7FhtjgQf6vRj++ubchl+5sg5v9gEyLz1kYmWXk62IXeBlOdlNA7fTXAIA3BXC3dAN7g4qlnMQpmH+jUrIe5qxR/047jpiuT7FOGsrJx0bGcfNGL68lS4nhNEu+gAA5vImDjGNuCyDjgTaXTWQggSvl7IAAHABIkrMhex5e3g6EjGxmeQN2beiyFIsMcXT9hZ3iuyPG+xLwkZ0je1mWAbOHxQNfKQpTmx6utzIWX3CX3kE3jpVnVXcTXJZCUe/tcVqnzf82BTL1RHGinX5gk01owAAG7FypjoLb2AATgBlas80DSjLDDQENMWSNAH2VG67rHZ9nrYUejhRlKgUI1qpTGTGF3BJr5fDAwCcXlAK+1EKkkWrqewEvULy2BZrcEF5WZuGkObGuuqUfsEkKmkb9kSXnAomtUSlWMAa3PdzsXaHIWs4UdUo7dmdYd2c+PANkUj5mKNI0finPMZ+7Q5msZJbXywQAmte7Cnnh4AIx+4TS5oJIjFCTBcDy+MV4BASLz0JALBuJLJcajcA4MoQFrF8LJ1nmNgilrLejmU3h9yVoTCYvedGEsw0EgIAmCQ5IpvLtrRwFBa7UcG6ui3NGr1awncZ2ga+y4QwofRV11jkIzgc831wRyDcOfZ9wuF8ujaslSif6D1qlWhvh0erDpx815boU9Cr1KLjboNFyIRZ7GvDwHIUp6MAAAr20U0nSOBQBuBlksIR2mzXma6B0G67BToSoavmSDqPxezCtWtGuM/7f56GAACIsTlRYnxOZSIXyZlr1AYAeD1DEM6oqJj9aA7ScNpM7RakydliXc/yg6hZLqUDyUu6a/3qPrPClqjkqmgU9+kSttRiwKbAu9ie6H6RzVoltjmJKhJMBLfdpUCIcDlsFAMRicNDGRAxu/QkAKAiJHFZajcA0L1Iiqf7kq4xPKBUc8cMpKp2VgRSHNZiQgDg4oTUauPSAlHOYKZRT5Qgo9K2IKOGsPluuPIquJia7Nufg4G3vbzgle+an/rvjhIrkkdV8vSiyY9lgfZxkXAaK9ey5KKIAgDcpWVv9UHkSpghSn0tAS+jlbvU2vmzK/RObXBA79VIJ85ccydtbi5QRKe03cTCKVGigz/+PQ67vqfziSqw0toAQFIrt7eSTrjssPD1jSVsyFzDbt8UKhDfeknToq27Ma/VLILrCknIq1vdzfGkfZYf9ZBRkydeukarr4LTHYTj3U7fmBxSsz48bCRP1SNCuQWUAMCm2Vm6GwDqgOI+9x4Jq+Fm7uL3eAcFCoZBm/3YTPOXj3u/dodfCq9c7Sr9478LSSSCQ4BKAPnt8RFmePFS/GQXvScfH5UKAPnP/GhWjT2uNvJPhw2292QYi3DRA5VSAAABI9UbVTFgYAs7yjNoOSDSoKFslJSKOlgwcduCqmxaW6QsEoh8IsEsxgMAOUAVkBcEcwY0HxcY4dbg8Ddo5thf+Or2EaYtZpAaF1cr2j59eY/k8Naz34seqeGRQSO5bhwydxXC3YniHBMA4ASoiwakl6g5B2F5DHDHQOZqZ6YHyJWuHE6sOcdQmIotHwvYqf/lXd/fFAn/IrGkC+jKzMsKG72neWn9SgIMsZb0gFdVW3Mn8JjlLAAAywXOwHDZ61tZUxJXozMvs129AjtniVWVBoJQcfffVak6ZognkNVP0rE+MijVuHUtoVZ7UQkaA41/VZxg8FE/kVvCOfkeIhEmfDpSQocNvw/f8R4uGSfp859wPXeh6nPW+BNxc6zfmDBuANxFcVoKAOAKDfUecH0lwJr9vJReqfpsVeMvb9s02OAtTaQ9wIUHXWM8bJOTKS9s3l1+DE6Zs0mUO5/eFUA99zqJEK7rFSaF3oZ4AEB0V1IlN8J+jBxRODTKapqeY73IUFli805CgE9geLP0VnmSFnsYwPK13nD62MBJa2QKhKCqeZcDUHUPeuq1xJBt7MI8D3lu+yBlRJuYz75QuY4eDVN/v/mwJRiiwrOMep/u1Qw7Boqcn6jpOpjfhm/FvzwPNuLtrWabFcXgVWG9nBXG/FP3N5slV1GFVP2BcohbSVCoXrdT3gNr7w3KIMOut9BvxuXNTe3gami2d2hgW7A8QabjNRuaaAkZkGmRFSH76GMMtFKFF6VJ4Uk/YIv/iZQooCIDM7pFPSQzdF2/py+WDSQo9rU0Q+FWmX3+t1DKAxY3EyLKkl0CC6AJmtF4eRiEqgChrTDnsh09afuxJ9csBnUPYVk35msPV7WwyOp94BCpCvT7TvyTaqY33Lgq5XAIY5butFhBbjePXBgoRYpxNObIQbCz3csteRS/Y0EWHXc/4gp8MA6BCw/mcqvz8y4kSiAYbIJFhjzwzQ5mXg7Fgl1oFHSKB1FRQ8hxY/qFJ8RHJz0PfDInOMJNxcuVPWiQ7nfORkOaaKIRaKEL8U5h3cf9ad3HCa378I+OqNf707oPi3wrHIAew+4tfQMpqChw+0EvGZ7pow/ub0BNi5yLvx78hDIKKaXMOUxKEKYekUoU7gfrPoYWiBUR9j45q3jGPQsjh1z+aRO6Bjnjwzj8El9kRqyraAuDfhWNNQ5YuDmIVjteui6G2rVJChUNWOnidyteR21FVirTNPBOzlnqOQjmclsbhdH3SMKeoktqZ2QQN9OLakubJS8mIGcB6ZArqOPhJXwgFqOiuycvMyMcatrFJ2bLsKAkuMb6VQkBgNzKzcTMqga1eAGOsqz4cJdkgqKo+DSXZQdoUfENL38INKIyXfvk4erResTmPg3OhDBdBdj6neA1KyFTSxVNuut6XZv8wHE1H3xq5dEiRPGueZJ5Rcc973b8I5quLGvS5D43j6or2+R3nrqKnGvVGOqyeEDPD+BhmkwoL3CfTRF7Xy7xm3cRKhw82Kq1Pj/QfJWv0EPRiRbc7pTb4/FqWa1QYWdkMWH25IuiwN7lKAAA+xirKBDL0plFqEz+p7pvwFjp323tmUvrTwFczQxcAVxkSa7FQzfvAgAYCrfHiaZu5oNNxKFVidrrH3hHarggHgCwJBNl/lh7wezEKrysprWgqMLYkiX7du5JjKm9txJqr4mT1QxYuElUS9aFnrwhZ5MowM5E9BI4tkOgBoAT9bA6MclJo376/N/FYJSFy3Vtq9Pg7S4nEwDUZ0hNt6dijFSLjECcqns/By5c2VhxF0+UCkZbvbdr/l1EouPM7GRskga1MrxBptUsW21kOsMgpAZZyLlWnmwdqBH3a7xpiG2Or1z4XkcTYqL/hS6wEvOvVTF07bUi4dtd3LLXvdMoAIAd2XU6zZlKsiLAHY7bzur25s9ce/WXdtUGLrSrSnJxZtT9L14AwIgCS8SKibYoXIui2cQJTTG5BwBUkFlhUuoWP76pxp15Fmfyxt44BDPx6BBTS+2gpaP33O0xtsjH/u0dqSy6UrDhOtScTxxBQE3QhCgWxrJtPUglqWpkgJrdNmjmlsoEgA2EHFMdGkoQpICMiMBd70UycRc2MGvGYVenseu8jVaekEL8m87+AEIM8TtT5989vD9lOjZNbhqj8EIG707iqQ6t03YLLYYNTCkFABigpbpRrAF3odnps31ZQGus2EALOkrSgirxAgAGpi7aBZ1NHG7oS+4BAJ2y1DAplvwRTS9zEkQoPjdccYBcT79lBR7BfaDZv/E1qef/onV5e7KR/4/t5Pf0CzxQ+7+qPP1X9c3e17palAmNWjQBAEBUmGFzFJrYQS3VgFvoNTviIgDHfqowrVLB+DuZ89x+zu953TiSprj7L+uPO6uJPq+ykAMAwGhd3JJaGW1w8H+vYfXZpBdaAIAx+qZyuU4FDIaSBpx5o+tY6ysxMbXW16qJ1Ky7ir2RUMZ/T91WKEiT+YGjqL2fzz/hHILfaDlBfarPwwjhnUJLzm0XUgCAKtpWcUMPQxQHvSiOAIvWO0s3smfOL+MtDQuD0SJZ9hxfazCqOwGEaWJ5FwDYwWhcnFF0nEtLProykWAVXhQPAHDxO2UX1g2yB9WH9CYXH6ONBXysKSXi6/R3hO8yBBKo1cO62lMDdm6yBduZ2N4ApBwCGgaoOGw0l0/T/10MRq3AQdc2HYG8Xk4mANC3EM1tTzlZJK0wAs60sUxy4AJruYqsxlS0gppaSAgATGX59QrWroVjGumTixk0g3y31hdazoZb69vzNuQgxIbqyVTFeM7P+6EhF+CDRh6WG1wf8aE4lFQvVYwDFc3u36vTOeHtZ1Txj6ejAAAqHpVTX52cnsoEVDNxVTzzzJl/fWTlSgZjZOWMpmPYogCkcRcAwDY0BXKiaaaBlhOpxqpE9wPu/46kuCAeAPBKpmW6WJ08zIO+UIzW9O52o2RlLbHTzeQlNag5JhUWmJ3idbsKocmKUyj+t1EQOpJQLMML/fhSJRT3GnpuonCa23qVCFY4nxVWO+eES6PG/5PwV5JjFG7dsa2eQapKy8kEAKEbUrvbU3EbqfZ1DYpXwKHZijtb5BQxUUMhAMCrZcrpY3WczSBNPaNmkLaZLTJIrwkhk/HEninzMcz0nzcDTo/z2RgbWqo9Z7SJof1NQSycOWQ6SokUAEDreTj+aCM/Bim1SwLejgZ1eTeyo9Kb1chc3cWVuZ8pf51qVt20ijFR9yzwAgADdCsuygvaOvGcqcSH6r7VcArxAMBokSx+dgOFsgjDmpOoZFrk4+IqZD0cqFoKDc2yK2ooeL9eyzEOKIvgHULLrn0MflgNbjpRfbQkAbSgwnAK0XaYCiUZ/UPfWNntSHdWoUwAKC0SGHV0sLKDq762BIrdk9PYYeP5CxDvGAte8KL06EJC/1ygT2p9ANGGeH50zxuWpP5ojzHlEiqVIw0J+tOCHkYMZ4pvPTVWKQUAWBXij8Z7YJBSqQbcheYyaARKHBiAcBqgS7wAQICKizJDn4fqM59YXMdiPAAQQBUQFgRzBjQfFxgx1eCE77oT8aG1hn+95Xg+xvMXOaKLqezwhuK7lqc/qjx4YZa9HELc2NV1mT1F6MFFEwDAQMRt0IMacEC98/td9tQ8eRs4/GBSFZlDFMve1d00hqHsblKeWYuQ8FFBMdFaXny6/Jou6idliJ+l3XXWcr3WLGpPXXl5UI4NLWx4V8qNCa14+0nhSQkOEAKyd3GFiuo18uLGPC+8MGFqQrFj3kmpv67078hXk0stMi2+frECpzezP5xLzKqmaqr+BIwIAHlx0mWje/pBvMGCHABgKMRMgbHMHJOxRSGZoLLmvMLsI3mdZhYAQEVB8pTposztl6cjSUFspm4WH/1BKVsPVEEcQaWYe6LeHZzl1vpL29NBmCA2NVDrsLRGsA60Uofd2c0BR4OG3DvDvOoIWsBXqc8/KWXy6td56555jDWs9IKBNcgXZK0vttHbZw6L7aiJj0RqozCEw6v8WHSlmhJqSqRATNPjaCEl9KYqiKQ73l9EeRL00EAN3JG8B59DKynocr5jPTlSDj6WNkLiMEHZhGxGciDWQnd3go42qClbafoELdPTDKM+/PrHeW+Iw/tdlTu5vqxiVkqanOxXrlg9QVTfbdZysCRR6mYUAEAaARNohgUb1yYPJIVYNgHFLe4B1Ecxhi+XUo0zYqzdTqFdJCR8VF0j2qqN9Ezkg8Mkz2lYRF/L5PHRJp2uINr+hcNcT/RitpEddkKCh4aWVF3zLjXuXw4XTpe/KzfMNa6xwnwF58PaMBxDV0J+hKulnP6E252B+GxGD6U1Ert8FwDQhkHX8iPOnlG09fitJ2NRl2heeaMiTXRDPABgubJ8pQA2f8ICOpHC7tuRaXaYWygUb0dWXCARUGjejnK7Rt8MEGfsNzI1hCLFC0MgQ0BY5XgRU5MCyrcqE6eQko8PxIWUprVwkrL/pFCltM0XM0RKN3Xb2WPgTkOZADAgmNCi7pFBpg2Cqw3NMP+tdLTGyu48xidts5kQAHA53Y0gi23jPAUNdu3MONCwwrPHCw0JBjEpaJXpMtsRJaPsxNklyHI7eR6H+EyAFr+Wu1tt+t7CSZCs/r/ONq6YFQWqy4bqrYWpLdVSUwspAADFht6u04NaSe5T0RpQ5HuGETJrbi5gZQYBsMQLACyomOgGejrYU4n1xIuDldwDAJr07YFSVPQzFfQdrKC5A146CsG4RnTvQch3ggndi56+BzucCEwxwnndLnYfcElnIhsD7AwjcGUO7aN2GZtrQe0xRteBuq7ddhf+saFMAHALdK1FNZuBa+sGTUCphKGE9aQzzU53X4hSIQDQYIW4+iXXwQkyPbSiHrDIHnuw4wd7MHkyMNDhKrwhI9zDMe6C+OWIeUU66f88q+/5bW7dywGKJYYbYCkFACAwoaGjCxYFSTgRSEC5uQUnMwggJV4AoFF7WjR34OQTl+u6GA8ACGwBZLCYUyD5eAHV7zrQDF7gSAHQnu60i91p7NkG57E7n9gb3yRlBYFnVZ0DJdhGB0owrpauzG3XaTVwoUwAoBYNGLV0sHKDraU9FQquNhPfk9rG91ypqz/kOwT2Ff2wRbbifQr3p/RAgEhX/K4dAJNcD2hetJu2v4D6iES54v9LDbPOdVxpeGK4AJRSAAAAkeoFrAgEwNzcgMkMNuASLwBQ4ERFj2Z9C5NPHLAW4wEAESz5Ixpc0Gxo9DqIUKyDlO8LiF/T1n/2LCb8d+qfvfXzbgzq18A/vhj2xwCb7fLg95bz4BvVQeTDRAPfs50lK1CV+dDjBRMAYJZ2qrlhmsbZkYMtCwKQBbuE1bV75mcPPbrSByhaGu+r6q74MPzus25ffqCBnb4/swfE/1X++1BdqH41n57m2UV39mbKtBUa2mmbMo3pijBXLQnXETtN1rJbid0/qYtdNeobpJrXZAEACO6JN86opJvmSq6FXDqt6U59KTfLta0uNqRy3fe3l9E7xFJQxtJ6l5XlmwRl3FqUsjiR5/hA8mtVILxavKcfPQIzjR8zj6aU0NEUTq9YsFYCk4oaMWHNAbo0owAArgLCMdMz3fQbIcYmoPTE498wUXHN1csxAqmtFVQVYBekfFwGOzu1EwAIaI62uZxooaSCmmx1baLjCXe16l0UDwBM42vzP+c+S4rv0ZvT+KnCeCoMky8lrfE+wV/o7xv8lSlwh7fNvHCDt6hPxC3ekBPogDfibDrhjTmjzngztdu6sDq3oEwAqGKgk0bt4WGdKgd7GXRPCcU3pWykNMvNhACAJeBgC5e+hhWkArOyM1uuUIZptsCztwaaxTKI7YL2wm6yA8/1mfYPU3HjUuX1KQBnOHmBh/jMaqX+RvfOlLzGFyswVv/5nL+qwNpM09lQw1qYyv3LNLWUAgBQtGHq9EzXU+FMjE4ApdqfxL9n9oXJmpsjaq4W5B2kK+oCAAInIjqQ2unBmkoswqGsG+YS8QBAffvuICOXfWTvG9vkQmal8dMDHYybhpAOtnwH6OB6noLlW6xwckiCBU4vEsHwLvLqlxUipK5Eqiy5bXfAVCB3xgqbPjjaSZ3GT5erYy7mJPexY9tc83aj0UwmAKgPafrsqfd4u5kxCHwVTEoOXDSdkWJlivj2HlSaEAB4pvs7qADXNEPvQYaZdI7HwY6zdXAiCB3E1JznlOvllt0FxUOllxDdpDdXOB5bcZf9EyOGg9qlFABAB0CqB+UqkAd0bs4AZwZ5KC3qAgA+ELKIIPOJAqcUDwBMt+3DwhFADSZsdgrqHsYnHwss+W6wGTwghcCyITCnXeRuq6UdwSsTyWPjVv6TwOTENNl4g/AptNhBapOVjAWtZrcn3FAslgkABRanFo1XEGybnj8GlxCBkjV2ui/HdD9v/xrmsdqFjZTKBItmxfcSFEjigQDRrfhdewJmzdTXA9cuZRLtdCWyFf/LTuD5Jbfu9VpBi2EDU0oBABboSL3ZSWiBYsAdK8CCys0JRGZwARZ1AYAFOyrqvcdZiHwiwSzGAwA5MAKoAB85c+CyMWl88l1gMbhBsP/ga70JnBvwnJXpxVHhNbLd7ylG7fI9tRH4kDISAKY4gQate1Cx0nMYOyWmaQiB4cRZeURPolI7P5cY/UImFqe7Ptx3/mWSDm4C7Hlb3c4bwRCm6nPMAqbyj/fYoyx8Pw9W77Z5aBpW6sERWsYBCUkKeAXWLb65e3yvxWCRRWniEIzl7Qhf+rFTQr83mCUQtK1DrWnuwj82gX2cp0vK7f0a1a075sa4iCnp6FqsoRcVp9w98OxdpKHRn9KNK15VN3oEIzK7mIWuGWyVGuwGfH58x4KvDEIVM0FsFm8AgAZKzNwfK7L4dlFptgaVQf58X62yzAIAREdJlnTZznr7jw+6Pg3I4MydDgg9ICaG9wtI+lDr5R2brvFXBIEa4LFH1uJN5c04CEpJNg2d7DKdYo6NJnEgQMyzHVxKb9MEHa7ZW3tum9WxwijycNI0itQ3Tseox9mncAd3S9gKAAvg4Bnm8X2a85Vj852EwM6fX+PDqV2BaNC+L6ymBfnXy8rqC87WjZkp7GZJFwDoQGpBlNOxqx5QLjFd5xYHWdoDAHgoTxQohRMl2pWp/K6jBeWweQh21aMmGNsDM+swNzJw/yeYg+Hu8zVkjX+fYAocLnMQbIvFSa/aQg4ul2NGsexGKwqOblKi7ehmSjQe3Wzy20e35cUyAcDF5RmyattdanbQoEvjVCWcnnK8G+okCgGAnj2LpRmWQ8kVbNGZZfbQjsahpsg+HeLVEBA0midLc2eZLlBPJYeBwipvDhNL8B2sGeN2zkTsBPCbzBUA3k8zd8L5lf4BFAVeedXP+pya8zsaJwb9TGdSFwCQVIIoH5oY6ANyKjFlvHYQyT0A4BhVOFAKG5d0tLP8igqaDUJ5BxOGj1YfboqJfR5AB4FPSAB/fLBY0OHfW24JjfDS9pawJex8oti6E0lAtu5ZyUa27l3JSLZGKbstXjTAYpkAIDpOsWpYczY/GMiSKPMIuL37Qk/vHbvJxvCCOa4rQwAHxDJztFHfg4iyvb9wI4iMts1BTpQ5UHo49E7S3c/QD0Annn/AwVGYJm4FgAUF8Qzz+J76M3cZZcEisIDOzQVkZrAAFXUBgAIpiwwyn2ium2I8AABwRA/B8CZofHxssLIPARG8979uBxVQPFzcElzhpa13YUso+USxdXskAdm6c5KNbN1zkpFs3efsNnnRaBXLBADRMc2qYc1cfjCQKVFmF57dD83ptfkYPWNU0zVv76h7ErsCwMKnSJNzAFH4eD4jhDIktZVbYwT3W+YdReCT0BUAFmjG08zt698j/RelKpAHVG7OAGYGeSgu6gIAPhCySCDyieK6FOMBgAYjegA6bDb5hixcNhaNL/tgsMPrkauPZ5Hh/xTVx9cy8jhHMpzD47/4Fx99uptiNG6wG0M4Wxt16Kmzte735N/vgqq3BxDt4vuLXcuP+m5O/KrHNQOEt3e3r3MTR7zVhdiXtWt+OywrmazPDUA93Fd82qtWXlzDyREPXF0sFF2rpHiSRAqkm9O0vnks6JXW0auyN3kfrYqZzW01yFo6JSEMGEDoBHISrfXXnaGBn2PjjPi+NnGstVVr1s/TIu6iYgQ+YbAPYGN56wZnTGXU89pAVxIAAudXACJYLd7u5Hvn3hQsXE/1FcZ4gX0WQHXr/hQ/PRI6rf9AIZYYkUnwuCN2bL5AhOglScUiRHdVXGRT9J9hTa0H+dZKTgIfURn9ZCuJxD1q+feF48pEzVHxf6ZtDotC6aiPBpTXnYNmibyhxiWQ16hJGk2TTk5j49pcHznrISXLcPjoXjyL7qO12v4raIhVQOLpe8qCLLNZZPeMTX6tkvcoY1N+3Lg+clEl6S7CRFWURYeLjv0yT9uU/urrwkbNt+Ms+ysCjcAKz7N1tc6uFqHVQYvQoX32t/je8bVtNyQQP6rWCrvAa/vDNeWZ7nnOsDUxfEVIgQxzPmSaC5kFfrecfUoKW/lHUhGY0xBayFMsQBzRTW9d/5m3qdcTVj9/h9BZWAf9ScJkpocTjamoWmXZOJMEhuMGgWpWHGmUyE9msihjgijVMayAsVUeG8zpC7L6YqEHGeBIIiJpAW808RWYRE6HofNLAmKkXFs70Nxl/70AMe1jfUm+wKJJxLalbtlCU+ABmc2IWeVjgVYyuIh+SrLeyQ9DXUScL8SpKUA+bTEtCIgKOa3jvWSVu0B/3AqoqHepvrEA3nB0LSQxy3dMX8RpZJ5BSUMAqYumdWepHnuI/XQewBJXXw2mrjhzjlCehsGI6MSKvXqaNFQvncKU+fAmGIGsBHNDlRBk1eaU+3Gvu/yN+g7BRp1z0FUQkPXkZRjxEzE3VLJZQcFsxoJ5aAtb/zLKbBpk6aQYjInSGrQlnrnzuvOfOYV5qjQtT0XJd5oq+pYJmV39gxMgLlB9uLT9vNhCMpk7A9PJeasWPBbOUlxIJEBqorrIesY35MkdxrFj9WrFDCDCkeyg7Je92OW05tDhKwiEnIWGwKkRpXURVNugtDIoMtm/XAKxpYZnzkT0YYnwxifqwmBJbqW0PtTNZvDU3te/d6b0Pt0X6kNuuKGHIxKDnyDu2Nq9Y3DYcPzDEtHiWZFDck++iCdgE9esQsy40FLokvtZ61HRKCrLTUIfBssNEEmHqbqfik6yMHX2w3v8hqGXdqyQjp0LDb8qhT7G/2Nvu73a78QS+5pYL6H5r9inSqjp8DJNqLnqoP7NvdlQMYSs0W3lopkwOX8O678qIepfbHXEH+ZGCq6yLd6yUA98mJLRse4/6Keyoa+zBb+bnzYhVeddHdxu6zBFhgxX6d63qeoJ6K4wu/seG7C+x49C6HWkkMTli+C1RBMSUdnmAiFYPRAPDHtUHqLPeReao6lgFEeI3EhzfReP1gjC8KlrdklHZoSX7Bj1W0Jnj7Ymv5tnADH3FDh+nVIytDyo1grvA0Do1k1IpVgE7nU8bFBDGRZD69nFSy3UvJf1OWwFrIhmWt90NtqgBDvj0fNHycyDc9QRRGvvgGUshqGtX42vAsO4tSt1DvJQ6UkBEIc+aXWOTVa99+WbOxDhMwRyYCZY7zYk3oihjI4Bj3kL7zfJ+BKQWzHwKH3DpQTdqeg7ED9yoRnQNJDCf7jcillJGhJxBYjYAdKwAaBsJ18S6D9nXmo4/0Lh+nPA8d9ZmIKPXeTN3dBwYB9C0UZp3KYoqKdEXz9k9zMNeD/9a0DyAwKKOmik5CAYeynb8raKJhY0Hc1g6fuEgWwmDO1mktqcDtBQXN5nqXnccYk8F1vfqQz7LE8mGKhHfkgsgwrUyHhBBdQO9F0QmHPB9MQU/YoUL/aNBXi5wPbup2Oa7DLrnACEWxzoLQ9QcTySOhYFZXvgQXcG8zE6q7xukivOOz8H44YT7rJJikywt0kwt1viT6vxy5oDz83yTouI78Z9Ux4EDbiWewhiI0fXSWVKSd+nUSdo2ZnBazv9m/rI9l1cH06KAswFolWytH4qZgmUJoE+lawZcgBlmXclXECDeU123a198j4H7Sq6GWUOTmj6tmqPJxGlopoSbbSo04Ci+jsTiUrROSNhs29ox7p2O98gnnrWh0S6UopfF8fRVZG6/o0nMEt8YpJH0iYKH3oXtdURpgo+zZI0pOnsWBZ5ha+gCftYn2KLHKSbUFQMC49QBm31FifBBwFENHeL0iTllYE5hRs57GbQ0LCI/z+gc5v+qZGBUY9HHYBU100FmUDfBVpn2QrLNamEbNhNWA+ynkyYvoLkZw1HdlmJ0dBB4ZhdmB/+DXVx3/Te3NZymCwMGM4MACcAvRGom6bwE2eKhIqHYVOtV2TgmoQDYw3qHl2HwrD+tM2+1ULm12r5nr4QjRzihyLnP4/edfJtsQWxdvD9YyfJxv/OeGDXhlF0x59Xv+UVvZm9XWFedVoyfQH2I0ztSxo20r1ZKcNmYXJC6PmIRwpNZp9S6lYVLsiUe5jR7JE35OFk1Ozsgojavt1k1ER7IohaZnd7lG8tmreZuYf2C43UlDQOfKx3WICBfv2VmUMjfcmdMTRyJOZ+KZGQ1eolpSWsOZ4qVm/qTnxP/6pP528flWdyglLkU5m6vnxPWUUFAptK2lE3ulEYfoiUlKlzR2TZ4EbuZDYDZwBYRfpZzvraIWXfTgZGt9t5YGE4435gov8/AwAC69pNBjLaXTJwe7sSckCDL15JSOvAiswKkb8HZr4YSLFd4EOchsPx6SL4efP+zAj6uIh2tqyebeyKLeqWraPrvGNyalt0n0tqRy99JfD5NOIPi4QCuTSTZyCZN0z+k9JewzvYJKhG7Kvkb+C/VPzjt3To9L7d5CPHfeXJembyomMU6pqBrBpcPgBncB8GdHkXgBPdZwEt7v4AnFtN0Hgz+wBM4RpYtPUuANO+Bhal2K0/DeT3zp9CPzGBb5MOCQhmi0oUuC4oHJzeUqkCV1gI22uNUzTGm2htZcG/r5QHAIYtTE5JBObnIiy/e4LVSVwaKCltZzKRuLu3rqBNp/eIkDZylGZ5iKMqoI01UReLUOSCj7DIgoEucKMXV4qKb6PKqT8HAj1Djqx/H3a5Fs8Gi2FZ+QVnERFZbSKHHHUN4TdjKApEeG9djAnBN8VfZPXMWsKxZZFvEb/SfJZOfvylx66TqaA2UjxdEG3TyEsSoUQtvZGkAxmzSov9x5toHtyz8+LXAiW68vpsbSnysrUogBb735H6ym8QdV5goZgU/qlQSMj3zjAIVzuFlfZP67IzcKUqA9hWiySaQiksO6PW6oZFO+vkQXcTKJX+asdnsYO7k2364jUgyVxH4jyuT3jl4jOFaOd4PCYixU28cAzA9kxmxEccZ5W+vgP7GIguiEjJc8x5CBsyX2gGQXvtHjQN7C3qAzjYxrKe0y+8RXAt7c4qEQixhKmPGUrUVqHR1/z8iMlni/EVOA29I+fINkuIQEDH59HwqBSfmitPhR/PM0RfBOLM/nyc0Nog1BON5D3QWzrGkMLaEbEkwqTR+V8f3y5gv+n0zn5M850OGBtfAApiQVsVfwwXEJVCH4WQTAl/5dvKHUF8UwJeSWeMRFdgUTnArtnOOdusnXNyWne2c153bnJid8ad2TK4GVI/a0jjrGKyxNhJQC/g6u+U5vLvFLv+O8c+gM7ufQGdYZ+ANyA0BBLy/OULODoFRJg6VoJwIUpx1Q5ZlDeqYRIVFgcTza1wmBQ7Iff+Oo6b7nq0qyjgQSqJSbUwnrDfOQaHtLm1/1GHd/PueSO0kCCUiSxb2Meps4Bad7mIfw39a1lJi0VlI765sx+ESHyMMyLHtuOD0QTK2yLayTMT3spDbUne9K0rp5iUA6XTrEpMk0tzs16wkk8oZzMhe8OHHoWA0sJIJsVXdjWnatsyay3IZRzCeqwY671Eza1dvLGVDCRJOfQDe0TMcB+sHoNJQemqQa2jjXaNyVlbGbtDQ4rfXSh8VfcN6N4xFR1rcp5Z4Jn9OCXcM9NGjSWbZIrBesmF1/iN86BGWmtvuQKJcpVGyYqbTdqAscRuR7cAD1d0p9z5TtnBGAYDRwqt+9ySNJvONDrn2TsDj3pWzmhQWN9R2oF27vxz1ZstYWeyUfI8qFMm5r4MDo+Ctsr+87qX0hum3GVWMnQlG4XCKSnql5PcV/e1RK0sW6K3/viVL6QqwJZkrPRasrNa1YLJxCg+GZMCM0dGRTYrUwDWo88FEaDCcG70apOyr8mXjNXqk7Fa3i6NKI7DKxNmJAwVrMlqh+XWSFHUOrAlVO+1ZGKWliI9qia9ymoJ2UHZqqmWJNZPLdFzQEZDk2Q45f4dufuyS8o1FRlzScWW+ZMeT7YpV1TIuaDiCIr7ur3KycRbtD+jTZyQbYnxmJKzKZThW4vzhdl9lTFufS6uqRIakE5ZNJACeJEQBS5xGgvljbLLN12Dk46bL0dx8TVwgfyy8XfXztmllhRfw7TpInvu/If6SrqmIuEr9krZsr8Ejc0Ts7hEvkwtsUEfGUterwtS5J98OfW5N1wzR8RbUgdCYq9GpuZvp5gHNEM5lZAFJCgJXbElXuiGByUFsMUl/yzkL4nILR4EgzmP4SVD9vyBVOu+ppTAacGj+v65MAWLr55QTV9kMTCfw+GiTCPM25vmGY/4E9+yD9T4hx4XX8pG/iT80Mx8Svng1YFTYKHgtXYqFz4CoTLA647tVU4I7tyfqyMsZX3XHfbFqSVtvZbbn9Hy/ORLoKNYofGbgo28BLeJapnGfgPig6vMrYu9okWpg2IzOyG3fiXpFeW834Q9yuNjJRF0nRjE0fZ7vv05MmviuhRP1dQP13cpQY3Ikf2AJU6UujIlOM5LzEXAi7QYN+iv1OL4Jgwau3Tresb39peHUu+2w591fvm9jY/Ivs5d2VHqqf694D4e9Hb1JnH3/Sx7XOag75knrm9oEFkEfZOChrCJy6RxVY+mUo/OKE6M34npq4GyF8enXlZf1ZBQSj4p8X1PA7hdkMREmnEgCa4iE8CU/Bp4oVCI5sKRaYp+tlQKweAJoJHwJpU7fHwOEQmhk/ntgyLZIGJB6ASXF5aWA6pT76qitdCeKT2QTYcFbffZ1s/7pqnywq3rWziqIKyvGnWIqlexPNQ1nJ+UP3vNTEIzjQksk/Lvy7DvKzGlLMBK/bC2AFjt2Ce+g0kg8gXdVfVW2wk7bstlfOjQAniWAA5wENiA6eLHcmubmEzvObFM+m6z77tB2qlNNcF/EKZWYU4Ty5gjOB0uBgt0GiGcofPoxOJgI0rc4oZRvCWB88saKH8wK6IFCRf4WgmuKMa9kg85JXjvEFKptgC+bQC2ADkDIISw06Li6lgbBlzSOcTlSitaDvhmAdyg0eFisQYARUSlXyPXgqGZdImceg/s3rWzr6sweDPYfqBVDKbaAvh6ACJtg0lTqSZk3mJbZmQmr1qDjAD2hwMGW7fRK77mUitexpHlc1msfthDomF11HS+hC7iq4IvNJhUmg+ONqc8l5R0QmPL89cKWUdTS3zxP8T6bgBB/DPok2JZOob4BOVxrENbnShM98RMysmfaXwqnbBlKYEO54w9X4wABB1OY8eOc3zWgkCodEEh5HqSqJ+aWLVmE//JKkBVrlqdjiJD+Wp9ukD451E7eM/As1ZCpOO7NaSZ13mh8fqGkFptLBwQ5uZ/4mXwf+K7Z8hvL8UmOHxZ0xWokU6fXq0BbuFfC/Lcxv2btgYYUW/YWLekvdmoKxN6qXV8qmEZdfj9d+CAzJudUy91O1bu4og01lJkTOTFHFHRO9frAEkHTzydVJwAQFDCC5wh2TOK6+enMTnXwVNK5RvCOWAFB5I94RgXL4ALTyk1CHLVgmKpIH301fWB8ibto2hKqRhhxQbECESYwtmTffMwaPV5lDDippaKi6GcQVjSBboYG0AODD2g5xXgTQWzKvPV/4IUDNQtRxdMrVYCNU3lT7ZZT3nzCBBAYK8F8DEFjD3RHvLw3sIdSE0GBuhXAELBWbdzUzbxq1A+aYWnYEt7PIxyZgF61g81yJa18fRK+hEl8ifpxh+Piz/xC5QFTuGaOZJsaXYINUAved54PjbeFwUHS5w8kc28cYfGno4OJizliCkGweF0sazgAkhMF/MPxIfj6tWUe+Ve4CTZW2Azf+zx2dM5o8ufVzqdYIoJazr/+HB8sFhuUAJCZw7nm388giN/2eLT4QIzfDocTofzD0ekw8VwASqIMQUxBZ+gEsJMUTv36ivJg5fgcdKsCT6/7IFI7IlGfM7ZE0JF1ndZeh1c50uDytl1k5Gj+UagknbzWfiVteODp9prGD3Fgtek4I65leMugso978cunBIfI8221n9WdL51XyAVAoOdDcc23YDZPt2muhvoS+NhdIbUuylyusTq9HIafR4dP/1zwFurCzmnm6r14eC5Z5cyFG3Icp8oOmLk9xGiQ7ePyOWRv+CFxXxKHhWR9JXwYAj7aqzQy2HtFX4CAKDzUwop3Kj9nAr+BK8I6QgKQipCA4GIAB9BB09owkQtPHUtCgy3wfSvtCzG6sABoxRV4mtaLOZW1Nyhj+Xady2aLyn/yRJcP86JBX2JRXWvHh5fH0N0QTujs5anK1eD9TgfRhJQi3zDL8/hC/kPvW/l0yvzFWOuT7dGZWE4gdFVMT1mTkbBjApPlBihJORJxsYKbxSo6b8r2Ow9WrA3aoEFmxxLGinRqEjEp+FR0ClQN39bcNyzsT3m73wUWguBiACg+/yVXFrBKv9tCbcXUq5bz8Dppkjpq75IvmROd0fGWVSgyQXYJlmjUdOIYIfAQnCCHm64d9LUPqk6KO1NlLGPsiaBGjNqkikJxKGnpx6dEHNlRT7MBRZL1psDk4eR2gN+RXt4M6hZye2qt1iP3xyAkHb6qv2eABhSnUVPIfAUM0JHPAIAFsrs8V0BTIRzxLwph/SN1g9OfWku8e3rCXY36mYvCj41ooH7Y57cpc0s10f4Oc2+Fox36Xv2+QVnCiQEv17N4zMZZAhE/Z2259iqT2baI2Y86YwnA5225+mCdNl5YZKJpQNe8P2HzwAAL1Yz46XcICq45KiUaLaHEzNHIPyZX5f0fY21m899lfmKUfwwUbdx8cGO0E3mvTfUPUOIkNO9FDKA0ViJSQCz4h5bhvuCY2foju96LsPldrCrolih55QtV4rMRHaruo43hCnaOeKBljBczeXNkUm4E7CsEIgnWTyJHry2askAXIS+mt0TV/xV0QAA3W6/ay9u9c1uGkW+QTRnPMqcZXmIyAVr+mn7Ka8ERWFD/moxtAiEQoBTP4OmsArmMYz1Dmmyrt2cwUc0XF2mzHWHC8EeB12GF6FpolsFosagKaJ7Kz2/GlVi3QJxYC+R9Wslt/w6S03FSVwT7eXXXUpy9k0sEZAwcQZXhNsDTWX0SRffyIprm1dJhFynuhD2ObfW3jn50W86OT0J/r4XmCHpKqLHyQLjhhIcnVySdhY7Xv75xrapwWY/MFfwPTn1wjSgsSxdUgmDk7C9WAeMI8kjil2onrJLbrrkSXrasCGQ8p422/I3YfAiXoqnYd6LptEZDxLPS808G7YlzW3RG9ETZ50DN7Z7uevubJaamvpOn0qjdovkBBN3hkq8pcTk+Gv4L82LZQ6aETE7bBQJEB1takIqYVyKUPYZpkT/pbNOZ19smJMNSmTURiiK77wKlZvYu8LmXmQFWP7zwaDaHbgNzBdgNBa+vHgA4TtnwO9I5N2RXI7etwscg7GFisbJi5v6o+68k5pPCiuvaIPwvkjbzOn1smMR7lzRyUKHhGFpzmdRTfOTpKiTOng3ehoHW/5UFM2LkgUg2wgnbcjAmsh+y0zQJj03oA8HJVNColAPYW9cVszdrRntOO2c5OBNqqitHOD1ZP0TiiX+noPLDLTMsx+7FtpmpgUFUsK6clkVK5bnQTn0Dv1WRcoj5qmhf4DN6jPP0xBt/Kk2X5KxA7NmWjs+MBe/zQNFbF+2jvwy0QdG5m6jmaIAHigFhb5LobPU1/My/2TeurS61yasvwNNbVkdM8AgMPSx4oL0yRm1DPqYaWP63AR9vGtb+myCPnW3eX0OQV96Wre+GYK+EK1p3xzJm08RJniX4vz88O5aiH5EegRIWr1q7VMNjO4zY8TcR51Wb8Qp2sQwKeNCUcCG4X1Am0kK0Tfqpw5vLMnjBpLS7ZRUhu7wds3dlAu2/vlaiS6Q/s06h11CjxfxcaoUKzCcx45U9M900Flq4HaXoAEArBWC8LFJcl1vnB1BVAxuZnq9EbNEZ97cDDQ71cG+pUPMXnXtbE1DyZ3rkt0yPYWECgcR1x/UAEKmjYFkAgh3bQukI4DY3eZBLgLIPa0bNEUAmWhNoQH1On103C3+/K2r3vy17GFlcQub/XBW/focHAPICc6nUOAtQ3c/c2JLbrAERGZM0Lpy5F5igG4U8Nm8JoFojvsJL5M/y/zJAHjAg30e2srcWH5yx7VFylr1i2/ZzhZZkrIYSUIDZXLX2ofdKejVbE8P4SFaX9/O4HZ1/5+JuqXnUwfAtqGpuWHvC5xKQ0eqsoJAsLsJ5iBBYXlCAABvQdDJPcQYEAE6/9QOxDm1HaptpH1tL3YO6dAW+UAo1ji6WQ7UFbV/zRmoMWnr20fCpvF1ydcO72AMXxTviK93PFn74/M6cGg8L/4SUpNwwwPRWhMu4PzSBYGIvWfrCpnu+n43ONzQ3Zk/fJxmIOd9zufJ6nSP42x+nd7qB5jucv+YfcTQ3eHW2gCAuvGwtluFwQ2NkS/Ma2h+IvCbm8DcRuNyNZM9JfrMp/dmxbB/MPpW/vz0ri5dSwg03CgdFRnOih9cfEaCwD2nghM13EJ79R6hw220qMI4jTskJhIFOD6fLOn4CFxLB6rZBCJOikDM14zAhHtkDEHA73ediZn8qdYFg0kQ4veVe19nci5/dxNv9XfesugnyIdnOfOolbWxdO+x8K1Vh8mlxMtx05pL1G4i/gr+QYsdFK67TfrGLgV42nwEXlFA9qYaxEUB7WxqQTYU0N2mPOSWHqb8u92V6GFQv9ceTMFqXm4COKQ+yKsinh6LwZ/fAazWf6039dGtZH7/MZKprOkc4TOTLuBLVfOmjzX1OmDHkiQ/OfIHQN0bgVLX+JCYnHC/XhKS89DfbylLpxaALXq63RR6Hdaro05eyxyGixAO65PR7mY9V0iC3Lq3+x/10KBo9f65U0d+L020uPWOAMCdZaK9f9zrNROd+W3UJ4r16UbfnQqvELGaJe3VUPbXoL435ou+fzNxmkn96ZH3j6aQDix1jykaDGOGvv77oexh4UAmz9433Levmf0wG8+yc6l+DfW6db9XyeWvUveUTUiElu5dbconDnSvsKUKocJjqNTjN758m/v0EXl8NLp4fXpIEAHEFMfGE7oDWrlkQZ/Po2J1VRArAoi/nWy42Rbc8Y4AYEqLTvX3eoct7H7EEQV4rpTn0+DYhyu9ubVjWDPvhLU93kHs9bVwewDDhEv3POHt7LGDRL1L0ACARGKYBOcEJ1mFAcHdW6wN66vDMP3M9kxypRPQQ2XF95PTbu1g7aAt3TVPpRVEdmvJtLx081zfBkemU3w0Uyg7mi4hTVzCFr/uzbuyorQR+sOJaNI07YfeeCT+kO2QLDmbIkdBEaZZpTRxoZ2VJSZ8ixPahjMTfYjn1Bi4QxzlmOtyJo7SQ0nOqP2mKz8K6wO0v+3Pr9NmPctarUhmuybxustm3pwRt4U3XZ23xYB1Z4R598GfZWqGGhJXuTMCJ81CrgIuYGVuQH+t+y6oquVLm7wRNB5Kfw1Vg79mfCcKSFEWhPkO/nnQUa02yaStZCVle9twrJ0Qn4Dhxto9COnri5l3buRlSuCV5bDJScQkAbjcNSmWWj3oYJk0yZQvJT2/YoagJNO8d/cqfIpqvRSPdPTw/q0DPyDbIx0/oj8ryM9Ds/3se5JEONLqIfNfN39k/Sck41nltNPfT0eoWWoPvei5O1J3JG98l5d9XQGUrR9v8skdAU7/eDAwfzoVp5zDWL2qlHR4aw0o8xu4LBIWahVb3xrdY3U/rMBWW4UtkX/t2SJneC67unXOuL+WoV1QW2HXVnhQhqqJjdg0x5CoNpEtDZYzkGCh3XN2HcRyloIBAGyjZyaQbK+kpmKBskLNjj9sMKQJt9Nfk5iD6/O2BpoLa9i3hZhb1u5sB5recV6G2WOcbhayR3AGVuZ84Jasy52B7bR5rhq+5EIHY66O0WTgohNr0IytX6Pzn82lO5Pj4DZsqvvqF8pX1zgFiy92MTHTzFutXSjP6x5yRUiLdglda9JV3UKRebjnO3O8mtGEpg/3+tEWO3VSNBow98QxxFRb6m20rTF2V87GETJu/3C7EHanrSdKhGFw6Drh8Lpt5O4VoHiq6lPWdtQeZNdK5Fq7t2Ta/Onm3XzLZJhmXUetz7pM473r3/Ngxg6mfyDu6tqBuzn/46ZaAFIxCGd9OcrrmQYTWPdQ6dPvOO9Q0t6ah/IO7L8LxFEuvNyh4ui4VjpUqozjPGlAi/csEW1L4/ItJQ2VKu2Mg8B8bHLA9tT+XQ5Yu4vapWamWn/HXTGuEHKBdyV0gx7Y/UkDu+2QsKaBE1obNge4UevCHgK3afPYa77EvisIsP0oeZ21jY99atCOjxomXbp0CP+OIWojqOah3Fc7Ptw/Z3ucENRt/oTu7V+vrfvwL12zwA83rNQMBY2qkXr/G3dWIWGVfxfTxztWnIgF3Qx0hVxWDgrycMt53Ic8bV9QpwxBN51OGAAJdzqUMDFzgus1jJCss4fjQBjzMsTCEmx1+J/glnge3v0i/ZfWfw4TOuUAQxzSbfWEESzdc7GSf3e/tP7kMmE8lx2Wl1djmpDsuaxofeylk6uRUn3P1RV5tNF2FWgLuwcrvA3FcqgXDhDeeYIVIwH0q+sBcAQQNh+zntA1UIklhWbD7yHBWap9aHcHnhhGrEhHADAHFh6fG2SEI2Depj46r1hfr1+DC9+b5DUeRxlWorgfhYRAMTaueIhzxT0/o6CzeikYAHAO09k6zM1ce5VbOtGX6elmfqFunYzSZhGXeP2rvM5fp0VfMhH8iM/q++1T7zMjvNLGq77GtxUk5DTfShc7jXcuFq6k43LugpTtTrRgek3BNL21eW56lasMjDrLYDU3SbC9jPVqgJY4HGSATI2eZLxRHbt76J1qdswjQLGsioHIpQDFrGJh3KvDTkap6ncWW5yMUvOqdmYgRz8fz2wcR7ggYxe/Mf8ezLRz5+feSh19zQ78H1WkPNGOi6anWzbV9/zsswMAk1/Q/VF98LP7ICi2MyMGYfjyXAhXD6sz6vCuonwvt542Mj555mIAAMChF1qextCbMMFWgUSZzEe8Rfl8ggcp2D2LwQAAtBRQO8uqF+1sWr0zizuC3k5tXhPILbh+HSVoS67dAQIq5C6RIMNwQSwKMts2xq4d2cJ1mBrbYpPrMFPugu3u/kzaGVfH40XaSyfWs8XIu7wHu/IWsyVMufQn27tMau6ga1x301FEXmuXIwQAxw10rHIPz16kU2L9m4XS43t+FHCiNbi5tmKRgbbA9njZDVzi6B4ciK5t/7hoiNNs61UswkRfkbzRjkI6qg6T6MnT0woyu9LDg+E04AAAo1L/lBYm1eFtXpcwhQVRMKu36Z/L0e6S8NcLzQCAHbxFVOf2qLdiZIvlbZPOPxcWvFYdelcBR9XHNIC3+x1pAqzc6qcoJNXHR1LHgFptk2FAt3aZRtKY3+kgU4v3PT4YH5zcB2nkYFbzITgYih0dyWBcLPhsSKW+xwgmdCR40FllwEcX+NJyK6u/Ny4Pq3uUDxmwakvVBZUl0ar0jg1OPT748z/OHsb/N/QQW9nIqaS3xGeLozO2Yyn+Ox4zRMoVSJtBkrPcc41GIJFzgg0JpPWYdqUkl/Dk6MYxkbRJ0R49xencyZ+rwXV7A2EPl5nuLHAKByZQnnzpVkSyLpUMC0mLF52VOIkbmrJGjkDz7L1zUEh1VSRcHkOHXeXRrfZg8Kqu/FXXmgdU9+F5BFDfAGg8oRRQiSWFvsZNz7EX3MH5QnUv0RfGkhhx4yYBwA648h99YCxDF+aPC+EPPYOfz7YgOd5X0PveM+rnVYeeYebN0cFxLgYo0g1OKQwAOGhLxAazAn7dt/Vi8HdjwvO58/2vN28eex/g8+Ojzpg247mlzEXvHnkO6L1a8EQ7mfp8u5/bWN0WlsEAgI39HLsAKop0yqZxASEmnDHa2W0gvVbnDSTEqcfGHDMkZFK1s3iyid4ZXRAUAPWp2hjUFdQ3aFvQCNS3dhfQPCT66OqAGiRQ5y6DOcKBipTffBT4V5EN8S5pI0F7K92zQnQrUZwLAACcQMfuCAUwxwRFAmky5mwAzjB0xaAaDWEAgGuB6dJXy3HhN4tWbBccuAUPWpzq88QDSdSwuxugUbdjErpyuS4HNpTVcZApjmzAm8g1tDJT1zcCMSfrMk0o53EXprXK6ZjtDN0tnOX0No8dDiMJiZwlbBZib0wpsucGBtOlUcUMkHY8pLbtZ85Ff0GLW/5oYkm7Pl3J69NPs3ToB6fyNeec9ryRFkyjVxU/1ESapHn/HPpfIC3o6n9ga0B8t9HjaA9if1aBk/pt4n+TiT735J/uB3VtBZPBIkgcUvRt0pdw6AhxfiTbW7rS6i0Fccd6MLiqtSpbzKHBdWEVpsteyZ60f949yLPd1qduuSEK6fUajgI732mg7x6Rp2bP0XQOkKoGHAAg1WDQ+gULBjAKcXgas9qGGoCZze6MgYOGF5oBADS+XdmTpX9ZZ8zdYMOdsu6PDaT7tgadK8jorY1RBeDgbuQUNALs/qQlV4WRuG8Oc0NX2hojAt3VtphVkLvlLpjNTZoAO7LR7wUGJnmwLdDBXcYrNlgHnSB2E2KjLytsEcnWsp6eAjtzQe09gimCqhiCtU5lH5p5rUk+7voUhTcSAACmfN3EglP5WnlOf27UCaZ0UsUcJ2xFwWDKc8rFcC3HRzHQ67vA9PmIDZJumwMbnsrj0q1kxpdKJ4bs7Uusd8EMVYbh4AeBcP2f1BeHe7wGrdFkwRHt/Qx55GI5gxWbgWpnOx/NFqHnzk+1WF51H55HAHUGAMcKsjtgicWFdsHqgYvOLvrqAhXcYFQIPP99BACpoF3nP86CkwxzmD/qgrRs07u/vQ323ixbI/agZ9BkHWPhszOz3saCo5WDCphmCX3yYwMFR3umwTg3yf5t+GKKnbBsVgwbwAunu6/dLAk6eI2PfesKE3IlhU6A6alZGhR4mEJn2spewVO9EtdXbbp+gK4Z+3EXxK0rn2diuop4UpXBlfOT7Mm/h6Cq0fCpGuuCMNbAF7p/jYPNjVNqtzTO9tehdaLuTGqKWI/mxerjx3dlUfrb5k8odZ1dOCA31SR72qON0BuV4sZAXYnwU4lz9CbIK8JUKrKxzJD+YO7Oky2gbI0QVFciRHRbGSAg2tYFLCboQMbADgNOGTuGA3AZMyzCwdv87k1rgz9fVet7FU8S37rZz0jeHI13tRAAADiCauidCSjYENwrDie6eznGPAIgwzy3Ik4l4u+cDwYArJHeLoO/ZsFXM9MXCsX2ksMtMR6I0nKmQs/QV1ex+/DEyp00dHCZL6fjXiinUkYIFPIPNA1amWFD07Z1GQqaznCGoV3lmDsOqzyj1gvshC+x9kJUtSvFNERh640iMJCmOSAAyBpMkR9uGtracfuXbjBpy3JaUBlrMTbobns8d6AspjsSlGq2fyGCDHptvWnCvR+8hVdHMfZe4B/tXTon74qzugFIVLmic3EAANPLWhhy6W39XtL1Kk7XkgFdwRCzThHvaGbvgMQ2mQEAYoHB/g7Gl+D9uTjpH85JOXCH0iWXx3YEFZ0YPCv/rkHMVGspCbhJJq93UxmzBuS+K4UHptfubw2IJiNREcTE2mgaZK11cQ1IFGNwHwNj2dFgGFjiwaMDlr7HpDTIbhYPoggKubBEAXNb6rnxXRTZi0SnUHGq6qIOZjB9TR8BwGWBHRuP3d2sEKfuYjkNJiTjBSYNpHlXi5IJMMvLZWoJ3F07FVYBW26NtmuA1bX3225gDrUVVzd8jD6GKqe/rwqbW/B0BaH6A/X5+EICqPQAZE/IC9RiSaOn6fdQ4CJWFGgHo1SMqOhHALAEVzePfb1wB+OrgtQR8jmSTztL6bmcWLsArN9kc/XJY/fymgogbeUQAcMxz8eHnEnBGSwGAwDmfDqppmw9FWflwCmGc1X0volr9L5s5epn8vDVXuXB7Wm1jhZvVbGz5oM7/7t41favd++//fife+PD3MryGqE8eqfrGCrC1vDB7aZ/Jj9PVR/kUeB2m8EAgJRUAHv1BZwFvDTisim1C8yoPm+X4DZq2M8WlqjduRnQFAvJHOgbHTN6omAI7TLbDu+ESIwBc0iswXZYhcRmeSwLJG8Y8JXWufUDI4SzT0KlhiRtLyp+0u0OgVAdPDHMSMk4Q9tKq2OnGdr2uYJ2wIa93fI3DnPv6nAqeikTPYcfLgoDAIb0jrULqgA4l+I0rJTSalOfFzZoqCJsKjkXzc4FS7U7A1/8jPmyBi0YIQNxUlZm5phMVFqXZYMxGMOK4KacnS03uBOHdmuIJKcuHB6x6+9g/D+JsaX5lBZm/39/j/8BVLxy5pQarOp6I7QZFKo5IACAF+yJgSgmmpY0t2GFC5O2vOonjfFUSzB+8x6dl2D0ridY/z1EBbpiPJESKuiKNp4zHpeJV1HaBb6qAHTmZ6n4siYOSKIZD8NOmtL85JCj6wOtrwr2ybvCwo5Ar5pOAIDeYV/7mU784ZCoHIV+GR/CRFAPL9QOkByvHi0ghWdbBWq7yQwA8BKc7Zq2awCd4mMsAXTX/rkIcq8O3WNAdbUxvgEc3o3GDW2l7f7CeVOm7zgk3l1x0tbmHHAu1uXOwNa6C6kaZKrjGgVtZIpwggMOGOKuExMM5m64Kva/S+2MIbeM2f/f7xOhDQ/hwMsKWoSAas4DIeP62yK48qKaWhA5E0E3ypPl7xxgd6EAAGAO5GTzF3oa4lWVIJureE1ZSKJ9gdE10jjWongKGO9lJOVl/K7j/0W2bPvn+3Drf/Zg87cglrtXhSH+2u/j0eUE7tWHMJcWaev2ACFeKY0v4G8qGK5IOHMcvGEE309e79B28qscVtOAbHFUaAOitQzRWqgzcreZh7mtc89zi6zkIcitFNX5YABAHCa1VsHVm7mfqbPScKjh5fSCJH6tof9L+vv6uPWpryoJez6948M7VDedwe7TOwHYhCk4RqbQefQ028JPLQoDANJshCnrC6QDEhlxk46XAWtX6F3y8EFvrx6bRWbI/jU5A8tPcj0p92AAXOiEgF35XByxkDaGPYFYaetC9OB0RKwhYyAwVztJYvvdSNHjYmFPSMd/1inf0e94n36o999UHX7hvMxf+DFpaAZJ3DixlIcp9LeMkGwUlMDanPg3KPO7yidJvXHRM51hTgHm9AInwyWcx+nMtBcqprbQmQJxFAy6LLhGeoPfhZO3f3drbiY7O0+F6cwFJCihz3gfqmBuzgkDAManVVXL1tXYpdNM9sAMYNaEc5WLtbH2WZ03Ja1vath3ho1Nj5U2c1LV4B8WnIWoF+VQRBDGQbpSlMZe4NcU9Pwkb6gkkW/4w626ZtNJwsEQdJ2MuILsWTAF+mmyLvkD+FT+CcF6KjzIcWIF5ilc6IJsyy2DtpA2ZtGEttJty8KAtobuwiJCLrYdoNWgy7Wfs07s6sR67kNHNlTFkhFVIa+nUsRxKatAcw2McVFk5JJyeDqwp7p/rgAy8tsj+Dacpol4U+wY6DLrnxx0Pb68nYJ8ncLtWIvG1B0GdtEiNxu4Ga4L5IueC4oTC5idcW0bZsYWTy0ryP5e2hp2cR5588OvEuHeENRY/wd+gaeeWYu7vt+IW9mpx3H7/vE7nuFhh6dJ+hk2kGmcJwG+Yk+Lvxl6ssISfPkkku8QOKj9bMCC7cFvaZVAmUU44kCP7Tdfq9qV891AIPcirduHo/6FQM3C2UuI4Qe31FqOBmirjr3x0zsV+kUTqjOZFwuDbuIKErqcOddRgcA6615enHLHxd9maKDSF+uQPaWw02DtBsA17AAAIOxl9IuZQF9ANG5hrBOGxau3Ds9laKfwrYVmAEDEYKWKtjEI0hybAQVV/k1ABbXo0dJb2PNMkRdq8FUIc1daCFT4O4pxSx8/pYAf4JsBfOwui/DSrWrz4QlTBfEuVG+mVeWU7jNJwikAyk/rmxAKeqxL1NmGIQZwGCLsNhDndxRmvD/xE9jxX0Em4e73sSWhh7P/UEamG5x4W2wVR7nLnBdCOY4OkEOCxoXFAzAs1rNuYJuXVRYH2Bo3o4sgxzUGvOEiSxYAgK4x+f3x3g1u4To23FBX5jLZFCCOdYlRsSBvuwsldYCCrctVvNUSqzKuu+huF3KJtkUBkcvY2ieDPHbXY6TNDx+1z2YeTbjH/MG3u/tP3t5A/wy4kmwmZlNnR2+6fL7RrqjgVRaDAQAHFWxtaf0arm1WDEsK+X08a/PeNZbeF5+plr2+qoPbC3VOiNj21DhtJ3xTgatiR1OHtQK8YYNSXQBn85waBY0UJGsxGADAU4HwKgwG4Zvav9S7h5W2GH/Wx6FtviD4bl9sWIfRqM0p3N+B4TXUzU8Tvn9uHpmlQtxcqqJUtOIL5K16mGwnjg2HwpsiPhLsuo/p1Gmy5zIOKmiKih501YqKtFY9Zks2r674l5Mza8zV7P863Tf9qtocqqPvE6lvjPrvCS1CMmE85aWQGrogSERZGWnwxbZFrsMXGYOMKVxaynMOkIZspgcpn3msxvlWVvKtohruZL0wb4X8xZvQnmjBHQnbn27dMz0hEymQuGkAAEgWuJLWucyEOwpcDxe8bQQ65z4DAv3L8HOVd6+0qapgMxgAoDoVj11e10Hum0khZx63RBlVYu9UoXc9FWP4V/rqwNxExZVhNBwmZ4xMXmr2uQPtqhZKpcMMCzk5YuzpqLIyZ0DHsXU5BzruMIbzIM93DtDNlfLSdmhvG5CbxYlMRh0qOZYj5Y0h9smmUJVcsr1kdH1xdH1BdH0F0/X9dM02mim1eKOrJJrWiHLGyPaS0vUZdE3+c+J5S7f30zWf0lipRTpdicw5hwyG4EoTp/9qFFmowXUrqi5sIiXctrUgMitgEAtqjckGxMs5boKPauDcUn0a/JfNhvXuDr4Hth6qifu+cVjpsFpX6iP3w9nvMn6kutByExbVhJ/SNdOO1gJeZW7Ipz1W63zQxB3qwdoy9QaEqu1fHYVp/Gri/e6KOHn7adnAtAi3ntbhfA55EzzG5r6tk7c3peumADcvDO4wx//BTx/GbV8WDUzICZdkaFU7CrP6JMwdz94juFSDGQBwDIQWOtqAIWCtRslNnxn72RjpHylrpqZuJwPkxJqzqbCayr+75zVt6F1bMjW7qUSonjXO4tTpGIfMuaAslMgqbJIlP2Bm969s0afumU7bAed16vPQ6SSm8SMlNftvpt+Mmw2nHGGvCborDTRX6dNlr4W9nW1iVBqhGcmkU4A2Gq3amskcNO6zLjO9ch6iMdtdmGFtckZ0mOYE5IzPCZ6LoC0XLYITAySH69ALMfFlhbuGeCLrUadDt5NafUkVYwhKMQ1kR7Cb/NYmobmmBQAAg9HqJrcvITR7xNXIdIMYXChxB3mqLjG+CTQzXYuypekkgxbM5WrNbLSKL7k7CcEVq+4TXaVAcEXxfv1VZIJr7Kpivz64q731t+j/Fxo6l8QIL0AqRH8oQycvx+/ti+LoD5fGF//K4BOdT1Yb8CgTLB5c9sU2rQo9fS9Zv5v0uBAGAKS1WgHVuqarUe6NRjxCD9nr4mDgFzx87jRotXJwk1ITO8lV8B6phnXYS26ttapiQR29G6EPQ7wOgYkwAMBeAjIGjbaqORvgdN6Yw+tAsxWdUlS1ZPAoxBvmXbMYhSy9IR2dHGXcIZnaSWWxi+2kFg1KnaO+r8BbDTTHOuoT5q3GgHmUd57xSvpd47IX3BH6VLs8AABMo+bIMw2h5KDQgxg6JFMtVfJcSzSkn8s7O2XgdJK6JNZxbPf2VNhIrowqR00+TzroSXgd8Ow9j0LFHxkENkjCCHH3c37FPxcyK55oXS4AT2IMF3LnYmkCraLRXlmdKsfGsf7aJNoDp86UOoRHKpFVj9CtMhGNV41v1z/Inrll6QkVUakZbHOlPsi+t8gW2cecWnZ+LXuP9xKXaWc20ZiarTdyKmqGIQ4Npo737xDE9oXNWSS7bS1UBDtljaVFqqtMN96CufIkFnfH/qEKeZWz79wQNuQeUjkaBevufHF3x8nbKxaCFaypYbP3sUqpw3upuIfcR6oMd7uS83UAgOOKihhxJWXDcGXL1sMKctqZjvBq77lmAMCh+HRlW8IKTLYNV3r+X9/993aUoiTOkxT3rkDf3vyf+XuFrwKNetwKyrpbi5mL37uyfI+gu584vL2CPe/n9g+p6/ZK8lvvL3EGM65h3/n1lmjHmG0isu15X9ayVBOu+jMGSQa0yt4MjT/WLyP8nRLDJohSyuqdyXQLbtsN3kKBXbnbsBcUwXUig4O+uJwa787kARZ0EhHv5qIqNOjMg3MoFZH9V8Zg/DBPs/CTuGHgzR/VuAAADLa3/89oo68mV82D8cMcdAYuGgxG4o/DGhMACMt6j7LLU24G1vG294qtNL7OfjOxwkKXmXQVeJVKlN78UIqW05eszbSYwoX3iqAYXTQcCwAU1La2n53dhxUUOnr9O4hC1cNOsw+D3wAYL3TwmZFby4HQKCDI5I42+6Nm1egSFC+FAQA76O4ZhAAT9Gf3tufFyMuWvCbCx9+TPLq9NFjpDvZQvyLUayethS3ExXjkYr+CDltjn14/3tf6LDEPuU4fn5X2XBW3C81zF0yq4vZsDN4xtBZ0z60dAmu9qhaDAQAHh3ZnugtsGKG037Oa3r3Pll+Um9J8FkLXqs9zIUE7JZ1hrVzH3ESFbkDuvmPK9p+Z9uwH3aN7PJsq7vVNr12XGsSZ3Lp8MJNv/FXyVLkgXg3kCdsYXxvy3OoXX850St4uxuDLZMcoU4ADlJ7dZIrLY4PKISiTN6zw7qa+92GMz65grmcc0HEk+/cx+B5Jn4K/N4xmuXFldyOqsWn6kHCt0FcFP9XBzfcT+/kBXXUCnGLACoHI1sX/zqsV63KPoYQG1g3964Dbhv7VEmevBynsEMJs6aIH+A3YOQBjKIwXewqwhifIscrtDAY/vx2l+b0oHJ5DMsSJtRjMVe8PXU/djVB7XIFAzhYMeDSyuV3urD1142583+I32Z2NWc03BJI4Oo3ew1QLpql0kLYoFInsqzpYe/No6WJL4Dn5wZcML+kXj4sOt7LX9Ql5wU7+r0+eDSRPhFs9+kwzH0bC+4Q/pBCV/N9j99bG99MjXrah7FP888CcJRPL5hfHSwJBMXaHLgSlY4N0IzjVaoznicLGGehOWry0qR25IAwAcBzqHb7OglNVikjl5MVzhY6KDK8zL7uBMjNd8DkvInPTuZHbgrBoZ4BVas3fgLW0C8KuDiXagLW3bQy7loB1pH5h53pMxDpdY+cXvM5ujwPEprnO7qFLy+ZA27RDtFRDm6MjtVeBMuxHcppXmih/rS/rLcCctbfx7yMZ15v9SO74SiPnMQEAa8bfNMjlhDct5Rrvgenh+qeDXJqkLpj94kBMsHnaGi9trhsow2krprBQZvO9NzVDoivLjG2I855042Qv6qQGo5Mhh5/5ML3dtLnZge3OzGyH0JQryQo0I7gZxjW+LYQ5bWI52VmIp0k+Fmsz5PMLxRNdcW9QX9qJWIyVee04ez8dcvZGUVGVvkcKMONiZ7PfKgVm1xRcRheGApmY50MVnO7FYADAjApUp76gawCRPM8MvUGNnpbApPWVbtlHOz/R/mwbDbp1IG1Gf58TPI8RcnXELe94+9Qy08Ba1iXV6/hQ8iYuQwrQHxlA4H66IqtX5VibvGGOfThx5zD6y/G3a2GBG7kie5xiOfR6yhlFqJxXonHYV6G/PExfYCdvz6UDXYQ76syf6CFdhsdA9dW/5O0PcpEcBK+0WAEAKAHI6R1yhaEkiIUzSGr1TAM6BRAwz9VrsGQF6akykJ2bZD9B3YJnA0JEpG8MvbBYURHtVuglUAxXw2cQsVxJkYFwfS4Bu3CvEnywDFItJBPx10XMrDpvIz6qaOmFgXLEJ0wGmFVVHqhfDkdWnZysI+WchhO1CRrFpYYEtq/TaYqODxGZ5eqjqZUd7umoAICUu/DDgfPwtM0T27J+eeck+c1z4by4mQ3luluLQfW9RMBL2We4wPOaxnCciCR2ktU8FNj8Er/D/o/SH4be//bMaS23l3LG1IsVvXbULkuH3GzimLOp7o4iiFRRyXgWYAgi1VFKg+lm6J+s7cfOJnpd4D9SHW5RGABQBzTowDdhpnLYEjyPoZfC056d5+5GrnjrSvjmcHgxcZWt3DCg+GSGZM59b1DisTPZymsJIQfrklWuU38nU/qHYCyk1MgTCcO92bNlGD2Ewz/FffCn4E7Y9xMfuroecun6/G5w9+qUsx7/BdRn/2A/gOe49gdftOrTCi8BqAHSb1fOQydWHq5SsmL5ejYbTp5uaGQG1FxuBAYw5SccEFU98jfgGwcWPaqaSnh8TDp6BK7k+eWFeP++s3kQ6PK7sSSwZOMFX1iH5+gSOPi9XH+6b3Y/cBe/Njjxd3h9Lub2VIfg7m/Wkp+fFaehNuqdqY7ORDGO8ewz/p9h5vPT4qo55YurCjzaLX8STLKf3ya4xZamKR30krko8TSYZDFNOu0u7rmLOqZigLFAU5AvYd9lS8pn7Ic+RzyBW5/D3K5n5gsjJ6Lt2NBHfV5KuWVZWr71XOmHmOFbXqFzXlvpmWjWXY6UoLYL+SJh09cnt+Q3hubO8COP6War8uqA+M9XqMh1l2+vFpfL4TU4H7gWB1cBfE7g+UFteZ7vI05o+u3xUsP9UZK3bgCNNCoAAI0D6NY76sWwwgYZaQyKByN1wjQ1oHfxTuXzPe7tCgq3GAwAMFRgKBN+05NcZkfAmOepBTipzpueqSzvJEXPhN9wHt9IQGs3tlLAJ5EEH6A72McDtjmqTJBB2bEBO1WKjpk1YIdWdMvCgB2NYi6sDNhrt25EiT9gb/afYgEQx7Vvp94/l4lQs3y6CpjUYRYL6FszcVtDtcmxChhMZolEADDXAGfpIG4dgHO/+42ekjghnfPv9q0OWvv8q/5UZR8eYx/f3Bvb+L6w7/pON2u7fbO85b0+3MlVn3053tMWO4O5xmTC1TofFrnRPXjqV+QxerGjYvs5jkrsR0f07/RUYf0w5vURO62d6WOAT+g4YLNWNuULi6qrWhCPU+jskS+PeK7S4LlRhzWPfrpIJ9ILzzZo5yfpZcvwbpisaQijY3lrQK64Oq/nkHdP3AUr4aEYG/qyG18xuJYrb+j2zYsdi1sFzZjG586pDdm9b/ZVu28Ca8fKT3aktXL+4rMD4H4jsyPodkZvG7OjPnfMKFeh/TmbB1kgnkauWMd0NbZUxN/JXs5nzij+XXnBF2UTNX/7m3YL63UvByhLwwXhxY7E6cOb7J8rx/4V9POIDU/l+xnxOsT4TbQn6svnbM8VFhiirzobqG7CMllCe++j7cI3F2l9Fnpwe67vKl14wWIFACDG2yl0vCDbVVBV5mBCT8efBwLEyqMvkagiXnxaGABgxJsqw98xPJ0dgTkzzxVnlhvJ2jP0dummQxlAX+Xm2ef5idunR18xMJThcjCJIR0Cbqf687AUB0F1F29XYG9sDGpV4AjbgoYKnMQX0HSLaEPrRhmJjq0BI2ANl+jKA/LuN0k3zNWcDWcUnDBQ+h7AOTO5krUrz+cekJFCPLOL/0THPo/AKTDmixuvK0vq9Ulp3dBwnWkOLa/4R9nkfs4U+aMIo00vYzBL1SeYrb3XoZplSZPq1Mvt2iUSAcDShVxM8UOzkFaK9Q8CpveiHw20NW0tlmkafNyGfV41X7yO/PcUnp3XZ+c1DM43ifNdG/8MbPHaM7ctvH7Bfe58+qy89rq+m+ziscCOY86oWkGDYscthaWA1uVBK5rxV1p9XuVEpti6T79c8Tg7i9Gl/YPz9uvXa4xrQ7a9TcBvPdn3rNsxnjiOveaCMABAc/iioafZem8NEzrTrSm8MECeZ+JARW/YPKvz4gUe8cSeqK0GiQz5/ETRF6Y8InJsl0NmmKSmSUfPzGTmhZOJe7MtW4OchAbDdjJnvzG7bfu2xQH21EJsOTxPXp8nr2ExvnyIdPR26W1/eH5x+D6ensGb1zDs4OA6HwX4qryTBV9CT8HeStOs6KvOZqiL3kwhONHhH+b156T7iGeuqDX6s9CDb73cd5M5wHONCgCAF8CWip1N5zMV2J7S4Pq0qkRnTa1mH8XLjT6SpoF5dvCLXtcnl02dqpxH8t42gwEAvps8UZ92+ka2PkQKETOT9WOHRTjexQxntaCiMg97QDODWT2nPlXwjN+Y1fcVA0N5UfojCuMOSN76sUtoaYQkcZ5DsGRjMJweBbcIz226ZcYtwteaC7MqsHXtG6sALNASsNAEKkiqDCJpMGIJVNt96k6qusBNfp1x5rVkx2sHMvorxoZ/qfU/87VzW1T9Hqi2arYe58Xt4n/WAYCthkgunYswtQKy/iD02p+bEGyVpIofsiQOxfsnBW7rgr8iQaruFF3BbUh3SrUU7SwapCkq//ZDm2P8bd+VPw8n6NvuWj/1sZt6S3d2UOFzb/eMqosIfIhLKXYsxK2UBuOkVa1BZePpFoUBAO4YpoHRVhcsm4VdjefJ6W2KNzo7b6NS9I7T7Znw9o7D1lSeBafbBFm3W5CCM9Ayh2ZhH8yWdrkwmG2D4Qbcon3bPnDLNmLRzKJzqCt5Ps+lYuchzZfhu/7UP+Hl9g2YZmXOe1PfTU4BaSxWAADSzb7uLTXPFd7aGLxG8e7Ka2P60duYUxPgqIYwAGCKfdsWB6xcYPA2Rt4dkd5MZR4xM4ArA7QKq0uxr+YniqC4snpAsQ2CdBewJYTHQbA4DzigBqeqmNkYj/Ex+gWHh1HKDCfiYt/YBnFjC9iDgqriRCmDN7KbvaEhH7bV4/9o8iqpt0UijZeK23fqXPbwbLEu9l5qH4qOLfxsXPvOyZqOi7ptV29mkEylzceyh1rHKduSdPqEVtt98zl85h7vsomK8+M9/w++WIvOoaq8J3yCf7UYvCR8OKm+lE/yGH2CB+m5Dv6JidLoIU/mh/hiOQXtjzhatQ85YkdsD7v/8VPmJEog7ZUKj2jCxvO6LsXNCcLK7+niPQryHDEdafxurmo3xH/8VbK/jwV5rg03y/tvC9T1Rd8JKI2usEZSQgV1ss8+gJtjtpcD","base64")).toString()),n9}var GBe=new Map([[q.makeIdent(null,"fsevents").identHash,HBe],[q.makeIdent(null,"resolve").identHash,jBe],[q.makeIdent(null,"typescript").identHash,qBe]]),YSt={hooks:{registerPackageExtensions:async(t,e)=>{for(let[r,s]of e9)e(q.parseDescriptor(r,!0),s)},getBuiltinPatch:async(t,e)=>{let r="compat/";if(!e.startsWith(r))return;let s=q.parseIdent(e.slice(r.length)),a=GBe.get(s.identHash)?.();return typeof a<"u"?a:null},reduceDependency:async(t,e,r,s)=>typeof GBe.get(t.identHash)>"u"?t:q.makeDescriptor(t,q.makeRange({protocol:"patch:",source:q.stringifyDescriptor(t),selector:`optional!builtin`,params:null}))}},VSt=YSt;var w9={};Vt(w9,{ConstraintsCheckCommand:()=>ew,ConstraintsQueryCommand:()=>XC,ConstraintsSourceCommand:()=>$C,default:()=>IDt});Ve();Ve();IS();var KC=class{constructor(e){this.project=e}createEnvironment(){let e=new VC(["cwd","ident"]),r=new VC(["workspace","type","ident"]),s=new VC(["ident"]),a={manifestUpdates:new Map,reportedErrors:new Map},n=new Map,c=new Map;for(let f of this.project.storedPackages.values()){let p=Array.from(f.peerDependencies.values(),h=>[q.stringifyIdent(h),h.range]);n.set(f.locatorHash,{workspace:null,ident:q.stringifyIdent(f),version:f.version,dependencies:new Map,peerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional!==!0)),optionalPeerDependencies:new Map(p.filter(([h])=>f.peerDependenciesMeta.get(h)?.optional===!0))})}for(let f of this.project.storedPackages.values()){let p=n.get(f.locatorHash);p.dependencies=new Map(Array.from(f.dependencies.values(),h=>{let E=this.project.storedResolutions.get(h.descriptorHash);if(typeof E>"u")throw new Error("Assertion failed: The resolution should have been registered");let C=n.get(E);if(typeof C>"u")throw new Error("Assertion failed: The package should have been registered");return[q.stringifyIdent(h),C]})),p.dependencies.delete(p.ident)}for(let f of this.project.workspaces){let p=q.stringifyIdent(f.anchoredLocator),h=f.manifest.exportTo({}),E=n.get(f.anchoredLocator.locatorHash);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");let C=(R,N,{caller:U=As.getCaller()}={})=>{let W=ES(R),te=je.getMapWithDefault(a.manifestUpdates,f.cwd),ie=je.getMapWithDefault(te,W),Ae=je.getSetWithDefault(ie,N);U!==null&&Ae.add(U)},S=R=>C(R,void 0,{caller:As.getCaller()}),P=R=>{je.getArrayWithDefault(a.reportedErrors,f.cwd).push(R)},I=e.insert({cwd:f.relativeCwd,ident:p,manifest:h,pkg:E,set:C,unset:S,error:P});c.set(f,I);for(let R of Ht.allDependencies)for(let N of f.manifest[R].values()){let U=q.stringifyIdent(N),W=()=>{C([R,U],void 0,{caller:As.getCaller()})},te=Ae=>{C([R,U],Ae,{caller:As.getCaller()})},ie=null;if(R!=="peerDependencies"&&(R!=="dependencies"||!f.manifest.devDependencies.has(N.identHash))){let Ae=f.anchoredPackage.dependencies.get(N.identHash);if(Ae){if(typeof Ae>"u")throw new Error("Assertion failed: The dependency should have been registered");let ce=this.project.storedResolutions.get(Ae.descriptorHash);if(typeof ce>"u")throw new Error("Assertion failed: The resolution should have been registered");let me=n.get(ce);if(typeof me>"u")throw new Error("Assertion failed: The package should have been registered");ie=me}}r.insert({workspace:I,ident:U,range:N.range,type:R,resolution:ie,update:te,delete:W,error:P})}}for(let f of this.project.storedPackages.values()){let p=this.project.tryWorkspaceByLocator(f);if(!p)continue;let h=c.get(p);if(typeof h>"u")throw new Error("Assertion failed: The workspace should have been registered");let E=n.get(f.locatorHash);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");E.workspace=h}return{workspaces:e,dependencies:r,packages:s,result:a}}async process(){let e=this.createEnvironment(),r={Yarn:{workspace:a=>e.workspaces.find(a)[0]??null,workspaces:a=>e.workspaces.find(a),dependency:a=>e.dependencies.find(a)[0]??null,dependencies:a=>e.dependencies.find(a),package:a=>e.packages.find(a)[0]??null,packages:a=>e.packages.find(a)}},s=await this.project.loadUserConfig();return s?.constraints?(await s.constraints(r),e.result):null}};Ve();Ve();Wt();var XC=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.query=ge.String()}static{this.paths=[["constraints","query"]]}static{this.usage=ot.Usage({category:"Constraints-related commands",description:"query the constraints fact database",details:` + This command will output all matches to the given prolog query. + `,examples:[["List all dependencies throughout the workspace","yarn constraints query 'workspace_has_dependency(_, DependencyName, _, _).'"]]})}async execute(){let{Constraints:r}=await Promise.resolve().then(()=>(vS(),BS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a),c=this.query;return c.endsWith(".")||(c=`${c}.`),(await Ot.start({configuration:s,json:this.json,stdout:this.context.stdout},async p=>{for await(let h of n.query(c)){let E=Array.from(Object.entries(h)),C=E.length,S=E.reduce((P,[I])=>Math.max(P,I.length),0);for(let P=0;P(vS(),BS)),s=await ze.find(this.context.cwd,this.context.plugins),{project:a}=await Tt.find(s,this.context.cwd),n=await r.find(a);this.context.stdout.write(this.verbose?n.fullSource:n.source)}};Ve();Ve();Wt();IS();var ew=class extends ut{constructor(){super(...arguments);this.fix=ge.Boolean("--fix",!1,{description:"Attempt to automatically fix unambiguous issues, following a multi-pass process"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["constraints"]]}static{this.usage=ot.Usage({category:"Constraints-related commands",description:"check that the project constraints are met",details:` + This command will run constraints on your project and emit errors for each one that is found but isn't met. If any error is emitted the process will exit with a non-zero exit code. + + If the \`--fix\` flag is used, Yarn will attempt to automatically fix the issues the best it can, following a multi-pass process (with a maximum of 10 iterations). Some ambiguous patterns cannot be autofixed, in which case you'll have to manually specify the right resolution. + + For more information as to how to write constraints, please consult our dedicated page on our website: https://yarnpkg.com/features/constraints. + `,examples:[["Check that all constraints are satisfied","yarn constraints"],["Autofix all unmet constraints","yarn constraints --fix"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd);await s.restoreInstallState();let a=await s.loadUserConfig(),n;if(a?.constraints)n=new KC(s);else{let{Constraints:h}=await Promise.resolve().then(()=>(vS(),BS));n=await h.find(s)}let c,f=!1,p=!1;for(let h=this.fix?10:1;h>0;--h){let E=await n.process();if(!E)break;let{changedWorkspaces:C,remainingErrors:S}=lF(s,E,{fix:this.fix}),P=[];for(let[I,R]of C){let N=I.manifest.indent;I.manifest=new Ht,I.manifest.indent=N,I.manifest.load(R),P.push(I.persistManifest())}if(await Promise.all(P),!(C.size>0&&h>1)){c=ZBe(S,{configuration:r}),f=!1,p=!0;for(let[,I]of S)for(let R of I)R.fixable?f=!0:p=!1}}if(c.children.length===0)return 0;if(f){let h=p?`Those errors can all be fixed by running ${he.pretty(r,"yarn constraints --fix",he.Type.CODE)}`:`Errors prefixed by '\u2699' can be fixed by running ${he.pretty(r,"yarn constraints --fix",he.Type.CODE)}`;await Ot.start({configuration:r,stdout:this.context.stdout,includeNames:!1,includeFooter:!1},async E=>{E.reportInfo(0,h),E.reportSeparator()})}return c.children=je.sortMap(c.children,h=>h.value[1]),ks.emitTree(c,{configuration:r,stdout:this.context.stdout,json:this.json,separators:1}),1}};IS();var EDt={configuration:{enableConstraintsChecks:{description:"If true, constraints will run during installs",type:"BOOLEAN",default:!1},constraintsPath:{description:"The path of the constraints file.",type:"ABSOLUTE_PATH",default:"./constraints.pro"}},commands:[XC,$C,ew],hooks:{async validateProjectAfterInstall(t,{reportError:e}){if(!t.configuration.get("enableConstraintsChecks"))return;let r=await t.loadUserConfig(),s;if(r?.constraints)s=new KC(t);else{let{Constraints:c}=await Promise.resolve().then(()=>(vS(),BS));s=await c.find(t)}let a=await s.process();if(!a)return;let{remainingErrors:n}=lF(t,a);if(n.size!==0)if(t.configuration.isCI)for(let[c,f]of n)for(let p of f)e(84,`${he.pretty(t.configuration,c.anchoredLocator,he.Type.IDENT)}: ${p.text}`);else e(84,`Constraint check failed; run ${he.pretty(t.configuration,"yarn constraints",he.Type.CODE)} for more details`)}}},IDt=EDt;var B9={};Vt(B9,{CreateCommand:()=>tw,DlxCommand:()=>rw,default:()=>wDt});Ve();Wt();var tw=class extends ut{constructor(){super(...arguments);this.pkg=ge.String("-p,--package",{description:"The package to run the provided command from"});this.quiet=ge.Boolean("-q,--quiet",!1,{description:"Only report critical errors instead of printing the full install logs"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[["create"]]}async execute(){let r=[];this.pkg&&r.push("--package",this.pkg),this.quiet&&r.push("--quiet");let s=this.command.replace(/^(@[^@/]+)(@|$)/,"$1/create$2"),a=q.parseDescriptor(s),n=a.name.match(/^create(-|$)/)?a:a.scope?q.makeIdent(a.scope,`create-${a.name}`):q.makeIdent(null,`create-${a.name}`),c=q.stringifyIdent(n);return a.range!=="unknown"&&(c+=`@${a.range}`),this.cli.run(["dlx",...r,c,...this.args])}};Ve();Ve();bt();Wt();var rw=class extends ut{constructor(){super(...arguments);this.packages=ge.Array("-p,--package",{description:"The package(s) to install before running the command"});this.quiet=ge.Boolean("-q,--quiet",!1,{description:"Only report critical errors instead of printing the full install logs"});this.command=ge.String();this.args=ge.Proxy()}static{this.paths=[["dlx"]]}static{this.usage=ot.Usage({description:"run a package in a temporary environment",details:"\n This command will install a package within a temporary environment, and run its binary script if it contains any. The binary will run within the current cwd.\n\n By default Yarn will download the package named `command`, but this can be changed through the use of the `-p,--package` flag which will instruct Yarn to still run the same command but from a different package.\n\n Using `yarn dlx` as a replacement of `yarn add` isn't recommended, as it makes your project non-deterministic (Yarn doesn't keep track of the packages installed through `dlx` - neither their name, nor their version).\n ",examples:[["Use create-react-app to create a new React app","yarn dlx create-react-app ./my-app"],["Install multiple packages for a single command",`yarn dlx -p typescript -p ts-node ts-node --transpile-only -e "console.log('hello!')"`]]})}async execute(){return ze.telemetry=null,await le.mktempPromise(async r=>{let s=K.join(r,`dlx-${process.pid}`);await le.mkdirPromise(s),await le.writeFilePromise(K.join(s,"package.json"),`{} +`),await le.writeFilePromise(K.join(s,"yarn.lock"),"");let a=K.join(s,".yarnrc.yml"),n=await ze.findProjectCwd(this.context.cwd),f={enableGlobalCache:!(await ze.find(this.context.cwd,null,{strict:!1})).get("enableGlobalCache"),enableTelemetry:!1,logFilters:[{code:Vf(68),level:he.LogLevel.Discard}]},p=n!==null?K.join(n,".yarnrc.yml"):null;p!==null&&le.existsSync(p)?(await le.copyFilePromise(p,a),await ze.updateConfiguration(s,N=>{let U=je.toMerged(N,f);return Array.isArray(N.plugins)&&(U.plugins=N.plugins.map(W=>{let te=typeof W=="string"?W:W.path,ie=ue.isAbsolute(te)?te:ue.resolve(ue.fromPortablePath(n),te);return typeof W=="string"?ie:{path:ie,spec:W.spec}})),U})):await le.writeJsonPromise(a,f);let h=this.packages??[this.command],E=q.parseDescriptor(this.command).name,C=await this.cli.run(["add","--fixed","--",...h],{cwd:s,quiet:this.quiet});if(C!==0)return C;this.quiet||this.context.stdout.write(` +`);let S=await ze.find(s,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,s);if(I===null)throw new ar(P.cwd,s);await P.restoreInstallState();let R=await In.getWorkspaceAccessibleBinaries(I);return R.has(E)===!1&&R.size===1&&typeof this.packages>"u"&&(E=Array.from(R)[0][0]),await In.executeWorkspaceAccessibleBinary(I,E,this.args,{packageAccessibleBinaries:R,cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr})})}};var CDt={commands:[tw,rw]},wDt=CDt;var D9={};Vt(D9,{ExecFetcher:()=>DS,ExecResolver:()=>bS,default:()=>SDt,execUtils:()=>AF});Ve();Ve();bt();var fA="exec:";var AF={};Vt(AF,{loadGeneratorFile:()=>SS,makeLocator:()=>S9,makeSpec:()=>Bve,parseSpec:()=>v9});Ve();bt();function v9(t){let{params:e,selector:r}=q.parseRange(t),s=ue.toPortablePath(r);return{parentLocator:e&&typeof e.locator=="string"?q.parseLocator(e.locator):null,path:s}}function Bve({parentLocator:t,path:e,generatorHash:r,protocol:s}){let a=t!==null?{locator:q.stringifyLocator(t)}:{},n=typeof r<"u"?{hash:r}:{};return q.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function S9(t,{parentLocator:e,path:r,generatorHash:s,protocol:a}){return q.makeLocator(t,Bve({parentLocator:e,path:r,generatorHash:s,protocol:a}))}async function SS(t,e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(t,{protocol:e}),n=K.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,n.localPath)}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=K.join(c.prefixPath,a);return await f.readFilePromise(p,"utf8")}var DS=class{supports(e,r){return!!e.reference.startsWith(fA)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:fA});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){let s=await SS(e.reference,fA,r);return le.mktempPromise(async a=>{let n=K.join(a,"generator.js");return await le.writeFilePromise(n,s),le.mktempPromise(async c=>{if(await this.generatePackage(c,e,n,r),!le.existsSync(K.join(c,"build")))throw new Error("The script should have generated a build directory");return await hs.makeArchiveFromDirectory(K.join(c,"build"),{prefixPath:q.getIdentVendorPath(e),compressionLevel:r.project.configuration.get("compressionLevel")})})})}async generatePackage(e,r,s,a){return await le.mktempPromise(async n=>{let c=await In.makeScriptEnv({project:a.project,binFolder:n}),f=K.join(e,"runtime.js");return await le.mktempPromise(async p=>{let h=K.join(p,"buildfile.log"),E=K.join(e,"generator"),C=K.join(e,"build");await le.mkdirPromise(E),await le.mkdirPromise(C);let S={tempDir:ue.fromPortablePath(E),buildDir:ue.fromPortablePath(C),locator:q.stringifyLocator(r)};await le.writeFilePromise(f,` + // Expose 'Module' as a global variable + Object.defineProperty(global, 'Module', { + get: () => require('module'), + configurable: true, + enumerable: false, + }); + + // Expose non-hidden built-in modules as global variables + for (const name of Module.builtinModules.filter((name) => name !== 'module' && !name.startsWith('_'))) { + Object.defineProperty(global, name, { + get: () => require(name), + configurable: true, + enumerable: false, + }); + } + + // Expose the 'execEnv' global variable + Object.defineProperty(global, 'execEnv', { + value: { + ...${JSON.stringify(S)}, + }, + enumerable: true, + }); + `);let P=c.NODE_OPTIONS||"",I=/\s*--require\s+\S*\.pnp\.c?js\s*/g;P=P.replace(I," ").trim(),c.NODE_OPTIONS=P;let{stdout:R,stderr:N}=a.project.configuration.getSubprocessStreams(h,{header:`# This file contains the result of Yarn generating a package (${q.stringifyLocator(r)}) +`,prefix:q.prettyLocator(a.project.configuration,r),report:a.report}),{code:U}=await Gr.pipevp(process.execPath,["--require",ue.fromPortablePath(f),ue.fromPortablePath(s),q.stringifyIdent(r)],{cwd:e,env:c,stdin:null,stdout:R,stderr:N});if(U!==0)throw le.detachTemp(p),new Error(`Package generation failed (exit code ${U}, logs can be found here: ${he.pretty(a.project.configuration,h,he.Type.PATH)})`)})})}};Ve();Ve();var BDt=2,bS=class{supportsDescriptor(e,r){return!!e.range.startsWith(fA)}supportsLocator(e,r){return!!e.reference.startsWith(fA)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=v9(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=await SS(q.makeRange({protocol:fA,source:a,selector:a,params:{locator:q.stringifyLocator(n)}}),fA,s.fetchOptions),f=Nn.makeHash(`${BDt}`,c).slice(0,6);return[S9(e,{parentLocator:n,path:a,generatorHash:f,protocol:fA})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var vDt={fetchers:[DS],resolvers:[bS]},SDt=vDt;var P9={};Vt(P9,{FileFetcher:()=>QS,FileResolver:()=>TS,TarballFileFetcher:()=>RS,TarballFileResolver:()=>NS,default:()=>PDt,fileUtils:()=>km});Ve();bt();var nw=/^(?:[a-zA-Z]:[\\/]|\.{0,2}\/)/,PS=/^[^?]*\.(?:tar\.gz|tgz)(?:::.*)?$/,es="file:";var km={};Vt(km,{fetchArchiveFromLocator:()=>kS,makeArchiveFromLocator:()=>pF,makeBufferFromLocator:()=>b9,makeLocator:()=>iw,makeSpec:()=>vve,parseSpec:()=>xS});Ve();bt();function xS(t){let{params:e,selector:r}=q.parseRange(t),s=ue.toPortablePath(r);return{parentLocator:e&&typeof e.locator=="string"?q.parseLocator(e.locator):null,path:s}}function vve({parentLocator:t,path:e,hash:r,protocol:s}){let a=t!==null?{locator:q.stringifyLocator(t)}:{},n=typeof r<"u"?{hash:r}:{};return q.makeRange({protocol:s,source:e,selector:e,params:{...n,...a}})}function iw(t,{parentLocator:e,path:r,hash:s,protocol:a}){return q.makeLocator(t,vve({parentLocator:e,path:r,hash:s,protocol:a}))}async function kS(t,e){let{parentLocator:r,path:s}=q.parseFileStyleRange(t.reference,{protocol:es}),a=K.isAbsolute(s)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await e.fetcher.fetch(r,e),n=a.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,a.localPath)}:a;a!==n&&a.releaseFs&&a.releaseFs();let c=n.packageFs,f=K.join(n.prefixPath,s);return await je.releaseAfterUseAsync(async()=>await c.readFilePromise(f),n.releaseFs)}async function pF(t,{protocol:e,fetchOptions:r,inMemory:s=!1}){let{parentLocator:a,path:n}=q.parseFileStyleRange(t.reference,{protocol:e}),c=K.isAbsolute(n)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(a,r),f=c.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,c.localPath)}:c;c!==f&&c.releaseFs&&c.releaseFs();let p=f.packageFs,h=K.join(f.prefixPath,n);return await je.releaseAfterUseAsync(async()=>await hs.makeArchiveFromDirectory(h,{baseFs:p,prefixPath:q.getIdentVendorPath(t),compressionLevel:r.project.configuration.get("compressionLevel"),inMemory:s}),f.releaseFs)}async function b9(t,{protocol:e,fetchOptions:r}){return(await pF(t,{protocol:e,fetchOptions:r,inMemory:!0})).getBufferAndClose()}var QS=class{supports(e,r){return!!e.reference.startsWith(es)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:es});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async fetchFromDisk(e,r){return pF(e,{protocol:es,fetchOptions:r})}};Ve();Ve();var DDt=2,TS=class{supportsDescriptor(e,r){return e.range.match(nw)?!0:!!e.range.startsWith(es)}supportsLocator(e,r){return!!e.reference.startsWith(es)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return nw.test(e.range)&&(e=q.makeDescriptor(e,`${es}${e.range}`)),q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=xS(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=await b9(q.makeLocator(e,q.makeRange({protocol:es,source:a,selector:a,params:{locator:q.stringifyLocator(n)}})),{protocol:es,fetchOptions:s.fetchOptions}),f=Nn.makeHash(`${DDt}`,c).slice(0,6);return[iw(e,{parentLocator:n,path:a,hash:f,protocol:es})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};Ve();var RS=class{supports(e,r){return PS.test(e.reference)?!!e.reference.startsWith(es):!1}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.fetchFromDisk(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromDisk(e,r){let s=await kS(e,r);return await hs.convertToZip(s,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}};Ve();Ve();Ve();var NS=class{supportsDescriptor(e,r){return PS.test(e.range)?!!(e.range.startsWith(es)||nw.test(e.range)):!1}supportsLocator(e,r){return PS.test(e.reference)?!!e.reference.startsWith(es):!1}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return nw.test(e.range)&&(e=q.makeDescriptor(e,`${es}${e.range}`)),q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{path:a,parentLocator:n}=xS(e.range);if(n===null)throw new Error("Assertion failed: The descriptor should have been bound");let c=iw(e,{parentLocator:n,path:a,hash:"",protocol:es}),f=await kS(c,s.fetchOptions),p=Nn.makeHash(f).slice(0,6);return[iw(e,{parentLocator:n,path:a,hash:p,protocol:es})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var bDt={fetchers:[RS,QS],resolvers:[NS,TS]},PDt=bDt;var Q9={};Vt(Q9,{GithubFetcher:()=>OS,default:()=>kDt,githubUtils:()=>hF});Ve();bt();var hF={};Vt(hF,{invalidGithubUrlMessage:()=>bve,isGithubUrl:()=>x9,parseGithubUrl:()=>k9});var Sve=et(ye("querystring")),Dve=[/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+)\/tarball\/([^/#]+)(?:#(.*))?$/,/^https?:\/\/(?:([^/]+?)@)?github.com\/([^/#]+)\/([^/#]+?)(?:\.git)?(?:#(.*))?$/];function x9(t){return t?Dve.some(e=>!!t.match(e)):!1}function k9(t){let e;for(let f of Dve)if(e=t.match(f),e)break;if(!e)throw new Error(bve(t));let[,r,s,a,n="master"]=e,{commit:c}=Sve.default.parse(n);return n=c||n.replace(/[^:]*:/,""),{auth:r,username:s,reponame:a,treeish:n}}function bve(t){return`Input cannot be parsed as a valid GitHub URL ('${t}').`}var OS=class{supports(e,r){return!!x9(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from GitHub`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await An.get(this.getLocatorUrl(e,r),{configuration:r.project.configuration});return await le.mktempPromise(async a=>{let n=new Sn(a);await hs.extractArchiveTo(s,n,{stripComponents:1});let c=Qa.splitRepoUrl(e.reference),f=K.join(a,"package.tgz");await In.prepareExternalProject(a,f,{configuration:r.project.configuration,report:r.report,workspace:c.extra.workspace,locator:e});let p=await le.readFilePromise(f);return await hs.convertToZip(p,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})})}getLocatorUrl(e,r){let{auth:s,username:a,reponame:n,treeish:c}=k9(e.reference);return`https://${s?`${s}@`:""}github.com/${a}/${n}/archive/${c}.tar.gz`}};var xDt={hooks:{async fetchHostedRepository(t,e,r){if(t!==null)return t;let s=new OS;if(!s.supports(e,r))return null;try{return await s.fetch(e,r)}catch{return null}}}},kDt=xDt;var T9={};Vt(T9,{TarballHttpFetcher:()=>MS,TarballHttpResolver:()=>_S,default:()=>TDt});Ve();function LS(t){let e;try{e=new URL(t)}catch{return!1}return!(e.protocol!=="http:"&&e.protocol!=="https:"||!e.pathname.match(/(\.tar\.gz|\.tgz|\/[^.]+)$/))}var MS=class{supports(e,r){return LS(e.reference)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s=await An.get(e.reference,{configuration:r.project.configuration});return await hs.convertToZip(s,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}};Ve();Ve();var _S=class{supportsDescriptor(e,r){return LS(e.range)}supportsLocator(e,r){return LS(e.reference)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){return[q.convertDescriptorToLocator(e)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"HARD",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var QDt={fetchers:[MS],resolvers:[_S]},TDt=QDt;var R9={};Vt(R9,{InitCommand:()=>Z0,InitInitializerCommand:()=>sw,default:()=>FDt});Wt();Ve();Ve();bt();Wt();var Z0=class extends ut{constructor(){super(...arguments);this.private=ge.Boolean("-p,--private",!1,{description:"Initialize a private package"});this.workspace=ge.Boolean("-w,--workspace",!1,{description:"Initialize a workspace root with a `packages/` directory"});this.install=ge.String("-i,--install",!1,{tolerateBoolean:!0,description:"Initialize a package with a specific bundle that will be locked in the project"});this.name=ge.String("-n,--name",{description:"Initialize a package with the given name"});this.usev2=ge.Boolean("-2",!1,{hidden:!0});this.yes=ge.Boolean("-y,--yes",{hidden:!0})}static{this.paths=[["init"]]}static{this.usage=ot.Usage({description:"create a new package",details:"\n This command will setup a new package in your local directory.\n\n If the `-p,--private` or `-w,--workspace` options are set, the package will be private by default.\n\n If the `-w,--workspace` option is set, the package will be configured to accept a set of workspaces in the `packages/` directory.\n\n If the `-i,--install` option is given a value, Yarn will first download it using `yarn set version` and only then forward the init call to the newly downloaded bundle. Without arguments, the downloaded bundle will be `latest`.\n\n The initial settings of the manifest can be changed by using the `initScope` and `initFields` configuration values. Additionally, Yarn will generate an EditorConfig file whose rules can be altered via `initEditorConfig`, and will initialize a Git repository in the current directory.\n ",examples:[["Create a new package in the local directory","yarn init"],["Create a new private package in the local directory","yarn init -p"],["Create a new package and store the Yarn release inside","yarn init -i=latest"],["Create a new private package and defines it as a workspace root","yarn init -w"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=typeof this.install=="string"?this.install:this.usev2||this.install===!0?"latest":null;return s!==null?await this.executeProxy(r,s):await this.executeRegular(r)}async executeProxy(r,s){if(r.projectCwd!==null&&r.projectCwd!==this.context.cwd)throw new nt("Cannot use the --install flag from within a project subdirectory");le.existsSync(this.context.cwd)||await le.mkdirPromise(this.context.cwd,{recursive:!0});let a=K.join(this.context.cwd,Er.lockfile);le.existsSync(a)||await le.writeFilePromise(a,"");let n=await this.cli.run(["set","version",s],{quiet:!0});if(n!==0)return n;let c=[];return this.private&&c.push("-p"),this.workspace&&c.push("-w"),this.name&&c.push(`-n=${this.name}`),this.yes&&c.push("-y"),await le.mktempPromise(async f=>{let{code:p}=await Gr.pipevp("yarn",["init",...c],{cwd:this.context.cwd,stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr,env:await In.makeScriptEnv({binFolder:f})});return p})}async initialize(){}async executeRegular(r){let s=null;try{s=(await Tt.find(r,this.context.cwd)).project}catch{s=null}le.existsSync(this.context.cwd)||await le.mkdirPromise(this.context.cwd,{recursive:!0});let a=await Ht.tryFind(this.context.cwd),n=a??new Ht,c=Object.fromEntries(r.get("initFields").entries());n.load(c),n.name=n.name??q.makeIdent(r.get("initScope"),this.name??K.basename(this.context.cwd)),n.packageManager=un&&je.isTaggedYarnVersion(un)?`yarn@${un}`:null,(!a&&this.workspace||this.private)&&(n.private=!0),this.workspace&&n.workspaceDefinitions.length===0&&(await le.mkdirPromise(K.join(this.context.cwd,"packages"),{recursive:!0}),n.workspaceDefinitions=[{pattern:"packages/*"}]);let f={};n.exportTo(f);let p=K.join(this.context.cwd,Ht.fileName);await le.changeFilePromise(p,`${JSON.stringify(f,null,2)} +`,{automaticNewlines:!0});let h=[p],E=K.join(this.context.cwd,"README.md");if(le.existsSync(E)||(await le.writeFilePromise(E,`# ${q.stringifyIdent(n.name)} +`),h.push(E)),!s||s.cwd===this.context.cwd){let C=K.join(this.context.cwd,Er.lockfile);le.existsSync(C)||(await le.writeFilePromise(C,""),h.push(C));let P=[".yarn/*","!.yarn/patches","!.yarn/plugins","!.yarn/releases","!.yarn/sdks","!.yarn/versions","","# Whether you use PnP or not, the node_modules folder is often used to store","# build artifacts that should be gitignored","node_modules","","# Swap the comments on the following lines if you wish to use zero-installs","# In that case, don't forget to run `yarn config set enableGlobalCache false`!","# Documentation here: https://yarnpkg.com/features/caching#zero-installs","","#!.yarn/cache",".pnp.*"].map(Ae=>`${Ae} +`).join(""),I=K.join(this.context.cwd,".gitignore");le.existsSync(I)||(await le.writeFilePromise(I,P),h.push(I));let N=["/.yarn/** linguist-vendored","/.yarn/releases/* binary","/.yarn/plugins/**/* binary","/.pnp.* binary linguist-generated"].map(Ae=>`${Ae} +`).join(""),U=K.join(this.context.cwd,".gitattributes");le.existsSync(U)||(await le.writeFilePromise(U,N),h.push(U));let W={"*":{charset:"utf-8",endOfLine:"lf",indentSize:2,indentStyle:"space",insertFinalNewline:!0}};je.mergeIntoTarget(W,r.get("initEditorConfig"));let te=`root = true +`;for(let[Ae,ce]of Object.entries(W)){te+=` +[${Ae}] +`;for(let[me,pe]of Object.entries(ce)){let Be=me.replace(/[A-Z]/g,Ce=>`_${Ce.toLowerCase()}`);te+=`${Be} = ${pe} +`}}let ie=K.join(this.context.cwd,".editorconfig");le.existsSync(ie)||(await le.writeFilePromise(ie,te),h.push(ie)),await this.cli.run(["install"],{quiet:!0}),await this.initialize(),le.existsSync(K.join(this.context.cwd,".git"))||(await Gr.execvp("git",["init"],{cwd:this.context.cwd}),await Gr.execvp("git",["add","--",...h],{cwd:this.context.cwd}),await Gr.execvp("git",["commit","--allow-empty","-m","First commit"],{cwd:this.context.cwd}))}}};var sw=class extends Z0{constructor(){super(...arguments);this.initializer=ge.String();this.argv=ge.Proxy()}static{this.paths=[["init"]]}async initialize(){this.context.stdout.write(` +`),await this.cli.run(["dlx",this.initializer,...this.argv],{quiet:!0})}};var RDt={configuration:{initScope:{description:"Scope used when creating packages via the init command",type:"STRING",default:null},initFields:{description:"Additional fields to set when creating packages via the init command",type:"MAP",valueDefinition:{description:"",type:"ANY"}},initEditorConfig:{description:"Extra rules to define in the generator editorconfig",type:"MAP",valueDefinition:{description:"",type:"ANY"}}},commands:[Z0,sw]},FDt=RDt;var TY={};Vt(TY,{SearchCommand:()=>Bw,UpgradeInteractiveCommand:()=>vw,default:()=>cTt});Ve();var xve=et(ye("os"));function ow({stdout:t}){if(xve.default.endianness()==="BE")throw new Error("Interactive commands cannot be used on big-endian systems because ink depends on yoga-layout-prebuilt which only supports little-endian architectures");if(!t.isTTY)throw new Error("Interactive commands can only be used inside a TTY environment")}Wt();var HSe=et(Z9()),X9={appId:"OFCNCOG2CU",apiKey:"6fe4476ee5a1832882e326b506d14126",indexName:"npm-search"},QPt=(0,HSe.default)(X9.appId,X9.apiKey).initIndex(X9.indexName),$9=async(t,e=0)=>await QPt.search(t,{analyticsTags:["yarn-plugin-interactive-tools"],attributesToRetrieve:["name","version","owner","repository","humanDownloadsLast30Days"],page:e,hitsPerPage:10});var LD=["regular","dev","peer"],Bw=class extends ut{static{this.paths=[["search"]]}static{this.usage=ot.Usage({category:"Interactive commands",description:"open the search interface",details:` + This command opens a fullscreen terminal interface where you can search for and install packages from the npm registry. + `,examples:[["Open the search window","yarn search"]]})}async execute(){ow(this.context);let{Gem:e}=await Promise.resolve().then(()=>($F(),CY)),{ScrollableItems:r}=await Promise.resolve().then(()=>(nN(),rN)),{useKeypress:s}=await Promise.resolve().then(()=>(FD(),zPe)),{useMinistore:a}=await Promise.resolve().then(()=>(bY(),DY)),{renderForm:n}=await Promise.resolve().then(()=>(aN(),oN)),{default:c}=await Promise.resolve().then(()=>et(oxe())),{Box:f,Text:p}=await Promise.resolve().then(()=>et(Vc())),{default:h,useEffect:E,useState:C}=await Promise.resolve().then(()=>et(hn())),S=await ze.find(this.context.cwd,this.context.plugins),P=()=>h.createElement(f,{flexDirection:"row"},h.createElement(f,{flexDirection:"column",width:48},h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to move between packages.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select a package.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," again to change the target."))),h.createElement(f,{flexDirection:"column"},h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to install the selected packages.")),h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to abort.")))),I=()=>h.createElement(h.Fragment,null,h.createElement(f,{width:15},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Owner")),h.createElement(f,{width:11},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Version")),h.createElement(f,{width:10},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Downloads"))),R=()=>h.createElement(f,{width:17},h.createElement(p,{bold:!0,underline:!0,color:"gray"},"Target")),N=({hit:pe,active:Be})=>{let[Ce,g]=a(pe.name,null);s({active:Be},(fe,se)=>{if(se.name!=="space")return;if(!Ce){g(LD[0]);return}let X=LD.indexOf(Ce)+1;X===LD.length?g(null):g(LD[X])},[Ce,g]);let we=q.parseIdent(pe.name),Ee=q.prettyIdent(S,we);return h.createElement(f,null,h.createElement(f,{width:45},h.createElement(p,{bold:!0,wrap:"wrap"},Ee)),h.createElement(f,{width:14,marginLeft:1},h.createElement(p,{bold:!0,wrap:"truncate"},pe.owner.name)),h.createElement(f,{width:10,marginLeft:1},h.createElement(p,{italic:!0,wrap:"truncate"},pe.version)),h.createElement(f,{width:16,marginLeft:1},h.createElement(p,null,pe.humanDownloadsLast30Days)))},U=({name:pe,active:Be})=>{let[Ce]=a(pe,null),g=q.parseIdent(pe);return h.createElement(f,null,h.createElement(f,{width:47},h.createElement(p,{bold:!0}," - ",q.prettyIdent(S,g))),LD.map(we=>h.createElement(f,{key:we,width:14,marginLeft:1},h.createElement(p,null," ",h.createElement(e,{active:Ce===we})," ",h.createElement(p,{bold:!0},we)))))},W=()=>h.createElement(f,{marginTop:1},h.createElement(p,null,"Powered by Algolia.")),ie=await n(({useSubmit:pe})=>{let Be=a();pe(Be);let Ce=Array.from(Be.keys()).filter(j=>Be.get(j)!==null),[g,we]=C(""),[Ee,fe]=C(0),[se,X]=C([]),De=j=>{j.match(/\t| /)||we(j)},Re=async()=>{fe(0);let j=await $9(g);j.query===g&&X(j.hits)},gt=async()=>{let j=await $9(g,Ee+1);j.query===g&&j.page-1===Ee&&(fe(j.page),X([...se,...j.hits]))};return E(()=>{g?Re():X([])},[g]),h.createElement(f,{flexDirection:"column"},h.createElement(P,null),h.createElement(f,{flexDirection:"row",marginTop:1},h.createElement(p,{bold:!0},"Search: "),h.createElement(f,{width:41},h.createElement(c,{value:g,onChange:De,placeholder:"i.e. babel, webpack, react...",showCursor:!1})),h.createElement(I,null)),se.length?h.createElement(r,{radius:2,loop:!1,children:se.map(j=>h.createElement(N,{key:j.name,hit:j,active:!1})),willReachEnd:gt}):h.createElement(p,{color:"gray"},"Start typing..."),h.createElement(f,{flexDirection:"row",marginTop:1},h.createElement(f,{width:49},h.createElement(p,{bold:!0},"Selected:")),h.createElement(R,null)),Ce.length?Ce.map(j=>h.createElement(U,{key:j,name:j,active:!1})):h.createElement(p,{color:"gray"},"No selected packages..."),h.createElement(W,null))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof ie>"u")return 1;let Ae=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="regular"),ce=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="dev"),me=Array.from(ie.keys()).filter(pe=>ie.get(pe)==="peer");return Ae.length&&await this.cli.run(["add",...Ae]),ce.length&&await this.cli.run(["add","--dev",...ce]),me&&await this.cli.run(["add","--peer",...me]),0}};Ve();Wt();yG();var pxe=et(fi()),Axe=/^((?:[\^~]|>=?)?)([0-9]+)(\.[0-9]+)(\.[0-9]+)((?:-\S+)?)$/;function hxe(t,e){return t.length>0?[t.slice(0,e)].concat(hxe(t.slice(e),e)):[]}var vw=class extends ut{static{this.paths=[["upgrade-interactive"]]}static{this.usage=ot.Usage({category:"Interactive commands",description:"open the upgrade interface",details:` + This command opens a fullscreen terminal interface where you can see any out of date packages used by your application, their status compared to the latest versions available on the remote registry, and select packages to upgrade. + `,examples:[["Open the upgrade window","yarn upgrade-interactive"]]})}async execute(){ow(this.context);let{ItemOptions:e}=await Promise.resolve().then(()=>(fxe(),uxe)),{Pad:r}=await Promise.resolve().then(()=>(QY(),cxe)),{ScrollableItems:s}=await Promise.resolve().then(()=>(nN(),rN)),{useMinistore:a}=await Promise.resolve().then(()=>(bY(),DY)),{renderForm:n}=await Promise.resolve().then(()=>(aN(),oN)),{Box:c,Text:f}=await Promise.resolve().then(()=>et(Vc())),{default:p,useEffect:h,useRef:E,useState:C}=await Promise.resolve().then(()=>et(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd),R=await Jr.find(S);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState({restoreResolutions:!1});let N=this.context.stdout.rows-7,U=(we,Ee)=>{let fe=ICe(we,Ee),se="";for(let X of fe)X.added?se+=he.pretty(S,X.value,"green"):X.removed||(se+=X.value);return se},W=(we,Ee)=>{if(we===Ee)return Ee;let fe=q.parseRange(we),se=q.parseRange(Ee),X=fe.selector.match(Axe),De=se.selector.match(Axe);if(!X||!De)return U(we,Ee);let Re=["gray","red","yellow","green","magenta"],gt=null,j="";for(let rt=1;rt{let se=await Xu.fetchDescriptorFrom(we,fe,{project:P,cache:R,preserveModifier:Ee,workspace:I});return se!==null?se.range:we.range},ie=async we=>{let Ee=pxe.default.valid(we.range)?`^${we.range}`:we.range,[fe,se]=await Promise.all([te(we,we.range,Ee).catch(()=>null),te(we,we.range,"latest").catch(()=>null)]),X=[{value:null,label:we.range}];return fe&&fe!==we.range?X.push({value:fe,label:W(we.range,fe)}):X.push({value:null,label:""}),se&&se!==fe&&se!==we.range?X.push({value:se,label:W(we.range,se)}):X.push({value:null,label:""}),X},Ae=()=>p.createElement(c,{flexDirection:"row"},p.createElement(c,{flexDirection:"column",width:49},p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},""),"/",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to select packages.")),p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},""),"/",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to select versions."))),p.createElement(c,{flexDirection:"column"},p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to install.")),p.createElement(c,{marginLeft:1},p.createElement(f,null,"Press ",p.createElement(f,{bold:!0,color:"cyanBright"},"")," to abort.")))),ce=()=>p.createElement(c,{flexDirection:"row",paddingTop:1,paddingBottom:1},p.createElement(c,{width:50},p.createElement(f,{bold:!0},p.createElement(f,{color:"greenBright"},"?")," Pick the packages you want to upgrade.")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Current")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Range")),p.createElement(c,{width:17},p.createElement(f,{bold:!0,underline:!0,color:"gray"},"Latest"))),me=({active:we,descriptor:Ee,suggestions:fe})=>{let[se,X]=a(Ee.descriptorHash,null),De=q.stringifyIdent(Ee),Re=Math.max(0,45-De.length);return p.createElement(p.Fragment,null,p.createElement(c,null,p.createElement(c,{width:45},p.createElement(f,{bold:!0},q.prettyIdent(S,Ee)),p.createElement(r,{active:we,length:Re})),p.createElement(e,{active:we,options:fe,value:se,skewer:!0,onChange:X,sizes:[17,17,17]})))},pe=({dependencies:we})=>{let[Ee,fe]=C(we.map(()=>null)),se=E(!0),X=async De=>{let Re=await ie(De);return Re.filter(gt=>gt.label!=="").length<=1?null:{descriptor:De,suggestions:Re}};return h(()=>()=>{se.current=!1},[]),h(()=>{let De=Math.trunc(N*1.75),Re=we.slice(0,De),gt=we.slice(De),j=hxe(gt,N),rt=Re.map(X).reduce(async(Fe,Ne)=>{await Fe;let Pe=await Ne;Pe!==null&&se.current&&fe(Ye=>{let ke=Ye.findIndex(_e=>_e===null),it=[...Ye];return it[ke]=Pe,it})},Promise.resolve());j.reduce((Fe,Ne)=>Promise.all(Ne.map(Pe=>Promise.resolve().then(()=>X(Pe)))).then(async Pe=>{Pe=Pe.filter(Ye=>Ye!==null),await Fe,se.current&&fe(Ye=>{let ke=Ye.findIndex(it=>it===null);return Ye.slice(0,ke).concat(Pe).concat(Ye.slice(ke+Pe.length))})}),rt).then(()=>{se.current&&fe(Fe=>Fe.filter(Ne=>Ne!==null))})},[]),Ee.length?p.createElement(s,{radius:N>>1,children:Ee.map((De,Re)=>De!==null?p.createElement(me,{key:Re,active:!1,descriptor:De.descriptor,suggestions:De.suggestions}):p.createElement(f,{key:Re},"Loading..."))}):p.createElement(f,null,"No upgrades found")},Ce=await n(({useSubmit:we})=>{we(a());let Ee=new Map;for(let se of P.workspaces)for(let X of["dependencies","devDependencies"])for(let De of se.manifest[X].values())P.tryWorkspaceByDescriptor(De)===null&&(De.range.startsWith("link:")||Ee.set(De.descriptorHash,De));let fe=je.sortMap(Ee.values(),se=>q.stringifyDescriptor(se));return p.createElement(c,{flexDirection:"column"},p.createElement(Ae,null),p.createElement(ce,null),p.createElement(pe,{dependencies:fe}))},{},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof Ce>"u")return 1;let g=!1;for(let we of P.workspaces)for(let Ee of["dependencies","devDependencies"]){let fe=we.manifest[Ee];for(let se of fe.values()){let X=Ce.get(se.descriptorHash);typeof X<"u"&&X!==null&&(fe.set(se.identHash,q.makeDescriptor(se,X)),g=!0)}}return g?await P.installWithNewReport({quiet:this.context.quiet,stdout:this.context.stdout},{cache:R}):0}};var lTt={commands:[Bw,vw]},cTt=lTt;var FY={};Vt(FY,{default:()=>pTt});Ve();var _D="jsr:";Ve();Ve();function Sw(t){let e=t.range.slice(4);if(Or.validRange(e))return q.makeDescriptor(t,`npm:${q.stringifyIdent(q.wrapIdentIntoScope(t,"jsr"))}@${e}`);let r=q.tryParseDescriptor(e,!0);if(r!==null)return q.makeDescriptor(t,`npm:${q.stringifyIdent(q.wrapIdentIntoScope(r,"jsr"))}@${r.range}`);throw new Error(`Invalid range: ${t.range}`)}function Dw(t){return q.makeLocator(q.wrapIdentIntoScope(t,"jsr"),`npm:${t.reference.slice(4)}`)}function RY(t){return q.makeLocator(q.unwrapIdentFromScope(t,"jsr"),`jsr:${t.reference.slice(4)}`)}var lN=class{supports(e,r){return e.reference.startsWith(_D)}getLocalPath(e,r){let s=Dw(e);return r.fetcher.getLocalPath(s,r)}fetch(e,r){let s=Dw(e);return r.fetcher.fetch(s,r)}};var cN=class{supportsDescriptor(e,r){return!!e.range.startsWith(_D)}supportsLocator(e,r){return!!e.reference.startsWith(_D)}shouldPersistResolution(e,r){let s=Dw(e);return r.resolver.shouldPersistResolution(s,r)}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{inner:Sw(e)}}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(Sw(e));return(await s.resolver.getCandidates(a,r,s)).map(c=>RY(c))}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(Sw(e));return a.resolver.getSatisfying(n,r,s,a)}async resolve(e,r){let s=Dw(e),a=await r.resolver.resolve(s,r);return{...a,...RY(a)}}};var uTt=["dependencies","devDependencies","peerDependencies"];function fTt(t,e){for(let r of uTt)for(let s of t.manifest.getForScope(r).values()){if(!s.range.startsWith("jsr:"))continue;let a=Sw(s),n=r==="dependencies"?q.makeDescriptor(s,"unknown"):null,c=n!==null&&t.manifest.ensureDependencyMeta(n).optional?"optionalDependencies":r;e[c][q.stringifyIdent(s)]=a.range}}var ATt={hooks:{beforeWorkspacePacking:fTt},resolvers:[cN],fetchers:[lN]},pTt=ATt;var NY={};Vt(NY,{LinkFetcher:()=>UD,LinkResolver:()=>HD,PortalFetcher:()=>jD,PortalResolver:()=>qD,default:()=>gTt});Ve();bt();var sh="portal:",oh="link:";var UD=class{supports(e,r){return!!e.reference.startsWith(oh)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:oh});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:oh}),n=K.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=K.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0,localPath:p}:{packageFs:new jf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,discardFromLookup:!0}}};Ve();bt();var HD=class{supportsDescriptor(e,r){return!!e.range.startsWith(oh)}supportsLocator(e,r){return!!e.reference.startsWith(oh)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(oh.length);return[q.makeLocator(e,`${oh}${ue.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){return{...e,version:"0.0.0",languageName:r.project.configuration.get("defaultLanguageName"),linkType:"SOFT",conditions:null,dependencies:new Map,peerDependencies:new Map,dependenciesMeta:new Map,peerDependenciesMeta:new Map,bin:new Map}}};Ve();bt();var jD=class{supports(e,r){return!!e.reference.startsWith(sh)}getLocalPath(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:sh});if(K.isAbsolute(a))return a;let n=r.fetcher.getLocalPath(s,r);return n===null?null:K.resolve(n,a)}async fetch(e,r){let{parentLocator:s,path:a}=q.parseFileStyleRange(e.reference,{protocol:sh}),n=K.isAbsolute(a)?{packageFs:new Sn(vt.root),prefixPath:vt.dot,localPath:vt.root}:await r.fetcher.fetch(s,r),c=n.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,n.localPath),localPath:vt.root}:n;n!==c&&n.releaseFs&&n.releaseFs();let f=c.packageFs,p=K.resolve(c.localPath??c.packageFs.getRealPath(),c.prefixPath,a);return n.localPath?{packageFs:new Sn(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot,localPath:p}:{packageFs:new jf(p,{baseFs:f}),releaseFs:c.releaseFs,prefixPath:vt.dot}}};Ve();Ve();bt();var qD=class{supportsDescriptor(e,r){return!!e.range.startsWith(sh)}supportsLocator(e,r){return!!e.reference.startsWith(sh)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){return q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(sh.length);return[q.makeLocator(e,`${sh}${ue.toPortablePath(a)}`)]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){if(!r.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let s=await r.fetchOptions.fetcher.fetch(e,r.fetchOptions),a=await je.releaseAfterUseAsync(async()=>await Ht.find(s.prefixPath,{baseFs:s.packageFs}),s.releaseFs);return{...e,version:a.version||"0.0.0",languageName:a.languageName||r.project.configuration.get("defaultLanguageName"),linkType:"SOFT",conditions:a.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(a.dependencies),peerDependencies:a.peerDependencies,dependenciesMeta:a.dependenciesMeta,peerDependenciesMeta:a.peerDependenciesMeta,bin:a.bin}}};var hTt={fetchers:[UD,jD],resolvers:[HD,qD]},gTt=hTt;var yV={};Vt(yV,{NodeModulesLinker:()=>ib,NodeModulesMode:()=>hV,PnpLooseLinker:()=>sb,default:()=>QRt});bt();Ve();bt();bt();var LY=(t,e)=>`${t}@${e}`,gxe=(t,e)=>{let r=e.indexOf("#"),s=r>=0?e.substring(r+1):e;return LY(t,s)};var mxe=(t,e={})=>{let r=e.debugLevel||Number(process.env.NM_DEBUG_LEVEL||-1),s=e.check||r>=9,a=e.hoistingLimits||new Map,n={check:s,debugLevel:r,hoistingLimits:a,fastLookupPossible:!0},c;n.debugLevel>=0&&(c=Date.now());let f=wTt(t,n),p=!1,h=0;do{let E=MY(f,[f],new Set([f.locator]),new Map,n);p=E.anotherRoundNeeded||E.isGraphChanged,n.fastLookupPossible=!1,h++}while(p);if(n.debugLevel>=0&&console.log(`hoist time: ${Date.now()-c}ms, rounds: ${h}`),n.debugLevel>=1){let E=GD(f);if(MY(f,[f],new Set([f.locator]),new Map,n).isGraphChanged)throw new Error(`The hoisting result is not terminal, prev tree: +${E}, next tree: +${GD(f)}`);let S=yxe(f);if(S)throw new Error(`${S}, after hoisting finished: +${GD(f)}`)}return n.debugLevel>=2&&console.log(GD(f)),BTt(f)},dTt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=n=>{if(!s.has(n)){s.add(n);for(let c of n.hoistedDependencies.values())r.set(c.name,c);for(let c of n.dependencies.values())n.peerNames.has(c.name)||a(c)}};return a(e),r},mTt=t=>{let e=t[t.length-1],r=new Map,s=new Set,a=new Set,n=(c,f)=>{if(s.has(c))return;s.add(c);for(let h of c.hoistedDependencies.values())if(!f.has(h.name)){let E;for(let C of t)E=C.dependencies.get(h.name),E&&r.set(E.name,E)}let p=new Set;for(let h of c.dependencies.values())p.add(h.name);for(let h of c.dependencies.values())c.peerNames.has(h.name)||n(h,p)};return n(e,a),r},dxe=(t,e)=>{if(e.decoupled)return e;let{name:r,references:s,ident:a,locator:n,dependencies:c,originalDependencies:f,hoistedDependencies:p,peerNames:h,reasons:E,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:I,hoistedTo:R}=e,N={name:r,references:new Set(s),ident:a,locator:n,dependencies:new Map(c),originalDependencies:new Map(f),hoistedDependencies:new Map(p),peerNames:new Set(h),reasons:new Map(E),decoupled:!0,isHoistBorder:C,hoistPriority:S,dependencyKind:P,hoistedFrom:new Map(I),hoistedTo:new Map(R)},U=N.dependencies.get(r);return U&&U.ident==N.ident&&N.dependencies.set(r,N),t.dependencies.set(N.name,N),N},yTt=(t,e)=>{let r=new Map([[t.name,[t.ident]]]);for(let a of t.dependencies.values())t.peerNames.has(a.name)||r.set(a.name,[a.ident]);let s=Array.from(e.keys());s.sort((a,n)=>{let c=e.get(a),f=e.get(n);if(f.hoistPriority!==c.hoistPriority)return f.hoistPriority-c.hoistPriority;{let p=c.dependents.size+c.peerDependents.size;return f.dependents.size+f.peerDependents.size-p}});for(let a of s){let n=a.substring(0,a.indexOf("@",1)),c=a.substring(n.length+1);if(!t.peerNames.has(n)){let f=r.get(n);f||(f=[],r.set(n,f)),f.indexOf(c)<0&&f.push(c)}}return r},OY=t=>{let e=new Set,r=(s,a=new Set)=>{if(!a.has(s)){a.add(s);for(let n of s.peerNames)if(!t.peerNames.has(n)){let c=t.dependencies.get(n);c&&!e.has(c)&&r(c,a)}e.add(s)}};for(let s of t.dependencies.values())t.peerNames.has(s.name)||r(s);return e},MY=(t,e,r,s,a,n=new Set)=>{let c=e[e.length-1];if(n.has(c))return{anotherRoundNeeded:!1,isGraphChanged:!1};n.add(c);let f=vTt(c),p=yTt(c,f),h=t==c?new Map:a.fastLookupPossible?dTt(e):mTt(e),E,C=!1,S=!1,P=new Map(Array.from(p.entries()).map(([R,N])=>[R,N[0]])),I=new Map;do{let R=CTt(t,e,r,h,P,p,s,I,a);R.isGraphChanged&&(S=!0),R.anotherRoundNeeded&&(C=!0),E=!1;for(let[N,U]of p)U.length>1&&!c.dependencies.has(N)&&(P.delete(N),U.shift(),P.set(N,U[0]),E=!0)}while(E);for(let R of c.dependencies.values())if(!c.peerNames.has(R.name)&&!r.has(R.locator)){r.add(R.locator);let N=MY(t,[...e,R],r,I,a);N.isGraphChanged&&(S=!0),N.anotherRoundNeeded&&(C=!0),r.delete(R.locator)}return{anotherRoundNeeded:C,isGraphChanged:S}},ETt=t=>{for(let[e,r]of t.dependencies)if(!t.peerNames.has(e)&&r.ident!==t.ident)return!0;return!1},ITt=(t,e,r,s,a,n,c,f,{outputReason:p,fastLookupPossible:h})=>{let E,C=null,S=new Set;p&&(E=`${Array.from(e).map(N=>Io(N)).join("\u2192")}`);let P=r[r.length-1],R=!(s.ident===P.ident);if(p&&!R&&(C="- self-reference"),R&&(R=s.dependencyKind!==1,p&&!R&&(C="- workspace")),R&&s.dependencyKind===2&&(R=!ETt(s),p&&!R&&(C="- external soft link with unhoisted dependencies")),R&&(R=!t.peerNames.has(s.name),p&&!R&&(C=`- cannot shadow peer: ${Io(t.originalDependencies.get(s.name).locator)} at ${E}`)),R){let N=!1,U=a.get(s.name);if(N=!U||U.ident===s.ident,p&&!N&&(C=`- filled by: ${Io(U.locator)} at ${E}`),N)for(let W=r.length-1;W>=1;W--){let ie=r[W].dependencies.get(s.name);if(ie&&ie.ident!==s.ident){N=!1;let Ae=f.get(P);Ae||(Ae=new Set,f.set(P,Ae)),Ae.add(s.name),p&&(C=`- filled by ${Io(ie.locator)} at ${r.slice(0,W).map(ce=>Io(ce.locator)).join("\u2192")}`);break}}R=N}if(R&&(R=n.get(s.name)===s.ident,p&&!R&&(C=`- filled by: ${Io(c.get(s.name)[0])} at ${E}`)),R){let N=!0,U=new Set(s.peerNames);for(let W=r.length-1;W>=1;W--){let te=r[W];for(let ie of U){if(te.peerNames.has(ie)&&te.originalDependencies.has(ie))continue;let Ae=te.dependencies.get(ie);Ae&&t.dependencies.get(ie)!==Ae&&(W===r.length-1?S.add(Ae):(S=null,N=!1,p&&(C=`- peer dependency ${Io(Ae.locator)} from parent ${Io(te.locator)} was not hoisted to ${E}`))),U.delete(ie)}if(!N)break}R=N}if(R&&!h)for(let N of s.hoistedDependencies.values()){let U=a.get(N.name)||t.dependencies.get(N.name);if(!U||N.ident!==U.ident){R=!1,p&&(C=`- previously hoisted dependency mismatch, needed: ${Io(N.locator)}, available: ${Io(U?.locator)}`);break}}return S!==null&&S.size>0?{isHoistable:2,dependsOn:S,reason:C}:{isHoistable:R?0:1,reason:C}},uN=t=>`${t.name}@${t.locator}`,CTt=(t,e,r,s,a,n,c,f,p)=>{let h=e[e.length-1],E=new Set,C=!1,S=!1,P=(U,W,te,ie,Ae)=>{if(E.has(ie))return;let ce=[...W,uN(ie)],me=[...te,uN(ie)],pe=new Map,Be=new Map;for(let fe of OY(ie)){let se=ITt(h,r,[h,...U,ie],fe,s,a,n,f,{outputReason:p.debugLevel>=2,fastLookupPossible:p.fastLookupPossible});if(Be.set(fe,se),se.isHoistable===2)for(let X of se.dependsOn){let De=pe.get(X.name)||new Set;De.add(fe.name),pe.set(X.name,De)}}let Ce=new Set,g=(fe,se,X)=>{if(!Ce.has(fe)){Ce.add(fe),Be.set(fe,{isHoistable:1,reason:X});for(let De of pe.get(fe.name)||[])g(ie.dependencies.get(De),se,p.debugLevel>=2?`- peer dependency ${Io(fe.locator)} from parent ${Io(ie.locator)} was not hoisted`:"")}};for(let[fe,se]of Be)se.isHoistable===1&&g(fe,se,se.reason);let we=!1;for(let fe of Be.keys())if(!Ce.has(fe)){S=!0;let se=c.get(ie);se&&se.has(fe.name)&&(C=!0),we=!0,ie.dependencies.delete(fe.name),ie.hoistedDependencies.set(fe.name,fe),ie.reasons.delete(fe.name);let X=h.dependencies.get(fe.name);if(p.debugLevel>=2){let De=Array.from(W).concat([ie.locator]).map(gt=>Io(gt)).join("\u2192"),Re=h.hoistedFrom.get(fe.name);Re||(Re=[],h.hoistedFrom.set(fe.name,Re)),Re.push(De),ie.hoistedTo.set(fe.name,Array.from(e).map(gt=>Io(gt.locator)).join("\u2192"))}if(!X)h.ident!==fe.ident&&(h.dependencies.set(fe.name,fe),Ae.add(fe));else for(let De of fe.references)X.references.add(De)}if(ie.dependencyKind===2&&we&&(C=!0),p.check){let fe=yxe(t);if(fe)throw new Error(`${fe}, after hoisting dependencies of ${[h,...U,ie].map(se=>Io(se.locator)).join("\u2192")}: +${GD(t)}`)}let Ee=OY(ie);for(let fe of Ee)if(Ce.has(fe)){let se=Be.get(fe);if((a.get(fe.name)===fe.ident||!ie.reasons.has(fe.name))&&se.isHoistable!==0&&ie.reasons.set(fe.name,se.reason),!fe.isHoistBorder&&me.indexOf(uN(fe))<0){E.add(ie);let De=dxe(ie,fe);P([...U,ie],ce,me,De,R),E.delete(ie)}}},I,R=new Set(OY(h)),N=Array.from(e).map(U=>uN(U));do{I=R,R=new Set;for(let U of I){if(U.locator===h.locator||U.isHoistBorder)continue;let W=dxe(h,U);P([],Array.from(r),N,W,R)}}while(R.size>0);return{anotherRoundNeeded:C,isGraphChanged:S}},yxe=t=>{let e=[],r=new Set,s=new Set,a=(n,c,f)=>{if(r.has(n)||(r.add(n),s.has(n)))return;let p=new Map(c);for(let h of n.dependencies.values())n.peerNames.has(h.name)||p.set(h.name,h);for(let h of n.originalDependencies.values()){let E=p.get(h.name),C=()=>`${Array.from(s).concat([n]).map(S=>Io(S.locator)).join("\u2192")}`;if(n.peerNames.has(h.name)){let S=c.get(h.name);(S!==E||!S||S.ident!==h.ident)&&e.push(`${C()} - broken peer promise: expected ${h.ident} but found ${S&&S.ident}`)}else{let S=f.hoistedFrom.get(n.name),P=n.hoistedTo.get(h.name),I=`${S?` hoisted from ${S.join(", ")}`:""}`,R=`${P?` hoisted to ${P}`:""}`,N=`${C()}${I}`;E?E.ident!==h.ident&&e.push(`${N} - broken require promise for ${h.name}${R}: expected ${h.ident}, but found: ${E.ident}`):e.push(`${N} - broken require promise: no required dependency ${h.name}${R} found`)}}s.add(n);for(let h of n.dependencies.values())n.peerNames.has(h.name)||a(h,p,n);s.delete(n)};return a(t,t.dependencies,t),e.join(` +`)},wTt=(t,e)=>{let{identName:r,name:s,reference:a,peerNames:n}=t,c={name:s,references:new Set([a]),locator:LY(r,a),ident:gxe(r,a),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(n),reasons:new Map,decoupled:!0,isHoistBorder:!0,hoistPriority:0,dependencyKind:1,hoistedFrom:new Map,hoistedTo:new Map},f=new Map([[t,c]]),p=(h,E)=>{let C=f.get(h),S=!!C;if(!C){let{name:P,identName:I,reference:R,peerNames:N,hoistPriority:U,dependencyKind:W}=h,te=e.hoistingLimits.get(E.locator);C={name:P,references:new Set([R]),locator:LY(I,R),ident:gxe(I,R),dependencies:new Map,originalDependencies:new Map,hoistedDependencies:new Map,peerNames:new Set(N),reasons:new Map,decoupled:!0,isHoistBorder:te?te.has(P):!1,hoistPriority:U||0,dependencyKind:W||0,hoistedFrom:new Map,hoistedTo:new Map},f.set(h,C)}if(E.dependencies.set(h.name,C),E.originalDependencies.set(h.name,C),S){let P=new Set,I=R=>{if(!P.has(R)){P.add(R),R.decoupled=!1;for(let N of R.dependencies.values())R.peerNames.has(N.name)||I(N)}};I(C)}else for(let P of h.dependencies)p(P,C)};for(let h of t.dependencies)p(h,c);return c},_Y=t=>t.substring(0,t.indexOf("@",1)),BTt=t=>{let e={name:t.name,identName:_Y(t.locator),references:new Set(t.references),dependencies:new Set},r=new Set([t]),s=(a,n,c)=>{let f=r.has(a),p;if(n===a)p=c;else{let{name:h,references:E,locator:C}=a;p={name:h,identName:_Y(C),references:E,dependencies:new Set}}if(c.dependencies.add(p),!f){r.add(a);for(let h of a.dependencies.values())a.peerNames.has(h.name)||s(h,a,p);r.delete(a)}};for(let a of t.dependencies.values())s(a,t,e);return e},vTt=t=>{let e=new Map,r=new Set([t]),s=c=>`${c.name}@${c.ident}`,a=c=>{let f=s(c),p=e.get(f);return p||(p={dependents:new Set,peerDependents:new Set,hoistPriority:0},e.set(f,p)),p},n=(c,f)=>{let p=!!r.has(f);if(a(f).dependents.add(c.ident),!p){r.add(f);for(let E of f.dependencies.values()){let C=a(E);C.hoistPriority=Math.max(C.hoistPriority,E.hoistPriority),f.peerNames.has(E.name)?C.peerDependents.add(f.ident):n(f,E)}}};for(let c of t.dependencies.values())t.peerNames.has(c.name)||n(t,c);return e},Io=t=>{if(!t)return"none";let e=t.indexOf("@",1),r=t.substring(0,e);r.endsWith("$wsroot$")&&(r=`wh:${r.replace("$wsroot$","")}`);let s=t.substring(e+1);if(s==="workspace:.")return".";if(s){let a=(s.indexOf("#")>0?s.split("#")[1]:s).replace("npm:","");return s.startsWith("virtual")&&(r=`v:${r}`),a.startsWith("workspace")&&(r=`w:${r}`,a=""),`${r}${a?`@${a}`:""}`}else return`${r}`};var GD=t=>{let e=0,r=(a,n,c="")=>{if(e>5e4||n.has(a))return"";e++;let f=Array.from(a.dependencies.values()).sort((h,E)=>h.name===E.name?0:h.name>E.name?1:-1),p="";n.add(a);for(let h=0;h":"")+(S!==E.name?`a:${E.name}:`:"")+Io(E.locator)+(C?` ${C}`:"")} +`,p+=r(E,n,`${c}${h5e4?` +Tree is too large, part of the tree has been dunped +`:"")};var WD=(s=>(s.WORKSPACES="workspaces",s.DEPENDENCIES="dependencies",s.NONE="none",s))(WD||{}),Exe="node_modules",ng="$wsroot$";var YD=(t,e)=>{let{packageTree:r,hoistingLimits:s,errors:a,preserveSymlinksRequired:n}=DTt(t,e),c=null;if(a.length===0){let f=mxe(r,{hoistingLimits:s});c=PTt(t,f,e)}return{tree:c,errors:a,preserveSymlinksRequired:n}},gA=t=>`${t.name}@${t.reference}`,HY=t=>{let e=new Map;for(let[r,s]of t.entries())if(!s.dirList){let a=e.get(s.locator);a||(a={target:s.target,linkType:s.linkType,locations:[],aliases:s.aliases},e.set(s.locator,a)),a.locations.push(r)}for(let r of e.values())r.locations=r.locations.sort((s,a)=>{let n=s.split(K.delimiter).length,c=a.split(K.delimiter).length;return a===s?0:n!==c?c-n:a>s?1:-1});return e},Ixe=(t,e)=>{let r=q.isVirtualLocator(t)?q.devirtualizeLocator(t):t,s=q.isVirtualLocator(e)?q.devirtualizeLocator(e):e;return q.areLocatorsEqual(r,s)},UY=(t,e,r,s)=>{if(t.linkType!=="SOFT")return!1;let a=ue.toPortablePath(r.resolveVirtual&&e.reference&&e.reference.startsWith("virtual:")?r.resolveVirtual(t.packageLocation):t.packageLocation);return K.contains(s,a)===null},STt=t=>{let e=t.getPackageInformation(t.topLevel);if(e===null)throw new Error("Assertion failed: Expected the top-level package to have been registered");if(t.findPackageLocator(e.packageLocation)===null)throw new Error("Assertion failed: Expected the top-level package to have a physical locator");let s=ue.toPortablePath(e.packageLocation.slice(0,-1)),a=new Map,n={children:new Map},c=t.getDependencyTreeRoots(),f=new Map,p=new Set,h=(S,P)=>{let I=gA(S);if(p.has(I))return;p.add(I);let R=t.getPackageInformation(S);if(R){let N=P?gA(P):"";if(gA(S)!==N&&R.linkType==="SOFT"&&!S.reference.startsWith("link:")&&!UY(R,S,t,s)){let U=Cxe(R,S,t);(!f.get(U)||S.reference.startsWith("workspace:"))&&f.set(U,S)}for(let[U,W]of R.packageDependencies)W!==null&&(R.packagePeers.has(U)||h(t.getLocator(U,W),S))}};for(let S of c)h(S,null);let E=s.split(K.sep);for(let S of f.values()){let P=t.getPackageInformation(S),R=ue.toPortablePath(P.packageLocation.slice(0,-1)).split(K.sep).slice(E.length),N=n;for(let U of R){let W=N.children.get(U);W||(W={children:new Map},N.children.set(U,W)),N=W}N.workspaceLocator=S}let C=(S,P)=>{if(S.workspaceLocator){let I=gA(P),R=a.get(I);R||(R=new Set,a.set(I,R)),R.add(S.workspaceLocator)}for(let I of S.children.values())C(I,S.workspaceLocator||P)};for(let S of n.children.values())C(S,n.workspaceLocator);return a},DTt=(t,e)=>{let r=[],s=!1,a=new Map,n=STt(t),c=t.getPackageInformation(t.topLevel);if(c===null)throw new Error("Assertion failed: Expected the top-level package to have been registered");let f=t.findPackageLocator(c.packageLocation);if(f===null)throw new Error("Assertion failed: Expected the top-level package to have a physical locator");let p=ue.toPortablePath(c.packageLocation.slice(0,-1)),h={name:f.name,identName:f.name,reference:f.reference,peerNames:c.packagePeers,dependencies:new Set,dependencyKind:1},E=new Map,C=(P,I)=>`${gA(I)}:${P}`,S=(P,I,R,N,U,W,te,ie)=>{let Ae=C(P,R),ce=E.get(Ae),me=!!ce;!me&&R.name===f.name&&R.reference===f.reference&&(ce=h,E.set(Ae,h));let pe=UY(I,R,t,p);if(!ce){let fe=0;pe?fe=2:I.linkType==="SOFT"&&R.name.endsWith(ng)&&(fe=1),ce={name:P,identName:R.name,reference:R.reference,dependencies:new Set,peerNames:fe===1?new Set:I.packagePeers,dependencyKind:fe},E.set(Ae,ce)}let Be;if(pe?Be=2:U.linkType==="SOFT"?Be=1:Be=0,ce.hoistPriority=Math.max(ce.hoistPriority||0,Be),ie&&!pe){let fe=gA({name:N.identName,reference:N.reference}),se=a.get(fe)||new Set;a.set(fe,se),se.add(ce.name)}let Ce=new Map(I.packageDependencies);if(e.project){let fe=e.project.workspacesByCwd.get(ue.toPortablePath(I.packageLocation.slice(0,-1)));if(fe){let se=new Set([...Array.from(fe.manifest.peerDependencies.values(),X=>q.stringifyIdent(X)),...Array.from(fe.manifest.peerDependenciesMeta.keys())]);for(let X of se)Ce.has(X)||(Ce.set(X,W.get(X)||null),ce.peerNames.add(X))}}let g=gA({name:R.name.replace(ng,""),reference:R.reference}),we=n.get(g);if(we)for(let fe of we)Ce.set(`${fe.name}${ng}`,fe.reference);(I!==U||I.linkType!=="SOFT"||!pe&&(!e.selfReferencesByCwd||e.selfReferencesByCwd.get(te)))&&N.dependencies.add(ce);let Ee=R!==f&&I.linkType==="SOFT"&&!R.name.endsWith(ng)&&!pe;if(!me&&!Ee){let fe=new Map;for(let[se,X]of Ce)if(X!==null){let De=t.getLocator(se,X),Re=t.getLocator(se.replace(ng,""),X),gt=t.getPackageInformation(Re);if(gt===null)throw new Error("Assertion failed: Expected the package to have been registered");let j=UY(gt,De,t,p);if(e.validateExternalSoftLinks&&e.project&&j){gt.packageDependencies.size>0&&(s=!0);for(let[Ye,ke]of gt.packageDependencies)if(ke!==null){let it=q.parseLocator(Array.isArray(ke)?`${ke[0]}@${ke[1]}`:`${Ye}@${ke}`);if(gA(it)!==gA(De)){let _e=Ce.get(Ye);if(_e){let x=q.parseLocator(Array.isArray(_e)?`${_e[0]}@${_e[1]}`:`${Ye}@${_e}`);Ixe(x,it)||r.push({messageName:71,text:`Cannot link ${q.prettyIdent(e.project.configuration,q.parseIdent(De.name))} into ${q.prettyLocator(e.project.configuration,q.parseLocator(`${R.name}@${R.reference}`))} dependency ${q.prettyLocator(e.project.configuration,it)} conflicts with parent dependency ${q.prettyLocator(e.project.configuration,x)}`})}else{let x=fe.get(Ye);if(x){let w=x.target,b=q.parseLocator(Array.isArray(w)?`${w[0]}@${w[1]}`:`${Ye}@${w}`);Ixe(b,it)||r.push({messageName:71,text:`Cannot link ${q.prettyIdent(e.project.configuration,q.parseIdent(De.name))} into ${q.prettyLocator(e.project.configuration,q.parseLocator(`${R.name}@${R.reference}`))} dependency ${q.prettyLocator(e.project.configuration,it)} conflicts with dependency ${q.prettyLocator(e.project.configuration,b)} from sibling portal ${q.prettyIdent(e.project.configuration,q.parseIdent(x.portal.name))}`})}else fe.set(Ye,{target:it.reference,portal:De})}}}}let rt=e.hoistingLimitsByCwd?.get(te),Fe=j?te:K.relative(p,ue.toPortablePath(gt.packageLocation))||vt.dot,Ne=e.hoistingLimitsByCwd?.get(Fe);S(se,gt,De,ce,I,Ce,Fe,rt==="dependencies"||Ne==="dependencies"||Ne==="workspaces")}}};return S(f.name,c,f,h,c,c.packageDependencies,vt.dot,!1),{packageTree:h,hoistingLimits:a,errors:r,preserveSymlinksRequired:s}};function Cxe(t,e,r){let s=r.resolveVirtual&&e.reference&&e.reference.startsWith("virtual:")?r.resolveVirtual(t.packageLocation):t.packageLocation;return ue.toPortablePath(s||t.packageLocation)}function bTt(t,e,r){let s=e.getLocator(t.name.replace(ng,""),t.reference),a=e.getPackageInformation(s);if(a===null)throw new Error("Assertion failed: Expected the package to be registered");return r.pnpifyFs?{linkType:"SOFT",target:ue.toPortablePath(a.packageLocation)}:{linkType:a.linkType,target:Cxe(a,t,e)}}var PTt=(t,e,r)=>{let s=new Map,a=(E,C,S)=>{let{linkType:P,target:I}=bTt(E,t,r);return{locator:gA(E),nodePath:C,target:I,linkType:P,aliases:S}},n=E=>{let[C,S]=E.split("/");return S?{scope:C,name:S}:{scope:null,name:C}},c=new Set,f=(E,C,S)=>{if(c.has(E))return;c.add(E);let P=Array.from(E.references).sort().join("#");for(let I of E.dependencies){let R=Array.from(I.references).sort().join("#");if(I.identName===E.identName.replace(ng,"")&&R===P)continue;let N=Array.from(I.references).sort(),U={name:I.identName,reference:N[0]},{name:W,scope:te}=n(I.name),ie=te?[te,W]:[W],Ae=K.join(C,Exe),ce=K.join(Ae,...ie),me=`${S}/${U.name}`,pe=a(U,S,N.slice(1)),Be=!1;if(pe.linkType==="SOFT"&&r.project){let Ce=r.project.workspacesByCwd.get(pe.target.slice(0,-1));Be=!!(Ce&&!Ce.manifest.name)}if(!I.name.endsWith(ng)&&!Be){let Ce=s.get(ce);if(Ce){if(Ce.dirList)throw new Error(`Assertion failed: ${ce} cannot merge dir node with leaf node`);{let Ee=q.parseLocator(Ce.locator),fe=q.parseLocator(pe.locator);if(Ce.linkType!==pe.linkType)throw new Error(`Assertion failed: ${ce} cannot merge nodes with different link types ${Ce.nodePath}/${q.stringifyLocator(Ee)} and ${S}/${q.stringifyLocator(fe)}`);if(Ee.identHash!==fe.identHash)throw new Error(`Assertion failed: ${ce} cannot merge nodes with different idents ${Ce.nodePath}/${q.stringifyLocator(Ee)} and ${S}/s${q.stringifyLocator(fe)}`);pe.aliases=[...pe.aliases,...Ce.aliases,q.parseLocator(Ce.locator).reference]}}s.set(ce,pe);let g=ce.split("/"),we=g.indexOf(Exe);for(let Ee=g.length-1;we>=0&&Ee>we;Ee--){let fe=ue.toPortablePath(g.slice(0,Ee).join(K.sep)),se=g[Ee],X=s.get(fe);if(!X)s.set(fe,{dirList:new Set([se])});else if(X.dirList){if(X.dirList.has(se))break;X.dirList.add(se)}}}f(I,pe.linkType==="SOFT"?pe.target:ce,me)}},p=a({name:e.name,reference:Array.from(e.references)[0]},"",[]),h=p.target;return s.set(h,p),f(e,h,""),s};Ve();Ve();bt();bt();rA();Bc();var oV={};Vt(oV,{PnpInstaller:()=>Gm,PnpLinker:()=>og,UnplugCommand:()=>Pw,default:()=>iRt,getPnpPath:()=>ag,jsInstallUtils:()=>mA,pnpUtils:()=>nb,quotePathIfNeeded:()=>ske});bt();var ike=ye("url");Ve();Ve();bt();bt();var wxe={DEFAULT:{collapsed:!1,next:{"*":"DEFAULT"}},TOP_LEVEL:{collapsed:!1,next:{fallbackExclusionList:"FALLBACK_EXCLUSION_LIST",packageRegistryData:"PACKAGE_REGISTRY_DATA","*":"DEFAULT"}},FALLBACK_EXCLUSION_LIST:{collapsed:!1,next:{"*":"FALLBACK_EXCLUSION_ENTRIES"}},FALLBACK_EXCLUSION_ENTRIES:{collapsed:!0,next:{"*":"FALLBACK_EXCLUSION_DATA"}},FALLBACK_EXCLUSION_DATA:{collapsed:!0,next:{"*":"DEFAULT"}},PACKAGE_REGISTRY_DATA:{collapsed:!1,next:{"*":"PACKAGE_REGISTRY_ENTRIES"}},PACKAGE_REGISTRY_ENTRIES:{collapsed:!0,next:{"*":"PACKAGE_STORE_DATA"}},PACKAGE_STORE_DATA:{collapsed:!1,next:{"*":"PACKAGE_STORE_ENTRIES"}},PACKAGE_STORE_ENTRIES:{collapsed:!0,next:{"*":"PACKAGE_INFORMATION_DATA"}},PACKAGE_INFORMATION_DATA:{collapsed:!1,next:{packageDependencies:"PACKAGE_DEPENDENCIES","*":"DEFAULT"}},PACKAGE_DEPENDENCIES:{collapsed:!1,next:{"*":"PACKAGE_DEPENDENCY"}},PACKAGE_DEPENDENCY:{collapsed:!0,next:{"*":"DEFAULT"}}};function xTt(t,e,r){let s="";s+="[";for(let a=0,n=t.length;a"u"||(f!==0&&(a+=", "),a+=JSON.stringify(p),a+=": ",a+=fN(p,h,e,r).replace(/^ +/g,""),f+=1)}return a+="}",a}function TTt(t,e,r){let s=Object.keys(t),a=`${r} `,n="";n+=r,n+=`{ +`;let c=0;for(let f=0,p=s.length;f"u"||(c!==0&&(n+=",",n+=` +`),n+=a,n+=JSON.stringify(h),n+=": ",n+=fN(h,E,e,a).replace(/^ +/g,""),c+=1)}return c!==0&&(n+=` +`),n+=r,n+="}",n}function fN(t,e,r,s){let{next:a}=wxe[r],n=a[t]||a["*"];return Bxe(e,n,s)}function Bxe(t,e,r){let{collapsed:s}=wxe[e];return Array.isArray(t)?s?xTt(t,e,r):kTt(t,e,r):typeof t=="object"&&t!==null?s?QTt(t,e,r):TTt(t,e,r):JSON.stringify(t)}function vxe(t){return Bxe(t,"TOP_LEVEL","")}function VD(t,e){let r=Array.from(t);Array.isArray(e)||(e=[e]);let s=[];for(let n of e)s.push(r.map(c=>n(c)));let a=r.map((n,c)=>c);return a.sort((n,c)=>{for(let f of s){let p=f[n]f[c]?1:0;if(p!==0)return p}return 0}),a.map(n=>r[n])}function RTt(t){let e=new Map,r=VD(t.fallbackExclusionList||[],[({name:s,reference:a})=>s,({name:s,reference:a})=>a]);for(let{name:s,reference:a}of r){let n=e.get(s);typeof n>"u"&&e.set(s,n=new Set),n.add(a)}return Array.from(e).map(([s,a])=>[s,Array.from(a)])}function FTt(t){return VD(t.fallbackPool||[],([e])=>e)}function NTt(t){let e=[],r=t.dependencyTreeRoots.find(s=>t.packageRegistry.get(s.name)?.get(s.reference)?.packageLocation==="./");for(let[s,a]of VD(t.packageRegistry,([n])=>n===null?"0":`1${n}`)){if(s===null)continue;let n=[];e.push([s,n]);for(let[c,{packageLocation:f,packageDependencies:p,packagePeers:h,linkType:E,discardFromLookup:C}]of VD(a,([S])=>S===null?"0":`1${S}`)){if(c===null)continue;let S=[];s!==null&&c!==null&&!p.has(s)&&S.push([s,c]);for(let[U,W]of p)S.push([U,W]);let P=VD(S,([U])=>U),I=h&&h.size>0?Array.from(h):void 0,N={packageLocation:f,packageDependencies:P,packagePeers:I,linkType:E,discardFromLookup:C||void 0};n.push([c,N]),r&&s===r.name&&c===r.reference&&e.unshift([null,[[null,N]]])}}return e}function KD(t){return{__info:["This file is automatically generated. Do not touch it, or risk","your modifications being lost."],dependencyTreeRoots:t.dependencyTreeRoots,enableTopLevelFallback:t.enableTopLevelFallback||!1,ignorePatternData:t.ignorePattern||null,pnpZipBackend:t.pnpZipBackend,fallbackExclusionList:RTt(t),fallbackPool:FTt(t),packageRegistryData:NTt(t)}}var bxe=et(Dxe());function Pxe(t,e){return[t?`${t} +`:"",`/* eslint-disable */ +`,`// @ts-nocheck +`,`"use strict"; +`,` +`,e,` +`,(0,bxe.default)()].join("")}function OTt(t){return JSON.stringify(t,null,2)}function LTt(t){return`'${t.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/\n/g,`\\ +`)}'`}function MTt(t){return[`const RAW_RUNTIME_STATE = +`,`${LTt(vxe(t))}; + +`,`function $$SETUP_STATE(hydrateRuntimeState, basePath) { +`,` return hydrateRuntimeState(JSON.parse(RAW_RUNTIME_STATE), {basePath: basePath || __dirname}); +`,`} +`].join("")}function _Tt(){return[`function $$SETUP_STATE(hydrateRuntimeState, basePath) { +`,` const fs = require('fs'); +`,` const path = require('path'); +`,` const pnpDataFilepath = path.resolve(__dirname, ${JSON.stringify(Er.pnpData)}); +`,` return hydrateRuntimeState(JSON.parse(fs.readFileSync(pnpDataFilepath, 'utf8')), {basePath: basePath || __dirname}); +`,`} +`].join("")}function xxe(t){let e=KD(t),r=MTt(e);return Pxe(t.shebang,r)}function kxe(t){let e=KD(t),r=_Tt(),s=Pxe(t.shebang,r);return{dataFile:OTt(e),loaderFile:s}}bt();function qY(t,{basePath:e}){let r=ue.toPortablePath(e),s=K.resolve(r),a=t.ignorePatternData!==null?new RegExp(t.ignorePatternData):null,n=new Map,c=new Map(t.packageRegistryData.map(([C,S])=>[C,new Map(S.map(([P,I])=>{if(C===null!=(P===null))throw new Error("Assertion failed: The name and reference should be null, or neither should");let R=I.discardFromLookup??!1,N={name:C,reference:P},U=n.get(I.packageLocation);U?(U.discardFromLookup=U.discardFromLookup&&R,R||(U.locator=N)):n.set(I.packageLocation,{locator:N,discardFromLookup:R});let W=null;return[P,{packageDependencies:new Map(I.packageDependencies),packagePeers:new Set(I.packagePeers),linkType:I.linkType,discardFromLookup:R,get packageLocation(){return W||(W=K.join(s,I.packageLocation))}}]}))])),f=new Map(t.fallbackExclusionList.map(([C,S])=>[C,new Set(S)])),p=new Map(t.fallbackPool),h=t.dependencyTreeRoots,E=t.enableTopLevelFallback;return{basePath:r,dependencyTreeRoots:h,enableTopLevelFallback:E,fallbackExclusionList:f,pnpZipBackend:t.pnpZipBackend,fallbackPool:p,ignorePattern:a,packageLocatorsByLocations:n,packageRegistry:c}}bt();bt();var lh=ye("module"),qm=ye("url"),$Y=ye("util");var ra=ye("url");var Fxe=et(ye("assert"));var GY=Array.isArray,JD=JSON.stringify,zD=Object.getOwnPropertyNames,jm=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),WY=(t,e)=>RegExp.prototype.exec.call(t,e),YY=(t,...e)=>RegExp.prototype[Symbol.replace].apply(t,e),ig=(t,...e)=>String.prototype.endsWith.apply(t,e),VY=(t,...e)=>String.prototype.includes.apply(t,e),KY=(t,...e)=>String.prototype.lastIndexOf.apply(t,e),ZD=(t,...e)=>String.prototype.indexOf.apply(t,e),Qxe=(t,...e)=>String.prototype.replace.apply(t,e),sg=(t,...e)=>String.prototype.slice.apply(t,e),dA=(t,...e)=>String.prototype.startsWith.apply(t,e),Txe=Map,Rxe=JSON.parse;function XD(t,e,r){return class extends r{constructor(...s){super(e(...s)),this.code=t,this.name=`${r.name} [${t}]`}}}var Nxe=XD("ERR_PACKAGE_IMPORT_NOT_DEFINED",(t,e,r)=>`Package import specifier "${t}" is not defined${e?` in package ${e}package.json`:""} imported from ${r}`,TypeError),JY=XD("ERR_INVALID_MODULE_SPECIFIER",(t,e,r=void 0)=>`Invalid module "${t}" ${e}${r?` imported from ${r}`:""}`,TypeError),Oxe=XD("ERR_INVALID_PACKAGE_TARGET",(t,e,r,s=!1,a=void 0)=>{let n=typeof r=="string"&&!s&&r.length&&!dA(r,"./");return e==="."?((0,Fxe.default)(s===!1),`Invalid "exports" main target ${JD(r)} defined in the package config ${t}package.json${a?` imported from ${a}`:""}${n?'; targets must start with "./"':""}`):`Invalid "${s?"imports":"exports"}" target ${JD(r)} defined for '${e}' in the package config ${t}package.json${a?` imported from ${a}`:""}${n?'; targets must start with "./"':""}`},Error),$D=XD("ERR_INVALID_PACKAGE_CONFIG",(t,e,r)=>`Invalid package config ${t}${e?` while importing ${e}`:""}${r?`. ${r}`:""}`,Error),Lxe=XD("ERR_PACKAGE_PATH_NOT_EXPORTED",(t,e,r=void 0)=>e==="."?`No "exports" main defined in ${t}package.json${r?` imported from ${r}`:""}`:`Package subpath '${e}' is not defined by "exports" in ${t}package.json${r?` imported from ${r}`:""}`,Error);var pN=ye("url");function Mxe(t,e){let r=Object.create(null);for(let s=0;se):t+e}eb(r,t,s,c,a)}WY(Uxe,sg(t,2))!==null&&eb(r,t,s,c,a);let p=new URL(t,s),h=p.pathname,E=new URL(".",s).pathname;if(dA(h,E)||eb(r,t,s,c,a),e==="")return p;if(WY(Uxe,e)!==null){let C=n?Qxe(r,"*",()=>e):r+e;jTt(C,s,c,a)}return n?new URL(YY(Hxe,p.href,()=>e)):new URL(e,p)}function GTt(t){let e=+t;return`${e}`!==t?!1:e>=0&&e<4294967295}function bw(t,e,r,s,a,n,c,f){if(typeof e=="string")return qTt(e,r,s,t,a,n,c,f);if(GY(e)){if(e.length===0)return null;let p;for(let h=0;hn?-1:n>a||r===-1?1:s===-1||t.length>e.length?-1:e.length>t.length?1:0}function WTt(t,e,r){if(typeof t=="string"||GY(t))return!0;if(typeof t!="object"||t===null)return!1;let s=zD(t),a=!1,n=0;for(let c=0;c=h.length&&ig(e,C)&&qxe(n,h)===1&&KY(h,"*")===E&&(n=h,c=sg(e,E,e.length-C.length))}}if(n){let p=r[n],h=bw(t,p,c,n,s,!0,!1,a);return h==null&&zY(e,t,s),h}zY(e,t,s)}function Wxe({name:t,base:e,conditions:r,readFileSyncFn:s}){if(t==="#"||dA(t,"#/")||ig(t,"/")){let c="is not a valid internal imports specifier name";throw new JY(t,c,(0,ra.fileURLToPath)(e))}let a,n=_xe(e,s);if(n.exists){a=(0,ra.pathToFileURL)(n.pjsonPath);let c=n.imports;if(c)if(jm(c,t)&&!VY(t,"*")){let f=bw(a,c[t],"",t,e,!1,!0,r);if(f!=null)return f}else{let f="",p,h=zD(c);for(let E=0;E=C.length&&ig(t,P)&&qxe(f,C)===1&&KY(C,"*")===S&&(f=C,p=sg(t,S,t.length-P.length))}}if(f){let E=c[f],C=bw(a,E,p,f,e,!0,!0,r);if(C!=null)return C}}}HTt(t,a,e)}bt();var VTt=new Set(["BUILTIN_NODE_RESOLUTION_FAILED","MISSING_DEPENDENCY","MISSING_PEER_DEPENDENCY","QUALIFIED_PATH_RESOLUTION_FAILED","UNDECLARED_DEPENDENCY"]);function ds(t,e,r={},s){s??=VTt.has(t)?"MODULE_NOT_FOUND":t;let a={configurable:!0,writable:!0,enumerable:!1};return Object.defineProperties(new Error(e),{code:{...a,value:s},pnpCode:{...a,value:t},data:{...a,value:r}})}function cf(t){return ue.normalize(ue.fromPortablePath(t))}var Jxe=et(Vxe());function zxe(t){return KTt(),XY[t]}var XY;function KTt(){XY||(XY={"--conditions":[],...Kxe(JTt()),...Kxe(process.execArgv)})}function Kxe(t){return(0,Jxe.default)({"--conditions":[String],"-C":"--conditions"},{argv:t,permissive:!0})}function JTt(){let t=[],e=zTt(process.env.NODE_OPTIONS||"",t);return t.length,e}function zTt(t,e){let r=[],s=!1,a=!0;for(let n=0;nparseInt(t,10)),Zxe=yl>19||yl===19&&ah>=2||yl===18&&ah>=13,lmr=yl===20&&ah<6||yl===19&&ah>=3,cmr=yl>19||yl===19&&ah>=6,umr=yl>=21||yl===20&&ah>=10||yl===18&&ah>=19,fmr=yl>=21||yl===20&&ah>=10||yl===18&&ah>=20,Amr=yl>=22;function Xxe(t){if(process.env.WATCH_REPORT_DEPENDENCIES&&process.send)if(t=t.map(e=>ue.fromPortablePath(fo.resolveVirtual(ue.toPortablePath(e)))),Zxe)process.send({"watch:require":t});else for(let e of t)process.send({"watch:require":e})}function eV(t,e){let r=Number(process.env.PNP_ALWAYS_WARN_ON_FALLBACK)>0,s=Number(process.env.PNP_DEBUG_LEVEL),a=/^(?![a-zA-Z]:[\\/]|\\\\|\.{0,2}(?:\/|$))((?:node:)?(?:@[^/]+\/)?[^/]+)\/*(.*|)$/,n=/^(\/|\.{1,2}(\/|$))/,c=/\/$/,f=/^\.{0,2}\//,p={name:null,reference:null},h=[],E=new Set;if(t.enableTopLevelFallback===!0&&h.push(p),e.compatibilityMode!==!1)for(let Fe of["react-scripts","gatsby"]){let Ne=t.packageRegistry.get(Fe);if(Ne)for(let Pe of Ne.keys()){if(Pe===null)throw new Error("Assertion failed: This reference shouldn't be null");h.push({name:Fe,reference:Pe})}}let{ignorePattern:C,packageRegistry:S,packageLocatorsByLocations:P}=t;function I(Fe,Ne){return{fn:Fe,args:Ne,error:null,result:null}}function R(Fe){let Ne=process.stderr?.hasColors?.()??process.stdout.isTTY,Pe=(it,_e)=>`\x1B[${it}m${_e}\x1B[0m`,Ye=Fe.error;console.error(Ye?Pe("31;1",`\u2716 ${Fe.error?.message.replace(/\n.*/s,"")}`):Pe("33;1","\u203C Resolution")),Fe.args.length>0&&console.error();for(let it of Fe.args)console.error(` ${Pe("37;1","In \u2190")} ${(0,$Y.inspect)(it,{colors:Ne,compact:!0})}`);Fe.result&&(console.error(),console.error(` ${Pe("37;1","Out \u2192")} ${(0,$Y.inspect)(Fe.result,{colors:Ne,compact:!0})}`));let ke=new Error().stack.match(/(?<=^ +)at.*/gm)?.slice(2)??[];if(ke.length>0){console.error();for(let it of ke)console.error(` ${Pe("38;5;244",it)}`)}console.error()}function N(Fe,Ne){if(e.allowDebug===!1)return Ne;if(Number.isFinite(s)){if(s>=2)return(...Pe)=>{let Ye=I(Fe,Pe);try{return Ye.result=Ne(...Pe)}catch(ke){throw Ye.error=ke}finally{R(Ye)}};if(s>=1)return(...Pe)=>{try{return Ne(...Pe)}catch(Ye){let ke=I(Fe,Pe);throw ke.error=Ye,R(ke),Ye}}}return Ne}function U(Fe){let Ne=g(Fe);if(!Ne)throw ds("INTERNAL","Couldn't find a matching entry in the dependency tree for the specified parent (this is probably an internal error)");return Ne}function W(Fe){if(Fe.name===null)return!0;for(let Ne of t.dependencyTreeRoots)if(Ne.name===Fe.name&&Ne.reference===Fe.reference)return!0;return!1}let te=new Set(["node","require",...zxe("--conditions")]);function ie(Fe,Ne=te,Pe){let Ye=fe(K.join(Fe,"internal.js"),{resolveIgnored:!0,includeDiscardFromLookup:!0});if(Ye===null)throw ds("INTERNAL",`The locator that owns the "${Fe}" path can't be found inside the dependency tree (this is probably an internal error)`);let{packageLocation:ke}=U(Ye),it=K.join(ke,Er.manifest);if(!e.fakeFs.existsSync(it))return null;let _e=JSON.parse(e.fakeFs.readFileSync(it,"utf8"));if(_e.exports==null)return null;let x=K.contains(ke,Fe);if(x===null)throw ds("INTERNAL","unqualifiedPath doesn't contain the packageLocation (this is probably an internal error)");x!=="."&&!f.test(x)&&(x=`./${x}`);try{let w=Gxe({packageJSONUrl:(0,qm.pathToFileURL)(ue.fromPortablePath(it)),packageSubpath:x,exports:_e.exports,base:Pe?(0,qm.pathToFileURL)(ue.fromPortablePath(Pe)):null,conditions:Ne});return ue.toPortablePath((0,qm.fileURLToPath)(w))}catch(w){throw ds("EXPORTS_RESOLUTION_FAILED",w.message,{unqualifiedPath:cf(Fe),locator:Ye,pkgJson:_e,subpath:cf(x),conditions:Ne},w.code)}}function Ae(Fe,Ne,{extensions:Pe}){let Ye;try{Ne.push(Fe),Ye=e.fakeFs.statSync(Fe)}catch{}if(Ye&&!Ye.isDirectory())return e.fakeFs.realpathSync(Fe);if(Ye&&Ye.isDirectory()){let ke;try{ke=JSON.parse(e.fakeFs.readFileSync(K.join(Fe,Er.manifest),"utf8"))}catch{}let it;if(ke&&ke.main&&(it=K.resolve(Fe,ke.main)),it&&it!==Fe){let _e=Ae(it,Ne,{extensions:Pe});if(_e!==null)return _e}}for(let ke=0,it=Pe.length;ke{let x=JSON.stringify(_e.name);if(Ye.has(x))return;Ye.add(x);let w=we(_e);for(let b of w)if(U(b).packagePeers.has(Fe))ke(b);else{let F=Pe.get(b.name);typeof F>"u"&&Pe.set(b.name,F=new Set),F.add(b.reference)}};ke(Ne);let it=[];for(let _e of[...Pe.keys()].sort())for(let x of[...Pe.get(_e)].sort())it.push({name:_e,reference:x});return it}function fe(Fe,{resolveIgnored:Ne=!1,includeDiscardFromLookup:Pe=!1}={}){if(pe(Fe)&&!Ne)return null;let Ye=K.relative(t.basePath,Fe);Ye.match(n)||(Ye=`./${Ye}`),Ye.endsWith("/")||(Ye=`${Ye}/`);do{let ke=P.get(Ye);if(typeof ke>"u"||ke.discardFromLookup&&!Pe){Ye=Ye.substring(0,Ye.lastIndexOf("/",Ye.length-2)+1);continue}return ke.locator}while(Ye!=="");return null}function se(Fe){try{return e.fakeFs.readFileSync(ue.toPortablePath(Fe),"utf8")}catch(Ne){if(Ne.code==="ENOENT")return;throw Ne}}function X(Fe,Ne,{considerBuiltins:Pe=!0}={}){if(Fe.startsWith("#"))throw new Error("resolveToUnqualified can not handle private import mappings");if(Fe==="pnpapi")return ue.toPortablePath(e.pnpapiResolution);if(Pe&&(0,lh.isBuiltin)(Fe))return null;let Ye=cf(Fe),ke=Ne&&cf(Ne);if(Ne&&pe(Ne)&&(!K.isAbsolute(Fe)||fe(Fe)===null)){let x=me(Fe,Ne);if(x===!1)throw ds("BUILTIN_NODE_RESOLUTION_FAILED",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer was explicitely ignored by the regexp) + +Require request: "${Ye}" +Required by: ${ke} +`,{request:Ye,issuer:ke});return ue.toPortablePath(x)}let it,_e=Fe.match(a);if(_e){if(!Ne)throw ds("API_ERROR","The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute",{request:Ye,issuer:ke});let[,x,w]=_e,b=fe(Ne);if(!b){let Te=me(Fe,Ne);if(Te===!1)throw ds("BUILTIN_NODE_RESOLUTION_FAILED",`The builtin node resolution algorithm was unable to resolve the requested module (it didn't go through the pnp resolver because the issuer doesn't seem to be part of the Yarn-managed dependency tree). + +Require path: "${Ye}" +Required by: ${ke} +`,{request:Ye,issuer:ke});return ue.toPortablePath(Te)}let F=U(b).packageDependencies.get(x),z=null;if(F==null&&b.name!==null){let Te=t.fallbackExclusionList.get(b.name);if(!Te||!Te.has(b.reference)){for(let Et=0,qt=h.length;EtW(lt))?Z=ds("MISSING_PEER_DEPENDENCY",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by your application; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ye?` (via "${Ye}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) +${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference} +`).join("")} +`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te}):Z=ds("MISSING_PEER_DEPENDENCY",`${b.name} tried to access ${x} (a peer dependency) but it isn't provided by its ancestors; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ye?` (via "${Ye}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) + +${Te.map(lt=>`Ancestor breaking the chain: ${lt.name}@${lt.reference} +`).join("")} +`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x,brokenAncestors:Te})}else F===void 0&&(!Pe&&(0,lh.isBuiltin)(Fe)?W(b)?Z=ds("UNDECLARED_DEPENDENCY",`Your application tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in your dependencies, this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ye?` (via "${Ye}")`:""} +Required by: ${ke} +`,{request:Ye,issuer:ke,dependencyName:x}):Z=ds("UNDECLARED_DEPENDENCY",`${b.name} tried to access ${x}. While this module is usually interpreted as a Node builtin, your resolver is running inside a non-Node resolution context where such builtins are ignored. Since ${x} isn't otherwise declared in ${b.name}'s dependencies, this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ye?` (via "${Ye}")`:""} +Required by: ${ke} +`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}):W(b)?Z=ds("UNDECLARED_DEPENDENCY",`Your application tried to access ${x}, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ye?` (via "${Ye}")`:""} +Required by: ${ke} +`,{request:Ye,issuer:ke,dependencyName:x}):Z=ds("UNDECLARED_DEPENDENCY",`${b.name} tried to access ${x}, but it isn't declared in its dependencies; this makes the require call ambiguous and unsound. + +Required package: ${x}${x!==Ye?` (via "${Ye}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) +`,{request:Ye,issuer:ke,issuerLocator:Object.assign({},b),dependencyName:x}));if(F==null){if(z===null||Z===null)throw Z||new Error("Assertion failed: Expected an error to have been set");F=z;let Te=Z.message.replace(/\n.*/g,"");Z.message=Te,!E.has(Te)&&s!==0&&(E.add(Te),process.emitWarning(Z))}let $=Array.isArray(F)?{name:F[0],reference:F[1]}:{name:x,reference:F},oe=U($);if(!oe.packageLocation)throw ds("MISSING_DEPENDENCY",`A dependency seems valid but didn't get installed for some reason. This might be caused by a partial install, such as dev vs prod. + +Required package: ${$.name}@${$.reference}${$.name!==Ye?` (via "${Ye}")`:""} +Required by: ${b.name}@${b.reference} (via ${ke}) +`,{request:Ye,issuer:ke,dependencyLocator:Object.assign({},$)});let xe=oe.packageLocation;w?it=K.join(xe,w):it=xe}else if(K.isAbsolute(Fe))it=K.normalize(Fe);else{if(!Ne)throw ds("API_ERROR","The resolveToUnqualified function must be called with a valid issuer when the path isn't a builtin nor absolute",{request:Ye,issuer:ke});let x=K.resolve(Ne);Ne.match(c)?it=K.normalize(K.join(x,Fe)):it=K.normalize(K.join(K.dirname(x),Fe))}return K.normalize(it)}function De(Fe,Ne,Pe=te,Ye){if(n.test(Fe))return Ne;let ke=ie(Ne,Pe,Ye);return ke?K.normalize(ke):Ne}function Re(Fe,{extensions:Ne=Object.keys(lh.Module._extensions)}={}){let Pe=[],Ye=Ae(Fe,Pe,{extensions:Ne});if(Ye)return K.normalize(Ye);{Xxe(Pe.map(_e=>ue.fromPortablePath(_e)));let ke=cf(Fe),it=fe(Fe);if(it){let{packageLocation:_e}=U(it),x=!0;try{e.fakeFs.accessSync(_e)}catch(w){if(w?.code==="ENOENT")x=!1;else{let b=(w?.message??w??"empty exception thrown").replace(/^[A-Z]/,y=>y.toLowerCase());throw ds("QUALIFIED_PATH_RESOLUTION_FAILED",`Required package exists but could not be accessed (${b}). + +Missing package: ${it.name}@${it.reference} +Expected package location: ${cf(_e)} +`,{unqualifiedPath:ke,extensions:Ne})}}if(!x){let w=_e.includes("/unplugged/")?"Required unplugged package missing from disk. This may happen when switching branches without running installs (unplugged packages must be fully materialized on disk to work).":"Required package missing from disk. If you keep your packages inside your repository then restarting the Node process may be enough. Otherwise, try to run an install first.";throw ds("QUALIFIED_PATH_RESOLUTION_FAILED",`${w} + +Missing package: ${it.name}@${it.reference} +Expected package location: ${cf(_e)} +`,{unqualifiedPath:ke,extensions:Ne})}}throw ds("QUALIFIED_PATH_RESOLUTION_FAILED",`Qualified path resolution failed: we looked for the following paths, but none could be accessed. + +Source path: ${ke} +${Pe.map(_e=>`Not found: ${cf(_e)} +`).join("")}`,{unqualifiedPath:ke,extensions:Ne})}}function gt(Fe,Ne,Pe){if(!Ne)throw new Error("Assertion failed: An issuer is required to resolve private import mappings");let Ye=Wxe({name:Fe,base:(0,qm.pathToFileURL)(ue.fromPortablePath(Ne)),conditions:Pe.conditions??te,readFileSyncFn:se});if(Ye instanceof URL)return Re(ue.toPortablePath((0,qm.fileURLToPath)(Ye)),{extensions:Pe.extensions});if(Ye.startsWith("#"))throw new Error("Mapping from one private import to another isn't allowed");return j(Ye,Ne,Pe)}function j(Fe,Ne,Pe={}){try{if(Fe.startsWith("#"))return gt(Fe,Ne,Pe);let{considerBuiltins:Ye,extensions:ke,conditions:it}=Pe,_e=X(Fe,Ne,{considerBuiltins:Ye});if(Fe==="pnpapi")return _e;if(_e===null)return null;let x=()=>Ne!==null?pe(Ne):!1,w=(!Ye||!(0,lh.isBuiltin)(Fe))&&!x()?De(Fe,_e,it,Ne):_e;return Re(w,{extensions:ke})}catch(Ye){throw Object.hasOwn(Ye,"pnpCode")&&Object.assign(Ye.data,{request:cf(Fe),issuer:Ne&&cf(Ne)}),Ye}}function rt(Fe){let Ne=K.normalize(Fe),Pe=fo.resolveVirtual(Ne);return Pe!==Ne?Pe:null}return{VERSIONS:Be,topLevel:Ce,getLocator:(Fe,Ne)=>Array.isArray(Ne)?{name:Ne[0],reference:Ne[1]}:{name:Fe,reference:Ne},getDependencyTreeRoots:()=>[...t.dependencyTreeRoots],getAllLocators(){let Fe=[];for(let[Ne,Pe]of S)for(let Ye of Pe.keys())Ne!==null&&Ye!==null&&Fe.push({name:Ne,reference:Ye});return Fe},getPackageInformation:Fe=>{let Ne=g(Fe);if(Ne===null)return null;let Pe=ue.fromPortablePath(Ne.packageLocation);return{...Ne,packageLocation:Pe}},findPackageLocator:Fe=>fe(ue.toPortablePath(Fe)),resolveToUnqualified:N("resolveToUnqualified",(Fe,Ne,Pe)=>{let Ye=Ne!==null?ue.toPortablePath(Ne):null,ke=X(ue.toPortablePath(Fe),Ye,Pe);return ke===null?null:ue.fromPortablePath(ke)}),resolveUnqualified:N("resolveUnqualified",(Fe,Ne)=>ue.fromPortablePath(Re(ue.toPortablePath(Fe),Ne))),resolveRequest:N("resolveRequest",(Fe,Ne,Pe)=>{let Ye=Ne!==null?ue.toPortablePath(Ne):null,ke=j(ue.toPortablePath(Fe),Ye,Pe);return ke===null?null:ue.fromPortablePath(ke)}),resolveVirtual:N("resolveVirtual",Fe=>{let Ne=rt(ue.toPortablePath(Fe));return Ne!==null?ue.fromPortablePath(Ne):null})}}bt();var $xe=(t,e,r)=>{let s=KD(t),a=qY(s,{basePath:e}),n=ue.join(e,Er.pnpCjs);return eV(a,{fakeFs:r,pnpapiResolution:n})};var rV=et(tke());Wt();var mA={};Vt(mA,{checkManifestCompatibility:()=>rke,extractBuildRequest:()=>hN,getExtractHint:()=>nV,hasBindingGyp:()=>iV});Ve();bt();function rke(t){return q.isPackageCompatible(t,As.getArchitectureSet())}function hN(t,e,r,{configuration:s}){let a=[];for(let n of["preinstall","install","postinstall"])e.manifest.scripts.has(n)&&a.push({type:0,script:n});return!e.manifest.scripts.has("install")&&e.misc.hasBindingGyp&&a.push({type:1,script:"node-gyp rebuild"}),a.length===0?null:t.linkType!=="HARD"?{skipped:!0,explain:n=>n.reportWarningOnce(6,`${q.prettyLocator(s,t)} lists build scripts, but is referenced through a soft link. Soft links don't support build scripts, so they'll be ignored.`)}:r&&r.built===!1?{skipped:!0,explain:n=>n.reportInfoOnce(5,`${q.prettyLocator(s,t)} lists build scripts, but its build has been explicitly disabled through configuration.`)}:!s.get("enableScripts")&&!r.built?{skipped:!0,explain:n=>n.reportWarningOnce(4,`${q.prettyLocator(s,t)} lists build scripts, but all build scripts have been disabled.`)}:rke(t)?{skipped:!1,directives:a}:{skipped:!0,explain:n=>n.reportWarningOnce(76,`${q.prettyLocator(s,t)} The ${As.getArchitectureName()} architecture is incompatible with this package, build skipped.`)}}var XTt=new Set([".exe",".bin",".h",".hh",".hpp",".c",".cc",".cpp",".java",".jar",".node"]);function nV(t){return t.packageFs.getExtractHint({relevantExtensions:XTt})}function iV(t){let e=K.join(t.prefixPath,"binding.gyp");return t.packageFs.existsSync(e)}var nb={};Vt(nb,{getUnpluggedPath:()=>rb});Ve();bt();function rb(t,{configuration:e}){return K.resolve(e.get("pnpUnpluggedFolder"),q.slugifyLocator(t))}var $Tt=new Set([q.makeIdent(null,"open").identHash,q.makeIdent(null,"opn").identHash]),og=class{constructor(){this.mode="strict";this.pnpCache=new Map}getCustomDataKey(){return JSON.stringify({name:"PnpLinker",version:2})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the PnP linker to be enabled");let s=ag(r.project).cjs;if(!le.existsSync(s))throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let a=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})),n={name:q.stringifyIdent(e),reference:e.reference},c=a.getPackageInformation(n);if(!c)throw new nt(`Couldn't find ${q.prettyLocator(r.project.configuration,e)} in the currently installed PnP map - running an install might help`);return ue.toPortablePath(c.packageLocation)}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=ag(r.project).cjs;if(!le.existsSync(s))return null;let n=je.getFactoryWithDefault(this.pnpCache,s,()=>je.dynamicRequire(s,{cachingStrategy:je.CachingStrategy.FsTime})).findPackageLocator(ue.fromPortablePath(e));return n?q.makeLocator(q.parseIdent(n.name),n.reference):null}makeInstaller(e){return new Gm(e)}isEnabled(e){return!(e.project.configuration.get("nodeLinker")!=="pnp"||e.project.configuration.get("pnpMode")!==this.mode)}},Gm=class{constructor(e){this.opts=e;this.mode="strict";this.asyncActions=new je.AsyncActions(10);this.packageRegistry=new Map;this.virtualTemplates=new Map;this.isESMLoaderRequired=!1;this.customData={store:new Map};this.unpluggedPaths=new Set;this.opts=e}attachCustomData(e){this.customData=e}async installPackage(e,r,s){let a=q.stringifyIdent(e),n=e.reference,c=!!this.opts.project.tryWorkspaceByLocator(e),f=q.isVirtualLocator(e),p=e.peerDependencies.size>0&&!f,h=!p&&!c,E=!p&&e.linkType!=="SOFT",C,S;if(h||E){let te=f?q.devirtualizeLocator(e):e;C=this.customData.store.get(te.locatorHash),typeof C>"u"&&(C=await eRt(r),e.linkType==="HARD"&&this.customData.store.set(te.locatorHash,C)),C.manifest.type==="module"&&(this.isESMLoaderRequired=!0),S=this.opts.project.getDependencyMeta(te,e.version)}let P=h?hN(e,C,S,{configuration:this.opts.project.configuration}):null,I=E?await this.unplugPackageIfNeeded(e,C,r,S,s):r.packageFs;if(K.isAbsolute(r.prefixPath))throw new Error(`Assertion failed: Expected the prefix path (${r.prefixPath}) to be relative to the parent`);let R=K.resolve(I.getRealPath(),r.prefixPath),N=sV(this.opts.project.cwd,R),U=new Map,W=new Set;if(f){for(let te of e.peerDependencies.values())U.set(q.stringifyIdent(te),null),W.add(q.stringifyIdent(te));if(!c){let te=q.devirtualizeLocator(e);this.virtualTemplates.set(te.locatorHash,{location:sV(this.opts.project.cwd,fo.resolveVirtual(R)),locator:te})}}return je.getMapWithDefault(this.packageRegistry,a).set(n,{packageLocation:N,packageDependencies:U,packagePeers:W,linkType:e.linkType,discardFromLookup:r.discardFromLookup||!1}),{packageLocation:R,buildRequest:P}}async attachInternalDependencies(e,r){let s=this.getPackageInformation(e);for(let[a,n]of r){let c=q.areIdentsEqual(a,n)?n.reference:[q.stringifyIdent(n),n.reference];s.packageDependencies.set(q.stringifyIdent(a),c)}}async attachExternalDependents(e,r){for(let s of r)this.getDiskInformation(s).packageDependencies.set(q.stringifyIdent(e),e.reference)}async finalizeInstall(){if(this.opts.project.configuration.get("pnpMode")!==this.mode)return;let e=ag(this.opts.project);if(this.isEsmEnabled()||await le.removePromise(e.esmLoader),this.opts.project.configuration.get("nodeLinker")!=="pnp"){await le.removePromise(e.cjs),await le.removePromise(e.data),await le.removePromise(e.esmLoader),await le.removePromise(this.opts.project.configuration.get("pnpUnpluggedFolder"));return}for(let{locator:C,location:S}of this.virtualTemplates.values())je.getMapWithDefault(this.packageRegistry,q.stringifyIdent(C)).set(C.reference,{packageLocation:S,packageDependencies:new Map,packagePeers:new Set,linkType:"SOFT",discardFromLookup:!1});let r=this.opts.project.configuration.get("pnpFallbackMode"),s=this.opts.project.workspaces.map(({anchoredLocator:C})=>({name:q.stringifyIdent(C),reference:C.reference})),a=r!=="none",n=[],c=new Map,f=je.buildIgnorePattern([".yarn/sdks/**",...this.opts.project.configuration.get("pnpIgnorePatterns")]),p=this.packageRegistry,h=this.opts.project.configuration.get("pnpShebang"),E=this.opts.project.configuration.get("pnpZipBackend");if(r==="dependencies-only")for(let C of this.opts.project.storedPackages.values())this.opts.project.tryWorkspaceByLocator(C)&&n.push({name:q.stringifyIdent(C),reference:C.reference});return await this.asyncActions.wait(),await this.finalizeInstallWithPnp({dependencyTreeRoots:s,enableTopLevelFallback:a,fallbackExclusionList:n,fallbackPool:c,ignorePattern:f,pnpZipBackend:E,packageRegistry:p,shebang:h}),{customData:this.customData}}async transformPnpSettings(e){}isEsmEnabled(){if(this.opts.project.configuration.sources.has("pnpEnableEsmLoader"))return this.opts.project.configuration.get("pnpEnableEsmLoader");if(this.isESMLoaderRequired)return!0;for(let e of this.opts.project.workspaces)if(e.manifest.type==="module")return!0;return!1}async finalizeInstallWithPnp(e){let r=ag(this.opts.project),s=await this.locateNodeModules(e.ignorePattern);if(s.length>0){this.opts.report.reportWarning(31,"One or more node_modules have been detected and will be removed. This operation may take some time.");for(let n of s)await le.removePromise(n)}if(await this.transformPnpSettings(e),this.opts.project.configuration.get("pnpEnableInlining")){let n=xxe(e);await le.changeFilePromise(r.cjs,n,{automaticNewlines:!0,mode:493}),await le.removePromise(r.data)}else{let{dataFile:n,loaderFile:c}=kxe(e);await le.changeFilePromise(r.cjs,c,{automaticNewlines:!0,mode:493}),await le.changeFilePromise(r.data,n,{automaticNewlines:!0,mode:420})}this.isEsmEnabled()&&(this.opts.report.reportWarning(0,"ESM support for PnP uses the experimental loader API and is therefore experimental"),await le.changeFilePromise(r.esmLoader,(0,rV.default)(),{automaticNewlines:!0,mode:420}));let a=this.opts.project.configuration.get("pnpUnpluggedFolder");if(this.unpluggedPaths.size===0)await le.removePromise(a);else for(let n of await le.readdirPromise(a)){let c=K.resolve(a,n);this.unpluggedPaths.has(c)||await le.removePromise(c)}}async locateNodeModules(e){let r=[],s=e?new RegExp(e):null;for(let a of this.opts.project.workspaces){let n=K.join(a.cwd,"node_modules");if(s&&s.test(K.relative(this.opts.project.cwd,a.cwd))||!le.existsSync(n))continue;let c=await le.readdirPromise(n,{withFileTypes:!0}),f=c.filter(p=>!p.isDirectory()||p.name===".bin"||!p.name.startsWith("."));if(f.length===c.length)r.push(n);else for(let p of f)r.push(K.join(n,p.name))}return r}async unplugPackageIfNeeded(e,r,s,a,n){return this.shouldBeUnplugged(e,r,a)?this.unplugPackage(e,s,n):s.packageFs}shouldBeUnplugged(e,r,s){return typeof s.unplugged<"u"?s.unplugged:$Tt.has(e.identHash)||e.conditions!=null?!0:r.manifest.preferUnplugged!==null?r.manifest.preferUnplugged:!!(hN(e,r,s,{configuration:this.opts.project.configuration})?.skipped===!1||r.misc.extractHint)}async unplugPackage(e,r,s){let a=rb(e,{configuration:this.opts.project.configuration});return this.opts.project.disabledLocators.has(e.locatorHash)?new Hf(a,{baseFs:r.packageFs,pathUtils:K}):(this.unpluggedPaths.add(a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{let n=K.join(a,r.prefixPath,".ready");await le.existsPromise(n)||(this.opts.project.storedBuildState.delete(e.locatorHash),await le.mkdirPromise(a,{recursive:!0}),await le.copyPromise(a,vt.dot,{baseFs:r.packageFs,overwrite:!1}),await le.writeFilePromise(n,""))})),new Sn(a))}getPackageInformation(e){let r=q.stringifyIdent(e),s=e.reference,a=this.packageRegistry.get(r);if(!a)throw new Error(`Assertion failed: The package information store should have been available (for ${q.prettyIdent(this.opts.project.configuration,e)})`);let n=a.get(s);if(!n)throw new Error(`Assertion failed: The package information should have been available (for ${q.prettyLocator(this.opts.project.configuration,e)})`);return n}getDiskInformation(e){let r=je.getMapWithDefault(this.packageRegistry,"@@disk"),s=sV(this.opts.project.cwd,e);return je.getFactoryWithDefault(r,s,()=>({packageLocation:s,packageDependencies:new Map,packagePeers:new Set,linkType:"SOFT",discardFromLookup:!1}))}};function sV(t,e){let r=K.relative(t,e);return r.match(/^\.{0,2}\//)||(r=`./${r}`),r.replace(/\/?$/,"/")}async function eRt(t){let e=await Ht.tryFind(t.prefixPath,{baseFs:t.packageFs})??new Ht,r=new Set(["preinstall","install","postinstall"]);for(let s of e.scripts.keys())r.has(s)||e.scripts.delete(s);return{manifest:{scripts:e.scripts,preferUnplugged:e.preferUnplugged,type:e.type},misc:{extractHint:nV(t),hasBindingGyp:iV(t)}}}Ve();Ve();Wt();var nke=et(Sa());var Pw=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Unplug direct dependencies from the entire project"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Unplug both direct and transitive dependencies"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.patterns=ge.Rest()}static{this.paths=[["unplug"]]}static{this.usage=ot.Usage({description:"force the unpacking of a list of packages",details:"\n This command will add the selectors matching the specified patterns to the list of packages that must be unplugged when installed.\n\n A package being unplugged means that instead of being referenced directly through its archive, it will be unpacked at install time in the directory configured via `pnpUnpluggedFolder`. Note that unpacking packages this way is generally not recommended because it'll make it harder to store your packages within the repository. However, it's a good approach to quickly and safely debug some packages, and can even sometimes be required depending on the context (for example when the package contains shellscripts).\n\n Running the command will set a persistent flag inside your top-level `package.json`, in the `dependenciesMeta` field. As such, to undo its effects, you'll need to revert the changes made to the manifest and run `yarn install` to apply the modification.\n\n By default, only direct dependencies from the current workspace are affected. If `-A,--all` is set, direct dependencies from the entire project are affected. Using the `-R,--recursive` flag will affect transitive dependencies as well as direct ones.\n\n This command accepts glob patterns inside the scope and name components (not the range). Make sure to escape the patterns to prevent your own shell from trying to expand them.\n ",examples:[["Unplug the lodash dependency from the active workspace","yarn unplug lodash"],["Unplug all instances of lodash referenced by any workspace","yarn unplug lodash -A"],["Unplug all instances of lodash referenced by the active workspace and its dependencies","yarn unplug lodash -R"],["Unplug all instances of lodash, anywhere","yarn unplug lodash -AR"],["Unplug one specific version of lodash","yarn unplug lodash@1.2.3"],["Unplug all packages with the `@babel` scope","yarn unplug '@babel/*'"],["Unplug all packages (only for testing, not recommended)","yarn unplug -R '*'"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);if(r.get("nodeLinker")!=="pnp")throw new nt("This command can only be used if the `nodeLinker` option is set to `pnp`");await s.restoreInstallState();let c=new Set(this.patterns),f=this.patterns.map(P=>{let I=q.parseDescriptor(P),R=I.range!=="unknown"?I:q.makeDescriptor(I,"*");if(!Or.validRange(R.range))throw new nt(`The range of the descriptor patterns must be a valid semver range (${q.prettyDescriptor(r,R)})`);return N=>{let U=q.stringifyIdent(N);return!nke.default.isMatch(U,q.stringifyIdent(R))||N.version&&!Or.satisfiesWithPrereleases(N.version,R.range)?!1:(c.delete(P),!0)}}),p=()=>{let P=[];for(let I of s.storedPackages.values())!s.tryWorkspaceByLocator(I)&&!q.isVirtualLocator(I)&&f.some(R=>R(I))&&P.push(I);return P},h=P=>{let I=new Set,R=[],N=(U,W)=>{if(I.has(U.locatorHash))return;let te=!!s.tryWorkspaceByLocator(U);if(!(W>0&&!this.recursive&&te)&&(I.add(U.locatorHash),!s.tryWorkspaceByLocator(U)&&f.some(ie=>ie(U))&&R.push(U),!(W>0&&!this.recursive)))for(let ie of U.dependencies.values()){let Ae=s.storedResolutions.get(ie.descriptorHash);if(!Ae)throw new Error("Assertion failed: The resolution should have been registered");let ce=s.storedPackages.get(Ae);if(!ce)throw new Error("Assertion failed: The package should have been registered");N(ce,W+1)}};for(let U of P)N(U.anchoredPackage,0);return R},E,C;if(this.all&&this.recursive?(E=p(),C="the project"):this.all?(E=h(s.workspaces),C="any workspace"):(E=h([a]),C="this workspace"),c.size>1)throw new nt(`Patterns ${he.prettyList(r,c,he.Type.CODE)} don't match any packages referenced by ${C}`);if(c.size>0)throw new nt(`Pattern ${he.prettyList(r,c,he.Type.CODE)} doesn't match any packages referenced by ${C}`);E=je.sortMap(E,P=>q.stringifyLocator(P));let S=await Ot.start({configuration:r,stdout:this.context.stdout,json:this.json},async P=>{for(let I of E){let R=I.version??"unknown",N=s.topLevelWorkspace.manifest.ensureDependencyMeta(q.makeDescriptor(I,R));N.unplugged=!0,P.reportInfo(0,`Will unpack ${q.prettyLocator(r,I)} to ${he.pretty(r,rb(I,{configuration:r}),he.Type.PATH)}`),P.reportJson({locator:q.stringifyLocator(I),version:R})}await s.topLevelWorkspace.persistManifest(),this.json||P.reportSeparator()});return S.hasErrors()?S.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};var ag=t=>({cjs:K.join(t.cwd,Er.pnpCjs),data:K.join(t.cwd,Er.pnpData),esmLoader:K.join(t.cwd,Er.pnpEsmLoader)}),ske=t=>/\s/.test(t)?JSON.stringify(t):t;async function tRt(t,e,r){let s=/\s*--require\s+\S*\.pnp\.c?js\s*/g,a=/\s*--experimental-loader\s+\S*\.pnp\.loader\.mjs\s*/,n=(e.NODE_OPTIONS??"").replace(s," ").replace(a," ").trim();if(t.configuration.get("nodeLinker")!=="pnp"){e.NODE_OPTIONS=n||void 0;return}let c=ag(t),f=`--require ${ske(ue.fromPortablePath(c.cjs))}`;le.existsSync(c.esmLoader)&&(f=`${f} --experimental-loader ${(0,ike.pathToFileURL)(ue.fromPortablePath(c.esmLoader)).href}`),le.existsSync(c.cjs)&&(e.NODE_OPTIONS=n?`${f} ${n}`:f)}async function rRt(t,e){let r=ag(t);e(r.cjs),e(r.data),e(r.esmLoader),e(t.configuration.get("pnpUnpluggedFolder"))}var nRt={hooks:{populateYarnPaths:rRt,setupScriptEnvironment:tRt},configuration:{nodeLinker:{description:'The linker used for installing Node packages, one of: "pnp", "pnpm", or "node-modules"',type:"STRING",default:"pnp"},minizip:{description:"Whether Yarn should use minizip to extract archives",type:"BOOLEAN",default:!1},winLinkType:{description:"Whether Yarn should use Windows Junctions or symlinks when creating links on Windows.",type:"STRING",values:["junctions","symlinks"],default:"junctions"},pnpMode:{description:"If 'strict', generates standard PnP maps. If 'loose', merges them with the n_m resolution.",type:"STRING",default:"strict"},pnpShebang:{description:"String to prepend to the generated PnP script",type:"STRING",default:"#!/usr/bin/env node"},pnpIgnorePatterns:{description:"Array of glob patterns; files matching them will use the classic resolution",type:"STRING",default:[],isArray:!0},pnpZipBackend:{description:"Whether to use the experimental js implementation for the ZipFS",type:"STRING",values:["libzip","js"],default:"libzip"},pnpEnableEsmLoader:{description:"If true, Yarn will generate an ESM loader (`.pnp.loader.mjs`). If this is not explicitly set Yarn tries to automatically detect whether ESM support is required.",type:"BOOLEAN",default:!1},pnpEnableInlining:{description:"If true, the PnP data will be inlined along with the generated loader",type:"BOOLEAN",default:!0},pnpFallbackMode:{description:"If true, the generated PnP loader will follow the top-level fallback rule",type:"STRING",default:"dependencies-only"},pnpUnpluggedFolder:{description:"Folder where the unplugged packages must be stored",type:"ABSOLUTE_PATH",default:"./.yarn/unplugged"}},linkers:[og],commands:[Pw]},iRt=nRt;var pke=et(uke());Wt();var pV=et(ye("crypto")),hke=et(ye("fs")),gke=1,_i="node_modules",gN=".bin",dke=".yarn-state.yml",CRt=1e3,hV=(s=>(s.CLASSIC="classic",s.HARDLINKS_LOCAL="hardlinks-local",s.HARDLINKS_GLOBAL="hardlinks-global",s))(hV||{}),ib=class{constructor(){this.installStateCache=new Map}getCustomDataKey(){return JSON.stringify({name:"NodeModulesLinker",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the node-modules linker to be enabled");let s=r.project.tryWorkspaceByLocator(e);if(s)return s.cwd;let a=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await AV(r.project,{unrollAliases:!0}));if(a===null)throw new nt("Couldn't find the node_modules state file - running an install might help (findPackageLocation)");let n=a.locatorMap.get(q.stringifyLocator(e));if(!n){let p=new nt(`Couldn't find ${q.prettyLocator(r.project.configuration,e)} in the currently installed node_modules map - running an install might help`);throw p.code="LOCATOR_NOT_INSTALLED",p}let c=n.locations.sort((p,h)=>p.split(K.sep).length-h.split(K.sep).length),f=K.join(r.project.configuration.startingCwd,_i);return c.find(p=>K.contains(f,p))||n.locations[0]}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=await je.getFactoryWithDefault(this.installStateCache,r.project.cwd,async()=>await AV(r.project,{unrollAliases:!0}));if(s===null)return null;let{locationRoot:a,segments:n}=dN(K.resolve(e),{skipPrefix:r.project.cwd}),c=s.locationTree.get(a);if(!c)return null;let f=c.locator;for(let p of n){if(c=c.children.get(p),!c)break;f=c.locator||f}return q.parseLocator(f)}makeInstaller(e){return new fV(e)}isEnabled(e){return e.project.configuration.get("nodeLinker")==="node-modules"}},fV=class{constructor(e){this.opts=e;this.localStore=new Map;this.realLocatorChecksums=new Map;this.customData={store:new Map}}attachCustomData(e){this.customData=e}async installPackage(e,r){let s=K.resolve(r.packageFs.getRealPath(),r.prefixPath),a=this.customData.store.get(e.locatorHash);if(typeof a>"u"&&(a=await wRt(e,r),e.linkType==="HARD"&&this.customData.store.set(e.locatorHash,a)),!q.isPackageCompatible(e,this.opts.project.configuration.getSupportedArchitectures()))return{packageLocation:null,buildRequest:null};let n=new Map,c=new Set;n.has(q.stringifyIdent(e))||n.set(q.stringifyIdent(e),e.reference);let f=e;if(q.isVirtualLocator(e)){f=q.devirtualizeLocator(e);for(let E of e.peerDependencies.values())n.set(q.stringifyIdent(E),null),c.add(q.stringifyIdent(E))}let p={packageLocation:`${ue.fromPortablePath(s)}/`,packageDependencies:n,packagePeers:c,linkType:e.linkType,discardFromLookup:r.discardFromLookup??!1};this.localStore.set(e.locatorHash,{pkg:e,customPackageData:a,dependencyMeta:this.opts.project.getDependencyMeta(e,e.version),pnpNode:p});let h=r.checksum?r.checksum.substring(r.checksum.indexOf("/")+1):null;return this.realLocatorChecksums.set(f.locatorHash,h),{packageLocation:s,buildRequest:null}}async attachInternalDependencies(e,r){let s=this.localStore.get(e.locatorHash);if(typeof s>"u")throw new Error("Assertion failed: Expected information object to have been registered");for(let[a,n]of r){let c=q.areIdentsEqual(a,n)?n.reference:[q.stringifyIdent(n),n.reference];s.pnpNode.packageDependencies.set(q.stringifyIdent(a),c)}}async attachExternalDependents(e,r){throw new Error("External dependencies haven't been implemented for the node-modules linker")}async finalizeInstall(){if(this.opts.project.configuration.get("nodeLinker")!=="node-modules")return;let e=new fo({baseFs:new tA({maxOpenFiles:80,readOnlyArchives:!0})}),r=await AV(this.opts.project),s=this.opts.project.configuration.get("nmMode");(r===null||s!==r.nmMode)&&(this.opts.project.storedBuildState.clear(),r={locatorMap:new Map,binSymlinks:new Map,locationTree:new Map,nmMode:s,mtimeMs:0});let a=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get("nmHoistingLimits");try{P=je.validateEnum(WD,S.manifest.installConfig?.hoistingLimits??P)}catch{let I=q.prettyWorkspace(this.opts.project.configuration,S);this.opts.report.reportWarning(57,`${I}: Invalid 'installConfig.hoistingLimits' value. Expected one of ${Object.values(WD).join(", ")}, using default: "${P}"`)}return[S.relativeCwd,P]})),n=new Map(this.opts.project.workspaces.map(S=>{let P=this.opts.project.configuration.get("nmSelfReferences");return P=S.manifest.installConfig?.selfReferences??P,[S.relativeCwd,P]})),c={VERSIONS:{std:1},topLevel:{name:null,reference:null},getLocator:(S,P)=>Array.isArray(P)?{name:P[0],reference:P[1]}:{name:S,reference:P},getDependencyTreeRoots:()=>this.opts.project.workspaces.map(S=>{let P=S.anchoredLocator;return{name:q.stringifyIdent(P),reference:P.reference}}),getPackageInformation:S=>{let P=S.reference===null?this.opts.project.topLevelWorkspace.anchoredLocator:q.makeLocator(q.parseIdent(S.name),S.reference),I=this.localStore.get(P.locatorHash);if(typeof I>"u")throw new Error("Assertion failed: Expected the package reference to have been registered");return I.pnpNode},findPackageLocator:S=>{let P=this.opts.project.tryWorkspaceByCwd(ue.toPortablePath(S));if(P!==null){let I=P.anchoredLocator;return{name:q.stringifyIdent(I),reference:I.reference}}throw new Error("Assertion failed: Unimplemented")},resolveToUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveUnqualified:()=>{throw new Error("Assertion failed: Unimplemented")},resolveRequest:()=>{throw new Error("Assertion failed: Unimplemented")},resolveVirtual:S=>ue.fromPortablePath(fo.resolveVirtual(ue.toPortablePath(S)))},{tree:f,errors:p,preserveSymlinksRequired:h}=YD(c,{pnpifyFs:!1,validateExternalSoftLinks:!0,hoistingLimitsByCwd:a,project:this.opts.project,selfReferencesByCwd:n});if(!f){for(let{messageName:S,text:P}of p)this.opts.report.reportError(S,P);return}let E=HY(f);await PRt(r,E,{baseFs:e,project:this.opts.project,report:this.opts.report,realLocatorChecksums:this.realLocatorChecksums,loadManifest:async S=>{let P=q.parseLocator(S),I=this.localStore.get(P.locatorHash);if(typeof I>"u")throw new Error("Assertion failed: Expected the slot to exist");return I.customPackageData.manifest}});let C=[];for(let[S,P]of E.entries()){if(Eke(S))continue;let I=q.parseLocator(S),R=this.localStore.get(I.locatorHash);if(typeof R>"u")throw new Error("Assertion failed: Expected the slot to exist");if(this.opts.project.tryWorkspaceByLocator(R.pkg))continue;let N=mA.extractBuildRequest(R.pkg,R.customPackageData,R.dependencyMeta,{configuration:this.opts.project.configuration});N&&C.push({buildLocations:P.locations,locator:I,buildRequest:N})}return h&&this.opts.report.reportWarning(72,`The application uses portals and that's why ${he.pretty(this.opts.project.configuration,"--preserve-symlinks",he.Type.CODE)} Node option is required for launching it`),{customData:this.customData,records:C}}};async function wRt(t,e){let r=await Ht.tryFind(e.prefixPath,{baseFs:e.packageFs})??new Ht,s=new Set(["preinstall","install","postinstall"]);for(let a of r.scripts.keys())s.has(a)||r.scripts.delete(a);return{manifest:{bin:r.bin,scripts:r.scripts},misc:{hasBindingGyp:mA.hasBindingGyp(e)}}}async function BRt(t,e,r,s,{installChangedByUser:a}){let n="";n+=`# Warning: This file is automatically generated. Removing it is fine, but will +`,n+=`# cause your node_modules installation to become invalidated. +`,n+=` +`,n+=`__metadata: +`,n+=` version: ${gke} +`,n+=` nmMode: ${s.value} +`;let c=Array.from(e.keys()).sort(),f=q.stringifyLocator(t.topLevelWorkspace.anchoredLocator);for(let E of c){let C=e.get(E);n+=` +`,n+=`${JSON.stringify(E)}: +`,n+=` locations: +`;for(let S of C.locations){let P=K.contains(t.cwd,S);if(P===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=` - ${JSON.stringify(P)} +`}if(C.aliases.length>0){n+=` aliases: +`;for(let S of C.aliases)n+=` - ${JSON.stringify(S)} +`}if(E===f&&r.size>0){n+=` bin: +`;for(let[S,P]of r){let I=K.contains(t.cwd,S);if(I===null)throw new Error(`Assertion failed: Expected the path to be within the project (${S})`);n+=` ${JSON.stringify(I)}: +`;for(let[R,N]of P){let U=K.relative(K.join(S,_i),N);n+=` ${JSON.stringify(R)}: ${JSON.stringify(U)} +`}}}}let p=t.cwd,h=K.join(p,_i,dke);a&&await le.removePromise(h),await le.changeFilePromise(h,n,{automaticNewlines:!0})}async function AV(t,{unrollAliases:e=!1}={}){let r=t.cwd,s=K.join(r,_i,dke),a;try{a=await le.statPromise(s)}catch{}if(!a)return null;let n=ls(await le.readFilePromise(s,"utf8"));if(n.__metadata.version>gke)return null;let c=n.__metadata.nmMode||"classic",f=new Map,p=new Map;delete n.__metadata;for(let[h,E]of Object.entries(n)){let C=E.locations.map(P=>K.join(r,P)),S=E.bin;if(S)for(let[P,I]of Object.entries(S)){let R=K.join(r,ue.toPortablePath(P)),N=je.getMapWithDefault(p,R);for(let[U,W]of Object.entries(I))N.set(U,ue.toPortablePath([R,_i,W].join(K.sep)))}if(f.set(h,{target:vt.dot,linkType:"HARD",locations:C,aliases:E.aliases||[]}),e&&E.aliases)for(let P of E.aliases){let{scope:I,name:R}=q.parseLocator(h),N=q.makeLocator(q.makeIdent(I,R),P),U=q.stringifyLocator(N);f.set(U,{target:vt.dot,linkType:"HARD",locations:C,aliases:[]})}}return{locatorMap:f,binSymlinks:p,locationTree:mke(f,{skipPrefix:t.cwd}),nmMode:c,mtimeMs:a.mtimeMs}}var kw=async(t,e)=>{if(t.split(K.sep).indexOf(_i)<0)throw new Error(`Assertion failed: trying to remove dir that doesn't contain node_modules: ${t}`);try{let r;if(!e.innerLoop&&(r=await le.lstatPromise(t),!r.isDirectory()&&!r.isSymbolicLink()||r.isSymbolicLink()&&!e.isWorkspaceDir)){await le.unlinkPromise(t);return}let s=await le.readdirPromise(t,{withFileTypes:!0});for(let n of s){let c=K.join(t,n.name);n.isDirectory()?(n.name!==_i||e&&e.innerLoop)&&await kw(c,{innerLoop:!0,contentsOnly:!1}):await le.unlinkPromise(c)}let a=!e.innerLoop&&e.isWorkspaceDir&&r?.isSymbolicLink();!e.contentsOnly&&!a&&await le.rmdirPromise(t)}catch(r){if(r.code!=="ENOENT"&&r.code!=="ENOTEMPTY")throw r}},fke=4,dN=(t,{skipPrefix:e})=>{let r=K.contains(e,t);if(r===null)throw new Error(`Assertion failed: Writing attempt prevented to ${t} which is outside project root: ${e}`);let s=r.split(K.sep).filter(p=>p!==""),a=s.indexOf(_i),n=s.slice(0,a).join(K.sep),c=K.join(e,n),f=s.slice(a);return{locationRoot:c,segments:f}},mke=(t,{skipPrefix:e})=>{let r=new Map;if(t===null)return r;let s=()=>({children:new Map,linkType:"HARD"});for(let[a,n]of t.entries()){if(n.linkType==="SOFT"&&K.contains(e,n.target)!==null){let f=je.getFactoryWithDefault(r,n.target,s);f.locator=a,f.linkType=n.linkType}for(let c of n.locations){let{locationRoot:f,segments:p}=dN(c,{skipPrefix:e}),h=je.getFactoryWithDefault(r,f,s);for(let E=0;E{if(process.platform==="win32"&&r==="junctions"){let s;try{s=await le.lstatPromise(t)}catch{}if(!s||s.isDirectory()){await le.symlinkPromise(t,e,"junction");return}}await le.symlinkPromise(K.relative(K.dirname(e),t),e)};async function yke(t,e,r){let s=K.join(t,`${pV.default.randomBytes(16).toString("hex")}.tmp`);try{await le.writeFilePromise(s,r);try{await le.linkPromise(s,e)}catch{}}finally{await le.unlinkPromise(s)}}async function vRt({srcPath:t,dstPath:e,entry:r,globalHardlinksStore:s,baseFs:a,nmMode:n}){if(r.kind==="file"){if(n.value==="hardlinks-global"&&s&&r.digest){let f=K.join(s,r.digest.substring(0,2),`${r.digest.substring(2)}.dat`),p;try{let h=await le.statPromise(f);if(h&&(!r.mtimeMs||h.mtimeMs>r.mtimeMs||h.mtimeMs{await le.mkdirPromise(t,{recursive:!0});let f=async(E=vt.dot)=>{let C=K.join(e,E),S=await r.readdirPromise(C,{withFileTypes:!0}),P=new Map;for(let I of S){let R=K.join(E,I.name),N,U=K.join(C,I.name);if(I.isFile()){if(N={kind:"file",mode:(await r.lstatPromise(U)).mode},a.value==="hardlinks-global"){let W=await Nn.checksumFile(U,{baseFs:r,algorithm:"sha1"});N.digest=W}}else if(I.isDirectory())N={kind:"directory"};else if(I.isSymbolicLink())N={kind:"symlink",symlinkTo:await r.readlinkPromise(U)};else throw new Error(`Unsupported file type (file: ${U}, mode: 0o${await r.statSync(U).mode.toString(8).padStart(6,"0")})`);if(P.set(R,N),I.isDirectory()&&R!==_i){let W=await f(R);for(let[te,ie]of W)P.set(te,ie)}}return P},p;if(a.value==="hardlinks-global"&&s&&c){let E=K.join(s,c.substring(0,2),`${c.substring(2)}.json`);try{p=new Map(Object.entries(JSON.parse(await le.readFilePromise(E,"utf8"))))}catch{p=await f()}}else p=await f();let h=!1;for(let[E,C]of p){let S=K.join(e,E),P=K.join(t,E);if(C.kind==="directory")await le.mkdirPromise(P,{recursive:!0});else if(C.kind==="file"){let I=C.mtimeMs;await vRt({srcPath:S,dstPath:P,entry:C,nmMode:a,baseFs:r,globalHardlinksStore:s}),C.mtimeMs!==I&&(h=!0)}else C.kind==="symlink"&&await gV(K.resolve(K.dirname(P),C.symlinkTo),P,n)}if(a.value==="hardlinks-global"&&s&&h&&c){let E=K.join(s,c.substring(0,2),`${c.substring(2)}.json`);await le.removePromise(E),await yke(s,E,Buffer.from(JSON.stringify(Object.fromEntries(p))))}};function DRt(t,e,r,s){let a=new Map,n=new Map,c=new Map,f=!1,p=(h,E,C,S,P)=>{let I=!0,R=K.join(h,E),N=new Set;if(E===_i||E.startsWith("@")){let W;try{W=le.statSync(R)}catch{}I=!!W,W?W.mtimeMs>r?(f=!0,N=new Set(le.readdirSync(R))):N=new Set(C.children.get(E).children.keys()):f=!0;let te=e.get(h);if(te){let ie=K.join(h,_i,gN),Ae;try{Ae=le.statSync(ie)}catch{}if(!Ae)f=!0;else if(Ae.mtimeMs>r){f=!0;let ce=new Set(le.readdirSync(ie)),me=new Map;n.set(h,me);for(let[pe,Be]of te)ce.has(pe)&&me.set(pe,Be)}else n.set(h,te)}}else I=P.has(E);let U=C.children.get(E);if(I){let{linkType:W,locator:te}=U,ie={children:new Map,linkType:W,locator:te};if(S.children.set(E,ie),te){let Ae=je.getSetWithDefault(c,te);Ae.add(R),c.set(te,Ae)}for(let Ae of U.children.keys())p(R,Ae,U,ie,N)}else U.locator&&s.storedBuildState.delete(q.parseLocator(U.locator).locatorHash)};for(let[h,E]of t){let{linkType:C,locator:S}=E,P={children:new Map,linkType:C,locator:S};if(a.set(h,P),S){let I=je.getSetWithDefault(c,E.locator);I.add(h),c.set(E.locator,I)}E.children.has(_i)&&p(h,_i,E,P,new Set)}return{locationTree:a,binSymlinks:n,locatorLocations:c,installChangedByUser:f}}function Eke(t){let e=q.parseDescriptor(t);return q.isVirtualDescriptor(e)&&(e=q.devirtualizeDescriptor(e)),e.range.startsWith("link:")}async function bRt(t,e,r,{loadManifest:s}){let a=new Map;for(let[f,{locations:p}]of t){let h=Eke(f)?null:await s(f,p[0]),E=new Map;if(h)for(let[C,S]of h.bin){let P=K.join(p[0],S);S!==""&&le.existsSync(P)&&E.set(C,S)}a.set(f,E)}let n=new Map,c=(f,p,h)=>{let E=new Map,C=K.contains(r,f);if(h.locator&&C!==null){let S=a.get(h.locator);for(let[P,I]of S){let R=K.join(f,ue.toPortablePath(I));E.set(P,R)}for(let[P,I]of h.children){let R=K.join(f,P),N=c(R,R,I);N.size>0&&n.set(f,new Map([...n.get(f)||new Map,...N]))}}else for(let[S,P]of h.children){let I=c(K.join(f,S),p,P);for(let[R,N]of I)E.set(R,N)}return E};for(let[f,p]of e){let h=c(f,f,p);h.size>0&&n.set(f,new Map([...n.get(f)||new Map,...h]))}return n}var Ake=(t,e)=>{if(!t||!e)return t===e;let r=q.parseLocator(t);q.isVirtualLocator(r)&&(r=q.devirtualizeLocator(r));let s=q.parseLocator(e);return q.isVirtualLocator(s)&&(s=q.devirtualizeLocator(s)),q.areLocatorsEqual(r,s)};function dV(t){return K.join(t.get("globalFolder"),"store")}async function PRt(t,e,{baseFs:r,project:s,report:a,loadManifest:n,realLocatorChecksums:c}){let f=K.join(s.cwd,_i),{locationTree:p,binSymlinks:h,locatorLocations:E,installChangedByUser:C}=DRt(t.locationTree,t.binSymlinks,t.mtimeMs,s),S=mke(e,{skipPrefix:s.cwd}),P=[],I=async({srcDir:Be,dstDir:Ce,linkType:g,globalHardlinksStore:we,nmMode:Ee,windowsLinkType:fe,packageChecksum:se})=>{let X=(async()=>{try{g==="SOFT"?(await le.mkdirPromise(K.dirname(Ce),{recursive:!0}),await gV(K.resolve(Be),Ce,fe)):await SRt(Ce,Be,{baseFs:r,globalHardlinksStore:we,nmMode:Ee,windowsLinkType:fe,packageChecksum:se})}catch(De){throw De.message=`While persisting ${Be} -> ${Ce} ${De.message}`,De}finally{ie.tick()}})().then(()=>P.splice(P.indexOf(X),1));P.push(X),P.length>fke&&await Promise.race(P)},R=async(Be,Ce,g)=>{let we=(async()=>{let Ee=async(fe,se,X)=>{try{X.innerLoop||await le.mkdirPromise(se,{recursive:!0});let De=await le.readdirPromise(fe,{withFileTypes:!0});for(let Re of De){if(!X.innerLoop&&Re.name===gN)continue;let gt=K.join(fe,Re.name),j=K.join(se,Re.name);Re.isDirectory()?(Re.name!==_i||X&&X.innerLoop)&&(await le.mkdirPromise(j,{recursive:!0}),await Ee(gt,j,{...X,innerLoop:!0})):me.value==="hardlinks-local"||me.value==="hardlinks-global"?await le.linkPromise(gt,j):await le.copyFilePromise(gt,j,hke.default.constants.COPYFILE_FICLONE)}}catch(De){throw X.innerLoop||(De.message=`While cloning ${fe} -> ${se} ${De.message}`),De}finally{X.innerLoop||ie.tick()}};await Ee(Be,Ce,g)})().then(()=>P.splice(P.indexOf(we),1));P.push(we),P.length>fke&&await Promise.race(P)},N=async(Be,Ce,g)=>{if(g)for(let[we,Ee]of Ce.children){let fe=g.children.get(we);await N(K.join(Be,we),Ee,fe)}else{Ce.children.has(_i)&&await kw(K.join(Be,_i),{contentsOnly:!1});let we=K.basename(Be)===_i&&p.has(K.join(K.dirname(Be)));await kw(Be,{contentsOnly:Be===f,isWorkspaceDir:we})}};for(let[Be,Ce]of p){let g=S.get(Be);for(let[we,Ee]of Ce.children){if(we===".")continue;let fe=g&&g.children.get(we),se=K.join(Be,we);await N(se,Ee,fe)}}let U=async(Be,Ce,g)=>{if(g){Ake(Ce.locator,g.locator)||await kw(Be,{contentsOnly:Ce.linkType==="HARD"});for(let[we,Ee]of Ce.children){let fe=g.children.get(we);await U(K.join(Be,we),Ee,fe)}}else{Ce.children.has(_i)&&await kw(K.join(Be,_i),{contentsOnly:!0});let we=K.basename(Be)===_i&&S.has(K.join(K.dirname(Be)));await kw(Be,{contentsOnly:Ce.linkType==="HARD",isWorkspaceDir:we})}};for(let[Be,Ce]of S){let g=p.get(Be);for(let[we,Ee]of Ce.children){if(we===".")continue;let fe=g&&g.children.get(we);await U(K.join(Be,we),Ee,fe)}}let W=new Map,te=[];for(let[Be,Ce]of E)for(let g of Ce){let{locationRoot:we,segments:Ee}=dN(g,{skipPrefix:s.cwd}),fe=S.get(we),se=we;if(fe){for(let X of Ee)if(se=K.join(se,X),fe=fe.children.get(X),!fe)break;if(fe){let X=Ake(fe.locator,Be),De=e.get(fe.locator),Re=De.target,gt=se,j=De.linkType;if(X)W.has(Re)||W.set(Re,gt);else if(Re!==gt){let rt=q.parseLocator(fe.locator);q.isVirtualLocator(rt)&&(rt=q.devirtualizeLocator(rt)),te.push({srcDir:Re,dstDir:gt,linkType:j,realLocatorHash:rt.locatorHash})}}}}for(let[Be,{locations:Ce}]of e.entries())for(let g of Ce){let{locationRoot:we,segments:Ee}=dN(g,{skipPrefix:s.cwd}),fe=p.get(we),se=S.get(we),X=we,De=e.get(Be),Re=q.parseLocator(Be);q.isVirtualLocator(Re)&&(Re=q.devirtualizeLocator(Re));let gt=Re.locatorHash,j=De.target,rt=g;if(j===rt)continue;let Fe=De.linkType;for(let Ne of Ee)se=se.children.get(Ne);if(!fe)te.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:gt});else for(let Ne of Ee)if(X=K.join(X,Ne),fe=fe.children.get(Ne),!fe){te.push({srcDir:j,dstDir:rt,linkType:Fe,realLocatorHash:gt});break}}let ie=ho.progressViaCounter(te.length),Ae=a.reportProgress(ie),ce=s.configuration.get("nmMode"),me={value:ce},pe=s.configuration.get("winLinkType");try{let Be=me.value==="hardlinks-global"?`${dV(s.configuration)}/v1`:null;if(Be&&!await le.existsPromise(Be)){await le.mkdirpPromise(Be);for(let g=0;g<256;g++)await le.mkdirPromise(K.join(Be,g.toString(16).padStart(2,"0")))}for(let g of te)(g.linkType==="SOFT"||!W.has(g.srcDir))&&(W.set(g.srcDir,g.dstDir),await I({...g,globalHardlinksStore:Be,nmMode:me,windowsLinkType:pe,packageChecksum:c.get(g.realLocatorHash)||null}));await Promise.all(P),P.length=0;for(let g of te){let we=W.get(g.srcDir);g.linkType!=="SOFT"&&g.dstDir!==we&&await R(we,g.dstDir,{nmMode:me})}await Promise.all(P),await le.mkdirPromise(f,{recursive:!0});let Ce=await bRt(e,S,s.cwd,{loadManifest:n});await xRt(h,Ce,s.cwd,pe),await BRt(s,e,Ce,me,{installChangedByUser:C}),ce=="hardlinks-global"&&me.value=="hardlinks-local"&&a.reportWarningOnce(74,"'nmMode' has been downgraded to 'hardlinks-local' due to global cache and install folder being on different devices")}finally{Ae.stop()}}async function xRt(t,e,r,s){for(let a of t.keys()){if(K.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);if(!e.has(a)){let n=K.join(a,_i,gN);await le.removePromise(n)}}for(let[a,n]of e){if(K.contains(r,a)===null)throw new Error(`Assertion failed. Excepted bin symlink location to be inside project dir, instead it was at ${a}`);let c=K.join(a,_i,gN),f=t.get(a)||new Map;await le.mkdirPromise(c,{recursive:!0});for(let p of f.keys())n.has(p)||(await le.removePromise(K.join(c,p)),process.platform==="win32"&&await le.removePromise(K.join(c,`${p}.cmd`)));for(let[p,h]of n){let E=f.get(p),C=K.join(c,p);E!==h&&(process.platform==="win32"?await(0,pke.default)(ue.fromPortablePath(h),ue.fromPortablePath(C),{createPwshFile:!1}):(await le.removePromise(C),await gV(h,C,s),K.contains(r,await le.realpathPromise(h))!==null&&await le.chmodPromise(h,493)))}}}Ve();bt();rA();var sb=class extends og{constructor(){super(...arguments);this.mode="loose"}makeInstaller(r){return new mV(r)}},mV=class extends Gm{constructor(){super(...arguments);this.mode="loose"}async transformPnpSettings(r){let s=new fo({baseFs:new tA({maxOpenFiles:80,readOnlyArchives:!0})}),a=$xe(r,this.opts.project.cwd,s),{tree:n,errors:c}=YD(a,{pnpifyFs:!1,project:this.opts.project});if(!n){for(let{messageName:C,text:S}of c)this.opts.report.reportError(C,S);return}let f=new Map;r.fallbackPool=f;let p=(C,S)=>{let P=q.parseLocator(S.locator),I=q.stringifyIdent(P);I===C?f.set(C,P.reference):f.set(C,[I,P.reference])},h=K.join(this.opts.project.cwd,Er.nodeModules),E=n.get(h);if(!(typeof E>"u")){if("target"in E)throw new Error("Assertion failed: Expected the root junction point to be a directory");for(let C of E.dirList){let S=K.join(h,C),P=n.get(S);if(typeof P>"u")throw new Error("Assertion failed: Expected the child to have been registered");if("target"in P)p(C,P);else for(let I of P.dirList){let R=K.join(S,I),N=n.get(R);if(typeof N>"u")throw new Error("Assertion failed: Expected the subchild to have been registered");if("target"in N)p(`${C}/${I}`,N);else throw new Error("Assertion failed: Expected the leaf junction to be a package")}}}}};var kRt={hooks:{cleanGlobalArtifacts:async t=>{let e=dV(t);await le.removePromise(e)}},configuration:{nmHoistingLimits:{description:"Prevents packages to be hoisted past specific levels",type:"STRING",values:["workspaces","dependencies","none"],default:"none"},nmMode:{description:"Defines in which measure Yarn must use hardlinks and symlinks when generated `node_modules` directories.",type:"STRING",values:["classic","hardlinks-local","hardlinks-global"],default:"classic"},nmSelfReferences:{description:"Defines whether the linker should generate self-referencing symlinks for workspaces.",type:"BOOLEAN",default:!0}},linkers:[ib,sb]},QRt=kRt;var yz={};Vt(yz,{NpmHttpFetcher:()=>lb,NpmRemapResolver:()=>ub,NpmSemverFetcher:()=>ch,NpmSemverResolver:()=>fb,NpmTagResolver:()=>Ab,default:()=>Hjt,npmConfigUtils:()=>pi,npmHttpUtils:()=>an,npmPublishUtils:()=>D1});Ve();var bke=et(fi());var si="npm:";var an={};Vt(an,{AuthType:()=>vke,customPackageError:()=>Wm,del:()=>WRt,get:()=>Ym,getIdentUrl:()=>mN,getPackageMetadata:()=>Rw,handleInvalidAuthenticationError:()=>lg,post:()=>qRt,put:()=>GRt});Ve();Ve();bt();var CV=et(lS()),wke=et(vG()),Bke=et(fi());var pi={};Vt(pi,{RegistryType:()=>Ike,getAuditRegistry:()=>TRt,getAuthConfiguration:()=>IV,getDefaultRegistry:()=>ob,getPublishRegistry:()=>RRt,getRegistryConfiguration:()=>Cke,getScopeConfiguration:()=>EV,getScopeRegistry:()=>Qw,normalizeRegistry:()=>zc});var Ike=(s=>(s.AUDIT_REGISTRY="npmAuditRegistry",s.FETCH_REGISTRY="npmRegistryServer",s.PUBLISH_REGISTRY="npmPublishRegistry",s))(Ike||{});function zc(t){return t.replace(/\/$/,"")}function TRt({configuration:t}){return ob({configuration:t,type:"npmAuditRegistry"})}function RRt(t,{configuration:e}){return t.publishConfig?.registry?zc(t.publishConfig.registry):t.name?Qw(t.name.scope,{configuration:e,type:"npmPublishRegistry"}):ob({configuration:e,type:"npmPublishRegistry"})}function Qw(t,{configuration:e,type:r="npmRegistryServer"}){let s=EV(t,{configuration:e});if(s===null)return ob({configuration:e,type:r});let a=s.get(r);return a===null?ob({configuration:e,type:r}):zc(a)}function ob({configuration:t,type:e="npmRegistryServer"}){let r=t.get(e);return zc(r!==null?r:t.get("npmRegistryServer"))}function Cke(t,{configuration:e}){let r=e.get("npmRegistries"),s=zc(t),a=r.get(s);if(typeof a<"u")return a;let n=r.get(s.replace(/^[a-z]+:/,""));return typeof n<"u"?n:null}var FRt=new Map([["npmRegistryServer","https://npm.jsr.io/"]]);function EV(t,{configuration:e}){if(t===null)return null;let s=e.get("npmScopes").get(t);return s||(t==="jsr"?FRt:null)}function IV(t,{configuration:e,ident:r}){let s=r&&EV(r.scope,{configuration:e});return s?.get("npmAuthIdent")||s?.get("npmAuthToken")?s:Cke(t,{configuration:e})||e}var vke=(a=>(a[a.NO_AUTH=0]="NO_AUTH",a[a.BEST_EFFORT=1]="BEST_EFFORT",a[a.CONFIGURATION=2]="CONFIGURATION",a[a.ALWAYS_AUTH=3]="ALWAYS_AUTH",a))(vke||{});async function lg(t,{attemptedAs:e,registry:r,headers:s,configuration:a}){if(EN(t))throw new Yt(41,"Invalid OTP token");if(t.originalError?.name==="HTTPError"&&t.originalError?.response.statusCode===401)throw new Yt(41,`Invalid authentication (${typeof e!="string"?`as ${await VRt(r,s,{configuration:a})}`:`attempted as ${e}`})`)}function Wm(t,e){let r=t.response?.statusCode;return r?r===404?"Package not found":r>=500&&r<600?`The registry appears to be down (using a ${he.applyHyperlink(e,"local cache","https://yarnpkg.com/advanced/lexicon#local-cache")} might have protected you against such outages)`:null:null}function mN(t){return t.scope?`/@${t.scope}%2f${t.name}`:`/${t.name}`}var Ske=new Map,NRt=new Map;async function ORt(t){return await je.getFactoryWithDefault(Ske,t,async()=>{let e=null;try{e=await le.readJsonPromise(t)}catch{}return e})}async function LRt(t,e,{configuration:r,cached:s,registry:a,headers:n,version:c,...f}){return await je.getFactoryWithDefault(NRt,t,async()=>await Ym(mN(e),{...f,customErrorMessage:Wm,configuration:r,registry:a,ident:e,headers:{...n,"If-None-Match":s?.etag,"If-Modified-Since":s?.lastModified},wrapNetworkRequest:async p=>async()=>{let h=await p();if(h.statusCode===304){if(s===null)throw new Error("Assertion failed: cachedMetadata should not be null");return{...h,body:s.metadata}}let E=_Rt(JSON.parse(h.body.toString())),C={metadata:E,etag:h.headers.etag,lastModified:h.headers["last-modified"]};return Ske.set(t,Promise.resolve(C)),Promise.resolve().then(async()=>{let S=`${t}-${process.pid}.tmp`;await le.mkdirPromise(K.dirname(S),{recursive:!0}),await le.writeJsonPromise(S,C,{compact:!0}),await le.renamePromise(S,t)}).catch(()=>{}),{...h,body:E}}}))}function MRt(t){return t.scope!==null?`@${t.scope}-${t.name}-${t.scope.length}`:t.name}async function Rw(t,{cache:e,project:r,registry:s,headers:a,version:n,...c}){let{configuration:f}=r;s=ab(f,{ident:t,registry:s});let p=HRt(f,s),h=K.join(p,`${MRt(t)}.json`),E=null;if(!r.lockfileNeedsRefresh&&(E=await ORt(h),E)){if(typeof n<"u"&&typeof E.metadata.versions[n]<"u")return E.metadata;if(f.get("enableOfflineMode")){let C=structuredClone(E.metadata),S=new Set;if(e){for(let I of Object.keys(C.versions)){let R=q.makeLocator(t,`npm:${I}`),N=e.getLocatorMirrorPath(R);(!N||!le.existsSync(N))&&(delete C.versions[I],S.add(I))}let P=C["dist-tags"].latest;if(S.has(P)){let I=Object.keys(E.metadata.versions).sort(Bke.default.compare),R=I.indexOf(P);for(;S.has(I[R])&&R>=0;)R-=1;R>=0?C["dist-tags"].latest=I[R]:delete C["dist-tags"].latest}}return C}}return await LRt(h,t,{...c,configuration:f,cached:E,registry:s,headers:a,version:n})}var Dke=["name","dist.tarball","bin","scripts","os","cpu","libc","dependencies","dependenciesMeta","optionalDependencies","peerDependencies","peerDependenciesMeta","deprecated"];function _Rt(t){return{"dist-tags":t["dist-tags"],versions:Object.fromEntries(Object.entries(t.versions).map(([e,r])=>[e,(0,wke.default)(r,Dke)]))}}var URt=Nn.makeHash(...Dke).slice(0,6);function HRt(t,e){let r=jRt(t),s=new URL(e);return K.join(r,URt,s.hostname)}function jRt(t){return K.join(t.get("globalFolder"),"metadata/npm")}async function Ym(t,{configuration:e,headers:r,ident:s,authType:a,registry:n,...c}){n=ab(e,{ident:s,registry:n}),s&&s.scope&&typeof a>"u"&&(a=1);let f=await yN(n,{authType:a,configuration:e,ident:s});f&&(r={...r,authorization:f});try{return await An.get(t.charAt(0)==="/"?`${n}${t}`:t,{configuration:e,headers:r,...c})}catch(p){throw await lg(p,{registry:n,configuration:e,headers:r}),p}}async function qRt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,registry:f,otp:p,...h}){f=ab(s,{ident:n,registry:f});let E=await yN(f,{authType:c,configuration:s,ident:n});E&&(a={...a,authorization:E}),p&&(a={...a,...Tw(p)});try{return await An.post(f+t,e,{configuration:s,headers:a,...h})}catch(C){if(!EN(C)||p)throw await lg(C,{attemptedAs:r,registry:f,configuration:s,headers:a}),C;p=await wV(C,{configuration:s});let S={...a,...Tw(p)};try{return await An.post(`${f}${t}`,e,{configuration:s,headers:S,...h})}catch(P){throw await lg(P,{attemptedAs:r,registry:f,configuration:s,headers:a}),P}}}async function GRt(t,e,{attemptedAs:r,configuration:s,headers:a,ident:n,authType:c=3,registry:f,otp:p,...h}){f=ab(s,{ident:n,registry:f});let E=await yN(f,{authType:c,configuration:s,ident:n});E&&(a={...a,authorization:E}),p&&(a={...a,...Tw(p)});try{return await An.put(f+t,e,{configuration:s,headers:a,...h})}catch(C){if(!EN(C))throw await lg(C,{attemptedAs:r,registry:f,configuration:s,headers:a}),C;p=await wV(C,{configuration:s});let S={...a,...Tw(p)};try{return await An.put(`${f}${t}`,e,{configuration:s,headers:S,...h})}catch(P){throw await lg(P,{attemptedAs:r,registry:f,configuration:s,headers:a}),P}}}async function WRt(t,{attemptedAs:e,configuration:r,headers:s,ident:a,authType:n=3,registry:c,otp:f,...p}){c=ab(r,{ident:a,registry:c});let h=await yN(c,{authType:n,configuration:r,ident:a});h&&(s={...s,authorization:h}),f&&(s={...s,...Tw(f)});try{return await An.del(c+t,{configuration:r,headers:s,...p})}catch(E){if(!EN(E)||f)throw await lg(E,{attemptedAs:e,registry:c,configuration:r,headers:s}),E;f=await wV(E,{configuration:r});let C={...s,...Tw(f)};try{return await An.del(`${c}${t}`,{configuration:r,headers:C,...p})}catch(S){throw await lg(S,{attemptedAs:e,registry:c,configuration:r,headers:s}),S}}}function ab(t,{ident:e,registry:r}){if(typeof r>"u"&&e)return Qw(e.scope,{configuration:t});if(typeof r!="string")throw new Error("Assertion failed: The registry should be a string");return zc(r)}async function yN(t,{authType:e=2,configuration:r,ident:s}){let a=IV(t,{configuration:r,ident:s}),n=YRt(a,e);if(!n)return null;let c=await r.reduceHook(f=>f.getNpmAuthenticationHeader,void 0,t,{configuration:r,ident:s});if(c)return c;if(a.get("npmAuthToken"))return`Bearer ${a.get("npmAuthToken")}`;if(a.get("npmAuthIdent")){let f=a.get("npmAuthIdent");return f.includes(":")?`Basic ${Buffer.from(f).toString("base64")}`:`Basic ${f}`}if(n&&e!==1)throw new Yt(33,"No authentication configured for request");return null}function YRt(t,e){switch(e){case 2:return t.get("npmAlwaysAuth");case 1:case 3:return!0;case 0:return!1;default:throw new Error("Unreachable")}}async function VRt(t,e,{configuration:r}){if(typeof e>"u"||typeof e.authorization>"u")return"an anonymous user";try{return(await An.get(new URL(`${t}/-/whoami`).href,{configuration:r,headers:e,jsonResponse:!0})).username??"an unknown user"}catch{return"an unknown user"}}async function wV(t,{configuration:e}){let r=t.originalError?.response.headers["npm-notice"];if(r&&(await Ot.start({configuration:e,stdout:process.stdout,includeFooter:!1},async a=>{if(a.reportInfo(0,r.replace(/(https?:\/\/\S+)/g,he.pretty(e,"$1",he.Type.URL))),!process.env.YARN_IS_TEST_ENV){let n=r.match(/open (https?:\/\/\S+)/i);if(n&&As.openUrl){let{openNow:c}=await(0,CV.prompt)({type:"confirm",name:"openNow",message:"Do you want to try to open this url now?",required:!0,initial:!0,onCancel:()=>process.exit(130)});c&&(await As.openUrl(n[1])||(a.reportSeparator(),a.reportWarning(0,"We failed to automatically open the url; you'll have to open it yourself in your browser of choice.")))}}}),process.stdout.write(` +`)),process.env.YARN_IS_TEST_ENV)return process.env.YARN_INJECT_NPM_2FA_TOKEN||"";let{otp:s}=await(0,CV.prompt)({type:"password",name:"otp",message:"One-time password:",required:!0,onCancel:()=>process.exit(130)});return process.stdout.write(` +`),s}function EN(t){if(t.originalError?.name!=="HTTPError")return!1;try{return(t.originalError?.response.headers["www-authenticate"].split(/,\s*/).map(r=>r.toLowerCase())).includes("otp")}catch{return!1}}function Tw(t){return{"npm-otp":t}}var lb=class{supports(e,r){if(!e.reference.startsWith(si))return!1;let{selector:s,params:a}=q.parseRange(e.reference);return!(!bke.default.valid(s)||a===null||typeof a.__archiveUrl!="string")}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote server`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let{params:s}=q.parseRange(e.reference);if(s===null||typeof s.__archiveUrl!="string")throw new Error("Assertion failed: The archiveUrl querystring parameter should have been available");let a=await Ym(s.__archiveUrl,{customErrorMessage:Wm,configuration:r.project.configuration,ident:e});return await hs.convertToZip(a,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}};Ve();var ub=class{supportsDescriptor(e,r){return!(!e.range.startsWith(si)||!q.tryParseDescriptor(e.range.slice(si.length),!0))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Unreachable")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){let s=r.project.configuration.normalizeDependency(q.parseDescriptor(e.range.slice(si.length),!0));return r.resolver.getResolutionDependencies(s,r)}async getCandidates(e,r,s){let a=s.project.configuration.normalizeDependency(q.parseDescriptor(e.range.slice(si.length),!0));return await s.resolver.getCandidates(a,r,s)}async getSatisfying(e,r,s,a){let n=a.project.configuration.normalizeDependency(q.parseDescriptor(e.range.slice(si.length),!0));return a.resolver.getSatisfying(n,r,s,a)}resolve(e,r){throw new Error("Unreachable")}};Ve();Ve();var Pke=et(fi());var ch=class t{supports(e,r){if(!e.reference.startsWith(si))return!1;let s=new URL(e.reference);return!(!Pke.default.valid(s.pathname)||s.searchParams.has("__archiveUrl"))}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the remote registry`),loader:()=>this.fetchFromNetwork(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),checksum:c}}async fetchFromNetwork(e,r){let s;try{s=await Ym(t.getLocatorUrl(e),{customErrorMessage:Wm,configuration:r.project.configuration,ident:e})}catch{s=await Ym(t.getLocatorUrl(e).replace(/%2f/g,"/"),{customErrorMessage:Wm,configuration:r.project.configuration,ident:e})}return await hs.convertToZip(s,{configuration:r.project.configuration,prefixPath:q.getIdentVendorPath(e),stripComponents:1})}static isConventionalTarballUrl(e,r,{configuration:s}){let a=Qw(e.scope,{configuration:s}),n=t.getLocatorUrl(e);return r=r.replace(/^https?:(\/\/(?:[^/]+\.)?npmjs.org(?:$|\/))/,"https:$1"),a=a.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"),r=r.replace(/^https:\/\/registry\.npmjs\.org($|\/)/,"https://registry.yarnpkg.com$1"),r===a+n||r===a+n.replace(/%2f/g,"/")}static getLocatorUrl(e){let r=Or.clean(e.reference.slice(si.length));if(r===null)throw new Yt(10,"The npm semver resolver got selected, but the version isn't semver");return`${mN(e)}/-/${e.name}-${r}.tgz`}};Ve();Ve();Ve();var BV=et(fi());var IN=q.makeIdent(null,"node-gyp"),KRt=/\b(node-gyp|prebuild-install)\b/,fb=class{supportsDescriptor(e,r){return e.range.startsWith(si)?!!Or.validRange(e.range.slice(si.length)):!1}supportsLocator(e,r){if(!e.reference.startsWith(si))return!1;let{selector:s}=q.parseRange(e.reference);return!!BV.default.valid(s)}shouldPersistResolution(e,r){return!0}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=Or.validRange(e.range.slice(si.length));if(a===null)throw new Error(`Expected a valid range, got ${e.range.slice(si.length)}`);let n=await Rw(e,{cache:s.fetchOptions?.cache,project:s.project,version:BV.default.valid(a.raw)?a.raw:void 0}),c=je.mapAndFilter(Object.keys(n.versions),h=>{try{let E=new Or.SemVer(h);if(a.test(E))return E}catch{}return je.mapAndFilter.skip}),f=c.filter(h=>!n.versions[h.raw].deprecated),p=f.length>0?f:c;return p.sort((h,E)=>-h.compare(E)),p.map(h=>{let E=q.makeLocator(e,`${si}${h.raw}`),C=n.versions[h.raw].dist.tarball;return ch.isConventionalTarballUrl(E,C,{configuration:s.project.configuration})?E:q.bindLocator(E,{__archiveUrl:C})})}async getSatisfying(e,r,s,a){let n=Or.validRange(e.range.slice(si.length));if(n===null)throw new Error(`Expected a valid range, got ${e.range.slice(si.length)}`);return{locators:je.mapAndFilter(s,p=>{if(p.identHash!==e.identHash)return je.mapAndFilter.skip;let h=q.tryParseRange(p.reference,{requireProtocol:si});if(!h)return je.mapAndFilter.skip;let E=new Or.SemVer(h.selector);return n.test(E)?{locator:p,version:E}:je.mapAndFilter.skip}).sort((p,h)=>-p.version.compare(h.version)).map(({locator:p})=>p),sorted:!0}}async resolve(e,r){let{selector:s}=q.parseRange(e.reference),a=Or.clean(s);if(a===null)throw new Yt(10,"The npm semver resolver got selected, but the version isn't semver");let n=await Rw(e,{cache:r.fetchOptions?.cache,project:r.project,version:a});if(!Object.hasOwn(n,"versions"))throw new Yt(15,'Registry returned invalid data for - missing "versions" field');if(!Object.hasOwn(n.versions,a))throw new Yt(16,`Registry failed to return reference "${a}"`);let c=new Ht;if(c.load(n.versions[a]),!c.dependencies.has(IN.identHash)&&!c.peerDependencies.has(IN.identHash)){for(let f of c.scripts.values())if(f.match(KRt)){c.dependencies.set(IN.identHash,q.makeDescriptor(IN,"latest"));break}}return{...e,version:a,languageName:"node",linkType:"HARD",conditions:c.getConditions(),dependencies:r.project.configuration.normalizeDependencyMap(c.dependencies),peerDependencies:c.peerDependencies,dependenciesMeta:c.dependenciesMeta,peerDependenciesMeta:c.peerDependenciesMeta,bin:c.bin}}};Ve();Ve();var xke=et(fi());var Ab=class{supportsDescriptor(e,r){return!(!e.range.startsWith(si)||!Hp.test(e.range.slice(si.length)))}supportsLocator(e,r){return!1}shouldPersistResolution(e,r){throw new Error("Unreachable")}bindDescriptor(e,r,s){return e}getResolutionDependencies(e,r){return{}}async getCandidates(e,r,s){let a=e.range.slice(si.length),n=await Rw(e,{cache:s.fetchOptions?.cache,project:s.project});if(!Object.hasOwn(n,"dist-tags"))throw new Yt(15,'Registry returned invalid data - missing "dist-tags" field');let c=n["dist-tags"];if(!Object.hasOwn(c,a))throw new Yt(16,`Registry failed to return tag "${a}"`);let f=c[a],p=q.makeLocator(e,`${si}${f}`),h=n.versions[f].dist.tarball;return ch.isConventionalTarballUrl(p,h,{configuration:s.project.configuration})?[p]:[q.bindLocator(p,{__archiveUrl:h})]}async getSatisfying(e,r,s,a){let n=[];for(let c of s){if(c.identHash!==e.identHash)continue;let f=q.tryParseRange(c.reference,{requireProtocol:si});if(!(!f||!xke.default.valid(f.selector))){if(f.params?.__archiveUrl){let p=q.makeRange({protocol:si,selector:f.selector,source:null,params:null}),[h]=await a.resolver.getCandidates(q.makeDescriptor(e,p),r,a);if(c.reference!==h.reference)continue}n.push(c)}}return{locators:n,sorted:!1}}async resolve(e,r){throw new Error("Unreachable")}};var D1={};Vt(D1,{getGitHead:()=>_jt,getPublishAccess:()=>EOe,getReadmeContent:()=>IOe,makePublishBody:()=>Mjt});Ve();Ve();bt();var f7={};Vt(f7,{PackCommand:()=>Gw,default:()=>bOt,packUtils:()=>IA});Ve();Ve();Ve();bt();Wt();var IA={};Vt(IA,{genPackList:()=>qN,genPackStream:()=>u7,genPackageManifest:()=>oTe,hasPackScripts:()=>l7,prepareForPack:()=>c7});Ve();bt();var a7=et(Sa()),iTe=et(eTe()),sTe=ye("zlib"),dOt=["/package.json","/readme","/readme.*","/license","/license.*","/licence","/licence.*","/changelog","/changelog.*"],mOt=["/package.tgz",".github",".git",".hg","node_modules",".npmignore",".gitignore",".#*",".DS_Store"];async function l7(t){return!!(In.hasWorkspaceScript(t,"prepack")||In.hasWorkspaceScript(t,"postpack"))}async function c7(t,{report:e},r){await In.maybeExecuteWorkspaceLifecycleScript(t,"prepack",{report:e});try{let s=K.join(t.cwd,Ht.fileName);await le.existsPromise(s)&&await t.manifest.loadFile(s,{baseFs:le}),await r()}finally{await In.maybeExecuteWorkspaceLifecycleScript(t,"postpack",{report:e})}}async function u7(t,e){typeof e>"u"&&(e=await qN(t));let r=new Set;for(let n of t.manifest.publishConfig?.executableFiles??new Set)r.add(K.normalize(n));for(let n of t.manifest.bin.values())r.add(K.normalize(n));let s=iTe.default.pack();process.nextTick(async()=>{for(let n of e){let c=K.normalize(n),f=K.resolve(t.cwd,c),p=K.join("package",c),h=await le.lstatPromise(f),E={name:p,mtime:new Date(ui.SAFE_TIME*1e3)},C=r.has(c)?493:420,S,P,I=new Promise((N,U)=>{S=N,P=U}),R=N=>{N?P(N):S()};if(h.isFile()){let N;c==="package.json"?N=Buffer.from(JSON.stringify(await oTe(t),null,2)):N=await le.readFilePromise(f),s.entry({...E,mode:C,type:"file"},N,R)}else h.isSymbolicLink()?s.entry({...E,mode:C,type:"symlink",linkname:await le.readlinkPromise(f)},R):R(new Error(`Unsupported file type ${h.mode} for ${ue.fromPortablePath(c)}`));await I}s.finalize()});let a=(0,sTe.createGzip)();return s.pipe(a),a}async function oTe(t){let e=JSON.parse(JSON.stringify(t.manifest.raw));return await t.project.configuration.triggerHook(r=>r.beforeWorkspacePacking,t,e),e}async function qN(t){let e=t.project,r=e.configuration,s={accept:[],reject:[]};for(let C of mOt)s.reject.push(C);for(let C of dOt)s.accept.push(C);s.reject.push(r.get("rcFilename"));let a=C=>{if(C===null||!C.startsWith(`${t.cwd}/`))return;let S=K.relative(t.cwd,C),P=K.resolve(vt.root,S);s.reject.push(P)};a(K.resolve(e.cwd,Er.lockfile)),a(r.get("cacheFolder")),a(r.get("globalFolder")),a(r.get("installStatePath")),a(r.get("virtualFolder")),a(r.get("yarnPath")),await r.triggerHook(C=>C.populateYarnPaths,e,C=>{a(C)});for(let C of e.workspaces){let S=K.relative(t.cwd,C.cwd);S!==""&&!S.match(/^(\.\.)?\//)&&s.reject.push(`/${S}`)}let n={accept:[],reject:[]},c=t.manifest.publishConfig?.main??t.manifest.main,f=t.manifest.publishConfig?.module??t.manifest.module,p=t.manifest.publishConfig?.browser??t.manifest.browser,h=t.manifest.publishConfig?.bin??t.manifest.bin;c!=null&&n.accept.push(K.resolve(vt.root,c)),f!=null&&n.accept.push(K.resolve(vt.root,f)),typeof p=="string"&&n.accept.push(K.resolve(vt.root,p));for(let C of h.values())n.accept.push(K.resolve(vt.root,C));if(p instanceof Map)for(let[C,S]of p.entries())n.accept.push(K.resolve(vt.root,C)),typeof S=="string"&&n.accept.push(K.resolve(vt.root,S));let E=t.manifest.files!==null;if(E){n.reject.push("/*");for(let C of t.manifest.files)aTe(n.accept,C,{cwd:vt.root})}return await yOt(t.cwd,{hasExplicitFileList:E,globalList:s,ignoreList:n})}async function yOt(t,{hasExplicitFileList:e,globalList:r,ignoreList:s}){let a=[],n=new jf(t),c=[[vt.root,[s]]];for(;c.length>0;){let[f,p]=c.pop(),h=await n.lstatPromise(f);if(!rTe(f,{globalList:r,ignoreLists:h.isDirectory()?null:p}))if(h.isDirectory()){let E=await n.readdirPromise(f),C=!1,S=!1;if(!e||f!==vt.root)for(let R of E)C=C||R===".gitignore",S=S||R===".npmignore";let P=S?await tTe(n,f,".npmignore"):C?await tTe(n,f,".gitignore"):null,I=P!==null?[P].concat(p):p;rTe(f,{globalList:r,ignoreLists:p})&&(I=[...p,{accept:[],reject:["**/*"]}]);for(let R of E)c.push([K.resolve(f,R),I])}else(h.isFile()||h.isSymbolicLink())&&a.push(K.relative(vt.root,f))}return a.sort()}async function tTe(t,e,r){let s={accept:[],reject:[]},a=await t.readFilePromise(K.join(e,r),"utf8");for(let n of a.split(/\n/g))aTe(s.reject,n,{cwd:e});return s}function EOt(t,{cwd:e}){let r=t[0]==="!";return r&&(t=t.slice(1)),t.match(/\.{0,1}\//)&&(t=K.resolve(e,t)),r&&(t=`!${t}`),t}function aTe(t,e,{cwd:r}){let s=e.trim();s===""||s[0]==="#"||t.push(EOt(s,{cwd:r}))}function rTe(t,{globalList:e,ignoreLists:r}){let s=jN(t,e.accept);if(s!==0)return s===2;let a=jN(t,e.reject);if(a!==0)return a===1;if(r!==null)for(let n of r){let c=jN(t,n.accept);if(c!==0)return c===2;let f=jN(t,n.reject);if(f!==0)return f===1}return!1}function jN(t,e){let r=e,s=[];for(let a=0;a{await c7(a,{report:p},async()=>{p.reportJson({base:ue.fromPortablePath(a.cwd)});let h=await qN(a);for(let E of h)p.reportInfo(null,ue.fromPortablePath(E)),p.reportJson({location:ue.fromPortablePath(E)});if(!this.dryRun){let E=await u7(a,h);await le.mkdirPromise(K.dirname(c),{recursive:!0});let C=le.createWriteStream(c);E.pipe(C),await new Promise(S=>{C.on("finish",S)})}}),this.dryRun||(p.reportInfo(0,`Package archive generated in ${he.pretty(r,c,he.Type.PATH)}`),p.reportJson({output:ue.fromPortablePath(c)}))})).exitCode()}};function IOt(t,{workspace:e}){let r=t.replace("%s",COt(e)).replace("%v",wOt(e));return ue.toPortablePath(r)}function COt(t){return t.manifest.name!==null?q.slugifyIdent(t.manifest.name):"package"}function wOt(t){return t.manifest.version!==null?t.manifest.version:"unknown"}var BOt=["dependencies","devDependencies","peerDependencies"],vOt="workspace:",SOt=(t,e)=>{e.publishConfig&&(e.publishConfig.type&&(e.type=e.publishConfig.type),e.publishConfig.main&&(e.main=e.publishConfig.main),e.publishConfig.browser&&(e.browser=e.publishConfig.browser),e.publishConfig.module&&(e.module=e.publishConfig.module),e.publishConfig.exports&&(e.exports=e.publishConfig.exports),e.publishConfig.imports&&(e.imports=e.publishConfig.imports),e.publishConfig.bin&&(e.bin=e.publishConfig.bin));let r=t.project;for(let s of BOt)for(let a of t.manifest.getForScope(s).values()){let n=r.tryWorkspaceByDescriptor(a),c=q.parseRange(a.range);if(c.protocol===vOt)if(n===null){if(r.tryWorkspaceByIdent(a)===null)throw new Yt(21,`${q.prettyDescriptor(r.configuration,a)}: No local workspace found for this range`)}else{let f;q.areDescriptorsEqual(a,n.anchoredDescriptor)||c.selector==="*"?f=n.manifest.version??"0.0.0":c.selector==="~"||c.selector==="^"?f=`${c.selector}${n.manifest.version??"0.0.0"}`:f=c.selector;let p=s==="dependencies"?q.makeDescriptor(a,"unknown"):null,h=p!==null&&t.manifest.ensureDependencyMeta(p).optional?"optionalDependencies":s;e[h][q.stringifyIdent(a)]=f}}},DOt={hooks:{beforeWorkspacePacking:SOt},commands:[Gw]},bOt=DOt;var yOe=et(dTe());Ve();var dOe=et(gOe()),{env:Bt}=process,xjt="application/vnd.in-toto+json",kjt="https://in-toto.io/Statement/v0.1",Qjt="https://in-toto.io/Statement/v1",Tjt="https://slsa.dev/provenance/v0.2",Rjt="https://slsa.dev/provenance/v1",Fjt="https://github.com/actions/runner",Njt="https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1",Ojt="https://github.com/npm/cli/gitlab",Ljt="v0alpha1",mOe=async(t,e)=>{let r;if(Bt.GITHUB_ACTIONS){if(!Bt.ACTIONS_ID_TOKEN_REQUEST_URL)throw new Yt(91,'Provenance generation in GitHub Actions requires "write" access to the "id-token" permission');let s=(Bt.GITHUB_WORKFLOW_REF||"").replace(`${Bt.GITHUB_REPOSITORY}/`,""),a=s.indexOf("@"),n=s.slice(0,a),c=s.slice(a+1);r={_type:Qjt,subject:t,predicateType:Rjt,predicate:{buildDefinition:{buildType:Njt,externalParameters:{workflow:{ref:c,repository:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}`,path:n}},internalParameters:{github:{event_name:Bt.GITHUB_EVENT_NAME,repository_id:Bt.GITHUB_REPOSITORY_ID,repository_owner_id:Bt.GITHUB_REPOSITORY_OWNER_ID}},resolvedDependencies:[{uri:`git+${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}@${Bt.GITHUB_REF}`,digest:{gitCommit:Bt.GITHUB_SHA}}]},runDetails:{builder:{id:`${Fjt}/${Bt.RUNNER_ENVIRONMENT}`},metadata:{invocationId:`${Bt.GITHUB_SERVER_URL}/${Bt.GITHUB_REPOSITORY}/actions/runs/${Bt.GITHUB_RUN_ID}/attempts/${Bt.GITHUB_RUN_ATTEMPT}`}}}}}else if(Bt.GITLAB_CI){if(!Bt.SIGSTORE_ID_TOKEN)throw new Yt(91,`Provenance generation in GitLab CI requires "SIGSTORE_ID_TOKEN" with "sigstore" audience to be present in "id_tokens". For more info see: +https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html`);r={_type:kjt,subject:t,predicateType:Tjt,predicate:{buildType:`${Ojt}/${Ljt}`,builder:{id:`${Bt.CI_PROJECT_URL}/-/runners/${Bt.CI_RUNNER_ID}`},invocation:{configSource:{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA},entryPoint:Bt.CI_JOB_NAME},parameters:{CI:Bt.CI,CI_API_GRAPHQL_URL:Bt.CI_API_GRAPHQL_URL,CI_API_V4_URL:Bt.CI_API_V4_URL,CI_BUILD_BEFORE_SHA:Bt.CI_BUILD_BEFORE_SHA,CI_BUILD_ID:Bt.CI_BUILD_ID,CI_BUILD_NAME:Bt.CI_BUILD_NAME,CI_BUILD_REF:Bt.CI_BUILD_REF,CI_BUILD_REF_NAME:Bt.CI_BUILD_REF_NAME,CI_BUILD_REF_SLUG:Bt.CI_BUILD_REF_SLUG,CI_BUILD_STAGE:Bt.CI_BUILD_STAGE,CI_COMMIT_BEFORE_SHA:Bt.CI_COMMIT_BEFORE_SHA,CI_COMMIT_BRANCH:Bt.CI_COMMIT_BRANCH,CI_COMMIT_REF_NAME:Bt.CI_COMMIT_REF_NAME,CI_COMMIT_REF_PROTECTED:Bt.CI_COMMIT_REF_PROTECTED,CI_COMMIT_REF_SLUG:Bt.CI_COMMIT_REF_SLUG,CI_COMMIT_SHA:Bt.CI_COMMIT_SHA,CI_COMMIT_SHORT_SHA:Bt.CI_COMMIT_SHORT_SHA,CI_COMMIT_TIMESTAMP:Bt.CI_COMMIT_TIMESTAMP,CI_COMMIT_TITLE:Bt.CI_COMMIT_TITLE,CI_CONFIG_PATH:Bt.CI_CONFIG_PATH,CI_DEFAULT_BRANCH:Bt.CI_DEFAULT_BRANCH,CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX:Bt.CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX,CI_DEPENDENCY_PROXY_SERVER:Bt.CI_DEPENDENCY_PROXY_SERVER,CI_DEPENDENCY_PROXY_USER:Bt.CI_DEPENDENCY_PROXY_USER,CI_JOB_ID:Bt.CI_JOB_ID,CI_JOB_NAME:Bt.CI_JOB_NAME,CI_JOB_NAME_SLUG:Bt.CI_JOB_NAME_SLUG,CI_JOB_STAGE:Bt.CI_JOB_STAGE,CI_JOB_STARTED_AT:Bt.CI_JOB_STARTED_AT,CI_JOB_URL:Bt.CI_JOB_URL,CI_NODE_TOTAL:Bt.CI_NODE_TOTAL,CI_PAGES_DOMAIN:Bt.CI_PAGES_DOMAIN,CI_PAGES_URL:Bt.CI_PAGES_URL,CI_PIPELINE_CREATED_AT:Bt.CI_PIPELINE_CREATED_AT,CI_PIPELINE_ID:Bt.CI_PIPELINE_ID,CI_PIPELINE_IID:Bt.CI_PIPELINE_IID,CI_PIPELINE_SOURCE:Bt.CI_PIPELINE_SOURCE,CI_PIPELINE_URL:Bt.CI_PIPELINE_URL,CI_PROJECT_CLASSIFICATION_LABEL:Bt.CI_PROJECT_CLASSIFICATION_LABEL,CI_PROJECT_DESCRIPTION:Bt.CI_PROJECT_DESCRIPTION,CI_PROJECT_ID:Bt.CI_PROJECT_ID,CI_PROJECT_NAME:Bt.CI_PROJECT_NAME,CI_PROJECT_NAMESPACE:Bt.CI_PROJECT_NAMESPACE,CI_PROJECT_NAMESPACE_ID:Bt.CI_PROJECT_NAMESPACE_ID,CI_PROJECT_PATH:Bt.CI_PROJECT_PATH,CI_PROJECT_PATH_SLUG:Bt.CI_PROJECT_PATH_SLUG,CI_PROJECT_REPOSITORY_LANGUAGES:Bt.CI_PROJECT_REPOSITORY_LANGUAGES,CI_PROJECT_ROOT_NAMESPACE:Bt.CI_PROJECT_ROOT_NAMESPACE,CI_PROJECT_TITLE:Bt.CI_PROJECT_TITLE,CI_PROJECT_URL:Bt.CI_PROJECT_URL,CI_PROJECT_VISIBILITY:Bt.CI_PROJECT_VISIBILITY,CI_REGISTRY:Bt.CI_REGISTRY,CI_REGISTRY_IMAGE:Bt.CI_REGISTRY_IMAGE,CI_REGISTRY_USER:Bt.CI_REGISTRY_USER,CI_RUNNER_DESCRIPTION:Bt.CI_RUNNER_DESCRIPTION,CI_RUNNER_ID:Bt.CI_RUNNER_ID,CI_RUNNER_TAGS:Bt.CI_RUNNER_TAGS,CI_SERVER_HOST:Bt.CI_SERVER_HOST,CI_SERVER_NAME:Bt.CI_SERVER_NAME,CI_SERVER_PORT:Bt.CI_SERVER_PORT,CI_SERVER_PROTOCOL:Bt.CI_SERVER_PROTOCOL,CI_SERVER_REVISION:Bt.CI_SERVER_REVISION,CI_SERVER_SHELL_SSH_HOST:Bt.CI_SERVER_SHELL_SSH_HOST,CI_SERVER_SHELL_SSH_PORT:Bt.CI_SERVER_SHELL_SSH_PORT,CI_SERVER_URL:Bt.CI_SERVER_URL,CI_SERVER_VERSION:Bt.CI_SERVER_VERSION,CI_SERVER_VERSION_MAJOR:Bt.CI_SERVER_VERSION_MAJOR,CI_SERVER_VERSION_MINOR:Bt.CI_SERVER_VERSION_MINOR,CI_SERVER_VERSION_PATCH:Bt.CI_SERVER_VERSION_PATCH,CI_TEMPLATE_REGISTRY_HOST:Bt.CI_TEMPLATE_REGISTRY_HOST,GITLAB_CI:Bt.GITLAB_CI,GITLAB_FEATURES:Bt.GITLAB_FEATURES,GITLAB_USER_ID:Bt.GITLAB_USER_ID,GITLAB_USER_LOGIN:Bt.GITLAB_USER_LOGIN,RUNNER_GENERATE_ARTIFACTS_METADATA:Bt.RUNNER_GENERATE_ARTIFACTS_METADATA},environment:{name:Bt.CI_RUNNER_DESCRIPTION,architecture:Bt.CI_RUNNER_EXECUTABLE_ARCH,server:Bt.CI_SERVER_URL,project:Bt.CI_PROJECT_PATH,job:{id:Bt.CI_JOB_ID},pipeline:{id:Bt.CI_PIPELINE_ID,ref:Bt.CI_CONFIG_PATH}}},metadata:{buildInvocationId:`${Bt.CI_JOB_URL}`,completeness:{parameters:!0,environment:!0,materials:!1},reproducible:!1},materials:[{uri:`git+${Bt.CI_PROJECT_URL}`,digest:{sha1:Bt.CI_COMMIT_SHA}}]}}}else throw new Yt(91,"Provenance generation is only supported in GitHub Actions and GitLab CI");return dOe.attest(Buffer.from(JSON.stringify(r)),xjt,e)};async function Mjt(t,e,{access:r,tag:s,registry:a,gitHead:n,provenance:c}){let f=t.manifest.name,p=t.manifest.version,h=q.stringifyIdent(f),E=yOe.default.fromData(e,{algorithms:["sha1","sha512"]}),C=r??EOe(t,f),S=await IOe(t),P=await IA.genPackageManifest(t),I=`${h}-${p}.tgz`,R=new URL(`${zc(a)}/${h}/-/${I}`),N={[I]:{content_type:"application/octet-stream",data:e.toString("base64"),length:e.length}};if(c){let U={name:`pkg:npm/${h.replace(/^@/,"%40")}@${p}`,digest:{sha512:E.sha512[0].hexDigest()}},W=await mOe([U]),te=JSON.stringify(W);N[`${h}-${p}.sigstore`]={content_type:W.mediaType,data:te,length:te.length}}return{_id:h,_attachments:N,name:h,access:C,"dist-tags":{[s]:p},versions:{[p]:{...P,_id:`${h}@${p}`,name:h,version:p,gitHead:n,dist:{shasum:E.sha1[0].hexDigest(),integrity:E.sha512[0].toString(),tarball:R.toString()}}},readme:S}}async function _jt(t){try{let{stdout:e}=await Gr.execvp("git",["rev-parse","--revs-only","HEAD"],{cwd:t});return e.trim()===""?void 0:e.trim()}catch{return}}function EOe(t,e){let r=t.project.configuration;return t.manifest.publishConfig&&typeof t.manifest.publishConfig.access=="string"?t.manifest.publishConfig.access:r.get("npmPublishAccess")!==null?r.get("npmPublishAccess"):e.scope?"restricted":"public"}async function IOe(t){let e=ue.toPortablePath(`${t.cwd}/README.md`),r=t.manifest.name,a=`# ${q.stringifyIdent(r)} +`;try{a=await le.readFilePromise(e,"utf8")}catch(n){if(n.code==="ENOENT")return a;throw n}return a}var mz={npmAlwaysAuth:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:"BOOLEAN",default:!1},npmAuthIdent:{description:"Authentication identity for the npm registry (_auth in npm and yarn v1)",type:"SECRET",default:null},npmAuthToken:{description:"Authentication token for the npm registry (_authToken in npm and yarn v1)",type:"SECRET",default:null}},COe={npmAuditRegistry:{description:"Registry to query for audit reports",type:"STRING",default:null},npmPublishRegistry:{description:"Registry to push packages to",type:"STRING",default:null},npmRegistryServer:{description:"URL of the selected npm registry (note: npm enterprise isn't supported)",type:"STRING",default:"https://registry.yarnpkg.com"}},Ujt={configuration:{...mz,...COe,npmScopes:{description:"Settings per package scope",type:"MAP",valueDefinition:{description:"",type:"SHAPE",properties:{...mz,...COe}}},npmRegistries:{description:"Settings per registry",type:"MAP",normalizeKeys:zc,valueDefinition:{description:"",type:"SHAPE",properties:{...mz}}}},fetchers:[lb,ch],resolvers:[ub,fb,Ab]},Hjt=Ujt;var bz={};Vt(bz,{NpmAuditCommand:()=>P1,NpmInfoCommand:()=>x1,NpmLoginCommand:()=>k1,NpmLogoutCommand:()=>T1,NpmPublishCommand:()=>R1,NpmTagAddCommand:()=>N1,NpmTagListCommand:()=>F1,NpmTagRemoveCommand:()=>O1,NpmWhoamiCommand:()=>L1,default:()=>Kjt,npmAuditTypes:()=>fP,npmAuditUtils:()=>ML});Ve();Ve();Wt();var Bz=et(Sa());Ul();var fP={};Vt(fP,{Environment:()=>cP,Severity:()=>uP});var cP=(s=>(s.All="all",s.Production="production",s.Development="development",s))(cP||{}),uP=(n=>(n.Info="info",n.Low="low",n.Moderate="moderate",n.High="high",n.Critical="critical",n))(uP||{});var ML={};Vt(ML,{allSeverities:()=>b1,getPackages:()=>wz,getReportTree:()=>Iz,getSeverityInclusions:()=>Ez,getTopLevelDependencies:()=>Cz});Ve();var wOe=et(fi());var b1=["info","low","moderate","high","critical"];function Ez(t){if(typeof t>"u")return new Set(b1);let e=b1.indexOf(t),r=b1.slice(e);return new Set(r)}function Iz(t){let e={},r={children:e};for(let[s,a]of je.sortMap(Object.entries(t),n=>n[0]))for(let n of je.sortMap(a,c=>`${c.id}`))e[`${s}/${n.id}`]={value:he.tuple(he.Type.IDENT,q.parseIdent(s)),children:{ID:typeof n.id<"u"&&{label:"ID",value:he.tuple(he.Type.ID,n.id)},Issue:{label:"Issue",value:he.tuple(he.Type.NO_HINT,n.title)},URL:typeof n.url<"u"&&{label:"URL",value:he.tuple(he.Type.URL,n.url)},Severity:{label:"Severity",value:he.tuple(he.Type.NO_HINT,n.severity)},"Vulnerable Versions":{label:"Vulnerable Versions",value:he.tuple(he.Type.RANGE,n.vulnerable_versions)},"Tree Versions":{label:"Tree Versions",children:[...n.versions].sort(wOe.default.compare).map(c=>({value:he.tuple(he.Type.REFERENCE,c)}))},Dependents:{label:"Dependents",children:je.sortMap(n.dependents,c=>q.stringifyLocator(c)).map(c=>({value:he.tuple(he.Type.LOCATOR,c)}))}}};return r}function Cz(t,e,{all:r,environment:s}){let a=[],n=r?t.workspaces:[e],c=["all","production"].includes(s),f=["all","development"].includes(s);for(let p of n)for(let h of p.anchoredPackage.dependencies.values())(p.manifest.devDependencies.has(h.identHash)?!f:!c)||a.push({workspace:p,dependency:h});return a}function wz(t,e,{recursive:r}){let s=new Map,a=new Set,n=[],c=(f,p)=>{let h=t.storedResolutions.get(p.descriptorHash);if(typeof h>"u")throw new Error("Assertion failed: The resolution should have been registered");if(!a.has(h))a.add(h);else return;let E=t.storedPackages.get(h);if(typeof E>"u")throw new Error("Assertion failed: The package should have been registered");if(q.ensureDevirtualizedLocator(E).reference.startsWith("npm:")&&E.version!==null){let S=q.stringifyIdent(E),P=je.getMapWithDefault(s,S);je.getArrayWithDefault(P,E.version).push(f)}if(r)for(let S of E.dependencies.values())n.push([E,S])};for(let{workspace:f,dependency:p}of e)n.push([f.anchoredLocator,p]);for(;n.length>0;){let[f,p]=n.shift();c(f,p)}return s}var P1=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean("-A,--all",!1,{description:"Audit dependencies from all workspaces"});this.recursive=ge.Boolean("-R,--recursive",!1,{description:"Audit transitive dependencies as well"});this.environment=ge.String("--environment","all",{description:"Which environments to cover",validator:Ao(cP)});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.noDeprecations=ge.Boolean("--no-deprecations",!1,{description:"Don't warn about deprecated packages"});this.severity=ge.String("--severity","info",{description:"Minimal severity requested for packages to be displayed",validator:Ao(uP)});this.excludes=ge.Array("--exclude",[],{description:"Array of glob patterns of packages to exclude from audit"});this.ignores=ge.Array("--ignore",[],{description:"Array of glob patterns of advisory ID's to ignore in the audit report"})}static{this.paths=[["npm","audit"]]}static{this.usage=ot.Usage({description:"perform a vulnerability audit against the installed packages",details:` + This command checks for known security reports on the packages you use. The reports are by default extracted from the npm registry, and may or may not be relevant to your actual program (not all vulnerabilities affect all code paths). + + For consistency with our other commands the default is to only check the direct dependencies for the active workspace. To extend this search to all workspaces, use \`-A,--all\`. To extend this search to both direct and transitive dependencies, use \`-R,--recursive\`. + + Applying the \`--severity\` flag will limit the audit table to vulnerabilities of the corresponding severity and above. Valid values are ${b1.map(r=>`\`${r}\``).join(", ")}. + + If the \`--json\` flag is set, Yarn will print the output exactly as received from the registry. Regardless of this flag, the process will exit with a non-zero exit code if a report is found for the selected packages. + + If certain packages produce false positives for a particular environment, the \`--exclude\` flag can be used to exclude any number of packages from the audit. This can also be set in the configuration file with the \`npmAuditExcludePackages\` option. + + If particular advisories are needed to be ignored, the \`--ignore\` flag can be used with Advisory ID's to ignore any number of advisories in the audit report. This can also be set in the configuration file with the \`npmAuditIgnoreAdvisories\` option. + + To understand the dependency tree requiring vulnerable packages, check the raw report with the \`--json\` flag or use \`yarn why package\` to get more information as to who depends on them. + `,examples:[["Checks for known security issues with the installed packages. The output is a list of known issues.","yarn npm audit"],["Audit dependencies in all workspaces","yarn npm audit --all"],["Limit auditing to `dependencies` (excludes `devDependencies`)","yarn npm audit --environment production"],["Show audit report as valid JSON","yarn npm audit --json"],["Audit all direct and transitive dependencies","yarn npm audit --recursive"],["Output moderate (or more severe) vulnerabilities","yarn npm audit --severity moderate"],["Exclude certain packages","yarn npm audit --exclude package1 --exclude package2"],["Ignore specific advisories","yarn npm audit --ignore 1234567 --ignore 7654321"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=Cz(s,a,{all:this.all,environment:this.environment}),c=wz(s,n,{recursive:this.recursive}),f=Array.from(new Set([...r.get("npmAuditExcludePackages"),...this.excludes])),p=Object.create(null);for(let[N,U]of c)f.some(W=>Bz.default.isMatch(N,W))||(p[N]=[...U.keys()]);let h=pi.getAuditRegistry({configuration:r}),E,C=await uA.start({configuration:r,stdout:this.context.stdout},async()=>{let N=an.post("/-/npm/v1/security/advisories/bulk",p,{authType:an.AuthType.BEST_EFFORT,configuration:r,jsonResponse:!0,registry:h}),U=this.noDeprecations?[]:await Promise.all(Array.from(Object.entries(p),async([te,ie])=>{let Ae=await an.getPackageMetadata(q.parseIdent(te),{project:s});return je.mapAndFilter(ie,ce=>{let{deprecated:me}=Ae.versions[ce];return me?[te,ce,me]:je.mapAndFilter.skip})})),W=await N;for(let[te,ie,Ae]of U.flat(1))Object.hasOwn(W,te)&&W[te].some(ce=>Or.satisfiesWithPrereleases(ie,ce.vulnerable_versions))||(W[te]??=[],W[te].push({id:`${te} (deprecation)`,title:(typeof Ae=="string"?Ae:"").trim()||"This package has been deprecated.",severity:"moderate",vulnerable_versions:ie}));E=W});if(C.hasErrors())return C.exitCode();let S=Ez(this.severity),P=Array.from(new Set([...r.get("npmAuditIgnoreAdvisories"),...this.ignores])),I=Object.create(null);for(let[N,U]of Object.entries(E)){let W=U.filter(te=>!Bz.default.isMatch(`${te.id}`,P)&&S.has(te.severity));W.length>0&&(I[N]=W.map(te=>{let ie=c.get(N);if(typeof ie>"u")throw new Error("Assertion failed: Expected the registry to only return packages that were requested");let Ae=[...ie.keys()].filter(me=>Or.satisfiesWithPrereleases(me,te.vulnerable_versions)),ce=new Map;for(let me of Ae)for(let pe of ie.get(me))ce.set(pe.locatorHash,pe);return{...te,versions:Ae,dependents:[...ce.values()]}}))}let R=Object.keys(I).length>0;return R?(ks.emitTree(Iz(I),{configuration:r,json:this.json,stdout:this.context.stdout,separators:2}),1):(await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async N=>{N.reportInfo(1,"No audit suggestions")}),R?1:0)}};Ve();Ve();bt();Wt();var vz=et(fi()),Sz=ye("util"),x1=class extends ut{constructor(){super(...arguments);this.fields=ge.String("-f,--fields",{description:"A comma-separated list of manifest fields that should be displayed"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.packages=ge.Rest()}static{this.paths=[["npm","info"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"show information about a package",details:"\n This command fetches information about a package from the npm registry and prints it in a tree format.\n\n The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).\n\n Append `@` to the package argument to provide information specific to the latest version that satisfies the range or to the corresponding tagged version. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.\n\n If the `-f,--fields` option is set, it's a comma-separated list of fields which will be used to only display part of the package information.\n\n By default, this command won't return the `dist`, `readme`, and `users` fields, since they are often very long. To explicitly request those fields, explicitly list them with the `--fields` flag or request the output in JSON mode.\n ",examples:[["Show all available information about react (except the `dist`, `readme`, and `users` fields)","yarn npm info react"],["Show all available information about react as valid JSON (including the `dist`, `readme`, and `users` fields)","yarn npm info react --json"],["Show all available information about react@16.12.0","yarn npm info react@16.12.0"],["Show all available information about react@next","yarn npm info react@next"],["Show the description of react","yarn npm info react --fields description"],["Show all available versions of react","yarn npm info react --fields versions"],["Show the readme of react","yarn npm info react --fields readme"],["Show a few fields of react","yarn npm info react --fields homepage,repository"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),a=typeof this.fields<"u"?new Set(["name",...this.fields.split(/\s*,\s*/)]):null,n=[],c=!1,f=await Ot.start({configuration:r,includeFooter:!1,json:this.json,stdout:this.context.stdout},async p=>{for(let h of this.packages){let E;if(h==="."){let ie=s.topLevelWorkspace;if(!ie.manifest.name)throw new nt(`Missing ${he.pretty(r,"name",he.Type.CODE)} field in ${ue.fromPortablePath(K.join(ie.cwd,Er.manifest))}`);E=q.makeDescriptor(ie.manifest.name,"unknown")}else E=q.parseDescriptor(h);let C=an.getIdentUrl(E),S=Dz(await an.get(C,{configuration:r,ident:E,jsonResponse:!0,customErrorMessage:an.customPackageError})),P=Object.keys(S.versions).sort(vz.default.compareLoose),R=S["dist-tags"].latest||P[P.length-1],N=Or.validRange(E.range);if(N){let ie=vz.default.maxSatisfying(P,N);ie!==null?R=ie:(p.reportWarning(0,`Unmet range ${q.prettyRange(r,E.range)}; falling back to the latest version`),c=!0)}else Object.hasOwn(S["dist-tags"],E.range)?R=S["dist-tags"][E.range]:E.range!=="unknown"&&(p.reportWarning(0,`Unknown tag ${q.prettyRange(r,E.range)}; falling back to the latest version`),c=!0);let U=S.versions[R],W={...S,...U,version:R,versions:P},te;if(a!==null){te={};for(let ie of a){let Ae=W[ie];if(typeof Ae<"u")te[ie]=Ae;else{p.reportWarning(1,`The ${he.pretty(r,ie,he.Type.CODE)} field doesn't exist inside ${q.prettyIdent(r,E)}'s information`),c=!0;continue}}}else this.json||(delete W.dist,delete W.readme,delete W.users),te=W;p.reportJson(te),this.json||n.push(te)}});Sz.inspect.styles.name="cyan";for(let p of n)(p!==n[0]||c)&&this.context.stdout.write(` +`),this.context.stdout.write(`${(0,Sz.inspect)(p,{depth:1/0,colors:!0,compact:!1})} +`);return f.exitCode()}};function Dz(t){if(Array.isArray(t)){let e=[];for(let r of t)r=Dz(r),r&&e.push(r);return e}else if(typeof t=="object"&&t!==null){let e={};for(let r of Object.keys(t)){if(r.startsWith("_"))continue;let s=Dz(t[r]);s&&(e[r]=s)}return e}else return t||null}Ve();Ve();Wt();var BOe=et(lS()),k1=class extends ut{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Login to the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Login to the publish registry"});this.alwaysAuth=ge.Boolean("--always-auth",{description:"Set the npmAlwaysAuth configuration"})}static{this.paths=[["npm","login"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"store new login info to access the npm registry",details:"\n This command will ask you for your username, password, and 2FA One-Time-Password (when it applies). It will then modify your local configuration (in your home folder, never in the project itself) to reference the new tokens thus generated.\n\n Adding the `-s,--scope` flag will cause the authentication to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the authentication to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n ",examples:[["Login to the default registry","yarn npm login"],["Login to the registry linked to the @my-scope registry","yarn npm login --scope my-scope"],["Login to the publish registry for the current package","yarn npm login --publish"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=await _L({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope});return(await Ot.start({configuration:r,stdout:this.context.stdout,includeFooter:!1},async n=>{let c=await Gjt({configuration:r,registry:s,report:n,stdin:this.context.stdin,stdout:this.context.stdout}),f=await jjt(s,c,r);return await qjt(s,f,{alwaysAuth:this.alwaysAuth,scope:this.scope}),n.reportInfo(0,"Successfully logged in")})).exitCode()}};async function _L({scope:t,publish:e,configuration:r,cwd:s}){return t&&e?pi.getScopeRegistry(t,{configuration:r,type:pi.RegistryType.PUBLISH_REGISTRY}):t?pi.getScopeRegistry(t,{configuration:r}):e?pi.getPublishRegistry((await rC(r,s)).manifest,{configuration:r}):pi.getDefaultRegistry({configuration:r})}async function jjt(t,e,r){let s=`/-/user/org.couchdb.user:${encodeURIComponent(e.name)}`,a={_id:`org.couchdb.user:${e.name}`,name:e.name,password:e.password,type:"user",roles:[],date:new Date().toISOString()},n={attemptedAs:e.name,configuration:r,registry:t,jsonResponse:!0,authType:an.AuthType.NO_AUTH};try{return(await an.put(s,a,n)).token}catch(E){if(!(E.originalError?.name==="HTTPError"&&E.originalError?.response.statusCode===409))throw E}let c={...n,authType:an.AuthType.NO_AUTH,headers:{authorization:`Basic ${Buffer.from(`${e.name}:${e.password}`).toString("base64")}`}},f=await an.get(s,c);for(let[E,C]of Object.entries(f))(!a[E]||E==="roles")&&(a[E]=C);let p=`${s}/-rev/${a._rev}`;return(await an.put(p,a,c)).token}async function qjt(t,e,{alwaysAuth:r,scope:s}){let a=c=>f=>{let p=je.isIndexableObject(f)?f:{},h=p[c],E=je.isIndexableObject(h)?h:{};return{...p,[c]:{...E,...r!==void 0?{npmAlwaysAuth:r}:{},npmAuthToken:e}}},n=s?{npmScopes:a(s)}:{npmRegistries:a(t)};return await ze.updateHomeConfiguration(n)}async function Gjt({configuration:t,registry:e,report:r,stdin:s,stdout:a}){r.reportInfo(0,`Logging in to ${he.pretty(t,e,he.Type.URL)}`);let n=!1;if(e.match(/^https:\/\/npm\.pkg\.github\.com(\/|$)/)&&(r.reportInfo(0,"You seem to be using the GitHub Package Registry. Tokens must be generated with the 'repo', 'write:packages', and 'read:packages' permissions."),n=!0),r.reportSeparator(),t.env.YARN_IS_TEST_ENV)return{name:t.env.YARN_INJECT_NPM_USER||"",password:t.env.YARN_INJECT_NPM_PASSWORD||""};let c=await(0,BOe.prompt)([{type:"input",name:"name",message:"Username:",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a},{type:"password",name:"password",message:n?"Token:":"Password:",required:!0,onCancel:()=>process.exit(130),stdin:s,stdout:a}]);return r.reportSeparator(),c}Ve();Ve();Wt();var Q1=new Set(["npmAuthIdent","npmAuthToken"]),T1=class extends ut{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Logout of the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Logout of the publish registry"});this.all=ge.Boolean("-A,--all",!1,{description:"Logout of all registries"})}static{this.paths=[["npm","logout"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"logout of the npm registry",details:"\n This command will log you out by modifying your local configuration (in your home folder, never in the project itself) to delete all credentials linked to a registry.\n\n Adding the `-s,--scope` flag will cause the deletion to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the deletion to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n\n Adding the `-A,--all` flag will cause the deletion to be done against all registries and scopes.\n ",examples:[["Logout of the default registry","yarn npm logout"],["Logout of the @my-scope scope","yarn npm logout --scope my-scope"],["Logout of the publish registry for the current package","yarn npm logout --publish"],["Logout of all registries","yarn npm logout --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s=async()=>{let n=await _L({configuration:r,cwd:this.context.cwd,publish:this.publish,scope:this.scope}),c=await ze.find(this.context.cwd,this.context.plugins),f=q.makeIdent(this.scope??null,"pkg");return!pi.getAuthConfiguration(n,{configuration:c,ident:f}).get("npmAuthToken")};return(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{if(this.all&&(await Yjt(),n.reportInfo(0,"Successfully logged out from everything")),this.scope){await vOe("npmScopes",this.scope),await s()?n.reportInfo(0,`Successfully logged out from ${this.scope}`):n.reportWarning(0,"Scope authentication settings removed, but some other ones settings still apply to it");return}let c=await _L({configuration:r,cwd:this.context.cwd,publish:this.publish});await vOe("npmRegistries",c),await s()?n.reportInfo(0,`Successfully logged out from ${c}`):n.reportWarning(0,"Registry authentication settings removed, but some other ones settings still apply to it")})).exitCode()}};function Wjt(t,e){let r=t[e];if(!je.isIndexableObject(r))return!1;let s=new Set(Object.keys(r));if([...Q1].every(n=>!s.has(n)))return!1;for(let n of Q1)s.delete(n);if(s.size===0)return t[e]=void 0,!0;let a={...r};for(let n of Q1)delete a[n];return t[e]=a,!0}async function Yjt(){let t=e=>{let r=!1,s=je.isIndexableObject(e)?{...e}:{};s.npmAuthToken&&(delete s.npmAuthToken,r=!0);for(let a of Object.keys(s))Wjt(s,a)&&(r=!0);if(Object.keys(s).length!==0)return r?s:e};return await ze.updateHomeConfiguration({npmRegistries:t,npmScopes:t})}async function vOe(t,e){return await ze.updateHomeConfiguration({[t]:r=>{let s=je.isIndexableObject(r)?r:{};if(!Object.hasOwn(s,e))return r;let a=s[e],n=je.isIndexableObject(a)?a:{},c=new Set(Object.keys(n));if([...Q1].every(p=>!c.has(p)))return r;for(let p of Q1)c.delete(p);if(c.size===0)return Object.keys(s).length===1?void 0:{...s,[e]:void 0};let f={};for(let p of Q1)f[p]=void 0;return{...s,[e]:{...n,...f}}}})}Ve();Wt();var R1=class extends ut{constructor(){super(...arguments);this.access=ge.String("--access",{description:"The access for the published package (public or restricted)"});this.tag=ge.String("--tag","latest",{description:"The tag on the registry that the package should be attached to"});this.tolerateRepublish=ge.Boolean("--tolerate-republish",!1,{description:"Warn and exit when republishing an already existing version of a package"});this.otp=ge.String("--otp",{description:"The OTP token to use with the command"});this.provenance=ge.Boolean("--provenance",!1,{description:"Generate provenance for the package. Only available in GitHub Actions and GitLab CI. Can be set globally through the `npmPublishProvenance` setting or the `YARN_NPM_CONFIG_PROVENANCE` environment variable, or per-package through the `publishConfig.provenance` field in package.json."})}static{this.paths=[["npm","publish"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"publish the active workspace to the npm registry",details:'\n This command will pack the active workspace into a fresh archive and upload it to the npm registry.\n\n The package will by default be attached to the `latest` tag on the registry, but this behavior can be overridden by using the `--tag` option.\n\n Note that for legacy reasons scoped packages are by default published with an access set to `restricted` (aka "private packages"). This requires you to register for a paid npm plan. In case you simply wish to publish a public scoped package to the registry (for free), just add the `--access public` flag. This behavior can be enabled by default through the `npmPublishAccess` settings.\n ',examples:[["Publish the active workspace","yarn npm publish"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);if(a.manifest.private)throw new nt("Private workspaces cannot be published");if(a.manifest.name===null||a.manifest.version===null)throw new nt("Workspaces must have valid names and versions to be published on an external registry");await s.restoreInstallState();let n=a.manifest.name,c=a.manifest.version,f=pi.getPublishRegistry(a.manifest,{configuration:r});return(await Ot.start({configuration:r,stdout:this.context.stdout},async h=>{if(this.tolerateRepublish)try{let E=await an.get(an.getIdentUrl(n),{configuration:r,registry:f,ident:n,jsonResponse:!0});if(!Object.hasOwn(E,"versions"))throw new Yt(15,'Registry returned invalid data for - missing "versions" field');if(Object.hasOwn(E.versions,c)){h.reportWarning(0,`Registry already knows about version ${c}; skipping.`);return}}catch(E){if(E.originalError?.response?.statusCode!==404)throw E}await In.maybeExecuteWorkspaceLifecycleScript(a,"prepublish",{report:h}),await IA.prepareForPack(a,{report:h},async()=>{let E=await IA.genPackList(a);for(let N of E)h.reportInfo(null,N);let C=await IA.genPackStream(a,E),S=await je.bufferStream(C),P=await D1.getGitHead(a.cwd),I=!1;a.manifest.publishConfig&&"provenance"in a.manifest.publishConfig?(I=!!a.manifest.publishConfig.provenance,I?h.reportInfo(null,"Generating provenance statement because `publishConfig.provenance` field is set."):h.reportInfo(null,"Skipping provenance statement because `publishConfig.provenance` field is set to false.")):this.provenance?(I=!0,h.reportInfo(null,"Generating provenance statement because `--provenance` flag is set.")):r.get("npmPublishProvenance")&&(I=!0,h.reportInfo(null,"Generating provenance statement because `npmPublishProvenance` setting is set."));let R=await D1.makePublishBody(a,S,{access:this.access,tag:this.tag,registry:f,gitHead:P,provenance:I});await an.put(an.getIdentUrl(n),R,{configuration:r,registry:f,ident:n,otp:this.otp,jsonResponse:!0})}),h.reportInfo(0,"Package archive published")})).exitCode()}};Ve();Wt();var SOe=et(fi());Ve();bt();Wt();var F1=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.package=ge.String({required:!1})}static{this.paths=[["npm","tag","list"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"list all dist-tags of a package",details:` + This command will list all tags of a package from the npm registry. + + If the package is not specified, Yarn will default to the current workspace. + `,examples:[["List all tags of package `my-pkg`","yarn npm tag list my-pkg"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n;if(typeof this.package<"u")n=q.parseIdent(this.package);else{if(!a)throw new ar(s.cwd,this.context.cwd);if(!a.manifest.name)throw new nt(`Missing 'name' field in ${ue.fromPortablePath(K.join(a.cwd,Er.manifest))}`);n=a.manifest.name}let c=await AP(n,r),p={children:je.sortMap(Object.entries(c),([h])=>h).map(([h,E])=>({value:he.tuple(he.Type.RESOLUTION,{descriptor:q.makeDescriptor(n,h),locator:q.makeLocator(n,E)})}))};return ks.emitTree(p,{configuration:r,json:this.json,stdout:this.context.stdout})}};async function AP(t,e){let r=`/-/package${an.getIdentUrl(t)}/dist-tags`;return an.get(r,{configuration:e,ident:t,jsonResponse:!0,customErrorMessage:an.customPackageError})}var N1=class extends ut{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[["npm","tag","add"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"add a tag for a specific version of a package",details:` + This command will add a tag to the npm registry for a specific version of a package. If the tag already exists, it will be overwritten. + `,examples:[["Add a `beta` tag for version `2.3.4-beta.4` of package `my-pkg`","yarn npm tag add my-pkg@2.3.4-beta.4 beta"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=q.parseDescriptor(this.package,!0),c=n.range;if(!SOe.default.valid(c))throw new nt(`The range ${he.pretty(r,n.range,he.Type.RANGE)} must be a valid semver version`);let f=pi.getPublishRegistry(a.manifest,{configuration:r}),p=he.pretty(r,n,he.Type.IDENT),h=he.pretty(r,c,he.Type.RANGE),E=he.pretty(r,this.tag,he.Type.CODE);return(await Ot.start({configuration:r,stdout:this.context.stdout},async S=>{let P=await AP(n,r);Object.hasOwn(P,this.tag)&&P[this.tag]===c&&S.reportWarning(0,`Tag ${E} is already set to version ${h}`);let I=`/-/package${an.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await an.put(I,c,{configuration:r,registry:f,ident:n,jsonRequest:!0,jsonResponse:!0}),S.reportInfo(0,`Tag ${E} added to version ${h} of package ${p}`)})).exitCode()}};Ve();Wt();var O1=class extends ut{constructor(){super(...arguments);this.package=ge.String();this.tag=ge.String()}static{this.paths=[["npm","tag","remove"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"remove a tag from a package",details:` + This command will remove a tag from a package from the npm registry. + `,examples:[["Remove the `beta` tag from package `my-pkg`","yarn npm tag remove my-pkg beta"]]})}async execute(){if(this.tag==="latest")throw new nt("The 'latest' tag cannot be removed.");let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=q.parseIdent(this.package),c=pi.getPublishRegistry(a.manifest,{configuration:r}),f=he.pretty(r,this.tag,he.Type.CODE),p=he.pretty(r,n,he.Type.IDENT),h=await AP(n,r);if(!Object.hasOwn(h,this.tag))throw new nt(`${f} is not a tag of package ${p}`);return(await Ot.start({configuration:r,stdout:this.context.stdout},async C=>{let S=`/-/package${an.getIdentUrl(n)}/dist-tags/${encodeURIComponent(this.tag)}`;await an.del(S,{configuration:r,registry:c,ident:n,jsonResponse:!0}),C.reportInfo(0,`Tag ${f} removed from package ${p}`)})).exitCode()}};Ve();Ve();Wt();var L1=class extends ut{constructor(){super(...arguments);this.scope=ge.String("-s,--scope",{description:"Print username for the registry configured for a given scope"});this.publish=ge.Boolean("--publish",!1,{description:"Print username for the publish registry"})}static{this.paths=[["npm","whoami"]]}static{this.usage=ot.Usage({category:"Npm-related commands",description:"display the name of the authenticated user",details:"\n Print the username associated with the current authentication settings to the standard output.\n\n When using `-s,--scope`, the username printed will be the one that matches the authentication settings of the registry associated with the given scope (those settings can be overriden using the `npmRegistries` map, and the registry associated with the scope is configured via the `npmScopes` map).\n\n When using `--publish`, the registry we'll select will by default be the one used when publishing packages (`publishConfig.registry` or `npmPublishRegistry` if available, otherwise we'll fallback to the regular `npmRegistryServer`).\n ",examples:[["Print username for the default registry","yarn npm whoami"],["Print username for the registry on a given scope","yarn npm whoami --scope company"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),s;return this.scope&&this.publish?s=pi.getScopeRegistry(this.scope,{configuration:r,type:pi.RegistryType.PUBLISH_REGISTRY}):this.scope?s=pi.getScopeRegistry(this.scope,{configuration:r}):this.publish?s=pi.getPublishRegistry((await rC(r,this.context.cwd)).manifest,{configuration:r}):s=pi.getDefaultRegistry({configuration:r}),(await Ot.start({configuration:r,stdout:this.context.stdout},async n=>{let c;try{c=await an.get("/-/whoami",{configuration:r,registry:s,authType:an.AuthType.ALWAYS_AUTH,jsonResponse:!0,ident:this.scope?q.makeIdent(this.scope,""):void 0})}catch(f){if(f.response?.statusCode===401||f.response?.statusCode===403){n.reportError(41,"Authentication failed - your credentials may have expired");return}else throw f}n.reportInfo(0,c.username)})).exitCode()}};var Vjt={configuration:{npmPublishAccess:{description:"Default access of the published packages",type:"STRING",default:null},npmPublishProvenance:{description:"Whether to generate provenance for the published packages",type:"BOOLEAN",default:!1},npmAuditExcludePackages:{description:"Array of glob patterns of packages to exclude from npm audit",type:"STRING",default:[],isArray:!0},npmAuditIgnoreAdvisories:{description:"Array of glob patterns of advisory IDs to exclude from npm audit",type:"STRING",default:[],isArray:!0}},commands:[P1,x1,k1,T1,R1,N1,F1,O1,L1]},Kjt=Vjt;var Fz={};Vt(Fz,{PatchCommand:()=>q1,PatchCommitCommand:()=>j1,PatchFetcher:()=>mP,PatchResolver:()=>yP,default:()=>A6t,patchUtils:()=>dy});Ve();Ve();bt();rA();var dy={};Vt(dy,{applyPatchFile:()=>HL,diffFolders:()=>Tz,ensureUnpatchedDescriptor:()=>Pz,ensureUnpatchedLocator:()=>qL,extractPackageToDisk:()=>Qz,extractPatchFlags:()=>TOe,isParentRequired:()=>kz,isPatchDescriptor:()=>jL,isPatchLocator:()=>Fg,loadPatchFiles:()=>dP,makeDescriptor:()=>WL,makeLocator:()=>xz,makePatchHash:()=>Rz,parseDescriptor:()=>hP,parseLocator:()=>gP,parsePatchFile:()=>pP,unpatchDescriptor:()=>c6t,unpatchLocator:()=>u6t});Ve();bt();Ve();bt();var Jjt=/^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*/;function M1(t){return K.relative(vt.root,K.resolve(vt.root,ue.toPortablePath(t)))}function zjt(t){let e=t.trim().match(Jjt);if(!e)throw new Error(`Bad header line: '${t}'`);return{original:{start:Math.max(Number(e[1]),1),length:Number(e[3]||1)},patched:{start:Math.max(Number(e[4]),1),length:Number(e[6]||1)}}}var Zjt=420,Xjt=493;var DOe=()=>({semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null}),$jt=t=>({header:zjt(t),parts:[]}),e6t={"@":"header","-":"deletion","+":"insertion"," ":"context","\\":"pragma",undefined:"context"};function t6t(t){let e=[],r=DOe(),s="parsing header",a=null,n=null;function c(){a&&(n&&(a.parts.push(n),n=null),r.hunks.push(a),a=null)}function f(){c(),e.push(r),r=DOe()}for(let p=0;p0?"patch":"mode change",W=null;switch(U){case"rename":{if(!E||!C)throw new Error("Bad parser state: rename from & to not given");e.push({type:"rename",semverExclusivity:s,fromPath:M1(E),toPath:M1(C)}),W=C}break;case"file deletion":{let te=a||I;if(!te)throw new Error("Bad parse state: no path given for file deletion");e.push({type:"file deletion",semverExclusivity:s,hunk:N&&N[0]||null,path:M1(te),mode:UL(p),hash:S})}break;case"file creation":{let te=n||R;if(!te)throw new Error("Bad parse state: no path given for file creation");e.push({type:"file creation",semverExclusivity:s,hunk:N&&N[0]||null,path:M1(te),mode:UL(h),hash:P})}break;case"patch":case"mode change":W=R||n;break;default:je.assertNever(U);break}W&&c&&f&&c!==f&&e.push({type:"mode change",semverExclusivity:s,path:M1(W),oldMode:UL(c),newMode:UL(f)}),W&&N&&N.length&&e.push({type:"patch",semverExclusivity:s,path:M1(W),hunks:N,beforeHash:S,afterHash:P})}if(e.length===0)throw new Error("Unable to parse patch file: No changes found. Make sure the patch is a valid UTF8 encoded string");return e}function UL(t){let e=parseInt(t,8)&511;if(e!==Zjt&&e!==Xjt)throw new Error(`Unexpected file mode string: ${t}`);return e}function pP(t){let e=t.split(/\n/g);return e[e.length-1]===""&&e.pop(),r6t(t6t(e))}function n6t(t){let e=0,r=0;for(let{type:s,lines:a}of t.parts)switch(s){case"context":r+=a.length,e+=a.length;break;case"deletion":e+=a.length;break;case"insertion":r+=a.length;break;default:je.assertNever(s);break}if(e!==t.header.original.length||r!==t.header.patched.length){let s=a=>a<0?a:`+${a}`;throw new Error(`hunk header integrity check failed (expected @@ ${s(t.header.original.length)} ${s(t.header.patched.length)} @@, got @@ ${s(e)} ${s(r)} @@)`)}}Ve();bt();var _1=class extends Error{constructor(r,s){super(`Cannot apply hunk #${r+1}`);this.hunk=s}};async function U1(t,e,r){let s=await t.lstatPromise(e),a=await r();typeof a<"u"&&(e=a),await t.lutimesPromise(e,s.atime,s.mtime)}async function HL(t,{baseFs:e=new Yn,dryRun:r=!1,version:s=null}={}){for(let a of t)if(!(a.semverExclusivity!==null&&s!==null&&!Or.satisfiesWithPrereleases(s,a.semverExclusivity)))switch(a.type){case"file deletion":if(r){if(!e.existsSync(a.path))throw new Error(`Trying to delete a file that doesn't exist: ${a.path}`)}else await U1(e,K.dirname(a.path),async()=>{await e.unlinkPromise(a.path)});break;case"rename":if(r){if(!e.existsSync(a.fromPath))throw new Error(`Trying to move a file that doesn't exist: ${a.fromPath}`)}else await U1(e,K.dirname(a.fromPath),async()=>{await U1(e,K.dirname(a.toPath),async()=>{await U1(e,a.fromPath,async()=>(await e.movePromise(a.fromPath,a.toPath),a.toPath))})});break;case"file creation":if(r){if(e.existsSync(a.path))throw new Error(`Trying to create a file that already exists: ${a.path}`)}else{let n=a.hunk?a.hunk.parts[0].lines.join(` +`)+(a.hunk.parts[0].noNewlineAtEndOfFile?"":` +`):"";await e.mkdirpPromise(K.dirname(a.path),{chmod:493,utimes:[ui.SAFE_TIME,ui.SAFE_TIME]}),await e.writeFilePromise(a.path,n,{mode:a.mode}),await e.utimesPromise(a.path,ui.SAFE_TIME,ui.SAFE_TIME)}break;case"patch":await U1(e,a.path,async()=>{await o6t(a,{baseFs:e,dryRun:r})});break;case"mode change":{let c=(await e.statPromise(a.path)).mode;if(bOe(a.newMode)!==bOe(c))continue;await U1(e,a.path,async()=>{await e.chmodPromise(a.path,a.newMode)})}break;default:je.assertNever(a);break}}function bOe(t){return(t&64)>0}function POe(t){return t.replace(/\s+$/,"")}function s6t(t,e){return POe(t)===POe(e)}async function o6t({hunks:t,path:e},{baseFs:r,dryRun:s=!1}){let a=await r.statSync(e).mode,c=(await r.readFileSync(e,"utf8")).split(/\n/),f=[],p=0,h=0;for(let C of t){let S=Math.max(h,C.header.patched.start+p),P=Math.max(0,S-h),I=Math.max(0,c.length-S-C.header.original.length),R=Math.max(P,I),N=0,U=0,W=null;for(;N<=R;){if(N<=P&&(U=S-N,W=xOe(C,c,U),W!==null)){N=-N;break}if(N<=I&&(U=S+N,W=xOe(C,c,U),W!==null))break;N+=1}if(W===null)throw new _1(t.indexOf(C),C);f.push(W),p+=N,h=U+C.header.original.length}if(s)return;let E=0;for(let C of f)for(let S of C)switch(S.type){case"splice":{let P=S.index+E;c.splice(P,S.numToDelete,...S.linesToInsert),E+=S.linesToInsert.length-S.numToDelete}break;case"pop":c.pop();break;case"push":c.push(S.line);break;default:je.assertNever(S);break}await r.writeFilePromise(e,c.join(` +`),{mode:a})}function xOe(t,e,r){let s=[];for(let a of t.parts)switch(a.type){case"context":case"deletion":{for(let n of a.lines){let c=e[r];if(c==null||!s6t(c,n))return null;r+=1}a.type==="deletion"&&(s.push({type:"splice",index:r-a.lines.length,numToDelete:a.lines.length,linesToInsert:[]}),a.noNewlineAtEndOfFile&&s.push({type:"push",line:""}))}break;case"insertion":s.push({type:"splice",index:r,numToDelete:0,linesToInsert:a.lines}),a.noNewlineAtEndOfFile&&s.push({type:"pop"});break;default:je.assertNever(a.type);break}return s}var l6t=/^builtin<([^>]+)>$/;function H1(t,e){let{protocol:r,source:s,selector:a,params:n}=q.parseRange(t);if(r!=="patch:")throw new Error("Invalid patch range");if(s===null)throw new Error("Patch locators must explicitly define their source");let c=a?a.split(/&/).map(E=>ue.toPortablePath(E)):[],f=n&&typeof n.locator=="string"?q.parseLocator(n.locator):null,p=n&&typeof n.version=="string"?n.version:null,h=e(s);return{parentLocator:f,sourceItem:h,patchPaths:c,sourceVersion:p}}function jL(t){return t.range.startsWith("patch:")}function Fg(t){return t.reference.startsWith("patch:")}function hP(t){let{sourceItem:e,...r}=H1(t.range,q.parseDescriptor);return{...r,sourceDescriptor:e}}function gP(t){let{sourceItem:e,...r}=H1(t.reference,q.parseLocator);return{...r,sourceLocator:e}}function c6t(t){let{sourceItem:e}=H1(t.range,q.parseDescriptor);return e}function u6t(t){let{sourceItem:e}=H1(t.reference,q.parseLocator);return e}function Pz(t){if(!jL(t))return t;let{sourceItem:e}=H1(t.range,q.parseDescriptor);return e}function qL(t){if(!Fg(t))return t;let{sourceItem:e}=H1(t.reference,q.parseLocator);return e}function kOe({parentLocator:t,sourceItem:e,patchPaths:r,sourceVersion:s,patchHash:a},n){let c=t!==null?{locator:q.stringifyLocator(t)}:{},f=typeof s<"u"?{version:s}:{},p=typeof a<"u"?{hash:a}:{};return q.makeRange({protocol:"patch:",source:n(e),selector:r.join("&"),params:{...f,...p,...c}})}function WL(t,{parentLocator:e,sourceDescriptor:r,patchPaths:s}){return q.makeDescriptor(t,kOe({parentLocator:e,sourceItem:r,patchPaths:s},q.stringifyDescriptor))}function xz(t,{parentLocator:e,sourcePackage:r,patchPaths:s,patchHash:a}){return q.makeLocator(t,kOe({parentLocator:e,sourceItem:r,sourceVersion:r.version,patchPaths:s,patchHash:a},q.stringifyLocator))}function QOe({onAbsolute:t,onRelative:e,onProject:r,onBuiltin:s},a){let n=a.lastIndexOf("!");n!==-1&&(a=a.slice(n+1));let c=a.match(l6t);return c!==null?s(c[1]):a.startsWith("~/")?r(a.slice(2)):K.isAbsolute(a)?t(a):e(a)}function TOe(t){let e=t.lastIndexOf("!");return{optional:(e!==-1?new Set(t.slice(0,e).split(/!/)):new Set).has("optional")}}function kz(t){return QOe({onAbsolute:()=>!1,onRelative:()=>!0,onProject:()=>!1,onBuiltin:()=>!1},t)}async function dP(t,e,r){let s=t!==null?await r.fetcher.fetch(t,r):null,a=s&&s.localPath?{packageFs:new Sn(vt.root),prefixPath:K.relative(vt.root,s.localPath)}:s;s&&s!==a&&s.releaseFs&&s.releaseFs();let n=await je.releaseAfterUseAsync(async()=>await Promise.all(e.map(async c=>{let f=TOe(c),p=await QOe({onAbsolute:async h=>await le.readFilePromise(h,"utf8"),onRelative:async h=>{if(a===null)throw new Error("Assertion failed: The parent locator should have been fetched");return await a.packageFs.readFilePromise(K.join(a.prefixPath,h),"utf8")},onProject:async h=>await le.readFilePromise(K.join(r.project.cwd,h),"utf8"),onBuiltin:async h=>await r.project.configuration.firstHook(E=>E.getBuiltinPatch,r.project,h)},c);return{...f,source:p}})));for(let c of n)typeof c.source=="string"&&(c.source=c.source.replace(/\r\n?/g,` +`));return n}async function Qz(t,{cache:e,project:r}){let s=r.storedPackages.get(t.locatorHash);if(typeof s>"u")throw new Error("Assertion failed: Expected the package to be registered");let a=qL(t),n=r.storedChecksums,c=new Wi,f=await le.mktempPromise(),p=K.join(f,"source"),h=K.join(f,"user"),E=K.join(f,".yarn-patch.json"),C=r.configuration.makeFetcher(),S=[];try{let P,I;if(t.locatorHash===a.locatorHash){let R=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c});S.push(()=>R.releaseFs?.()),P=R,I=R}else P=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>P.releaseFs?.()),I=await C.fetch(t,{cache:e,project:r,fetcher:C,checksums:n,report:c}),S.push(()=>I.releaseFs?.());await Promise.all([le.copyPromise(p,P.prefixPath,{baseFs:P.packageFs}),le.copyPromise(h,I.prefixPath,{baseFs:I.packageFs}),le.writeJsonPromise(E,{locator:q.stringifyLocator(t),version:s.version})])}finally{for(let P of S)P()}return le.detachTemp(f),h}async function Tz(t,e){let r=ue.fromPortablePath(t).replace(/\\/g,"/"),s=ue.fromPortablePath(e).replace(/\\/g,"/"),{stdout:a,stderr:n}=await Gr.execvp("git",["-c","core.safecrlf=false","diff","--src-prefix=a/","--dst-prefix=b/","--ignore-cr-at-eol","--full-index","--no-index","--no-renames","--text",r,s],{cwd:ue.toPortablePath(process.cwd()),env:{...process.env,GIT_CONFIG_NOSYSTEM:"1",HOME:"",XDG_CONFIG_HOME:"",USERPROFILE:""}});if(n.length>0)throw new Error(`Unable to diff directories. Make sure you have a recent version of 'git' available in PATH. +The following error was reported by 'git': +${n}`);let c=r.startsWith("/")?f=>f.slice(1):f=>f;return a.replace(new RegExp(`(a|b)(${je.escapeRegExp(`/${c(r)}/`)})`,"g"),"$1/").replace(new RegExp(`(a|b)${je.escapeRegExp(`/${c(s)}/`)}`,"g"),"$1/").replace(new RegExp(je.escapeRegExp(`${r}/`),"g"),"").replace(new RegExp(je.escapeRegExp(`${s}/`),"g"),"")}function Rz(t,e){let r=[];for(let{source:s}of t){if(s===null)continue;let a=pP(s);for(let n of a){let{semverExclusivity:c,...f}=n;c!==null&&e!==null&&!Or.satisfiesWithPrereleases(e,c)||r.push(JSON.stringify(f))}}return Nn.makeHash(`${3}`,...r).slice(0,6)}Ve();function ROe(t,{configuration:e,report:r}){for(let s of t.parts)for(let a of s.lines)switch(s.type){case"context":r.reportInfo(null,` ${he.pretty(e,a,"grey")}`);break;case"deletion":r.reportError(28,`- ${he.pretty(e,a,he.Type.REMOVED)}`);break;case"insertion":r.reportError(28,`+ ${he.pretty(e,a,he.Type.ADDED)}`);break;default:je.assertNever(s.type)}}var mP=class{supports(e,r){return!!Fg(e)}getLocalPath(e,r){return null}async fetch(e,r){let s=r.checksums.get(e.locatorHash)||null,[a,n,c]=await r.cache.fetchPackageFromCache(e,s,{onHit:()=>r.report.reportCacheHit(e),onMiss:()=>r.report.reportCacheMiss(e,`${q.prettyLocator(r.project.configuration,e)} can't be found in the cache and will be fetched from the disk`),loader:()=>this.patchPackage(e,r),...r.cacheOptions});return{packageFs:a,releaseFs:n,prefixPath:q.getIdentVendorPath(e),localPath:this.getLocalPath(e,r),checksum:c}}async patchPackage(e,r){let{parentLocator:s,sourceLocator:a,sourceVersion:n,patchPaths:c}=gP(e),f=await dP(s,c,r),p=await le.mktempPromise(),h=K.join(p,"current.zip"),E=await r.fetcher.fetch(a,r),C=q.getIdentVendorPath(e),S=new ps(h,{create:!0,level:r.project.configuration.get("compressionLevel")});await je.releaseAfterUseAsync(async()=>{await S.copyPromise(C,E.prefixPath,{baseFs:E.packageFs,stableSort:!0})},E.releaseFs),S.saveAndClose();for(let{source:P,optional:I}of f){if(P===null)continue;let R=new ps(h,{level:r.project.configuration.get("compressionLevel")}),N=new Sn(K.resolve(vt.root,C),{baseFs:R});try{await HL(pP(P),{baseFs:N,version:n})}catch(U){if(!(U instanceof _1))throw U;let W=r.project.configuration.get("enableInlineHunks"),te=!W&&!I?" (set enableInlineHunks for details)":"",ie=`${q.prettyLocator(r.project.configuration,e)}: ${U.message}${te}`,Ae=ce=>{W&&ROe(U.hunk,{configuration:r.project.configuration,report:ce})};if(R.discardAndClose(),I){r.report.reportWarningOnce(66,ie,{reportExtra:Ae});continue}else throw new Yt(66,ie,Ae)}R.saveAndClose()}return new ps(h,{level:r.project.configuration.get("compressionLevel")})}};Ve();var yP=class{supportsDescriptor(e,r){return!!jL(e)}supportsLocator(e,r){return!!Fg(e)}shouldPersistResolution(e,r){return!1}bindDescriptor(e,r,s){let{patchPaths:a}=hP(e);return a.every(n=>!kz(n))?e:q.bindDescriptor(e,{locator:q.stringifyLocator(r)})}getResolutionDependencies(e,r){let{sourceDescriptor:s}=hP(e);return{sourceDescriptor:r.project.configuration.normalizeDependency(s)}}async getCandidates(e,r,s){if(!s.fetchOptions)throw new Error("Assertion failed: This resolver cannot be used unless a fetcher is configured");let{parentLocator:a,patchPaths:n}=hP(e),c=await dP(a,n,s.fetchOptions),f=r.sourceDescriptor;if(typeof f>"u")throw new Error("Assertion failed: The dependency should have been resolved");let p=Rz(c,f.version);return[xz(e,{parentLocator:a,sourcePackage:f,patchPaths:n,patchHash:p})]}async getSatisfying(e,r,s,a){let[n]=await this.getCandidates(e,r,a);return{locators:s.filter(c=>c.locatorHash===n.locatorHash),sorted:!1}}async resolve(e,r){let{sourceLocator:s}=gP(e);return{...await r.resolver.resolve(s,r),...e}}};Ve();bt();Wt();var j1=class extends ut{constructor(){super(...arguments);this.save=ge.Boolean("-s,--save",!1,{description:"Add the patch to your resolution entries"});this.patchFolder=ge.String()}static{this.paths=[["patch-commit"]]}static{this.usage=ot.Usage({description:"generate a patch out of a directory",details:"\n By default, this will print a patchfile on stdout based on the diff between the folder passed in and the original version of the package. Such file is suitable for consumption with the `patch:` protocol.\n\n With the `-s,--save` option set, the patchfile won't be printed on stdout anymore and will instead be stored within a local file (by default kept within `.yarn/patches`, but configurable via the `patchFolder` setting). A `resolutions` entry will also be added to your top-level manifest, referencing the patched package via the `patch:` protocol.\n\n Note that only folders generated by `yarn patch` are accepted as valid input for `yarn patch-commit`.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=K.resolve(this.context.cwd,ue.toPortablePath(this.patchFolder)),c=K.join(n,"../source"),f=K.join(n,"../.yarn-patch.json");if(!le.existsSync(c))throw new nt("The argument folder didn't get created by 'yarn patch'");let p=await Tz(c,n),h=await le.readJsonPromise(f),E=q.parseLocator(h.locator,!0);if(!s.storedPackages.has(E.locatorHash))throw new nt("No package found in the project for the given locator");if(!this.save){this.context.stdout.write(p);return}let C=r.get("patchFolder"),S=K.join(C,`${q.slugifyLocator(E)}.patch`);await le.mkdirPromise(C,{recursive:!0}),await le.writeFilePromise(S,p);let P=[],I=new Map;for(let R of s.storedPackages.values()){if(q.isVirtualLocator(R))continue;let N=R.dependencies.get(E.identHash);if(!N)continue;let U=q.ensureDevirtualizedDescriptor(N),W=Pz(U),te=s.storedResolutions.get(W.descriptorHash);if(!te)throw new Error("Assertion failed: Expected the resolution to have been registered");if(!s.storedPackages.get(te))throw new Error("Assertion failed: Expected the package to have been registered");let Ae=s.tryWorkspaceByLocator(R);if(Ae)P.push(Ae);else{let ce=s.originalPackages.get(R.locatorHash);if(!ce)throw new Error("Assertion failed: Expected the original package to have been registered");let me=ce.dependencies.get(N.identHash);if(!me)throw new Error("Assertion failed: Expected the original dependency to have been registered");I.set(me.descriptorHash,me)}}for(let R of P)for(let N of Ht.hardDependencies){let U=R.manifest[N].get(E.identHash);if(!U)continue;let W=WL(U,{parentLocator:null,sourceDescriptor:q.convertLocatorToDescriptor(E),patchPaths:[K.join(Er.home,K.relative(s.cwd,S))]});R.manifest[N].set(U.identHash,W)}for(let R of I.values()){let N=WL(R,{parentLocator:null,sourceDescriptor:q.convertLocatorToDescriptor(E),patchPaths:[K.join(Er.home,K.relative(s.cwd,S))]});s.topLevelWorkspace.manifest.resolutions.push({pattern:{descriptor:{fullName:q.stringifyIdent(N),description:R.range}},reference:N.range})}await s.persist()}};Ve();bt();Wt();var q1=class extends ut{constructor(){super(...arguments);this.update=ge.Boolean("-u,--update",!1,{description:"Reapply local patches that already apply to this packages"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.package=ge.String()}static{this.paths=[["patch"]]}static{this.usage=ot.Usage({description:"prepare a package for patching",details:"\n This command will cause a package to be extracted in a temporary directory intended to be editable at will.\n\n Once you're done with your changes, run `yarn patch-commit -s path` (with `path` being the temporary directory you received) to generate a patchfile and register it into your top-level manifest via the `patch:` protocol. Run `yarn patch-commit -h` for more details.\n\n Calling the command when you already have a patch won't import it by default (in other words, the default behavior is to reset existing patches). However, adding the `-u,--update` flag will import any current patch.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let c=q.parseLocator(this.package);if(c.reference==="unknown"){let f=je.mapAndFilter([...s.storedPackages.values()],p=>p.identHash!==c.identHash?je.mapAndFilter.skip:q.isVirtualLocator(p)?je.mapAndFilter.skip:Fg(p)!==this.update?je.mapAndFilter.skip:p);if(f.length===0)throw new nt("No package found in the project for the given locator");if(f.length>1)throw new nt(`Multiple candidate packages found; explicitly choose one of them (use \`yarn why \` to get more information as to who depends on them): +${f.map(p=>` +- ${q.prettyLocator(r,p)}`).join("")}`);c=f[0]}if(!s.storedPackages.has(c.locatorHash))throw new nt("No package found in the project for the given locator");await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=qL(c),h=await Qz(c,{cache:n,project:s});f.reportJson({locator:q.stringifyLocator(p),path:ue.fromPortablePath(h)});let E=this.update?" along with its current modifications":"";f.reportInfo(0,`Package ${q.prettyLocator(r,p)} got extracted with success${E}!`),f.reportInfo(0,`You can now edit the following folder: ${he.pretty(r,ue.fromPortablePath(h),"magenta")}`),f.reportInfo(0,`Once you are done run ${he.pretty(r,`yarn patch-commit -s ${process.platform==="win32"?'"':""}${ue.fromPortablePath(h)}${process.platform==="win32"?'"':""}`,"cyan")} and Yarn will store a patchfile based on your changes.`)})}};var f6t={configuration:{enableInlineHunks:{description:"If true, the installs will print unmatched patch hunks",type:"BOOLEAN",default:!1},patchFolder:{description:"Folder where the patch files must be written",type:"ABSOLUTE_PATH",default:"./.yarn/patches"}},commands:[j1,q1],fetchers:[mP],resolvers:[yP]},A6t=f6t;var Lz={};Vt(Lz,{PnpmLinker:()=>EP,default:()=>y6t});Ve();bt();Wt();var EP=class{getCustomDataKey(){return JSON.stringify({name:"PnpmLinker",version:3})}supportsPackage(e,r){return this.isEnabled(r)}async findPackageLocation(e,r){if(!this.isEnabled(r))throw new Error("Assertion failed: Expected the pnpm linker to be enabled");let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=a.pathsByLocator.get(e.locatorHash);if(typeof n>"u")throw new nt(`Couldn't find ${q.prettyLocator(r.project.configuration,e)} in the currently installed pnpm map - running an install might help`);return n.packageLocation}async findPackageLocator(e,r){if(!this.isEnabled(r))return null;let s=this.getCustomDataKey(),a=r.project.linkersCustomData.get(s);if(!a)throw new nt(`The project in ${he.pretty(r.project.configuration,`${r.project.cwd}/package.json`,he.Type.PATH)} doesn't seem to have been installed - running an install there might help`);let n=e.match(/(^.*\/node_modules\/(@[^/]*\/)?[^/]+)(\/.*$)/);if(n){let p=a.locatorByPath.get(n[1]);if(p)return p}let c=e,f=e;do{f=c,c=K.dirname(f);let p=a.locatorByPath.get(f);if(p)return p}while(c!==f);return null}makeInstaller(e){return new Nz(e)}isEnabled(e){return e.project.configuration.get("nodeLinker")==="pnpm"}},Nz=class{constructor(e){this.opts=e;this.asyncActions=new je.AsyncActions(10);this.customData={pathsByLocator:new Map,locatorByPath:new Map};this.indexFolderPromise=px(le,{indexPath:K.join(e.project.configuration.get("globalFolder"),"index")})}attachCustomData(e){}async installPackage(e,r,s){switch(e.linkType){case"SOFT":return this.installPackageSoft(e,r,s);case"HARD":return this.installPackageHard(e,r,s)}throw new Error("Assertion failed: Unsupported package link type")}async installPackageSoft(e,r,s){let a=K.resolve(r.packageFs.getRealPath(),r.prefixPath),n=this.opts.project.tryWorkspaceByLocator(e)?K.join(a,Er.nodeModules):null;return this.customData.pathsByLocator.set(e.locatorHash,{packageLocation:a,dependenciesLocation:n}),{packageLocation:a,buildRequest:null}}async installPackageHard(e,r,s){let a=h6t(e,{project:this.opts.project}),n=a.packageLocation;this.customData.locatorByPath.set(n,q.stringifyLocator(e)),this.customData.pathsByLocator.set(e.locatorHash,a),s.holdFetchResult(this.asyncActions.set(e.locatorHash,async()=>{await le.mkdirPromise(n,{recursive:!0}),await le.copyPromise(n,r.prefixPath,{baseFs:r.packageFs,overwrite:!1,linkStrategy:{type:"HardlinkFromIndex",indexPath:await this.indexFolderPromise,autoRepair:!0}})}));let f=q.isVirtualLocator(e)?q.devirtualizeLocator(e):e,p={manifest:await Ht.tryFind(r.prefixPath,{baseFs:r.packageFs})??new Ht,misc:{hasBindingGyp:mA.hasBindingGyp(r)}},h=this.opts.project.getDependencyMeta(f,e.version),E=mA.extractBuildRequest(e,p,h,{configuration:this.opts.project.configuration});return{packageLocation:n,buildRequest:E}}async attachInternalDependencies(e,r){if(this.opts.project.configuration.get("nodeLinker")!=="pnpm"||!FOe(e,{project:this.opts.project}))return;let s=this.customData.pathsByLocator.get(e.locatorHash);if(typeof s>"u")throw new Error(`Assertion failed: Expected the package to have been registered (${q.stringifyLocator(e)})`);let{dependenciesLocation:a}=s;a&&this.asyncActions.reduce(e.locatorHash,async n=>{await le.mkdirPromise(a,{recursive:!0});let c=await g6t(a),f=new Map(c),p=[n],h=(C,S)=>{let P=S;FOe(S,{project:this.opts.project})||(this.opts.report.reportWarningOnce(0,"The pnpm linker doesn't support providing different versions to workspaces' peer dependencies"),P=q.devirtualizeLocator(S));let I=this.customData.pathsByLocator.get(P.locatorHash);if(typeof I>"u")throw new Error(`Assertion failed: Expected the package to have been registered (${q.stringifyLocator(S)})`);let R=q.stringifyIdent(C),N=K.join(a,R),U=K.relative(K.dirname(N),I.packageLocation),W=f.get(R);f.delete(R),p.push(Promise.resolve().then(async()=>{if(W){if(W.isSymbolicLink()&&await le.readlinkPromise(N)===U)return;await le.removePromise(N)}await le.mkdirpPromise(K.dirname(N)),process.platform=="win32"&&this.opts.project.configuration.get("winLinkType")==="junctions"?await le.symlinkPromise(I.packageLocation,N,"junction"):await le.symlinkPromise(U,N)}))},E=!1;for(let[C,S]of r)C.identHash===e.identHash&&(E=!0),h(C,S);!E&&!this.opts.project.tryWorkspaceByLocator(e)&&h(q.convertLocatorToDescriptor(e),e),p.push(d6t(a,f)),await Promise.all(p)})}async attachExternalDependents(e,r){throw new Error("External dependencies haven't been implemented for the pnpm linker")}async finalizeInstall(){let e=NOe(this.opts.project);if(this.opts.project.configuration.get("nodeLinker")!=="pnpm")await le.removePromise(e);else{let r;try{r=new Set(await le.readdirPromise(e))}catch{r=new Set}for(let{dependenciesLocation:s}of this.customData.pathsByLocator.values()){if(!s)continue;let a=K.contains(e,s);if(a===null)continue;let[n]=a.split(K.sep);r.delete(n)}await Promise.all([...r].map(async s=>{await le.removePromise(K.join(e,s))}))}return await this.asyncActions.wait(),await Oz(e),this.opts.project.configuration.get("nodeLinker")!=="node-modules"&&await Oz(p6t(this.opts.project)),{customData:this.customData}}};function p6t(t){return K.join(t.cwd,Er.nodeModules)}function NOe(t){return t.configuration.get("pnpmStoreFolder")}function h6t(t,{project:e}){let r=q.slugifyLocator(t),s=NOe(e),a=K.join(s,r,"package"),n=K.join(s,r,Er.nodeModules);return{packageLocation:a,dependenciesLocation:n}}function FOe(t,{project:e}){return!q.isVirtualLocator(t)||!e.tryWorkspaceByLocator(t)}async function g6t(t){let e=new Map,r=[];try{r=await le.readdirPromise(t,{withFileTypes:!0})}catch(s){if(s.code!=="ENOENT")throw s}try{for(let s of r)if(!s.name.startsWith("."))if(s.name.startsWith("@")){let a=await le.readdirPromise(K.join(t,s.name),{withFileTypes:!0});if(a.length===0)e.set(s.name,s);else for(let n of a)e.set(`${s.name}/${n.name}`,n)}else e.set(s.name,s)}catch(s){if(s.code!=="ENOENT")throw s}return e}async function d6t(t,e){let r=[],s=new Set;for(let a of e.keys()){r.push(le.removePromise(K.join(t,a)));let n=q.tryParseIdent(a)?.scope;n&&s.add(`@${n}`)}return Promise.all(r).then(()=>Promise.all([...s].map(a=>Oz(K.join(t,a)))))}async function Oz(t){try{await le.rmdirPromise(t)}catch(e){if(e.code!=="ENOENT"&&e.code!=="ENOTEMPTY")throw e}}var m6t={configuration:{pnpmStoreFolder:{description:"By default, the store is stored in the 'node_modules/.store' of the project. Sometimes in CI scenario's it is convenient to store this in a different location so it can be cached and reused.",type:"ABSOLUTE_PATH",default:"./node_modules/.store"}},linkers:[EP]},y6t=m6t;var Gz={};Vt(Gz,{StageCommand:()=>G1,default:()=>x6t,stageUtils:()=>VL});Ve();bt();Wt();Ve();bt();var VL={};Vt(VL,{ActionType:()=>Mz,checkConsensus:()=>YL,expandDirectory:()=>Hz,findConsensus:()=>jz,findVcsRoot:()=>_z,genCommitMessage:()=>qz,getCommitPrefix:()=>OOe,isYarnFile:()=>Uz});bt();var Mz=(n=>(n[n.CREATE=0]="CREATE",n[n.DELETE=1]="DELETE",n[n.ADD=2]="ADD",n[n.REMOVE=3]="REMOVE",n[n.MODIFY=4]="MODIFY",n))(Mz||{});async function _z(t,{marker:e}){do if(!le.existsSync(K.join(t,e)))t=K.dirname(t);else return t;while(t!=="/");return null}function Uz(t,{roots:e,names:r}){if(r.has(K.basename(t)))return!0;do if(!e.has(t))t=K.dirname(t);else return!0;while(t!=="/");return!1}function Hz(t){let e=[],r=[t];for(;r.length>0;){let s=r.pop(),a=le.readdirSync(s);for(let n of a){let c=K.resolve(s,n);le.lstatSync(c).isDirectory()?r.push(c):e.push(c)}}return e}function YL(t,e){let r=0,s=0;for(let a of t)a!=="wip"&&(e.test(a)?r+=1:s+=1);return r>=s}function jz(t){let e=YL(t,/^(\w\(\w+\):\s*)?\w+s/),r=YL(t,/^(\w\(\w+\):\s*)?[A-Z]/),s=YL(t,/^\w\(\w+\):/);return{useThirdPerson:e,useUpperCase:r,useComponent:s}}function OOe(t){return t.useComponent?"chore(yarn): ":""}var E6t=new Map([[0,"create"],[1,"delete"],[2,"add"],[3,"remove"],[4,"update"]]);function qz(t,e){let r=OOe(t),s=[],a=e.slice().sort((n,c)=>n[0]-c[0]);for(;a.length>0;){let[n,c]=a.shift(),f=E6t.get(n);t.useUpperCase&&s.length===0&&(f=`${f[0].toUpperCase()}${f.slice(1)}`),t.useThirdPerson&&(f+="s");let p=[c];for(;a.length>0&&a[0][0]===n;){let[,E]=a.shift();p.push(E)}p.sort();let h=p.shift();p.length===1?h+=" (and one other)":p.length>1&&(h+=` (and ${p.length} others)`),s.push(`${f} ${h}`)}return`${r}${s.join(", ")}`}var I6t="Commit generated via `yarn stage`",C6t=11;async function LOe(t){let{code:e,stdout:r}=await Gr.execvp("git",["log","-1","--pretty=format:%H"],{cwd:t});return e===0?r.trim():null}async function w6t(t,e){let r=[],s=e.filter(h=>K.basename(h.path)==="package.json");for(let{action:h,path:E}of s){let C=K.relative(t,E);if(h===4){let S=await LOe(t),{stdout:P}=await Gr.execvp("git",["show",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ht.fromText(P),R=await Ht.fromFile(E),N=new Map([...R.dependencies,...R.devDependencies]),U=new Map([...I.dependencies,...I.devDependencies]);for(let[W,te]of U){let ie=q.stringifyIdent(te),Ae=N.get(W);Ae?Ae.range!==te.range&&r.push([4,`${ie} to ${Ae.range}`]):r.push([3,ie])}for(let[W,te]of N)U.has(W)||r.push([2,q.stringifyIdent(te)])}else if(h===0){let S=await Ht.fromFile(E);S.name?r.push([0,q.stringifyIdent(S.name)]):r.push([0,"a package"])}else if(h===1){let S=await LOe(t),{stdout:P}=await Gr.execvp("git",["show",`${S}:${C}`],{cwd:t,strict:!0}),I=await Ht.fromText(P);I.name?r.push([1,q.stringifyIdent(I.name)]):r.push([1,"a package"])}else throw new Error("Assertion failed: Unsupported action type")}let{code:a,stdout:n}=await Gr.execvp("git",["log",`-${C6t}`,"--pretty=format:%s"],{cwd:t}),c=a===0?n.split(/\n/g).filter(h=>h!==""):[],f=jz(c);return qz(f,r)}var B6t={0:[" A ","?? "],4:[" M "],1:[" D "]},v6t={0:["A "],4:["M "],1:["D "]},MOe={async findRoot(t){return await _z(t,{marker:".git"})},async filterChanges(t,e,r,s){let{stdout:a}=await Gr.execvp("git",["status","-s"],{cwd:t,strict:!0}),n=a.toString().split(/\n/g),c=s?.staged?v6t:B6t;return[].concat(...n.map(p=>{if(p==="")return[];let h=p.slice(0,3),E=K.resolve(t,p.slice(3));if(!s?.staged&&h==="?? "&&p.endsWith("/"))return Hz(E).map(C=>({action:0,path:C}));{let S=[0,4,1].find(P=>c[P].includes(h));return S!==void 0?[{action:S,path:E}]:[]}})).filter(p=>Uz(p.path,{roots:e,names:r}))},async genCommitMessage(t,e){return await w6t(t,e)},async makeStage(t,e){let r=e.map(s=>ue.fromPortablePath(s.path));await Gr.execvp("git",["add","--",...r],{cwd:t,strict:!0})},async makeCommit(t,e,r){let s=e.map(a=>ue.fromPortablePath(a.path));await Gr.execvp("git",["add","-N","--",...s],{cwd:t,strict:!0}),await Gr.execvp("git",["commit","-m",`${r} + +${I6t} +`,"--",...s],{cwd:t,strict:!0})},async makeReset(t,e){let r=e.map(s=>ue.fromPortablePath(s.path));await Gr.execvp("git",["reset","HEAD","--",...r],{cwd:t,strict:!0})}};var S6t=[MOe],G1=class extends ut{constructor(){super(...arguments);this.commit=ge.Boolean("-c,--commit",!1,{description:"Commit the staged files"});this.reset=ge.Boolean("-r,--reset",!1,{description:"Remove all files from the staging area"});this.dryRun=ge.Boolean("-n,--dry-run",!1,{description:"Print the commit message and the list of modified files without staging / committing"});this.update=ge.Boolean("-u,--update",!1,{hidden:!0})}static{this.paths=[["stage"]]}static{this.usage=ot.Usage({description:"add all yarn files to your vcs",details:"\n This command will add to your staging area the files belonging to Yarn (typically any modified `package.json` and `.yarnrc.yml` files, but also linker-generated files, cache data, etc). It will take your ignore list into account, so the cache files won't be added if the cache is ignored in a `.gitignore` file (assuming you use Git).\n\n Running `--reset` will instead remove them from the staging area (the changes will still be there, but won't be committed until you stage them back).\n\n Since the staging area is a non-existent concept in Mercurial, Yarn will always create a new commit when running this command on Mercurial repositories. You can get this behavior when using Git by using the `--commit` flag which will directly create a commit.\n ",examples:[["Adds all modified project files to the staging area","yarn stage"],["Creates a new commit containing all modified project files","yarn stage --commit"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s}=await Tt.find(r,this.context.cwd),{driver:a,root:n}=await D6t(s.cwd),c=[r.get("cacheFolder"),r.get("globalFolder"),r.get("virtualFolder"),r.get("yarnPath")];await r.triggerHook(C=>C.populateYarnPaths,s,C=>{c.push(C)});let f=new Set;for(let C of c)for(let S of b6t(n,C))f.add(S);let p=new Set([r.get("rcFilename"),Er.lockfile,Er.manifest]),h=await a.filterChanges(n,f,p),E=await a.genCommitMessage(n,h);if(this.dryRun)if(this.commit)this.context.stdout.write(`${E} +`);else for(let C of h)this.context.stdout.write(`${ue.fromPortablePath(C.path)} +`);else if(this.reset){let C=await a.filterChanges(n,f,p,{staged:!0});C.length===0?this.context.stdout.write("No staged changes found!"):await a.makeReset(n,C)}else h.length===0?this.context.stdout.write("No changes found!"):this.commit?await a.makeCommit(n,h,E):(await a.makeStage(n,h),this.context.stdout.write(E))}};async function D6t(t){let e=null,r=null;for(let s of S6t)if((r=await s.findRoot(t))!==null){e=s;break}if(e===null||r===null)throw new nt("No stage driver has been found for your current project");return{driver:e,root:r}}function b6t(t,e){let r=[];if(e===null)return r;for(;;){(e===t||e.startsWith(`${t}/`))&&r.push(e);let s;try{s=le.statSync(e)}catch{break}if(s.isSymbolicLink())e=K.resolve(K.dirname(e),le.readlinkSync(e));else break}return r}var P6t={commands:[G1]},x6t=P6t;var Wz={};Vt(Wz,{default:()=>L6t});Ve();Ve();bt();var HOe=et(fi());Ve();var _Oe=et(Z9()),k6t="e8e1bd300d860104bb8c58453ffa1eb4",Q6t="OFCNCOG2CU",UOe=async(t,e)=>{let r=q.stringifyIdent(t),a=T6t(e).initIndex("npm-search");try{return(await a.getObject(r,{attributesToRetrieve:["types"]})).types?.ts==="definitely-typed"}catch{return!1}},T6t=t=>(0,_Oe.default)(Q6t,k6t,{requester:{async send(r){try{let s=await An.request(r.url,r.data||null,{configuration:t,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var jOe=t=>t.scope?`${t.scope}__${t.name}`:`${t.name}`,R6t=async(t,e,r,s)=>{if(r.scope==="types")return;let{project:a}=t,{configuration:n}=a;if(!(n.get("tsEnableAutoTypes")??(le.existsSync(K.join(t.cwd,"tsconfig.json"))||le.existsSync(K.join(a.cwd,"tsconfig.json")))))return;let f=n.makeResolver(),p={project:a,resolver:f,report:new Wi};if(!await UOe(r,n))return;let E=jOe(r),C=q.parseRange(r.range).selector;if(!Or.validRange(C)){let N=n.normalizeDependency(r),U=await f.getCandidates(N,{},p);C=q.parseRange(U[0].reference).selector}let S=HOe.default.coerce(C);if(S===null)return;let P=`${Xu.Modifier.CARET}${S.major}`,I=q.makeDescriptor(q.makeIdent("types",E),P),R=je.mapAndFind(a.workspaces,N=>{let U=N.manifest.dependencies.get(r.identHash)?.descriptorHash,W=N.manifest.devDependencies.get(r.identHash)?.descriptorHash;if(U!==r.descriptorHash&&W!==r.descriptorHash)return je.mapAndFind.skip;let te=[];for(let ie of Ht.allDependencies){let Ae=N.manifest[ie].get(I.identHash);typeof Ae>"u"||te.push([ie,Ae])}return te.length===0?je.mapAndFind.skip:te});if(typeof R<"u")for(let[N,U]of R)t.manifest[N].set(U.identHash,U);else{try{let N=n.normalizeDependency(I);if((await f.getCandidates(N,{},p)).length===0)return}catch{return}t.manifest[Xu.Target.DEVELOPMENT].set(I.identHash,I)}},F6t=async(t,e,r)=>{if(r.scope==="types")return;let{project:s}=t,{configuration:a}=s;if(!(a.get("tsEnableAutoTypes")??(le.existsSync(K.join(t.cwd,"tsconfig.json"))||le.existsSync(K.join(s.cwd,"tsconfig.json")))))return;let c=jOe(r),f=q.makeIdent("types",c);for(let p of Ht.allDependencies)typeof t.manifest[p].get(f.identHash)>"u"||t.manifest[p].delete(f.identHash)},N6t=(t,e)=>{e.publishConfig&&e.publishConfig.typings&&(e.typings=e.publishConfig.typings),e.publishConfig&&e.publishConfig.types&&(e.types=e.publishConfig.types)},O6t={configuration:{tsEnableAutoTypes:{description:"Whether Yarn should auto-install @types/ dependencies on 'yarn add'",type:"BOOLEAN",isNullable:!0,default:null}},hooks:{afterWorkspaceDependencyAddition:R6t,afterWorkspaceDependencyRemoval:F6t,beforeWorkspacePacking:N6t}},L6t=O6t;var zz={};Vt(zz,{VersionApplyCommand:()=>J1,VersionCheckCommand:()=>z1,VersionCommand:()=>Z1,default:()=>rqt,versionUtils:()=>K1});Ve();Ve();Wt();var K1={};Vt(K1,{Decision:()=>Y1,applyPrerelease:()=>KOe,applyReleases:()=>Jz,applyStrategy:()=>JL,clearVersionFiles:()=>Yz,getUndecidedDependentWorkspaces:()=>CP,getUndecidedWorkspaces:()=>KL,openVersionFile:()=>V1,requireMoreDecisions:()=>$6t,resolveVersionFiles:()=>IP,suggestStrategy:()=>Kz,updateVersionFiles:()=>Vz,validateReleaseDecision:()=>W1});Ve();bt();Bc();Wt();var VOe=et(YOe()),TA=et(fi()),X6t=/^(>=|[~^]|)(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/,Y1=(c=>(c.UNDECIDED="undecided",c.DECLINE="decline",c.MAJOR="major",c.MINOR="minor",c.PATCH="patch",c.PRERELEASE="prerelease",c))(Y1||{});function W1(t){let e=TA.default.valid(t);return e||je.validateEnum((0,VOe.default)(Y1,"UNDECIDED"),t)}async function IP(t,{prerelease:e=null}={}){let r=new Map,s=t.configuration.get("deferredVersionFolder");if(!le.existsSync(s))return r;let a=await le.readdirPromise(s);for(let n of a){if(!n.endsWith(".yml"))continue;let c=K.join(s,n),f=await le.readFilePromise(c,"utf8"),p=ls(f);for(let[h,E]of Object.entries(p.releases||{})){if(E==="decline")continue;let C=q.parseIdent(h),S=t.tryWorkspaceByIdent(C);if(S===null)throw new Error(`Assertion failed: Expected a release definition file to only reference existing workspaces (${K.basename(c)} references ${h})`);if(S.manifest.version===null)throw new Error(`Assertion failed: Expected the workspace to have a version (${q.prettyLocator(t.configuration,S.anchoredLocator)})`);let P=S.manifest.raw.stableVersion??S.manifest.version,I=r.get(S),R=JL(P,W1(E));if(R===null)throw new Error(`Assertion failed: Expected ${P} to support being bumped via strategy ${E}`);let N=typeof I<"u"?TA.default.gt(R,I)?R:I:R;r.set(S,N)}}return e&&(r=new Map([...r].map(([n,c])=>[n,KOe(c,{current:n.manifest.version,prerelease:e})]))),r}async function Yz(t){let e=t.configuration.get("deferredVersionFolder");le.existsSync(e)&&await le.removePromise(e)}async function Vz(t,e){let r=new Set(e),s=t.configuration.get("deferredVersionFolder");if(!le.existsSync(s))return;let a=await le.readdirPromise(s);for(let n of a){if(!n.endsWith(".yml"))continue;let c=K.join(s,n),f=await le.readFilePromise(c,"utf8"),p=ls(f),h=p?.releases;if(h){for(let E of Object.keys(h)){let C=q.parseIdent(E),S=t.tryWorkspaceByIdent(C);(S===null||r.has(S))&&delete p.releases[E]}Object.keys(p.releases).length>0?await le.changeFilePromise(c,il(new il.PreserveOrdering(p))):await le.unlinkPromise(c)}}}async function V1(t,{allowEmpty:e=!1}={}){let r=t.configuration;if(r.projectCwd===null)throw new nt("This command can only be run from within a Yarn project");let s=await Qa.fetchRoot(r.projectCwd),a=s!==null?await Qa.fetchBase(s,{baseRefs:r.get("changesetBaseRefs")}):null,n=s!==null?await Qa.fetchChangedFiles(s,{base:a.hash,project:t}):[],c=r.get("deferredVersionFolder"),f=n.filter(P=>K.contains(c,P)!==null);if(f.length>1)throw new nt(`Your current branch contains multiple versioning files; this isn't supported: +- ${f.map(P=>ue.fromPortablePath(P)).join(` +- `)}`);let p=new Set(je.mapAndFilter(n,P=>{let I=t.tryWorkspaceByFilePath(P);return I===null?je.mapAndFilter.skip:I}));if(f.length===0&&p.size===0&&!e)return null;let h=f.length===1?f[0]:K.join(c,`${Nn.makeHash(Math.random().toString()).slice(0,8)}.yml`),E=le.existsSync(h)?await le.readFilePromise(h,"utf8"):"{}",C=ls(E),S=new Map;for(let P of C.declined||[]){let I=q.parseIdent(P),R=t.getWorkspaceByIdent(I);S.set(R,"decline")}for(let[P,I]of Object.entries(C.releases||{})){let R=q.parseIdent(P),N=t.getWorkspaceByIdent(R);S.set(N,W1(I))}return{project:t,root:s,baseHash:a!==null?a.hash:null,baseTitle:a!==null?a.title:null,changedFiles:new Set(n),changedWorkspaces:p,releaseRoots:new Set([...p].filter(P=>P.manifest.version!==null)),releases:S,async saveAll(){let P={},I=[],R=[];for(let N of t.workspaces){if(N.manifest.version===null)continue;let U=q.stringifyIdent(N.anchoredLocator),W=S.get(N);W==="decline"?I.push(U):typeof W<"u"?P[U]=W1(W):p.has(N)&&R.push(U)}await le.mkdirPromise(K.dirname(h),{recursive:!0}),await le.changeFilePromise(h,il(new il.PreserveOrdering({releases:Object.keys(P).length>0?P:void 0,declined:I.length>0?I:void 0,undecided:R.length>0?R:void 0})))}}}function $6t(t){return KL(t).size>0||CP(t).length>0}function KL(t){let e=new Set;for(let r of t.changedWorkspaces)r.manifest.version!==null&&(t.releases.has(r)||e.add(r));return e}function CP(t,{include:e=new Set}={}){let r=[],s=new Map(je.mapAndFilter([...t.releases],([n,c])=>c==="decline"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n])),a=new Map(je.mapAndFilter([...t.releases],([n,c])=>c!=="decline"?je.mapAndFilter.skip:[n.anchoredLocator.locatorHash,n]));for(let n of t.project.workspaces)if(!(!e.has(n)&&(a.has(n.anchoredLocator.locatorHash)||s.has(n.anchoredLocator.locatorHash)))&&n.manifest.version!==null)for(let c of Ht.hardDependencies)for(let f of n.manifest.getForScope(c).values()){let p=t.project.tryWorkspaceByDescriptor(f);p!==null&&s.has(p.anchoredLocator.locatorHash)&&r.push([n,p])}return r}function Kz(t,e){let r=TA.default.clean(e);for(let s of Object.values(Y1))if(s!=="undecided"&&s!=="decline"&&TA.default.inc(t,s)===r)return s;return null}function JL(t,e){if(TA.default.valid(e))return e;if(t===null)throw new nt(`Cannot apply the release strategy "${e}" unless the workspace already has a valid version`);if(!TA.default.valid(t))throw new nt(`Cannot apply the release strategy "${e}" on a non-semver version (${t})`);let r=TA.default.inc(t,e);if(r===null)throw new nt(`Cannot apply the release strategy "${e}" on the specified version (${t})`);return r}function Jz(t,e,{report:r,exact:s}){let a=new Map;for(let n of t.workspaces)for(let c of Ht.allDependencies)for(let f of n.manifest[c].values()){let p=t.tryWorkspaceByDescriptor(f);if(p===null||!e.has(p))continue;je.getArrayWithDefault(a,p).push([n,c,f.identHash])}for(let[n,c]of e){let f=n.manifest.version;n.manifest.version=c,TA.default.prerelease(c)===null?delete n.manifest.raw.stableVersion:n.manifest.raw.stableVersion||(n.manifest.raw.stableVersion=f);let p=n.manifest.name!==null?q.stringifyIdent(n.manifest.name):null;r.reportInfo(0,`${q.prettyLocator(t.configuration,n.anchoredLocator)}: Bumped to ${c}`),r.reportJson({cwd:ue.fromPortablePath(n.cwd),ident:p,oldVersion:f,newVersion:c});let h=a.get(n);if(!(typeof h>"u"))for(let[E,C,S]of h){let P=E.manifest[C].get(S);if(typeof P>"u")throw new Error("Assertion failed: The dependency should have existed");let I=P.range,R=!1;if(I.startsWith(yi.protocol)&&(I=I.slice(yi.protocol.length),R=!0,I===n.relativeCwd))continue;let N=I.match(X6t);if(!N){r.reportWarning(0,`Couldn't auto-upgrade range ${I} (in ${q.prettyLocator(t.configuration,E.anchoredLocator)})`);continue}let U=s?`${c}`:`${N[1]}${c}`;R&&(U=`${yi.protocol}${U}`);let W=q.makeDescriptor(P,U);E.manifest[C].set(S,W)}}}var eqt=new Map([["%n",{extract:t=>t.length>=1?[t[0],t.slice(1)]:null,generate:(t=0)=>`${t+1}`}]]);function KOe(t,{current:e,prerelease:r}){let s=new TA.default.SemVer(e),a=s.prerelease.slice(),n=[];s.prerelease=[],s.format()!==t&&(a.length=0);let c=!0,f=r.split(/\./g);for(let p of f){let h=eqt.get(p);if(typeof h>"u")n.push(p),a[0]===p?a.shift():c=!1;else{let E=c?h.extract(a):null;E!==null&&typeof E[0]=="number"?(n.push(h.generate(E[0])),a=E[1]):(n.push(h.generate()),c=!1)}}return s.prerelease&&(s.prerelease=[]),`${t}-${n.join(".")}`}var J1=class extends ut{constructor(){super(...arguments);this.all=ge.Boolean("--all",!1,{description:"Apply the deferred version changes on all workspaces"});this.dryRun=ge.Boolean("--dry-run",!1,{description:"Print the versions without actually generating the package archive"});this.prerelease=ge.String("--prerelease",{description:"Add a prerelease identifier to new versions",tolerateBoolean:!0});this.exact=ge.Boolean("--exact",!1,{description:"Use the exact version of each package, removes any range. Useful for nightly releases where the range might match another version."});this.recursive=ge.Boolean("-R,--recursive",{description:"Release the transitive workspaces as well"});this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"})}static{this.paths=[["version","apply"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"apply all the deferred version bumps at once",details:` + This command will apply the deferred version changes and remove their definitions from the repository. + + Note that if \`--prerelease\` is set, the given prerelease identifier (by default \`rc.%n\`) will be used on all new versions and the version definitions will be kept as-is. + + By default only the current workspace will be bumped, but you can configure this behavior by using one of: + + - \`--recursive\` to also apply the version bump on its dependencies + - \`--all\` to apply the version bump on all packages in the repository + + Note that this command will also update the \`workspace:\` references across all your local workspaces, thus ensuring that they keep referring to the same workspaces even after the version bump. + `,examples:[["Apply the version change to the local workspace","yarn version apply"],["Apply the version change to all the workspaces in the local workspace","yarn version apply --all"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);if(!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState({restoreResolutions:!1});let c=await Ot.start({configuration:r,json:this.json,stdout:this.context.stdout},async f=>{let p=this.prerelease?typeof this.prerelease!="boolean"?this.prerelease:"rc.%n":null,h=await IP(s,{prerelease:p}),E=new Map;if(this.all)E=h;else{let C=this.recursive?a.getRecursiveWorkspaceDependencies():[a];for(let S of C){let P=h.get(S);typeof P<"u"&&E.set(S,P)}}if(E.size===0){let C=h.size>0?" Did you want to add --all?":"";f.reportWarning(0,`The current workspace doesn't seem to require a version bump.${C}`);return}Jz(s,E,{report:f,exact:this.exact}),this.dryRun||(p||(this.all?await Yz(s):await Vz(s,[...E.keys()])),f.reportSeparator())});return this.dryRun||c.hasErrors()?c.exitCode():await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n})}};Ve();bt();Wt();var zL=et(fi());var z1=class extends ut{constructor(){super(...arguments);this.interactive=ge.Boolean("-i,--interactive",{description:"Open an interactive interface used to set version bumps"})}static{this.paths=[["version","check"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"check that all the relevant packages have been bumped",details:"\n **Warning:** This command currently requires Git.\n\n This command will check that all the packages covered by the files listed in argument have been properly bumped or declined to bump.\n\n In the case of a bump, the check will also cover transitive packages - meaning that should `Foo` be bumped, a package `Bar` depending on `Foo` will require a decision as to whether `Bar` will need to be bumped. This check doesn't cross packages that have declined to bump.\n\n In case no arguments are passed to the function, the list of modified files will be generated by comparing the HEAD against `master`.\n ",examples:[["Check whether the modified packages need a bump","yarn version check"]]})}async execute(){return this.interactive?await this.executeInteractive():await this.executeStandard()}async executeInteractive(){ow(this.context);let{Gem:r}=await Promise.resolve().then(()=>($F(),CY)),{ScrollableItems:s}=await Promise.resolve().then(()=>(nN(),rN)),{FocusRequest:a}=await Promise.resolve().then(()=>(BY(),XPe)),{useListInput:n}=await Promise.resolve().then(()=>(tN(),$Pe)),{renderForm:c}=await Promise.resolve().then(()=>(aN(),oN)),{Box:f,Text:p}=await Promise.resolve().then(()=>et(Vc())),{default:h,useCallback:E,useState:C}=await Promise.resolve().then(()=>et(hn())),S=await ze.find(this.context.cwd,this.context.plugins),{project:P,workspace:I}=await Tt.find(S,this.context.cwd);if(!I)throw new ar(P.cwd,this.context.cwd);await P.restoreInstallState();let R=await V1(P);if(R===null||R.releaseRoots.size===0)return 0;if(R.root===null)throw new nt("This command can only be run on Git repositories");let N=()=>h.createElement(f,{flexDirection:"row",paddingBottom:1},h.createElement(f,{flexDirection:"column",width:60},h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select workspaces.")),h.createElement(f,null,h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},""),"/",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to select release strategies."))),h.createElement(f,{flexDirection:"column"},h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to save.")),h.createElement(f,{marginLeft:1},h.createElement(p,null,"Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to abort.")))),U=({workspace:me,active:pe,decision:Be,setDecision:Ce})=>{let g=me.manifest.raw.stableVersion??me.manifest.version;if(g===null)throw new Error(`Assertion failed: The version should have been set (${q.prettyLocator(S,me.anchoredLocator)})`);if(zL.default.prerelease(g)!==null)throw new Error(`Assertion failed: Prerelease identifiers shouldn't be found (${g})`);let we=["undecided","decline","patch","minor","major"];n(Be,we,{active:pe,minus:"left",plus:"right",set:Ce});let Ee=Be==="undecided"?h.createElement(p,{color:"yellow"},g):Be==="decline"?h.createElement(p,{color:"green"},g):h.createElement(p,null,h.createElement(p,{color:"magenta"},g)," \u2192 ",h.createElement(p,{color:"green"},zL.default.valid(Be)?Be:zL.default.inc(g,Be)));return h.createElement(f,{flexDirection:"column"},h.createElement(f,null,h.createElement(p,null,q.prettyLocator(S,me.anchoredLocator)," - ",Ee)),h.createElement(f,null,we.map(fe=>h.createElement(f,{key:fe,paddingLeft:2},h.createElement(p,null,h.createElement(r,{active:fe===Be})," ",fe)))))},W=me=>{let pe=new Set(R.releaseRoots),Be=new Map([...me].filter(([Ce])=>pe.has(Ce)));for(;;){let Ce=CP({project:R.project,releases:Be}),g=!1;if(Ce.length>0){for(let[we]of Ce)if(!pe.has(we)){pe.add(we),g=!0;let Ee=me.get(we);typeof Ee<"u"&&Be.set(we,Ee)}}if(!g)break}return{relevantWorkspaces:pe,relevantReleases:Be}},te=()=>{let[me,pe]=C(()=>new Map(R.releases)),Be=E((Ce,g)=>{let we=new Map(me);g!=="undecided"?we.set(Ce,g):we.delete(Ce);let{relevantReleases:Ee}=W(we);pe(Ee)},[me,pe]);return[me,Be]},ie=({workspaces:me,releases:pe})=>{let Be=[];Be.push(`${me.size} total`);let Ce=0,g=0;for(let we of me){let Ee=pe.get(we);typeof Ee>"u"?g+=1:Ee!=="decline"&&(Ce+=1)}return Be.push(`${Ce} release${Ce===1?"":"s"}`),Be.push(`${g} remaining`),h.createElement(p,{color:"yellow"},Be.join(", "))},ce=await c(({useSubmit:me})=>{let[pe,Be]=te();me(pe);let{relevantWorkspaces:Ce}=W(pe),g=new Set([...Ce].filter(se=>!R.releaseRoots.has(se))),[we,Ee]=C(0),fe=E(se=>{switch(se){case a.BEFORE:Ee(we-1);break;case a.AFTER:Ee(we+1);break}},[we,Ee]);return h.createElement(f,{flexDirection:"column"},h.createElement(N,null),h.createElement(f,null,h.createElement(p,{wrap:"wrap"},"The following files have been modified in your local checkout.")),h.createElement(f,{flexDirection:"column",marginTop:1,paddingLeft:2},[...R.changedFiles].map(se=>h.createElement(f,{key:se},h.createElement(p,null,h.createElement(p,{color:"grey"},ue.fromPortablePath(R.root)),ue.sep,ue.relative(ue.fromPortablePath(R.root),ue.fromPortablePath(se)))))),R.releaseRoots.size>0&&h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:"wrap"},"Because of those files having been modified, the following workspaces may need to be released again (note that private workspaces are also shown here, because even though they won't be published, releasing them will allow us to flag their dependents for potential re-release):")),g.size>3?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:R.releaseRoots,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:"column"},h.createElement(s,{active:we%2===0,radius:1,size:2,onFocusRequest:fe},[...R.releaseRoots].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||"undecided",setDecision:X=>Be(se,X)}))))),g.size>0?h.createElement(h.Fragment,null,h.createElement(f,{marginTop:1},h.createElement(p,{wrap:"wrap"},"The following workspaces depend on other workspaces that have been marked for release, and thus may need to be released as well:")),h.createElement(f,null,h.createElement(p,null,"(Press ",h.createElement(p,{bold:!0,color:"cyanBright"},"")," to move the focus between the workspace groups.)")),g.size>5?h.createElement(f,{marginTop:1},h.createElement(ie,{workspaces:g,releases:pe})):null,h.createElement(f,{marginTop:1,flexDirection:"column"},h.createElement(s,{active:we%2===1,radius:2,size:2,onFocusRequest:fe},[...g].map(se=>h.createElement(U,{key:se.cwd,workspace:se,decision:pe.get(se)||"undecided",setDecision:X=>Be(se,X)}))))):null)},{versionFile:R},{stdin:this.context.stdin,stdout:this.context.stdout,stderr:this.context.stderr});if(typeof ce>"u")return 1;R.releases.clear();for(let[me,pe]of ce)R.releases.set(me,pe);await R.saveAll()}async executeStandard(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);return await s.restoreInstallState(),(await Ot.start({configuration:r,stdout:this.context.stdout},async c=>{let f=await V1(s);if(f===null||f.releaseRoots.size===0)return;if(f.root===null)throw new nt("This command can only be run on Git repositories");if(c.reportInfo(0,`Your PR was started right after ${he.pretty(r,f.baseHash.slice(0,7),"yellow")} ${he.pretty(r,f.baseTitle,"magenta")}`),f.changedFiles.size>0){c.reportInfo(0,"You have changed the following files since then:"),c.reportSeparator();for(let S of f.changedFiles)c.reportInfo(null,`${he.pretty(r,ue.fromPortablePath(f.root),"gray")}${ue.sep}${ue.relative(ue.fromPortablePath(f.root),ue.fromPortablePath(S))}`)}let p=!1,h=!1,E=KL(f);if(E.size>0){p||c.reportSeparator();for(let S of E)c.reportError(0,`${q.prettyLocator(r,S.anchoredLocator)} has been modified but doesn't have a release strategy attached`);p=!0}let C=CP(f);for(let[S,P]of C)h||c.reportSeparator(),c.reportError(0,`${q.prettyLocator(r,S.anchoredLocator)} doesn't have a release strategy attached, but depends on ${q.prettyWorkspace(r,P)} which is planned for release.`),h=!0;(p||h)&&(c.reportSeparator(),c.reportInfo(0,"This command detected that at least some workspaces have received modifications without explicit instructions as to how they had to be released (if needed)."),c.reportInfo(0,"To correct these errors, run `yarn version check --interactive` then follow the instructions."))})).exitCode()}};Ve();Wt();var ZL=et(fi());var Z1=class extends ut{constructor(){super(...arguments);this.deferred=ge.Boolean("-d,--deferred",{description:"Prepare the version to be bumped during the next release cycle"});this.immediate=ge.Boolean("-i,--immediate",{description:"Bump the version immediately"});this.strategy=ge.String()}static{this.paths=[["version"]]}static{this.usage=ot.Usage({category:"Release-related commands",description:"apply a new version to the current package",details:"\n This command will bump the version number for the given package, following the specified strategy:\n\n - If `major`, the first number from the semver range will be increased (`X.0.0`).\n - If `minor`, the second number from the semver range will be increased (`0.X.0`).\n - If `patch`, the third number from the semver range will be increased (`0.0.X`).\n - If prefixed by `pre` (`premajor`, ...), a `-0` suffix will be set (`0.0.0-0`).\n - If `prerelease`, the suffix will be increased (`0.0.0-X`); the third number from the semver range will also be increased if there was no suffix in the previous version.\n - If `decline`, the nonce will be increased for `yarn version check` to pass without version bump.\n - If a valid semver range, it will be used as new version.\n - If unspecified, Yarn will ask you for guidance.\n\n For more information about the `--deferred` flag, consult our documentation (https://yarnpkg.com/features/release-workflow#deferred-versioning).\n ",examples:[["Immediately bump the version to the next major","yarn version major"],["Prepare the version to be bumped to the next major","yarn version major --deferred"]]})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!a)throw new ar(s.cwd,this.context.cwd);let n=r.get("preferDeferredVersions");this.deferred&&(n=!0),this.immediate&&(n=!1);let c=ZL.default.valid(this.strategy),f=this.strategy==="decline",p;if(c)if(a.manifest.version!==null){let E=Kz(a.manifest.version,this.strategy);E!==null?p=E:p=this.strategy}else p=this.strategy;else{let E=a.manifest.version;if(!f){if(E===null)throw new nt("Can't bump the version if there wasn't a version to begin with - use 0.0.0 as initial version then run the command again.");if(typeof E!="string"||!ZL.default.valid(E))throw new nt(`Can't bump the version (${E}) if it's not valid semver`)}p=W1(this.strategy)}if(!n){let C=(await IP(s)).get(a);if(typeof C<"u"&&p!=="decline"){let S=JL(a.manifest.version,p);if(ZL.default.lt(S,C))throw new nt(`Can't bump the version to one that would be lower than the current deferred one (${C})`)}}let h=await V1(s,{allowEmpty:!0});return h.releases.set(a,p),await h.saveAll(),n?0:await this.cli.run(["version","apply"])}};var tqt={configuration:{deferredVersionFolder:{description:"Folder where are stored the versioning files",type:"ABSOLUTE_PATH",default:"./.yarn/versions"},preferDeferredVersions:{description:"If true, running `yarn version` will assume the `--deferred` flag unless `--immediate` is set",type:"BOOLEAN",default:!1}},commands:[J1,z1,Z1]},rqt=tqt;var Zz={};Vt(Zz,{WorkspacesFocusCommand:()=>X1,WorkspacesForeachCommand:()=>e2,default:()=>sqt});Ve();Ve();Wt();var X1=class extends ut{constructor(){super(...arguments);this.json=ge.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=ge.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=ge.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=ge.Rest()}static{this.paths=[["workspaces","focus"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "})}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd),n=await Jr.find(r);await s.restoreInstallState({restoreResolutions:!1});let c;if(this.all)c=new Set(s.workspaces);else if(this.workspaces.length===0){if(!a)throw new ar(s.cwd,this.context.cwd);c=new Set([a])}else c=new Set(this.workspaces.map(f=>s.getWorkspaceByIdent(q.parseIdent(f))));for(let f of c)for(let p of this.production?["dependencies"]:Ht.hardDependencies)for(let h of f.manifest.getForScope(p).values()){let E=s.tryWorkspaceByDescriptor(h);E!==null&&c.add(E)}for(let f of s.workspaces)c.has(f)?this.production&&f.manifest.devDependencies.clear():(f.manifest.installConfig=f.manifest.installConfig||{},f.manifest.installConfig.selfReferences=!1,f.manifest.dependencies.clear(),f.manifest.devDependencies.clear(),f.manifest.peerDependencies.clear(),f.manifest.scripts.clear());return await s.installWithNewReport({json:this.json,stdout:this.context.stdout},{cache:n,persistProject:!1})}};Ve();Ve();Ve();Wt();var $1=et(Sa()),zOe=et(Md());Ul();var e2=class extends ut{constructor(){super(...arguments);this.from=ge.Array("--from",{description:"An array of glob pattern idents or paths from which to base any recursion"});this.all=ge.Boolean("-A,--all",{description:"Run the command on all workspaces of a project"});this.recursive=ge.Boolean("-R,--recursive",{description:"Run the command on the current workspace and all of its recursive dependencies"});this.worktree=ge.Boolean("-W,--worktree",{description:"Run the command on all workspaces of the current worktree"});this.verbose=ge.Counter("-v,--verbose",{description:"Increase level of logging verbosity up to 2 times"});this.parallel=ge.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=ge.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=ge.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:vU([Ao(["unlimited"]),rB(BU(),[DU(),SU(1)])])});this.topological=ge.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=ge.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=ge.Array("--include",[],{description:"An array of glob pattern idents or paths; only matching workspaces will be traversed"});this.exclude=ge.Array("--exclude",[],{description:"An array of glob pattern idents or paths; matching workspaces won't be traversed"});this.publicOnly=ge.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=ge.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.dryRun=ge.Boolean("-n,--dry-run",{description:"Print the commands that would be run, without actually running them"});this.commandName=ge.String();this.args=ge.Proxy()}static{this.paths=[["workspaces","foreach"]]}static{this.usage=ot.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `-W,--worktree` is set, Yarn will find workspaces to run the command on by looking at the current worktree.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - If `--dry-run` is set, Yarn will explain what it would do without actually doing anything.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them. You can also use the `--no-private` flag to avoid running the command in private workspaces.\n\n The `-v,--verbose` flag can be passed up to twice: once to prefix output lines with the originating workspace's name, and again to include start/finish/timing log lines. Maximum verbosity is enabled by default in terminal environments.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish all packages","yarn workspaces foreach -A --no-private npm publish --tolerate-republish"],["Run the build script on all descendant packages","yarn workspaces foreach -A run build"],["Run the build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -Apt run build"],["Run the build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -Rpt --from '{workspace-a,workspace-b}' run build"]]})}static{this.schema=[iB("all",Wf.Forbids,["from","recursive","since","worktree"],{missingIf:"undefined"}),bU(["all","recursive","since","worktree"],{missingIf:"undefined"})]}async execute(){let r=await ze.find(this.context.cwd,this.context.plugins),{project:s,workspace:a}=await Tt.find(r,this.context.cwd);if(!this.all&&!a)throw new ar(s.cwd,this.context.cwd);await s.restoreInstallState();let n=this.cli.process([this.commandName,...this.args]),c=n.path.length===1&&n.path[0]==="run"&&typeof n.scriptName<"u"?n.scriptName:null;if(n.path.length===0)throw new nt("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let f=Ce=>{this.dryRun&&this.context.stdout.write(`${Ce} +`)},p=()=>{let Ce=this.from.map(g=>$1.default.matcher(g));return s.workspaces.filter(g=>{let we=q.stringifyIdent(g.anchoredLocator),Ee=g.relativeCwd;return Ce.some(fe=>fe(we)||fe(Ee))})},h=[];if(this.since?(f("Option --since is set; selecting the changed workspaces as root for workspace selection"),h=Array.from(await Qa.fetchChangedWorkspaces({ref:this.since,project:s}))):this.from?(f("Option --from is set; selecting the specified workspaces"),h=[...p()]):this.worktree?(f("Option --worktree is set; selecting the current workspace"),h=[a]):this.recursive?(f("Option --recursive is set; selecting the current workspace"),h=[a]):this.all&&(f("Option --all is set; selecting all workspaces"),h=[...s.workspaces]),this.dryRun&&!this.all){for(let Ce of h)f(` +- ${Ce.relativeCwd} + ${q.prettyLocator(r,Ce.anchoredLocator)}`);h.length>0&&f("")}let E;if(this.recursive?this.since?(f("Option --recursive --since is set; recursively selecting all dependent workspaces"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependents()]).flat())):(f("Option --recursive is set; recursively selecting all transitive dependencies"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceDependencies()]).flat())):this.worktree?(f("Option --worktree is set; recursively selecting all nested workspaces"),E=new Set(h.map(Ce=>[...Ce.getRecursiveWorkspaceChildren()]).flat())):E=null,E!==null&&(h=[...new Set([...h,...E])],this.dryRun))for(let Ce of E)f(` +- ${Ce.relativeCwd} + ${q.prettyLocator(r,Ce.anchoredLocator)}`);let C=[],S=!1;if(c?.includes(":")){for(let Ce of s.workspaces)if(Ce.manifest.scripts.has(c)&&(S=!S,S===!1))break}for(let Ce of h){if(c&&!Ce.manifest.scripts.has(c)&&!S&&!(await In.getWorkspaceAccessibleBinaries(Ce)).has(c)){f(`Excluding ${Ce.relativeCwd} because it doesn't have a "${c}" script`);continue}if(!(c===r.env.npm_lifecycle_event&&Ce.cwd===a.cwd)){if(this.include.length>0&&!$1.default.isMatch(q.stringifyIdent(Ce.anchoredLocator),this.include)&&!$1.default.isMatch(Ce.relativeCwd,this.include)){f(`Excluding ${Ce.relativeCwd} because it doesn't match the --include filter`);continue}if(this.exclude.length>0&&($1.default.isMatch(q.stringifyIdent(Ce.anchoredLocator),this.exclude)||$1.default.isMatch(Ce.relativeCwd,this.exclude))){f(`Excluding ${Ce.relativeCwd} because it matches the --exclude filter`);continue}if(this.publicOnly&&Ce.manifest.private===!0){f(`Excluding ${Ce.relativeCwd} because it's a private workspace and --no-private was set`);continue}C.push(Ce)}}if(this.dryRun)return 0;let P=this.verbose??(this.context.stdout.isTTY?1/0:0),I=P>0,R=P>1,N=this.parallel?this.jobs==="unlimited"?1/0:Number(this.jobs)||Math.ceil(As.availableParallelism()/2):1,U=N===1?!1:this.parallel,W=U?this.interlaced:!0,te=(0,zOe.default)(N),ie=new Map,Ae=new Set,ce=0,me=null,pe=!1,Be=await Ot.start({configuration:r,stdout:this.context.stdout,includePrefix:!1},async Ce=>{let g=async(we,{commandIndex:Ee})=>{if(pe)return-1;!U&&R&&Ee>1&&Ce.reportSeparator();let fe=nqt(we,{configuration:r,label:I,commandIndex:Ee}),[se,X]=JOe(Ce,{prefix:fe,interlaced:W}),[De,Re]=JOe(Ce,{prefix:fe,interlaced:W});try{R&&Ce.reportInfo(null,`${fe?`${fe} `:""}Process started`);let gt=Date.now(),j=await this.cli.run([this.commandName,...this.args],{cwd:we.cwd,stdout:se,stderr:De})||0;se.end(),De.end(),await X,await Re;let rt=Date.now();if(R){let Fe=r.get("enableTimers")?`, completed in ${he.pretty(r,rt-gt,he.Type.DURATION)}`:"";Ce.reportInfo(null,`${fe?`${fe} `:""}Process exited (exit code ${j})${Fe}`)}return j===130&&(pe=!0,me=j),j}catch(gt){throw se.end(),De.end(),await X,await Re,gt}};for(let we of C)ie.set(we.anchoredLocator.locatorHash,we);for(;ie.size>0&&!Ce.hasErrors();){let we=[];for(let[X,De]of ie){if(Ae.has(De.anchoredDescriptor.descriptorHash))continue;let Re=!0;if(this.topological||this.topologicalDev){let gt=this.topologicalDev?new Map([...De.manifest.dependencies,...De.manifest.devDependencies]):De.manifest.dependencies;for(let j of gt.values()){let rt=s.tryWorkspaceByDescriptor(j);if(Re=rt===null||!ie.has(rt.anchoredLocator.locatorHash),!Re)break}}if(Re&&(Ae.add(De.anchoredDescriptor.descriptorHash),we.push(te(async()=>{let gt=await g(De,{commandIndex:++ce});return ie.delete(X),Ae.delete(De.anchoredDescriptor.descriptorHash),{workspace:De,exitCode:gt}})),!U))break}if(we.length===0){let X=Array.from(ie.values()).map(De=>q.prettyLocator(r,De.anchoredLocator)).join(", ");Ce.reportError(3,`Dependency cycle detected (${X})`);return}let Ee=await Promise.all(we);Ee.forEach(({workspace:X,exitCode:De})=>{De!==0&&Ce.reportError(0,`The command failed in workspace ${q.prettyLocator(r,X.anchoredLocator)} with exit code ${De}`)});let se=Ee.map(X=>X.exitCode).find(X=>X!==0);(this.topological||this.topologicalDev)&&typeof se<"u"&&Ce.reportError(0,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return me!==null?me:Be.exitCode()}};function JOe(t,{prefix:e,interlaced:r}){let s=t.createStreamReporter(e),a=new je.DefaultStream;a.pipe(s,{end:!1}),a.on("finish",()=>{s.end()});let n=new Promise(f=>{s.on("finish",()=>{f(a.active)})});if(r)return[a,n];let c=new je.BufferStream;return c.pipe(a,{end:!1}),c.on("finish",()=>{a.end()}),[c,n]}function nqt(t,{configuration:e,commandIndex:r,label:s}){if(!s)return null;let n=`[${q.stringifyIdent(t.anchoredLocator)}]:`,c=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],f=c[r%c.length];return he.pretty(e,n,f)}var iqt={commands:[X1,e2]},sqt=iqt;var nC=()=>({modules:new Map([["@yarnpkg/cli",iS],["@yarnpkg/core",nS],["@yarnpkg/fslib",q2],["@yarnpkg/libzip",Sv],["@yarnpkg/parsers",Z2],["@yarnpkg/shell",Qv],["clipanion",cB],["semver",oqt],["typanion",Ia],["@yarnpkg/plugin-essentials",$5],["@yarnpkg/plugin-compat",i9],["@yarnpkg/plugin-constraints",w9],["@yarnpkg/plugin-dlx",B9],["@yarnpkg/plugin-exec",D9],["@yarnpkg/plugin-file",P9],["@yarnpkg/plugin-git",X5],["@yarnpkg/plugin-github",Q9],["@yarnpkg/plugin-http",T9],["@yarnpkg/plugin-init",R9],["@yarnpkg/plugin-interactive-tools",TY],["@yarnpkg/plugin-jsr",FY],["@yarnpkg/plugin-link",NY],["@yarnpkg/plugin-nm",yV],["@yarnpkg/plugin-npm",yz],["@yarnpkg/plugin-npm-cli",bz],["@yarnpkg/plugin-pack",f7],["@yarnpkg/plugin-patch",Fz],["@yarnpkg/plugin-pnp",oV],["@yarnpkg/plugin-pnpm",Lz],["@yarnpkg/plugin-stage",Gz],["@yarnpkg/plugin-typescript",Wz],["@yarnpkg/plugin-version",zz],["@yarnpkg/plugin-workspace-tools",Zz]]),plugins:new Set(["@yarnpkg/plugin-essentials","@yarnpkg/plugin-compat","@yarnpkg/plugin-constraints","@yarnpkg/plugin-dlx","@yarnpkg/plugin-exec","@yarnpkg/plugin-file","@yarnpkg/plugin-git","@yarnpkg/plugin-github","@yarnpkg/plugin-http","@yarnpkg/plugin-init","@yarnpkg/plugin-interactive-tools","@yarnpkg/plugin-jsr","@yarnpkg/plugin-link","@yarnpkg/plugin-nm","@yarnpkg/plugin-npm","@yarnpkg/plugin-npm-cli","@yarnpkg/plugin-pack","@yarnpkg/plugin-patch","@yarnpkg/plugin-pnp","@yarnpkg/plugin-pnpm","@yarnpkg/plugin-stage","@yarnpkg/plugin-typescript","@yarnpkg/plugin-version","@yarnpkg/plugin-workspace-tools"])});function $Oe({cwd:t,pluginConfiguration:e}){let r=new wa({binaryLabel:"Yarn Package Manager",binaryName:"yarn",binaryVersion:un??""});return Object.assign(r,{defaultContext:{...wa.defaultContext,cwd:t,plugins:e,quiet:!1,stdin:process.stdin,stdout:process.stdout,stderr:process.stderr}})}function aqt(t){if(je.parseOptionalBoolean(process.env.YARN_IGNORE_NODE))return!0;let r=process.versions.node,s=">=18.12.0";if(Or.satisfiesWithPrereleases(r,s))return!0;let a=new nt(`This tool requires a Node version compatible with ${s} (got ${r}). Upgrade Node, or set \`YARN_IGNORE_NODE=1\` in your environment.`);return wa.defaultContext.stdout.write(t.error(a)),!1}async function eLe({selfPath:t,pluginConfiguration:e}){return await ze.find(ue.toPortablePath(process.cwd()),e,{strict:!1,usePathCheck:t})}function lqt(t,e,{yarnPath:r}){if(!le.existsSync(r))return t.error(new Error(`The "yarn-path" option has been set, but the specified location doesn't exist (${r}).`)),1;process.on("SIGINT",()=>{});let s={stdio:"inherit",env:{...process.env,YARN_IGNORE_PATH:"1"}};try{(0,ZOe.execFileSync)(process.execPath,[ue.fromPortablePath(r),...e],s)}catch(a){return a.status??1}return 0}function cqt(t,e){let r=null,s=e;return e.length>=2&&e[0]==="--cwd"?(r=ue.toPortablePath(e[1]),s=e.slice(2)):e.length>=1&&e[0].startsWith("--cwd=")?(r=ue.toPortablePath(e[0].slice(6)),s=e.slice(1)):e[0]==="add"&&e[e.length-2]==="--cwd"&&(r=ue.toPortablePath(e[e.length-1]),s=e.slice(0,e.length-2)),t.defaultContext.cwd=r!==null?K.resolve(r):K.cwd(),s}function uqt(t,{configuration:e}){if(!e.get("enableTelemetry")||XOe.isCI||!process.stdout.isTTY)return;ze.telemetry=new eC(e,"puba9cdc10ec5790a2cf4969dd413a47270");let s=/^@yarnpkg\/plugin-(.*)$/;for(let a of e.plugins.keys())tC.has(a.match(s)?.[1]??"")&&ze.telemetry?.reportPluginName(a);t.binaryVersion&&ze.telemetry.reportVersion(t.binaryVersion)}function tLe(t,{configuration:e}){for(let r of e.plugins.values())for(let s of r.commands||[])t.register(s)}async function fqt(t,e,{selfPath:r,pluginConfiguration:s}){if(!aqt(t))return 1;let a=await eLe({selfPath:r,pluginConfiguration:s}),n=a.get("yarnPath"),c=a.get("ignorePath");if(n&&!c)return lqt(t,e,{yarnPath:n});delete process.env.YARN_IGNORE_PATH;let f=cqt(t,e);uqt(t,{configuration:a}),tLe(t,{configuration:a});let p=t.process(f,t.defaultContext);return p.help||ze.telemetry?.reportCommandName(p.path.join(" ")),await t.run(p,t.defaultContext)}async function cwe({cwd:t=K.cwd(),pluginConfiguration:e=nC()}={}){let r=$Oe({cwd:t,pluginConfiguration:e}),s=await eLe({pluginConfiguration:e,selfPath:null});return tLe(r,{configuration:s}),r}async function zR(t,{cwd:e=K.cwd(),selfPath:r,pluginConfiguration:s}){let a=$Oe({cwd:e,pluginConfiguration:s});function n(){wa.defaultContext.stdout.write(`ERROR: Yarn is terminating due to an unexpected empty event loop. +Please report this issue at https://github.com/yarnpkg/berry/issues.`)}process.once("beforeExit",n);try{process.exitCode=42,process.exitCode=await fqt(a,t,{selfPath:r,pluginConfiguration:s})}catch(c){wa.defaultContext.stdout.write(a.error(c)),process.exitCode=1}finally{process.off("beforeExit",n),await le.rmtempPromise()}}zR(process.argv.slice(2),{cwd:K.cwd(),selfPath:ue.toPortablePath(ue.resolve(process.argv[1])),pluginConfiguration:nC()});})(); +/** + @license + Copyright (c) 2015, Rebecca Turner + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + */ +/** + @license + Copyright Node.js contributors. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to + deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + sell copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + IN THE SOFTWARE. +*/ +/** + @license + The MIT License (MIT) + + Copyright (c) 2014 Blake Embrey (hello@blakeembrey.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +/** + @license + Copyright Joyent, Inc. and other Node contributors. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to permit + persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +/*! Bundled license information: + +is-number/index.js: + (*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + *) + +to-regex-range/index.js: + (*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + *) + +fill-range/index.js: + (*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + *) + +is-extglob/index.js: + (*! + * is-extglob + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + *) + +is-glob/index.js: + (*! + * is-glob + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + *) + +queue-microtask/index.js: + (*! queue-microtask. MIT License. Feross Aboukhadijeh *) + +run-parallel/index.js: + (*! run-parallel. MIT License. Feross Aboukhadijeh *) + +git-url-parse/lib/index.js: + (*! + * buildToken + * Builds OAuth token prefix (helper function) + * + * @name buildToken + * @function + * @param {GitUrl} obj The parsed Git url object. + * @return {String} token prefix + *) + +object-assign/index.js: + (* + object-assign + (c) Sindre Sorhus + @license MIT + *) + +react/cjs/react.production.min.js: + (** @license React v17.0.2 + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +scheduler/cjs/scheduler.production.min.js: + (** @license React v0.20.2 + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +react-reconciler/cjs/react-reconciler.production.min.js: + (** @license React v0.26.2 + * react-reconciler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +is-windows/index.js: + (*! + * is-windows + * + * Copyright © 2015-2018, Jon Schlinkert. + * Released under the MIT License. + *) +*/ diff --git a/backend/.yarnrc.yml b/backend/.yarnrc.yml new file mode 100644 index 0000000..62657af --- /dev/null +++ b/backend/.yarnrc.yml @@ -0,0 +1,5 @@ +compressionLevel: mixed + +nodeLinker: node-modules + +yarnPath: .yarn/releases/yarn-4.9.1.cjs diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..3254ff7 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,26 @@ +# Rifeberry backend + +## Общее описание системы + +* [Описание системы](https://olivergrand.atlassian.net/wiki/spaces/TEAM/pages/6291457) +* [Упрощенная ER диаграмма](https://olivergrand.atlassian.net/wiki/spaces/BACK/pages/6389854/ER). + +## Настройка локального окружения для разработки + +[Setup local environment](doc/setup-local.md). + +## Deploy + +Деплой: [Gitlab](https://gitlab.com/rifeberry1/nest-backend/-/pipelines). + +## Links + +* [Jira project board](https://olivergrand.atlassian.net/jira/software/projects/BACK/boards/1) +* [Confluence space](https://olivergrand.atlassian.net/wiki/spaces/BACK/pages) + +## Именование веток и комитов + +Ветки именуем следующим образом: название задачи и описание в настоящем времени, +например, `BACK-123-add-api-doc` + +Комиты именуем название задачи + описание комита: `BACK-123: Add api documentation` diff --git a/backend/artifacts/_environment-config/http-client.env.json.dist b/backend/artifacts/_environment-config/http-client.env.json.dist new file mode 100644 index 0000000..c0285ff --- /dev/null +++ b/backend/artifacts/_environment-config/http-client.env.json.dist @@ -0,0 +1,104 @@ +{ + "nestdev": { + "url": "http://test.amwork.loc", + "baseDomain": "amwork.loc", + "subdomain": "test", + "email": "test@test.com", + "password": "test", + "entityTypeId": 123, + "entityId": 123, + "teslaDealEntityId": 123, + "elonContactEntityId": 123, + "boardId": 123, + "stageId": 123, + "fieldGroupId": 123, + "activityTypeId": 123, + "taskId": 123, + "subtaskId": 123, + "taskBoardId": 123, + "taskStageId": 15022001, + "taskSettingsId": 123, + "taskCommentId": 123, + "activityId": 123, + "noteId": 123, + "userId": 123, + "budgetFieldId": 123, + "fileId": "11111111-1111-1111-1111-111111111111", + "mailboxId": 123, + "folderId": 123, + "messageId": 123, + "payloadId": 123, + "automationId": 123, + "taskAutomationId": 123, + "changeStageAutomationId": 123, + "notificationId": 123 + }, + "nestprod": { + "url": "https://test.amwork.com", + "baseDomain": "amwork.com", + "subdomain": "test", + "email": "test@test.com", + "password": "test", + "entityTypeId": 123, + "entityId": 123, + "teslaDealEntityId": 123, + "elonContactEntityId": 123, + "boardId": 123, + "stageId": 123, + "fieldGroupId": 123, + "activityTypeId": 123, + "taskId": 123, + "subtaskId": 123, + "taskBoardId": 123, + "taskStageId": 15022001, + "taskSettingsId": 123, + "taskCommentId": 123, + "activityId": 123, + "noteId": 123, + "userId": 123, + "budgetFieldId": 123, + "fileId": "11111111-1111-1111-1111-111111111111", + "mailboxId": 123, + "folderId": 123, + "messageId": 123, + "payloadId": 123, + "automationId": 123, + "taskAutomationId": 123, + "changeStageAutomationId": 123, + "notificationId": 123 + }, + "neststaging": { + "url": "https://test.amwork.com", + "baseDomain": "amwork.com", + "subdomain": "test", + "email": "test@test.com", + "password": "test", + "entityTypeId": 123, + "entityId": 123, + "teslaDealEntityId": 123, + "elonContactEntityId": 123, + "boardId": 123, + "stageId": 123, + "fieldGroupId": 123, + "activityTypeId": 123, + "taskId": 123, + "subtaskId": 123, + "taskBoardId": 123, + "taskStageId": 15022001, + "taskSettingsId": 123, + "taskCommentId": 123, + "activityId": 123, + "noteId": 123, + "userId": 123, + "budgetFieldId": 123, + "fileId": "11111111-1111-1111-1111-111111111111", + "mailboxId": 123, + "folderId": 123, + "messageId": 123, + "payloadId": 123, + "automationId": 123, + "taskAutomationId": 123, + "changeStageAutomationId": 123, + "notificationId": 123 + } +} diff --git a/backend/artifacts/_resources/test.png b/backend/artifacts/_resources/test.png new file mode 100644 index 0000000..9947820 Binary files /dev/null and b/backend/artifacts/_resources/test.png differ diff --git a/backend/artifacts/account-settings.http b/backend/artifacts/account-settings.http new file mode 100644 index 0000000..4b66b34 --- /dev/null +++ b/backend/artifacts/account-settings.http @@ -0,0 +1,5 @@ +### Get account settings + +GET {{url}}/api/account/settings +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/auth.http b/backend/artifacts/auth.http new file mode 100644 index 0000000..921a2ac --- /dev/null +++ b/backend/artifacts/auth.http @@ -0,0 +1,92 @@ +### Login by subdomain + +POST {{url}}/api/auth/login +Content-Type: application/json + +{ + "email": "{{email}}", + "password": "{{password}}" +} + +> {% +client.global.set("token", response.body.token); +client.global.set("userId", response.body.userId); +%} + +### Login for site + +POST {{url}}/api/auth/login-site +Content-Type: application/json + +{ + "email": "test@test.com", + "password": "test" +} + +> {% +client.global.set("loginLink", response.body.loginLink); +client.global.set("subdomain", response.body.subdomain); +%} + +### Login for extension + +POST {{url}}/api/auth/login-ext +Content-Type: application/json + +{ + "email": "test@test.com", + "password": "test" +} + +> {% +client.global.set("token", response.body.token); +client.global.set("userId", response.body.userId); +%} + +### Decode login link + +POST {{subdomain}}.{{baseDomain}}/api/auth/decode-login-link +Content-Type: application/json + +{ + "loginLink": "{{loginLink}}" +} + +> {% +client.global.set("token", response.body.token); +client.global.set("userId", response.body.userId); +%} + +### Refresh token + +POST {{url}}/api/auth/refresh-token +Content-Type: application/json +Authorization: Bearer {{token}} + +> {% +client.global.set("token", response.body.token); +client.global.set("userId", response.body.userId); + +%} + +### Create account + +POST {{url}}/api/auth/accounts +Content-Type: application/json + +{ + "firstName": "John", + "lastName": "Doe", + "email": "test{{$randomInt}}@test.com", + "phone": "+79998887766", + "companyName": "test5", + "password": "test", + "ref": "{{partnerRef}}", + "promoCode": "some-promo-code", + "rmsCode": "demo" +} + +> {% +client.global.set("loginLink", response.body.loginLink); +client.global.set("subdomain", response.body.subdomain); +%} \ No newline at end of file diff --git a/backend/artifacts/automation/activity-automation.http b/backend/artifacts/automation/activity-automation.http new file mode 100644 index 0000000..84b061c --- /dev/null +++ b/backend/artifacts/automation/activity-automation.http @@ -0,0 +1,93 @@ +### Create automation with activity action + +POST {{url}}/api/automation/automations +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_or_create_entity", + "settings": null + }, + "action": { + "type": "create_activity", + "delay": 300, + "settings": { + "responsibleUserType": "custom", + "responsibleUserId": {{userId}}, + "activityTypeId": {{activityTypeId}}, + "text": "some-text", + "deadlineType": "immediately", + "deadlineTime": null + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [ + { + "type": "responsible_user", + "settings": { + "userIds": [{{userId}}] + } + }, + { + "type": "field", + "settings": { + "fieldId": {{budgetFieldId}}, + "fieldType": "value", + "payload": { + "from": 100, + "to": 200 + } + } + } + ] +} + +### Update automation with activity action + +PUT {{url}}/api/automation/automations/{{activityAutomationId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_or_create_entity", + "settings": null + }, + "action": { + "type": "create_activity", + "delay": 300, + "settings": { + "responsibleUserType": "custom", + "responsibleUserId": {{userId}}, + "activityTypeId": {{activityTypeId}}, + "text": "test", + "deadlineType": "immediately", + "deadlineTime": null + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [ + { + "type": "responsible_user", + "settings": { + "userIds": [{{userId}}] + } + }, + { + "type": "field", + "settings": { + "fieldId": {{budgetFieldId}}, + "fieldType": "value", + "payload": { + "from": 100, + "to": 200 + } + } + } + ] +} \ No newline at end of file diff --git a/backend/artifacts/automation/automation.http b/backend/artifacts/automation/automation.http new file mode 100644 index 0000000..48402dd --- /dev/null +++ b/backend/artifacts/automation/automation.http @@ -0,0 +1,11 @@ +### Get automations + +GET {{url}}/api/automation/board/{{boardId}}/automations +Content-Type: application/json +Authorization: Bearer {{token}} + +### Delete automation + +DELETE {{url}}/api/automation/automations/51011020 +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/automation/change-stage-automation.http b/backend/artifacts/automation/change-stage-automation.http new file mode 100644 index 0000000..369baa3 --- /dev/null +++ b/backend/artifacts/automation/change-stage-automation.http @@ -0,0 +1,47 @@ +### Create automation with task action + +POST {{url}}/api/automation/automations +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_or_create_entity", + "settings": null + }, + "action": { + "type": "change_stage", + "delay": null, + "settings": { + "stageId": {{stageId}} + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [] +} + +### Update automation with task action + +PUT {{url}}/api/automation/automations/{{changeStageAutomationId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_or_create_entity", + "settings": null + }, + "action": { + "type": "change_stage", + "delay": 300, + "settings": { + "stageId": {{stageId}} + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [] +} \ No newline at end of file diff --git a/backend/artifacts/automation/email-automation.http b/backend/artifacts/automation/email-automation.http new file mode 100644 index 0000000..b4c032d --- /dev/null +++ b/backend/artifacts/automation/email-automation.http @@ -0,0 +1,63 @@ +### Create automation with email action + +POST {{url}}/api/automation/automations +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_or_create_entity", + "settings": null + }, + "action": { + "type": "send_email", + "delay": null, + "settings": { + "subject": "email subject", + "content": "email body", + "signature": "some signature", + "sendAsHtml": false, + "mailboxId": 27023020, + "userId": {{userId}}, + "fileIds": [] + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [], + "isActive": true, + "applyTrigger": false +} + +### Update automation with email action + +PUT {{url}}/api/automation/automations/{{emailAutomationId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_or_create_entity", + "settings": null + }, + "action": { + "type": "send_email", + "delay": null, + "settings": { + "subject": "email subject", + "content": "email body", + "signature": "new signature", + "sendAsHtml": false, + "mailboxId": 27023020, + "userId": {{userId}}, + "fileIds": [] + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [], + "isActive": true, + "applyTrigger": false +} \ No newline at end of file diff --git a/backend/artifacts/automation/task-automation.http b/backend/artifacts/automation/task-automation.http new file mode 100644 index 0000000..2b19a10 --- /dev/null +++ b/backend/artifacts/automation/task-automation.http @@ -0,0 +1,57 @@ +### Create automation with task action + +POST {{url}}/api/automation/automations +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_or_create_entity", + "settings": null + }, + "action": { + "type": "create_task", + "delay": 300, + "settings": { + "responsibleUserType": "custom", + "responsibleUserId": {{userId}}, + "title": "some-title", + "text": "some-text", + "deadlineType": "immediately", + "deadlineTime": null + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [] +} + +### Update automation with task action + +PUT {{url}}/api/automation/automations/{{taskAutomationId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "trigger": { + "type": "move_entity", + "settings": null + }, + "action": { + "type": "create_task", + "delay": 3000, + "settings": { + "responsibleUserType": "custom", + "responsibleUserId": {{userId}}, + "title": "new-title", + "text": "new-text", + "deadlineType": "custom", + "deadlineTime": 3000 + } + }, + "stageIds": [ + {{stageId}} + ], + "conditions": [] +} \ No newline at end of file diff --git a/backend/artifacts/board.http b/backend/artifacts/board.http new file mode 100644 index 0000000..a241131 --- /dev/null +++ b/backend/artifacts/board.http @@ -0,0 +1,35 @@ +### Get boards + +GET {{url}}/api/crm/boards +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create board + +POST {{url}}/api/crm/boards +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Board name", + "sortOrder": 1, + "type": "entity_type", + "recordId": {{entityTypeId}} +} + +### Update board + +PUT {{url}}/api/crm/boards/{{boardId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Board name", + "sortOrder": 1 +} + +### Delete board + +DELETE {{url}}/api/crm/boards/321 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/constructor.http b/backend/artifacts/constructor.http new file mode 100644 index 0000000..f3fb006 --- /dev/null +++ b/backend/artifacts/constructor.http @@ -0,0 +1,72 @@ +### Create entity type + +POST {{url}}/api/crm/constructor/create-entity-type +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Object", + "entityCategory": "universal", + "fieldGroups": [ + { + "id": {{fieldGroupId}}, + "name": "Details", + "sortOrder": 0, + "state": "created" + } + ], + "fields": [ + { + "id": 4201, + "name": "Field name", + "type": "text", + "sortOrder": 1, + "fieldGroupId": {{fieldGroupId}}, + "state": "created" + }, + { + "id": 4202, + "name": "Field name", + "type": "select", + "sortOrder": 1, + "fieldGroupId": {{fieldGroupId}}, + "options": [ + { + "id": 1, + "label": "Option 1", + "sortOrder": 0, + "state": "created" + }, + { + "id": 2, + "label": "Option 2", + "sortOrder": 1, + "state": "created" + } + ], + "state": "created" + } + ], + "featureIds": [1, 2, 3], + "taskSettingsActiveFields": ["planned_time", "board_name", "start_date", "end_date", "description", "subtasks"], + "linkedEntityTypes": [ + { + "sortOrder": 0, + "targetEntityTypeId": "{{entityTypeId}}" + } + ], + "linkedEntityCategories": [{ + "entityCategory": "contact", + "entityTypeName": "Partner contact", + "sectionName": "Partner contacts" + }, { + "entityCategory": "company", + "entityTypeName": "Partner company", + "sectionName": "Partner companies" + }], + "section": { + "name": "Objects", + "view": "board", + "icon": "crown" + } +} diff --git a/backend/artifacts/crm.http b/backend/artifacts/crm.http new file mode 100644 index 0000000..60de027 --- /dev/null +++ b/backend/artifacts/crm.http @@ -0,0 +1,10 @@ +### Delete user and change responsible + +DELETE {{url}}/api/crm/user/delete +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "currentUserId": 12022001, + "newUserId": 12022002 +} \ No newline at end of file diff --git a/backend/artifacts/demo.http b/backend/artifacts/demo.http new file mode 100644 index 0000000..e87cce0 --- /dev/null +++ b/backend/artifacts/demo.http @@ -0,0 +1,5 @@ +### Create demo massive data + +POST {{url}}/api/crm/demo-massive-data +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/entity-type.http b/backend/artifacts/entity-type.http new file mode 100644 index 0000000..bd9fa8a --- /dev/null +++ b/backend/artifacts/entity-type.http @@ -0,0 +1,76 @@ +### Get entity type + +GET {{url}}/api/crm/entity-types/{{entityTypeId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get entity types +GET {{url}}/api/crm/entity-types +Content-Type: application/json +Authorization: Bearer {{token}} + +### Delete entity type + +DELETE {{url}}/api/crm/entity-types/321 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Update entity type + +PUT {{url}}/api/crm/entity-types/{{entityTypeId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "id": {{entityTypeId}}, + "name": "Deal", + "cardView": "fields_and_notes", + "fieldGroups": [ + { + "id": {{fieldGroupId}}, + "name": "Details", + "sortOrder": 0, + "state": "created" + } + ], + "fields": [ + { + "id": 4201, + "name": "Field name", + "type": "text", + "sortOrder": 1, + "fieldGroupId": {{fieldGroupId}}, + "state": "updated" + }, + { + "id": 4202, + "name": "Field name", + "type": "select", + "sortOrder": 1, + "fieldGroupId": {{fieldGroupId}}, + "options": [ + { + "id": 1, + "label": "Option 1", + "sortOrder": 0, + "state": "created" + }, + { + "id": 2, + "label": "Option 2", + "sortOrder": 1, + "state": "created" + } + ], + "state": "created" + } + ], + "featureIds": [1, 2, 3], + "taskSettingsActiveFields": ["planned_time", "board_name", "start_date", "end_date", "description", "subtasks"], + "linkedEntityTypes": [], + "section": { + "name": "Deals", + "view": "board", + "icon": "crown" + } +} diff --git a/backend/artifacts/entity.http b/backend/artifacts/entity.http new file mode 100644 index 0000000..fa96ea5 --- /dev/null +++ b/backend/artifacts/entity.http @@ -0,0 +1,108 @@ +### Get entity + +GET {{url}}/api/crm/entities/{{entityId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create entity + +POST {{url}}/api/crm/entities +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "id": {{$randomInt}}, + "name": "Tesla", + "responsibleUserId": {{userId}}, + "entityTypeId": {{entityTypeId}}, + "stageId": {{stageId}}, + "fieldValues": [ + { + "id": {{$randomInt}}, + "fieldId": {{budgetFieldId}}, + "fieldType": "number", + "payload": { + "value": 100000 + }, + "state": "created" + } + ], + "entityLinks": [ + { + "id": {{$randomInt}}, + "sourceId": {{entityId}}, + "targetId": {{elonContactEntityId}}, + "sortOrder": 0, + "state": "created" + } + ] +} + +### Update entity + +PUT {{url}}/api/crm/entities/{{entityId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "id": {{entityId}}, + "name": "Tesla", + "responsibleUserId": {{userId}}, + "entityTypeId": {{entityTypeId}}, + "stageId": {{stageId}}, + "fieldValues": [ + { + "fieldId": {{budgetFieldId}}, + "fieldType": "number", + "payload": { + "value": 10000 + }, + "state": "updated" + } + ], + "entityLinks": [ + { + "id": {{$randomInt}}, + "sourceId": {{entityId}}, + "targetId": {{elonContactEntityId}}, + "sortOrder": 0, + "state": "created" + } + ] +} + +### Update entity stage + +PUT {{url}}/api/crm/entities/{{entityId}}/stage/{{stageId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Delete entity + +DELETE {{url}}/api/crm/entities/321 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get entities for board + +GET {{url}}/api/crm/entities/{{entityTypeId}}/{{boardId}}/cards +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get entities list items + +GET {{url}}/api/crm/entities/{{entityTypeId}}/list-items +Content-Type: application/json +Authorization: Bearer {{token}} + +### Search entities in name, all fields and linked entities + +GET {{url}}/api/crm/entities/{{entityTypeId}}/search?value=book +Content-Type: application/json +Authorization: Bearer {{token}} + +### Search entities by name or field value + +GET {{url}}/api/crm/entities/{{entityTypeId}}/search/fields?name=book&field[42022001]=4&field[42022003]=com +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/extension.http b/backend/artifacts/extension.http new file mode 100644 index 0000000..7f4da00 --- /dev/null +++ b/backend/artifacts/extension.http @@ -0,0 +1,15 @@ +### Create external link + +POST {{url}}/api/extension/external-link +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "url": "https://salesforce.com", + "name": "Tesla", + "responsibleUserId": {{userId}}, + "entityTypeId": {{entityTypeId}}, + "stageId": {{stageId}}, + "fieldValues": [], + "entityLinks": [] +} \ No newline at end of file diff --git a/backend/artifacts/feed-items.http b/backend/artifacts/feed-items.http new file mode 100644 index 0000000..27d8c35 --- /dev/null +++ b/backend/artifacts/feed-items.http @@ -0,0 +1,5 @@ +### Get feed items for entity with paging + +GET {{url}}/api/crm/feed-items?entityId={{entityId}}&offset=0&limit=10 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/feedback.http b/backend/artifacts/feedback.http new file mode 100644 index 0000000..28d29fc --- /dev/null +++ b/backend/artifacts/feedback.http @@ -0,0 +1,17 @@ +### Send feedback + +POST {{url}}/api/mailing/feedback +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "type": "trial_expired", + "payload": { + "name": "John Dow", + "phone": "+1234567890", + "email": "john.dow@company.com", + "userNumber": 10, + "subscribe": "yearly", + "plan": "basic" + } +} \ No newline at end of file diff --git a/backend/artifacts/field-settings.http b/backend/artifacts/field-settings.http new file mode 100644 index 0000000..16e9769 --- /dev/null +++ b/backend/artifacts/field-settings.http @@ -0,0 +1,18 @@ +### Get fields settings + +GET {{url}}/api/crm/entity-types/13022735/fields-settings +Content-Type: application/json +Authorization: Bearer {{token}} + +### Update fields settings + +PUT {{url}}/api/crm/entity-types/13022735/fields-settings +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "activeFieldCodes": [ + "participants", + "description" + ] +} \ No newline at end of file diff --git a/backend/artifacts/field-value.http b/backend/artifacts/field-value.http new file mode 100644 index 0000000..ad483e9 --- /dev/null +++ b/backend/artifacts/field-value.http @@ -0,0 +1,14 @@ +### Save field value + +POST {{url}}/api/crm/entities/{{entityId}}/field-values/{{budgetFieldId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "fieldId": {{budgetFieldId}}, + "fieldType": "value", + "payload": { + "value": 500 + }, + "state": "updated" +} \ No newline at end of file diff --git a/backend/artifacts/fields.http b/backend/artifacts/fields.http new file mode 100644 index 0000000..9a89893 --- /dev/null +++ b/backend/artifacts/fields.http @@ -0,0 +1,48 @@ +### Update entity type fields and groups + +PUT {{url}}/api/crm/entity-types/{{entityTypeId}}/fields-and-groups +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "fieldGroups": [ + { + "id": {{fieldGroupId}}, + "name": "Details", + "sortOrder": 0, + "state": "created" + } + ], + "fields": [ + { + "id": 4201, + "name": "Field name", + "type": "text", + "sortOrder": 1, + "fieldGroupId": {{fieldGroupId}}, + "state": "updated" + }, + { + "id": 4202, + "name": "Field name", + "type": "select", + "sortOrder": 1, + "fieldGroupId": {{fieldGroupId}}, + "options": [ + { + "id": 1, + "label": "Option 1", + "sortOrder": 0, + "state": "created" + }, + { + "id": 2, + "label": "Option 2", + "sortOrder": 1, + "state": "created" + } + ], + "state": "created" + } + ] +} \ No newline at end of file diff --git a/backend/artifacts/file-link.http b/backend/artifacts/file-link.http new file mode 100644 index 0000000..9a8420f --- /dev/null +++ b/backend/artifacts/file-link.http @@ -0,0 +1,5 @@ +### Delete file + +DELETE {{url}}/api/crm/file-link/1 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/forms.http b/backend/artifacts/forms.http new file mode 100644 index 0000000..8b551d7 --- /dev/null +++ b/backend/artifacts/forms.http @@ -0,0 +1,31 @@ +### Send contact us form + +POST {{url}}/api/forms/contact-us +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "test 10", + "phone": "+7999888776633", + "email": "test@test.com", + "comment": "some comment" +} + +### Send partner form + +POST {{url}}/api/forms/partner +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "firstName": "John", + "lastName": "Doe", + "phone": "+7999888776633", + "email": "test@test.com", + "company": "Tesla", + "country": "USA", + "website": "tesla.com", + "employees": "10000", + "comment": "some comment" +} + diff --git a/backend/artifacts/identity.http b/backend/artifacts/identity.http new file mode 100644 index 0000000..2296d03 --- /dev/null +++ b/backend/artifacts/identity.http @@ -0,0 +1,17 @@ +### Get next identity for sequence + +GET {{url}}/api/crm/identity/board_id_seq +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get all identities pool + +GET {{url}}/api/crm/identities/all +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get identities pool + +GET {{url}}/api/crm/identities/feed_item_id_seq +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/import.http b/backend/artifacts/import.http new file mode 100644 index 0000000..b79ea88 --- /dev/null +++ b/backend/artifacts/import.http @@ -0,0 +1,5 @@ +### Get import template for entityType + +GET {{url}}/api/crm/entity-types/{{entityTypeId}}/template +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/mail-messages.http b/backend/artifacts/mail-messages.http new file mode 100644 index 0000000..789d024 --- /dev/null +++ b/backend/artifacts/mail-messages.http @@ -0,0 +1,30 @@ +### Get mails for mailbox + +GET {{url}}/api/mailing/mailboxes/{{mailboxId}}/messages?folderId={{folderId}}&offset=0&limit=50 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get mails for section + +GET {{url}}/api/mailing/section/inbox/messages?mailboxId={{mailboxId}}&offset=0&limit=50 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get mail thread + +GET {{url}}/api/mailing/mailboxes/{{mailboxId}}/threads/1234567890 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get mail message + +GET {{url}}/api/mailing/mailboxes/{{mailboxId}}/messages/{{messageId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get mail attachment + +GET {{url}}'/api/mailing/mailboxes/{{mailboxId}}/messages/{{messageId}}/attachments/{{payloadId}} +Content-Type: application/json +Authorization: Bearer {{token}} + diff --git a/backend/artifacts/mailbox-settings.http b/backend/artifacts/mailbox-settings.http new file mode 100644 index 0000000..b7703e1 --- /dev/null +++ b/backend/artifacts/mailbox-settings.http @@ -0,0 +1,76 @@ +### Create mailbox + +POST {{url}}/api/mailing/settings/mailboxes +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "email": "test@company.com" +} + + +### Update mailbox + +PUT {{url}}/api/mailing/settings/mailboxes/{{mailboxId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "email": "test2@company.com", + "ownerId": {{userid}}, + "accessibleUserIds": [{{userid}}], + "createContact": true, + "syncDays": 7 +} + +### Delete mailbox + +DELETE {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}?save=true +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get mailboxes for settings + +GET {{url}}/api/mailing/settings/mailboxes +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get mailbox for settings + +GET {{url}}/api/mailing/settings/mailboxes/{{mailboxId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get mailbox manual settings + +GET {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}/manual +Content-Type: application/json +Authorization: Bearer {{token}} + +### Update mailbox manual settings + +POST {{url}}/api/mailing/settings/mailboxes/{{mailboxId}}/manual +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "password": "12345678", + "imapServer": "imap.company.com", + "imapPort": 993, + "imapSecure": true, + "smtpServer": "smtp.company.com", + "smtpPort": 465, + "smtpSecure": true +} + +### Get Gmail connection string for redirect + +GET {{url}}/api/mailing/settings/mailboxes/gmail/connect/{{mailboxId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Gmail callback endpoint + +GET {{url}}/api/mailing/settings/mailboxes/gmail/callback?state={{mailboxId}}&code=1234567890 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/mailbox.http b/backend/artifacts/mailbox.http new file mode 100644 index 0000000..bb5d8e2 --- /dev/null +++ b/backend/artifacts/mailbox.http @@ -0,0 +1,5 @@ +### Get mailboxes + +GET {{url}}/api/mailing/mailboxes +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/migration.http b/backend/artifacts/migration.http new file mode 100644 index 0000000..9259ed4 --- /dev/null +++ b/backend/artifacts/migration.http @@ -0,0 +1,5 @@ +### Run migration + +POST {{url}}/api/crm/run-migration +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/module/module.http b/backend/artifacts/module/module.http new file mode 100644 index 0000000..be0715e --- /dev/null +++ b/backend/artifacts/module/module.http @@ -0,0 +1,17 @@ +### Get modules + +GET {{url}}/api/crm/modules +Content-Type: application/json +Authorization: Bearer {{token}} + +### Activate module + +POST {{url}}/api/crm/modules/{{moduleId}}/activate +Content-Type: application/json +Authorization: Bearer {{token}} + +### Deactivate module + +POST {{url}}/api/crm/modules/{{moduleId}}/deactivate +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/note.http b/backend/artifacts/note.http new file mode 100644 index 0000000..8fbcda1 --- /dev/null +++ b/backend/artifacts/note.http @@ -0,0 +1,27 @@ +### Create note + +POST {{url}}/api/crm/notes +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "entityId": {{entityId}}, + "text": "Hello world!!!" +} + +### Update note + +PUT {{url}}/api/crm/notes/{{noteId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "entityId": {{entityId}}, + "text": "Updated note" +} + +### Delete note + +DELETE {{url}}/api/crm/notes/321 +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/notification.http b/backend/artifacts/notification.http new file mode 100644 index 0000000..8330591 --- /dev/null +++ b/backend/artifacts/notification.http @@ -0,0 +1,23 @@ +### Get notifications + +GET {{url}}/api/notifications +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get unseen count + +GET {{url}}/api/notifications/unseen-count +Content-Type: application/json +Authorization: Bearer {{token}} + +### Mark seen all + +PUT {{url}}/api/notifications/seen +Content-Type: application/json +Authorization: Bearer {{token}} + +### Mark seen notification + +PUT {{url}}/api/notifications/{{notificationId}}/seen +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/partner.http b/backend/artifacts/partner.http new file mode 100644 index 0000000..ee455d4 --- /dev/null +++ b/backend/artifacts/partner.http @@ -0,0 +1,11 @@ +### Get partner summary + +GET {{url}}/api/partners/{{partnerId}}/summary +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get partner leads + +GET {{url}}/api/partners/{{partnerId}}/leads +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/products/order-status.http b/backend/artifacts/products/order-status.http new file mode 100644 index 0000000..e11a7e9 --- /dev/null +++ b/backend/artifacts/products/order-status.http @@ -0,0 +1,5 @@ +### Get order statuses + +GET {{url}}/api/products/order-statuses +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/products/order.http b/backend/artifacts/products/order.http new file mode 100644 index 0000000..cc97ec8 --- /dev/null +++ b/backend/artifacts/products/order.http @@ -0,0 +1,92 @@ +### Get entity order + +GET {{url}}/api/products/orders/entity/{{entityId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get entity order products count + +GET {{url}}/api/products/orders/entity/{{entityId}}/products-count +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create order + +POST {{url}}/api/products/orders +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "entityId": {{entityId}}, + "currency": "USD", + "taxIncluded": true, + "statusId": {{orderStatusId}}, + "warehouseId": {{warehouseId}}, + "items": [ + { + "productId": {{productId}}, + "quantity": 5, + "unitPrice": 100, + "tax": 10, + "discount": 0, + "sortOrder": 0, + "reservations": [ + { + "warehouseId": {{warehouseId}}, + "quantity": 5 + }, + { + "warehouseId": {{secondWarehouseId}}, + "quantity": 10 + } + ] + } + ] +} + +### Update order + +PUT {{url}}/api/products/orders/{{orderId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "entityId": {{entityId}}, + "currency": "USD", + "taxIncluded": true, + "statusId": {{orderStatusId}}, + "warehouseId": {{secondWarehouseId}}, + "items": [ + { + "id": 14, + "productId": {{productId}}, + "quantity": 10, + "unitPrice": 200, + "tax": 10, + "discount": 30, + "sortOrder": 2, + "reservations": [ + { + "warehouseId": {{warehouseId}}, + "quantity": 5 + } + ] + }, + { + "id": -1, + "productId": {{productId}}, + "quantity": 20, + "unitPrice": 100, + "tax": 20, + "discount": 30, + "sortOrder": 3, + "reservations": [] + } + ] +} + +### Change order status + +PUT {{url}}/api/products/orders/{{orderId}}/status/{{orderStatusId}} +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/products/product-category.http b/backend/artifacts/products/product-category.http new file mode 100644 index 0000000..015bf9b --- /dev/null +++ b/backend/artifacts/products/product-category.http @@ -0,0 +1,32 @@ +### Get categories + +GET {{url}}/api/products/categories +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create category + +POST {{url}}/api/products/categories +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Category 1", + "parentId": null +} + +### Update category + +PUT {{url}}/api/products/categories/{{productCategoryId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Category new name" +} + +### Delete category + +DELETE {{url}}/api/products/categories/123 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/products/product-price.http b/backend/artifacts/products/product-price.http new file mode 100644 index 0000000..20344f7 --- /dev/null +++ b/backend/artifacts/products/product-price.http @@ -0,0 +1,29 @@ +### Create product price + +POST {{url}}/api/products/{{productId}}/prices +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": null, + "unitPrice": 100, + "currency": "EUR" +} + +### Update product price + +PUT {{url}}/api/products/{{productId}}/prices/{{productPriceId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Updated price", + "unitPrice": 200, + "currency": "USD" +} + +### Delete product price + +DELETE {{url}}/api/products/{{productId}}/prices/67 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/products/product.http b/backend/artifacts/products/product.http new file mode 100644 index 0000000..32fc616 --- /dev/null +++ b/backend/artifacts/products/product.http @@ -0,0 +1,86 @@ +### Get products + +GET {{url}}/api/products?categoryId={{productCategoryId}}&search=some&offset=0&limit=10 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get products by ids + +GET {{url}}/api/products?ids=1,2,3 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get product + +GET {{url}}/api/products/{{productId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create product + +POST {{url}}/api/products +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Some product", + "type": "service", + "description": "Some description", + "sku": "some-product", + "unit": "some-unit", + "tax": 10, + "categoryId": {{productCategoryId}}, + "prices": [ + { + "name": "Price in USD", + "unitPrice": 100, + "currency": "USD" + }, + { + "name": "Price in KZT", + "unitPrice": 200, + "currency": "KZT" + } + ], + "photoFileIds": ["c7d48430-a2b7-4ea5-96a2-58bc40d90574"], + "stocks": [ + { + "warehouseId": {{warehouseId}}, + "stockQuantity": 100 + } + ] +} + +### Update product + +PUT {{url}}/api/products/{{productId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "New product name", + "description": "New description", + "sku": "new-product", + "unit": "new-unit", + "tax": 20, + "categoryId": {{productCategoryId}} +} + +### Delete product + +DELETE {{url}}/api/products/123 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Upload product photos + +POST {{url}}/api/products/{{productId}}/photos +Authorization: Bearer {{token}} +Content-Type: multipart/form-data; boundary="abcd" + +--abcd +Content-Disposition: form-data; name="test"; filename="test.png" +Content-Type: image/png + +< ../_resources/test.png +--abcd-- diff --git a/backend/artifacts/products/shipment-status.http b/backend/artifacts/products/shipment-status.http new file mode 100644 index 0000000..c386449 --- /dev/null +++ b/backend/artifacts/products/shipment-status.http @@ -0,0 +1,5 @@ +### Get shipment statuses + +GET {{url}}/api/products/shipment-statuses +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/products/shipment.http b/backend/artifacts/products/shipment.http new file mode 100644 index 0000000..f124f80 --- /dev/null +++ b/backend/artifacts/products/shipment.http @@ -0,0 +1,17 @@ +### Get shipments + +GET {{url}}/api/products/shipments?offset=0&limit=10 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get shipment by id + +GET {{url}}/api/products/shipments/{{shipmentId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Change shipment status + +PUT {{url}}/api/products/shipments/{{shipmentId}}/status/{{shipmentStatusId}} +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/products/stock.http b/backend/artifacts/products/stock.http new file mode 100644 index 0000000..677dc74 --- /dev/null +++ b/backend/artifacts/products/stock.http @@ -0,0 +1,18 @@ +### Update product stocks + +PUT {{url}}/api/products/{{productId}}/stocks +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "stocks": [ + { + "warehouseId": {{warehouseId}}, + "stockQuantity": 100 + }, + { + "warehouseId": {{secondWarehouseId}}, + "stockQuantity": 200 + } + ] +} \ No newline at end of file diff --git a/backend/artifacts/products/warehouse.http b/backend/artifacts/products/warehouse.http new file mode 100644 index 0000000..019da49 --- /dev/null +++ b/backend/artifacts/products/warehouse.http @@ -0,0 +1,31 @@ +### Get warehouses + +GET {{url}}/api/products/warehouses +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create warehouse + +POST {{url}}/api/products/warehouses +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Some warehouse" +} + +### Update warehouse + +PUT {{url}}/api/products/warehouses/{{warehouseId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "New warehouse name" +} + +### Delete warehouse + +DELETE {{url}}/api/products/warehouses/123 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/rabbit.http b/backend/artifacts/rabbit.http new file mode 100644 index 0000000..389eca1 --- /dev/null +++ b/backend/artifacts/rabbit.http @@ -0,0 +1,5 @@ +### Send event to rabbit + +POST {{url}}/api/rabbit +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/rms.http b/backend/artifacts/rms.http new file mode 100644 index 0000000..f9e5691 --- /dev/null +++ b/backend/artifacts/rms.http @@ -0,0 +1,10 @@ +### Get industries + +GET {{url}}/api/crm/rms/industries +Content-Type: application/json + +### Delete demo data + +DELETE {{url}}/api/crm/rms/demo-data +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/stage.http b/backend/artifacts/stage.http new file mode 100644 index 0000000..5c6f461 --- /dev/null +++ b/backend/artifacts/stage.http @@ -0,0 +1,47 @@ +### Get all stages or by boardId + +GET {{url}}/api/crm/stages +Content-Type: application/json +Authorization: Bearer {{token}} + +### Save stages batch + +POST {{url}}/api/crm/stages/batch +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "stages": [ + { + "id": "172", + "name": "Stage 1", + "color": "#fff", + "code": null, + "isSystem": false, + "sortOrder": 1, + "boardId": {{boardId}}, + "state": "unchanged" + }, + { + "id": "298", + "name": "Stage 2", + "color": "#fff", + "code": null, + "isSystem": false, + "sortOrder": 2, + "boardId": {{boardId}}, + "state": "updated" + }, + { + "id": "{{$randomInt}}", + "name": "Stage 4", + "color": "#fff", + "code": null, + "isSystem": false, + "sortOrder": 1, + "boardId": {{boardId}}, + "state": "added" + } + ] +} + diff --git a/backend/artifacts/storage/file-link.http b/backend/artifacts/storage/file-link.http new file mode 100644 index 0000000..4b94f10 --- /dev/null +++ b/backend/artifacts/storage/file-link.http @@ -0,0 +1,12 @@ +### Delete file link by id + +DELETE {{url}}/api/crm/file-links/123 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Delete file links by ids + +DELETE {{url}}/api/crm/file-links?ids=72,73 +Content-Type: application/json +Authorization: Bearer {{token}} + diff --git a/backend/artifacts/storage/file.http b/backend/artifacts/storage/file.http new file mode 100644 index 0000000..5aa4486 --- /dev/null +++ b/backend/artifacts/storage/file.http @@ -0,0 +1,24 @@ +### Upload file + +POST {{url}}/api/storage/upload +Authorization: Bearer {{token}} +Content-Type: multipart/form-data; boundary="abcd" + +--abcd +Content-Disposition: form-data; name="test"; filename="test.png" +Content-Type: image/png + +< ../_resources/test.png +--abcd-- + +### Delete file + +DELETE {{url}}/api/storage/file/{{fileId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get file + +GET {{url}}/api/storage/file/{{fileId}} +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/subtasks.http b/backend/artifacts/subtasks.http new file mode 100644 index 0000000..dc64317 --- /dev/null +++ b/backend/artifacts/subtasks.http @@ -0,0 +1,27 @@ +### Create subtask + +POST {{url}}/api/crm/tasks/{{taskId}}/subtasks +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "text": "new subtask", + "resolved": false +} + +### Update subtask + +PUT {{url}}/api/crm/tasks/{{taskId}}/subtasks/{{subtaskId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "text": "updated subtask", + "resolved": false +} + +### Delete subtask + +DELETE {{url}}/api/crm/tasks/{{taskId}}/subtasks/46022010 +Content-Type: application/json +Authorization: Bearer {{token}} diff --git a/backend/artifacts/task-comment-like.http b/backend/artifacts/task-comment-like.http new file mode 100644 index 0000000..8c2a213 --- /dev/null +++ b/backend/artifacts/task-comment-like.http @@ -0,0 +1,11 @@ +### Like task comment + +POST {{url}}/api/crm/tasks/{{taskId}}/comments/{{taskCommentId}}/like +Content-Type: application/json +Authorization: Bearer {{token}} + +### Unlike task comment + +POST {{url}}/api/crm/tasks/{{taskId}}/comments/{{taskCommentId}}/unlike +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/task-comment.http b/backend/artifacts/task-comment.http new file mode 100644 index 0000000..2f76dfc --- /dev/null +++ b/backend/artifacts/task-comment.http @@ -0,0 +1,33 @@ +### Get task comments + +GET {{url}}/api/crm/tasks/{{taskId}}/comments?offset=0&limit=10 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create task comment + +POST {{url}}/api/crm/tasks/{{taskId}}/comments +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "text": "Hello world!!!", + "fileIds": [] +} + +### Update task comment + +PUT {{url}}/api/crm/tasks/{{taskId}}/comments/{{taskCommentId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "text": "New text" +} + +### Delete task comment + +DELETE {{url}}/api/crm/tasks/{{taskId}}/comments/47022018 +Content-Type: application/json +Authorization: Bearer {{token}} + diff --git a/backend/artifacts/task-settings.http b/backend/artifacts/task-settings.http new file mode 100644 index 0000000..9d76149 --- /dev/null +++ b/backend/artifacts/task-settings.http @@ -0,0 +1,39 @@ +### Get task settings + +GET {{url}}/api/crm/task-settings +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create task settings + +POST {{url}}/api/crm/task-settings +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "type": "task_board", + "recordId": 123, + "activeFields": [ + "planned_time", + "board_name", + "start_date", + "end_date" + ] +} + +### Update task settings + +PUT {{url}}/api/crm/task-settings/{{taskSettingsId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "activeFields": [ + "planned_time", + "board_name", + "start_date", + "end_date", + "description", + "subtasks" + ] +} \ No newline at end of file diff --git a/backend/artifacts/task-type.http b/backend/artifacts/task-type.http new file mode 100644 index 0000000..821687d --- /dev/null +++ b/backend/artifacts/task-type.http @@ -0,0 +1,21 @@ +### Get task types + +GET {{url}}/api/crm/task-types +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create task type + +POST {{url}}/api/crm/task-types +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "name": "Call" +} + +### Delete task type + +DELETE {{url}}/api/crm/task-types/321 +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/artifacts/task.http b/backend/artifacts/task.http new file mode 100644 index 0000000..721ee6f --- /dev/null +++ b/backend/artifacts/task.http @@ -0,0 +1,134 @@ +### Get task by id + +GET {{url}}/api/crm/tasks/{{taskId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create task + +POST {{url}}/api/crm/tasks +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "responsibleUserId": {{userId}}, + "startDate": "2022-11-21T17:37:03", + "endDate": "2022-11-22T17:37:03", + "text": "Task with start and end time", + "isResolved": false, + "result": null, + "entityId": {{entityId}}, + "title": "Task 1", + "plannedTime": 3600, + "stageId": {{taskStageId}}, + "settingsId": {{taskSettingsId}}, + "subtasks": [ + {"text": "subtask 1", "resolved": true}, + {"text": "subtask 2", "resolved": false} + ] +} + +### Update task + +PUT {{url}}/api/crm/tasks/{{taskId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "responsibleUserId": {{userId}}, + "startDate": "2022-11-23T17:37:03", + "endDate": "2022-11-24T17:37:03", + "text": "Updated Task 1 with start and end time", + "isResolved": true, + "result": "Result", + "entityId": {{entityId}}, + "title": "Updated Task 1" +} + +### Delete task + +DELETE {{url}}/api/crm/tasks/321 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Create activity + +POST {{url}}/api/crm/activities +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "responsibleUserId": {{userId}}, + "startDate": "2022-11-21T17:37:03", + "endDate": "2022-11-22T17:37:03", + "text": "Activity with start and end time", + "isResolved": false, + "result": null, + "entityId": {{entityId}}, + "activityTypeId": {{activityTypeId}} +} + +### Update activity + +PUT {{url}}/api/crm/activities/{{activityId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "responsibleUserId": {{userId}}, + "startDate": "2022-11-23T17:37:03", + "endDate": "2022-11-24T17:37:03", + "text": "Updated Activity 1 with start and end time", + "isResolved": true, + "result": "Result", + "entityId": {{entityId}}, + "activityTypeId": {{activityTypeId}} +} + +### Delete activity + +DELETE {{url}}/api/crm/activities/321 +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get activities + +GET {{url}}/api/crm/activities/cards?responsibleUserId={{userId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get tasks by time + +POST {{url}}/api/crm/tasks/by_time +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get time board meta + +POST {{url}}/api/crm/tasks/by_time/meta +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "stageIds": [{{taskStageId}}] +} + +### Get tasks by board + +POST {{url}}/api/crm/tasks/boards/{{taskBoardId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "ownerIds": [{{userId}}] +} + +### Get tasks board meta + +POST {{url}}/api/crm/tasks/boards/{{taskBoardId}}/meta +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "stageIds": [{{taskStageId}}] +} diff --git a/backend/artifacts/user.http b/backend/artifacts/user.http new file mode 100644 index 0000000..f980c1a --- /dev/null +++ b/backend/artifacts/user.http @@ -0,0 +1,73 @@ +### Get user by id + +GET {{url}}/api/users/{{userId}} +Content-Type: application/json +Authorization: Bearer {{token}} + + +### Create user + +POST {{url}}/api/settings/users +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "firstName": "Genadiy", + "lastName": "Genadiyev", + "email": "genadiy.genadiyev@test1.amwork.com", + "password": "123", + "phone": "+79998887766", + "role": "user" +} + +### Update user + +PUT {{url}}/api/settings/users/{{userId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "firstName": "Genadiy!", + "lastName": "Genadiyev!", + "email": "genadiy.genadiyev@test1.amwork.com", + "password": "123", + "phone": "+79998887766", + "role": "admin" +} + +### Delete activity + +DELETE {{url}}/api/settings/users/{{userId}} +Content-Type: application/json +Authorization: Bearer {{token}} + +### Get user list + +GET {{url}}/api/settings/users +Content-Type: application/json +Authorization: Bearer {{token}} + + +### Get user by id + +GET {{url}}/api/settings/users/{{userId}} +Content-Type: application/json +Authorization: Bearer {{token}} + + +### Update user profile + +PUT {{url}}/api/user/{{userId}}/profile +Content-Type: application/json +Authorization: Bearer {{token}} + +{ + "birthDate": "1996-11-25T14:36:45", + "phone": "+79998887766" +} + +### Get user profile by user id + +GET {{url}}/api/user/{{userId}}/profile +Content-Type: application/json +Authorization: Bearer {{token}} \ No newline at end of file diff --git a/backend/doc/camunda.md b/backend/doc/camunda.md new file mode 100644 index 0000000..62fdb04 --- /dev/null +++ b/backend/doc/camunda.md @@ -0,0 +1,32 @@ +# camunda platform + +## configuration + +github: https://github.com/camunda/camunda-platform +vps: **/home/devops/lvluu/camunda-platform/docker-compose/camunda-8.6** + +## start all services + +please note that you HAVE to cd to `/home/devops/lvluu/camunda-platform/docker-compose/camunda-8.6` before executing any command with docker compose: + +```bash +docker compose --profile full up -d +``` + +## stop all services + +```bash +docker compose --profile full down -d +``` + +## restart a container + +you can press tab to auto suggest the [service_name] + +```bash +docker compose --profile full restart [service_name] +``` + +## change version of a service + +edit the `/home/devops/lvluu/camunda-platform/docker-compose/camunda-8.6/.env` \ No newline at end of file diff --git a/backend/doc/commands.md b/backend/doc/commands.md new file mode 100644 index 0000000..25de3a0 --- /dev/null +++ b/backend/doc/commands.md @@ -0,0 +1,11 @@ +### Create migration + +```bash +yarn typeorm:create-migration AddAccount +``` + +### Run migrations + +```bash +yarn typeorm:run-migrations +``` \ No newline at end of file diff --git a/backend/doc/setup-local.md b/backend/doc/setup-local.md new file mode 100644 index 0000000..2326c12 --- /dev/null +++ b/backend/doc/setup-local.md @@ -0,0 +1,56 @@ +# Setup local development environment + +## Start docker services + +``` +docker-compose up -d +``` + +## Setup database + +- Add new postgres connection in PHP Storm + +If you have different psql version on your host, you can manually restore dump inside container: +- Put dump to `./dumps` directory +- Connect to postgres container +- Restore database: `psql -h localhost --set ON_ERROR_STOP=on -U root -d rifeberry -1 -f /dumps/rifeberry.sql` + +## Connect to node service and run other commands inside container + +``` +docker exec -it nest-backend_node_1 sh +``` + +### Install packages + +``` +yarn +``` + +### Generate JWT keys if not exists + +``` +openssl rsa -pubout -in var/jwt/private.pem -out var/jwt/public.pem +``` + +### Migrate database + +``` +yarn typeorm:run-migrations +``` + +### Add host to /etc/hosts + +``` +127.0.0.1 test.rifeberry.loc +``` + +### Setup node debug + +[Настройка node debug](https://olivergrand.atlassian.net/wiki/spaces/BACK/pages/14319617/Node+debug) + +### Send test request to test.rifeberry.loc/api + +### Optional: Setup wildcard subdomains using dnsmasq + +https://askubuntu.com/questions/1029882/how-can-i-set-up-local-wildcard-127-0-0-1-domain-resolution-on-18-04-20-04 \ No newline at end of file diff --git a/backend/eslint.config.js b/backend/eslint.config.js new file mode 100644 index 0000000..679b48e --- /dev/null +++ b/backend/eslint.config.js @@ -0,0 +1,35 @@ +const eslint = require('@eslint/js'); +const tseslint = require('typescript-eslint'); +const eslintPluginPrettierRecommended = require('eslint-plugin-prettier/recommended'); +const globals = require('globals'); + +module.exports = tseslint.config( + { + files: ["**/*.ts"], + languageOptions: { + globals: { + ...globals.node, + 'Express': false, + 'BufferEncoding': false, + } + } + }, + { + ignores: ['eslint.config.js'], + }, + eslint.configs.recommended, + ...tseslint.configs.strict, + ...tseslint.configs.stylistic, + eslintPluginPrettierRecommended, + { + rules: { + '@typescript-eslint/no-extraneous-class': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/consistent-type-definitions': 'warn', + "no-undef": "error", + "no-global-assign": "error", + "no-new-object": "error", + 'max-len': ['warn', 120, 2], + }, + }, +); diff --git a/backend/knip.json b/backend/knip.json new file mode 100644 index 0000000..6482938 --- /dev/null +++ b/backend/knip.json @@ -0,0 +1,4 @@ +{ + "entry": ["src/main.ts"], + "project": ["src/**/*.ts"] +} \ No newline at end of file diff --git a/backend/nest-cli.json b/backend/nest-cli.json new file mode 100644 index 0000000..4f71b1e --- /dev/null +++ b/backend/nest-cli.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "assets": [ + { "include": "Mailing/system-mailing/templates/**/*.html", "outDir": "./dist", "watchAssets": true }, + { "include": "modules/**/templates/**", "outDir": "./dist", "watchAssets": true } + ] + } +} diff --git a/backend/newrelic.js b/backend/newrelic.js new file mode 100644 index 0000000..00f5fd9 --- /dev/null +++ b/backend/newrelic.js @@ -0,0 +1,53 @@ +'use strict'; +/** + * New Relic agent configuration. + * + * See lib/config/default.js in the agent distribution for a more complete + * description of configuration variables and their potential values. + */ +exports.config = { + /** + * Array of application names. + */ + app_name: [process.env.NEW_RELIC_APP_NAME], + /** + * Your New Relic license key. + */ + license_key: process.env.NEW_RELIC_LICENSE_KEY, + logging: { + /** + * Level at which to log. 'trace' is most useful to New Relic when diagnosing + * issues with the agent, 'info' and higher will impose the least overhead on + * production applications. + */ + level: process.env.NEW_RELIC_LOG_LEVEL, + }, + /** + * When true, all request headers except for those listed in attributes.exclude + * will be captured for all traces, unless otherwise specified in a destination's + * attributes include/exclude lists. + */ + allow_all_headers: true, + attributes: { + /** + * Prefix of attributes to exclude from all destinations. Allows * as wildcard + * at end. + * + * NOTE: If excluding headers, they must be in camelCase form to be filtered. + * + * @name NEW_RELIC_ATTRIBUTES_EXCLUDE + */ + exclude: [ + 'request.headers.cookie', + 'request.headers.authorization', + 'request.headers.proxyAuthorization', + 'request.headers.setCookie*', + 'request.headers.x*', + 'response.headers.cookie', + 'response.headers.authorization', + 'response.headers.proxyAuthorization', + 'response.headers.setCookie*', + 'response.headers.x*', + ], + }, +}; diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..a4e7968 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,18536 @@ +{ + "name": "amwork-backend", + "version": "3.14.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "amwork-backend", + "version": "3.14.1", + "license": "UNLICENSED", + "dependencies": { + "@amwork/voximplant-apiclient-nodejs": "^2.3.0-f", + "@aws-sdk/client-s3": "^3.817.0", + "@camunda8/sdk": "^8.7.9", + "@date-fns/tz": "^1.2.0", + "@date-fns/utc": "^2.1.0", + "@esm2cjs/cacheable-lookup": "^7.0.0", + "@faker-js/faker": "^9.8.0", + "@nestjs-modules/mailer": "2.0.2", + "@nestjs/axios": "^4.0.0", + "@nestjs/common": "^11.1.2", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.1.2", + "@nestjs/event-emitter": "^3.0.1", + "@nestjs/jwt": "^11.0.0", + "@nestjs/platform-express": "^11.1.2", + "@nestjs/platform-socket.io": "^11.1.2", + "@nestjs/schedule": "^6.0.0", + "@nestjs/swagger": "^11.2.0", + "@nestjs/terminus": "^11.0.0", + "@nestjs/typeorm": "^11.0.0", + "@nestjs/websockets": "^11.1.2", + "@newrelic/native-metrics": "^11.1.0", + "@voximplant/apiclient-nodejs": "^4.2.0", + "angular-expressions": "^1.4.3", + "axios": "^1.9.0", + "bcrypt": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", + "decimal.js": "^10.5.0", + "docxtemplater": "^3.63.2", + "dotenv": "^16.6.1", + "exceljs": "^4.4.0", + "express": "^5.1.0", + "form-data": "^4.0.2", + "generate-password": "^1.7.1", + "googleapis": "^149.0.0", + "handlebars": "^4.7.8", + "heapdump": "^0.3.15", + "html-to-text": "^9.0.5", + "iconv-lite": "^0.6.3", + "imap-simple": "^5.1.0", + "imapflow": "^1.0.187", + "ioredis": "^5.6.1", + "jsonwebtoken": "^9.0.2", + "libphonenumber-js": "^1.12.8", + "lvovich": "^2.0.2", + "mailparser": "^3.7.3", + "mathjs": "^14.5.1", + "mime-types": "^3.0.1", + "multer": "^2.0.0", + "nest-winston": "^1.10.2", + "newrelic": "^12.20.0", + "nodemailer": "^7.0.3", + "number-to-words-ru": "^2.4.1", + "path-to-regexp": "^8.2.0", + "pg": "^8.16.0", + "pizzip": "^3.2.0", + "qs": "^6.14.0", + "quoted-printable": "^1.0.1", + "reflect-metadata": "^0.2.2", + "rimraf": "^6.0.1", + "rxjs": "^7.8.2", + "sharp": "^0.34.2", + "slugify": "^1.6.6", + "socket.io": "^4.8.1", + "stripe": "^17.7.0", + "twilio": "^5.7.0", + "typeorm": "^0.3.24", + "typeorm-naming-strategies": "^4.1.0", + "uuid": "^11.1.0", + "winston": "^3.17.0", + "written-number": "^0.11.1" + }, + "devDependencies": { + "@eslint/js": "^9.27.0", + "@nestjs/cli": "^11.0.7", + "@nestjs/schematics": "^11.0.5", + "@swc/cli": "^0.7.7", + "@swc/core": "^1.11.29", + "@total-typescript/ts-reset": "^0.6.1", + "@types/bcrypt": "^5.0.2", + "@types/express": "^5.0.2", + "@types/form-data": "^2.5.2", + "@types/heapdump": "^0", + "@types/html-to-text": "^9.0.4", + "@types/imap-simple": "^4.2.10", + "@types/mailparser": "^3.4.6", + "@types/mime-types": "^2", + "@types/multer": "^1.4.12", + "@types/node": "^22.15.24", + "@types/nodemailer": "^6.4.17", + "@types/quoted-printable": "^1", + "@types/uuid": "^10.0.0", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.0", + "knip": "^5.59.1", + "prettier": "^3.5.3", + "ts-node": "^10.9.2", + "tsconfig-paths": "4.2.0", + "typescript": "^5.8.3", + "typescript-eslint": "^8.33.0" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs": { + "version": "2.3.0-f", + "resolved": "https://registry.npmjs.org/@amwork/voximplant-apiclient-nodejs/-/voximplant-apiclient-nodejs-2.3.0-f.tgz", + "integrity": "sha512-tveFRjiAoox4fPed2kSyzVecgofueYuaUkZyB5w8AEGC3ooG4mzH0QNkLB8swNFRCEotY0RpvGF7VOIOwuznnQ==", + "dependencies": { + "axios": "0.21.4", + "form-data": "2.5.1", + "jsonwebtoken": "8.5.1" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=4", + "npm": ">=1.4.28" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "dependencies": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@amwork/voximplant-apiclient-nodejs/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@angular-devkit/core": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.19.tgz", + "integrity": "sha512-JbLL+4IMLMBgjLZlnPG4lYDfz4zGrJ/s6Aoon321NJKuw1Kb1k5KpFu9dUY0BqLIe8xPQ2UJBpI+xXdK5MXMHQ==", + "dev": true, + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.19.tgz", + "integrity": "sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "19.2.19", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli": { + "version": "19.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.19.tgz", + "integrity": "sha512-7q9UY6HK6sccL9F3cqGRUwKhM7b/XfD2YcVaZ2WD7VMaRlRm85v6mRjSrfKIAwxcQU0UK27kMc79NIIqaHjzxA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "19.2.19", + "@angular-devkit/schematics": "19.2.19", + "@inquirer/prompts": "7.3.2", + "ansi-colors": "4.1.3", + "symbol-observable": "4.0.0", + "yargs-parser": "21.1.1" + }, + "bin": { + "schematics": "bin/schematics.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", + "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^4.1.2", + "@inquirer/confirm": "^5.1.6", + "@inquirer/editor": "^4.2.7", + "@inquirer/expand": "^4.0.9", + "@inquirer/input": "^4.1.6", + "@inquirer/number": "^3.0.9", + "@inquirer/password": "^4.0.9", + "@inquirer/rawlist": "^4.0.9", + "@inquirer/search": "^3.0.9", + "@inquirer/select": "^4.0.9" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.965.0.tgz", + "integrity": "sha512-BTeaaU1iK0BfatTCrtYjNkIHCoZH256qOI18l9bK4z6mVOgpHkYN4RvOu+NnKgyX58n+HWfOuhtKUD4OE33Vdw==", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.965.0", + "@aws-sdk/credential-provider-node": "3.965.0", + "@aws-sdk/middleware-bucket-endpoint": "3.965.0", + "@aws-sdk/middleware-expect-continue": "3.965.0", + "@aws-sdk/middleware-flexible-checksums": "3.965.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-location-constraint": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-sdk-s3": "3.965.0", + "@aws-sdk/middleware-ssec": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.965.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/signature-v4-multi-region": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.965.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/eventstream-serde-browser": "^4.2.7", + "@smithy/eventstream-serde-config-resolver": "^4.3.7", + "@smithy/eventstream-serde-node": "^4.2.7", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-blob-browser": "^4.2.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/hash-stream-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/md5-js": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-ses": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.965.0.tgz", + "integrity": "sha512-RZXJBoHA3I6Ts1/bjOLDceT0hbK00lVkXAXFpcz5At+p6Yu52jVmdAdKDmLuf1IgCDw7s2IsrR4Us2Od1AabCg==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.965.0", + "@aws-sdk/credential-provider-node": "3.965.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.965.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.965.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.965.0.tgz", + "integrity": "sha512-iv2tr+n4aZ+nPUFFvG00hISPuEd4DU+1/Q8rPAYKXsM+vEPJ2nAnP5duUOa2fbOLIUCRxX3dcQaQaghVHDHzQw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.965.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.965.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.965.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.965.0.tgz", + "integrity": "sha512-aq9BhQxdHit8UUJ9C0im9TtuKeK0pT6NXmNJxMTCFeStI7GG7ImIsSislg3BZTIifVg1P6VLdzMyz9de85iutQ==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@aws-sdk/xml-builder": "3.965.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.965.0.tgz", + "integrity": "sha512-9FbIyJ/Zz1AdEIrb0+Pn7wRi+F/0Y566ooepg0hDyHUzRV3ZXKjOlu3wJH3YwTz2UkdwQmldfUos2yDJps7RyA==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.965.0.tgz", + "integrity": "sha512-mdGnaIjMxTIjsb70dEj3VsWPWpoq1V5MWzBSfJq2H8zgMBXjn6d5/qHP8HMf53l9PrsgqzMpXGv3Av549A2x1g==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.965.0.tgz", + "integrity": "sha512-YuGQel9EgA/z25oeLM+GYYQS750+8AESvr7ZEmVnRPL0sg+K3DmGqdv+9gFjFd0UkLjTlC/jtbP2cuY6UcPiHQ==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.965.0.tgz", + "integrity": "sha512-xRo72Prer5s0xYVSCxCymVIRSqrVlevK5cmU0GWq9yJtaBNpnx02jwdJg80t/Ni7pgbkQyFWRMcq38c1tc6M/w==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/credential-provider-env": "3.965.0", + "@aws-sdk/credential-provider-http": "3.965.0", + "@aws-sdk/credential-provider-login": "3.965.0", + "@aws-sdk/credential-provider-process": "3.965.0", + "@aws-sdk/credential-provider-sso": "3.965.0", + "@aws-sdk/credential-provider-web-identity": "3.965.0", + "@aws-sdk/nested-clients": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.965.0.tgz", + "integrity": "sha512-43/H8Qku8LHyugbhLo8kjD+eauhybCeVkmrnvWl8bXNHJP7xi1jCdtBQJKKJqiIHZws4MOEwkji8kFdAVRCe6g==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/nested-clients": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.965.0.tgz", + "integrity": "sha512-cRxmMHF+Zh2lkkkEVduKl+8OQdtg/DhYA69+/7SPSQURlgyjFQGlRQ58B7q8abuNlrGT3sV+UzeOylZpJbV61Q==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.965.0", + "@aws-sdk/credential-provider-http": "3.965.0", + "@aws-sdk/credential-provider-ini": "3.965.0", + "@aws-sdk/credential-provider-process": "3.965.0", + "@aws-sdk/credential-provider-sso": "3.965.0", + "@aws-sdk/credential-provider-web-identity": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.965.0.tgz", + "integrity": "sha512-gmkPmdiR0yxnTzLPDb7rwrDhGuCUjtgnj8qWP+m0gSz/W43rR4jRPVEf6DUX2iC+ImQhxo3NFhuB3V42Kzo3TQ==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.965.0.tgz", + "integrity": "sha512-N01AYvtCqG3Wo/s/LvYt19ity18/FqggiXT+elAs3X9Om/Wfx+hw9G+i7jaDmy+/xewmv8AdQ2SK5Q30dXw/Fw==", + "dependencies": { + "@aws-sdk/client-sso": "3.965.0", + "@aws-sdk/core": "3.965.0", + "@aws-sdk/token-providers": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.965.0.tgz", + "integrity": "sha512-T4gMZ2JzXnfxe1oTD+EDGLSxFfk1+WkLZdiHXEMZp8bFI1swP/3YyDFXI+Ib9Uq1JhnAmrCXtOnkicKEhDkdhQ==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/nested-clients": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.965.0.tgz", + "integrity": "sha512-gbdv3Dl8l8xmg4oH60fXvfDyTxfx28w5/Hxdymx3vurM07tAyd4qld8zEXejnSpraTo45QcHRtk5auELIMfeag==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-arn-parser": "3.965.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.965.0.tgz", + "integrity": "sha512-UBxVytsmhEmFwkBnt+aV0eAJ7uc+ouNokCqMBrQ7Oc5A77qhlcHfOgXIKz2SxqsiYTsDq+a0lWFM/XpyRWraqA==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.965.0.tgz", + "integrity": "sha512-5rzEW08trcpHMe6jkQyYc4PL1KG/H7BbnySFSzhih+r/gktQEiE36sb1BNf7av9I0Vk2Ccmt7wocB5PIT7GDkQ==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.965.0", + "@aws-sdk/crc64-nvme": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.965.0.tgz", + "integrity": "sha512-SfpSYqoPOAmdb3DBsnNsZ0vix+1VAtkUkzXM79JL3R5IfacpyKE2zytOgVAQx/FjhhlpSTwuXd+LRhUEVb3MaA==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.965.0.tgz", + "integrity": "sha512-07T1rwAarQs33mVg5U28AsSdLB5JUXu9yBTBmspFGajKVsEahIyntf53j9mAXF1N2KR0bNdP0J4A0kst4t43UQ==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.965.0.tgz", + "integrity": "sha512-gjUvJRZT1bUABKewnvkj51LAynFrfz2h5DYAg5/2F4Utx6UOGByTSr9Rq8JCLbURvvzAbCtcMkkIJRxw+8Zuzw==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.965.0.tgz", + "integrity": "sha512-6dvD+18Ni14KCRu+tfEoNxq1sIGVp9tvoZDZ7aMvpnA7mDXuRLrOjRQ/TAZqXwr9ENKVGyxcPl0cRK8jk1YWjA==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.965.0.tgz", + "integrity": "sha512-dXEgnojaaVRl+OlOx35mg3rYEbfffIN4X6tLmIfDnaKz0hMaDMvsE9jJXb/vBvokbdO1sVB27/2FEM4ttLSLnw==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-arn-parser": "3.965.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.965.0.tgz", + "integrity": "sha512-dke++CTw26y+a2D1DdVuZ4+2TkgItdx6TeuE0zOl4lsqXGvTBUG4eaIZalt7ZOAW5ys2pbDOk1bPuh4opoD3pQ==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.965.0.tgz", + "integrity": "sha512-RBEYVGgu/WeAt+H/qLrGc+t8LqAUkbyvh3wBfTiuAD+uBcWsKnvnB1iSBX75FearC0fmoxzXRUc0PMxMdqpjJQ==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@smithy/core": "^3.20.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.965.0.tgz", + "integrity": "sha512-muNVUjUEU+/KLFrLzQ8PMXyw4+a/MP6t4GIvwLtyx/kH0rpSy5s0YmqacMXheuIe6F/5QT8uksXGNAQenitkGQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.965.0", + "@aws-sdk/middleware-host-header": "3.965.0", + "@aws-sdk/middleware-logger": "3.965.0", + "@aws-sdk/middleware-recursion-detection": "3.965.0", + "@aws-sdk/middleware-user-agent": "3.965.0", + "@aws-sdk/region-config-resolver": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@aws-sdk/util-endpoints": "3.965.0", + "@aws-sdk/util-user-agent-browser": "3.965.0", + "@aws-sdk/util-user-agent-node": "3.965.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.965.0.tgz", + "integrity": "sha512-RoMhu9ly2B0coxn8ctXosPP2WmDD0MkQlZGLjoYHQUOCBmty5qmCxOqBmBDa6wbWbB8xKtMQ/4VXloQOgzjHXg==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.965.0.tgz", + "integrity": "sha512-hgbAThbsUrWtNpFBQxzXevIfd5Qgr4TLbXY1AIbmpSX9fPVC114pdieRMpopJ0fYaJ7v5/blTiS6wzVdXleZ/w==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.965.0.tgz", + "integrity": "sha512-aR0qxg0b8flkXJVE+CM1gzo7uJ57md50z2eyCwofC0QIz5Y0P7/7vvb9/dmUQt6eT9XRN5iRcUqq2IVxVDvJOw==", + "dependencies": { + "@aws-sdk/core": "3.965.0", + "@aws-sdk/nested-clients": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.965.0.tgz", + "integrity": "sha512-jvodoJdMavvg8faN7co58vVJRO5MVep4JFPRzUNCzpJ98BDqWDk/ad045aMJcmxkLzYLS2UAnUmqjJ/tUPNlzQ==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.965.0.tgz", + "integrity": "sha512-bNGKr5Tct28jGLkL8xIkGu7swpDgBpkTVbGaofhzr/X80iclbOv656RGxhMpDvmc4S9UuQnqLRXyceNFNF2V7Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.965.0.tgz", + "integrity": "sha512-WqSCB0XIsGUwZWvrYkuoofi2vzoVHqyeJ2kN+WyoOsxPLTiQSBIoqm/01R/qJvoxwK/gOOF7su9i84Vw2NQQpQ==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.0.tgz", + "integrity": "sha512-9LJFand4bIoOjOF4x3wx0UZYiFZRo4oUauxQSiEX2dVg+5qeBOJSjp2SeWykIE6+6frCZ5wvWm2fGLK8D32aJw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.965.0.tgz", + "integrity": "sha512-Xiza/zMntQGpkd2dETQeAK8So1pg5+STTzpcdGWxj5q0jGO5ayjqT/q1Q7BrsX5KIr6PvRkl9/V7lLCv04wGjQ==", + "dependencies": { + "@aws-sdk/types": "3.965.0", + "@smithy/types": "^4.11.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.965.0.tgz", + "integrity": "sha512-kokIHUfNT3/P55E4fUJJrFHuuA9BbjFKUIxoLrd3UaRfdafT0ScRfg2eaZie6arf60EuhlUIZH0yALxttMEjxQ==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.965.0", + "@aws-sdk/types": "3.965.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.965.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.965.0.tgz", + "integrity": "sha512-Tcod25/BTupraQwtb+Q+GX8bmEZfxIFjjJ/AvkhUZsZlkPeVluzq1uu3Oeqf145DCdMjzLIN6vab5MrykbDP+g==", + "dependencies": { + "@smithy/types": "^4.11.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", + "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "optional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "devOptional": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "optional": true, + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "optional": true, + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", + "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@camunda8/orchestration-cluster-api": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@camunda8/orchestration-cluster-api/-/orchestration-cluster-api-1.2.3.tgz", + "integrity": "sha512-JQm/rOjPmC63iw25Vuf45YxiCB98gwatLoaHdKOZLGFamMckX5fJspgxipEERF/aEj6DvqdxADlhxWM7WNL2Xg==", + "dependencies": { + "p-retry": "^6.0.1", + "typed-env": "^2.0.0", + "zod": "^4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@camunda8/sdk": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@camunda8/sdk/-/sdk-8.8.4.tgz", + "integrity": "sha512-Ie7kfTaXSKws8t3dM9FLR6lbw5u1ZUHB/LzlHpW0MbindFC04o4dOgSVwqlrHhUSdCe1djXr3K62xw2l65D6Og==", + "dependencies": { + "@camunda8/orchestration-cluster-api": "^1.2.2", + "@grpc/grpc-js": "1.12.5", + "@grpc/proto-loader": "0.7.13", + "@types/form-data": "^2.2.1", + "@types/node": "^22.18.6", + "chalk": "^2.4.2", + "console-stamp": "^3.0.2", + "dayjs": "^1.8.15", + "debug": "^4.3.4", + "fast-xml-parser": "^4.1.3", + "form-data": "^4.0.2", + "got": "^11.8.6", + "jwt-decode": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "long": "^4.0.0", + "lossless-json": "^4.0.1", + "p-cancelable": "^2.1.1", + "promise-retry": "^1.1.1", + "reflect-metadata": "^0.2.1", + "stack-trace": "0.0.10", + "typed-duration": "^1.0.12", + "typed-emitter": "^2.1.0", + "typed-env": "^2.0.0", + "winston": "^3.14.2" + }, + "optionalDependencies": { + "win-ca": "3.5.1" + } + }, + "node_modules/@camunda8/sdk/node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@camunda8/sdk/node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@css-inline/css-inline": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline/-/css-inline-0.14.1.tgz", + "integrity": "sha512-u4eku+hnPqqHIGq/ZUQcaP0TrCbYeLIYBaK7qClNRGZbnh8RC4gVxLEIo8Pceo1nOK9E5G4Lxzlw5KnXcvflfA==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@css-inline/css-inline-android-arm-eabi": "0.14.1", + "@css-inline/css-inline-android-arm64": "0.14.1", + "@css-inline/css-inline-darwin-arm64": "0.14.1", + "@css-inline/css-inline-darwin-x64": "0.14.1", + "@css-inline/css-inline-linux-arm-gnueabihf": "0.14.1", + "@css-inline/css-inline-linux-arm64-gnu": "0.14.1", + "@css-inline/css-inline-linux-arm64-musl": "0.14.1", + "@css-inline/css-inline-linux-x64-gnu": "0.14.1", + "@css-inline/css-inline-linux-x64-musl": "0.14.1", + "@css-inline/css-inline-win32-x64-msvc": "0.14.1" + } + }, + "node_modules/@css-inline/css-inline-android-arm-eabi": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-android-arm-eabi/-/css-inline-android-arm-eabi-0.14.1.tgz", + "integrity": "sha512-LNUR8TY4ldfYi0mi/d4UNuHJ+3o8yLQH9r2Nt6i4qeg1i7xswfL3n/LDLRXvGjBYqeEYNlhlBQzbPwMX1qrU6A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-android-arm64": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-android-arm64/-/css-inline-android-arm64-0.14.1.tgz", + "integrity": "sha512-tH5us0NYGoTNBHOUHVV7j9KfJ4DtFOeTLA3cM0XNoMtArNu2pmaaBMFJPqECzavfXkLc7x5Z22UPZYjoyHfvCA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-darwin-arm64": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-darwin-arm64/-/css-inline-darwin-arm64-0.14.1.tgz", + "integrity": "sha512-QE5W1YRIfRayFrtrcK/wqEaxNaqLULPI0gZB4ArbFRd3d56IycvgBasDTHPre5qL2cXCO3VyPx+80XyHOaVkag==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-darwin-x64": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-darwin-x64/-/css-inline-darwin-x64-0.14.1.tgz", + "integrity": "sha512-mAvv2sN8awNFsbvBzlFkZPbCNZ6GCWY5/YcIz7V5dPYw+bHHRbjnlkNTEZq5BsDxErVrMIGvz05PGgzuNvZvdQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-arm-gnueabihf": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-arm-gnueabihf/-/css-inline-linux-arm-gnueabihf-0.14.1.tgz", + "integrity": "sha512-AWC44xL0X7BgKvrWEqfSqkT2tJA5kwSGrAGT+m0gt11wnTYySvQ6YpX0fTY9i3ppYGu4bEdXFjyK2uY1DTQMHA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-arm64-gnu": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-arm64-gnu/-/css-inline-linux-arm64-gnu-0.14.1.tgz", + "integrity": "sha512-drj0ciiJgdP3xKXvNAt4W+FH4KKMs8vB5iKLJ3HcH07sNZj58Sx++2GxFRS1el3p+GFp9OoYA6dgouJsGEqt0Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-arm64-musl": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-arm64-musl/-/css-inline-linux-arm64-musl-0.14.1.tgz", + "integrity": "sha512-FzknI+st8eA8YQSdEJU9ykcM0LZjjigBuynVF5/p7hiMm9OMP8aNhWbhZ8LKJpKbZrQsxSGS4g9Vnr6n6FiSdQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-x64-gnu": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-x64-gnu/-/css-inline-linux-x64-gnu-0.14.1.tgz", + "integrity": "sha512-yubbEye+daDY/4vXnyASAxH88s256pPati1DfVoZpU1V0+KP0BZ1dByZOU1ktExurbPH3gZOWisAnBE9xon0Uw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-x64-musl": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-x64-musl/-/css-inline-linux-x64-musl-0.14.1.tgz", + "integrity": "sha512-6CRAZzoy1dMLPC/tns2rTt1ZwPo0nL/jYBEIAsYTCWhfAnNnpoLKVh5Nm+fSU3OOwTTqU87UkGrFJhObD/wobQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-win32-x64-msvc": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-win32-x64-msvc/-/css-inline-win32-x64-msvc-0.14.1.tgz", + "integrity": "sha512-nzotGiaiuiQW78EzsiwsHZXbxEt6DiMUFcDJ6dhiliomXxnlaPyBfZb6/FMBgRJOf6sknDt/5695OttNmbMYzg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz", + "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==", + "dependencies": { + "@so-ric/colorspace": "^1.1.6", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@date-fns/tz": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz", + "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==" + }, + "node_modules/@date-fns/utc": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-2.1.1.tgz", + "integrity": "sha512-SlJDfG6RPeEX8wEVv6ZB3kak4MmbtyiI2qX/5zuKdordbrhB/iaJ58GVMZgJ6P1sJaM1gMgENFYYeg1JWrCFrA==" + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@esm2cjs/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@esm2cjs/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-5HzrA5N0lSMtx2RdXfD9Z4HUFaRGwVOFs7jsFG8jDivoZjYYwZFsSqvA17TaNZYFcwBrkSCqHlxDu2YDpjjUBA==", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/AlCalzone" + } + }, + "node_modules/@faker-js/faker": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.9.0.tgz", + "integrity": "sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/format/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/@fast-csv/parse/node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==" + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.5.tgz", + "integrity": "sha512-d3iiHxdpg5+ZcJ6jnDSOT8Z0O0VMVGy34jAnYLUX8yd36b1qn8f1TwOA/Lc7TsOh03IkPJ38eGI5qD2EjNkoEA==", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@grpc/proto-loader/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@ioredis/commands": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.0.tgz", + "integrity": "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==" + }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "devOptional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "devOptional": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", + "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==" + }, + "node_modules/@napi-rs/nice": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.1.1.tgz", + "integrity": "sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.1.1", + "@napi-rs/nice-android-arm64": "1.1.1", + "@napi-rs/nice-darwin-arm64": "1.1.1", + "@napi-rs/nice-darwin-x64": "1.1.1", + "@napi-rs/nice-freebsd-x64": "1.1.1", + "@napi-rs/nice-linux-arm-gnueabihf": "1.1.1", + "@napi-rs/nice-linux-arm64-gnu": "1.1.1", + "@napi-rs/nice-linux-arm64-musl": "1.1.1", + "@napi-rs/nice-linux-ppc64-gnu": "1.1.1", + "@napi-rs/nice-linux-riscv64-gnu": "1.1.1", + "@napi-rs/nice-linux-s390x-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-gnu": "1.1.1", + "@napi-rs/nice-linux-x64-musl": "1.1.1", + "@napi-rs/nice-openharmony-arm64": "1.1.1", + "@napi-rs/nice-win32-arm64-msvc": "1.1.1", + "@napi-rs/nice-win32-ia32-msvc": "1.1.1", + "@napi-rs/nice-win32-x64-msvc": "1.1.1" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.1.1.tgz", + "integrity": "sha512-kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz", + "integrity": "sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz", + "integrity": "sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz", + "integrity": "sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz", + "integrity": "sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz", + "integrity": "sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz", + "integrity": "sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz", + "integrity": "sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz", + "integrity": "sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz", + "integrity": "sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-s390x-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz", + "integrity": "sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-gnu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz", + "integrity": "sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-x64-musl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz", + "integrity": "sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-openharmony-arm64": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz", + "integrity": "sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz", + "integrity": "sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz", + "integrity": "sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz", + "integrity": "sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@nestjs-modules/mailer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nestjs-modules/mailer/-/mailer-2.0.2.tgz", + "integrity": "sha512-+z4mADQasg0H1ZaGu4zZTuKv2pu+XdErqx99PLFPzCDNTN/q9U59WPgkxVaHnsvKHNopLj5Xap7G4ZpptduoYw==", + "dependencies": { + "@css-inline/css-inline": "0.14.1", + "glob": "10.3.12" + }, + "optionalDependencies": { + "@types/ejs": "^3.1.5", + "@types/mjml": "^4.7.4", + "@types/pug": "^2.0.10", + "ejs": "^3.1.10", + "handlebars": "^4.7.8", + "liquidjs": "^10.11.1", + "mjml": "^4.15.3", + "preview-email": "^3.0.19", + "pug": "^3.0.2" + }, + "peerDependencies": { + "@nestjs/common": ">=7.0.9", + "@nestjs/core": ">=7.0.9", + "@types/ejs": ">=3.0.3", + "@types/mjml": ">=4.7.4", + "@types/pug": ">=2.0.6", + "ejs": ">=3.1.2", + "handlebars": ">=4.7.6", + "liquidjs": ">=10.8.2", + "mjml": ">=4.15.3", + "nodemailer": ">=6.4.6", + "preview-email": ">=3.0.19", + "pug": ">=3.0.1" + } + }, + "node_modules/@nestjs/axios": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-4.0.1.tgz", + "integrity": "sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "axios": "^1.3.1", + "rxjs": "^7.0.0" + } + }, + "node_modules/@nestjs/cli": { + "version": "11.0.14", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.14.tgz", + "integrity": "sha512-YwP03zb5VETTwelXU+AIzMVbEZKk/uxJL+z9pw0mdG9ogAtqZ6/mpmIM4nEq/NU8D0a7CBRLcMYUmWW/55pfqw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "19.2.19", + "@angular-devkit/schematics": "19.2.19", + "@angular-devkit/schematics-cli": "19.2.19", + "@inquirer/prompts": "7.10.1", + "@nestjs/schematics": "^11.0.1", + "ansis": "4.2.0", + "chokidar": "4.0.3", + "cli-table3": "0.6.5", + "commander": "4.1.1", + "fork-ts-checker-webpack-plugin": "9.1.0", + "glob": "13.0.0", + "node-emoji": "1.11.0", + "ora": "5.4.1", + "tsconfig-paths": "4.2.0", + "tsconfig-paths-webpack-plugin": "4.2.0", + "typescript": "5.9.3", + "webpack": "5.103.0", + "webpack-node-externals": "3.0.0" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 20.11" + }, + "peerDependencies": { + "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0", + "@swc/core": "^1.3.62" + }, + "peerDependenciesMeta": { + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nestjs/cli/node_modules/glob": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "dev": true, + "dependencies": { + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@nestjs/cli/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/cli/node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nestjs/common": { + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.11.tgz", + "integrity": "sha512-R/+A8XFqLgN8zNs2twhrOaE7dJbRQhdPX3g46am4RT/x8xGLqDphrXkUIno4cGUZHxbczChBAaAPTdPv73wDZA==", + "dependencies": { + "file-type": "21.2.0", + "iterare": "1.2.1", + "load-esm": "1.0.3", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": ">=0.4.1", + "class-validator": ">=0.13.2", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", + "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "dependencies": { + "dotenv": "16.4.7", + "dotenv-expand": "12.0.1", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/config/node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@nestjs/core": { + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.11.tgz", + "integrity": "sha512-H9i+zT3RvHi7tDc+lCmWHJ3ustXveABCr+Vcpl96dNOxgmrx4elQSTC4W93Mlav2opfLV+p0UTHY6L+bpUA4zA==", + "hasInstallScript": true, + "dependencies": { + "@nuxt/opencollective": "0.4.1", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "8.3.0", + "tslib": "2.8.1", + "uid": "2.0.2" + }, + "engines": { + "node": ">= 20" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/event-emitter": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-3.0.1.tgz", + "integrity": "sha512-0Ln/x+7xkU6AJFOcQI9tIhUMXVF7D5itiaQGOyJbXtlAfAIt8gzDdJm+Im7cFzKoWkiW5nCXCPh6GSvdQd/3Dw==", + "dependencies": { + "eventemitter2": "6.4.9" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/jwt": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", + "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", + "dependencies": { + "@types/jsonwebtoken": "9.0.10", + "jsonwebtoken": "9.0.3" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/mapped-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz", + "integrity": "sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/platform-express": { + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.11.tgz", + "integrity": "sha512-kyABSskdMRIAMWL0SlbwtDy4yn59RL4HDdwHDz/fxWuv7/53YP8Y2DtV3/sHqY5Er0msMVTZrM38MjqXhYL7gw==", + "dependencies": { + "cors": "2.8.5", + "express": "5.2.1", + "multer": "2.0.2", + "path-to-regexp": "8.3.0", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0" + } + }, + "node_modules/@nestjs/platform-socket.io": { + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.11.tgz", + "integrity": "sha512-0z6pLg9CuTXtz7q2lRZoPOU94DN28OTa39f4cQrlZysKA6QrKM7w7z6xqb4g32qjF+LQHFNRmMJtE/pLrxBaig==", + "dependencies": { + "socket.io": "4.8.3", + "tslib": "2.8.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/websockets": "^11.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/schedule": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.1.0.tgz", + "integrity": "sha512-W25Ydc933Gzb1/oo7+bWzzDiOissE+h/dhIAPugA39b9MuIzBbLybuXpc1AjoQLczO3v0ldmxaffVl87W0uqoQ==", + "dependencies": { + "cron": "4.3.5" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/@nestjs/schematics": { + "version": "11.0.9", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.9.tgz", + "integrity": "sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "19.2.17", + "@angular-devkit/schematics": "19.2.17", + "comment-json": "4.4.1", + "jsonc-parser": "3.3.1", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "typescript": ">=4.8.2" + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.17.tgz", + "integrity": "sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ==", + "dev": true, + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.17.tgz", + "integrity": "sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "19.2.17", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@nestjs/swagger": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.4.tgz", + "integrity": "sha512-7MLqtHfD2qfhZUyg13FyX6liwigtXUL8gHXq7PaBcGo9cu8QWDDT//Fn3qzJx59+Wh+Ly/Zn+prCMpskPI5nrQ==", + "dependencies": { + "@microsoft/tsdoc": "0.16.0", + "@nestjs/mapped-types": "2.1.0", + "js-yaml": "4.1.1", + "lodash": "4.17.21", + "path-to-regexp": "8.3.0", + "swagger-ui-dist": "5.31.0" + }, + "peerDependencies": { + "@fastify/static": "^8.0.0 || ^9.0.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/terminus": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/terminus/-/terminus-11.0.0.tgz", + "integrity": "sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==", + "dependencies": { + "boxen": "5.1.2", + "check-disk-space": "3.4.0" + }, + "peerDependencies": { + "@grpc/grpc-js": "*", + "@grpc/proto-loader": "*", + "@mikro-orm/core": "*", + "@mikro-orm/nestjs": "*", + "@nestjs/axios": "^2.0.0 || ^3.0.0 || ^4.0.0", + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "@nestjs/microservices": "^10.0.0 || ^11.0.0", + "@nestjs/mongoose": "^11.0.0", + "@nestjs/sequelize": "^10.0.0 || ^11.0.0", + "@nestjs/typeorm": "^10.0.0 || ^11.0.0", + "@prisma/client": "*", + "mongoose": "*", + "reflect-metadata": "0.1.x || 0.2.x", + "rxjs": "7.x", + "sequelize": "*", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@grpc/grpc-js": { + "optional": true + }, + "@grpc/proto-loader": { + "optional": true + }, + "@mikro-orm/core": { + "optional": true + }, + "@mikro-orm/nestjs": { + "optional": true + }, + "@nestjs/axios": { + "optional": true + }, + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/mongoose": { + "optional": true + }, + "@nestjs/sequelize": { + "optional": true + }, + "@nestjs/typeorm": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "mongoose": { + "optional": true + }, + "sequelize": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/@nestjs/typeorm": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", + "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "@nestjs/core": "^10.0.0 || ^11.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, + "node_modules/@nestjs/websockets": { + "version": "11.1.11", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-11.1.11.tgz", + "integrity": "sha512-apuP7C/gtMBIYNgA8IWt75GTZeWya5JQCnrLZFcOu+IZt00j9Xd/Bm7hbj/Qr/JVoM/7q6c/4p4oOZtBGx4aeA==", + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.8.1" + }, + "peerDependencies": { + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/platform-socket.io": "^11.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, + "node_modules/@newrelic/fn-inspect": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@newrelic/fn-inspect/-/fn-inspect-4.4.0.tgz", + "integrity": "sha512-VgoXZp3zqP1167XvrA772EHDFUNuYGQh14whFq1d2sE6dC3ZL46tXI9JY0yZdAAeyCERi7mhq7CPx9BYiTOl/A==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "nan": "^2.22.2", + "node-gyp-build": "^4.8.1", + "prebuildify": "^6.0.1" + }, + "engines": { + "node": ">=16.9.1" + } + }, + "node_modules/@newrelic/native-metrics": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@newrelic/native-metrics/-/native-metrics-11.1.0.tgz", + "integrity": "sha512-V+FRgRgv6R8LAnVsix8d1bL5SMmBONURaT/6F//rOgycvU7PfPoVEocacpSRzPFxMI27e46iAf3W5fGosbQ8yQ==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.22.2", + "node-gyp-build": "^4.8.1", + "prebuildify": "^6.0.1" + }, + "engines": { + "node": ">=18", + "npm": ">=6" + } + }, + "node_modules/@newrelic/security-agent": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@newrelic/security-agent/-/security-agent-2.4.4.tgz", + "integrity": "sha512-KQs76SGqTOG/pTuqRGwzy1lNAm96z8gbb5XHl9FsbOY7Kt9Ltbr46AiiovDHhNQbXFTPnHEY7FfmNJ4GK4W5dQ==", + "dependencies": { + "axios": "^1.12.0", + "check-disk-space": "^3.4.0", + "content-type": "^1.0.5", + "cron": "^3.1.7", + "fast-safe-stringify": "^2.1.1", + "find-package-json": "^1.2.0", + "hash.js": "^1.1.7", + "html-entities": "^2.3.6", + "https-proxy-agent": "^7.0.4", + "is-invalid-path": "^1.0.2", + "js-yaml": "^4.1.0", + "jsonschema": "^1.4.1", + "lodash": "^4.17.21", + "log4js": "^6.9.1", + "pretty-bytes": "^5.6.0", + "request-ip": "^3.3.0", + "ringbufferjs": "^2.0.0", + "semver": "^7.5.4", + "unescape": "^1.0.1", + "unescape-js": "^1.1.4", + "uuid": "^9.0.1", + "ws": "^8.17.1" + }, + "engines": { + "node": ">=18", + "npm": ">=6.0.0" + } + }, + "node_modules/@newrelic/security-agent/node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, + "node_modules/@newrelic/security-agent/node_modules/cron": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.5.0.tgz", + "integrity": "sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==", + "dependencies": { + "@types/luxon": "~3.4.0", + "luxon": "~3.5.0" + } + }, + "node_modules/@newrelic/security-agent/node_modules/luxon": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", + "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/@newrelic/security-agent/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nuxt/opencollective": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz", + "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==", + "dependencies": { + "consola": "^3.2.3" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": "^14.18.0 || >=16.10.0", + "npm": ">=5.10.0" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "optional": true + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.201.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.201.1.tgz", + "integrity": "sha512-IxcFDP1IGMDemVFG2by/AMK+/o6EuBQ8idUq3xZ6MxgQGeumYZuX5OwR0h9HuvcUc/JPjQGfU5OHKIKYDJcXeA==", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http": { + "version": "0.201.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.201.1.tgz", + "integrity": "sha512-LMRVg2yTev28L51RLLUK3gY0avMa1RVBq7IkYNtXDBxJRcd0TGGq/0rqfk7Y4UIM9NCJhDIUFHeGg8NpSgSWcw==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-exporter-base": "0.201.1", + "@opentelemetry/otlp-transformer": "0.201.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", + "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", + "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", + "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { + "version": "0.201.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.201.1.tgz", + "integrity": "sha512-9ie2jcaUQZdIoe6B02r0rF4Gz+JsZ9mev/2pYou1N0woOUkFM8xwO6BAlORnrFVslqF/XO5WG3q5FsTbuC5iiw==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/exporter-metrics-otlp-http": "0.201.1", + "@opentelemetry/otlp-exporter-base": "0.201.1", + "@opentelemetry/otlp-transformer": "0.201.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-metrics": "2.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", + "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", + "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", + "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.201.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.201.1.tgz", + "integrity": "sha512-FiS/mIWmZXyRxYGyXPHY+I/4+XrYVTD7Fz/zwOHkVPQsA1JTakAOP9fAi6trXMio0dIpzvQujLNiBqGM7ExrQw==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/otlp-transformer": "0.201.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base/node_modules/@opentelemetry/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", + "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.201.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.201.1.tgz", + "integrity": "sha512-+q/8Yuhtu9QxCcjEAXEO8fXLjlSnrnVwfzi9jiWaMAppQp69MoagHHomQj02V2WnGjvBod5ajgkbK4IoWab50A==", + "dependencies": { + "@opentelemetry/api-logs": "0.201.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/sdk-logs": "0.201.1", + "@opentelemetry/sdk-metrics": "2.0.1", + "@opentelemetry/sdk-trace-base": "2.0.1", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", + "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", + "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", + "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", + "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.201.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.201.1.tgz", + "integrity": "sha512-Ug8gtpssUNUnfpotB9ZhnSsPSGDu+7LngTMgKl31mmVJwLAKyl6jC8diZrMcGkSgBh0o5dbg9puvLyR25buZfw==", + "dependencies": { + "@opentelemetry/api-logs": "0.201.1", + "@opentelemetry/core": "2.0.1", + "@opentelemetry/resources": "2.0.1" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/core": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", + "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", + "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", + "dependencies": { + "@opentelemetry/core": "2.0.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.38.0.tgz", + "integrity": "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.16.2.tgz", + "integrity": "sha512-lVJbvydLQIDZHKUb6Zs9Rq80QVTQ9xdCQE30eC9/cjg4wsMoEOg65QZPymUAIVJotpUAWJD0XYcwE7ugfxx5kQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.16.2.tgz", + "integrity": "sha512-fEk+g/g2rJ6LnBVPqeLcx+/alWZ/Db1UlXG+ZVivip0NdrnOzRL48PAmnxTMGOrLwsH1UDJkwY3wOjrrQltCqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.16.2.tgz", + "integrity": "sha512-Pkbp1qi7kdUX6k3Fk1PvAg6p7ruwaWKg1AhOlDgrg2vLXjtv9ZHo7IAQN6kLj0W771dPJZWqNxoqTPacp2oYWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.16.2.tgz", + "integrity": "sha512-FYCGcU1iSoPkADGLfQbuj0HWzS+0ItjDCt9PKtu2Hzy6T0dxO4Y1enKeCOxCweOlmLEkSxUlW5UPT4wvT3LnAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.16.2.tgz", + "integrity": "sha512-1zHCoK6fMcBjE54P2EG/z70rTjcRxvyKfvk4E/QVrWLxNahuGDFZIxoEoo4kGnnEcmPj41F0c2PkrQbqlpja5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.16.2.tgz", + "integrity": "sha512-+ucLYz8EO5FDp6kZ4o1uDmhoP+M98ysqiUW4hI3NmfiOJQWLrAzQjqaTdPfIOzlCXBU9IHp5Cgxu6wPjVb8dbA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.16.2.tgz", + "integrity": "sha512-qq+TpNXyw1odDgoONRpMLzH4hzhwnEw55398dL8rhKGvvYbio71WrJ00jE+hGlEi7H1Gkl11KoPJRaPlRAVGPw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.16.2.tgz", + "integrity": "sha512-xlMh4gNtplNQEwuF5icm69udC7un0WyzT5ywOeHrPMEsghKnLjXok2wZgAA7ocTm9+JsI+nVXIQa5XO1x+HPQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.16.2.tgz", + "integrity": "sha512-OZs33QTMi0xmHv/4P0+RAKXJTBk7UcMH5tpTaCytWRXls/DGaJ48jOHmriQGK2YwUqXl+oneuNyPOUO0obJ+Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.16.2.tgz", + "integrity": "sha512-UVyuhaV32dJGtF6fDofOcBstg9JwB2Jfnjfb8jGlu3xcG+TsubHRhuTwQ6JZ1sColNT1nMxBiu7zdKUEZi1kwg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.16.2.tgz", + "integrity": "sha512-YZZS0yv2q5nE1uL/Fk4Y7m9018DSEmDNSG8oJzy1TJjA1jx5HL52hEPxi98XhU6OYhSO/vC1jdkJeE8TIHugug==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.16.2.tgz", + "integrity": "sha512-9VYuypwtx4kt1lUcwJAH4dPmgJySh4/KxtAPdRoX2BTaZxVm/yEXHq0mnl/8SEarjzMvXKbf7Cm6UBgptm3DZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.16.2.tgz", + "integrity": "sha512-3gbwQ+xlL5gpyzgSDdC8B4qIM4mZaPDLaFOi3c/GV7CqIdVJc5EZXW4V3T6xwtPBOpXPXfqQLbhTnUD4SqwJtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.16.2.tgz", + "integrity": "sha512-m0WcK0j54tSwWa+hQaJMScZdWneqE7xixp/vpFqlkbhuKW9dRHykPAFvSYg1YJ3MJgu9ZzVNpYHhPKJiEQq57Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.16.2.tgz", + "integrity": "sha512-ZjUm3w96P2t47nWywGwj1A2mAVBI/8IoS7XHhcogWCfXnEI3M6NPIRQPYAZW4s5/u3u6w1uPtgOwffj2XIOb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.16.2.tgz", + "integrity": "sha512-OFVQ2x3VenTp13nIl6HcQ/7dmhFmM9dg2EjKfHcOtYfrVLQdNR6THFU7GkMdmc8DdY1zLUeilHwBIsyxv5hkwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.16.2.tgz", + "integrity": "sha512-+O1sY3RrGyA2AqDnd3yaDCsqZqCblSTEpY7TbbaOaw0X7iIbGjjRLdrQk9StG3QSiZuBy9FdFwotIiSXtwvbAQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.16.2.tgz", + "integrity": "sha512-jMrMJL+fkx6xoSMFPOeyQ1ctTFjavWPOSZEKUY5PebDwQmC9cqEr4LhdTnGsOtFrWYLXlEU4xWeMdBoc/XKkOA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.16.2.tgz", + "integrity": "sha512-tl0xDA5dcQplG2yg2ZhgVT578dhRFafaCfyqMEAXq8KNpor85nJ53C3PLpfxD2NKzPioFgWEexNsjqRi+kW2Mg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.16.2.tgz", + "integrity": "sha512-M7z0xjYQq1HdJk2DxTSLMvRMyBSI4wn4FXGcVQBsbAihgXevAReqwMdb593nmCK/OiFwSNcOaGIzUvzyzQ+95w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@prisma/prisma-fmt-wasm": { + "version": "4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085", + "resolved": "https://registry.npmjs.org/@prisma/prisma-fmt-wasm/-/prisma-fmt-wasm-4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085.tgz", + "integrity": "sha512-zYz3rFwPB82mVlHGknAPdnSY/a308dhPOblxQLcZgZTDRtDXOE1MgxoRAys+jekwR4/bm3+rZDPs1xsFMsPZig==", + "optional": true + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@smithy/abort-controller": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz", + "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz", + "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz", + "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==", + "dependencies": { + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", + "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.20.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.1.tgz", + "integrity": "sha512-wOboSEdQ85dbKAJ0zL+wQ6b0HTSBRhtGa0PYKysQXkRg+vK0tdCRRVruiFM2QMprkOQwSYOnwF4og96PAaEGag==", + "dependencies": { + "@smithy/middleware-serde": "^4.2.8", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz", + "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-codec": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.7.tgz", + "integrity": "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.11.0", + "@smithy/util-hex-encoding": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.7.tgz", + "integrity": "sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.7.tgz", + "integrity": "sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.7.tgz", + "integrity": "sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.7.tgz", + "integrity": "sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g==", + "dependencies": { + "@smithy/eventstream-codec": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz", + "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.8.tgz", + "integrity": "sha512-07InZontqsM1ggTCPSRgI7d8DirqRrnpL7nIACT4PW0AWrgDiHhjGZzbAE5UtRSiU0NISGUYe7/rri9ZeWyDpw==", + "dependencies": { + "@smithy/chunked-blob-reader": "^5.2.0", + "@smithy/chunked-blob-reader-native": "^4.2.1", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz", + "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.7.tgz", + "integrity": "sha512-ZQVoAwNYnFMIbd4DUc517HuwNelJUY6YOzwqrbcAgCnVn+79/OK7UjwA93SPpdTOpKDVkLIzavWm/Ck7SmnDPQ==", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz", + "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.7.tgz", + "integrity": "sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw==", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz", + "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.2.tgz", + "integrity": "sha512-mqpAdux0BNmZu/SqkFhQEnod4fX23xxTvU2LUpmKp0JpSI+kPYCiHJMmzREr8yxbNxKL2/DU1UZm9i++ayU+2g==", + "dependencies": { + "@smithy/core": "^3.20.1", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-middleware": "^4.2.7", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.4.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.18.tgz", + "integrity": "sha512-E5hulijA59nBk/zvcwVMaS7FG7Y4l6hWA9vrW018r+8kiZef4/ETQaPI4oY+3zsy9f6KqDv3c4VKtO4DwwgpCg==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz", + "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==", + "dependencies": { + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz", + "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", + "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz", + "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==", + "dependencies": { + "@smithy/abort-controller": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz", + "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz", + "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-builder": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz", + "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==", + "dependencies": { + "@smithy/types": "^4.11.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/querystring-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz", + "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/service-error-classification": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz", + "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==", + "dependencies": { + "@smithy/types": "^4.11.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", + "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz", + "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.3.tgz", + "integrity": "sha512-EfECiO/0fAfb590LBnUe7rI5ux7XfquQ8LBzTe7gxw0j9QW/q8UT/EHWHlxV/+jhQ3+Ssga9uUYXCQgImGMbNg==", + "dependencies": { + "@smithy/core": "^3.20.1", + "@smithy/middleware-endpoint": "^4.4.2", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz", + "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz", + "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==", + "dependencies": { + "@smithy/querystring-parser": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.17", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.17.tgz", + "integrity": "sha512-dwN4GmivYF1QphnP3xJESXKtHvkkvKHSZI8GrSKMVoENVSKW2cFPRYC4ZgstYjUHdR3zwaDkIaTDIp26JuY7Cw==", + "dependencies": { + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.20", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.20.tgz", + "integrity": "sha512-VD/I4AEhF1lpB3B//pmOIMBNLMrtdMXwy9yCOfa2QkJGDr63vH3RqPbSAKzoGMov3iryCxTXCxSsyGmEB8PDpg==", + "dependencies": { + "@smithy/config-resolver": "^4.4.5", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.3", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", + "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", + "dependencies": { + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", + "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", + "dependencies": { + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz", + "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==", + "dependencies": { + "@smithy/service-error-classification": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz", + "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/types": "^4.11.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.7.tgz", + "integrity": "sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw==", + "dependencies": { + "@smithy/abort-controller": "^4.2.7", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@so-ric/colorspace": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz", + "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==", + "dependencies": { + "color": "^5.0.2", + "text-hex": "1.0.x" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + }, + "node_modules/@swc/cli": { + "version": "0.7.9", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.9.tgz", + "integrity": "sha512-AFQu3ZZ9IcdClTknxbug08S9ed/q8F3aYkO5NoZ+6IjQ5UEo1s2HN1GRKNvUslYx2EoVYxd+6xGcp6C7wwtxyQ==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@xhmikosr/bin-wrapper": "^13.0.5", + "commander": "^8.3.0", + "minimatch": "^9.0.3", + "piscina": "^4.3.1", + "semver": "^7.3.8", + "slash": "3.0.0", + "source-map": "^0.7.3", + "tinyglobby": "^0.2.13" + }, + "bin": { + "spack": "bin/spack.js", + "swc": "bin/swc.js", + "swcx": "bin/swcx.js" + }, + "engines": { + "node": ">= 16.14.0" + }, + "peerDependencies": { + "@swc/core": "^1.2.66", + "chokidar": "^4.0.1" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@swc/cli/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@swc/core": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.8.tgz", + "integrity": "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.25" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.8", + "@swc/core-darwin-x64": "1.15.8", + "@swc/core-linux-arm-gnueabihf": "1.15.8", + "@swc/core-linux-arm64-gnu": "1.15.8", + "@swc/core-linux-arm64-musl": "1.15.8", + "@swc/core-linux-x64-gnu": "1.15.8", + "@swc/core-linux-x64-musl": "1.15.8", + "@swc/core-win32-arm64-msvc": "1.15.8", + "@swc/core-win32-ia32-msvc": "1.15.8", + "@swc/core-win32-x64-msvc": "1.15.8" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.8.tgz", + "integrity": "sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.8.tgz", + "integrity": "sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.8.tgz", + "integrity": "sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.8.tgz", + "integrity": "sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.8.tgz", + "integrity": "sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.8.tgz", + "integrity": "sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.8.tgz", + "integrity": "sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.8.tgz", + "integrity": "sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.8.tgz", + "integrity": "sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.8.tgz", + "integrity": "sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", + "dependencies": { + "debug": "^4.4.3", + "token-types": "^6.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "node_modules/@total-typescript/ts-reset": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.6.1.tgz", + "integrity": "sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==", + "dev": true + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "devOptional": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", + "optional": true + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.0.tgz", + "integrity": "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-tfmcyHn1Pp9YHAO5r40+UuZUPAZbUEgqTel3EuEKpmF9hPkXgR4l41853raliXnb4gwyPNoQOfvgGGlHN5WSog==", + "deprecated": "This is a stub types definition. form-data provides its own type definitions, so you do not need this installed.", + "dependencies": { + "form-data": "*" + } + }, + "node_modules/@types/heapdump": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@types/heapdump/-/heapdump-0.3.4.tgz", + "integrity": "sha512-gTGn7W7i58F1yzvLZ5+hRE7M/VqgEzgccERJ9ZThC6qor5R8wo3oP7iiHvwgHIZzoqPF22ngu8mBagcqPWMVTg==", + "dev": true + }, + "node_modules/@types/html-to-text": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/html-to-text/-/html-to-text-9.0.4.tgz", + "integrity": "sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==", + "dev": true + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true + }, + "node_modules/@types/imap": { + "version": "0.8.43", + "resolved": "https://registry.npmjs.org/@types/imap/-/imap-0.8.43.tgz", + "integrity": "sha512-POPoqrDax9mxM2N4ITZYCWaFtg1ORVfzJe4S7xwSh9aHawdEb7FwWTJYiAhzIvWp7DM+6BajnzYOwZ1BUrqtow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/imap-simple": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/@types/imap-simple/-/imap-simple-4.2.10.tgz", + "integrity": "sha512-ZLITvpfY4+oPtH3z7axYRGJqOB/9M6cb4ye8eEydOlgXZuJFX0z9l+hxzXgrFDnWKwxH39ZPCT4bGIFUKSJ52g==", + "dev": true, + "dependencies": { + "@types/imap": "*", + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/luxon": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz", + "integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==" + }, + "node_modules/@types/mailparser": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@types/mailparser/-/mailparser-3.4.6.tgz", + "integrity": "sha512-wVV3cnIKzxTffaPH8iRnddX1zahbYB1ZEoAxyhoBo3TBCBuK6nZ8M8JYO/RhsCuuBVOw/DEN/t/ENbruwlxn6Q==", + "dev": true, + "dependencies": { + "@types/node": "*", + "iconv-lite": "^0.6.3" + } + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true + }, + "node_modules/@types/mjml": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz", + "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==", + "optional": true, + "dependencies": { + "@types/mjml-core": "*" + } + }, + "node_modules/@types/mjml-core": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.15.2.tgz", + "integrity": "sha512-Q7SxFXgoX979HP57DEVsRI50TV8x1V4lfCA4Up9AvfINDM5oD/X9ARgfoyX1qS987JCnDLv85JjkqAjt3hZSiQ==", + "optional": true + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "node_modules/@types/multer": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", + "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/nodemailer": { + "version": "6.4.21", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.21.tgz", + "integrity": "sha512-Eix+sb/Nj28MNnWvO2X1OLrk5vuD4C9SMnb2Vf4itWnxphYeSceqkFX7IdmxTzn+dvmnNz7paMbg4Uc60wSfJg==", + "dev": true, + "dependencies": { + "@aws-sdk/client-ses": "^3.731.1", + "@types/node": "*" + } + }, + "node_modules/@types/pug": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", + "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", + "optional": true + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true + }, + "node_modules/@types/quoted-printable": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/quoted-printable/-/quoted-printable-1.0.2.tgz", + "integrity": "sha512-3B28oB1rRaZNb3N5dlxysm8lH1ujzvReDuYBiIO4jvpTIg9ksrILCNgPxSGVyTWE/qwuxzgHaVehwMK3CVqAtA==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==" + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz", + "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/type-utils": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.52.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", + "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", + "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.52.0", + "@typescript-eslint/types": "^8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", + "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", + "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz", + "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", + "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", + "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/project-service": "8.52.0", + "@typescript-eslint/tsconfig-utils": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz", + "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", + "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@tyriar/fibonacci-heap": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@tyriar/fibonacci-heap/-/fibonacci-heap-2.0.9.tgz", + "integrity": "sha512-bYuSNomfn4hu2tPiDN+JZtnzCpSpbJ/PNeulmocDy3xN2X5OkJL65zo6rPZp65cPPhLF9vfT/dgE+RtFRCSxOA==" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + }, + "node_modules/@voximplant/apiclient-nodejs": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@voximplant/apiclient-nodejs/-/apiclient-nodejs-4.5.0.tgz", + "integrity": "sha512-YceoIpEAVGyVL2rVncdCXCgIg3UkkclIU5dyCcIlmZpdTkVs1c/36WKEuALnCzZeSVJiWEBn+wGakyvs4cCcNw==", + "dependencies": { + "axios": "0.21.4", + "form-data": "2.5.1", + "jsonwebtoken": "9.0.2" + } + }, + "node_modules/@voximplant/apiclient-nodejs/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/@voximplant/apiclient-nodejs/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@voximplant/apiclient-nodejs/node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/@voximplant/apiclient-nodejs/node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@voximplant/apiclient-nodejs/node_modules/jws": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "dependencies": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@voximplant/apiclient-nodejs/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@voximplant/apiclient-nodejs/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xhmikosr/archive-type": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz", + "integrity": "sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==", + "dev": true, + "dependencies": { + "file-type": "^20.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/archive-type/node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@xhmikosr/archive-type/node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dev": true, + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/bin-check": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.1.0.tgz", + "integrity": "sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==", + "dev": true, + "dependencies": { + "execa": "^5.1.1", + "isexe": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/bin-wrapper": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.2.0.tgz", + "integrity": "sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==", + "dev": true, + "dependencies": { + "@xhmikosr/bin-check": "^7.1.0", + "@xhmikosr/downloader": "^15.2.0", + "@xhmikosr/os-filter-obj": "^3.0.0", + "bin-version-check": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.2.0.tgz", + "integrity": "sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.1.0", + "@xhmikosr/decompress-tarbz2": "^8.1.0", + "@xhmikosr/decompress-targz": "^8.1.0", + "@xhmikosr/decompress-unzip": "^7.1.0", + "graceful-fs": "^4.2.11", + "strip-dirs": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tar": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.1.0.tgz", + "integrity": "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==", + "dev": true, + "dependencies": { + "file-type": "^20.5.0", + "is-stream": "^2.0.1", + "tar-stream": "^3.1.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tar/node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@xhmikosr/decompress-tar/node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dev": true, + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz", + "integrity": "sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^20.5.0", + "is-stream": "^2.0.1", + "seek-bzip": "^2.0.0", + "unbzip2-stream": "^1.4.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2/node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2/node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dev": true, + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/decompress-targz": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.1.0.tgz", + "integrity": "sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==", + "dev": true, + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^20.5.0", + "is-stream": "^2.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-targz/node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@xhmikosr/decompress-targz/node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dev": true, + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/decompress-unzip": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.1.0.tgz", + "integrity": "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==", + "dev": true, + "dependencies": { + "file-type": "^20.5.0", + "get-stream": "^6.0.1", + "yauzl": "^3.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-unzip/node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@xhmikosr/decompress-unzip/node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dev": true, + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/downloader": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.2.0.tgz", + "integrity": "sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==", + "dev": true, + "dependencies": { + "@xhmikosr/archive-type": "^7.1.0", + "@xhmikosr/decompress": "^10.2.0", + "content-disposition": "^0.5.4", + "defaults": "^2.0.2", + "ext-name": "^5.0.0", + "file-type": "^20.5.0", + "filenamify": "^6.0.0", + "get-stream": "^6.0.1", + "got": "^13.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dev": true, + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/normalize-url": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", + "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@xhmikosr/downloader/node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@xhmikosr/os-filter-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", + "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", + "dev": true, + "dependencies": { + "arch": "^3.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz", + "integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==", + "engines": { + "node": ">=14.6" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@zone-eu/mailsplit": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.8.tgz", + "integrity": "sha512-eEyACj4JZ7sjzRvy26QhLgKEMWwQbsw1+QZnlLX+/gihcNH07lVPOcnwf5U6UAL7gkc//J3jVd76o/WS+taUiA==", + "dependencies": { + "libbase64": "1.3.0", + "libmime": "5.3.7", + "libqp": "2.1.1" + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "optional": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "devOptional": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/alce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/alce/-/alce-1.2.0.tgz", + "integrity": "sha512-XppPf2S42nO2WhvKzlwzlfcApcXHzjlod30pKmcWjRgLOtqoe5DMuqdiYoM6AgyXksc6A6pV4v1L/WW217e57w==", + "optional": true, + "dependencies": { + "esprima": "^1.2.0", + "estraverse": "^1.5.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/alce/node_modules/esprima": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==", + "optional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alce/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/angular-expressions": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/angular-expressions/-/angular-expressions-1.5.1.tgz", + "integrity": "sha512-Ukcyuye0eb15zuvMFR7Kziuf7gV0gYAZXMevNI40rXF8f9k+/yk8+r5edFWRFOwnqSvAEFoP2bKNXaXgYa+Ayw==" + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "engines": { + "node": ">=14" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "optional": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/arch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", + "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/archiver/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "optional": true + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "optional": true + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "optional": true, + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.12.tgz", + "integrity": "sha512-Mij6Lij93pTAIsSYy5cyBQ975Qh9uLEc5rwGTpomiZeXZL9yIS6uORJakb3ScHgfs0serMMfIbXzokPMuEiRyw==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "engines": { + "node": "*" + } + }, + "node_modules/bin-version": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", + "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "find-versions": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bin-version-check": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", + "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", + "dev": true, + "dependencies": { + "bin-version": "^6.0.0", + "semver": "^7.5.3", + "semver-truncate": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==" + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "optional": true + }, + "node_modules/bowser": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==" + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "devOptional": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "optional": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001763", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001763.tgz", + "integrity": "sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "optional": true, + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "dev": true + }, + "node_modules/check-disk-space": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.4.0.tgz", + "integrity": "sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==", + "engines": { + "node": ">=16" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "optional": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==" + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz", + "integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.20" + } + }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "optional": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", + "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==", + "dependencies": { + "color-convert": "^3.1.3", + "color-string": "^2.1.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", + "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/color-string/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz", + "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-json": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.4.1.tgz", + "integrity": "sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==", + "dev": true, + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/complex.js": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.4.3.tgz", + "integrity": "sha512-UrQVSUur14tNX6tiP4y8T4w4FeJAX3bi2cIv0pu/DTLFNxoq7z2Yh83Vfzztj6Px3X/lubqQ9IrPp7Bpn6p4MQ==", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "optional": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/console-stamp": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/console-stamp/-/console-stamp-3.1.2.tgz", + "integrity": "sha512-ab66x3NxOTxPuq71dI6gXEiw2X6ql4Le5gZz0bm7FW3FSCB00eztra/oQUuCoCGlsyKOxtULnHwphzMrRtzMBg==", + "dependencies": { + "chalk": "^4.1.2", + "dateformat": "^4.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/console-stamp/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/console-stamp/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/console-stamp/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/console-stamp/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/console-stamp/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/console-stamp/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true + }, + "node_modules/cron": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.3.5.tgz", + "integrity": "sha512-hKPP7fq1+OfyCqoePkKfVq7tNAdFwiQORr4lZUHwrf0tebC65fYEeWgOrXOL6prn1/fegGOdTfrM6e34PJfksg==", + "dependencies": { + "@types/luxon": "~3.7.0", + "luxon": "~3.7.0" + }, + "engines": { + "node": ">=18.x" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "optional": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/date-fns-tz": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.2.0.tgz", + "integrity": "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ==", + "peerDependencies": { + "date-fns": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz", + "integrity": "sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "optional": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/display-notification": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/display-notification/-/display-notification-2.0.0.tgz", + "integrity": "sha512-TdmtlAcdqy1NU+j7zlkDdMnCL878zriLaBmoD9quOoq1ySSSGv03l0hXK5CvIFZlIfFI/hizqdQuW+Num7xuhw==", + "optional": true, + "dependencies": { + "escape-string-applescript": "^1.0.0", + "run-applescript": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "optional": true + }, + "node_modules/docxtemplater": { + "version": "3.67.6", + "resolved": "https://registry.npmjs.org/docxtemplater/-/docxtemplater-3.67.6.tgz", + "integrity": "sha512-IvdTz9druTlQrsB0zlqvAqrImEydFgtvHp0uUnx+hQ9W4hmBsorrJIBJKCfkhK6QRz/jpJnA6BaTd6rEK6/Usw==", + "dependencies": { + "@xmldom/xmldom": "^0.9.8" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", + "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "optional": true, + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "optional": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-japanese": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.2.0.tgz", + "integrity": "sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==", + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/err-code": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", + "integrity": "sha512-CJAN+O0/yA1CKfRn9SXOGctSpEM7DCon/r/5r2eXFMY2zCCJBasFhcM5I+1kh3Ap11FsQCX+vGHceNPvpWKhoA==" + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", + "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-latex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", + "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" + }, + "node_modules/escape-string-applescript": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/escape-string-applescript/-/escape-string-applescript-1.0.0.tgz", + "integrity": "sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.4.tgz", + "integrity": "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.11.7" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/extend-object/-/extend-object-1.0.0.tgz", + "integrity": "sha512-0dHDIXC7y7LDmCh/lp1oYkmv73K25AMugQI07r8eFopkW6f7Ufn1q+ETMsJjnV9Am14SlElkqy3O92r6xEaxPw==", + "optional": true + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-type": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.2.0.tgz", + "integrity": "sha512-vCYBgFOrJQLoTzDyAXAL/RFfKnXXpUYt4+tipVy26nJJhT7ftgGETf2tAQF59EEL61i3MrorV/PG6tf7LJK7eg==", + "dependencies": { + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "optional": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/filename-reserved-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", + "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/filenamify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", + "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "devOptional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-package-json": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/find-package-json/-/find-package-json-1.2.0.tgz", + "integrity": "sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", + "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", + "dev": true, + "dependencies": { + "semver-regex": "^4.0.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fixpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fixpack/-/fixpack-4.0.0.tgz", + "integrity": "sha512-5SM1+H2CcuJ3gGEwTiVo/+nd/hYpNj9Ch3iMDOQ58ndY+VGQ2QdvaUTkd3otjZvYnd/8LF/HkJ5cx7PBq0orCQ==", + "optional": true, + "dependencies": { + "alce": "1.2.0", + "chalk": "^3.0.0", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "extend-object": "^1.0.0", + "rc": "^1.2.8" + }, + "bin": { + "fixpack": "bin/fixpack" + } + }, + "node_modules/fixpack/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fixpack/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fixpack/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fixpack/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, + "node_modules/fixpack/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/fixpack/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "optional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^4.0.1", + "cosmiconfig": "^8.2.0", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", + "dev": true, + "dependencies": { + "fd-package-json": "^2.0.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.1.0.tgz", + "integrity": "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/fstream/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fstream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fstream/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/fstream/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/generate-password": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/generate-password/-/generate-password-1.7.1.tgz", + "integrity": "sha512-9bVYY+16m7W7GczRBDqXE+VVuCX+bWNrfYKC/2p2JkZukFb2sKxT6E3zZ3mJGz7GMe5iRK0A/WawSL3jQfJuNQ==" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "149.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-149.0.0.tgz", + "integrity": "sha512-LTMc/njwYy7KTeaUHDcQt7KxftHyghdzm2XzbL46PRLd1AXB09utT9Po2ZJn2X0EApz0pE2T5x5A9zM8iue6zw==", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/heapdump": { + "version": "0.3.15", + "resolved": "https://registry.npmjs.org/heapdump/-/heapdump-0.3.15.tgz", + "integrity": "sha512-n8aSFscI9r3gfhOcAECAtXFaQ1uy4QSke6bnaL+iymYZ/dWs9cqDqHM+rALfsHUwukUbxsdlECZ0pKmJdQ/4OA==", + "hasInstallScript": true, + "dependencies": { + "nan": "^2.13.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "optional": true, + "dependencies": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/html-minifier/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "optional": true + }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/imap": { + "version": "0.8.19", + "resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz", + "integrity": "sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==", + "dependencies": { + "readable-stream": "1.1.x", + "utf7": ">=1.0.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/imap-simple": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/imap-simple/-/imap-simple-5.1.0.tgz", + "integrity": "sha512-FLZm1v38C5ekN46l/9X5gBRNMQNVc5TSLYQ3Hsq3xBLvKwt1i5fcuShyth8MYMPuvId1R46oaPNrH92hFGHr/g==", + "dependencies": { + "iconv-lite": "~0.4.13", + "imap": "^0.8.18", + "nodeify": "^1.0.0", + "quoted-printable": "^1.0.0", + "utf8": "^2.1.1", + "uuencode": "0.0.4" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imap-simple/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/imap/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/imap/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/imap/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/imapflow": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.2.4.tgz", + "integrity": "sha512-X/eRQeje33uZycfopjwoQKKbya+bBIaqpviOFxhPOD24DXU2hMfXwYe9e8j1+ADwFVgTvKq4G2/ljjZK3Y8mvg==", + "dependencies": { + "@zone-eu/mailsplit": "5.4.8", + "encoding-japanese": "2.2.0", + "iconv-lite": "0.7.1", + "libbase64": "1.3.0", + "libmime": "5.3.7", + "libqp": "2.1.1", + "nodemailer": "7.0.12", + "pino": "10.1.0", + "socks": "2.8.7" + } + }, + "node_modules/imapflow/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-in-the-middle": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", + "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", + "dependencies": { + "acorn": "^8.14.0", + "acorn-import-attributes": "^1.9.5", + "cjs-module-lexer": "^1.2.2", + "module-details-from-path": "^1.0.3" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true + }, + "node_modules/inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + } + }, + "node_modules/ioredis": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.0.tgz", + "integrity": "sha512-T3VieIilNumOJCXI9SDgo4NnF6sZkd6XcmPi6qWtw4xqbt8nNz/ZVNiIH1L9puMTSHZh1mUWA4xKa2nWPF4NwQ==", + "dependencies": { + "@ioredis/commands": "1.5.0", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "optional": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "optional": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "optional": true + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "optional": true, + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-invalid-path": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-1.0.2.tgz", + "integrity": "sha512-6KLcFrPCEP3AFXMfnWrIFkZpYNBVzZAoBJJDEZKtI3LXkaDjM3uFMJQjxiizUuZTZ9Oh9FNv/soXbx5TcpaDmA==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-1.0.1.tgz", + "integrity": "sha512-mjWH5XxnhMA8cFnDchr6qRP9S/kLntKuEfIYku+PaN1CnS8v+OG9O/BKpRCVRJvpIkgAZm0Pf5Is3iSSOILlcg==" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "optional": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "optional": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "optional": true, + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "optional": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "optional": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "optional": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "optional": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonschema": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", + "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", + "engines": { + "node": "*" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "optional": true, + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/jstransformer/node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "optional": true + }, + "node_modules/jstransformer/node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "optional": true, + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/juice": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/juice/-/juice-10.0.1.tgz", + "integrity": "sha512-ZhJT1soxJCkOiO55/mz8yeBKTAJhRzX9WBO+16ZTqNTONnnVlUPyVBIzQ7lDRjaBdTbid+bAnyIon/GM3yp4cA==", + "optional": true, + "dependencies": { + "cheerio": "1.0.0-rc.12", + "commander": "^6.1.0", + "mensch": "^0.3.4", + "slick": "^1.12.2", + "web-resource-inliner": "^6.0.1" + }, + "bin": { + "juice": "bin/juice" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/juice/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/knip": { + "version": "5.80.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.80.0.tgz", + "integrity": "sha512-K/Ga2f/SHEUXXriVdaw2GfeIUJ5muwdqusHGkCtaG/1qeMmQJiuwZj9KnPxaDbnYPAu8RWjYYh8Nyb+qlJ3d8A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "dependencies": { + "@nodelib/fs.walk": "^1.2.3", + "fast-glob": "^3.3.3", + "formatly": "^0.3.0", + "jiti": "^2.6.0", + "js-yaml": "^4.1.1", + "minimist": "^1.2.8", + "oxc-resolver": "^11.15.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.1", + "smol-toml": "^1.5.2", + "strip-json-comments": "5.0.3", + "zod": "^4.1.11" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "typescript": ">=5.0.4 <7" + } + }, + "node_modules/knip/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libbase64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz", + "integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==" + }, + "node_modules/libmime": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.7.tgz", + "integrity": "sha512-FlDb3Wtha8P01kTL3P9M+ZDNDWPKPmKHWaU/cG/lg5pfuAwdflVpZE+wm9m7pKmC5ww6s+zTxBKS1p6yl3KpSw==", + "dependencies": { + "encoding-japanese": "2.2.0", + "iconv-lite": "0.6.3", + "libbase64": "1.3.0", + "libqp": "2.1.1" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.33", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.33.tgz", + "integrity": "sha512-r9kw4OA6oDO4dPXkOrXTkArQAafIKAU71hChInV4FxZ69dxCfbwQGDPzqR5/vea94wU705/3AZroEbSoeVWrQw==" + }, + "node_modules/libqp": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.1.tgz", + "integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==" + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/liquidjs": { + "version": "10.24.0", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.24.0.tgz", + "integrity": "sha512-TAUNAdgwaAXjjcUFuYVJm9kOVH7zc0mTKxsG9t9Lu4qdWjB2BEblyVIYpjWcmJLMGgiYqnGNJjpNMHx0gp/46A==", + "optional": true, + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/liquidjs/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" + }, + "node_modules/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "engines": { + "node": ">=13.2.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "dev": true, + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "node_modules/lossless-json": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.3.0.tgz", + "integrity": "sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==" + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "optional": true + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "engines": { + "node": ">=12" + } + }, + "node_modules/lvovich": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lvovich/-/lvovich-2.1.0.tgz", + "integrity": "sha512-+dfkF0+5MKgNU/IuEtfwsodeD1snhR1DDB57KeLdH+M7TEI7OZzd4qbrAFQSzAPYNdC8mcLb+OQqvFijVrGStg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/mailparser": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.1.tgz", + "integrity": "sha512-6vHZcco3fWsDMkf4Vz9iAfxvwrKNGbHx0dV1RKVphQ/zaNY34Buc7D37LSa09jeSeybWzYcTPjhiZFxzVRJedA==", + "dependencies": { + "@zone-eu/mailsplit": "5.4.8", + "encoding-japanese": "2.2.0", + "he": "1.2.0", + "html-to-text": "9.0.5", + "iconv-lite": "0.7.0", + "libmime": "5.3.7", + "linkify-it": "5.0.0", + "nodemailer": "7.0.11", + "punycode.js": "2.3.1", + "tlds": "1.261.0" + } + }, + "node_modules/mailparser/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mailparser/node_modules/nodemailer": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", + "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "optional": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mathjs": { + "version": "14.9.1", + "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-14.9.1.tgz", + "integrity": "sha512-xhqv8Xjf+caWG3WlaPekg4v8QFOR3D5+8ycfcjMcPcnCNDgAONQLaLfyGgrggJrcHx2yUGCpACRpiD4GmXwX+Q==", + "dependencies": { + "@babel/runtime": "^7.26.10", + "complex.js": "^2.2.5", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^5.2.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/mensch": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", + "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==", + "optional": true + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mjml": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.18.0.tgz", + "integrity": "sha512-rQM4aqFRrNvV1k733e8hJSopBjZvoSdBpRYzNTMAN+As0jqJsO5eN0wTT2IFtfe4PREzzu5b06RkPiUQdd0IIg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "mjml-cli": "4.18.0", + "mjml-core": "4.18.0", + "mjml-migrate": "4.18.0", + "mjml-preset-core": "4.18.0", + "mjml-validator": "4.18.0" + }, + "bin": { + "mjml": "bin/mjml" + } + }, + "node_modules/mjml-accordion": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.18.0.tgz", + "integrity": "sha512-9PUmy2JxIOGgAaVHvgVYX21nVAo3o/+wJckTTF/YTLGAqB+nm+44buxRzaXxVk7qXRwbCNfE8c8mlGVNh7vB1g==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-body": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.18.0.tgz", + "integrity": "sha512-34AwX70/7NkRIajPsa5j6NySRiNrlLatTKhiLwTVFiVtrEFlfCcbeMNmdVixI3Ldvs8209ZC6euaAnXDRyR1zw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-button": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.18.0.tgz", + "integrity": "sha512-ZsWMI0j7EcFCMqbqdVwMWhmsVc03FhmypWXokKopGhwySn4IAB4AOURonRmFrO7k6sDeQ+iJ9QtTu7jA+S8wmg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-carousel": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.18.0.tgz", + "integrity": "sha512-wY4g1CHCOoVSZuar7CLFon/qkPbICu71IT+6pa4BDwkAiaAMAemZPyy+a+iIUgdc8kHgSuHGsGf6PQzBSMWRZA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-cli": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.18.0.tgz", + "integrity": "sha512-N6CnA4o/q/VRnGPxTzvVnjAEcF7WUVVQGYfS9SPAp0qwyf7RysMmewdS9yN8GwXwZV6L2sKdn+3ANNi2FNsJ7w==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "chokidar": "^3.0.0", + "glob": "^10.3.10", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "minimatch": "^9.0.3", + "mjml-core": "4.18.0", + "mjml-migrate": "4.18.0", + "mjml-parser-xml": "4.18.0", + "mjml-validator": "4.18.0", + "yargs": "^17.7.2" + }, + "bin": { + "mjml-cli": "bin/mjml" + } + }, + "node_modules/mjml-cli/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/mjml-cli/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "optional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mjml-cli/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mjml-cli/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "optional": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/mjml-column": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.18.0.tgz", + "integrity": "sha512-0QZ1whxbHUmJaRT8tW+wmr3fWZ/kpsHKAd24c7Z/N1Otm/U2G0T/FFEFJ6cB25X6ZN0K40QZ8L9gdLfiSVuRbA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-core": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.18.0.tgz", + "integrity": "sha512-yey72LszXvIo5p0R6DB+YU8er/nP2wPsqpLKQCB0H8vG0WRT1sbSUvnCUOkKGn7subuyWDTdzHKbQO3XYIOmvg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "cheerio": "1.0.0-rc.12", + "detect-node": "^2.0.4", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "juice": "^10.0.0", + "lodash": "^4.17.21", + "mjml-migrate": "4.18.0", + "mjml-parser-xml": "4.18.0", + "mjml-validator": "4.18.0" + } + }, + "node_modules/mjml-divider": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.18.0.tgz", + "integrity": "sha512-FmGUVJqi4RYroh7y85vDx0aUKZgECkxHtMQ4pkLGQbZ2g93/Qt0Ek88DVCNJ5XwUAQQkE/TvrGMLHp3CIqpQ9Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-group": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.18.0.tgz", + "integrity": "sha512-28ABkXsKljBqj7XCC8GkQ94xz8HEU2XTyD+9LTlkDafzGp/MGJb8DcLh/7IkxCwqkQWyeMiDNLf1djsQ909Vxw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.18.0.tgz", + "integrity": "sha512-DS0adpIAsVMDIk2DOsHzjg+RNjQU0fF8jiVP9BmdRHVGrLPmpL9wIHZk2KvsKvZe7VaXXBijFt3DZ5/CQ/+D7Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head-attributes": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.18.0.tgz", + "integrity": "sha512-nLzix1wrMnojE0RPGhk4iKqSRwHKjie2EPzgKT7CDzfqN+Ref03E5Q19x3cQTLgxvq3C3CnvCQBfnhoS3Eakug==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head-breakpoint": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.18.0.tgz", + "integrity": "sha512-k6rwff+7i+vTQYJ/CjBfE20qNqPaW60IRH2x2oEPuCzmwDmoVWOcplJIuotSqIAdfwF9hLkICknisp1BpczVlQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head-font": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.18.0.tgz", + "integrity": "sha512-ao8HB5nf+Dmxw4GO6lMMOlnj1lNZONai0GC9RobrZgPlghZw6hpURWGpkON7pQcy6XnOHwYwkV7Go/npzA2i7w==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head-html-attributes": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.18.0.tgz", + "integrity": "sha512-xaQE1rthe0RrNotwEr71X1tE+QQ489Yc0ynMm3oNMrohDI/TaCeazx8GAHPMM7VLduDA8D4A5wkZ6PuEvlJu4w==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head-preview": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.18.0.tgz", + "integrity": "sha512-2JvYqhbLyU/+Te6/1AXxzTNoHYCDYhXOVZP7wMvU4t7K34pXqyRUNO405atyHUY1MRafrl6RJ8cIx0x5vUX7PA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head-style": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.18.0.tgz", + "integrity": "sha512-nEwDHkAqY3Fm7QWeAZc/a7MakZpXh6THfrE8/AWrfpgzTHrD/wihNUc09ztNpr6z/K1+JWgQfSF2BRc+X3P46g==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-head-title": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.18.0.tgz", + "integrity": "sha512-0Hm8o50rPMUQLSCOOa4D4pz9NajmCDccLvBYE4fwKdeUXjSJ6bwAYeMpveel8oNZMDUVJ4Hx+PskisEGHMHM2w==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-hero": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.18.0.tgz", + "integrity": "sha512-rujm0ROM4QGWw77vnl3NaVaCKXrT4xTSHeAnkHKiY5AuRf6HPTgEtutq5pdel/y6Q9GrmxvN3HRESum7tpJCJw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-image": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.18.0.tgz", + "integrity": "sha512-e09NkoYwvzMcTv7V6H5doWD6Te2E1y2EvOLQJoXKVdQpDwyBWGdfnZke0scJGdA58HLAB+0mLYogpLwmfLaP5Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-migrate": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.18.0.tgz", + "integrity": "sha512-qfNCgW9zhJIsbPyXFA5RT/WY4mlje3N0WhHHOsHc0nY89Q01DenyslUy9nLLGXwi4K5FHS58oCjwWbMhwDcj1w==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "mjml-core": "4.18.0", + "mjml-parser-xml": "4.18.0", + "yargs": "^17.7.2" + }, + "bin": { + "migrate": "lib/cli.js" + } + }, + "node_modules/mjml-navbar": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.18.0.tgz", + "integrity": "sha512-uho/MS2tfNAe+V9u2X7NoCco34MDbdp30ETA8009Qo1VCP/D8lZ+s69WGRPu6hvN/Y2pzBgZly++CMg3qFZqBQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-parser-xml": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.18.0.tgz", + "integrity": "sha512-sHSsZg4afY1heThuJzxa1Kvfh/QzB7/9P5fFUHeVnnxb07ZTXnhXWA6YbobdND5/l9+5yjN5/UgqDZm3tIT4Uw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "detect-node": "2.1.0", + "htmlparser2": "^9.1.0", + "lodash": "^4.17.21" + } + }, + "node_modules/mjml-parser-xml/node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/mjml-preset-core": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.18.0.tgz", + "integrity": "sha512-x3l8vMVtsaqM/jauMeZIN7HFD2t5A28J4U0o4849yIlRxiWguLFV5l3BL8Byol+YLkoLuT9PjaZs9RYv+FGfeg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "mjml-accordion": "4.18.0", + "mjml-body": "4.18.0", + "mjml-button": "4.18.0", + "mjml-carousel": "4.18.0", + "mjml-column": "4.18.0", + "mjml-divider": "4.18.0", + "mjml-group": "4.18.0", + "mjml-head": "4.18.0", + "mjml-head-attributes": "4.18.0", + "mjml-head-breakpoint": "4.18.0", + "mjml-head-font": "4.18.0", + "mjml-head-html-attributes": "4.18.0", + "mjml-head-preview": "4.18.0", + "mjml-head-style": "4.18.0", + "mjml-head-title": "4.18.0", + "mjml-hero": "4.18.0", + "mjml-image": "4.18.0", + "mjml-navbar": "4.18.0", + "mjml-raw": "4.18.0", + "mjml-section": "4.18.0", + "mjml-social": "4.18.0", + "mjml-spacer": "4.18.0", + "mjml-table": "4.18.0", + "mjml-text": "4.18.0", + "mjml-wrapper": "4.18.0" + } + }, + "node_modules/mjml-raw": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.18.0.tgz", + "integrity": "sha512-F/kViAwXm3ccPP52kw++/mHQbcYbYYxC8JH15TZxH8GLVZkX5CGKgcBrHhDK7WoIlfEIsVRZ6IZdlHjH8vgyxw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-section": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.18.0.tgz", + "integrity": "sha512-bB8My9zvIEkTOxej+TrjEeaeRT0lsypGeRADtdrRZXeqUClkkuCnCXlsNKSLGT8ZRqjUqWRc5z8ubDOvGk2+Gg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-social": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.18.0.tgz", + "integrity": "sha512-iAQc9g59L6L3VHDd55BxeIvk/zHkxflxmvuyYyOOvpmmKAvUBC//ULfpxiiM4yupofsThqFfrO+wc8d4kTRkbQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-spacer": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.18.0.tgz", + "integrity": "sha512-FK/0f5IBiONgaRpwNBs7G8EbLdAbmYqcIfHR8O8tP4LipAChLQKHO9vX3vrRMGLBZZNTESLObcFSVWmA40Mfpw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-table": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.18.0.tgz", + "integrity": "sha512-vJysCPUL3CHcsQDAFpW+skzBtY0RYsmMBYswI4WX0B05GLKlOjXqpYOwcmAupWeGoBVL5r/t28ynu2PqnOlN3w==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-text": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.18.0.tgz", + "integrity": "sha512-hBLmF3JgveUKktKQFWHqHAr7qr92j1CxAvq7mtpDUgiWgyPFzqRX8mUsFYgZ7DmRxG4UE+Kzpt8/YFd9+E98lw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0" + } + }, + "node_modules/mjml-validator": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.18.0.tgz", + "integrity": "sha512-JmpWAsNTUlAxJOz2zHYfF8Vod8OzM3Qp5JXtrVw5tivZQzq88ZfqVGuqsas51z0pp1/ilfD4lC17YGfGwKGyhA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4" + } + }, + "node_modules/mjml-wrapper": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.18.0.tgz", + "integrity": "sha512-TZeOvLjIhXEK60rjWNiYhEYNlv5GKYahE+96ifcT5OGkWkRA0DsQDfp+6VI32OS5VxsfKq2h/UdERPlQijjpAQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.28.4", + "lodash": "^4.17.21", + "mjml-core": "4.18.0", + "mjml-section": "4.18.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/module-details-from-path": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", + "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/nan": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/nest-winston": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/nest-winston/-/nest-winston-1.10.2.tgz", + "integrity": "sha512-Z9IzL/nekBOF/TEwBHUJDiDPMaXUcFquUQOFavIRet6xF0EbuWnOzslyN/ksgzG+fITNgXhMdrL/POp9SdaFxA==", + "dependencies": { + "fast-safe-stringify": "^2.1.1" + }, + "peerDependencies": { + "@nestjs/common": "^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "winston": "^3.0.0" + } + }, + "node_modules/newrelic": { + "version": "12.25.0", + "resolved": "https://registry.npmjs.org/newrelic/-/newrelic-12.25.0.tgz", + "integrity": "sha512-WqYv7EvOcOQhvnMbPvjhZzcihO36qj2iNOO6jTfaixku+f558KFawXdNjw3Gl3kL1ycrTfO7N0ywrIZ1ggKGbA==", + "dependencies": { + "@grpc/grpc-js": "^1.13.2", + "@grpc/proto-loader": "^0.7.5", + "@newrelic/security-agent": "^2.4.2", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/core": "^2.0.0", + "@opentelemetry/exporter-metrics-otlp-proto": "^0.201.1", + "@opentelemetry/resources": "^2.0.1", + "@opentelemetry/sdk-metrics": "^2.0.1", + "@opentelemetry/sdk-trace-base": "^2.0.0", + "@tyriar/fibonacci-heap": "^2.0.7", + "concat-stream": "^2.0.0", + "https-proxy-agent": "^7.0.1", + "import-in-the-middle": "^1.13.0", + "json-bigint": "^1.0.0", + "json-stringify-safe": "^5.0.0", + "module-details-from-path": "^1.0.3", + "readable-stream": "^3.6.1", + "require-in-the-middle": "^7.4.0", + "semver": "^7.5.2", + "winston-transport": "^4.5.0" + }, + "bin": { + "newrelic-naming-rules": "bin/test-naming-rules.js" + }, + "engines": { + "node": ">=18", + "npm": ">=6.0.0" + }, + "optionalDependencies": { + "@newrelic/fn-inspect": "^4.4.0", + "@newrelic/native-metrics": "^11.1.0", + "@prisma/prisma-fmt-wasm": "^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085" + } + }, + "node_modules/newrelic/node_modules/@grpc/grpc-js": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/newrelic/node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/newrelic/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "optional": true + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "optional": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true + }, + "node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "optional": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true + }, + "node_modules/nodeify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nodeify/-/nodeify-1.0.1.tgz", + "integrity": "sha512-n7C2NyEze8GCo/z73KdbjRsBiLbv6eBn1FxwYKQ23IqGo7pQY3mhQan61Sv7eEDJCiyUjTVrVkXTzJCo1dW7Aw==", + "dependencies": { + "is-promise": "~1.0.0", + "promise": "~1.3.0" + } + }, + "node_modules/nodemailer": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "optional": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-to-words-ru": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/number-to-words-ru/-/number-to-words-ru-2.4.1.tgz", + "integrity": "sha512-NrjvYLonGUptrU6eVm/G3rwY2hnd4NTE/K25Sx+sbifZ2EGSUPda6c+3iueqmfpPadNhQhyHJ5qFIDGRhNSXIQ==", + "dependencies": { + "@ungap/structured-clone": "^1.2.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "optional": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ora/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/oxc-resolver": { + "version": "11.16.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.16.2.tgz", + "integrity": "sha512-Uy76u47vwhhF7VAmVY61Srn+ouiOobf45MU9vGct9GD2ARy6hKoqEElyHDB0L+4JOM6VLuZ431KiLwyjI/A21g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.16.2", + "@oxc-resolver/binding-android-arm64": "11.16.2", + "@oxc-resolver/binding-darwin-arm64": "11.16.2", + "@oxc-resolver/binding-darwin-x64": "11.16.2", + "@oxc-resolver/binding-freebsd-x64": "11.16.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.16.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.16.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.16.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.16.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.16.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.16.2", + "@oxc-resolver/binding-linux-x64-musl": "11.16.2", + "@oxc-resolver/binding-openharmony-arm64": "11.16.2", + "@oxc-resolver/binding-wasm32-wasi": "11.16.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.16.2", + "@oxc-resolver/binding-win32-ia32-msvc": "11.16.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.16.2" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "optional": true, + "dependencies": { + "p-timeout": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "optional": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-wait-for": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz", + "integrity": "sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==", + "optional": true, + "dependencies": { + "p-timeout": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "optional": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "optional": true, + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "optional": true, + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "optional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true + }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "devOptional": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pino": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz", + "integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "node_modules/piscina": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.9.2.tgz", + "integrity": "sha512-Fq0FERJWFEUpB4eSY59wSNwXD4RYqR+nR/WiEVcZW8IWfVBxJJafcgTEZDQo8k3w0sUarJ8RyVbbUF4GQ2LGbQ==", + "dev": true, + "optionalDependencies": { + "@napi-rs/nice": "^1.0.1" + } + }, + "node_modules/pizzip": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pizzip/-/pizzip-3.2.0.tgz", + "integrity": "sha512-X4NPNICxCfIK8VYhF6wbksn81vTiziyLbvKuORVAmolvnUzl1A1xmz9DAWKxPRq9lZg84pJOOAMq3OE61bD8IQ==", + "dependencies": { + "pako": "^2.1.0" + } + }, + "node_modules/pizzip/node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prebuildify": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz", + "integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==", + "dependencies": { + "minimist": "^1.2.5", + "mkdirp-classic": "^0.5.3", + "node-abi": "^3.3.0", + "npm-run-path": "^3.1.0", + "pump": "^3.0.0", + "tar-fs": "^2.1.0" + }, + "bin": { + "prebuildify": "bin.js" + } + }, + "node_modules/prebuildify/node_modules/npm-run-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", + "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/preview-email": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/preview-email/-/preview-email-3.1.0.tgz", + "integrity": "sha512-ZtV1YrwscEjlrUzYrTSs6Nwo49JM3pXLM4fFOBSC3wSni+bxaWlw9/Qgk75PZO8M7cX2EybmL2iwvaV3vkAttw==", + "optional": true, + "dependencies": { + "ci-info": "^3.8.0", + "display-notification": "2.0.0", + "fixpack": "^4.0.0", + "get-port": "5.1.1", + "mailparser": "^3.7.1", + "nodemailer": "^6.9.13", + "open": "7", + "p-event": "4.2.0", + "p-wait-for": "3.2.0", + "pug": "^3.0.3", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/preview-email/node_modules/nodemailer": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/preview-email/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, + "node_modules/promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-1.3.0.tgz", + "integrity": "sha512-R9WrbTF3EPkVtWjp7B7umQGVndpsi+rsDAfrR4xAALQpFLa/+2OriecLhawxzvii2gd9+DZFwROWDuUUaqS5yA==", + "dependencies": { + "is-promise": "~1" + } + }, + "node_modules/promise-retry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", + "integrity": "sha512-StEy2osPr28o17bIW776GtwO6+Q+M9zPiZkYfosciUUMYqjhU/ffwRAH0zN2+uvGyUsn8/YICIHRzLbPacpZGw==", + "dependencies": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/promise-retry/node_modules/retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==", + "engines": { + "node": "*" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "optional": true + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pug": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", + "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", + "optional": true, + "dependencies": { + "pug-code-gen": "^3.0.3", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "optional": true, + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz", + "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==", + "optional": true, + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "optional": true + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "optional": true, + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "optional": true, + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "optional": true, + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "optional": true, + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "optional": true, + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "optional": true + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "optional": true, + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "optional": true + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quoted-printable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/quoted-printable/-/quoted-printable-1.0.1.tgz", + "integrity": "sha512-cihC68OcGiQOjGiXuo5Jk6XHANTHl1K4JLk/xlEJRTIXfy19Sg6XzB95XonYgr+1rB88bCpr7WZE7D7AlZow4g==", + "dependencies": { + "utf8": "^2.1.0" + }, + "bin": { + "quoted-printable": "bin/quoted-printable" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "optional": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/request-ip": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", + "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-in-the-middle": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", + "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", + "dependencies": { + "debug": "^4.3.5", + "module-details-from-path": "^1.0.3", + "resolve": "^1.22.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/rimraf": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.2.tgz", + "integrity": "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", + "dependencies": { + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ringbufferjs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ringbufferjs/-/ringbufferjs-2.0.0.tgz", + "integrity": "sha512-GCOqTzUsTHF7nrqcgtNGAFotXztLgiePpIDpyWZ7R5I02tmfJWV+/yuJc//Hlsd8G+WzI1t/dc2y/w2imDZdog==" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" + }, + "node_modules/run-applescript": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-3.2.0.tgz", + "integrity": "sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg==", + "optional": true, + "dependencies": { + "execa": "^0.10.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "optional": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "optional": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/run-applescript/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, + "node_modules/run-applescript/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", + "deprecated": "Just use Node.js's crypto.timingSafeEqual()" + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" + }, + "node_modules/seek-bzip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", + "dev": true, + "dependencies": { + "commander": "^6.0.0" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/seek-bzip/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-truncate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", + "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slick": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", + "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/slugify": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz", + "integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smol-toml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", + "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", + "dev": true, + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "optional": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sql-highlight": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.1.0.tgz", + "integrity": "sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==", + "funding": [ + "https://github.com/scriptcoded/sql-highlight?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/scriptcoded" + } + ], + "engines": { + "node": ">=14" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.fromcodepoint": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.fromcodepoint/-/string.fromcodepoint-0.2.1.tgz", + "integrity": "sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==" + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", + "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", + "dev": true, + "dependencies": { + "inspect-with-kind": "^1.0.5", + "is-plain-obj": "^1.1.0" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stripe": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.7.0.tgz", + "integrity": "sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/terser": { + "version": "5.44.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", + "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "devOptional": true + }, + "node_modules/tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tlds": { + "version": "1.261.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", + "integrity": "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA==", + "bin": { + "tlds": "bin.js" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "optional": true + }, + "node_modules/token-types": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "dependencies": { + "@borewit/text-codec": "^0.2.1", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "engines": { + "node": "*" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "devOptional": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/twilio": { + "version": "5.11.2", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.11.2.tgz", + "integrity": "sha512-+pl0sbdj50UGtlhENGTmSnEsKeo4vBkHM62UUiysV+4amxQBmhNX3i3NGJVE+7CFqACzMkgoDTB3tjBthcHyyQ==", + "dependencies": { + "axios": "^1.12.0", + "dayjs": "^1.11.9", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^9.0.2", + "qs": "^6.9.4", + "scmp": "^2.1.0", + "xmlbuilder": "^13.0.2" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/twilio/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/twilio/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-duration": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/typed-duration/-/typed-duration-1.0.13.tgz", + "integrity": "sha512-HLwA+hNq/2eXe03isJSfa7YJt6NikplBGdNKvlhyuR6WL5iZi2uXJIZv1SSOMEIukCZbeQ8QwIcQ801S0/Qulw==" + }, + "node_modules/typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz", + "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==", + "optionalDependencies": { + "rxjs": "*" + } + }, + "node_modules/typed-env": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typed-env/-/typed-env-2.0.0.tgz", + "integrity": "sha512-9nP+7+pctDlpFNKS4nkXPVkCOdAj7Ax8QeHhQkQ0hEGzsJ5ClgfhjwGN89q6s/EukHqyq9rDRzZ2yctCIEJGxA==", + "engines": { + "node": "^14.18.0 || >=16.0.0" + } + }, + "node_modules/typed-function": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.2.2.tgz", + "integrity": "sha512-VwaXim9Gp1bngi/q3do8hgttYn2uC3MoT/gfuMWylnj1IeZBUAyPddHZlo1K05BDoj8DYPpMdiHqH1dDYdJf2A==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/typeorm": { + "version": "0.3.28", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz", + "integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "ansis": "^4.2.0", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "dayjs": "^1.11.19", + "debug": "^4.4.3", + "dedent": "^1.7.0", + "dotenv": "^16.6.1", + "glob": "^10.5.0", + "reflect-metadata": "^0.2.2", + "sha.js": "^2.4.12", + "sql-highlight": "^6.1.0", + "tslib": "^2.8.1", + "uuid": "^11.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@sap/hana-client": "^2.14.22", + "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0 || ^6.0.0", + "mssql": "^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0 || ^5.0.14", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm-naming-strategies": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/typeorm-naming-strategies/-/typeorm-naming-strategies-4.1.0.tgz", + "integrity": "sha512-vPekJXzZOTZrdDvTl1YoM+w+sUIfQHG4kZTpbFYoTsufyv9NIBRe4Q+PdzhEAFA2std3D9LZHEb1EjE9zhRpiQ==", + "peerDependencies": { + "typeorm": "^0.2.0 || ^0.3.0" + } + }, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/typeorm/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typeorm/node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.52.0.tgz", + "integrity": "sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.52.0", + "@typescript-eslint/parser": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, + "node_modules/unescape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", + "integrity": "sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==", + "dependencies": { + "extend-shallow": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unescape-js": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unescape-js/-/unescape-js-1.1.4.tgz", + "integrity": "sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==", + "dependencies": { + "string.fromcodepoint": "^0.2.1" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, + "node_modules/unzipper/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/unzipper/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/unzipper/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/unzipper/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "optional": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, + "node_modules/utf7": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", + "integrity": "sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==", + "dependencies": { + "semver": "~5.3.0" + } + }, + "node_modules/utf7/node_modules/semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/utf8": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz", + "integrity": "sha512-QXo+O/QkLP/x1nyi54uQiG0XrODxdysuQvE5dtVqv7F5K2Qb6FsN+qbr6KhF5wQ20tfcV3VQp0/2x1e1MRSPWg==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuencode": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uuencode/-/uuencode-0.0.4.tgz", + "integrity": "sha512-yEEhCuCi5wRV7Z5ZVf9iV2gWMvUZqKJhAs1ecFdKJ0qzbyaVelmsE3QjYAamehfp9FKLiZbKldd+jklG3O0LfA==" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true + }, + "node_modules/valid-data-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", + "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/watchpack": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.0.tgz", + "integrity": "sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wcwidth/node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/web-resource-inliner": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", + "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==", + "optional": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "escape-goat": "^3.0.0", + "htmlparser2": "^5.0.0", + "mime": "^2.4.6", + "node-fetch": "^2.6.0", + "valid-data-url": "^3.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/web-resource-inliner/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "optional": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/dom-serializer/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "optional": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "optional": true, + "dependencies": { + "domelementtype": "^2.0.1" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "optional": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/domutils/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "optional": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/htmlparser2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "optional": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/fb55/htmlparser2?sponsor=1" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/webpack": { + "version": "5.103.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", + "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.26.3", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.3.1", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.4", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/win-ca": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/win-ca/-/win-ca-3.5.1.tgz", + "integrity": "sha512-RNy9gpBS6cxWHjfbqwBA7odaHyT+YQNhtdpJZwYCFoxB/Dq22oeOZ9YCXMwjhLytKpo7JJMnKdJ/ve7N12zzfQ==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "is-electron": "^2.2.0", + "make-dir": "^1.3.0", + "node-forge": "^1.2.1", + "split": "^1.0.1" + } + }, + "node_modules/winston": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.8", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston/node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/written-number": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/written-number/-/written-number-0.11.1.tgz", + "integrity": "sha512-LhQ68uUnzHH0bwm/QiGA9JwqgadSDOwqB2AIs/LBsrOY6ScqVXKRN2slTCeKAhstDBJ/Of/Yxcjn0pnQmVlmtg==" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/zip-stream/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..119c343 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,135 @@ +{ + "name": "amwork-backend", + "version": "3.14.1", + "description": "", + "author": "", + "private": true, + "license": "UNLICENSED", + "scripts": { + "ts": "tsc --noEmit", + "prebuild": "rimraf dist", + "build": "NODE_OPTIONS='--max-old-space-size=4096' nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "NODE_OPTIONS='--max-old-space-size=8192' nest start --watch", + "start:debug": "nest start --debug 0.0.0.0:9229 --watch", + "start:prod": "node --max-old-space-size=8192 dist/main", + "start:inspect": "node --max-old-space-size=8192 --inspect dist/main", + "lint": "eslint \"{src,apps,libs,test}/**/*.ts\"", + "lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", + "typeorm": "ts-node ./node_modules/typeorm/cli.js", + "to:rm": "yarn typeorm migration:run -d ./src/database/typeorm-migration.config.ts", + "to:cm": "yarn typeorm migration:create ./src/database/migrations/${0}", + "knip": "NODE_OPTIONS='--max-old-space-size=4096' knip" + }, + "dependencies": { + "@amwork/voximplant-apiclient-nodejs": "^2.3.0-f", + "@aws-sdk/client-s3": "^3.817.0", + "@camunda8/sdk": "^8.7.9", + "@date-fns/tz": "^1.2.0", + "@date-fns/utc": "^2.1.0", + "@esm2cjs/cacheable-lookup": "^7.0.0", + "@faker-js/faker": "^9.8.0", + "@nestjs-modules/mailer": "2.0.2", + "@nestjs/axios": "^4.0.0", + "@nestjs/common": "^11.1.2", + "@nestjs/config": "^4.0.2", + "@nestjs/core": "^11.1.2", + "@nestjs/event-emitter": "^3.0.1", + "@nestjs/jwt": "^11.0.0", + "@nestjs/platform-express": "^11.1.2", + "@nestjs/platform-socket.io": "^11.1.2", + "@nestjs/schedule": "^6.0.0", + "@nestjs/swagger": "^11.2.0", + "@nestjs/terminus": "^11.0.0", + "@nestjs/typeorm": "^11.0.0", + "@nestjs/websockets": "^11.1.2", + "@newrelic/native-metrics": "^11.1.0", + "@voximplant/apiclient-nodejs": "^4.2.0", + "angular-expressions": "^1.4.3", + "axios": "^1.9.0", + "bcrypt": "^6.0.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "cookie-parser": "^1.4.7", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", + "decimal.js": "^10.5.0", + "docxtemplater": "^3.63.2", + "dotenv": "^16.6.1", + "exceljs": "^4.4.0", + "express": "^5.1.0", + "express-rate-limit": "^8.2.1", + "form-data": "^4.0.2", + "generate-password": "^1.7.1", + "googleapis": "^149.0.0", + "handlebars": "^4.7.8", + "heapdump": "^0.3.15", + "helmet": "^8.1.0", + "html-to-text": "^9.0.5", + "iconv-lite": "^0.6.3", + "imap-simple": "^5.1.0", + "imapflow": "^1.0.187", + "ioredis": "^5.6.1", + "jsonwebtoken": "^9.0.2", + "libphonenumber-js": "^1.12.8", + "lvovich": "^2.0.2", + "mailparser": "^3.7.3", + "mathjs": "^14.5.1", + "mime-types": "^3.0.1", + "multer": "^2.0.0", + "nest-winston": "^1.10.2", + "newrelic": "^12.20.0", + "nodemailer": "^7.0.3", + "number-to-words-ru": "^2.4.1", + "path-to-regexp": "^8.2.0", + "pg": "^8.16.0", + "pizzip": "^3.2.0", + "qs": "^6.14.0", + "quoted-printable": "^1.0.1", + "reflect-metadata": "^0.2.2", + "rimraf": "^6.0.1", + "rxjs": "^7.8.2", + "sharp": "^0.34.2", + "slugify": "^1.6.6", + "socket.io": "^4.8.1", + "stripe": "^17.7.0", + "twilio": "^5.7.0", + "typeorm": "^0.3.24", + "typeorm-naming-strategies": "^4.1.0", + "uuid": "^11.1.0", + "winston": "^3.17.0", + "written-number": "^0.11.1" + }, + "devDependencies": { + "@eslint/js": "^9.27.0", + "@nestjs/cli": "^11.0.7", + "@nestjs/schematics": "^11.0.5", + "@swc/cli": "^0.7.7", + "@swc/core": "^1.11.29", + "@total-typescript/ts-reset": "^0.6.1", + "@types/bcrypt": "^5.0.2", + "@types/cookie-parser": "^1", + "@types/express": "^5.0.2", + "@types/heapdump": "^0", + "@types/html-to-text": "^9.0.4", + "@types/imap-simple": "^4.2.10", + "@types/mailparser": "^3.4.6", + "@types/mime-types": "^2", + "@types/multer": "^1.4.12", + "@types/node": "^22.15.24", + "@types/nodemailer": "^6.4.17", + "@types/quoted-printable": "^1", + "@types/uuid": "^10.0.0", + "eslint": "^9.27.0", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.4.0", + "knip": "^5.59.1", + "prettier": "^3.5.3", + "ts-node": "^10.9.2", + "tsconfig-paths": "4.2.0", + "typescript": "^5.8.3", + "typescript-eslint": "^8.33.0" + }, + "packageManager": "yarn@4.9.1" +} diff --git a/backend/src/CRM/Controller/Entity/Board/Filter/EntityBoardCardFilter.ts b/backend/src/CRM/Controller/Entity/Board/Filter/EntityBoardCardFilter.ts new file mode 100644 index 0000000..8acfb07 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Board/Filter/EntityBoardCardFilter.ts @@ -0,0 +1,57 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsObject, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { DatePeriodFilter } from '@/common'; + +import { EntitySorting } from './EntitySorting'; +import { EntityFieldFilter } from './EntityFieldFilter'; +import { EntityTaskFilter } from './entity-task-filter.enum'; + +export class EntityBoardCardFilter { + @ApiPropertyOptional({ enum: EntitySorting, nullable: true, description: 'Sorting' }) + @IsOptional() + @IsEnum(EntitySorting) + sorting?: EntitySorting | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Include stage IDs' }) + @IsOptional() + @IsArray() + includeStageIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Exclude stage IDs' }) + @IsOptional() + @IsArray() + excludeStageIds?: number[] | null; + + @ApiPropertyOptional({ nullable: true, description: 'Search text in entity name' }) + @IsOptional() + @IsString() + search?: string | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Created at' }) + @IsOptional() + @IsObject() + createdAt?: DatePeriodFilter | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Closed at' }) + @IsOptional() + @IsObject() + closedAt?: DatePeriodFilter | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Owner IDs' }) + @IsOptional() + @IsArray() + ownerIds?: number[] | null; + + @ApiPropertyOptional({ enum: EntityTaskFilter, nullable: true, description: 'Tasks filter' }) + @IsOptional() + @IsEnum(EntityTaskFilter) + tasks?: EntityTaskFilter | null; + + @ApiPropertyOptional({ type: [EntityFieldFilter], nullable: true, description: 'Fields filters' }) + @IsOptional() + @IsArray() + @Type(() => EntityFieldFilter) + fields?: EntityFieldFilter[] | null; +} diff --git a/backend/src/CRM/Controller/Entity/Board/Filter/EntityFieldFilter.ts b/backend/src/CRM/Controller/Entity/Board/Filter/EntityFieldFilter.ts new file mode 100644 index 0000000..9854e7a --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Board/Filter/EntityFieldFilter.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { SimpleFilter } from '@/common'; + +export class EntityFieldFilter extends SimpleFilter { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + fieldId: number; +} diff --git a/backend/src/CRM/Controller/Entity/Board/Filter/EntitySorting.ts b/backend/src/CRM/Controller/Entity/Board/Filter/EntitySorting.ts new file mode 100644 index 0000000..cfcb435 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Board/Filter/EntitySorting.ts @@ -0,0 +1,7 @@ +export enum EntitySorting { + Manual = 'manual', + CreatedAsc = 'created_asc', + CreatedDesc = 'created_desc', + NameAsc = 'name_asc', + NameDesc = 'name_desc', +} diff --git a/backend/src/CRM/Controller/Entity/Board/Filter/entity-task-filter.enum.ts b/backend/src/CRM/Controller/Entity/Board/Filter/entity-task-filter.enum.ts new file mode 100644 index 0000000..1365d38 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Board/Filter/entity-task-filter.enum.ts @@ -0,0 +1,7 @@ +export enum EntityTaskFilter { + All = 'all', + WithTask = 'with_task', + WithoutTask = 'without_task', + OverdueTask = 'overdue_task', + TodayTask = 'today_task', +} diff --git a/backend/src/CRM/Controller/Entity/Board/entity-board.controller.ts b/backend/src/CRM/Controller/Entity/Board/entity-board.controller.ts new file mode 100644 index 0000000..ff92583 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Board/entity-board.controller.ts @@ -0,0 +1,86 @@ +import { Body, Controller, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; +import { plainToInstance } from 'class-transformer'; + +import { PagingQuery } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { EntityBoardService } from '../../../Service/Entity/EntityBoardService'; +import { EntityBoardCard } from '../../../Service/Entity/Dto/Board/EntityBoardCard'; +import { EntityBoardMeta } from '../../../Service/Entity/Dto/Board/EntityBoardMeta'; +import { EntitySimpleDto } from '../../../Service/Entity/Dto/EntitySimpleDto'; + +import { EntityBoardCardFilter } from './Filter/EntityBoardCardFilter'; + +@ApiTags('crm/entities/board') +@Controller('crm/entities/:entityTypeId/board/:boardId') +@JwtAuthorized({ prefetch: { user: true } }) +export class EntityBoardController { + constructor(private readonly service: EntityBoardService) {} + + @ApiOperation({ summary: 'Get entities list for board', description: 'Get entities list for board' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiParam({ name: 'boardId', type: Number, required: true, description: 'Board ID' }) + @ApiBody({ type: EntityBoardCardFilter, description: 'Filter' }) + @ApiOkResponse({ description: 'Entities for board', type: [EntityBoardCard] }) + @Post('cards') + async getEntityBoardCards( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() filter: EntityBoardCardFilter, + @Query() paging: PagingQuery, + ): Promise { + return this.service.getEntityBoardCards({ accountId, user, entityTypeId, boardId, filter, paging }); + } + + @ApiOperation({ summary: 'Get entity for board', description: 'Get entity for board' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiParam({ name: 'boardId', type: Number, required: true, description: 'Board ID' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiBody({ type: EntityBoardCardFilter, description: 'Filter' }) + @ApiOkResponse({ description: 'Entity for board', type: EntityBoardCard }) + @Post('cards/:entityId') + async getEntityBoardCard( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Param('boardId', ParseIntPipe) boardId: number, + @Param('entityId', ParseIntPipe) entityId: number, + @Body() filter: EntityBoardCardFilter, + ): Promise { + return this.service.getEntityBoardCard({ accountId, user, entityTypeId, boardId, entityId, filter }); + } + + /** + * @deprecated create find entity endpoint + */ + @ApiOkResponse({ description: 'Get entities simple info list for report filter', type: [EntitySimpleDto] }) + @Post('entities') + async getEntityBoardEntities( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Param('boardId', ParseIntPipe) boardId: number, + @Query() paging: PagingQuery, + ): Promise { + const entities = await this.service.getEntityBoardEntities(accountId, user, entityTypeId, boardId, paging); + + return plainToInstance(EntitySimpleDto, entities, { excludeExtraneousValues: true }); + } + + @ApiOperation({ summary: 'Get meta for board', description: 'Get meta for board' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiParam({ name: 'boardId', type: Number, required: true, description: 'Board ID' }) + @ApiBody({ type: EntityBoardCardFilter, description: 'Filter' }) + @ApiOkResponse({ description: 'Meta for board', type: EntityBoardMeta }) + @Post('meta') + async getEntityBoardMeta( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() filter: EntityBoardCardFilter, + ): Promise { + return this.service.getEntityBoardMeta({ accountId, user, entityTypeId, boardId, filter }); + } +} diff --git a/backend/src/CRM/Controller/Entity/CreateEntityController.ts b/backend/src/CRM/Controller/Entity/CreateEntityController.ts new file mode 100644 index 0000000..24bcfa7 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/CreateEntityController.ts @@ -0,0 +1,26 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { EntityService } from '../../Service/Entity/EntityService'; +import { CreateEntityDto } from '../../Service/Entity/Dto/CreateEntityDto'; +import { EntityDto } from '../../Service/Entity/Dto/EntityDto'; + +@ApiTags('crm/entities') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class CreateEntityController { + constructor(private service: EntityService) {} + + @ApiCreatedResponse({ description: 'Entity', type: EntityDto }) + @Post('/crm/entities') + public async createEntity( + @CurrentAuth() { accountId, user }: AuthData, + @Body() dto: CreateEntityDto, + ): Promise { + return this.service.createAndGetDto(accountId, user, dto); + } +} diff --git a/backend/src/CRM/Controller/Entity/CreateSimpleEntityController.ts b/backend/src/CRM/Controller/Entity/CreateSimpleEntityController.ts new file mode 100644 index 0000000..4a0d32d --- /dev/null +++ b/backend/src/CRM/Controller/Entity/CreateSimpleEntityController.ts @@ -0,0 +1,26 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { EntityService } from '../../Service/Entity/EntityService'; +import { CreateSimpleEntityDto } from '../../Service/Entity/Dto/CreateSimpleEntityDto'; + +@ApiTags('crm/entities') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class CreateSimpleEntityController { + constructor(private readonly service: EntityService) {} + + @ApiCreatedResponse({ description: 'Entities', type: [EntityInfoDto] }) + @Post('/crm/entities/simple') + public async createEntity( + @CurrentAuth() { accountId, user }: AuthData, + @Body() dto: CreateSimpleEntityDto, + ): Promise { + return this.service.createSimpleAndGetInfo({ accountId, user, dto }); + } +} diff --git a/backend/src/CRM/Controller/Entity/DeleteEntityController.ts b/backend/src/CRM/Controller/Entity/DeleteEntityController.ts new file mode 100644 index 0000000..3a9ebb8 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/DeleteEntityController.ts @@ -0,0 +1,20 @@ +import { Controller, Delete, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { EntityService } from '../../Service/Entity/EntityService'; + +@ApiTags('crm/entities') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class DeleteEntityController { + constructor(private readonly service: EntityService) {} + + @Delete('crm/entities/:entityId') + async delete(@CurrentAuth() { accountId, user }: AuthData, @Param('entityId', ParseIntPipe) entityId: number) { + await this.service.delete(accountId, user, entityId); + } +} diff --git a/backend/src/CRM/Controller/Entity/Documents/GetEntityDocumentsController.ts b/backend/src/CRM/Controller/Entity/Documents/GetEntityDocumentsController.ts new file mode 100644 index 0000000..c42a185 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Documents/GetEntityDocumentsController.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FileLinkDto } from '../../../Service/FileLink/FileLinkDto'; +import { EntityService } from '../../../Service/Entity/EntityService'; + +@ApiTags('crm/entities/documents') +@Controller() +@JwtAuthorized({ prefetch: { account: true } }) +export class GetEntityDocumentsController { + constructor(private entityService: EntityService) {} + + @ApiOkResponse({ description: 'Entity documents', type: [FileLinkDto] }) + @Get('/crm/entities/:id/documents') + public async getEntityDocuments( + @CurrentAuth() { account }: AuthData, + @Param('id', ParseIntPipe) id: number, + ): Promise { + return await this.entityService.getDocumentLinks(account, id); + } +} diff --git a/backend/src/CRM/Controller/Entity/Files/AddEntityFilesController.ts b/backend/src/CRM/Controller/Entity/Files/AddEntityFilesController.ts new file mode 100644 index 0000000..33bf6f6 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Files/AddEntityFilesController.ts @@ -0,0 +1,28 @@ +import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { FileLinkSource } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FileLinkService } from '../../../Service/FileLink/FileLinkService'; +import { FileLinkDto } from '../../../Service/FileLink/FileLinkDto'; +import { AddEntityFilesDto } from '../../../Service/Entity/Dto/Files/AddEntityFilesDto'; + +@ApiTags('crm/entities') +@Controller() +@JwtAuthorized({ prefetch: { account: true } }) +export class AddEntityFilesController { + constructor(private fileLinkService: FileLinkService) {} + + @ApiOkResponse({ description: 'Added entity files', type: [FileLinkDto] }) + @Post('/crm/entities/:id/files') + public async addEntityFiles( + @CurrentAuth() { account }: AuthData, + @Param('id', ParseIntPipe) id: number, + @Body() dto: AddEntityFilesDto, + ): Promise { + return await this.fileLinkService.addFiles(account, FileLinkSource.ENTITY, id, dto.fileIds); + } +} diff --git a/backend/src/CRM/Controller/Entity/Files/GetEntityFilesController.ts b/backend/src/CRM/Controller/Entity/Files/GetEntityFilesController.ts new file mode 100644 index 0000000..cebfb19 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Files/GetEntityFilesController.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FileLinkDto } from '../../../Service/FileLink/FileLinkDto'; +import { EntityService } from '../../../Service/Entity/EntityService'; + +@ApiTags('crm/entities') +@Controller() +@JwtAuthorized({ prefetch: { account: true } }) +export class GetEntityFilesController { + constructor(private entityService: EntityService) {} + + @ApiOkResponse({ description: 'Entity files', type: [FileLinkDto] }) + @Get('/crm/entities/:id/files') + public async getEntityFiles( + @CurrentAuth() { account }: AuthData, + @Param('id', ParseIntPipe) id: number, + ): Promise { + return await this.entityService.getFileLinks(account, id); + } +} diff --git a/backend/src/CRM/Controller/Entity/GetEntityController.ts b/backend/src/CRM/Controller/Entity/GetEntityController.ts new file mode 100644 index 0000000..0b5982c --- /dev/null +++ b/backend/src/CRM/Controller/Entity/GetEntityController.ts @@ -0,0 +1,40 @@ +import { Controller, Get, Param, ParseIntPipe, Req } from '@nestjs/common'; +import { ApiCreatedResponse, ApiExcludeEndpoint, ApiTags } from '@nestjs/swagger'; +import { Request } from 'express'; + +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { EntityDto } from '../../Service/Entity/Dto/EntityDto'; +import { EntityService } from '../../Service/Entity/EntityService'; + +@ApiTags('crm/entities') +@Controller() +@JwtAuthorized() +export class GetEntityController { + constructor(private entityService: EntityService) {} + + //HACK: this is fake entity generator + @ApiExcludeEndpoint() + @ApiCreatedResponse({ description: 'Entity', type: EntityDto }) + @Get('/crm/entities/None') + public async getEntityFake(): Promise { + return EntityDto.fake(); + } + + @ApiCreatedResponse({ description: 'Entity', type: EntityDto }) + @Get('/crm/entities/:entityId') + @AuthDataPrefetch({ user: true }) + public async getEntity( + @CurrentAuth() { accountId, user }: AuthData, + @Req() request: Request, + @Param('entityId', ParseIntPipe) entityId: number, + ): Promise { + const ip = request.ips?.[0] ?? request.ip; + //HACK: fake for kedma bot + if (accountId === 11023389 && user.id === 12024444 && ip === '209.250.243.107') { + return EntityDto.fake(); + } + + return this.entityService.getDtoByIdForUI(accountId, user, entityId); + } +} diff --git a/backend/src/CRM/Controller/Entity/Import/GetEntitiesImportTemplateController.ts b/backend/src/CRM/Controller/Entity/Import/GetEntitiesImportTemplateController.ts new file mode 100644 index 0000000..41cdb56 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Import/GetEntitiesImportTemplateController.ts @@ -0,0 +1,31 @@ +import { Controller, Get, Param, Res, StreamableFile } from '@nestjs/common'; +import { Response } from 'express'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ImportService } from '../../../Service/Import/ImportService'; + +@ApiTags('crm/entities/import') +@Controller() +@JwtAuthorized() +export class GetEntitiesImportTemplateController { + constructor(private importService: ImportService) {} + + @Get('/crm/entities/:entityTypeId/import/template') + @ApiOkResponse({ description: 'Get import template for entityType', type: StreamableFile }) + async getTemplate( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId') entityTypeId: number, + @Res() res: Response, + ) { + const content = await this.importService.generateTemplateForEntityType(accountId, entityTypeId); + + res.setHeader('Content-Disposition', `attachment; filename="import-template.xlsx"`); + res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + + res.send(content); + } +} diff --git a/backend/src/CRM/Controller/Entity/Import/UploadEntitiesImportController.ts b/backend/src/CRM/Controller/Entity/Import/UploadEntitiesImportController.ts new file mode 100644 index 0000000..6f00f5c --- /dev/null +++ b/backend/src/CRM/Controller/Entity/Import/UploadEntitiesImportController.ts @@ -0,0 +1,45 @@ +import { + Controller, + MaxFileSizeValidator, + Param, + ParseFilePipe, + Post, + UploadedFile, + UseInterceptors, +} from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { memoryStorage } from 'multer'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { ImportService } from '../../../Service/Import/ImportService'; + +const ImportFile = { + MaxSize: 10485760, +}; + +@ApiTags('crm/entities/import') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class UploadEntitiesImportController { + constructor(private importService: ImportService) {} + + @Post('/crm/entities/:entityTypeId/import') + @UseInterceptors(FileInterceptor('file', { storage: memoryStorage() })) + async uploadImportData( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId') entityTypeId: number, + @UploadedFile( + new ParseFilePipe({ + validators: [new MaxFileSizeValidator({ maxSize: ImportFile.MaxSize })], + }), + ) + file: Express.Multer.File, + ): Promise { + return await this.importService.importDataBackground(accountId, user, entityTypeId, StorageFile.fromMulter(file)); + } +} diff --git a/backend/src/CRM/Controller/Entity/List/entity-list.controller.ts b/backend/src/CRM/Controller/Entity/List/entity-list.controller.ts new file mode 100644 index 0000000..34025e4 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/List/entity-list.controller.ts @@ -0,0 +1,97 @@ +import { Body, Controller, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { EntityBoardService } from '../../../Service/Entity/EntityBoardService'; +import { EntityListItem } from '../../../Service/Entity/Dto/List/EntityListItem'; +import { EntityListMeta } from '../../../Service/Entity/Dto/List/EntityListMeta'; +import { UpdateEntitiesBatchFilterDto } from '../../../Service/Entity/Dto/Batch/update-entities-batch-filter.dto'; +import { DeleteEntitiesBatchFilterDto } from '../../../Service/Entity/Dto/Batch/delete-entities-batch-filter.dto'; +import { EntityBoardCardFilter } from '../Board/Filter/EntityBoardCardFilter'; + +@ApiTags('crm/entities/list') +@Controller('crm/entities/:entityTypeId/list') +@JwtAuthorized({ prefetch: { user: true } }) +export class EntityListController { + constructor(private readonly service: EntityBoardService) {} + + @ApiOperation({ summary: 'Get entities list', description: 'Get entities list' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' }) + @ApiBody({ type: EntityBoardCardFilter, description: 'Filter' }) + @ApiOkResponse({ description: 'Entities', type: [EntityListItem] }) + @Post() + async getEntityListItems( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Query('boardId') boardId: number | null, + @Body() filter: EntityBoardCardFilter, + @Query() paging: PagingQuery, + ): Promise { + return this.service.getEntityListItems({ accountId, user, entityTypeId, boardId, filter, paging }); + } + + @ApiOperation({ summary: 'Get meta for list', description: 'Get meta for list' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' }) + @ApiBody({ type: EntityBoardCardFilter, description: 'Filter' }) + @ApiOkResponse({ description: 'Meta for list', type: EntityListMeta }) + @Post('meta') + async getEntityListMeta( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Query('boardId') boardId: number | null, + @Body() filter: EntityBoardCardFilter, + ): Promise { + return this.service.getEntityListMeta({ accountId, user, entityTypeId, boardId, filter }); + } + + @ApiOperation({ summary: 'Update entities', description: 'Update entities' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' }) + @ApiBody({ type: UpdateEntitiesBatchFilterDto, description: 'Update data' }) + @ApiOkResponse({ description: 'Updated entities count', type: Number }) + @Post('update') + async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Query('boardId') boardId: number | null, + @Body() dto: UpdateEntitiesBatchFilterDto, + ): Promise { + return this.service.batchUpdate({ accountId, user, entityTypeId, boardId, dto }); + } + + @ApiOperation({ summary: 'Delete entities', description: 'Delete entities' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiQuery({ name: 'boardId', type: Number, required: false, description: 'Board ID' }) + @ApiBody({ type: DeleteEntitiesBatchFilterDto, description: 'Delete data' }) + @ApiOkResponse({ description: 'Delete entity list', type: Number }) + @Post('delete') + async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Query('boardId') boardId: number | null, + @Body() dto: DeleteEntitiesBatchFilterDto, + ): Promise { + return this.service.batchDelete({ accountId, user, entityTypeId, boardId, dto }); + } + + @ApiOperation({ summary: 'Get entity for list', description: 'Get entity for list' }) + @ApiParam({ name: 'entityTypeId', type: Number, required: true, description: 'Entity type ID' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiBody({ type: EntityBoardCardFilter, description: 'Filter' }) + @ApiOkResponse({ description: 'Entity for list', type: EntityListItem }) + @Post(':entityId') + async getEntityListItem( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Param('entityId', ParseIntPipe) entityId: number, + @Body() filter: EntityBoardCardFilter, + ): Promise { + return this.service.getEntityListItem({ accountId, user, entityTypeId, entityId, filter }); + } +} diff --git a/backend/src/CRM/Controller/Entity/UpdateEntityController.ts b/backend/src/CRM/Controller/Entity/UpdateEntityController.ts new file mode 100644 index 0000000..cfec873 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/UpdateEntityController.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Param, ParseIntPipe, Patch } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { EntityService } from '../../Service/Entity/EntityService'; +import { UpdateEntityDto } from '../../Service/Entity/Dto/UpdateEntityDto'; +import { EntityDto } from '../../Service/Entity/Dto/EntityDto'; + +@ApiTags('crm/entities') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class UpdateEntityController { + constructor(private entityService: EntityService) {} + + @ApiCreatedResponse({ description: 'Entity', type: EntityDto }) + @Patch('crm/entities/:id') + async updateEntity( + @CurrentAuth() { accountId, user }: AuthData, + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateEntityDto, + ): Promise { + return this.entityService.updateAndGetDto(accountId, user, id, dto); + } +} diff --git a/backend/src/CRM/Controller/Entity/UpdateEntityFieldController.ts b/backend/src/CRM/Controller/Entity/UpdateEntityFieldController.ts new file mode 100644 index 0000000..297b9a2 --- /dev/null +++ b/backend/src/CRM/Controller/Entity/UpdateEntityFieldController.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { CreateFieldValueDto } from '@/modules/entity/entity-field/field-value/dto/create-field-value.dto'; +import { EntityService } from '../../Service/Entity/EntityService'; + +@ApiTags('crm/fields') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class UpdateEntityFieldController { + constructor(private readonly service: EntityService) {} + + @ApiOkResponse({ description: 'Set entity field value' }) + @Post('/crm/entities/:entityId/field-values/:fieldId') + public async updateFieldValue( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + @Param('fieldId', ParseIntPipe) fieldId: number, + @Body() dto: CreateFieldValueDto, + ) { + await this.service.updateFieldValue(accountId, user, entityId, fieldId, dto); + } +} diff --git a/backend/src/CRM/Controller/ExternalEntity/CreateExternalLinkController.ts b/backend/src/CRM/Controller/ExternalEntity/CreateExternalLinkController.ts new file mode 100644 index 0000000..4c08810 --- /dev/null +++ b/backend/src/CRM/Controller/ExternalEntity/CreateExternalLinkController.ts @@ -0,0 +1,26 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiExcludeController } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ExternalEntityService } from '../../Service/ExternalEntity/ExternalEntityService'; +import { CreateExternalEntityDto } from '../../Service/ExternalEntity/CreateExternalEntityDto'; +import { CreateExternalEntityResult } from '../../Service/ExternalEntity/CreateExternalEntityResult'; + +@ApiExcludeController(true) +@Controller() +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class CreateExternalLinkController { + constructor(private readonly service: ExternalEntityService) {} + + @ApiCreatedResponse({ description: 'Entity', type: CreateExternalEntityResult }) + @Post('/extension/external-link') + public async createExternalLink( + @CurrentAuth() { account, user }: AuthData, + @Body() dto: CreateExternalEntityDto, + ): Promise { + return await this.service.create(account, user, dto); + } +} diff --git a/backend/src/CRM/Controller/FileLink/DeleteFileLinkController.ts b/backend/src/CRM/Controller/FileLink/DeleteFileLinkController.ts new file mode 100644 index 0000000..0ac9c72 --- /dev/null +++ b/backend/src/CRM/Controller/FileLink/DeleteFileLinkController.ts @@ -0,0 +1,20 @@ +import { Controller, Delete, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FileLinkService } from '../../Service/FileLink/FileLinkService'; + +@ApiTags('crm/file-link') +@Controller() +@JwtAuthorized() +export class DeleteFileLinkController { + constructor(private readonly fileLinkService: FileLinkService) {} + + @Delete('/crm/file-link/:fileLinkId') + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('fileLinkId', ParseIntPipe) fileLinkId: number) { + await this.fileLinkService.deleteFileLink(accountId, fileLinkId); + } +} diff --git a/backend/src/CRM/Controller/FileLink/DeleteFileLinksController.ts b/backend/src/CRM/Controller/FileLink/DeleteFileLinksController.ts new file mode 100644 index 0000000..40d8358 --- /dev/null +++ b/backend/src/CRM/Controller/FileLink/DeleteFileLinksController.ts @@ -0,0 +1,22 @@ +import { Controller, Delete, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FileLinkService } from '../../Service/FileLink/FileLinkService'; + +@ApiTags('crm/file-link') +@Controller() +@JwtAuthorized() +export class DeleteFileLinksController { + constructor(private readonly fileLinkService: FileLinkService) {} + + @ApiCreatedResponse({ description: 'Delete file links by ids' }) + @Delete('/crm/file-links') + public async delete(@CurrentAuth() { accountId }: AuthData, @Query('ids') ids: string): Promise { + const fileLinkIds = ids.split(',').map((id) => parseInt(id)); + await this.fileLinkService.deleteFileLinks(accountId, fileLinkIds); + } +} diff --git a/backend/src/CRM/Controller/TimeBoard/GetTimeBoardCalendarController.ts b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardCalendarController.ts new file mode 100644 index 0000000..c020d8e --- /dev/null +++ b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardCalendarController.ts @@ -0,0 +1,29 @@ +import { Body, Controller, Post, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { DatePeriodDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService'; +import { TaskOrActivityCard } from '../../Service/TimeBoard/TaskOrActivityCard'; +import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter'; + +@ApiTags('crm/tasks/time-board/calendar') +@Controller() +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class GetTimeBoardCalendarController { + constructor(private readonly service: TimeBoardService) {} + + @ApiOkResponse({ description: 'Time board calendar' }) + @Post('/crm/tasks/by_time/calendar') + public async getCalendar( + @CurrentAuth() { account, user }: AuthData, + @Query() period: DatePeriodDto, + @Body() filter: TimeBoardFilter, + ): Promise { + return this.service.getCalendar(account, user, period, filter); + } +} diff --git a/backend/src/CRM/Controller/TimeBoard/GetTimeBoardCalendarMetaController.ts b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardCalendarMetaController.ts new file mode 100644 index 0000000..2f07974 --- /dev/null +++ b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardCalendarMetaController.ts @@ -0,0 +1,29 @@ +import { Body, Controller, Post, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { DatePeriodDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService'; +import { TimeBoardCalendarMeta } from '../../Service/TimeBoard/TimeBoardCalendarMeta'; +import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter'; + +@ApiTags('crm/tasks/time-board/calendar') +@Controller() +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class GetTimeBoardCalendarMetaController { + constructor(private readonly service: TimeBoardService) {} + + @ApiOkResponse({ description: 'Meta for calendar', type: TimeBoardCalendarMeta }) + @Post('/crm/tasks/by_time/calendar/meta') + public async getCalendarMeta( + @CurrentAuth() { account, user }: AuthData, + @Query() period: DatePeriodDto, + @Body() filter: TimeBoardFilter, + ): Promise { + return this.service.getCalendarMeta(account, user, period, filter); + } +} diff --git a/backend/src/CRM/Controller/TimeBoard/GetTimeBoardController.ts b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardController.ts new file mode 100644 index 0000000..94a7b51 --- /dev/null +++ b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardController.ts @@ -0,0 +1,28 @@ +import { Body, Controller, Post, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService'; +import { TaskOrActivityCard } from '../../Service/TimeBoard/TaskOrActivityCard'; +import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter'; + +@ApiTags('crm/tasks/time-board') +@Controller() +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class GetTimeBoardController { + constructor(private readonly service: TimeBoardService) {} + + @ApiCreatedResponse({ description: 'All tasks and activities' }) + @Post('/crm/tasks/by_time') + public async getTimeBoard( + @CurrentAuth() { account, user }: AuthData, + @Body() filter: TimeBoardFilter, + @Query() paging: PagingQuery, + ): Promise { + return this.service.getTimeBoardCards(account, user, filter, paging); + } +} diff --git a/backend/src/CRM/Controller/TimeBoard/GetTimeBoardItemController.ts b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardItemController.ts new file mode 100644 index 0000000..6ffc552 --- /dev/null +++ b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardItemController.ts @@ -0,0 +1,31 @@ +import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TaskView } from '../../base-task'; +import { ActivityCardDto } from '../../activity-card'; +import { TaskBoardCardDto } from '../../task-board'; + +import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService'; +import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter'; + +@ApiTags('crm/tasks/time-board') +@Controller() +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class GetTimeBoardItemController { + constructor(private readonly service: TimeBoardService) {} + + @ApiCreatedResponse({ description: 'Task or activity' }) + @Post('/crm/tasks/by_time/:type/:id') + public async getTimeBoardItem( + @CurrentAuth() { account, user }: AuthData, + @Param('type') type: TaskView, + @Param('id', ParseIntPipe) id: number, + @Body() filter: TimeBoardFilter, + ): Promise { + return this.service.getTimeBoardItem(account, user, type, id, filter); + } +} diff --git a/backend/src/CRM/Controller/TimeBoard/GetTimeBoardMetaController.ts b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardMetaController.ts new file mode 100644 index 0000000..2008838 --- /dev/null +++ b/backend/src/CRM/Controller/TimeBoard/GetTimeBoardMetaController.ts @@ -0,0 +1,26 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TimeBoardService } from '../../Service/TimeBoard/TimeBoardService'; +import { TimeBoardMeta } from '../../Service/TimeBoard/TimeBoardMeta'; +import { TimeBoardFilter } from '../../Service/TimeBoard/TimeBoardFilter'; + +@ApiTags('crm/tasks/time-board') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class GetTimeBoardMetaController { + constructor(private readonly service: TimeBoardService) {} + + @ApiCreatedResponse({ description: 'Meta for time board', type: TimeBoardMeta }) + @Post('/crm/tasks/by_time/meta') + public async getTimeBoardMeta( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: TimeBoardFilter, + ): Promise { + return this.service.getTimeBoardMeta(accountId, user, filter); + } +} diff --git a/backend/src/CRM/Model/Entity/Entity.ts b/backend/src/CRM/Model/Entity/Entity.ts new file mode 100644 index 0000000..31bbb96 --- /dev/null +++ b/backend/src/CRM/Model/Entity/Entity.ts @@ -0,0 +1,177 @@ +import { Column, Entity as OrmEntity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; + +import { UpdateEntityDto } from '../../Service/Entity/Dto/UpdateEntityDto'; +import { EntityDto } from '../../Service/Entity/Dto/EntityDto'; + +@OrmEntity() +export class Entity implements Authorizable { + @Column() + accountId: number; + + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column() + entityTypeId: number; + + @Column() + responsibleUserId: number; + + @Column() + boardId: number | null; + + @Column() + stageId: number | null; + + @Column() + createdBy: number; + + @Column({ type: 'jsonb' }) + participantIds: number[] | null; + + @Column({ type: 'double precision' }) + weight: number; + + @Column({ default: false }) + focused: boolean; + + @Column({ nullable: true }) + copiedFrom: number | null; + + @Column({ nullable: true }) + copiedCount: number | null; + + @Column({ nullable: true }) + closedAt: Date | null; + + @Column() + createdAt: Date; + + @Column() + updatedAt: Date; + + @Column({ + type: 'numeric', + default: 0, + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + value: number; + + constructor( + accountId: number, + name: string, + entityTypeId: number, + responsibleUserId: number, + boardId: number | null, + stageId: number | null, + createdBy: number, + weight: number, + focused: boolean, + closedAt: Date | null, + updatedAt: Date | null, + createdAt: Date | null, + participantIds: number[] | null, + copiedFrom: number | null, + copiedCount: number | null, + value = 0, + ) { + this.accountId = accountId; + this.name = name; + this.entityTypeId = entityTypeId; + this.responsibleUserId = responsibleUserId; + this.boardId = boardId; + this.stageId = stageId; + this.createdBy = createdBy; + this.weight = weight; + this.focused = focused; + this.closedAt = closedAt; + this.participantIds = participantIds; + this.copiedFrom = copiedFrom; + this.copiedCount = copiedCount; + this.createdAt = createdAt ?? DateUtil.now(); + this.updatedAt = updatedAt ?? createdAt ?? DateUtil.now(); + this.value = value; + } + + toSimpleDto(): EntityDto { + return new EntityDto( + this.id, + this.name, + this.entityTypeId, + this.responsibleUserId, + this.boardId, + this.stageId, + this.createdBy, + this.weight, + this.focused, + [], + [], + [], + null, + this.createdAt.toISOString(), + this.updatedAt?.toISOString() ?? null, + this.closedAt?.toISOString() ?? null, + this.copiedFrom, + this.copiedCount, + null, + null, + ); + } + + copy(): Entity { + this.copiedCount = (this.copiedCount ?? 0) + 1; + + return new Entity( + this.accountId, + this.name, + this.entityTypeId, + this.responsibleUserId, + this.boardId, + this.stageId, + this.createdBy, + this.weight, + this.focused, + this.closedAt, + this.updatedAt, + this.createdAt, + this.participantIds, + this.id, + this.copiedCount, + ); + } + + update(dto: UpdateEntityDto): Entity { + this.name = dto.name !== undefined ? dto.name : this.name; + this.responsibleUserId = dto.responsibleUserId !== undefined ? dto.responsibleUserId : this.responsibleUserId; + this.boardId = dto.boardId !== undefined ? dto.boardId : this.boardId; + this.stageId = dto.stageId !== undefined ? dto.stageId : this.stageId; + this.closedAt = dto.closedAt !== undefined ? dto.closedAt : this.closedAt; + this.focused = dto.focused !== undefined ? dto.focused : this.focused; + + this.updatedAt = DateUtil.now(); + + return this; + } + + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.EntityType, + id: this.entityTypeId, + ownerId: this.responsibleUserId, + createdBy: this.createdBy, + participantIds: this.participantIds, + }; + } +} diff --git a/backend/src/CRM/Model/ExternalEntity/ExternalEntity.ts b/backend/src/CRM/Model/ExternalEntity/ExternalEntity.ts new file mode 100644 index 0000000..777f0d4 --- /dev/null +++ b/backend/src/CRM/Model/ExternalEntity/ExternalEntity.ts @@ -0,0 +1,49 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { UIDataRecord } from './UIDataRecord'; + +@Entity() +export class ExternalEntity { + @PrimaryColumn() + id: number; + + @Column() + entityId: number; + + @Column() + url: string; + + @Column({ nullable: true }) + system: number | null; + + @Column({ type: 'jsonb', nullable: true }) + rawData: object | null; + + @Column({ type: 'jsonb', nullable: true }) + uiData: UIDataRecord[] | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + entityId: number, + url: string, + system: number | null = null, + rawData: object | null = null, + uiData: UIDataRecord[] | null = null, + ) { + this.accountId = accountId; + this.entityId = entityId; + this.url = url; + this.system = system; + this.rawData = rawData; + this.uiData = uiData; + this.createdAt = DateUtil.now(); + } +} diff --git a/backend/src/CRM/Model/ExternalEntity/ExternalSystem.ts b/backend/src/CRM/Model/ExternalEntity/ExternalSystem.ts new file mode 100644 index 0000000..9cdfd7a --- /dev/null +++ b/backend/src/CRM/Model/ExternalEntity/ExternalSystem.ts @@ -0,0 +1,16 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class ExternalSystem { + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column() + code: string; + + @Column('character varying', { array: true }) + urlTemplates: string[]; +} diff --git a/backend/src/CRM/Model/ExternalEntity/ExternalSystemCode.ts b/backend/src/CRM/Model/ExternalEntity/ExternalSystemCode.ts new file mode 100644 index 0000000..c9858e2 --- /dev/null +++ b/backend/src/CRM/Model/ExternalEntity/ExternalSystemCode.ts @@ -0,0 +1,3 @@ +export enum ExternalSystemCode { + SalesForce = 'salesforce', +} diff --git a/backend/src/CRM/Model/ExternalEntity/UIDataRecord.ts b/backend/src/CRM/Model/ExternalEntity/UIDataRecord.ts new file mode 100644 index 0000000..aa59687 --- /dev/null +++ b/backend/src/CRM/Model/ExternalEntity/UIDataRecord.ts @@ -0,0 +1,12 @@ +export class UIDataRecord { + key: string; + label: string; + value: any; + sortOrder: number; + constructor(key: string, label: string, value: any, sortOrder: number) { + this.key = key; + this.label = label; + this.value = value; + this.sortOrder = sortOrder; + } +} diff --git a/backend/src/CRM/Model/FileLink/FileLink.ts b/backend/src/CRM/Model/FileLink/FileLink.ts new file mode 100644 index 0000000..aeac33c --- /dev/null +++ b/backend/src/CRM/Model/FileLink/FileLink.ts @@ -0,0 +1,64 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { FileLinkSource } from '@/common'; + +import { imageMimeTypes, MimeType } from '@/modules/storage/enums/mime-type.enum'; + +@Entity() +export class FileLink { + @PrimaryGeneratedColumn() + id: number; + + @Column() + sourceType: FileLinkSource; + + @Column() + sourceId: number; + + @Column() + fileId: string; + + @Column() + fileName: string; + + @Column() + fileSize: number; + + @Column() + fileType: string; + + @Column() + createdBy: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + sourceType: FileLinkSource, + sourceId: number, + fileId: string, + fileName: string, + fileSize: number, + fileType: string, + createdBy: number, + createdAt: Date, + ) { + this.accountId = accountId; + this.sourceType = sourceType; + this.sourceId = sourceId; + this.fileId = fileId; + this.fileName = fileName; + this.fileSize = fileSize; + this.fileType = fileType; + this.createdBy = createdBy; + this.createdAt = createdAt; + } + + public isImage(): boolean { + return imageMimeTypes.includes(this.fileType as MimeType); + } +} diff --git a/backend/src/CRM/Salesforce/Controller/Auth/AuthCallbackController.ts b/backend/src/CRM/Salesforce/Controller/Auth/AuthCallbackController.ts new file mode 100644 index 0000000..6f7629e --- /dev/null +++ b/backend/src/CRM/Salesforce/Controller/Auth/AuthCallbackController.ts @@ -0,0 +1,24 @@ +import { Controller, Get, Query, Redirect } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { Subdomain } from '@/common'; + +import { SalesforceIntegrationService } from '../../Service/SalesforceIntegrationService'; + +@ApiExcludeController(true) +@Controller() +export class AuthCallbackController { + constructor(private readonly integrationService: SalesforceIntegrationService) {} + + @Redirect() + @Get('/integration/salesforce/auth/callback') + public async callback( + @Subdomain() subdomain: string | null, + @Query('code') code: string, + @Query('state') state: string, + ) { + const redirectUrl = await this.integrationService.processAuthCode(subdomain, code, state); + + return { url: redirectUrl, statusCode: 302 }; + } +} diff --git a/backend/src/CRM/Salesforce/Controller/Auth/AuthConnectController.ts b/backend/src/CRM/Salesforce/Controller/Auth/AuthConnectController.ts new file mode 100644 index 0000000..9bba3bf --- /dev/null +++ b/backend/src/CRM/Salesforce/Controller/Auth/AuthConnectController.ts @@ -0,0 +1,20 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { SalesforceIntegrationService } from '../../Service/SalesforceIntegrationService'; + +@ApiTags('integration/salesforce') +@Controller() +@JwtAuthorized({ prefetch: { account: true } }) +export class AuthConnectController { + constructor(private readonly integrationService: SalesforceIntegrationService) {} + + @Get('/integration/salesforce/auth/connect/:id') + public async connect(@CurrentAuth() { account }: AuthData, @Param('id') id: string) { + return await this.integrationService.getAuthorizeUrl(account.subdomain, id); + } +} diff --git a/backend/src/CRM/Salesforce/Controller/Auth/AuthDisconnectController.ts b/backend/src/CRM/Salesforce/Controller/Auth/AuthDisconnectController.ts new file mode 100644 index 0000000..47b7a17 --- /dev/null +++ b/backend/src/CRM/Salesforce/Controller/Auth/AuthDisconnectController.ts @@ -0,0 +1,20 @@ +import { Controller, Param, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { SalesforceIntegrationService } from '../../Service/SalesforceIntegrationService'; + +@ApiTags('integration/salesforce') +@Controller() +@JwtAuthorized() +export class AuthDisconnectController { + constructor(private readonly integrationService: SalesforceIntegrationService) {} + + @Post('/integration/salesforce/auth/disconnect/:id') + public async connect(@CurrentAuth() { accountId }: AuthData, @Param('id') id: string) { + return await this.integrationService.disconnect(accountId, id); + } +} diff --git a/backend/src/CRM/Salesforce/Controller/Settings/CreateSettingsController.ts b/backend/src/CRM/Salesforce/Controller/Settings/CreateSettingsController.ts new file mode 100644 index 0000000..8354485 --- /dev/null +++ b/backend/src/CRM/Salesforce/Controller/Settings/CreateSettingsController.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { SalesforceSettingsService } from '../../Service/Settings/SalesforceSettingsService'; +import { CreateSalesforceSettingsDto } from '../../Service/Settings/CreateSalesforceSettingsDto'; +import { SalesforceSettingsDto } from '../../Service/Settings/SalesforceSettingsDto'; + +@ApiTags('integration/salesforce/settings') +@Controller() +@JwtAuthorized() +export class CreateSettingsController { + constructor(private readonly settingsService: SalesforceSettingsService) {} + + @Post('/integration/salesforce/settings') + @ApiCreatedResponse({ description: 'SalesForce settings', type: SalesforceSettingsDto }) + public async createIntegration( + @CurrentAuth() { accountId }: AuthData, + @Body() dto: CreateSalesforceSettingsDto, + ): Promise { + const settings = await this.settingsService.create(accountId, dto); + return SalesforceSettingsDto.create(settings); + } +} diff --git a/backend/src/CRM/Salesforce/Controller/Settings/DeleteSettingsController.ts b/backend/src/CRM/Salesforce/Controller/Settings/DeleteSettingsController.ts new file mode 100644 index 0000000..0ed6b2a --- /dev/null +++ b/backend/src/CRM/Salesforce/Controller/Settings/DeleteSettingsController.ts @@ -0,0 +1,20 @@ +import { Controller, Delete, Param } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { SalesforceSettingsService } from '../../Service/Settings/SalesforceSettingsService'; + +@ApiTags('integration/salesforce/settings') +@Controller() +@JwtAuthorized() +export class DeleteSettingsController { + constructor(private readonly settingsService: SalesforceSettingsService) {} + + @Delete('/integration/salesforce/settings/:id') + public async createIntegration(@CurrentAuth() { accountId }: AuthData, @Param('id') id: string) { + await this.settingsService.delete(accountId, id); + } +} diff --git a/backend/src/CRM/Salesforce/Controller/Settings/GetSettingsController.ts b/backend/src/CRM/Salesforce/Controller/Settings/GetSettingsController.ts new file mode 100644 index 0000000..f938179 --- /dev/null +++ b/backend/src/CRM/Salesforce/Controller/Settings/GetSettingsController.ts @@ -0,0 +1,23 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { SalesforceSettingsService } from '../../Service/Settings/SalesforceSettingsService'; +import { SalesforceSettingsDto } from '../../Service/Settings/SalesforceSettingsDto'; + +@ApiTags('integration/salesforce/settings') +@Controller() +@JwtAuthorized() +export class GetSettingsController { + constructor(private readonly settingsService: SalesforceSettingsService) {} + + @Get('/integration/salesforce/settings') + @ApiOkResponse({ description: 'SalesForce settings for account', type: [SalesforceSettingsDto] }) + public async createIntegration(@CurrentAuth() { accountId }: AuthData): Promise { + const allSettings = await this.settingsService.getAll(accountId); + return allSettings.map((settings) => SalesforceSettingsDto.create(settings)); + } +} diff --git a/backend/src/CRM/Salesforce/Model/IntegrationData.ts b/backend/src/CRM/Salesforce/Model/IntegrationData.ts new file mode 100644 index 0000000..a385293 --- /dev/null +++ b/backend/src/CRM/Salesforce/Model/IntegrationData.ts @@ -0,0 +1,11 @@ +import { UIDataRecord } from '../../Model/ExternalEntity/UIDataRecord'; + +export class IntegrationData { + rawData: object; + uiData: UIDataRecord[]; + + constructor(rawData: object, uiData: UIDataRecord[]) { + this.rawData = rawData; + this.uiData = uiData; + } +} diff --git a/backend/src/CRM/Salesforce/Model/Settings/SalesforceSettings.ts b/backend/src/CRM/Salesforce/Model/Settings/SalesforceSettings.ts new file mode 100644 index 0000000..8a7c137 --- /dev/null +++ b/backend/src/CRM/Salesforce/Model/Settings/SalesforceSettings.ts @@ -0,0 +1,35 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +@Entity('salesforce_settings') +export class SalesforceSettings { + @PrimaryGeneratedColumn() + id: string; + + @Column() + domain: string; + + @Column() + key: string; + + @Column() + secret: string; + + @Column({ nullable: true }) + refreshToken: string | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, domain: string, key: string, secret: string) { + this.accountId = accountId; + this.domain = domain; + this.key = key; + this.secret = secret; + this.createdAt = DateUtil.now(); + } +} diff --git a/backend/src/CRM/Salesforce/SalesforceModule.ts b/backend/src/CRM/Salesforce/SalesforceModule.ts new file mode 100644 index 0000000..208c31c --- /dev/null +++ b/backend/src/CRM/Salesforce/SalesforceModule.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { SalesforceSettings } from './Model/Settings/SalesforceSettings'; +import { SalesforceIntegrationService } from './Service/SalesforceIntegrationService'; +import { SalesforceSettingsService } from './Service/Settings/SalesforceSettingsService'; +import { CreateSettingsController } from './Controller/Settings/CreateSettingsController'; +import { GetSettingsController } from './Controller/Settings/GetSettingsController'; +import { DeleteSettingsController } from './Controller/Settings/DeleteSettingsController'; +import { AuthCallbackController } from './Controller/Auth/AuthCallbackController'; +import { AuthConnectController } from './Controller/Auth/AuthConnectController'; +import { AuthDisconnectController } from './Controller/Auth/AuthDisconnectController'; + +@Module({ + imports: [TypeOrmModule.forFeature([SalesforceSettings]), IAMModule], + providers: [SalesforceIntegrationService, SalesforceSettingsService], + controllers: [ + CreateSettingsController, + GetSettingsController, + DeleteSettingsController, + AuthConnectController, + AuthCallbackController, + AuthDisconnectController, + ], + exports: [SalesforceIntegrationService], +}) +export class SalesforceModule {} diff --git a/backend/src/CRM/Salesforce/Service/SalesforceIntegrationService.ts b/backend/src/CRM/Salesforce/Service/SalesforceIntegrationService.ts new file mode 100644 index 0000000..275aa20 --- /dev/null +++ b/backend/src/CRM/Salesforce/Service/SalesforceIntegrationService.ts @@ -0,0 +1,222 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { InjectRepository } from '@nestjs/typeorm'; +import { IsNull, Not, Repository } from 'typeorm'; +import { lastValueFrom } from 'rxjs'; +import * as qs from 'qs'; + +import { UrlGeneratorService, FrontendRoute, NotFoundError, formatUrlQuery } from '@/common'; + +import { UIDataRecord } from '../../Model/ExternalEntity/UIDataRecord'; + +import { SalesforceUIFields } from '../Util/SalesforceUIFields'; +import { SalesforceCodeToType } from '../Util/SalesforceCodeToType'; +import { SalesforceSettings } from '../Model/Settings/SalesforceSettings'; +import { IntegrationData } from '../Model/IntegrationData'; + +const CALLBACK_PATH = '/api/integration/salesforce/auth/callback'; +const SaleForceUrls = { + services(domain: string) { + return `https://${domain}.my.salesforce.com/services`; + }, + auth(domain: string) { + return `${this.services(domain)}/oauth2`; + }, + token(domain: string) { + return `${this.auth(domain)}/token`; + }, + authorize(domain: string) { + return `${this.auth(domain)}/authorize`; + }, + sobjects(domain: string, type: string, id: string) { + return `${this.services(domain)}/data/v56.0/sobjects/${type}/${id}`; + }, +} as const; + +interface SalesforceObject { + type: string; + id: string; +} +interface ConnectionSettings extends SalesforceSettings { + accessToken: string; +} + +@Injectable() +export class SalesforceIntegrationService { + private readonly logger = new Logger(SalesforceIntegrationService.name); + constructor( + private readonly httpService: HttpService, + @InjectRepository(SalesforceSettings) + private readonly repository: Repository, + private readonly urlGenerator: UrlGeneratorService, + ) {} + + public async getAuthorizeUrl(subdomain: string, id: string) { + const settings = await this.repository.findOneBy({ id }); + if (!settings) { + throw NotFoundError.withId(SalesforceSettings, id); + } + + return formatUrlQuery(SaleForceUrls.authorize(settings.domain), { + client_id: settings.key, + redirect_uri: this.getCallbackUrl(subdomain), + state: settings.id, + response_type: 'code', + }); + } + + public async disconnect(accountId: number, id: string) { + await this.repository.update({ accountId, id }, { refreshToken: null }); + } + + public async processAuthCode(subdomain: string, code: string, state: string): Promise { + if (!state) { + return this.getAuthRedirectUrl(subdomain, false, `SalesForce authentication callback state doesn't set`); + } + const settings = await this.repository.findOneBy({ id: state }); + if (!settings) { + return this.getAuthRedirectUrl(subdomain, false, `SalesForce settings with id=${state} don't founded`); + } + + const response = await this.callWithCatch(async () => { + const { data } = await lastValueFrom( + this.httpService.post( + SaleForceUrls.token(settings.domain), + qs.stringify({ + grant_type: 'authorization_code', + code: code, + client_id: settings.key, + client_secret: settings.secret, + redirect_uri: this.getCallbackUrl(subdomain), + }), + ), + ); + return data; + }); + + if (response?.refresh_token) { + await this.repository.update(settings.id, { refreshToken: response.refresh_token }); + return this.getAuthRedirectUrl(subdomain, true); + } + + return this.getAuthRedirectUrl(subdomain, false, `Can't get Salesforce refresh token`); + } + + private getAuthRedirectUrl(subdomain: string, result: boolean, message?: string): string { + return this.urlGenerator.createUrl({ + route: FrontendRoute.settings.salesforce(), + subdomain, + query: { result: String(result), message }, + }); + } + + public async getDataFromUrl(accountId: number, url: string, entityUrl?: string): Promise { + const settings = await this.getConnectionSettings(accountId); + const urlParam = this.parseUrl(url); + if (urlParam && settings) { + const objectData = await this.getDataForObject(settings, urlParam.type, urlParam.id); + const valuableData = Object.fromEntries(Object.entries(objectData).filter(([, value]) => value !== null)); + const uiData = Object.entries(valuableData) + .filter(([key]) => SalesforceUIFields.get(urlParam.type).includes(key)) + .map( + ([key, value]) => + new UIDataRecord( + key, + key.replace(/([A-Z])/g, ' $1'), + value, + SalesforceUIFields.get(urlParam.type).findIndex((field) => key === field), + ), + ); + uiData.push(new UIDataRecord('SalesforceType', 'Salesforce Type', urlParam.type, -1)); + const ownerId = uiData.find((item) => item.key === 'OwnerId'); + const ownerData = ownerId ? await this.getDataForObject(settings, 'user', ownerId.value) : null; + if (ownerData) { + uiData.push(new UIDataRecord('OwnerName', 'Owner Name', ownerData['Name'], ownerId.sortOrder)); + const ownerIdIdx = uiData.findIndex((item) => item.key === 'OwnerId'); + uiData.splice(ownerIdIdx, 1); + } + if (entityUrl) { + await this.setDataForObject(settings, urlParam.type, urlParam.id, { AmworkEntityUrl__c: entityUrl }); + } + return new IntegrationData(valuableData, uiData); + } + return null; + } + + private getCallbackUrl(subdomain: string) { + return this.urlGenerator.createUrl({ route: CALLBACK_PATH, subdomain }); + } + + private parseUrl(originalUrl: string): SalesforceObject | null { + const { pathname } = new URL(originalUrl); + const match = pathname.match(/\/lightning\/r\/([^/]*)\/([a-zA-Z0-9]{15,18})\/view/); + if (match) { + return { type: match[1], id: match[2] }; + } else { + const match = pathname.match(/\/([a-zA-Z0-9]{15,18})$/); + if (match) { + const type = this.findObjectType(match[1].substring(0, 3)); + return { type, id: match[1] }; + } + } + return null; + } + + private findObjectType(typeCode: string): string { + return SalesforceCodeToType.get(typeCode); + } + + private getDataForObject(settings: ConnectionSettings, type: string, id: string): Promise { + return this.callWithCatch(async () => { + const { data } = await lastValueFrom( + this.httpService.get(SaleForceUrls.sobjects(settings.domain, type, id), { + headers: { Authorization: `Bearer ${settings.accessToken}` }, + }), + ); + return data; + }); + } + + private setDataForObject(settings: ConnectionSettings, type: string, id: string, data: object) { + this.callWithCatch(async () => { + await lastValueFrom( + this.httpService.patch(SaleForceUrls.sobjects(settings.domain, type, id), data, { + headers: { Authorization: `Bearer ${settings.accessToken}` }, + }), + ); + }); + } + + private async getConnectionSettings(accountId: number, domain?: string): Promise { + const settingsList = await this.repository.find({ where: { accountId, domain, refreshToken: Not(IsNull()) } }); + for (const settings of settingsList) { + const response = await this.callWithCatch(async () => { + const { data } = await lastValueFrom( + this.httpService.post( + SaleForceUrls.token(settings.domain), + qs.stringify({ + grant_type: 'refresh_token', + refresh_token: settings.refreshToken, + client_id: settings.key, + client_secret: settings.secret, + }), + ), + ); + return data; + }); + if (response?.access_token) { + return { ...settings, accessToken: response.access_token }; + } + } + return null; + } + + private async callWithCatch(func: () => Promise): Promise { + try { + return await func(); + } catch (e) { + this.logger.error(`Error in SalesforceIntegration`, (e as Error)?.stack); + return {}; + } + } +} diff --git a/backend/src/CRM/Salesforce/Service/Settings/CreateSalesforceSettingsDto.ts b/backend/src/CRM/Salesforce/Service/Settings/CreateSalesforceSettingsDto.ts new file mode 100644 index 0000000..2549c81 --- /dev/null +++ b/backend/src/CRM/Salesforce/Service/Settings/CreateSalesforceSettingsDto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class CreateSalesforceSettingsDto { + @ApiProperty() + @IsString() + domain: string; + + @ApiProperty() + @IsString() + key: string; + + @ApiProperty() + @IsString() + secret: string; +} diff --git a/backend/src/CRM/Salesforce/Service/Settings/SalesforceSettingsDto.ts b/backend/src/CRM/Salesforce/Service/Settings/SalesforceSettingsDto.ts new file mode 100644 index 0000000..6d941d1 --- /dev/null +++ b/backend/src/CRM/Salesforce/Service/Settings/SalesforceSettingsDto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { SalesforceSettings } from '../../Model/Settings/SalesforceSettings'; + +export class SalesforceSettingsDto { + @ApiProperty() + id: string; + + @ApiProperty() + domain: string; + + @ApiProperty() + key: string; + + @ApiProperty() + isConnected: boolean; + + constructor(id: string, domain: string, key: string, isConnected: boolean) { + this.id = id; + this.domain = domain; + this.key = key; + this.isConnected = isConnected; + } + + public static create(settings: SalesforceSettings) { + return new SalesforceSettingsDto(settings.id, settings.domain, settings.key, !!settings.refreshToken); + } +} diff --git a/backend/src/CRM/Salesforce/Service/Settings/SalesforceSettingsService.ts b/backend/src/CRM/Salesforce/Service/Settings/SalesforceSettingsService.ts new file mode 100644 index 0000000..dc97e82 --- /dev/null +++ b/backend/src/CRM/Salesforce/Service/Settings/SalesforceSettingsService.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { SalesforceSettings } from '../../Model/Settings/SalesforceSettings'; +import { CreateSalesforceSettingsDto } from './CreateSalesforceSettingsDto'; + +@Injectable() +export class SalesforceSettingsService { + constructor( + @InjectRepository(SalesforceSettings) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, dto: CreateSalesforceSettingsDto): Promise { + return await this.repository.save(new SalesforceSettings(accountId, dto.domain, dto.key, dto.secret)); + } + + public async getAll(accountId: number): Promise { + return await this.repository.findBy({ accountId }); + } + + public async delete(accountId: number, id: string) { + await this.repository.delete({ accountId, id }); + } +} diff --git a/backend/src/CRM/Salesforce/Util/SalesforceCodeToType.ts b/backend/src/CRM/Salesforce/Util/SalesforceCodeToType.ts new file mode 100644 index 0000000..ec72e87 --- /dev/null +++ b/backend/src/CRM/Salesforce/Util/SalesforceCodeToType.ts @@ -0,0 +1,34 @@ +export const SalesforceCodeToType = new Map([ + ['001', 'Account'], + ['007', 'Activity'], + ['806', 'Approval'], + ['00P', 'Attachment'], + ['00v', 'CampaignMember'], + ['701', 'Campaigns'], + ['500', 'Case'], + ['003', 'Contact'], + ['800', 'Contract'], + ['01Z', 'Dashboard'], + ['01a', 'DashboardComponent'], + ['00X', 'EmailTemplate'], + ['00U', 'Event'], + ['00l', 'Folder'], + ['00G', 'Group'], + ['00h', 'PageLayout'], + ['00Q', 'Lead'], + ['00B', 'ListView'], + ['006', 'Opportunity'], + ['00k', 'OpportunityLineItem'], + ['801', 'Order'], + ['802', 'OrderItem'], + ['00i', 'Pricebook'], + ['01s', 'Pricebook2'], + ['00j', 'Product'], + ['01t', 'Product2'], + ['00e', 'Profile'], + ['00O', 'Report'], + ['02c', 'SharingRule'], + ['00T', 'Task'], + ['005', 'User'], + ['00E', 'UserRole'], +]); diff --git a/backend/src/CRM/Salesforce/Util/SalesforceUIFields.ts b/backend/src/CRM/Salesforce/Util/SalesforceUIFields.ts new file mode 100644 index 0000000..25157f4 --- /dev/null +++ b/backend/src/CRM/Salesforce/Util/SalesforceUIFields.ts @@ -0,0 +1,36 @@ +const DefaultUIFields = ['OwnerId', 'Name', 'Description']; + +export const SalesforceUIFields = new Map([ + ['Account', [...DefaultUIFields, 'Phone', 'Website', 'Industry', 'Type']], + ['Activity', [...DefaultUIFields, 'Subject', 'Status', 'ActivityDate', 'StartDateTime', 'EndDateTime']], + ['Approval', DefaultUIFields], + ['Attachment', DefaultUIFields], + ['CampaignMember', DefaultUIFields], + ['Campaigns', DefaultUIFields], + ['Case', DefaultUIFields], + ['Contact', [...DefaultUIFields, 'Email', 'Phone', 'Title']], + ['Contract', DefaultUIFields], + ['Dashboard', DefaultUIFields], + ['DashboardComponent', DefaultUIFields], + ['EmailTemplate', DefaultUIFields], + ['Event', DefaultUIFields], + ['Folder', DefaultUIFields], + ['Group', DefaultUIFields], + ['PageLayout', DefaultUIFields], + ['Lead', [...DefaultUIFields, 'Email', 'Phone', 'Company', 'Title', 'Status', 'Rating']], + ['ListView', DefaultUIFields], + ['Opportunity', [...DefaultUIFields, 'StageName', 'Amount', 'Probability', 'Type', 'ForecastCategory', 'CloseDate']], + ['OpportunityLineItem', DefaultUIFields], + ['Order', DefaultUIFields], + ['OrderItem', DefaultUIFields], + ['Pricebook', DefaultUIFields], + ['Pricebook2', DefaultUIFields], + ['Product', DefaultUIFields], + ['Product2', DefaultUIFields], + ['Profile', DefaultUIFields], + ['Report', DefaultUIFields], + ['SharingRule', DefaultUIFields], + ['Task', [...DefaultUIFields, 'Subject', 'Status', 'Priority', 'DueDate', 'IsClosed']], + ['User', [...DefaultUIFields, 'Email', 'Phone', 'Company', 'Title', 'Username', 'IsActive']], + ['UserRole', DefaultUIFields], +]); diff --git a/backend/src/CRM/Service/BaseTaskBoard/BaseTaskBoardFilter.ts b/backend/src/CRM/Service/BaseTaskBoard/BaseTaskBoardFilter.ts new file mode 100644 index 0000000..fc58e22 --- /dev/null +++ b/backend/src/CRM/Service/BaseTaskBoard/BaseTaskBoardFilter.ts @@ -0,0 +1,58 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsObject, IsOptional, IsString } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +import { TaskSorting } from './TaskSorting'; + +export class BaseTaskBoardFilter { + @ApiPropertyOptional({ description: 'Sorting', enum: TaskSorting }) + @IsOptional() + @IsEnum(TaskSorting) + sorting?: TaskSorting | null; + + @ApiPropertyOptional({ description: 'Search text in task' }) + @IsOptional() + @IsString() + search?: string | null; + + @ApiPropertyOptional({ description: 'Show resolved tasks' }) + @IsOptional() + @IsBoolean() + showResolved?: boolean | null; + + @ApiPropertyOptional({ description: 'Created by User IDs', type: [Number] }) + @IsOptional() + @IsArray() + createdBy?: number[] | null; + + @ApiPropertyOptional({ description: 'Owner (Responsible) by User IDs', type: [Number] }) + @IsOptional() + @IsArray() + ownerIds?: number[] | null; + + @ApiPropertyOptional({ description: 'Entity ids', type: [Number] }) + @IsOptional() + @IsArray() + entityIds?: number[] | null; + + @ApiPropertyOptional({ description: 'Created at', type: DatePeriodFilter }) + @IsOptional() + @IsObject() + createdAt?: DatePeriodFilter; + + @ApiPropertyOptional({ description: 'Task start date', type: DatePeriodFilter }) + @IsOptional() + @IsObject() + startDate?: DatePeriodFilter; + + @ApiPropertyOptional({ description: 'Task end date', type: DatePeriodFilter }) + @IsOptional() + @IsObject() + endDate?: DatePeriodFilter; + + @ApiPropertyOptional({ description: 'Task resolved date', type: DatePeriodFilter }) + @IsOptional() + @IsObject() + resolvedDate?: DatePeriodFilter; +} diff --git a/backend/src/CRM/Service/BaseTaskBoard/TaskBoardQueryHelper.ts b/backend/src/CRM/Service/BaseTaskBoard/TaskBoardQueryHelper.ts new file mode 100644 index 0000000..98a6f31 --- /dev/null +++ b/backend/src/CRM/Service/BaseTaskBoard/TaskBoardQueryHelper.ts @@ -0,0 +1,106 @@ +import { Brackets, type Repository, type SelectQueryBuilder } from 'typeorm'; + +import { DatePeriod, DatePeriodFilter, intersection } from '@/common'; + +import { TaskSorting } from './TaskSorting'; +import { BaseTaskBoardFilter } from './BaseTaskBoardFilter'; + +export class TaskBoardQueryHelper { + public static createBoardQueryBuilder( + accountId: number, + userId: number, + repository: Repository, + filter: BaseTaskBoardFilter, + responsibles: number[], + withOrder: boolean, + titleField: string, + ): SelectQueryBuilder { + const qb = repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + const users = intersection(filter.ownerIds, responsibles); + if (users) { + if (users.length > 0) { + qb.andWhere( + new Brackets((qb1) => { + qb1.where('responsible_user_id IN (:...users)', { users }); + if (!filter.ownerIds) { + qb1.orWhere('created_by = :userId', { userId }); + } + }), + ); + } else { + qb.andWhere( + new Brackets((qb1) => { + qb1.where('responsible_user_id IS NULL'); + if (!filter.ownerIds) { + qb1.orWhere('created_by = :userId', { userId }); + } + }), + ); + } + } + + if (filter.createdBy?.length) { + qb.andWhere('created_by IN (:...createdBy)', { createdBy: filter.createdBy }); + } + + if (filter.search) { + qb.andWhere(`${titleField} ILIKE :search`, { search: `%${filter.search}%` }); + } + + if (filter.entityIds) { + qb.andWhere('entity_id IN (:...entityIds)', { entityIds: filter.entityIds }); + } + + if (filter.createdAt) { + TaskBoardQueryHelper.addDateCondition(qb, filter.createdAt, 'created_at'); + } + + if (filter.startDate) { + TaskBoardQueryHelper.addDateCondition(qb, filter.startDate, 'start_date'); + } + + if (filter.endDate) { + TaskBoardQueryHelper.addDateCondition(qb, filter.endDate, 'end_date'); + } + + if (filter.resolvedDate) { + TaskBoardQueryHelper.addDateCondition(qb, filter.resolvedDate, 'resolved_date'); + } + + if (withOrder) { + TaskBoardQueryHelper.addBoardOrderBy(qb, filter.sorting); + } + + return qb; + } + + private static addDateCondition(qb: SelectQueryBuilder, dateFilter: DatePeriodFilter, fieldName: string) { + const dates = DatePeriod.fromFilter(dateFilter); + if (dates.from) { + qb.andWhere(`${fieldName} >= :${fieldName}from`, { [`${fieldName}from`]: dates.from }); + } + if (dates.to) { + qb.andWhere(`${fieldName} <= :${fieldName}to`, { [`${fieldName}to`]: dates.to }); + } + return qb; + } + + public static addBoardOrderBy(qb: SelectQueryBuilder, sorting: TaskSorting | null | undefined) { + switch (sorting) { + case TaskSorting.MANUAL: + qb.orderBy('weight', 'ASC'); + break; + case TaskSorting.CREATED_ASC: + qb.orderBy('created_at', 'ASC'); + break; + case TaskSorting.CREATED_DESC: + qb.orderBy('created_at', 'DESC'); + break; + default: + qb.orderBy('weight', 'ASC'); + break; + } + return qb.addOrderBy('id', 'DESC'); + } +} diff --git a/backend/src/CRM/Service/BaseTaskBoard/TaskSorting.ts b/backend/src/CRM/Service/BaseTaskBoard/TaskSorting.ts new file mode 100644 index 0000000..767544d --- /dev/null +++ b/backend/src/CRM/Service/BaseTaskBoard/TaskSorting.ts @@ -0,0 +1,5 @@ +export enum TaskSorting { + MANUAL = 'manual', + CREATED_ASC = 'created_asc', + CREATED_DESC = 'created_desc', +} diff --git a/backend/src/CRM/Service/BaseTaskBoard/UserTimeAllocation.ts b/backend/src/CRM/Service/BaseTaskBoard/UserTimeAllocation.ts new file mode 100644 index 0000000..88fd7ed --- /dev/null +++ b/backend/src/CRM/Service/BaseTaskBoard/UserTimeAllocation.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UserTimeAllocation { + @ApiProperty() + userId: number; + + @ApiProperty() + plannedTime: number; + + constructor(userId: number, plannedTime: number) { + this.userId = userId; + this.plannedTime = plannedTime; + } +} diff --git a/backend/src/CRM/Service/Entity/CommonEntityBoardCardService.ts b/backend/src/CRM/Service/Entity/CommonEntityBoardCardService.ts new file mode 100644 index 0000000..b1f87fb --- /dev/null +++ b/backend/src/CRM/Service/Entity/CommonEntityBoardCardService.ts @@ -0,0 +1,267 @@ +import { Injectable } from '@nestjs/common'; + +import { isUnique } from '@/common'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value/field-value.service'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; + +import { EntityCategory } from '../../common'; +import { Activity } from '../../activity/entities'; +import { ActivityService } from '../../activity/activity.service'; +import { BaseTask } from '../../base-task'; +import { EntityLink } from '../../entity-link/entities'; +import { EntityLinkService } from '../../entity-link/entity-link.service'; +import { EntityType } from '../../entity-type/entities'; +import { EntityTypeLinkService } from '../../entity-type-link/entity-type-link.service'; +import { Task } from '../../task/entities'; +import { TaskService } from '../../task/task.service'; + +import { Entity } from '../../Model/Entity/Entity'; + +import { TaskIndicator } from './enums/task-indicator.enum'; +import { EntityBoardCard } from './Dto/Board/EntityBoardCard'; +import { CommonEntityCard } from './Dto/Board/CommonEntityCard'; +import { EntityService } from './EntityService'; + +@Injectable() +export class CommonEntityBoardCardService { + constructor( + private readonly authService: AuthorizationService, + private readonly entityService: EntityService, + private readonly entityLinkService: EntityLinkService, + private readonly entityTypeLinkService: EntityTypeLinkService, + private readonly fieldService: FieldService, + private readonly fieldValueService: FieldValueService, + private readonly taskService: TaskService, + private readonly activityService: ActivityService, + ) {} + + public async getBoardCard( + accountId: number, + user: User, + entity: Entity, + entityCategory: EntityCategory, + ): Promise { + const links = await this.entityLinkService.findMany({ accountId, sourceId: entity.id }); + const linkedIds = links.map((l) => l.targetId).filter(isUnique); + const linkedEntities = linkedIds.length + ? await this.entityService.findMany(accountId, { entityId: linkedIds }) + : []; + + const linkedEntityTypes = await this.entityTypeLinkService.findMany({ accountId, sourceId: entity.entityTypeId }); + const allowedEntityTypeLinks = ( + await Promise.all( + linkedEntityTypes.map(async (linked) => { + const hasAccess = await this.authService.check({ + action: 'view', + user, + authorizable: EntityType.getAuthorizable(linked.targetId), + }); + return hasAccess ? linked : null; + }), + ) + ).filter(Boolean); + + const sortOrderMap: Record = {}; + allowedEntityTypeLinks.forEach((link) => { + sortOrderMap[link.targetId] = link.sortOrder; + }); + + const filteredLinkedEntities = linkedEntities.filter((e) => + allowedEntityTypeLinks.some((etl) => etl.targetId === e.entityTypeId), + ); + + const priceField = await this.fieldService.findOne({ + accountId, + entityTypeId: entity.entityTypeId, + type: FieldType.Value, + }); + + const taskResponsibles = await this.authService.whoCanView({ user, authorizable: Task.getAuthorizable() }); + const activityResponsibles = await this.authService.whoCanView({ user, authorizable: Activity.getAuthorizable() }); + + return this.createBoardCard({ + accountId, + user, + entity, + entityCategory, + sortOrderMap, + linksCache: links, + entityCache: filteredLinkedEntities, + priceFieldId: priceField?.id, + taskResponsibles, + activityResponsibles, + }); + } + + public async getBoardCards( + accountId: number, + user: User, + entityTypeId: number, + entities: Entity[], + entityCategory: EntityCategory, + ): Promise { + const links = await this.entityLinkService.findMany({ accountId, sourceId: entities.map((e) => e.id) }); + const linkedIds = links.map((l) => l.targetId).filter(isUnique); + const linkedEntities = linkedIds.length + ? await this.entityService.findMany(accountId, { entityId: linkedIds }) + : []; + + const linkedEntityTypes = await this.entityTypeLinkService.findMany({ accountId, sourceId: entityTypeId }); + const allowedEntityTypeLinks = ( + await Promise.all( + linkedEntityTypes.map(async (linked) => { + const hasAccess = await this.authService.check({ + action: 'view', + user, + authorizable: EntityType.getAuthorizable(linked.targetId), + }); + return hasAccess ? linked : null; + }), + ) + ).filter(Boolean); + const sortOrderMap: Record = {}; + allowedEntityTypeLinks.forEach((link) => { + sortOrderMap[link.targetId] = link.sortOrder; + }); + const filteredLinkedEntities = linkedEntities.filter((e) => + allowedEntityTypeLinks.some((etl) => etl.targetId === e.entityTypeId), + ); + + const priceField = await this.fieldService.findOne({ accountId, entityTypeId, type: FieldType.Value }); + + const taskResponsibles = await this.authService.whoCanView({ user, authorizable: Task.getAuthorizable() }); + const activityResponsibles = await this.authService.whoCanView({ user, authorizable: Activity.getAuthorizable() }); + + const cards = await Promise.all( + entities.map(async (entity, idx) => { + const card = await this.createBoardCard({ + accountId, + user, + entity, + entityCategory, + sortOrderMap, + linksCache: links, + entityCache: filteredLinkedEntities, + priceFieldId: priceField?.id, + taskResponsibles, + activityResponsibles, + }); + return { idx, card }; + }), + ); + + return cards.sort((a, b) => a.idx - b.idx).map((c) => c.card); + } + + private async createBoardCard({ + accountId, + user, + entity, + entityCategory, + sortOrderMap, + linksCache, + entityCache, + priceFieldId, + taskResponsibles, + activityResponsibles, + }: { + accountId: number; + user: User; + entity: Entity; + entityCategory: EntityCategory; + sortOrderMap: Record; + linksCache: EntityLink[]; + entityCache: Entity[]; + priceFieldId: number | null | undefined; + taskResponsibles: number[] | undefined; + activityResponsibles: number[] | undefined; + }): Promise { + const targets = linksCache.filter((l) => l.sourceId === entity.id).map((l) => l.targetId); + const linkedEntities = entityCache + .filter((ec) => targets.includes(ec.id)) + .sort((a, b) => (sortOrderMap[a.entityTypeId] || 0) - (sortOrderMap[b.entityTypeId] || 0)); + const price = await this.getPrice(accountId, entity, priceFieldId); + const taskIndicatorColor = await this.getTaskIndicatorColor({ + accountId, + entityId: entity.id, + taskResponsibles, + activityResponsibles, + }); + const userRights = await this.authService.getUserRights({ user, authorizable: entity }); + return new EntityBoardCard({ + id: entity.id, + entityCategory, + boardId: entity.boardId, + stageId: entity.stageId, + price, + weight: entity.weight, + focused: entity.focused, + closedAt: entity.closedAt?.toISOString() ?? null, + data: CommonEntityCard.create( + entity, + linkedEntities.map((le) => le.name), + taskIndicatorColor, + userRights, + ), + }); + } + + private async getPrice( + accountId: number, + entity: Entity, + priceFieldId: number | null | undefined, + ): Promise { + if (!priceFieldId) return null; + + const fieldValue = await this.fieldValueService.findOne({ accountId, entityId: entity.id, fieldId: priceFieldId }); + if (!fieldValue) return null; + + const value = fieldValue.getValue(); + return isNaN(value) ? null : value; + } + + private async getTaskIndicatorColor({ + accountId, + entityId, + taskResponsibles, + activityResponsibles, + }: { + accountId: number; + entityId: number; + taskResponsibles: number[] | undefined; + activityResponsibles: number[] | undefined; + }): Promise { + const tasks: BaseTask[] = + !taskResponsibles || taskResponsibles.length + ? await this.taskService.findMany({ + accountId, + entityId, + responsibles: taskResponsibles, + isResolved: false, + }) + : []; + const activities: BaseTask[] = + !activityResponsibles || activityResponsibles.length + ? await this.activityService.findMany({ + accountId, + entityId, + responsibles: activityResponsibles, + isResolved: false, + }) + : []; + const allTasks = [...tasks, ...activities]; + + if (allTasks.length === 0) return TaskIndicator.Empty; + + const expiredTask = allTasks.find((task) => task.isTaskExpired()); + if (expiredTask) return TaskIndicator.Overdue; + + const todayTask = allTasks.find((task) => task.isTaskToday()); + if (todayTask) return TaskIndicator.Today; + + return TaskIndicator.Upcoming; + } +} diff --git a/backend/src/CRM/Service/Entity/Dto/Batch/delete-entities-batch-filter.dto.ts b/backend/src/CRM/Service/Entity/Dto/Batch/delete-entities-batch-filter.dto.ts new file mode 100644 index 0000000..c809767 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Batch/delete-entities-batch-filter.dto.ts @@ -0,0 +1,12 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +import { EntityBoardCardFilter } from '../../../../Controller/Entity/Board/Filter/EntityBoardCardFilter'; + +export class DeleteEntitiesBatchFilterDto extends EntityBoardCardFilter { + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + @IsNumber({}, { each: true }) + entityIds?: number[]; +} diff --git a/backend/src/CRM/Service/Entity/Dto/Batch/index.ts b/backend/src/CRM/Service/Entity/Dto/Batch/index.ts new file mode 100644 index 0000000..ccdbb5a --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Batch/index.ts @@ -0,0 +1,2 @@ +export * from './delete-entities-batch-filter.dto'; +export * from './update-entities-batch-filter.dto'; diff --git a/backend/src/CRM/Service/Entity/Dto/Batch/update-entities-batch-filter.dto.ts b/backend/src/CRM/Service/Entity/Dto/Batch/update-entities-batch-filter.dto.ts new file mode 100644 index 0000000..2df979c --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Batch/update-entities-batch-filter.dto.ts @@ -0,0 +1,33 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +import { EntityBoardCardFilter } from '../../../../Controller/Entity/Board/Filter/EntityBoardCardFilter'; + +export class UpdateEntitiesBatchFilterDto extends EntityBoardCardFilter { + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + @IsNumber({}, { each: true }) + entityIds?: number[]; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + responsibleUserId?: number | null; + + @ApiPropertyOptional({ type: [Number] }) + @IsOptional() + @IsArray() + @IsNumber({}, { each: true }) + responsibleEntityTypeIds?: number[] | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + boardId?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + stageId?: number | null; +} diff --git a/backend/src/CRM/Service/Entity/Dto/Board/CommonEntityCard.ts b/backend/src/CRM/Service/Entity/Dto/Board/CommonEntityCard.ts new file mode 100644 index 0000000..8e4d0a2 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Board/CommonEntityCard.ts @@ -0,0 +1,107 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; + +import { Entity } from '../../../../Model/Entity/Entity'; + +export class CommonEntityCard { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsNumber() + userId: number; + + @ApiProperty() + @IsString() + createdAt: string; + + @ApiProperty() + @IsNumber() + weight: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + closedAt?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedFrom?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedCount?: number | null; + + @ApiProperty({ type: [String] }) + linkedEntityNames: string[]; + + @ApiProperty() + @IsString() + taskIndicatorColor: string; + + @ApiProperty({ type: () => UserRights }) + userRights: UserRights; + + constructor({ + id, + entityTypeId, + name, + createdAt, + userId, + weight, + closedAt, + copiedFrom, + copiedCount, + linkedEntityNames, + taskIndicatorColor, + userRights, + }: CommonEntityCard) { + this.id = id; + this.entityTypeId = entityTypeId; + this.name = name; + this.createdAt = createdAt; + this.userId = userId; + this.weight = weight; + this.closedAt = closedAt; + this.copiedFrom = copiedFrom; + this.copiedCount = copiedCount; + this.linkedEntityNames = linkedEntityNames; + this.taskIndicatorColor = taskIndicatorColor; + this.userRights = userRights; + } + + public static create( + entity: Entity, + linkedEntityNames: string[], + taskIndicatorColor: string, + userRights: UserRights, + ) { + return new CommonEntityCard({ + id: entity.id, + entityTypeId: entity.entityTypeId, + name: entity.name, + createdAt: entity.createdAt.toISOString(), + userId: entity.responsibleUserId, + weight: entity.weight, + closedAt: entity.closedAt?.toISOString(), + copiedFrom: entity.copiedFrom, + copiedCount: entity.copiedCount, + linkedEntityNames, + taskIndicatorColor, + userRights, + }); + } +} diff --git a/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardCard.ts b/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardCard.ts new file mode 100644 index 0000000..581ed10 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardCard.ts @@ -0,0 +1,59 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { EntityCategory } from '../../../../common'; + +import { CommonEntityCard } from './CommonEntityCard'; +import { ProjectEntityCard } from './ProjectEntityCard'; + +export class EntityBoardCard { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty({ enum: EntityCategory }) + @IsEnum(EntityCategory) + entityCategory: EntityCategory; + + @ApiProperty() + @IsNumber() + boardId: number; + + @ApiProperty() + @IsNumber() + stageId: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + price: number | null; + + @ApiProperty() + @IsNumber() + weight: number; + + @ApiPropertyOptional({ description: 'Is focused?' }) + @IsOptional() + @IsBoolean() + focused: boolean; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + closedAt?: string | null; + + @ApiProperty() + data: CommonEntityCard | ProjectEntityCard; + + constructor({ id, entityCategory, boardId, stageId, price, weight, focused, closedAt, data }: EntityBoardCard) { + this.id = id; + this.entityCategory = entityCategory; + this.boardId = boardId; + this.stageId = stageId; + this.price = price; + this.weight = weight; + this.focused = focused; + this.closedAt = closedAt; + this.data = data; + } +} diff --git a/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardMeta.ts b/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardMeta.ts new file mode 100644 index 0000000..a60108d --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardMeta.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { EntityBoardStageMeta } from './EntityBoardStageMeta'; + +export class EntityBoardMeta { + @ApiProperty() + totalCount: number; + + @ApiProperty() + hasPrice: boolean; + + @ApiProperty({ nullable: true }) + totalPrice: number | null; + + @ApiProperty({ type: [EntityBoardStageMeta] }) + stages: EntityBoardStageMeta[]; +} diff --git a/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardStageMeta.ts b/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardStageMeta.ts new file mode 100644 index 0000000..3ac5659 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Board/EntityBoardStageMeta.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class EntityBoardStageMeta { + @ApiProperty() + id: number; + + @ApiProperty() + totalCount: number; + + @ApiProperty({ nullable: true }) + totalPrice: number | null; +} diff --git a/backend/src/CRM/Service/Entity/Dto/Board/ProjectEntityCard.ts b/backend/src/CRM/Service/Entity/Dto/Board/ProjectEntityCard.ts new file mode 100644 index 0000000..0bbc920 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Board/ProjectEntityCard.ts @@ -0,0 +1,125 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; + +import { Entity } from '../../../../Model/Entity/Entity'; +import { TasksCount } from './TasksCount'; + +export class ProjectEntityCard { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsNumber() + ownerId: number; + + @ApiProperty({ type: [Number] }) + @IsNumber({}, { each: true }) + participantIds: number[]; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + startDate: string | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + endDate: string | null; + + @ApiProperty({ type: TasksCount }) + tasksCount: TasksCount; + + @ApiProperty({ type: () => UserRights }) + userRights: UserRights; + + @ApiProperty() + @IsString() + createdAt: string; + + @ApiProperty() + @IsNumber() + weight: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + closedAt?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedFrom?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedCount?: number | null; + + constructor({ + id, + entityTypeId, + name, + ownerId, + participantIds, + startDate, + endDate, + tasksCount, + userRights, + createdAt, + weight, + closedAt, + copiedFrom, + copiedCount, + }: ProjectEntityCard) { + this.id = id; + this.entityTypeId = entityTypeId; + this.name = name; + this.ownerId = ownerId; + this.participantIds = participantIds; + this.startDate = startDate; + this.endDate = endDate; + this.tasksCount = tasksCount; + this.userRights = userRights; + this.createdAt = createdAt; + this.weight = weight; + this.closedAt = closedAt; + this.copiedFrom = copiedFrom; + this.copiedCount = copiedCount; + } + + public static create( + entity: Entity, + startDate: string | null, + endDate: string | null, + tasksCount: TasksCount, + userRights: UserRights, + ): ProjectEntityCard { + return new ProjectEntityCard({ + id: entity.id, + entityTypeId: entity.entityTypeId, + name: entity.name, + ownerId: entity.responsibleUserId, + participantIds: entity.participantIds ?? [], + startDate, + endDate, + tasksCount, + userRights, + createdAt: entity.createdAt.toISOString(), + weight: entity.weight, + closedAt: entity.closedAt?.toISOString(), + copiedFrom: entity.copiedFrom, + copiedCount: entity.copiedCount, + }); + } +} diff --git a/backend/src/CRM/Service/Entity/Dto/Board/TasksCount.ts b/backend/src/CRM/Service/Entity/Dto/Board/TasksCount.ts new file mode 100644 index 0000000..02177f7 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Board/TasksCount.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class TasksCount { + @ApiProperty() + notResolved: number; + + @ApiProperty() + overdue: number; + + @ApiProperty() + today: number; + + @ApiProperty() + resolved: number; + + constructor(notResolved: number, overdue: number, today: number, resolved: number) { + this.notResolved = notResolved; + this.overdue = overdue; + this.today = today; + this.resolved = resolved; + } +} diff --git a/backend/src/CRM/Service/Entity/Dto/Board/index.ts b/backend/src/CRM/Service/Entity/Dto/Board/index.ts new file mode 100644 index 0000000..3c68424 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Board/index.ts @@ -0,0 +1,6 @@ +export * from './CommonEntityCard'; +export * from './EntityBoardCard'; +export * from './EntityBoardMeta'; +export * from './EntityBoardStageMeta'; +export * from './ProjectEntityCard'; +export * from './TasksCount'; diff --git a/backend/src/CRM/Service/Entity/Dto/CreateEntityDto.ts b/backend/src/CRM/Service/Entity/Dto/CreateEntityDto.ts new file mode 100644 index 0000000..448db2f --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/CreateEntityDto.ts @@ -0,0 +1,48 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ManualSorting } from '@/common'; +import { UpdateFieldValueDto } from '@/modules/entity/entity-field/field-value/dto/update-field-value.dto'; + +import { EntityLinkDto } from '../../../entity-link/dto'; + +export class CreateEntityDto { + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsNumber() + responsibleUserId: number; + + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + boardId?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + stageId?: number | null; + + @ApiPropertyOptional({ type: [UpdateFieldValueDto] }) + fieldValues?: UpdateFieldValueDto[]; + + @ApiPropertyOptional({ type: [EntityLinkDto] }) + entityLinks?: EntityLinkDto[]; + + @ApiPropertyOptional({ type: ManualSorting }) + @IsOptional() + sorting?: ManualSorting; + + closedAt?: Date | null; + + @ApiPropertyOptional({ description: 'Is focused?' }) + @IsOptional() + @IsBoolean() + focused?: boolean; +} diff --git a/backend/src/CRM/Service/Entity/Dto/CreateSimpleEntityDto.ts b/backend/src/CRM/Service/Entity/Dto/CreateSimpleEntityDto.ts new file mode 100644 index 0000000..8601871 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/CreateSimpleEntityDto.ts @@ -0,0 +1,51 @@ +import { ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { SimpleFieldValueDto } from '@/modules/entity/entity-field/field-value/dto/simple-field-value.dto'; + +type LinkedEntity = CreateSimpleEntityDto | number; + +export class CreateSimpleEntityDto { + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + ownerId?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + boardId?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + stageId?: number | null; + + @ApiPropertyOptional({ description: 'Is focused?' }) + @IsOptional() + @IsBoolean() + focused?: boolean; + + @ApiPropertyOptional({ nullable: true, type: [SimpleFieldValueDto] }) + @IsOptional() + @IsArray() + fieldValues?: SimpleFieldValueDto[] | null; + + @ApiPropertyOptional({ + nullable: true, + type: 'array', + items: { oneOf: [{ $ref: getSchemaPath(CreateSimpleEntityDto) }, { type: 'number' }] }, + }) + @IsOptional() + @IsArray() + linkedEntities?: LinkedEntity[] | null; +} diff --git a/backend/src/CRM/Service/Entity/Dto/EntityDto.ts b/backend/src/CRM/Service/Entity/Dto/EntityDto.ts new file mode 100644 index 0000000..36aa5e0 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/EntityDto.ts @@ -0,0 +1,210 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; +import { faker } from '@faker-js/faker'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { ChatDto } from '@/modules/multichat/chat/dto/chat.dto'; +import { FieldValue } from '@/modules/entity/entity-field/field-value/entities/field-value.entity'; +import { FieldValueDto } from '@/modules/entity/entity-field/field-value/dto/field-value.dto'; + +import { EntityLinkDto } from '../../../entity-link/dto'; +import { Entity } from '../../../Model/Entity/Entity'; +import { ExternalEntityDto } from '../../ExternalEntity/ExternalEntityDto'; +import { ExternalEntity } from '../../../Model/ExternalEntity/ExternalEntity'; +import { ExternalSystem } from '../../../Model/ExternalEntity/ExternalSystem'; + +export class EntityDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiProperty() + @IsNumber() + responsibleUserId: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + boardId: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + stageId: number | null; + + @ApiProperty() + @IsNumber() + createdBy: number; + + @ApiProperty() + @IsNumber() + weight: number; + + @ApiPropertyOptional({ description: 'Is focused?' }) + @IsOptional() + @IsBoolean() + focused?: boolean; + + @ApiProperty({ type: [FieldValueDto] }) + @IsArray() + fieldValues: FieldValueDto[]; + + @ApiProperty({ type: [EntityLinkDto] }) + @IsArray() + entityLinks: EntityLinkDto[]; + + @ApiProperty({ type: [ExternalEntityDto] }) + @IsArray() + externalEntities: ExternalEntityDto[]; + + @ApiProperty({ type: UserRights }) + @IsObject() + userRights: UserRights; + + @ApiPropertyOptional() + @IsString() + createdAt: string; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + updatedAt?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + closedAt?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedFrom?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedCount?: number | null; + + @ApiPropertyOptional({ type: [ChatDto], nullable: true }) + @IsOptional() + chats?: ChatDto[] | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + lastShipmentDate?: string | null; + + constructor( + id: number, + name: string, + entityTypeId: number, + responsibleUserId: number, + boardId: number | null, + stageId: number | null, + createdBy: number, + weight: number, + focused: boolean, + fieldValues: FieldValueDto[], + entityLinks: EntityLinkDto[], + externalEntities: ExternalEntityDto[], + userRights: UserRights, + createdAt: string, + updatedAt: string | null, + closedAt: string | null, + copiedFrom: number | null, + copiedCount: number | null, + chats: ChatDto[] | null, + lastShipmentDate: string | null, + ) { + this.id = id; + this.name = name; + this.entityTypeId = entityTypeId; + this.responsibleUserId = responsibleUserId; + this.boardId = boardId; + this.stageId = stageId; + this.createdBy = createdBy; + this.weight = weight; + this.focused = focused; + this.fieldValues = fieldValues; + this.entityLinks = entityLinks; + this.externalEntities = externalEntities; + this.userRights = userRights; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.closedAt = closedAt; + this.copiedFrom = copiedFrom; + this.copiedCount = copiedCount; + this.chats = chats; + this.lastShipmentDate = lastShipmentDate; + } + + public static fromEntity( + entity: Entity, + fieldValues: FieldValue[], + entityLinks: EntityLinkDto[], + externalEntities: { externalEntity: ExternalEntity; type: ExternalSystem }[], + userRights: UserRights, + chats: ChatDto[] | null, + lastShipmentDate: string | null, + ) { + const fieldValueDtos = fieldValues.map((fieldValue) => fieldValue.toDto()); + const externalEntitiesDtos = externalEntities.map((ee) => ExternalEntityDto.create(ee.externalEntity, ee.type)); + + return new EntityDto( + entity.id, + entity.name, + entity.entityTypeId, + entity.responsibleUserId, + entity.boardId, + entity.stageId, + entity.createdBy, + entity.weight, + entity.focused, + fieldValueDtos, + entityLinks, + externalEntitiesDtos, + userRights, + entity.createdAt.toISOString(), + entity.updatedAt.toISOString(), + entity.closedAt?.toISOString() ?? null, + entity.copiedFrom, + entity.copiedCount, + chats, + lastShipmentDate, + ); + } + + public static fake(): EntityDto { + const closedAt = faker.date.past(); + return new EntityDto( + faker.number.int({ min: 10000000, max: 99999999 }), + faker.person.fullName(), + faker.number.int({ min: 10000000, max: 99999999 }), + faker.number.int({ min: 10000000, max: 99999999 }), + faker.number.int({ min: 10000000, max: 99999999 }), + faker.number.int({ min: 10000000, max: 99999999 }), + faker.number.int({ min: 10000000, max: 99999999 }), + faker.number.float({ min: -1000000, max: 1000000 }), + false, + [], + [], + [], + UserRights.full(), + faker.date.past({ refDate: closedAt }).toISOString(), + faker.date.past({ refDate: closedAt }).toISOString(), + closedAt.toISOString(), + faker.number.int({ min: 10000000, max: 99999999 }), + faker.number.int({ min: 10000000, max: 99999999 }), + [], + faker.date.past({ refDate: closedAt }).toISOString(), + ); + } +} diff --git a/backend/src/CRM/Service/Entity/Dto/EntitySimpleDto.ts b/backend/src/CRM/Service/Entity/Dto/EntitySimpleDto.ts new file mode 100644 index 0000000..9a5bb6b --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/EntitySimpleDto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; +import { IsNumber, IsString } from 'class-validator'; + +export class EntitySimpleDto { + @ApiProperty() + @IsNumber() + @Expose() + id: number; + + @ApiProperty() + @IsString() + @Expose() + name: string; +} diff --git a/backend/src/CRM/Service/Entity/Dto/Files/AddEntityFilesDto.ts b/backend/src/CRM/Service/Entity/Dto/Files/AddEntityFilesDto.ts new file mode 100644 index 0000000..037c3d7 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Files/AddEntityFilesDto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class AddEntityFilesDto { + @ApiProperty() + fileIds: string[]; +} diff --git a/backend/src/CRM/Service/Entity/Dto/Files/index.ts b/backend/src/CRM/Service/Entity/Dto/Files/index.ts new file mode 100644 index 0000000..b1293dc --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/Files/index.ts @@ -0,0 +1 @@ +export * from './AddEntityFilesDto'; diff --git a/backend/src/CRM/Service/Entity/Dto/List/EntityListItem.ts b/backend/src/CRM/Service/Entity/Dto/List/EntityListItem.ts new file mode 100644 index 0000000..29d6661 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/List/EntityListItem.ts @@ -0,0 +1,120 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { FieldValueDto } from '@/modules/entity/entity-field/field-value/dto/field-value.dto'; + +import { Entity } from '../../../../Model/Entity/Entity'; + +export class EntityListItem { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsNumber() + responsibleUserId: number; + + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + boardId: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + stageId: number | null; + + @ApiProperty() + @IsString() + createdAt: string; + + @ApiProperty() + @IsNumber() + weight: number; + + @ApiPropertyOptional({ description: 'Is focused?' }) + @IsOptional() + @IsBoolean() + focused: boolean; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + closedAt?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedFrom?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + copiedCount?: number | null; + + @ApiProperty({ type: [FieldValueDto] }) + fieldValues: FieldValueDto[]; + + @ApiProperty({ type: () => UserRights }) + userRights: UserRights; + + constructor({ + id, + name, + createdAt, + responsibleUserId, + entityTypeId, + boardId, + stageId, + weight, + focused, + closedAt, + copiedFrom, + copiedCount, + fieldValues, + userRights, + }: EntityListItem) { + this.id = id; + this.name = name; + this.createdAt = createdAt; + this.responsibleUserId = responsibleUserId; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.stageId = stageId; + this.weight = weight; + this.focused = focused; + this.closedAt = closedAt; + this.copiedFrom = copiedFrom; + this.copiedCount = copiedCount; + this.fieldValues = fieldValues; + this.userRights = userRights; + } + + static create(entity: Entity, fieldValues: FieldValueDto[], userRights: UserRights) { + return new EntityListItem({ + id: entity.id, + name: entity.name, + createdAt: entity.createdAt.toISOString(), + responsibleUserId: entity.responsibleUserId, + entityTypeId: entity.entityTypeId, + boardId: entity.boardId, + stageId: entity.stageId, + weight: entity.weight, + focused: entity.focused, + closedAt: entity.closedAt?.toISOString(), + copiedFrom: entity.copiedFrom, + copiedCount: entity.copiedCount, + fieldValues, + userRights, + }); + } +} diff --git a/backend/src/CRM/Service/Entity/Dto/List/EntityListMeta.ts b/backend/src/CRM/Service/Entity/Dto/List/EntityListMeta.ts new file mode 100644 index 0000000..c5d0a96 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/List/EntityListMeta.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class EntityListMeta { + @ApiProperty() + totalCount: number; + + @ApiProperty() + hasPrice: boolean; + + @ApiProperty({ nullable: true }) + totalPrice: number | null; +} diff --git a/backend/src/CRM/Service/Entity/Dto/List/index.ts b/backend/src/CRM/Service/Entity/Dto/List/index.ts new file mode 100644 index 0000000..8684254 --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/List/index.ts @@ -0,0 +1,2 @@ +export * from './EntityListItem'; +export * from './EntityListMeta'; diff --git a/backend/src/CRM/Service/Entity/Dto/UpdateEntityDto.ts b/backend/src/CRM/Service/Entity/Dto/UpdateEntityDto.ts new file mode 100644 index 0000000..89e934c --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/UpdateEntityDto.ts @@ -0,0 +1,49 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ManualSorting } from '@/common'; +import { UpdateFieldValueDto } from '@/modules/entity/entity-field/field-value/dto/update-field-value.dto'; +import { EntityLinkDto } from '../../../entity-link/dto'; + +export class UpdateEntityDto { + @ApiPropertyOptional() + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + responsibleUserId?: number; + + @ApiPropertyOptional({ type: [UpdateFieldValueDto] }) + @IsOptional() + @IsArray() + fieldValues?: UpdateFieldValueDto[]; + + @ApiPropertyOptional({ type: [EntityLinkDto] }) + @IsOptional() + @IsArray() + entityLinks?: EntityLinkDto[]; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + boardId?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + stageId?: number | null; + + @ApiPropertyOptional({ type: ManualSorting }) + @IsOptional() + sorting?: ManualSorting; + + closedAt?: Date | null; + + @ApiPropertyOptional({ description: 'Is focused?' }) + @IsOptional() + @IsBoolean() + focused?: boolean; +} diff --git a/backend/src/CRM/Service/Entity/Dto/index.ts b/backend/src/CRM/Service/Entity/Dto/index.ts new file mode 100644 index 0000000..7ef54ff --- /dev/null +++ b/backend/src/CRM/Service/Entity/Dto/index.ts @@ -0,0 +1,9 @@ +export * from './Batch'; +export * from './Board'; +export * from './CreateEntityDto'; +export * from './CreateSimpleEntityDto'; +export * from './EntityDto'; +export * from './EntitySimpleDto'; +export * from './Files'; +export * from './List'; +export * from './UpdateEntityDto'; diff --git a/backend/src/CRM/Service/Entity/EntityBoardService.ts b/backend/src/CRM/Service/Entity/EntityBoardService.ts new file mode 100644 index 0000000..3c7c101 --- /dev/null +++ b/backend/src/CRM/Service/Entity/EntityBoardService.ts @@ -0,0 +1,874 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, DataSource, Repository, SelectQueryBuilder } from 'typeorm'; + +import { + BooleanFilter, + DateFilter, + DatePeriod, + DateUtil, + ExistsFilter, + ExistsFilterType, + intersection, + NumberFilter, + PagingQuery, + SelectFilter, + SimpleFilterType, + StringFilter, + StringFilterType, + UserNotification, +} from '@/common'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldValue } from '@/modules/entity/entity-field/field-value/entities/field-value.entity'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value/field-value.service'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; +import { Field } from '@/modules/entity/entity-field/field/entities/field.entity'; + +import { EntityCategory } from '../../common'; +import { BoardStageService } from '../../board-stage'; +import { EntityType } from '../../entity-type/entities/entity-type.entity'; +import { EntityTypeService } from '../../entity-type/entity-type.service'; + +import { Entity } from '../../Model/Entity/Entity'; + +import { ProjectEntityBoardCardService } from './ProjectEntityBoardCardService'; +import { CommonEntityBoardCardService } from './CommonEntityBoardCardService'; +import { EntityService } from './EntityService'; + +import { EntityBoardCardFilter } from '../../Controller/Entity/Board/Filter/EntityBoardCardFilter'; +import { EntitySorting } from '../../Controller/Entity/Board/Filter/EntitySorting'; +import { EntityTaskFilter } from '../../Controller/Entity/Board/Filter/entity-task-filter.enum'; +import { EntityFieldFilter } from '../../Controller/Entity/Board/Filter/EntityFieldFilter'; +import { + EntityBoardCard, + EntityBoardMeta, + EntityBoardStageMeta, + EntityListItem, + EntityListMeta, + UpdateEntitiesBatchFilterDto, + DeleteEntitiesBatchFilterDto, +} from './Dto'; + +const EntityBatchLimit = 50; + +@Injectable() +export class EntityBoardService { + constructor( + @InjectRepository(Entity) + private readonly repository: Repository, + private readonly dataSource: DataSource, + private readonly authService: AuthorizationService, + private readonly fieldValueService: FieldValueService, + private readonly entityTypeService: EntityTypeService, + private readonly stageService: BoardStageService, + private readonly fieldService: FieldService, + private readonly entityService: EntityService, + private readonly commonBoardCardService: CommonEntityBoardCardService, + private readonly projectBoardCardService: ProjectEntityBoardCardService, + ) {} + + async getEntityBoardCards({ + accountId, + user, + entityTypeId, + boardId, + filter, + paging, + }: { + accountId: number; + user: User; + entityTypeId: number; + boardId: number; + filter: EntityBoardCardFilter; + paging: PagingQuery; + }): Promise { + const stageIds = await this.getStageIds({ + accountId, + boardId, + includeStageIds: filter.includeStageIds, + excludeStageIds: filter.excludeStageIds, + }); + if (!stageIds?.length) { + return []; + } + + const qb = await this.createFilteredQb({ + accountId, + user, + entityTypeId, + filter, + sorting: filter.sorting ?? EntitySorting.Manual, + }); + + const entityIds = ( + await Promise.all( + stageIds.map(async (stageId) => { + return ( + await qb + .clone() + .andWhere('e.stage_id = :stageId', { stageId }) + .offset(paging.skip) + .limit(paging.take) + .getRawMany<{ id: number }>() + ).map((i) => i.id); + }), + ) + ).flat(); + + if (entityIds.length) { + const entities = await this.entityService.getByIdsOrdered({ accountId, entityIds }); + const entityType = await this.entityTypeService.findOne(accountId, { id: entityTypeId }); + return entityType.entityCategory === EntityCategory.PROJECT + ? this.projectBoardCardService.getBoardCards(accountId, user, entityTypeId, entities, entityType.entityCategory) + : this.commonBoardCardService.getBoardCards(accountId, user, entityTypeId, entities, entityType.entityCategory); + } else { + return []; + } + } + + async getEntityBoardCard({ + accountId, + user, + entityTypeId, + boardId, + entityId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + boardId: number; + entityId: number; + filter: EntityBoardCardFilter; + }): Promise { + const stageIds = await this.getStageIds({ + accountId, + boardId, + includeStageIds: filter.includeStageIds, + excludeStageIds: filter.excludeStageIds, + }); + if (!stageIds?.length) { + return null; + } + + const qb = await this.createFilteredQb({ accountId, user, entityTypeId, stageIds, filter }); + + const filteredEntityId = await qb.andWhere('e.id = :entityId', { entityId }).getRawOne<{ id: number }>(); + + if (!filteredEntityId?.id) { + return null; + } + + const entity = await this.entityService.findOne(accountId, { entityId }); + const entityType = await this.entityTypeService.findOne(accountId, { id: entityTypeId }); + return entityType.entityCategory === EntityCategory.PROJECT + ? await this.projectBoardCardService.getBoardCard(accountId, user, entity, entityType.entityCategory) + : await this.commonBoardCardService.getBoardCard(accountId, user, entity, entityType.entityCategory); + } + + async getEntityBoardMeta({ + accountId, + user, + entityTypeId, + boardId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + boardId: number; + filter: EntityBoardCardFilter; + }): Promise { + const stageIds = await this.getStageIds({ + accountId, + boardId, + includeStageIds: filter.includeStageIds, + excludeStageIds: filter.excludeStageIds, + }); + const hasPrice = await this.fieldService.hasPriceField({ accountId, entityTypeId }); + if (!stageIds?.length) { + return { totalCount: 0, hasPrice, totalPrice: hasPrice ? 0 : null, stages: [] }; + } + + const qb = await this.createFilteredQb({ accountId, user, entityTypeId, filter }); + + const stageMetas: EntityBoardStageMeta[] = await Promise.all( + stageIds.map(async (stageId) => { + const stageQb = qb.clone().andWhere('e.stage_id = :stageId', { stageId }); + + const count = await stageQb.getCount(); + const price = hasPrice ? await this.getTotalPrice(stageQb) : null; + + return { id: stageId, totalCount: count, totalPrice: price }; + }), + ); + + const totalCount = stageMetas.reduce((acc, cur) => acc + cur.totalCount, 0); + const totalPrice = hasPrice ? stageMetas.reduce((acc, cur) => acc + cur.totalPrice, 0) : null; + + return { totalCount, hasPrice, totalPrice, stages: stageMetas }; + } + + /** + * @deprecated Use @see EntityService findMany + */ + async getEntityBoardEntities( + accountId: number, + user: User, + entityTypeId: number, + boardId: number, + paging: PagingQuery, + ): Promise { + await this.authService.check({ + action: 'view', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + throwError: true, + }); + + return this.entityService.findMany(accountId, { boardId }, paging); + } + + async getEntityListItems({ + accountId, + user, + entityTypeId, + boardId, + filter, + paging, + }: { + accountId: number; + user: User; + entityTypeId: number; + boardId: number | null; + filter: EntityBoardCardFilter; + paging: PagingQuery; + }): Promise { + const stageIds = await this.getStageIds({ + accountId, + boardId, + includeStageIds: filter.includeStageIds, + excludeStageIds: filter.excludeStageIds, + }); + if (boardId && stageIds?.length === 0) { + return []; + } + + const qb = await this.createFilteredQb({ + accountId, + user, + entityTypeId, + stageIds: boardId ? stageIds : undefined, + filter, + sorting: filter.sorting ?? EntitySorting.Manual, + }); + + const entityIds = (await qb.offset(paging.skip).limit(paging.take).getRawMany<{ id: number }>()).map((e) => e.id); + + return entityIds.length ? this.createListItems({ accountId, user, entityIds }) : []; + } + + async getEntityListItem({ + accountId, + user, + entityTypeId, + entityId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + entityId: number; + filter: EntityBoardCardFilter; + }): Promise { + const qb = await this.createFilteredQb({ + accountId, + user, + entityTypeId, + stageIds: filter.includeStageIds?.length ? filter.includeStageIds : undefined, + filter, + }); + + const filteredEntityId = await qb.andWhere('e.id = :entityId', { entityId }).getRawOne<{ id: number }>(); + return filteredEntityId?.id + ? (await this.createListItems({ accountId, user, entityIds: [filteredEntityId.id] }))[0] + : null; + } + + async createListItems({ accountId, user, entityIds }: { accountId: number; user: User; entityIds: number[] }) { + const entities = await this.entityService.getByIdsOrdered({ accountId, entityIds }); + + const items = await Promise.all( + entities.map(async (entity, idx) => { + const fieldValues = await this.fieldValueService.findMany({ accountId, entityId: entity.id }); + const values = fieldValues.map((value) => value.toDto()); + const userRights = await this.authService.getUserRights({ user, authorizable: entity }); + return { idx, item: EntityListItem.create(entity, values, userRights) }; + }), + ); + + return items.sort((a, b) => a.idx - b.idx).map((i) => i.item); + } + + async getEntityListMeta({ + accountId, + user, + entityTypeId, + boardId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + boardId: number | null; + filter: EntityBoardCardFilter; + }): Promise { + const stageIds = await this.getStageIds({ + accountId, + boardId, + includeStageIds: filter.includeStageIds, + excludeStageIds: filter.excludeStageIds, + }); + const hasPrice = await this.fieldService.hasPriceField({ accountId, entityTypeId }); + if (boardId && stageIds?.length === 0) { + return { totalCount: 0, hasPrice, totalPrice: hasPrice ? 0 : null }; + } + + const qb = await this.createFilteredQb({ + accountId, + user, + entityTypeId, + stageIds: boardId ? stageIds : undefined, + filter, + }); + + const totalCount = await qb.getCount(); + const totalPrice = hasPrice ? await this.getTotalPrice(qb) : null; + + return { totalCount, hasPrice, totalPrice }; + } + + async batchUpdate({ + accountId, + user, + entityTypeId, + boardId, + dto, + }: { + accountId: number; + user: User; + entityTypeId: number; + boardId: number | null; + dto: UpdateEntitiesBatchFilterDto; + }): Promise { + if (dto.entityIds) { + return this.entityService.batchUpdateEntityIds(accountId, user, dto.entityIds, dto, UserNotification.Suppressed); + } + + const stageIds = await this.getStageIds({ + accountId, + boardId, + includeStageIds: dto.includeStageIds, + excludeStageIds: dto.excludeStageIds, + }); + if (boardId && stageIds?.length === 0) { + return 0; + } + + const qb = await this.createFilteredQb({ + accountId, + user, + entityTypeId, + stageIds: boardId ? stageIds : undefined, + filter: dto, + }); + qb.orderBy('e.id', 'ASC'); + + let updated = 0; + let offset = 0; + let entityIds: number[] = []; + let baseCount = await qb.getCount(); + do { + const count = await qb.getCount(); + if (count !== baseCount) { + baseCount = count; + offset = 0; + } + entityIds = (await qb.clone().offset(offset).limit(EntityBatchLimit).getRawMany<{ id: number }>()).map( + (e) => e.id, + ); + if (entityIds.length > 0) { + const pageUpdated = await this.entityService.batchUpdateEntityIds( + accountId, + user, + entityIds, + dto, + UserNotification.Suppressed, + ); + updated += pageUpdated; + offset += entityIds.length; + } + } while (entityIds.length > 0); + + return updated; + } + + async batchDelete({ + accountId, + user, + entityTypeId, + boardId, + dto, + }: { + accountId: number; + user: User; + entityTypeId: number; + boardId: number | null; + dto: DeleteEntitiesBatchFilterDto; + }): Promise { + if (dto.entityIds) { + return this.entityService.deleteMany(accountId, user, dto.entityIds, UserNotification.Suppressed); + } + + const stageIds = await this.getStageIds({ + accountId, + boardId, + includeStageIds: dto.includeStageIds, + excludeStageIds: dto.excludeStageIds, + }); + if (boardId && stageIds?.length === 0) { + return 0; + } + + const qb = await this.createFilteredQb({ + accountId, + user, + entityTypeId, + stageIds: boardId ? stageIds : undefined, + filter: dto, + }); + qb.orderBy('e.id', 'ASC'); + + let deleted = 0; + let offset = 0; + let entityIds: number[] = []; + do { + entityIds = (await qb.clone().offset(offset).limit(EntityBatchLimit).getRawMany<{ id: number }>()).map( + (e) => e.id, + ); + if (entityIds.length > 0) { + const pageDeleted = await this.entityService.deleteMany( + accountId, + user, + entityIds, + UserNotification.Suppressed, + ); + deleted += pageDeleted; + offset += entityIds.length - pageDeleted; + } + } while (entityIds.length > 0); + + return deleted; + } + + private async getStageIds({ + accountId, + boardId, + includeStageIds, + excludeStageIds, + }: { + accountId: number; + boardId: number | null | undefined; + includeStageIds: number[] | null | undefined; + excludeStageIds: number[] | null | undefined; + }): Promise { + const stageIds = includeStageIds?.length + ? includeStageIds + : boardId + ? await this.stageService.findManyIds({ accountId, boardId }) + : []; + + return excludeStageIds?.length ? stageIds.filter((id) => !excludeStageIds.includes(id)) : stageIds; + } + + private async createFilteredQb({ + accountId, + user, + entityTypeId, + stageIds, + filter, + sorting, + }: { + accountId: number; + user: User; + entityTypeId: number; + stageIds?: number[]; + filter: EntityBoardCardFilter; + sorting?: EntitySorting; + }) { + const responsibles = await this.authService.whoCanView({ + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + + const fieldIds = filter.fields?.map((f) => f.fieldId); + const fields = fieldIds?.length + ? await this.fieldService.findMany({ accountId, entityTypeId, id: fieldIds }) + : undefined; + + return this.createQb({ + accountId, + userId: user.id, + responsibles, + entityTypeId, + stageId: stageIds, + name: filter.search, + createdAt: filter.createdAt ? DatePeriod.fromFilter(filter.createdAt) : undefined, + closedAt: filter.closedAt ? DatePeriod.fromFilter(filter.closedAt) : undefined, + ownerIds: filter.ownerIds, + tasks: filter.tasks, + fieldFilters: filter.fields, + fields, + sorting, + }); + } + + private createQb({ + accountId, + userId, + responsibles, + entityTypeId, + stageId, + name, + createdAt, + closedAt, + ownerIds, + tasks, + fieldFilters, + fields, + sorting, + }: { + accountId: number; + userId: number; + responsibles: number[]; + entityTypeId: number; + stageId?: number | number[]; + name?: string | null; + createdAt?: DatePeriod; + closedAt?: DatePeriod; + ownerIds?: number[]; + tasks?: EntityTaskFilter; + fieldFilters?: EntityFieldFilter[]; + fields?: Field[]; + sorting?: EntitySorting; + }) { + const qb = this.repository + .createQueryBuilder('e') + .select('e.id', 'id') + .where('e.account_id = :accountId', { accountId }) + .andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }); + + if (stageId) { + if (Array.isArray(stageId)) { + qb.andWhere('e.stage_id IN (:...stageId)', { stageId }); + } else { + qb.andWhere('e.stage_id = :stageId', { stageId }); + } + } + + if (name) { + qb.andWhere('e.name ILIKE :name', { name: `%${name}%` }); + } + + if (createdAt) { + if (createdAt.from) { + qb.andWhere('e.created_at >= :createdAtFrom', { createdAtFrom: createdAt.from }); + } + if (createdAt.to) { + qb.andWhere('e.created_at <= :createdAtTo', { createdAtTo: createdAt.to }); + } + } + if (closedAt) { + if (closedAt.from) { + qb.andWhere('e.closed_at >= :closedAtFrom', { closedAtFrom: closedAt.from }); + } + if (closedAt.to) { + qb.andWhere('e.closed_at <= :closedAtTo', { closedAtTo: closedAt.to }); + } + } + + const users = intersection(ownerIds, responsibles); + if (users) { + qb.andWhere( + new Brackets((qb1) => { + if (users.length > 0) { + qb1.where('e.responsible_user_id in (:...responsibles)', { responsibles: users }); + } else { + qb1.where('e.responsible_user_id IS NULL'); + } + if (!ownerIds) { + qb1.orWhere(`e.created_by = ${userId}`).orWhere(`e.participant_ids @> '${userId}'`); + } + }), + ); + } + + if (tasks) { + const taskQb = this.dataSource + .createQueryBuilder() + .select('at.id') + .from('all_tasks', 'at') + .where('at.entity_id = e.id') + .andWhere('at.is_resolved = FALSE'); + if (tasks === EntityTaskFilter.WithoutTask) { + qb.andWhere(`NOT EXISTS (${taskQb.getQuery()})`); + } else if (tasks === EntityTaskFilter.WithTask) { + qb.andWhere(`EXISTS (${taskQb.getQuery()})`); + } else if (tasks === EntityTaskFilter.OverdueTask) { + taskQb.andWhere('at.end_date < NOW()'); + qb.andWhere(`EXISTS (${taskQb.getQuery()})`); + } else if (tasks === EntityTaskFilter.TodayTask) { + taskQb.andWhere( + new Brackets((todayQb) => { + todayQb + .where('at.start_date < NOW() AND at.end_date > NOW()') + .orWhere('at.start_date IS NOT NULL AND at.end_date IS NULL AND at.start_date < NOW()') + .orWhere('at.start_date IS NULL AND at.end_date IS NOT NULL AND at.end_date > NOW()'); + }), + ); + qb.andWhere(`EXISTS (${taskQb.getQuery()})`); + } + } + + if (fieldFilters && fields) { + this.addBoardFieldsCondition({ qb, fieldFilters, fields }); + } + + if (sorting) { + switch (sorting) { + case EntitySorting.Manual: + qb.addSelect('e.weight', 'weight').orderBy('e.weight', 'ASC'); + break; + case EntitySorting.CreatedAsc: + qb.addSelect('e.created_at', 'created_at').orderBy('e.created_at', 'ASC'); + break; + case EntitySorting.CreatedDesc: + qb.addSelect('e.created_at', 'created_at').orderBy('e.created_at', 'DESC'); + break; + case EntitySorting.NameAsc: + qb.addSelect('e.name', 'name').orderBy('e.name', 'ASC'); + break; + case EntitySorting.NameDesc: + qb.addSelect('e.name', 'name').orderBy('e.name', 'DESC'); + break; + } + } + qb.addOrderBy('e.id', 'DESC'); + + return qb; + } + + private addBoardFieldsCondition({ + qb, + fieldFilters, + fields, + }: { + qb: SelectQueryBuilder; + fieldFilters: EntityFieldFilter[]; + fields: Field[]; + }) { + for (const fieldFilter of fieldFilters) { + const field = fields.find((f) => f.id === fieldFilter.fieldId); + if (!field) continue; + + const fieldAlias = `fv_${field.id}`; + qb.leftJoin(FieldValue, fieldAlias, `${fieldAlias}.entity_id = e.id AND ${fieldAlias}.field_id = ${field.id}`); + + switch (field.type) { + case FieldType.Text: + case FieldType.RichText: + case FieldType.Link: + case FieldType.Checklist: + if (fieldFilter.type === SimpleFilterType.String) { + this.addStringFieldCondition(qb, fieldAlias, 'value', fieldFilter.filter as StringFilter); + } + break; + case FieldType.MultiText: + case FieldType.Email: + case FieldType.Phone: + if (fieldFilter.type === SimpleFilterType.String) { + this.addStringFieldCondition(qb, fieldAlias, 'values', fieldFilter.filter as StringFilter); + } + break; + case FieldType.Number: + case FieldType.Value: + case FieldType.Formula: + if (fieldFilter.type === SimpleFilterType.Number) { + this.addNumberFieldCondition(qb, fieldAlias, fieldFilter.filter as NumberFilter); + } + break; + case FieldType.Date: + if (fieldFilter.type === SimpleFilterType.Date) { + this.addDateFieldCondition(qb, fieldAlias, fieldFilter.filter as DateFilter); + } + break; + case FieldType.Switch: + if (fieldFilter.type === SimpleFilterType.Boolean) { + this.addBooleanFieldCondition(qb, fieldAlias, fieldFilter.filter as BooleanFilter); + } + break; + case FieldType.Select: + case FieldType.ColoredSelect: + if (fieldFilter.type === SimpleFilterType.Select) { + this.addSelectFieldCondition(qb, fieldAlias, 'optionId', fieldFilter.filter as SelectFilter); + } + break; + case FieldType.MultiSelect: + case FieldType.ColoredMultiSelect: + case FieldType.CheckedMultiSelect: + if (fieldFilter.type === SimpleFilterType.Select) { + this.addMultiSelectFieldCondition(qb, fieldAlias, 'optionIds', fieldFilter.filter as SelectFilter); + } + break; + case FieldType.Participant: + if (fieldFilter.type === SimpleFilterType.Select) { + this.addSelectFieldCondition(qb, fieldAlias, 'value', fieldFilter.filter as SelectFilter); + } + break; + case FieldType.Participants: + if (fieldFilter.type === SimpleFilterType.Select) { + this.addMultiSelectFieldCondition(qb, fieldAlias, 'userIds', fieldFilter.filter as SelectFilter); + } + break; + case FieldType.File: + if (fieldFilter.type === SimpleFilterType.Exists) { + this.addExistsFieldCondition(qb, fieldAlias, 'value', fieldFilter.filter as ExistsFilter); + } + break; + } + } + } + private addExistsFieldCondition( + qb: SelectQueryBuilder, + fieldAlias: string, + valueAlias: string, + filter: ExistsFilter, + ) { + switch (filter.type) { + case ExistsFilterType.Empty: + qb.andWhere( + new Brackets((qb1) => + qb1.where(`${fieldAlias}.payload is null`).orWhere(`${fieldAlias}.payload->>'${valueAlias}' = ''`), + ), + ); + break; + case ExistsFilterType.NotEmpty: + qb.andWhere(`${fieldAlias}.payload->>'${valueAlias}' != ''`); + break; + } + } + private addStringFieldCondition( + qb: SelectQueryBuilder, + fieldAlias: string, + valueAlias: string, + filter: StringFilter, + ) { + switch (filter.type) { + case StringFilterType.Empty: + qb.andWhere( + new Brackets((qb1) => + qb1.where(`${fieldAlias}.payload is null`).orWhere(`${fieldAlias}.payload->>'${valueAlias}' = ''`), + ), + ); + break; + case StringFilterType.NotEmpty: + qb.andWhere(`${fieldAlias}.payload->>'${valueAlias}' != ''`); + break; + default: + if (filter.text) { + qb.andWhere(`${fieldAlias}.payload->>'${valueAlias}' ilike :${fieldAlias}_value`, { + [`${fieldAlias}_value`]: `%${filter.text}%`, + }); + } + } + } + private addNumberFieldCondition(qb: SelectQueryBuilder, fieldAlias: string, filter: NumberFilter) { + if (filter.min && !isNaN(Number(filter.min))) { + qb.andWhere(`(${fieldAlias}.payload->>'value')::numeric >= :${fieldAlias}_from`, { + [`${fieldAlias}_from`]: filter.min, + }); + } + if (filter.max && !isNaN(Number(filter.max))) { + qb.andWhere(`(${fieldAlias}.payload->>'value')::numeric <= :${fieldAlias}_to`, { + [`${fieldAlias}_to`]: filter.max, + }); + } + } + private addDateFieldCondition(qb: SelectQueryBuilder, fieldAlias: string, filter: DateFilter) { + if (filter.from) { + const fromDate = DateUtil.fromISOString(filter.from); + qb.andWhere(`(${fieldAlias}.payload->>'value')::timestamp >= '${fromDate.toISOString()}'::timestamp`); + } + if (filter.to) { + const toDate = DateUtil.fromISOString(filter.to); + qb.andWhere(`(${fieldAlias}.payload->>'value')::timestamp <= '${toDate.toISOString()}'::timestamp`); + } + } + private addBooleanFieldCondition(qb: SelectQueryBuilder, fieldAlias: string, filter: BooleanFilter) { + if (filter.value !== undefined && filter.value !== null) { + if (filter.value) { + qb.andWhere(`(${fieldAlias}.payload->>'value')::boolean = true`); + } else { + qb.andWhere( + new Brackets((qb1) => + qb1.where(`${fieldAlias}.payload is null`).orWhere(`(${fieldAlias}.payload->>'value')::boolean = false`), + ), + ); + } + } + } + private addSelectFieldCondition( + qb: SelectQueryBuilder, + fieldAlias: string, + valueAlias: string, + filter: SelectFilter, + ) { + if (filter.optionIds?.length) { + qb.andWhere(`COALESCE((${fieldAlias}.payload->>'${valueAlias}')::integer, 0) IN (:...${fieldAlias}_value)`, { + [`${fieldAlias}_value`]: filter.optionIds, + }); + } + } + private addMultiSelectFieldCondition( + qb: SelectQueryBuilder, + fieldAlias: string, + valueAlias: string, + filter: SelectFilter, + ) { + if (filter.optionIds?.length > 0) { + qb.leftJoin( + '(select 1)', + `dummy_${fieldAlias}`, + `true cross join jsonb_array_elements(${fieldAlias}.payload->'${valueAlias}') a(opt_id_${fieldAlias})`, + ); + qb.andWhere(`opt_id_${fieldAlias}::text::integer IN (:...${fieldAlias}_value)`, { + [`${fieldAlias}_value`]: filter.optionIds, + }); + } + } + + private async getTotalPrice(qb: SelectQueryBuilder): Promise { + const { sum } = await this.dataSource + .createQueryBuilder() + .select(`sum(cast(fv.payload::jsonb->>'value' as decimal))::decimal`, 'sum') + .from('field_value', 'fv') + .where(`fv.field_type = '${FieldType.Value}'`) + .andWhere(`fv.entity_id in (${qb.getQuery()})`) + .setParameters(qb.getParameters()) + .getRawOne<{ sum: number }>(); + + return sum ? Number(sum) : 0; + } +} diff --git a/backend/src/CRM/Service/Entity/EntityService.ts b/backend/src/CRM/Service/Entity/EntityService.ts new file mode 100644 index 0000000..d1487f2 --- /dev/null +++ b/backend/src/CRM/Service/Entity/EntityService.ts @@ -0,0 +1,1212 @@ +import { ForbiddenException, forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { + DateUtil, + FileLinkSource, + ManualSorting, + NotFoundError, + ObjectState, + PagingQuery, + UserNotification, +} from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { ChatService } from '@/modules/multichat/chat/services/chat.service'; +import { ShipmentService } from '@/modules/inventory/shipment/shipment.service'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldAccess } from '@/modules/entity/entity-field/field-settings/enums/field-access.enum'; +import { UpdateFieldValueDto } from '@/modules/entity/entity-field/field-value/dto/update-field-value.dto'; +import { FieldSettingsService } from '@/modules/entity/entity-field/field-settings/field-settings.service'; +import { FieldValue } from '@/modules/entity/entity-field/field-value/entities/field-value.entity'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value/field-value.service'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; +import { EntityInfoDto, EntityInfoService } from '@/modules/entity/entity-info'; + +import { EntityCategory } from '../../common'; +import { BoardService } from '../../board/board.service'; +import { BoardStage, BoardStageService } from '../../board-stage'; +import { EntityLink } from '../../entity-link/entities'; +import { EntityLinkService } from '../../entity-link/entity-link.service'; +import { EntityTypeService } from '../../entity-type/entity-type.service'; +import { EntitySearchService } from '../../entity-search/entity-search.service'; + +import { Entity } from '../../Model/Entity/Entity'; +import { ExternalEntityService } from '../ExternalEntity/ExternalEntityService'; +import { FileLinkService } from '../FileLink/FileLinkService'; + +import { EntityServiceEmitter } from './entity-service.emitter'; +import { ReadonlyFieldChangedError } from './errors/readonly-field-changed.error'; +import { RequiredFieldEmptyError } from './errors/required-field-empty.error'; +import { FileLinkDto } from '../FileLink/FileLinkDto'; +import { EntityDto } from './Dto/EntityDto'; +import { CreateEntityDto } from './Dto/CreateEntityDto'; +import { UpdateEntityDto } from './Dto/UpdateEntityDto'; +import { CreateSimpleEntityDto } from './Dto/CreateSimpleEntityDto'; + +const Weight = { + min: 100.0, + step: 100.0, +}; +const BatchProcessLimit = 100; + +interface CreateSimpleOptions { + linkedEntities?: number[]; + createdAt?: Date; + userNotification?: UserNotification; + checkActiveLead?: boolean; + checkActiveLeadContactEntityId?: number; + checkDuplicate?: boolean; +} +type EntityWithOrder = Record; + +interface FindFilter { + entityId?: number | number[]; + entityTypeId?: number | number[]; + boardId?: number | number[]; + stageId?: number | number[]; + fieldValue?: { type: FieldType; value: string }; + exclude?: { entityId?: number | number[]; entityTypeId?: number | number[] }; +} +interface CalculateEntity { + entityTypeId: number; + entityId: number; + recalculate?: boolean; + children?: CalculateEntity[]; +} + +//TODO: need to refactor all class +@Injectable() +export class EntityService { + constructor( + @InjectRepository(Entity) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly fieldValueService: FieldValueService, + private readonly fieldService: FieldService, + private readonly entityLinkService: EntityLinkService, + @Inject(forwardRef(() => ExternalEntityService)) + private readonly externalEntityService: ExternalEntityService, + private readonly entityTypeService: EntityTypeService, + private readonly fileLinkService: FileLinkService, + @Inject(forwardRef(() => BoardStageService)) + private readonly stageService: BoardStageService, + @Inject(forwardRef(() => BoardService)) + private readonly boardService: BoardService, + private readonly fieldSettingsService: FieldSettingsService, + @Inject(forwardRef(() => ChatService)) + private readonly chatService: ChatService, + private readonly shipmentService: ShipmentService, + private readonly entityEmitter: EntityServiceEmitter, + private readonly entityInfoService: EntityInfoService, + @Inject(forwardRef(() => EntitySearchService)) + private readonly entitySearchService: EntitySearchService, + ) {} + + ////////////////// + // Create + ////////////////// + //TODO: remove this + /** + * @deprecated + */ + async save(entity: Entity): Promise { + return this.repository.save(entity); + } + + async createAndGetDto(accountId: number, user: User, dto: CreateEntityDto): Promise { + const entity = await this.create(accountId, user, dto); + + return this.getDtoForEntity(accountId, user, entity); + } + + async create( + accountId: number, + user: User, + dto: CreateEntityDto, + userNotification = UserNotification.Default, + ): Promise { + const entity = await this.createEntity({ + accountId, + user, + data: { + entityTypeId: dto.entityTypeId, + name: dto.name, + boardId: dto.boardId, + stageId: dto.stageId, + ownerId: dto.responsibleUserId, + sorting: dto.sorting, + focused: dto.focused, + }, + }); + + if (dto.entityLinks) { + await this.entityLinkService.processMany({ accountId, links: dto.entityLinks }); + } + + if (dto.fieldValues?.length) { + const { updateParticipants, value } = await this.fieldValueService.processBatch({ + accountId, + entityId: entity.id, + dtos: dto.fieldValues, + }); + await this.postProcessFieldValues(accountId, entity, updateParticipants, value, true); + } + + this.entityEmitter.emitCreatedEvent(accountId, entity, { userNotification }); + + return entity; + } + + async createSimpleAndGetInfo({ + accountId, + user, + dto, + options, + }: { + accountId: number; + user: User; + dto: CreateSimpleEntityDto; + options?: CreateSimpleOptions; + }): Promise { + const entities = await this.createSimple({ accountId, user, dto, options }); + + return Promise.all(entities.map((entity) => this.entityInfoService.getEntityInfo({ user, entity, access: true }))); + } + + async createSimple({ + accountId, + user, + dto, + options, + }: { + accountId: number; + user: User; + dto: CreateSimpleEntityDto; + options?: CreateSimpleOptions; + }): Promise { + let entity = options?.checkDuplicate + ? await this.findDuplicateEntity({ + accountId, + user, + dto, + checkActive: options?.checkActiveLead, + checkActiveLeadContactEntityId: options?.checkActiveLeadContactEntityId, + }) + : null; + const entityExists = !!entity; + + let checkActiveLeadContactEntityId: number | undefined = undefined; + if (entityExists && options?.checkActiveLead) { + const entityType = await this.entityTypeService.findOne(accountId, { id: entity.entityTypeId }); + if (entityType.entityCategory === EntityCategory.CONTACT) { + checkActiveLeadContactEntityId = entity.id; + } + } + + if (!entityExists) { + entity = await this.createEntity({ + accountId, + user, + data: { + entityTypeId: dto.entityTypeId, + name: dto.name, + boardId: dto.boardId, + stageId: dto.stageId, + createdAt: options?.createdAt, + ownerId: dto.ownerId, + focused: dto.focused, + }, + }); + } + + if (options?.linkedEntities?.length) { + await Promise.all( + options.linkedEntities.map((targetId) => + this.entityLinkService.create({ accountId, sourceId: entity.id, targetId }), + ), + ); + } + if (dto.linkedEntities?.length) { + await Promise.all( + dto.linkedEntities + .filter((linkedEntity): linkedEntity is number => typeof linkedEntity === 'number') + .map((targetId) => this.entityLinkService.create({ accountId, sourceId: entity.id, targetId })), + ); + } + + if (dto.fieldValues?.length) { + const { updateParticipants, value } = await this.fieldValueService.createManySimple({ + accountId, + entityTypeId: entity.entityTypeId, + entityId: entity.id, + dtos: dto.fieldValues, + }); + await this.postProcessFieldValues(accountId, entity, updateParticipants, value, true); + } + + const entities = [entity]; + if (dto.linkedEntities?.length) { + entities.push( + ...( + await Promise.all( + dto.linkedEntities + .filter((linkedEntity): linkedEntity is CreateSimpleEntityDto => typeof linkedEntity !== 'number') + .map((dto) => + this.createSimple({ + accountId, + user, + dto, + options: { ...options, linkedEntities: [entity.id], checkActiveLeadContactEntityId }, + }), + ), + ) + ).flat(), + ); + } + + if (!entityExists) { + this.entityEmitter.emitCreatedEvent(accountId, entity, { userNotification: options?.userNotification }); + } + + return entities; + } + + private async createEntity({ + accountId, + user, + data, + }: { + accountId: number; + user: User; + data: { + entityTypeId: number; + name?: string | null; + boardId?: number | null; + stageId?: number | null; + createdAt?: Date | null; + ownerId?: number | null; + sorting?: ManualSorting | null; + focused?: boolean | null; + }; + }): Promise { + const entityType = await this.entityTypeService.findOne(accountId, { id: data.entityTypeId }); + await this.authService.check({ action: 'create', user, authorizable: entityType, throwError: true }); + + const name = + data.name && data.name.trim() !== '' + ? data.name + : `${entityType.name} ${Math.trunc(new Date().getTime() / 1000)}`; + + const stage = await this.findStage({ + accountId, + entityTypeId: data.entityTypeId, + boardId: data.boardId, + stageId: data.stageId, + }); + + const weight = await this.calculateWeight({ + accountId, + entityTypeId: entityType.id, + boardId: stage?.boardId, + afterId: data.sorting?.afterId, + beforeId: data.sorting?.beforeId, + }); + return this.repository.save( + new Entity( + accountId, + name, + entityType.id, + data.ownerId ?? user.id, + stage?.boardId ?? null, + stage?.id ?? null, + user.id, + weight, + data.focused ?? false, + this.getClosedAt(null, stage), + null, + data.createdAt, + null, + null, + null, + ), + ); + } + + private async findDuplicateEntity({ + accountId, + user, + dto, + checkActive, + checkActiveLeadContactEntityId, + }: { + accountId: number; + user: User; + dto: CreateSimpleEntityDto; + checkActive?: boolean | null; + checkActiveLeadContactEntityId?: number | null; + }): Promise { + const findByField = async ({ + accountId, + user, + dto, + fieldType, + checkActive, + }: { + accountId: number; + user: User; + dto: CreateSimpleEntityDto; + fieldType: FieldType; + checkActive?: boolean | null; + }): Promise => { + const fieldValue = dto.fieldValues.find((fv) => fv.fieldType === fieldType); + if (fieldValue) { + const { entities } = await this.entitySearchService.search({ + accountId, + user, + filter: { entityTypeId: dto.entityTypeId, fieldType, fieldValue: fieldValue.value as string }, + paging: new PagingQuery(0, checkActive ? 20 : 1), + }); + if (entities.length) { + return checkActive ? entities.find((e) => !e.closedAt) : entities[0]; + } + } + return null; + }; + + let entity = await findByField({ accountId, user, dto, fieldType: FieldType.Phone, checkActive }); + if (!entity) entity = await findByField({ accountId, user, dto, fieldType: FieldType.Email, checkActive }); + + if (!entity && checkActiveLeadContactEntityId) { + const linked = await this.findLinkedEntities({ accountId, entityId: checkActiveLeadContactEntityId }); + entity = linked + .filter((l) => l.entityTypeId === dto.entityTypeId) + .sort((a, b) => DateUtil.sort(b.createdAt, a.createdAt)) + .find((e) => !e.closedAt); + } + + return entity; + } + + async copyToStage( + accountId: number, + entityId: number, + stageId: number, + copyOriginal: boolean, + ): Promise { + const sourceEntity = await this.findOne(accountId, { entityId }); + const stage = await this.stageService.findOne({ accountId, stageId }); + + if (sourceEntity && stage) { + let copiedEntity = sourceEntity.copy(); + if (!copyOriginal) { + copiedEntity.update({ + stageId: stage.id, + boardId: stage.boardId, + closedAt: this.getClosedAt(sourceEntity.closedAt, stage), + }); + } + copiedEntity = await this.repository.save(copiedEntity); + await this.entityLinkService.copyEntityLinks({ + accountId, + sourceId: sourceEntity.id, + targetId: copiedEntity.id, + }); + await this.fieldValueService.copyEntityFieldValues({ + accountId, + sourceEntityId: sourceEntity.id, + targetEntityId: copiedEntity.id, + }); + this.entityEmitter.emitCreatedEvent(accountId, copiedEntity, { + copiedFrom: copyOriginal ? sourceEntity.id : undefined, + }); + + if (copyOriginal) { + sourceEntity.update({ + stageId: stage.id, + boardId: stage.boardId, + closedAt: this.getClosedAt(sourceEntity.closedAt, stage), + }); + this.repository.save(sourceEntity); + this.entityEmitter.emitUpdatedEvent(accountId, sourceEntity); + this.entityEmitter.emitStageChangedEvent(accountId, sourceEntity); + } + + return copiedEntity; + } + return null; + } + + private async findStage({ + accountId, + entityTypeId, + boardId, + stageId, + }: { + accountId: number; + entityTypeId: number; + boardId?: number | null; + stageId?: number | null; + }) { + const stage = + stageId || boardId + ? await this.stageService.findOne({ accountId, boardId: boardId ?? undefined, stageId: stageId ?? undefined }) + : null; + if (stage) return stage; + + const board = await this.boardService.findOneId({ accountId, recordId: entityTypeId }); + return board ? this.stageService.findOne({ accountId, boardId: board }) : null; + } + ////////////////// + + ////////////////// + // Get + ////////////////// + async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + async findMany(accountId: number, filter?: FindFilter, paging?: PagingQuery): Promise { + return this.createFindQb(accountId, filter).skip(paging?.skip).take(paging?.take).orderBy('e.id').getMany(); + } + + async getByIdsOrdered({ accountId, entityIds }: { accountId: number; entityIds: number[] }): Promise { + const entities = await this.createFindQb(accountId, { entityId: entityIds }).getMany(); + + const entityMap = new Map(entities.map((entity) => [entity.id, entity])); + return entityIds.map((id) => entityMap.get(id)).filter(Boolean); + } + + async ensureExistId(accountId: number, id: number): Promise { + const entity = await this.repository.findOneBy({ accountId, id }); + return entity ? id : null; + } + + async getDtoByIdForUI(accountId: number, user: User, entityId: number, checkPermission = true): Promise { + const entity = await this.findOne(accountId, { entityId }); + if (!entity) { + throw NotFoundError.withId(Entity, entityId); + } + + return this.getDtoForEntity(accountId, user, entity, checkPermission); + } + + async getDtoForEntity(accountId: number, user: User, entity: Entity, checkPermission = true): Promise { + if (checkPermission) { + await this.authService.check({ action: 'view', user, authorizable: entity, throwError: true }); + } + + const allFieldValues = await this.fieldValueService.findMany({ accountId, entityId: entity.id }); + const fieldValues = await this.excludeHiddenFields(accountId, user.id, entity, allFieldValues); + const entityLinks = await this.entityLinkService.findMany({ accountId, sourceId: entity.id }); + const allowedLinks: EntityLink[] = []; + for (const link of entityLinks) { + const linkedEntity = await this.repository.findOneBy({ id: link.targetId }); + if (await this.authService.check({ action: 'view', user, authorizable: linkedEntity })) { + allowedLinks.push(link); + } + } + const externalEntities = await this.externalEntityService.getExternalEntitiesWithType(entity.id); + const userRights = await this.authService.getUserRights({ user, authorizable: entity }); + const chats = await this.chatService.findMany({ + accountId, + filter: { entityId: entity.id }, + accessUserId: user.id, + }); + const shipment = await this.shipmentService.findOne({ accountId, user, filter: { entityId: entity.id } }); + + return EntityDto.fromEntity( + entity, + fieldValues, + allowedLinks?.map((l) => l.toDto()), + externalEntities, + userRights, + chats.map((chat) => chat.toDto()), + shipment?.shippedAt?.toISOString() ?? null, + ); + } + + async getFileLinks(account: Account, entityId: number): Promise { + return this.fileLinkService.getFileLinkDtos(account, FileLinkSource.ENTITY, entityId); + } + + async getDocumentLinks(account: Account, entityId: number): Promise { + return this.fileLinkService.getFileLinkDtos(account, FileLinkSource.ENTITY_DOCUMENT, entityId, 'DESC'); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('e').where('e.account_id = :accountId', { accountId }); + + if (filter?.entityId) { + if (Array.isArray(filter.entityId)) { + qb.andWhere('e.id IN (:...entityIds)', { entityIds: filter.entityId }); + } else { + qb.andWhere('e.id = :entityId', { entityId: filter.entityId }); + } + } + if (filter?.entityTypeId) { + if (Array.isArray(filter.entityTypeId)) { + qb.andWhere('e.entity_type_id IN (:...entityTypeIds)', { entityTypeIds: filter.entityTypeId }); + } else { + qb.andWhere('e.entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + } + } + if (filter?.exclude?.entityId) { + if (Array.isArray(filter.exclude.entityId)) { + if (filter.exclude.entityId.length) { + qb.andWhere('e.id NOT IN (:...exEntityIds)', { exEntityIds: filter.exclude.entityId }); + } + } else { + qb.andWhere('e.id != :exEntityId', { exEntityId: filter.exclude.entityId }); + } + } + if (filter?.exclude?.entityTypeId) { + if (Array.isArray(filter.exclude.entityTypeId)) { + if (filter.exclude.entityTypeId.length) { + qb.andWhere('e.entity_type_id NOT IN (:...exEntityTypeIds)', { + exEntityTypeIds: filter.exclude.entityTypeId, + }); + } + } else { + qb.andWhere('e.entity_type_id != :exEntityTypeId', { exEntityTypeId: filter.exclude.entityTypeId }); + } + } + if (filter?.boardId) { + if (Array.isArray(filter.boardId)) { + qb.andWhere('e.board_id IN (:...boardIds)', { boardIds: filter.boardId }); + } else { + qb.andWhere('e.board_id = :boardId', { boardId: filter.boardId }); + } + } + if (filter?.stageId) { + if (Array.isArray(filter.stageId)) { + qb.andWhere('e.stage_id IN (:...stageIds)', { stageIds: filter.stageId }); + } else { + qb.andWhere('e.stage_id = :stageId', { stageId: filter.stageId }); + } + } + if (filter?.fieldValue) { + qb.leftJoin('field_value', 'fv', 'fv.entity_id = e.id AND fv.field_type = :fieldType', { + fieldType: filter.fieldValue.type, + }).andWhere(`(fv.payload::jsonb::text ilike :fieldValue)`, { fieldValue: `%${filter.fieldValue.value}%` }); + } + + return qb; + } + ////////////////// + + ////////////////// + // Update + ////////////////// + async updateAndGetDto(accountId: number, user: User, id: number, dto: UpdateEntityDto): Promise { + const entity = await this.update(accountId, user, id, dto); + + return this.getDtoForEntity(accountId, user, entity); + } + + async update( + accountId: number, + user: User | null, + entityId: number, + dto: UpdateEntityDto, + { + userNotification = UserNotification.Default, + skipCheckRestrictions = false, + skipCheckPermissions = false, + }: { + userNotification?: UserNotification; + skipCheckRestrictions?: boolean; + skipCheckPermissions?: boolean; + } = {}, + ): Promise { + const entity = await this.repository.findOneBy({ accountId, id: entityId }); + if (!entity) { + throw NotFoundError.withId(Entity, entityId); + } + + if (!skipCheckPermissions && user) { + await this.authService.check({ action: 'edit', user, authorizable: entity, throwError: true }); + } + + const ownerChanged = dto.responsibleUserId && entity.responsibleUserId !== dto.responsibleUserId; + const stageChanged = dto.stageId && entity.stageId !== dto.stageId; + const stage = entity.stageId + ? await this.stageService.findOne({ accountId, stageId: dto.stageId ?? entity.stageId }) + : null; + if (entity.stageId && !stage) { + throw NotFoundError.withId(BoardStage, dto.stageId ?? entity.stageId); + } + + if (!skipCheckRestrictions && stageChanged && user) { + await this.checkFieldsRestriction(accountId, user.id, entity, stage.id, dto); + } + + dto.closedAt = this.getClosedAt(entity.closedAt, stage); + if (stage && !dto.boardId) dto.boardId = stage.boardId; + entity.update(dto); + if (dto.sorting) { + entity.weight = await this.calculateWeight({ + accountId, + entityTypeId: entity.entityTypeId, + boardId: stage ? stage.boardId : null, + afterId: dto.sorting.afterId, + beforeId: dto.sorting.beforeId, + }); + } + await this.repository.save(entity); + + let linksChanged = false; + if (dto.entityLinks) { + linksChanged = await this.entityLinkService.processMany({ accountId, links: dto.entityLinks }); + } + if (dto.fieldValues?.length) { + const { recalculate, updateParticipants, value } = await this.fieldValueService.processBatch({ + accountId, + entityId: entity.id, + dtos: dto.fieldValues, + }); + await this.postProcessFieldValues(accountId, entity, updateParticipants, value, recalculate || linksChanged); + } else if (linksChanged) { + await this.calculateFormulas(accountId, entity); + } + + this.entityEmitter.emitUpdatedEvent(accountId, entity, { userNotification }); + if (stageChanged) { + this.entityEmitter.emitStageChangedEvent(accountId, entity, { userNotification }); + } + if (ownerChanged && user) { + this.entityEmitter.emitOwnerChangedEvent(accountId, user.id, entity, { userNotification }); + } + + return entity; + } + + async updateFieldValue( + accountId: number, + user: User, + entityId: number, + fieldId: number, + dto: UpdateFieldValueDto, + ): Promise { + dto.fieldId = dto.fieldId || fieldId; + await this.update(accountId, user, entityId, { fieldValues: [dto] }); + } + + async updateValue(accountId: number, entityId: number, price: number): Promise { + const entity = await this.findOne(accountId, { entityId }); + + if (entity) { + const field = await this.fieldService.findOne({ + accountId, + entityTypeId: entity.entityTypeId, + type: FieldType.Value, + }); + + if (field && !field.value) { + await this.fieldValueService.setValue({ + accountId, + entityId, + fieldId: field.id, + dto: { + fieldId: field.id, + fieldType: field.type, + payload: { value: price }, + }, + }); + + entity.value = price; + await this.repository.save(entity); + + await this.calculateFormulas(accountId, entity); + + this.entityEmitter.emitUpdatedEvent(accountId, entity); + } + } + } + + async batchUpdateEntityIds( + accountId: number, + user: User, + entityIds: number[], + { + responsibleUserId, + boardId, + stageId, + responsibleEntityTypeIds, + }: { responsibleUserId?: number; boardId?: number; stageId?: number; responsibleEntityTypeIds?: number[] }, + userNotification = UserNotification.Default, + ): Promise { + let updated = 0; + for (const entityId of entityIds) { + try { + await this.update(accountId, user, entityId, { responsibleUserId, boardId, stageId }, { userNotification }); + updated++; + + if (responsibleEntityTypeIds?.length) { + const links = await this.entityLinkService.findMany({ accountId, sourceId: entityId }); + for (const link of links) { + const linkedEntity = await this.repository.findOneBy({ id: link.targetId }); + if (responsibleEntityTypeIds.includes(linkedEntity.entityTypeId)) { + await this.update(accountId, user, linkedEntity.id, { responsibleUserId }, { userNotification }); + } + } + } + } catch (e) { + if (!(e instanceof ForbiddenException)) throw e; + } + } + return updated; + } + + async removeUser({ accountId, userId, newUserId }: { accountId: number; userId: number; newUserId?: number | null }) { + if (newUserId) { + await this.repository.update( + { accountId, responsibleUserId: userId }, + { responsibleUserId: newUserId, updatedAt: DateUtil.now() }, + ); + + await this.repository + .createQueryBuilder() + .update() + .set({ + participantIds: () => + // eslint-disable-next-line max-len + `(SELECT jsonb_agg(DISTINCT CASE WHEN elem::integer = ${userId} THEN ${newUserId} ELSE elem::integer END) FROM jsonb_array_elements(participant_ids) AS elem)`, + }) + .where('account_id = :accountId', { accountId }) + .andWhere('participant_ids IS NOT NULL') + .andWhere(`participant_ids @> jsonb_build_array(${userId})`) + .execute(); + } else { + await this.repository + .createQueryBuilder() + .update() + .set({ + participantIds: () => + // eslint-disable-next-line max-len + `(SELECT jsonb_agg(elem) FROM jsonb_array_elements(participant_ids) AS elem WHERE elem::integer != ${userId})`, + }) + .where('account_id = :accountId', { accountId }) + .andWhere('participant_ids IS NOT NULL') + .andWhere(`participant_ids @> jsonb_build_array(${userId})`) + .execute(); + } + } + + async changeStageForAll({ + accountId, + boardId, + stageId, + newStageId, + }: { + accountId: number; + boardId: number; + stageId: number; + newStageId: number; + }) { + const qb = this.repository + .createQueryBuilder('entity') + .select('entity.id', 'id') + .where('entity.account_id = :accountId', { accountId }) + .andWhere('entity.board_id = :boardId', { boardId }) + .andWhere('entity.stage_id = :stageId', { stageId }) + .limit(BatchProcessLimit); + let entities: { id: number }[] = []; + do { + entities = await qb.getRawMany<{ id: number }>(); + for (const entity of entities) { + await this.update( + accountId, + null, + entity.id, + { boardId: boardId, stageId: newStageId }, + { skipCheckRestrictions: true, skipCheckPermissions: true }, + ); + } + } while (entities.length); + } + ////////////////// + + ////////////////// + // Delete + ////////////////// + async delete(accountId: number, user: User, entityId: number, userNotification = UserNotification.Default) { + const entity = await this.repository.findOneBy({ accountId, id: entityId }); + if (entity) { + await this.authService.check({ action: 'delete', user, authorizable: entity, throwError: true }); + + const links = await this.entityLinkService.findMany({ accountId, sourceId: entityId }); + + await this.repository.delete({ accountId, id: entityId }); + this.fileLinkService.processFiles(accountId, FileLinkSource.ENTITY, entityId, []); + this.fileLinkService.processFiles(accountId, FileLinkSource.ENTITY_DOCUMENT, entityId, []); + + Promise.all( + links.map(async (link) => { + const linked = await this.findOne(accountId, { entityId: link.targetId }); + await this.calculateFormulas(accountId, linked); + }), + ); + + this.entityEmitter.emitDeletedEvent(accountId, entity, { userNotification }); + } + } + + async deleteMany( + accountId: number, + user: User, + entityIds: number[], + userNotification = UserNotification.Default, + ): Promise { + let deleted = 0; + for (const id of entityIds) { + try { + await this.delete(accountId, user, id, userNotification); + deleted++; + } catch (e) { + if (!(e instanceof ForbiddenException)) throw e; + } + } + return deleted; + } + ////////////////// + + ////////////////// + // Linked entities + ////////////////// + async findLinkedEntities({ + accountId, + entityId, + entityTypeId, + }: { + accountId: number; + entityId: number; + entityTypeId?: number; + }): Promise { + const qb = this.repository + .createQueryBuilder('e') + .innerJoin(EntityLink, 'el', 'el.target_id = e.id and el.source_id = :entityId', { entityId }) + .where('e.account_id = :accountId', { accountId }) + .orderBy('el.sort_order'); + if (entityTypeId) { + qb.andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }); + } + + return qb.getMany(); + } + + async findFirstLinkedEntityByType(accountId: number, entityId: number): Promise { + const links = await this.entityLinkService.findMany({ accountId, sourceId: entityId }); + const entities = await this.repository.findBy({ id: In(links.map((l) => l.targetId)) }); + const groupedEntities = entities.reduce((acc, entity) => { + const link = links.find((l) => l.targetId === entity.id); + if (!acc[entity.entityTypeId]) { + acc[entity.entityTypeId] = { entity, order: link.sortOrder }; + } else if (acc[entity.entityTypeId].order > link.sortOrder) { + acc[entity.entityTypeId] = { entity, order: link.sortOrder }; + } + return acc; + }, {}); + return Object.values(groupedEntities).map((g) => g.entity); + } + + async findLastLinkedEntity({ + accountId, + entityId, + category, + }: { + accountId: number; + entityId: number; + category: EntityCategory; + }): Promise { + return this.repository + .createQueryBuilder('e') + .innerJoin('entity_link', 'el', 'el.target_id = e.id and el.source_id = :entityId', { entityId }) + .innerJoin('entity_type', 'et', 'et.id = e.entity_type_id and et.entity_category = :category', { category }) + .where('e.account_id = :accountId', { accountId }) + .orderBy('e.created_at', 'DESC') + .getOne(); + } + ////////////////// + + ////////////////// + // Entity values helpers + ////////////////// + private getClosedAt(closedAt: Date | null, stage: BoardStage | null | undefined): Date | null { + return stage?.isSystem ? (closedAt ?? DateUtil.now()) : null; + } + ////////////////// + + ////////////////// + // Weight helpers + ////////////////// + private async calculateWeight({ + accountId, + entityTypeId, + boardId, + afterId, + beforeId, + }: { + accountId: number; + entityTypeId: number; + boardId: number | null; + afterId?: number | null; + beforeId?: number | null; + }): Promise { + let afterWeight = afterId ? await this.getWeightById(accountId, afterId) : null; + let beforeWeight = beforeId ? await this.getWeightById(accountId, beforeId) : null; + if (afterWeight === null && beforeWeight === null) { + const minWeight = await this.getMinWeight(accountId, entityTypeId, boardId); + return minWeight - Weight.step; + } else if (afterWeight !== null && beforeWeight === null) { + beforeWeight = await this.getMinWeightMoreThan(accountId, entityTypeId, boardId, afterWeight); + if (beforeWeight === null) { + return afterWeight + Weight.step; + } + } else if (afterWeight === null && beforeWeight !== null) { + afterWeight = await this.getMaxWeightLessThan(accountId, entityTypeId, boardId, beforeWeight); + if (afterWeight === null) { + return beforeWeight - Weight.step; + } + } + return (afterWeight + beforeWeight) / 2.0; + } + + private async getWeightById(accountId: number, id: number): Promise { + const result = await this.repository.findOne({ where: { accountId, id }, select: { weight: true } }); + return result ? result.weight : null; + } + + private async getMinWeight(accountId: number, entityTypeId: number, boardId: number | null): Promise { + const query = this.repository + .createQueryBuilder('e') + .select('min(e.weight)', 'weight') + .where('e.account_id = :accountId', { accountId }) + .andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }); + if (boardId) { + query.andWhere('e.board_id = :boardId', { boardId }); + } + const { weight } = await query.getRawOne<{ weight: number | null }>(); + return weight ?? Weight.min; + } + + private async getMinWeightMoreThan( + accountId: number, + entityTypeId: number, + boardId: number | null, + limitWeight: number, + ): Promise { + const query = this.repository + .createQueryBuilder('e') + .select('min(e.weight)', 'weight') + .where('e.account_id = :accountId', { accountId }) + .andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }) + .andWhere('e.weight > :limitWeight', { limitWeight }); + if (boardId) { + query.andWhere('e.board_id = :boardId', { boardId }); + } + const { weight } = await query.getRawOne(); + return weight; + } + + private async getMaxWeightLessThan( + accountId: number, + entityTypeId: number, + boardId: number | null, + limitWeight: number, + ): Promise { + const query = this.repository + .createQueryBuilder('e') + .select('max(e.weight)', 'weight') + .where('e.account_id = :accountId', { accountId }) + .andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }) + .andWhere('e.weight < :limitWeight', { limitWeight }); + if (boardId) { + query.andWhere('e.board_id = :boardId', { boardId }); + } + const { weight } = await query.getRawOne(); + return weight; + } + ////////////////// + + ////////////////// + // Field values + ////////////////// + private async postProcessFieldValues( + accountId: number, + entity: Entity, + updateParticipants: boolean, + value: number | null | undefined, + calculateFormulas: boolean, + ) { + if (updateParticipants) { + entity.participantIds = await this.fieldValueService.getParticipantIds({ accountId, entityId: entity.id }); + await this.repository.update({ accountId, id: entity.id }, { participantIds: entity.participantIds }); + } + if (value !== undefined && value !== null) { + entity.value = value; + await this.repository.update({ accountId, id: entity.id }, { value }); + } + + if (calculateFormulas) { + await this.calculateFormulas(accountId, entity); + } + } + + async recalculateFormulas({ accountId, entityTypeId }: { accountId: number; entityTypeId: number }) { + const batchSize = 100; + let page = 0; + let entities: Entity[]; + + do { + entities = await this.findMany(accountId, { entityTypeId }, new PagingQuery(page * batchSize, batchSize)); + + for (const entity of entities) { + await this.calculateFormulas(accountId, entity); + + const fieldValue = await this.fieldValueService.findOne({ + accountId, + entityId: entity.id, + type: FieldType.Value, + }); + if (fieldValue) { + entity.value = fieldValue.getValue() ?? entity.value; + await this.repository.update({ accountId, id: entity.id }, { value: entity.value }); + } + } + + page++; + } while (entities.length === batchSize); + } + + private async calculateFormulas(accountId: number, entity: Entity) { + const entityHierarchy = await this.buildCalculationHierarchy({ accountId, entity }); + await this.fieldValueService.calculateFormulas({ + accountId, + calcEntity: entityHierarchy, + previousEntityIds: [], + hasUpdates: true, + }); + } + + private async buildCalculationHierarchy({ + accountId, + entity, + previous = [], + }: { + accountId: number; + entity: Entity; + previous?: CalculateEntity[]; + }): Promise { + const children = previous.length ? [previous[previous.length - 1]] : []; + const calcEntity = { entityId: entity.id, entityTypeId: entity.entityTypeId, recalculate: true, children }; + + const links = await this.entityLinkService.findMany({ accountId, sourceId: entity.id }); + if (links.length) { + const linkedEntities = await this.findMany(accountId, { + entityId: links.map((l) => l.targetId), + exclude: { entityId: previous.map((prev) => prev.entityId) }, + }); + if (linkedEntities.length) { + const prevEntityTypeIds = previous.map((prev) => prev.entityTypeId); + calcEntity.children.push( + ...(await Promise.all( + linkedEntities.map((linked) => + prevEntityTypeIds.includes(linked.entityTypeId) + ? { entityId: linked.id, entityTypeId: linked.entityTypeId } + : this.buildCalculationHierarchy({ + accountId, + entity: linked, + previous: [...previous, { entityId: entity.id, entityTypeId: entity.entityTypeId }], + }), + ), + )), + ); + } + } + + return calcEntity; + } + ////////////////// + + ////////////////// + // Field settings + ////////////////// + private async checkFieldsRestriction( + accountId: number, + userId: number, + entity: Entity, + stageId: number | null, + dto?: UpdateEntityDto, + ) { + if (dto) { + const updatedFieldIds = dto.fieldValues?.filter((f) => f.state !== ObjectState.Unchanged)?.map((f) => f.fieldId); + if (updatedFieldIds?.length > 0) { + const readonlyFieldIds = await this.fieldSettingsService.getRestrictedFields({ + accountId, + entityTypeId: entity.entityTypeId, + access: FieldAccess.READONLY, + userId, + }); + if (updatedFieldIds.some((id) => readonlyFieldIds.includes(id))) { + throw new ReadonlyFieldChangedError(); + } + } + } + if (stageId) { + const requiredFieldIds = await this.fieldSettingsService.getRestrictedFields({ + accountId, + entityTypeId: entity.entityTypeId, + access: FieldAccess.REQUIRED, + userId, + stageId, + }); + if (requiredFieldIds.length > 0) { + const fieldValues = await this.fieldValueService.findMany({ accountId, entityId: entity.id }); + for (const requiredFieldId of requiredFieldIds) { + const dtoValue = dto?.fieldValues?.find((f) => f.fieldId === requiredFieldId); + if ( + (dtoValue && dtoValue.state === ObjectState.Deleted) || + (!dtoValue && !fieldValues.find((fv) => fv.fieldId === requiredFieldId)) + ) { + throw new RequiredFieldEmptyError(); + } + } + } + } + } + + private async excludeHiddenFields(accountId: number, userId: number, entity: Entity, fieldValues: FieldValue[]) { + const hiddenFieldIds = await this.fieldSettingsService.getRestrictedFields({ + accountId, + entityTypeId: entity.entityTypeId, + access: FieldAccess.HIDDEN, + userId, + stageId: entity.stageId, + }); + return hiddenFieldIds.length > 0 ? fieldValues.filter((f) => !hiddenFieldIds.includes(f.fieldId)) : fieldValues; + } + ////////////////// + + ////////////////// + // Automation helpers + ////////////////// + async applyAutomation({ + accountId, + processId, + entityTypeId, + stageId, + }: { + accountId: number; + processId: number; + entityTypeId: number; + stageId?: number | null; + }) { + const entities = await this.findMany(accountId, { entityTypeId, stageId }); + entities.forEach((entity) => this.entityEmitter.emitProcessStart({ accountId, entity, processId })); + } + ////////////////// +} diff --git a/backend/src/CRM/Service/Entity/ProjectEntityBoardCardService.ts b/backend/src/CRM/Service/Entity/ProjectEntityBoardCardService.ts new file mode 100644 index 0000000..2b3439f --- /dev/null +++ b/backend/src/CRM/Service/Entity/ProjectEntityBoardCardService.ts @@ -0,0 +1,151 @@ +import { Injectable } from '@nestjs/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { FieldCode } from '@/modules/entity/entity-field/field/enums/field-code.enum'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value/field-value.service'; + +import { EntityCategory } from '../../common'; + +import { Entity } from '../../Model/Entity/Entity'; +import { Task } from '../../task/entities'; +import { TaskService } from '../../task/task.service'; + +import { EntityBoardCard } from './Dto/Board/EntityBoardCard'; +import { ProjectEntityCard } from './Dto/Board/ProjectEntityCard'; +import { TasksCount } from './Dto/Board/TasksCount'; + +@Injectable() +export class ProjectEntityBoardCardService { + constructor( + private readonly authService: AuthorizationService, + private readonly fieldService: FieldService, + private readonly fieldValueService: FieldValueService, + private readonly taskService: TaskService, + ) {} + + public async getBoardCards( + accountId: number, + user: User, + entityTypeId: number, + entities: Entity[], + entityCategory: EntityCategory, + ): Promise { + const startDateField = await this.fieldService.findOne({ accountId, entityTypeId, code: FieldCode.StartDate }); + const endDateField = await this.fieldService.findOne({ accountId, entityTypeId, code: FieldCode.EndDate }); + + const taskResponsibles = await this.authService.whoCanView({ user, authorizable: Task.getAuthorizable() }); + + const cards = await Promise.all( + entities.map(async (entity, idx) => { + const card = await this.createBoardCard({ + accountId, + user, + entity, + entityCategory, + startDateFieldId: startDateField?.id, + endDateFieldId: endDateField?.id, + taskResponsibles, + }); + return { idx, card }; + }), + ); + + return cards.sort((a, b) => a.idx - b.idx).map((c) => c.card); + } + + public async getBoardCard( + accountId: number, + user: User, + entity: Entity, + entityCategory: EntityCategory, + ): Promise { + const startDateField = await this.fieldService.findOne({ + accountId, + entityTypeId: entity.entityTypeId, + code: FieldCode.StartDate, + }); + const endDateField = await this.fieldService.findOne({ + accountId, + entityTypeId: entity.entityTypeId, + code: FieldCode.EndDate, + }); + const taskResponsibles = await this.authService.whoCanView({ user, authorizable: Task.getAuthorizable() }); + + return this.createBoardCard({ + accountId, + user, + entity, + entityCategory, + startDateFieldId: startDateField?.id, + endDateFieldId: endDateField?.id, + taskResponsibles, + }); + } + + public async createBoardCard({ + accountId, + user, + entity, + entityCategory, + startDateFieldId, + endDateFieldId, + taskResponsibles, + }: { + accountId: number; + user: User; + entity: Entity; + entityCategory: EntityCategory; + startDateFieldId: number | null | undefined; + endDateFieldId: number | null | undefined; + taskResponsibles: number[] | undefined; + }): Promise { + const startDateFV = startDateFieldId + ? await this.fieldValueService.findOne({ accountId, entityId: entity.id, fieldId: startDateFieldId }) + : null; + const endDateFV = endDateFieldId + ? await this.fieldValueService.findOne({ accountId, entityId: entity.id, fieldId: endDateFieldId }) + : null; + + const startDate = startDateFV ? startDateFV.getValue() : null; + const endDate = endDateFV ? endDateFV.getValue() : null; + + const tasksCount = await this.getTasksCount({ accountId, entityId: entity.id, taskResponsibles }); + const userRights = await this.authService.getUserRights({ user, authorizable: entity }); + + return new EntityBoardCard({ + id: entity.id, + entityCategory, + boardId: entity.boardId, + stageId: entity.stageId, + price: null, + weight: entity.weight, + focused: entity.focused, + closedAt: entity.closedAt?.toISOString() ?? null, + data: ProjectEntityCard.create(entity, startDate, endDate, tasksCount, userRights), + }); + } + + private async getTasksCount({ + accountId, + entityId, + taskResponsibles, + }: { + accountId: number; + entityId: number; + taskResponsibles: number[] | undefined; + }): Promise { + const tasks = + !taskResponsibles || taskResponsibles.length + ? await this.taskService.findMany({ accountId, entityId, responsibles: taskResponsibles }) + : []; + + const notResolved = tasks.filter((task) => !task.isResolved); + const overdue = tasks.filter((task) => !task.isResolved && task.isTaskExpired()); + const today = tasks.filter((task) => !task.isResolved && task.isTaskToday()); + const resolved = tasks.filter((task) => task.isResolved); + + return new TasksCount(notResolved.length, overdue.length, today.length, resolved.length); + } +} diff --git a/backend/src/CRM/Service/Entity/entity-service.emitter.ts b/backend/src/CRM/Service/Entity/entity-service.emitter.ts new file mode 100644 index 0000000..a1f2408 --- /dev/null +++ b/backend/src/CRM/Service/Entity/entity-service.emitter.ts @@ -0,0 +1,238 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; + +import { UserNotification } from '@/common'; + +import { AutomationEventType, ProcessStartEvent, SendMessageEvent } from '@/modules/automation'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value'; +import { FieldType } from '@/modules/entity/entity-field/common'; + +import { CrmEventType, EntityCreatedEvent, EntityEvent, EntityOwnerChangedEvent } from '../../common'; +import { Entity } from '../../Model/Entity/Entity'; + +@Injectable() +export class EntityServiceEmitter { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly fieldValueService: FieldValueService, + ) {} + + public async emitCreatedEvent( + accountId: number, + entity: Entity, + options?: { + copiedFrom?: number; + userNotification?: UserNotification; + }, + ) { + this.eventEmitter.emit( + CrmEventType.EntityCreated, + new EntityCreatedEvent({ + accountId, + entityId: entity.id, + entityName: entity.name, + boardId: entity.boardId, + stageId: entity.stageId, + createdBy: entity.createdBy, + ownerId: entity.responsibleUserId, + entityTypeId: entity.entityTypeId, + copiedFrom: options?.copiedFrom, + userNotification: options?.userNotification ?? UserNotification.Default, + }), + ); + + if (!options?.copiedFrom) { + this.sendAutomationMessage(accountId, entity, CrmEventType.EntityCreated); + } + } + + public async emitUpdatedEvent( + accountId: number, + entity: Entity, + options?: { + userNotification?: UserNotification; + }, + ) { + this.eventEmitter.emit( + CrmEventType.EntityUpdated, + new EntityEvent({ + accountId, + entityId: entity.id, + entityTypeId: entity.entityTypeId, + boardId: entity.boardId, + stageId: entity.stageId, + ownerId: entity.responsibleUserId, + userNotification: options?.userNotification, + }), + ); + } + + public async emitStageChangedEvent( + accountId: number, + entity: Entity, + options?: { + userNotification?: UserNotification; + }, + ) { + this.eventEmitter.emit( + CrmEventType.EntityStageChanged, + new EntityEvent({ + accountId, + entityId: entity.id, + entityTypeId: entity.entityTypeId, + boardId: entity.boardId, + stageId: entity.stageId, + ownerId: entity.responsibleUserId, + userNotification: options?.userNotification, + }), + ); + + this.sendAutomationMessage(accountId, entity, CrmEventType.EntityStageChanged); + } + + public async emitOwnerChangedEvent( + accountId: number, + changedBy: number, + entity: Entity, + options?: { + copiedFrom?: number; + userNotification?: UserNotification; + }, + ) { + this.eventEmitter.emit( + CrmEventType.EntityOwnerChanged, + new EntityOwnerChangedEvent({ + accountId, + entityId: entity.id, + entityName: entity.name, + boardId: entity.boardId, + stageId: entity.stageId, + changedBy, + ownerId: entity.responsibleUserId, + entityTypeId: entity.entityTypeId, + userNotification: options?.userNotification, + }), + ); + + this.sendAutomationMessage(accountId, entity, CrmEventType.EntityOwnerChanged); + } + + public async emitDeletedEvent( + accountId: number, + entity: Entity, + options?: { + userNotification?: UserNotification; + }, + ) { + this.eventEmitter.emit( + CrmEventType.EntityDeleted, + new EntityEvent({ + accountId, + entityId: entity.id, + entityTypeId: entity.entityTypeId, + boardId: entity.boardId, + stageId: entity.stageId, + ownerId: entity.responsibleUserId, + userNotification: options?.userNotification, + }), + ); + } + + private async sendAutomationMessage(accountId: number, entity: Entity, eventType: CrmEventType) { + const entityVar = await this.createEntityVariable({ accountId, entity }); + + this.eventEmitter.emit( + AutomationEventType.SendMessage, + new SendMessageEvent({ + accountId, + message: { + name: [accountId, entity.entityTypeId, eventType], + variables: { accountId, entity: entityVar }, + }, + }), + ); + } + + public async emitProcessStart({ + accountId, + entity, + processId, + }: { + accountId: number; + entity: Entity; + processId: number; + }) { + const entityVar = await this.createEntityVariable({ accountId, entity }); + + this.eventEmitter.emit( + AutomationEventType.ProcessStart, + new ProcessStartEvent({ accountId, processId, variables: { accountId, entity: entityVar } }), + ); + } + + public async createEntityVariable({ accountId, entity }: { accountId: number; entity: Entity }) { + const fields = await this.getFieldValues({ accountId, entity }); + + return { + id: entity.id, + name: entity.name, + entityTypeId: entity.entityTypeId, + responsibleUserId: entity.responsibleUserId, + boardId: entity.boardId, + stageId: entity.stageId, + createdBy: entity.createdBy, + participantIds: entity.participantIds, + copiedFrom: entity.copiedFrom, + copiedCount: entity.copiedCount, + closedAt: entity.closedAt?.toISOString(), + createdAt: entity.createdAt.toISOString(), + updatedAt: entity.updatedAt.toISOString(), + fields, + }; + } + + private async getFieldValues({ + accountId, + entity, + }: { + accountId: number; + entity: Entity; + }): Promise> { + const fields = {}; + const fieldValues = await this.fieldValueService.findMany({ accountId, entityId: entity.id }); + fieldValues.forEach((fieldValue) => { + let value = fieldValue.getValue(); + if (value !== null && value !== undefined) { + switch (fieldValue.fieldType) { + case FieldType.Email: + case FieldType.Phone: + case FieldType.MultiText: + value = value + ? Array.isArray(value) + ? value + .filter((v) => v) + .map((v) => encodeURIComponent(v.toString())) + .join('\n') + : encodeURIComponent(value.toString()) + : null; + break; + case FieldType.Select: + case FieldType.ColoredSelect: + value = value ? [value] : null; + break; + case FieldType.Link: + case FieldType.Text: + case FieldType.RichText: + value = value ? encodeURIComponent(value.toString()) : null; + break; + case FieldType.Checklist: + value = null; //skip checklist fields + } + if (value !== null && value !== undefined) { + fields[`f_${fieldValue.fieldId}`] = value; + } + } + }); + return fields; + } +} diff --git a/backend/src/CRM/Service/Entity/entity-service.handler.ts b/backend/src/CRM/Service/Entity/entity-service.handler.ts new file mode 100644 index 0000000..e96235f --- /dev/null +++ b/backend/src/CRM/Service/Entity/entity-service.handler.ts @@ -0,0 +1,251 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; +import { UserService } from '@/modules/iam/user/user.service'; +import { FieldEvent, FieldEventType, FieldTypes } from '@/modules/entity/entity-field/common'; +import { + ActionEntityCreateSettings, + ActionEntityStageChangeSettings, + AutomationEventType, + EntityTypeApplyEvent, + OnAutomationJob, + EntityTypeActionType, + AutomationJob, + ChangeStageType, + ActionEntityResponsibleChangeSettings, + ActionEntityLinkedStageChangeSettings, +} from '@/modules/automation'; + +import { CrmEventType, EntityPriceUpdateEvent } from '../../common'; +import { Entity } from '../../Model/Entity/Entity'; +import { EntityService } from './EntityService'; +import { EntityServiceEmitter } from './entity-service.emitter'; + +interface EntityVariables { + id?: number | null; + stageId?: number | null; + entityTypeId?: number | null; + responsibleUserId?: number | null; +} + +interface EntityCreateVariables { + accountId: number; + entity?: EntityVariables | null; + entitySettings?: ActionEntityCreateSettings | null; +} + +interface EntityChangeStageVariables { + accountId: number; + entity?: EntityVariables | null; + entitySettings?: ActionEntityStageChangeSettings | null; +} + +interface EntityLinkedChangeStageVariables { + accountId: number; + entity?: EntityVariables | null; + entitySettings?: ActionEntityLinkedStageChangeSettings | null; +} + +interface EntityResponsibleChangeVariables { + accountId: number; + entity?: EntityVariables | null; + entitySettings?: ActionEntityResponsibleChangeSettings | null; +} + +@Injectable() +export class EntityServiceHandler { + private readonly logger = new Logger(EntityServiceHandler.name); + constructor( + private readonly userService: UserService, + private readonly entityService: EntityService, + private readonly entityEmitter: EntityServiceEmitter, + ) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + await this.entityService.removeUser(event); + } + + @OnEvent(CrmEventType.EntityPriceUpdate, { async: true }) + public async onEntityPriceUpdate(event: EntityPriceUpdateEvent) { + this.entityService.updateValue(event.accountId, event.entityId, event.price); + } + + @OnEvent(FieldEventType.FieldCreated, { async: true }) + public async onFieldCreated(event: FieldEvent) { + if (FieldTypes.formula.includes(event.type)) { + this.entityService.recalculateFormulas(event); + } + } + + @OnEvent(FieldEventType.FieldUpdated, { async: true }) + public async onFieldUpdated(event: FieldEvent) { + if (FieldTypes.formula.includes(event.type)) { + this.entityService.recalculateFormulas(event); + } + } + + @OnEvent(AutomationEventType.EntityTypeApply, { async: true }) + public async onAutomationApply(event: EntityTypeApplyEvent) { + this.entityService.applyAutomation({ + accountId: event.accountId, + processId: event.processId, + entityTypeId: event.entityTypeId, + stageId: event.stageId, + }); + } + + /** + * @deprecated use new @see handleStageChangeJob instead + */ + @OnAutomationJob(EntityTypeActionType.ChangeStage) + async handleStageChangeJobOld(job: AutomationJob): Promise<{ variables?: unknown }> { + return this.handleStageChangeJob(job); + } + @OnAutomationJob(EntityTypeActionType.EntityStageChange) + async handleStageChangeJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.entitySettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, entitySettings } = job.variables; + + const current = await this.entityService.findOne(accountId, { entityId: entity.id }); + if (current && (!current.stageId || entitySettings.allowAnyStage || current.stageId === entity.stageId)) { + const user = await this.userService.findOne({ accountId, id: entity.responsibleUserId }); + let updated: Entity = null; + switch (entitySettings.operationType) { + case ChangeStageType.Move: + updated = await this.entityService.update(accountId, user, entity.id, { stageId: entitySettings.stageId }); + break; + case ChangeStageType.CopyOriginal: + updated = await this.entityService.copyToStage(accountId, entity.id, entitySettings.stageId, true); + break; + case ChangeStageType.CopyNew: + updated = await this.entityService.copyToStage(accountId, entity.id, entitySettings.stageId, false); + break; + } + const updatedVar = updated + ? await this.entityEmitter.createEntityVariable({ accountId, entity: updated }) + : null; + return { variables: { ...job.variables, entity: updatedVar ?? entity } }; + } + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + } + return { variables: job.variables }; + } + + @OnAutomationJob(EntityTypeActionType.EntityLinkedStageChange) + async handleLinkedStageChangeJob( + job: AutomationJob, + ): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.entitySettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, entitySettings } = job.variables; + + const linkedEntities = await this.entityService.findLinkedEntities({ + accountId, + entityId: entity.id, + entityTypeId: entitySettings.entityTypeId, + }); + if (linkedEntities.length) { + const current = linkedEntities[0]; + const user = await this.userService.findOne({ accountId, id: current.responsibleUserId }); + let updated: Entity = null; + switch (entitySettings.operationType) { + case ChangeStageType.Move: + updated = await this.entityService.update(accountId, user, current.id, { + stageId: entitySettings.stageId, + }); + break; + case ChangeStageType.CopyOriginal: + updated = await this.entityService.copyToStage(accountId, current.id, entitySettings.stageId, true); + break; + case ChangeStageType.CopyNew: + updated = await this.entityService.copyToStage(accountId, current.id, entitySettings.stageId, false); + break; + } + const updatedVar = updated + ? await this.entityEmitter.createEntityVariable({ accountId, entity: updated }) + : null; + return { variables: { ...job.variables, entity: updatedVar ?? entity } }; + } + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + } + return { variables: job.variables }; + } + + @OnAutomationJob(EntityTypeActionType.EntityResponsibleChange) + async handleResponsibleChangeJob( + job: AutomationJob, + ): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.entitySettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, entitySettings } = job.variables; + + const current = await this.entityService.findOne(accountId, { entityId: entity.id }); + if (current && (!current.stageId || entitySettings.allowAnyStage || current.stageId === entity.stageId)) { + const user = await this.userService.findOne({ accountId, id: entity.responsibleUserId }); + + const updated = await this.entityService.update(accountId, user, entity.id, { + responsibleUserId: entitySettings.responsibleUserId, + }); + + const updatedVar = await this.entityEmitter.createEntityVariable({ accountId, entity: updated }); + return { variables: { ...job.variables, entity: updatedVar } }; + } + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + } + + return { variables: job.variables }; + } + + @OnAutomationJob(EntityTypeActionType.EntityCreate) + async handleCreateJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.entitySettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, entitySettings } = job.variables; + + const current = await this.entityService.findOne(accountId, { entityId: entity.id }); + if (current && (!current.stageId || entitySettings.allowAnyStage || current.stageId === entity.stageId)) { + const user = await this.userService.findOne({ accountId, id: entity.responsibleUserId }); + const [created] = await this.entityService.createSimple({ + accountId, + user, + dto: { + entityTypeId: entitySettings.entityTypeId, + boardId: entitySettings.boardId, + stageId: entitySettings.stageId, + ownerId: entitySettings.ownerId, + name: entitySettings.name, + }, + options: { linkedEntities: [entity.id] }, + }); + + const createdVar = await this.entityEmitter.createEntityVariable({ accountId, entity: created }); + return { variables: { ...job.variables, entity: createdVar } }; + } + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + } + return { variables: job.variables }; + } +} diff --git a/backend/src/CRM/Service/Entity/enums/task-indicator.enum.ts b/backend/src/CRM/Service/Entity/enums/task-indicator.enum.ts new file mode 100644 index 0000000..1d822ca --- /dev/null +++ b/backend/src/CRM/Service/Entity/enums/task-indicator.enum.ts @@ -0,0 +1,6 @@ +export enum TaskIndicator { + Empty = 'empty', + Overdue = 'overdue', + Today = 'today', + Upcoming = 'upcoming', +} diff --git a/backend/src/CRM/Service/Entity/errors/index.ts b/backend/src/CRM/Service/Entity/errors/index.ts new file mode 100644 index 0000000..98682d8 --- /dev/null +++ b/backend/src/CRM/Service/Entity/errors/index.ts @@ -0,0 +1,2 @@ +export * from './readonly-field-changed.error'; +export * from './required-field-empty.error'; diff --git a/backend/src/CRM/Service/Entity/errors/readonly-field-changed.error.ts b/backend/src/CRM/Service/Entity/errors/readonly-field-changed.error.ts new file mode 100644 index 0000000..1ed9d11 --- /dev/null +++ b/backend/src/CRM/Service/Entity/errors/readonly-field-changed.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class ReadonlyFieldChangedError extends ServiceError { + constructor(message = 'Readonly field changed') { + super({ errorCode: 'entity.readonly_field_changed', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/CRM/Service/Entity/errors/required-field-empty.error.ts b/backend/src/CRM/Service/Entity/errors/required-field-empty.error.ts new file mode 100644 index 0000000..7fe0d83 --- /dev/null +++ b/backend/src/CRM/Service/Entity/errors/required-field-empty.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class RequiredFieldEmptyError extends ServiceError { + constructor(message = 'Required field do not set') { + super({ errorCode: 'required_field_empty', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/CRM/Service/ExternalEntity/CreateExternalEntityDto.ts b/backend/src/CRM/Service/ExternalEntity/CreateExternalEntityDto.ts new file mode 100644 index 0000000..c620097 --- /dev/null +++ b/backend/src/CRM/Service/ExternalEntity/CreateExternalEntityDto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsUrl } from 'class-validator'; + +import { CreateSimpleEntityDto } from '../Entity/Dto'; + +export class CreateExternalEntityDto extends CreateSimpleEntityDto { + @ApiProperty() + @IsUrl() + @IsNotEmpty() + url: string; +} diff --git a/backend/src/CRM/Service/ExternalEntity/CreateExternalEntityResult.ts b/backend/src/CRM/Service/ExternalEntity/CreateExternalEntityResult.ts new file mode 100644 index 0000000..10a9804 --- /dev/null +++ b/backend/src/CRM/Service/ExternalEntity/CreateExternalEntityResult.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateExternalEntityResult { + @ApiProperty() + entityCardUrl: string; + + constructor(crmCardUrl: string) { + this.entityCardUrl = crmCardUrl; + } +} diff --git a/backend/src/CRM/Service/ExternalEntity/ExternalEntityDto.ts b/backend/src/CRM/Service/ExternalEntity/ExternalEntityDto.ts new file mode 100644 index 0000000..7f47bc7 --- /dev/null +++ b/backend/src/CRM/Service/ExternalEntity/ExternalEntityDto.ts @@ -0,0 +1,47 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { UIDataRecord } from '../../Model/ExternalEntity/UIDataRecord'; +import { ExternalEntity } from '../../Model/ExternalEntity/ExternalEntity'; +import { ExternalSystem } from '../../Model/ExternalEntity/ExternalSystem'; +import { ExternalSystemDto } from './ExternalSystemDto'; + +export class ExternalEntityDto { + @ApiProperty() + id: number; + + @ApiProperty() + url: string; + + @ApiProperty() + entityId: number; + + @ApiProperty() + uiData: UIDataRecord[]; + + @ApiProperty({ nullable: true }) + system: ExternalSystemDto | null; + + private constructor( + id: number, + url: string, + entityId: number, + system: ExternalSystemDto | null, + uiData: UIDataRecord[] | null, + ) { + this.id = id; + this.url = url; + this.entityId = entityId; + this.system = system; + this.uiData = uiData; + } + + public static create(entity: ExternalEntity, system: ExternalSystem) { + return new ExternalEntityDto( + entity.id, + entity.url, + entity.entityId, + system ? ExternalSystemDto.create(system) : null, + entity.uiData, + ); + } +} diff --git a/backend/src/CRM/Service/ExternalEntity/ExternalEntityService.ts b/backend/src/CRM/Service/ExternalEntity/ExternalEntityService.ts new file mode 100644 index 0000000..30b06f2 --- /dev/null +++ b/backend/src/CRM/Service/ExternalEntity/ExternalEntityService.ts @@ -0,0 +1,65 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { FrontendRoute, UrlGeneratorService } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { EntityService } from '../Entity/EntityService'; +import { ExternalSystemService } from './ExternalSystemService'; + +import { ExternalEntity } from '../../Model/ExternalEntity/ExternalEntity'; +import { ExternalSystem } from '../../Model/ExternalEntity/ExternalSystem'; +import { ExternalSystemCode } from '../../Model/ExternalEntity/ExternalSystemCode'; + +import { SalesforceIntegrationService } from '../../Salesforce/Service/SalesforceIntegrationService'; + +import { CreateExternalEntityDto } from './CreateExternalEntityDto'; +import { CreateExternalEntityResult } from './CreateExternalEntityResult'; + +@Injectable() +export class ExternalEntityService { + constructor( + @InjectRepository(ExternalEntity) + private readonly repository: Repository, + @Inject(forwardRef(() => EntityService)) + private readonly entityService: EntityService, + private readonly externalSystemService: ExternalSystemService, + private readonly urlGenerator: UrlGeneratorService, + private readonly salesforceIntegrationService: SalesforceIntegrationService, + ) {} + + public async create(account: Account, user: User, dto: CreateExternalEntityDto): Promise { + const [entity] = await this.entityService.createSimple({ accountId: account.id, user, dto }); + + const entityUrl = this.urlGenerator.createUrl({ + route: FrontendRoute.entity.card({ entityTypeId: dto.entityTypeId, entityId: entity.id }), + subdomain: account.subdomain, + }); + + const externalSystem = await this.externalSystemService.getMatched(dto.url); + const data = + externalSystem?.code === ExternalSystemCode.SalesForce + ? await this.salesforceIntegrationService.getDataFromUrl(account.id, dto.url, entityUrl) + : null; + await this.repository.save( + new ExternalEntity(account.id, entity.id, dto.url, externalSystem?.id, data?.rawData, data?.uiData), + ); + + return new CreateExternalEntityResult(entityUrl); + } + + public async getExternalEntitiesWithType( + entityId: number, + ): Promise<{ externalEntity: ExternalEntity; type: ExternalSystem }[]> { + const entities = await this.repository.findBy({ entityId }); + const result = []; + for (const entity of entities) { + const type = entity.system ? await this.externalSystemService.getById(entity.system) : null; + result.push({ externalEntity: entity, type }); + } + return result; + } +} diff --git a/backend/src/CRM/Service/ExternalEntity/ExternalSystemDto.ts b/backend/src/CRM/Service/ExternalEntity/ExternalSystemDto.ts new file mode 100644 index 0000000..f33848e --- /dev/null +++ b/backend/src/CRM/Service/ExternalEntity/ExternalSystemDto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { ExternalSystem } from '../../Model/ExternalEntity/ExternalSystem'; + +export class ExternalSystemDto { + @ApiProperty() + id: number; + + @ApiProperty() + name: string; + + @ApiProperty() + code: string; + + private constructor(id: number, name: string, code: string) { + this.id = id; + this.name = name; + this.code = code; + } + + public static create(type: ExternalSystem) { + return new ExternalSystemDto(type.id, type.name, type.code); + } +} diff --git a/backend/src/CRM/Service/ExternalEntity/ExternalSystemService.ts b/backend/src/CRM/Service/ExternalEntity/ExternalSystemService.ts new file mode 100644 index 0000000..576854a --- /dev/null +++ b/backend/src/CRM/Service/ExternalEntity/ExternalSystemService.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ArrayContains, Repository } from 'typeorm'; + +import { ExternalSystem } from '../../Model/ExternalEntity/ExternalSystem'; + +@Injectable() +export class ExternalSystemService { + constructor( + @InjectRepository(ExternalSystem) + private readonly repository: Repository, + ) {} + + public async getById(id: number): Promise { + return await this.repository.findOneBy({ id }); + } + + public async getMatched(url: string): Promise { + const { hostname } = new URL(url); + const host = hostname.split('.').slice(-2).join('.'); + return await this.repository.findOneBy({ urlTemplates: ArrayContains([host]) }); + } +} diff --git a/backend/src/CRM/Service/FileLink/FileLinkDto.ts b/backend/src/CRM/Service/FileLink/FileLinkDto.ts new file mode 100644 index 0000000..421c719 --- /dev/null +++ b/backend/src/CRM/Service/FileLink/FileLinkDto.ts @@ -0,0 +1,67 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { FileLink } from '../../Model/FileLink/FileLink'; + +export class FileLinkDto { + @ApiProperty() + id: number; + + @ApiProperty() + fileId: string; + + @ApiProperty() + fileName: string; + + @ApiProperty() + fileSize: number; + + @ApiProperty() + fileType: string; + + @ApiProperty() + downloadUrl: string; + + @ApiProperty() + previewUrl: string | null; + + @ApiProperty() + createdBy: number; + + @ApiProperty() + createdAt: string; + + private constructor( + id: number, + fileId: string, + fileName: string, + fileSize: number, + fileType: string, + createdAt: string, + createdBy: number, + downloadUrl: string, + previewUrl: string | null, + ) { + this.id = id; + this.fileId = fileId; + this.fileName = fileName; + this.fileSize = fileSize; + this.fileType = fileType; + this.createdAt = createdAt; + this.createdBy = createdBy; + this.downloadUrl = downloadUrl; + this.previewUrl = previewUrl; + } + + public static create(fileLink: FileLink, downloadUrl: string, previewUrl: string | null) { + return new FileLinkDto( + fileLink.id, + fileLink.fileId, + fileLink.fileName, + fileLink.fileSize, + fileLink.fileType, + fileLink.createdAt.toISOString(), + fileLink.createdBy, + downloadUrl, + previewUrl, + ); + } +} diff --git a/backend/src/CRM/Service/FileLink/FileLinkService.ts b/backend/src/CRM/Service/FileLink/FileLinkService.ts new file mode 100644 index 0000000..74c27ac --- /dev/null +++ b/backend/src/CRM/Service/FileLink/FileLinkService.ts @@ -0,0 +1,163 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { FindOptionsOrderValue, Repository } from 'typeorm'; + +import { FileLinkSource, NotFoundError } from '@/common'; +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { StorageUrlService } from '@/modules/storage/storage-url.service'; +import { StorageService } from '@/modules/storage/storage.service'; + +import { CrmEventType, FileLinkCreatedEvent, FileLinkEvent } from '../../common'; + +import { FileLink } from '../../Model/FileLink/FileLink'; +import { FileLinkDto } from './FileLinkDto'; + +@Injectable() +export class FileLinkService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(FileLink) + private readonly repository: Repository, + private readonly storageService: StorageService, + private readonly storageUrlService: StorageUrlService, + ) {} + + private async create( + accountId: number, + sourceType: FileLinkSource, + sourceId: number, + fileInfoId: string, + ): Promise { + const fileInfo = await this.storageService.markUsed({ accountId, id: fileInfoId }); + if (!fileInfo) { + return null; + } + const fileLink = await this.repository.save( + new FileLink( + accountId, + sourceType, + sourceId, + fileInfoId, + fileInfo.originalName, + fileInfo.size, + fileInfo.mimeType, + fileInfo.createdBy, + fileInfo.createdAt, + ), + ); + + this.eventEmitter.emit( + CrmEventType.FileLinkCreated, + new FileLinkCreatedEvent({ + accountId, + sourceType: fileLink.sourceType, + sourceId: fileLink.sourceId, + fileLinkId: fileLink.id, + createdAt: fileLink.createdAt.toISOString(), + }), + ); + + return fileLink; + } + public async addFile( + account: Account, + sourceType: FileLinkSource, + sourceId: number, + fileId: string, + ): Promise { + const link = await this.create(account.id, sourceType, sourceId, fileId); + return link ? this.getFileLinkDto(account, link) : null; + } + + public async addFiles( + account: Account, + sourceType: FileLinkSource, + sourceId: number, + fileIds: string[], + ): Promise { + const linkDtos: FileLinkDto[] = []; + for (const file of fileIds) { + const linkDto = await this.addFile(account, sourceType, sourceId, file); + if (linkDto) linkDtos.push(linkDto); + } + return linkDtos; + } + + public async findFileLinks(accountId: number, sourceType: FileLinkSource, sourceId: number): Promise { + return await this.repository.find({ where: { accountId, sourceType, sourceId } }); + } + + public async findDtoById(account: Account, fileLinkId: number): Promise { + const link = await this.repository.findOneBy({ id: fileLinkId, accountId: account.id }); + return link ? this.getFileLinkDto(account, link) : null; + } + + public async getFileLinkDtos( + account: Account, + sourceType: FileLinkSource, + sourceId: number, + order: FindOptionsOrderValue = 'ASC', + ) { + const links = await this.repository.find({ + where: { accountId: account.id, sourceType, sourceId }, + order: { id: order }, + }); + + return links.map((link) => this.getFileLinkDto(account, link)); + } + + public async processFiles(accountId: number, sourceType: FileLinkSource, sourceId: number, fileIds: string[]) { + const currentLinks = await this.repository.findBy({ accountId, sourceType, sourceId }); + const currentFiles = currentLinks ? currentLinks.map((link) => link.fileId) : []; + + const createdFiles = fileIds.filter((file) => !currentFiles.includes(file)); + for (const file of createdFiles) { + await this.create(accountId, sourceType, sourceId, file); + } + + const deletedFiles = currentFiles.filter((file) => !fileIds.includes(file)); + for (const file of deletedFiles) { + await this.delete(accountId, sourceType, sourceId, file); + } + } + + public async deleteFileLink(accountId: number, id: number) { + const link = await this.repository.findOneBy({ id, accountId }); + if (!link) { + throw NotFoundError.withId(FileLink, id); + } + return await this.delete(accountId, link.sourceType, link.sourceId, link.fileId); + } + + public async deleteFileLinks(accountId: number, ids: number[]): Promise { + await Promise.all(ids.map((id) => this.deleteFileLink(accountId, id))); + } + + private getFileLinkDto(account: Account, link: FileLink): FileLinkDto { + const downloadUrl = this.storageUrlService.getDownloadUrl(account.subdomain, link.fileId); + const previewUrl = link.isImage() + ? this.storageUrlService.getImageUrl(account.id, account.subdomain, link.fileId) + : null; + return FileLinkDto.create(link, downloadUrl, previewUrl); + } + + private async delete(accountId: number, sourceType: FileLinkSource, sourceId: number, fileId: string) { + const deleted = await this.storageService.delete({ accountId, id: fileId }); + if (deleted) { + const fileLink = await this.repository.findOneBy({ accountId, sourceType, sourceId, fileId }); + await this.repository.delete({ accountId, sourceType, sourceId, fileId }); + if (fileLink) { + this.eventEmitter.emit( + CrmEventType.FileLinkDeleted, + new FileLinkEvent({ + accountId, + sourceType: fileLink.sourceType, + sourceId: fileLink.sourceId, + fileLinkId: fileLink.id, + }), + ); + } + } + } +} diff --git a/backend/src/CRM/Service/Import/ImportService.ts b/backend/src/CRM/Service/Import/ImportService.ts new file mode 100644 index 0000000..91a73af --- /dev/null +++ b/backend/src/CRM/Service/Import/ImportService.ts @@ -0,0 +1,470 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Cell, CellRichTextValue, CellValue, Row, Workbook, Worksheet } from 'exceljs'; + +import { DateUtil, isUnique, UserNotification } from '@/common'; +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldOption } from '@/modules/entity/entity-field/field-option/entities/field-option.entity'; +import { FieldOptionService } from '@/modules/entity/entity-field/field-option/field-option.service'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; +import { Field } from '@/modules/entity/entity-field/field/entities/field.entity'; +import { SimpleFieldValueDto } from '@/modules/entity/entity-field/field-value/dto'; + +import { CrmEventType, EntityImportEvent } from '../../common'; +import { Board } from '../../board'; +import { BoardService } from '../../board/board.service'; +import { BoardStage, BoardStageService } from '../../board-stage'; +import { EntityType } from '../../entity-type/entities/entity-type.entity'; +import { EntityTypeService } from '../../entity-type/entity-type.service'; + +import { EntityService } from '../Entity/EntityService'; + +type SystemFieldName = 'Board' | 'Stage' | 'Name' | 'Owner'; +type SystemFieldType = Record; +const SystemField: SystemFieldType = { + Board: 'Board', + Stage: 'Stage', + Name: 'Name', + Owner: 'Owner', +}; +const SystemFields = [SystemField.Board, SystemField.Stage, SystemField.Name, SystemField.Owner]; + +interface ImportInfo { + key: string | null; + entityTypeId: number; + fields: { fieldId: number | SystemFieldName; colNumber: number }[]; +} + +interface EntityIdWithKey { + key: string; + entityId: number; +} + +@Injectable() +export class ImportService { + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly userService: UserService, + private readonly entityTypeService: EntityTypeService, + private readonly fieldService: FieldService, + private readonly entityService: EntityService, + private readonly boardService: BoardService, + private readonly stageService: BoardStageService, + private readonly fieldOptionService: FieldOptionService, + ) {} + + public async generateTemplateForEntityType(accountId: number, entityTypeId: number) { + const entityType = await this.entityTypeService.getById(accountId, entityTypeId); + const linkedEntityTypes = await this.entityTypeService.findLinkedTypes(accountId, entityType.id); + const entityTypes = [entityType, ...linkedEntityTypes]; + + const fieldCodes: string[] = []; + for (const et of entityTypes) { + const prefix = et.id !== entityType.id ? '1//' : ''; + const boards = await this.boardService.findMany({ filter: { accountId, recordId: et.id } }); + if (boards && boards.length > 0) { + fieldCodes.push(`${prefix}${et.name}//${SystemField.Board} {{${et.id}/${SystemField.Board}}}`); + fieldCodes.push(`${prefix}${et.name}//${SystemField.Stage} {{${et.id}/${SystemField.Stage}}}`); + } + fieldCodes.push(`${prefix}${et.name}//${SystemField.Name} {{${et.id}/${SystemField.Name}}}`); + fieldCodes.push(`${prefix}${et.name}//${SystemField.Owner} {{${et.id}/${SystemField.Owner}}}`); + const fields = await this.fieldService.findMany({ accountId, entityTypeId: et.id }); + fieldCodes.push(...fields.map((field) => `${prefix}${et.name}//${field.name} {{${et.id}/${field.id}}}`)); + } + + const workbook = new Workbook(); + const worksheet = workbook.addWorksheet(entityType.sectionView); + worksheet.addRow(fieldCodes); + + // Set column widths to fit column name + const numberOfColumns = worksheet.columns.length; + for (let columnIndex = 1; columnIndex <= numberOfColumns; columnIndex++) { + const columnWidth = this.calculateColumnWidth(worksheet, columnIndex); + worksheet.getColumn(columnIndex).width = columnWidth; + } + + const buffer = await workbook.xlsx.writeBuffer(); + + return buffer; + } + + private calculateColumnWidth(worksheet: Worksheet, columnIndex: number) { + let maxWidth = 0; + worksheet.eachRow((row) => { + const cellValue = row.getCell(columnIndex).value; + const cellWidth = typeof cellValue === 'string' ? cellValue.length : String(cellValue).length; + maxWidth = Math.max(maxWidth, cellWidth); + }); + + return maxWidth; + } + + public async importDataBackground(accountId: number, user: User, entityTypeId: number, file: StorageFile) { + this.importDataForEntityType(accountId, user, entityTypeId, file); + } + + public async importDataForEntityType(accountId: number, user: User, entityTypeId: number, file: StorageFile) { + const workbook = new Workbook(); + await workbook.xlsx.load(file.buffer as any); + const worksheet = workbook.worksheets[0]; + + const importInfos = this.processHeaderRow(worksheet.getRow(1)); + const mainEntityInfo = importInfos.find((ii) => ii.entityTypeId === entityTypeId); + + let totalCount = 0; + let entityTypeName: string | null = null; + + if (mainEntityInfo) { + const entityTypeIds = importInfos.map((ii) => ii.entityTypeId).filter(isUnique); + const entityTypeCache = await this.getEntityTypeCache(accountId, entityTypeIds); + const boardCache = await this.getBoardCache(accountId, entityTypeIds); + const stageCache = await this.getStageCache( + accountId, + boardCache.map((b) => b.id), + ); + const fieldCache = await this.getFieldCache(accountId, importInfos); + const fieldOptionCache = await this.getFieldOptionCache(accountId, fieldCache); + const userCache: User[] = [user]; + const linkedEntitiesInfo = importInfos.filter((ii) => ii.entityTypeId !== entityTypeId); + + for (let rowNumber = 2; rowNumber <= worksheet.lastRow.number; rowNumber++) { + const result = await this.processDataRow( + accountId, + user, + mainEntityInfo, + linkedEntitiesInfo, + worksheet.getRow(rowNumber), + boardCache, + stageCache, + fieldCache, + fieldOptionCache, + userCache, + ); + if (result) totalCount++; + } + + entityTypeName = entityTypeCache.find((et) => et.id === entityTypeId)?.sectionName ?? null; + } + + this.eventEmitter.emit( + CrmEventType.EntityImportCompleted, + new EntityImportEvent({ + accountId, + userId: user.id, + fileName: decodeURIComponent(file.originalName), + entityTypeId, + entityTypeName, + totalCount, + }), + ); + } + + private processHeaderRow(row: Row): ImportInfo[] { + const importInfo: ImportInfo[] = []; + const regex = /(([^/]+)\/\/)?(?:.+)\/\/(?:.+)\s+{{(\d+)\/(\d+|Board|Stage|Name|Owner)}}/; + + row.eachCell({ includeEmpty: false }, (cell, colNumber) => { + const cellValue = this.getCellValueAsString(cell.value); + const match = cellValue.match(regex); + if (match) { + const [, , key, entityTypeIdStr, fieldIdStr] = match; + const entityTypeId = parseInt(entityTypeIdStr); + const fieldId = SystemFields.includes(fieldIdStr as SystemFieldName) + ? (fieldIdStr as SystemFieldName) + : parseInt(fieldIdStr); + const entityType = importInfo.find((ii) => ii.key === key && ii.entityTypeId === entityTypeId); + if (entityType) { + if (!entityType.fields) entityType.fields = []; + entityType.fields.push({ fieldId, colNumber }); + } else { + importInfo.push({ key, entityTypeId, fields: [{ fieldId, colNumber }] }); + } + } + }); + return importInfo; + } + + private async processDataRow( + accountId: number, + user: User, + mainEntityInfo: ImportInfo, + allEntityInfo: ImportInfo[], + row: Row, + boardCache: Board[], + stageCache: BoardStage[], + fieldCache: Field[], + fieldOptionCache: FieldOption[], + userCache: User[], + ): Promise { + const mainEntity = await this.createEntity( + accountId, + user, + mainEntityInfo, + row, + boardCache, + stageCache, + fieldCache, + fieldOptionCache, + userCache, + ); + if (mainEntity) { + const entitiesToLink: EntityIdWithKey[] = []; + for (const entityInfo of allEntityInfo) { + const linkedEntity = await this.createEntity( + accountId, + user, + entityInfo, + row, + boardCache, + stageCache, + fieldCache, + fieldOptionCache, + userCache, + mainEntity.id, + entitiesToLink, + ); + if (linkedEntity) entitiesToLink.push({ key: entityInfo.key, entityId: linkedEntity.id }); + } + } + return !!mainEntity; + } + + private async createEntity( + accountId: number, + user: User, + importInfo: ImportInfo, + row: Row, + boardCache: Board[], + stageCache: BoardStage[], + fieldCache: Field[], + fieldOptionCache: FieldOption[], + userCache: User[], + mainEntityId?: number, + entitiesToLink?: EntityIdWithKey[], + ) { + const fieldValues: SimpleFieldValueDto[] = []; + let boardName: string | null = null; + let stageName: string | null = null; + let name: string | null = null; + let owner: string | null = null; + for (const fieldInfo of importInfo.fields) { + if (fieldInfo.fieldId === SystemField.Board) { + boardName = this.getCellValueAsString(row.getCell(fieldInfo.colNumber).value); + } else if (fieldInfo.fieldId === SystemField.Stage) { + stageName = this.getCellValueAsString(row.getCell(fieldInfo.colNumber).value); + } else if (fieldInfo.fieldId === SystemField.Name) { + name = this.getCellValueAsString(row.getCell(fieldInfo.colNumber).value); + } else if (fieldInfo.fieldId === SystemField.Owner) { + owner = this.getCellValueAsString(row.getCell(fieldInfo.colNumber).value); + } else { + const fieldId = Number(fieldInfo.fieldId); + const field = fieldCache.find((f) => f.id === fieldId); + if (field) { + const fieldValue = await this.getFieldValue(field, row.getCell(fieldInfo.colNumber), fieldOptionCache); + if (fieldValue) { + fieldValues.push({ fieldId, payload: fieldValue }); + } + } + } + } + if (!name && !owner && !boardName && !stageName && fieldValues.length === 0) { + return null; + } + + const { boardId, stageId } = await this.getBoardAndStageIds( + importInfo.entityTypeId, + boardName, + stageName, + boardCache, + stageCache, + ); + + const ownerId = await this.getResponsibleUserId(accountId, owner, user, userCache); + const linkedEntities = mainEntityId ? [mainEntityId] : []; + if (entitiesToLink && importInfo.key) { + linkedEntities.push( + ...entitiesToLink.filter((entity) => entity.key === importInfo.key).map((entity) => entity.entityId), + ); + } + + const [entity] = await this.entityService.createSimple({ + accountId, + user, + dto: { entityTypeId: importInfo.entityTypeId, name, ownerId, boardId, stageId, fieldValues }, + options: { linkedEntities, userNotification: UserNotification.Suppressed }, + }); + + return entity; + } + + private async getEntityTypeCache(accountId: number, entityTypeIds: number[]): Promise { + return await this.entityTypeService.findMany(accountId, { id: entityTypeIds }); + } + + private async getBoardCache(accountId: number, entityTypeIds: number[]): Promise { + const boards: Board[] = []; + for (const entityTypeId of entityTypeIds) { + const etBoards = await this.boardService.findMany({ filter: { accountId, recordId: entityTypeId } }); + etBoards.forEach((b) => { + if (b.sortOrder === 0) b['_first'] = true; + }); + boards.push(...etBoards); + } + return boards; + } + + private async getStageCache(accountId: number, boardIds: number[]): Promise { + const stages: BoardStage[] = []; + for (const boardId of boardIds) { + const boardStages = await this.stageService.findMany({ accountId, boardId }); + boardStages.forEach((s) => { + if (s.sortOrder === 0) s['_first'] = true; + }); + stages.push(...boardStages); + } + return stages; + } + + private async getFieldCache(accountId: number, importInfos: ImportInfo[]): Promise { + const fieldIds = importInfos + .map((ii) => ii.fields) + .flat() + .filter((fi) => typeof fi.fieldId === 'number') + .map((fi) => fi.fieldId as number) + .filter(isUnique); + return this.fieldService.findMany({ accountId, id: fieldIds }); + } + + private async getFieldOptionCache(accountId: number, fieldCache: Field[]): Promise { + const cache: FieldOption[] = []; + for (const field of fieldCache) { + cache.push(...(await this.fieldOptionService.findMany({ accountId, fieldId: field.id }))); + } + return cache; + } + + private async getBoardAndStageIds( + entityTypeId: number, + boardName: string | null, + stageName: string | null, + boardCache: Board[], + stageCache: BoardStage[], + ): Promise<{ boardId: number | null; stageId: number | null }> { + const boards = boardCache.filter((b) => b.recordId === entityTypeId); + if (boards?.length > 0) { + const board = boardName + ? boards.find((b) => b.name.toLowerCase() === boardName.toLowerCase()) + : (boards.find((b) => b['_first']) ?? boards[0]); + if (board) { + const stages = stageCache.filter((s) => s.boardId === board.id); + const stage = stageName + ? stages.find((s) => s.name.toLowerCase() === stageName.toLowerCase()) + : (stages.find((s) => s['_first']) ?? null); + if (stage) return { boardId: board.id, stageId: stage.id }; + } + return { boardId: board.id, stageId: null }; + } + return { boardId: null, stageId: null }; + } + + private async getResponsibleUserId( + accountId: number, + owner: string | null, + user: User, + userCache: User[], + ): Promise { + if (!owner) { + return user.id; + } + + const cachedUser = userCache.find((u) => u.fullName.toLowerCase() === owner.toLowerCase()); + if (cachedUser) { + return cachedUser.id; + } + + const dbUser = await this.userService.findOne({ accountId, fullName: owner }); + if (dbUser) { + userCache.push(dbUser); + + return dbUser.id; + } + + return user.id; + } + + private async getFieldValue(field: Field, cell: Cell, fieldOptionCache: FieldOption[]): Promise { + const cellValue = cell.value; + if (!cellValue) return null; + const valueAsString = this.getCellValueAsString(cellValue); + switch (field.type) { + case FieldType.Text: + case FieldType.RichText: + case FieldType.Link: + return { value: valueAsString }; + case FieldType.Number: + case FieldType.Value: + case FieldType.Formula: + return { value: Number(cellValue) }; + case FieldType.MultiText: + case FieldType.Phone: + case FieldType.Email: + return { values: valueAsString.split(/[,;]+/).map((v) => v.trim()) }; + case FieldType.Switch: + return { value: Boolean(cellValue) }; + case FieldType.Date: { + const date = cellValue ? new Date(cellValue as string | number | Date) : null; + return { value: date && DateUtil.isValid(date) ? date.toISOString() : null }; + } + case FieldType.Select: + case FieldType.ColoredSelect: { + const options = fieldOptionCache.filter((fo) => fo.fieldId === field.id); + const option = options.find((o) => o.label === valueAsString); + return { optionId: option ? option.id : null }; + } + case FieldType.MultiSelect: + case FieldType.ColoredMultiSelect: + case FieldType.CheckedMultiSelect: { + const multiOptions = fieldOptionCache.filter((fo) => fo.fieldId === field.id); + const values = valueAsString.split(/[,;]+/).map((v) => v.trim()); + const fieldOptionIds = multiOptions + .filter((o) => values.includes(o.label)) + .map((fo) => fo.id) + .filter(isUnique); + return { optionIds: fieldOptionIds?.length > 0 ? fieldOptionIds : [] }; + } + case FieldType.Participant: + return { value: Number(cellValue) }; + case FieldType.Participants: + //TODO: parse participants + return { userIds: [] }; + case FieldType.File: + return { value: null }; + case FieldType.Checklist: + return valueAsString + .split(/[,;]+/) + .map((v) => v.trim()) + .filter((v) => v.length) + .map((v) => ({ text: v, checked: false })); + } + } + + private getCellValueAsString(cellValue: CellValue): string { + if (!cellValue) return ''; + if (cellValue instanceof Date) { + return DateUtil.formatPreset(cellValue, 'dateAndTime'); + } + if (cellValue instanceof Object) { + const anyValue = cellValue as any; + if (anyValue.richText) { + const value = anyValue as CellRichTextValue; + return value.richText.map((i) => this.getCellValueAsString(i.text)).join(''); + } + return this.getCellValueAsString(anyValue.error || anyValue.text || anyValue.result); + } + return String(cellValue).trim(); + } +} diff --git a/backend/src/CRM/Service/TimeBoard/TaskGroupByTime.ts b/backend/src/CRM/Service/TimeBoard/TaskGroupByTime.ts new file mode 100644 index 0000000..ad2a18b --- /dev/null +++ b/backend/src/CRM/Service/TimeBoard/TaskGroupByTime.ts @@ -0,0 +1,8 @@ +export enum TaskGroupByTime { + Unallocated = 'unallocated', + Overdue = 'overdue', + Today = 'today', + Tomorrow = 'tomorrow', + Upcoming = 'upcoming', + Resolved = 'resolved', +} diff --git a/backend/src/CRM/Service/TimeBoard/TaskOrActivityCard.ts b/backend/src/CRM/Service/TimeBoard/TaskOrActivityCard.ts new file mode 100644 index 0000000..b848ff8 --- /dev/null +++ b/backend/src/CRM/Service/TimeBoard/TaskOrActivityCard.ts @@ -0,0 +1,4 @@ +import { ActivityCardDto } from '../../activity-card'; +import { TaskBoardCardDto } from '../../task-board'; + +export type TaskOrActivityCard = TaskBoardCardDto | ActivityCardDto; diff --git a/backend/src/CRM/Service/TimeBoard/TimeBoardCalendarMeta.ts b/backend/src/CRM/Service/TimeBoard/TimeBoardCalendarMeta.ts new file mode 100644 index 0000000..8ac78bb --- /dev/null +++ b/backend/src/CRM/Service/TimeBoard/TimeBoardCalendarMeta.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class TimeBoardCalendarMeta { + @ApiProperty() + total: number; + + constructor({ total }: TimeBoardCalendarMeta) { + this.total = total; + } +} diff --git a/backend/src/CRM/Service/TimeBoard/TimeBoardFilter.ts b/backend/src/CRM/Service/TimeBoard/TimeBoardFilter.ts new file mode 100644 index 0000000..602fd33 --- /dev/null +++ b/backend/src/CRM/Service/TimeBoard/TimeBoardFilter.ts @@ -0,0 +1,12 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { BaseTaskBoardFilter } from '../../Service/BaseTaskBoard/BaseTaskBoardFilter'; +import { TaskGroupByTime } from './TaskGroupByTime'; + +export class TimeBoardFilter extends BaseTaskBoardFilter { + @ApiPropertyOptional({ enum: TaskGroupByTime }) + @IsOptional() + @IsArray() + groups?: TaskGroupByTime[] | null; +} diff --git a/backend/src/CRM/Service/TimeBoard/TimeBoardMeta.ts b/backend/src/CRM/Service/TimeBoard/TimeBoardMeta.ts new file mode 100644 index 0000000..3fbee1a --- /dev/null +++ b/backend/src/CRM/Service/TimeBoard/TimeBoardMeta.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { UserTimeAllocation } from '../BaseTaskBoard/UserTimeAllocation'; +import { TimeBoardStageMeta } from './TimeBoardStageMeta'; + +export class TimeBoardMeta { + @ApiProperty() + total: number; + + @ApiProperty({ nullable: true }) + unallocated: TimeBoardStageMeta | null = null; + + @ApiProperty({ nullable: true }) + overdue: TimeBoardStageMeta | null = null; + + @ApiProperty({ nullable: true }) + today: TimeBoardStageMeta | null = null; + + @ApiProperty({ nullable: true }) + tomorrow: TimeBoardStageMeta | null = null; + + @ApiProperty({ nullable: true }) + upcoming: TimeBoardStageMeta | null = null; + + @ApiProperty({ nullable: true }) + resolved: TimeBoardStageMeta | null = null; + + @ApiProperty({ type: [UserTimeAllocation] }) + timeAllocation: UserTimeAllocation[]; +} diff --git a/backend/src/CRM/Service/TimeBoard/TimeBoardService.ts b/backend/src/CRM/Service/TimeBoard/TimeBoardService.ts new file mode 100644 index 0000000..a2da93f --- /dev/null +++ b/backend/src/CRM/Service/TimeBoard/TimeBoardService.ts @@ -0,0 +1,627 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Brackets, DataSource, SelectQueryBuilder } from 'typeorm'; + +import { DatePeriod, DatePeriodDto, DateUtil, ForbiddenError, intersection, isUnique, PagingQuery } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoDto, EntityInfoService } from '@/modules/entity/entity-info'; + +import { Activity } from '../../activity'; +import { ActivityCardDto, ActivityCardService } from '../../activity-card'; +import { TaskView } from '../../base-task'; +import { BoardService } from '../../board/board.service'; +import { Task } from '../../task'; +import { TaskBoardCardDto, TaskBoardService } from '../../task-board'; + +import { TaskBoardQueryHelper } from '../BaseTaskBoard/TaskBoardQueryHelper'; +import { TaskSorting } from '../BaseTaskBoard/TaskSorting'; +import { UserTimeAllocation } from '../BaseTaskBoard/UserTimeAllocation'; +import { TimeBoardFilter } from './TimeBoardFilter'; +import { TimeBoardMeta } from './TimeBoardMeta'; +import { TaskOrActivityCard } from './TaskOrActivityCard'; +import { TimeBoardStageMeta } from './TimeBoardStageMeta'; +import { TimeBoardCalendarMeta } from './TimeBoardCalendarMeta'; +import { TaskGroupByTime } from './TaskGroupByTime'; + +@Injectable() +export class TimeBoardService { + private readonly logger = new Logger(TimeBoardService.name); + constructor( + private readonly dataSource: DataSource, + private readonly authService: AuthorizationService, + private readonly activityCardService: ActivityCardService, + private readonly taskBoardService: TaskBoardService, + private readonly boardService: BoardService, + private readonly entityInfoService: EntityInfoService, + ) {} + + public async getTimeBoardCards( + account: Account, + user: User, + filter: TimeBoardFilter, + paging: PagingQuery, + ): Promise { + const activityPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Activity.getAuthorizable(), + }); + const taskPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Task.getAuthorizable(), + }); + if (!activityPerm.allow && !taskPerm.allow) { + throw new ForbiddenError(); + } + + const allowedBoardIds = await this.boardService.getAllowedTaskBoardIds({ accountId: account.id, userId: user.id }); + + const allowedTypes = []; + if (activityPerm.allow) { + allowedTypes.push(TaskView.Activity); + } + if (taskPerm.allow) { + allowedTypes.push(TaskView.Task); + } + + const activityUsers = intersection(filter.ownerIds, activityPerm.userIds); + const taskUsers = intersection(filter.ownerIds, taskPerm.userIds); + + const now = DateUtil.now(); + const tasks = []; + const qb = this.createQueryBuilder( + account.id, + user.id, + allowedBoardIds, + allowedTypes, + activityUsers, + taskUsers, + filter, + ); + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Unallocated)) { + const unallocatedQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere('at.start_date is null') + .andWhere('at.end_date is null'); + + tasks.push(...(await this.getTasks(unallocatedQb.clone(), filter.sorting, paging))); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Overdue)) { + const expiredQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbE) => { + qbE.where('at.end_date < :now', { now }).orWhere('at.end_date is null AND at.start_date < :now', { now }); + }), + ); + + tasks.push(...(await this.getTasks(expiredQb.clone(), filter.sorting, paging))); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Today)) { + const endOfTheDay = DateUtil.endOf(now, 'day'); + const todayQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbT) => { + qbT + .where('at.start_date <= :endOfTheDay AND at.end_date > :now', { endOfTheDay, now }) + .orWhere('at.end_date is null AND at.start_date >= :now AND at.start_date <= :endOfTheDay', { + endOfTheDay, + now, + }) + .orWhere('at.start_date is null AND at.end_date >= :now AND at.end_date <= :endOfTheDay', { + endOfTheDay, + now, + }); + }), + ); + + tasks.push(...(await this.getTasks(todayQb.clone(), filter.sorting, paging))); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Tomorrow)) { + const tomorrowStart = DateUtil.add(DateUtil.startOf(now, 'day'), { days: 1 }); + const tomorrowEnd = DateUtil.add(DateUtil.endOf(now, 'day'), { days: 1 }); + const tomorrowQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbTm) => { + qbTm + .where('at.start_date >= :tomorrowStart AND at.start_date <= :tomorrowEnd', { + tomorrowStart, + tomorrowEnd, + }) + .orWhere('at.start_date is null AND at.end_date >= :tomorrowStart AND at.end_date <= :tomorrowEnd', { + tomorrowStart, + tomorrowEnd, + }); + }), + ); + + tasks.push(...(await this.getTasks(tomorrowQb.clone(), filter.sorting, paging))); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Upcoming)) { + const tomorrowEnd = DateUtil.add(DateUtil.startOf(now, 'day'), { days: 2 }); + const upcomingQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbU) => { + qbU + .where('at.start_date > :tomorrowEnd', { tomorrowEnd }) + .orWhere('at.start_date is null AND at.end_date > :tomorrowEnd', { tomorrowEnd }); + }), + ); + + tasks.push(...(await this.getTasks(upcomingQb.clone(), filter.sorting, paging))); + } + if ((!filter.groups && filter.showResolved !== false) || filter.groups?.includes(TaskGroupByTime.Resolved)) { + const resolvedQb = qb.clone().andWhere('at.is_resolved = true'); + + tasks.push(...(await this.getTasks(resolvedQb.clone(), filter.sorting, paging))); + } + + return this.createItemsFromRaw(account, user, tasks); + } + + public async getTimeBoardMeta(accountId: number, user: User, filter: TimeBoardFilter): Promise { + const activityPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Activity.getAuthorizable(), + }); + const taskPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Task.getAuthorizable(), + }); + if (!activityPerm.allow && !taskPerm.allow) { + throw new ForbiddenError(); + } + + const allowedBoardIds = await this.boardService.getAllowedTaskBoardIds({ accountId, userId: user.id }); + + const allowedTypes = []; + if (activityPerm.allow) { + allowedTypes.push(TaskView.Activity); + } + if (taskPerm.allow) { + allowedTypes.push(TaskView.Task); + } + + const activityUsers = intersection(filter.ownerIds, activityPerm.userIds); + const taskUsers = intersection(filter.ownerIds, taskPerm.userIds); + + const now = DateUtil.now(); + const meta = new TimeBoardMeta(); + const qb = this.createQueryBuilder( + accountId, + user.id, + allowedBoardIds, + allowedTypes, + activityUsers, + taskUsers, + filter, + ); + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Unallocated)) { + const unallocatedQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere('at.start_date is null') + .andWhere('at.end_date is null'); + + meta.unallocated = await this.getStageMeta(unallocatedQb); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Overdue)) { + const overdueQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbE) => { + qbE.where('at.end_date < :now', { now }).orWhere('at.end_date is null AND at.start_date < :now', { now }); + }), + ); + + meta.overdue = await this.getStageMeta(overdueQb); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Today)) { + const endOfTheDay = DateUtil.endOf(now, 'day'); + const todayQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbT) => { + qbT + .where('at.start_date <= :endOfTheDay AND at.end_date > :now', { endOfTheDay, now }) + .orWhere('at.end_date is null AND at.start_date >= :now AND at.start_date <= :endOfTheDay', { + endOfTheDay, + now, + }) + .orWhere('at.start_date is null AND at.end_date >= :now AND at.end_date <= :endOfTheDay', { + endOfTheDay, + now, + }); + }), + ); + + meta.today = await this.getStageMeta(todayQb); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Tomorrow)) { + const tomorrowStart = DateUtil.add(DateUtil.startOf(now, 'day'), { days: 1 }); + const tomorrowEnd = DateUtil.add(DateUtil.endOf(now, 'day'), { days: 1 }); + const tomorrowQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbTm) => { + qbTm + .where('at.start_date >= :tomorrowStart AND at.start_date <= :tomorrowEnd', { + tomorrowStart, + tomorrowEnd, + }) + .orWhere('at.start_date is null AND at.end_date >= :tomorrowStart AND at.end_date <= :tomorrowEnd', { + tomorrowStart, + tomorrowEnd, + }); + }), + ); + + meta.tomorrow = await this.getStageMeta(tomorrowQb); + } + if (!filter.groups || filter.groups.includes(TaskGroupByTime.Upcoming)) { + const tomorrowEnd = DateUtil.add(DateUtil.startOf(now, 'day'), { days: 2 }); + const upcomingQb = qb + .clone() + .andWhere('at.is_resolved = false') + .andWhere( + new Brackets((qbU) => { + qbU + .where('at.start_date > :tomorrowEnd', { tomorrowEnd }) + .orWhere('at.start_date is null AND at.end_date > :tomorrowEnd', { tomorrowEnd }); + }), + ); + + meta.upcoming = await this.getStageMeta(upcomingQb); + } + if ((!filter.groups && filter.showResolved !== false) || filter.groups?.includes(TaskGroupByTime.Resolved)) { + const resolvedQb = qb.clone().andWhere('at.is_resolved = true'); + + meta.resolved = await this.getStageMeta(resolvedQb); + } + + const boardQb = qb.clone(); + if (filter.showResolved === false) { + boardQb.andWhere(`at.is_resolved = false`); + } + meta.total = await this.getCount(boardQb.clone()); + meta.timeAllocation = await this.getUserTimeAllocation(boardQb.clone()); + + return meta; + } + + public async getTimeBoardItem( + account: Account, + user: User, + type: TaskView, + id: number, + filter: TimeBoardFilter, + ): Promise { + switch (type) { + case TaskView.Activity: + return await this.activityCardService.getActivityCard(account.id, user, id, filter); + case TaskView.Task: + return await this.taskBoardService.getTaskBoardCard(account, user, id, filter); + } + } + + public async getCalendar( + account: Account, + user: User, + periodDto: DatePeriodDto, + filter: TimeBoardFilter, + ): Promise { + const activityPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Activity.getAuthorizable(), + }); + const taskPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Task.getAuthorizable(), + }); + if (!activityPerm.allow && !taskPerm.allow) { + throw new ForbiddenError(); + } + + const allowedBoardIds = await this.boardService.getAllowedTaskBoardIds({ accountId: account.id, userId: user.id }); + + const allowedTypes = []; + if (activityPerm.allow) { + allowedTypes.push(TaskView.Activity); + } + if (taskPerm.allow) { + allowedTypes.push(TaskView.Task); + } + + const activityUsers = intersection(filter.ownerIds, activityPerm.userIds); + const taskUsers = intersection(filter.ownerIds, taskPerm.userIds); + + const qb = this.createQueryBuilder( + account.id, + user.id, + allowedBoardIds, + allowedTypes, + activityUsers, + taskUsers, + filter, + ); + if (filter.showResolved === false) { + qb.andWhere('at.is_resolved = :isResolved', { isResolved: false }); + } + + const period = DatePeriod.fromDto(periodDto); + if (period.from && period.to) { + qb.andWhere('start_date < :to', { to: period.to }).andWhere('end_date > :from', { from: period.from }); + } + + const tasks = await qb.getRawMany(); + + return this.createItemsFromRaw(account, user, tasks); + } + + public async getCalendarMeta( + account: Account, + user: User, + periodDto: DatePeriodDto, + filter: TimeBoardFilter, + ): Promise { + const activityPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Activity.getAuthorizable(), + }); + const taskPerm = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Task.getAuthorizable(), + }); + if (!activityPerm.allow && !taskPerm.allow) { + throw new ForbiddenError(); + } + + const allowedBoardIds = await this.boardService.getAllowedTaskBoardIds({ accountId: account.id, userId: user.id }); + + const allowedTypes = []; + if (activityPerm.allow) { + allowedTypes.push(TaskView.Activity); + } + if (taskPerm.allow) { + allowedTypes.push(TaskView.Task); + } + + const activityUsers = intersection(filter.ownerIds, activityPerm.userIds); + const taskUsers = intersection(filter.ownerIds, taskPerm.userIds); + + const qb = this.createQueryBuilder( + account.id, + user.id, + allowedBoardIds, + allowedTypes, + activityUsers, + taskUsers, + filter, + ); + + qb.select('count(*)', 'total'); + + if (filter.showResolved === false) { + qb.andWhere('at.is_resolved = :isResolved', { isResolved: false }); + } + + const period = DatePeriod.fromDto(periodDto); + if (period.from && period.to) { + qb.andWhere('start_date < :to', { to: period.to }).andWhere('end_date > :from', { from: period.from }); + } + const { total } = await qb.getRawOne<{ total: number }>(); + + return new TimeBoardCalendarMeta({ total }); + } + + private createQueryBuilder( + accountId: number, + userId: number, + _allowedBoardIds: number[], + allowedTypes: string[], + activityUsers: number[] | null | undefined, + taskUsers: number[] | null | undefined, + filter: TimeBoardFilter, + ) { + const qb = this.dataSource + .createQueryBuilder() + .select('at.*') + .from('all_tasks', 'at') + .where('at.account_id = :accountId', { accountId }) + .andWhere('at.type IN (:...allowedTypes)', { allowedTypes }); + //HACK: skip board check + //.andWhere('(at.board_id is null or at.board_id IN (:...allowedBoardIds))', { allowedBoardIds }); + + if (activityUsers || taskUsers) { + qb.andWhere( + new Brackets((qbP) => { + qbP + .where( + new Brackets((qbA) => { + qbA.where(`at.type = 'activity'`); + if (activityUsers && activityUsers.length > 0) { + qbA.andWhere(`at.responsible_user_id IN (:...activityUsers)`, { activityUsers }); + } else if (activityUsers) { + qbA.andWhere(`at.responsible_user_id IS NULL`); + } + }), + ) + .orWhere( + new Brackets((qbT) => { + qbT.where(`at.type = 'task'`); + if (taskUsers && taskUsers.length > 0) { + qbT.andWhere(`at.responsible_user_id IN (:...taskUsers)`, { taskUsers }); + } else if (taskUsers) { + qbT.andWhere(`at.responsible_user_id IS NULL`); + } + }), + ); + if (!filter.ownerIds) { + qbP.orWhere('at.created_by = :userId', { userId }); + } + }), + ); + } + + if (filter.entityIds) { + qb.andWhere(`at.entity_id IN (:...entityIds)`, { entityIds: filter.entityIds }); + } + + if (filter.search) { + qb.andWhere( + new Brackets((qbS) => { + qbS + .where(`at.text ILIKE :searchText`, { searchText: `%${filter.search}%` }) + .orWhere(`at.title ILIKE :searchTitle`, { searchTitle: `%${filter.search}%` }); + }), + ); + } + + return qb; + } + + private async getTasks( + qb: SelectQueryBuilder, + sorting: TaskSorting | null | undefined, + paging: PagingQuery, + ): Promise { + try { + return await TaskBoardQueryHelper.addBoardOrderBy(qb, sorting) + .offset(paging.skip) + .limit(paging.take) + .getRawMany(); + } catch (e) { + this.logger.error(`getTasks query: ${qb.getQuery()}`); + this.logger.error(`getTasks error`, (e as Error)?.stack); + throw e; + } + } + + private async getStageMeta(qb: SelectQueryBuilder): Promise { + const unallocatedCount = await this.getCount(qb.clone()); + const allocations = await this.getUserTimeAllocation(qb.clone()); + + return new TimeBoardStageMeta(unallocatedCount, allocations); + } + + private async getCount(qb: SelectQueryBuilder): Promise { + const { count } = await qb.select('COUNT(at.id)', 'count').getRawOne(); + return Number(count); + } + + private async getUserTimeAllocation(qb: SelectQueryBuilder): Promise { + const allocations = await qb + .groupBy('at.responsible_user_id') + .select('at.responsible_user_id, SUM(at.planned_time) AS planned_time') + .getRawMany(); + + return allocations.map( + (a) => new UserTimeAllocation(a.responsible_user_id, a.planned_time ? Number(a.planned_time) : 0), + ); + } + + private async createItemsFromRaw(account: Account, user: User, items: any[]): Promise { + const entityIds = items + .map((item) => item.entity_id) + .filter((id) => !!id) + .filter(isUnique); + const entityInfoCache = entityIds.length + ? await this.entityInfoService.findMany({ accountId: account.id, user, entityIds }) + : []; + + const cards: TaskOrActivityCard[] = []; + for (const item of items) { + const entityInfo = item.entity_id ? entityInfoCache.find((ei) => ei.id === item.entity_id) : null; + cards.push(await this.createItemFromRaw(account, user, item, entityInfo)); + } + return cards; + } + + private async createItemFromRaw( + account: Account, + user: User, + item: any, + entityInfo: EntityInfoDto | null, + ): Promise { + if (item.type === TaskView.Activity) { + return await this.createActivityCardItem(user, item, entityInfo); + } else if (item.type === TaskView.Task) { + return await this.createTaskCardItem(account, user, item, entityInfo); + } else { + return null; + } + } + + private async createActivityCardItem( + user: User, + item: any, + entityInfo: EntityInfoDto | null, + ): Promise { + const activity = new Activity( + item.account_id, + item.entity_id, + item.created_by, + item.responsible_user_id, + item.text, + item.activity_type_id, + item.is_resolved, + item.start_date ? new Date(item.start_date) : null, + item.end_date ? new Date(item.end_date) : null, + item.resolved_date ? new Date(item.resolved_date) : null, + item.weight, + item.result, + new Date(item.created_at), + ); + activity.id = item.id; + return await this.activityCardService.createActivityCard(user, activity, entityInfo); + } + + private async createTaskCardItem( + account: Account, + user: User, + item: any, + entityInfo: EntityInfoDto | null, + ): Promise { + const task = new Task( + item.account_id, + item.entity_id, + item.created_by, + item.responsible_user_id, + item.text, + item.title, + item.planned_time, + item.is_resolved, + item.start_date ? new Date(item.start_date) : null, + item.end_date ? new Date(item.end_date) : null, + item.resolved_date ? new Date(item.resolved_date) : null, + item.weight, + item.board_id, + item.stage_id, + item.settings_id, + item.external_id, + new Date(item.created_at), + ); + task.id = item.id; + return await this.taskBoardService.createTaskBoardCard({ account, user, task, entityInfo }); + } +} diff --git a/backend/src/CRM/Service/TimeBoard/TimeBoardStageMeta.ts b/backend/src/CRM/Service/TimeBoard/TimeBoardStageMeta.ts new file mode 100644 index 0000000..3be3384 --- /dev/null +++ b/backend/src/CRM/Service/TimeBoard/TimeBoardStageMeta.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { UserTimeAllocation } from '../BaseTaskBoard/UserTimeAllocation'; + +export class TimeBoardStageMeta { + @ApiProperty() + total: number; + + @ApiProperty() + timeAllocation: UserTimeAllocation[]; + + constructor(total: number, timeAllocation: UserTimeAllocation[]) { + this.total = total; + this.timeAllocation = timeAllocation; + } +} diff --git a/backend/src/CRM/activity-card/activity-card.controller.ts b/backend/src/CRM/activity-card/activity-card.controller.ts new file mode 100644 index 0000000..bd208d9 --- /dev/null +++ b/backend/src/CRM/activity-card/activity-card.controller.ts @@ -0,0 +1,66 @@ +import { Body, Controller, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { DatePeriodDto, PagingQuery } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ActivityCalendarMetaDto, ActivityCardDto, ActivityCardFilterDto, ActivityCardMetaDto } from './dto'; +import { ActivityCardService } from './activity-card.service'; + +@ApiTags('crm/activity/card') +@Controller('/crm/activities') +@JwtAuthorized({ prefetch: { user: true } }) +export class ActivityCardController { + constructor(private readonly service: ActivityCardService) {} + + @ApiOkResponse({ description: 'Activities for board', type: [ActivityCardDto] }) + @Post('cards') + public async getActivityCards( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: ActivityCardFilterDto, + @Query() paging: PagingQuery, + ): Promise { + return this.service.getActivityCards(accountId, user, filter, paging); + } + + @ApiOkResponse({ description: 'Meta for activities board', type: ActivityCardMetaDto }) + @Post('cards/meta') + public async getActivityCardsMeta( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: ActivityCardFilterDto, + ): Promise { + return this.service.getActivityCardsMeta(accountId, user, filter); + } + + @ApiOkResponse({ description: 'Activity for board', type: ActivityCardDto }) + @Post('cards/:activityId') + public async getActivityCard( + @CurrentAuth() { accountId, user }: AuthData, + @Param('activityId', ParseIntPipe) activityId: number, + @Body() filter: ActivityCardFilterDto, + ): Promise { + return this.service.getActivityCard(accountId, user, activityId, filter); + } + + @ApiOkResponse({ description: 'Activities calendar', type: [ActivityCardDto] }) + @Post('calendar') + public async getActivityCalendar( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: ActivityCardFilterDto, + @Query() period: DatePeriodDto, + ): Promise { + return this.service.getActivityCalendar(accountId, user, period, filter); + } + + @ApiOkResponse({ description: 'Meta for activities calendar', type: ActivityCalendarMetaDto }) + @Post('calendar/meta') + public async getActivityCalendarMeta( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: ActivityCardFilterDto, + @Query() period: DatePeriodDto, + ): Promise { + return this.service.getActivityCalendarMeta(accountId, user, period, filter); + } +} diff --git a/backend/src/CRM/activity-card/activity-card.service.ts b/backend/src/CRM/activity-card/activity-card.service.ts new file mode 100644 index 0000000..bd60584 --- /dev/null +++ b/backend/src/CRM/activity-card/activity-card.service.ts @@ -0,0 +1,244 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { DatePeriod, DatePeriodDto, isUnique, PagingQuery } from '@/common'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoDto, EntityInfoService } from '@/modules/entity/entity-info'; + +import { Activity } from '../activity'; +import { ActivityTypeService } from '../activity-type'; +import { TaskBoardQueryHelper } from '../Service/BaseTaskBoard/TaskBoardQueryHelper'; + +import { + ActivityCalendarMetaDto, + ActivityCardDto, + ActivityCardFilterDto, + ActivityCardMetaDto, + ActivityCardTypeMetaDto, +} from './dto'; + +@Injectable() +export class ActivityCardService { + constructor( + @InjectRepository(Activity) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly activityTypeService: ActivityTypeService, + private readonly entityInfoService: EntityInfoService, + ) {} + + public async getActivityCards( + accountId: number, + user: User, + filter: ActivityCardFilterDto, + paging: PagingQuery, + ): Promise { + const userIds = await this.authService.whoCanView({ user, authorizable: Activity.getAuthorizable() }); + + const typeIds = filter.typeIds ?? (await this.activityTypeService.findManyIds({ accountId })); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + accountId, + user.id, + this.repository, + filter, + userIds, + true, + 'text', + ); + const activities: Activity[] = []; + for (const typeId of typeIds) { + const typedActivities = await qb + .clone() + .andWhere('activity_type_id = :typeId', { typeId }) + .andWhere('is_resolved = :isResolved', { isResolved: false }) + .offset(paging.skip) + .limit(paging.take) + .getMany(); + activities.push(...typedActivities); + } + if (filter.showResolved !== false) { + const resolvedActivities = await qb + .clone() + .andWhere('is_resolved = :isResolved', { isResolved: true }) + .offset(paging.skip) + .limit(paging.take) + .getMany(); + activities.push(...resolvedActivities); + } + const entityIds = activities.map((activity) => activity.entityId).filter(isUnique); + const entityInfoCache = entityIds.length + ? await this.entityInfoService.findMany({ accountId, user, entityIds }) + : []; + + const activityCards: ActivityCardDto[] = []; + for (const activity of activities) { + const entityInfo = entityInfoCache.find((ei) => ei.id === activity.entityId); + activityCards.push(await this.createActivityCard(user, activity, entityInfo)); + } + return activityCards; + } + + public async getActivityCard( + accountId: number, + user: User, + activityId: number, + filter: ActivityCardFilterDto, + ): Promise { + const userIds = await this.authService.whoCanView({ user, authorizable: Activity.getAuthorizable() }); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + accountId, + user.id, + this.repository, + filter, + userIds, + false, + 'text', + ); + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const activity = await qb.andWhere('id = :id', { id: activityId }).getOne(); + + if (!activity) return null; + + const entityInfo = activity.entityId + ? await this.entityInfoService.findOne({ accountId, user, entityId: activity.entityId }) + : null; + + return await this.createActivityCard(user, activity, entityInfo); + } + + public async getActivityCardsMeta( + accountId: number, + user: User, + filter: ActivityCardFilterDto, + ): Promise { + const userIds = await this.authService.whoCanView({ user, authorizable: Activity.getAuthorizable() }); + + const typeIds = filter.typeIds ?? (await this.activityTypeService.findManyIds({ accountId })); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + accountId, + user.id, + this.repository, + filter, + userIds, + false, + 'text', + ); + let totalCount = 0; + const typeMeta: ActivityCardTypeMetaDto[] = []; + for (const typeId of typeIds) { + const count = await qb + .clone() + .andWhere('activity_type_id = :typeId', { typeId }) + .andWhere('is_resolved = :isResolved', { isResolved: false }) + .getCount(); + typeMeta.push(new ActivityCardTypeMetaDto(typeId, count)); + totalCount += count; + } + let resolvedCount = 0; + if (filter.showResolved !== false) { + resolvedCount = await qb.clone().andWhere('is_resolved = :isResolved', { isResolved: true }).getCount(); + totalCount += resolvedCount; + } + + return new ActivityCardMetaDto(totalCount, typeMeta, resolvedCount); + } + + public async createActivityCard( + user: User, + activity: Activity, + entityInfo: EntityInfoDto | null, + ): Promise { + const userRights = await this.authService.getUserRights({ user, authorizable: activity }); + + return new ActivityCardDto(activity, entityInfo, [], userRights); + } + + public async getActivityCalendar( + accountId: number, + user: User, + periodDto: DatePeriodDto, + filter: ActivityCardFilterDto, + ): Promise { + const userIds = await this.authService.whoCanView({ user, authorizable: Activity.getAuthorizable() }); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + accountId, + user.id, + this.repository, + filter, + userIds, + true, + 'text', + ); + + const typeIds = filter.typeIds ?? (await this.activityTypeService.findManyIds({ accountId })); + if (typeIds) { + qb.andWhere('activity_type_id IN (:...typeIds)', { typeIds }); + } + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const period = DatePeriod.fromDto(periodDto); + if (period.from && period.to) { + qb.andWhere('start_date < :to', { to: period.to }).andWhere('end_date > :from', { from: period.from }); + } + + const activities = await qb.getMany(); + + const entityIds = activities.map((activity) => activity.entityId).filter(isUnique); + const entityInfoCache = entityIds.length + ? await this.entityInfoService.findMany({ accountId, user, entityIds }) + : []; + + const activityCards: ActivityCardDto[] = []; + for (const activity of activities) { + const entityInfo = entityInfoCache.find((ei) => ei.id === activity.entityId); + activityCards.push(await this.createActivityCard(user, activity, entityInfo)); + } + return activityCards; + } + + public async getActivityCalendarMeta( + accountId: number, + user: User, + periodDto: DatePeriodDto, + filter: ActivityCardFilterDto, + ): Promise { + const userIds = await this.authService.whoCanView({ user, authorizable: Activity.getAuthorizable() }); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + accountId, + user.id, + this.repository, + filter, + userIds, + true, + 'text', + ); + + const typeIds = filter.typeIds ?? (await this.activityTypeService.findManyIds({ accountId })); + if (typeIds) { + qb.andWhere('activity_type_id IN (:...typeIds)', { typeIds }); + } + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const period = DatePeriod.fromDto(periodDto); + if (period.from && period.to) { + qb.andWhere('start_date < :to', { to: period.to }).andWhere('end_date > :from', { from: period.from }); + } + const total = await qb.getCount(); + + return new ActivityCalendarMetaDto({ total }); + } +} diff --git a/backend/src/CRM/activity-card/dto/activity-calendar-meta.dto.ts b/backend/src/CRM/activity-card/dto/activity-calendar-meta.dto.ts new file mode 100644 index 0000000..599feb8 --- /dev/null +++ b/backend/src/CRM/activity-card/dto/activity-calendar-meta.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class ActivityCalendarMetaDto { + @ApiProperty() + @IsNumber() + total: number; + + constructor({ total }: ActivityCalendarMetaDto) { + this.total = total; + } +} diff --git a/backend/src/CRM/activity-card/dto/activity-card-filter.dto.ts b/backend/src/CRM/activity-card/dto/activity-card-filter.dto.ts new file mode 100644 index 0000000..94a6e94 --- /dev/null +++ b/backend/src/CRM/activity-card/dto/activity-card-filter.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { BaseTaskBoardFilter } from '../../Service/BaseTaskBoard/BaseTaskBoardFilter'; + +export class ActivityCardFilterDto extends BaseTaskBoardFilter { + @ApiPropertyOptional({ type: [Number] }) + @IsOptional() + @IsArray() + typeIds?: number[] | null; +} diff --git a/backend/src/CRM/activity-card/dto/activity-card-meta.dto.ts b/backend/src/CRM/activity-card/dto/activity-card-meta.dto.ts new file mode 100644 index 0000000..fa8d76a --- /dev/null +++ b/backend/src/CRM/activity-card/dto/activity-card-meta.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { ActivityCardTypeMetaDto } from './activity-card-type-meta.dto'; + +export class ActivityCardMetaDto { + @ApiProperty() + total: number; + + @ApiProperty({ type: [ActivityCardTypeMetaDto] }) + types: ActivityCardTypeMetaDto[]; + + @ApiProperty() + resolvedTotal: number; + + constructor(total: number, types: ActivityCardTypeMetaDto[], resolvedTotal: number) { + this.total = total; + this.types = types; + this.resolvedTotal = resolvedTotal; + } +} diff --git a/backend/src/CRM/activity-card/dto/activity-card-type-meta.dto.ts b/backend/src/CRM/activity-card/dto/activity-card-type-meta.dto.ts new file mode 100644 index 0000000..d6ae47d --- /dev/null +++ b/backend/src/CRM/activity-card/dto/activity-card-type-meta.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class ActivityCardTypeMetaDto { + @ApiProperty() + id: number; + + @ApiProperty() + totalCount: number; + + constructor(id: number, totalCount: number) { + this.id = id; + this.totalCount = totalCount; + } +} diff --git a/backend/src/CRM/activity-card/dto/activity-card.dto.ts b/backend/src/CRM/activity-card/dto/activity-card.dto.ts new file mode 100644 index 0000000..6839fc7 --- /dev/null +++ b/backend/src/CRM/activity-card/dto/activity-card.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { BaseTaskDto } from '../../base-task'; +import { Activity } from '../../activity'; +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; + +export class ActivityCardDto extends BaseTaskDto { + @ApiProperty() + activityTypeId: number; + + constructor(activity: Activity, entityInfo: EntityInfoDto | null, fileLinks: FileLinkDto[], userRights: UserRights) { + super(activity, entityInfo, fileLinks, userRights); + + this.activityTypeId = activity.activityTypeId; + } +} diff --git a/backend/src/CRM/activity-card/dto/index.ts b/backend/src/CRM/activity-card/dto/index.ts new file mode 100644 index 0000000..ad0e855 --- /dev/null +++ b/backend/src/CRM/activity-card/dto/index.ts @@ -0,0 +1,5 @@ +export * from './activity-calendar-meta.dto'; +export * from './activity-card-filter.dto'; +export * from './activity-card-meta.dto'; +export * from './activity-card-type-meta.dto'; +export * from './activity-card.dto'; diff --git a/backend/src/CRM/activity-card/index.ts b/backend/src/CRM/activity-card/index.ts new file mode 100644 index 0000000..6419861 --- /dev/null +++ b/backend/src/CRM/activity-card/index.ts @@ -0,0 +1,3 @@ +export * from './activity-card.controller'; +export * from './activity-card.service'; +export * from './dto'; diff --git a/backend/src/CRM/activity-type/activity-type.controller.ts b/backend/src/CRM/activity-type/activity-type.controller.ts new file mode 100644 index 0000000..59405ec --- /dev/null +++ b/backend/src/CRM/activity-type/activity-type.controller.ts @@ -0,0 +1,54 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ActivityTypeDto, CreateActivityTypeDto, UpdateActivityTypeDto } from './dto'; +import { ActivityTypeService } from './activity-type.service'; + +@ApiTags('crm/activity-type') +@Controller('crm/activity-types') +@JwtAuthorized() +@TransformToDto() +export class ActivityTypeController { + constructor(private readonly service: ActivityTypeService) {} + + @ApiOperation({ summary: 'Create activity type', description: 'Create activity type' }) + @ApiBody({ type: CreateActivityTypeDto, required: true, description: 'Data to create activity type' }) + @ApiCreatedResponse({ description: 'Activity type', type: ActivityTypeDto }) + @Post() + async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateActivityTypeDto) { + return this.service.create({ accountId, dto }); + } + + @ApiOperation({ summary: 'Get all activity types', description: 'Get all activity types for account' }) + @ApiOkResponse({ description: 'Activity types', type: [ActivityTypeDto] }) + @Get() + async findMany(@CurrentAuth() { accountId }: AuthData) { + return this.service.findMany({ accountId }); + } + + @ApiOperation({ summary: 'Update activity type', description: 'Update activity type' }) + @ApiParam({ name: 'activityTypeId', type: Number, required: true, description: 'Activity type ID' }) + @ApiBody({ type: UpdateActivityTypeDto, required: true, description: 'Data to update activity type' }) + @ApiOkResponse({ description: 'Activity type', type: ActivityTypeDto }) + @Patch(':activityTypeId') + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('activityTypeId', ParseIntPipe) activityTypeId: number, + @Body() dto: UpdateActivityTypeDto, + ) { + return this.service.update({ accountId, activityTypeId, dto }); + } + + @ApiOperation({ summary: 'Delete activity type', description: 'Delete activity type' }) + @ApiParam({ name: 'activityTypeId', type: Number, required: true, description: 'Activity type ID' }) + @ApiOkResponse() + @Delete(':activityTypeId') + async delete(@CurrentAuth() { accountId }: AuthData, @Param('activityTypeId', ParseIntPipe) activityTypeId: number) { + await this.service.setActive({ accountId, activityTypeId, isActive: false }); + } +} diff --git a/backend/src/CRM/activity-type/activity-type.service.ts b/backend/src/CRM/activity-type/activity-type.service.ts new file mode 100644 index 0000000..c47e40e --- /dev/null +++ b/backend/src/CRM/activity-type/activity-type.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { CreateActivityTypeDto, UpdateActivityTypeDto } from './dto'; +import { ActivityType } from './entities'; +import { NotFoundError } from '@/common'; + +interface FindFilter { + accountId: number; + id?: number | number[]; + name?: string; +} + +@Injectable() +export class ActivityTypeService { + constructor( + @InjectRepository(ActivityType) + private readonly repository: Repository, + ) {} + + async create({ accountId, dto }: { accountId: number; dto: CreateActivityTypeDto }): Promise { + let activityType = await this.findOne({ accountId, name: dto.name }); + if (activityType && activityType.isActive) { + return activityType; + } + if (activityType && !activityType.isActive) { + activityType.isActive = true; + } else { + activityType = new ActivityType(accountId, dto.name); + } + return this.repository.save(activityType); + } + + async findOne(filter: FindFilter): Promise { + return await this.createFindQb(filter).getOne(); + } + async findMany(filter: FindFilter): Promise { + return await this.createFindQb(filter).orderBy('at.created_at', 'ASC').getMany(); + } + + async findManyIds(filter: FindFilter): Promise { + const result = await this.createFindQb(filter).select('at.id', 'id').getRawMany(); + + return result.map((r) => r.id); + } + + async update({ + accountId, + activityTypeId, + dto, + }: { + accountId: number; + activityTypeId: number; + dto: UpdateActivityTypeDto; + }): Promise { + const at = await this.findOne({ accountId, id: activityTypeId }); + if (!at) { + throw NotFoundError.withId(ActivityType, activityTypeId); + } + + if (dto.name) { + at.name = dto.name; + } + + await this.repository.save(at); + + return at; + } + + async setActive({ + accountId, + activityTypeId, + isActive, + }: { + accountId: number; + activityTypeId: number; + isActive: boolean; + }): Promise { + await this.repository.update({ accountId, id: activityTypeId }, { isActive }); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('at') + .where('at.account_id = :accountId', { accountId: filter.accountId }); + + if (filter?.id) { + if (Array.isArray(filter.id)) { + qb.andWhere('at.id IN (:...ids)', { ids: filter.id }); + } else { + qb.andWhere('at.id = :id', { id: filter.id }); + } + } + + if (filter?.name) { + qb.andWhere('at.name = :name', { name: filter.name }); + } + + return qb; + } +} diff --git a/backend/src/CRM/activity-type/dto/activity-type.dto.ts b/backend/src/CRM/activity-type/dto/activity-type.dto.ts new file mode 100644 index 0000000..9a96468 --- /dev/null +++ b/backend/src/CRM/activity-type/dto/activity-type.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsString } from 'class-validator'; + +export class ActivityTypeDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsBoolean() + isActive: boolean; + + @ApiProperty() + @IsString() + createdAt: string; +} diff --git a/backend/src/CRM/activity-type/dto/create-activity-type.dto.ts b/backend/src/CRM/activity-type/dto/create-activity-type.dto.ts new file mode 100644 index 0000000..bd4c772 --- /dev/null +++ b/backend/src/CRM/activity-type/dto/create-activity-type.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { ActivityTypeDto } from './activity-type.dto'; + +export class CreateActivityTypeDto extends PickType(ActivityTypeDto, ['name'] as const) {} diff --git a/backend/src/CRM/activity-type/dto/index.ts b/backend/src/CRM/activity-type/dto/index.ts new file mode 100644 index 0000000..4c2f91c --- /dev/null +++ b/backend/src/CRM/activity-type/dto/index.ts @@ -0,0 +1,3 @@ +export * from './activity-type.dto'; +export * from './create-activity-type.dto'; +export * from './update-activity-type.dto'; diff --git a/backend/src/CRM/activity-type/dto/update-activity-type.dto.ts b/backend/src/CRM/activity-type/dto/update-activity-type.dto.ts new file mode 100644 index 0000000..2998272 --- /dev/null +++ b/backend/src/CRM/activity-type/dto/update-activity-type.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { ActivityTypeDto } from './activity-type.dto'; + +export class UpdateActivityTypeDto extends PickType(ActivityTypeDto, ['name'] as const) {} diff --git a/backend/src/CRM/activity-type/entities/activity-type.entity.ts b/backend/src/CRM/activity-type/entities/activity-type.entity.ts new file mode 100644 index 0000000..7d3b445 --- /dev/null +++ b/backend/src/CRM/activity-type/entities/activity-type.entity.ts @@ -0,0 +1,39 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { ActivityTypeDto } from '../dto/activity-type.dto'; + +@Entity('activity_type') +export class ActivityType { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column() + accountId: number; + + @Column() + isActive: boolean; + + @Column() + createdAt: Date; + + constructor(accountId: number, name: string, isActive?: boolean, createdAt?: Date) { + this.accountId = accountId; + this.name = name; + this.isActive = isActive ?? true; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public toDto(): ActivityTypeDto { + return { + id: this.id, + name: this.name, + isActive: this.isActive, + createdAt: this.createdAt.toISOString(), + }; + } +} diff --git a/backend/src/CRM/activity-type/entities/index.ts b/backend/src/CRM/activity-type/entities/index.ts new file mode 100644 index 0000000..16cc30b --- /dev/null +++ b/backend/src/CRM/activity-type/entities/index.ts @@ -0,0 +1 @@ +export * from './activity-type.entity'; diff --git a/backend/src/CRM/activity-type/index.ts b/backend/src/CRM/activity-type/index.ts new file mode 100644 index 0000000..201789b --- /dev/null +++ b/backend/src/CRM/activity-type/index.ts @@ -0,0 +1,4 @@ +export * from './activity-type.controller'; +export * from './activity-type.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/CRM/activity/activity.controller.ts b/backend/src/CRM/activity/activity.controller.ts new file mode 100644 index 0000000..9f3f911 --- /dev/null +++ b/backend/src/CRM/activity/activity.controller.ts @@ -0,0 +1,38 @@ +import { Body, Controller, Delete, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ActivityDto, CreateActivityDto, UpdateActivityDto } from './dto'; +import { ActivityService } from './activity.service'; + +@ApiTags('crm/activity') +@Controller('crm/activities') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class ActivityController { + constructor(private readonly service: ActivityService) {} + + @ApiCreatedResponse({ description: 'Create activity', type: ActivityDto }) + @Post() + async create(@CurrentAuth() { account, user }: AuthData, @Body() dto: CreateActivityDto): Promise { + return this.service.createAndGetDto(account, user, dto); + } + + @ApiCreatedResponse({ description: 'Update activity', type: ActivityDto }) + @Patch(':activityId') + async update( + @CurrentAuth() { account, user }: AuthData, + @Param('activityId', ParseIntPipe) activityId: number, + @Body() dto: UpdateActivityDto, + ): Promise { + return this.service.update(account, user, activityId, dto); + } + + @ApiOkResponse({ description: 'Delete activity' }) + @Delete(':activityId') + async delete(@CurrentAuth() { accountId, user }: AuthData, @Param('activityId', ParseIntPipe) activityId: number) { + await this.service.delete(accountId, user, activityId); + } +} diff --git a/backend/src/CRM/activity/activity.handler.ts b/backend/src/CRM/activity/activity.handler.ts new file mode 100644 index 0000000..0202961 --- /dev/null +++ b/backend/src/CRM/activity/activity.handler.ts @@ -0,0 +1,71 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; +import { + ActionActivityCreateSettings, + AutomationJob, + EntityTypeActionType, + OnAutomationJob, +} from '@/modules/automation'; + +import { ActivityService } from './activity.service'; + +interface EntityVariables { + id?: number | null; + stageId?: number | null; + responsibleUserId?: number | null; +} + +interface CreateActivityVariables { + accountId: number; + entity?: EntityVariables | null; + activitySettings?: ActionActivityCreateSettings | null; +} + +@Injectable() +export class ActivityHandler { + private readonly logger = new Logger(ActivityHandler.name); + constructor(private readonly service: ActivityService) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + async onUserDeleted(event: UserDeletedEvent) { + if (event.newUserId) { + await this.service.changeResponsible({ + accountId: event.accountId, + currentUserId: event.userId, + newUserId: event.newUserId, + }); + } + } + + /** + * @deprecated use new @see handleCreateJob instead + */ + @OnAutomationJob(EntityTypeActionType.CreateActivity) + async handleCreateJobOld(job: AutomationJob): Promise<{ variables?: unknown }> { + return this.handleCreateJob(job); + } + @OnAutomationJob(EntityTypeActionType.ActivityCreate) + async handleCreateJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.activitySettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, activitySettings } = job.variables; + const activity = await this.service.processAutomation({ + accountId, + entityId: entity.id, + entityOwnerId: entity.responsibleUserId, + entityStageId: entity.stageId, + settings: activitySettings, + }); + return { variables: { ...job.variables, activity: activity?.toSimpleDto() } }; + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + return { variables: job.variables }; + } + } +} diff --git a/backend/src/CRM/activity/activity.service.ts b/backend/src/CRM/activity/activity.service.ts new file mode 100644 index 0000000..67a9274 --- /dev/null +++ b/backend/src/CRM/activity/activity.service.ts @@ -0,0 +1,325 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Repository } from 'typeorm'; +import { convert } from 'html-to-text'; + +import { DateUtil, FileLinkSource, NotFoundError } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { UserService } from '@/modules/iam/user/user.service'; +import { ActionActivityCreateSettings, ActionHelper } from '@/modules/automation'; +import { EntityInfoService } from '@/modules/entity/entity-info'; +import { NotificationType } from '@/modules/notification/notification/enums/notification-type.enum'; +import { CreateNotificationDto } from '@/modules/notification/notification/dto/create-notification.dto'; + +import { ActivityCreatedEvent, ActivityEvent, CrmEventType } from '../common'; +import { ActivityTypeService } from '../activity-type/activity-type.service'; +import { BaseTaskService } from '../base-task'; +import { FileLinkService } from '../Service/FileLink/FileLinkService'; + +import { Activity } from './entities'; +import { ActivityDto, CreateActivityDto, UpdateActivityDto } from './dto'; + +interface FindFilter { + accountId: number; + activityId?: number | number; + entityId?: number; + isResolved?: boolean; + responsibles?: number | number[]; +} + +@Injectable() +export class ActivityService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Activity) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly userService: UserService, + private readonly activityTypeService: ActivityTypeService, + private readonly fileLinkService: FileLinkService, + private readonly baseTaskService: BaseTaskService, + private readonly entityInfoService: EntityInfoService, + ) {} + + public async create( + accountId: number, + user: User, + dto: CreateActivityDto, + options?: { skipPermissionCheck?: boolean }, + ): Promise { + if (!options?.skipPermissionCheck) { + await this.authService.check({ + action: 'create', + user, + authorizable: Activity.getAuthorizable(), + throwError: true, + }); + } + + dto.weight = dto.weight ?? (await this.baseTaskService.calculateWeight(accountId, dto.afterId, dto.beforeId)); + const activity = await this.repository.save(Activity.create(accountId, user.id, dto)); + + if (dto.fileIds) { + await this.fileLinkService.processFiles(accountId, FileLinkSource.ACTIVITY, activity.id, dto.fileIds); + } + + const activityType = await this.activityTypeService.findOne({ accountId, id: dto.activityTypeId }); + + this.eventEmitter.emit( + CrmEventType.ActivityCreated, + new ActivityCreatedEvent({ + accountId, + activityId: activity.id, + ownerId: activity.responsibleUserId, + entityId: activity.entityId, + createdBy: activity.createdBy, + activityText: activity.text, + activityTypeName: activityType.name, + createdAt: activity.createdAt.toISOString(), + }), + ); + + return activity; + } + + public async createAndGetDto(account: Account, user: User, dto: CreateActivityDto): Promise { + const activity = await this.create(account.id, user, dto); + + return await this.createDtoForActivity(account, user, activity); + } + + public async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + public async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).orderBy('activity.id', 'DESC').getMany(); + } + + public async update(account: Account, user: User, id: number, dto: UpdateActivityDto): Promise { + let activity = await this.repository.findOneBy({ id }); + if (!activity) { + throw NotFoundError.withId(Activity, id); + } + + await this.authService.check({ action: 'edit', user, authorizable: activity, throwError: true }); + + if (dto.sorting) { + activity.weight = await this.baseTaskService.calculateWeight( + account.id, + dto.sorting.afterId, + dto.sorting.beforeId, + ); + } + activity = await this.repository.save(activity.update(dto)); + + if (dto.fileIds !== undefined) { + await this.fileLinkService.processFiles(account.id, FileLinkSource.ACTIVITY, id, dto.fileIds ?? []); + } + + this.eventEmitter.emit( + CrmEventType.ActivityUpdated, + new ActivityEvent({ accountId: account.id, entityId: activity.entityId, activityId: id }), + ); + + return await this.createDtoForActivity(account, user, activity); + } + + public async delete(accountId: number, user: User, id: number) { + const activity = await this.repository.findOneBy({ id }); + await this.authService.check({ action: 'delete', user, authorizable: activity, throwError: true }); + + await this.fileLinkService.processFiles(accountId, FileLinkSource.ACTIVITY, id, []); + const result = await this.repository.delete({ id }); + if (result.affected === 0) { + throw NotFoundError.withId(Activity, id); + } + + this.eventEmitter.emit( + CrmEventType.ActivityDeleted, + new ActivityEvent({ accountId, entityId: activity.entityId, activityId: id }), + ); + } + + public async findDtoForId(account: Account, user: User, id: number): Promise { + const activity = await this.repository.findOneBy({ id }); + return activity ? await this.createDtoForActivity(account, user, activity) : null; + } + + public async createDtoForActivity(account: Account, user: User, activity: Activity): Promise { + if (!(await this.authService.check({ action: 'view', user, authorizable: activity }))) { + return null; + } + + const entityInfo = activity.entityId + ? await this.entityInfoService.findOne({ + accountId: account.id, + user, + entityId: activity.entityId, + }) + : null; + const fileLinks = await this.fileLinkService.getFileLinkDtos(account, FileLinkSource.ACTIVITY, activity.id); + const userRights = await this.authService.getUserRights({ user, authorizable: activity }); + + return new ActivityDto(activity, entityInfo, fileLinks, userRights); + } + + public async changeResponsible({ + accountId, + currentUserId, + newUserId, + }: { + accountId: number; + currentUserId: number; + newUserId: number; + }) { + await this.repository.update({ accountId, responsibleUserId: currentUserId }, { responsibleUserId: newUserId }); + } + + public async processAutomation({ + accountId, + entityId, + entityOwnerId, + entityStageId, + settings, + }: { + accountId: number; + entityId: number; + entityOwnerId: number; + entityStageId: number | null | undefined; + settings: ActionActivityCreateSettings; + }): Promise { + const entity = await this.entityInfoService.findOne({ accountId, entityId }); + if (entity && (!entity.stageId || settings.allowAnyStage || entity.stageId === entityStageId)) { + const user = await this.userService.findOne({ accountId, id: entityOwnerId }); + const ownerId = settings.responsibleUserId ?? entityOwnerId; + const startDate = DateUtil.add(DateUtil.now(), { seconds: settings.deferStart ?? 0 }); + const endDate = ActionHelper.getEndDate({ + startDate, + deadlineType: settings.deadlineType, + deadlineTime: settings.deadlineTime, + }); + return this.create( + accountId, + user, + { + responsibleUserId: ownerId, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + activityTypeId: settings.activityTypeId, + text: settings.text, + entityId, + }, + { skipPermissionCheck: true }, + ); + } + return null; + } + + public async getOverdueNotifications(from: Date, to: Date): Promise { + const activities = await this.repository + .createQueryBuilder('activity') + .where('activity.is_resolved = false') + .andWhere('activity.end_date > :from', { from }) + .andWhere('activity.end_date <= :to', { to }) + .getMany(); + return activities.map( + (activity) => + new CreateNotificationDto( + activity.accountId, + activity.responsibleUserId, + NotificationType.ACTIVITY_OVERDUE, + activity.id, + activity.entityId, + activity.createdBy, + null, + convert(activity.text), + ), + ); + } + + public async getOverdueForFollowNotifications( + notifyUserId: number, + from: Date, + to: Date, + followUserIds: number[], + ): Promise { + const activities = await this.repository + .createQueryBuilder('activity') + .where('activity.is_resolved = false') + .andWhere('activity.end_date > :from', { from }) + .andWhere('activity.end_date <= :to', { to }) + .andWhere('activity.responsible_user_id in (:...userIds)', { userIds: followUserIds }) + .getMany(); + return activities.map( + (activity) => + new CreateNotificationDto( + activity.accountId, + notifyUserId, + NotificationType.ACTIVITY_OVERDUE_EMPLOYEE, + activity.id, + activity.entityId, + activity.responsibleUserId, + null, + convert(activity.text), + ), + ); + } + + public async getBeforeStartNotifications(userId: number, from: Date, to: Date) { + const activities = await this.repository + .createQueryBuilder('activity') + .where('activity.is_resolved = false') + .andWhere('activity.responsible_user_id = :userId', { userId }) + .andWhere('activity.start_date > :from', { from }) + .andWhere('activity.start_date <= :to', { to }) + .getMany(); + return activities.map( + (activity) => + new CreateNotificationDto( + activity.accountId, + activity.responsibleUserId, + NotificationType.ACTIVITY_BEFORE_START, + activity.id, + activity.entityId, + activity.createdBy, + null, + convert(activity.text), + ), + ); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('activity') + .where('activity.accountId = :accountId', { accountId: filter.accountId }); + if (filter.activityId) { + if (Array.isArray(filter.activityId)) { + qb.andWhere('activity.id IN (:...activityIds)', { activityIds: filter.activityId }); + } else { + qb.andWhere('activity.id = :activityId', { activityId: filter.activityId }); + } + } + if (filter.entityId) { + qb.andWhere('activity.entity_id = :entityId', { entityId: filter.entityId }); + } + if (filter.isResolved !== undefined) { + qb.andWhere('activity.is_resolved = :isResolved', { isResolved: filter.isResolved }); + } + if (filter.responsibles) { + if (Array.isArray(filter.responsibles)) { + if (filter.responsibles.length === 0) { + return qb.where('1 = 0'); + } + qb.andWhere('activity.responsible_user_id IN (:...responsibles)', { responsibles: filter.responsibles }); + } else { + qb.andWhere('activity.responsible_user_id = :responsible', { responsible: filter.responsibles }); + } + } + + return qb; + } +} diff --git a/backend/src/CRM/activity/dto/activity.dto.ts b/backend/src/CRM/activity/dto/activity.dto.ts new file mode 100644 index 0000000..8bcaf85 --- /dev/null +++ b/backend/src/CRM/activity/dto/activity.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; + +import { BaseTaskDto } from '../../base-task'; +import { Activity } from '../entities'; + +export class ActivityDto extends BaseTaskDto { + @ApiProperty() + activityTypeId: number; + + @ApiProperty({ required: false }) + result?: string; + + constructor(activity: Activity, entityInfo: EntityInfoDto | null, fileLinks: FileLinkDto[], userRights: UserRights) { + super(activity, entityInfo, fileLinks, userRights); + this.activityTypeId = activity.activityTypeId; + this.result = activity.result; + } +} diff --git a/backend/src/CRM/activity/dto/create-activity.dto.ts b/backend/src/CRM/activity/dto/create-activity.dto.ts new file mode 100644 index 0000000..6e337ef --- /dev/null +++ b/backend/src/CRM/activity/dto/create-activity.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { CreateBaseTaskDto } from '../../base-task'; + +export class CreateActivityDto extends CreateBaseTaskDto { + @ApiProperty() + @IsNumber() + entityId: number; + + @ApiProperty() + @IsNumber() + activityTypeId: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + fileIds?: string[] | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + result?: string | null; +} diff --git a/backend/src/CRM/activity/dto/index.ts b/backend/src/CRM/activity/dto/index.ts new file mode 100644 index 0000000..51002bc --- /dev/null +++ b/backend/src/CRM/activity/dto/index.ts @@ -0,0 +1,3 @@ +export * from './activity.dto'; +export * from './create-activity.dto'; +export * from './update-activity.dto'; diff --git a/backend/src/CRM/activity/dto/update-activity.dto.ts b/backend/src/CRM/activity/dto/update-activity.dto.ts new file mode 100644 index 0000000..23091d2 --- /dev/null +++ b/backend/src/CRM/activity/dto/update-activity.dto.ts @@ -0,0 +1,26 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UpdateBaseTaskDto } from '../../base-task'; + +export class UpdateActivityDto extends UpdateBaseTaskDto { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + entityId?: number; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + activityTypeId?: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsArray() + fileIds?: string[] | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + result?: string | null; +} diff --git a/backend/src/CRM/activity/entities/activity.entity.ts b/backend/src/CRM/activity/entities/activity.entity.ts new file mode 100644 index 0000000..a421df3 --- /dev/null +++ b/backend/src/CRM/activity/entities/activity.entity.ts @@ -0,0 +1,107 @@ +import { Column, Entity } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; +import { BaseTask, TaskView } from '../../base-task'; +import { ActivityDto, CreateActivityDto, UpdateActivityDto } from '../dto'; + +@Entity('activity') +export class Activity extends BaseTask implements Authorizable { + @Column() + entityId: number; + + @Column() + activityTypeId: number; + + @Column({ nullable: true }) + result?: string; + + constructor( + accountId: number, + entityId: number, + createdBy: number, + responsibleUserId: number, + text: string, + activityTypeId: number, + isResolved: boolean, + startDate: Date, + endDate: Date, + resolvedDate: Date | null, + weight: number, + result?: string, + createdAt?: Date, + ) { + super( + accountId, + createdBy, + responsibleUserId, + text, + isResolved, + startDate, + endDate, + resolvedDate, + weight, + createdAt, + ); + this.entityId = entityId; + this.activityTypeId = activityTypeId; + this.result = result; + } + + public static create(accountId: number, createdBy: number, dto: CreateActivityDto): Activity { + return new Activity( + accountId, + dto.entityId, + createdBy, + dto.responsibleUserId, + dto.text, + dto.activityTypeId, + !!dto.isResolved, + dto.startDate ? DateUtil.fromISOString(dto.startDate) : DateUtil.now(), + dto.endDate ? DateUtil.fromISOString(dto.endDate) : DateUtil.now(), + dto.resolvedDate ? DateUtil.fromISOString(dto.resolvedDate) : null, + dto.weight, + dto.result, + ); + } + + public update(dto: UpdateActivityDto): Activity { + this.entityId = dto.entityId !== undefined ? dto.entityId : this.entityId; + this.responsibleUserId = dto.responsibleUserId !== undefined ? dto.responsibleUserId : this.responsibleUserId; + this.text = dto.text !== undefined ? dto.text : this.text; + this.activityTypeId = dto.activityTypeId !== undefined ? dto.activityTypeId : this.activityTypeId; + this.result = dto.result !== undefined ? dto.result : this.result; + this.startDate = dto.startDate !== undefined ? DateUtil.fromISOString(dto.startDate) : this.startDate; + this.endDate = dto.endDate !== undefined ? DateUtil.fromISOString(dto.endDate) : this.endDate; + if (dto.isResolved !== undefined) { + if (!this.isResolved && dto.isResolved) this.resolvedDate = DateUtil.now(); + if (this.isResolved && !dto.isResolved) this.resolvedDate = null; + this.isResolved = dto.isResolved; + } + return this; + } + + public view(): TaskView { + return TaskView.Activity; + } + + public toSimpleDto(): ActivityDto { + return new ActivityDto(this, null, [], null); + } + + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.Activity, + id: null, + ownerId: this.responsibleUserId, + createdBy: this.createdBy, + }; + } + + static getAuthorizable(): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Activity, id: null }); + } +} diff --git a/backend/src/CRM/activity/entities/index.ts b/backend/src/CRM/activity/entities/index.ts new file mode 100644 index 0000000..97645a8 --- /dev/null +++ b/backend/src/CRM/activity/entities/index.ts @@ -0,0 +1 @@ +export * from './activity.entity'; diff --git a/backend/src/CRM/activity/index.ts b/backend/src/CRM/activity/index.ts new file mode 100644 index 0000000..190678d --- /dev/null +++ b/backend/src/CRM/activity/index.ts @@ -0,0 +1,5 @@ +export * from './activity.controller'; +export * from './activity.handler'; +export * from './activity.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/CRM/base-task/base-task.service.ts b/backend/src/CRM/base-task/base-task.service.ts new file mode 100644 index 0000000..9beb906 --- /dev/null +++ b/backend/src/CRM/base-task/base-task.service.ts @@ -0,0 +1,64 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; + +const WEIGHT_MIN = 100.0; +const WEIGHT_STEP = 100.0; + +@Injectable() +export class BaseTaskService { + constructor(private dataSource: DataSource) {} + + public async calculateWeight( + accountId: number, + afterId: number | null | undefined, + beforeId: number | null | undefined, + ): Promise { + let afterWeight = afterId ? await this.getWeightById(accountId, afterId) : null; + let beforeWeight = beforeId ? await this.getWeightById(accountId, beforeId) : null; + if (afterWeight === null && beforeWeight === null) { + const minWeight = await this.getMinWeightForAccount(accountId); + return minWeight === null ? WEIGHT_MIN : minWeight - WEIGHT_STEP; + } else if (afterWeight !== null && beforeWeight === null) { + beforeWeight = await this.getMinWeightMoreThan(accountId, afterWeight); + if (beforeWeight === null) { + return afterWeight + WEIGHT_STEP; + } + } else if (afterWeight === null && beforeWeight !== null) { + afterWeight = await this.getMaxWeightLessThan(accountId, beforeWeight); + if (afterWeight === null) { + return beforeWeight - WEIGHT_STEP; + } + } + return (afterWeight + beforeWeight) / 2.0; + } + + private async getMinWeightForAccount(accountId: number): Promise { + const result = await this.dataSource.query( + `select min(at.weight) as weight from all_tasks at where at.account_id = ${accountId}`, + ); + return result && result.length > 0 ? result[0].weight : null; + } + + private async getWeightById(accountId: number, id: number): Promise { + const result = await this.dataSource.query( + `select at.weight from all_tasks at where at.account_id = ${accountId} and at.id = ${id};`, + ); + return result && result.length > 0 ? result[0].weight : null; + } + + private async getMinWeightMoreThan(accountId: number, limitWeight: number): Promise { + const result = await this.dataSource.query( + // eslint-disable-next-line max-len + `select min(at.weight) as weight from all_tasks at where at.account_id = ${accountId} and at.weight > ${limitWeight}`, + ); + return result && result.length > 0 ? result[0].weight : null; + } + + private async getMaxWeightLessThan(accountId: number, limitWeight: number): Promise { + const result = await this.dataSource.query( + // eslint-disable-next-line max-len + `select max(at.weight) as weight from all_tasks at where at.account_id = ${accountId} and at.weight < ${limitWeight}`, + ); + return result && result.length > 0 ? result[0].weight : null; + } +} diff --git a/backend/src/CRM/base-task/dto/base-task.dto.ts b/backend/src/CRM/base-task/dto/base-task.dto.ts new file mode 100644 index 0000000..ffecf87 --- /dev/null +++ b/backend/src/CRM/base-task/dto/base-task.dto.ts @@ -0,0 +1,70 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; + +import { TaskView } from '../enums'; +import { BaseTask } from '../entities'; + +export abstract class BaseTaskDto { + @ApiProperty() + id: number; + + @ApiProperty() + responsibleUserId: number; + + @ApiProperty({ nullable: true }) + startDate: string | null; + + @ApiProperty({ nullable: true }) + endDate: string | null; + + @ApiProperty() + text: string; + + @ApiProperty() + isResolved: boolean; + + @ApiProperty({ nullable: true }) + resolvedDate: string | null; + + @ApiProperty() + createdBy: number; + + @ApiProperty() + weight: number; + + @ApiProperty() + createdAt: string; + + @ApiProperty() + view: TaskView; + + @ApiProperty({ type: [FileLinkDto] }) + fileLinks: FileLinkDto[]; + + @ApiProperty({ nullable: true }) + entityInfo: EntityInfoDto | null; + + @ApiProperty({ type: () => UserRights }) + userRights: UserRights; + + constructor(baseTask: BaseTask, entityInfo: EntityInfoDto | null, fileLinks: FileLinkDto[], userRights: UserRights) { + this.id = baseTask.id; + this.responsibleUserId = baseTask.responsibleUserId; + this.startDate = baseTask.startDate ? baseTask.startDate.toISOString() : null; + this.endDate = baseTask.endDate ? baseTask.endDate.toISOString() : null; + this.text = baseTask.text; + this.isResolved = baseTask.isResolved; + this.createdBy = baseTask.createdBy; + this.weight = baseTask.weight; + this.createdAt = baseTask.createdAt.toISOString(); + this.view = baseTask.view(); + this.fileLinks = fileLinks; + this.resolvedDate = baseTask.resolvedDate ? baseTask.resolvedDate.toISOString() : null; + this.entityInfo = entityInfo; + this.userRights = userRights; + } +} diff --git a/backend/src/CRM/base-task/dto/create-base-task.dto.ts b/backend/src/CRM/base-task/dto/create-base-task.dto.ts new file mode 100644 index 0000000..45e621f --- /dev/null +++ b/backend/src/CRM/base-task/dto/create-base-task.dto.ts @@ -0,0 +1,48 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export abstract class CreateBaseTaskDto { + @ApiProperty({ description: 'Responsible user ID' }) + @IsNumber() + responsibleUserId: number; + + @ApiPropertyOptional({ nullable: true, description: 'Start date' }) + @IsOptional() + @IsString() + startDate?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'End date' }) + @IsOptional() + @IsString() + endDate?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Text' }) + @IsOptional() + @IsString() + text?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Is resolved' }) + @IsOptional() + @IsBoolean() + isResolved?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Resolved date' }) + @IsOptional() + @IsString() + resolvedDate?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Weight' }) + @IsOptional() + @IsNumber() + weight?: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'Task ID to place after on weight' }) + @IsOptional() + @IsNumber() + afterId?: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'Task ID to place before on weight' }) + @IsOptional() + @IsNumber() + beforeId?: number | null; +} diff --git a/backend/src/CRM/base-task/dto/index.ts b/backend/src/CRM/base-task/dto/index.ts new file mode 100644 index 0000000..0ebce87 --- /dev/null +++ b/backend/src/CRM/base-task/dto/index.ts @@ -0,0 +1,3 @@ +export * from './base-task.dto'; +export * from './create-base-task.dto'; +export * from './update-base-task.dto'; diff --git a/backend/src/CRM/base-task/dto/update-base-task.dto.ts b/backend/src/CRM/base-task/dto/update-base-task.dto.ts new file mode 100644 index 0000000..230493c --- /dev/null +++ b/backend/src/CRM/base-task/dto/update-base-task.dto.ts @@ -0,0 +1,35 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ManualSorting } from '@/common'; + +export abstract class UpdateBaseTaskDto { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + responsibleUserId?: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + startDate?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + endDate?: string | null; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + text?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsBoolean() + isResolved?: boolean; + + @ApiPropertyOptional({ type: ManualSorting, nullable: true }) + @IsOptional() + sorting?: ManualSorting | null; +} diff --git a/backend/src/CRM/base-task/entities/base-task.entity.ts b/backend/src/CRM/base-task/entities/base-task.entity.ts new file mode 100644 index 0000000..c07d7a0 --- /dev/null +++ b/backend/src/CRM/base-task/entities/base-task.entity.ts @@ -0,0 +1,89 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { TaskView } from '../enums'; + +@Entity() +export abstract class BaseTask { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + responsibleUserId: number; + + @Column({ nullable: true }) + startDate: Date | null; + + @Column({ nullable: true }) + endDate: Date | null; + + @Column() + text: string; + + @Column() + createdBy: number; + + @Column() + isResolved: boolean; + + @Column({ nullable: true }) + resolvedDate: Date | null; + + @Column({ type: 'double precision' }) + weight: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + createdBy: number, + responsibleUserId: number, + text: string, + isResolved: boolean, + startDate: Date | null, + endDate: Date | null, + resolvedDate: Date | null, + weight: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.createdBy = createdBy; + this.responsibleUserId = responsibleUserId; + this.text = text; + this.isResolved = isResolved; + this.startDate = startDate; + this.endDate = endDate; + this.resolvedDate = resolvedDate; + this.weight = weight; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public isTaskExpired(): boolean { + if (this.endDate) { + return DateUtil.isPast(this.endDate); + } + + return false; + } + + public isTaskToday(): boolean { + const now = DateUtil.now(); + + if (this.startDate && this.endDate) { + return this.startDate <= now && this.endDate >= now; + } else if (this.startDate) { + return DateUtil.isToday(this.startDate); + } else if (this.endDate) { + return DateUtil.isToday(this.endDate); + } + + return false; + } + + public abstract view(): TaskView; +} diff --git a/backend/src/CRM/base-task/entities/index.ts b/backend/src/CRM/base-task/entities/index.ts new file mode 100644 index 0000000..62c3a22 --- /dev/null +++ b/backend/src/CRM/base-task/entities/index.ts @@ -0,0 +1 @@ +export * from './base-task.entity'; diff --git a/backend/src/CRM/base-task/enums/index.ts b/backend/src/CRM/base-task/enums/index.ts new file mode 100644 index 0000000..51c69e6 --- /dev/null +++ b/backend/src/CRM/base-task/enums/index.ts @@ -0,0 +1 @@ +export * from './task-view.enum'; diff --git a/backend/src/CRM/base-task/enums/task-view.enum.ts b/backend/src/CRM/base-task/enums/task-view.enum.ts new file mode 100644 index 0000000..9f1deb3 --- /dev/null +++ b/backend/src/CRM/base-task/enums/task-view.enum.ts @@ -0,0 +1,4 @@ +export enum TaskView { + Activity = 'activity', + Task = 'task', +} diff --git a/backend/src/CRM/base-task/index.ts b/backend/src/CRM/base-task/index.ts new file mode 100644 index 0000000..d37410d --- /dev/null +++ b/backend/src/CRM/base-task/index.ts @@ -0,0 +1,4 @@ +export * from './base-task.service'; +export * from './dto'; +export * from './entities'; +export * from './enums'; diff --git a/backend/src/CRM/board-stage/board-stage.controller.ts b/backend/src/CRM/board-stage/board-stage.controller.ts new file mode 100644 index 0000000..0b45ea8 --- /dev/null +++ b/backend/src/CRM/board-stage/board-stage.controller.ts @@ -0,0 +1,120 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized, UserAccess } from '@/modules/iam/common'; + +import { + BoardStageDto, + CreateBoardStageDto, + ProcessBoardStageDto, + UpdateBoardStageDto, + UpdateBoardStagesDto, +} from './dto'; +import { BoardStageService } from './board-stage.service'; + +@ApiTags('crm/boards/stages') +@Controller('crm/boards/:boardId/stages') +@JwtAuthorized() +@TransformToDto() +export class BoardStageController { + constructor(private readonly service: BoardStageService) {} + + @ApiOperation({ summary: 'Create board stage', description: 'Create board stage' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ type: CreateBoardStageDto, description: 'Data for creating board stage', required: true }) + @ApiCreatedResponse({ description: 'Board stage', type: BoardStageDto }) + @Post() + @UserAccess({ adminOnly: true }) + async create( + @CurrentAuth() { accountId }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() dto: CreateBoardStageDto, + ) { + return this.service.create({ accountId, boardId, dto }); + } + + @ApiOperation({ + summary: 'Get board stages', + description: `Get board stages. Use 'all' for boardId to get all stages for account.`, + }) + @ApiParam({ name: 'boardId', description: `Board ID`, type: Number, required: true }) + @ApiOkResponse({ description: 'Board stages', type: [BoardStageDto] }) + @Get() + async findMany(@CurrentAuth() { accountId }: AuthData, @Param('boardId', ParseIntPipe) boardId: number) { + return this.service.findMany({ accountId, boardId }); + } + + @ApiOperation({ summary: 'Get board stage', description: 'Get board stage.' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiParam({ name: 'stageId', description: 'Stage ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Board stage', type: BoardStageDto }) + @Get(':stageId') + async findOne( + @CurrentAuth() { accountId }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Param('stageId', ParseIntPipe) stageId: number, + ) { + return this.service.findOne({ accountId, boardId, stageId }); + } + + @ApiOperation({ summary: 'Update board stages', description: 'Update board stages' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ type: UpdateBoardStagesDto, description: 'Data for updating board stages', required: true }) + @ApiOkResponse({ description: 'Board stage', type: BoardStageDto }) + @Patch() + @UserAccess({ adminOnly: true }) + async updateMany( + @CurrentAuth() { accountId }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() dto: UpdateBoardStagesDto, + ) { + return this.service.updateMany({ accountId, boardId, dtos: dto.stages }); + } + + @ApiOperation({ summary: 'Update board stage', description: 'Update board stage.' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiParam({ name: 'stageId', description: 'Stage ID', type: Number, required: true }) + @ApiBody({ type: UpdateBoardStageDto, description: 'Data for updating board stage', required: true }) + @ApiOkResponse({ description: 'Board stage', type: BoardStageDto }) + @Patch(':stageId') + @UserAccess({ adminOnly: true }) + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Param('stageId', ParseIntPipe) stageId: number, + @Body() dto: UpdateBoardStageDto, + ) { + return this.service.update({ accountId, boardId, stageId, dto }); + } + + @ApiOperation({ summary: 'Process board stages', description: 'Process board stages. Create or update stages.' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ type: ProcessBoardStageDto, description: 'Data for processing board stages', required: true }) + @ApiCreatedResponse({ description: 'Board stages', type: [BoardStageDto] }) + @Post('batch') + @UserAccess({ adminOnly: true }) + async processBatch( + @CurrentAuth() { accountId }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() dto: ProcessBoardStageDto, + ) { + return this.service.processBatch({ accountId, boardId, dtos: dto?.stages }); + } + + @ApiOperation({ summary: 'Delete board stage', description: 'Delete board stage and move related to new stage.' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiParam({ name: 'stageId', description: 'Stage ID', type: Number, required: true }) + @ApiQuery({ name: 'newStageId', description: 'New stage ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Deleted stage ID', type: Number }) + @Delete(':stageId') + @UserAccess({ adminOnly: true }) + async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Param('stageId', ParseIntPipe) stageId: number, + @Query('newStageId', ParseIntPipe) newStageId: number, + ) { + return this.service.delete({ accountId, boardId, stageId, newStageId }); + } +} diff --git a/backend/src/CRM/board-stage/board-stage.service.ts b/backend/src/CRM/board-stage/board-stage.service.ts new file mode 100644 index 0000000..9f9eb55 --- /dev/null +++ b/backend/src/CRM/board-stage/board-stage.service.ts @@ -0,0 +1,331 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; +import { SequenceIdService } from '@/database'; + +import { BoardStageDeletedEvent, BoardStageEvent, CrmEventType, SequenceName } from '../common'; +import { TaskService } from '../task'; +import { EntityService } from '../Service/Entity/EntityService'; + +import { CreateBoardStageDto, UpdateBoardStageDto } from './dto'; +import { BoardStage } from './entities'; +import { BoardStageCode, BoardStageCodes, BoardStageType } from './enums'; +import { GroupedStages } from './types'; + +interface FindFilter { + accountId: number; + stageId?: number; + boardId?: number | number[]; + includeCodes?: BoardStageCode[]; + excludeCodes?: BoardStageCode[]; + afterStageId?: number; +} + +@Injectable() +export class BoardStageService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(BoardStage) + private readonly repository: Repository, + private readonly sequenceIdService: SequenceIdService, + @Inject(forwardRef(() => EntityService)) + private readonly entityService: EntityService, + @Inject(forwardRef(() => TaskService)) + private readonly taskService: TaskService, + ) {} + + /** + * @deprecated + */ + private async nextIdentity(): Promise { + return this.sequenceIdService.nextIdentity(SequenceName.Stage); + } + + async create({ + accountId, + boardId, + dto, + }: { + accountId: number; + boardId: number; + dto: CreateBoardStageDto; + }): Promise { + dto.id ??= await this.nextIdentity(); + if (dto.sortOrder === undefined) { + const maxSortOrder = await this.createFindQb({ accountId, boardId }) + .select('MAX(stage.sort_order)', 'max') + .orderBy() + .getRawOne<{ max: number }>(); + + dto.sortOrder = (maxSortOrder?.max ?? 0) + 1; + } + const stage = await this.repository.save(BoardStage.fromDto(accountId, boardId, dto)); + + this.eventEmitter.emit( + CrmEventType.BoardStageCreated, + new BoardStageEvent({ accountId, boardId, stageId: stage.id }), + ); + + return stage; + } + async createMany({ + accountId, + boardId, + dtos, + }: { + accountId: number; + boardId: number; + dtos: CreateBoardStageDto[]; + }): Promise { + return Promise.all(dtos.map((dto) => this.create({ accountId, boardId, dto }))); + } + + async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).getMany(); + } + + async findOneId(filter: FindFilter): Promise { + const stage = await this.createFindQb(filter).select('stage.id', 'id').getRawOne<{ id: number }>(); + + return stage?.id ?? null; + } + async findManyIds(filter: FindFilter): Promise { + const stages = await this.createFindQb(filter).select('stage.id', 'id').getRawMany<{ id: number }>(); + + return stages.map((stage) => stage.id); + } + + async exists(filter: FindFilter): Promise { + return (await this.createFindQb(filter).getCount()) > 0; + } + + async getGroupedByType({ + accountId, + entityTypeId, + boardId, + type, + }: { + accountId: number; + entityTypeId?: number | number[] | null; + boardId?: number | number[] | null; + type?: BoardStageType | null; + }): Promise { + const qb = this.repository.createQueryBuilder('stage').where('stage.account_id = :accountId', { accountId }); + if (boardId) { + if (Array.isArray(boardId)) { + qb.andWhere('stage.board_id IN (:...boardId)', { boardId }); + } else { + qb.andWhere('stage.board_id = :boardId', { boardId }); + } + } + if (entityTypeId) { + qb.innerJoin('board', 'board', 'board.id = stage.board_id'); + if (Array.isArray(entityTypeId)) { + qb.andWhere('board.record_id IN (:...entityTypeId)', { entityTypeId }); + } else { + qb.andWhere('board.record_id = :entityTypeId', { entityTypeId }); + } + } + const closed = [...BoardStageCodes.won, ...BoardStageCodes.lost]; + if (type) { + if (type === BoardStageType.Open) { + qb.andWhere('(stage.code NOT IN (:...codes) OR stage.code IS NULL)', { codes: closed }); + } else if (type === BoardStageType.Won) { + qb.andWhere('stage.code IN (:...codes)', { codes: BoardStageCodes.won }); + } else if (type === BoardStageType.Lost) { + qb.andWhere('stage.code IN (:...codes)', { codes: BoardStageCodes.lost }); + } + } + qb.select(`array_agg(stage.id)::int[]`, 'all'); + qb.addSelect( + `array_agg(stage.id) filter (where stage.code IN (${BoardStageCodes.won.map((c) => `'${c}'`).join(',')}))::int[]`, + 'won', + ); + qb.addSelect( + // eslint-disable-next-line max-len + `array_agg(stage.id) filter (where stage.code IN (${BoardStageCodes.lost.map((c) => `'${c}'`).join(',')}))::int[]`, + 'lost', + ); + qb.addSelect( + // eslint-disable-next-line max-len + `array_agg(stage.id) filter (where stage.code NOT IN (${closed.map((c) => `'${c}'`).join(',')}) OR stage.code IS NULL)::int[]`, + 'open', + ); + + const result = await qb.getRawOne(); + return result; + } + + async update({ + accountId, + boardId, + stageId, + dto, + }: { + accountId: number; + boardId: number; + stageId: number; + dto: UpdateBoardStageDto; + }): Promise { + const stage = await this.findOne({ accountId, boardId, stageId }); + if (!stage) { + throw NotFoundError.withId(BoardStage, stageId); + } + + await this.repository.save(stage.update(dto)); + + this.eventEmitter.emit( + CrmEventType.BoardStageUpdated, + new BoardStageEvent({ accountId, boardId, stageId: stage.id }), + ); + + return stage; + } + + async updateMany({ + accountId, + boardId, + dtos, + }: { + accountId: number; + boardId: number; + dtos: UpdateBoardStageDto[]; + }): Promise { + return Promise.all(dtos.map((dto) => this.update({ accountId, boardId, stageId: dto.id, dto }))); + } + + async processBatch({ + accountId, + boardId, + dtos, + }: { + accountId: number; + boardId: number; + dtos: (CreateBoardStageDto | UpdateBoardStageDto)[]; + }): Promise { + //TODO: This is right way to check for created or updated stages. Use this after Frontend refactor. + // const created = dtos.filter((dto) => !dto['id']).map((dto) => dto as CreateBoardStageDto); + // const updated = dtos.filter((dto) => dto['id']).map((dto) => dto as UpdateBoardStageDto); + const created: CreateBoardStageDto[] = []; + const updated: UpdateBoardStageDto[] = []; + for (const dto of dtos) { + if (!dto['id'] || !(await this.exists({ accountId, boardId, stageId: dto['id'] }))) { + created.push(dto as CreateBoardStageDto); + } else { + updated.push(dto as UpdateBoardStageDto); + } + } + + const result: BoardStage[] = []; + + result.push(...(await this.createMany({ accountId, boardId, dtos: created }))); + result.push(...(await this.updateMany({ accountId, boardId, dtos: updated }))); + + return result; + } + + async delete({ + accountId, + boardId, + stageId, + newStageId, + }: { + accountId: number; + boardId: number; + stageId: number; + newStageId?: number | null; + }) { + const stage = await this.findOne({ accountId, boardId, stageId }); + if (!stage) { + throw NotFoundError.withId(BoardStage, stageId); + } + + return await this.deleteStage({ stage, newStageId }); + } + + async deleteMany(filter: FindFilter) { + const stages = await this.findMany(filter); + for (const stage of stages) { + await this.deleteStage({ stage }); + } + } + + private async deleteStage({ stage, newStageId }: { stage: BoardStage; newStageId?: number | null }) { + if (newStageId) { + await Promise.all([ + this.entityService.changeStageForAll({ + accountId: stage.accountId, + boardId: stage.boardId, + stageId: stage.id, + newStageId, + }), + this.taskService.changeStageForAll({ + accountId: stage.accountId, + boardId: stage.boardId, + stageId: stage.id, + newStageId: newStageId, + }), + ]); + } + + await this.repository.delete(stage.id); + + this.eventEmitter.emit( + CrmEventType.BoardStageDeleted, + new BoardStageDeletedEvent({ accountId: stage.accountId, boardId: stage.boardId, stageId: stage.id, newStageId }), + ); + + return stage.id; + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('stage') + .where('stage.account_id = :accountId', { accountId: filter.accountId }) + .orderBy('stage.sort_order', 'ASC'); + + if (filter.stageId) { + qb.andWhere('stage.id = :stageId', { stageId: filter.stageId }); + } + if (filter.boardId) { + if (Array.isArray(filter.boardId)) { + qb.andWhere('stage.board_id IN (:...boardIds)', { boardIds: filter.boardId }); + } else { + qb.andWhere('stage.board_id = :boardId', { boardId: filter.boardId }); + } + } + if (filter.includeCodes) { + qb.andWhere('stage.code IN (:...includeCodes)', { includeCodes: filter.includeCodes }); + } + if (filter.excludeCodes) { + qb.andWhere( + new Brackets((qb) => { + qb.where('stage.code NOT IN (:...excludeCodes)', { excludeCodes: filter.excludeCodes }).orWhere( + 'code IS NULL', + ); + }), + ); + } + if (filter.afterStageId) { + qb.innerJoin( + (subQuery) => { + return subQuery + .select('s.id', 'id') + .addSelect('s.sort_order', 'sort_order') + .from('stage', 's') + .where('s.id = :afterStageId', { afterStageId: filter.afterStageId }); + }, + 'after_stage', + 'stage.sort_order > after_stage.sort_order', + ); + } + + return qb; + } +} diff --git a/backend/src/CRM/board-stage/dto/board-stage.dto.ts b/backend/src/CRM/board-stage/dto/board-stage.dto.ts new file mode 100644 index 0000000..acded72 --- /dev/null +++ b/backend/src/CRM/board-stage/dto/board-stage.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { BoardStageCode } from '../enums'; + +export class BoardStageDto { + @ApiProperty({ description: 'Stage ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Board ID' }) + @IsNumber() + boardId: number; + + @ApiProperty({ description: 'Stage name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Stage color' }) + @IsString() + color: string; + + @ApiProperty({ enum: BoardStageCode, nullable: true, description: 'Stage code' }) + @IsOptional() + @IsEnum(BoardStageCode) + code?: BoardStageCode | null; + + @ApiProperty({ description: 'Is system stage' }) + @IsBoolean() + isSystem: boolean; + + @ApiProperty({ description: 'Sort order' }) + @IsNumber() + sortOrder: number; +} diff --git a/backend/src/CRM/board-stage/dto/create-board-stage.dto.ts b/backend/src/CRM/board-stage/dto/create-board-stage.dto.ts new file mode 100644 index 0000000..b277944 --- /dev/null +++ b/backend/src/CRM/board-stage/dto/create-board-stage.dto.ts @@ -0,0 +1,21 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional } from 'class-validator'; + +import { BoardStageDto } from './board-stage.dto'; + +export class CreateBoardStageDto extends PickType(BoardStageDto, ['name', 'color', 'code'] as const) { + @ApiPropertyOptional({ description: 'Stage ID' }) + @IsOptional() + @IsNumber() + id?: number; + + @ApiPropertyOptional({ description: 'Sort order' }) + @IsOptional() + @IsNumber() + sortOrder?: number; + + @ApiPropertyOptional({ description: 'Is system' }) + @IsOptional() + @IsBoolean() + isSystem?: boolean; +} diff --git a/backend/src/CRM/board-stage/dto/index.ts b/backend/src/CRM/board-stage/dto/index.ts new file mode 100644 index 0000000..7299e03 --- /dev/null +++ b/backend/src/CRM/board-stage/dto/index.ts @@ -0,0 +1,5 @@ +export * from './board-stage.dto'; +export * from './create-board-stage.dto'; +export * from './process-board-stage.dto'; +export * from './update-board-stage.dto'; +export * from './update-board-stages.dto'; diff --git a/backend/src/CRM/board-stage/dto/process-board-stage.dto.ts b/backend/src/CRM/board-stage/dto/process-board-stage.dto.ts new file mode 100644 index 0000000..1732597 --- /dev/null +++ b/backend/src/CRM/board-stage/dto/process-board-stage.dto.ts @@ -0,0 +1,18 @@ +import { ApiExtraModels, ApiProperty, getSchemaPath } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { CreateBoardStageDto } from './create-board-stage.dto'; +import { UpdateBoardStageDto } from './update-board-stage.dto'; + +@ApiExtraModels(CreateBoardStageDto) +@ApiExtraModels(UpdateBoardStageDto) +export class ProcessBoardStageDto { + @ApiProperty({ + description: 'Array of stages', + type: 'array', + items: { oneOf: [{ $ref: getSchemaPath(CreateBoardStageDto) }, { $ref: getSchemaPath(UpdateBoardStageDto) }] }, + }) + @IsOptional() + @IsArray() + stages: (CreateBoardStageDto | UpdateBoardStageDto)[]; +} diff --git a/backend/src/CRM/board-stage/dto/update-board-stage.dto.ts b/backend/src/CRM/board-stage/dto/update-board-stage.dto.ts new file mode 100644 index 0000000..ec533e4 --- /dev/null +++ b/backend/src/CRM/board-stage/dto/update-board-stage.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty, PartialType, PickType } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { BoardStageDto } from './board-stage.dto'; + +export class UpdateBoardStageDto extends PartialType( + PickType(BoardStageDto, ['name', 'color', 'code', 'isSystem', 'sortOrder'] as const), +) { + @ApiProperty({ description: 'Stage ID' }) + @IsNumber() + id: number; +} diff --git a/backend/src/CRM/board-stage/dto/update-board-stages.dto.ts b/backend/src/CRM/board-stage/dto/update-board-stages.dto.ts new file mode 100644 index 0000000..3f5a541 --- /dev/null +++ b/backend/src/CRM/board-stage/dto/update-board-stages.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { UpdateBoardStageDto } from './update-board-stage.dto'; + +export class UpdateBoardStagesDto { + @ApiProperty({ type: [UpdateBoardStageDto], description: 'Stage dtos' }) + @IsArray() + stages: UpdateBoardStageDto[]; +} diff --git a/backend/src/CRM/board-stage/entities/board-stage.entity.ts b/backend/src/CRM/board-stage/entities/board-stage.entity.ts new file mode 100644 index 0000000..b475abe --- /dev/null +++ b/backend/src/CRM/board-stage/entities/board-stage.entity.ts @@ -0,0 +1,91 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { BoardStageCode } from '../enums'; +import { CreateBoardStageDto, BoardStageDto, UpdateBoardStageDto } from '../dto'; + +@Entity() +export class BoardStage { + @PrimaryColumn() + id: number; + + @Column() + accountId: number; + + @Column() + boardId: number; + + @Column() + createdAt: Date; + + @Column() + name: string; + + @Column() + color: string; + + @Column() + code: BoardStageCode | null; + + @Column() + isSystem: boolean; + + @Column() + sortOrder: number; + + constructor( + accountId: number, + id: number, + boardId: number, + name: string, + color: string, + code: BoardStageCode | null, + isSystem: boolean, + sortOrder: number, + ) { + this.accountId = accountId; + this.id = id; + this.boardId = boardId; + this.name = name; + this.color = color; + this.code = code; + this.isSystem = isSystem; + this.sortOrder = sortOrder; + this.createdAt = DateUtil.now(); + } + + public static fromDto(accountId: number, boardId: number, dto: CreateBoardStageDto): BoardStage { + return new BoardStage( + accountId, + dto.id, + boardId, + dto.name, + dto.color, + dto.code, + dto.isSystem ?? false, + dto.sortOrder ?? 0, + ); + } + + public update(dto: Omit): BoardStage { + this.name = dto.name !== undefined ? dto.name : this.name; + this.color = dto.color !== undefined ? dto.color : this.color; + this.code = dto.code !== undefined ? dto.code : this.code; + this.isSystem = dto.isSystem !== undefined ? dto.isSystem : this.isSystem; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + + return this; + } + + public toDto(): BoardStageDto { + return { + id: this.id, + boardId: this.boardId, + name: this.name, + color: this.color, + code: this.code, + isSystem: this.isSystem, + sortOrder: this.sortOrder, + }; + } +} diff --git a/backend/src/CRM/board-stage/entities/index.ts b/backend/src/CRM/board-stage/entities/index.ts new file mode 100644 index 0000000..50a5a94 --- /dev/null +++ b/backend/src/CRM/board-stage/entities/index.ts @@ -0,0 +1 @@ +export * from './board-stage.entity'; diff --git a/backend/src/CRM/board-stage/enums/board-stage-code.enum.ts b/backend/src/CRM/board-stage/enums/board-stage-code.enum.ts new file mode 100644 index 0000000..846a336 --- /dev/null +++ b/backend/src/CRM/board-stage/enums/board-stage-code.enum.ts @@ -0,0 +1,24 @@ +import { BoardStageType } from './board-stage-type.enum'; + +export enum BoardStageCode { + Win = 'win', + Lost = 'lost', + Hired = 'hired', + Rejected = 'rejected', + Fired = 'fired', + Done = 'done', + NotDone = 'not_done', + Completed = 'completed', + NotSatisfied = 'not_satisfied', +} + +export const BoardStageCodes = { + [BoardStageType.Won]: [BoardStageCode.Win, BoardStageCode.Hired, BoardStageCode.Done, BoardStageCode.Completed], + [BoardStageType.Lost]: [ + BoardStageCode.Lost, + BoardStageCode.Rejected, + BoardStageCode.Fired, + BoardStageCode.NotDone, + BoardStageCode.NotSatisfied, + ], +}; diff --git a/backend/src/CRM/board-stage/enums/board-stage-type.enum.ts b/backend/src/CRM/board-stage/enums/board-stage-type.enum.ts new file mode 100644 index 0000000..038b23a --- /dev/null +++ b/backend/src/CRM/board-stage/enums/board-stage-type.enum.ts @@ -0,0 +1,6 @@ +export enum BoardStageType { + All = 'all', + Open = 'open', + Won = 'won', + Lost = 'lost', +} diff --git a/backend/src/CRM/board-stage/enums/index.ts b/backend/src/CRM/board-stage/enums/index.ts new file mode 100644 index 0000000..9daa0c4 --- /dev/null +++ b/backend/src/CRM/board-stage/enums/index.ts @@ -0,0 +1,2 @@ +export * from './board-stage-code.enum'; +export * from './board-stage-type.enum'; diff --git a/backend/src/CRM/board-stage/index.ts b/backend/src/CRM/board-stage/index.ts new file mode 100644 index 0000000..f175d19 --- /dev/null +++ b/backend/src/CRM/board-stage/index.ts @@ -0,0 +1,6 @@ +export * from './board-stage.controller'; +export * from './board-stage.service'; +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './types'; diff --git a/backend/src/CRM/board-stage/types/grouped-stages.ts b/backend/src/CRM/board-stage/types/grouped-stages.ts new file mode 100644 index 0000000..bb4c8f4 --- /dev/null +++ b/backend/src/CRM/board-stage/types/grouped-stages.ts @@ -0,0 +1,3 @@ +import { BoardStageType } from '../enums'; + +export type GroupedStages = Record; diff --git a/backend/src/CRM/board-stage/types/index.ts b/backend/src/CRM/board-stage/types/index.ts new file mode 100644 index 0000000..97c518d --- /dev/null +++ b/backend/src/CRM/board-stage/types/index.ts @@ -0,0 +1 @@ +export * from './grouped-stages'; diff --git a/backend/src/CRM/board/board.controller.ts b/backend/src/CRM/board/board.controller.ts new file mode 100644 index 0000000..68f99f4 --- /dev/null +++ b/backend/src/CRM/board/board.controller.ts @@ -0,0 +1,60 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { BoardDto, BoardFilterDto, CreateBoardDto, UpdateBoardDto } from './dto'; +import { BoardService } from './board.service'; + +@ApiTags('crm/boards') +@Controller('crm/boards') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class BoardController { + constructor(private readonly service: BoardService) {} + + @ApiOperation({ summary: 'Create board', description: 'Create board' }) + @ApiBody({ type: CreateBoardDto, required: true, description: 'Data for creating board' }) + @ApiCreatedResponse({ type: BoardDto, description: 'Created board' }) + @Post() + public async create(@CurrentAuth() { accountId, user }: AuthData, @Body() dto: CreateBoardDto) { + return await this.service.create({ accountId, user, dto }); + } + + @ApiOperation({ summary: 'Get boards', description: 'Get boards' }) + @ApiOkResponse({ description: 'Boards', type: [BoardDto] }) + @Get() + public async findMany(@CurrentAuth() { accountId, user }: AuthData, @Query() filter: BoardFilterDto) { + return await this.service.findMany({ user, filter: { accountId, ...filter } }); + } + + @ApiOperation({ summary: 'Get board', description: 'Get board' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Board', type: BoardDto }) + @Get(':boardId') + public async findOne(@CurrentAuth() { accountId, user }: AuthData, @Param('boardId', ParseIntPipe) boardId: number) { + return await this.service.findOne({ user, filter: { accountId, boardId } }); + } + + @ApiOperation({ summary: 'Update board', description: 'Update board' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ type: UpdateBoardDto, required: true, description: 'Data for updating board' }) + @ApiOkResponse({ description: 'Updated board', type: BoardDto }) + @Patch(':boardId') + public async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() dto: UpdateBoardDto, + ) { + return await this.service.update({ accountId, user, boardId, dto }); + } + + @ApiOperation({ summary: 'Delete board', description: 'Delete board' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiOkResponse() + @Delete(':boardId') + public async delete(@CurrentAuth() { accountId, userId }: AuthData, @Param('boardId', ParseIntPipe) boardId: number) { + await this.service.delete({ accountId, userId, boardId, preserveLast: true }); + } +} diff --git a/backend/src/CRM/board/board.handler.ts b/backend/src/CRM/board/board.handler.ts new file mode 100644 index 0000000..61ee9c1 --- /dev/null +++ b/backend/src/CRM/board/board.handler.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; + +import { CrmEventType, EntityTypeEvent } from '../common'; +import { BoardService } from './board.service'; + +@Injectable() +export class BoardHandler { + constructor(private readonly service: BoardService) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + await this.service.changeUser({ accountId: event.accountId, userId: event.userId, newUserId: event.newUserId }); + } + + @OnEvent(CrmEventType.EntityTypeDeleted, { async: true }) + public async onEntityTypeDeleted(event: EntityTypeEvent): Promise { + this.service.deleteMany({ + userId: event.userId, + filter: { accountId: event.accountId, recordId: event.entityTypeId }, + }); + } +} diff --git a/backend/src/CRM/board/board.service.ts b/backend/src/CRM/board/board.service.ts new file mode 100644 index 0000000..4c39516 --- /dev/null +++ b/backend/src/CRM/board/board.service.ts @@ -0,0 +1,428 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { BadRequestError, isUnique, NotFoundError } from '@/common'; + +import { UserRights } from '@/modules/iam/common'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { BoardEvent, CrmEventType, EntityCategory } from '../common'; +import { BoardStageCode, BoardStageService } from '../board-stage'; +import { EntityTypeService } from '../entity-type/entity-type.service'; + +import { CreateBoardDto, UpdateBoardDto } from './dto'; +import { Board } from './entities'; +import { BoardType } from './enums'; + +interface FindFilter { + accountId: number; + boardId?: number; + isSystem?: boolean; + type?: BoardType; + recordId?: number; +} + +@Injectable() +export class BoardService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Board) + private readonly repository: Repository, + @Inject(forwardRef(() => EntityTypeService)) + private readonly entityTypeService: EntityTypeService, + @Inject(forwardRef(() => BoardStageService)) + private readonly stageService: BoardStageService, + ) {} + + //TODO: split user and owner!!! + public async create({ + accountId, + user, + dto, + options: { ownerId = null, isSystem = false, taskBoardId = null, createDefaultStages = true } = {}, + }: { + accountId: number; + user: User | null; + dto: CreateBoardDto; + options?: { + ownerId?: number | null; + isSystem?: boolean; + taskBoardId?: number | null; + createDefaultStages?: boolean; + }; + }): Promise { + let ownerIdCurrent = ownerId; + if (!ownerIdCurrent) { + ownerIdCurrent = isSystem || dto.type === BoardType.EntityType ? null : user?.id; + } + let taskBoardIdCurrent = taskBoardId; + if (!taskBoardIdCurrent && dto.type === BoardType.EntityType && dto.recordId) { + const taskBoard = await this.createLinkedTaskBoard({ + accountId, + user, + name: dto.name, + entityTypeId: dto.recordId, + }); + taskBoardIdCurrent = taskBoard?.id; + } + if (dto.sortOrder === undefined) { + const maxSortOrder = await this.createFindQb({ accountId, type: dto.type, recordId: dto.recordId }) + .select('MAX(board.sort_order)', 'max') + .orderBy() + .getRawOne<{ max: number }>(); + + dto.sortOrder = (maxSortOrder?.max ?? 0) + 1; + } + const board = await this.repository.save( + Board.fromDto({ accountId, dto, isSystem, ownerId: ownerIdCurrent, taskBoardId: taskBoardIdCurrent }), + ); + if (createDefaultStages) { + await this.createDefaultStages({ accountId, board }); + } + + if (user) { + board.userRights = this.getUserRights({ user, board }); + } + + this.eventEmitter.emit( + CrmEventType.BoardCreated, + new BoardEvent({ accountId, userId: ownerIdCurrent, boardId: board.id }), + ); + return board; + } + + public async findOne({ user = null, filter }: { user?: User | null; filter: FindFilter }): Promise { + const board = await this.createFindQb(filter).getOne(); + + if (board && user) { + board.userRights = this.getUserRights({ user, board }); + } + + return board; + } + public async findMany({ user = null, filter }: { user?: User | null; filter: FindFilter }): Promise { + const boards = await this.createFindQb(filter).getMany(); + + if (user) { + for (const board of boards) { + board.userRights = this.getUserRights({ user, board }); + } + + return boards.filter((board) => board.userRights.canView); + } + + return boards; + } + + public async findOneId(filter: FindFilter): Promise { + const result = await this.createFindQb(filter).select('board.id', 'id').getRawOne(); + + return result?.id ?? null; + } + public async findManyIds(filter: FindFilter): Promise { + const result = await this.createFindQb(filter).select('board.id', 'id').getRawMany(); + + return result.map((r) => r.id); + } + + public async count(filter: FindFilter): Promise { + return await this.createFindQb(filter).getCount(); + } + + public async getAllowedTaskBoardIds({ accountId, userId }: { accountId: number; userId: number }): Promise { + const ids = await this.repository + .createQueryBuilder('b') + .select('b.id', 'id') + .where('b.account_id = :accountId', { accountId }) + .andWhere('b.type = :boardType', { boardType: BoardType.Task }) + .andWhere( + new Brackets((qb) => + qb + .where('b.owner_id = :ownerId', { ownerId: userId }) + .orWhere(`b.participant_ids @> '[${userId}]'`) + .orWhere('b.is_system = true'), + ), + ) + .getRawMany(); + return ids.map((i) => i.id); + } + + public async update({ + accountId, + user, + boardId, + dto, + }: { + accountId: number; + user: User; + boardId: number; + dto: UpdateBoardDto; + }): Promise { + const board = await this.findOne({ user, filter: { accountId, boardId } }); + if (!board) { + throw NotFoundError.withId(Board, boardId); + } + + await this.repository.save(board.update(dto)); + + if (dto.name && board.type === BoardType.EntityType && board.taskBoardId !== null) { + this.update({ accountId, user, boardId: board.taskBoardId, dto: { name: dto.name } }); + } + + board.userRights = this.getUserRights({ user, board }); + + this.eventEmitter.emit( + CrmEventType.BoardUpdated, + new BoardEvent({ accountId, userId: user.id, boardId: board.id }), + ); + + return board; + } + + public async changeUser({ + accountId, + userId, + newUserId, + }: { + accountId: number; + userId: number; + newUserId?: number | null; + }) { + if (newUserId) { + await this.repository.update({ accountId, ownerId: userId }, { ownerId: newUserId }); + } else { + await this.repository.delete({ accountId, ownerId: userId }); + } + + const boards = await this.repository + .createQueryBuilder('board') + .where(`board.account_id = :accountId`, { accountId }) + .andWhere(`board.participant_ids @> :userId`, { userId }) + .getMany(); + for (const board of boards) { + board.participantIds = newUserId + ? board.participantIds.map((id) => (id === userId ? newUserId : id)).filter(isUnique) + : board.participantIds.filter((id) => id !== userId); + await this.repository.save(board); + } + } + + public async delete({ + accountId, + userId, + boardId, + preserveLast, + }: { + accountId: number; + userId: number; + boardId: number; + preserveLast?: boolean; + }) { + const board = await this.findOne({ filter: { accountId, boardId } }); + if (board) { + if (board.isSystem) { + throw new BadRequestError('Cannot delete system board'); + } + if (board.type === BoardType.EntityType) { + if (preserveLast && board.recordId) { + const count = await this.count({ accountId, type: BoardType.EntityType, recordId: board.recordId }); + if (count <= 1) { + throw new BadRequestError('Cannot delete last entity board'); + } + } + if (board.taskBoardId) { + await this.delete({ accountId, userId, boardId: board.taskBoardId }); + } + } + await this.stageService.deleteMany({ accountId: accountId, boardId: board.id }); + await this.repository.delete({ id: board.id }); + this.eventEmitter.emit( + CrmEventType.BoardDeleted, + new BoardEvent({ accountId: accountId, userId, boardId: board.id }), + ); + } + } + + public async deleteMany({ userId, filter }: { userId: number; filter: FindFilter }) { + const boardIds = await this.findManyIds(filter); + await Promise.all(boardIds.map((boardId) => this.delete({ accountId: filter.accountId, userId, boardId }))); + } + + private getUserRights({ user, board }: { user: User | null; board: Board }): UserRights { + if (user === null) { + return UserRights.none(); + } else if (board.type === BoardType.EntityType) { + return UserRights.full(); + } else if (board.type === BoardType.Task) { + const canView = + board.isSystem || + board.ownerId === user.id || + (board.participantIds !== null && + (board.participantIds.length === 0 || board.participantIds.includes(user.id))); + const canEdit = board.ownerId === user.id || user.isAdmin; + const canDelete = !board.isSystem && (board.ownerId === user.id || user.isAdmin); + return { canView, canEdit, canDelete }; + } + + return UserRights.none(); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('board') + .where('board.account_id = :accountId', { accountId: filter.accountId }) + .orderBy('board.sort_order', 'ASC'); + + if (filter?.boardId) { + qb.andWhere('board.id = :boardId', { boardId: filter.boardId }); + } + if (filter?.type) { + qb.andWhere('board.type = :type', { type: filter.type }); + } + if (filter?.recordId) { + qb.andWhere('board.record_id = :recordId', { recordId: filter.recordId }); + } + + if (filter?.isSystem !== undefined) { + qb.andWhere('board.is_system = :isSystem', { isSystem: filter.isSystem }); + } + + return qb; + } + + private async createLinkedTaskBoard({ + accountId, + user, + name, + entityTypeId, + }: { + accountId: number; + user: User; + name: string; + entityTypeId: number; + }): Promise { + const entityType = await this.entityTypeService.getById(accountId, entityTypeId); + + return entityType.entityCategory === EntityCategory.PROJECT + ? this.create({ accountId, user, dto: { name, type: BoardType.Task, recordId: null, sortOrder: 1 } }) + : null; + } + + private async createDefaultStages({ accountId, board }: { accountId: number; board: Board }) { + if (board.type === BoardType.Task) { + return this.createTasksStages({ accountId, boardId: board.id }); + } else if (board.type === BoardType.EntityType && board.recordId) { + const entityType = await this.entityTypeService.getById(accountId, board.recordId); + + switch (entityType.entityCategory) { + case EntityCategory.DEAL: + case EntityCategory.CUSTOMER: + return this.createDealsStages({ accountId, boardId: board.id }); + case EntityCategory.PROJECT: + return this.createProjectsStages({ accountId, boardId: board.id }); + case EntityCategory.SUPPLIER: + return this.createSuppliersStages({ accountId, boardId: board.id }); + case EntityCategory.CONTRACTOR: + return this.createContractorsStages({ accountId, boardId: board.id }); + case EntityCategory.HR: + return this.createApplicantsStages({ accountId, boardId: board.id }); + default: + return this.createEntityTypeStages({ accountId, boardId: board.id }); + } + } else { + return null; + } + } + private async createTasksStages({ accountId, boardId }: { accountId: number; boardId: number }) { + return this.stageService.createMany({ + accountId, + boardId, + dtos: [ + { name: 'To Do', color: '#555', sortOrder: 0, code: null, isSystem: false }, + { name: 'In Progress', color: '#555', sortOrder: 1, code: null, isSystem: false }, + { name: 'Done', color: '#555', sortOrder: 2, code: BoardStageCode.Done, isSystem: true }, + ], + }); + } + private async createDealsStages({ accountId, boardId }: { accountId: number; boardId: number }) { + return this.stageService.createMany({ + accountId, + boardId, + dtos: [ + { name: 'Qualified Lead', color: 'rgb(168, 227, 121)', sortOrder: 0, isSystem: false, code: null }, + { name: 'Contact Made', color: 'rgb(255, 227, 133)', sortOrder: 1, isSystem: false, code: null }, + { name: 'Proposal Made', color: 'rgb(255, 158, 158)', sortOrder: 2, isSystem: false, code: null }, + { name: 'Objections Identified', color: 'rgb(202, 183, 239)', sortOrder: 3, isSystem: false, code: null }, + { name: 'Invoice Sent', color: 'rgb(152, 215, 255)', sortOrder: 4, isSystem: false, code: null }, + { name: 'Closed Won', color: '#50b810', sortOrder: 5, isSystem: true, code: BoardStageCode.Win }, + { name: 'Closed Lost', color: '#ff5a71', sortOrder: 6, isSystem: true, code: BoardStageCode.Lost }, + ], + }); + } + private async createProjectsStages({ accountId, boardId }: { accountId: number; boardId: number }) { + return this.stageService.createMany({ + accountId, + boardId, + dtos: [ + { name: 'To Do', color: 'rgb(255, 227, 133)', sortOrder: 0, isSystem: false, code: null }, + { name: 'In progress', color: 'rgb(202, 183, 239)', sortOrder: 1, isSystem: false, code: null }, + { name: 'Completed', color: '#50b810', sortOrder: 2, isSystem: true, code: BoardStageCode.Completed }, + ], + }); + } + private async createSuppliersStages({ accountId, boardId }: { accountId: number; boardId: number }) { + return this.stageService.createMany({ + accountId, + boardId, + dtos: [ + { name: 'To Do', color: 'rgb(255, 227, 133)', sortOrder: 0, isSystem: false, code: null }, + { name: 'Request Supplier', color: 'rgb(202, 183, 239)', sortOrder: 1, isSystem: false, code: null }, + { name: 'Negotiate', color: 'rgb(168, 227, 121)', sortOrder: 2, isSystem: false, code: null }, + { name: 'Paid', color: 'rgb(152, 215, 255)', sortOrder: 3, isSystem: false, code: null }, + { name: 'Completed', color: '#50b810', sortOrder: 4, isSystem: true, code: BoardStageCode.Completed }, + { name: 'Not satisfied', color: '#ff5a71', sortOrder: 5, isSystem: true, code: BoardStageCode.NotSatisfied }, + ], + }); + } + private async createContractorsStages({ accountId, boardId }: { accountId: number; boardId: number }) { + return this.stageService.createMany({ + accountId, + boardId, + dtos: [ + { name: 'To Do', color: 'rgb(255, 227, 133)', sortOrder: 0, isSystem: false, code: null }, + { name: 'Request Contractor', color: 'rgb(202, 183, 239)', sortOrder: 1, isSystem: false, code: null }, + { name: 'Negotiate', color: 'rgb(168, 227, 121)', sortOrder: 2, isSystem: false, code: null }, + { name: 'Paid', color: 'rgb(152, 215, 255)', sortOrder: 3, isSystem: false, code: null }, + { name: 'Completed', color: '#50b810', sortOrder: 4, isSystem: true, code: BoardStageCode.Completed }, + { name: 'Not satisfied', color: '#ff5a71', sortOrder: 5, isSystem: true, code: BoardStageCode.NotSatisfied }, + ], + }); + } + private async createApplicantsStages({ accountId, boardId }: { accountId: number; boardId: number }) { + return this.stageService.createMany({ + accountId, + boardId, + dtos: [ + { name: 'Interview', color: 'rgb(255, 227, 133)', sortOrder: 0, isSystem: false, code: null }, + { name: 'Approved', color: 'rgb(202, 183, 239)', sortOrder: 1, isSystem: false, code: null }, + { name: 'Hired', color: '#50b810', sortOrder: 2, isSystem: true, code: BoardStageCode.Hired }, + { name: 'Rejected', color: '#ff5a71', sortOrder: 3, isSystem: true, code: BoardStageCode.Rejected }, + ], + }); + } + public async createEntityTypeStages({ accountId, boardId }: { accountId: number; boardId: number }) { + return this.stageService.createMany({ + accountId, + boardId, + dtos: [ + { name: 'Name your status', color: '#A8E379', sortOrder: 0, isSystem: false, code: null }, + { name: 'Name your status', color: '#FFE385', sortOrder: 1, isSystem: false, code: null }, + { name: 'Name your status', color: '#CAB7EF', sortOrder: 2, isSystem: false, code: null }, + { name: 'Done', color: '#50b810', sortOrder: 3, isSystem: true, code: BoardStageCode.Done }, + { name: 'Not done', color: '#ff5a71', sortOrder: 3, isSystem: true, code: BoardStageCode.NotDone }, + ], + }); + } +} diff --git a/backend/src/CRM/board/dto/board-filter.dto.ts b/backend/src/CRM/board/dto/board-filter.dto.ts new file mode 100644 index 0000000..18d8891 --- /dev/null +++ b/backend/src/CRM/board/dto/board-filter.dto.ts @@ -0,0 +1,16 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { BoardType } from '../enums'; + +export class BoardFilterDto { + @ApiPropertyOptional({ enum: BoardType, description: 'Board type' }) + @IsOptional() + @IsEnum(BoardType) + type?: BoardType; + + @ApiPropertyOptional({ description: 'Object ID associated with board' }) + @IsOptional() + @IsNumber() + recordId?: number; +} diff --git a/backend/src/CRM/board/dto/board.dto.ts b/backend/src/CRM/board/dto/board.dto.ts new file mode 100644 index 0000000..8db3e7c --- /dev/null +++ b/backend/src/CRM/board/dto/board.dto.ts @@ -0,0 +1,52 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common'; + +import { BoardType } from '../enums'; + +export class BoardDto { + @ApiProperty({ description: 'Board ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Board name' }) + @IsString() + name: string; + + @ApiProperty({ enum: BoardType, description: 'Board type' }) + @IsEnum(BoardType) + type: BoardType; + + @ApiProperty({ nullable: true, description: 'Object ID associated with board' }) + @IsOptional() + @IsNumber() + recordId: number | null; + + @ApiProperty({ description: 'Indicates if the board is a system board' }) + @IsBoolean() + isSystem: boolean; + + @ApiProperty({ nullable: true, description: 'Owner ID of the board' }) + @IsOptional() + @IsNumber() + ownerId: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'Array of participant IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + participantIds?: number[] | null; + + @ApiProperty({ description: 'Sort order of the board' }) + @IsNumber() + sortOrder: number; + + @ApiPropertyOptional({ nullable: true, description: 'Task board ID associated with this board' }) + @IsOptional() + @IsNumber() + taskBoardId: number | null; + + @ApiProperty({ type: () => UserRights, nullable: true, description: 'User rights for the board' }) + @IsOptional() + userRights: UserRights | null; +} diff --git a/backend/src/CRM/board/dto/create-board.dto.ts b/backend/src/CRM/board/dto/create-board.dto.ts new file mode 100644 index 0000000..84b8c2b --- /dev/null +++ b/backend/src/CRM/board/dto/create-board.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { BoardDto } from './board.dto'; + +export class CreateBoardDto extends PickType(BoardDto, ['name', 'type', 'recordId', 'participantIds'] as const) { + @ApiPropertyOptional({ description: 'Sort order' }) + @IsOptional() + @IsNumber() + sortOrder?: number; +} diff --git a/backend/src/CRM/board/dto/index.ts b/backend/src/CRM/board/dto/index.ts new file mode 100644 index 0000000..7008ced --- /dev/null +++ b/backend/src/CRM/board/dto/index.ts @@ -0,0 +1,4 @@ +export * from './board-filter.dto'; +export * from './board.dto'; +export * from './create-board.dto'; +export * from './update-board.dto'; diff --git a/backend/src/CRM/board/dto/update-board.dto.ts b/backend/src/CRM/board/dto/update-board.dto.ts new file mode 100644 index 0000000..90249df --- /dev/null +++ b/backend/src/CRM/board/dto/update-board.dto.ts @@ -0,0 +1,5 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { BoardDto } from './board.dto'; + +export class UpdateBoardDto extends PartialType(PickType(BoardDto, ['name', 'sortOrder', 'participantIds'] as const)) {} diff --git a/backend/src/CRM/board/entities/board.entity.ts b/backend/src/CRM/board/entities/board.entity.ts new file mode 100644 index 0000000..247db9c --- /dev/null +++ b/backend/src/CRM/board/entities/board.entity.ts @@ -0,0 +1,123 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { UserRights } from '@/modules/iam/common'; + +import { BoardDto, CreateBoardDto, UpdateBoardDto } from '../dto'; +import { BoardType } from '../enums'; + +@Entity() +export class Board { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column() + type: BoardType; + + @Column({ nullable: true }) + recordId: number | null; + + @Column() + isSystem: boolean; + + @Column() + ownerId: number | null; + + @Column({ type: 'jsonb' }) + participantIds: number[] | null; + + @Column() + sortOrder: number; + + @Column() + taskBoardId: number | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + name: string, + type: BoardType, + recordId: number | null, + isSystem: boolean, + ownerId: number | null, + participantIds: number[] | null, + sortOrder: number, + taskBoardId: number | null, + ) { + this.accountId = accountId; + this.name = name; + this.type = type; + this.recordId = recordId; + this.isSystem = isSystem; + this.ownerId = ownerId; + this.participantIds = participantIds; + this.sortOrder = sortOrder; + this.taskBoardId = taskBoardId; + this.createdAt = DateUtil.now(); + } + + private _userRights: UserRights | null; + public get userRights(): UserRights { + return this._userRights; + } + public set userRights(value: UserRights | null) { + this._userRights = value; + } + + public static fromDto({ + accountId, + isSystem = false, + ownerId = null, + taskBoardId = null, + dto, + }: { + accountId: number; + isSystem?: boolean; + ownerId?: number | null; + taskBoardId?: number | null; + dto: CreateBoardDto; + }): Board { + return new Board( + accountId, + dto.name, + dto.type, + dto.recordId, + isSystem, + ownerId, + dto.participantIds, + dto.sortOrder, + taskBoardId, + ); + } + + public update(dto: UpdateBoardDto): Board { + this.name = dto.name !== undefined ? dto.name : this.name; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + this.participantIds = dto.participantIds !== undefined ? dto.participantIds : this.participantIds; + + return this; + } + + public toDto(): BoardDto { + return { + id: this.id, + name: this.name, + type: this.type, + recordId: this.recordId, + isSystem: this.isSystem, + ownerId: this.ownerId, + participantIds: this.participantIds, + sortOrder: this.sortOrder, + taskBoardId: this.taskBoardId, + userRights: this.userRights, + }; + } +} diff --git a/backend/src/CRM/board/entities/index.ts b/backend/src/CRM/board/entities/index.ts new file mode 100644 index 0000000..73f4c10 --- /dev/null +++ b/backend/src/CRM/board/entities/index.ts @@ -0,0 +1 @@ +export * from './board.entity'; diff --git a/backend/src/CRM/board/enums/board-type.enum.ts b/backend/src/CRM/board/enums/board-type.enum.ts new file mode 100644 index 0000000..d147054 --- /dev/null +++ b/backend/src/CRM/board/enums/board-type.enum.ts @@ -0,0 +1,4 @@ +export enum BoardType { + EntityType = 'entity_type', + Task = 'task', +} diff --git a/backend/src/CRM/board/enums/index.ts b/backend/src/CRM/board/enums/index.ts new file mode 100644 index 0000000..4adfb14 --- /dev/null +++ b/backend/src/CRM/board/enums/index.ts @@ -0,0 +1 @@ +export * from './board-type.enum'; diff --git a/backend/src/CRM/board/index.ts b/backend/src/CRM/board/index.ts new file mode 100644 index 0000000..f0e6538 --- /dev/null +++ b/backend/src/CRM/board/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; diff --git a/backend/src/CRM/common/enums/entity-category.enum.ts b/backend/src/CRM/common/enums/entity-category.enum.ts new file mode 100644 index 0000000..242226a --- /dev/null +++ b/backend/src/CRM/common/enums/entity-category.enum.ts @@ -0,0 +1,15 @@ +export enum EntityCategory { + UNIVERSAL = 'universal', + PARTNER = 'partner', + CUSTOMER = 'customer', + LOGISTICS = 'logistics', + PRODUCT = 'product', + REAL_ESTATE_PROPERTY = 'real_estate_property', + DEAL = 'deal', + CONTACT = 'contact', + COMPANY = 'company', + SUPPLIER = 'supplier', + CONTRACTOR = 'contractor', + HR = 'hr', + PROJECT = 'project', +} diff --git a/backend/src/CRM/common/enums/index.ts b/backend/src/CRM/common/enums/index.ts new file mode 100644 index 0000000..d818964 --- /dev/null +++ b/backend/src/CRM/common/enums/index.ts @@ -0,0 +1,4 @@ +export * from './entity-category.enum'; +export * from './permission-object-type.enum'; +export * from './sequence-name.enum'; +export * from './sort-order.enum'; diff --git a/backend/src/CRM/common/enums/permission-object-type.enum.ts b/backend/src/CRM/common/enums/permission-object-type.enum.ts new file mode 100644 index 0000000..50a0f39 --- /dev/null +++ b/backend/src/CRM/common/enums/permission-object-type.enum.ts @@ -0,0 +1,5 @@ +export enum PermissionObjectType { + EntityType = 'entity_type', + Task = 'task', + Activity = 'activity', +} diff --git a/backend/src/CRM/common/enums/sequence-name.enum.ts b/backend/src/CRM/common/enums/sequence-name.enum.ts new file mode 100644 index 0000000..81b8e38 --- /dev/null +++ b/backend/src/CRM/common/enums/sequence-name.enum.ts @@ -0,0 +1,9 @@ +/** + * @deprecated + */ +export enum SequenceName { + Field = 'field_id_seq', + FieldGroup = 'field_group_id_seq', + FieldOption = 'field_option_id_seq', + Stage = 'stage_id_seq', +} diff --git a/backend/src/CRM/common/enums/sort-order.enum.ts b/backend/src/CRM/common/enums/sort-order.enum.ts new file mode 100644 index 0000000..5881975 --- /dev/null +++ b/backend/src/CRM/common/enums/sort-order.enum.ts @@ -0,0 +1,4 @@ +export enum SortOrder { + First = 0, + Last = 32767, +} diff --git a/backend/src/CRM/common/events/activity/activity-created.event.ts b/backend/src/CRM/common/events/activity/activity-created.event.ts new file mode 100644 index 0000000..7943fbc --- /dev/null +++ b/backend/src/CRM/common/events/activity/activity-created.event.ts @@ -0,0 +1,28 @@ +import { ActivityEvent } from './activity.event'; + +export class ActivityCreatedEvent extends ActivityEvent { + ownerId: number; + createdBy: number; + activityText: string; + activityTypeName: string; + createdAt: string; + + constructor({ + accountId, + activityId, + ownerId, + entityId, + createdBy, + activityText, + activityTypeName, + createdAt, + }: ActivityCreatedEvent) { + super({ accountId, entityId, activityId }); + + this.ownerId = ownerId; + this.createdBy = createdBy; + this.activityText = activityText; + this.activityTypeName = activityTypeName; + this.createdAt = createdAt; + } +} diff --git a/backend/src/CRM/common/events/activity/activity.event.ts b/backend/src/CRM/common/events/activity/activity.event.ts new file mode 100644 index 0000000..f63c087 --- /dev/null +++ b/backend/src/CRM/common/events/activity/activity.event.ts @@ -0,0 +1,11 @@ +export class ActivityEvent { + accountId: number; + entityId: number; + activityId: number; + + constructor({ accountId, entityId, activityId }: ActivityEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.activityId = activityId; + } +} diff --git a/backend/src/CRM/common/events/activity/index.ts b/backend/src/CRM/common/events/activity/index.ts new file mode 100644 index 0000000..d94999c --- /dev/null +++ b/backend/src/CRM/common/events/activity/index.ts @@ -0,0 +1,2 @@ +export * from './activity-created.event'; +export * from './activity.event'; diff --git a/backend/src/CRM/common/events/board-stage/board-stage-deleted.event.ts b/backend/src/CRM/common/events/board-stage/board-stage-deleted.event.ts new file mode 100644 index 0000000..c8e09f2 --- /dev/null +++ b/backend/src/CRM/common/events/board-stage/board-stage-deleted.event.ts @@ -0,0 +1,11 @@ +import { BoardStageEvent } from './board-stage.event'; + +export class BoardStageDeletedEvent extends BoardStageEvent { + newStageId?: number | null; + + constructor({ accountId, boardId, stageId, newStageId }: BoardStageDeletedEvent) { + super({ accountId, boardId, stageId }); + + this.newStageId = newStageId; + } +} diff --git a/backend/src/CRM/common/events/board-stage/board-stage.event.ts b/backend/src/CRM/common/events/board-stage/board-stage.event.ts new file mode 100644 index 0000000..1923e83 --- /dev/null +++ b/backend/src/CRM/common/events/board-stage/board-stage.event.ts @@ -0,0 +1,11 @@ +export class BoardStageEvent { + accountId: number; + boardId: number; + stageId: number; + + constructor({ accountId, boardId, stageId }: BoardStageEvent) { + this.accountId = accountId; + this.boardId = boardId; + this.stageId = stageId; + } +} diff --git a/backend/src/CRM/common/events/board-stage/index.ts b/backend/src/CRM/common/events/board-stage/index.ts new file mode 100644 index 0000000..8bf42c9 --- /dev/null +++ b/backend/src/CRM/common/events/board-stage/index.ts @@ -0,0 +1,2 @@ +export * from './board-stage-deleted.event'; +export * from './board-stage.event'; diff --git a/backend/src/CRM/common/events/board/board.event.ts b/backend/src/CRM/common/events/board/board.event.ts new file mode 100644 index 0000000..5edea62 --- /dev/null +++ b/backend/src/CRM/common/events/board/board.event.ts @@ -0,0 +1,11 @@ +export class BoardEvent { + accountId: number; + userId: number; + boardId: number; + + constructor({ accountId, userId, boardId }: BoardEvent) { + this.accountId = accountId; + this.userId = userId; + this.boardId = boardId; + } +} diff --git a/backend/src/CRM/common/events/board/index.ts b/backend/src/CRM/common/events/board/index.ts new file mode 100644 index 0000000..e03a21e --- /dev/null +++ b/backend/src/CRM/common/events/board/index.ts @@ -0,0 +1 @@ +export * from './board.event'; diff --git a/backend/src/CRM/common/events/crm-event-type.enum.ts b/backend/src/CRM/common/events/crm-event-type.enum.ts new file mode 100644 index 0000000..d047e7a --- /dev/null +++ b/backend/src/CRM/common/events/crm-event-type.enum.ts @@ -0,0 +1,29 @@ +export enum CrmEventType { + ActivityCreated = 'activity:created', + ActivityUpdated = 'activity:updated', + ActivityDeleted = 'activity:deleted', + BoardCreated = 'board:created', + BoardUpdated = 'board:updated', + BoardDeleted = 'board:deleted', + BoardStageCreated = 'board:stage:created', + BoardStageUpdated = 'board:stage:updated', + BoardStageDeleted = 'board:stage:deleted', + EntityCreated = 'entity:created', + EntityUpdated = 'entity:updated', + EntityOwnerChanged = 'entity:owner_changed', + EntityStageChanged = 'entity:stage_changed', + EntityImportCompleted = 'entity:import_completed', + EntityDeleted = 'entity:deleted', + EntityPriceUpdate = 'entity:price:update', + EntityTypeDeleted = 'entity_type:deleted', + FileLinkCreated = 'file_link:created', + FileLinkDeleted = 'file_link:deleted', + NoteCreated = 'note:created', + NoteDeleted = 'note:deleted', + TaskCreated = 'task:created', + TaskDeleted = 'task:deleted', + TaskDeleteExt = 'task:delete-ext', + TaskUpdated = 'task:updated', + TaskUpsertExt = 'task:upsert-ext', + TaskCommentCreated = 'task:comment:created', +} diff --git a/backend/src/CRM/common/events/entity-type/entity-type.event.ts b/backend/src/CRM/common/events/entity-type/entity-type.event.ts new file mode 100644 index 0000000..4bcf148 --- /dev/null +++ b/backend/src/CRM/common/events/entity-type/entity-type.event.ts @@ -0,0 +1,11 @@ +export class EntityTypeEvent { + accountId: number; + userId: number; + entityTypeId: number; + + constructor({ accountId, userId, entityTypeId }: EntityTypeEvent) { + this.accountId = accountId; + this.userId = userId; + this.entityTypeId = entityTypeId; + } +} diff --git a/backend/src/CRM/common/events/entity-type/index.ts b/backend/src/CRM/common/events/entity-type/index.ts new file mode 100644 index 0000000..ae61275 --- /dev/null +++ b/backend/src/CRM/common/events/entity-type/index.ts @@ -0,0 +1 @@ +export * from './entity-type.event'; diff --git a/backend/src/CRM/common/events/entity/entity-created.event.ts b/backend/src/CRM/common/events/entity/entity-created.event.ts new file mode 100644 index 0000000..1cd6757 --- /dev/null +++ b/backend/src/CRM/common/events/entity/entity-created.event.ts @@ -0,0 +1,29 @@ +import { UserNotification } from '@/common/enums'; + +import { EntityEvent } from './entity.event'; + +export class EntityCreatedEvent extends EntityEvent { + entityName: string; + createdBy: number; + copiedFrom?: number | null; + + constructor({ + accountId, + entityId, + entityName, + boardId, + stageId, + createdBy, + ownerId, + entityTypeId, + userNotification = UserNotification.Default, + copiedFrom = undefined, + }: EntityCreatedEvent) { + super({ accountId, entityId, entityTypeId, boardId, stageId, ownerId, userNotification }); + + this.entityName = entityName; + this.stageId = stageId; + this.createdBy = createdBy; + this.copiedFrom = copiedFrom; + } +} diff --git a/backend/src/CRM/common/events/entity/entity-import.event.ts b/backend/src/CRM/common/events/entity/entity-import.event.ts new file mode 100644 index 0000000..ee52ae7 --- /dev/null +++ b/backend/src/CRM/common/events/entity/entity-import.event.ts @@ -0,0 +1,17 @@ +export class EntityImportEvent { + accountId: number; + userId: number; + fileName: string; + entityTypeId: number; + entityTypeName: string | null; + totalCount: number; + + constructor({ accountId, userId, fileName, entityTypeId, entityTypeName, totalCount }: EntityImportEvent) { + this.accountId = accountId; + this.userId = userId; + this.fileName = fileName; + this.entityTypeId = entityTypeId; + this.entityTypeName = entityTypeName; + this.totalCount = totalCount; + } +} diff --git a/backend/src/CRM/common/events/entity/entity-owner-changed.event.ts b/backend/src/CRM/common/events/entity/entity-owner-changed.event.ts new file mode 100644 index 0000000..0645c84 --- /dev/null +++ b/backend/src/CRM/common/events/entity/entity-owner-changed.event.ts @@ -0,0 +1,25 @@ +import { UserNotification } from '@/common/enums'; +import { EntityEvent } from './entity.event'; + +export class EntityOwnerChangedEvent extends EntityEvent { + entityName: string; + changedBy: number; + + constructor({ + accountId, + entityId, + entityName, + boardId, + stageId, + changedBy, + ownerId, + entityTypeId, + userNotification = UserNotification.Default, + }: EntityOwnerChangedEvent) { + super({ accountId, entityId, entityTypeId, boardId, stageId, ownerId, userNotification }); + + this.entityName = entityName; + this.changedBy = changedBy; + this.ownerId = ownerId; + } +} diff --git a/backend/src/CRM/common/events/entity/entity-price.update.event.ts b/backend/src/CRM/common/events/entity/entity-price.update.event.ts new file mode 100644 index 0000000..00c48aa --- /dev/null +++ b/backend/src/CRM/common/events/entity/entity-price.update.event.ts @@ -0,0 +1,11 @@ +export class EntityPriceUpdateEvent { + accountId: number; + entityId: number; + price: number; + + constructor({ accountId, entityId, price }: EntityPriceUpdateEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.price = price; + } +} diff --git a/backend/src/CRM/common/events/entity/entity.event.ts b/backend/src/CRM/common/events/entity/entity.event.ts new file mode 100644 index 0000000..10e31d0 --- /dev/null +++ b/backend/src/CRM/common/events/entity/entity.event.ts @@ -0,0 +1,29 @@ +import { UserNotification } from '@/common/enums'; + +export class EntityEvent { + accountId: number; + entityId: number; + entityTypeId: number; + boardId: number | null; + stageId: number | null; + ownerId: number; + userNotification: UserNotification; + + constructor({ + accountId, + entityId, + entityTypeId, + boardId, + stageId, + ownerId, + userNotification = UserNotification.Default, + }: EntityEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.stageId = stageId; + this.ownerId = ownerId; + this.userNotification = userNotification; + } +} diff --git a/backend/src/CRM/common/events/entity/index.ts b/backend/src/CRM/common/events/entity/index.ts new file mode 100644 index 0000000..ccb9847 --- /dev/null +++ b/backend/src/CRM/common/events/entity/index.ts @@ -0,0 +1,5 @@ +export * from './entity-created.event'; +export * from './entity-import.event'; +export * from './entity-owner-changed.event'; +export * from './entity-price.update.event'; +export * from './entity.event'; diff --git a/backend/src/CRM/common/events/file-link/file-link-created.event.ts b/backend/src/CRM/common/events/file-link/file-link-created.event.ts new file mode 100644 index 0000000..5c15ac7 --- /dev/null +++ b/backend/src/CRM/common/events/file-link/file-link-created.event.ts @@ -0,0 +1,11 @@ +import { FileLinkEvent } from './file-link.event'; + +export class FileLinkCreatedEvent extends FileLinkEvent { + createdAt: string; + + constructor({ accountId, sourceType, sourceId, fileLinkId, createdAt }: FileLinkCreatedEvent) { + super({ accountId, sourceType, sourceId, fileLinkId }); + + this.createdAt = createdAt; + } +} diff --git a/backend/src/CRM/common/events/file-link/file-link.event.ts b/backend/src/CRM/common/events/file-link/file-link.event.ts new file mode 100644 index 0000000..725604e --- /dev/null +++ b/backend/src/CRM/common/events/file-link/file-link.event.ts @@ -0,0 +1,15 @@ +import { type FileLinkSource } from '@/common/enums'; + +export class FileLinkEvent { + accountId: number; + sourceType: FileLinkSource; + sourceId: number | null; + fileLinkId: number; + + constructor({ accountId, sourceType, sourceId, fileLinkId }: FileLinkEvent) { + this.accountId = accountId; + this.sourceType = sourceType; + this.sourceId = sourceId; + this.fileLinkId = fileLinkId; + } +} diff --git a/backend/src/CRM/common/events/file-link/index.ts b/backend/src/CRM/common/events/file-link/index.ts new file mode 100644 index 0000000..1777198 --- /dev/null +++ b/backend/src/CRM/common/events/file-link/index.ts @@ -0,0 +1,2 @@ +export * from './file-link-created.event'; +export * from './file-link.event'; diff --git a/backend/src/CRM/common/events/index.ts b/backend/src/CRM/common/events/index.ts new file mode 100644 index 0000000..50d2e63 --- /dev/null +++ b/backend/src/CRM/common/events/index.ts @@ -0,0 +1,9 @@ +export * from './activity'; +export * from './board'; +export * from './board-stage'; +export * from './crm-event-type.enum'; +export * from './entity'; +export * from './entity-type'; +export * from './file-link'; +export * from './note'; +export * from './task'; diff --git a/backend/src/CRM/common/events/note/index.ts b/backend/src/CRM/common/events/note/index.ts new file mode 100644 index 0000000..1fd88a5 --- /dev/null +++ b/backend/src/CRM/common/events/note/index.ts @@ -0,0 +1,2 @@ +export * from './note-created.event'; +export * from './note.event'; diff --git a/backend/src/CRM/common/events/note/note-created.event.ts b/backend/src/CRM/common/events/note/note-created.event.ts new file mode 100644 index 0000000..972f530 --- /dev/null +++ b/backend/src/CRM/common/events/note/note-created.event.ts @@ -0,0 +1,18 @@ +import { NoteEvent } from './note.event'; + +export class NoteCreatedEvent extends NoteEvent { + entityName: string; + createdBy: number; + ownerId: number; + noteText: string; + createdAt: string; + + constructor({ accountId, entityId, entityName, createdBy, ownerId, noteId, noteText, createdAt }: NoteCreatedEvent) { + super({ accountId, entityId, noteId }); + this.entityName = entityName; + this.createdBy = createdBy; + this.ownerId = ownerId; + this.noteText = noteText; + this.createdAt = createdAt; + } +} diff --git a/backend/src/CRM/common/events/note/note.event.ts b/backend/src/CRM/common/events/note/note.event.ts new file mode 100644 index 0000000..b70387f --- /dev/null +++ b/backend/src/CRM/common/events/note/note.event.ts @@ -0,0 +1,11 @@ +export class NoteEvent { + accountId: number; + entityId: number | null; + noteId: number; + + constructor({ accountId, entityId, noteId }: NoteEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.noteId = noteId; + } +} diff --git a/backend/src/CRM/common/events/task/index.ts b/backend/src/CRM/common/events/task/index.ts new file mode 100644 index 0000000..18f0e23 --- /dev/null +++ b/backend/src/CRM/common/events/task/index.ts @@ -0,0 +1,6 @@ +export * from './task-comment-created.event'; +export * from './task-created.event'; +export * from './task-ext-upsert.event'; +export * from './task-ext.event'; +export * from './task-updated.event'; +export * from './task.event'; diff --git a/backend/src/CRM/common/events/task/task-comment-created.event.ts b/backend/src/CRM/common/events/task/task-comment-created.event.ts new file mode 100644 index 0000000..18fbe31 --- /dev/null +++ b/backend/src/CRM/common/events/task/task-comment-created.event.ts @@ -0,0 +1,11 @@ +import { TaskCreatedEvent } from './task-created.event'; + +export class TaskCommentCreatedEvent extends TaskCreatedEvent { + taskComment: string; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.taskComment = data.taskComment; + } +} diff --git a/backend/src/CRM/common/events/task/task-created.event.ts b/backend/src/CRM/common/events/task/task-created.event.ts new file mode 100644 index 0000000..1518f3e --- /dev/null +++ b/backend/src/CRM/common/events/task/task-created.event.ts @@ -0,0 +1,23 @@ +import { TaskEvent } from './task.event'; + +export class TaskCreatedEvent extends TaskEvent { + ownerId: number; + createdBy: number; + taskTitle: string; + taskText: string; + createdAt: Date; + startDate: Date | null; + endDate: Date | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.ownerId = data.ownerId; + this.createdBy = data.createdBy; + this.taskTitle = data.taskTitle; + this.taskText = data.taskText; + this.createdAt = data.createdAt; + this.startDate = data.startDate; + this.endDate = data.endDate; + } +} diff --git a/backend/src/CRM/common/events/task/task-ext-upsert.event.ts b/backend/src/CRM/common/events/task/task-ext-upsert.event.ts new file mode 100644 index 0000000..f536b5a --- /dev/null +++ b/backend/src/CRM/common/events/task/task-ext-upsert.event.ts @@ -0,0 +1,19 @@ +import { TaskExtEvent } from './task-ext.event'; + +export class TaskExtUpsertEvent extends TaskExtEvent { + ownerId: number; + title: string; + text?: string | null; + startDate?: Date | null; + endDate?: Date | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.ownerId = data.ownerId; + this.title = data.title; + this.text = data.text; + this.startDate = data.startDate; + this.endDate = data.endDate; + } +} diff --git a/backend/src/CRM/common/events/task/task-ext.event.ts b/backend/src/CRM/common/events/task/task-ext.event.ts new file mode 100644 index 0000000..c307fbb --- /dev/null +++ b/backend/src/CRM/common/events/task/task-ext.event.ts @@ -0,0 +1,17 @@ +import { ServiceEvent } from '@/common'; + +export class TaskExtEvent extends ServiceEvent { + externalId?: string | null; + accountId: number; + boardId: number; + taskId?: number | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.externalId = data.externalId; + this.accountId = data.accountId; + this.boardId = data.boardId; + this.taskId = data.taskId; + } +} diff --git a/backend/src/CRM/common/events/task/task-updated.event.ts b/backend/src/CRM/common/events/task/task-updated.event.ts new file mode 100644 index 0000000..0cc3d50 --- /dev/null +++ b/backend/src/CRM/common/events/task/task-updated.event.ts @@ -0,0 +1,11 @@ +import { TaskCreatedEvent } from './task-created.event'; + +export class TaskUpdatedEvent extends TaskCreatedEvent { + prevEntityId: number | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.prevEntityId = data.prevEntityId; + } +} diff --git a/backend/src/CRM/common/events/task/task.event.ts b/backend/src/CRM/common/events/task/task.event.ts new file mode 100644 index 0000000..387d52f --- /dev/null +++ b/backend/src/CRM/common/events/task/task.event.ts @@ -0,0 +1,19 @@ +import { ServiceEvent } from '@/common'; + +export class TaskEvent extends ServiceEvent { + accountId: number; + taskId: number; + boardId: number; + entityId?: number | null; + externalId?: string | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.accountId = data.accountId; + this.taskId = data.taskId; + this.boardId = data.boardId; + this.entityId = data.entityId; + this.externalId = data.externalId; + } +} diff --git a/backend/src/CRM/common/index.ts b/backend/src/CRM/common/index.ts new file mode 100644 index 0000000..df1eda9 --- /dev/null +++ b/backend/src/CRM/common/index.ts @@ -0,0 +1,2 @@ +export * from './enums'; +export * from './events'; diff --git a/backend/src/CRM/crm.module.ts b/backend/src/CRM/crm.module.ts new file mode 100644 index 0000000..b961db1 --- /dev/null +++ b/backend/src/CRM/crm.module.ts @@ -0,0 +1,216 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; +import { InventoryModule } from '@/modules/inventory/inventory.module'; +import { SchedulerModule } from '@/modules/scheduler/scheduler.module'; +import { TelephonyModule } from '@/modules/telephony/telephony.module'; +import { MultichatModule } from '@/modules/multichat/multichat.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { EntityFieldModule } from '@/modules/entity/entity-field/entity-field.module'; + +import { MailingModule } from '@/Mailing/MailingModule'; + +import { CrmReportingModule } from './reporting/crm-reporting.module'; +import { SalesPlanModule } from './sales-plan/sales-plan.module'; +import { TaskSettingsModule } from './task-settings/task-settings.module'; +import { SalesforceModule } from './Salesforce/SalesforceModule'; + +/// Entity +import { Entity } from './Model/Entity/Entity'; +import { EntityService } from './Service/Entity/EntityService'; +import { EntityServiceEmitter } from './Service/Entity/entity-service.emitter'; +import { EntityServiceHandler } from './Service/Entity/entity-service.handler'; +import { EntityBoardService } from './Service/Entity/EntityBoardService'; +import { CreateEntityController } from './Controller/Entity/CreateEntityController'; +import { CreateSimpleEntityController } from './Controller/Entity/CreateSimpleEntityController'; +import { UpdateEntityController } from './Controller/Entity/UpdateEntityController'; +import { UpdateEntityFieldController } from './Controller/Entity/UpdateEntityFieldController'; +import { GetEntityController } from './Controller/Entity/GetEntityController'; +import { GetEntityFilesController } from './Controller/Entity/Files/GetEntityFilesController'; +import { AddEntityFilesController } from './Controller/Entity/Files/AddEntityFilesController'; +import { GetTimeBoardController } from './Controller/TimeBoard/GetTimeBoardController'; +import { GetTimeBoardMetaController } from './Controller/TimeBoard/GetTimeBoardMetaController'; +import { GetTimeBoardCalendarController } from './Controller/TimeBoard/GetTimeBoardCalendarController'; +import { GetTimeBoardCalendarMetaController } from './Controller/TimeBoard/GetTimeBoardCalendarMetaController'; +import { GetTimeBoardItemController } from './Controller/TimeBoard/GetTimeBoardItemController'; +import { TimeBoardService } from './Service/TimeBoard/TimeBoardService'; +import { DeleteEntityController } from './Controller/Entity/DeleteEntityController'; +import { ExternalEntity } from './Model/ExternalEntity/ExternalEntity'; +import { ExternalEntityService } from './Service/ExternalEntity/ExternalEntityService'; +import { CreateExternalLinkController } from './Controller/ExternalEntity/CreateExternalLinkController'; +import { ExternalSystem } from './Model/ExternalEntity/ExternalSystem'; +import { ExternalSystemService } from './Service/ExternalEntity/ExternalSystemService'; +import { FileLink } from './Model/FileLink/FileLink'; +import { FileLinkService } from './Service/FileLink/FileLinkService'; +import { DeleteFileLinkController } from './Controller/FileLink/DeleteFileLinkController'; +import { ProjectEntityBoardCardService } from './Service/Entity/ProjectEntityBoardCardService'; +import { CommonEntityBoardCardService } from './Service/Entity/CommonEntityBoardCardService'; +import { ImportService } from './Service/Import/ImportService'; +import { GetEntitiesImportTemplateController } from './Controller/Entity/Import/GetEntitiesImportTemplateController'; +import { UploadEntitiesImportController } from './Controller/Entity/Import/UploadEntitiesImportController'; +import { GetEntityDocumentsController } from './Controller/Entity/Documents/GetEntityDocumentsController'; +import { DeleteFileLinksController } from './Controller/FileLink/DeleteFileLinksController'; +import { EntityBoardController } from './Controller/Entity/Board/entity-board.controller'; +import { EntityListController } from './Controller/Entity/List/entity-list.controller'; + +import { Activity, ActivityController, ActivityHandler, ActivityService } from './activity'; +import { ActivityCardController, ActivityCardService } from './activity-card'; +import { ActivityType, ActivityTypeController, ActivityTypeService } from './activity-type'; +import { BaseTaskService } from './base-task'; +import { Board } from './board/entities'; +import { BoardController } from './board/board.controller'; +import { BoardService } from './board/board.service'; +import { BoardHandler } from './board/board.handler'; +import { BoardStage, BoardStageController, BoardStageService } from './board-stage'; +import { EntityLink } from './entity-link/entities'; +import { EntityLinkService } from './entity-link/entity-link.service'; +import { EntitySearchController, EntitySearchService } from './entity-search'; +import { EntityType } from './entity-type/entities'; +import { EntityTypeController } from './entity-type/entity-type.controller'; +import { EntityTypeService } from './entity-type/entity-type.service'; +import { EntityTypeLink } from './entity-type-link/entities'; +import { EntityTypeLinkService } from './entity-type-link/entity-type-link.service'; +import { EntityTypeFeature, EntityTypeFeatureService, Feature, FeatureController, FeatureService } from './feature'; +import { Task, TaskController, TaskHandler, TaskService } from './task'; +import { TaskBoardController, TaskBoardService } from './task-board'; +import { TaskComment, TaskCommentController, TaskCommentService } from './task-comment'; +import { TaskCommentLike, TaskCommentLikeController, TaskCommentLikeService } from './task-comment-like'; +import { TaskSubtask, TaskSubtaskController, TaskSubtaskService } from './task-subtask'; +import { IdentityController } from './identity/identity.controller'; +import { IdentityService } from './identity/identity.service'; + +import { Note, NoteController, NoteHandler, NoteService } from './note'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Activity, + ActivityType, + Board, + BoardStage, + Entity, + EntityLink, + EntityType, + Feature, + EntityTypeFeature, + EntityTypeLink, + ExternalEntity, + ExternalSystem, + FileLink, + Note, + TaskSubtask, + Task, + TaskComment, + TaskCommentLike, + ]), + IAMModule, + forwardRef(() => MailingModule), + forwardRef(() => InventoryModule), + forwardRef(() => CrmReportingModule), + forwardRef(() => SchedulerModule), + forwardRef(() => SalesPlanModule), + forwardRef(() => TelephonyModule), + forwardRef(() => MultichatModule), + SalesforceModule, + StorageModule, + TaskSettingsModule, + EntityFieldModule, + EntityInfoModule, + ], + controllers: [ + IdentityController, + EntityTypeController, + CreateEntityController, + CreateSimpleEntityController, + UpdateEntityController, + UpdateEntityFieldController, + DeleteEntityController, + EntitySearchController, + GetEntityController, + EntityBoardController, + EntityListController, + GetEntityFilesController, + AddEntityFilesController, + NoteController, + BoardController, + BoardStageController, + ActivityTypeController, + ActivityController, + TaskController, + GetTimeBoardController, + GetTimeBoardMetaController, + GetTimeBoardCalendarController, + GetTimeBoardCalendarMetaController, + GetTimeBoardItemController, + FeatureController, + CreateExternalLinkController, + TaskBoardController, + ActivityCardController, + DeleteFileLinkController, + DeleteFileLinksController, + TaskSubtaskController, + TaskCommentController, + TaskCommentLikeController, + GetEntitiesImportTemplateController, + UploadEntitiesImportController, + GetEntityDocumentsController, + ], + providers: [ + IdentityService, + ActivityCardService, + ActivityService, + ActivityHandler, + ActivityTypeService, + BaseTaskService, + BoardService, + BoardHandler, + BoardStageService, + CommonEntityBoardCardService, + EntityBoardService, + EntityLinkService, + EntitySearchService, + EntityService, + EntityServiceEmitter, + EntityServiceHandler, + EntityTypeFeatureService, + EntityTypeLinkService, + EntityTypeService, + ExternalEntityService, + ExternalSystemService, + FeatureService, + FileLinkService, + ImportService, + NoteService, + NoteHandler, + ProjectEntityBoardCardService, + TaskSubtaskService, + TaskBoardService, + TaskCommentService, + TaskCommentLikeService, + TaskService, + TaskHandler, + TimeBoardService, + ], + exports: [ + EntityService, + EntitySearchService, + EntityTypeService, + EntityLinkService, + EntityBoardService, + EntityTypeFeatureService, + BoardStageService, + ActivityService, + TaskService, + FileLinkService, + ActivityTypeService, + BoardService, + EntityTypeLinkService, + NoteService, + TaskSubtaskService, + CrmReportingModule, + EntityFieldModule, + ], +}) +export class CrmModule {} diff --git a/backend/src/CRM/entity-link/dto/entity-link.dto.ts b/backend/src/CRM/entity-link/dto/entity-link.dto.ts new file mode 100644 index 0000000..6b9ec18 --- /dev/null +++ b/backend/src/CRM/entity-link/dto/entity-link.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { ObjectState } from '@/common'; + +export class EntityLinkDto { + @ApiProperty() + @IsNumber() + sourceId: number; + + @ApiProperty() + @IsNumber() + targetId: number; + + @ApiProperty() + @IsNumber() + sortOrder: number; + + @ApiProperty({ enum: ObjectState }) + @IsEnum(ObjectState) + state: ObjectState; +} diff --git a/backend/src/CRM/entity-link/dto/index.ts b/backend/src/CRM/entity-link/dto/index.ts new file mode 100644 index 0000000..216fc12 --- /dev/null +++ b/backend/src/CRM/entity-link/dto/index.ts @@ -0,0 +1 @@ +export * from './entity-link.dto'; diff --git a/backend/src/CRM/entity-link/entities/entity-link.entity.ts b/backend/src/CRM/entity-link/entities/entity-link.entity.ts new file mode 100644 index 0000000..16cfffc --- /dev/null +++ b/backend/src/CRM/entity-link/entities/entity-link.entity.ts @@ -0,0 +1,36 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ObjectState } from '@/common'; + +import { EntityLinkDto } from '../dto'; + +@Entity() +export class EntityLink { + @PrimaryColumn() + sourceId: number; + + @PrimaryColumn() + targetId: number; + + @Column() + sortOrder: number; + + @Column() + accountId: number; + + constructor(accountId: number, sourceId: number, targetId: number, sortOrder: number) { + this.accountId = accountId; + this.sourceId = sourceId; + this.targetId = targetId; + this.sortOrder = sortOrder; + } + + public toDto(): EntityLinkDto { + return { + sourceId: this.sourceId, + targetId: this.targetId, + sortOrder: this.sortOrder, + state: ObjectState.Unchanged, + }; + } +} diff --git a/backend/src/CRM/entity-link/entities/index.ts b/backend/src/CRM/entity-link/entities/index.ts new file mode 100644 index 0000000..7f33a02 --- /dev/null +++ b/backend/src/CRM/entity-link/entities/index.ts @@ -0,0 +1 @@ +export * from './entity-link.entity'; diff --git a/backend/src/CRM/entity-link/entity-link.service.ts b/backend/src/CRM/entity-link/entity-link.service.ts new file mode 100644 index 0000000..507e669 --- /dev/null +++ b/backend/src/CRM/entity-link/entity-link.service.ts @@ -0,0 +1,151 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ObjectState } from '@/common'; + +import { EntityLinkDto } from './dto'; +import { EntityLink } from './entities'; + +interface FindFilter { + accountId: number; + linkId?: number; + sourceId?: number | number[]; + targetId?: number | number[]; +} + +@Injectable() +export class EntityLinkService { + private readonly logger = new Logger(EntityLinkService.name); + constructor( + @InjectRepository(EntityLink) + private readonly repository: Repository, + ) {} + + async create({ + accountId, + sourceId, + targetId, + sortOrder, + createBackLink = true, + }: { + accountId: number; + sourceId: number; + targetId: number; + sortOrder?: number; + createBackLink?: boolean; + }): Promise { + sortOrder ??= (await this.getMaxSortOrder({ accountId, sourceId })) + 1; + const link = await this.repository.save(new EntityLink(accountId, sourceId, targetId, sortOrder)); + if (createBackLink) { + await this.create({ accountId, sourceId: targetId, targetId: sourceId, createBackLink: false }); + } + return link; + } + + async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).getMany(); + } + + async processMany({ accountId, links }: { accountId: number; links: EntityLinkDto[] }): Promise { + let changed = false; + for (const link of links) { + try { + if (link.state === ObjectState.Created) { + await this.create({ accountId, sourceId: link.sourceId, targetId: link.targetId, sortOrder: link.sortOrder }); + changed = true; + } else if (link.state === ObjectState.Updated) { + await this.update({ accountId, sourceId: link.sourceId, targetId: link.targetId, sortOrder: link.sortOrder }); + } else if (link.state === ObjectState.Deleted) { + await this.delete({ accountId, sourceId: link.sourceId, targetId: link.targetId }); + changed = true; + } + } catch (error) { + this.logger.warn(`Failed to process link: ${JSON.stringify(link)}. Error: ${error.toString()}`); + } + } + return changed; + } + + async update({ + accountId, + sourceId, + targetId, + sortOrder, + }: { + accountId: number; + sourceId: number; + targetId: number; + sortOrder: number; + }) { + await this.repository.update({ accountId, sourceId, targetId }, { sortOrder }); + } + + async delete({ accountId, sourceId, targetId }: { accountId: number; sourceId: number; targetId: number }) { + await this.repository.delete({ accountId, sourceId, targetId }); + await this.repository.delete({ accountId, sourceId: targetId, targetId: sourceId }); + } + + async copyEntityLinks({ + accountId, + sourceId, + targetId, + }: { + accountId: number; + sourceId: number; + targetId: number; + }): Promise { + const links = await this.findMany({ accountId, sourceId }); + return Promise.all( + links.map((link) => + this.create({ accountId, sourceId: targetId, targetId: link.targetId, sortOrder: link.sortOrder }), + ), + ); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('el') + .where('el.account_id = :accountId', { accountId: filter.accountId }) + .orderBy('el.sort_order', 'ASC'); + if (filter.linkId) { + qb.andWhere('el.id = :linkId', { linkId: filter.linkId }); + } + if (filter.sourceId) { + if (Array.isArray(filter.sourceId)) { + if (filter.sourceId.length) { + qb.andWhere('el.source_id IN (:...sourceIds)', { sourceIds: filter.sourceId }); + } else { + qb.andWhere('1 = 0'); + } + } else { + qb.andWhere('el.source_id = :sourceId', { sourceId: filter.sourceId }); + } + } + if (filter.targetId) { + if (Array.isArray(filter.targetId)) { + if (filter.targetId.length) { + qb.andWhere('el.target_id IN (:...targetIds)', { targetIds: filter.targetId }); + } else { + qb.andWhere('1 = 0'); + } + } else { + qb.andWhere('el.target_id = :targetId', { targetId: filter.targetId }); + } + } + return qb; + } + + private async getMaxSortOrder({ accountId, sourceId }: { accountId: number; sourceId: number }): Promise { + const result = await this.repository + .createQueryBuilder('el') + .select('max(el.sort_order)', 'max') + .where('el.account_id = :accountId', { accountId }) + .andWhere('el.source_id = :sourceId', { sourceId }) + .getRawOne<{ max: number }>(); + return result?.max ?? 0; + } +} diff --git a/backend/src/CRM/entity-search/dto/entity-search-filter.dto.ts b/backend/src/CRM/entity-search/dto/entity-search-filter.dto.ts new file mode 100644 index 0000000..87d5651 --- /dev/null +++ b/backend/src/CRM/entity-search/dto/entity-search-filter.dto.ts @@ -0,0 +1,38 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +import { FieldType } from '@/modules/entity/entity-field/common'; + +export class EntitySearchFilterDto { + @ApiPropertyOptional({ description: 'Filter by entity type ID.', nullable: true, type: [Number] }) + @IsOptional() + entityTypeId?: number | number[] | null; + + @ApiPropertyOptional({ description: 'Filter by entity board ID.', nullable: true, type: [Number] }) + @IsOptional() + boardId?: number | number[] | null; + + @ApiPropertyOptional({ description: 'Filter by entity name.', nullable: true }) + @IsOptional() + @IsString() + name?: string | null; + + @ApiPropertyOptional({ description: 'Exclude entity id from search', nullable: true, type: [Number] }) + @IsOptional() + excludeEntityId?: number | number[] | null; + + @ApiPropertyOptional({ description: 'Filter by field value.', nullable: true }) + @IsOptional() + @IsString() + fieldValue?: string | null; + + @ApiPropertyOptional({ enum: FieldType, description: 'Filter by field type for field value.', nullable: true }) + @IsOptional() + @IsString() + fieldType?: FieldType | null; + + @ApiPropertyOptional({ description: 'Search in linked entities', nullable: true }) + @IsOptional() + @IsBoolean() + searchInLinked?: boolean | null; +} diff --git a/backend/src/CRM/entity-search/dto/entity-search-for-call-result.dto.ts b/backend/src/CRM/entity-search/dto/entity-search-for-call-result.dto.ts new file mode 100644 index 0000000..7d89872 --- /dev/null +++ b/backend/src/CRM/entity-search/dto/entity-search-for-call-result.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +import { EntityInfoDto } from '@/modules/entity/entity-info'; +import { IsOptional } from 'class-validator'; + +export class EntitySearchForCallResultDto { + @ApiProperty({ description: 'Entity info', type: EntityInfoDto, nullable: true }) + @IsOptional() + entity: EntityInfoDto | null; + + @ApiPropertyOptional({ description: 'Entity info', type: EntityInfoDto, nullable: true }) + @IsOptional() + linked?: EntityInfoDto | null; +} diff --git a/backend/src/CRM/entity-search/dto/entity-search-full-result.dto.ts b/backend/src/CRM/entity-search/dto/entity-search-full-result.dto.ts new file mode 100644 index 0000000..d3fa47a --- /dev/null +++ b/backend/src/CRM/entity-search/dto/entity-search-full-result.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { EntityDto } from '../../Service/Entity/Dto/EntityDto'; + +export class EntitySearchFullResultDto { + @ApiProperty({ description: 'List of entities', type: [EntityDto], nullable: true }) + entities: EntityDto[] | null; + + @ApiProperty({ description: 'Paging metadata', type: PagingMeta }) + meta: PagingMeta; +} diff --git a/backend/src/CRM/entity-search/dto/entity-search-result.dto.ts b/backend/src/CRM/entity-search/dto/entity-search-result.dto.ts new file mode 100644 index 0000000..3b19784 --- /dev/null +++ b/backend/src/CRM/entity-search/dto/entity-search-result.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +export class EntitySearchResultDto { + @ApiProperty({ description: 'List of entities', type: [EntityInfoDto], nullable: true }) + entities: EntityInfoDto[] | null; + + @ApiProperty({ description: 'Paging metadata', type: PagingMeta }) + meta: PagingMeta; +} diff --git a/backend/src/CRM/entity-search/dto/index.ts b/backend/src/CRM/entity-search/dto/index.ts new file mode 100644 index 0000000..097f5c6 --- /dev/null +++ b/backend/src/CRM/entity-search/dto/index.ts @@ -0,0 +1,4 @@ +export * from './entity-search-filter.dto'; +export * from './entity-search-for-call-result.dto'; +export * from './entity-search-full-result.dto'; +export * from './entity-search-result.dto'; diff --git a/backend/src/CRM/entity-search/entity-search.controller.ts b/backend/src/CRM/entity-search/entity-search.controller.ts new file mode 100644 index 0000000..c1d7b58 --- /dev/null +++ b/backend/src/CRM/entity-search/entity-search.controller.ts @@ -0,0 +1,60 @@ +import { Body, Controller, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { + EntitySearchFilterDto, + EntitySearchForCallResultDto, + EntitySearchFullResultDto, + EntitySearchResultDto, +} from './dto'; +import { EntitySearchService } from './entity-search.service'; + +@ApiTags('crm/entities/search') +@Controller('crm/entities/search') +@JwtAuthorized({ prefetch: { user: true } }) +export class EntitySearchController { + constructor(private readonly service: EntitySearchService) {} + + @ApiOperation({ + summary: 'Search entities with full information', + description: 'Search entities with full information by name according permissions', + }) + @ApiBody({ type: EntitySearchFilterDto, required: true, description: 'Search filter' }) + @ApiOkResponse({ description: 'Entity search result', type: EntitySearchFullResultDto }) + @Post('full') + async searchFull( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: EntitySearchFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.searchFull({ accountId, user, filter, paging }); + } + + @ApiOperation({ summary: 'Search entities', description: 'Search entities by name according permissions' }) + @ApiBody({ type: EntitySearchFilterDto, required: true, description: 'Search filter' }) + @ApiOkResponse({ description: 'Entity search result', type: EntitySearchResultDto }) + @Post() + async search( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: EntitySearchFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.searchInfo({ accountId, user, filter, paging }); + } + + @ApiOperation({ + summary: 'Search entity for call info', + description: 'Search entities for call information with last deal', + }) + @ApiBody({ type: EntitySearchFilterDto, required: true, description: 'Search filter' }) + @ApiOkResponse({ description: 'Entity search result', type: EntitySearchForCallResultDto }) + @Post('for-call') + async searchForCall(@CurrentAuth() { accountId, user }: AuthData, @Body() filter: EntitySearchFilterDto) { + return this.service.searchForCall({ accountId, user, filter }); + } +} diff --git a/backend/src/CRM/entity-search/entity-search.service.ts b/backend/src/CRM/entity-search/entity-search.service.ts new file mode 100644 index 0000000..e87040d --- /dev/null +++ b/backend/src/CRM/entity-search/entity-search.service.ts @@ -0,0 +1,258 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, DataSource, Repository } from 'typeorm'; + +import { PagingMeta, PagingQuery } from '@/common'; +import { buildSearchParams } from '@/database/common'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities'; +import { EntityInfoService } from '@/modules/entity/entity-info'; +import { FieldType } from '@/modules/entity/entity-field/common'; + +import { EntityCategory } from '../common'; +import { Entity } from '../Model/Entity/Entity'; +import { EntityService } from '../Service/Entity/EntityService'; +import { EntityTypeService } from '../entity-type/entity-type.service'; + +import { EntitySearchForCallResultDto, EntitySearchFullResultDto, EntitySearchResultDto } from './dto'; + +interface SearchFilter { + entityTypeId?: number | number[] | null; + boardId?: number | number[] | null; + name?: string | null; + excludeEntityId?: number | number[] | null; + fieldValue?: string | null; + fieldType?: FieldType | null; + searchInLinked?: boolean | null; +} + +@Injectable() +export class EntitySearchService { + constructor( + private readonly dataSource: DataSource, + @InjectRepository(Entity) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly entityTypeService: EntityTypeService, + @Inject(forwardRef(() => EntityService)) + private readonly entityService: EntityService, + private readonly entityInfoService: EntityInfoService, + ) {} + + async searchFull({ + accountId, + user, + filter, + paging, + }: { + accountId: number; + user: User; + filter: SearchFilter; + paging: PagingQuery; + }): Promise { + const { entities, total } = await this.search({ accountId, user, filter, paging }); + + return { + entities: await Promise.all( + entities.map(async (entity) => await this.entityService.getDtoForEntity(accountId, user, entity, false)), + ), + meta: new PagingMeta(paging.skip + paging.take, total), + }; + } + + async searchInfo({ + accountId, + user, + filter, + paging, + }: { + accountId: number; + user: User; + filter: SearchFilter; + paging: PagingQuery; + }): Promise { + const { entities, total } = await this.search({ accountId, user, filter, paging }); + + return { + entities: await Promise.all( + entities.map(async (entity) => await this.entityInfoService.getEntityInfo({ user, entity, access: true })), + ), + meta: new PagingMeta(paging.skip + paging.take, total), + }; + } + + async searchForCall({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: SearchFilter; + }): Promise { + const { entities } = await this.search({ accountId, user, filter, paging: { skip: 0, take: 1 } }); + const entity = entities.length + ? await this.entityInfoService.getEntityInfo({ user, entity: entities[0], access: true }) + : null; + + if (entity) { + const linked = await this.getLinkedForCall({ accountId, user, entityId: entity.id }); + + return { entity, linked }; + } + + return { entity }; + } + + async search({ + accountId, + user, + filter, + paging, + }: { + accountId: number; + user: User; + filter: SearchFilter; + paging: PagingQuery; + }): Promise<{ entities: Entity[]; total: number }> { + const qb = this.repository.createQueryBuilder('entity').where('entity.account_id = :accountId', { accountId }); + + const accessibleEntityTypes = await this.entityTypeService.getAccessibleForUser(accountId, user); + const entityTypeIds = filter.entityTypeId + ? Array.isArray(filter.entityTypeId) + ? filter.entityTypeId + : [filter.entityTypeId] + : null; + const entityTypes = entityTypeIds + ? accessibleEntityTypes.filter((et) => entityTypeIds.includes(et.id)) + : accessibleEntityTypes; + + if (entityTypes.length) { + const etWithResponsible = await Promise.all( + entityTypes.map(async (entityType) => ({ + entityType, + responsibles: await this.authService.whoCanView({ user, authorizable: entityType }), + })), + ); + qb.andWhere( + new Brackets((qb1) => { + etWithResponsible.forEach((et) => + qb1.orWhere( + new Brackets((qb2) => { + qb2.where(`entity.entity_type_id = ${et.entityType.id}`); + if (et.responsibles) { + qb2.andWhere( + new Brackets((qb3) => { + qb3.where(`entity.created_by = ${user.id}`); + if (et.responsibles.length) { + qb3.orWhere(`entity.responsible_user_id IN (${et.responsibles.join(',')})`); + } + }), + ); + } + }), + ), + ); + }), + ); + } else { + return { entities: [], total: 0 }; + } + + const nameFilter = buildSearchParams(filter.name); + if (nameFilter?.length && filter.searchInLinked) { + qb.addCommonTableExpression( + this.dataSource + .createQueryBuilder() + .select('link.source_id', 'entity_id') + .from('entity_link', 'link') + .leftJoin('entity', 'linked', 'linked.id = link.target_id') + .where(`linked.name_tsv @@ to_tsquery('${nameFilter}')`), + 'linked', + ); + } + + const fieldFilter = buildSearchParams(filter.fieldValue); + if (fieldFilter?.length) { + const fqb = this.dataSource + .createQueryBuilder() + .select('fv.entity_id') + .from('field_value', 'fv') + .where(`fv.payload_tsv @@ to_tsquery('${fieldFilter}')`); + if (filter.fieldType) { + fqb.andWhere(`fv.field_type = '${filter.fieldType}'`); + } + qb.addCommonTableExpression(fqb, 'field_values'); + if (filter.searchInLinked) { + qb.addCommonTableExpression( + this.dataSource + .createQueryBuilder() + .select('link.source_id', 'entity_id') + .from('entity_link', 'link') + .leftJoin('entity', 'linked', 'linked.id = link.target_id') + .where(`link.target_id IN (SELECT entity_id FROM field_values)`), + 'linked_field_values', + ); + } + } + + if (nameFilter?.length || fieldFilter?.length) { + qb.andWhere( + new Brackets((qb1) => { + if (nameFilter?.length) { + qb1.where('entity.name_tsv @@ to_tsquery(:name)', { name: nameFilter }); + } + if (fieldFilter?.length) { + qb1.orWhere('entity.id IN (SELECT entity_id FROM field_values)'); + } + if (filter.searchInLinked) { + if (nameFilter?.length) { + qb1.orWhere('entity.id IN (SELECT entity_id FROM linked)'); + } + if (fieldFilter?.length) { + qb1.orWhere('entity.id IN (SELECT entity_id FROM linked_field_values)'); + } + } + }), + ); + } else if (filter.name?.length || filter.fieldValue?.length) { + qb.andWhere('1 = 0'); + } + + if (filter.excludeEntityId) { + if (Array.isArray(filter.excludeEntityId)) { + qb.andWhere('entity.id NOT IN (:...excludeEntityIds)', { excludeEntityIds: filter.excludeEntityId }); + } else { + qb.andWhere('entity.id != :excludeEntityId', { excludeEntityId: filter.excludeEntityId }); + } + } + + if (filter.boardId) { + if (Array.isArray(filter.boardId)) { + qb.andWhere('entity.board_id IN (:...boardIds)', { boardIds: filter.boardId }); + } else { + qb.andWhere('entity.board_id = :boardId', { boardId: filter.boardId }); + } + } + + const total = await qb.getCount(); + const entities = await qb + .offset(paging.skip) + .limit(paging.take) + .orderBy('entity.created_at', 'DESC') + .addOrderBy('entity.id', 'DESC') + .getMany(); + + return { entities, total }; + } + + private async getLinkedForCall({ accountId, user, entityId }: { accountId: number; user: User; entityId: number }) { + const entity = await this.entityService.findLastLinkedEntity({ + accountId, + entityId: entityId, + category: EntityCategory.DEAL, + }); + + return entity ? this.entityInfoService.getEntityInfo({ user, entity, access: true }) : null; + } +} diff --git a/backend/src/CRM/entity-search/index.ts b/backend/src/CRM/entity-search/index.ts new file mode 100644 index 0000000..0c7a0f3 --- /dev/null +++ b/backend/src/CRM/entity-search/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './entity-search.controller'; +export * from './entity-search.service'; diff --git a/backend/src/CRM/entity-type-link/dto/create-entity-type-link.dto.ts b/backend/src/CRM/entity-type-link/dto/create-entity-type-link.dto.ts new file mode 100644 index 0000000..ca8479a --- /dev/null +++ b/backend/src/CRM/entity-type-link/dto/create-entity-type-link.dto.ts @@ -0,0 +1,10 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; +import { EntityTypeLinkDto } from './entity-type-link.dto'; + +export class CreateEntityTypeLinkDto extends PickType(EntityTypeLinkDto, ['targetId'] as const) { + @ApiPropertyOptional({ description: 'Sort order' }) + @IsOptional() + @IsNumber() + sortOrder?: number; +} diff --git a/backend/src/CRM/entity-type-link/dto/entity-type-link.dto.ts b/backend/src/CRM/entity-type-link/dto/entity-type-link.dto.ts new file mode 100644 index 0000000..f65bb14 --- /dev/null +++ b/backend/src/CRM/entity-type-link/dto/entity-type-link.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class EntityTypeLinkDto { + @ApiProperty({ description: 'Source entity type ID' }) + @IsNumber() + sourceId: number; + + @ApiProperty({ description: 'Target entity type ID' }) + @IsNumber() + targetId: number; + + @ApiProperty({ description: 'Sort order' }) + @IsNumber() + sortOrder: number; +} diff --git a/backend/src/CRM/entity-type-link/dto/index.ts b/backend/src/CRM/entity-type-link/dto/index.ts new file mode 100644 index 0000000..9403b0f --- /dev/null +++ b/backend/src/CRM/entity-type-link/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-entity-type-link.dto'; +export * from './entity-type-link.dto'; +export * from './update-entity-type-link.dto'; diff --git a/backend/src/CRM/entity-type-link/dto/update-entity-type-link.dto.ts b/backend/src/CRM/entity-type-link/dto/update-entity-type-link.dto.ts new file mode 100644 index 0000000..689248c --- /dev/null +++ b/backend/src/CRM/entity-type-link/dto/update-entity-type-link.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { EntityTypeLinkDto } from './entity-type-link.dto'; + +export class UpdateEntityTypeLinkDto extends PickType(EntityTypeLinkDto, ['targetId', 'sortOrder'] as const) {} diff --git a/backend/src/CRM/entity-type-link/entities/entity-type-link.entity.ts b/backend/src/CRM/entity-type-link/entities/entity-type-link.entity.ts new file mode 100644 index 0000000..37cf617 --- /dev/null +++ b/backend/src/CRM/entity-type-link/entities/entity-type-link.entity.ts @@ -0,0 +1,41 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { CreateEntityTypeLinkDto, EntityTypeLinkDto } from '../dto'; + +@Entity() +export class EntityTypeLink { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sourceId: number; + + @Column() + targetId: number; + + @Column() + sortOrder: number; + + @Column() + accountId: number; + + constructor(accountId: number, sourceId: number, targetId: number, sortOrder: number) { + this.accountId = accountId; + this.sourceId = sourceId; + this.targetId = targetId; + this.sortOrder = sortOrder; + } + + public static fromDto(accountId: number, sourceId: number, dto: CreateEntityTypeLinkDto): EntityTypeLink { + return new EntityTypeLink(accountId, sourceId, dto.targetId, dto.sortOrder); + } + + public update({ sortOrder }: { sortOrder?: number }): EntityTypeLink { + this.sortOrder = sortOrder !== undefined ? sortOrder : this.sortOrder; + + return this; + } + + public toDto(): EntityTypeLinkDto { + return { sourceId: this.sourceId, targetId: this.targetId, sortOrder: this.sortOrder }; + } +} diff --git a/backend/src/CRM/entity-type-link/entities/index.ts b/backend/src/CRM/entity-type-link/entities/index.ts new file mode 100644 index 0000000..e281727 --- /dev/null +++ b/backend/src/CRM/entity-type-link/entities/index.ts @@ -0,0 +1 @@ +export * from './entity-type-link.entity'; diff --git a/backend/src/CRM/entity-type-link/entity-type-link.service.ts b/backend/src/CRM/entity-type-link/entity-type-link.service.ts new file mode 100644 index 0000000..9a58d4b --- /dev/null +++ b/backend/src/CRM/entity-type-link/entity-type-link.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { CreateEntityTypeLinkDto } from './dto'; +import { EntityTypeLink } from './entities'; + +interface FindFilter { + accountId: number; + sourceId?: number; + targetId?: number; +} + +@Injectable() +export class EntityTypeLinkService { + constructor( + @InjectRepository(EntityTypeLink) + private repository: Repository, + ) {} + + public async create({ + accountId, + sourceId, + dto, + createBackLink = true, + }: { + accountId: number; + sourceId: number; + dto: CreateEntityTypeLinkDto; + createBackLink?: boolean; + }): Promise { + dto.sortOrder ??= (await this.getMaxSortOrder({ accountId, sourceId })) + 1; + + const link = await this.repository.save(EntityTypeLink.fromDto(accountId, sourceId, dto)); + if (createBackLink) { + await this.create({ accountId, sourceId: dto.targetId, dto: { targetId: sourceId }, createBackLink: false }); + } + return link; + } + + public async findMany(filter: FindFilter): Promise { + return this.repository.find({ where: filter, order: { sortOrder: 'ASC' } }); + } + + public async processMany({ + accountId, + sourceId, + dtos, + }: { + accountId: number; + sourceId: number; + dtos: CreateEntityTypeLinkDto[]; + }): Promise { + const existingLinks = await this.findMany({ accountId, sourceId }); + + const deleted = existingLinks.filter((link) => !dtos.some((dto) => dto.targetId === link.targetId)); + if (deleted.length) { + await this.repository.delete(deleted.map((d) => d.id)); + await Promise.all( + deleted.map(async (link) => { + await this.repository.delete({ accountId, sourceId: link.targetId, targetId: sourceId }); + }), + ); + } + + return Promise.all( + dtos.map(async (dto) => { + const existingLink = existingLinks.find((link) => link.targetId === dto.targetId); + if (existingLink) { + return this.repository.save(existingLink.update({ sortOrder: dto.sortOrder })); + } else { + return this.create({ accountId, sourceId, dto }); + } + }), + ); + } + + private async getMaxSortOrder({ accountId, sourceId }: { accountId: number; sourceId: number }): Promise { + const result = await this.repository + .createQueryBuilder('etl') + .select('max(etl.sort_order)', 'max') + .where('etl.account_id = :accountId', { accountId }) + .andWhere('etl.source_id = :sourceId', { sourceId }) + .getRawOne<{ max: number }>(); + return result?.max ?? 0; + } +} diff --git a/backend/src/CRM/entity-type/dto/create-entity-type.dto.ts b/backend/src/CRM/entity-type/dto/create-entity-type.dto.ts new file mode 100644 index 0000000..8cecc24 --- /dev/null +++ b/backend/src/CRM/entity-type/dto/create-entity-type.dto.ts @@ -0,0 +1,68 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; + +import { UpdateFieldGroupDto } from '@/modules/entity/entity-field/field-group/dto/update-field-group.dto'; +import { FieldsSettingsDto } from '@/modules/entity/entity-field/field/dto/fields-settings.dto'; +import { CreateFieldDto } from '@/modules/entity/entity-field/field/dto/create-field.dto'; + +import { EntityCategory } from '../../common'; +import { CreateEntityTypeLinkDto } from '../../entity-type-link/dto'; +import { FeatureCode } from '../../feature'; +import { TaskFieldCode } from '../../task-settings/enums/task-field-code.enum'; + +import { EntityTypeSectionDto } from './entity-type-section.dto'; + +export class CreateEntityTypeDto { + @ApiProperty({ description: 'Entity type name' }) + @IsString() + name: string; + + @ApiProperty({ enum: EntityCategory, description: 'Entity type category' }) + @IsEnum(EntityCategory) + entityCategory: EntityCategory; + + @ApiProperty({ type: EntityTypeSectionDto, description: 'Entity type section info' }) + @IsObject() + section: EntityTypeSectionDto; + + @ApiProperty({ type: [UpdateFieldGroupDto], description: 'Entity type field groups' }) + @IsArray() + fieldGroups: UpdateFieldGroupDto[]; + + @ApiProperty({ type: [CreateFieldDto], description: 'Entity type fields' }) + @IsArray() + fields: CreateFieldDto[]; + + @ApiProperty({ type: [CreateEntityTypeLinkDto], description: 'Linked entity types' }) + @IsArray() + linkedEntityTypes: CreateEntityTypeLinkDto[]; + + @ApiProperty({ enum: FeatureCode, isArray: true, description: 'Feature codes' }) + @IsArray() + featureCodes: FeatureCode[]; + + @ApiPropertyOptional({ enum: TaskFieldCode, isArray: true, description: 'Task settings active fields' }) + @IsOptional() + @IsArray() + taskSettingsActiveFields: TaskFieldCode[]; + + @ApiPropertyOptional({ type: FieldsSettingsDto, description: 'Fields settings' }) + @IsOptional() + @IsObject() + fieldsSettings?: FieldsSettingsDto; + + @ApiPropertyOptional({ required: false, nullable: true, description: 'Sort order' }) + @IsOptional() + @IsNumber() + sortOrder?: number | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Linked products section IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + linkedProductsSectionIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Linked scheduler IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + linkedSchedulerIds?: number[] | null; +} diff --git a/backend/src/CRM/entity-type/dto/entity-type-section.dto.ts b/backend/src/CRM/entity-type/dto/entity-type-section.dto.ts new file mode 100644 index 0000000..556cbe2 --- /dev/null +++ b/backend/src/CRM/entity-type/dto/entity-type-section.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsString } from 'class-validator'; + +import { SectionView } from '../enums'; + +export class EntityTypeSectionDto { + @ApiProperty({ description: 'Section name' }) + @IsString() + name: string; + + @ApiProperty({ enum: SectionView, description: 'Section view' }) + @IsEnum(SectionView) + view: SectionView; + + @ApiProperty({ description: 'Section icon' }) + @IsString() + icon: string; +} diff --git a/backend/src/CRM/entity-type/dto/entity-type.dto.ts b/backend/src/CRM/entity-type/dto/entity-type.dto.ts new file mode 100644 index 0000000..75c7c98 --- /dev/null +++ b/backend/src/CRM/entity-type/dto/entity-type.dto.ts @@ -0,0 +1,91 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { FieldDto } from '@/modules/entity/entity-field/field/dto/field.dto'; +import { FieldGroupDto } from '@/modules/entity/entity-field/field-group/dto/field-group.dto'; + +import { EntityCategory } from '../../common'; +import { EntityTypeLinkDto } from '../../entity-type-link/dto'; +import { FeatureCode } from '../../feature'; + +import { EntityTypeSectionDto } from './entity-type-section.dto'; + +export class EntityTypeDto { + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Entity type name' }) + @IsString() + name: string; + + @ApiProperty({ enum: EntityCategory, description: 'Entity type category' }) + @IsEnum(EntityCategory) + entityCategory: EntityCategory; + + @ApiProperty({ type: EntityTypeSectionDto, description: 'Entity type section info' }) + section: EntityTypeSectionDto; + + @ApiProperty({ type: [FieldGroupDto], description: 'Entity type field groups' }) + @IsArray() + fieldGroups: FieldGroupDto[]; + + @ApiProperty({ type: [FieldDto], description: 'Entity type fields' }) + @IsArray() + fields: FieldDto[]; + + @ApiProperty({ type: [EntityTypeLinkDto], description: 'Linked entity types' }) + @IsArray() + linkedEntityTypes: EntityTypeLinkDto[]; + + @ApiProperty({ enum: FeatureCode, isArray: true, description: 'Feature codes' }) + @IsArray() + featureCodes: FeatureCode[]; + + @ApiPropertyOptional({ description: 'Sort order' }) + @IsOptional() + @IsNumber() + sortOrder?: number | null; + + @ApiProperty({ description: 'Created at in ISO format' }) + @IsString() + createdAt: string; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Linked products section IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + linkedProductsSectionIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Linked scheduler IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + linkedSchedulerIds?: number[] | null; + + constructor({ + id, + name, + entityCategory, + section, + fieldGroups, + fields, + linkedEntityTypes, + featureCodes, + sortOrder, + createdAt, + linkedProductsSectionIds, + linkedSchedulerIds, + }: EntityTypeDto) { + this.id = id; + this.name = name; + this.entityCategory = entityCategory; + this.section = section; + this.fieldGroups = fieldGroups; + this.fields = fields; + this.linkedEntityTypes = linkedEntityTypes; + this.featureCodes = featureCodes; + this.sortOrder = sortOrder; + this.createdAt = createdAt; + this.linkedProductsSectionIds = linkedProductsSectionIds; + this.linkedSchedulerIds = linkedSchedulerIds; + } +} diff --git a/backend/src/CRM/entity-type/dto/index.ts b/backend/src/CRM/entity-type/dto/index.ts new file mode 100644 index 0000000..bf7eabd --- /dev/null +++ b/backend/src/CRM/entity-type/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-entity-type.dto'; +export * from './entity-type-section.dto'; +export * from './entity-type.dto'; +export * from './update-entity-type-fields.dto'; +export * from './update-entity-type.dto'; diff --git a/backend/src/CRM/entity-type/dto/update-entity-type-fields.dto.ts b/backend/src/CRM/entity-type/dto/update-entity-type-fields.dto.ts new file mode 100644 index 0000000..b6cb9a9 --- /dev/null +++ b/backend/src/CRM/entity-type/dto/update-entity-type-fields.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { UpdateFieldGroupDto } from '@/modules/entity/entity-field/field-group/dto/update-field-group.dto'; +import { UpdateFieldDto } from '@/modules/entity/entity-field/field/dto/update-field.dto'; + +export class UpdateEntityTypeFieldsDto { + @ApiProperty({ type: [UpdateFieldGroupDto], description: 'Field groups' }) + @IsArray() + fieldGroups: UpdateFieldGroupDto[]; + + @ApiProperty({ type: [UpdateFieldDto], description: 'Fields' }) + @IsArray() + fields: UpdateFieldDto[]; +} diff --git a/backend/src/CRM/entity-type/dto/update-entity-type.dto.ts b/backend/src/CRM/entity-type/dto/update-entity-type.dto.ts new file mode 100644 index 0000000..43625ba --- /dev/null +++ b/backend/src/CRM/entity-type/dto/update-entity-type.dto.ts @@ -0,0 +1,71 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; + +import { UpdateFieldGroupDto } from '@/modules/entity/entity-field/field-group/dto/update-field-group.dto'; +import { FieldsSettingsDto } from '@/modules/entity/entity-field/field/dto/fields-settings.dto'; +import { UpdateFieldDto } from '@/modules/entity/entity-field/field/dto/update-field.dto'; + +import { EntityCategory } from '../../common'; +import { CreateEntityTypeLinkDto } from '../../entity-type-link/dto'; +import { FeatureCode } from '../../feature'; +import { TaskFieldCode } from '../../task-settings/enums/task-field-code.enum'; + +import { EntityTypeSectionDto } from './entity-type-section.dto'; + +export class UpdateEntityTypeDto { + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Entity type name' }) + @IsString() + name: string; + + @ApiProperty({ enum: EntityCategory, description: 'Entity type category' }) + @IsEnum(EntityCategory) + entityCategory: EntityCategory; + + @ApiProperty({ type: EntityTypeSectionDto, description: 'Entity type section info' }) + section: EntityTypeSectionDto; + + @ApiProperty({ type: [UpdateFieldGroupDto], description: 'Entity type field groups' }) + @IsArray() + fieldGroups: UpdateFieldGroupDto[]; + + @ApiProperty({ type: [UpdateFieldDto], description: 'Entity type fields' }) + @IsArray() + fields: UpdateFieldDto[]; + + @ApiProperty({ type: [CreateEntityTypeLinkDto], description: 'Linked entity types' }) + @IsArray() + linkedEntityTypes: CreateEntityTypeLinkDto[]; + + @ApiProperty({ enum: FeatureCode, isArray: true, description: 'Feature codes' }) + @IsArray() + featureCodes: FeatureCode[]; + + @ApiPropertyOptional({ enum: TaskFieldCode, isArray: true, description: 'Task settings active fields' }) + @IsOptional() + @IsArray() + taskSettingsActiveFields: TaskFieldCode[]; + + @ApiPropertyOptional({ type: FieldsSettingsDto, description: 'Fields settings' }) + @IsOptional() + @IsObject() + fieldsSettings: FieldsSettingsDto; + + @ApiPropertyOptional({ description: 'Sort order' }) + @IsOptional() + @IsNumber() + sortOrder?: number; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Linked products section IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + linkedProductsSectionIds: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Linked scheduler IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + linkedSchedulerIds?: number[] | null; +} diff --git a/backend/src/CRM/entity-type/entities/entity-type.entity.ts b/backend/src/CRM/entity-type/entities/entity-type.entity.ts new file mode 100644 index 0000000..ab39e24 --- /dev/null +++ b/backend/src/CRM/entity-type/entities/entity-type.entity.ts @@ -0,0 +1,85 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; + +import { EntityCategory, PermissionObjectType } from '../../common'; +import { SectionView } from '../enums'; + +@Entity() +export class EntityType implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + name: string; + + @Column() + entityCategory: EntityCategory; + + @Column() + sectionName: string; + + @Column() + sectionView: SectionView; + + @Column() + sectionIcon: string; + + @Column() + sortOrder: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + name: string, + entityCategory: EntityCategory, + sectionName: string, + sectionView: SectionView, + sectionIcon: string, + sortOrder: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.name = name; + this.entityCategory = entityCategory; + this.sectionName = sectionName; + this.sectionView = sectionView; + this.sectionIcon = sectionIcon; + this.sortOrder = sortOrder; + this.createdAt = createdAt ?? DateUtil.now(); + } + + hasBoardSectionView(): boolean { + return this.sectionView === SectionView.BOARD; + } + + getAuthorizableObject(): AuthorizableObject { + return { type: PermissionObjectType.EntityType, id: this.id }; + } + static getAuthorizable(entityTypeId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.EntityType, id: entityTypeId }); + } + + public isProject() { + return this.entityCategory === EntityCategory.PROJECT; + } + + public static copy(accountId: number, et: EntityType): EntityType { + return new EntityType( + accountId, + et.name, + et.entityCategory, + et.sectionName, + et.sectionView, + et.sectionIcon, + et.sortOrder, + ); + } +} diff --git a/backend/src/CRM/entity-type/entities/index.ts b/backend/src/CRM/entity-type/entities/index.ts new file mode 100644 index 0000000..b714563 --- /dev/null +++ b/backend/src/CRM/entity-type/entities/index.ts @@ -0,0 +1 @@ +export * from './entity-type.entity'; diff --git a/backend/src/CRM/entity-type/entity-type.controller.ts b/backend/src/CRM/entity-type/entity-type.controller.ts new file mode 100644 index 0000000..2f357c5 --- /dev/null +++ b/backend/src/CRM/entity-type/entity-type.controller.ts @@ -0,0 +1,90 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { UserAccess } from '@/modules/iam/common/decorators/user-access.decorator'; + +import { EntityTypeDto, CreateEntityTypeDto, UpdateEntityTypeDto, UpdateEntityTypeFieldsDto } from './dto'; +import { EntityTypeService } from './entity-type.service'; + +@ApiTags('crm/entity-types') +@Controller('crm/entity-types') +@JwtAuthorized({ prefetch: { user: true } }) +export class EntityTypeController { + constructor(private readonly service: EntityTypeService) {} + + @ApiOperation({ summary: 'Create entity type', description: 'Create entity type' }) + @ApiBody({ type: CreateEntityTypeDto, description: 'Data for creating entity type' }) + @ApiCreatedResponse({ description: 'Created entity type', type: EntityTypeDto }) + @Post() + @UserAccess({ adminOnly: true }) + public async create( + @CurrentAuth() { accountId, user }: AuthData, + @Body() dto: CreateEntityTypeDto, + ): Promise { + return this.service.create(accountId, user, dto); + } + + @ApiOperation({ summary: 'Get entity types', description: 'Get all entity types' }) + @ApiOkResponse({ description: 'Entity types', type: [EntityTypeDto] }) + @Get() + public async getEntityTypes(@CurrentAuth() { accountId, user }: AuthData): Promise { + return this.service.getDtosByAccountId(accountId, user); + } + + @ApiOperation({ summary: 'Get entity type', description: 'Get entity type by id' }) + @ApiParam({ name: 'entityTypeId', description: 'Entity type ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Entity type', type: EntityTypeDto }) + @Get(':entityTypeId') + public async getEntityType( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + ): Promise { + return this.service.getDtoById(accountId, user, entityTypeId); + } + + @ApiOperation({ summary: 'Update entity type', description: 'Update entity type' }) + @ApiParam({ name: 'entityTypeId', description: 'Entity type ID', type: Number, required: true }) + @ApiBody({ type: UpdateEntityTypeDto, description: 'Data for updating entity type' }) + @ApiOkResponse({ description: 'Updated entity type', type: EntityTypeDto }) + @Put(':entityTypeId') + @UserAccess({ adminOnly: true }) + public async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() dto: UpdateEntityTypeDto, + ): Promise { + return this.service.update(accountId, user, entityTypeId, dto); + } + + @ApiOperation({ + summary: 'Update entity type fields and groups', + description: 'Update entity type fields and groups', + }) + @ApiParam({ name: 'entityTypeId', description: 'Entity type ID', type: Number, required: true }) + @ApiBody({ type: UpdateEntityTypeFieldsDto, description: 'Data for updating entity type fields and groups' }) + @ApiOkResponse({ description: 'Updated entity type', type: EntityTypeDto }) + @Put(':entityTypeId/fields') + @UserAccess({ adminOnly: true }) + public async updateEntityTypeFields( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() dto: UpdateEntityTypeFieldsDto, + ): Promise { + return this.service.updateFields(accountId, user, entityTypeId, dto); + } + + @ApiOperation({ summary: 'Delete entity type', description: 'Delete entity type' }) + @ApiParam({ name: 'entityTypeId', description: 'Entity type ID', type: Number, required: true }) + @ApiOkResponse() + @Delete(':entityTypeId') + @UserAccess({ adminOnly: true }) + public async delete( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + ) { + await this.service.delete(accountId, userId, entityTypeId); + } +} diff --git a/backend/src/CRM/entity-type/entity-type.service.ts b/backend/src/CRM/entity-type/entity-type.service.ts new file mode 100644 index 0000000..e20c4e0 --- /dev/null +++ b/backend/src/CRM/entity-type/entity-type.service.ts @@ -0,0 +1,403 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { ObjectPermissionService } from '@/modules/iam/object-permission/object-permission.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { ProductsSectionService } from '@/modules/inventory/products-section/services/products-section.service'; +import { ScheduleService } from '@/modules/scheduler/schedule/services/schedule.service'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldGroupService } from '@/modules/entity/entity-field/field-group/field-group.service'; +import { FieldCode } from '@/modules/entity/entity-field/field/enums/field-code.enum'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; + +import { CrmEventType, EntityCategory, EntityTypeEvent, PermissionObjectType, SortOrder } from '../common'; + +import { BoardService } from '../board/board.service'; +import { BoardType } from '../board'; +import { EntityTypeLinkService } from '../entity-type-link/entity-type-link.service'; +import { EntityTypeFeatureService, FeatureCode } from '../feature'; +import { TaskSettingsService } from '../task-settings/task-settings.service'; + +import { CreateEntityTypeDto, EntityTypeDto, UpdateEntityTypeDto, UpdateEntityTypeFieldsDto } from './dto'; +import { EntityType } from './entities'; +import { SectionView } from './enums'; +import { EntityTypeUsedInFormulaError } from './errors'; + +interface FindFilter { + id?: number | number[]; + name?: string; + category?: EntityCategory; +} + +@Injectable() +export class EntityTypeService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(EntityType) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly objectPermissionService: ObjectPermissionService, + private readonly fieldGroupService: FieldGroupService, + private readonly fieldService: FieldService, + private readonly entityTypeLinkService: EntityTypeLinkService, + private readonly entityTypeFeatureService: EntityTypeFeatureService, + private readonly taskSettingsService: TaskSettingsService, + @Inject(forwardRef(() => BoardService)) + private readonly boardService: BoardService, + private readonly productsSectionService: ProductsSectionService, + private readonly scheduleService: ScheduleService, + ) {} + + public async save(entityType: EntityType): Promise { + return await this.repository.save(entityType); + } + + public async create(accountId: number, user: User, dto: CreateEntityTypeDto): Promise { + const sectionView = dto.entityCategory === EntityCategory.PROJECT ? SectionView.BOARD : dto.section.view; + + const entityType = await this.repository.save( + new EntityType( + accountId, + dto.name, + dto.entityCategory, + dto.section.name, + sectionView, + dto.section.icon, + dto.sortOrder ?? (await this.getMaxSortOrder(accountId)), + ), + ); + + if (entityType.entityCategory === EntityCategory.PROJECT) { + await this.createProjectSystemFields(entityType.accountId, entityType); + } + + if (entityType.sectionView === SectionView.BOARD) { + await this.boardService.create({ + accountId, + user, + dto: { + name: entityType.name, + type: BoardType.EntityType, + recordId: entityType.id, + sortOrder: 0, + }, + }); + } + + await this.fieldGroupService.saveBatch({ accountId, entityTypeId: entityType.id, dtos: dto.fieldGroups }); + await this.fieldService.createMany({ accountId, entityTypeId: entityType.id, dtos: dto.fields }); + + await this.entityTypeLinkService.processMany({ accountId, sourceId: entityType.id, dtos: dto.linkedEntityTypes }); + await this.entityTypeFeatureService.setFeaturesForEntityType(accountId, entityType.id, dto.featureCodes); + + if (dto.linkedProductsSectionIds) { + await this.productsSectionService.linkSections(accountId, entityType.id, dto.linkedProductsSectionIds); + } + if (dto.linkedSchedulerIds) { + await this.scheduleService.linkEntityType(accountId, dto.linkedSchedulerIds, entityType.id); + } + + await this.createTaskSettingsIfNeeded(accountId, entityType.id, dto); + + if (entityType.entityCategory === EntityCategory.PROJECT) { + await this.fieldService.updateFieldsSettings({ + accountId, + entityTypeId: entityType.id, + activeFieldCodes: dto.fieldsSettings.activeFieldCodes, + }); + } + + return this.getDtoById(accountId, user, entityType.id); + } + + private async getMaxSortOrder(accountId: number): Promise { + const result = await this.repository + .createQueryBuilder('et') + .select('MAX(et.sortOrder)', 'max') + .where('et.accountId = :accountId', { accountId }) + .getRawOne(); + + return Math.min(Number(result.max ?? 0) + 1, SortOrder.Last); + } + + private async createTaskSettingsIfNeeded(accountId: number, entityTypeId: number, dto: CreateEntityTypeDto) { + if (dto.featureCodes.includes(FeatureCode.TASK)) { + await this.taskSettingsService.setTaskSettingsForEntityType( + accountId, + entityTypeId, + dto.taskSettingsActiveFields, + ); + } + } + + public async getById(accountId: number, id: number): Promise { + const entityType = await this.findOne(accountId, { id }); + if (!entityType) { + throw NotFoundError.withId(EntityType, id); + } + + return entityType; + } + + public async getDtoById(accountId: number, user: User, id: number): Promise { + const entityType = await this.getById(accountId, id); + + return this.createDto(accountId, user, entityType); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + public async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).orderBy('et.sort_order', 'ASC').getMany(); + } + + public async getDtosByAccountId(accountId: number, user: User): Promise { + const entityTypes = await this.findMany(accountId); + const dtos = []; + + for (const entityType of entityTypes) { + dtos.push(await this.getDtoById(accountId, user, entityType.id)); + } + + return dtos; + } + + public async getAccessibleForUser(accountId: number, user: User): Promise { + const entityTypes = await this.findMany(accountId); + const result: EntityType[] = []; + for (const et of entityTypes) { + if (await this.authService.check({ action: 'view', user, authorizable: et })) { + result.push(et); + } + } + return result; + } + + public async findLinkedTypes(accountId: number, entityTypeId: number): Promise { + const links = await this.entityTypeLinkService.findMany({ accountId, sourceId: entityTypeId }); + return this.repository.findBy({ id: In(links.map((l) => l.targetId)) }); + } + + public async update( + accountId: number, + user: User, + entityTypeId: number, + dto: UpdateEntityTypeDto, + ): Promise { + const links = await this.entityTypeLinkService.findMany({ accountId, sourceId: entityTypeId }); + const usedInFormula = await Promise.all( + links.map(async (link) => + !dto.linkedEntityTypes.some((linked) => linked.targetId === link.targetId) + ? await this.fieldService.checkFormulaUsageEntityType({ + accountId, + entityTypeId, + checkEntityTypeId: link.targetId, + }) + : false, + ), + ); + if (usedInFormula.some((usage) => usage)) { + throw new EntityTypeUsedInFormulaError(); + } + + await this.repository.update( + { id: entityTypeId }, + { + name: dto.name, + entityCategory: dto.entityCategory, + sectionName: dto.section.name, + sectionView: dto.section.view, + sectionIcon: dto.section.icon, + sortOrder: dto.sortOrder, + }, + ); + + await this.fieldGroupService.saveBatch({ accountId, entityTypeId, dtos: dto.fieldGroups }); + await this.fieldService.updateBatch({ accountId, entityTypeId, dtos: dto.fields }); + + await this.entityTypeLinkService.processMany({ accountId, sourceId: entityTypeId, dtos: dto.linkedEntityTypes }); + await this.entityTypeFeatureService.setFeaturesForEntityType(accountId, entityTypeId, dto.featureCodes); + + if (dto.linkedProductsSectionIds) { + await this.productsSectionService.linkSections(accountId, entityTypeId, dto.linkedProductsSectionIds); + } + if (dto.linkedSchedulerIds) { + await this.scheduleService.linkEntityType(accountId, dto.linkedSchedulerIds, entityTypeId); + } + + if (dto.featureCodes.includes(FeatureCode.TASK)) { + await this.taskSettingsService.setTaskSettingsForEntityType( + accountId, + entityTypeId, + dto.taskSettingsActiveFields, + ); + } + if (dto.entityCategory === EntityCategory.PROJECT && dto.fieldsSettings) { + await this.fieldService.updateFieldsSettings({ + accountId, + entityTypeId, + activeFieldCodes: dto.fieldsSettings.activeFieldCodes, + }); + } + + return this.getDtoById(accountId, user, entityTypeId); + } + + public async updateFields( + accountId: number, + user: User, + entityTypeId: number, + dto: UpdateEntityTypeFieldsDto, + ): Promise { + await this.fieldGroupService.saveBatch({ accountId, entityTypeId, dtos: dto.fieldGroups }); + await this.fieldService.updateBatch({ accountId, entityTypeId, dtos: dto.fields }); + + return this.getDtoById(accountId, user, entityTypeId); + } + + public async delete(accountId: number, userId: number, entityTypeId: number): Promise { + if ( + await this.fieldService.checkFormulaUsageEntityType({ + accountId, + excludeEntityTypeId: entityTypeId, + checkEntityTypeId: entityTypeId, + }) + ) { + throw new EntityTypeUsedInFormulaError(); + } + + await this.repository.delete({ id: entityTypeId, accountId }); + await this.objectPermissionService.delete({ + accountId, + objectType: PermissionObjectType.EntityType, + objectId: entityTypeId, + }); + this.eventEmitter.emit(CrmEventType.EntityTypeDeleted, new EntityTypeEvent({ accountId, userId, entityTypeId })); + } + + private async createDto(accountId: number, user: User, entityType: EntityType) { + const fieldGroups = await this.fieldGroupService.findMany({ accountId, entityTypeId: entityType.id }); + const fields = await this.fieldService.findMany( + { accountId, entityTypeId: entityType.id }, + { expand: ['options'] }, + ); + const links = await this.entityTypeLinkService.findMany({ accountId, sourceId: entityType.id }); + const featureCodes = await this.entityTypeFeatureService.getFeatureCodesForEntityType(accountId, entityType.id); + const sectionIds = await this.productsSectionService.getLinkedSectionIds(accountId, user, entityType.id); + const schedulerIds = await this.scheduleService.getLinkedSchedulerIds(accountId, { entityTypeId: entityType.id }); + + return new EntityTypeDto({ + id: entityType.id, + name: entityType.name, + entityCategory: entityType.entityCategory, + createdAt: entityType.createdAt.toISOString(), + section: { + name: entityType.sectionName, + view: entityType.sectionView, + icon: entityType.sectionIcon, + }, + sortOrder: entityType.sortOrder, + fieldGroups: fieldGroups.map((fieldGroup) => fieldGroup.toDto()), + fields: fields.map((field) => field.toDto()), + featureCodes: featureCodes, + linkedEntityTypes: links.map((link) => link.toDto()), + linkedProductsSectionIds: sectionIds, + linkedSchedulerIds: schedulerIds, + }); + } + + private async createProjectSystemFields(accountId: number, entityType: EntityType) { + await this.fieldService.create({ + accountId, + entityTypeId: entityType.id, + dto: { + name: 'Value', + type: FieldType.Value, + code: FieldCode.Value, + active: true, + sortOrder: -1, + entityTypeId: entityType.id, + fieldGroupId: null, + }, + }); + await this.fieldService.create({ + accountId, + entityTypeId: entityType.id, + dto: { + name: 'Start date', + type: FieldType.Date, + code: FieldCode.StartDate, + active: true, + sortOrder: -1, + entityTypeId: entityType.id, + fieldGroupId: null, + }, + }); + await this.fieldService.create({ + accountId, + entityTypeId: entityType.id, + dto: { + name: 'End date', + type: FieldType.Date, + code: FieldCode.EndDate, + active: true, + sortOrder: -1, + entityTypeId: entityType.id, + fieldGroupId: null, + }, + }); + await this.fieldService.create({ + accountId, + entityTypeId: entityType.id, + dto: { + name: 'Participants', + type: FieldType.Participants, + code: FieldCode.Participants, + active: true, + sortOrder: -1, + entityTypeId: entityType.id, + fieldGroupId: null, + }, + }); + await this.fieldService.create({ + accountId, + entityTypeId: entityType.id, + dto: { + name: 'Description', + type: FieldType.Text, + code: FieldCode.Description, + active: true, + sortOrder: -1, + entityTypeId: entityType.id, + fieldGroupId: null, + }, + }); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('et').where('et.account_id = :accountId', { accountId }); + + if (filter?.id) { + if (Array.isArray(filter.id)) { + qb.andWhere('et.id IN (:...ids)', { ids: filter.id }); + } else { + qb.andWhere('et.id = :id', { id: filter.id }); + } + } + if (filter?.name) { + qb.andWhere('et.name = :name', { name: filter.name }); + } + if (filter?.category) { + qb.andWhere('et.entity_category = :category', { category: filter.category }); + } + + return qb; + } +} diff --git a/backend/src/CRM/entity-type/enums/index.ts b/backend/src/CRM/entity-type/enums/index.ts new file mode 100644 index 0000000..e75e9ea --- /dev/null +++ b/backend/src/CRM/entity-type/enums/index.ts @@ -0,0 +1 @@ +export * from './section-view.enum'; diff --git a/backend/src/CRM/entity-type/enums/section-view.enum.ts b/backend/src/CRM/entity-type/enums/section-view.enum.ts new file mode 100644 index 0000000..3c98232 --- /dev/null +++ b/backend/src/CRM/entity-type/enums/section-view.enum.ts @@ -0,0 +1,5 @@ +export enum SectionView { + PIPELINE = 'pipeline', + LIST = 'list', + BOARD = 'board', +} diff --git a/backend/src/CRM/entity-type/errors/entity-type-used-in-formula.error.ts b/backend/src/CRM/entity-type/errors/entity-type-used-in-formula.error.ts new file mode 100644 index 0000000..70268cf --- /dev/null +++ b/backend/src/CRM/entity-type/errors/entity-type-used-in-formula.error.ts @@ -0,0 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class EntityTypeUsedInFormulaError extends ServiceError { + constructor(message = 'Entity type used in formula') { + super({ + errorCode: 'entity_type.used_in_formula', + status: HttpStatus.BAD_REQUEST, + message, + }); + } +} diff --git a/backend/src/CRM/entity-type/errors/index.ts b/backend/src/CRM/entity-type/errors/index.ts new file mode 100644 index 0000000..0894482 --- /dev/null +++ b/backend/src/CRM/entity-type/errors/index.ts @@ -0,0 +1 @@ +export * from './entity-type-used-in-formula.error'; diff --git a/backend/src/CRM/entity-type/index.ts b/backend/src/CRM/entity-type/index.ts new file mode 100644 index 0000000..61e2dd1 --- /dev/null +++ b/backend/src/CRM/entity-type/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './errors'; diff --git a/backend/src/CRM/feature/dto/feature.dto.ts b/backend/src/CRM/feature/dto/feature.dto.ts new file mode 100644 index 0000000..06b14a1 --- /dev/null +++ b/backend/src/CRM/feature/dto/feature.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsString } from 'class-validator'; + +export class FeatureDto { + @ApiProperty({ description: 'Feature ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Feature name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Feature code' }) + @IsString() + code: string; + + @ApiProperty({ description: 'Is feature enabled' }) + @IsBoolean() + isEnabled: boolean; + + constructor(id: number, name: string, code: string, isEnabled: boolean) { + this.id = id; + this.name = name; + this.code = code; + this.isEnabled = isEnabled; + } +} diff --git a/backend/src/CRM/feature/dto/index.ts b/backend/src/CRM/feature/dto/index.ts new file mode 100644 index 0000000..ad3e3d4 --- /dev/null +++ b/backend/src/CRM/feature/dto/index.ts @@ -0,0 +1 @@ +export * from './feature.dto'; diff --git a/backend/src/CRM/feature/entities/entity-type-feature.entity.ts b/backend/src/CRM/feature/entities/entity-type-feature.entity.ts new file mode 100644 index 0000000..63fd1f1 --- /dev/null +++ b/backend/src/CRM/feature/entities/entity-type-feature.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class EntityTypeFeature { + @PrimaryColumn() + entityTypeId: number; + + @PrimaryColumn() + featureId: number; + + @Column() + accountId: number; + + constructor(entityTypeId: number, featureId: number, accountId: number) { + this.entityTypeId = entityTypeId; + this.featureId = featureId; + this.accountId = accountId; + } +} diff --git a/backend/src/CRM/feature/entities/feature.entity.ts b/backend/src/CRM/feature/entities/feature.entity.ts new file mode 100644 index 0000000..ab663a0 --- /dev/null +++ b/backend/src/CRM/feature/entities/feature.entity.ts @@ -0,0 +1,29 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { FeatureDto } from '../dto'; + +@Entity() +export class Feature { + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column() + code: string; + + @Column() + isEnabled: boolean; + + constructor(id: number, name: string, code: string, isEnabled: boolean) { + this.id = id; + this.name = name; + this.code = code; + this.isEnabled = isEnabled; + } + + public toDto(): FeatureDto { + return new FeatureDto(this.id, this.name, this.code, this.isEnabled); + } +} diff --git a/backend/src/CRM/feature/entities/index.ts b/backend/src/CRM/feature/entities/index.ts new file mode 100644 index 0000000..3a2df5e --- /dev/null +++ b/backend/src/CRM/feature/entities/index.ts @@ -0,0 +1,2 @@ +export * from './entity-type-feature.entity'; +export * from './feature.entity'; diff --git a/backend/src/CRM/feature/entity-type-feature.service.ts b/backend/src/CRM/feature/entity-type-feature.service.ts new file mode 100644 index 0000000..aec9aea --- /dev/null +++ b/backend/src/CRM/feature/entity-type-feature.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { EntityTypeFeature } from './entities'; +import { FeatureCode } from './enums'; +import { FeatureService } from './feature.service'; + +@Injectable() +export class EntityTypeFeatureService { + constructor( + @InjectRepository(EntityTypeFeature) + private repository: Repository, + private featureService: FeatureService, + ) {} + + public async findByEntityTypeId(accountId: number, entityTypeId: number): Promise { + return await this.repository.findBy({ accountId, entityTypeId }); + } + + public async getFeatureCodesForEntityType(accountId: number, entityTypeId: number): Promise { + const etfs = await this.findByEntityTypeId(accountId, entityTypeId); + const features = await this.featureService.getByIds(etfs.map((etf) => etf.featureId)); + + return features.map((feature) => feature.code as FeatureCode); + } + + public async setEntityTypeFeatures(accountId: number, entityTypeId: number, featureIds: number[]): Promise { + await this.repository.delete({ entityTypeId }); + await this.repository.insert( + featureIds.map((featureId) => new EntityTypeFeature(entityTypeId, featureId, accountId)), + ); + return featureIds; + } + + public async setFeaturesForEntityType( + accountId: number, + entityTypeId: number, + featureCodes: FeatureCode[], + ): Promise { + const features = await this.featureService.getByCodes(featureCodes); + const featureIds = features.map((feature) => feature.id); + + return await this.setEntityTypeFeatures(accountId, entityTypeId, featureIds); + } + + public async setFeatureForEntityTypes( + accountId: number, + entityTypes: number[], + featureCode: FeatureCode, + ): Promise { + const feature = await this.featureService.getByCode(featureCode); + + await this.repository.delete({ featureId: feature.id }); + + const features = await this.repository.save( + entityTypes.map((entityTypeId) => new EntityTypeFeature(entityTypeId, feature.id, accountId)), + ); + + return features.length > 0; + } + + public async getEntityTypeIdsWithFeature(accountId: number, featureCode: FeatureCode): Promise { + const feature = await this.featureService.getByCode(featureCode); + + return (await this.repository.findBy({ accountId, featureId: feature.id })).map((etf) => etf.entityTypeId); + } +} diff --git a/backend/src/CRM/feature/enums/feature-code.enum.ts b/backend/src/CRM/feature/enums/feature-code.enum.ts new file mode 100644 index 0000000..7d080c8 --- /dev/null +++ b/backend/src/CRM/feature/enums/feature-code.enum.ts @@ -0,0 +1,9 @@ +export enum FeatureCode { + ACTIVITY = 'activities', + TASK = 'tasks', + NOTE = 'notes', + CHAT = 'chat', + ENTITY_FILES = 'saveFiles', + ENTITY_DOCUMENTS = 'documents', + PRODUCTS = 'products', +} diff --git a/backend/src/CRM/feature/enums/index.ts b/backend/src/CRM/feature/enums/index.ts new file mode 100644 index 0000000..ef7a5c2 --- /dev/null +++ b/backend/src/CRM/feature/enums/index.ts @@ -0,0 +1 @@ +export * from './feature-code.enum'; diff --git a/backend/src/CRM/feature/feature.controller.ts b/backend/src/CRM/feature/feature.controller.ts new file mode 100644 index 0000000..900615c --- /dev/null +++ b/backend/src/CRM/feature/feature.controller.ts @@ -0,0 +1,23 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FeatureDto } from './dto'; +import { FeatureService } from './feature.service'; + +@ApiTags('crm/features') +@Controller('crm/features') +@JwtAuthorized() +@TransformToDto() +export class FeatureController { + constructor(private readonly service: FeatureService) {} + + @ApiOperation({ summary: 'Get features', description: 'Get all features' }) + @ApiOkResponse({ description: 'Features', type: [FeatureDto] }) + @Get() + public async getEnabled() { + return this.service.getEnabled(); + } +} diff --git a/backend/src/CRM/feature/feature.service.ts b/backend/src/CRM/feature/feature.service.ts new file mode 100644 index 0000000..41e1501 --- /dev/null +++ b/backend/src/CRM/feature/feature.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { Feature } from './entities'; +import { FeatureCode } from './enums'; + +@Injectable() +export class FeatureService { + constructor( + @InjectRepository(Feature) + private featureRepository: Repository, + ) {} + + public async getEnabled(): Promise { + return await this.featureRepository.find({ where: { isEnabled: true }, order: { id: 'ASC' } }); + } + + public async getByCode(code: FeatureCode): Promise { + const feature = this.featureRepository.findOneBy({ code }); + if (!feature) { + throw NotFoundError.withMessage(Feature, `with code '${code}' is not found`); + } + + return feature; + } + + public async getByCodes(codes: FeatureCode[]): Promise { + return await this.featureRepository.findBy({ code: In(codes) }); + } + + public async getByIds(ids: number[]): Promise { + return await this.featureRepository.findBy({ id: In(ids) }); + } +} diff --git a/backend/src/CRM/feature/index.ts b/backend/src/CRM/feature/index.ts new file mode 100644 index 0000000..8b54001 --- /dev/null +++ b/backend/src/CRM/feature/index.ts @@ -0,0 +1,6 @@ +export * from './dto'; +export * from './entities'; +export * from './entity-type-feature.service'; +export * from './enums'; +export * from './feature.controller'; +export * from './feature.service'; diff --git a/backend/src/CRM/identity/dto/identity-pool.dto.ts b/backend/src/CRM/identity/dto/identity-pool.dto.ts new file mode 100644 index 0000000..204bbd8 --- /dev/null +++ b/backend/src/CRM/identity/dto/identity-pool.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +import { SequenceName } from '../../common'; + +export class IdentityPoolDto { + @ApiProperty({ enum: SequenceName, description: 'Identity pool name' }) + @IsString() + name: SequenceName; + + @ApiProperty({ description: 'Identity pool values', type: [Number] }) + @IsNumber({}, { each: true }) + values: number[]; +} diff --git a/backend/src/CRM/identity/dto/index.ts b/backend/src/CRM/identity/dto/index.ts new file mode 100644 index 0000000..a9f0011 --- /dev/null +++ b/backend/src/CRM/identity/dto/index.ts @@ -0,0 +1 @@ +export * from './identity-pool.dto'; diff --git a/backend/src/CRM/identity/identity.controller.ts b/backend/src/CRM/identity/identity.controller.ts new file mode 100644 index 0000000..0e864bf --- /dev/null +++ b/backend/src/CRM/identity/identity.controller.ts @@ -0,0 +1,32 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { SequenceName } from '../common'; +import { IdentityPoolDto } from './dto'; +import { IdentityService } from './identity.service'; + +@ApiTags('crm/identity') +@Controller('crm/identities') +@JwtAuthorized() +@TransformToDto() +export class IdentityController { + constructor(private readonly service: IdentityService) {} + + @ApiOperation({ summary: 'Get all available identity pools', description: 'Get all available identity pools' }) + @ApiOkResponse({ type: [IdentityPoolDto], description: 'All available identity pools' }) + @Get('all') + public async getAllIdentityPools(): Promise { + return await this.service.getMany(); + } + + @ApiOperation({ summary: 'Get identity pool for sequence name', description: 'Get identity pool for sequence name' }) + @ApiParam({ name: 'name', enum: SequenceName, description: 'Sequence name', required: true }) + @ApiOkResponse({ type: [Number], description: 'Identity pool values' }) + @Get(':name') + public async getIdentityPool(@Param('name') name: SequenceName): Promise { + return await this.service.getOne(name); + } +} diff --git a/backend/src/CRM/identity/identity.service.ts b/backend/src/CRM/identity/identity.service.ts new file mode 100644 index 0000000..861a462 --- /dev/null +++ b/backend/src/CRM/identity/identity.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; + +import { NotFoundError } from '@/common'; +import { SequenceIdService } from '@/database'; + +import { SequenceName } from '../common'; +import { IdentityPool } from './types'; + +const DefaultPoolSize = 20; + +@Injectable() +export class IdentityService { + constructor(private readonly sequenceIdService: SequenceIdService) {} + + public async getOne(sequence: SequenceName): Promise { + if (!Object.values(SequenceName).includes(sequence)) { + throw new NotFoundError(`Sequence with name '${sequence}' is not found`); + } else { + return await this.sequenceIdService.getIdentityPool(sequence, DefaultPoolSize); + } + } + + public async getMany(): Promise { + const sequences = Object.values(SequenceName); + const pool: IdentityPool[] = []; + for (const sequence of sequences) { + const values = await this.getOne(sequence); + pool.push(new IdentityPool({ name: sequence, values })); + } + return pool; + } +} diff --git a/backend/src/CRM/identity/types/identity-pool.ts b/backend/src/CRM/identity/types/identity-pool.ts new file mode 100644 index 0000000..6f087c8 --- /dev/null +++ b/backend/src/CRM/identity/types/identity-pool.ts @@ -0,0 +1,16 @@ +import { SequenceName } from '../../common'; +import { IdentityPoolDto } from '../dto'; + +export class IdentityPool { + name: SequenceName; + values: number[]; + + constructor({ name, values }: { name: SequenceName; values: number[] }) { + this.name = name; + this.values = values; + } + + public toDto(): IdentityPoolDto { + return { name: this.name, values: this.values }; + } +} diff --git a/backend/src/CRM/identity/types/index.ts b/backend/src/CRM/identity/types/index.ts new file mode 100644 index 0000000..2fa162a --- /dev/null +++ b/backend/src/CRM/identity/types/index.ts @@ -0,0 +1 @@ +export * from './identity-pool'; diff --git a/backend/src/CRM/note/dto/create-note.dto.ts b/backend/src/CRM/note/dto/create-note.dto.ts new file mode 100644 index 0000000..57ba1a0 --- /dev/null +++ b/backend/src/CRM/note/dto/create-note.dto.ts @@ -0,0 +1,10 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +import { NoteDto } from './note.dto'; + +export class CreateNoteDto extends PickType(NoteDto, ['text']) { + @ApiPropertyOptional({ type: [String], nullable: true, description: 'File IDs' }) + @IsOptional() + fileIds?: string[] | null; +} diff --git a/backend/src/CRM/note/dto/index.ts b/backend/src/CRM/note/dto/index.ts new file mode 100644 index 0000000..db33b34 --- /dev/null +++ b/backend/src/CRM/note/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-note.dto'; +export * from './note.dto'; +export * from './update-note.dto'; diff --git a/backend/src/CRM/note/dto/note.dto.ts b/backend/src/CRM/note/dto/note.dto.ts new file mode 100644 index 0000000..9b48051 --- /dev/null +++ b/backend/src/CRM/note/dto/note.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; + +export class NoteDto { + @ApiProperty({ description: 'Note ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Entity ID' }) + @IsNumber() + entityId: number; + + @ApiProperty({ description: 'User ID' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ description: 'Note text' }) + @IsString() + text: string; + + @ApiProperty({ description: 'Note creation date' }) + @IsDateString() + createdAt: string; + + @ApiPropertyOptional({ type: [FileLinkDto], nullable: true, description: 'File links' }) + @IsOptional() + fileLinks?: FileLinkDto[] | null; +} diff --git a/backend/src/CRM/note/dto/update-note.dto.ts b/backend/src/CRM/note/dto/update-note.dto.ts new file mode 100644 index 0000000..b22973a --- /dev/null +++ b/backend/src/CRM/note/dto/update-note.dto.ts @@ -0,0 +1,10 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +import { NoteDto } from './note.dto'; + +export class UpdateNoteDto extends PickType(NoteDto, ['text']) { + @ApiPropertyOptional({ type: [String], nullable: true, description: 'File IDs' }) + @IsOptional() + fileIds?: string[] | null; +} diff --git a/backend/src/CRM/note/entities/index.ts b/backend/src/CRM/note/entities/index.ts new file mode 100644 index 0000000..511e546 --- /dev/null +++ b/backend/src/CRM/note/entities/index.ts @@ -0,0 +1 @@ +export * from './note.entity'; diff --git a/backend/src/CRM/note/entities/note.entity.ts b/backend/src/CRM/note/entities/note.entity.ts new file mode 100644 index 0000000..56f25c5 --- /dev/null +++ b/backend/src/CRM/note/entities/note.entity.ts @@ -0,0 +1,52 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; +import { NoteDto, UpdateNoteDto } from '../dto'; + +@Entity('note') +export class Note { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + entityId: number; + + @Column() + createdBy: number; + + @Column() + text: string; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, entityId: number, userId: number, text: string, createdAt?: Date) { + this.accountId = accountId; + this.entityId = entityId; + this.createdBy = userId; + this.text = text; + this.createdAt = createdAt ?? DateUtil.now(); + } + + update(dto: UpdateNoteDto): Note { + this.text = dto.text !== undefined ? dto.text : this.text; + + return this; + } + + toDto(fileLinks?: FileLinkDto[] | null): NoteDto { + return { + id: this.id, + entityId: this.entityId, + createdBy: this.createdBy, + text: this.text, + createdAt: this.createdAt.toISOString(), + fileLinks: fileLinks, + }; + } +} diff --git a/backend/src/CRM/note/index.ts b/backend/src/CRM/note/index.ts new file mode 100644 index 0000000..4001c9f --- /dev/null +++ b/backend/src/CRM/note/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './note.controller'; +export * from './note.handler'; +export * from './note.service'; diff --git a/backend/src/CRM/note/note.controller.ts b/backend/src/CRM/note/note.controller.ts new file mode 100644 index 0000000..74d0e0c --- /dev/null +++ b/backend/src/CRM/note/note.controller.ts @@ -0,0 +1,96 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CreateNoteDto, NoteDto, UpdateNoteDto } from './dto'; +import { NoteService } from './note.service'; + +@ApiTags('crm/entities/notes') +@Controller('crm/entities/:entityId/notes') +@JwtAuthorized({ prefetch: { account: true } }) +@TransformToDto() +export class NoteController { + constructor(private readonly service: NoteService) {} + + @ApiOperation({ summary: 'Create entity note', description: 'Create entity note' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiBody({ type: CreateNoteDto, required: true, description: 'Entity note data' }) + @ApiCreatedResponse({ description: 'Entity note', type: NoteDto }) + @Post() + async create( + @CurrentAuth() { account, userId }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + @Body() dto: CreateNoteDto, + ): Promise { + return this.service.createAndGetDto({ account, userId, entityId, dto }); + } + + @ApiOperation({ summary: 'Get entity note', description: 'Get entity note' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiParam({ name: 'noteId', type: Number, required: true, description: 'Entity note ID' }) + @ApiOkResponse({ description: 'Entity note', type: NoteDto }) + @Get(':noteId') + async findOne( + @CurrentAuth() { account }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + @Param('noteId', ParseIntPipe) noteId: number, + ): Promise { + return this.service.findOneDto({ account, filter: { entityId, noteId } }); + } + + @ApiOperation({ summary: 'Get entity notes', description: 'Get entity notes' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiOkResponse({ description: 'Entity notes', type: NoteDto }) + @Get() + async findMany( + @CurrentAuth() { account }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + ): Promise { + return this.service.findManyDto({ account, filter: { entityId } }); + } + + @ApiOperation({ summary: 'Update entity note', description: 'Update entity note' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiParam({ name: 'noteId', type: Number, required: true, description: 'Entity note ID' }) + @ApiBody({ type: UpdateNoteDto, required: true, description: 'Entity note data' }) + @ApiOkResponse({ description: 'Entity note', type: NoteDto }) + @Put(':noteId') + async updatePut( + @CurrentAuth() { account }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + @Param('noteId', ParseIntPipe) noteId: number, + @Body() dto: UpdateNoteDto, + ): Promise { + return this.service.updateAndGetDto({ account, entityId, noteId, dto }); + } + + @ApiOperation({ summary: 'Update entity note', description: 'Update entity note' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiParam({ name: 'noteId', type: Number, required: true, description: 'Entity note ID' }) + @ApiBody({ type: UpdateNoteDto, required: true, description: 'Entity note data' }) + @ApiOkResponse({ description: 'Entity note', type: NoteDto }) + @Patch(':noteId') + async updatePatch( + @CurrentAuth() { account }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + @Param('noteId', ParseIntPipe) noteId: number, + @Body() dto: UpdateNoteDto, + ): Promise { + return this.service.updateAndGetDto({ account, entityId, noteId, dto }); + } + + @ApiOperation({ summary: 'Delete entity note', description: 'Delete entity note' }) + @ApiParam({ name: 'entityId', type: Number, required: true, description: 'Entity ID' }) + @ApiParam({ name: 'noteId', type: Number, required: true, description: 'Entity note ID' }) + @ApiOkResponse() + @Delete(':noteId') + async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + @Param('noteId', ParseIntPipe) noteId: number, + ) { + await this.service.delete({ accountId, entityId, noteId }); + } +} diff --git a/backend/src/CRM/note/note.handler.ts b/backend/src/CRM/note/note.handler.ts new file mode 100644 index 0000000..610ff68 --- /dev/null +++ b/backend/src/CRM/note/note.handler.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { CrmEventType, EntityCreatedEvent } from '../common'; +import { NoteService } from './note.service'; + +@Injectable() +export class NoteHandler { + constructor(private readonly service: NoteService) {} + + @OnEvent(CrmEventType.EntityCreated, { async: true }) + async onEntityCreated(event: EntityCreatedEvent): Promise { + if (event.copiedFrom) { + await this.service.copyEntityNotes({ + accountId: event.accountId, + sourceEntityId: event.copiedFrom, + targetEntityId: event.entityId, + }); + } + } +} diff --git a/backend/src/CRM/note/note.service.ts b/backend/src/CRM/note/note.service.ts new file mode 100644 index 0000000..7d708c5 --- /dev/null +++ b/backend/src/CRM/note/note.service.ts @@ -0,0 +1,206 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { NotFoundError, FileLinkSource } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; + +import { CrmEventType, NoteCreatedEvent, NoteEvent } from '../common'; +import { FileLinkService } from '../Service/FileLink/FileLinkService'; + +import { Note } from './entities'; +import { CreateNoteDto, NoteDto, UpdateNoteDto } from './dto'; + +interface CreateOptions { + createdAt?: Date; +} +interface FindFilterDto { + noteId?: number; + entityId?: number; +} +interface FindFilter extends FindFilterDto { + accountId: number; +} + +@Injectable() +export class NoteService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Note) + private readonly repository: Repository, + private readonly fileLinkService: FileLinkService, + private readonly entityInfoService: EntityInfoService, + ) {} + + async create({ + accountId, + userId, + entityId, + dto, + options, + }: { + accountId: number; + userId: number; + entityId: number; + dto: CreateNoteDto; + options?: CreateOptions; + }): Promise { + const note = await this.repository.save(new Note(accountId, entityId, userId, dto.text, options?.createdAt)); + if (dto.fileIds) { + await this.fileLinkService.processFiles(accountId, FileLinkSource.NOTE, note.id, dto.fileIds); + } + + const entityInfo = await this.entityInfoService.findOne({ accountId, entityId }); + this.eventEmitter.emit( + CrmEventType.NoteCreated, + new NoteCreatedEvent({ + accountId, + entityId: entityInfo.id, + entityName: entityInfo.name, + createdBy: userId, + ownerId: entityInfo.ownerId, + noteId: note.id, + noteText: note.text, + createdAt: note.createdAt.toISOString(), + }), + ); + + return note; + } + + async createAndGetDto({ + account, + userId, + entityId, + dto, + }: { + account: Account; + userId: number; + entityId: number; + dto: CreateNoteDto; + }): Promise { + const note = await this.create({ accountId: account.id, userId, entityId, dto }); + + return this.createDtoForNote({ account, note }); + } + + async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).orderBy('note.created_at', 'DESC').getMany(); + } + + async findOneDto({ account, filter }: { account: Account; filter: FindFilterDto }): Promise { + const note = await this.findOne({ accountId: account.id, ...filter }); + + return note ? this.createDtoForNote({ account, note }) : null; + } + async findManyDto({ account, filter }: { account: Account; filter: FindFilterDto }): Promise { + const notes = await this.findMany({ accountId: account.id, ...filter }); + + return Promise.all(notes.map((note) => this.createDtoForNote({ account, note }))); + } + + async update({ + accountId, + entityId, + noteId, + dto, + }: { + accountId: number; + entityId: number; + noteId: number; + dto: UpdateNoteDto; + }): Promise { + const note = await this.findOne({ accountId, entityId, noteId }); + if (!note) { + throw NotFoundError.withId(Note, noteId); + } + await this.repository.save(note.update(dto)); + + if (dto.fileIds) { + await this.fileLinkService.processFiles(accountId, FileLinkSource.NOTE, noteId, dto.fileIds); + } + + return note; + } + + async updateAndGetDto({ + account, + entityId, + noteId, + dto, + }: { + account: Account; + entityId: number; + noteId: number; + dto: UpdateNoteDto; + }): Promise { + const note = await this.update({ accountId: account.id, entityId, noteId, dto }); + + return this.createDtoForNote({ account, note }); + } + + async delete({ accountId, entityId, noteId }: { accountId: number; entityId: number; noteId: number }) { + const note = await this.findOne({ accountId, entityId, noteId }); + if (!note) { + throw NotFoundError.withId(Note, noteId); + } + + await this.fileLinkService.processFiles(accountId, FileLinkSource.NOTE, noteId, []); + await this.repository.delete({ accountId, entityId, id: noteId }); + + this.eventEmitter.emit( + CrmEventType.NoteDeleted, + new NoteEvent({ accountId: accountId, entityId: entityId, noteId: note.id }), + ); + } + + async copyEntityNotes({ + accountId, + sourceEntityId, + targetEntityId, + }: { + accountId: number; + sourceEntityId: number; + targetEntityId: number; + }) { + const notes = await this.findMany({ accountId, entityId: sourceEntityId }); + await Promise.all( + notes.map((note) => + this.create({ + accountId, + userId: note.createdBy, + entityId: targetEntityId, + dto: { text: note.text }, + options: { createdAt: note.createdAt }, + }), + ), + ); + } + + private async createDtoForNote({ account, note }: { account: Account; note: Note }): Promise { + const links = await this.fileLinkService.getFileLinkDtos(account, FileLinkSource.NOTE, note.id); + + return note.toDto(links); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('note') + .where('note.accountId = :accountId', { accountId: filter.accountId }); + + if (filter?.noteId) { + qb.andWhere('note.id = :noteId', { noteId: filter.noteId }); + } + if (filter?.entityId) { + qb.andWhere('note.entityId = :entityId', { entityId: filter.entityId }); + } + + return qb; + } +} diff --git a/backend/src/CRM/reporting/common/enums/index.ts b/backend/src/CRM/reporting/common/enums/index.ts new file mode 100644 index 0000000..0fd046b --- /dev/null +++ b/backend/src/CRM/reporting/common/enums/index.ts @@ -0,0 +1,2 @@ +export * from './report-user-type.enum'; +export * from './sales-pipeline-type.enum'; diff --git a/backend/src/CRM/reporting/common/enums/report-user-type.enum.ts b/backend/src/CRM/reporting/common/enums/report-user-type.enum.ts new file mode 100644 index 0000000..6785d34 --- /dev/null +++ b/backend/src/CRM/reporting/common/enums/report-user-type.enum.ts @@ -0,0 +1,4 @@ +export enum ReportUserType { + Owner = 'owner', + Participant = 'participant', +} diff --git a/backend/src/CRM/reporting/common/enums/sales-pipeline-type.enum.ts b/backend/src/CRM/reporting/common/enums/sales-pipeline-type.enum.ts new file mode 100644 index 0000000..5e2f6b1 --- /dev/null +++ b/backend/src/CRM/reporting/common/enums/sales-pipeline-type.enum.ts @@ -0,0 +1,16 @@ +export enum SalesPipelineType { + All = 'all', + AllActive = 'all_active', + Open = 'open', + OpenActive = 'open_active', + Closed = 'closed', + Created = 'created', +} + +export const SalesPipelineTypes = { + All: [SalesPipelineType.All, SalesPipelineType.AllActive], + Open: [SalesPipelineType.Open, SalesPipelineType.OpenActive], + Closed: [SalesPipelineType.Closed], + Created: [SalesPipelineType.Created], + Active: [SalesPipelineType.AllActive, SalesPipelineType.OpenActive], +}; diff --git a/backend/src/CRM/reporting/common/helpers/entity-query-builder.helper.ts b/backend/src/CRM/reporting/common/helpers/entity-query-builder.helper.ts new file mode 100644 index 0000000..205778b --- /dev/null +++ b/backend/src/CRM/reporting/common/helpers/entity-query-builder.helper.ts @@ -0,0 +1,65 @@ +import { SelectQueryBuilder } from 'typeorm'; + +import { DatePeriod, PagingQuery } from '@/common'; +import { Entity } from '../../../Model/Entity/Entity'; + +interface Conditions { + accountId?: number | null; + entityTypeIds?: number[] | null; + stageIds?: number[] | null; + search?: string | null; + createdAt?: DatePeriod | null; + closedAt?: DatePeriod | null; + ownerIds?: number[] | null; +} + +export class EntityQueryBuilderHelper { + public static addConditions(qb: SelectQueryBuilder, conditions?: Conditions, paging?: PagingQuery) { + if (conditions.accountId) { + qb.andWhere('e.account_id = :accountId', { accountId: conditions.accountId }); + } + + if (conditions.entityTypeIds?.length) { + qb.andWhere(`e.entity_type_id IN (:...entityTypeIds)`, { entityTypeIds: conditions.entityTypeIds }); + } + + if (conditions.stageIds?.length) { + qb.andWhere(`e.stage_id IN (:...stageIds)`, { stageIds: conditions.stageIds }); + } + + if (conditions.search) { + qb.andWhere(`e.name ILIKE :search`, { search: `%${conditions.search}%` }); + } + + if (conditions.createdAt) { + EntityQueryBuilderHelper.addDateCondition(qb, conditions.createdAt, 'created_at'); + } + + if (conditions.closedAt) { + EntityQueryBuilderHelper.addDateCondition(qb, conditions.closedAt, 'closed_at'); + } + + if (conditions.ownerIds) { + if (conditions.ownerIds.length) { + qb.andWhere(`e.responsible_user_id in (:...ownerIds)`, { ownerIds: conditions.ownerIds }); + } else { + qb.andWhere(`e.responsible_user_id is null`); + } + } + + if (paging) { + qb.offset(paging.skip).limit(paging.take); + } + + return qb; + } + + private static addDateCondition(qb: SelectQueryBuilder, dates: DatePeriod, fieldName: string) { + if (dates.from) { + qb.andWhere(`e.${fieldName} >= :${fieldName}_from`, { [`${fieldName}_from`]: dates.from }); + } + if (dates.to) { + qb.andWhere(`e.${fieldName} <= :${fieldName}_to`, { [`${fieldName}_to`]: dates.to }); + } + } +} diff --git a/backend/src/CRM/reporting/common/helpers/index.ts b/backend/src/CRM/reporting/common/helpers/index.ts new file mode 100644 index 0000000..9d3531e --- /dev/null +++ b/backend/src/CRM/reporting/common/helpers/index.ts @@ -0,0 +1 @@ +export * from './entity-query-builder.helper'; diff --git a/backend/src/CRM/reporting/common/index.ts b/backend/src/CRM/reporting/common/index.ts new file mode 100644 index 0000000..5ef24a3 --- /dev/null +++ b/backend/src/CRM/reporting/common/index.ts @@ -0,0 +1,3 @@ +export * from './enums'; +export * from './helpers'; +export * from './types'; diff --git a/backend/src/CRM/reporting/common/types/index.ts b/backend/src/CRM/reporting/common/types/index.ts new file mode 100644 index 0000000..5a88621 --- /dev/null +++ b/backend/src/CRM/reporting/common/types/index.ts @@ -0,0 +1,2 @@ +export * from './owner-date-value'; +export * from './report-row-owner'; diff --git a/backend/src/CRM/reporting/common/types/owner-date-value.ts b/backend/src/CRM/reporting/common/types/owner-date-value.ts new file mode 100644 index 0000000..60f103c --- /dev/null +++ b/backend/src/CRM/reporting/common/types/owner-date-value.ts @@ -0,0 +1,5 @@ +export interface OwnerDateValue { + ownerId: number | null; + value: number; + date: string | null | undefined; +} diff --git a/backend/src/CRM/reporting/common/types/report-row-owner.ts b/backend/src/CRM/reporting/common/types/report-row-owner.ts new file mode 100644 index 0000000..fa9301e --- /dev/null +++ b/backend/src/CRM/reporting/common/types/report-row-owner.ts @@ -0,0 +1 @@ +export type ReportRowOwner = 'total' | 'user' | 'department'; diff --git a/backend/src/CRM/reporting/comparative/comparative-report.controller.ts b/backend/src/CRM/reporting/comparative/comparative-report.controller.ts new file mode 100644 index 0000000..493ea6e --- /dev/null +++ b/backend/src/CRM/reporting/comparative/comparative-report.controller.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { ComparativeReportDto, ComparativeReportFilterDto } from './dto'; +import { ComparativeReportService } from './comparative-report.service'; + +@ApiTags('crm/reporting') +@Controller('crm/reporting/comparative') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ComparativeReportController { + constructor(private readonly service: ComparativeReportService) {} + + @ApiOperation({ summary: 'Get comparative report', description: 'Get comparative report' }) + @ApiBody({ type: ComparativeReportFilterDto, required: true, description: 'Comparative report filter' }) + @ApiOkResponse({ description: 'Comparative report', type: ComparativeReportDto }) + @Post() + public async getComparativeReport( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: ComparativeReportFilterDto, + ) { + return this.service.getReport({ accountId, user, filter }); + } +} diff --git a/backend/src/CRM/reporting/comparative/comparative-report.service.ts b/backend/src/CRM/reporting/comparative/comparative-report.service.ts new file mode 100644 index 0000000..02c42ff --- /dev/null +++ b/backend/src/CRM/reporting/comparative/comparative-report.service.ts @@ -0,0 +1,360 @@ +import { Injectable } from '@nestjs/common'; + +import { + GroupByDate, + QuantityAmount, + DatePeriod, + DateUtil, + ForbiddenError, + propagateData, + intersection, +} from '@/common'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { DepartmentService } from '@/modules/iam/department/department.service'; +import { User } from '@/modules/iam/user/entities'; + +import { GroupedStages } from '../../board-stage/types'; +import { BoardStageService } from '../../board-stage/board-stage.service'; +import { EntityType } from '../../entity-type/entities'; + +import { ReportRowOwner } from '../common'; +import { CrmReportingService } from '../crm-reporting.service'; + +import { ComparativeReportFilterDto } from './dto'; +import { ComparativeReport, ComparativeReportRow, ComparativeReportCell } from './types'; + +interface Filter { + type: GroupByDate; + stages: GroupedStages; + userIds?: number[] | null; + period?: DatePeriod | null; +} + +@Injectable() +export class ComparativeReportService { + constructor( + private readonly authService: AuthorizationService, + private readonly departmentService: DepartmentService, + private readonly stageService: BoardStageService, + private readonly reportingService: CrmReportingService, + ) {} + + public async getReport({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: ComparativeReportFilterDto; + }): Promise { + const { allow, userIds: allowedUserIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: EntityType.getAuthorizable(filter.entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + const userIds = filter.userIds?.length ? intersection(filter.userIds, allowedUserIds) : allowedUserIds; + + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId: filter.entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + type: filter.stageType, + }); + + let period = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + if (filter.period?.from) { + switch (filter.type) { + case GroupByDate.Day: + period = new DatePeriod(DateUtil.sub(period.from, { weeks: 1 }), period.to); + break; + case GroupByDate.Week: + period = new DatePeriod(DateUtil.sub(period.from, { weeks: 1 }), period.to); + break; + case GroupByDate.Month: + period = new DatePeriod(DateUtil.sub(period.from, { months: 1 }), period.to); + break; + case GroupByDate.Quarter: + period = new DatePeriod(DateUtil.sub(period.from, { months: 3 }), period.to); + break; + case GroupByDate.Year: + period = new DatePeriod(DateUtil.sub(period.from, { years: 1 }), period.to); + break; + } + } + + const [users, departments, total] = await Promise.all([ + this.getGroupBy({ + accountId, + owner: 'user', + userOwnerFieldId: filter.ownerFieldId, + filter: { type: filter.type, stages, userIds, period }, + }), + this.getGroupBy({ + accountId, + owner: 'department', + userOwnerFieldId: filter.ownerFieldId, + filter: { type: filter.type, stages, userIds, period }, + }), + this.getGroupBy({ + accountId, + owner: 'total', + userOwnerFieldId: filter.ownerFieldId, + filter: { type: filter.type, stages, userIds, period }, + }), + ]); + + if (departments.size) { + const hierarchy = await this.departmentService.getHierarchy({ accountId }); + + if (hierarchy.length) { + propagateData(hierarchy, departments, (ownerId: number) => { + return ComparativeReport.createEmptyRow(ownerId); + }); + } + } + + return new ComparativeReport(users, departments, total.values().next().value); + } + + private async getGroupBy({ + accountId, + owner, + userOwnerFieldId, + filter, + }: { + accountId: number; + owner: ReportRowOwner; + userOwnerFieldId: number | undefined; + filter: Filter; + }): Promise> { + const rowMap = new Map(); + + if (filter.stages?.open?.length) { + await this.processEntitiesOpen({ accountId, owner, userOwnerFieldId, filter, rowMap }); + } + if (filter.stages?.lost?.length) { + await this.processEntitiesLost({ accountId, owner, userOwnerFieldId, filter, rowMap }); + } + if (filter.stages?.won?.length) { + await this.processEntitiesWon({ accountId, owner, userOwnerFieldId, filter, rowMap }); + } + + for (const row of rowMap.values()) { + const newCells: ComparativeReportCell[] = []; + for (const cell of row.cells.values()) { + const [prevDate, nextDate] = this.getPrevAndNextDates({ date: cell.date, type: filter.type }); + const prevCell = row.cells.get(prevDate); + if (prevCell) { + this.setCellPrevious({ cell, previous: prevCell }); + } else { + const newCell = ComparativeReportCell.empty(prevDate); + this.setCellPrevious({ cell, previous: newCell }); + } + const nextCell = row.cells.get(nextDate); + if (!nextCell) { + const newCell = ComparativeReportCell.empty(nextDate); + this.setCellPrevious({ cell: newCell, previous: cell }); + newCells.push(newCell); + } + } + newCells.forEach((c) => row.cells.set(c.date, c)); + } + + return rowMap; + } + + private async processEntitiesOpen({ + accountId, + owner, + userOwnerFieldId, + filter, + rowMap, + }: { + accountId: number; + owner: ReportRowOwner; + userOwnerFieldId: number | undefined; + filter: Filter; + rowMap: Map; + }) { + const result = await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.open, + { owner, userOwnerFieldId, date: { type: filter.type, fieldName: 'created_at' } }, + { amount: true, quantity: true }, + { createdAt: filter.period, userIds: filter.userIds }, + ); + for (const { ownerId, value, date } of result.quantity) { + const row = rowMap.get(ownerId) ?? ComparativeReportRow.empty(ownerId); + const cell = row.cells.get(date) ?? ComparativeReportCell.empty(date); + cell.all.current.quantity += value; + cell.open.current.quantity = value; + row.cells.set(date, cell); + rowMap.set(ownerId, row); + } + for (const { ownerId, value, date } of result.amount) { + const row = rowMap.get(ownerId) ?? ComparativeReportRow.empty(ownerId); + const cell = row.cells.get(date) ?? ComparativeReportCell.empty(date); + cell.all.current.amount += value; + cell.open.current.amount = value; + row.cells.set(date, cell); + rowMap.set(ownerId, row); + } + } + + private async processEntitiesLost({ + accountId, + owner, + userOwnerFieldId, + filter, + rowMap, + }: { + accountId: number; + owner: ReportRowOwner; + userOwnerFieldId: number | undefined; + filter: Filter; + rowMap: Map; + }) { + const result = await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.lost, + { owner, userOwnerFieldId, date: { type: filter.type, fieldName: 'closed_at' } }, + { amount: true, quantity: true }, + { closedAt: filter.period, userIds: filter.userIds }, + ); + for (const { ownerId, value, date } of result.quantity) { + const row = rowMap.get(ownerId) ?? ComparativeReportRow.empty(ownerId); + const cell = row.cells.get(date) ?? ComparativeReportCell.empty(date); + cell.all.current.quantity += value; + cell.lost.current.quantity = value; + row.cells.set(date, cell); + rowMap.set(ownerId, row); + } + for (const { ownerId, value, date } of result.amount) { + const row = rowMap.get(ownerId) ?? ComparativeReportRow.empty(ownerId); + const cell = row.cells.get(date) ?? ComparativeReportCell.empty(date); + cell.all.current.amount += value; + cell.lost.current.amount = value; + row.cells.set(date, cell); + rowMap.set(ownerId, row); + } + } + + private async processEntitiesWon({ + accountId, + owner, + userOwnerFieldId, + filter, + rowMap, + }: { + accountId: number; + owner: ReportRowOwner; + userOwnerFieldId: number | undefined; + filter: Filter; + rowMap: Map; + }) { + const result = await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.won, + { owner, userOwnerFieldId, date: { type: filter.type, fieldName: 'closed_at' } }, + { amount: true, quantity: true }, + { closedAt: filter.period, userIds: filter.userIds }, + ); + for (const { ownerId, value, date } of result.quantity) { + const row = rowMap.get(ownerId) ?? ComparativeReportRow.empty(ownerId); + const cell = row.cells.get(date) ?? ComparativeReportCell.empty(date); + cell.all.current.quantity += value; + cell.won.current.quantity = value; + row.cells.set(date, cell); + rowMap.set(ownerId, row); + } + for (const { ownerId, value, date } of result.amount) { + const row = rowMap.get(ownerId) ?? ComparativeReportRow.empty(ownerId); + const cell = row.cells.get(date) ?? ComparativeReportCell.empty(date); + cell.all.current.amount += value; + cell.won.current.amount = value; + row.cells.set(date, cell); + rowMap.set(ownerId, row); + } + } + + private getPrevAndNextDates({ date, type }: { date: string; type: GroupByDate }): [string, string] { + const current = this.parseDate({ date, type }); + switch (type) { + case GroupByDate.Day: { + const prev = DateUtil.format(DateUtil.sub(current, { weeks: 1 }), 'yyyy-MM-dd'); + const next = DateUtil.format(DateUtil.add(current, { weeks: 1 }), 'yyyy-MM-dd'); + return [prev, next]; + } + case GroupByDate.Week: { + const prev = DateUtil.format(DateUtil.sub(current, { weeks: 1 }), 'yyyy-ww'); + const next = DateUtil.format(DateUtil.add(current, { weeks: 1 }), 'yyyy-ww'); + return [prev, next]; + } + case GroupByDate.Month: { + const prev = DateUtil.format(DateUtil.sub(current, { months: 1 }), 'yyyy-MM'); + const next = DateUtil.format(DateUtil.add(current, { months: 1 }), 'yyyy-MM'); + return [prev, next]; + } + case GroupByDate.Quarter: { + const prev = DateUtil.format(DateUtil.sub(current, { months: 3 }), 'yyyy-Q'); + const next = DateUtil.format(DateUtil.add(current, { months: 3 }), 'yyyy-Q'); + return [prev, next]; + } + case GroupByDate.Year: { + const prev = DateUtil.format(DateUtil.sub(current, { years: 1 }), 'yyyy'); + const next = DateUtil.format(DateUtil.add(current, { years: 1 }), 'yyyy'); + return [prev, next]; + } + } + } + + private parseDate({ date, type }: { date: string; type: GroupByDate }): Date { + switch (type) { + case GroupByDate.Day: + return DateUtil.parse(date, 'yyyy-MM-dd'); + case GroupByDate.Week: { + const [yearStr, weekStr] = date.split('-'); + const weeks = parseInt(weekStr, 10) - 1; + return DateUtil.add(DateUtil.parse(yearStr, 'yyyy'), { weeks }); + } + case GroupByDate.Month: + return DateUtil.parse(date, 'yyyy-MM'); + case GroupByDate.Quarter: + return DateUtil.parse(date, 'yyyy-Q'); + case GroupByDate.Year: + return DateUtil.parse(date, 'yyyy'); + } + } + + private setCellPrevious({ cell, previous }: { cell: ComparativeReportCell; previous: ComparativeReportCell }) { + cell.all.previous = new QuantityAmount(previous.all.current.quantity, previous.all.current.amount); + cell.open.previous = new QuantityAmount(previous.open.current.quantity, previous.open.current.amount); + cell.lost.previous = new QuantityAmount(previous.lost.current.quantity, previous.lost.current.amount); + cell.won.previous = new QuantityAmount(previous.won.current.quantity, previous.won.current.amount); + + cell.all.difference = new QuantityAmount( + this.difference(cell.all.current.quantity, cell.all.previous.quantity), + this.difference(cell.all.current.amount, cell.all.previous.amount), + ); + cell.open.difference = new QuantityAmount( + this.difference(cell.open.current.quantity, cell.open.previous.quantity), + this.difference(cell.open.current.amount, cell.open.previous.amount), + ); + cell.lost.difference = new QuantityAmount( + this.difference(cell.lost.current.quantity, cell.lost.previous.quantity), + this.difference(cell.lost.current.amount, cell.lost.previous.amount), + ); + cell.won.difference = new QuantityAmount( + this.difference(cell.won.current.quantity, cell.won.previous.quantity), + this.difference(cell.won.current.amount, cell.won.previous.amount), + ); + } + + private difference(current: number, previous: number): number { + return previous ? (current - previous) / previous : current ? 1 : 0; + } +} diff --git a/backend/src/CRM/reporting/comparative/dto/comparative-report-cell.dto.ts b/backend/src/CRM/reporting/comparative/dto/comparative-report-cell.dto.ts new file mode 100644 index 0000000..2c43458 --- /dev/null +++ b/backend/src/CRM/reporting/comparative/dto/comparative-report-cell.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { ComparativeReportValueDto } from './comparative-report-value.dto'; + +export class ComparativeReportCellDto { + @ApiProperty({ description: 'Date depending on report type' }) + @IsString() + date: string; + + @ApiProperty({ type: ComparativeReportValueDto, description: 'All' }) + all: ComparativeReportValueDto; + + @ApiProperty({ type: ComparativeReportValueDto, description: 'Open' }) + open: ComparativeReportValueDto; + + @ApiProperty({ type: ComparativeReportValueDto, description: 'Lost' }) + lost: ComparativeReportValueDto; + + @ApiProperty({ type: ComparativeReportValueDto, description: 'Won' }) + won: ComparativeReportValueDto; +} diff --git a/backend/src/CRM/reporting/comparative/dto/comparative-report-filter.dto.ts b/backend/src/CRM/reporting/comparative/dto/comparative-report-filter.dto.ts new file mode 100644 index 0000000..459da59 --- /dev/null +++ b/backend/src/CRM/reporting/comparative/dto/comparative-report-filter.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { GroupByDate, DatePeriodFilter } from '@/common'; + +import { BoardStageType } from '../../../board-stage'; + +export class ComparativeReportFilterDto { + @ApiProperty({ enum: GroupByDate, description: 'Type of grouping by date' }) + @IsEnum(GroupByDate) + type: GroupByDate; + + @ApiPropertyOptional({ description: 'Field ID for owner instead of responsibleUserId' }) + @IsOptional() + ownerFieldId?: number; + + @ApiPropertyOptional({ type: Number, description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Board IDs' }) + @IsOptional() + @IsArray() + boardIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs' }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + @ApiPropertyOptional({ enum: BoardStageType, nullable: true, description: 'Stage type' }) + @IsOptional() + @IsEnum(BoardStageType) + stageType?: BoardStageType | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/CRM/reporting/comparative/dto/comparative-report-row.dto.ts b/backend/src/CRM/reporting/comparative/dto/comparative-report-row.dto.ts new file mode 100644 index 0000000..d691937 --- /dev/null +++ b/backend/src/CRM/reporting/comparative/dto/comparative-report-row.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber } from 'class-validator'; + +import { ComparativeReportCellDto } from './comparative-report-cell.dto'; + +export class ComparativeReportRowDto { + @ApiProperty({ description: 'Owner ID' }) + @IsNumber() + ownerId: number; + + @ApiProperty({ type: [ComparativeReportCellDto], description: 'Cells' }) + @IsArray() + cells: ComparativeReportCellDto[]; +} diff --git a/backend/src/CRM/reporting/comparative/dto/comparative-report-value.dto.ts b/backend/src/CRM/reporting/comparative/dto/comparative-report-value.dto.ts new file mode 100644 index 0000000..057c481 --- /dev/null +++ b/backend/src/CRM/reporting/comparative/dto/comparative-report-value.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { QuantityAmountDto } from '@/common'; + +export class ComparativeReportValueDto { + @ApiProperty({ type: QuantityAmountDto, description: 'Current value' }) + current: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Previous value' }) + previous: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Difference' }) + difference: QuantityAmountDto; +} diff --git a/backend/src/CRM/reporting/comparative/dto/comparative-report.dto.ts b/backend/src/CRM/reporting/comparative/dto/comparative-report.dto.ts new file mode 100644 index 0000000..282143a --- /dev/null +++ b/backend/src/CRM/reporting/comparative/dto/comparative-report.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { ComparativeReportRowDto } from './comparative-report-row.dto'; + +export class ComparativeReportDto { + @ApiProperty({ type: [ComparativeReportRowDto], description: 'Users' }) + users: ComparativeReportRowDto[]; + + @ApiProperty({ type: [ComparativeReportRowDto], description: 'Departments' }) + departments: ComparativeReportRowDto[]; + + @ApiProperty({ type: ComparativeReportRowDto, description: 'Total' }) + total: ComparativeReportRowDto; +} diff --git a/backend/src/CRM/reporting/comparative/dto/index.ts b/backend/src/CRM/reporting/comparative/dto/index.ts new file mode 100644 index 0000000..5958d60 --- /dev/null +++ b/backend/src/CRM/reporting/comparative/dto/index.ts @@ -0,0 +1,5 @@ +export * from './comparative-report-cell.dto'; +export * from './comparative-report-filter.dto'; +export * from './comparative-report-row.dto'; +export * from './comparative-report-value.dto'; +export * from './comparative-report.dto'; diff --git a/backend/src/CRM/reporting/comparative/types/comparative-report-cell.ts b/backend/src/CRM/reporting/comparative/types/comparative-report-cell.ts new file mode 100644 index 0000000..0326fcf --- /dev/null +++ b/backend/src/CRM/reporting/comparative/types/comparative-report-cell.ts @@ -0,0 +1,53 @@ +import { ComparativeReportCellDto } from '../dto'; +import { ComparativeReportValue } from './comparative-report-value'; + +export class ComparativeReportCell { + date: string; + all: ComparativeReportValue; + open: ComparativeReportValue; + lost: ComparativeReportValue; + won: ComparativeReportValue; + + constructor( + date: string, + all: ComparativeReportValue, + open: ComparativeReportValue, + lost: ComparativeReportValue, + won: ComparativeReportValue, + ) { + this.date = date; + this.all = all; + this.open = open; + this.lost = lost; + this.won = won; + } + + public static empty(date: string): ComparativeReportCell { + return new ComparativeReportCell( + date, + ComparativeReportValue.empty(), + ComparativeReportValue.empty(), + ComparativeReportValue.empty(), + ComparativeReportValue.empty(), + ); + } + + public toDto(): ComparativeReportCellDto { + return { + date: this.date, + all: this.all.toDto(), + open: this.open.toDto(), + lost: this.lost.toDto(), + won: this.won.toDto(), + }; + } + + public add(cell: ComparativeReportCell): ComparativeReportCell { + this.all.add(cell.all); + this.open.add(cell.open); + this.lost.add(cell.lost); + this.won.add(cell.won); + + return this; + } +} diff --git a/backend/src/CRM/reporting/comparative/types/comparative-report-row.ts b/backend/src/CRM/reporting/comparative/types/comparative-report-row.ts new file mode 100644 index 0000000..a28160b --- /dev/null +++ b/backend/src/CRM/reporting/comparative/types/comparative-report-row.ts @@ -0,0 +1,36 @@ +import { ComparativeReportRowDto } from '../dto'; +import { ComparativeReportCell } from './comparative-report-cell'; + +export class ComparativeReportRow { + ownerId: number; + cells: Map; + + constructor(ownerId: number, cells: Map) { + this.ownerId = ownerId; + this.cells = cells; + } + + public static empty(ownerId: number): ComparativeReportRow { + return new ComparativeReportRow(ownerId, new Map()); + } + + public toDto(): ComparativeReportRowDto { + return { + ownerId: this.ownerId, + cells: Array.from(this.cells.values()).map((v) => v.toDto()), + }; + } + + public add(row: ComparativeReportRow): ComparativeReportRow { + for (const [rowCellId, rowCell] of row.cells) { + let cell = this.cells.get(rowCellId); + if (!cell) { + cell = ComparativeReportCell.empty(rowCellId); + this.cells.set(rowCellId, cell); + } + cell.add(rowCell); + } + + return this; + } +} diff --git a/backend/src/CRM/reporting/comparative/types/comparative-report-value.ts b/backend/src/CRM/reporting/comparative/types/comparative-report-value.ts new file mode 100644 index 0000000..2b5382c --- /dev/null +++ b/backend/src/CRM/reporting/comparative/types/comparative-report-value.ts @@ -0,0 +1,31 @@ +import { QuantityAmount } from '@/common'; + +import { ComparativeReportValueDto } from '../dto/comparative-report-value.dto'; + +export class ComparativeReportValue { + current: QuantityAmount; + previous: QuantityAmount; + difference: QuantityAmount; + + constructor(current: QuantityAmount, previous: QuantityAmount, difference: QuantityAmount) { + this.current = current; + this.previous = previous; + this.difference = difference; + } + + public static empty(): ComparativeReportValue { + return new ComparativeReportValue(QuantityAmount.empty(), QuantityAmount.empty(), QuantityAmount.empty()); + } + + public toDto(): ComparativeReportValueDto { + return { current: this.current.toDto(), previous: this.previous.toDto(), difference: this.difference.toDto() }; + } + + public add(cell: ComparativeReportValue): ComparativeReportValue { + this.current.add(cell.current); + this.previous.add(cell.previous); + this.difference.add(cell.difference); + + return this; + } +} diff --git a/backend/src/CRM/reporting/comparative/types/comparative-report.ts b/backend/src/CRM/reporting/comparative/types/comparative-report.ts new file mode 100644 index 0000000..5c813e8 --- /dev/null +++ b/backend/src/CRM/reporting/comparative/types/comparative-report.ts @@ -0,0 +1,38 @@ +import { ComparativeReportDto } from '../dto/comparative-report.dto'; +import { ComparativeReportRow } from './comparative-report-row'; + +export class ComparativeReport { + users: Map; + departments: Map; + total: ComparativeReportRow | null; + + constructor( + users: Map, + departments: Map, + total: ComparativeReportRow | null, + ) { + this.users = users; + this.departments = departments; + this.total = total; + } + + public static empty(): ComparativeReport { + return new ComparativeReport( + new Map(), + new Map(), + ComparativeReportRow.empty(-1), + ); + } + + public static createEmptyRow(ownerId: number): ComparativeReportRow { + return ComparativeReportRow.empty(ownerId); + } + + public toDto(): ComparativeReportDto { + return { + users: Array.from(this.users.values()).map((u) => u.toDto()), + departments: Array.from(this.departments.values()).map((u) => u.toDto()), + total: this.total?.toDto(), + }; + } +} diff --git a/backend/src/CRM/reporting/comparative/types/index.ts b/backend/src/CRM/reporting/comparative/types/index.ts new file mode 100644 index 0000000..5553603 --- /dev/null +++ b/backend/src/CRM/reporting/comparative/types/index.ts @@ -0,0 +1,4 @@ +export * from './comparative-report-cell'; +export * from './comparative-report-row'; +export * from './comparative-report-value'; +export * from './comparative-report'; diff --git a/backend/src/CRM/reporting/crm-reporting.module.ts b/backend/src/CRM/reporting/crm-reporting.module.ts new file mode 100644 index 0000000..3ceb46c --- /dev/null +++ b/backend/src/CRM/reporting/crm-reporting.module.ts @@ -0,0 +1,49 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { TelephonyModule } from '@/modules/telephony/telephony.module'; +import { CrmModule } from '../crm.module'; +import { SalesPlanModule } from '../sales-plan/sales-plan.module'; + +import { Entity } from '../Model/Entity/Entity'; +import { Task } from '../task'; + +import { CrmReportingService } from './crm-reporting.service'; +import { ComparativeReportService } from './comparative/comparative-report.service'; +import { ComparativeReportController } from './comparative/comparative-report.controller'; +import { CustomerReportService } from './customer/customer-report.service'; +import { CustomerReportController } from './customer/customer-report.controller'; +import { DashboardController } from './dashboard/dashboard.controller'; +import { DashboardService } from './dashboard/dashboard.service'; +import { GeneralReportService } from './general/general-report.service'; +import { GeneralReportController } from './general/general-report.controller'; +import { ProjectReportService } from './project/project-report.service'; +import { ProjectReportController } from './project/project-report.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Entity, Task]), + IAMModule, + forwardRef(() => CrmModule), + forwardRef(() => SalesPlanModule), + forwardRef(() => TelephonyModule), + ], + controllers: [ + ComparativeReportController, + CustomerReportController, + GeneralReportController, + DashboardController, + ProjectReportController, + ], + providers: [ + ComparativeReportService, + CrmReportingService, + CustomerReportService, + DashboardService, + GeneralReportService, + ProjectReportService, + ], + exports: [CrmReportingService], +}) +export class CrmReportingModule {} diff --git a/backend/src/CRM/reporting/crm-reporting.service.ts b/backend/src/CRM/reporting/crm-reporting.service.ts new file mode 100644 index 0000000..9a56d9a --- /dev/null +++ b/backend/src/CRM/reporting/crm-reporting.service.ts @@ -0,0 +1,259 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { GroupByDate, NumberUtil, DateUtil, DatePeriod } from '@/common'; + +import { Entity } from '../Model/Entity/Entity'; + +import { OwnerDateValue, ReportRowOwner } from './common'; + +interface EntityFieldFilter { + fieldId: number; + optionId?: number; + optionsId?: number; + switch?: boolean; +} +interface EntityTotalFilter { + createdAt?: DatePeriod; + closedAt?: DatePeriod; + userIds?: number[]; + field?: EntityFieldFilter; +} +interface GroupBy { + owner?: ReportRowOwner; + userOwnerFieldId?: number; + date?: { type: GroupByDate; fieldName: 'created_at' | 'closed_at' }; +} +interface EntityTotal { + quantity: OwnerDateValue[]; + amount: OwnerDateValue[]; + close: OwnerDateValue[]; + fieldQuantity: OwnerDateValue[]; + fieldAmount: OwnerDateValue[]; +} + +@Injectable() +export class CrmReportingService { + constructor( + private readonly dataSource: DataSource, + @InjectRepository(Entity) + private readonly entityRepository: Repository, + ) {} + + public async getEntityGroupBy( + accountId: number, + stageIds: number[], + groupBy: GroupBy, + include: { quantity?: boolean; amount?: boolean; close?: boolean; fieldQuantity?: number; fieldAmount?: number }, + filter: EntityTotalFilter, + ): Promise { + const qb = this.entityRepository.createQueryBuilder('e').where('e.account_id = :accountId', { accountId }); + if (stageIds.length) { + qb.andWhere(`e.stage_id IN (:...stageIds)`, { stageIds: stageIds }); + } + if (filter.createdAt) { + if (filter.createdAt.from) { + qb.andWhere(`e.created_at >= :createdAtFrom`, { createdAtFrom: filter.createdAt.from }); + } + if (filter.createdAt.to) { + qb.andWhere(`e.created_at <= :createdAtTo`, { createdAtTo: filter.createdAt.to }); + } + } + if (filter.closedAt) { + if (filter.closedAt.from) { + qb.andWhere(`e.closed_at >= :closedAtFrom`, { closedAtFrom: filter.closedAt.from }); + } + if (filter.closedAt.to) { + qb.andWhere(`e.closed_at <= :closedAtTo`, { closedAtTo: filter.closedAt.to }); + } + } + if (filter.userIds) { + if (filter.userIds.length) { + qb.andWhere(`u.id IN (:...userIds)`, { userIds: filter.userIds }); + } else { + qb.andWhere(`e.responsible_user_id IS NULL`); + } + } + + if (groupBy.owner) { + if (groupBy.userOwnerFieldId) { + qb.innerJoin('field_value', 'fvuo', `fvuo.entity_id = e.id and fvuo.field_id = ${groupBy.userOwnerFieldId}`); + qb.leftJoin('users', 'u', `u.id = (fvuo.payload->>'value')::integer`); + } else { + qb.leftJoin('users', 'u', 'e.responsible_user_id = u.id'); + } + switch (groupBy.owner) { + case 'user': + qb.select('u.id', 'owner_id').groupBy('u.id'); + break; + case 'department': + qb.select('u.department_id', 'owner_id').groupBy('u.department_id'); + break; + case 'total': + qb.select('0::int', 'owner_id'); + break; + } + } + if (groupBy.date) { + const groupByDate = `TO_CHAR(DATE(e.${groupBy.date.fieldName}), '${this.getDatePattern(groupBy.date.type)}')`; + qb.addSelect(groupByDate, 'date').addGroupBy(groupByDate); + } + + if (filter.field?.fieldId && filter.field?.optionId) { + // eslint-disable-next-line prettier/prettier + qb.leftJoin('field_value', 'fvt', `fvt.entity_id = e.id and fvt.field_id = ${filter.field.fieldId}`) + .andWhere(`fvt.payload @> '{"optionId": ${filter.field.optionId}}'`); + } else if (filter.field?.fieldId && filter.field?.optionsId) { + // eslint-disable-next-line prettier/prettier + qb.leftJoin('field_value', 'fvt', `fvt.entity_id = e.id and fvt.field_id = ${filter.field.fieldId}`) + .andWhere(`fvt.payload @> '{"optionIds": [${filter.field.optionsId}]}'`); + } else if (filter.field?.fieldId && filter.field?.switch !== undefined) { + qb.leftJoin('field_value', 'fvs', 'fvs.entity_id = e.id and fvs.field_id = :fvsId', { + fvsId: filter.field.fieldId, + }); + if (filter.field.switch) { + qb.andWhere(`fvs.payload @> '{"value": true}'::jsonb`); + } else { + qb.andWhere(`(fvs.payload @> '{"value": true}'::jsonb OR fvs.id IS NULL)`); + } + } + + const [countByUser, countByField, amountByUser, amountByField, avgCloseByUser] = await Promise.all([ + include.quantity ? qb.clone().addSelect('count(e.id)', 'cnt').getRawMany() : Promise.resolve([]), + include.fieldQuantity + ? qb + .clone() + .addSelect(`count(e.id)`, 'field_cnt') + .innerJoin('field_value', 'fv', `fv.entity_id = e.id and fv.field_id = :fieldQuantityId`, { + fieldQuantityId: include.fieldQuantity, + }) + .getRawMany() + : Promise.resolve([]), + include.amount + ? qb + .clone() + .addSelect(`sum(cast(av.payload::json->>'value' as decimal))`, 'amount') + .innerJoin('field_value', 'av', `av.entity_id = e.id and av.field_type = 'value'`) + .getRawMany() + : Promise.resolve([]), + include.fieldAmount + ? qb + .clone() + .addSelect(`sum(cast(fv.payload::json->>'value' as decimal))`, 'field_amount') + .innerJoin('field_value', 'fv', `fv.entity_id = e.id and fv.field_id = :fieldAmountId`, { + fieldAmountId: include.fieldAmount, + }) + .getRawMany() + : Promise.resolve([]), + include.close + ? qb + .clone() + .addSelect(`sum(extract(epoch from (e.closed_at - e.created_at)))`, 'close') + .andWhere('e.closed_at is not null') + .getRawMany() + : Promise.resolve([]), + ]); + + return { + quantity: countByUser.map((c) => { + return { ownerId: NumberUtil.toNumber(c.owner_id), value: NumberUtil.toNumber(c.cnt), date: c.date }; + }), + amount: amountByUser.map((c) => { + return { ownerId: NumberUtil.toNumber(c.owner_id), value: NumberUtil.toNumber(c.amount), date: c.date }; + }), + close: avgCloseByUser.map((c) => { + return { ownerId: NumberUtil.toNumber(c.owner_id), value: NumberUtil.toNumber(c.close), date: c.date }; + }), + fieldQuantity: countByField.map((c) => { + return { ownerId: NumberUtil.toNumber(c.owner_id), value: NumberUtil.toNumber(c.field_cnt), date: c.date }; + }), + fieldAmount: amountByField.map((c) => { + return { ownerId: NumberUtil.toNumber(c.owner_id), value: NumberUtil.toNumber(c.field_amount), date: c.date }; + }), + }; + } + + public async getTaskOrActivityGroupBy( + accountId: number, + source: 'task' | 'activity', + stageIds: number[], + groupBy: GroupBy, + options?: { + resolved?: boolean; + expired?: boolean; + dateField?: string; + period?: DatePeriod | null; + ownerIds?: number[]; + departmentIds?: number[]; + }, + ): Promise { + const qb = this.dataSource + .createQueryBuilder() + .select('count(s.*)', 'cnt') + .from(source, 's') + .leftJoin('users', 'u', 's.responsible_user_id = u.id') + .innerJoin('entity', 'e', 'e.id = s.entity_id') + .where('s.account_id = :accountId', { accountId }) + .andWhere('e.stage_id IN (:...stageIds)', { stageIds }); + + if (groupBy.owner) { + switch (groupBy.owner) { + case 'user': + qb.addSelect('u.id', 'owner_id').groupBy('u.id'); + break; + case 'department': + qb.addSelect('u.department_id', 'owner_id').groupBy('u.department_id'); + break; + } + } + if (groupBy.date) { + const groupByDate = `TO_CHAR(DATE(s.${groupBy.date.fieldName}), '${this.getDatePattern(groupBy.date.type)}')`; + qb.addSelect(groupByDate, 'date').addGroupBy(groupByDate); + } + + if (options?.period && options?.dateField) { + if (options.period.from) { + qb.andWhere(`s.${options.dateField} >= :from`, { from: options.period.from }); + } + if (options.period.to) { + qb.andWhere(`s.${options.dateField} <= :to`, { to: options.period.to }); + } + } + + if (options?.resolved === true) { + qb.andWhere('s.is_resolved = true'); + } else if (options?.resolved === false) { + qb.andWhere('s.is_resolved = false'); + } + + if (options?.expired) { + qb.andWhere('s.end_date < :now', { now: DateUtil.now() }); + } + + if (options?.ownerIds?.length > 0) { + qb.andWhere('s.responsible_user_id IN (:...ownerIds)', { ownerIds: options.ownerIds }); + } + + const countByUser = await qb.getRawMany(); + + return countByUser.map((c) => { + return { ownerId: NumberUtil.toNumber(c.owner_id), value: NumberUtil.toNumber(c.cnt), date: c.date }; + }); + } + + private getDatePattern(type: GroupByDate): string { + switch (type) { + case GroupByDate.Day: + return 'YYYY-MM-DD'; + case GroupByDate.Week: + return 'YYYY-WW'; + case GroupByDate.Month: + return 'YYYY-MM'; + case GroupByDate.Quarter: + return 'YYYY-Q'; + case GroupByDate.Year: + return 'YYYY'; + } + } +} diff --git a/backend/src/CRM/reporting/customer/customer-report.controller.ts b/backend/src/CRM/reporting/customer/customer-report.controller.ts new file mode 100644 index 0000000..11449e2 --- /dev/null +++ b/backend/src/CRM/reporting/customer/customer-report.controller.ts @@ -0,0 +1,28 @@ +import { Body, Controller, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CustomerReportDto, CustomerReportFilterDto } from './dto'; +import { CustomerReportService } from './customer-report.service'; + +@ApiTags('crm/reporting') +@Controller('crm/reporting/customer') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class CustomerReportController { + constructor(private readonly service: CustomerReportService) {} + + @ApiOperation({ summary: 'Get customer report', description: 'Get customer report' }) + @ApiBody({ type: CustomerReportFilterDto, required: true, description: 'Customer report filter' }) + @ApiOkResponse({ description: 'Customer report', type: CustomerReportDto }) + @Post() + async getCustomerReport( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: CustomerReportFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.getReport({ accountId, user, filter, paging }); + } +} diff --git a/backend/src/CRM/reporting/customer/customer-report.service.ts b/backend/src/CRM/reporting/customer/customer-report.service.ts new file mode 100644 index 0000000..bc32173 --- /dev/null +++ b/backend/src/CRM/reporting/customer/customer-report.service.ts @@ -0,0 +1,305 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { DatePeriod, ForbiddenError, intersection, NumberUtil, PagingQuery, QuantityAmount } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; + +import { EntityCategory } from '../../common'; +import { GroupedStages } from '../../board-stage/types'; +import { BoardStageService } from '../../board-stage/board-stage.service'; +import { EntityType } from '../../entity-type/entities'; +import { Entity } from '../../Model/Entity/Entity'; + +import { CustomerReportFilterDto } from './dto'; +import { CustomerReportType } from './enums'; +import { + CustomerReport, + CustomerReportRow, + CustomerReportMeta, + CustomerReportFieldMeta, + CustomerReportField, +} from './types'; + +interface Filter { + entityTypeId: number; + type?: CustomerReportType | null; + userIds?: number[] | null; + period?: DatePeriod; +} + +@Injectable() +export class CustomerReportService { + constructor( + @InjectRepository(Entity) + private readonly repository: Repository, + private readonly dataSource: DataSource, + private readonly authService: AuthorizationService, + private readonly stageService: BoardStageService, + private readonly fieldService: FieldService, + ) {} + + public async getReport({ + accountId, + user, + filter, + paging, + }: { + accountId: number; + user: User; + filter: CustomerReportFilterDto; + paging: PagingQuery; + }) { + const { allow, userIds: allowedUserIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: EntityType.getAuthorizable(filter.entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + const userIds = filter.ownerIds?.length ? intersection(filter.ownerIds, allowedUserIds) : allowedUserIds; + + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId: filter.entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + }); + + const fieldsMeta = await this.getFieldsMeta({ accountId, entityTypeId: filter.entityTypeId }); + + //HACK: for overall report + if (filter.type === CustomerReportType.ContactCompany) { + filter.type = null; + } + + const groupFilter: Filter = { + entityTypeId: filter.entityTypeId, + type: filter.type, + userIds, + period: filter.period ? DatePeriod.fromFilter(filter.period) : undefined, + }; + const rows = await this.getGroupBy({ accountId, stages, isTotal: false, fieldsMeta, filter: groupFilter, paging }); + // const totalRow = filter.type + // ? await this.getGroupBy({ accountId, stages, isTotal: true, fieldsMeta, filter: groupFilter }) + // : null; + const total = await this.createQb({ accountId, stages, isTotal: false, filter: groupFilter, fieldsMeta }) + .select('count(distinct(te.id))::integer', 'cnt') + .orderBy() + .groupBy() + .getRawOne(); + + return new CustomerReport( + Array.from(rows.values()), + // eslint-disable-next-line max-len + CustomerReportRow.empty(0, 0, null), //totalRow ? totalRow.values().next().value : CustomerReportRow.empty(0, 0, null), + new CustomerReportMeta({ offset: paging.skip + paging.take, total: total.cnt, fields: fieldsMeta }), + ); + } + + private async getGroupBy({ + accountId, + stages, + isTotal, + filter, + fieldsMeta, + paging, + }: { + accountId: number; + stages: GroupedStages; + isTotal: boolean; + filter: Filter; + fieldsMeta: CustomerReportFieldMeta[]; + paging?: PagingQuery; + }): Promise> { + const rowMap = new Map(); + const qb = this.createQb({ accountId, stages, isTotal, filter, fieldsMeta }); + if (paging) { + qb.offset(paging.skip).limit(paging.take); + } + const rows = await qb.getRawMany(); + for (const row of rows) { + const fields = new Map(); + for (const fieldMeta of fieldsMeta) { + const value = NumberUtil.toNumber(row[`fv_${fieldMeta.fieldId}_amount`]); + if (value) { + fields.set(fieldMeta.fieldId, new CustomerReportField(fieldMeta.fieldId, fieldMeta.fieldName, value)); + } + } + rowMap.set( + row.owner_id, + new CustomerReportRow( + row.owner_id, + row.owner_entity_type_id, + row.owner_name ?? null, + NumberUtil.toNumber(row.won_product_quantity), + new QuantityAmount(NumberUtil.toNumber(row.won_quantity), NumberUtil.toNumber(row.won_amount)), + new QuantityAmount(NumberUtil.toNumber(row.open_quantity), NumberUtil.toNumber(row.open_amount)), + new QuantityAmount(NumberUtil.toNumber(row.lost_quantity), NumberUtil.toNumber(row.lost_amount)), + new QuantityAmount(NumberUtil.toNumber(row.all_quantity), NumberUtil.toNumber(row.all_amount)), + NumberUtil.toNumber(row.won_avg_quantity), + NumberUtil.toNumber(row.won_avg_amount), + NumberUtil.toNumber(row.won_avg_time), + fields, + ), + ); + } + return rowMap; + } + + private createQb({ + accountId, + stages, + isTotal, + filter, + fieldsMeta, + }: { + accountId: number; + stages: GroupedStages; + isTotal: boolean; + filter: Filter; + fieldsMeta: CustomerReportFieldMeta[]; + }) { + const qb = this.repository + .createQueryBuilder('se') + .select(`count(se.id)::integer`, 'all_quantity') + .addSelect(`coalesce(sum(cast(se_fv.payload::json->>'value' as decimal)), 0)::decimal`, 'all_amount') + .innerJoin('entity_link', 'el', 'el.target_id = se.id') + .innerJoin('entity', 'te', 'te.id = el.source_id') + .innerJoin('entity_type', 'te_et', 'te_et.id = te.entity_type_id') + .leftJoin('orders', 'se_o', 'se_o.entity_id = se.id') + .leftJoin('order_item', 'se_oi', 'se_oi.order_id = se_o.id') + .leftJoin('field_value', 'se_fv', `se_fv.entity_id = se.id and se_fv.field_type = '${FieldType.Value}'`) + .where('se.account_id = :accountId', { accountId }) + .andWhere('se.entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }) + .andWhere('te_et.entity_category in (:...types)', { + types: filter.type ? [filter.type] : [EntityCategory.COMPANY, EntityCategory.CONTACT], + }); + if (isTotal) { + qb.addSelect('0::integer', 'owner_id'); + } else { + qb.addSelect('te.entity_type_id', 'owner_entity_type_id') + .addSelect('te.id', 'owner_id') + .addSelect('te.name', 'owner_name') + .groupBy('te.entity_type_id') + .addGroupBy('te.id') + .addGroupBy('te.name'); + } + if (stages.all?.length > 0) { + qb.andWhere('se.stage_id in (:...stageIds)', { stageIds: stages.all }); + } + if (stages.open?.length > 0) { + qb.addSelect( + `count(se.id) filter (where se.stage_id in (${stages.open.join(',')}))::integer`, + 'open_quantity', + ).addSelect( + // eslint-disable-next-line max-len + `coalesce(sum(cast(se_fv.payload::json->>'value' as decimal)) filter (where se.stage_id in (${stages.open.join(',')})), 0)::decimal`, + 'open_amount', + ); + } + if (stages.won?.length > 0) { + qb.addSelect(`count(se.id) filter (where se.stage_id in (${stages.won.join(',')}))::integer`, 'won_quantity') + .addSelect( + // eslint-disable-next-line max-len + `coalesce(sum(cast(se_fv.payload::json->>'value' as decimal)) filter (where se.stage_id in (${stages.won.join(',')})), 0)::decimal`, + 'won_amount', + ) + .addSelect( + // eslint-disable-next-line max-len + `coalesce(avg(cast(se_fv.payload::json->>'value' as decimal)) filter (where se.stage_id in (${stages.won.join(',')})), 0)::decimal`, + 'won_avg_amount', + ) + .addSelect( + // eslint-disable-next-line max-len + `coalesce(avg(extract(EPOCH from (se.closed_at - se.created_at))) filter (where se.stage_id in (${stages.won.join(',')})), 0)::decimal`, + 'won_avg_time', + ) + .addSelect( + `coalesce(sum(se_oi.quantity) filter (where se.stage_id in (${stages.won.join(',')})), 0)::decimal`, + 'won_product_quantity', + ) + .orderBy('won_amount', 'DESC') + .addOrderBy('won_quantity', 'DESC') + .addOrderBy('all_amount', 'DESC') + .addOrderBy('all_quantity', 'DESC'); + + qb.addCommonTableExpression( + this.dataSource + .createQueryBuilder() + .select('el.source_id', 'owner_id') + .addSelect(`date_trunc('month', se.created_at)`, 'month') + .addSelect('COUNT(se.id)', 'monthly_deal_count') + .from('entity', 'se') + .innerJoin('entity_link', 'el', 'el.target_id = se.id') + .where(`se.account_id = ${accountId}`) + .andWhere(`se.entity_type_id = ${filter.entityTypeId}`) + .groupBy('el.source_id') + .addGroupBy(`date_trunc('month', se.created_at)`), + 'se_month_counts', + ).leftJoin( + // eslint-disable-next-line max-len + '(SELECT owner_id, AVG(monthly_deal_count)::integer AS won_avg_quantity FROM se_month_counts GROUP BY owner_id)', + 'se_month_avg', + 'se_month_avg.owner_id = te.id', + ); + if (isTotal) { + qb.addSelect('AVG(se_month_avg.won_avg_quantity)', 'won_avg_quantity'); + } else { + qb.addSelect('se_month_avg.won_avg_quantity', 'won_avg_quantity').addGroupBy('se_month_avg.won_avg_quantity'); + } + } else { + qb.orderBy('all_amount', 'DESC').addOrderBy('all_quantity', 'DESC'); + } + if (stages.lost?.length > 0) { + qb.addSelect( + `count(se.id) filter (where se.stage_id in (${stages.lost.join(',')}))::integer`, + 'lost_quantity', + ).addSelect( + // eslint-disable-next-line max-len + `coalesce(sum(cast(se_fv.payload::json->>'value' as decimal)) filter (where se.stage_id in (${stages.lost.join(',')})), 0)::decimal`, + 'lost_amount', + ); + } + if (filter.userIds?.length) { + qb.andWhere('se.responsible_user_id in (:...userIds)', { userIds: filter.userIds }); + } + if (filter.period?.from) { + qb.andWhere('se.created_at >= :from', { from: filter.period.from }); + } + if (filter.period?.to) { + qb.andWhere('se.created_at <= :to', { to: filter.period.to }); + } + for (const fieldMeta of fieldsMeta) { + const fieldKey = `fv_${fieldMeta.fieldId}`; + qb.leftJoin( + // eslint-disable-next-line max-len + `(select entity_id, sum(cast(payload::json->>'value' as decimal)) as amount from field_value where field_id = ${fieldMeta.fieldId} group by entity_id)`, + fieldKey, + `${fieldKey}.entity_id = se.id`, + ).addSelect(`sum(${fieldKey}.amount)`, `${fieldKey}_amount`); + } + return qb; + } + + private async getFieldsMeta({ + accountId, + entityTypeId, + }: { + accountId: number; + entityTypeId: number; + }): Promise { + const fieldsMeta: CustomerReportFieldMeta[] = []; + + const formulaFields = await this.fieldService.findMany({ accountId, entityTypeId, type: FieldType.Formula }); + for (const field of formulaFields) { + fieldsMeta.push(new CustomerReportFieldMeta(field.id, field.name)); + } + + return fieldsMeta; + } +} diff --git a/backend/src/CRM/reporting/customer/dto/customer-report-field-meta.dto.ts b/backend/src/CRM/reporting/customer/dto/customer-report-field-meta.dto.ts new file mode 100644 index 0000000..5f965d9 --- /dev/null +++ b/backend/src/CRM/reporting/customer/dto/customer-report-field-meta.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class CustomerReportFieldMetaDto { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + fieldId: number; + + @ApiProperty({ description: 'Field name' }) + @IsString() + fieldName: string; +} diff --git a/backend/src/CRM/reporting/customer/dto/customer-report-field.dto.ts b/backend/src/CRM/reporting/customer/dto/customer-report-field.dto.ts new file mode 100644 index 0000000..8694ace --- /dev/null +++ b/backend/src/CRM/reporting/customer/dto/customer-report-field.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class CustomerReportFieldDto { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + fieldId: number; + + @ApiProperty({ description: 'Field name' }) + @IsString() + fieldName: string; + + @ApiProperty({ description: 'Field value' }) + @IsNumber() + value: number; +} diff --git a/backend/src/CRM/reporting/customer/dto/customer-report-filter.dto.ts b/backend/src/CRM/reporting/customer/dto/customer-report-filter.dto.ts new file mode 100644 index 0000000..87be20f --- /dev/null +++ b/backend/src/CRM/reporting/customer/dto/customer-report-filter.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +import { CustomerReportType } from '../enums'; + +export class CustomerReportFilterDto { + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiPropertyOptional({ enum: CustomerReportType, nullable: true, description: 'Report type' }) + @IsOptional() + @IsEnum(CustomerReportType) + type?: CustomerReportType | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Board IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + boardIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Owner IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + ownerIds?: number[] | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/CRM/reporting/customer/dto/customer-report-meta.dto.ts b/backend/src/CRM/reporting/customer/dto/customer-report-meta.dto.ts new file mode 100644 index 0000000..57d0b6e --- /dev/null +++ b/backend/src/CRM/reporting/customer/dto/customer-report-meta.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { CustomerReportFieldMetaDto } from './customer-report-field-meta.dto'; + +export class CustomerReportMetaDto extends PagingMeta { + @ApiProperty({ type: [CustomerReportFieldMetaDto], description: 'Fields meta' }) + fields: CustomerReportFieldMetaDto[]; + + constructor({ offset, total, fields }: CustomerReportMetaDto) { + super(offset, total); + + this.fields = fields; + } +} diff --git a/backend/src/CRM/reporting/customer/dto/customer-report-row.dto.ts b/backend/src/CRM/reporting/customer/dto/customer-report-row.dto.ts new file mode 100644 index 0000000..b114974 --- /dev/null +++ b/backend/src/CRM/reporting/customer/dto/customer-report-row.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { QuantityAmountDto } from '@/common'; +import { CustomerReportFieldDto } from './customer-report-field.dto'; + +export class CustomerReportRowDto { + @ApiProperty({ description: 'Owner ID' }) + @IsNumber() + ownerId: number; + + @ApiProperty({ description: 'Owner entity type ID' }) + @IsNumber() + ownerEntityTypeId: number; + + @ApiProperty({ description: 'Owner name' }) + @IsString() + ownerName: string; + + @ApiProperty({ description: 'Won product quantity' }) + @IsNumber() + wonProductQuantity: number; + + @ApiProperty({ type: QuantityAmountDto, description: 'Won quantity and amount' }) + won: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Open quantity and amount' }) + open: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Lost quantity and amount' }) + lost: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'All quantity and amount' }) + all: QuantityAmountDto; + + @ApiProperty({ description: 'Average won deal quantity' }) + @IsNumber() + avgWonDealQuantity: number; + + @ApiProperty({ description: 'Average won deal budget' }) + @IsNumber() + avgWonDealBudget: number; + + @ApiProperty({ description: 'Average won deal time' }) + @IsNumber() + avgWonDealTime: number; + + @ApiPropertyOptional({ type: [CustomerReportFieldDto], nullable: true, description: 'Fields' }) + @IsOptional() + fields?: CustomerReportFieldDto[] | null; +} diff --git a/backend/src/CRM/reporting/customer/dto/customer-report.dto.ts b/backend/src/CRM/reporting/customer/dto/customer-report.dto.ts new file mode 100644 index 0000000..d0da241 --- /dev/null +++ b/backend/src/CRM/reporting/customer/dto/customer-report.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +import { CustomerReportRowDto } from './customer-report-row.dto'; +import { CustomerReportMetaDto } from './customer-report-meta.dto'; + +export class CustomerReportDto { + @ApiProperty({ type: [CustomerReportRowDto], description: 'Rows' }) + rows: CustomerReportRowDto[]; + + @ApiPropertyOptional({ type: CustomerReportRowDto, nullable: true, description: 'Total' }) + total?: CustomerReportRowDto | null; + + @ApiProperty({ type: CustomerReportMetaDto, description: 'Meta' }) + meta: CustomerReportMetaDto; +} diff --git a/backend/src/CRM/reporting/customer/dto/index.ts b/backend/src/CRM/reporting/customer/dto/index.ts new file mode 100644 index 0000000..131a1a5 --- /dev/null +++ b/backend/src/CRM/reporting/customer/dto/index.ts @@ -0,0 +1,6 @@ +export * from './customer-report-field-meta.dto'; +export * from './customer-report-field.dto'; +export * from './customer-report-filter.dto'; +export * from './customer-report-meta.dto'; +export * from './customer-report-row.dto'; +export * from './customer-report.dto'; diff --git a/backend/src/CRM/reporting/customer/enums/customer-report-type.enum.ts b/backend/src/CRM/reporting/customer/enums/customer-report-type.enum.ts new file mode 100644 index 0000000..40438bc --- /dev/null +++ b/backend/src/CRM/reporting/customer/enums/customer-report-type.enum.ts @@ -0,0 +1,5 @@ +export enum CustomerReportType { + Contact = 'contact', + Company = 'company', + ContactCompany = 'contactAndCompany', +} diff --git a/backend/src/CRM/reporting/customer/enums/index.ts b/backend/src/CRM/reporting/customer/enums/index.ts new file mode 100644 index 0000000..e3b15cc --- /dev/null +++ b/backend/src/CRM/reporting/customer/enums/index.ts @@ -0,0 +1 @@ +export * from './customer-report-type.enum'; diff --git a/backend/src/CRM/reporting/customer/types/customer-report-field-meta.ts b/backend/src/CRM/reporting/customer/types/customer-report-field-meta.ts new file mode 100644 index 0000000..f971a4a --- /dev/null +++ b/backend/src/CRM/reporting/customer/types/customer-report-field-meta.ts @@ -0,0 +1,15 @@ +import { CustomerReportFieldMetaDto } from '../dto/customer-report-field-meta.dto'; + +export class CustomerReportFieldMeta { + fieldId: number; + fieldName: string; + + constructor(fieldId: number, fieldName: string) { + this.fieldId = fieldId; + this.fieldName = fieldName; + } + + public toDto(): CustomerReportFieldMetaDto { + return { fieldId: this.fieldId, fieldName: this.fieldName }; + } +} diff --git a/backend/src/CRM/reporting/customer/types/customer-report-field.ts b/backend/src/CRM/reporting/customer/types/customer-report-field.ts new file mode 100644 index 0000000..ed7d369 --- /dev/null +++ b/backend/src/CRM/reporting/customer/types/customer-report-field.ts @@ -0,0 +1,17 @@ +import { CustomerReportFieldDto } from '../dto/customer-report-field.dto'; + +export class CustomerReportField { + fieldId: number; + fieldName: string; + value: number; + + constructor(fieldId: number, fieldName: string, value: number) { + this.fieldId = fieldId; + this.fieldName = fieldName; + this.value = value; + } + + public toDto(): CustomerReportFieldDto { + return { fieldId: this.fieldId, fieldName: this.fieldName, value: this.value }; + } +} diff --git a/backend/src/CRM/reporting/customer/types/customer-report-meta.ts b/backend/src/CRM/reporting/customer/types/customer-report-meta.ts new file mode 100644 index 0000000..67512f5 --- /dev/null +++ b/backend/src/CRM/reporting/customer/types/customer-report-meta.ts @@ -0,0 +1,22 @@ +import { CustomerReportMetaDto } from '../dto/customer-report-meta.dto'; +import { type CustomerReportFieldMeta } from './customer-report-field-meta'; + +export class CustomerReportMeta { + fields: CustomerReportFieldMeta[]; + offset: number; + total: number; + + constructor({ offset, total, fields }: { offset: number; total: number; fields: CustomerReportFieldMeta[] }) { + this.offset = offset; + this.total = total; + this.fields = fields; + } + + public toDto(): CustomerReportMetaDto { + return new CustomerReportMetaDto({ + offset: this.offset, + total: this.total, + fields: this.fields?.map((f) => f.toDto()), + }); + } +} diff --git a/backend/src/CRM/reporting/customer/types/customer-report-row.ts b/backend/src/CRM/reporting/customer/types/customer-report-row.ts new file mode 100644 index 0000000..a0877f3 --- /dev/null +++ b/backend/src/CRM/reporting/customer/types/customer-report-row.ts @@ -0,0 +1,81 @@ +import { QuantityAmount } from '@/common'; + +import { CustomerReportRowDto } from '../dto'; +import { type CustomerReportField } from './customer-report-field'; + +export class CustomerReportRow { + ownerId: number; + ownerEntityTypeId: number; + ownerName: string; + wonProductQuantity: number; + won: QuantityAmount; + open: QuantityAmount; + lost: QuantityAmount; + all: QuantityAmount; + avgWonDealQuantity: number; + avgWonDealBudget: number; + avgWonDealTime: number; + fields: Map | null; + + constructor( + ownerId: number, + ownerEntityTypeId: number, + ownerName: string, + wonProductQuantity: number, + won: QuantityAmount, + open: QuantityAmount, + lost: QuantityAmount, + all: QuantityAmount, + avgWonDealQuantity: number, + avgWonDealBudget: number, + avgWonDealTime: number, + fields: Map | null, + ) { + this.ownerId = ownerId; + this.ownerEntityTypeId = ownerEntityTypeId; + this.ownerName = ownerName; + this.wonProductQuantity = wonProductQuantity; + this.won = won; + this.open = open; + this.lost = lost; + this.all = all; + this.avgWonDealQuantity = avgWonDealQuantity; + this.avgWonDealBudget = avgWonDealBudget; + this.avgWonDealTime = avgWonDealTime; + this.fields = fields; + } + + public static empty(ownerId: number, ownerEntityTypeId: number, ownerName: string): CustomerReportRow { + return new CustomerReportRow( + ownerId, + ownerEntityTypeId, + ownerName, + 0, + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + 0, + 0, + 0, + null, + ); + } + + public toDto(): CustomerReportRowDto { + return { + ownerId: this.ownerId, + ownerEntityTypeId: this.ownerEntityTypeId, + ownerName: this.ownerName, + wonProductQuantity: this.wonProductQuantity, + won: this.won.toDto(), + open: this.open.toDto(), + lost: this.lost.toDto(), + all: this.all.toDto(), + avgWonDealQuantity: this.avgWonDealQuantity, + avgWonDealBudget: this.avgWonDealBudget, + avgWonDealTime: this.avgWonDealTime, + fields: this.fields ? Array.from(this.fields.values()).map((v) => v.toDto()) : null, + }; + } +} diff --git a/backend/src/CRM/reporting/customer/types/customer-report.ts b/backend/src/CRM/reporting/customer/types/customer-report.ts new file mode 100644 index 0000000..2a10f19 --- /dev/null +++ b/backend/src/CRM/reporting/customer/types/customer-report.ts @@ -0,0 +1,24 @@ +import { CustomerReportDto } from '../dto'; + +import { type CustomerReportRow } from './customer-report-row'; +import { type CustomerReportMeta } from './customer-report-meta'; + +export class CustomerReport { + rows: CustomerReportRow[]; + total: CustomerReportRow | null; + meta: CustomerReportMeta; + + constructor(rows: CustomerReportRow[], total: CustomerReportRow | null, meta: CustomerReportMeta) { + this.rows = rows; + this.total = total; + this.meta = meta; + } + + public toDto(): CustomerReportDto { + return { + rows: this.rows.map((r) => r.toDto()), + total: this.total?.toDto(), + meta: this.meta.toDto(), + }; + } +} diff --git a/backend/src/CRM/reporting/customer/types/index.ts b/backend/src/CRM/reporting/customer/types/index.ts new file mode 100644 index 0000000..cbc0785 --- /dev/null +++ b/backend/src/CRM/reporting/customer/types/index.ts @@ -0,0 +1,5 @@ +export * from './customer-report-field-meta'; +export * from './customer-report-field'; +export * from './customer-report-meta'; +export * from './customer-report-row'; +export * from './customer-report'; diff --git a/backend/src/CRM/reporting/dashboard/dashboard.controller.ts b/backend/src/CRM/reporting/dashboard/dashboard.controller.ts new file mode 100644 index 0000000..d802917 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dashboard.controller.ts @@ -0,0 +1,126 @@ +import { Body, Controller, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto, PagingQuery } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { + SellersRatingReportDto, + DashboardFilterDto, + TopSellersReportDto, + SalesPlanReportDto, + EntitySummaryReportDto, + TaskSummaryReportDto, + SalesPipelineReportDto, + SalesPipelineFilterDto, +} from './dto'; +import { DashboardService } from './dashboard.service'; + +@ApiTags('crm/reporting/dashboard') +@Controller('crm/entity-types/:entityTypeId/dashboard') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class DashboardController { + constructor(private readonly service: DashboardService) {} + + @ApiOperation({ summary: 'Get sellers rating report', description: 'Get sellers rating report' }) + @ApiParam({ name: 'entityTypeId', description: 'EntityType ID', type: Number, required: true }) + @ApiBody({ type: DashboardFilterDto, required: true, description: 'Dashboard filter' }) + @ApiOkResponse({ description: 'Sellers report', type: SellersRatingReportDto }) + @Post('rating') + public async getSellersRating( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() filter: DashboardFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.getSellersRating({ accountId, user, entityTypeId, filter, paging }); + } + + @ApiOperation({ summary: 'Get top sellers report', description: 'Get top sellers report' }) + @ApiParam({ name: 'entityTypeId', description: 'EntityType ID', type: Number, required: true }) + @ApiBody({ type: DashboardFilterDto, required: true, description: 'Dashboard filter' }) + @ApiOkResponse({ description: 'Top sellers report', type: TopSellersReportDto }) + @Post('top-sellers') + public async getTopSellers( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() filter: DashboardFilterDto, + @Query('limit') limit?: string, + ) { + return this.service.getTopSellers({ + accountId, + user, + entityTypeId, + filter, + limit: limit ? Number(limit) : undefined, + }); + } + + @ApiOperation({ summary: 'Get sales plan report', description: 'Get sales plan report' }) + @ApiParam({ name: 'entityTypeId', description: 'EntityType ID', type: Number, required: true }) + @ApiBody({ type: DashboardFilterDto, required: true, description: 'Dashboard filter' }) + @ApiOkResponse({ description: 'Sales plan report', type: SalesPlanReportDto }) + @Post('sales-plan') + public async getSalesPlanSummary( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() filter: DashboardFilterDto, + ) { + return this.service.getSalesPlanSummary({ accountId, user, entityTypeId, filter }); + } + + @ApiOperation({ summary: 'Get entity type summary report', description: 'Get entity type summary report' }) + @ApiParam({ name: 'entityTypeId', description: 'EntityType ID', type: Number, required: true }) + @ApiBody({ type: DashboardFilterDto, required: true, description: 'Dashboard filter' }) + @ApiOkResponse({ description: 'Entity type summary report', type: EntitySummaryReportDto }) + @Post('summary/entities') + public async getEntitiesSummary( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() filter: DashboardFilterDto, + ) { + return this.service.getEntitiesSummary({ accountId, user, entityTypeId, filter }); + } + + @ApiOperation({ summary: 'Get tasks summary report', description: 'Get tasks summary report' }) + @ApiParam({ name: 'entityTypeId', description: 'EntityType ID', type: Number, required: true }) + @ApiBody({ type: DashboardFilterDto, required: true, description: 'Dashboard filter' }) + @ApiOkResponse({ description: 'Tasks summary report', type: TaskSummaryReportDto }) + @Post('summary/tasks') + public async getTasksSummary( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() filter: DashboardFilterDto, + ) { + return this.service.getTasksSummary({ accountId, user, entityTypeId, filter }); + } + + @ApiOperation({ summary: 'Get activities summary report', description: 'Get activities summary report' }) + @ApiParam({ name: 'entityTypeId', description: 'EntityType ID', type: Number, required: true }) + @ApiBody({ type: DashboardFilterDto, required: true, description: 'Dashboard filter' }) + @ApiOkResponse({ description: 'Get activities summary report', type: TaskSummaryReportDto }) + @Post('summary/activities') + public async getActivitiesSummary( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() filter: DashboardFilterDto, + ) { + return this.service.getActivitiesSummary({ accountId, user, entityTypeId, filter }); + } + + @ApiOperation({ summary: 'Get sales pipeline report', description: 'Get sales pipeline report' }) + @ApiParam({ name: 'entityTypeId', description: 'EntityType ID', type: Number, required: true }) + @ApiBody({ type: SalesPipelineFilterDto, required: true, description: 'Sales pipeline filter' }) + @ApiOkResponse({ description: 'Get top sales pipeline report', type: SalesPipelineReportDto }) + @Post('pipeline') + public async getSalesPipeline( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() filter: SalesPipelineFilterDto, + ) { + return this.service.getSalesPipeline({ accountId, user, entityTypeId, filter }); + } +} diff --git a/backend/src/CRM/reporting/dashboard/dashboard.service.ts b/backend/src/CRM/reporting/dashboard/dashboard.service.ts new file mode 100644 index 0000000..7acb5e7 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dashboard.service.ts @@ -0,0 +1,935 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, DataSource, Repository } from 'typeorm'; + +import { + DatePeriod, + DateUtil, + ForbiddenError, + intersection, + NumberUtil, + PagingMeta, + PagingQuery, + QuantityAmountDto, +} from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; + +import { SalesPlanService } from '../../sales-plan/sales-plan.service'; +import { BoardStage, BoardStageCodes, BoardStageService } from '../../board-stage'; +import { EntityType } from '../../entity-type/entities'; +import { Entity } from '../../Model/Entity/Entity'; + +import { EntityQueryBuilderHelper, SalesPipelineType, SalesPipelineTypes } from '../common'; +import { + DashboardFilterDto, + SellersRatingReportDto, + TopSellersReportDto, + SalesPlanReportDto, + EntitySummaryReportDto, + TaskSummaryReportDto, + SalesPipelineFilterDto, + SalesPipelineReportDto, + SalesPipelineReportRowDto, +} from './dto'; + +interface SalePipelineStageDuration { + stageId: number; + duration: number; +} +interface SalePipelineRowData { + quantity: number; + amount: number; +} +interface EntityTotalFilter { + createdAt?: DatePeriod; + closedAt?: DatePeriod; + userIds?: number[]; +} +interface TaskFilter { + userIds?: number[] | null; + period?: DatePeriod | null; +} + +@Injectable() +export class DashboardService { + constructor( + private readonly dataSource: DataSource, + @InjectRepository(Entity) + private readonly entityRepository: Repository, + private readonly authService: AuthorizationService, + @Inject(forwardRef(() => SalesPlanService)) + private readonly salesPlanService: SalesPlanService, + private readonly stageService: BoardStageService, + ) {} + + public async getSellersRating({ + accountId, + user, + entityTypeId, + filter, + paging, + }: { + accountId: number; + user: User; + entityTypeId: number; + filter: DashboardFilterDto; + paging: PagingQuery; + }): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'dashboard', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + filter.userIds = filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds; + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + }); + if (!stages.won.length) { + return { users: [], meta: PagingMeta.empty() }; + } + + const closedAt = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const qb = this.createSellersRatingQb(); + EntityQueryBuilderHelper.addConditions( + qb, + { accountId, stageIds: stages.won, closedAt, ownerIds: filter.userIds }, + paging, + ); + const users = await qb.getRawMany(); + + const cntQb = this.entityRepository + .createQueryBuilder('e') + .select('count(distinct(e.responsible_user_id))', 'cnt') + .leftJoin('field_value', 'fv', `fv.entity_id = e.id and fv.field_type = 'value'`); + EntityQueryBuilderHelper.addConditions(cntQb, { + accountId, + stageIds: stages.won, + closedAt, + ownerIds: filter.userIds, + }); + const { cnt } = await cntQb.getRawOne(); + + return { + users: users.map((r) => ({ + userId: r.ownerId, + quantity: NumberUtil.toNumber(r.cnt), + amount: NumberUtil.toNumber(r.amount), + })), + meta: new PagingMeta(paging.skip + paging.take, NumberUtil.toNumber(cnt)), + }; + } + + public async getTopSellers({ + accountId, + user, + entityTypeId, + filter, + limit = 5, + }: { + accountId: number; + user: User; + entityTypeId: number; + filter: DashboardFilterDto; + limit?: number; + }): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'dashboard', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + filter.userIds = filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds; + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + }); + if (!stages.won.length) { + return { users: [], others: QuantityAmountDto.empty(), total: QuantityAmountDto.empty() }; + } + + const closedAt = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const qb = this.createSellersRatingQb(); + EntityQueryBuilderHelper.addConditions(qb, { + accountId, + stageIds: stages.won, + closedAt, + ownerIds: filter.userIds, + }); + qb.limit(limit); + const rawUsers = await qb.getRawMany(); + const users = rawUsers.map((r) => ({ + userId: r.ownerId, + quantity: NumberUtil.toNumber(r.cnt), + amount: NumberUtil.toNumber(r.amount), + })); + + const [totalQuantity, totalAmount] = await this.getEntityTotal(accountId, stages.won, { + closedAt, + userIds: filter.userIds, + }); + + const othersQuantity = users.reduce((acc, cur) => acc - cur.quantity, totalQuantity); + const othersAmount = users.reduce((acc, cur) => acc - cur.amount, totalAmount); + + return { + users, + others: new QuantityAmountDto(othersQuantity, othersAmount), + total: new QuantityAmountDto(totalQuantity, totalAmount), + }; + } + + public async getSalesPlanSummary({ + accountId, + user, + entityTypeId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + filter: DashboardFilterDto; + }): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'dashboard', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + filter.userIds = filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds; + const salesPlans = await this.salesPlanService.getActualPlans( + accountId, + entityTypeId, + filter.userIds, + filter.period, + ); + + if (salesPlans.length === 0) { + return { + amount: { current: 0, plannedToday: 0, plannedTotal: 0 }, + quantity: { current: 0, plannedToday: 0, plannedTotal: 0 }, + }; + } + + const quantityPlanned = salesPlans.reduce((acc, cur) => acc + cur.quantity, 0); + const amountPlanned = salesPlans.reduce((acc, cur) => acc + cur.amount, 0); + + const { startDate, endDate } = salesPlans[0]; + + const planDays = DateUtil.diff({ startDate, endDate, unit: 'day' }); + const todayDays = DateUtil.diff({ startDate, endDate: DateUtil.now(), unit: 'day' }); + const quantityToday = quantityPlanned * (todayDays / planDays); + const amountToday = amountPlanned * (todayDays / planDays); + + const { quantity, amount } = await this.salesPlanService.getSalesPlanProgress( + accountId, + entityTypeId, + startDate, + endDate, + salesPlans.map((sp) => sp.userId), + filter.boardIds, + ); + const quantityCurrent = quantity.reduce((acc, cur) => acc + cur.value, 0); + const amountCurrent = amount.reduce((acc, cur) => acc + cur.value, 0); + + return { + amount: { current: amountCurrent, plannedToday: amountToday, plannedTotal: amountPlanned }, + quantity: { current: quantityCurrent, plannedToday: quantityToday, plannedTotal: quantityPlanned }, + }; + } + + public async getEntitiesSummary({ + accountId, + user, + entityTypeId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + filter: DashboardFilterDto; + }): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'dashboard', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + filter.userIds = filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds; + const period = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + }); + + const [totalQuantity, totalAmount] = await this.getEntityTotal(accountId, stages.open, { + userIds: filter.userIds, + }); + const [winQuantity, winAmount] = await this.getEntityTotal(accountId, stages.won, { + closedAt: period, + userIds: filter.userIds, + }); + const [lostQuantity, lostAmount] = await this.getEntityTotal(accountId, stages.lost, { + closedAt: period, + userIds: filter.userIds, + }); + const [newQuantity, newAmount] = await this.getEntityTotal(accountId, stages.all, { + createdAt: period, + userIds: filter.userIds, + }); + + return { + total: { quantity: totalQuantity, amount: totalAmount }, + win: { quantity: winQuantity, amount: winAmount }, + lost: { quantity: lostQuantity, amount: lostAmount }, + new: { quantity: newQuantity, amount: newAmount }, + }; + } + + public async getTasksSummary({ + accountId, + user, + entityTypeId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + filter: DashboardFilterDto; + }): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'dashboard', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + filter.userIds = filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds; + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + }); + if (!stages.all.length) { + return { total: 0, completed: 0, expired: 0, noTask: 0 }; + } + + const period = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const total = await this.getTasksOrActivityTotal( + accountId, + 'task', + stages.all, + { userIds: filter.userIds }, + { resolved: false }, + ); + const completed = await this.getTasksOrActivityTotal( + accountId, + 'task', + stages.all, + { userIds: filter.userIds, period }, + { resolved: true, dateField: 'resolved_date' }, + ); + const expired = await this.getTasksOrActivityTotal( + accountId, + 'task', + stages.all, + { userIds: filter.userIds }, + { resolved: false, expired: true }, + ); + const noTask = await this.getEntityWithoutTasksCount(accountId, 'task', stages.all, { + userIds: filter.userIds, + period, + }); + + return { total, completed, expired, noTask }; + } + + public async getActivitiesSummary({ + accountId, + user, + entityTypeId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + filter: DashboardFilterDto; + }): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'dashboard', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + filter.userIds = filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds; + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + }); + if (!stages.all.length) { + return { total: 0, completed: 0, expired: 0, noTask: 0 }; + } + + const period = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const total = await this.getTasksOrActivityTotal( + accountId, + 'activity', + stages.all, + { userIds: filter.userIds }, + { resolved: false }, + ); + const completed = await this.getTasksOrActivityTotal( + accountId, + 'activity', + stages.all, + { userIds: filter.userIds, period }, + { resolved: true, dateField: 'resolved_date' }, + ); + const expired = await this.getTasksOrActivityTotal( + accountId, + 'activity', + stages.all, + { userIds: filter.userIds }, + { resolved: false, expired: true }, + ); + const noTask = await this.getEntityWithoutTasksCount(accountId, 'activity', stages.all, { + userIds: filter.userIds, + period, + }); + + return { total, completed, expired, noTask }; + } + + public async getSalesPipeline({ + accountId, + user, + entityTypeId, + filter, + }: { + accountId: number; + user: User; + entityTypeId: number; + filter: SalesPipelineFilterDto; + }): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'dashboard', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + filter.userIds = filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds; + const stages = await this.stageService.findMany({ accountId, boardId: filter.boardId }); + if (stages.length === 0) { + return { rows: [], totalSales: null, conversionToSale: null, averageAmount: null, averageTerm: null }; + } + const entityStageIds = this.getSalesPipelineEntityStageIds({ type: filter.type, stages }); + const period = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const durations = await this.getSalesPipelineStageDuration({ + accountId, + entityTypeId, + stageIds: stages.map((s) => s.id), + entityStageIds, + type: filter.type, + period, + userIds: filter.userIds, + }); + let quantity = 0; + let isFirstStage = true; + let totalSales: number | null = null; + let conversionToSale: number | null = null; + let averageAmount: number | null = null; + const rows: SalesPipelineReportRowDto[] = []; + const rowStages = SalesPipelineTypes.Open.includes(filter.type) + ? stages.filter((s) => !s.isSystem) + : stages.filter((s) => !BoardStageCodes.lost.includes(s.code)); + const rowStageIds = rowStages.map((s) => s.id); + while (rowStages.length > 0) { + const stageIds = rowStages.map((s) => s.id); + const data = await this.getSalesPipelineRowData({ + accountId, + entityTypeId, + stageIds, + entityStageIds, + type: filter.type, + period, + userIds: filter.userIds, + }); + if (isFirstStage) { + quantity = data.quantity; + isFirstStage = false; + } + const stage = rowStages.shift(); + const duration = durations.find((d) => d.stageId === stage.id)?.duration ?? 0; + rows.push({ + stageId: stage.id, + stageName: stage.name, + stageColor: stage.color, + stageOrder: stage.sortOrder, + daysCount: !stage.isSystem ? duration : null, + percent: quantity ? data.quantity / quantity : 1, + value: data.amount, + count: data.quantity, + }); + if (BoardStageCodes.won.includes(stage.code)) { + totalSales = data.amount; + conversionToSale = quantity ? data.quantity / quantity : 1; + averageAmount = data.quantity ? data.amount / data.quantity : 0; + } + } + let averageTerm: number | null = null; + if (!SalesPipelineTypes.Open.includes(filter.type)) { + const lostStages = stages.filter((s) => BoardStageCodes.lost.includes(s.code)); + const stage = lostStages[0]; + const data = await this.getSalesPipelineRowData({ + accountId, + entityTypeId, + stageIds: [stage.id], + entityStageIds, + type: filter.type, + period, + userIds: filter.userIds, + }); + rows.push({ + stageId: stage.id, + stageName: stage.name, + stageColor: stage.color, + stageOrder: stage.sortOrder, + daysCount: null, + percent: quantity ? data.quantity / quantity : 1, + value: data.amount, + count: data.quantity, + }); + + const wonStages = stages.filter((s) => BoardStageCodes.won.includes(s.code)); + const wonStage = wonStages[0]; + averageTerm = await this.getSalesPipelineAverageTerm({ + accountId, + entityTypeId, + stageIds: rowStageIds, + entityStageIds: [wonStage.id], + type: filter.type, + period, + userIds: filter.userIds, + }); + } + + return { totalSales, conversionToSale, averageAmount, averageTerm, rows }; + } + + private async getEntityTotal( + accountId: number, + stageIds: number[], + filter?: EntityTotalFilter, + ): Promise<[number, number]> { + if (!stageIds?.length) { + return [0, 0]; + } + + const qb = this.entityRepository.createQueryBuilder('e').select('count(e.id)', 'cnt'); + + EntityQueryBuilderHelper.addConditions(qb, { + accountId, + stageIds, + createdAt: filter?.createdAt, + closedAt: filter?.closedAt, + ownerIds: filter?.userIds, + }); + + const { cnt } = await qb.getRawOne(); + const { amount } = await qb + .clone() + .select(`sum(cast(fv.payload::json->>'value' as decimal))`, 'amount') + .innerJoin('field_value', 'fv', `fv.entity_id = e.id and fv.field_type = 'value'`) + .getRawOne(); + + return [NumberUtil.toNumber(cnt), NumberUtil.toNumber(amount)]; + } + + private async getTasksOrActivityTotal( + accountId: number, + source: 'task' | 'activity', + stageIds: number[], + filter: TaskFilter, + options?: { resolved?: boolean; expired?: boolean; dateField?: string }, + ): Promise { + const qb = this.dataSource + .createQueryBuilder() + .select('count(s.*)', 'cnt') + .from(source, 's') + .innerJoin('entity', 'e', 'e.id = s.entity_id') + .where('s.account_id = :accountId', { accountId }) + .andWhere('e.stage_id IN (:...stageIds)', { stageIds }); + + if (filter.userIds?.length) { + qb.andWhere('s.responsible_user_id IN (:...userIds)', { userIds: filter.userIds }); + } + + if (filter.period && options?.dateField) { + if (filter.period.from) { + qb.andWhere(`s.${options.dateField} >= :from`, { from: filter.period.from }); + } + if (filter.period.to) { + qb.andWhere(`s.${options.dateField} <= :to`, { to: filter.period.to }); + } + } + + if (options?.resolved === true) { + qb.andWhere('s.is_resolved = true'); + } else if (options?.resolved === false) { + qb.andWhere('s.is_resolved = false'); + } + + if (options?.expired) { + qb.andWhere('s.end_date < :now', { now: DateUtil.now() }); + } + + const { cnt } = await qb.getRawOne(); + + return NumberUtil.toNumber(cnt); + } + + private async getEntityWithoutTasksCount( + accountId: number, + source: 'task' | 'activity', + stageIds: number[], + filter: TaskFilter, + ): Promise { + const qb = this.entityRepository + .createQueryBuilder('e') + .select('count(distinct(e.id))', 'cnt') + .leftJoin(source, 's', 's.entity_id = e.id') + .where('s.id IS NULL'); + + EntityQueryBuilderHelper.addConditions(qb, { + accountId, + stageIds, + createdAt: filter.period, + ownerIds: filter.userIds, + }); + + const { cnt } = await qb.getRawOne(); + return NumberUtil.toNumber(cnt); + } + + private getSalesPipelineEntityStageIds({ + type, + stages, + }: { + type: SalesPipelineType; + stages: BoardStage[]; + }): number[] | null { + if (SalesPipelineTypes.Open.includes(type)) { + return stages.filter((s) => !s.isSystem).map((s) => s.id); + } else if (SalesPipelineTypes.Closed.includes(type)) { + return stages.filter((s) => s.isSystem).map((s) => s.id); + } else { + return null; + } + } + + private async getSalesPipelineStageDuration({ + accountId, + entityTypeId, + stageIds, + entityStageIds, + type, + period, + userIds, + }: { + accountId: number; + entityTypeId: number; + stageIds: number[]; + entityStageIds: number[] | null; + type: SalesPipelineType; + period: DatePeriod | null | undefined; + userIds: number[] | null | undefined; + }): Promise { + const qb = this.dataSource + .createQueryBuilder() + .select('esh.stage_id', 'stage_id') + .addSelect('esh.entity_id', 'entity_id') + .addSelect( + 'lead(esh.created_at) over (partition by esh.entity_id order by esh.created_at) - esh.created_at', + 'duration', + ) + .from('entity_stage_history', 'esh') + .innerJoin('entity', 'e', 'e.id = esh.entity_id') + .where(`esh.account_id = ${accountId}`) + .andWhere(`esh.stage_id in (${stageIds.join(', ')})`) + .andWhere(`e.entity_type_id = ${entityTypeId}`); + if (entityStageIds) { + qb.andWhere(`e.stage_id in (${entityStageIds.join(',')})`); + } + if (userIds?.length) { + qb.andWhere(`e.responsible_user_id in (${userIds.join(',')})`); + } + if (period) { + if (SalesPipelineTypes.Active.includes(type)) { + if (period.from) { + qb.andWhere(`esh.created_at >= :eshFrom`, { eshFrom: period.from }); + } + if (period.to) { + qb.andWhere(`esh.created_at <= :eshTo`, { eshTo: period.to }); + } + } + if (SalesPipelineTypes.All.includes(type)) { + qb.andWhere( + new Brackets((qb1) => + qb1 + .where('e.closed_at is null') + .orWhere('e.closed_at > :closedLimit', { closedLimit: period.from || period.to }), + ), + ); + } else if (SalesPipelineTypes.Open.includes(type)) { + qb.andWhere( + new Brackets((qb1) => + qb1 + .where('e.closed_at is null') + .orWhere('e.closed_at > :closedLimit', { closedLimit: period.to || period.from }), + ), + ); + } else if (SalesPipelineTypes.Closed.includes(type)) { + if (period.from) { + qb.andWhere(`e.closed_at >= :closedFrom`, { closedFrom: period.from }); + } + if (period.to) { + qb.andWhere(`e.closed_at <= :closedTo`, { closedTo: period.to }); + } + } else if (SalesPipelineTypes.Created.includes(type)) { + if (period.from) { + qb.andWhere(`e.created_at >= :createdFrom`, { createdFrom: period.from }); + } + if (period.to) { + qb.andWhere(`e.created_at <= :createdTo`, { createdTo: period.to }); + } + } + } + + const result = await this.dataSource + .createQueryBuilder() + .select('durations.stage_id', 'stage_id') + .addSelect('avg(extract(epoch from durations.duration))', 'duration') + .from(`(${qb.getQuery()})`, 'durations') + .where('durations.duration is not null') + .groupBy('durations.stage_id') + .setParameters(qb.getParameters()) + .getRawMany(); + + return result.map((r) => ({ stageId: r.stage_id, duration: NumberUtil.toNumber(r.duration) })); + } + + private async getSalesPipelineRowData({ + accountId, + entityTypeId, + stageIds, + entityStageIds, + type, + period, + userIds, + }: { + accountId: number; + entityTypeId: number; + stageIds: number[]; + entityStageIds: number[] | null; + type: SalesPipelineType; + period: DatePeriod | null | undefined; + userIds: number[] | null | undefined; + }): Promise { + const historyQb = this.createSalesPipelineHistoryQb({ accountId, stageIds, type, period }); + + const qb = this.entityRepository + .createQueryBuilder('e') + .select('count(e.id)::integer', 'quantity') + .addSelect(`coalesce(sum(cast(fv.payload::json->>'value' as decimal)), 0)::decimal`, 'amount') + .innerJoin(`(${historyQb.getQuery()})`, 'history', 'history.entity_id = e.id') + .leftJoin('field_value', 'fv', `fv.entity_id = e.id and fv.field_type = '${FieldType.Value}'`) + .where('e.account_id = :accountId', { accountId }) + .andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }); + if (entityStageIds) { + qb.andWhere('e.stage_id in (:...stageIds)', { stageIds: entityStageIds }); + } + if (userIds?.length) { + qb.andWhere('e.responsible_user_id in (:...userIds)', { userIds }); + } + if (period) { + if (SalesPipelineTypes.All.includes(type)) { + qb.andWhere( + new Brackets((qb1) => + qb1 + .where('e.closed_at is null') + .orWhere('e.closed_at > :closedLimit', { closedLimit: period.from || period.to }), + ), + ); + } else if (SalesPipelineTypes.Open.includes(type)) { + qb.andWhere( + new Brackets((qb1) => + qb1 + .where('e.closed_at is null') + .orWhere('e.closed_at > :closedLimit', { closedLimit: period.to || period.from }), + ), + ); + } else if (SalesPipelineTypes.Closed.includes(type)) { + if (period.from) { + qb.andWhere(`e.closed_at >= :closedFrom`, { closedFrom: period.from }); + } + if (period.to) { + qb.andWhere(`e.closed_at <= :closedTo`, { closedTo: period.to }); + } + } else if (SalesPipelineTypes.Created.includes(type)) { + if (period.from) { + qb.andWhere(`e.created_at >= :createdFrom`, { createdFrom: period.from }); + } + if (period.to) { + qb.andWhere(`e.created_at <= :createdTo`, { createdTo: period.to }); + } + } + } + + qb.setParameters({ ...historyQb.getParameters(), ...qb.getParameters() }); + + const result = await qb.getRawOne(); + + return { quantity: result.quantity, amount: NumberUtil.toNumber(result.amount) }; + } + + private async getSalesPipelineAverageTerm({ + accountId, + entityTypeId, + stageIds, + entityStageIds, + type, + period, + userIds, + }: { + accountId: number; + entityTypeId: number; + stageIds: number[]; + entityStageIds: number[] | null; + type: SalesPipelineType; + period: DatePeriod | null | undefined; + userIds: number[] | null | undefined; + }): Promise { + const historyQb = this.createSalesPipelineHistoryQb({ accountId, stageIds, type, period }); + + const qb = this.entityRepository + .createQueryBuilder('e') + .select('avg(extract(epoch from (e.closed_at - e.created_at)))', 'term') + .innerJoin(`(${historyQb.getQuery()})`, 'history', 'history.entity_id = e.id') + .where('e.account_id = :accountId', { accountId }) + .andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }); + if (entityStageIds) { + qb.andWhere('e.stage_id in (:...stageIds)', { stageIds: entityStageIds }); + } + if (userIds?.length) { + qb.andWhere('e.responsible_user_id in (:...userIds)', { userIds }); + } + if (period) { + if (SalesPipelineTypes.All.includes(type)) { + qb.andWhere( + new Brackets((qb1) => + qb1 + .where('e.closed_at is null') + .orWhere('e.closed_at > :closedLimit', { closedLimit: period.from || period.to }), + ), + ); + } else if (SalesPipelineTypes.Open.includes(type)) { + qb.andWhere( + new Brackets((qb1) => + qb1 + .where('e.closed_at is null') + .orWhere('e.closed_at > :closedLimit', { closedLimit: period.to || period.from }), + ), + ); + } else if (SalesPipelineTypes.Closed.includes(type)) { + if (period.from) { + qb.andWhere(`e.closed_at >= :closedFrom`, { closedFrom: period.from }); + } + if (period.to) { + qb.andWhere(`e.closed_at <= :closedTo`, { closedTo: period.to }); + } + } else if (SalesPipelineTypes.Created.includes(type)) { + if (period.from) { + qb.andWhere(`e.created_at >= :createdFrom`, { createdFrom: period.from }); + } + if (period.to) { + qb.andWhere(`e.created_at <= :createdTo`, { createdTo: period.to }); + } + } + } + + qb.setParameters({ ...historyQb.getParameters(), ...qb.getParameters() }); + + const result = await qb.getRawOne(); + + return NumberUtil.toNumber(result.term); + } + + private createSalesPipelineHistoryQb({ + accountId, + stageIds, + type, + period, + }: { + accountId: number; + stageIds: number[]; + type: SalesPipelineType; + period: DatePeriod | null | undefined; + }) { + const historyQb = this.dataSource + .createQueryBuilder() + .select('distinct esh.entity_id', 'entity_id') + .from('entity_stage_history', 'esh') + .where(`esh.account_id = ${accountId}`) + .andWhere(`esh.stage_id in (${stageIds.join(',')})`); + if (period && SalesPipelineTypes.Active.includes(type)) { + if (period.from) { + historyQb.andWhere(`esh.created_at >= :eshFrom`, { eshFrom: period.from }); + } + if (period.to) { + historyQb.andWhere(`esh.created_at <= :eshTo`, { eshTo: period.to }); + } + } + + return historyQb; + } + + private createSellersRatingQb() { + return this.entityRepository + .createQueryBuilder('e') + .select('e.responsible_user_id', 'ownerId') + .addSelect('count(e.id)', 'cnt') + .addSelect(`sum(cast(fv.payload::json->>'value' as decimal))`, 'amount') + .leftJoin('field_value', 'fv', `fv.entity_id = e.id and fv.field_type = 'value'`) + .groupBy('e.responsible_user_id') + .orderBy('amount', 'DESC', 'NULLS LAST') + .addOrderBy('cnt', 'DESC', 'NULLS LAST'); + } +} diff --git a/backend/src/CRM/reporting/dashboard/dto/dashboard-filter.dto.ts b/backend/src/CRM/reporting/dashboard/dto/dashboard-filter.dto.ts new file mode 100644 index 0000000..5aa0a64 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/dashboard-filter.dto.ts @@ -0,0 +1,20 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +export class DashboardFilterDto { + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs' }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Board IDs' }) + @IsOptional() + @IsArray() + boardIds?: number[] | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/entity-summary-report.dto.ts b/backend/src/CRM/reporting/dashboard/dto/entity-summary-report.dto.ts new file mode 100644 index 0000000..e755df7 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/entity-summary-report.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { QuantityAmountDto } from '@/common'; + +export class EntitySummaryReportDto { + @ApiProperty({ type: QuantityAmountDto, description: 'Total quantity and amount' }) + total: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Won quantity and amount' }) + win: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Lost quantity and amount' }) + lost: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'New quantity and amount' }) + new: QuantityAmountDto; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/index.ts b/backend/src/CRM/reporting/dashboard/dto/index.ts new file mode 100644 index 0000000..061ca7d --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/index.ts @@ -0,0 +1,11 @@ +export * from './dashboard-filter.dto'; +export * from './entity-summary-report.dto'; +export * from './sales-pipeline-filter.dto'; +export * from './sales-pipeline-report-row.dto'; +export * from './sales-pipeline-report.dto'; +export * from './sales-plan-report.dto'; +export * from './sales-plan-value.dto'; +export * from './sellers-rating-report.dto'; +export * from './task-summary-report.dto'; +export * from './top-sellers-report.dto'; +export * from './user-quantity-amount.dto'; diff --git a/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-filter.dto.ts b/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-filter.dto.ts new file mode 100644 index 0000000..e71fcd9 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-filter.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +import { SalesPipelineType } from '../../common'; + +export class SalesPipelineFilterDto { + @ApiProperty({ enum: SalesPipelineType, description: 'Sales pipeline type' }) + @IsEnum(SalesPipelineType) + type: SalesPipelineType; + + @ApiProperty({ description: 'Board ID' }) + @IsNumber() + boardId: number; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs' }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-report-row.dto.ts b/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-report-row.dto.ts new file mode 100644 index 0000000..c040e77 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-report-row.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class SalesPipelineReportRowDto { + @ApiProperty({ description: 'Stage ID' }) + @IsNumber() + stageId: number; + + @ApiProperty({ description: 'Stage name' }) + @IsString() + stageName: string; + + @ApiProperty({ description: 'Stage color' }) + @IsString() + stageColor: string; + + @ApiProperty({ description: 'Stage order' }) + @IsNumber() + stageOrder: number; + + @ApiProperty({ nullable: true, description: 'Days count' }) + @IsOptional() + @IsNumber() + daysCount: number | null; + + @ApiProperty({ description: 'Percent' }) + @IsNumber() + percent: number; + + @ApiProperty({ description: 'Value' }) + @IsNumber() + value: number; + + @ApiProperty({ description: 'Count' }) + @IsNumber() + count: number; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-report.dto.ts b/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-report.dto.ts new file mode 100644 index 0000000..e91cd1b --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/sales-pipeline-report.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { SalesPipelineReportRowDto } from './sales-pipeline-report-row.dto'; + +export class SalesPipelineReportDto { + @ApiProperty({ nullable: true, description: 'Total sales' }) + @IsOptional() + @IsNumber() + totalSales: number | null; + + @ApiProperty({ nullable: true, description: 'Conversion to sale' }) + @IsOptional() + @IsNumber() + conversionToSale: number | null; + + @ApiProperty({ nullable: true, description: 'Average amount' }) + @IsOptional() + @IsNumber() + averageAmount: number | null; + + @ApiProperty({ nullable: true, description: 'Average term' }) + @IsOptional() + @IsNumber() + averageTerm: number | null; + + @ApiProperty({ type: [SalesPipelineReportRowDto], description: 'Rows' }) + rows: SalesPipelineReportRowDto[]; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/sales-plan-report.dto.ts b/backend/src/CRM/reporting/dashboard/dto/sales-plan-report.dto.ts new file mode 100644 index 0000000..e0d8a63 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/sales-plan-report.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { SalesPlanValueDto } from './sales-plan-value.dto'; + +export class SalesPlanReportDto { + @ApiProperty({ type: SalesPlanValueDto, description: 'Sales plan amount' }) + amount: SalesPlanValueDto; + + @ApiProperty({ type: SalesPlanValueDto, description: 'Sales plan quantity' }) + quantity: SalesPlanValueDto; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/sales-plan-value.dto.ts b/backend/src/CRM/reporting/dashboard/dto/sales-plan-value.dto.ts new file mode 100644 index 0000000..e99e48d --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/sales-plan-value.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class SalesPlanValueDto { + @ApiProperty({ description: 'The current value of the sales plan' }) + @IsNumber() + current: number; + + @ApiProperty({ description: 'The planned value for today' }) + @IsNumber() + plannedToday: number; + + @ApiProperty({ description: 'The planned total value' }) + @IsNumber() + plannedTotal: number; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/sellers-rating-report.dto.ts b/backend/src/CRM/reporting/dashboard/dto/sellers-rating-report.dto.ts new file mode 100644 index 0000000..a95800e --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/sellers-rating-report.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; + +import { UserQuantityAmountDto } from './user-quantity-amount.dto'; + +export class SellersRatingReportDto { + @ApiProperty({ type: [UserQuantityAmountDto], description: 'Sellers rating' }) + users: UserQuantityAmountDto[]; + + @ApiProperty({ type: PagingMeta, description: 'Paging metadata' }) + meta: PagingMeta; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/task-summary-report.dto.ts b/backend/src/CRM/reporting/dashboard/dto/task-summary-report.dto.ts new file mode 100644 index 0000000..7a83076 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/task-summary-report.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class TaskSummaryReportDto { + @ApiProperty({ description: 'Total tasks' }) + @IsNumber() + total: number; + + @ApiProperty({ description: 'Completed tasks' }) + @IsNumber() + completed: number; + + @ApiProperty({ description: 'Expired tasks' }) + @IsNumber() + expired: number; + + @ApiProperty({ description: 'No task' }) + @IsNumber() + noTask: number; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/top-sellers-report.dto.ts b/backend/src/CRM/reporting/dashboard/dto/top-sellers-report.dto.ts new file mode 100644 index 0000000..4c42cdf --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/top-sellers-report.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { QuantityAmountDto } from '@/common'; + +import { UserQuantityAmountDto } from './user-quantity-amount.dto'; + +export class TopSellersReportDto { + @ApiProperty({ type: [UserQuantityAmountDto], description: 'List of users with quantity and amount' }) + users: UserQuantityAmountDto[]; + + @ApiProperty({ type: QuantityAmountDto, description: 'Other users with quantity and amount' }) + others: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Total quantity and amount' }) + total: QuantityAmountDto; +} diff --git a/backend/src/CRM/reporting/dashboard/dto/user-quantity-amount.dto.ts b/backend/src/CRM/reporting/dashboard/dto/user-quantity-amount.dto.ts new file mode 100644 index 0000000..c889078 --- /dev/null +++ b/backend/src/CRM/reporting/dashboard/dto/user-quantity-amount.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { QuantityAmountDto } from '@/common'; + +export class UserQuantityAmountDto extends QuantityAmountDto { + @ApiProperty({ description: 'User ID' }) + @IsNumber() + userId: number; +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-entity.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-entity.dto.ts new file mode 100644 index 0000000..fcf4dda --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-entity.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { QuantityAmountDto } from '@/common'; + +export class CrmGeneralReportEntityDto { + @ApiProperty({ type: QuantityAmountDto }) + all: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto }) + open: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto }) + lost: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto }) + won: QuantityAmountDto; + + @ApiProperty() + @IsNumber() + avgAmount: number; + + @ApiProperty() + @IsNumber() + avgClose: number; + + constructor( + all: QuantityAmountDto, + open: QuantityAmountDto, + lost: QuantityAmountDto, + won: QuantityAmountDto, + avgAmount: number, + avgClose: number, + ) { + this.all = all; + this.open = open; + this.lost = lost; + this.won = won; + this.avgAmount = avgAmount; + this.avgClose = avgClose; + } +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-field-meta.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-field-meta.dto.ts new file mode 100644 index 0000000..c697d39 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-field-meta.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; +import { CrmGeneralReportFieldOptionMetaDto } from './crm-general-report-field-option-meta.dto'; + +export class CrmGeneralReportFieldMetaDto { + @ApiProperty() + @IsNumber() + fieldId: number; + + @ApiProperty() + @IsString() + fieldName: string; + + @ApiPropertyOptional({ type: [CrmGeneralReportFieldOptionMetaDto], nullable: true }) + @IsOptional() + @IsArray() + values?: CrmGeneralReportFieldOptionMetaDto[] | null; +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-field-option-meta.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-field-option-meta.dto.ts new file mode 100644 index 0000000..64cd8ac --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-field-option-meta.dto.ts @@ -0,0 +1,21 @@ +import { FieldFormat } from '@/modules/entity/entity-field/field'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +export class CrmGeneralReportFieldOptionMetaDto { + @ApiProperty() + @IsNumber() + optionId: number; + + @ApiProperty() + optionLabel: string | boolean; + + @ApiPropertyOptional({ + enum: FieldFormat, + nullable: true, + description: 'Field format of display for specific field types', + }) + @IsOptional() + @IsEnum(FieldFormat) + format?: FieldFormat | null; +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-field-value.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-field-value.dto.ts new file mode 100644 index 0000000..a1831c9 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-field-value.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class CrmGeneralReportFieldValueDto { + @ApiProperty() + @IsNumber() + optionId: number; + + @ApiProperty() + optionLabel: string | boolean; + + @ApiProperty() + @IsNumber() + quantity: number; + + @ApiProperty() + @IsNumber() + amount: number; + + constructor(optionId: number, optionLabel: string | boolean, quantity: number, amount: number) { + this.optionId = optionId; + this.optionLabel = optionLabel; + this.quantity = quantity; + this.amount = amount; + } +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-field.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-field.dto.ts new file mode 100644 index 0000000..36bff67 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-field.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsString } from 'class-validator'; + +import { CrmGeneralReportFieldValueDto } from './crm-general-report-field-value.dto'; + +export class CrmGeneralReportFieldDto { + @ApiProperty() + @IsNumber() + fieldId: number; + + @ApiProperty() + @IsString() + fieldName: string; + + @ApiProperty({ type: [CrmGeneralReportFieldValueDto] }) + @IsArray() + values: CrmGeneralReportFieldValueDto[]; + + constructor(fieldId: number, fieldName: string, values: CrmGeneralReportFieldValueDto[]) { + this.fieldId = fieldId; + this.fieldName = fieldName; + this.values = values; + } +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-meta.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-meta.dto.ts new file mode 100644 index 0000000..579066f --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-meta.dto.ts @@ -0,0 +1,7 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CrmGeneralReportFieldMetaDto } from './crm-general-report-field-meta.dto'; + +export class CrmGeneralReportMetaDto { + @ApiProperty({ type: [CrmGeneralReportFieldMetaDto] }) + fields: CrmGeneralReportFieldMetaDto[]; +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-row.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-row.dto.ts new file mode 100644 index 0000000..a0a596e --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-row.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { CrmGeneralReportEntityDto } from './crm-general-report-entity.dto'; +import { CrmGeneralReportTaskDto } from './crm-general-report-task.dto'; +import { CrmGeneralReportFieldDto } from './crm-general-report-field.dto'; + +export class CrmGeneralReportRowDto { + @ApiProperty() + @IsNumber() + ownerId: number; + + @ApiProperty({ type: CrmGeneralReportEntityDto }) + entity: CrmGeneralReportEntityDto; + + @ApiProperty({ type: CrmGeneralReportTaskDto }) + task: CrmGeneralReportTaskDto; + + @ApiProperty({ type: CrmGeneralReportTaskDto }) + activity: CrmGeneralReportTaskDto; + + @ApiProperty({ type: [CrmGeneralReportFieldDto] }) + fields: CrmGeneralReportFieldDto[]; + + constructor( + ownerId: number, + entity: CrmGeneralReportEntityDto, + task: CrmGeneralReportTaskDto, + activity: CrmGeneralReportTaskDto, + fields: CrmGeneralReportFieldDto[], + ) { + this.ownerId = ownerId; + this.entity = entity; + this.task = task; + this.activity = activity; + this.fields = fields; + } +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report-task.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report-task.dto.ts new file mode 100644 index 0000000..55eb520 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report-task.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class CrmGeneralReportTaskDto { + @ApiProperty() + @IsNumber() + all: number; + + @ApiProperty() + @IsNumber() + open: number; + + @ApiProperty() + @IsNumber() + expired: number; + + @ApiProperty() + @IsNumber() + resolved: number; + + constructor(all: number, open: number, expired: number, resolved: number) { + this.all = all; + this.open = open; + this.expired = expired; + this.resolved = resolved; + } +} diff --git a/backend/src/CRM/reporting/general/dto/crm-general-report.dto.ts b/backend/src/CRM/reporting/general/dto/crm-general-report.dto.ts new file mode 100644 index 0000000..2850d81 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/crm-general-report.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { CrmGeneralReportMetaDto } from './crm-general-report-meta.dto'; +import { CrmGeneralReportRowDto } from './crm-general-report-row.dto'; + +export class CrmGeneralReportDto { + @ApiProperty({ type: [CrmGeneralReportRowDto] }) + users: CrmGeneralReportRowDto[]; + + @ApiProperty({ type: [CrmGeneralReportRowDto] }) + departments: CrmGeneralReportRowDto[]; + + @ApiProperty({ type: CrmGeneralReportRowDto }) + total: CrmGeneralReportRowDto; + + @ApiProperty({ type: CrmGeneralReportMetaDto }) + meta: CrmGeneralReportMetaDto; + + constructor( + users: CrmGeneralReportRowDto[], + departments: CrmGeneralReportRowDto[], + total: CrmGeneralReportRowDto, + meta: CrmGeneralReportMetaDto, + ) { + this.users = users; + this.departments = departments; + this.total = total; + this.meta = meta; + } +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-call.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-call.dto.ts new file mode 100644 index 0000000..bdfdcc5 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-call.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class GeneralReportFilterVisibilityCallDto { + @ApiPropertyOptional({ nullable: true, description: 'Exclude calls block' }) + @IsOptional() + @IsBoolean() + exclude?: boolean | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-entity.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-entity.dto.ts new file mode 100644 index 0000000..ce21989 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-entity.dto.ts @@ -0,0 +1,24 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class GeneralReportFilterVisibilityEntityDto { + @ApiPropertyOptional({ nullable: true, description: 'Exclude entities block' }) + @IsOptional() + @IsBoolean() + exclude?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude open entities' }) + @IsOptional() + @IsBoolean() + excludeOpen?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude lost entities' }) + @IsOptional() + @IsBoolean() + excludeLost?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude won entities' }) + @IsOptional() + @IsBoolean() + excludeWon?: boolean | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-field-option.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-field-option.dto.ts new file mode 100644 index 0000000..2dae7cb --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-field-option.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional } from 'class-validator'; + +export class GeneralReportFilterVisibilityFieldOptionDto { + @ApiProperty({ nullable: true, description: 'Option ID' }) + @IsNumber() + optionId: number; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude option' }) + @IsOptional() + @IsBoolean() + exclude?: boolean | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-field.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-field.dto.ts new file mode 100644 index 0000000..516a336 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-field.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional } from 'class-validator'; +import { GeneralReportFilterVisibilityFieldOptionDto } from './general-report-filter-visibility-field-option.dto'; + +export class GeneralReportFilterVisibilityFieldDto { + @ApiProperty({ nullable: true, description: 'Field ID' }) + @IsNumber() + fieldId: number; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude field' }) + @IsOptional() + @IsBoolean() + exclude?: boolean | null; + + @ApiPropertyOptional({ + type: GeneralReportFilterVisibilityFieldOptionDto, + nullable: true, + description: 'Field options', + }) + @IsOptional() + options?: GeneralReportFilterVisibilityFieldOptionDto[] | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-fields.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-fields.dto.ts new file mode 100644 index 0000000..3bf9a4a --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-fields.dto.ts @@ -0,0 +1,15 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +import { GeneralReportFilterVisibilityFieldDto } from './general-report-filter-visibility-field.dto'; + +export class GeneralReportFilterVisibilityFieldsDto { + @ApiPropertyOptional({ nullable: true, description: 'Exclude fields block' }) + @IsOptional() + @IsBoolean() + exclude?: boolean | null; + + @ApiPropertyOptional({ type: [GeneralReportFilterVisibilityFieldDto], nullable: true, description: 'Fields' }) + @IsOptional() + fields?: GeneralReportFilterVisibilityFieldDto[] | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-task.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-task.dto.ts new file mode 100644 index 0000000..630b156 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility-task.dto.ts @@ -0,0 +1,24 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class GeneralReportFilterVisibilityTaskDto { + @ApiPropertyOptional({ nullable: true, description: 'Exclude tasks block' }) + @IsOptional() + @IsBoolean() + exclude?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude open tasks' }) + @IsOptional() + @IsBoolean() + excludeOpen?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude expired tasks' }) + @IsOptional() + @IsBoolean() + excludeExpired?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Exclude resolved tasks' }) + @IsOptional() + @IsBoolean() + excludeResolved?: boolean | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter-visibility.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility.dto.ts new file mode 100644 index 0000000..5f4d4b6 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter-visibility.dto.ts @@ -0,0 +1,41 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +import { GeneralReportFilterVisibilityEntityDto } from './general-report-filter-visibility-entity.dto'; +import { GeneralReportFilterVisibilityTaskDto } from './general-report-filter-visibility-task.dto'; +import { GeneralReportFilterVisibilityFieldsDto } from './general-report-filter-visibility-fields.dto'; +import { GeneralReportFilterVisibilityCallDto } from './general-report-filter-visibility-call.dto'; + +export class GeneralReportFilterVisibilityDto { + @ApiPropertyOptional({ + type: GeneralReportFilterVisibilityEntityDto, + nullable: true, + description: 'Entities visibility', + }) + @IsOptional() + entity?: GeneralReportFilterVisibilityEntityDto | null; + + @ApiPropertyOptional({ type: GeneralReportFilterVisibilityTaskDto, nullable: true, description: 'Tasks visibility' }) + @IsOptional() + task?: GeneralReportFilterVisibilityTaskDto | null; + + @ApiPropertyOptional({ + type: GeneralReportFilterVisibilityTaskDto, + nullable: true, + description: 'Activities visibility', + }) + @IsOptional() + activity?: GeneralReportFilterVisibilityTaskDto | null; + + @ApiPropertyOptional({ + type: GeneralReportFilterVisibilityFieldsDto, + nullable: true, + description: 'Fields visibility', + }) + @IsOptional() + fields?: GeneralReportFilterVisibilityFieldsDto | null; + + @ApiPropertyOptional({ type: GeneralReportFilterVisibilityCallDto, nullable: true, description: 'Calls visibility' }) + @IsOptional() + call?: GeneralReportFilterVisibilityCallDto | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-filter.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-filter.dto.ts new file mode 100644 index 0000000..53c2dfe --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-filter.dto.ts @@ -0,0 +1,45 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +import { BoardStageType } from '../../../board-stage'; +import { GeneralReportType } from '../enums'; +import { GeneralReportFilterVisibilityDto } from './general-report-filter-visibility.dto'; + +export class GeneralReportFilterDto { + @ApiProperty({ enum: GeneralReportType, description: 'Report type' }) + @IsEnum(GeneralReportType) + type: GeneralReportType; + + @ApiPropertyOptional({ description: 'Field ID for owner instead of responsibleUserId' }) + @IsOptional() + ownerFieldId?: number; + + @ApiPropertyOptional({ description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Board IDs' }) + @IsOptional() + @IsArray() + boardIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs' }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + @ApiPropertyOptional({ enum: BoardStageType, nullable: true, description: 'Stage type' }) + @IsOptional() + @IsEnum(BoardStageType) + stageType?: BoardStageType | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; + + @ApiPropertyOptional({ type: GeneralReportFilterVisibilityDto, nullable: true, description: 'Visibility' }) + @IsOptional() + visibility?: GeneralReportFilterVisibilityDto | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report-row.dto.ts b/backend/src/CRM/reporting/general/dto/general-report-row.dto.ts new file mode 100644 index 0000000..bf9649a --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report-row.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { CallReportBlockDto } from '@/modules/telephony/voximplant/voximplant-reporting/dto/call-report-block.dto'; +import { CrmGeneralReportEntityDto } from '@/CRM/reporting/general/dto/crm-general-report-entity.dto'; +import { CrmGeneralReportFieldDto } from '@/CRM/reporting/general/dto/crm-general-report-field.dto'; +import { CrmGeneralReportTaskDto } from '@/CRM/reporting/general/dto/crm-general-report-task.dto'; + +export class GeneralReportRowDto { + @ApiProperty({ description: 'Owner ID' }) + @IsNumber() + ownerId: number; + + @ApiPropertyOptional({ nullable: true, type: CrmGeneralReportEntityDto, description: 'Entities report block' }) + @IsOptional() + entity?: CrmGeneralReportEntityDto | null; + + @ApiPropertyOptional({ nullable: true, type: CrmGeneralReportTaskDto, description: 'Tasks report block' }) + @IsOptional() + task?: CrmGeneralReportTaskDto | null; + + @ApiPropertyOptional({ nullable: true, type: CrmGeneralReportTaskDto, description: 'Activities report block' }) + @IsOptional() + activity?: CrmGeneralReportTaskDto | null; + + @ApiPropertyOptional({ nullable: true, type: [CrmGeneralReportFieldDto], description: 'Fields report block' }) + @IsOptional() + fields?: CrmGeneralReportFieldDto[] | null; + + @ApiPropertyOptional({ nullable: true, type: CallReportBlockDto, description: 'Calls report block' }) + @IsOptional() + call?: CallReportBlockDto | null; +} diff --git a/backend/src/CRM/reporting/general/dto/general-report.dto.ts b/backend/src/CRM/reporting/general/dto/general-report.dto.ts new file mode 100644 index 0000000..249c737 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/general-report.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { CrmGeneralReportMetaDto } from '@/CRM/reporting/general/dto/crm-general-report-meta.dto'; +import { GeneralReportRowDto } from './general-report-row.dto'; + +export class GeneralReportDto { + @ApiProperty({ type: [GeneralReportRowDto], description: 'Users' }) + users: GeneralReportRowDto[]; + + @ApiProperty({ type: [GeneralReportRowDto], description: 'Departments' }) + departments: GeneralReportRowDto[]; + + @ApiProperty({ type: GeneralReportRowDto, description: 'Total' }) + total: GeneralReportRowDto; + + @ApiProperty({ type: CrmGeneralReportMetaDto, description: 'Meta' }) + meta: CrmGeneralReportMetaDto; +} diff --git a/backend/src/CRM/reporting/general/dto/index.ts b/backend/src/CRM/reporting/general/dto/index.ts new file mode 100644 index 0000000..8b1d705 --- /dev/null +++ b/backend/src/CRM/reporting/general/dto/index.ts @@ -0,0 +1,19 @@ +export * from './crm-general-report-entity.dto'; +export * from './crm-general-report-field-meta.dto'; +export * from './crm-general-report-field-option-meta.dto'; +export * from './crm-general-report-field-value.dto'; +export * from './crm-general-report-field.dto'; +export * from './crm-general-report-meta.dto'; +export * from './crm-general-report-row.dto'; +export * from './crm-general-report-task.dto'; +export * from './crm-general-report.dto'; +export * from './general-report-filter-visibility-call.dto'; +export * from './general-report-filter-visibility-entity.dto'; +export * from './general-report-filter-visibility-field-option.dto'; +export * from './general-report-filter-visibility-field.dto'; +export * from './general-report-filter-visibility-fields.dto'; +export * from './general-report-filter-visibility-task.dto'; +export * from './general-report-filter-visibility.dto'; +export * from './general-report-filter.dto'; +export * from './general-report-row.dto'; +export * from './general-report.dto'; diff --git a/backend/src/CRM/reporting/general/enums/general-report-type.enum.ts b/backend/src/CRM/reporting/general/enums/general-report-type.enum.ts new file mode 100644 index 0000000..219501a --- /dev/null +++ b/backend/src/CRM/reporting/general/enums/general-report-type.enum.ts @@ -0,0 +1,5 @@ +export enum GeneralReportType { + User = 'user', + Rating = 'rating', + Department = 'department', +} diff --git a/backend/src/CRM/reporting/general/enums/index.ts b/backend/src/CRM/reporting/general/enums/index.ts new file mode 100644 index 0000000..03aed19 --- /dev/null +++ b/backend/src/CRM/reporting/general/enums/index.ts @@ -0,0 +1 @@ +export * from './general-report-type.enum'; diff --git a/backend/src/CRM/reporting/general/general-report.controller.ts b/backend/src/CRM/reporting/general/general-report.controller.ts new file mode 100644 index 0000000..c3b4e20 --- /dev/null +++ b/backend/src/CRM/reporting/general/general-report.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { GeneralReportService } from './general-report.service'; +import { GeneralReportFilterDto, GeneralReportDto } from './dto'; + +@ApiTags('crm/reporting') +@Controller('crm/reporting/general') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class GeneralReportController { + constructor(private readonly service: GeneralReportService) {} + + @ApiOperation({ summary: 'Get general report', description: 'Get general report' }) + @ApiBody({ type: GeneralReportFilterDto, required: true, description: 'General report filter' }) + @ApiOkResponse({ description: 'General report', type: GeneralReportDto }) + @Post() + public async getGeneralReport(@CurrentAuth() { accountId, user }: AuthData, @Body() filter: GeneralReportFilterDto) { + return this.service.getGeneralReport({ accountId, user, filter }); + } +} diff --git a/backend/src/CRM/reporting/general/general-report.service.ts b/backend/src/CRM/reporting/general/general-report.service.ts new file mode 100644 index 0000000..7f54a46 --- /dev/null +++ b/backend/src/CRM/reporting/general/general-report.service.ts @@ -0,0 +1,823 @@ +import { Injectable } from '@nestjs/common'; + +import { DatePeriod, ForbiddenError, intersection, propagateData } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization'; +import { DepartmentService } from '@/modules/iam/department'; +import { User } from '@/modules/iam/user/entities'; +import { FieldType, FieldTypes } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; +import { TelephonyReportType } from '@/modules/telephony/voximplant/voximplant-reporting/enums'; +import { CallReportRow } from '@/modules/telephony/voximplant/voximplant-reporting/types'; +// eslint-disable-next-line max-len +import { VoximplantReportingService } from '@/modules/telephony/voximplant/voximplant-reporting/voximplant-reporting.service'; + +import { BoardStageService, GroupedStages } from '../../board-stage'; +import { EntityType } from '../../entity-type'; + +import { OwnerDateValue, ReportRowOwner } from '../common'; +import { CrmReportingService } from '../crm-reporting.service'; + +import { GeneralReportFilterDto } from './dto'; +import { + CrmGeneralReport, + CrmGeneralReportField, + CrmGeneralReportFieldMeta, + CrmGeneralReportFieldOptionMeta, + CrmGeneralReportFieldValue, + CrmGeneralReportMeta, + CrmGeneralReportRow, + GeneralReport, + GeneralReportRow, +} from './types'; +import { GeneralReportType } from './enums'; + +interface VisibilityEntity { + exclude?: boolean | null; + excludeOpen?: boolean | null; + excludeLost?: boolean | null; + excludeWon?: boolean | null; +} +interface VisibilityTask { + exclude?: boolean | null; + excludeOpen?: boolean | null; + excludeExpired?: boolean | null; + excludeResolved?: boolean | null; +} +interface VisibilityFieldOption { + optionId: number; + exclude?: boolean | null; +} +interface VisibilityField { + fieldId: number; + exclude?: boolean | null; + options?: VisibilityFieldOption[] | null; +} +interface VisibilityFields { + exclude?: boolean | null; + fields?: VisibilityField[] | null; +} +interface VisibilityCall { + exclude?: boolean | null; +} +interface Visibility { + entity?: VisibilityEntity | null; + task?: VisibilityTask | null; + activity?: VisibilityTask | null; + fields?: VisibilityFields | null; + call?: VisibilityCall | null; +} + +interface Filter { + include: { users: boolean; departments: boolean }; + stages: GroupedStages; + period?: DatePeriod; + userIds?: number[]; + visibility?: Visibility | null; +} + +@Injectable() +export class GeneralReportService { + constructor( + private readonly authService: AuthorizationService, + private readonly departmentService: DepartmentService, + private readonly stageService: BoardStageService, + private readonly reportingService: CrmReportingService, + private readonly fieldService: FieldService, + private readonly telephonyReporting: VoximplantReportingService, + ) {} + + public async getGeneralReport({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: GeneralReportFilterDto; + }): Promise { + const crmReport = await this.getCrmGeneralReport(accountId, user, filter); + const callReport = !filter.visibility?.call?.exclude + ? await this.telephonyReporting.getCallReport({ + accountId, + user, + filter: { ...filter, type: this.toTelephoneReportType(filter.type) }, + }) + : null; + + const users = this.combineGeneralReportRows(crmReport.users, callReport?.users); + const departments = this.combineGeneralReportRows(crmReport.departments, callReport?.departments); + + const total = new GeneralReportRow( + crmReport.total?.ownerId, + crmReport.total?.entity, + crmReport.total?.task, + crmReport.total?.activity, + crmReport.total?.field, + callReport?.total?.call, + ); + + return new GeneralReport(users, departments, total, crmReport.meta); + } + + private combineGeneralReportRows( + crmRows: Map | null | undefined, + callRows: Map | null | undefined, + ): Map { + const rows: Map = new Map(); + if (crmRows) { + for (const [key, crmRow] of crmRows) { + const callRaw = callRows?.get(key); + rows.set( + key, + new GeneralReportRow(key, crmRow.entity, crmRow.task, crmRow.activity, crmRow.field, callRaw?.call), + ); + } + } + if (callRows) { + for (const [key, callRaw] of callRows) { + if (!rows.has(key)) { + rows.set(key, new GeneralReportRow(key, null, null, null, null, callRaw?.call)); + } + } + } + return rows; + } + private toTelephoneReportType(type: GeneralReportType): TelephonyReportType { + switch (type) { + case GeneralReportType.Department: + return TelephonyReportType.Department; + case GeneralReportType.Rating: + return TelephonyReportType.Rating; + case GeneralReportType.User: + return TelephonyReportType.User; + } + } + + private async getCrmGeneralReport( + accountId: number, + user: User, + filter: GeneralReportFilterDto, + ): Promise { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: EntityType.getAuthorizable(filter.entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId: filter.entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + type: filter.stageType, + }); + const include = { + users: filter.type !== GeneralReportType.Department, + departments: filter.type !== GeneralReportType.Rating, + }; + + const report = await this.getReport( + accountId, + filter.entityTypeId, + { + include, + stages, + period: filter.period ? DatePeriod.fromFilter(filter.period) : undefined, + userIds: filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds, + visibility: filter.visibility, + }, + filter.ownerFieldId, + filter.type === GeneralReportType.Rating, + ); + + if (report.departments.size) { + const useWon = stages.won?.length > 0; + const hierarchy = await this.departmentService.getHierarchy({ accountId }); + + if (hierarchy.length) { + propagateData(hierarchy, report.departments, (ownerId: number) => { + return CrmGeneralReport.createEmptyRow(ownerId, useWon); + }); + } + } + + return report; + } + + private async getReport( + accountId: number, + entityTypeId: number, + filter: Filter, + ownerFieldId: number | undefined, + sort: boolean, + ): Promise { + const useWon = filter.stages.won?.length > 0; + const [meta, users, departments, total] = await Promise.all([ + this.getReportMeta(accountId, entityTypeId), + filter.include.users + ? this.getReportGroupBy(accountId, entityTypeId, 'user', ownerFieldId, useWon, sort, filter) + : Promise.resolve(new Map()), + filter.include.departments + ? await this.getReportGroupBy(accountId, entityTypeId, 'department', ownerFieldId, useWon, sort, filter) + : Promise.resolve(new Map()), + this.getReportGroupBy(accountId, entityTypeId, 'total', ownerFieldId, useWon, false, filter), + ]); + + return new CrmGeneralReport(users, departments, total.values().next().value, meta); + } + + private async getReportMeta(accountId: number, entityTypeId: number): Promise { + const fieldsMeta: CrmGeneralReportFieldMeta[] = ( + await Promise.all([ + this.getSelectFieldsMeta({ accountId, entityTypeId }), + this.getSwitchFieldsMeta({ accountId, entityTypeId }), + this.getFormulaFieldsMeta({ accountId, entityTypeId }), + ]) + ).flat(); + return new CrmGeneralReportMeta(fieldsMeta); + } + private async getSelectFieldsMeta({ + accountId, + entityTypeId, + }: { + accountId: number; + entityTypeId: number; + }): Promise { + const fields = await this.fieldService.findMany( + { + accountId, + entityTypeId, + type: [...FieldTypes.select, ...FieldTypes.multiSelect], + }, + { expand: ['options'] }, + ); + return fields.map( + (field) => + new CrmGeneralReportFieldMeta( + field.id, + field.name, + field.options.map((o) => new CrmGeneralReportFieldOptionMeta(o.id, o.label, field.format)), + ), + ); + } + private async getSwitchFieldsMeta({ + accountId, + entityTypeId, + }: { + accountId: number; + entityTypeId: number; + }): Promise { + const fields = await this.fieldService.findMany({ accountId, entityTypeId, type: FieldType.Switch }); + return fields.map( + (field) => + new CrmGeneralReportFieldMeta(field.id, field.name, [ + new CrmGeneralReportFieldOptionMeta(0, false, field.format), + new CrmGeneralReportFieldOptionMeta(1, true, field.format), + ]), + ); + } + private async getFormulaFieldsMeta({ + accountId, + entityTypeId, + }: { + accountId: number; + entityTypeId: number; + }): Promise { + const fields = await this.fieldService.findMany({ + accountId, + entityTypeId, + type: [FieldType.Formula, FieldType.Number], + }); + return fields.map( + (field) => + new CrmGeneralReportFieldMeta(field.id, field.name, [new CrmGeneralReportFieldOptionMeta(0, '', field.format)]), + ); + } + + private async getReportGroupBy( + accountId: number, + entityTypeId: number, + owner: ReportRowOwner, + ownerFieldId: number | undefined, + useWon: boolean, + sort: boolean, + filter: Filter, + ): Promise> { + const rowMap = new Map(); + + if (!filter.visibility?.entity?.exclude) + await this.processEntities(accountId, owner, ownerFieldId, useWon, filter, rowMap); + if (!filter.visibility?.task?.exclude) + await this.processTasks(accountId, filter.stages.all, useWon, owner, filter, rowMap); + if (!filter.visibility?.activity?.exclude) + await this.processActivities(accountId, filter.stages.all, useWon, owner, filter, rowMap); + + if (!filter.visibility?.fields?.exclude) { + const excludeFields = filter.visibility?.fields?.fields?.filter((f) => f.exclude).map((f) => f.fieldId); + await this.processSelectField( + accountId, + entityTypeId, + owner, + ownerFieldId, + useWon, + filter, + excludeFields, + rowMap, + ); + await this.processMultiSelectField( + accountId, + entityTypeId, + owner, + ownerFieldId, + useWon, + filter, + excludeFields, + rowMap, + ); + await this.processSwitchField( + accountId, + entityTypeId, + owner, + ownerFieldId, + useWon, + filter, + excludeFields, + rowMap, + ); + await this.processNumberField( + accountId, + entityTypeId, + owner, + ownerFieldId, + useWon, + filter, + excludeFields, + rowMap, + ); + } + + if (sort) { + const rows = Array.from(rowMap.values()); + + const sorted = rows.sort((rowA, rowB) => + useWon ? rowB.entity.won.amount - rowA.entity.won.amount : rowB.entity.all.amount - rowA.entity.all.amount, + ); + + return new Map(sorted.map((row) => [row.ownerId, row])); + } + + return rowMap; + } + + private async processEntities( + accountId: number, + owner: ReportRowOwner, + userOwnerFieldId: number | undefined, + useWon: boolean, + filter: Filter, + rowMap: Map, + ) { + const [open, lost, won] = await Promise.all([ + filter.stages.open?.length && !filter.visibility?.entity?.excludeOpen + ? this.reportingService.getEntityGroupBy( + accountId, + filter.stages.open, + { owner, userOwnerFieldId }, + { amount: true, quantity: true }, + { createdAt: filter.period, userIds: filter.userIds }, + ) + : Promise.resolve({ quantity: [], amount: [] }), + filter.stages.lost?.length && !filter.visibility?.entity?.excludeLost + ? this.reportingService.getEntityGroupBy( + accountId, + filter.stages.lost, + { owner, userOwnerFieldId }, + { amount: true, quantity: true, close: !useWon }, + { closedAt: filter.period, userIds: filter.userIds }, + ) + : Promise.resolve({ quantity: [], amount: [], close: [] }), + filter.stages.won?.length && !filter.visibility?.entity?.excludeWon + ? this.reportingService.getEntityGroupBy( + accountId, + filter.stages.won, + { owner, userOwnerFieldId }, + { amount: true, quantity: true, close: true }, + { closedAt: filter.period, userIds: filter.userIds }, + ) + : Promise.resolve({ quantity: [], amount: [], close: [] }), + ]); + for (const { ownerId, value } of open.quantity) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.all.quantity += value; + values.entity.open.quantity = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of open.amount) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.all.amount += value; + values.entity.open.amount = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of lost.quantity) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.all.quantity += value; + values.entity.lost.quantity = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of lost.amount) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.all.amount += value; + values.entity.lost.amount = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of lost.close) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.close = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of won.quantity) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.all.quantity += value; + values.entity.won.quantity = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of won.amount) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.all.amount += value; + values.entity.won.amount = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of won.close) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.entity.close = value; + rowMap.set(ownerId, values); + } + } + private async processTasks( + accountId: number, + stageIds: number[], + useWon: boolean, + owner: ReportRowOwner, + filter: Filter, + rowMap: Map, + ) { + const [open, expired, resolved] = await this.getTasksOrActivity(accountId, 'task', stageIds, owner, filter); + + for (const { ownerId, value } of open) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.task.all += value; + values.task.open = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of expired) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.task.expired = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of resolved) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.task.all += value; + values.task.resolved = value; + rowMap.set(ownerId, values); + } + } + private async processActivities( + accountId: number, + stageIds: number[], + useWon: boolean, + owner: ReportRowOwner, + filter: Filter, + rowMap: Map, + ) { + const [open, expired, resolved] = await this.getTasksOrActivity(accountId, 'activity', stageIds, owner, filter); + + for (const { ownerId, value } of open) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.activity.all += value; + values.activity.open = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of expired) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.activity.expired = value; + rowMap.set(ownerId, values); + } + for (const { ownerId, value } of resolved) { + const values = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + values.activity.all += value; + values.activity.resolved = value; + rowMap.set(ownerId, values); + } + } + private async getTasksOrActivity( + accountId: number, + source: 'task' | 'activity', + stageIds: number[], + owner: ReportRowOwner, + filter: Filter, + ): Promise<[OwnerDateValue[], OwnerDateValue[], OwnerDateValue[]]> { + return Promise.all([ + !filter.visibility?.task?.excludeOpen + ? this.reportingService.getTaskOrActivityGroupBy( + accountId, + source, + stageIds, + { owner }, + { resolved: false, period: filter.period, dateField: 'created_at', ownerIds: filter.userIds }, + ) + : Promise.resolve([] as OwnerDateValue[]), + !filter.visibility?.task?.excludeExpired + ? this.reportingService.getTaskOrActivityGroupBy( + accountId, + source, + stageIds, + { owner }, + { + expired: true, + resolved: false, + period: filter.period, + dateField: 'created_at', + ownerIds: filter.userIds, + }, + ) + : Promise.resolve([] as OwnerDateValue[]), + !filter.visibility?.task?.excludeResolved + ? this.reportingService.getTaskOrActivityGroupBy( + accountId, + source, + stageIds, + { owner }, + { resolved: true, period: filter.period, dateField: 'created_at', ownerIds: filter.userIds }, + ) + : Promise.resolve([] as OwnerDateValue[]), + ]); + } + private async processSelectField( + accountId: number, + entityTypeId: number, + owner: ReportRowOwner, + userOwnerFieldId: number | undefined, + useWon: boolean, + filter: Filter, + excludeFieldIds: number[] | null | undefined, + rowMap: Map, + ) { + const fields = await this.fieldService.findMany( + { + accountId, + entityTypeId, + type: FieldTypes.select, + excludeId: excludeFieldIds, + }, + { expand: ['options'] }, + ); + const results = await Promise.all( + fields + .map((field) => { + // const excludeIds = filter.visibility?.fields?.fields + // ?.find((f) => f.fieldId === field.id) + // ?.options?.filter((o) => o.exclude) + // .map((o) => o.optionId); + // eslint-disable-next-line max-len + // const options = excludeIds?.length ? field.options.filter((o) => !excludeIds.includes(o.id)) : field.options; + const options = field.options; + return options.map(async (option) => ({ + fieldId: field.id, + fieldName: field.name, + optionId: option.id, + optionLabel: option.label, + result: await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.all, + { owner, userOwnerFieldId }, + { amount: true, quantity: true }, + { createdAt: filter.period, userIds: filter.userIds, field: { fieldId: field.id, optionId: option.id } }, + ), + })); + }) + .flat(), + ); + for (const { fieldId, fieldName, optionId, optionLabel, result } of results) { + for (const { ownerId, value } of result.quantity) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(optionId) ?? CrmGeneralReportFieldValue.empty(optionId, optionLabel); + grFieldValues.quantity = value; + grField.values.set(optionId, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + for (const { ownerId, value } of result.amount) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(optionId) ?? CrmGeneralReportFieldValue.empty(optionId, optionLabel); + grFieldValues.amount = value; + grField.values.set(optionId, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + } + } + private async processMultiSelectField( + accountId: number, + entityTypeId: number, + owner: ReportRowOwner, + userOwnerFieldId: number | undefined, + useWon: boolean, + filter: Filter, + excludeFieldIds: number[] | null | undefined, + rowMap: Map, + ) { + const fields = await this.fieldService.findMany( + { + accountId, + entityTypeId, + type: FieldTypes.multiSelect, + excludeId: excludeFieldIds, + }, + { expand: ['options'] }, + ); + const results = await Promise.all( + fields + .map((field) => { + // const excludeIds = filter.visibility?.fields?.fields + // ?.find((f) => f.fieldId === field.id) + // ?.options?.filter((o) => o.exclude) + // .map((o) => o.optionId); + // eslint-disable-next-line max-len + // const options = excludeIds?.length ? field.options.filter((o) => !excludeIds.includes(o.id)) : field.options; + const options = field.options; + return options.map(async (option) => ({ + fieldId: field.id, + fieldName: field.name, + optionId: option.id, + optionLabel: option.label, + result: await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.all, + { owner, userOwnerFieldId }, + { amount: true, quantity: true }, + { + createdAt: filter.period, + userIds: filter.userIds, + field: { fieldId: field.id, optionsId: option.id }, + }, + ), + })); + }) + .flat(), + ); + for (const { fieldId, fieldName, optionId, optionLabel, result } of results) { + for (const { ownerId, value } of result.quantity) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(optionId) ?? CrmGeneralReportFieldValue.empty(optionId, optionLabel); + grFieldValues.quantity = value; + grField.values.set(optionId, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + for (const { ownerId, value } of result.amount) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(optionId) ?? CrmGeneralReportFieldValue.empty(optionId, optionLabel); + grFieldValues.amount = value; + grField.values.set(optionId, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + } + } + private async processSwitchField( + accountId: number, + entityTypeId: number, + owner: ReportRowOwner, + userOwnerFieldId: number | undefined, + useWon: boolean, + filter: Filter, + excludeFieldIds: number[] | null | undefined, + rowMap: Map, + ) { + const fields = await this.fieldService.findMany({ + accountId, + entityTypeId, + type: FieldType.Switch, + excludeId: excludeFieldIds, + }); + const results = ( + await Promise.all( + fields.map(async (field) => [ + { + fieldId: field.id, + fieldName: field.name, + optionId: 1, + optionLabel: true, + result: await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.all, + { owner, userOwnerFieldId }, + { amount: true, quantity: true }, + { + createdAt: filter.period, + userIds: filter.userIds, + field: { fieldId: field.id, switch: true }, + }, + ), + }, + { + fieldId: field.id, + fieldName: field.name, + optionId: 0, + optionLabel: false, + result: await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.all, + { owner, userOwnerFieldId }, + { amount: true, quantity: true }, + { + createdAt: filter.period, + userIds: filter.userIds, + field: { fieldId: field.id, switch: false }, + }, + ), + }, + ]), + ) + ).flat(); + for (const { fieldId, fieldName, optionId, optionLabel, result } of results) { + for (const { ownerId, value } of result.quantity) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(optionId) ?? CrmGeneralReportFieldValue.empty(optionId, optionLabel); + grFieldValues.quantity = value; + grField.values.set(optionId, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + for (const { ownerId, value } of result.amount) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(optionId) ?? CrmGeneralReportFieldValue.empty(optionId, optionLabel); + grFieldValues.amount = value; + grField.values.set(optionId, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + } + } + private async processNumberField( + accountId: number, + entityTypeId: number, + owner: ReportRowOwner, + userOwnerFieldId: number | undefined, + useWon: boolean, + filter: Filter, + excludeFieldIds: number[] | null | undefined, + rowMap: Map, + ) { + const fields = await this.fieldService.findMany({ + accountId, + entityTypeId, + type: [FieldType.Number, FieldType.Formula], + excludeId: excludeFieldIds, + }); + const results = ( + await Promise.all( + fields.map(async (field) => ({ + fieldId: field.id, + fieldName: field.name, + result: await this.reportingService.getEntityGroupBy( + accountId, + filter.stages.all, + { owner, userOwnerFieldId }, + { fieldAmount: field.id, fieldQuantity: field.id }, + { createdAt: filter.period, userIds: filter.userIds }, + ), + })), + ) + ).flat(); + for (const { fieldId, fieldName, result } of results) { + for (const { ownerId, value } of result.fieldQuantity) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(0) ?? CrmGeneralReportFieldValue.empty(0, ''); + grFieldValues.quantity = value; + grField.values.set(0, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + for (const { ownerId, value } of result.fieldAmount) { + const grUser = rowMap.get(ownerId) ?? CrmGeneralReportRow.empty(ownerId, useWon); + const grField = grUser.field.get(fieldId) ?? CrmGeneralReportField.empty(fieldId, fieldName); + const grFieldValues = grField.values.get(0) ?? CrmGeneralReportFieldValue.empty(0, ''); + grFieldValues.amount = value; + grField.values.set(0, grFieldValues); + grUser.field.set(fieldId, grField); + rowMap.set(ownerId, grUser); + } + } + } +} diff --git a/backend/src/CRM/reporting/general/index.ts b/backend/src/CRM/reporting/general/index.ts new file mode 100644 index 0000000..e05c43a --- /dev/null +++ b/backend/src/CRM/reporting/general/index.ts @@ -0,0 +1,2 @@ +export * from './dto'; +export * from './types'; diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-entity.ts b/backend/src/CRM/reporting/general/types/crm-general-report-entity.ts new file mode 100644 index 0000000..428b7c8 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-entity.ts @@ -0,0 +1,70 @@ +import { QuantityAmount } from '@/common'; + +import { CrmGeneralReportEntityDto } from '../dto/crm-general-report-entity.dto'; + +export class CrmGeneralReportEntity { + useWon: boolean; + all: QuantityAmount; + open: QuantityAmount; + lost: QuantityAmount; + won: QuantityAmount; + close: number; + + constructor( + useWon: boolean, + all: QuantityAmount, + open: QuantityAmount, + lost: QuantityAmount, + won: QuantityAmount, + close: number, + ) { + this.useWon = useWon; + this.all = all; + this.open = open; + this.lost = lost; + this.won = won; + this.close = close; + } + + public static empty(useWon: boolean): CrmGeneralReportEntity { + return new CrmGeneralReportEntity( + useWon, + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + 0, + ); + } + + public get avgAmount(): number { + const avg = this.useWon ? this.won.amount / this.won.quantity : this.all.amount / this.all.quantity; + + return Number.isNaN(avg) ? 0 : avg; + } + + public get avgClose(): number { + const avg = this.useWon ? this.close / this.won.quantity : this.close / this.lost.quantity; + + return Number.isNaN(avg) ? 0 : avg; + } + + public toDto(): CrmGeneralReportEntityDto { + return new CrmGeneralReportEntityDto( + this.all.toDto(), + this.open.toDto(), + this.lost.toDto(), + this.won.toDto(), + this.avgAmount, + this.avgClose, + ); + } + + public add(entity: CrmGeneralReportEntity) { + this.all.add(entity.all); + this.open.add(entity.open); + this.won.add(entity.won); + this.lost.add(entity.lost); + this.close += entity.close; + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-field-meta.ts b/backend/src/CRM/reporting/general/types/crm-general-report-field-meta.ts new file mode 100644 index 0000000..8e47476 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-field-meta.ts @@ -0,0 +1,23 @@ +import { CrmGeneralReportFieldMetaDto } from '../dto'; + +import { CrmGeneralReportFieldOptionMeta } from './crm-general-report-field-option-meta'; + +export class CrmGeneralReportFieldMeta { + fieldId: number; + fieldName: string; + options?: CrmGeneralReportFieldOptionMeta[] | null; + + constructor(fieldId: number, fieldName: string, options?: CrmGeneralReportFieldOptionMeta[] | null) { + this.fieldId = fieldId; + this.fieldName = fieldName; + this.options = options; + } + + public toDto(): CrmGeneralReportFieldMetaDto { + return { + fieldId: this.fieldId, + fieldName: this.fieldName, + values: this.options?.map((o) => o.toDto()), + }; + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-field-option-meta.ts b/backend/src/CRM/reporting/general/types/crm-general-report-field-option-meta.ts new file mode 100644 index 0000000..efa3e05 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-field-option-meta.ts @@ -0,0 +1,18 @@ +import type { FieldFormat } from '@/modules/entity/entity-field/field'; +import { CrmGeneralReportFieldOptionMetaDto } from '../dto/crm-general-report-field-option-meta.dto'; + +export class CrmGeneralReportFieldOptionMeta { + optionId: number; + optionLabel: string | boolean; + format?: FieldFormat | null; + + constructor(optionId: number, optionLabel: string | boolean, format?: FieldFormat | null) { + this.optionId = optionId; + this.optionLabel = optionLabel; + this.format = format; + } + + public toDto(): CrmGeneralReportFieldOptionMetaDto { + return { optionId: this.optionId, optionLabel: this.optionLabel, format: this.format }; + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-field-value.ts b/backend/src/CRM/reporting/general/types/crm-general-report-field-value.ts new file mode 100644 index 0000000..d8fe114 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-field-value.ts @@ -0,0 +1,28 @@ +import { CrmGeneralReportFieldValueDto } from '../dto/crm-general-report-field-value.dto'; + +export class CrmGeneralReportFieldValue { + optionId: number; + optionLabel: string | boolean; + quantity: number; + amount: number; + + constructor(optionId: number, optionLabel: string | boolean, quantity: number, amount: number) { + this.optionId = optionId; + this.optionLabel = optionLabel; + this.quantity = quantity; + this.amount = amount; + } + + public static empty(optionId: number, optionLabel: string | boolean): CrmGeneralReportFieldValue { + return new CrmGeneralReportFieldValue(optionId, optionLabel, 0, 0); + } + + public toDto(): CrmGeneralReportFieldValueDto { + return new CrmGeneralReportFieldValueDto(this.optionId, this.optionLabel, this.quantity, this.amount); + } + + public add(value: CrmGeneralReportFieldValue) { + this.quantity += value.quantity; + this.amount += value.amount; + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-field.ts b/backend/src/CRM/reporting/general/types/crm-general-report-field.ts new file mode 100644 index 0000000..6ec60a8 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-field.ts @@ -0,0 +1,37 @@ +import { CrmGeneralReportFieldDto } from '../dto/crm-general-report-field.dto'; +import { CrmGeneralReportFieldValue } from './crm-general-report-field-value'; + +export class CrmGeneralReportField { + fieldId: number; + fieldName: string; + values: Map; + + constructor(fieldId: number, fieldName: string, values: Map) { + this.fieldId = fieldId; + this.fieldName = fieldName; + this.values = values; + } + + public static empty(fieldId: number, fieldName: string): CrmGeneralReportField { + return new CrmGeneralReportField(fieldId, fieldName, new Map()); + } + + public toDto(): CrmGeneralReportFieldDto { + return new CrmGeneralReportFieldDto( + this.fieldId, + this.fieldName, + Array.from(this.values.values()).map((v) => v.toDto()), + ); + } + + public add(field: CrmGeneralReportField) { + for (const [fieldValueId, fieldValue] of field.values) { + let value = this.values.get(fieldValueId); + if (!value) { + value = CrmGeneralReportFieldValue.empty(fieldValueId, fieldValue.optionLabel); + this.values.set(fieldValueId, value); + } + value.add(fieldValue); + } + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-meta.ts b/backend/src/CRM/reporting/general/types/crm-general-report-meta.ts new file mode 100644 index 0000000..40aa842 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-meta.ts @@ -0,0 +1,14 @@ +import { CrmGeneralReportMetaDto } from '../dto/crm-general-report-meta.dto'; +import { type CrmGeneralReportFieldMeta } from './crm-general-report-field-meta'; + +export class CrmGeneralReportMeta { + fields: CrmGeneralReportFieldMeta[]; + + constructor(fields: CrmGeneralReportFieldMeta[]) { + this.fields = fields; + } + + public toDto(): CrmGeneralReportMetaDto { + return { fields: this.fields.map((u) => u.toDto()) }; + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-row.ts b/backend/src/CRM/reporting/general/types/crm-general-report-row.ts new file mode 100644 index 0000000..b22e4ea --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-row.ts @@ -0,0 +1,65 @@ +import { CrmGeneralReportRowDto } from '../dto'; + +import { CrmGeneralReportEntity } from './crm-general-report-entity'; +import { CrmGeneralReportField } from './crm-general-report-field'; +import { CrmGeneralReportTask } from './crm-general-report-task'; + +export class CrmGeneralReportRow { + ownerId: number; + entity: CrmGeneralReportEntity; + task: CrmGeneralReportTask; + activity: CrmGeneralReportTask; + field: Map; + + constructor( + ownerId: number, + entity: CrmGeneralReportEntity, + task: CrmGeneralReportTask, + activity: CrmGeneralReportTask, + field: Map, + ) { + this.ownerId = ownerId; + this.entity = entity; + this.task = task; + this.activity = activity; + this.field = field; + } + + public static empty(ownerId: number, useWon: boolean): CrmGeneralReportRow { + return new CrmGeneralReportRow( + ownerId, + CrmGeneralReportEntity.empty(useWon), + CrmGeneralReportTask.empty(), + CrmGeneralReportTask.empty(), + new Map(), + ); + } + + public toDto(): CrmGeneralReportRowDto { + return new CrmGeneralReportRowDto( + this.ownerId, + this.entity.toDto(), + this.task.toDto(), + this.activity.toDto(), + Array.from(this.field.values()).map((v) => v.toDto()), + ); + } + + public add(row: CrmGeneralReportRow): CrmGeneralReportRow { + this.entity.add(row.entity); + + this.task.add(row.task); + this.activity.add(row.activity); + + for (const [rowFieldId, rowField] of row.field) { + let field = this.field.get(rowFieldId); + if (!field) { + field = CrmGeneralReportField.empty(rowFieldId, rowField.fieldName); + this.field.set(rowFieldId, field); + } + field.add(rowField); + } + + return this; + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report-task.ts b/backend/src/CRM/reporting/general/types/crm-general-report-task.ts new file mode 100644 index 0000000..13a61e1 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report-task.ts @@ -0,0 +1,30 @@ +import { CrmGeneralReportTaskDto } from '../dto/crm-general-report-task.dto'; + +export class CrmGeneralReportTask { + all: number; + open: number; + expired: number; + resolved: number; + + constructor(all: number, open: number, expired: number, resolved: number) { + this.all = all; + this.open = open; + this.expired = expired; + this.resolved = resolved; + } + + public static empty(): CrmGeneralReportTask { + return new CrmGeneralReportTask(0, 0, 0, 0); + } + + public toDto(): CrmGeneralReportTaskDto { + return new CrmGeneralReportTaskDto(this.all, this.open, this.expired, this.resolved); + } + + public add(task: CrmGeneralReportTask) { + this.all += task.all; + this.open += task.open; + this.expired += task.expired; + this.resolved += task.resolved; + } +} diff --git a/backend/src/CRM/reporting/general/types/crm-general-report.ts b/backend/src/CRM/reporting/general/types/crm-general-report.ts new file mode 100644 index 0000000..51b59d8 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/crm-general-report.ts @@ -0,0 +1,35 @@ +import { CrmGeneralReportDto } from '../dto/crm-general-report.dto'; +import { type CrmGeneralReportMeta } from './crm-general-report-meta'; +import { CrmGeneralReportRow } from './crm-general-report-row'; + +export class CrmGeneralReport { + users: Map; + departments: Map; + total: CrmGeneralReportRow | null; + meta: CrmGeneralReportMeta; + + constructor( + users: Map, + departments: Map, + total: CrmGeneralReportRow | null, + meta: CrmGeneralReportMeta, + ) { + this.users = users; + this.departments = departments; + this.total = total; + this.meta = meta; + } + + public static createEmptyRow(ownerId: number, useWon: boolean): CrmGeneralReportRow { + return CrmGeneralReportRow.empty(ownerId, useWon); + } + + public toDto(): CrmGeneralReportDto { + return new CrmGeneralReportDto( + Array.from(this.users.values()).map((u) => u.toDto()), + Array.from(this.departments.values()).map((u) => u.toDto()), + this.total?.toDto(), + this.meta.toDto(), + ); + } +} diff --git a/backend/src/CRM/reporting/general/types/general-report-row.ts b/backend/src/CRM/reporting/general/types/general-report-row.ts new file mode 100644 index 0000000..23015d4 --- /dev/null +++ b/backend/src/CRM/reporting/general/types/general-report-row.ts @@ -0,0 +1,83 @@ +import { CallReportBlock } from '@/modules/telephony/voximplant/voximplant-reporting/types/call-report-block'; + +import { GeneralReportRowDto } from '../dto'; +import { CrmGeneralReportEntity } from './crm-general-report-entity'; +import { CrmGeneralReportField } from './crm-general-report-field'; +import { CrmGeneralReportTask } from './crm-general-report-task'; + +export class GeneralReportRow { + ownerId: number; + entity: CrmGeneralReportEntity | null | undefined; + task: CrmGeneralReportTask | null | undefined; + activity: CrmGeneralReportTask | null | undefined; + field: Map | null | undefined; + call: CallReportBlock | null | undefined; + + constructor( + ownerId: number, + entity: CrmGeneralReportEntity | null | undefined, + task: CrmGeneralReportTask | null | undefined, + activity: CrmGeneralReportTask | null | undefined, + field: Map | null | undefined, + call: CallReportBlock | null | undefined, + ) { + this.ownerId = ownerId; + this.entity = entity; + this.task = task; + this.activity = activity; + this.field = field; + this.call = call; + } + + public toDto(): GeneralReportRowDto { + return { + ownerId: this.ownerId, + entity: this.entity?.toDto(), + task: this.task?.toDto(), + activity: this.activity?.toDto(), + fields: this.field ? Array.from(this.field.values()).map((v) => v.toDto()) : undefined, + call: this.call?.toDto(), + }; + } + + public add(row: GeneralReportRow): GeneralReportRow { + if (this.entity && row.entity) { + this.entity.add(row.entity); + } else if (row.entity) { + this.entity = row.entity; + } + + if (this.task && row.task) { + this.task.add(row.task); + } else if (row.task) { + this.task = row.task; + } + + if (this.activity && row.activity) { + this.activity.add(row.activity); + } else if (row.activity) { + this.activity = row.activity; + } + + if (this.field && row.field) { + for (const [rowFieldId, rowField] of row.field) { + let field = this.field.get(rowFieldId); + if (!field) { + field = CrmGeneralReportField.empty(rowFieldId, rowField.fieldName); + this.field.set(rowFieldId, field); + } + field.add(rowField); + } + } else if (row.field) { + this.field = row.field; + } + + if (this.call && row.call) { + this.call.add(row.call); + } else if (row.call) { + this.call = row.call; + } + + return this; + } +} diff --git a/backend/src/CRM/reporting/general/types/general-report.ts b/backend/src/CRM/reporting/general/types/general-report.ts new file mode 100644 index 0000000..9f02a8d --- /dev/null +++ b/backend/src/CRM/reporting/general/types/general-report.ts @@ -0,0 +1,31 @@ +import { GeneralReportDto } from '../dto'; +import { CrmGeneralReportMeta } from './crm-general-report-meta'; +import { GeneralReportRow } from './general-report-row'; + +export class GeneralReport { + users: Map; + departments: Map; + total: GeneralReportRow; + meta: CrmGeneralReportMeta; + + constructor( + users: Map, + departments: Map, + total: GeneralReportRow, + meta: CrmGeneralReportMeta, + ) { + this.users = users; + this.departments = departments; + this.total = total; + this.meta = meta; + } + + public toDto(): GeneralReportDto { + return { + users: Array.from(this.users.values()).map((u) => u.toDto()), + departments: Array.from(this.departments.values()).map((u) => u.toDto()), + total: this.total.toDto(), + meta: this.meta.toDto(), + }; + } +} diff --git a/backend/src/CRM/reporting/general/types/index.ts b/backend/src/CRM/reporting/general/types/index.ts new file mode 100644 index 0000000..8303b6d --- /dev/null +++ b/backend/src/CRM/reporting/general/types/index.ts @@ -0,0 +1,11 @@ +export * from './crm-general-report-entity'; +export * from './crm-general-report-field-meta'; +export * from './crm-general-report-field-option-meta'; +export * from './crm-general-report-field-value'; +export * from './crm-general-report-field'; +export * from './crm-general-report-meta'; +export * from './crm-general-report-row'; +export * from './crm-general-report-task'; +export * from './crm-general-report'; +export * from './general-report-row'; +export * from './general-report'; diff --git a/backend/src/CRM/reporting/project/dto/index.ts b/backend/src/CRM/reporting/project/dto/index.ts new file mode 100644 index 0000000..a8fa737 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/index.ts @@ -0,0 +1,12 @@ +export * from './project-entities-report-filter.dto'; +export * from './project-entities-report-meta.dto'; +export * from './project-entities-report-row.dto'; +export * from './project-entities-report.dto'; +export * from './project-report-field-meta.dto'; +export * from './project-report-field.dto'; +export * from './project-report-item.dto'; +export * from './project-stage-item.dto'; +export * from './project-task-user-report-filter.dto'; +export * from './project-task-user-report-row.dto'; +export * from './project-task-user-report-total-row.dto'; +export * from './project-task-user-report.dto'; diff --git a/backend/src/CRM/reporting/project/dto/project-entities-report-filter.dto.ts b/backend/src/CRM/reporting/project/dto/project-entities-report-filter.dto.ts new file mode 100644 index 0000000..4bc50cf --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-entities-report-filter.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +export class ProjectEntitiesReportFilterDto { + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiProperty({ description: 'Board ID' }) + @IsNumber() + boardId: number; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Project user IDs' }) + @IsOptional() + @IsArray() + ownerIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Project stage IDs' }) + @IsOptional() + @IsArray() + taskBoardStageIds?: number[] | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/CRM/reporting/project/dto/project-entities-report-meta.dto.ts b/backend/src/CRM/reporting/project/dto/project-entities-report-meta.dto.ts new file mode 100644 index 0000000..e204432 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-entities-report-meta.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { ProjectReportFieldMetaDto } from './project-report-field-meta.dto'; + +export class ProjectEntitiesReportMetaDto { + @ApiProperty({ type: [ProjectReportFieldMetaDto], description: 'Fields meta' }) + fields: ProjectReportFieldMetaDto[]; +} diff --git a/backend/src/CRM/reporting/project/dto/project-entities-report-row.dto.ts b/backend/src/CRM/reporting/project/dto/project-entities-report-row.dto.ts new file mode 100644 index 0000000..e3296a3 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-entities-report-row.dto.ts @@ -0,0 +1,41 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ProjectReportItemDto } from './project-report-item.dto'; +import { ProjectStageItemDto } from './project-stage-item.dto'; +import { ProjectReportFieldDto } from './project-report-field.dto'; + +export class ProjectEntitiesReportRowDto { + @ApiProperty({ description: 'Entity ID' }) + @IsNumber() + entityId: number; + + @ApiProperty({ description: 'Entity name' }) + @IsString() + entityName: string; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'All tasks' }) + all: ProjectReportItemDto; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Done tasks' }) + done: ProjectReportItemDto; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Overdue tasks' }) + overdue: ProjectReportItemDto; + + @ApiProperty({ type: [ProjectStageItemDto], description: 'Tasks by project stages' }) + stages: ProjectStageItemDto[]; + + @ApiProperty({ nullable: true, description: 'Project stage ID' }) + @IsOptional() + @IsNumber() + projectStageId: number | null; + + @ApiProperty({ description: 'Completion percent' }) + @IsNumber() + completionPercent: number; + + @ApiPropertyOptional({ type: [ProjectReportFieldDto], nullable: true, description: 'Fields' }) + @IsOptional() + fields: ProjectReportFieldDto[] | null; +} diff --git a/backend/src/CRM/reporting/project/dto/project-entities-report.dto.ts b/backend/src/CRM/reporting/project/dto/project-entities-report.dto.ts new file mode 100644 index 0000000..f97ed44 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-entities-report.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { ProjectEntitiesReportRowDto } from './project-entities-report-row.dto'; +import { ProjectEntitiesReportMetaDto } from './project-entities-report-meta.dto'; + +export class ProjectEntitiesReportDto { + @ApiProperty({ type: [ProjectEntitiesReportRowDto], description: 'Rows' }) + rows: ProjectEntitiesReportRowDto[]; + + @ApiProperty({ type: ProjectEntitiesReportRowDto, description: 'Total' }) + total: ProjectEntitiesReportRowDto; + + @ApiProperty({ type: ProjectEntitiesReportMetaDto, description: 'Meta' }) + meta: ProjectEntitiesReportMetaDto; +} diff --git a/backend/src/CRM/reporting/project/dto/project-report-field-meta.dto.ts b/backend/src/CRM/reporting/project/dto/project-report-field-meta.dto.ts new file mode 100644 index 0000000..f782887 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-report-field-meta.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class ProjectReportFieldMetaDto { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + fieldId: number; + + @ApiProperty({ description: 'Field name' }) + @IsString() + fieldName: string; +} diff --git a/backend/src/CRM/reporting/project/dto/project-report-field.dto.ts b/backend/src/CRM/reporting/project/dto/project-report-field.dto.ts new file mode 100644 index 0000000..41003ec --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-report-field.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class ProjectReportFieldDto { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + fieldId: number; + + @ApiProperty({ description: 'Field name' }) + @IsString() + fieldName: string; + + @ApiProperty({ description: 'Field value' }) + @IsNumber() + value: number; +} diff --git a/backend/src/CRM/reporting/project/dto/project-report-item.dto.ts b/backend/src/CRM/reporting/project/dto/project-report-item.dto.ts new file mode 100644 index 0000000..3dbac85 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-report-item.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class ProjectReportItemDto { + @ApiProperty({ description: 'Task count' }) + @IsNumber() + taskCount: number; + + @ApiProperty({ nullable: true, description: 'Planned time' }) + @IsNumber() + plannedTime: number | null; +} diff --git a/backend/src/CRM/reporting/project/dto/project-stage-item.dto.ts b/backend/src/CRM/reporting/project/dto/project-stage-item.dto.ts new file mode 100644 index 0000000..09ca6e5 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-stage-item.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { ProjectReportItemDto } from './project-report-item.dto'; + +export class ProjectStageItemDto { + @ApiProperty({ description: 'Stage ID' }) + @IsNumber() + stageId: number; + + @ApiProperty({ type: ProjectReportItemDto, description: 'Project report item' }) + item: ProjectReportItemDto; +} diff --git a/backend/src/CRM/reporting/project/dto/project-task-user-report-filter.dto.ts b/backend/src/CRM/reporting/project/dto/project-task-user-report-filter.dto.ts new file mode 100644 index 0000000..9e80216 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-task-user-report-filter.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +export class ProjectTaskUserReportFilterDto { + @ApiProperty({ description: 'Board ID' }) + @IsNumber() + boardId: number; + + @ApiPropertyOptional({ description: 'Entity ID' }) + @IsNumber() + entityId: number; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Task user IDs' }) + @IsOptional() + @IsArray() + taskUserIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Task board stage IDs' }) + @IsOptional() + @IsArray() + taskBoardStageIds?: number[] | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/CRM/reporting/project/dto/project-task-user-report-row.dto.ts b/backend/src/CRM/reporting/project/dto/project-task-user-report-row.dto.ts new file mode 100644 index 0000000..3dc8799 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-task-user-report-row.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { ProjectReportItemDto } from './project-report-item.dto'; +import { ProjectStageItemDto } from './project-stage-item.dto'; + +export class ProjectTaskUserReportRowDto { + @ApiProperty({ description: 'User ID' }) + @IsNumber() + userId: number; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Opened tasks' }) + opened: ProjectReportItemDto; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Done tasks' }) + done: ProjectReportItemDto; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Overdue tasks' }) + overdue: ProjectReportItemDto; + + @ApiProperty({ type: [ProjectStageItemDto], description: 'Tasks by stages' }) + stages: ProjectStageItemDto[]; + + @ApiProperty({ description: 'Planned time' }) + @IsNumber() + planedTime: number; + + @ApiProperty({ description: 'Completion percent' }) + @IsNumber() + completionPercent: number; +} diff --git a/backend/src/CRM/reporting/project/dto/project-task-user-report-total-row.dto.ts b/backend/src/CRM/reporting/project/dto/project-task-user-report-total-row.dto.ts new file mode 100644 index 0000000..f4e0b47 --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-task-user-report-total-row.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { ProjectReportItemDto } from './project-report-item.dto'; +import { ProjectStageItemDto } from './project-stage-item.dto'; + +export class ProjectTaskUserReportTotalRowDto { + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Open tasks' }) + opened: ProjectReportItemDto; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Done tasks' }) + done: ProjectReportItemDto; + + @ApiProperty({ type: () => ProjectReportItemDto, description: 'Overdue tasks' }) + overdue: ProjectReportItemDto; + + @ApiProperty({ type: [ProjectStageItemDto], description: 'Tasks by project stages' }) + stages: ProjectStageItemDto[]; + + @ApiProperty({ description: 'Planned time' }) + @IsNumber() + planedTime: number; + + @ApiProperty({ description: 'Completion percent' }) + @IsNumber() + completionPercent: number; +} diff --git a/backend/src/CRM/reporting/project/dto/project-task-user-report.dto.ts b/backend/src/CRM/reporting/project/dto/project-task-user-report.dto.ts new file mode 100644 index 0000000..056df8e --- /dev/null +++ b/backend/src/CRM/reporting/project/dto/project-task-user-report.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { ProjectTaskUserReportRowDto } from './project-task-user-report-row.dto'; +import { ProjectTaskUserReportTotalRowDto } from './project-task-user-report-total-row.dto'; + +export class ProjectTaskUserReportDto { + @ApiProperty({ type: [ProjectTaskUserReportRowDto], description: 'Project task user report rows' }) + rows: ProjectTaskUserReportRowDto[]; + + @ApiProperty({ type: ProjectTaskUserReportTotalRowDto, description: 'Project task user report total row' }) + total: ProjectTaskUserReportTotalRowDto; +} diff --git a/backend/src/CRM/reporting/project/project-report.controller.ts b/backend/src/CRM/reporting/project/project-report.controller.ts new file mode 100644 index 0000000..2261af4 --- /dev/null +++ b/backend/src/CRM/reporting/project/project-report.controller.ts @@ -0,0 +1,46 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { + ProjectEntitiesReportDto, + ProjectEntitiesReportFilterDto, + ProjectTaskUserReportDto, + ProjectTaskUserReportFilterDto, +} from './dto'; +import { ProjectReportService } from './project-report.service'; + +@ApiTags('crm/reporting') +@Controller('crm/reporting/project') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ProjectReportController { + constructor(private readonly service: ProjectReportService) {} + + @ApiOperation({ summary: 'Get project entities report', description: 'Get project entities report' }) + @ApiBody({ type: ProjectEntitiesReportFilterDto, required: true, description: 'Project report filter' }) + @ApiOkResponse({ description: 'Get project entities report', type: ProjectEntitiesReportDto }) + @Post('entities') + public async getProjectEntitiesReport( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: ProjectEntitiesReportFilterDto, + ) { + return this.service.getEntitiesReport({ accountId, user, filter }); + } + + @ApiOperation({ + summary: 'Get project report by task responsible users', + description: 'Get project report by task responsible users', + }) + @ApiBody({ type: ProjectTaskUserReportFilterDto, required: true, description: 'Project report filter' }) + @ApiOkResponse({ description: 'Project report by task responsible users', type: ProjectTaskUserReportDto }) + @Post('users') + public async getProjectTaskUserReport( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: ProjectTaskUserReportFilterDto, + ) { + return this.service.getTaskUserReport({ accountId, user, filter }); + } +} diff --git a/backend/src/CRM/reporting/project/project-report.service.ts b/backend/src/CRM/reporting/project/project-report.service.ts new file mode 100644 index 0000000..29b1e19 --- /dev/null +++ b/backend/src/CRM/reporting/project/project-report.service.ts @@ -0,0 +1,350 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, SelectQueryBuilder } from 'typeorm'; + +import { DatePeriod, ForbiddenError, intersection, NumberUtil } from '@/common'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; + +import { BoardService } from '../../board/board.service'; +import { BoardStageService } from '../../board-stage/board-stage.service'; +import { EntityType } from '../../entity-type/entities'; +import { Entity } from '../../Model/Entity/Entity'; +import { EntityService } from '../../Service/Entity/EntityService'; +import { Task } from '../../task/entities'; + +import { ProjectEntitiesReportFilterDto, ProjectTaskUserReportFilterDto } from './dto'; +import { + ProjectEntitiesReport, + ProjectEntitiesReportMeta, + ProjectEntitiesReportRow, + ProjectReportField, + ProjectReportFieldMeta, + ProjectReportItem, + ProjectStageItem, + ProjectTaskUserReport, + ProjectTaskUserReportRow, + ProjectTaskUserReportTotalRow, +} from './types'; + +interface Filter { + userIds?: number[]; + projectStageIds?: number[]; + taskUserIds?: number[]; + taskBoardStageIds?: number[]; + period?: DatePeriod; +} + +@Injectable() +export class ProjectReportService { + constructor( + @InjectRepository(Task) + private readonly taskRepository: Repository, + @InjectRepository(Entity) + private readonly entityRepository: Repository, + private readonly authService: AuthorizationService, + private readonly boardService: BoardService, + private readonly stageService: BoardStageService, + private readonly entityService: EntityService, + private readonly fieldService: FieldService, + ) {} + + public async getEntitiesReport({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: ProjectEntitiesReportFilterDto; + }): Promise { + const { entityTypeId, boardId, taskBoardStageIds } = filter; + const { allow, userIds: allowedUserIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: EntityType.getAuthorizable(entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + const userIds = filter.ownerIds?.length ? intersection(filter.ownerIds, allowedUserIds) : allowedUserIds; + + const period = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const fieldsMeta = await this.getFieldsMeta({ accountId, entityTypeId: entityTypeId }); + const projectStageIds = await this.stageService.findManyIds({ accountId, boardId }); + const stageIds = await this.getTaskBoardStageIds({ accountId, boardId, taskBoardStageIds }); + const groupFilter: Filter = { userIds, projectStageIds, taskBoardStageIds: stageIds, period }; + + const rows = await this.getEntitiesGroupBy({ accountId, isTotal: false, filter: groupFilter, fieldsMeta }); + const [total] = await this.getEntitiesGroupBy({ accountId, isTotal: true, filter: groupFilter, fieldsMeta }); + + return new ProjectEntitiesReport(rows, total, new ProjectEntitiesReportMeta({ fields: fieldsMeta })); + } + + public async getTaskUserReport({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: ProjectTaskUserReportFilterDto; + }): Promise { + const { entityId, boardId, taskBoardStageIds } = filter; + const entity = await this.entityService.findOne(accountId, { entityId }); + const { allow, userIds: allowedUserIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: entity, + }); + if (!allow) { + throw new ForbiddenError(); + } + const userIds = filter.taskUserIds?.length ? intersection(filter.taskUserIds, allowedUserIds) : allowedUserIds; + + const period = filter.period ? DatePeriod.fromFilter(filter.period) : undefined; + const stageIds = await this.getTaskBoardStageIds({ accountId, boardId, taskBoardStageIds }); + + const taskFilter: Filter = { taskUserIds: userIds, taskBoardStageIds: stageIds, period }; + + const taskUserQb = this.getTaskUserReportQb(accountId, entityId, false, taskFilter); + const taskUserRawData = await taskUserQb.getRawMany(); + let taskUserReportRows: ProjectTaskUserReportRow[] = []; + if (taskUserRawData.length > 0) { + taskUserReportRows = taskUserRawData.map( + (d) => + new ProjectTaskUserReportRow( + d?.user_id, + new ProjectReportItem(d?.opened?.task_count, d?.opened?.planned_time), + new ProjectReportItem(d?.done?.task_count, d?.done?.planned_time), + new ProjectReportItem(d?.overdue?.task_count, d?.overdue?.planned_time), + stageIds.map( + (id) => new ProjectStageItem(id, new ProjectReportItem(d[id]['task_count'], d[id]['planned_time'])), + ), + d?.total_planned_time, + +d?.completion_percent, + ), + ); + } + + const totalTaskUserQb = this.getTaskUserReportQb(accountId, entityId, true, taskFilter); + const totalTaskUserRawData = await totalTaskUserQb.getRawOne(); + let taskUserReportTotalRow = ProjectTaskUserReportTotalRow.empty(); + if (totalTaskUserRawData !== undefined) { + taskUserReportTotalRow = new ProjectTaskUserReportTotalRow( + new ProjectReportItem(totalTaskUserRawData?.opened?.task_count, totalTaskUserRawData?.opened?.planned_time), + new ProjectReportItem(totalTaskUserRawData?.done?.task_count, totalTaskUserRawData?.done?.planned_time), + new ProjectReportItem(totalTaskUserRawData?.overdue?.task_count, totalTaskUserRawData?.overdue?.planned_time), + taskBoardStageIds.map( + (id) => + new ProjectStageItem( + id, + new ProjectReportItem(totalTaskUserRawData[id]['task_count'], totalTaskUserRawData[id]['planned_time']), + ), + ), + totalTaskUserRawData?.total_planned_time, + +totalTaskUserRawData?.completion_percent, + ); + } + return new ProjectTaskUserReport(taskUserReportRows, taskUserReportTotalRow); + } + + private async getEntitiesGroupBy({ + accountId, + isTotal, + filter, + fieldsMeta, + }: { + accountId: number; + isTotal: boolean; + filter: Filter; + fieldsMeta: ProjectReportFieldMeta[]; + }): Promise { + const rowArray: ProjectEntitiesReportRow[] = []; + const fieldRows = await this.getFieldsReportQb(accountId, isTotal, filter, fieldsMeta).getRawMany(); + const rows = await this.getEntitiesReportQb(accountId, isTotal, filter).getRawMany(); + for (const row of rows) { + const ownerId = row.entity_id || 0; + const fieldRow = fieldRows.find((r) => r.entity_id === ownerId); + const fields: ProjectReportField[] = []; + if (fieldRow) { + for (const fieldMeta of fieldsMeta) { + const value = NumberUtil.toNumber(fieldRow[`fv_${fieldMeta.fieldId}_amount`]); + if (value) { + fields.push(new ProjectReportField(fieldMeta.fieldId, fieldMeta.fieldName, value)); + } + } + } + rowArray.push( + new ProjectEntitiesReportRow( + ownerId, + row.entity_name || '', + new ProjectReportItem(row.all?.task_count, row.all?.planned_time), + new ProjectReportItem(row.done?.task_count, row.done?.planned_time), + new ProjectReportItem(row.overdue?.task_count, row.overdue?.planned_time), + filter.taskBoardStageIds.map( + (id) => new ProjectStageItem(id, new ProjectReportItem(row[id]['task_count'], row[id]['planned_time'])), + ), + row.project_stage_id || null, + row.completion_percent, + fields, + ), + ); + } + + return rowArray; + } + + private async getFieldsMeta({ + accountId, + entityTypeId, + }: { + accountId: number; + entityTypeId: number; + }): Promise { + const fieldsMeta: ProjectReportFieldMeta[] = []; + + const formulaFields = await this.fieldService.findMany({ accountId, entityTypeId, type: FieldType.Formula }); + for (const field of formulaFields) { + fieldsMeta.push(new ProjectReportFieldMeta(field.id, field.name)); + } + + return fieldsMeta; + } + + private async getTaskBoardStageIds({ + accountId, + boardId, + taskBoardStageIds, + }: { + accountId: number; + boardId: number; + taskBoardStageIds?: number[]; + }) { + const board = await this.boardService.findOne({ filter: { accountId, boardId } }); + const stageIds = board.taskBoardId + ? await this.stageService.findManyIds({ accountId, boardId: board.taskBoardId }) + : []; + + return taskBoardStageIds?.length ? stageIds.filter((s) => taskBoardStageIds.includes(s)) : stageIds; + } + + private getEntitiesReportQb(accountId: number, isTotal: boolean, filter: Filter) { + const qb = this.entityRepository + .createQueryBuilder('entity') + .where('entity.account_id = :accountId', { accountId }) + .andWhere('entity.stage_id in (:...stageIds)', { stageIds: filter?.projectStageIds }); + + if (filter.userIds?.length) { + qb.andWhere(`entity.responsible_user_id in (:...ownerIds)`, { ownerIds: filter.userIds }); + } + + if (isTotal) { + qb.select('0::integer', 'entity_id'); + } else { + qb.select('entity.id', 'entity_id') + .addSelect('entity.stage_id', 'project_stage_id') + .addSelect('entity.name', 'entity_name') + .groupBy('entity.id') + .addGroupBy('entity.name'); + } + qb.leftJoin(Task, 'task', 'task.entity_id = entity.id').addSelect( + "json_build_object('task_count', count(task.id), 'planned_time', coalesce(sum(task.planned_time), 0))", + 'all', + ); + this.specifyBaseFieldsQb(qb, filter); + + return qb; + } + + private getFieldsReportQb(accountId: number, isTotal: boolean, filter: Filter, fieldsMeta: ProjectReportFieldMeta[]) { + const qb = this.entityRepository + .createQueryBuilder('entity') + .where('entity.account_id = :accountId', { accountId }) + .andWhere('entity.stage_id in (:...stageIds)', { stageIds: filter?.projectStageIds }); + + if (filter.userIds?.length) { + qb.andWhere(`entity.responsible_user_id in (:...ownerIds)`, { ownerIds: filter.userIds }); + } + if (isTotal) { + qb.select('0::integer', 'entity_id'); + } else { + qb.select('entity.id', 'entity_id').groupBy('entity.id'); + } + + for (const fieldMeta of fieldsMeta) { + const fieldKey = `fv_${fieldMeta.fieldId}`; + qb.leftJoin( + // eslint-disable-next-line max-len + `(select entity_id, sum(cast(payload::json->>'value' as decimal)) as amount from field_value where field_id = ${fieldMeta.fieldId} group by entity_id)`, + fieldKey, + `${fieldKey}.entity_id = entity.id`, + ).addSelect(`sum(${fieldKey}.amount)`, `${fieldKey}_amount`); + } + + return qb; + } + + private getTaskUserReportQb(accountId: number, entityId: number, isTotal: boolean, filter: Filter) { + const qb = this.taskRepository.createQueryBuilder('task'); + if (isTotal) { + qb.select('task.entity_id', 'entity_id').groupBy('task.entity_id'); + } else { + qb.select('task.responsible_user_id', 'user_id').groupBy('task.responsible_user_id'); + } + this.specifyBaseFieldsQb(qb, filter); + qb.addSelect('coalesce(sum(task.planned_time), 0)::integer', 'total_planned_time') + .addSelect( + 'json_build_object(' + + "'task_count', count(task.id) filter (where task.is_resolved=false), " + + "'planned_time', coalesce(sum(task.planned_time) filter (where task.is_resolved=false), 0))", + 'opened', + ) + .andWhere('task.account_id = :accountId', { accountId }) + .andWhere('task.entity_id = :entityId', { entityId: entityId }); + + if (filter.taskUserIds?.length) { + qb.andWhere('task.responsible_user_id in (:...userIds)', { userIds: filter.taskUserIds }); + } + return qb; + } + + private specifyBaseFieldsQb(qb: SelectQueryBuilder, filter: Filter) { + qb.addSelect( + 'json_build_object(' + + "'task_count', count(task.id) filter (where task.is_resolved=true), " + + "'planned_time', coalesce(sum(task.planned_time) filter (where task.is_resolved=true), 0))", + 'done', + ) + .addSelect( + 'json_build_object(' + + "'task_count', count(task.id) filter (where task.end_date < now() and task.is_resolved=false), " + + // eslint-disable-next-line max-len + "'planned_time', coalesce(sum(task.planned_time) filter (where task.end_date < now() and task.is_resolved=false), 0))", + 'overdue', + ) + .addSelect( + 'coalesce(count(task.id) filter (where task.is_resolved = true)::float / nullif(count(task.id), 0), 0)::float', + 'completion_percent', + ); + + if (filter.taskBoardStageIds?.length) { + for (const stageId of filter.taskBoardStageIds) { + qb.addSelect( + `json_build_object('task_count', count(task.id) filter (where task.stage_id = ${stageId}), ` + + `'planned_time', coalesce(sum(task.planned_time) filter (where task.stage_id = ${stageId}), 0))`, + `${stageId}`, + ); + } + } + + if (filter.period?.from) { + qb.andWhere('task.created_at >= :from', { from: filter.period.from }); + } + if (filter.period?.to) { + qb.andWhere('task.created_at <= :to', { to: filter.period.to }); + } + } +} diff --git a/backend/src/CRM/reporting/project/types/index.ts b/backend/src/CRM/reporting/project/types/index.ts new file mode 100644 index 0000000..00fcb2a --- /dev/null +++ b/backend/src/CRM/reporting/project/types/index.ts @@ -0,0 +1,10 @@ +export * from './project-entities-report-meta'; +export * from './project-entities-report-row'; +export * from './project-entities-report'; +export * from './project-report-field-meta'; +export * from './project-report-field'; +export * from './project-report-item'; +export * from './project-stage-item'; +export * from './project-task-user-report-row'; +export * from './project-task-user-report-total-row'; +export * from './project-task-user-report'; diff --git a/backend/src/CRM/reporting/project/types/project-entities-report-meta.ts b/backend/src/CRM/reporting/project/types/project-entities-report-meta.ts new file mode 100644 index 0000000..c07f7e6 --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-entities-report-meta.ts @@ -0,0 +1,14 @@ +import { ProjectEntitiesReportMetaDto } from '../dto'; +import { type ProjectReportFieldMeta } from './project-report-field-meta'; + +export class ProjectEntitiesReportMeta { + fields: ProjectReportFieldMeta[]; + + constructor({ fields }: { fields: ProjectReportFieldMeta[] }) { + this.fields = fields; + } + + public toDto(): ProjectEntitiesReportMetaDto { + return { fields: this.fields?.map((f) => f.toDto()) }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-entities-report-row.ts b/backend/src/CRM/reporting/project/types/project-entities-report-row.ts new file mode 100644 index 0000000..276653a --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-entities-report-row.ts @@ -0,0 +1,52 @@ +import { ProjectEntitiesReportRowDto } from '../dto'; +import { type ProjectReportField } from './project-report-field'; +import { type ProjectReportItem } from './project-report-item'; +import { type ProjectStageItem } from './project-stage-item'; + +export class ProjectEntitiesReportRow { + entityId: number; + entityName: string; + all: ProjectReportItem; + done: ProjectReportItem; + overdue: ProjectReportItem; + stages: ProjectStageItem[]; + projectStageId: number | null; + completionPercent: number; + fields: ProjectReportField[]; + + constructor( + entityId: number, + entityName: string, + all: ProjectReportItem, + done: ProjectReportItem, + overdue: ProjectReportItem, + stages: ProjectStageItem[], + projectStageId: number | null, + completionPercent: number, + fields: ProjectReportField[], + ) { + this.entityId = entityId; + this.entityName = entityName; + this.all = all; + this.done = done; + this.overdue = overdue; + this.stages = stages; + this.projectStageId = projectStageId; + this.completionPercent = completionPercent; + this.fields = fields; + } + + public toDto(): ProjectEntitiesReportRowDto { + return { + entityId: this.entityId, + entityName: this.entityName, + all: this.all.toDto(), + done: this.done.toDto(), + overdue: this.overdue.toDto(), + stages: this.stages.map((stage) => stage.toDto()), + projectStageId: this.projectStageId, + completionPercent: this.completionPercent, + fields: this.fields?.map((field) => field.toDto()), + }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-entities-report.ts b/backend/src/CRM/reporting/project/types/project-entities-report.ts new file mode 100644 index 0000000..760e534 --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-entities-report.ts @@ -0,0 +1,19 @@ +import { ProjectEntitiesReportDto } from '../dto'; +import { type ProjectEntitiesReportMeta } from './project-entities-report-meta'; +import { type ProjectEntitiesReportRow } from './project-entities-report-row'; + +export class ProjectEntitiesReport { + rows: ProjectEntitiesReportRow[]; + total: ProjectEntitiesReportRow; + meta: ProjectEntitiesReportMeta; + + constructor(rows: ProjectEntitiesReportRow[], total: ProjectEntitiesReportRow, meta: ProjectEntitiesReportMeta) { + this.rows = rows; + this.total = total; + this.meta = meta; + } + + public toDto(): ProjectEntitiesReportDto { + return { rows: this.rows.map((d) => d.toDto()), total: this.total.toDto(), meta: this.meta.toDto() }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-report-field-meta.ts b/backend/src/CRM/reporting/project/types/project-report-field-meta.ts new file mode 100644 index 0000000..a5ba8e6 --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-report-field-meta.ts @@ -0,0 +1,15 @@ +import { ProjectReportFieldMetaDto } from '../dto'; + +export class ProjectReportFieldMeta { + fieldId: number; + fieldName: string; + + constructor(fieldId: number, fieldName: string) { + this.fieldId = fieldId; + this.fieldName = fieldName; + } + + public toDto(): ProjectReportFieldMetaDto { + return { fieldId: this.fieldId, fieldName: this.fieldName }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-report-field.ts b/backend/src/CRM/reporting/project/types/project-report-field.ts new file mode 100644 index 0000000..9252af1 --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-report-field.ts @@ -0,0 +1,17 @@ +import { ProjectReportFieldDto } from '../dto'; + +export class ProjectReportField { + fieldId: number; + fieldName: string; + value: number; + + constructor(fieldId: number, fieldName: string, value: number) { + this.fieldId = fieldId; + this.fieldName = fieldName; + this.value = value; + } + + public toDto(): ProjectReportFieldDto { + return { fieldId: this.fieldId, fieldName: this.fieldName, value: this.value }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-report-item.ts b/backend/src/CRM/reporting/project/types/project-report-item.ts new file mode 100644 index 0000000..ef572eb --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-report-item.ts @@ -0,0 +1,19 @@ +import { ProjectReportItemDto } from '../dto'; + +export class ProjectReportItem { + taskCount: number; + plannedTime: number | null; + + constructor(taskCount: number, plannedTime: number | null) { + this.taskCount = taskCount; + this.plannedTime = plannedTime; + } + + public static empty(): ProjectReportItem { + return new ProjectReportItem(0, 0); + } + + public toDto(): ProjectReportItemDto { + return { taskCount: this.taskCount, plannedTime: this.plannedTime }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-stage-item.ts b/backend/src/CRM/reporting/project/types/project-stage-item.ts new file mode 100644 index 0000000..5c6abc1 --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-stage-item.ts @@ -0,0 +1,16 @@ +import { ProjectStageItemDto } from '../dto'; +import { type ProjectReportItem } from './project-report-item'; + +export class ProjectStageItem { + stageId: number; + item: ProjectReportItem; + + constructor(stageId: number, item: ProjectReportItem) { + this.stageId = stageId; + this.item = item; + } + + public toDto(): ProjectStageItemDto { + return { stageId: this.stageId, item: this.item.toDto() }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-task-user-report-row.ts b/backend/src/CRM/reporting/project/types/project-task-user-report-row.ts new file mode 100644 index 0000000..cda19ec --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-task-user-report-row.ts @@ -0,0 +1,43 @@ +import { ProjectTaskUserReportRowDto } from '../dto'; +import { type ProjectReportItem } from './project-report-item'; +import { type ProjectStageItem } from './project-stage-item'; + +export class ProjectTaskUserReportRow { + userId: number; + opened: ProjectReportItem; + done: ProjectReportItem; + overdue: ProjectReportItem; + stages: ProjectStageItem[]; + plannedTime: number; + completionPercent: number; + + constructor( + userId: number, + opened: ProjectReportItem, + done: ProjectReportItem, + overdue: ProjectReportItem, + stages: ProjectStageItem[], + plannedTime: number, + completionPercent: number, + ) { + this.userId = userId; + this.opened = opened; + this.done = done; + this.overdue = overdue; + this.stages = stages; + this.plannedTime = plannedTime; + this.completionPercent = completionPercent; + } + + public toDto(): ProjectTaskUserReportRowDto { + return { + userId: this.userId, + opened: this.opened.toDto(), + done: this.done.toDto(), + overdue: this.overdue.toDto(), + stages: this.stages.map((s) => s.toDto()), + planedTime: this.plannedTime, + completionPercent: this.completionPercent, + }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-task-user-report-total-row.ts b/backend/src/CRM/reporting/project/types/project-task-user-report-total-row.ts new file mode 100644 index 0000000..4365289 --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-task-user-report-total-row.ts @@ -0,0 +1,50 @@ +import { ProjectTaskUserReportTotalRowDto } from '../dto'; +import { ProjectReportItem } from './project-report-item'; +import { type ProjectStageItem } from './project-stage-item'; + +export class ProjectTaskUserReportTotalRow { + opened: ProjectReportItem; + done: ProjectReportItem; + overdue: ProjectReportItem; + stages: ProjectStageItem[]; + plannedTime: number; + completionPercent: number; + + constructor( + opened: ProjectReportItem, + done: ProjectReportItem, + overdue: ProjectReportItem, + stages: ProjectStageItem[], + plannedTime: number, + completionPercent: number, + ) { + this.opened = opened; + this.done = done; + this.overdue = overdue; + this.stages = stages; + this.plannedTime = plannedTime; + this.completionPercent = completionPercent; + } + + public static empty(): ProjectTaskUserReportTotalRow { + return new ProjectTaskUserReportTotalRow( + ProjectReportItem.empty(), + ProjectReportItem.empty(), + ProjectReportItem.empty(), + [], + 0, + 0, + ); + } + + public toDto(): ProjectTaskUserReportTotalRowDto { + return { + opened: this.opened.toDto(), + done: this.done.toDto(), + overdue: this.overdue.toDto(), + stages: this.stages.map((s) => s.toDto()), + planedTime: this.plannedTime, + completionPercent: this.completionPercent, + }; + } +} diff --git a/backend/src/CRM/reporting/project/types/project-task-user-report.ts b/backend/src/CRM/reporting/project/types/project-task-user-report.ts new file mode 100644 index 0000000..47e634a --- /dev/null +++ b/backend/src/CRM/reporting/project/types/project-task-user-report.ts @@ -0,0 +1,17 @@ +import { ProjectTaskUserReportDto } from '../dto'; +import { type ProjectTaskUserReportRow } from './project-task-user-report-row'; +import { type ProjectTaskUserReportTotalRow } from './project-task-user-report-total-row'; + +export class ProjectTaskUserReport { + rows: ProjectTaskUserReportRow[]; + total: ProjectTaskUserReportTotalRow; + + constructor(rows: ProjectTaskUserReportRow[], total: ProjectTaskUserReportTotalRow) { + this.rows = rows; + this.total = total; + } + + public toDto(): ProjectTaskUserReportDto { + return { rows: this.rows.map((r) => r.toDto()), total: this.total.toDto() }; + } +} diff --git a/backend/src/CRM/sales-plan/dto/index.ts b/backend/src/CRM/sales-plan/dto/index.ts new file mode 100644 index 0000000..3818c78 --- /dev/null +++ b/backend/src/CRM/sales-plan/dto/index.ts @@ -0,0 +1,2 @@ +export * from './sales-plan-progress.dto'; +export * from './sales-plan.dto'; diff --git a/backend/src/CRM/sales-plan/dto/sales-plan-progress.dto.ts b/backend/src/CRM/sales-plan/dto/sales-plan-progress.dto.ts new file mode 100644 index 0000000..3970701 --- /dev/null +++ b/backend/src/CRM/sales-plan/dto/sales-plan-progress.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class SalesPlanProgressDto { + @ApiProperty() + @IsNumber() + userId: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + currentQuantity: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + currentAmount: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + plannedQuantity: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + plannedAmount: number | null; + + constructor( + userId: number, + currentQuantity: number | null, + currentAmount: number | null, + plannedQuantity: number | null, + plannedAmount: number | null, + ) { + this.userId = userId; + this.currentQuantity = currentQuantity; + this.currentAmount = currentAmount; + this.plannedQuantity = plannedQuantity; + this.plannedAmount = plannedAmount; + } +} diff --git a/backend/src/CRM/sales-plan/dto/sales-plan.dto.ts b/backend/src/CRM/sales-plan/dto/sales-plan.dto.ts new file mode 100644 index 0000000..4a4bc36 --- /dev/null +++ b/backend/src/CRM/sales-plan/dto/sales-plan.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsObject, IsOptional } from 'class-validator'; + +import { DatePeriodDto } from '@/common'; + +export class SalesPlanDto { + @ApiProperty() + @IsNumber() + userId: number; + + @ApiProperty({ type: DatePeriodDto }) + @IsObject() + period: DatePeriodDto; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + quantity: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + amount: number | null; + + constructor(userId: number, period: DatePeriodDto, quantity: number | null, amount: number | null) { + this.userId = userId; + this.period = period; + this.quantity = quantity; + this.amount = amount; + } +} diff --git a/backend/src/CRM/sales-plan/entities/index.ts b/backend/src/CRM/sales-plan/entities/index.ts new file mode 100644 index 0000000..0c4ac40 --- /dev/null +++ b/backend/src/CRM/sales-plan/entities/index.ts @@ -0,0 +1 @@ +export * from './sales-plan.entity'; diff --git a/backend/src/CRM/sales-plan/entities/sales-plan.entity.ts b/backend/src/CRM/sales-plan/entities/sales-plan.entity.ts new file mode 100644 index 0000000..07d3d91 --- /dev/null +++ b/backend/src/CRM/sales-plan/entities/sales-plan.entity.ts @@ -0,0 +1,76 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DatePeriod, DateUtil } from '@/common'; + +import { SalesPlanDto } from '../dto/sales-plan.dto'; + +@Entity() +export class SalesPlan { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + entityTypeId: number; + + @Column() + userId: number; + + @Column() + startDate: Date; + + @Column() + endDate: Date; + + @Column({ nullable: true }) + quantity: number | null; + + @Column({ nullable: true }) + amount: number | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + entityTypeId: number, + userId: number, + startDate: Date, + endDate: Date, + quantity: number | null, + amount: number | null, + accountId: number, + createdAt?: Date, + ) { + this.entityTypeId = entityTypeId; + this.userId = userId; + this.startDate = startDate; + this.endDate = endDate; + this.quantity = quantity; + this.amount = amount; + this.accountId = accountId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public static fromDto(accountId: number, entityTypeId: number, dto: SalesPlanDto): SalesPlan { + const period = DatePeriod.fromDto(dto.period); + return new SalesPlan(entityTypeId, dto.userId, period.from, period.to, dto.quantity, dto.amount, accountId); + } + + public update(dto: SalesPlanDto): SalesPlan { + this.amount = dto.amount; + this.quantity = dto.quantity; + + return this; + } + + public toDto(): SalesPlanDto { + return new SalesPlanDto( + this.userId, + new DatePeriod(this.startDate, this.endDate).toDto(), + this.quantity, + this.amount, + ); + } +} diff --git a/backend/src/CRM/sales-plan/sales-plan.controller.ts b/backend/src/CRM/sales-plan/sales-plan.controller.ts new file mode 100644 index 0000000..7837d41 --- /dev/null +++ b/backend/src/CRM/sales-plan/sales-plan.controller.ts @@ -0,0 +1,80 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { DatePeriodDto, TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { SalesPlanProgressDto } from './dto/sales-plan-progress.dto'; +import { SalesPlanDto } from './dto/sales-plan.dto'; +import { SalesPlanService } from './sales-plan.service'; + +@ApiTags('crm/sales-plans') +@Controller('/crm/entity-types/:entityTypeId/sales-plans') +@JwtAuthorized() +@TransformToDto() +export class SalesPlanController { + constructor(private readonly service: SalesPlanService) {} + + @ApiCreatedResponse({ type: [SalesPlanDto] }) + @Post('settings') + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() dtos: SalesPlanDto[], + ) { + return await this.service.upsertMany(accountId, entityTypeId, dtos); + } + + @ApiCreatedResponse({ type: [SalesPlanDto] }) + @Get('settings') + public async getMany( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Query() period: DatePeriodDto, + ) { + return await this.service.getMany(accountId, entityTypeId, period); + } + + @ApiCreatedResponse({ type: [SalesPlanProgressDto] }) + @Get() + public async getProgress( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Query() period: DatePeriodDto, + ) { + return await this.service.getProgress(accountId, entityTypeId, period); + } + + @ApiCreatedResponse({ type: [SalesPlanDto] }) + @Put('settings') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() dtos: SalesPlanDto[], + ) { + return await this.service.upsertMany(accountId, entityTypeId, dtos); + } + + @ApiCreatedResponse({ type: [SalesPlanDto] }) + @Delete('settings') + public async deleteAll( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Query() period: DatePeriodDto, + ) { + return await this.service.deleteAll(accountId, entityTypeId, period); + } + + @ApiCreatedResponse({ type: [SalesPlanDto] }) + @Delete('settings/users/:userId') + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Param('userId', ParseIntPipe) userId: number, + @Query() period: DatePeriodDto, + ) { + return await this.service.delete(accountId, entityTypeId, userId, period); + } +} diff --git a/backend/src/CRM/sales-plan/sales-plan.module.ts b/backend/src/CRM/sales-plan/sales-plan.module.ts new file mode 100644 index 0000000..926d7a6 --- /dev/null +++ b/backend/src/CRM/sales-plan/sales-plan.module.ts @@ -0,0 +1,23 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { CrmModule } from '../crm.module'; + +import { CrmReportingModule } from '../reporting/crm-reporting.module'; +import { SalesPlan } from './entities/sales-plan.entity'; +import { SalesPlanService } from './sales-plan.service'; +import { SalesPlanController } from './sales-plan.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([SalesPlan]), + IAMModule, + forwardRef(() => CrmModule), + forwardRef(() => CrmReportingModule), + ], + controllers: [SalesPlanController], + providers: [SalesPlanService], + exports: [SalesPlanService], +}) +export class SalesPlanModule {} diff --git a/backend/src/CRM/sales-plan/sales-plan.service.ts b/backend/src/CRM/sales-plan/sales-plan.service.ts new file mode 100644 index 0000000..4973d17 --- /dev/null +++ b/backend/src/CRM/sales-plan/sales-plan.service.ts @@ -0,0 +1,177 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { DatePeriod, DatePeriodDto, DatePeriodFilter, DatePeriodFilterType, DateUtil } from '@/common'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; + +import { BoardStageService } from '../board-stage'; +import { OwnerDateValue } from '../reporting/common'; +import { CrmReportingService } from '../reporting/crm-reporting.service'; + +import { SalesPlanDto, SalesPlanProgressDto } from './dto'; +import { SalesPlan } from './entities'; + +@Injectable() +export class SalesPlanService { + constructor( + @InjectRepository(SalesPlan) + private readonly repository: Repository, + private readonly stageService: BoardStageService, + @Inject(forwardRef(() => CrmReportingService)) + private readonly reportingService: CrmReportingService, + ) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + await this.deleteForUser(event.accountId, event.userId); + } + + public async upsertMany(accountId: number, entityTypeId: number, dtos: SalesPlanDto[]): Promise { + return await Promise.all(dtos.map((dto) => this.upsertOne(accountId, entityTypeId, dto))); + } + + public async upsertOne(accountId: number, entityTypeId: number, dto: SalesPlanDto): Promise { + const period = DatePeriod.fromDto(dto.period); + const sp = await this.createQb(accountId, { entityTypeId, period, userId: dto.userId }).getOne(); + + if (sp) { + return await this.repository.save(sp.update(dto)); + } else { + return await this.repository.save(SalesPlan.fromDto(accountId, entityTypeId, dto)); + } + } + + public async getMany(accountId: number, entityTypeId: number, periodDto: DatePeriodDto): Promise { + const period = DatePeriod.fromDto(periodDto); + + return await this.createQb(accountId, { entityTypeId, period }).getMany(); + } + + public async getProgress( + accountId: number, + entityTypeId: number, + periodDto: DatePeriodDto, + ): Promise { + const period = DatePeriod.fromDto(periodDto); + const salesPlans = await this.createQb(accountId, { entityTypeId, period }).getMany(); + + if (salesPlans.length === 0) { + return []; + } + + const { quantity, amount } = await this.getSalesPlanProgress( + accountId, + entityTypeId, + salesPlans[0].startDate, + salesPlans[0].endDate, + salesPlans.map((sp) => sp.userId), + ); + + const result: SalesPlanProgressDto[] = []; + for (const salePlan of salesPlans) { + const currentCount = quantity.find((q) => q.ownerId === salePlan.userId)?.value ?? 0; + const currentAmount = amount.find((a) => a.ownerId === salePlan.userId)?.value ?? 0; + result.push( + new SalesPlanProgressDto(salePlan.userId, currentCount, currentAmount, salePlan.quantity, salePlan.amount), + ); + } + + return result; + } + + public async getSalesPlanProgress( + accountId: number, + entityTypeId: number, + startDate: Date, + endDate: Date, + userIds: number[], + boardIds?: number[], + ): Promise<{ quantity: OwnerDateValue[]; amount: OwnerDateValue[] }> { + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId, + boardId: boardIds?.length ? boardIds : undefined, + }); + + return this.reportingService.getEntityGroupBy( + accountId, + stages.won, + { owner: 'user' }, + { amount: true, quantity: true }, + { closedAt: new DatePeriod(startDate, endDate), userIds }, + ); + } + + public async getActualPlans( + accountId: number, + entityTypeId: number, + userIds: number[] | null | undefined, + period: DatePeriodFilter | null | undefined, + ): Promise { + const dates = period + ? DatePeriod.fromFilter(period) + : DatePeriod.fromFilter({ type: DatePeriodFilterType.CurrentMonth }); + + const qb = this.repository + .createQueryBuilder() + .where('account_id = :accountId', { accountId }) + .andWhere('entity_type_id = :entityTypeId', { entityTypeId }); + if (userIds?.length > 0) { + qb.andWhere('user_id IN (:...userIds)', { userIds }); + } + if (dates.from && dates.to) { + dates.to.setMilliseconds(0); + // eslint-disable-next-line prettier/prettier + qb.andWhere('start_date <= :startDate', { startDate: dates.from }) + .andWhere('end_date >= :endDate', { endDate: dates.to }); + } else { + const now = DateUtil.now(); + // eslint-disable-next-line prettier/prettier + qb.andWhere('start_date < :startDate', { startDate: now }) + .andWhere('end_date > :endDate', { endDate: now }); + } + + return await qb.getMany(); + } + + public async deleteAll(accountId: number, entityTypeId: number, periodDto: DatePeriodDto): Promise { + const period = DatePeriod.fromDto(periodDto); + await this.createQb(accountId, { entityTypeId, period }).delete().execute(); + } + + public async delete( + accountId: number, + entityTypeId: number, + userId: number, + periodDto: DatePeriodDto, + ): Promise { + const period = DatePeriod.fromDto(periodDto); + await this.createQb(accountId, { entityTypeId, period, userId }).delete().execute(); + } + + private async deleteForUser(accountId: number, userId: number) { + await this.repository.delete({ accountId, userId }); + } + + private createQb(accountId: number, filter: { entityTypeId: number; period?: DatePeriod; userId?: number }) { + const qb = this.repository + .createQueryBuilder() + .where('account_id = :accountId', { accountId }) + .andWhere('entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + if (filter.period?.from) { + qb.andWhere('start_date >= :startDate', { startDate: filter.period.from }); + } + if (filter.period?.to) { + qb.andWhere('end_date <= :endDate', { endDate: filter.period.to }); + } + + if (filter.userId) { + qb.andWhere('user_id = :userId', { userId: filter.userId }); + } + + return qb; + } +} diff --git a/backend/src/CRM/task-board/dto/index.ts b/backend/src/CRM/task-board/dto/index.ts new file mode 100644 index 0000000..cce2ded --- /dev/null +++ b/backend/src/CRM/task-board/dto/index.ts @@ -0,0 +1,6 @@ +export * from './task-board-card.dto'; +export * from './task-board-filter.dto'; +export * from './task-board-meta.dto'; +export * from './task-board-stage-meta.dto'; +export * from './task-calendar-meta.dto'; +export * from './task-list-meta.dto'; diff --git a/backend/src/CRM/task-board/dto/task-board-card.dto.ts b/backend/src/CRM/task-board/dto/task-board-card.dto.ts new file mode 100644 index 0000000..e7b7e37 --- /dev/null +++ b/backend/src/CRM/task-board/dto/task-board-card.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { BaseTaskDto } from '../../base-task'; +import { Task } from '../../task'; +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; + +export class TaskBoardCardDto extends BaseTaskDto { + @ApiProperty({ description: 'Title' }) + @IsString() + title: string; + + @ApiProperty({ description: 'Planned time', nullable: true }) + @IsOptional() + @IsNumber() + plannedTime: number | null; + + @ApiProperty({ description: 'Stage ID', nullable: true }) + @IsOptional() + @IsNumber() + stageId: number | null; + + @ApiProperty({ description: 'Settings ID', nullable: true }) + @IsOptional() + @IsNumber() + settingsId: number | null; + + @ApiProperty({ description: 'Subtask count', nullable: true }) + @IsOptional() + @IsNumber() + subtaskCount: number | null; + + constructor( + task: Task, + entityInfo: EntityInfoDto | null, + fileLinks: FileLinkDto[], + userRights: UserRights, + subtaskCount: number | null, + ) { + super(task, entityInfo, fileLinks, userRights); + + this.title = task.title; + this.plannedTime = task.plannedTime; + this.stageId = task.stageId; + this.settingsId = task.settingsId; + this.subtaskCount = subtaskCount; + } +} diff --git a/backend/src/CRM/task-board/dto/task-board-filter.dto.ts b/backend/src/CRM/task-board/dto/task-board-filter.dto.ts new file mode 100644 index 0000000..5cbb270 --- /dev/null +++ b/backend/src/CRM/task-board/dto/task-board-filter.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { BaseTaskBoardFilter } from '../../Service/BaseTaskBoard/BaseTaskBoardFilter'; + +export class TaskBoardFilterDto extends BaseTaskBoardFilter { + @ApiPropertyOptional({ description: 'Stage ids', type: [Number] }) + @IsOptional() + @IsArray() + stageIds?: number[] | null; +} diff --git a/backend/src/CRM/task-board/dto/task-board-meta.dto.ts b/backend/src/CRM/task-board/dto/task-board-meta.dto.ts new file mode 100644 index 0000000..2cd0763 --- /dev/null +++ b/backend/src/CRM/task-board/dto/task-board-meta.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { UserTimeAllocation } from '../../Service/BaseTaskBoard/UserTimeAllocation'; +import { TaskBoardStageMeta } from './task-board-stage-meta.dto'; + +export class TaskBoardMeta { + @ApiProperty({ description: 'Total tasks count' }) + @IsNumber() + total: number; + + @ApiProperty({ description: 'Task board stages meta', type: [TaskBoardStageMeta] }) + stages: TaskBoardStageMeta[]; + + @ApiProperty({ description: 'User time allocation', type: [UserTimeAllocation] }) + timeAllocation: UserTimeAllocation[]; +} diff --git a/backend/src/CRM/task-board/dto/task-board-stage-meta.dto.ts b/backend/src/CRM/task-board/dto/task-board-stage-meta.dto.ts new file mode 100644 index 0000000..97d49ef --- /dev/null +++ b/backend/src/CRM/task-board/dto/task-board-stage-meta.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { UserTimeAllocation } from '../../Service/BaseTaskBoard/UserTimeAllocation'; + +export class TaskBoardStageMeta { + @ApiProperty({ description: 'Stage ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Total tasks count' }) + @IsNumber() + total: number; + + @ApiProperty({ description: 'User time allocation', type: [UserTimeAllocation] }) + timeAllocation: UserTimeAllocation[]; +} diff --git a/backend/src/CRM/task-board/dto/task-calendar-meta.dto.ts b/backend/src/CRM/task-board/dto/task-calendar-meta.dto.ts new file mode 100644 index 0000000..f62f771 --- /dev/null +++ b/backend/src/CRM/task-board/dto/task-calendar-meta.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class TaskCalendarMeta { + @ApiProperty({ description: 'Total tasks count' }) + @IsNumber() + total: number; +} diff --git a/backend/src/CRM/task-board/dto/task-list-meta.dto.ts b/backend/src/CRM/task-board/dto/task-list-meta.dto.ts new file mode 100644 index 0000000..7ee1d27 --- /dev/null +++ b/backend/src/CRM/task-board/dto/task-list-meta.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { UserTimeAllocation } from '../../Service/BaseTaskBoard/UserTimeAllocation'; + +export class TaskListMeta { + @ApiProperty({ description: 'Total tasks count' }) + @IsNumber() + total: number; + + @ApiProperty({ description: 'User time allocation', type: [UserTimeAllocation] }) + timeAllocation: UserTimeAllocation[]; +} diff --git a/backend/src/CRM/task-board/index.ts b/backend/src/CRM/task-board/index.ts new file mode 100644 index 0000000..447b999 --- /dev/null +++ b/backend/src/CRM/task-board/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './task-board.controller'; +export * from './task-board.service'; diff --git a/backend/src/CRM/task-board/task-board.controller.ts b/backend/src/CRM/task-board/task-board.controller.ts new file mode 100644 index 0000000..7dc6843 --- /dev/null +++ b/backend/src/CRM/task-board/task-board.controller.ts @@ -0,0 +1,113 @@ +import { Body, Controller, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { DatePeriodDto, PagingQuery } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TaskBoardCardDto, TaskBoardFilterDto, TaskBoardMeta, TaskCalendarMeta, TaskListMeta } from './dto'; +import { TaskBoardService } from './task-board.service'; + +@ApiTags('crm/tasks/board') +@Controller('/crm/tasks') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class TaskBoardController { + constructor(private readonly service: TaskBoardService) {} + + @ApiOperation({ summary: 'Get list of tasks', description: 'Get list of tasks' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ description: 'Filter data for list of tasks', type: TaskBoardFilterDto }) + @ApiOkResponse({ description: 'Task cards', type: [TaskBoardCardDto] }) + @Post('list/:boardId') + public async getTaskList( + @CurrentAuth() { account, user }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() filter: TaskBoardFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.getTaskList(account, user, boardId, filter, paging); + } + + @ApiOperation({ summary: 'Get meta for list of tasks', description: 'Get meta for list of tasks' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ description: 'Filter data for list of tasks', type: TaskBoardFilterDto }) + @ApiOkResponse({ description: 'Meta for list of tasks', type: TaskListMeta }) + @Post('list/:boardId/meta') + public async getTaskListMeta( + @CurrentAuth() { accountId, user }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() filter: TaskBoardFilterDto, + ) { + return this.service.getTaskListMeta(accountId, user, boardId, filter); + } + + @ApiOperation({ summary: 'Get tasks for board', description: 'Get tasks for board' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ description: 'Filter data for board', type: TaskBoardFilterDto }) + @ApiOkResponse({ description: 'Task cards', type: [TaskBoardCardDto] }) + @Post('boards/:boardId') + public async getTaskBoardCards( + @CurrentAuth() { account, user }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() filter: TaskBoardFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.getTaskBoardCards(account, user, boardId, filter, paging); + } + + @ApiOperation({ summary: 'Get meta for board', description: 'Get meta for board' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ description: 'Filter data for board', type: TaskBoardFilterDto }) + @ApiOkResponse({ description: 'Meta for board', type: TaskBoardMeta }) + @Post('boards/:boardId/meta') + public async getTaskBoardMeta( + @CurrentAuth() { accountId, user }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Body() filter: TaskBoardFilterDto, + ) { + return this.service.getTaskBoardMeta(accountId, user, boardId, filter); + } + + @ApiOperation({ summary: 'Get calendar for tasks', description: 'Get calendar for tasks' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ description: 'Filter data for calendar', type: TaskBoardFilterDto }) + @ApiOkResponse({ description: 'Task cards', type: [TaskBoardCardDto] }) + @Post('calendar/:boardId') + public async getTaskCalendar( + @CurrentAuth() { account, user }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Query() period: DatePeriodDto, + @Body() filter: TaskBoardFilterDto, + ) { + return this.service.getTaskCalendar(account, user, boardId, period, filter); + } + + @ApiOperation({ summary: 'Get meta for calendar', description: 'Get meta for calendar' }) + @ApiParam({ name: 'boardId', description: 'Board ID', type: Number, required: true }) + @ApiBody({ description: 'Filter data for calendar', type: TaskBoardFilterDto }) + @ApiOkResponse({ description: 'Meta for calendar', type: TaskCalendarMeta }) + @Post('calendar/:boardId/meta') + public async getTaskCalendarMeta( + @CurrentAuth() { account, user }: AuthData, + @Param('boardId', ParseIntPipe) boardId: number, + @Query() period: DatePeriodDto, + @Body() filter: TaskBoardFilterDto, + ) { + return this.service.getTaskCalendarMeta(account, user, boardId, period, filter); + } + + @ApiOperation({ summary: 'Get task card', description: 'Get task card' }) + @ApiParam({ name: 'taskId', description: 'Task ID', type: Number, required: true }) + @ApiBody({ description: 'Filter data for task card', type: TaskBoardFilterDto }) + @ApiOkResponse({ description: 'Task card', type: TaskBoardCardDto }) + @Post('cards/:taskId') + public async getTaskBoardCard( + @CurrentAuth() { account, user }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + @Body() filter: TaskBoardFilterDto, + @Query('boardId') boardId?: number, + ) { + return this.service.getTaskBoardCard(account, user, taskId, filter, boardId); + } +} diff --git a/backend/src/CRM/task-board/task-board.service.ts b/backend/src/CRM/task-board/task-board.service.ts new file mode 100644 index 0000000..4d1278c --- /dev/null +++ b/backend/src/CRM/task-board/task-board.service.ts @@ -0,0 +1,384 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { DatePeriod, DatePeriodDto, FileLinkSource, isUnique, NumberUtil, PagingQuery } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoDto, EntityInfoService } from '@/modules/entity/entity-info'; + +import { BoardStageService } from '../board-stage'; +import { Task } from '../task'; +import { TaskSubtaskService } from '../task-subtask/task-subtask.service'; + +import { FileLinkService } from '../Service/FileLink/FileLinkService'; + +import { TaskBoardQueryHelper } from '../Service/BaseTaskBoard/TaskBoardQueryHelper'; +import { UserTimeAllocation } from '../Service/BaseTaskBoard/UserTimeAllocation'; +import { + TaskBoardCardDto, + TaskBoardFilterDto, + TaskBoardMeta, + TaskBoardStageMeta, + TaskCalendarMeta, + TaskListMeta, +} from './dto'; + +@Injectable() +export class TaskBoardService { + constructor( + @InjectRepository(Task) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + @Inject(forwardRef(() => BoardStageService)) + private readonly stageService: BoardStageService, + private readonly fileLinkService: FileLinkService, + private readonly subtaskService: TaskSubtaskService, + private readonly entityInfoService: EntityInfoService, + ) {} + + public async getTaskList( + account: Account, + user: User, + boardId: number, + filter: TaskBoardFilterDto, + paging: PagingQuery, + ): Promise { + const responsibles = await this.getResponsibles({ accountId: account.id, user, entityIds: filter?.entityIds }); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + account.id, + user.id, + this.repository, + filter, + responsibles, + true, + 'title', + ); + + const stageIds = filter.stageIds ?? (await this.stageService.findManyIds({ accountId: account.id, boardId })); + if (stageIds) { + qb.andWhere('stage_id IN (:...stageIds)', { stageIds }); + } + + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const tasks = await qb.offset(paging.skip).limit(paging.take).getMany(); + + return this.createTaskBoardCards({ account, user, tasks }); + } + + public async getTaskListMeta( + accountId: number, + user: User, + boardId: number, + filter: TaskBoardFilterDto, + ): Promise { + const responsibles = await this.getResponsibles({ accountId, user, entityIds: filter?.entityIds }); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + accountId, + user.id, + this.repository, + filter, + responsibles, + false, + 'title', + ); + + const stageIds = filter.stageIds ?? (await this.stageService.findManyIds({ accountId, boardId })); + if (stageIds) { + qb.andWhere('stage_id IN (:...stageIds)', { stageIds }); + } + + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const total = await qb.getCount(); + + const boardAllocations = await qb + .groupBy('responsible_user_id') + .select('responsible_user_id, SUM(planned_time) AS planned_time') + .getRawMany(); + const timeAllocation = boardAllocations.map( + (a) => new UserTimeAllocation(a.responsible_user_id, NumberUtil.toNumber(a.planned_time)), + ); + + return { total, timeAllocation }; + } + + public async getTaskBoardCards( + account: Account, + user: User, + boardId: number, + filter: TaskBoardFilterDto, + paging: PagingQuery, + ): Promise { + const responsibles = await this.getResponsibles({ accountId: account.id, user, entityIds: filter?.entityIds }); + + const stageIds = filter.stageIds ?? (await this.stageService.findManyIds({ accountId: account.id, boardId })); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + account.id, + user.id, + this.repository, + filter, + responsibles, + true, + 'title', + ); + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const tasks = ( + await Promise.all( + stageIds.map(async (stageId) => + qb.clone().andWhere('stage_id = :stageId', { stageId }).offset(paging.skip).limit(paging.take).getMany(), + ), + ) + ).flat(); + + return this.createTaskBoardCards({ account, user, tasks }); + } + + public async getTaskBoardCard( + account: Account, + user: User, + taskId: number, + filter: TaskBoardFilterDto, + boardId?: number, + ): Promise { + const responsibles = await this.getResponsibles({ accountId: account.id, user, entityIds: filter?.entityIds }); + + const stageIds = + filter.stageIds ?? (boardId ? await this.stageService.findManyIds({ accountId: account.id, boardId }) : null); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + account.id, + user.id, + this.repository, + filter, + responsibles, + false, + 'title', + ); + + if (stageIds) { + qb.andWhere('stage_id IN (:...stageIds)', { stageIds }); + } + + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const task = await qb.andWhere('id = :id', { id: taskId }).getOne(); + + return task ? this.createTaskBoardCard({ account, user, task }) : null; + } + + public async getTaskBoardMeta( + accountId: number, + user: User, + boardId: number, + filter: TaskBoardFilterDto, + ): Promise { + const responsibles = await this.getResponsibles({ accountId, user, entityIds: filter?.entityIds }); + + const stageIds = filter.stageIds ?? (await this.stageService.findManyIds({ accountId, boardId })); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + accountId, + user.id, + this.repository, + filter, + responsibles, + false, + 'title', + ); + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const stages: TaskBoardStageMeta[] = ( + await Promise.all( + stageIds.map(async (stageId) => { + const stagedQb = qb.clone().andWhere('stage_id = :stageId', { stageId }); + const total = await stagedQb.getCount(); + const allocations = await stagedQb + .groupBy('responsible_user_id') + .select('responsible_user_id', 'user_id') + .addSelect('COALESCE(SUM(planned_time), 0)::int', 'planned_time') + .getRawMany<{ user_id: number; planned_time: number }>(); + + return { + id: stageId, + total, + timeAllocation: allocations.map((a) => new UserTimeAllocation(a.user_id, a.planned_time)), + }; + }), + ) + ).flat(); + + const boardQb = qb.clone().andWhere('stage_id IN (:...stageIds)', { stageIds }); + if (filter.showResolved === false) { + boardQb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + const total = await boardQb.getCount(); + const boardAllocations = await boardQb + .groupBy('responsible_user_id') + .select('responsible_user_id', 'user_id') + .addSelect('COALESCE(SUM(planned_time), 0)::int', 'planned_time') + .getRawMany<{ user_id: number; planned_time: number }>(); + const timeAllocation = boardAllocations.map( + (a) => new UserTimeAllocation(a.user_id, NumberUtil.toNumber(a.planned_time)), + ); + + return { total, stages, timeAllocation }; + } + + public async getTaskCalendar( + account: Account, + user: User, + boardId: number, + periodDto: DatePeriodDto, + filter: TaskBoardFilterDto, + ): Promise { + const responsibles = await this.getResponsibles({ accountId: account.id, user, entityIds: filter?.entityIds }); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + account.id, + user.id, + this.repository, + filter, + responsibles, + true, + 'title', + ); + + const stageIds = filter.stageIds ?? (await this.stageService.findManyIds({ accountId: account.id, boardId })); + if (stageIds) { + qb.andWhere('stage_id IN (:...stageIds)', { stageIds }); + } + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const period = DatePeriod.fromDto(periodDto); + if (period.from && period.to) { + qb.andWhere('start_date < :to', { to: period.to }).andWhere('end_date > :from', { from: period.from }); + } + + const tasks = await qb.getMany(); + + return this.createTaskBoardCards({ account, user, tasks }); + } + + public async getTaskCalendarMeta( + account: Account, + user: User, + boardId: number, + periodDto: DatePeriodDto, + filter: TaskBoardFilterDto, + ): Promise { + const responsibles = await this.getResponsibles({ accountId: account.id, user, entityIds: filter?.entityIds }); + + const qb = TaskBoardQueryHelper.createBoardQueryBuilder( + account.id, + user.id, + this.repository, + filter, + responsibles, + true, + 'title', + ); + + const stageIds = filter.stageIds ?? (await this.stageService.findManyIds({ accountId: account.id, boardId })); + if (stageIds) { + qb.andWhere('stage_id IN (:...stageIds)', { stageIds }); + } + if (filter.showResolved === false) { + qb.andWhere('is_resolved = :isResolved', { isResolved: false }); + } + + const period = DatePeriod.fromDto(periodDto); + if (period.from && period.to) { + qb.andWhere('start_date < :to', { to: period.to }).andWhere('end_date > :from', { from: period.from }); + } + const total = await qb.getCount(); + + return { total }; + } + + public async createTaskBoardCards({ + account, + user, + tasks, + }: { + account: Account; + user: User; + tasks: Task[]; + }): Promise { + const entityIds = tasks + .map((task) => task.entityId) + .filter((id) => !!id) + .filter(isUnique); + const entityInfoCache = entityIds.length + ? await this.entityInfoService.findMany({ accountId: account.id, user, entityIds }) + : []; + + const cards = await Promise.all( + tasks.map(async (task, idx) => { + const entityInfo = task.entityId ? entityInfoCache.find((ei) => ei.id === task.entityId) : null; + const card = await this.createTaskBoardCard({ account, user, task, entityInfo }); + return { idx, card }; + }), + ); + return cards.sort((a, b) => a.idx - b.idx).map((c) => c.card); + } + + public async createTaskBoardCard({ + account, + user, + task, + entityInfo = null, + }: { + account: Account; + user: User; + task: Task; + entityInfo?: EntityInfoDto | null; + }): Promise { + const entityInfoCurrent = + !entityInfo && task.entityId + ? await this.entityInfoService.findOne({ accountId: account.id, user, entityId: task.entityId }) + : entityInfo; + const userRights = await this.authService.getUserRights({ user, authorizable: task }); + const fileLinks = await this.fileLinkService.getFileLinkDtos(account, FileLinkSource.TASK, task.id); + const subtaskCount = await this.subtaskService.getCount(account.id, { taskId: task.id }); + + return new TaskBoardCardDto(task, entityInfoCurrent, fileLinks, userRights, subtaskCount); + } + + private async getResponsibles({ + accountId, + user, + entityIds, + }: { + accountId: number; + user: User; + entityIds?: number[] | null; + }): Promise { + if (entityIds?.length === 1) { + const entityInfo = await this.entityInfoService.findOne({ accountId, entityId: entityIds[0] }); + if (entityInfo.participantIds?.length > 0) { + return entityInfo.participantIds.includes(user.id) ? null : [user.id]; + } + } + return await this.authService.whoCanView({ user, authorizable: Task.getAuthorizable() }); + } +} diff --git a/backend/src/CRM/task-comment-like/dto/index.ts b/backend/src/CRM/task-comment-like/dto/index.ts new file mode 100644 index 0000000..7358713 --- /dev/null +++ b/backend/src/CRM/task-comment-like/dto/index.ts @@ -0,0 +1 @@ +export * from './task-comment-like.dto'; diff --git a/backend/src/CRM/task-comment-like/dto/task-comment-like.dto.ts b/backend/src/CRM/task-comment-like/dto/task-comment-like.dto.ts new file mode 100644 index 0000000..c83fa09 --- /dev/null +++ b/backend/src/CRM/task-comment-like/dto/task-comment-like.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class TaskCommentLikeDto { + @ApiProperty({ description: 'Task comment ID' }) + @IsNumber() + commentId: number; + + @ApiProperty({ description: 'User ID who liked the comment' }) + @IsNumber() + userId: number; +} diff --git a/backend/src/CRM/task-comment-like/entities/index.ts b/backend/src/CRM/task-comment-like/entities/index.ts new file mode 100644 index 0000000..65399de --- /dev/null +++ b/backend/src/CRM/task-comment-like/entities/index.ts @@ -0,0 +1 @@ +export * from './task-comment-like.entity'; diff --git a/backend/src/CRM/task-comment-like/entities/task-comment-like.entity.ts b/backend/src/CRM/task-comment-like/entities/task-comment-like.entity.ts new file mode 100644 index 0000000..d66eb30 --- /dev/null +++ b/backend/src/CRM/task-comment-like/entities/task-comment-like.entity.ts @@ -0,0 +1,24 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { TaskCommentLikeDto } from '../dto'; + +@Entity() +export class TaskCommentLike { + @PrimaryColumn() + commentId: number; + + @PrimaryColumn() + userId: number; + + @Column() + accountId: number; + + constructor(commentId: number, userId: number, accountId: number) { + this.commentId = commentId; + this.userId = userId; + this.accountId = accountId; + } + + public toDto(): TaskCommentLikeDto { + return { commentId: this.commentId, userId: this.userId }; + } +} diff --git a/backend/src/CRM/task-comment-like/index.ts b/backend/src/CRM/task-comment-like/index.ts new file mode 100644 index 0000000..2d21bab --- /dev/null +++ b/backend/src/CRM/task-comment-like/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './task-comment-like.controller'; +export * from './task-comment-like.service'; diff --git a/backend/src/CRM/task-comment-like/task-comment-like.controller.ts b/backend/src/CRM/task-comment-like/task-comment-like.controller.ts new file mode 100644 index 0000000..7083831 --- /dev/null +++ b/backend/src/CRM/task-comment-like/task-comment-like.controller.ts @@ -0,0 +1,43 @@ +import { Controller, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TaskCommentLikeDto } from './dto'; +import { TaskCommentLikeService } from './task-comment-like.service'; + +@ApiTags('crm/task-comments') +@Controller('crm/tasks/:taskId/comments/:commentId') +@JwtAuthorized() +@TransformToDto() +export class TaskCommentLikeController { + constructor(private readonly service: TaskCommentLikeService) {} + + @ApiOperation({ summary: 'Like task comment', description: 'Like task comment for current user' }) + @ApiParam({ name: 'taskId', type: Number, required: true, description: 'Task ID' }) + @ApiParam({ name: 'commentId', type: Number, required: true, description: 'Comment ID' }) + @ApiCreatedResponse({ type: TaskCommentLikeDto, description: 'Task comment like' }) + @Post('like') + public async like( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('commentId', ParseIntPipe) commentId: number, + ) { + return await this.service.like({ accountId, userId, commentId }); + } + + @ApiOperation({ summary: 'Unlike task comment', description: 'Unlike task comment for current user' }) + @ApiParam({ name: 'taskId', type: Number, required: true, description: 'Task ID' }) + @ApiParam({ name: 'commentId', type: Number, required: true, description: 'Comment ID' }) + @ApiOkResponse() + @Post('unlike') + public async unlike( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('commentId', ParseIntPipe) commentId: number, + ) { + return await this.service.unlike({ accountId, userId, commentId }); + } +} diff --git a/backend/src/CRM/task-comment-like/task-comment-like.service.ts b/backend/src/CRM/task-comment-like/task-comment-like.service.ts new file mode 100644 index 0000000..8654b69 --- /dev/null +++ b/backend/src/CRM/task-comment-like/task-comment-like.service.ts @@ -0,0 +1,46 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { TaskCommentLike } from './entities'; + +interface FindFilter { + accountId: number; + commentId?: number; + userId?: number; +} + +export class TaskCommentLikeService { + constructor( + @InjectRepository(TaskCommentLike) + private readonly repository: Repository, + ) {} + + public async like({ + accountId, + commentId, + userId, + }: { + accountId: number; + commentId: number; + userId: number; + }): Promise { + return this.repository.save(new TaskCommentLike(commentId, userId, accountId)); + } + + public async unlike({ accountId, commentId, userId }: { accountId: number; commentId: number; userId: number }) { + await this.repository.delete({ accountId, commentId, userId }); + } + + public async findMany({ accountId, commentId, userId }: FindFilter): Promise { + const qb = this.repository.createQueryBuilder('tcl').where('tcl.account_id = :accountId', { accountId }); + + if (commentId) { + qb.andWhere('tcl.comment_id = :commentId', { commentId }); + } + if (userId) { + qb.andWhere('tcl.user_id = :userId', { userId }); + } + + return qb.getMany(); + } +} diff --git a/backend/src/CRM/task-comment/dto/create-task-comment.dto.ts b/backend/src/CRM/task-comment/dto/create-task-comment.dto.ts new file mode 100644 index 0000000..ee99e4b --- /dev/null +++ b/backend/src/CRM/task-comment/dto/create-task-comment.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional, IsString } from 'class-validator'; + +export class CreateTaskCommentDto { + @ApiProperty({ description: 'Text of comment' }) + @IsString() + text: string; + + @ApiPropertyOptional({ type: [String], nullable: true, description: 'Task comment attached file IDs' }) + @IsOptional() + @IsArray() + fileIds: string[] | null; +} diff --git a/backend/src/CRM/task-comment/dto/index.ts b/backend/src/CRM/task-comment/dto/index.ts new file mode 100644 index 0000000..3a3fb0e --- /dev/null +++ b/backend/src/CRM/task-comment/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-task-comment.dto'; +export * from './task-comment-result.dto'; +export * from './task-comment.dto'; +export * from './update-task-comment.dto'; diff --git a/backend/src/CRM/task-comment/dto/task-comment-result.dto.ts b/backend/src/CRM/task-comment/dto/task-comment-result.dto.ts new file mode 100644 index 0000000..019b28c --- /dev/null +++ b/backend/src/CRM/task-comment/dto/task-comment-result.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { TaskCommentDto } from './task-comment.dto'; + +export class TaskCommentResultDto { + @ApiProperty({ type: [TaskCommentDto], description: 'Task comment list' }) + result: TaskCommentDto[]; + + @ApiProperty({ type: PagingMeta, description: 'Paging meta' }) + meta: PagingMeta; +} diff --git a/backend/src/CRM/task-comment/dto/task-comment.dto.ts b/backend/src/CRM/task-comment/dto/task-comment.dto.ts new file mode 100644 index 0000000..d5c1ab7 --- /dev/null +++ b/backend/src/CRM/task-comment/dto/task-comment.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; + +export class TaskCommentDto { + @ApiProperty({ description: 'Task comment ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Task ID of comment' }) + @IsNumber() + taskId: number; + + @ApiProperty({ description: 'User ID of comment' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ description: 'Text of comment' }) + @IsString() + text: string; + + @ApiProperty({ type: [FileLinkDto], description: 'Task comment attached file IDs' }) + fileLinks: FileLinkDto[]; + + @ApiProperty({ type: [Number], description: 'User IDs who liked the comment' }) + likedUserIds: number[]; + + @ApiProperty({ description: 'Date and time when the comment was created in ISO format' }) + @IsString() + createdAt: string; +} diff --git a/backend/src/CRM/task-comment/dto/update-task-comment.dto.ts b/backend/src/CRM/task-comment/dto/update-task-comment.dto.ts new file mode 100644 index 0000000..5f6497f --- /dev/null +++ b/backend/src/CRM/task-comment/dto/update-task-comment.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class UpdateTaskCommentDto { + @ApiProperty({ description: 'Text of comment' }) + @IsString() + text: string; +} diff --git a/backend/src/CRM/task-comment/entities/index.ts b/backend/src/CRM/task-comment/entities/index.ts new file mode 100644 index 0000000..f4e9be4 --- /dev/null +++ b/backend/src/CRM/task-comment/entities/index.ts @@ -0,0 +1 @@ +export * from './task-comment.entity'; diff --git a/backend/src/CRM/task-comment/entities/task-comment.entity.ts b/backend/src/CRM/task-comment/entities/task-comment.entity.ts new file mode 100644 index 0000000..9bb45d2 --- /dev/null +++ b/backend/src/CRM/task-comment/entities/task-comment.entity.ts @@ -0,0 +1,63 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; +import { TaskCommentLike } from '../../task-comment-like'; +import { TaskCommentDto } from '../dto'; + +@Entity() +export class TaskComment { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + text: string; + + @Column() + taskId: number; + + @Column() + createdBy: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, createdBy: number, text: string, taskId: number, createdAt?: Date) { + this.accountId = accountId; + this.createdBy = createdBy; + this.text = text; + this.taskId = taskId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _fileLinks: FileLinkDto[] | null; + public get fileLinks(): FileLinkDto[] | null { + return this._fileLinks; + } + public set fileLinks(value: FileLinkDto[] | null) { + this._fileLinks = value; + } + private _likes: TaskCommentLike[] | null; + public get likes(): TaskCommentLike[] | null { + return this._likes; + } + public set likes(value: TaskCommentLike[] | null) { + this._likes = value; + } + + public toDto(): TaskCommentDto { + return { + id: this.id, + taskId: this.taskId, + createdBy: this.createdBy, + text: this.text, + createdAt: this.createdAt.toISOString(), + fileLinks: this.fileLinks ?? [], + likedUserIds: this.likes ? this.likes.map((l) => l.userId) : [], + }; + } +} diff --git a/backend/src/CRM/task-comment/index.ts b/backend/src/CRM/task-comment/index.ts new file mode 100644 index 0000000..b44b5e4 --- /dev/null +++ b/backend/src/CRM/task-comment/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './task-comment.controller'; +export * from './task-comment.service'; diff --git a/backend/src/CRM/task-comment/task-comment.controller.ts b/backend/src/CRM/task-comment/task-comment.controller.ts new file mode 100644 index 0000000..a6b48a9 --- /dev/null +++ b/backend/src/CRM/task-comment/task-comment.controller.ts @@ -0,0 +1,66 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { CreateTaskCommentDto, TaskCommentDto, TaskCommentResultDto, UpdateTaskCommentDto } from './dto'; +import { TaskCommentService } from './task-comment.service'; + +@ApiTags('crm/task-comments') +@Controller('crm/tasks/:taskId/comments') +@JwtAuthorized({ prefetch: { account: true } }) +@TransformToDto() +export class TaskCommentController { + constructor(private readonly service: TaskCommentService) {} + + @ApiOperation({ summary: 'Create task comment', description: 'Create task comment' }) + @ApiParam({ name: 'taskId', type: Number, required: true, description: 'Task ID' }) + @ApiBody({ type: CreateTaskCommentDto, required: true, description: 'Data for creating task comment' }) + @ApiCreatedResponse({ description: 'Task comment', type: TaskCommentDto }) + @Post() + public async create( + @CurrentAuth() { account, userId }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + @Body() dto: CreateTaskCommentDto, + ) { + return this.service.create({ account, taskId, userId, dto }); + } + + @ApiOperation({ summary: 'Get task comments', description: 'Get task comments' }) + @ApiParam({ name: 'taskId', type: Number, required: true, description: 'Task ID' }) + @ApiOkResponse({ description: 'Task comments', type: TaskCommentResultDto }) + @Get() + public async getComments( + @CurrentAuth() { account }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + @Query() paging: PagingQuery, + ): Promise { + return this.service.getComments({ account, taskId, paging }); + } + + @ApiOperation({ summary: 'Update task comment', description: 'Update task comment' }) + @ApiParam({ name: 'taskId', type: Number, required: true, description: 'Task ID' }) + @ApiParam({ name: 'commentId', type: Number, required: true, description: 'Comment ID' }) + @ApiBody({ type: UpdateTaskCommentDto, required: true, description: 'Data for updating task comment' }) + @ApiOkResponse({ description: 'Task comment', type: TaskCommentDto }) + @Put(':commentId') + public async update( + @CurrentAuth() { account }: AuthData, + @Param('commentId', ParseIntPipe) commentId: number, + @Body() dto: UpdateTaskCommentDto, + ) { + return this.service.update({ account, commentId, dto }); + } + + @ApiOperation({ summary: 'Delete task comment', description: 'Delete task comment' }) + @ApiParam({ name: 'taskId', type: Number, required: true, description: 'Task ID' }) + @ApiParam({ name: 'commentId', type: Number, required: true, description: 'Comment ID' }) + @ApiOkResponse() + @Delete(':commentId') + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('commentId', ParseIntPipe) commentId: number) { + return this.service.delete({ accountId, commentId }); + } +} diff --git a/backend/src/CRM/task-comment/task-comment.service.ts b/backend/src/CRM/task-comment/task-comment.service.ts new file mode 100644 index 0000000..b0e9346 --- /dev/null +++ b/backend/src/CRM/task-comment/task-comment.service.ts @@ -0,0 +1,152 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Repository } from 'typeorm'; + +import { FileLinkSource, PagingQuery, PagingMeta } from '@/common'; +import { Account } from '@/modules/iam/account/entities/account.entity'; + +import { CrmEventType, TaskCommentCreatedEvent } from '../common'; +import { TaskService } from '../task'; +import { TaskCommentLikeService } from '../task-comment-like'; + +import { FileLinkService } from '../Service/FileLink/FileLinkService'; + +import { CreateTaskCommentDto, TaskCommentResultDto, UpdateTaskCommentDto } from './dto'; +import { TaskComment } from './entities'; + +interface FindFilter { + accountId: number; + commentId?: number; + taskId?: number; +} + +@Injectable() +export class TaskCommentService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(TaskComment) + private readonly repository: Repository, + private readonly fileLinkService: FileLinkService, + private readonly likeService: TaskCommentLikeService, + private readonly taskService: TaskService, + ) {} + + public async create({ + account, + userId, + taskId, + dto, + }: { + account: Account; + userId: number; + taskId: number; + dto: CreateTaskCommentDto; + }): Promise { + const comment = await this.repository.save(new TaskComment(account.id, userId, dto.text, taskId)); + + if (dto.fileIds) { + await this.fileLinkService.processFiles(account.id, FileLinkSource.TASK_COMMENT, comment.id, dto.fileIds); + } + + await this.expandOne({ account, comment }); + + const task = await this.taskService.findOne({ accountId: account.id, taskId }); + this.eventEmitter.emit( + CrmEventType.TaskCommentCreated, + new TaskCommentCreatedEvent({ + source: TaskCommentService.name, + accountId: account.id, + taskId: task.id, + boardId: task.boardId, + ownerId: task.responsibleUserId, + entityId: task.entityId ?? null, + createdBy: userId, + taskTitle: task.title, + taskText: task.text, + createdAt: comment.createdAt, + startDate: task.startDate, + endDate: task.endDate, + taskComment: comment.text, + }), + ); + + return comment; + } + + public async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + public async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).orderBy('tc.created_at', 'DESC').getMany(); + } + + public async getComments({ + account, + taskId, + paging, + }: { + account: Account; + taskId: number; + paging: PagingQuery; + }): Promise { + const qb = this.createFindQb({ accountId: account.id, taskId }); + const comments = await qb.orderBy('tc.created_at', 'DESC').offset(paging.skip).limit(paging.take).getMany(); + await this.expandMany({ account, comments }); + const total = await qb.getCount(); + + return { result: comments.map((r) => r.toDto()), meta: new PagingMeta(paging.offset + paging.limit, total) }; + } + + public async update({ + account, + commentId, + dto, + }: { + account: Account; + commentId: number; + dto: UpdateTaskCommentDto; + }): Promise { + await this.repository.update({ accountId: account.id, id: commentId }, { text: dto.text }); + + const comment = await this.findOne({ accountId: account.id, commentId }); + await this.expandOne({ account, comment }); + + return comment; + } + + public async delete({ accountId, commentId }: { accountId: number; commentId: number }): Promise { + await this.repository.delete({ accountId, id: commentId }); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('tc') + .where('tc.account_id = :accountId', { accountId: filter.accountId }); + + if (filter.commentId) { + qb.andWhere('tc.id = :commentId', { commentId: filter.commentId }); + } + if (filter.taskId) { + qb.andWhere('tc.task_id = :taskId', { taskId: filter.taskId }); + } + + return qb; + } + + private async expandOne({ account, comment }: { account: Account; comment: TaskComment }): Promise { + comment.fileLinks = await this.fileLinkService.getFileLinkDtos(account, FileLinkSource.TASK_COMMENT, comment.id); + comment.likes = await this.likeService.findMany({ accountId: account.id, commentId: comment.id }); + + return comment; + } + private async expandMany({ + account, + comments, + }: { + account: Account; + comments: TaskComment[]; + }): Promise { + return await Promise.all(comments.map((comment) => this.expandOne({ account, comment }))); + } +} diff --git a/backend/src/CRM/task-settings/dto/create-task-settings.dto.ts b/backend/src/CRM/task-settings/dto/create-task-settings.dto.ts new file mode 100644 index 0000000..d2a3979 --- /dev/null +++ b/backend/src/CRM/task-settings/dto/create-task-settings.dto.ts @@ -0,0 +1,5 @@ +import { PickType } from '@nestjs/swagger'; + +import { TaskSettingsDto } from './task-settings.dto'; + +export class CreateTaskSettingsDto extends PickType(TaskSettingsDto, ['type', 'recordId', 'activeFields'] as const) {} diff --git a/backend/src/CRM/task-settings/dto/index.ts b/backend/src/CRM/task-settings/dto/index.ts new file mode 100644 index 0000000..0d1c871 --- /dev/null +++ b/backend/src/CRM/task-settings/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-task-settings.dto'; +export * from './task-settings.dto'; +export * from './update-task-settings.dto'; diff --git a/backend/src/CRM/task-settings/dto/task-settings.dto.ts b/backend/src/CRM/task-settings/dto/task-settings.dto.ts new file mode 100644 index 0000000..18574d8 --- /dev/null +++ b/backend/src/CRM/task-settings/dto/task-settings.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { TaskFieldCode, TaskSettingsType } from '../enums'; + +export class TaskSettingsDto { + @ApiProperty({ description: 'Task settings id' }) + @IsNumber() + id: number; + + @ApiProperty({ enum: TaskSettingsType, description: 'Task settings connected object type' }) + @IsEnum(TaskSettingsType) + type: TaskSettingsType; + + @ApiProperty({ nullable: true, description: 'Connected object id' }) + @IsOptional() + @IsNumber() + recordId: number | null; + + @ApiProperty({ enum: TaskFieldCode, isArray: true, description: 'Task settings active fields' }) + @IsEnum(TaskFieldCode, { each: true }) + activeFields: TaskFieldCode[]; +} diff --git a/backend/src/CRM/task-settings/dto/update-task-settings.dto.ts b/backend/src/CRM/task-settings/dto/update-task-settings.dto.ts new file mode 100644 index 0000000..77ee9c5 --- /dev/null +++ b/backend/src/CRM/task-settings/dto/update-task-settings.dto.ts @@ -0,0 +1,5 @@ +import { PickType } from '@nestjs/swagger'; + +import { TaskSettingsDto } from './task-settings.dto'; + +export class UpdateTaskSettingsDto extends PickType(TaskSettingsDto, ['activeFields'] as const) {} diff --git a/backend/src/CRM/task-settings/entities/index.ts b/backend/src/CRM/task-settings/entities/index.ts new file mode 100644 index 0000000..a295a1f --- /dev/null +++ b/backend/src/CRM/task-settings/entities/index.ts @@ -0,0 +1 @@ +export * from './task-settings.entity'; diff --git a/backend/src/CRM/task-settings/entities/task-settings.entity.ts b/backend/src/CRM/task-settings/entities/task-settings.entity.ts new file mode 100644 index 0000000..eed8888 --- /dev/null +++ b/backend/src/CRM/task-settings/entities/task-settings.entity.ts @@ -0,0 +1,39 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { TaskFieldCode } from '../enums/task-field-code.enum'; +import { TaskSettingsType } from '../enums/task-settings-type.enum'; +import { CreateTaskSettingsDto } from '../dto/create-task-settings.dto'; +import { TaskSettingsDto } from '../dto/task-settings.dto'; + +@Entity() +export class TaskSettings { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column({ type: 'jsonb' }) + activeFields: TaskFieldCode[]; + + @Column() + type: TaskSettingsType; + + @Column({ nullable: true }) + recordId: number | null; + + constructor(accountId: number, type: TaskSettingsType, recordId: number | null, activeFields: TaskFieldCode[]) { + this.accountId = accountId; + this.type = type; + this.recordId = recordId; + this.activeFields = activeFields; + } + + public static fromDto(accountId: number, dto: CreateTaskSettingsDto) { + return new TaskSettings(accountId, dto.type, dto.recordId, dto.activeFields); + } + + public toDto(): TaskSettingsDto { + return { id: this.id, type: this.type, recordId: this.recordId, activeFields: this.activeFields }; + } +} diff --git a/backend/src/CRM/task-settings/enums/index.ts b/backend/src/CRM/task-settings/enums/index.ts new file mode 100644 index 0000000..996f7c9 --- /dev/null +++ b/backend/src/CRM/task-settings/enums/index.ts @@ -0,0 +1,2 @@ +export * from './task-field-code.enum'; +export * from './task-settings-type.enum'; diff --git a/backend/src/CRM/task-settings/enums/task-field-code.enum.ts b/backend/src/CRM/task-settings/enums/task-field-code.enum.ts new file mode 100644 index 0000000..69ab10b --- /dev/null +++ b/backend/src/CRM/task-settings/enums/task-field-code.enum.ts @@ -0,0 +1,8 @@ +export enum TaskFieldCode { + PlannedTime = 'planned_time', + BoardName = 'board_name', + StartDate = 'start_date', + EndDate = 'end_date', + Description = 'description', + Subtasks = 'subtasks', +} diff --git a/backend/src/CRM/task-settings/enums/task-settings-type.enum.ts b/backend/src/CRM/task-settings/enums/task-settings-type.enum.ts new file mode 100644 index 0000000..09a916c --- /dev/null +++ b/backend/src/CRM/task-settings/enums/task-settings-type.enum.ts @@ -0,0 +1,5 @@ +export enum TaskSettingsType { + EntityType = 'entity_type', + TaskBoard = 'task_board', + TimeBoard = 'time_board', +} diff --git a/backend/src/CRM/task-settings/task-settings.controller.ts b/backend/src/CRM/task-settings/task-settings.controller.ts new file mode 100644 index 0000000..d765ee4 --- /dev/null +++ b/backend/src/CRM/task-settings/task-settings.controller.ts @@ -0,0 +1,40 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TaskSettingsDto, CreateTaskSettingsDto, UpdateTaskSettingsDto } from './dto'; +import { TaskSettingsService } from './task-settings.service'; + +@ApiTags('crm/task-settings') +@Controller('/crm/task-settings') +@JwtAuthorized() +@TransformToDto() +export class TaskSettingsController { + constructor(private readonly service: TaskSettingsService) {} + + @ApiCreatedResponse({ description: 'Create task settings', type: TaskSettingsDto }) + @Post() + public async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateTaskSettingsDto) { + return await this.service.create(accountId, dto); + } + + @ApiCreatedResponse({ description: 'Task settings list', type: [TaskSettingsDto] }) + @Get() + public async findMany(@CurrentAuth() { accountId }: AuthData) { + return await this.service.findMany(accountId); + } + + @ApiCreatedResponse({ description: 'Update task settings', type: TaskSettingsDto }) + @Put(':id') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateTaskSettingsDto, + ) { + return await this.service.update(accountId, id, dto); + } +} diff --git a/backend/src/CRM/task-settings/task-settings.handler.ts b/backend/src/CRM/task-settings/task-settings.handler.ts new file mode 100644 index 0000000..ddaff77 --- /dev/null +++ b/backend/src/CRM/task-settings/task-settings.handler.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { BoardEvent, CrmEventType, EntityTypeEvent } from '@/CRM/common'; + +import { TaskSettingsType } from './enums/task-settings-type.enum'; +import { TaskSettingsService } from './task-settings.service'; + +@Injectable() +export class TaskSettingsHandler { + constructor(private readonly service: TaskSettingsService) {} + + @OnEvent(CrmEventType.EntityTypeDeleted, { async: true }) + public async onEntityTypeDeleted(event: EntityTypeEvent) { + await this.service.delete(event.accountId, event.entityTypeId, TaskSettingsType.EntityType); + } + + @OnEvent(CrmEventType.BoardDeleted, { async: true }) + public async onBoardDeleted(event: BoardEvent) { + await this.service.delete(event.accountId, event.boardId, TaskSettingsType.TaskBoard); + } +} diff --git a/backend/src/CRM/task-settings/task-settings.module.ts b/backend/src/CRM/task-settings/task-settings.module.ts new file mode 100644 index 0000000..317c687 --- /dev/null +++ b/backend/src/CRM/task-settings/task-settings.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { TaskSettings } from './entities'; +import { TaskSettingsService } from './task-settings.service'; +import { TaskSettingsHandler } from './task-settings.handler'; +import { TaskSettingsController } from './task-settings.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([TaskSettings]), IAMModule], + controllers: [TaskSettingsController], + providers: [TaskSettingsHandler, TaskSettingsService], + exports: [TaskSettingsService], +}) +export class TaskSettingsModule {} diff --git a/backend/src/CRM/task-settings/task-settings.service.ts b/backend/src/CRM/task-settings/task-settings.service.ts new file mode 100644 index 0000000..b9dd4ca --- /dev/null +++ b/backend/src/CRM/task-settings/task-settings.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { CreateTaskSettingsDto, UpdateTaskSettingsDto } from './dto'; +import { TaskSettings } from './entities'; +import { TaskFieldCode, TaskSettingsType } from './enums'; + +@Injectable() +export class TaskSettingsService { + constructor( + @InjectRepository(TaskSettings) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, dto: CreateTaskSettingsDto): Promise { + return this.repository.save(TaskSettings.fromDto(accountId, dto)); + } + + public async findOne(accountId: number, settingsId: number): Promise { + return await this.repository.findOne({ where: { accountId, id: settingsId } }); + } + + public async findMany(accountId: number): Promise { + return await this.repository.find({ where: { accountId } }); + } + + public async setTaskSettingsForEntityType( + accountId: number, + entityTypeId: number, + activeFields: TaskFieldCode[] | null, + ) { + if (activeFields) { + await this.repository.delete({ accountId, type: TaskSettingsType.EntityType, recordId: entityTypeId }); + await this.create(accountId, { type: TaskSettingsType.EntityType, recordId: entityTypeId, activeFields }); + } + } + + public async update(accountId: number, settingsId: number, dto: UpdateTaskSettingsDto): Promise { + await this.repository.update({ accountId, id: settingsId }, { activeFields: dto.activeFields }); + + return this.findOne(accountId, settingsId); + } + + public async delete(accountId: number, recordId: number, type: TaskSettingsType): Promise { + await this.repository.delete({ accountId, recordId, type }); + } +} diff --git a/backend/src/CRM/task-subtask/dto/create-task-subtask.dto.ts b/backend/src/CRM/task-subtask/dto/create-task-subtask.dto.ts new file mode 100644 index 0000000..7c98814 --- /dev/null +++ b/backend/src/CRM/task-subtask/dto/create-task-subtask.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { TaskSubtaskDto } from './task-subtask.dto'; + +export class CreateTaskSubtaskDto extends PickType(TaskSubtaskDto, ['text', 'resolved', 'sortOrder'] as const) {} diff --git a/backend/src/CRM/task-subtask/dto/index.ts b/backend/src/CRM/task-subtask/dto/index.ts new file mode 100644 index 0000000..7f49914 --- /dev/null +++ b/backend/src/CRM/task-subtask/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-task-subtask.dto'; +export * from './task-subtask.dto'; +export * from './update-task-subtask.dto'; diff --git a/backend/src/CRM/task-subtask/dto/task-subtask.dto.ts b/backend/src/CRM/task-subtask/dto/task-subtask.dto.ts new file mode 100644 index 0000000..58d7519 --- /dev/null +++ b/backend/src/CRM/task-subtask/dto/task-subtask.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class TaskSubtaskDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + text: string; + + @ApiProperty() + @IsBoolean() + resolved: boolean; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + sortOrder?: number; + + constructor({ id, text, resolved, sortOrder }: TaskSubtaskDto) { + this.id = id; + this.text = text; + this.resolved = resolved; + this.sortOrder = sortOrder; + } +} diff --git a/backend/src/CRM/task-subtask/dto/update-task-subtask.dto.ts b/backend/src/CRM/task-subtask/dto/update-task-subtask.dto.ts new file mode 100644 index 0000000..5815960 --- /dev/null +++ b/backend/src/CRM/task-subtask/dto/update-task-subtask.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty, PartialType, PickType } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { TaskSubtaskDto } from './task-subtask.dto'; + +export class UpdateTaskSubtaskDto extends PartialType( + PickType(TaskSubtaskDto, ['text', 'resolved', 'sortOrder'] as const), +) { + @ApiProperty() + @IsNumber() + id: number; +} diff --git a/backend/src/CRM/task-subtask/entities/index.ts b/backend/src/CRM/task-subtask/entities/index.ts new file mode 100644 index 0000000..10da639 --- /dev/null +++ b/backend/src/CRM/task-subtask/entities/index.ts @@ -0,0 +1 @@ +export * from './task-subtask.entity'; diff --git a/backend/src/CRM/task-subtask/entities/task-subtask.entity.ts b/backend/src/CRM/task-subtask/entities/task-subtask.entity.ts new file mode 100644 index 0000000..b2f04b7 --- /dev/null +++ b/backend/src/CRM/task-subtask/entities/task-subtask.entity.ts @@ -0,0 +1,48 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { CreateTaskSubtaskDto, TaskSubtaskDto, UpdateTaskSubtaskDto } from '../dto'; + +@Entity() +export class TaskSubtask { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + text: string; + + @Column() + resolved: boolean; + + @Column() + taskId: number; + + @Column() + sortOrder: number; + + @Column() + accountId: number; + + constructor(accountId: number, taskId: number, text: string, resolved: boolean, sortOrder: number) { + this.accountId = accountId; + this.text = text; + this.resolved = resolved; + this.taskId = taskId; + this.sortOrder = sortOrder; + } + + public static fromDto(accountId: number, taskId: number, dto: CreateTaskSubtaskDto): TaskSubtask { + return new TaskSubtask(accountId, taskId, dto.text, dto.resolved, dto.sortOrder || 0); + } + + public update(dto: UpdateTaskSubtaskDto): TaskSubtask { + this.text = dto.text !== undefined ? dto.text : this.text; + this.resolved = dto.resolved !== undefined ? dto.resolved : this.resolved; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + + return this; + } + + public toDto(): TaskSubtaskDto { + return new TaskSubtaskDto(this); + } +} diff --git a/backend/src/CRM/task-subtask/index.ts b/backend/src/CRM/task-subtask/index.ts new file mode 100644 index 0000000..bb1b49c --- /dev/null +++ b/backend/src/CRM/task-subtask/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './task-subtask.controller'; +export * from './task-subtask.service'; diff --git a/backend/src/CRM/task-subtask/task-subtask.controller.ts b/backend/src/CRM/task-subtask/task-subtask.controller.ts new file mode 100644 index 0000000..434532f --- /dev/null +++ b/backend/src/CRM/task-subtask/task-subtask.controller.ts @@ -0,0 +1,49 @@ +import { Body, Controller, Delete, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { CreateTaskSubtaskDto, TaskSubtaskDto, UpdateTaskSubtaskDto } from './dto'; +import { TaskSubtaskService } from './task-subtask.service'; + +@ApiTags('crm/tasks/subtasks') +@Controller('/crm/tasks/:taskId/subtasks') +@JwtAuthorized() +@TransformToDto() +export class TaskSubtaskController { + constructor(private readonly service: TaskSubtaskService) {} + + @ApiCreatedResponse({ description: 'Create subtask', type: TaskSubtaskDto }) + @Post() + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + @Body() dto: CreateTaskSubtaskDto, + ) { + return this.service.create(accountId, taskId, dto); + } + + @ApiOkResponse({ description: 'Update subtask', type: TaskSubtaskDto }) + @Put(':subtaskId') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + @Param('subtaskId', ParseIntPipe) subtaskId: number, + @Body() dto: UpdateTaskSubtaskDto, + ) { + return this.service.update(accountId, taskId, subtaskId, dto); + } + + @ApiOkResponse({ description: 'Delete subtask' }) + @Delete(':subtaskId') + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + @Param('subtaskId', ParseIntPipe) subtaskId: number, + ) { + await this.service.delete(accountId, taskId, subtaskId); + } +} diff --git a/backend/src/CRM/task-subtask/task-subtask.service.ts b/backend/src/CRM/task-subtask/task-subtask.service.ts new file mode 100644 index 0000000..507c80f --- /dev/null +++ b/backend/src/CRM/task-subtask/task-subtask.service.ts @@ -0,0 +1,104 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { TaskSubtask } from './entities'; +import { CreateTaskSubtaskDto, UpdateTaskSubtaskDto } from './dto'; + +interface FindFilter { + taskId?: number; + subtaskId?: number | number[]; +} + +@Injectable() +export class TaskSubtaskService { + constructor( + @InjectRepository(TaskSubtask) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, taskId: number, dto: CreateTaskSubtaskDto): Promise { + return this.repository.save(TaskSubtask.fromDto(accountId, taskId, dto)); + } + + public async createMany(accountId: number, taskId: number, dtos: CreateTaskSubtaskDto[]): Promise { + return Promise.all(dtos.map((dto) => this.create(accountId, taskId, dto))); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + public async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).orderBy('sort_order', 'ASC').getMany(); + } + + public async getCount(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getCount(); + } + + public async update( + accountId: number, + taskId: number, + subtaskId: number, + dto: UpdateTaskSubtaskDto, + ): Promise { + const subtask = await this.findOne(accountId, { taskId, subtaskId }); + if (!subtask) { + throw NotFoundError.withId(TaskSubtask, subtaskId); + } + + return this.repository.save(subtask.update(dto)); + } + + public async updateMany(accountId: number, taskId: number, dtos: UpdateTaskSubtaskDto[]): Promise { + return Promise.all(dtos.map((dto) => this.update(accountId, taskId, dto.id, dto))); + } + + public async processBatch( + accountId: number, + taskId: number, + dtos: (CreateTaskSubtaskDto | UpdateTaskSubtaskDto)[], + ): Promise { + const subtasks = await this.findMany(accountId, { taskId }); + + const created = dtos.filter((dto) => !dto['id']).map((dto) => dto as CreateTaskSubtaskDto); + const updated = dtos.filter((dto) => dto['id']).map((dto) => dto as UpdateTaskSubtaskDto); + const deleted = subtasks.filter((f) => !updated.some((dto) => dto.id === f.id)).map((f) => f.id); + + const result: TaskSubtask[] = []; + + result.push(...(await this.createMany(accountId, taskId, created))); + result.push(...(await this.updateMany(accountId, taskId, updated))); + + if (deleted.length) { + await this.delete(accountId, taskId, deleted); + } + + return result; + } + + public async delete(accountId: number, taskId: number, subtaskId: number | number[]): Promise { + await this.createFindQb(accountId, { taskId, subtaskId }).delete().execute(); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + if (filter?.taskId) { + qb.andWhere('task_id = :taskId', { taskId: filter.taskId }); + } + + if (filter?.subtaskId) { + if (Array.isArray(filter.subtaskId)) { + qb.andWhere('id IN (:...ids)', { ids: filter.subtaskId }); + } else { + qb.andWhere('id = :id', { id: filter.subtaskId }); + } + } + + return qb; + } +} diff --git a/backend/src/CRM/task/dto/create-task.dto.ts b/backend/src/CRM/task/dto/create-task.dto.ts new file mode 100644 index 0000000..a9ed5cb --- /dev/null +++ b/backend/src/CRM/task/dto/create-task.dto.ts @@ -0,0 +1,53 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { CreateBaseTaskDto } from '../../base-task'; +import { CreateTaskSubtaskDto } from '../../task-subtask/dto'; + +export class CreateTaskDto extends CreateBaseTaskDto { + @ApiPropertyOptional({ nullable: true, description: 'Entity ID' }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiProperty({ description: 'Title' }) + @IsString() + title: string; + + @ApiPropertyOptional({ nullable: true, description: 'Planned time' }) + @IsOptional() + @IsNumber() + plannedTime?: number | null; + + @ApiPropertyOptional({ description: 'Board ID' }) + @IsOptional() + @IsNumber() + boardId?: number; + + @ApiPropertyOptional({ description: 'Stage ID' }) + @IsOptional() + @IsNumber() + stageId?: number; + + @ApiProperty({ nullable: true, description: 'Settings ID' }) + @IsOptional() + @IsNumber() + settingsId?: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'External ID' }) + @IsOptional() + @IsString() + externalId?: string | null; + + @ApiPropertyOptional({ nullable: true, type: [String], description: 'File IDs' }) + @IsOptional() + @IsString({ each: true }) + fileIds?: string[] | null; + + @ApiPropertyOptional({ type: [CreateTaskSubtaskDto], description: 'Subtasks' }) + @IsOptional() + @IsArray() + @Type(() => CreateTaskSubtaskDto) + subtasks?: CreateTaskSubtaskDto[]; +} diff --git a/backend/src/CRM/task/dto/index.ts b/backend/src/CRM/task/dto/index.ts new file mode 100644 index 0000000..5bda572 --- /dev/null +++ b/backend/src/CRM/task/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-task.dto'; +export * from './task.dto'; +export * from './update-task.dto'; diff --git a/backend/src/CRM/task/dto/task.dto.ts b/backend/src/CRM/task/dto/task.dto.ts new file mode 100644 index 0000000..bac991e --- /dev/null +++ b/backend/src/CRM/task/dto/task.dto.ts @@ -0,0 +1,61 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { BaseTaskDto } from '../../base-task'; +import { TaskSubtaskDto } from '../../task-subtask/dto/task-subtask.dto'; +import { FileLinkDto } from '../../Service/FileLink/FileLinkDto'; + +import { Task } from '../entities'; + +export class TaskDto extends BaseTaskDto { + @ApiProperty() + @IsString() + title: string; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + plannedTime: number | null; + + @ApiProperty() + @IsNumber() + boardId: number; + + @ApiProperty() + @IsNumber() + stageId: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + settingsId: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'External ID' }) + @IsOptional() + @IsString() + externalId?: string | null; + + @ApiProperty({ type: [TaskSubtaskDto] }) + subtasks: TaskSubtaskDto[]; + + constructor( + task: Task, + entityInfo: EntityInfoDto | null, + fileLinks: FileLinkDto[], + subtasks: TaskSubtaskDto[], + userRights: UserRights, + ) { + super(task, entityInfo, fileLinks, userRights); + + this.title = task.title; + this.plannedTime = task.plannedTime; + this.boardId = task.boardId; + this.stageId = task.stageId; + this.settingsId = task.settingsId; + this.externalId = task.externalId; + this.subtasks = subtasks; + } +} diff --git a/backend/src/CRM/task/dto/update-task.dto.ts b/backend/src/CRM/task/dto/update-task.dto.ts new file mode 100644 index 0000000..ac69a79 --- /dev/null +++ b/backend/src/CRM/task/dto/update-task.dto.ts @@ -0,0 +1,55 @@ +import { ApiExtraModels, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { UpdateBaseTaskDto } from '../../base-task'; +import { CreateTaskSubtaskDto, UpdateTaskSubtaskDto } from '../../task-subtask/dto'; + +@ApiExtraModels(CreateTaskSubtaskDto) +@ApiExtraModels(UpdateTaskSubtaskDto) +export class UpdateTaskDto extends UpdateBaseTaskDto { + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + title?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + plannedTime?: number | null; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + boardId?: number; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + stageId?: number; + + @ApiPropertyOptional({ nullable: true, description: 'External ID' }) + @IsOptional() + @IsString() + externalId?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsArray() + fileIds?: string[] | null; + + @ApiPropertyOptional({ + description: 'Array of subtasks', + type: 'array', + items: { oneOf: [{ $ref: getSchemaPath(CreateTaskSubtaskDto) }, { $ref: getSchemaPath(UpdateTaskSubtaskDto) }] }, + }) + @IsOptional() + @IsArray() + @Type(() => Object) //HACK: Nest make items array not object?!?! + subtasks?: (CreateTaskSubtaskDto | UpdateTaskSubtaskDto)[] | null; +} diff --git a/backend/src/CRM/task/entities/index.ts b/backend/src/CRM/task/entities/index.ts new file mode 100644 index 0000000..1ab88d2 --- /dev/null +++ b/backend/src/CRM/task/entities/index.ts @@ -0,0 +1 @@ +export * from './task.entity'; diff --git a/backend/src/CRM/task/entities/task.entity.ts b/backend/src/CRM/task/entities/task.entity.ts new file mode 100644 index 0000000..f1d0574 --- /dev/null +++ b/backend/src/CRM/task/entities/task.entity.ts @@ -0,0 +1,152 @@ +import { Column, Entity } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; +import { BaseTask, TaskView } from '../../base-task'; + +import { CreateTaskDto, TaskDto, UpdateTaskDto } from '../dto'; + +@Entity('task') +export class Task extends BaseTask implements Authorizable { + @Column({ nullable: true }) + entityId: number | null; + + @Column() + title: string; + + @Column() + plannedTime: number; // interval in seconds + + @Column() + boardId: number; + + @Column() + stageId: number; + + @Column() + settingsId: number | null; + + @Column() + externalId: string | null; + + constructor( + accountId: number, + entityId: number | null, + createdBy: number, + responsibleUserId: number, + text: string, + title: string, + plannedTime: number | null, + isResolved: boolean, + startDate: Date, + endDate: Date, + resolvedDate: Date | null, + weight: number, + boardId: number, + stageId: number, + settingsId: number | null, + externalId: string | null, + createdAt?: Date, + ) { + super( + accountId, + createdBy, + responsibleUserId, + text, + isResolved, + startDate, + endDate, + resolvedDate, + weight, + createdAt, + ); + this.entityId = entityId; + this.title = title; + this.plannedTime = plannedTime; + this.boardId = boardId; + this.stageId = stageId; + this.settingsId = settingsId; + this.externalId = externalId; + } + + public static create(accountId: number, createdBy: number, dto: CreateTaskDto): Task { + return new Task( + accountId, + dto.entityId, + createdBy, + dto.responsibleUserId, + dto.text ?? '', + dto.title, + dto.plannedTime, + false, + dto.startDate ? DateUtil.fromISOString(dto.startDate) : null, + dto.endDate ? DateUtil.fromISOString(dto.endDate) : null, + dto.resolvedDate ? DateUtil.fromISOString(dto.resolvedDate) : null, + dto.weight, + dto.boardId, + dto.stageId, + dto.settingsId, + dto.externalId, + ); + } + + public update(dto: UpdateTaskDto): Task { + this.entityId = dto.entityId !== undefined ? dto.entityId : this.entityId; + this.responsibleUserId = dto.responsibleUserId !== undefined ? dto.responsibleUserId : this.responsibleUserId; + this.text = dto.text !== undefined ? dto.text : this.text; + this.title = dto.title !== undefined ? dto.title : this.title; + this.plannedTime = dto.plannedTime !== undefined ? dto.plannedTime : this.plannedTime; + this.startDate = dto.startDate !== undefined ? DateUtil.fromISOString(dto.startDate) : this.startDate; + this.endDate = dto.endDate !== undefined ? DateUtil.fromISOString(dto.endDate) : this.endDate; + this.boardId = dto.boardId ? dto.boardId : this.boardId; + this.stageId = dto.stageId ? dto.stageId : this.stageId; + this.externalId = dto.externalId !== undefined ? dto.externalId : this.externalId; + + if (dto.isResolved !== undefined) { + if (!this.isResolved && dto.isResolved) this.resolvedDate = DateUtil.now(); + if (this.isResolved && !dto.isResolved) this.resolvedDate = null; + this.isResolved = dto.isResolved; + } + + return this; + } + + public hasChanges(dto: UpdateTaskDto): boolean { + return ( + (dto.entityId !== undefined && dto.entityId !== this.entityId) || + (dto.responsibleUserId !== undefined && dto.responsibleUserId !== this.responsibleUserId) || + (dto.text !== undefined && dto.text !== this.text) || + (dto.title !== undefined && dto.title !== this.title) || + (dto.plannedTime !== undefined && dto.plannedTime !== this.plannedTime) || + (dto.startDate !== undefined && DateUtil.fromISOString(dto.startDate) !== this.startDate) || + (dto.endDate !== undefined && DateUtil.fromISOString(dto.endDate) !== this.endDate) || + (dto.boardId !== undefined && dto.boardId !== this.boardId) || + (dto.stageId !== undefined && dto.stageId !== this.stageId) || + (dto.externalId !== undefined && dto.externalId !== this.externalId) || + (dto.isResolved !== undefined && dto.isResolved !== this.isResolved) + ); + } + + public view(): TaskView { + return TaskView.Task; + } + + public toSimpleDto(): TaskDto { + return new TaskDto(this, null, [], [], null); + } + + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.Task, + id: null, + ownerId: this.responsibleUserId, + createdBy: this.createdBy, + }; + } + static getAuthorizable(): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Task, id: null }); + } +} diff --git a/backend/src/CRM/task/index.ts b/backend/src/CRM/task/index.ts new file mode 100644 index 0000000..375c931 --- /dev/null +++ b/backend/src/CRM/task/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './task.controller'; +export * from './task.handler'; +export * from './task.service'; diff --git a/backend/src/CRM/task/task.controller.ts b/backend/src/CRM/task/task.controller.ts new file mode 100644 index 0000000..644e8dc --- /dev/null +++ b/backend/src/CRM/task/task.controller.ts @@ -0,0 +1,47 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { CreateTaskDto, TaskDto, UpdateTaskDto } from './dto'; +import { TaskService } from './task.service'; + +@ApiTags('crm/tasks') +@Controller('/crm/tasks') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class TaskController { + constructor(private readonly service: TaskService) {} + + @ApiCreatedResponse({ description: 'Create task', type: TaskDto }) + @Post() + public async create(@CurrentAuth() { account, user }: AuthData, @Body() dto: CreateTaskDto): Promise { + return await this.service.createAndGetDto(account, user, dto); + } + + @ApiCreatedResponse({ description: 'Get task', type: TaskDto }) + @Get(':taskId') + public async findOne( + @CurrentAuth() { account, user }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + ): Promise { + return await this.service.findDtoById(account, user, taskId); + } + + @ApiCreatedResponse({ description: 'Update task', type: TaskDto }) + @Patch(':taskId') + public async update( + @CurrentAuth() { account, user }: AuthData, + @Param('taskId', ParseIntPipe) taskId: number, + @Body() dto: UpdateTaskDto, + ): Promise { + return await this.service.updateAndGetDto(account, user, taskId, dto); + } + + @ApiOkResponse({ description: 'Delete task' }) + @Delete(':taskId') + public async delete(@CurrentAuth() { accountId, user }: AuthData, @Param('taskId', ParseIntPipe) taskId: number) { + await this.service.delete({ user, filter: { accountId, taskId } }); + } +} diff --git a/backend/src/CRM/task/task.handler.ts b/backend/src/CRM/task/task.handler.ts new file mode 100644 index 0000000..bc76655 --- /dev/null +++ b/backend/src/CRM/task/task.handler.ts @@ -0,0 +1,83 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; +import { ActionTaskCreateSettings, AutomationJob, EntityTypeActionType, OnAutomationJob } from '@/modules/automation'; + +import { CrmEventType, TaskExtEvent, TaskExtUpsertEvent } from '../common'; +import { TaskService } from './task.service'; + +interface EntityVariables { + id?: number | null; + stageId?: number | null; + responsibleUserId?: number | null; +} + +interface CreateTaskVariables { + accountId: number; + entity?: EntityVariables | null; + taskSettings?: ActionTaskCreateSettings | null; +} + +@Injectable() +export class TaskHandler { + private readonly logger = new Logger(TaskHandler.name); + constructor(private readonly service: TaskService) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + if (event.newUserId) { + await this.service.changeResponsible({ + accountId: event.accountId, + currentUserId: event.userId, + newUserId: event.newUserId, + }); + } + } + + @OnEvent(CrmEventType.TaskUpsertExt, { async: true }) + public async onTaskUpsertExt(event: TaskExtUpsertEvent) { + await this.service.handleUpsertExt(event); + } + + @OnEvent(CrmEventType.TaskDeleteExt, { async: true }) + public async onTaskDeleteExt(event: TaskExtEvent) { + const { accountId, boardId, taskId, externalId } = event; + if (accountId && boardId && externalId) { + await this.service.delete({ user: null, filter: { accountId, boardId, externalId }, event }); + } + if (accountId && boardId && taskId) { + await this.service.delete({ user: null, filter: { accountId, boardId, taskId }, event }); + } + } + + /** + * @deprecated use new @see handleCreateJob instead + */ + @OnAutomationJob(EntityTypeActionType.CreateTask) + async handleCreateJobOld(job: AutomationJob): Promise<{ variables?: unknown }> { + return this.handleCreateJob(job); + } + @OnAutomationJob(EntityTypeActionType.TaskCreate) + async handleCreateJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.taskSettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, taskSettings } = job.variables; + const task = await this.service.processAutomation({ + accountId, + entityId: entity.id, + entityOwnerId: entity.responsibleUserId, + entityStageId: entity.stageId, + settings: taskSettings, + }); + return { variables: { ...job.variables, task: task?.toSimpleDto() } }; + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + return { variables: job.variables }; + } + } +} diff --git a/backend/src/CRM/task/task.service.ts b/backend/src/CRM/task/task.service.ts new file mode 100644 index 0000000..b7d2ab0 --- /dev/null +++ b/backend/src/CRM/task/task.service.ts @@ -0,0 +1,584 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Repository } from 'typeorm'; +import { convert } from 'html-to-text'; + +import { DateUtil, FileLinkSource, NotFoundError, ServiceEvent } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { UserService } from '@/modules/iam/user/user.service'; +import { ActionHelper, ActionTaskCreateSettings } from '@/modules/automation'; +import { EntityInfoService } from '@/modules/entity/entity-info'; +import { NotificationType } from '@/modules/notification/notification/enums/notification-type.enum'; +import { CreateNotificationDto } from '@/modules/notification/notification/dto/create-notification.dto'; + +import { CrmEventType, TaskCreatedEvent, TaskEvent, TaskExtUpsertEvent, TaskUpdatedEvent } from '../common'; +import { BaseTaskService } from '../base-task'; +import { BoardService } from '../board/board.service'; +import { BoardType } from '../board/enums'; +import { BoardStageCode, BoardStageService } from '../board-stage'; +import { TaskSubtaskService } from '../task-subtask/task-subtask.service'; + +import { EntityService } from '../Service/Entity/EntityService'; +import { FileLinkService } from '../Service/FileLink/FileLinkService'; + +import { CreateTaskDto, TaskDto, UpdateTaskDto } from './dto'; +import { Task } from './entities'; + +const BatchProcessLimit = 100; + +interface FindFilter { + accountId: number; + boardId?: number; + taskId?: number | number[]; + entityId?: number; + isResolved?: boolean; + responsibles?: number | number[]; + externalId?: string; +} + +@Injectable() +export class TaskService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Task) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly userService: UserService, + @Inject(forwardRef(() => EntityService)) + private readonly entityService: EntityService, + private readonly fileLinkService: FileLinkService, + private readonly subtaskService: TaskSubtaskService, + private readonly baseTaskService: BaseTaskService, + @Inject(forwardRef(() => BoardStageService)) + private readonly stageService: BoardStageService, + @Inject(forwardRef(() => BoardService)) + private readonly boardService: BoardService, + private readonly entityInfoService: EntityInfoService, + ) {} + + public async create({ + accountId, + user, + dto, + skipPermissionCheck, + event, + }: { + accountId: number; + user: User; + dto: CreateTaskDto; + skipPermissionCheck?: boolean; + event?: ServiceEvent; + }): Promise { + if (!skipPermissionCheck) { + await this.authService.check({ + action: 'create', + user, + authorizable: Task.getAuthorizable(), + throwError: true, + }); + } + + if (!dto.boardId) { + dto.boardId = await this.boardService.findOneId({ accountId, isSystem: true, type: BoardType.Task }); + } + + if (dto.isResolved || !dto.stageId) { + dto.stageId = await this.stageService.findOneId({ + accountId, + boardId: dto.boardId, + includeCodes: dto.isResolved ? [BoardStageCode.Done] : undefined, + }); + } + + dto.weight = dto.weight ?? (await this.baseTaskService.calculateWeight(accountId, dto.afterId, dto.beforeId)); + const task = await this.repository.save(Task.create(accountId, user.id, dto)); + + if (dto.fileIds) { + await this.fileLinkService.processFiles(accountId, FileLinkSource.TASK, task.id, dto.fileIds); + } + + if (dto.subtasks) { + await this.subtaskService.createMany(accountId, task.id, dto.subtasks); + } + + this.eventEmitter.emit( + CrmEventType.TaskCreated, + new TaskCreatedEvent({ + source: Task.name, + accountId, + taskId: task.id, + boardId: task.boardId, + externalId: task.externalId, + ownerId: task.responsibleUserId, + entityId: task.entityId ?? null, + createdBy: task.createdBy, + taskTitle: task.title, + taskText: task.text, + createdAt: task.createdAt, + startDate: task.startDate, + endDate: task.endDate, + prevEvent: event, + }), + ); + + return task; + } + + public async createAndGetDto(account: Account, user: User, dto: CreateTaskDto): Promise { + const task = await this.create({ accountId: account.id, user, dto }); + + return await this.createDtoForTask(account, user, task); + } + + public async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + public async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).orderBy('task.id', 'DESC').getMany(); + } + + public async findDtoById(account: Account, user: User, id: number): Promise { + const task = await this.findOne({ accountId: account.id, taskId: id }); + return task ? await this.createDtoForTask(account, user, task) : null; + } + + public async update({ + accountId, + user, + taskId, + dto, + skipPermissionCheck, + event, + }: { + accountId: number; + user: User | null; + taskId: number; + dto: UpdateTaskDto; + skipPermissionCheck?: boolean; + event?: ServiceEvent; + }): Promise { + const task = await this.repository.findOneBy({ id: taskId }); + if (!task) { + throw NotFoundError.withId(Task, taskId); + } + return this.updateTask({ accountId, user, task, dto, skipPermissionCheck, event }); + } + + private async updateTask({ + accountId, + user, + task, + dto, + skipPermissionCheck, + event, + }: { + accountId: number; + user: User | null; + task: Task; + dto: UpdateTaskDto; + skipPermissionCheck?: boolean; + event?: ServiceEvent; + }): Promise { + if (!skipPermissionCheck && user) { + await this.authService.check({ action: 'edit', user, authorizable: task, throwError: true }); + } + + if (!task.hasChanges(dto) && !dto.sorting && !dto.fileIds && !dto.subtasks) { + return task; + } + + if (dto.fileIds !== undefined) { + await this.fileLinkService.processFiles(accountId, FileLinkSource.TASK, task.id, dto.fileIds ?? []); + } + + if (dto.subtasks !== undefined) { + await this.subtaskService.processBatch(accountId, task.id, dto.subtasks); + } + + const { entityId } = task; + + if (dto.sorting) { + task.weight = await this.baseTaskService.calculateWeight(accountId, dto.sorting.afterId, dto.sorting.beforeId); + } + + if (dto.entityId !== undefined && dto.entityId !== task.entityId) { + const taskBoardId = dto.entityId ? await this.getProjectTaskBoardId(accountId, dto.entityId) : undefined; + if (taskBoardId && taskBoardId !== task.boardId) { + dto.boardId = taskBoardId; + dto.stageId = await this.stageService.findOneId({ + accountId, + boardId: dto.boardId ?? task.boardId, + includeCodes: (dto.isResolved ?? task.isResolved) ? [BoardStageCode.Done] : undefined, + }); + } else if (!taskBoardId && task.boardId) { + dto.boardId = await this.boardService.findOneId({ accountId, isSystem: true, type: BoardType.Task }); + dto.stageId = await this.stageService.findOneId({ + accountId, + boardId: dto.boardId ?? task.boardId, + includeCodes: (dto.isResolved ?? task.isResolved) ? [BoardStageCode.Done] : undefined, + }); + } + } else if (dto.isResolved !== undefined && !dto.stageId && dto.isResolved !== task.isResolved) { + dto.stageId = await this.stageService.findOneId({ + accountId, + boardId: dto.boardId ?? task.boardId, + includeCodes: dto.isResolved ? [BoardStageCode.Done] : undefined, + }); + } else if (dto.isResolved === undefined) { + const stageChanged = dto.stageId && dto.stageId !== task.stageId; + const boardChanged = dto.boardId && dto.boardId !== task.boardId; + if (stageChanged || boardChanged) { + const stage = await this.stageService.findOne({ + accountId, + stageId: stageChanged ? dto.stageId : undefined, + boardId: boardChanged ? dto.boardId : undefined, + includeCodes: !stageChanged && boardChanged && task.isResolved ? [BoardStageCode.Done] : undefined, + }); + dto.isResolved = stage?.code === BoardStageCode.Done; + dto.stageId = stage?.id; + dto.boardId = stage?.boardId; + } + } + + await this.repository.save(task.update(dto)); + + this.eventEmitter.emit( + CrmEventType.TaskUpdated, + new TaskUpdatedEvent({ + source: Task.name, + accountId, + taskId: task.id, + boardId: task.boardId, + externalId: task.externalId, + ownerId: task.responsibleUserId, + entityId: task.entityId ?? null, + createdBy: task.createdBy, + taskTitle: task.title, + taskText: task.text, + createdAt: task.createdAt, + startDate: task.startDate, + endDate: task.endDate, + prevEntityId: entityId, + prevEvent: event, + }), + ); + + return task; + } + + public async updateAndGetDto(account: Account, user: User, taskId: number, dto: UpdateTaskDto): Promise { + const task = await this.update({ accountId: account.id, user, taskId, dto }); + + return await this.createDtoForTask(account, user, task); + } + + private async getProjectTaskBoardId(accountId: number, entityId: number): Promise { + const entity = await this.entityService.findOne(accountId, { entityId }); + if (entity?.boardId) { + const board = await this.boardService.findOne({ filter: { accountId, boardId: entity.boardId } }); + + return board?.taskBoardId; + } + + return undefined; + } + + public async delete({ user, filter, event }: { user: User | null; filter: FindFilter; event?: ServiceEvent | null }) { + const tasks = await this.findMany(filter); + await Promise.all( + tasks.map(async (task) => { + if (user) { + await this.authService.check({ action: 'delete', user, authorizable: task, throwError: true }); + } + + await this.fileLinkService.processFiles(task.accountId, FileLinkSource.TASK, task.id, []); + await this.repository.delete({ id: task.id }); + this.eventEmitter.emit( + CrmEventType.TaskDeleted, + new TaskEvent({ + source: Task.name, + accountId: task.accountId, + taskId: task.id, + boardId: task.boardId, + externalId: task.externalId, + entityId: task.entityId, + prevEvent: event, + }), + ); + }), + ); + } + + public async changeResponsible({ + accountId, + currentUserId, + newUserId, + }: { + accountId: number; + currentUserId: number; + newUserId: number; + }) { + await this.repository.update({ accountId, responsibleUserId: currentUserId }, { responsibleUserId: newUserId }); + } + + public async changeStageForAll({ + accountId, + boardId, + stageId, + newStageId, + }: { + accountId: number; + boardId: number; + stageId: number; + newStageId: number; + }) { + const qb = this.repository + .createQueryBuilder('task') + .select('task.id', 'id') + .where('task.account_id = :accountId', { accountId }) + .andWhere('task.board_id = :boardId', { boardId }) + .andWhere('task.stage_id = :stageId', { stageId }) + .limit(BatchProcessLimit); + let tasks: { id: number }[] = []; + do { + tasks = await qb.getRawMany<{ id: number }>(); + for (const task of tasks) { + await this.update({ + accountId, + user: null, + taskId: task.id, + dto: { boardId: boardId, stageId: newStageId }, + skipPermissionCheck: true, + }); + } + } while (tasks.length); + } + + public async handleUpsertExt(event: TaskExtUpsertEvent): Promise { + const user = await this.userService.findOne({ accountId: event.accountId, id: event.ownerId }); + if (user) { + const { accountId, boardId, taskId, externalId } = event; + let task = event.externalId ? await this.findOne({ accountId, boardId, externalId }) : undefined; + if (!task && event.taskId) { + task = await this.findOne({ accountId, boardId, taskId }); + } + + if (task) { + return this.updateTask({ + accountId, + user, + task, + dto: { + title: event.title, + text: event.text, + startDate: event.startDate.toISOString(), + endDate: event.endDate.toISOString(), + externalId, + }, + skipPermissionCheck: true, + event, + }); + } else { + return this.create({ + accountId, + user, + dto: { + boardId, + responsibleUserId: event.ownerId, + title: event.title, + text: event.text, + startDate: event.startDate.toISOString(), + endDate: event.endDate.toISOString(), + externalId, + }, + skipPermissionCheck: true, + event, + }); + } + } + return null; + } + + public async processAutomation({ + accountId, + entityId, + entityOwnerId, + entityStageId, + settings, + }: { + accountId: number; + entityId: number; + entityOwnerId: number; + entityStageId: number | null | undefined; + settings: ActionTaskCreateSettings; + }): Promise { + const entity = await this.entityInfoService.findOne({ accountId, entityId }); + if (entity && (!entity.stageId || settings.allowAnyStage || entity.stageId === entityStageId)) { + const user = await this.userService.findOne({ accountId, id: entityOwnerId }); + const ownerId = settings.responsibleUserId ?? entityOwnerId; + const startDate = DateUtil.add(DateUtil.now(), { seconds: settings.deferStart ?? 0 }); + const endDate = ActionHelper.getEndDate({ + startDate, + deadlineType: settings.deadlineType, + deadlineTime: settings.deadlineTime, + }); + return this.create({ + accountId, + user, + dto: { + responsibleUserId: ownerId, + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), + title: settings.title, + text: settings.text, + entityId: entityId, + boardId: null, + stageId: null, + settingsId: null, + plannedTime: null, + }, + skipPermissionCheck: true, + }); + } + return null; + } + + public async getOverdueNotifications(from: Date, to: Date): Promise { + const tasks = await this.repository + .createQueryBuilder('task') + .where('task.is_resolved = false') + .andWhere('task.end_date > :from', { from }) + .andWhere('task.end_date <= :to', { to }) + .getMany(); + return tasks.map( + (task) => + new CreateNotificationDto( + task.accountId, + task.responsibleUserId, + NotificationType.TASK_OVERDUE, + task.id, + task.entityId, + task.createdBy, + task.title, + convert(task.text), + ), + ); + } + + public async getOverdueForFollowNotifications( + notifyUserId: number, + from: Date, + to: Date, + followUserIds: number[], + ): Promise { + const tasks = await this.repository + .createQueryBuilder('task') + .where('task.is_resolved = false') + .andWhere('task.end_date > :from', { from }) + .andWhere('task.end_date <= :to', { to }) + .andWhere('task.responsible_user_id in (:...userIds)', { userIds: followUserIds }) + .getMany(); + return tasks.map( + (task) => + new CreateNotificationDto( + task.accountId, + notifyUserId, + NotificationType.TASK_OVERDUE_EMPLOYEE, + task.id, + task.entityId, + task.responsibleUserId, + task.title, + convert(task.text), + ), + ); + } + + public async getBeforeStartNotifications(userId: number, from: Date, to: Date) { + const tasks = await this.repository + .createQueryBuilder('task') + .where('task.is_resolved = false') + .andWhere('task.responsible_user_id = :userId', { userId }) + .andWhere('task.start_date > :from', { from }) + .andWhere('task.start_date <= :to', { to }) + .getMany(); + return tasks.map( + (task) => + new CreateNotificationDto( + task.accountId, + task.responsibleUserId, + NotificationType.TASK_BEFORE_START, + task.id, + task.entityId, + task.createdBy, + task.title, + convert(task.text), + ), + ); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('task') + .where('task.accountId = :accountId', { accountId: filter.accountId }); + if (filter.boardId) { + qb.andWhere('task.board_id = :boardId', { boardId: filter.boardId }); + } + if (filter.taskId) { + if (Array.isArray(filter.taskId)) { + qb.andWhere('task.id IN (:...taskIds)', { taskIds: filter.taskId }); + } else { + qb.andWhere('task.id = :taskId', { taskId: filter.taskId }); + } + } + if (filter.entityId) { + qb.andWhere('task.entity_id = :entityId', { entityId: filter.entityId }); + } + if (filter.isResolved !== undefined) { + qb.andWhere('task.is_resolved = :isResolved', { isResolved: filter.isResolved }); + } + if (filter.responsibles) { + if (Array.isArray(filter.responsibles)) { + if (filter.responsibles.length === 0) { + return qb.where('1 = 0'); + } + qb.andWhere('task.responsible_user_id IN (:...responsibles)', { responsibles: filter.responsibles }); + } else { + qb.andWhere('task.responsible_user_id = :responsible', { responsible: filter.responsibles }); + } + } + if (filter.externalId) { + qb.andWhere('task.external_id = :externalId', { externalId: filter.externalId }); + } + + return qb; + } + + private async createDtoForTask(account: Account, user: User, task: Task): Promise { + if (!(await this.authService.check({ action: 'view', user, authorizable: task }))) { + return null; + } + + const fileLinks = await this.fileLinkService.getFileLinkDtos(account, FileLinkSource.TASK, task.id); + const subtasks = await this.subtaskService.findMany(task.accountId, { taskId: task.id }); + const entityInfo = task.entityId + ? await this.entityInfoService.findOne({ + accountId: account.id, + user, + entityId: task.entityId, + }) + : null; + const userRights = await this.authService.getUserRights({ user, authorizable: task }); + + return new TaskDto( + task, + entityInfo, + fileLinks, + subtasks.map((s) => s.toDto()), + userRights, + ); + } +} diff --git a/backend/src/Mailing/Controller/MailMessage/CreateContactAndLeadController.ts b/backend/src/Mailing/Controller/MailMessage/CreateContactAndLeadController.ts new file mode 100644 index 0000000..4cf2c52 --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/CreateContactAndLeadController.ts @@ -0,0 +1,28 @@ +import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { MailMessageService } from '../../Service/MailMessage/MailMessageService'; +import { CreateContactLeadDto } from '../../Service/MailMessage/Dto/CreateContactLeadDto'; + +@ApiTags('mailing/messages') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class CreateContactAndLeadController { + constructor(private mailMessageService: MailMessageService) {} + + @Post('/mailing/mailboxes/:mailboxId/messages/:messageId/contact') + @ApiOkResponse({ description: 'Created contact and lead', type: EntityInfoDto }) + async createContactAndLead( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + @Body() dto: CreateContactLeadDto, + ): Promise { + return await this.mailMessageService.createContact(accountId, user, mailboxId, messageId, dto); + } +} diff --git a/backend/src/Mailing/Controller/MailMessage/GetAttachmentController.ts b/backend/src/Mailing/Controller/MailMessage/GetAttachmentController.ts new file mode 100644 index 0000000..c3c7612 --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/GetAttachmentController.ts @@ -0,0 +1,44 @@ +import { Controller, Get, HttpStatus, Param, ParseIntPipe, Res, StreamableFile } from '@nestjs/common'; +import { Response } from 'express'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailMessageService } from '../../Service/MailMessage/MailMessageService'; + +@ApiTags('mailing/messages') +@Controller() +@JwtAuthorized() +export class GetAttachmentController { + constructor(private mailMessageService: MailMessageService) {} + + @Get('/mailing/mailboxes/:mailboxId/messages/:messageId/attachments/:payloadId') + @ApiOkResponse({ description: 'Get attachment for mail message', type: StreamableFile }) + async getAttachment( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + @Param('payloadId', ParseIntPipe) payloadId: number, + @Res({ passthrough: true }) res: Response, + ): Promise { + const attachment = await this.mailMessageService.getMessageAttachment( + accountId, + userId, + mailboxId, + messageId, + payloadId, + ); + if (attachment) { + res.set({ + 'Content-Type': attachment.mimeType, + 'Content-Disposition': `attachment; filename="${encodeURI(attachment.filename)}"`, + }); + return new StreamableFile(attachment.content); + } else { + res.sendStatus(HttpStatus.NOT_FOUND); + return null; + } + } +} diff --git a/backend/src/Mailing/Controller/MailMessage/GetMailMessageController.ts b/backend/src/Mailing/Controller/MailMessage/GetMailMessageController.ts new file mode 100644 index 0000000..a5012e3 --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/GetMailMessageController.ts @@ -0,0 +1,26 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailMessageService } from '../../Service/MailMessage/MailMessageService'; +import { MailMessageDto } from '../../Service/MailMessage/Dto/MailMessageDto'; + +@ApiTags('mailing/messages') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class GetMailMessageController { + constructor(private mailMessageService: MailMessageService) {} + + @Get('/mailing/mailboxes/:mailboxId/messages/:messageId') + @ApiOkResponse({ description: 'Mail', type: MailMessageDto }) + async getMessage( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ): Promise { + return await this.mailMessageService.getMessageWithPayload(accountId, user, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/MailMessage/GetMailThreadController.ts b/backend/src/Mailing/Controller/MailMessage/GetMailThreadController.ts new file mode 100644 index 0000000..4f0e220 --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/GetMailThreadController.ts @@ -0,0 +1,26 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailMessageService } from '../../Service/MailMessage/MailMessageService'; +import { MailMessageDto } from '../../Service/MailMessage/Dto/MailMessageDto'; + +@ApiTags('mailing/messages') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class GetMailThreadController { + constructor(private mailMessageService: MailMessageService) {} + + @Get('/mailing/mailboxes/:mailboxId/threads/:messageId') + @ApiOkResponse({ description: 'Mail thread', type: [MailMessageDto] }) + async getThread( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ): Promise { + return await this.mailMessageService.getThreadWithPayload(accountId, user, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/MailMessage/GetMailboxMessagesController.ts b/backend/src/Mailing/Controller/MailMessage/GetMailboxMessagesController.ts new file mode 100644 index 0000000..297c812 --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/GetMailboxMessagesController.ts @@ -0,0 +1,27 @@ +import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailMessageService } from '../../Service/MailMessage/MailMessageService'; +import { MailThreadResult } from '../../Service/MailMessage/MailThreadResult'; +import { GetMailboxMessagesFilter } from './GetMailboxMessagesFilter'; + +@ApiTags('mailing/messages') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class GetMailboxMessagesController { + constructor(private mailMessageService: MailMessageService) {} + + @Get('/mailing/mailboxes/:mailboxId/messages') + @ApiOkResponse({ description: 'Mail thread list', type: MailThreadResult }) + async getMessages( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Query() filter: GetMailboxMessagesFilter, + ): Promise { + return await this.mailMessageService.getMessagesForMailbox(accountId, user, mailboxId, filter); + } +} diff --git a/backend/src/Mailing/Controller/MailMessage/GetMailboxMessagesFilter.ts b/backend/src/Mailing/Controller/MailMessage/GetMailboxMessagesFilter.ts new file mode 100644 index 0000000..c72df5b --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/GetMailboxMessagesFilter.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { PagingQuery } from '@/common'; + +export class GetMailboxMessagesFilter extends PagingQuery { + @ApiProperty() + @IsOptional() + @IsNumber() + folderId?: number; + + @ApiProperty() + @IsOptional() + @IsString() + search?: string; +} diff --git a/backend/src/Mailing/Controller/MailMessage/GetSectionMessagesController.ts b/backend/src/Mailing/Controller/MailMessage/GetSectionMessagesController.ts new file mode 100644 index 0000000..9a5b99c --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/GetSectionMessagesController.ts @@ -0,0 +1,28 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxFolderType } from '../../common'; +import { MailMessageService } from '../../Service/MailMessage/MailMessageService'; +import { MailThreadResult } from '../../Service/MailMessage/MailThreadResult'; +import { GetSectionMessagesFilter } from './GetSectionMessagesFilter'; + +@ApiTags('mailing/messages') +@Controller() +@JwtAuthorized({ prefetch: { user: true } }) +export class GetSectionMessagesController { + constructor(private mailMessageService: MailMessageService) {} + + @Get('/mailing/section/:type/messages') + @ApiOkResponse({ description: 'Mail list', type: MailThreadResult }) + async getMessages( + @CurrentAuth() { accountId, user }: AuthData, + @Param('type') type: MailboxFolderType, + @Query() filter: GetSectionMessagesFilter, + ): Promise { + return await this.mailMessageService.getMessagesForSection(accountId, user, type, filter); + } +} diff --git a/backend/src/Mailing/Controller/MailMessage/GetSectionMessagesFilter.ts b/backend/src/Mailing/Controller/MailMessage/GetSectionMessagesFilter.ts new file mode 100644 index 0000000..d31e1bb --- /dev/null +++ b/backend/src/Mailing/Controller/MailMessage/GetSectionMessagesFilter.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { PagingQuery } from '@/common'; + +export class GetSectionMessagesFilter extends PagingQuery { + @ApiProperty() + @IsOptional() + @IsNumber() + mailboxId?: number; + + @ApiProperty() + @IsOptional() + @IsString() + search?: string; +} diff --git a/backend/src/Mailing/Controller/Mailbox/GetMailboxesInfoController.ts b/backend/src/Mailing/Controller/Mailbox/GetMailboxesInfoController.ts new file mode 100644 index 0000000..41b0614 --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/GetMailboxesInfoController.ts @@ -0,0 +1,22 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxService } from '../../Service/Mailbox/MailboxService'; +import { MailboxesInfoDto } from '../../Service/Mailbox/Dto/mailboxes-info.dto'; + +@ApiTags('mailing/mailbox') +@Controller() +@JwtAuthorized() +export class GetMailboxesInfoController { + constructor(private mailboxService: MailboxService) {} + + @ApiOkResponse({ description: 'Mailboxes', type: MailboxesInfoDto }) + @Get('mailing/mailboxes') + public async getMailboxes(@CurrentAuth() { accountId, userId }: AuthData): Promise { + return await this.mailboxService.getMailboxesForInfo(accountId, userId); + } +} diff --git a/backend/src/Mailing/Controller/Mailbox/SeenMailThreadController.ts b/backend/src/Mailing/Controller/Mailbox/SeenMailThreadController.ts new file mode 100644 index 0000000..88005f8 --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/SeenMailThreadController.ts @@ -0,0 +1,24 @@ +import { Controller, Param, ParseIntPipe, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxService } from '../../Service/Mailbox/MailboxService'; + +@ApiTags('mailing/mailbox') +@Controller() +@JwtAuthorized() +export class SeenMailThreadController { + constructor(private mailboxService: MailboxService) {} + + @Put('/mailing/mailboxes/:mailboxId/threads/:messageId/seen') + public async markSeenMailThread( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return await this.mailboxService.markSeenThread(accountId, userId, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/Mailbox/SendMailMessageController.ts b/backend/src/Mailing/Controller/Mailbox/SendMailMessageController.ts new file mode 100644 index 0000000..c3ef807 --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/SendMailMessageController.ts @@ -0,0 +1,32 @@ +import { Body, Controller, Param, ParseIntPipe, Post, UploadedFiles, UseInterceptors } from '@nestjs/common'; +import { FilesInterceptor } from '@nestjs/platform-express'; +import { memoryStorage } from 'multer'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { SendMailMessageDto } from '../../common'; +import { MailboxService } from '../../Service/Mailbox/MailboxService'; + +@ApiTags('mailing/messages') +@Controller() +@JwtAuthorized() +export class SendMailMessageController { + constructor(private mailboxService: MailboxService) {} + + @Post('/mailing/mailboxes/:mailboxId/send') + @UseInterceptors(FilesInterceptor('attachment', 100, { storage: memoryStorage() })) + async sendMessage( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Body('message') message: string, + @UploadedFiles() files: Express.Multer.File[], + ): Promise { + const dto = JSON.parse(message) as SendMailMessageDto; + const attachments = files ? files.map((file) => StorageFile.fromMulter(file)) : []; + return await this.mailboxService.sendMessage(accountId, userId, mailboxId, dto, attachments); + } +} diff --git a/backend/src/Mailing/Controller/Mailbox/SpamMailThreadController.ts b/backend/src/Mailing/Controller/Mailbox/SpamMailThreadController.ts new file mode 100644 index 0000000..33b28ad --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/SpamMailThreadController.ts @@ -0,0 +1,24 @@ +import { Controller, Param, ParseIntPipe, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxService } from '../../Service/Mailbox/MailboxService'; + +@ApiTags('mailing/mailbox') +@Controller() +@JwtAuthorized() +export class SpamMailThreadController { + constructor(private mailboxService: MailboxService) {} + + @Put('/mailing/mailboxes/:mailboxId/threads/:messageId/spam') + public async spamMailThread( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return await this.mailboxService.spamThread(accountId, userId, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/Mailbox/TrashMailThreadController.ts b/backend/src/Mailing/Controller/Mailbox/TrashMailThreadController.ts new file mode 100644 index 0000000..0303b68 --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/TrashMailThreadController.ts @@ -0,0 +1,24 @@ +import { Controller, Param, ParseIntPipe, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxService } from '../../Service/Mailbox/MailboxService'; + +@ApiTags('mailing/mailbox') +@Controller() +@JwtAuthorized() +export class TrashMailThreadController { + constructor(private mailboxService: MailboxService) {} + + @Put('/mailing/mailboxes/:mailboxId/threads/:messageId/trash') + public async trashMailThread( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return await this.mailboxService.trashThread(accountId, userId, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/Mailbox/UnseenMailThreadController.ts b/backend/src/Mailing/Controller/Mailbox/UnseenMailThreadController.ts new file mode 100644 index 0000000..d11dd1c --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/UnseenMailThreadController.ts @@ -0,0 +1,24 @@ +import { Controller, Param, ParseIntPipe, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxService } from '../../Service/Mailbox/MailboxService'; + +@ApiTags('mailing/mailbox') +@Controller() +@JwtAuthorized() +export class UnseenMailThreadController { + constructor(private mailboxService: MailboxService) {} + + @Put('/mailing/mailboxes/:mailboxId/threads/:messageId/unseen') + public async markUnseenMailThread( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return await this.mailboxService.markUnseenThread(accountId, userId, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/Mailbox/UnspamMailThreadController.ts b/backend/src/Mailing/Controller/Mailbox/UnspamMailThreadController.ts new file mode 100644 index 0000000..74a779d --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/UnspamMailThreadController.ts @@ -0,0 +1,24 @@ +import { Controller, Param, ParseIntPipe, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxService } from '../../Service/Mailbox/MailboxService'; + +@ApiTags('mailing/mailbox') +@Controller() +@JwtAuthorized() +export class UnspamMailThreadController { + constructor(private mailboxService: MailboxService) {} + + @Put('/mailing/mailboxes/:mailboxId/threads/:messageId/unspam') + public async unspamMailThread( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return await this.mailboxService.unspamThread(accountId, userId, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/Mailbox/UntrashMailThreadController.ts b/backend/src/Mailing/Controller/Mailbox/UntrashMailThreadController.ts new file mode 100644 index 0000000..1c72bff --- /dev/null +++ b/backend/src/Mailing/Controller/Mailbox/UntrashMailThreadController.ts @@ -0,0 +1,24 @@ +import { Controller, Param, ParseIntPipe, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxService } from '../../Service/Mailbox/MailboxService'; + +@ApiTags('mailing/mailbox') +@Controller() +@JwtAuthorized() +export class UntrashMailThreadController { + constructor(private mailboxService: MailboxService) {} + + @Put('/mailing/mailboxes/:mailboxId/threads/:messageId/untrash') + async untrashMailThread( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return await this.mailboxService.untrashThread(accountId, userId, mailboxId, messageId); + } +} diff --git a/backend/src/Mailing/Controller/MailboxGmail/GmailAuthCallbackController.ts b/backend/src/Mailing/Controller/MailboxGmail/GmailAuthCallbackController.ts new file mode 100644 index 0000000..760f917 --- /dev/null +++ b/backend/src/Mailing/Controller/MailboxGmail/GmailAuthCallbackController.ts @@ -0,0 +1,18 @@ +import { Controller, Get, Query, Redirect } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { MailboxGmailService } from '../../Service/MailboxGmail/MailboxGmailService'; + +@ApiExcludeController(true) +@Controller() +export class GmailAuthCallbackController { + constructor(private gmailService: MailboxGmailService) {} + + @Redirect() + @Get('/mailing/settings/mailboxes/gmail/callback') + public async callback(@Query('code') code: string, @Query('state') state: string) { + const redirectUrl = await this.gmailService.processAuthCode({ code, state }); + + return { url: redirectUrl, statusCode: 302 }; + } +} diff --git a/backend/src/Mailing/Controller/MailboxGmail/GmailAuthConnectController.ts b/backend/src/Mailing/Controller/MailboxGmail/GmailAuthConnectController.ts new file mode 100644 index 0000000..824e266 --- /dev/null +++ b/backend/src/Mailing/Controller/MailboxGmail/GmailAuthConnectController.ts @@ -0,0 +1,22 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { MailboxGmailService } from '../../Service/MailboxGmail/MailboxGmailService'; + +@ApiTags('mailing/settings/mailbox') +@Controller() +@JwtAuthorized() +export class GmailAuthConnectController { + constructor(private readonly gmailService: MailboxGmailService) {} + + @Get('/mailing/settings/mailboxes/gmail/connect/:mailboxId') + @ApiOkResponse({ description: 'Gmail connection url', type: String }) + async connect( + @CurrentAuth() { accountId }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + ): Promise { + return await this.gmailService.getAuthorizeUrl({ accountId, mailboxId }); + } +} diff --git a/backend/src/Mailing/Controller/MailboxManual/GetMailboxSettingsManualController.ts b/backend/src/Mailing/Controller/MailboxManual/GetMailboxSettingsManualController.ts new file mode 100644 index 0000000..3565a15 --- /dev/null +++ b/backend/src/Mailing/Controller/MailboxManual/GetMailboxSettingsManualController.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxManualService } from '../../Service/MailboxManual/MailboxManualService'; +import { MailboxSettingsManualDto } from '../../Service/MailboxManual/Dto/MailboxSettingsManualDto'; + +@ApiTags('mailing/settings/mailbox') +@Controller() +@JwtAuthorized() +export class GetMailboxSettingsManualController { + constructor(private mailboxManualService: MailboxManualService) {} + + @ApiOkResponse({ description: 'Mailbox manual settings', type: MailboxSettingsManualDto }) + @Get('/mailing/settings/mailboxes/:id/manual') + public async getMailboxSettings( + @CurrentAuth() { accountId }: AuthData, + @Param('id', ParseIntPipe) id: number, + ): Promise { + return await this.mailboxManualService.getManualSettings(accountId, id); + } +} diff --git a/backend/src/Mailing/Controller/MailboxManual/UpdateMailboxSettingsManualController.ts b/backend/src/Mailing/Controller/MailboxManual/UpdateMailboxSettingsManualController.ts new file mode 100644 index 0000000..9100a2f --- /dev/null +++ b/backend/src/Mailing/Controller/MailboxManual/UpdateMailboxSettingsManualController.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MailboxManualService } from '../../Service/MailboxManual/MailboxManualService'; +import { MailboxSettingsManualDto } from '../../Service/MailboxManual/Dto/MailboxSettingsManualDto'; +import { UpdateMailboxSettingsManualDto } from '../../Service/MailboxManual/Dto/UpdateMailboxSettingsManualDto'; + +@ApiTags('mailing/settings/mailbox') +@Controller() +@JwtAuthorized() +export class UpdateMailboxSettingsManualController { + constructor(private mailboxManualService: MailboxManualService) {} + + @Post('mailing/settings/mailboxes/:id/manual') + @ApiCreatedResponse({ description: 'Mailbox', type: MailboxSettingsManualDto }) + public async updateMailboxSettings( + @CurrentAuth() { accountId }: AuthData, + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateMailboxSettingsManualDto, + ) { + return await this.mailboxManualService.saveManualSettings(accountId, id, dto); + } +} diff --git a/backend/src/Mailing/MailingModule.ts b/backend/src/Mailing/MailingModule.ts new file mode 100644 index 0000000..199f8d5 --- /dev/null +++ b/backend/src/Mailing/MailingModule.ts @@ -0,0 +1,170 @@ +/* eslint-disable max-len */ +import { forwardRef, Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { DiscoveryModule } from '@nestjs/core'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { MailerModule } from '@nestjs-modules/mailer'; +import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; +import { join } from 'path'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import mailConfig, { MailConfig } from './config/mail.config'; + +import { MailMessageBuilderService } from './mail-message-builder'; +import { MailMessagePayload, MailMessagePayloadService } from './mail-message-payload'; +import { Mailbox, MailboxAccessibleUser, MailboxEntitySettings } from './mailbox/entities'; +import { + MailboxAccessibleUserService, + MailboxEntitySettingsService, + MailboxHandler, + MailboxLockService, + MailboxService as MailboxSettingsService, +} from './mailbox/services'; +import { MailboxController } from './mailbox/mailbox.controller'; +import { MailboxFolder, MailboxFolderService } from './mailbox-folder'; +import { + MailboxSignature, + MailboxSignatureController, + MailboxSignatureMailbox, + MailboxSignatureService, +} from './mailbox-signature'; +import { MailProviderRegistry } from './mail-provider'; +import { UnsubscribeController } from './subscription'; +import { PublicSystemMailingController, SystemMailingController, SystemMailingService } from './system-mailing'; + +import { MailboxSettingsManual } from './Model/MailboxManual/MailboxSettingsManual'; +import { MailboxSettingsGmail } from './Model/MailboxGmail/MailboxSettingsGmail'; +import { MailMessage } from './Model/MailMessage/MailMessage'; +import { MailMessageFolder } from './Model/MailMessage/MailMessageFolder'; + +import { MailboxService } from './Service/Mailbox/MailboxService'; +import { MailboxManualService } from './Service/MailboxManual/MailboxManualService'; +import { MailboxGmailService } from './Service/MailboxGmail/MailboxGmailService'; +import { MailMessageService } from './Service/MailMessage/MailMessageService'; + +import { GetMailboxSettingsManualController } from './Controller/MailboxManual/GetMailboxSettingsManualController'; +import { UpdateMailboxSettingsManualController } from './Controller/MailboxManual/UpdateMailboxSettingsManualController'; +import { GmailAuthConnectController } from './Controller/MailboxGmail/GmailAuthConnectController'; +import { GmailAuthCallbackController } from './Controller/MailboxGmail/GmailAuthCallbackController'; +import { GetAttachmentController } from './Controller/MailMessage/GetAttachmentController'; +import { GetMailboxesInfoController } from './Controller/Mailbox/GetMailboxesInfoController'; +import { GetSectionMessagesController } from './Controller/MailMessage/GetSectionMessagesController'; +import { GetMailboxMessagesController } from './Controller/MailMessage/GetMailboxMessagesController'; +import { GetMailMessageController } from './Controller/MailMessage/GetMailMessageController'; +import { GetMailThreadController } from './Controller/MailMessage/GetMailThreadController'; +import { SendMailMessageController } from './Controller/Mailbox/SendMailMessageController'; +import { TrashMailThreadController } from './Controller/Mailbox/TrashMailThreadController'; +import { UntrashMailThreadController } from './Controller/Mailbox/UntrashMailThreadController'; +import { SpamMailThreadController } from './Controller/Mailbox/SpamMailThreadController'; +import { UnspamMailThreadController } from './Controller/Mailbox/UnspamMailThreadController'; +import { SeenMailThreadController } from './Controller/Mailbox/SeenMailThreadController'; +import { UnseenMailThreadController } from './Controller/Mailbox/UnseenMailThreadController'; +import { CreateContactAndLeadController } from './Controller/MailMessage/CreateContactAndLeadController'; + +@Module({ + imports: [ + ConfigModule.forFeature(mailConfig), + DiscoveryModule, + MailerModule.forRootAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService) => { + const config = configService.get('mail'); + return { + transport: { + host: config.mailing.host, + port: config.mailing.port, + secure: config.mailing.secure, + auth: { + user: config.mailing.user, + pass: config.mailing.password, + }, + }, + defaults: { + from: config.mailing.from, + replyTo: config.mailing.replyTo, + }, + template: { + dir: join(__dirname, './system-mailing/templates'), + adapter: new HandlebarsAdapter(), + options: { + strict: true, + }, + }, + }; + }, + }), + TypeOrmModule.forFeature([ + Mailbox, + MailboxAccessibleUser, + MailboxEntitySettings, + MailboxSettingsManual, + MailboxSettingsGmail, + MailboxFolder, + MailMessage, + MailMessagePayload, + MailMessageFolder, + MailboxSignature, + MailboxSignatureMailbox, + ]), + IAMModule, + StorageModule, + forwardRef(() => CrmModule), + EntityInfoModule, + ], + providers: [ + MailboxSettingsService, + MailboxHandler, + MailboxLockService, + MailboxAccessibleUserService, + MailboxEntitySettingsService, + SystemMailingService, + MailboxService, + MailboxManualService, + MailboxGmailService, + MailboxFolderService, + MailMessageService, + MailMessagePayloadService, + MailMessageBuilderService, + MailboxSignatureService, + MailProviderRegistry, + ], + controllers: [ + MailboxController, + SystemMailingController, + PublicSystemMailingController, + GetMailboxSettingsManualController, + UpdateMailboxSettingsManualController, + GmailAuthConnectController, + GmailAuthCallbackController, + GetAttachmentController, + GetMailboxesInfoController, + GetSectionMessagesController, + GetMailboxMessagesController, + GetMailMessageController, + GetMailThreadController, + SendMailMessageController, + TrashMailThreadController, + UntrashMailThreadController, + SpamMailThreadController, + UnspamMailThreadController, + SeenMailThreadController, + UnseenMailThreadController, + CreateContactAndLeadController, + MailboxSignatureController, + UnsubscribeController, + ], + exports: [ + SystemMailingService, + MailMessageService, + MailboxService, + MailboxSettingsService, + MailboxFolderService, + MailMessageBuilderService, + ], +}) +export class MailingModule {} diff --git a/backend/src/Mailing/Model/MailMessage/MailMessage.ts b/backend/src/Mailing/Model/MailMessage/MailMessage.ts new file mode 100644 index 0000000..47c3070 --- /dev/null +++ b/backend/src/Mailing/Model/MailMessage/MailMessage.ts @@ -0,0 +1,141 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { MailMessageExternal } from '../../common'; + +@Entity() +export class MailMessage { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + mailboxId: number; + + @Column() + externalId: string; + + @Column() + threadId: string; + + @Column({ nullable: true }) + snippet: string | null; + + @Column({ nullable: true }) + sentFrom: string | null; + + @Column({ nullable: true }) + sentTo: string | null; + + @Column({ nullable: true }) + replyTo: string | null; + + @Column({ nullable: true }) + cc: string | null; + + @Column({ nullable: true }) + subject: string | null; + + @Column() + date: Date; + + @Column() + hasAttachment: boolean; + + @Column({ nullable: true }) + messageId: string | null; + + @Column('simple-array', { nullable: true }) + referencesTo: string[] | null; + + @Column({ nullable: true }) + inReplyTo: string | null; + + @Column({ nullable: true }) + entityId: number | null; + + @Column() + isSeen: boolean; + + @Column() + accountId: number; + + constructor( + accountId: number, + mailboxId: number, + externalId: string, + threadId: string, + snippet: string | null, + sentFrom: string | null, + sentTo: string | null, + replyTo: string | null, + cc: string | null, + subject: string | null, + date: Date, + hasAttachment: boolean, + messageId: string | null, + referencesTo: string[] | null, + inReplyTo: string | null, + entityId: number | null, + isSeen: boolean, + ) { + this.accountId = accountId; + this.mailboxId = mailboxId; + this.externalId = externalId; + this.threadId = threadId; + this.snippet = snippet; + this.sentFrom = sentFrom; + this.sentTo = sentTo; + this.replyTo = replyTo; + this.cc = cc; + this.subject = subject; + this.date = date; + this.hasAttachment = hasAttachment; + this.messageId = messageId; + this.referencesTo = referencesTo; + this.inReplyTo = inReplyTo; + this.entityId = entityId; + this.isSeen = isSeen; + } + + public static create(accountId: number, mailboxId: number, entityId: number | null, message: MailMessageExternal) { + return new MailMessage( + accountId, + mailboxId, + message.id, + message.threadId, + message.snippet, + message.sentFrom?.text ?? null, + message.sentTo?.text ?? null, + message.replyTo?.text ?? null, + message.cc?.text ?? null, + message.subject, + message.date, + message.hasAttachment, + message.messageId, + message.references, + message.inReplyTo, + entityId, + message.isSeen, + ); + } + + public update(message: MailMessageExternal): MailMessage { + this.externalId = message.id; + if (message.threadId) { + this.threadId = message.threadId; + } + this.snippet = message.snippet; + this.sentFrom = message.sentFrom?.text ?? null; + this.sentTo = message.sentTo?.text ?? null; + this.replyTo = message.replyTo?.text ?? null; + this.cc = message.cc?.text ?? null; + this.subject = message.subject; + this.date = message.date; + this.hasAttachment = message.hasAttachment; + this.messageId = message.messageId; + this.referencesTo = message.references; + this.inReplyTo = message.inReplyTo; + this.isSeen = message.isSeen; + this.entityId = message.entityId; + return this; + } +} diff --git a/backend/src/Mailing/Model/MailMessage/MailMessageFolder.ts b/backend/src/Mailing/Model/MailMessage/MailMessageFolder.ts new file mode 100644 index 0000000..731d850 --- /dev/null +++ b/backend/src/Mailing/Model/MailMessage/MailMessageFolder.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class MailMessageFolder { + @PrimaryColumn() + messageId: number; + + @PrimaryColumn() + folderId: number; + + constructor(messageId: number, folderId: number) { + this.messageId = messageId; + this.folderId = folderId; + } +} diff --git a/backend/src/Mailing/Model/MailMessage/MailMessageWithFolders.ts b/backend/src/Mailing/Model/MailMessage/MailMessageWithFolders.ts new file mode 100644 index 0000000..6e68c5d --- /dev/null +++ b/backend/src/Mailing/Model/MailMessage/MailMessageWithFolders.ts @@ -0,0 +1,12 @@ +import { MailboxFolder } from '../../mailbox-folder'; +import { type MailMessage } from './MailMessage'; + +export class MailMessageWithFolders { + message: MailMessage; + folders: MailboxFolder[]; + + constructor(message: MailMessage, folders: MailboxFolder[]) { + this.message = message; + this.folders = folders; + } +} diff --git a/backend/src/Mailing/Model/MailboxGmail/MailboxSettingsGmail.ts b/backend/src/Mailing/Model/MailboxGmail/MailboxSettingsGmail.ts new file mode 100644 index 0000000..28d5346 --- /dev/null +++ b/backend/src/Mailing/Model/MailboxGmail/MailboxSettingsGmail.ts @@ -0,0 +1,24 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { Auth } from 'googleapis'; + +@Entity() +export class MailboxSettingsGmail { + @PrimaryColumn() + mailboxId: number; + + @Column({ type: 'jsonb' }) + tokens: Auth.Credentials; + + @Column() + historyId: string | null; + + @Column() + accountId: number; + + constructor(mailboxId: number, accountId: number, tokens: Auth.Credentials, historyId: string | null = null) { + this.mailboxId = mailboxId; + this.accountId = accountId; + this.tokens = tokens; + this.historyId = historyId; + } +} diff --git a/backend/src/Mailing/Model/MailboxManual/MailboxSettingsManual.ts b/backend/src/Mailing/Model/MailboxManual/MailboxSettingsManual.ts new file mode 100644 index 0000000..ef05428 --- /dev/null +++ b/backend/src/Mailing/Model/MailboxManual/MailboxSettingsManual.ts @@ -0,0 +1,93 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ImapSyncInfo } from '../../common'; +import { UpdateMailboxSettingsManualDto } from '../../Service/MailboxManual/Dto/UpdateMailboxSettingsManualDto'; + +@Entity() +export class MailboxSettingsManual { + @PrimaryColumn() + mailboxId: number; + + @Column() + password: string; + + @Column() + imapServer: string; + + @Column() + imapPort: number; + + @Column() + imapSecure: boolean; + + @Column() + smtpServer: string; + + @Column() + smtpPort: number; + + @Column() + smtpSecure: boolean; + + @Column({ type: 'jsonb', nullable: true }) + imapSync: ImapSyncInfo[] | null; + + @Column() + accountId: number; + + constructor( + mailboxId: number, + accountId: number, + password: string, + imapServer: string, + imapPort: number, + imapSecure: boolean, + smtpServer: string, + smtpPort: number, + smtpSecure: boolean, + imapSync: ImapSyncInfo[] | null = null, + ) { + this.mailboxId = mailboxId; + this.accountId = accountId; + this.password = password; + this.imapServer = imapServer; + this.imapPort = imapPort; + this.imapSecure = imapSecure; + this.smtpServer = smtpServer; + this.smtpPort = smtpPort; + this.smtpSecure = smtpSecure; + this.imapSync = imapSync; + } + + public static create(mailboxId: number, accountId: number, dto: UpdateMailboxSettingsManualDto) { + return new MailboxSettingsManual( + mailboxId, + accountId, + dto.password, + dto.imapServer, + dto.imapPort, + dto.imapSecure, + dto.smtpServer, + dto.smtpPort, + dto.smtpSecure, + ); + } + + public update(dto: UpdateMailboxSettingsManualDto): MailboxSettingsManual { + if (dto.password) { + this.password = dto.password; + } + this.imapServer = dto.imapServer; + this.imapPort = dto.imapPort; + this.imapSecure = dto.imapSecure; + this.smtpServer = dto.smtpServer; + this.smtpPort = dto.smtpPort; + this.smtpSecure = dto.smtpSecure; + return this; + } + + public updateImapSync(imapSync: ImapSyncInfo[] | null): MailboxSettingsManual { + this.imapSync = imapSync; + return this; + } +} diff --git a/backend/src/Mailing/Service/MailMessage/Dto/CreateContactLeadDto.ts b/backend/src/Mailing/Service/MailMessage/Dto/CreateContactLeadDto.ts new file mode 100644 index 0000000..a691cd1 --- /dev/null +++ b/backend/src/Mailing/Service/MailMessage/Dto/CreateContactLeadDto.ts @@ -0,0 +1,44 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class CreateContactLeadDto { + @ApiPropertyOptional({ description: 'Contact entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + contactTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + leadTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead board ID', nullable: true }) + @IsOptional() + @IsNumber() + leadBoardId?: number | null; + + @ApiPropertyOptional({ description: 'Lead stage ID', nullable: true }) + @IsOptional() + @IsNumber() + leadStageId?: number | null; + + @ApiPropertyOptional({ description: 'Lead name', nullable: true }) + @IsOptional() + @IsString() + leadName?: string | null; + + @ApiPropertyOptional({ description: 'Lead and Contact responsible user ID', nullable: true }) + @IsOptional() + @IsNumber() + ownerId?: number | null; + + @ApiPropertyOptional({ description: 'Do not create lead if active lead exists', nullable: true }) + @IsOptional() + @IsBoolean() + checkActiveLead?: boolean; + + @ApiPropertyOptional({ description: 'Do not create duplicate contact', nullable: true }) + @IsOptional() + @IsBoolean() + checkDuplicate?: boolean; +} diff --git a/backend/src/Mailing/Service/MailMessage/Dto/MailMessageDto.ts b/backend/src/Mailing/Service/MailMessage/Dto/MailMessageDto.ts new file mode 100644 index 0000000..da1cb39 --- /dev/null +++ b/backend/src/Mailing/Service/MailMessage/Dto/MailMessageDto.ts @@ -0,0 +1,105 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { MailMessagePayloadDto } from '../../../mail-message-payload'; +import { MailMessage } from '../../../Model/MailMessage/MailMessage'; + +export class MailMessageDto { + @ApiProperty() + id: number; + + @ApiProperty() + mailboxId: number; + + @ApiProperty() + threadId: string; + + @ApiProperty() + snippet: string | null; + + @ApiProperty() + sentFrom: string | null; + + @ApiProperty() + sentTo: string | null; + + @ApiProperty() + replyTo: string | null; + + @ApiProperty() + cc: string | null; + + @ApiProperty() + subject: string | null; + + @ApiProperty() + date: string; + + @ApiProperty() + hasAttachment: boolean; + + @ApiProperty() + isSeen: boolean; + + @ApiProperty() + payloads: MailMessagePayloadDto[]; + + @ApiProperty({ nullable: true }) + entityInfo: EntityInfoDto | null; + + constructor( + id: number, + mailboxId: number, + threadId: string, + snippet: string | null, + sentFrom: string, + sentTo: string | null, + replyTo: string | null, + cc: string | null, + subject: string | null, + date: string, + hasAttachment: boolean, + isSeen: boolean, + payloads: MailMessagePayloadDto[], + entityInfo: EntityInfoDto | null, + ) { + this.id = id; + this.mailboxId = mailboxId; + this.threadId = threadId; + this.snippet = snippet; + this.sentFrom = sentFrom; + this.sentTo = sentTo; + this.replyTo = replyTo; + this.cc = cc; + this.subject = subject; + this.date = date; + this.hasAttachment = hasAttachment; + this.isSeen = isSeen; + this.payloads = payloads; + this.entityInfo = entityInfo; + } + + static create( + message: MailMessage, + payloads: MailMessagePayloadDto[], + entityInfo: EntityInfoDto | null, + ): MailMessageDto { + return new MailMessageDto( + message.id, + message.mailboxId, + message.threadId, + message.snippet, + message.sentFrom, + message.sentTo, + message.replyTo, + message.cc, + message.subject, + message.date.toISOString(), + message.hasAttachment, + message.isSeen, + payloads, + entityInfo, + ); + } +} diff --git a/backend/src/Mailing/Service/MailMessage/MailMessageInfo.ts b/backend/src/Mailing/Service/MailMessage/MailMessageInfo.ts new file mode 100644 index 0000000..9eb1e8d --- /dev/null +++ b/backend/src/Mailing/Service/MailMessage/MailMessageInfo.ts @@ -0,0 +1,88 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { MailMessage } from '../../Model/MailMessage/MailMessage'; + +export class MailMessageInfo { + @ApiProperty() + id: number; + + @ApiProperty() + mailboxId: number; + + @ApiProperty() + threadId: string | null; + + @ApiProperty() + snippet: string | null; + + @ApiProperty() + sentFrom: string | null; + + @ApiProperty() + sentTo: string | null; + + @ApiProperty() + subject: string | null; + + @ApiProperty() + date: string; + + @ApiProperty() + hasAttachment: boolean; + + @ApiProperty() + isSeen: boolean; + + @ApiProperty() + folders: string[]; + + @ApiProperty({ nullable: true, type: EntityInfoDto }) + entityInfo: EntityInfoDto | null; + + constructor( + id: number, + mailboxId: number, + threadId: string | null, + snippet: string | null, + sentFrom: string | null, + sentTo: string | null, + subject: string | null, + date: string, + hasAttachment: boolean, + isSeen: boolean, + folders: string[], + entityInfo: EntityInfoDto | null, + ) { + this.id = id; + this.mailboxId = mailboxId; + this.threadId = threadId; + this.snippet = snippet; + this.sentFrom = sentFrom; + this.sentTo = sentTo; + this.subject = subject; + this.date = date; + this.hasAttachment = hasAttachment; + this.isSeen = isSeen; + this.folders = folders; + this.entityInfo = entityInfo; + } + + public static create(message: MailMessage, folders: string[], entityInfo: EntityInfoDto | null): MailMessageInfo { + return new MailMessageInfo( + message.id, + message.mailboxId, + message.threadId, + message.snippet, + message.sentFrom, + message.sentTo, + message.subject, + message.date.toISOString(), + message.hasAttachment, + message.isSeen, + folders, + entityInfo, + ); + } +} diff --git a/backend/src/Mailing/Service/MailMessage/MailMessageService.ts b/backend/src/Mailing/Service/MailMessage/MailMessageService.ts new file mode 100644 index 0000000..967d528 --- /dev/null +++ b/backend/src/Mailing/Service/MailMessage/MailMessageService.ts @@ -0,0 +1,675 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Brackets, In, Repository, WhereExpressionBuilder } from 'typeorm'; +import { v4 as uuidv4 } from 'uuid'; +import addressparser from 'nodemailer/lib/addressparser'; + +import { PagingMeta, NotFoundError } from '@/common'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { Entity } from '@/CRM/Model/Entity/Entity'; + +import { + EmailAddressValue, + FolderMessages, + MailboxFolderType, + MailboxState, + MailEventType, + MailMessageAttachment, + MailMessageEvent, + MailMessageExternal, + MailMessageReceivedEvent, +} from '../../common'; +import { Mailbox } from '../../mailbox/entities'; +import { MailboxService } from '../../mailbox/services'; +import { MailboxFolder, MailboxFolderService } from '../../mailbox-folder'; +import { MailMessagePayloadService } from '../../mail-message-payload'; + +import { MailMessage } from '../../Model/MailMessage/MailMessage'; +import { MailMessageFolder } from '../../Model/MailMessage/MailMessageFolder'; +import { MailMessageWithFolders } from '../../Model/MailMessage/MailMessageWithFolders'; + +import { MailThreadInfo } from './MailThreadInfo'; +import { MailMessageInfo } from './MailMessageInfo'; +import { MailThreadResult } from './MailThreadResult'; + +import { MailMessageDto } from './Dto/MailMessageDto'; +import { CreateContactLeadDto } from './Dto/CreateContactLeadDto'; + +import { GetSectionMessagesFilter } from '../../Controller/MailMessage/GetSectionMessagesFilter'; +import { GetMailboxMessagesFilter } from '../../Controller/MailMessage/GetMailboxMessagesFilter'; + +@Injectable() +export class MailMessageService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(MailMessage) + private readonly repository: Repository, + @InjectRepository(MailMessageFolder) + private readonly repositoryFolderLink: Repository, + @Inject(forwardRef(() => MailboxService)) + private readonly mailboxService: MailboxService, + private readonly mailboxFolderService: MailboxFolderService, + private readonly mailMessagePayloadService: MailMessagePayloadService, + private readonly userService: UserService, + private readonly entityService: EntityService, + private readonly entityInfoService: EntityInfoService, + ) {} + + async getById(accountId: number, mailboxId: number, messageId: number): Promise { + const message = await this.findById(accountId, mailboxId, messageId); + if (!message) { + throw NotFoundError.withId(MailMessage, messageId); + } + return message; + } + + async findById(accountId: number, mailboxId: number, messageId: number): Promise { + return this.repository.findOneBy({ accountId, mailboxId, id: messageId }); + } + + async getByThreadIdGroupByFolder(accountId: number, mailboxId: number, threadId: string): Promise { + const links = await this.repository + .createQueryBuilder('mm') + .select('mm.external_id', 'messageId') + .addSelect('mf.external_id', 'folderId') + .leftJoin('mail_message_folder', 'mmf', 'mmf.message_id = mm.id') + .leftJoin('mailbox_folder', 'mf', 'mf.id = mmf.folder_id') + .where('mm.account_id = :accountId', { accountId }) + .andWhere('mm.mailbox_id = :mailboxId', { mailboxId }) + .andWhere('mm.thread_id = :threadId', { threadId }) + .getRawMany<{ messageId: string; folderId: string }>(); + + const groupByFolder: FolderMessages[] = links.reduce((groups, link) => { + let group = groups.find((g) => g.folderId === link.folderId); + if (!group) { + group = { folderId: link.folderId, messageIds: [] }; + groups.push(group); + } + group.messageIds.push(link.messageId); + return groups; + }, [] as FolderMessages[]); + + return groupByFolder; + } + + async getMessagesForMailbox( + accountId: number, + user: User, + mailboxId: number, + filter: GetMailboxMessagesFilter, + ): Promise { + const { folderId, search, skip, take } = filter; + const mailbox = await this.mailboxService.findOne({ accountId, mailboxId, accessibleUserId: user.id }); + + if (!mailbox) { + return new MailThreadResult([], PagingMeta.empty()); + } + + const threadsQuery = this.repository + .createQueryBuilder('message') + .select('message.thread_id', 'threadId') + .where('message.account_id = :accountId', { accountId }) + .andWhere('message.mailbox_id = :mailboxId', { mailboxId: mailbox.id }); + if (folderId) { + threadsQuery + .leftJoin(MailMessageFolder, 'mmf', 'mmf.message_id = message.id') + .andWhere('mmf.folder_id = :folderId', { folderId }); + } + if (search) { + threadsQuery.andWhere(new Brackets((qb) => this.createSearchQuery(qb, search))); + } + + const { count } = await threadsQuery.clone().select('COUNT(DISTINCT(message.thread_id))').getRawOne(); + const threads = await threadsQuery + .groupBy('message.thread_id') + .orderBy('MAX(message.date)', 'DESC') + .offset(skip) + .limit(take) + .getRawMany(); + + const threadInfos = await this.getMessagesForThreads( + accountId, + user, + [mailbox.id], + threads.map((thread) => thread.threadId), + ); + + return new MailThreadResult(threadInfos, new PagingMeta(skip + threads.length, Number(count))); + } + + async getMessagesForSection( + accountId: number, + user: User, + type: MailboxFolderType, + filter: GetSectionMessagesFilter, + ): Promise { + const { mailboxId, search, skip, take } = filter; + + const mailboxes = await this.mailboxService.findMany({ accountId, mailboxId, accessibleUserId: user.id }); + + const mailboxesIds = mailboxes + .filter((mailbox) => mailbox.state !== MailboxState.Deleted) + .map((mailbox) => mailbox.id); + + if (mailboxesIds.length === 0) { + return new MailThreadResult([], PagingMeta.empty()); + } + + const threadsQuery = this.repository + .createQueryBuilder('message') + .select('message.thread_id', 'threadId') + .leftJoin(MailMessageFolder, 'mmf', 'mmf.message_id = message.id') + .leftJoin(MailboxFolder, 'folder', 'folder.id = mmf.folder_id') + .where('message.account_id = :accountId', { accountId }) + .andWhere('message.mailbox_id IN (:...mailboxIds)', { mailboxIds: mailboxesIds }) + .andWhere('folder.type = :type', { type }); + if (search) { + threadsQuery.andWhere(new Brackets((qb) => this.createSearchQuery(qb, search))); + } + + const { count } = await threadsQuery.clone().select('COUNT(DISTINCT(message.thread_id))').getRawOne(); + const threads = await threadsQuery + .groupBy('message.thread_id') + .orderBy('MAX(message.date)', 'DESC') + .offset(skip) + .limit(take) + .getRawMany(); + + const threadInfos = await this.getMessagesForThreads( + accountId, + user, + mailboxesIds, + threads.map((thread) => thread.threadId), + ); + + return new MailThreadResult(threadInfos, new PagingMeta(skip + threads.length, Number(count))); + } + + private createSearchQuery(qb: WhereExpressionBuilder, search: string): void { + qb.where(`message.sent_from ilike '%${search}%'`) + .orWhere(`message.sent_to ilike '%${search}%'`) + .orWhere(`message.cc ilike '%${search}%'`) + .orWhere(`message.subject ilike '%${search}%'`) + .orWhere(`message.snippet ilike '%${search}%'`); + } + + async getThreadForMessageId(accountId: number, user: User, messageId: number): Promise { + const message = await this.repository.findOneBy({ accountId, id: messageId }); + if (message) { + const threads = await this.getMessagesForThreads(accountId, user, [message.mailboxId], [message.threadId]); + + return threads?.length ? threads[0] : null; + } + + return null; + } + + private async getMessagesForThreads( + accountId: number, + user: User, + mailboxIds: number[], + threadIds: string[], + ): Promise { + const messages = await this.repository.find({ + where: { accountId, mailboxId: In(mailboxIds), threadId: In(threadIds) }, + order: { date: 'DESC' }, + }); + + const messagesWithFolders: MailMessageWithFolders[] = []; + for (const msg of messages) { + const folders = await this.mailboxFolderService.findMany({ accountId, messageId: msg.id }); + messagesWithFolders.push(new MailMessageWithFolders(msg, folders)); + } + + const entityInfoCache: EntityInfoDto[] = []; + const threads: MailThreadInfo[] = []; + for (const threadId of threadIds) { + const messages: MailMessageInfo[] = []; + const threadMessages = messagesWithFolders.filter((mwf) => mwf.message.threadId === threadId); + for (const tm of threadMessages) { + const entityInfo = tm.message.entityId + ? await this.getEntityInfoCached(accountId, user, tm.message.entityId, entityInfoCache) + : null; + messages.push( + MailMessageInfo.create( + tm.message, + tm.folders.map((f) => f.name), + entityInfo, + ), + ); + } + threads.push(new MailThreadInfo(threadId, messages)); + } + return threads; + } + + private async getEntityInfoCached(accountId: number, user: User, entityId: number, entityInfoCache: EntityInfoDto[]) { + let entityInfo: EntityInfoDto | null = entityInfoCache.find((e) => e.id === entityId); + if (!entityInfo) { + entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId }); + if (entityInfo) { + entityInfoCache.push(entityInfo); + } + } + return entityInfo ?? null; + } + + async getThreadWithPayload( + accountId: number, + user: User, + mailboxId: number, + messageId: number, + ): Promise { + const mailbox = await this.mailboxService.findOne({ accountId, mailboxId, accessibleUserId: user.id }); + const { threadId } = await this.getById(accountId, mailboxId, messageId); + const messages = await this.repository.find({ + where: { accountId, mailboxId: mailbox.id, threadId }, + order: { date: 'DESC' }, + }); + + const payloads = await this.mailMessagePayloadService.findByMessageIds( + accountId, + messages.map((message) => message.id), + ); + + const entityInfoCache: EntityInfoDto[] = []; + const messageDtos: MailMessageDto[] = []; + for (const message of messages) { + const payload = payloads.filter((p) => p.messageId === message.id).map((p) => p.toDto()); + const entityInfo = message.entityId + ? await this.getEntityInfoCached(accountId, user, message.entityId, entityInfoCache) + : null; + messageDtos.push(MailMessageDto.create(message, payload, entityInfo)); + } + return messageDtos; + } + + async getMessageWithPayload( + accountId: number, + user: User, + mailboxId: number, + messageId: number, + ): Promise { + const mailbox = await this.mailboxService.findOne({ accountId, mailboxId, accessibleUserId: user.id }); + if (mailbox) { + const message = await this.repository.findOneBy({ accountId, mailboxId: mailbox.id, id: messageId }); + if (message) { + const payloads = await this.mailMessagePayloadService.findByMessageId(accountId, message.id); + + const entityInfo = message.entityId + ? await this.entityInfoService.findOne({ accountId, user, entityId: message.entityId }) + : null; + + return MailMessageDto.create( + message, + payloads.map((p) => p.toDto()), + entityInfo, + ); + } + } + + throw NotFoundError.withId(MailMessage, messageId); + } + + async getMessageAttachment( + accountId: number, + userId: number, + mailboxId: number, + messageId: number, + payloadId: number, + ): Promise { + const mailbox = await this.mailboxService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + const message = await this.repository.findOneBy({ accountId, mailboxId, id: messageId }); + return this.mailMessagePayloadService.getAttachment(accountId, mailbox, message, payloadId); + } + + async moveThreadToSpecialFolder( + accountId: number, + mailboxId: number, + threadId: string, + folderType: MailboxFolderType, + ) { + const messages = await this.repository.findBy({ accountId, mailboxId, threadId }); + if (messages) { + await this.moveMessagesToSpecialFolder( + accountId, + mailboxId, + messages.map((m) => m.id), + folderType, + ); + } + } + + async moveMessagesToSpecialFolder( + accountId: number, + mailboxId: number, + messageIds: number[], + type: MailboxFolderType, + ) { + const folder = await this.mailboxFolderService.findOne({ accountId, mailboxId, type }); + if (folder) { + await this.repositoryFolderLink.delete({ messageId: In(messageIds) }); + await this.repositoryFolderLink.insert(messageIds.map((m) => new MailMessageFolder(m, folder.id))); + } + } + + async markSeenThread(accountId: number, mailboxId: number, threadId: string, isSeen: boolean) { + await this.repository.update({ accountId, mailboxId, threadId }, { isSeen }); + } + + async createContact( + accountId: number, + user: User, + mailboxId: number, + messageId: number, + dto: CreateContactLeadDto, + ): Promise { + const message = await this.repository.findOneBy({ accountId, mailboxId, id: messageId }); + if (!message || !message.sentFrom) { + return null; + } + + const email = addressparser(message.sentFrom, { flatten: true })[0]; + const entities = await this.createEntities({ + accountId, + ownerId: user.id, + email, + subject: message.subject, + settings: dto, + }); + + if (entities?.length) { + message.entityId = entities[0].id; + await this.repository.save(message); + + this.eventEmitter.emit( + MailEventType.MailMessageLinked, + new MailMessageEvent({ + accountId: message.accountId, + entityId: message.entityId, + messageId: message.id, + messageDate: message.date.toISOString(), + }), + ); + } + + const entity = entities?.length > 1 ? entities[1] : entities?.[0]; + + return entity ? this.entityInfoService.getEntityInfo({ user, entity, access: true }) : null; + } + + async processExternalMessages({ + accountId, + mailbox, + added, + updated, + deleted, + }: { + accountId: number; + mailbox: Mailbox; + added?: MailMessageExternal[]; + updated?: MailMessageExternal[]; + deleted?: string[]; + }) { + if (added?.length) { + await this.upsertMessage({ accountId, mailbox, messages: added }); + } + if (updated?.length) { + await this.upsertMessage({ accountId, mailbox, messages: updated }); + } + if (deleted?.length) { + await this.deleteMessages(deleted); + } + + if (added?.length || updated?.length || deleted?.length) { + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + } + } + + private async upsertMessage({ + accountId, + mailbox, + messages, + }: { + accountId: number; + mailbox: Mailbox; + messages: MailMessageExternal[]; + }) { + for (const message of messages) { + //TODO: check folder because messageId could be in many folders like Sent and Inbox then it message to yourself + const current = await this.repository + .createQueryBuilder('message') + .where('message.account_id = :accountId', { accountId: mailbox.accountId }) + .andWhere('message.mailbox_id = :mailboxId', { mailboxId: mailbox.id }) + .andWhere( + new Brackets((qb) => + qb + .where('message.external_id = :externalId', { externalId: message.id }) + .orWhere('message.message_id = :messageId', { messageId: message.messageId }), + ), + ) + .getOne(); + if (current) { + await this.updateMessage({ accountId, mailbox, current, message }); + } else { + await this.addMessage({ accountId, mailbox, extMessage: message }); + } + } + } + + private async addMessage({ + accountId, + mailbox, + extMessage, + }: { + accountId: number; + mailbox: Mailbox; + extMessage: MailMessageExternal; + }) { + if (!extMessage.id) { + extMessage.id = uuidv4(); + } + const prevMessage = await this.findPreviousMessage(accountId, mailbox.id, extMessage); + if (!extMessage.threadId) { + extMessage.threadId = prevMessage ? prevMessage.threadId : uuidv4(); + } + const folders = await this.mailboxFolderService.findMany({ + accountId, + mailboxId: mailbox.id, + externalId: extMessage.folders, + }); + + const isInbox = folders.some((f) => f.type === MailboxFolderType.Inbox); + const entityId = await this.findEntityId({ accountId, message: extMessage, mailbox, prevMessage, isInbox }); + + const message = await this.repository.save(MailMessage.create(accountId, mailbox.id, entityId, extMessage)); + if (folders) { + await this.repositoryFolderLink.insert(folders.map((f) => new MailMessageFolder(message.id, f.id))); + } + if (extMessage.payloads && extMessage.payloads.length > 0) { + await this.mailMessagePayloadService.processExternalPayloads(accountId, message.id, extMessage.payloads); + } + + this.eventEmitter.emit( + MailEventType.MailMessageReceived, + new MailMessageReceivedEvent({ + accountId: accountId, + ownerId: mailbox.ownerId, + entityId: entityId, + messageId: message.id, + messageSubject: message.subject, + messageSnippet: message.snippet, + messageDate: message.date.toISOString(), + isInbox, + }), + ); + } + + private async updateMessage({ + accountId, + mailbox, + current, + message, + }: { + accountId: number; + mailbox: Mailbox; + current: MailMessage; + message: MailMessageExternal; + }) { + message.entityId = message.entityId + ? await this.entityService.ensureExistId(accountId, message.entityId) + : current.entityId; + await this.repository.update(current.id, current.update(message)); + await this.repositoryFolderLink.delete({ messageId: current.id }); + const folders = await this.mailboxFolderService.findMany({ + accountId, + mailboxId: mailbox.id, + externalId: message.folders, + }); + if (folders) { + await this.repositoryFolderLink.insert(folders.map((f) => new MailMessageFolder(current.id, f.id))); + } + } + + private async deleteMessages(messagesDeleted: string[]) { + const messages = await this.repository.findBy({ externalId: In(messagesDeleted) }); + const result = await this.repository.delete({ externalId: In(messagesDeleted) }); + if (!result.affected && !messages) { + return; + } + for (const message of messages) { + this.eventEmitter.emit( + MailEventType.MailMessageDeleted, + new MailMessageEvent({ + accountId: message.accountId, + entityId: message.entityId, + messageId: message.id, + messageDate: message.date.toISOString(), + }), + ); + } + } + + private async findPreviousMessage( + accountId: number, + mailboxId: number, + message: MailMessageExternal, + ): Promise { + if (message.inReplyTo) { + const prevMessage = await this.repository.findOneBy({ accountId, mailboxId, messageId: message.inReplyTo }); + if (prevMessage) { + return prevMessage; + } + } + if (message.references) { + const refMessages = await this.repository.find({ + where: { accountId, mailboxId, messageId: In(message.references) }, + order: { date: 'desc' }, + take: 1, + }); + if (refMessages && refMessages.length > 0) { + return refMessages[0]; + } + } + return null; + } + + private async findEntityId({ + accountId, + message, + mailbox, + prevMessage, + isInbox, + }: { + accountId: number; + message: MailMessageExternal; + mailbox: Mailbox; + prevMessage: MailMessage | null; + isInbox: boolean; + }): Promise { + if (message.entityId) { + return this.entityService.ensureExistId(accountId, message.entityId); + } + if (prevMessage?.entityId) { + return this.entityService.ensureExistId(accountId, prevMessage.entityId); + } + if (isInbox && mailbox.entitySettings) { + const entities = await this.createEntities({ + accountId, + ownerId: mailbox.ownerId, + email: message.sentFrom.values[0], + subject: message.subject, + settings: { + contactTypeId: mailbox.entitySettings.contactEntityTypeId, + leadTypeId: mailbox.entitySettings.leadEntityTypeId, + leadBoardId: mailbox.entitySettings.leadBoardId, + leadStageId: mailbox.entitySettings.leadStageId, + leadName: mailbox.entitySettings.leadName, + ownerId: mailbox.entitySettings.ownerId, + checkActiveLead: mailbox.entitySettings.checkActiveLead, + checkDuplicate: mailbox.entitySettings.checkDuplicate, + }, + }); + return entities.length ? entities[0].id : null; + } + return null; + } + + private async createEntities({ + accountId, + ownerId, + email, + subject, + settings, + }: { + accountId: number; + ownerId: number; + email: EmailAddressValue; + subject: string; + settings: CreateContactLeadDto; + }): Promise { + if (!settings.leadTypeId && !settings.contactTypeId) { + return null; + } + + const fieldValues = email?.address ? [{ fieldType: FieldType.Email, value: email.address }] : []; + const lead = settings.leadTypeId + ? { + ownerId: settings.ownerId, + entityTypeId: settings.leadTypeId, + boardId: settings.leadBoardId, + stageId: settings.leadStageId, + name: settings.leadName || subject || email?.name || email?.address, + fieldValues, + } + : null; + const contact = settings.contactTypeId + ? { + ownerId: settings.ownerId, + entityTypeId: settings.contactTypeId, + name: email?.name, + fieldValues, + linkedEntities: lead ? [lead] : undefined, + } + : null; + + if (contact || lead) { + const user = await this.userService.findOne({ accountId, id: settings.ownerId ?? ownerId }); + return this.entityService.createSimple({ + accountId, + user, + dto: contact ?? lead, + options: { checkActiveLead: settings.checkActiveLead, checkDuplicate: settings.checkDuplicate }, + }); + } + + return []; + } +} diff --git a/backend/src/Mailing/Service/MailMessage/MailThreadInfo.ts b/backend/src/Mailing/Service/MailMessage/MailThreadInfo.ts new file mode 100644 index 0000000..6e48a64 --- /dev/null +++ b/backend/src/Mailing/Service/MailMessage/MailThreadInfo.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { MailMessageInfo } from './MailMessageInfo'; + +export class MailThreadInfo { + @ApiProperty() + id: string; + + @ApiProperty() + messages: MailMessageInfo[]; + + constructor(id: string, messages: MailMessageInfo[]) { + this.id = id; + this.messages = messages; + } +} diff --git a/backend/src/Mailing/Service/MailMessage/MailThreadResult.ts b/backend/src/Mailing/Service/MailMessage/MailThreadResult.ts new file mode 100644 index 0000000..ae855c8 --- /dev/null +++ b/backend/src/Mailing/Service/MailMessage/MailThreadResult.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { MailThreadInfo } from './MailThreadInfo'; + +export class MailThreadResult { + @ApiProperty() + threads: MailThreadInfo[]; + + @ApiProperty() + meta: PagingMeta; + + constructor(threads: MailThreadInfo[], meta: PagingMeta) { + this.threads = threads; + this.meta = meta; + } +} diff --git a/backend/src/Mailing/Service/Mailbox/Dto/mailbox-full-info.dto.ts b/backend/src/Mailing/Service/Mailbox/Dto/mailbox-full-info.dto.ts new file mode 100644 index 0000000..8a3b386 --- /dev/null +++ b/backend/src/Mailing/Service/Mailbox/Dto/mailbox-full-info.dto.ts @@ -0,0 +1,59 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsString } from 'class-validator'; + +import { MailboxState } from '../../../common'; +import { Mailbox } from '../../../mailbox/entities'; +import { MailboxFolderDto } from '../../../mailbox-folder'; + +export class MailboxFullInfoDto { + @ApiProperty({ description: 'Mailbox ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Mailbox name (email' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Owner ID' }) + @IsNumber() + ownerId: number; + + @ApiProperty({ description: 'Unread count' }) + @IsNumber() + unread: number; + + @ApiProperty({ description: 'Total count' }) + @IsNumber() + total: number; + + @ApiProperty({ enum: MailboxState, description: 'Mailbox state' }) + @IsEnum(MailboxState) + state: MailboxState; + + @ApiProperty({ type: [MailboxFolderDto], description: 'Mailbox folders' }) + folders: MailboxFolderDto[]; + + constructor( + id: number, + name: string, + ownerId: number, + unread: number, + total: number, + state: MailboxState, + folders: MailboxFolderDto[], + ) { + this.id = id; + this.name = name; + this.ownerId = ownerId; + this.unread = unread; + this.total = total; + this.state = state; + this.folders = folders; + } + + static create(mailbox: Mailbox, folders: MailboxFolderDto[]): MailboxFullInfoDto { + const unread = folders.reduce((acc, cur) => acc + cur.unread, 0); + const total = folders.reduce((acc, cur) => acc + cur.total, 0); + return new MailboxFullInfoDto(mailbox.id, mailbox.email, mailbox.ownerId, unread, total, mailbox.state, folders); + } +} diff --git a/backend/src/Mailing/Service/Mailbox/Dto/mailbox-section.dto.ts b/backend/src/Mailing/Service/Mailbox/Dto/mailbox-section.dto.ts new file mode 100644 index 0000000..a57f95c --- /dev/null +++ b/backend/src/Mailing/Service/Mailbox/Dto/mailbox-section.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { MailboxFolderType } from '../../../common'; +import { MailboxShortInfoDto } from './mailbox-short-info.dto'; + +export class MailboxSectionDto { + @ApiProperty({ enum: MailboxFolderType, description: 'Folder type' }) + @IsEnum(MailboxFolderType) + type: MailboxFolderType; + + @ApiProperty({ description: 'Unread count' }) + @IsNumber() + unread: number; + + @ApiProperty({ description: 'Total count' }) + @IsNumber() + total: number; + + @ApiProperty({ type: [MailboxShortInfoDto], description: 'Mailboxes' }) + mailboxes: MailboxShortInfoDto[]; + + constructor(type: MailboxFolderType) { + this.type = type; + this.unread = 0; + this.total = 0; + this.mailboxes = []; + } + + addMailboxInfo(mailbox: MailboxShortInfoDto) { + this.unread += mailbox.unread; + this.total += mailbox.total; + this.mailboxes.push(mailbox); + } +} diff --git a/backend/src/Mailing/Service/Mailbox/Dto/mailbox-short-info.dto.ts b/backend/src/Mailing/Service/Mailbox/Dto/mailbox-short-info.dto.ts new file mode 100644 index 0000000..8e80674 --- /dev/null +++ b/backend/src/Mailing/Service/Mailbox/Dto/mailbox-short-info.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsString } from 'class-validator'; + +import { MailboxState } from '../../../common'; + +export class MailboxShortInfoDto { + @ApiProperty({ description: 'Mailbox ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Mailbox name (email' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Unread count' }) + @IsNumber() + unread: number; + + @ApiProperty({ description: 'Total count' }) + @IsNumber() + total: number; + + @ApiProperty({ enum: MailboxState, description: 'Mailbox state' }) + @IsEnum(MailboxState) + state: MailboxState; +} diff --git a/backend/src/Mailing/Service/Mailbox/Dto/mailboxes-info.dto.ts b/backend/src/Mailing/Service/Mailbox/Dto/mailboxes-info.dto.ts new file mode 100644 index 0000000..1afdcea --- /dev/null +++ b/backend/src/Mailing/Service/Mailbox/Dto/mailboxes-info.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { MailboxSectionDto } from './mailbox-section.dto'; +import { MailboxFullInfoDto } from './mailbox-full-info.dto'; + +export class MailboxesInfoDto { + @ApiProperty({ type: [MailboxSectionDto], description: 'Mailboxes by sections' }) + sections: MailboxSectionDto[]; + + @ApiProperty({ type: [MailboxFullInfoDto], description: 'Mailboxes' }) + mailboxes: MailboxFullInfoDto[]; +} diff --git a/backend/src/Mailing/Service/Mailbox/MailboxService.ts b/backend/src/Mailing/Service/Mailbox/MailboxService.ts new file mode 100644 index 0000000..a0a7a93 --- /dev/null +++ b/backend/src/Mailing/Service/Mailbox/MailboxService.ts @@ -0,0 +1,281 @@ +import { Injectable } from '@nestjs/common'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { MailboxFolderType, MailboxState, SendMailMessageDto } from '../../common'; +import { Mailbox } from '../../mailbox/entities'; +import { MailboxService as MailboxSettingsService } from '../../mailbox/services'; +import { MailboxFolderService } from '../../mailbox-folder'; +import { MailProviderRegistry } from '../../mail-provider'; + +import { MailMessageService } from '../MailMessage/MailMessageService'; + +import { MailboxesInfoDto } from './Dto/mailboxes-info.dto'; +import { MailboxSectionDto } from './Dto/mailbox-section.dto'; +import { MailboxFullInfoDto } from './Dto/mailbox-full-info.dto'; + +@Injectable() +export class MailboxService { + constructor( + private readonly storageService: StorageService, + private readonly mailboxSettingsService: MailboxSettingsService, + private readonly mailboxFolderService: MailboxFolderService, + private readonly mailMessageService: MailMessageService, + private readonly mailProviderRegistry: MailProviderRegistry, + private readonly userService: UserService, + ) {} + + async getMailboxesForInfo(accountId: number, userId: number): Promise { + const accessibleMailboxes = await this.mailboxSettingsService.findMany({ + accountId, + accessibleUserId: userId, + state: [MailboxState.Active, MailboxState.Inactive], + }); + + const folderTypes = Object.values(MailboxFolderType); + const sections = folderTypes.map((type) => new MailboxSectionDto(type)); + const mailboxes: MailboxFullInfoDto[] = []; + for (const mailbox of accessibleMailboxes.sort((mb1, mb2) => mb1.id - mb2.id)) { + const hierarchy = await this.mailboxFolderService.getHierarchy({ accountId, mailboxId: mailbox.id }); + mailboxes.push( + MailboxFullInfoDto.create( + mailbox, + hierarchy.map((f) => f.toDto()), + ), + ); + + const typed = await this.mailboxFolderService.findMany({ accountId, mailboxId: mailbox.id, type: folderTypes }); + for (const folder of typed) { + const section = sections.find((ms) => ms.type === folder.type); + if (section) { + section.addMailboxInfo({ + id: mailbox.id, + name: mailbox.email, + unread: folder.unread, + total: folder.total, + state: mailbox.state, + }); + } + } + } + return { sections, mailboxes }; + } + + async sendMessage( + accountId: number, + userId: number, + mailboxId: number, + dto: SendMailMessageDto, + attachments?: StorageFile[] | null, + senderUserId?: number, + ): Promise { + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + + return mailbox ? this.sendMessageForMailbox(accountId, mailbox, dto, attachments, senderUserId) : false; + } + + async sendMessageForMailbox( + accountId: number, + mailbox: Mailbox, + dto: SendMailMessageDto, + attachments?: StorageFile[] | null, + senderUserId?: number, + ): Promise { + const replyToMessage = dto.replyToMessageId + ? await this.mailMessageService.findById(accountId, mailbox.id, dto.replyToMessageId) + : null; + + const files = dto.fileIds?.length > 0 ? await this.getMessageFiles(accountId, dto.fileIds) : []; + const allAttachments = [...files, ...(attachments || [])]; + + const sendFrom = await this.userService.findOne({ + accountId: mailbox.accountId, + id: senderUserId ?? mailbox.ownerId, + }); + + const provider = this.mailProviderRegistry.get(mailbox.provider); + const message = await provider.send({ + accountId, + mailbox, + userName: sendFrom.fullName, + dto, + replyToMessage, + attachments: allAttachments, + }); + + if (message) { + await this.mailMessageService.processExternalMessages({ accountId, mailbox, added: [message] }); + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + + return true; + } + + return false; + } + + private async getMessageFiles(accountId: number, fileIds: string[]): Promise { + const attachments: StorageFile[] = []; + + for (const fileId of fileIds) { + const { file, content } = await this.storageService.getFile({ fileId, accountId }); + const buffer = Buffer.from(content.buffer); + attachments.push(StorageFile.fromFileInfo(file, buffer)); + } + + return attachments; + } + + async trashThread(accountId: number, userId: number, mailboxId: number, messageId: number): Promise { + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + const message = await this.mailMessageService.getById(accountId, mailbox.id, messageId); + const provider = this.mailProviderRegistry.get(mailbox.provider); + let result = false; + if (provider.isCapable('thread')) { + result = await provider.trash({ mailbox, messages: { threadId: message.threadId } }); + } else { + const messages = await this.mailMessageService.getByThreadIdGroupByFolder( + accountId, + mailbox.id, + message.threadId, + ); + result = await provider.trash({ mailbox, messages }); + } + if (result) { + await this.mailMessageService.moveThreadToSpecialFolder( + accountId, + mailbox.id, + message.threadId, + MailboxFolderType.Trash, + ); + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + } + return result; + } + + async untrashThread(accountId: number, userId: number, mailboxId: number, messageId: number): Promise { + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + const message = await this.mailMessageService.getById(accountId, mailbox.id, messageId); + const provider = this.mailProviderRegistry.get(mailbox.provider); + let result = false; + if (provider.isCapable('thread')) { + result = await provider.untrash({ mailbox, messages: { threadId: message.threadId } }); + } else { + const messages = await this.mailMessageService.getByThreadIdGroupByFolder( + accountId, + mailbox.id, + message.threadId, + ); + result = await provider.untrash({ mailbox, messages }); + } + if (result) { + await this.mailMessageService.moveThreadToSpecialFolder( + accountId, + mailbox.id, + message.threadId, + MailboxFolderType.Inbox, + ); + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + } + return result; + } + + async spamThread(accountId: number, userId: number, mailboxId: number, messageId: number): Promise { + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + const message = await this.mailMessageService.getById(accountId, mailbox.id, messageId); + const provider = this.mailProviderRegistry.get(mailbox.provider); + let result = false; + if (provider.isCapable('thread')) { + result = await provider.spam({ mailbox, messages: { threadId: message.threadId } }); + } else { + const messages = await this.mailMessageService.getByThreadIdGroupByFolder( + accountId, + mailbox.id, + message.threadId, + ); + result = await provider.spam({ mailbox, messages }); + } + if (result) { + await this.mailMessageService.moveThreadToSpecialFolder( + accountId, + mailbox.id, + message.threadId, + MailboxFolderType.Junk, + ); + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + } + return result; + } + + async unspamThread(accountId: number, userId: number, mailboxId: number, messageId: number): Promise { + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + const message = await this.mailMessageService.getById(accountId, mailbox.id, messageId); + const provider = this.mailProviderRegistry.get(mailbox.provider); + let result = false; + if (provider.isCapable('thread')) { + result = await provider.unspam({ mailbox, messages: { threadId: message.threadId } }); + } else { + const messages = await this.mailMessageService.getByThreadIdGroupByFolder( + accountId, + mailbox.id, + message.threadId, + ); + result = await provider.unspam({ mailbox, messages }); + } + if (result) { + await this.mailMessageService.moveThreadToSpecialFolder( + accountId, + mailbox.id, + message.threadId, + MailboxFolderType.Inbox, + ); + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + } + return result; + } + + async markSeenThread(accountId: number, userId: number, mailboxId: number, messageId: number) { + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + const message = await this.mailMessageService.getById(accountId, mailbox.id, messageId); + const provider = this.mailProviderRegistry.get(mailbox.provider); + let result = false; + if (provider.isCapable('thread')) { + result = await provider.setSeen({ mailbox, seen: true, messages: { threadId: message.threadId } }); + } else { + const messages = await this.mailMessageService.getByThreadIdGroupByFolder( + accountId, + mailbox.id, + message.threadId, + ); + result = await provider.setSeen({ mailbox, seen: true, messages }); + } + if (result) { + await this.mailMessageService.markSeenThread(accountId, mailbox.id, message.threadId, true); + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + } + return result; + } + + async markUnseenThread(accountId: number, userId: number, mailboxId: number, messageId: number) { + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId, accessibleUserId: userId }); + const message = await this.mailMessageService.getById(accountId, mailbox.id, messageId); + const provider = this.mailProviderRegistry.get(mailbox.provider); + let result = false; + if (provider.isCapable('thread')) { + result = await provider.setSeen({ mailbox, seen: false, messages: { threadId: message.threadId } }); + } else { + const messages = await this.mailMessageService.getByThreadIdGroupByFolder( + accountId, + mailbox.id, + message.threadId, + ); + result = await provider.setSeen({ mailbox, seen: false, messages }); + } + if (result) { + await this.mailMessageService.markSeenThread(accountId, mailbox.id, message.threadId, true); + await this.mailboxFolderService.actualizeCounters({ accountId, mailboxId: mailbox.id }); + } + return result; + } +} diff --git a/backend/src/Mailing/Service/MailboxGmail/MailboxGmailService.ts b/backend/src/Mailing/Service/MailboxGmail/MailboxGmailService.ts new file mode 100644 index 0000000..4fcfb1f --- /dev/null +++ b/backend/src/Mailing/Service/MailboxGmail/MailboxGmailService.ts @@ -0,0 +1,724 @@ +import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { google, Auth, gmail_v1 } from 'googleapis'; +import addressparser from 'nodemailer/lib/addressparser'; + +import { formatState, FrontendRoute, isUnique, parseState, StringUtil, UrlGeneratorService } from '@/common'; +import { AccountService } from '@/modules/iam/account/account.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { MailConfig } from '../../config'; +import { + detectMailboxFolderType, + FolderMessages, + MailboxFolderExternal, + MailboxFolderType, + MailboxSyncResult, + MailMessageAttachment, + MailMessageExternal, + MailMessagePayloadExternal, + SendMailMessageDto, +} from '../../common'; +import { Mailbox } from '../../mailbox/entities'; +import { MailboxService } from '../../mailbox/services'; +import { MailboxFolderService } from '../../mailbox-folder'; +import { MailIntegration, MailProvider, MailProviderCapability } from '../../mail-provider'; +import { MailMessagePayload } from '../../mail-message-payload'; +import { HEADER_ENTITY_ID, MailMessageBuilderService } from '../../mail-message-builder'; + +import { MailboxSettingsGmail } from '../../Model/MailboxGmail/MailboxSettingsGmail'; +import { MailMessage } from '../../Model/MailMessage/MailMessage'; + +import { MailMessageService } from '../MailMessage/MailMessageService'; + +const CALLBACK_PATH = '/api/mailing/settings/mailboxes/gmail/callback'; + +const GmailSystemLabels = ['INBOX', 'SENT', 'IMPORTANT', 'STARRED', 'TRASH', 'DRAFT', 'SPAM']; +const IgnoreMimeType = [ + 'message/delivery-status', + 'message/global-delivery-status', + 'message/disposition-notification', +]; + +const ProviderName = 'gmail'; + +@Injectable() +@MailIntegration(ProviderName) +export class MailboxGmailService implements MailProvider { + private readonly logger = new Logger(MailboxGmailService.name); + private readonly _config: MailConfig; + private oAuth2Client: Auth.OAuth2Client; + + constructor( + private readonly urlGenerator: UrlGeneratorService, + private readonly configService: ConfigService, + @InjectRepository(MailboxSettingsGmail) + private readonly repository: Repository, + private readonly accountService: AccountService, + @Inject(forwardRef(() => MailboxService)) + private readonly mailboxService: MailboxService, + private readonly mailboxFolderService: MailboxFolderService, + @Inject(forwardRef(() => MailMessageService)) + private readonly mailMessageService: MailMessageService, + private readonly mailMessageBuilder: MailMessageBuilderService, + ) { + this._config = this.configService.get('mail'); + this.oAuth2Client = this.getOAuth2Client(); + } + + isCapable(capability: MailProviderCapability): boolean { + if (capability === 'thread') { + return true; + } + + return false; + } + + async getAuthorizeUrl({ accountId, mailboxId }: { accountId: number; mailboxId: number }): Promise { + const authUrl = this.oAuth2Client.generateAuthUrl({ + access_type: 'offline', + prompt: 'consent', + scope: ['https://www.googleapis.com/auth/gmail.modify'], + redirect_uri: this.getRedirectUrl(), + state: formatState(accountId, mailboxId), + }); + return authUrl as string; + } + + async processAuthCode({ code, state }: { code: string; state: string }): Promise { + const { tokens } = await this.oAuth2Client.getToken({ + code, + redirect_uri: this.getRedirectUrl(), + }); + const [accountId, mailboxId] = parseState(state, Number); + const mailbox = await this.mailboxService.findOne({ accountId, mailboxId }); + const profile = await this.getProfile(tokens); + await this.repository.save(new MailboxSettingsGmail(mailbox.id, mailbox.accountId, tokens, profile.historyId)); + if (profile.emailAddress && profile.emailAddress !== mailbox.email) { + await this.mailboxService.update({ + accountId: mailbox.accountId, + mailboxId: mailbox.id, + dto: { email: profile.emailAddress }, + }); + } + const { subdomain } = await this.accountService.findOne({ accountId: mailbox.accountId }); + + return this.urlGenerator.createUrl({ + route: FrontendRoute.settings.mailing(), + subdomain, + query: { mailboxId: String(mailbox.id) }, + }); + } + + private getRedirectUrl() { + return this.urlGenerator.createUrl({ route: CALLBACK_PATH }); + } + + private getOAuth2Client(tokens?: Auth.Credentials): Auth.OAuth2Client { + const client = new google.auth.OAuth2(this._config.gmail.apiClientId, this._config.gmail.apiClientSecret); + if (tokens) { + client.setCredentials(tokens); + } + return client; + } + + private async getProfile(tokens: Auth.Credentials) { + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(tokens) }); + const { data } = await gmail.users.getProfile({ userId: 'me' }); + return data; + } + + async sync({ + mailbox, + syncFull, + syncDate, + }: { + mailbox: Mailbox; + syncFull?: boolean; + syncDate?: Date; + }): Promise { + return syncFull ? this.syncFull(mailbox, syncDate) : this.syncPartial(mailbox); + } + + async syncFull(mailbox: Mailbox, syncDate: Date | null): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await this.updateFolders(mailbox, gmail); + + if (syncDate) { + const added: string[] = []; + let historyId: string = undefined; + let nextPageToken: string = undefined; + let listMessages = true; + while (listMessages) { + const { data } = await gmail.users.messages.list({ + userId: 'me', + pageToken: nextPageToken, + includeSpamTrash: true, + }); + if (!historyId && data.messages.length > 0) { + const message = await gmail.users.messages.get({ userId: 'me', id: data.messages[0].id }); + historyId = message.data.historyId; + } + + nextPageToken = data.nextPageToken; + listMessages = !!nextPageToken; + + added.push(...data.messages.map((m) => m.id)); + } + + if (added.length) { + await this.updateMessages({ accountId: mailbox.accountId, mailbox, gmail, added }); + } + + if (historyId) { + await this.repository.update(mailbox.id, { historyId }); + } + } + + return { result: true, message: null }; + } catch (e) { + this.logger.warn(`Gmail full synchronization error for mailbox ${mailbox.id}. ${e.toString()}`); + if (e instanceof Error) { + return { result: false, message: e.message }; + } else { + return { result: false, message: e.toString() }; + } + } + } + + async syncPartial(mailbox: Mailbox): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await this.updateFolders(mailbox, gmail); + + let added: string[] = []; + let updated: string[] = []; + let deleted: string[] = []; + let historyId = settings.historyId; + let nextPageToken: string = undefined; + let checkHistory = true; + while (checkHistory) { + const { data } = await gmail.users.history.list({ + userId: 'me', + startHistoryId: settings.historyId, + pageToken: nextPageToken, + }); + if (!nextPageToken) { + historyId = data.historyId; + } + + nextPageToken = data.nextPageToken; + checkHistory = !!nextPageToken; + + if (data.history) { + for (const record of data.history) { + let recordMessagesAdded: string[] = []; + let recordMessagesUpdated: string[] = []; + let recordMessagesDeleted: string[] = []; + if (record.messagesAdded) { + recordMessagesAdded = record.messagesAdded.map((m) => m.message.id).filter(isUnique); + } + if (record.messages) { + recordMessagesUpdated = record.messages.map((m) => m.id).filter(isUnique); + } + if (record.messagesDeleted) { + recordMessagesDeleted = record.messagesDeleted.map((m) => m.message.id).filter(isUnique); + } + if (recordMessagesUpdated.length > 0) { + recordMessagesUpdated = recordMessagesUpdated.filter((m) => !added.includes(m)); + updated.push(...recordMessagesUpdated); + deleted = deleted.filter((m) => !recordMessagesUpdated.includes(m)); + } + if (recordMessagesDeleted.length > 0) { + added = added.filter((m) => !recordMessagesDeleted.includes(m)); + updated = updated.filter((m) => !recordMessagesDeleted.includes(m)); + deleted.push(...recordMessagesDeleted); + } + if (recordMessagesAdded.length > 0) { + added.push(...recordMessagesAdded); + updated = updated.filter((m) => !recordMessagesAdded.includes(m)); + deleted = deleted.filter((m) => !recordMessagesAdded.includes(m)); + } + } + } + } + + added = added.filter(isUnique); + updated = updated.filter(isUnique); + deleted = deleted.filter(isUnique); + + if (added.length || updated.length || deleted.length) { + await this.updateMessages({ accountId: mailbox.accountId, mailbox, gmail, added, updated, deleted }); + } + + if (historyId) { + await this.repository.update(settings.mailboxId, { historyId }); + } + + return { result: true, message: null }; + } catch (e) { + this.logger.warn(`Gmail partial synchronization error for mailbox ${mailbox.id}. ${e.toString()}`); + if (e instanceof Error) { + return { result: false, message: e.message }; + } else { + return { result: false, message: e.toString() }; + } + } + } + + async getAttachment({ + mailbox, + message, + payload, + }: { + mailbox: Mailbox; + message: MailMessage; + payload: MailMessagePayload; + }): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + const { data } = await gmail.users.messages.attachments.get({ + userId: 'me', + messageId: message.externalId, + id: payload.attachment, + }); + + return { + mimeType: payload.mimeType, + filename: payload.filename, + content: new Uint8Array(Buffer.from(data.data, 'base64')), + }; + } catch (e) { + this.logger.error(`Gmail get attachment error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return null; + } + } + + private async updateFolders(mailbox: Mailbox, gmail: gmail_v1.Gmail) { + const { data } = await gmail.users.labels.list({ userId: 'me' }); + const folders = data.labels + .filter((l) => l.type === 'user' || GmailSystemLabels.includes(l.id)) + .map((label) => this.convertToExternalFolder(label)); + if (folders.length) { + await this.mailboxFolderService.processExternal({ + accountId: mailbox.accountId, + mailboxId: mailbox.id, + extFolders: folders, + }); + } + } + + private convertToExternalFolder(label: gmail_v1.Schema$Label): MailboxFolderExternal { + return { + id: label.id, + name: label.name, + type: detectMailboxFolderType({ name: label.id }), + }; + } + + private async updateMessages({ + accountId, + mailbox, + gmail, + added, + updated, + deleted, + }: { + accountId: number; + mailbox: Mailbox; + gmail: gmail_v1.Gmail; + added?: string[]; + updated?: string[]; + deleted?: string[]; + }) { + const addedMsgs = added?.length + ? (await Promise.all(added.map((messageId) => this.findMessage(mailbox.id, gmail, messageId)))).filter(Boolean) + : undefined; + + const updatedMsgs = updated?.length + ? (await Promise.all(updated.map((messageId) => this.findMessage(mailbox.id, gmail, messageId)))).filter(Boolean) + : undefined; + + await this.mailMessageService.processExternalMessages({ + accountId, + mailbox, + added: addedMsgs, + updated: updatedMsgs, + deleted, + }); + } + + private async findMessage( + mailboxId: number, + gmail: gmail_v1.Gmail, + messageId: string, + ): Promise { + try { + const { data } = await gmail.users.messages.get({ userId: 'me', id: messageId }); + const from = this.getMessageHeadersValue(data.payload.headers, 'From'); + if (!from) { + this.logger.warn( + `Null 'From' value! Mailbox: ${mailboxId}. Message payload header: ${JSON.stringify(data.payload.headers)}`, + ); + return null; + } + const payloads = this.getMessagePayloads(data.payload); + const hasAttachment = payloads.some((payload) => payload.attachmentId); + const to = this.getMessageHeadersValue(data.payload.headers, 'To'); + const replyTo = this.getMessageHeadersValue(data.payload.headers, 'Reply-To'); + const cc = this.getMessageHeadersValue(data.payload.headers, 'Cc'); + const isUnread = data.labelIds.includes('UNREAD'); + const entityId = this.getMessageHeadersValue(data.payload.headers, HEADER_ENTITY_ID); + return { + id: data.id, + threadId: data.threadId, + snippet: data.snippet, + sentFrom: { text: from, values: addressparser(from, { flatten: true }) }, + sentTo: to ? { text: to, values: addressparser(to, { flatten: true }) } : null, + replyTo: replyTo ? { text: replyTo, values: addressparser(replyTo, { flatten: true }) } : null, + cc: cc ? { text: cc, values: addressparser(cc, { flatten: true }) } : null, + subject: this.getMessageHeadersValue(data.payload.headers, 'Subject'), + date: new Date(parseInt(data.internalDate)), + hasAttachment, + messageId: this.getMessageHeadersValue(data.payload.headers, 'Message-Id'), + inReplyTo: this.getMessageHeadersValue(data.payload.headers, 'In-Reply-To'), + references: this.getMessageHeadersValue(data.payload.headers, 'References') + ?.split(',') + ?.map((i) => i.trim()), + isSeen: !isUnread, + entityId: entityId ? parseInt(entityId) : null, + folders: data.labelIds, + payloads, + }; + } catch { + return null; + } + } + + private getMessageHeadersValue(headers: gmail_v1.Schema$MessagePartHeader[], name: string): string { + return headers.find((header) => header.name.toLowerCase() === name.toLowerCase())?.value; + } + + private getMessagePayloads(part: gmail_v1.Schema$MessagePart): MailMessagePayloadExternal[] { + if (part.mimeType.startsWith('text') || part.filename || part.body?.attachmentId) { + const attachmentId = part.body?.attachmentId ?? null; + const content = part.body?.data ? StringUtil.decode(part.body.data, 'base64', 'utf-8') : null; + const size = part.body?.size ?? null; + return [{ id: part.partId, mimeType: part.mimeType, filename: part.filename, attachmentId, content, size }]; + } else if (part.mimeType.startsWith('multipart') || part.mimeType === 'message/rfc822') { + const payloads: MailMessagePayloadExternal[] = []; + for (const nestedPart of part.parts) { + const nestedPayloads = this.getMessagePayloads(nestedPart); + if (nestedPayloads.length > 0) { + payloads.push(...nestedPayloads); + } + } + return payloads; + } else if (IgnoreMimeType.includes(part.mimeType)) { + //TODO: process delivery status + return []; + } else if (part.mimeType.startsWith('image')) { + //TODO: process inline images + return []; + } else { + this.logger.error(`Gmail synchronization. Unknown message part mime type '${part.mimeType}' or empty file name`); + return []; + } + } + + async send({ + accountId, + mailbox, + userName, + dto, + replyToMessage, + attachments, + }: { + accountId: number; + mailbox: Mailbox; + userName: string; + dto: SendMailMessageDto; + replyToMessage?: MailMessage | null; + attachments: StorageFile[]; + }): Promise { + try { + const settings = await this.repository.findOneBy({ accountId, mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + const mail = await this.mailMessageBuilder.createNodemailerMessage( + mailbox.email, + userName, + dto, + replyToMessage, + attachments, + ); + const request = { + threadId: replyToMessage ? replyToMessage.threadId : null, + raw: await this.mailMessageBuilder.createRawMessage(mail), + }; + + const { data } = await gmail.users.messages.send({ userId: 'me', requestBody: request }); + + if (data?.id) { + return await this.findMessage(mailbox.id, gmail, data.id); + } + } catch (e) { + const error = e as Error; + this.logger.error(`SMTP send message error for mailbox ${mailbox.id}: ${error?.message}`, error?.stack); + } + + return null; + } + + async setSeen({ + mailbox, + seen, + messages, + }: { + accountId: number; + mailbox: Mailbox; + seen: boolean; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + const messageIds = messages.map((m) => m.messageIds).flat(); + return seen + ? this.removeLabelFromMessages(mailbox, messageIds, ['UNREAD']) + : this.addLabelToMessages(mailbox, messageIds, ['UNREAD']); + } else { + return seen + ? this.removeLabelFromThread(mailbox, messages.threadId, ['UNREAD']) + : this.addLabelToThread(mailbox, messages.threadId, ['UNREAD']); + } + } + private async addLabelToThread(mailbox: Mailbox, threadId: string, labels: string[]): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await gmail.users.threads.modify({ + userId: 'me', + id: threadId, + requestBody: { addLabelIds: labels }, + }); + + return true; + } catch (e) { + this.logger.error(`Gmail add label ${labels} to thread error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return false; + } + } + private async removeLabelFromThread(mailbox: Mailbox, threadId: string, labels: string[]): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await gmail.users.threads.modify({ + userId: 'me', + id: threadId, + requestBody: { removeLabelIds: labels }, + }); + + return true; + } catch (e) { + this.logger.error(`Gmail remove label ${labels} to thread error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return false; + } + } + private async addLabelToMessages(mailbox: Mailbox, messageExtIds: string[], labels: string[]): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await gmail.users.messages.batchModify({ + userId: 'me', + requestBody: { ids: messageExtIds, addLabelIds: labels }, + }); + + return true; + } catch (e) { + this.logger.error(`Gmail add label ${labels} to messages error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return false; + } + } + private async removeLabelFromMessages(mailbox: Mailbox, messageExtIds: string[], labels: string[]): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await gmail.users.messages.batchModify({ + userId: 'me', + requestBody: { ids: messageExtIds, removeLabelIds: labels }, + }); + + return true; + } catch (e) { + this.logger.error( + `Gmail remove label ${labels} to messages error for mailbox ${mailbox.id}`, + (e as Error)?.stack, + ); + return false; + } + } + + async trash({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + const messageIds = messages.map((m) => m.messageIds).flat(); + return this.trashMessages(mailbox, messageIds); + } else { + return this.trashThread(mailbox, messages.threadId); + } + } + private async trashMessages(mailbox: Mailbox, messageExtIds: string[]): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + for (const messageExtId of messageExtIds) { + await gmail.users.messages.trash({ userId: 'me', id: messageExtId }); + } + + return true; + } catch (e) { + this.logger.error(`Gmail trash message error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return false; + } + } + private async trashThread(mailbox: Mailbox, threadId: string): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await gmail.users.threads.trash({ userId: 'me', id: threadId }); + + return true; + } catch (e) { + this.logger.error(`Gmail trash thread error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return false; + } + } + + async untrash({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + const messageIds = messages.map((m) => m.messageIds).flat(); + return this.untrashMessages(mailbox, messageIds); + } else { + return this.untrashThread(mailbox, messages.threadId); + } + } + private async untrashMessages(mailbox: Mailbox, messageExtIds: string[]): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + for (const messageExtId of messageExtIds) { + await gmail.users.messages.untrash({ userId: 'me', id: messageExtId }); + } + + return true; + } catch (e) { + this.logger.error(`Gmail untrash message error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return false; + } + } + private async untrashThread(mailbox: Mailbox, threadId: string): Promise { + try { + const settings = await this.repository.findOneBy({ mailboxId: mailbox.id }); + const gmail = google.gmail({ version: 'v1', auth: this.getOAuth2Client(settings.tokens) }); + + await gmail.users.threads.untrash({ userId: 'me', id: threadId }); + + return true; + } catch (e) { + this.logger.error(`Gmail untrash thread error for mailbox ${mailbox.id}`, (e as Error)?.stack); + return false; + } + } + + async spam({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + const messageIds = messages.map((m) => m.messageIds).flat(); + return this.addMessagesToFolder(mailbox.accountId, mailbox, messageIds, MailboxFolderType.Junk); + } else { + return this.addThreadToFolder(mailbox.accountId, mailbox, messages.threadId, MailboxFolderType.Junk); + } + } + private async addThreadToFolder( + accountId: number, + mailbox: Mailbox, + threadId: string, + type: MailboxFolderType, + ): Promise { + const toFolder = await this.mailboxFolderService.findOne({ accountId, mailboxId: mailbox.id, type }); + return await this.addLabelToThread(mailbox, threadId, [toFolder.externalId]); + } + private async addMessagesToFolder( + accountId: number, + mailbox: Mailbox, + messageExtIds: string[], + type: MailboxFolderType, + ): Promise { + const toFolder = await this.mailboxFolderService.findOne({ accountId, mailboxId: mailbox.id, type }); + return await this.addLabelToMessages(mailbox, messageExtIds, [toFolder.externalId]); + } + + async unspam({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + const messageIds = messages.map((m) => m.messageIds).flat(); + return this.removeMessagesFromFolder(mailbox.accountId, mailbox, messageIds, MailboxFolderType.Junk); + } else { + return this.removeThreadFromFolder(mailbox.accountId, mailbox, messages.threadId, MailboxFolderType.Junk); + } + } + private async removeThreadFromFolder( + accountId: number, + mailbox: Mailbox, + threadId: string, + type: MailboxFolderType, + ): Promise { + const fromFolder = await this.mailboxFolderService.findOne({ accountId, mailboxId: mailbox.id, type }); + return await this.removeLabelFromThread(mailbox, threadId, [fromFolder.externalId]); + } + private async removeMessagesFromFolder( + accountId: number, + mailbox: Mailbox, + messageExtIds: string[], + type: MailboxFolderType, + ): Promise { + const fromFolder = await this.mailboxFolderService.findOne({ accountId, mailboxId: mailbox.id, type }); + return await this.removeLabelFromMessages(mailbox, messageExtIds, [fromFolder.externalId]); + } +} diff --git a/backend/src/Mailing/Service/MailboxManual/Dto/MailboxSettingsManualDto.ts b/backend/src/Mailing/Service/MailboxManual/Dto/MailboxSettingsManualDto.ts new file mode 100644 index 0000000..762c130 --- /dev/null +++ b/backend/src/Mailing/Service/MailboxManual/Dto/MailboxSettingsManualDto.ts @@ -0,0 +1,61 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsString } from 'class-validator'; + +import { MailboxSettingsManual } from '../../../Model/MailboxManual/MailboxSettingsManual'; + +export class MailboxSettingsManualDto { + @ApiProperty() + @IsString() + imapServer: string; + + @ApiProperty() + @IsNumber() + imapPort: number; + + @ApiProperty() + @IsBoolean() + imapSecure: boolean; + + @ApiProperty() + @IsString() + smtpServer: string; + + @ApiProperty() + @IsNumber() + smtpPort: number; + + @ApiProperty() + @IsBoolean() + smtpSecure: boolean; + + private constructor( + imapServer: string, + imapPort: number, + imapSecure: boolean, + smtpServer: string, + smtpPort: number, + smtpSecure: boolean, + ) { + this.imapServer = imapServer; + this.imapPort = imapPort; + this.imapSecure = imapSecure; + this.smtpServer = smtpServer; + this.smtpPort = smtpPort; + this.smtpSecure = smtpSecure; + } + + public static create(settings: MailboxSettingsManual): MailboxSettingsManualDto { + return new MailboxSettingsManualDto( + settings.imapServer, + settings.imapPort, + settings.imapSecure, + settings.smtpServer, + settings.smtpPort, + settings.smtpSecure, + ); + } + + public static createDefault(): MailboxSettingsManualDto { + return new MailboxSettingsManualDto('', 0, true, '', 0, true); + } +} diff --git a/backend/src/Mailing/Service/MailboxManual/Dto/UpdateMailboxSettingsManualDto.ts b/backend/src/Mailing/Service/MailboxManual/Dto/UpdateMailboxSettingsManualDto.ts new file mode 100644 index 0000000..64c6bff --- /dev/null +++ b/backend/src/Mailing/Service/MailboxManual/Dto/UpdateMailboxSettingsManualDto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class UpdateMailboxSettingsManualDto { + @ApiProperty() + @IsOptional() + @IsString() + email: string; + + @ApiProperty() + @IsOptional() + @IsString() + password: string | null; + + @ApiProperty() + @IsString() + imapServer: string; + + @ApiProperty() + @IsNumber() + imapPort: number; + + @ApiProperty() + @IsBoolean() + imapSecure: boolean; + + @ApiProperty() + @IsString() + smtpServer: string; + + @ApiProperty() + @IsNumber() + smtpPort: number; + + @ApiProperty() + @IsBoolean() + smtpSecure: boolean; +} diff --git a/backend/src/Mailing/Service/MailboxManual/MailboxManualService.ts b/backend/src/Mailing/Service/MailboxManual/MailboxManualService.ts new file mode 100644 index 0000000..f5adeca --- /dev/null +++ b/backend/src/Mailing/Service/MailboxManual/MailboxManualService.ts @@ -0,0 +1,803 @@ +import { forwardRef, Inject, Injectable, Logger, NotImplementedException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { connect, getParts, ImapSimple, Message } from 'imap-simple'; +import * as Imap from 'imap'; +import nodemailer from 'nodemailer'; +import Mail from 'nodemailer/lib/mailer'; +import addressparser from 'nodemailer/lib/addressparser'; +import { AddressObject, simpleParser, Source } from 'mailparser'; +import { v4 as uuidv4 } from 'uuid'; +import { convert } from 'html-to-text'; + +import { capitalizeFirst, DateUtil, StringUtil, withTimeout } from '@/common'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { MailConfig } from '../../config'; +import { + detectMailboxFolderType, + EmailAddress, + EmailAddressValue, + FolderMessages, + ImapSyncInfo, + MailboxFolderExternal, + MailboxFolderType, + MailboxSyncResult, + MailMessageAttachment, + MailMessageExternal, + MailMessagePayloadExternal, + SendMailMessageDto, +} from '../../common'; +import { Mailbox } from '../../mailbox/entities'; +import { MailboxFolderService } from '../../mailbox-folder'; +import { MailIntegration, MailProvider, MailProviderCapability } from '../../mail-provider'; +import { MailMessagePayload } from '../../mail-message-payload'; +import { HEADER_ENTITY_ID, MailMessageBuilderService } from '../../mail-message-builder'; + +import { MailboxSettingsManual } from '../../Model/MailboxManual/MailboxSettingsManual'; +import { MailMessage } from '../../Model/MailMessage/MailMessage'; + +import { MailMessageService } from '../MailMessage/MailMessageService'; + +import { MailboxSettingsManualDto } from './Dto/MailboxSettingsManualDto'; +import { UpdateMailboxSettingsManualDto } from './Dto/UpdateMailboxSettingsManualDto'; + +// eslint-disable-next-line no-control-regex +const cleanString = (str: string) => str?.replace(/[\u0000\f]/g, '')?.trim(); + +const ProviderName = 'manual'; + +@Injectable() +@MailIntegration(ProviderName) +export class MailboxManualService implements MailProvider { + private readonly logger = new Logger(MailboxManualService.name); + private readonly _config: MailConfig; + + constructor( + private readonly configService: ConfigService, + @InjectRepository(MailboxSettingsManual) + private readonly repository: Repository, + private readonly mailboxFolderService: MailboxFolderService, + @Inject(forwardRef(() => MailMessageService)) + private readonly mailMessageService: MailMessageService, + private readonly mailMessageBuilder: MailMessageBuilderService, + ) { + this._config = this.configService.get('mail'); + } + + isCapable(capability: MailProviderCapability): boolean { + if (capability === 'thread') { + return false; + } + + return false; + } + + async getManualSettings(accountId: number, mailboxId: number): Promise { + const settings = await this.repository.findOneBy({ accountId, mailboxId }); + if (!settings) { + return MailboxSettingsManualDto.createDefault(); + } + return MailboxSettingsManualDto.create(settings); + } + + async saveManualSettings( + accountId: number, + mailboxId: number, + dto: UpdateMailboxSettingsManualDto, + ): Promise<{ result: boolean; state: MailboxSettingsManualDto | string }> { + let settings = await this.repository.findOneBy({ accountId, mailboxId }); + settings = settings ? settings.update(dto) : MailboxSettingsManual.create(mailboxId, accountId, dto); + const { result, message } = await this.checkSettings(dto.email, settings); + if (result) { + await this.repository.save(settings); + return { result, state: MailboxSettingsManualDto.create(settings) }; + } else { + return { result, state: message }; + } + } + + private async withConnection({ + email, + mailboxId, + settings, + action, + onError, + }: { + email: string; + mailboxId?: number; + settings?: MailboxSettingsManual; + action: (connection: ImapSimple, settings: MailboxSettingsManual) => Promise; + onError?: (error: unknown) => E; + }) { + if (!mailboxId && !settings) { + throw new Error('No mailboxId or settings provided'); + } + const localSettings = settings ?? (await this.repository.findOneBy({ mailboxId })); + let connection: ImapSimple; + try { + connection = await connect(this.getSimpleImapConfig(email, localSettings)); + // connection.on('error', (error) => { + // this.logger.error(`Connection onError for mailbox ${localSettings.mailboxId}`, error.stack); + // }); + return await action(connection, localSettings); + } catch (e) { + this.logger.warn(`Connection error for mailbox ${localSettings.mailboxId}. ${e.toString()}`); + return onError ? onError(e as Error) : null; + } finally { + connection?.end(); + } + } + + private getSimpleImapConfig(user: string, settings: MailboxSettingsManual) { + return { + imap: { + user: user, + password: settings.password, + host: settings.imapServer, + port: settings.imapPort, + tls: settings.imapSecure, + tlsOptions: { servername: settings.imapServer }, + authTimeout: 3000, + }, + }; + } + + private async withBox({ + mailboxId, + connection, + boxName, + autoExpunge, + action, + onError, + }: { + mailboxId: number; + connection: ImapSimple; + boxName: string; + autoExpunge: boolean; + action: (box: Imap.Box) => Promise; + onError?: (error: unknown) => E; + }) { + let box: Imap.Box; + try { + box = (await connection.openBox(boxName)) as unknown as Imap.Box; + return await action(box); + } catch (e) { + this.logger.warn(`Box <${boxName}> error for mailbox ${mailboxId}. ${e.toString()}`); + return onError ? onError(e) : null; + } finally { + try { + await connection.closeBox(autoExpunge); + } catch (closeError) { + this.logger.warn(`Failed to close box <${boxName}> for mailbox ${mailboxId}. ${closeError.toString()}`); + } + } + } + + private async checkSettings(email: string, settings: MailboxSettingsManual): Promise { + return await this.withConnection({ + email, + settings, + action: async () => { + return { result: true, message: null }; + }, + onError: (error) => { + return { result: false, message: (error as Error)?.message }; + }, + }); + } + + async sync({ + mailbox, + syncFull, + syncDate, + }: { + mailbox: Mailbox; + syncFull?: boolean; + syncDate?: Date; + }): Promise { + return syncFull ? this.syncFull(mailbox, syncDate) : this.syncPartial(mailbox); + } + + async syncFull(mailbox: Mailbox, syncDate: Date | null): Promise { + return await this.withConnection({ + mailboxId: mailbox.id, + email: mailbox.email, + action: async (connection) => { + const syncInfo = await this.processMailbox({ + connection, + mailbox, + syncInfo: [], + fromDate: syncDate ?? DateUtil.now(), + }); + await this.repository.update(mailbox.id, { imapSync: syncInfo }); + return { result: true, message: null }; + }, + onError: (error) => { + return { result: false, message: (error as Error)?.message }; + }, + }); + } + + async syncPartial(mailbox: Mailbox): Promise { + return await this.withConnection({ + mailboxId: mailbox.id, + email: mailbox.email, + action: async (connection, settings) => { + const syncInfo = await this.processMailbox({ + connection, + mailbox, + syncInfo: settings.imapSync ?? [], + fromDate: DateUtil.now(), + }); + await this.repository.update(mailbox.id, { imapSync: syncInfo }); + return { result: true, message: null }; + }, + onError: (error) => { + return { result: false, message: (error as Error)?.message }; + }, + }); + } + + async getAttachment({ + mailbox, + message, + payload, + }: { + mailbox: Mailbox; + message: MailMessage; + payload: MailMessagePayload; + }): Promise { + const content: Uint8Array | null = await this.withConnection({ + mailboxId: mailbox.id, + email: mailbox.email, + action: async (connection) => { + const folders = await this.mailboxFolderService.findMany({ + accountId: message.accountId, + messageId: message.id, + }); + if (folders && folders.length > 0) { + return await this.withBox({ + mailboxId: mailbox.id, + connection, + boxName: folders[0].externalId, + autoExpunge: false, + action: async () => { + const uid = this.parseMessageId(message.externalId); + const mails = await connection.search(['ALL', ['UID', `${uid}`]], { bodies: ['HEADER'], struct: true }); + let data = null; + if (mails && mails.length > 0) { + const parts = getParts(mails[0].attributes.struct); + const part = parts.find((p) => p.partID === payload.externalId); + if (part) { + data = await connection.getPartData(mails[0], part); + } + } + return data ? new Uint8Array(data) : null; + }, + onError: () => { + return null; + }, + }); + } else { + return null; + } + }, + }); + + return content ? { mimeType: payload.mimeType, filename: payload.filename, content } : null; + } + + async send({ + accountId, + mailbox, + userName, + dto, + replyToMessage, + attachments, + }: { + accountId: number; + mailbox: Mailbox; + userName: string; + dto: SendMailMessageDto; + replyToMessage?: MailMessage | null; + attachments: StorageFile[]; + }): Promise { + try { + const settings = await this.repository.findOneBy({ accountId, mailboxId: mailbox.id }); + const transporter = nodemailer.createTransport({ + host: settings.smtpServer, + port: settings.smtpPort, + secure: settings.smtpSecure, + auth: { + user: mailbox.email, + pass: settings.password, + }, + }); + + const mail = await this.mailMessageBuilder.createNodemailerMessage( + mailbox.email, + userName, + dto, + replyToMessage, + attachments, + ); + const { messageId } = await transporter.sendMail(mail); + if (messageId) { + mail.messageId = messageId; + const appended = await this.appendToFolder(accountId, mailbox, settings, MailboxFolderType.Sent, mail); + if (appended) { + return await this.createExternalMessage(uuidv4(), appended.message, appended.folderExternalId, true); + } + } + } catch (e) { + const error = e as Error; + this.logger.error(`SMTP send message error for mailbox ${mailbox.id}: ${error?.message}`, error?.stack); + } + + return null; + } + + private async processMailbox({ + connection, + mailbox, + syncInfo, + fromDate, + }: { + connection: ImapSimple; + mailbox: Mailbox; + syncInfo: ImapSyncInfo[]; + fromDate: Date; + }): Promise { + const sync: ImapSyncInfo[] = []; + const boxes = await connection.getBoxes(); + const boxesNames = Object.getOwnPropertyNames(boxes); + const folders: MailboxFolderExternal[] = []; + const messages: MailMessageExternal[] = []; + + for (const boxName of boxesNames) { + await this.withBox({ + mailboxId: mailbox.id, + connection, + boxName, + autoExpunge: true, + action: async (box) => { + folders.push(this.convertToExternalFolder(box)); + const search: unknown[] = ['ALL']; + let uidTo: number | undefined = undefined; + const boxSyncInfo = syncInfo.find((si) => si.boxName === box.name); + if (boxSyncInfo) { + if (boxSyncInfo.uidnext >= box.uidnext) { + // No new mail in box + sync.push({ boxName: box.name, uidnext: box.uidnext }); + return; + } + uidTo = Math.min(boxSyncInfo.uidnext + this._config.manual.searchBatchSize, box.uidnext); + search.push(['UID', `${boxSyncInfo.uidnext}:${uidTo}`]); + } else { + search.push(['SINCE', fromDate]); + } + const mails = await withTimeout( + connection.search(search, { size: true, struct: true, bodies: ['HEADER'] }), + this._config.manual.searchTimeout, + [], + ); + for (const mail of mails) { + if (!boxSyncInfo || boxSyncInfo.uidnext <= mail.attributes.uid) { + const message = await this.processMessage({ connection, folderName: box.name, message: mail }); + if (message) messages.push(message); + } + } + sync.push({ boxName: box.name, uidnext: Math.min((uidTo ?? 0) + 1, box.uidnext) }); + }, + }); + } + + await this.mailboxFolderService.processExternal({ + accountId: mailbox.accountId, + mailboxId: mailbox.id, + extFolders: folders, + }); + await this.mailMessageService.processExternalMessages({ accountId: mailbox.accountId, mailbox, added: messages }); + + return sync; + } + + private async processMessage({ + connection, + folderName, + message, + }: { + connection: ImapSimple; + folderName: string; + message: Message; + }): Promise { + const header = message.parts.find((p) => p.which === 'HEADER'); + if (!header || !header.body) return null; + + const from = cleanString((header.body['from'] as string[])?.join(', ')); + const to = cleanString((header.body['to'] as string[])?.join(', ')); + const replyTo = cleanString((header.body['reply-to'] as string[])?.join(', ')); + const cc = cleanString((header.body['cc'] as string[])?.join(', ')); + const subject = cleanString((header.body['subject'] as string[])?.join(', ')); + let date = new Date(header.body['date']?.[0] as string); + if (!date || isNaN(date.getTime())) { + date = new Date(); + } + const messageId = header.body['message-id']?.[0] as string; + const inReplyTo = header.body['in-reply-to']?.[0] as string; + const references = (header.body['references'] as string[]) + ?.join(',') + ?.split(',') + ?.map((i) => i.trim()); + const isSeen = message.attributes.flags.includes(`\\Seen`); + const entityId = header.body[HEADER_ENTITY_ID] ? parseInt(header.body[HEADER_ENTITY_ID][0] as string) : null; + + const parts = getParts(message.attributes.struct); + const { hasAttachment, payloads } = await this.getMessagePayloads({ connection, message, parts }); + const snippet = this.getMessageSnippet(payloads); + + return { + id: this.formatExternalId(folderName, message.attributes.uid), + threadId: null, + snippet: snippet, + sentFrom: { text: from, values: addressparser(from, { flatten: true }) }, + sentTo: to ? { text: to, values: addressparser(to, { flatten: true }) } : null, + replyTo: replyTo ? { text: replyTo, values: addressparser(replyTo, { flatten: true }) } : null, + cc: cc ? { text: cc, values: addressparser(cc, { flatten: true }) } : null, + subject: subject, + date: date, + hasAttachment: hasAttachment, + messageId: messageId, + inReplyTo: inReplyTo, + references: references, + isSeen: isSeen, + entityId: entityId, + folders: [folderName], + payloads: payloads, + }; + } + + private async getMessagePayloads({ + connection, + message, + parts, + }: { + connection: ImapSimple; + message: Message; + parts: any[]; + }): Promise<{ + hasAttachment: boolean; + payloads: MailMessagePayloadExternal[]; + }> { + let hasAttachment = false; + const payloads: MailMessagePayloadExternal[] = []; + for (const part of parts) { + hasAttachment ||= part.type !== 'text'; + const mimeType = `${part.type}/${part.subtype}`; + if (part.type === 'text') { + const partData = part.size + ? await withTimeout(connection.getPartData(message, part), this._config.manual.partLoadTimeout, null) + : null; + const content = partData ? cleanString(partData.toString()) : null; + payloads.push({ id: part.partID, mimeType, filename: null, attachmentId: part.id, content, size: part.size }); + } else { + let fileName = null; + if (part.params?.name) { + fileName = StringUtil.decodeMimeWord(part.params.name); + } + if (!fileName && part.disposition?.params?.filename) { + fileName = StringUtil.decodeRFC5987(part.disposition.params.filename); + } + payloads.push({ + id: part.partID, + mimeType, + filename: fileName, + attachmentId: part.id, + content: null, + size: part.size, + }); + } + } + + return { hasAttachment, payloads }; + } + + private getMessageSnippet(payloads: MailMessagePayloadExternal[]): string { + const plain = payloads.find((p) => p.mimeType === 'text/plain'); + if (plain && plain.content) { + return plain.content.trim().substring(0, 150).trim(); + } + + const html = payloads.find((p) => p.mimeType === 'text/html'); + if (html && html.content) { + return convert(html.content)?.trim()?.substring(0, 150)?.trim(); + } + + return ''; + } + + private convertToExternalFolder(box: Imap.Box): MailboxFolderExternal { + return { + id: box.name, + name: capitalizeFirst(box.name), + uidValidity: box.uidvalidity, + type: detectMailboxFolderType({ name: box.name }), + }; + } + + private async createExternalMessage( + id: number | string, + raw: Source, + boxName: string, + isSeen: boolean, + ): Promise { + const parsed = await simpleParser(raw); + if (!parsed) { + return null; + } + + const { from, to, replyTo, cc, subject, date, messageId, inReplyTo, references, text, html, attachments, headers } = + parsed; + const messageDate = date ?? DateUtil.now(); + const snippet = text ? text.substring(0, 150).trim() : ''; + const entityId: number | null = headers.has(HEADER_ENTITY_ID) + ? parseInt(headers.get(HEADER_ENTITY_ID) as string) + : null; + const payloads = this.formatAsExternalPayload(text, html, attachments); + return { + id: this.formatExternalId(boxName, id), + threadId: null, + snippet, + sentFrom: this.convertAddress(from), + sentTo: this.convertAddress(to), + replyTo: this.convertAddress(replyTo), + cc: this.convertAddress(cc), + subject, + date: messageDate, + hasAttachment: attachments && attachments.length > 0, + messageId, + inReplyTo: inReplyTo, + references: typeof references === 'string' ? [references] : references, + isSeen, + entityId, + folders: [boxName], + payloads, + }; + } + + private convertAddress(addresses: AddressObject | AddressObject[] | null | undefined): EmailAddress | null { + if (!addresses) { + return null; + } + + if (Array.isArray(addresses)) { + const texts: string[] = []; + const values: EmailAddressValue[] = []; + for (const address of addresses) { + if (address) { + texts.push(address.text); + values.push(...address.value.filter((v) => v?.address).map((v) => ({ address: v.address, name: v.name }))); + } + } + return { text: texts.join(', '), values }; + } + + return { + text: addresses.text, + values: addresses.value.filter((v) => v?.address).map((v) => ({ address: v.address, name: v.name })), + }; + } + + private formatAsExternalPayload( + text: string, + html: string | boolean, + attachments: any[], + ): MailMessagePayloadExternal[] { + const payloads: MailMessagePayloadExternal[] = []; + if (text && text.trim()) { + payloads.push({ + id: null, + mimeType: 'text/plain', + filename: null, + attachmentId: null, + content: text, + size: text.length, + }); + } + if (html && typeof html === 'string') { + payloads.push({ + id: null, + mimeType: 'text/html', + filename: null, + attachmentId: null, + content: html.trim(), + size: html.trim().length, + }); + } + if (attachments && attachments.length > 0) { + attachments.forEach((a) => + payloads.push({ + id: a.partId, + mimeType: a.contentType, + filename: a.filename, + attachmentId: a.id, + content: null, + size: a.size, + }), + ); + } + return payloads; + } + + private formatExternalId(boxName: string, id: number | string): string { + return `${boxName}-${id}`; + } + private parseMessageId(externalId: string): number { + return Number(externalId.slice(externalId.lastIndexOf('-') + 1)); + } + + private async appendToFolder( + accountId: number, + mailbox: Mailbox, + settings: MailboxSettingsManual, + type: MailboxFolderType, + mail: Mail.Options, + ): Promise<{ message: string; folderExternalId: string } | null> { + return await this.withConnection({ + email: mailbox.email, + settings: settings, + action: async (connection) => { + const folder = await this.mailboxFolderService.findOne({ accountId, mailboxId: mailbox.id, type }); + if (folder) { + const message = await this.mailMessageBuilder.createRawMessage(mail, 'utf-8'); + connection.append(message, { mailbox: folder.externalId }); + return { message, folderExternalId: folder.externalId }; + } + return null; + }, + }); + } + + async setSeen({ + mailbox, + seen, + messages, + }: { + accountId: number; + mailbox: Mailbox; + seen: boolean; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.changeFlags(mailbox, messages, ['\\Seen'], seen ? 'add' : 'del'); + } else { + throw new NotImplementedException(); + } + } + + private async changeFlags( + mailbox: Mailbox, + groupedMessages: FolderMessages[], + flags: string[], + action: 'add' | 'del', + ): Promise { + return await this.withConnection({ + email: mailbox.email, + mailboxId: mailbox.id, + action: async (connection) => { + for (const { folderId, messageIds } of groupedMessages) { + await this.withBox({ + mailboxId: mailbox.id, + connection, + boxName: folderId, + autoExpunge: true, + action: async () => { + const parsedIds = messageIds.map((m) => this.parseMessageId(m)); + switch (action) { + case 'add': + await connection.addFlags(parsedIds, flags); + break; + case 'del': + await connection.delFlags(parsedIds, flags); + break; + } + }, + }); + } + return true; + }, + onError: () => { + return false; + }, + }); + } + + async trash({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox.accountId, mailbox, messages, MailboxFolderType.Trash); + } else { + throw new NotImplementedException(); + } + } + async untrash({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox.accountId, mailbox, messages, MailboxFolderType.Inbox); + } else { + throw new NotImplementedException(); + } + } + + async spam({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox.accountId, mailbox, messages, MailboxFolderType.Junk); + } else { + throw new NotImplementedException(); + } + } + async unspam({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox.accountId, mailbox, messages, MailboxFolderType.Inbox); + } else { + throw new NotImplementedException(); + } + } + + private async moveMessages( + accountId: number, + mailbox: Mailbox, + groupedMessages: FolderMessages[], + type: MailboxFolderType, + ): Promise { + return await this.withConnection({ + email: mailbox.email, + mailboxId: mailbox.id, + action: async (connection) => { + const toFolder = await this.mailboxFolderService.findOne({ accountId, mailboxId: mailbox.id, type }); + if (toFolder) { + for (const { folderId, messageIds } of groupedMessages) { + const parsedIds = messageIds.map((m) => this.parseMessageId(m).toString()); + await this.withBox({ + mailboxId: mailbox.id, + connection, + boxName: folderId, + autoExpunge: true, + action: async () => { + await connection.moveMessage(parsedIds, toFolder.externalId); + }, + }); + } + } + return true; + }, + onError: () => { + return false; + }, + }); + } +} diff --git a/backend/src/Mailing/common/dto/delete-mailbox-query.ts b/backend/src/Mailing/common/dto/delete-mailbox-query.ts new file mode 100644 index 0000000..3f996b0 --- /dev/null +++ b/backend/src/Mailing/common/dto/delete-mailbox-query.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class DeleteMailboxQuery { + @ApiPropertyOptional({ description: 'Save mailbox mail' }) + @IsOptional() + @IsBoolean() + save?: boolean; +} diff --git a/backend/src/Mailing/common/dto/index.ts b/backend/src/Mailing/common/dto/index.ts new file mode 100644 index 0000000..3115ed1 --- /dev/null +++ b/backend/src/Mailing/common/dto/index.ts @@ -0,0 +1,2 @@ +export * from './delete-mailbox-query'; +export * from './send-mail-message.dto'; diff --git a/backend/src/Mailing/common/dto/send-mail-message.dto.ts b/backend/src/Mailing/common/dto/send-mail-message.dto.ts new file mode 100644 index 0000000..1fcc638 --- /dev/null +++ b/backend/src/Mailing/common/dto/send-mail-message.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +export class SendMailMessageDto { + @ApiProperty() + sentTo: string[]; + + @ApiProperty() + cc: string[] | null; + + @ApiProperty() + bcc: string[] | null; + + @ApiProperty() + replyTo: string | null; + + @ApiProperty() + subject: string | null; + + @ApiProperty() + contentText: string | null; + + @ApiProperty() + contentHtml: string | null; + + @ApiProperty() + replyToMessageId: number | null; + + @ApiProperty() + entityId: number | null; + + @ApiProperty() + @IsOptional() + @IsArray() + fileIds: string[] | null | undefined; +} diff --git a/backend/src/Mailing/common/enums/index.ts b/backend/src/Mailing/common/enums/index.ts new file mode 100644 index 0000000..2b431ee --- /dev/null +++ b/backend/src/Mailing/common/enums/index.ts @@ -0,0 +1,3 @@ +export * from './mailbox-folder-type.enum'; +export * from './mailbox-provider.enum'; +export * from './mailbox-state.enum'; diff --git a/backend/src/Mailing/common/enums/mailbox-folder-type.enum.ts b/backend/src/Mailing/common/enums/mailbox-folder-type.enum.ts new file mode 100644 index 0000000..d76608b --- /dev/null +++ b/backend/src/Mailing/common/enums/mailbox-folder-type.enum.ts @@ -0,0 +1,10 @@ +export enum MailboxFolderType { + Inbox = 'inbox', + Sent = 'sent', + Trash = 'trash', + Drafts = 'drafts', + Junk = 'junk', + Archive = 'archive', + Flagged = 'flagged', + All = 'all', +} diff --git a/backend/src/Mailing/common/enums/mailbox-provider.enum.ts b/backend/src/Mailing/common/enums/mailbox-provider.enum.ts new file mode 100644 index 0000000..9a4e17f --- /dev/null +++ b/backend/src/Mailing/common/enums/mailbox-provider.enum.ts @@ -0,0 +1,4 @@ +export enum MailboxProvider { + Manual = 'manual', + GMail = 'gmail', +} diff --git a/backend/src/Mailing/common/enums/mailbox-state.enum.ts b/backend/src/Mailing/common/enums/mailbox-state.enum.ts new file mode 100644 index 0000000..7440469 --- /dev/null +++ b/backend/src/Mailing/common/enums/mailbox-state.enum.ts @@ -0,0 +1,8 @@ +export enum MailboxState { + Draft = 'draft', + Init = 'init', + Active = 'active', + Inactive = 'inactive', + Deleted = 'deleted', + Sync = 'sync', +} diff --git a/backend/src/Mailing/common/events/index.ts b/backend/src/Mailing/common/events/index.ts new file mode 100644 index 0000000..603e1d9 --- /dev/null +++ b/backend/src/Mailing/common/events/index.ts @@ -0,0 +1,3 @@ +export * from './mail-event-type.enum'; +export * from './mail-message'; +export * from './mailbox'; diff --git a/backend/src/Mailing/common/events/mail-event-type.enum.ts b/backend/src/Mailing/common/events/mail-event-type.enum.ts new file mode 100644 index 0000000..e321820 --- /dev/null +++ b/backend/src/Mailing/common/events/mail-event-type.enum.ts @@ -0,0 +1,6 @@ +export enum MailEventType { + MailboxDeleted = 'mail:mailbox:deleted', + MailMessageDeleted = 'mail:message:deleted', + MailMessageLinked = 'mail:message:linked', + MailMessageReceived = 'mail:message:received', +} diff --git a/backend/src/Mailing/common/events/mail-message/index.ts b/backend/src/Mailing/common/events/mail-message/index.ts new file mode 100644 index 0000000..548d2cf --- /dev/null +++ b/backend/src/Mailing/common/events/mail-message/index.ts @@ -0,0 +1,2 @@ +export * from './mail-message-received.event'; +export * from './mail-message.event'; diff --git a/backend/src/Mailing/common/events/mail-message/mail-message-received.event.ts b/backend/src/Mailing/common/events/mail-message/mail-message-received.event.ts new file mode 100644 index 0000000..7db6a81 --- /dev/null +++ b/backend/src/Mailing/common/events/mail-message/mail-message-received.event.ts @@ -0,0 +1,26 @@ +import { MailMessageEvent } from './mail-message.event'; + +export class MailMessageReceivedEvent extends MailMessageEvent { + isInbox: boolean; + ownerId: number; + messageSubject: string; + messageSnippet: string; + + constructor({ + accountId, + ownerId, + entityId, + messageId, + messageSubject, + messageSnippet, + messageDate, + isInbox, + }: MailMessageReceivedEvent) { + super({ accountId, entityId, messageId, messageDate }); + + this.ownerId = ownerId; + this.messageSubject = messageSubject; + this.messageSnippet = messageSnippet; + this.isInbox = isInbox; + } +} diff --git a/backend/src/Mailing/common/events/mail-message/mail-message.event.ts b/backend/src/Mailing/common/events/mail-message/mail-message.event.ts new file mode 100644 index 0000000..892be47 --- /dev/null +++ b/backend/src/Mailing/common/events/mail-message/mail-message.event.ts @@ -0,0 +1,13 @@ +export class MailMessageEvent { + accountId: number; + entityId: number | null; + messageId: number; + messageDate: string; + + constructor({ accountId, entityId, messageId, messageDate }: MailMessageEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.messageId = messageId; + this.messageDate = messageDate; + } +} diff --git a/backend/src/Mailing/common/events/mailbox/index.ts b/backend/src/Mailing/common/events/mailbox/index.ts new file mode 100644 index 0000000..cf0aa44 --- /dev/null +++ b/backend/src/Mailing/common/events/mailbox/index.ts @@ -0,0 +1 @@ +export * from './mailbox.event'; diff --git a/backend/src/Mailing/common/events/mailbox/mailbox.event.ts b/backend/src/Mailing/common/events/mailbox/mailbox.event.ts new file mode 100644 index 0000000..ab714b0 --- /dev/null +++ b/backend/src/Mailing/common/events/mailbox/mailbox.event.ts @@ -0,0 +1,9 @@ +export class MailboxEvent { + accountId: number; + mailboxId: number; + + constructor({ accountId, mailboxId }: MailboxEvent) { + this.accountId = accountId; + this.mailboxId = mailboxId; + } +} diff --git a/backend/src/Mailing/common/index.ts b/backend/src/Mailing/common/index.ts new file mode 100644 index 0000000..5f7094d --- /dev/null +++ b/backend/src/Mailing/common/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './enums'; +export * from './events'; +export * from './types'; +export * from './utils'; diff --git a/backend/src/Mailing/common/types/email-address.ts b/backend/src/Mailing/common/types/email-address.ts new file mode 100644 index 0000000..4cec2d1 --- /dev/null +++ b/backend/src/Mailing/common/types/email-address.ts @@ -0,0 +1,9 @@ +export interface EmailAddressValue { + address?: string; + name?: string; +} + +export interface EmailAddress { + text: string; + values: EmailAddressValue[]; +} diff --git a/backend/src/Mailing/common/types/folder-messages.ts b/backend/src/Mailing/common/types/folder-messages.ts new file mode 100644 index 0000000..51111cd --- /dev/null +++ b/backend/src/Mailing/common/types/folder-messages.ts @@ -0,0 +1,4 @@ +export interface FolderMessages { + folderId: string; + messageIds: string[]; +} diff --git a/backend/src/Mailing/common/types/imap-sync-info.ts b/backend/src/Mailing/common/types/imap-sync-info.ts new file mode 100644 index 0000000..048730c --- /dev/null +++ b/backend/src/Mailing/common/types/imap-sync-info.ts @@ -0,0 +1,4 @@ +export class ImapSyncInfo { + boxName: string; + uidnext: number; +} diff --git a/backend/src/Mailing/common/types/index.ts b/backend/src/Mailing/common/types/index.ts new file mode 100644 index 0000000..824e8de --- /dev/null +++ b/backend/src/Mailing/common/types/index.ts @@ -0,0 +1,9 @@ +export * from './email-address'; +export * from './folder-messages'; +export * from './imap-sync-info'; +export * from './mail-message-attachment.dto'; +export * from './mail-message-external'; +export * from './mail-message-payload-external'; +export * from './mailbox-folder-external'; +export * from './mailbox-sync-messages'; +export * from './mailbox-sync-result'; diff --git a/backend/src/Mailing/common/types/mail-message-attachment.dto.ts b/backend/src/Mailing/common/types/mail-message-attachment.dto.ts new file mode 100644 index 0000000..116c8df --- /dev/null +++ b/backend/src/Mailing/common/types/mail-message-attachment.dto.ts @@ -0,0 +1,5 @@ +export interface MailMessageAttachment { + mimeType: string; + filename: string; + content: Uint8Array | Buffer; +} diff --git a/backend/src/Mailing/common/types/mail-message-external.ts b/backend/src/Mailing/common/types/mail-message-external.ts new file mode 100644 index 0000000..058f674 --- /dev/null +++ b/backend/src/Mailing/common/types/mail-message-external.ts @@ -0,0 +1,22 @@ +import { EmailAddress } from './email-address'; +import { MailMessagePayloadExternal } from './mail-message-payload-external'; + +export interface MailMessageExternal { + id: string; + threadId: string | null; + snippet: string | null; + sentFrom: EmailAddress | null; + sentTo: EmailAddress | null; + replyTo: EmailAddress | null; + cc: EmailAddress | null; + subject: string | null; + date: Date; + hasAttachment: boolean; + messageId: string | null; + inReplyTo: string | null; + references: string[] | null; + isSeen: boolean; + entityId: number | null; + folders: string[]; + payloads: MailMessagePayloadExternal[]; +} diff --git a/backend/src/Mailing/common/types/mail-message-payload-external.ts b/backend/src/Mailing/common/types/mail-message-payload-external.ts new file mode 100644 index 0000000..aa237f4 --- /dev/null +++ b/backend/src/Mailing/common/types/mail-message-payload-external.ts @@ -0,0 +1,8 @@ +export class MailMessagePayloadExternal { + id: string | null; + mimeType: string; + filename: string | null; + attachmentId: string | null; + content: string | null; + size: number | null; +} diff --git a/backend/src/Mailing/common/types/mailbox-folder-external.ts b/backend/src/Mailing/common/types/mailbox-folder-external.ts new file mode 100644 index 0000000..67c71e0 --- /dev/null +++ b/backend/src/Mailing/common/types/mailbox-folder-external.ts @@ -0,0 +1,10 @@ +import { MailboxFolderType } from '../enums'; + +export interface MailboxFolderExternal { + id: string; + uidValidity?: number | null; + uidNext?: number | null; + name: string; + type?: MailboxFolderType | null; + folders?: MailboxFolderExternal[] | null; +} diff --git a/backend/src/Mailing/common/types/mailbox-sync-messages.ts b/backend/src/Mailing/common/types/mailbox-sync-messages.ts new file mode 100644 index 0000000..1407839 --- /dev/null +++ b/backend/src/Mailing/common/types/mailbox-sync-messages.ts @@ -0,0 +1,7 @@ +import { MailMessageExternal } from './mail-message-external'; + +export interface MailboxSyncMessages { + added?: MailMessageExternal[] | null; + updated?: MailMessageExternal[] | null; + deleted?: string[] | null; +} diff --git a/backend/src/Mailing/common/types/mailbox-sync-result.ts b/backend/src/Mailing/common/types/mailbox-sync-result.ts new file mode 100644 index 0000000..a711305 --- /dev/null +++ b/backend/src/Mailing/common/types/mailbox-sync-result.ts @@ -0,0 +1,9 @@ +import { MailboxFolderExternal } from './mailbox-folder-external'; +import { MailboxSyncMessages } from './mailbox-sync-messages'; + +export interface MailboxSyncResult { + result: boolean; + message?: string | null; + folders?: MailboxFolderExternal[] | null; + messages?: MailboxSyncMessages | null; +} diff --git a/backend/src/Mailing/common/utils/index.ts b/backend/src/Mailing/common/utils/index.ts new file mode 100644 index 0000000..cebf0a2 --- /dev/null +++ b/backend/src/Mailing/common/utils/index.ts @@ -0,0 +1 @@ +export * from './mailbox-folder-type.util'; diff --git a/backend/src/Mailing/common/utils/mailbox-folder-type.util.ts b/backend/src/Mailing/common/utils/mailbox-folder-type.util.ts new file mode 100644 index 0000000..c135870 --- /dev/null +++ b/backend/src/Mailing/common/utils/mailbox-folder-type.util.ts @@ -0,0 +1,78 @@ +/* eslint-disable prettier/prettier */ +import { MailboxFolderType } from '../enums'; + +const SpecialUseToFolderType: Record = { + '\\Inbox': MailboxFolderType.Inbox, + '\\Sent': MailboxFolderType.Sent, + '\\Trash': MailboxFolderType.Trash, + '\\Drafts': MailboxFolderType.Drafts, + '\\Junk': MailboxFolderType.Junk, + '\\Archive': MailboxFolderType.Archive, + '\\All': MailboxFolderType.All, + '\\Flagged': MailboxFolderType.Flagged, +}; + +const CommonNamesMap: Record = { + // Inbox + 'inbox': MailboxFolderType.Inbox, + 'входящие': MailboxFolderType.Inbox, + 'boîte de réception': MailboxFolderType.Inbox, + + // Sent + 'sent': MailboxFolderType.Sent, + 'отправленные': MailboxFolderType.Sent, + 'envoyés': MailboxFolderType.Sent, + + // Trash + 'trash': MailboxFolderType.Trash, + 'корзина': MailboxFolderType.Trash, + 'corbeille': MailboxFolderType.Trash, + + // Drafts + 'draft': MailboxFolderType.Drafts, + 'drafts': MailboxFolderType.Drafts, + 'черновики': MailboxFolderType.Drafts, + 'brouillons': MailboxFolderType.Drafts, + + // Junk + 'junk': MailboxFolderType.Junk, + 'spam': MailboxFolderType.Junk, + 'спам': MailboxFolderType.Junk, + 'courrier indésirable': MailboxFolderType.Junk, + + // Archive + 'archive': MailboxFolderType.Archive, + 'архив': MailboxFolderType.Archive, + 'archives': MailboxFolderType.Archive, + + // All + 'all mail': MailboxFolderType.All, + '[gmail]/all mail': MailboxFolderType.All, + + // Flagged + 'flagged': MailboxFolderType.Flagged, + 'important': MailboxFolderType.Flagged, + 'starred': MailboxFolderType.Flagged, +}; + +export const detectMailboxFolderType = ({ + specialUse, + name, +}: { + specialUse?: string | null; + name?: string; +}): MailboxFolderType | null => { + if (specialUse) { + const normalizedSpecialUse = specialUse.trim(); + const mapped = SpecialUseToFolderType[normalizedSpecialUse]; + if (mapped) return mapped; + } + + if (name) { + const normalizedName = name.trim().toLowerCase(); + const mapped = CommonNamesMap[normalizedName]; + if (mapped) return mapped; + } + + return undefined; +}; diff --git a/backend/src/Mailing/config/index.ts b/backend/src/Mailing/config/index.ts new file mode 100644 index 0000000..e98842c --- /dev/null +++ b/backend/src/Mailing/config/index.ts @@ -0,0 +1 @@ +export * from './mail.config'; diff --git a/backend/src/Mailing/config/mail.config.ts b/backend/src/Mailing/config/mail.config.ts new file mode 100644 index 0000000..2f8f5c6 --- /dev/null +++ b/backend/src/Mailing/config/mail.config.ts @@ -0,0 +1,52 @@ +import { registerAs } from '@nestjs/config'; + +interface Mailing { + host: string; + port: number; + secure: boolean; + user: string; + password: string; + from: string; + replyTo: string; +} + +interface GMail { + apiClientId: string; + apiClientSecret: string; +} + +interface Manual { + searchTimeout: number; + searchBatchSize: number; + partLoadTimeout: number; +} + +export interface MailConfig { + mailing: Mailing; + gmail: GMail; + manual: Manual; +} + +export default registerAs( + 'mail', + (): MailConfig => ({ + mailing: { + host: process.env.MAILING_HOST, + port: parseInt(process.env.MAILING_PORT, 10) || 465, + secure: process.env.MAILING_SECURE === 'on', + user: process.env.MAILING_USER, + password: process.env.MAILING_PASSWORD, + from: process.env.MAILING_FROM, + replyTo: process.env.MAILING_REPLY_TO, + }, + gmail: { + apiClientId: process.env.GMAIL_API_CLIENT_ID, + apiClientSecret: process.env.GMAIL_API_CLIENT_SECRET, + }, + manual: { + searchTimeout: parseInt(process.env.MAIL_MANUAL_SEARCH_TIMEOUT, 10) || 30000, + searchBatchSize: parseInt(process.env.MAIL_MANUAL_SEARCH_BATCH_SIZE, 10) || 100, + partLoadTimeout: parseInt(process.env.MAIL_MANUAL_PART_LOAD_TIMEOUT, 10) || 15000, + }, + }), +); diff --git a/backend/src/Mailing/mail-message-builder/index.ts b/backend/src/Mailing/mail-message-builder/index.ts new file mode 100644 index 0000000..4e9eceb --- /dev/null +++ b/backend/src/Mailing/mail-message-builder/index.ts @@ -0,0 +1 @@ +export * from './mail-message-builder.service'; diff --git a/backend/src/Mailing/mail-message-builder/mail-message-builder.service.ts b/backend/src/Mailing/mail-message-builder/mail-message-builder.service.ts new file mode 100644 index 0000000..2a3bdbd --- /dev/null +++ b/backend/src/Mailing/mail-message-builder/mail-message-builder.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import Mail, { TextEncoding } from 'nodemailer/lib/mailer'; +import MailComposer from 'nodemailer/lib/mail-composer'; + +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { SendMailMessageDto } from '../common'; +import { MailMessage } from '../Model/MailMessage/MailMessage'; + +export const HEADER_ENTITY_ID = 'x-amwork-entityid'; + +@Injectable() +export class MailMessageBuilderService { + async createNodemailerMessage( + fromEmail: string, + fromName: string, + dto: SendMailMessageDto, + replyToMessage: MailMessage | null, + files: StorageFile[] | null, + ): Promise { + const attachments = files + ? files.map((file) => { + return { + filename: file.originalName, + encoding: file.encoding, + contentType: file.mimeType, + content: file.buffer, + }; + }) + : undefined; + const references = replyToMessage?.referencesTo ? replyToMessage.referencesTo : []; + if (replyToMessage?.messageId) { + references.push(replyToMessage.messageId); + } + const headers = dto.entityId ? [{ key: HEADER_ENTITY_ID, value: dto.entityId.toString() }] : undefined; + const options = { + from: { name: fromName, address: fromEmail }, + to: dto.sentTo.join(','), + cc: dto.cc?.join(','), + bcc: dto.bcc?.join(','), + replyTo: dto.replyTo, + inReplyTo: replyToMessage?.messageId, + references: references && references.length > 0 ? references : undefined, + subject: dto.subject, + text: dto.contentText, + html: dto.contentHtml, + textEncoding: 'base64' as TextEncoding, + headers: headers, + attachments, + }; + return options; + } + + async createRawMessage(options: Mail.Options, encoding: BufferEncoding = 'base64url') { + const mail = new MailComposer(options).compile(); + mail['keepBcc'] = true; + return (await mail.build()).toString(encoding); + } +} diff --git a/backend/src/Mailing/mail-message-payload/dto/index.ts b/backend/src/Mailing/mail-message-payload/dto/index.ts new file mode 100644 index 0000000..5f4e2df --- /dev/null +++ b/backend/src/Mailing/mail-message-payload/dto/index.ts @@ -0,0 +1 @@ +export * from './mail-message-payload.dto'; diff --git a/backend/src/Mailing/mail-message-payload/dto/mail-message-payload.dto.ts b/backend/src/Mailing/mail-message-payload/dto/mail-message-payload.dto.ts new file mode 100644 index 0000000..588f56a --- /dev/null +++ b/backend/src/Mailing/mail-message-payload/dto/mail-message-payload.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class MailMessagePayloadDto { + @ApiProperty() + id: number; + + @ApiProperty() + mimeType: string; + + @ApiProperty() + filename: string | null; + + @ApiProperty() + content: string | null; + + @ApiProperty() + size: number | null; + + @ApiProperty() + sortOrder: number; +} diff --git a/backend/src/Mailing/mail-message-payload/entities/index.ts b/backend/src/Mailing/mail-message-payload/entities/index.ts new file mode 100644 index 0000000..eecd98b --- /dev/null +++ b/backend/src/Mailing/mail-message-payload/entities/index.ts @@ -0,0 +1 @@ +export * from './mail-message-payload.entity'; diff --git a/backend/src/Mailing/mail-message-payload/entities/mail-message-payload.entity.ts b/backend/src/Mailing/mail-message-payload/entities/mail-message-payload.entity.ts new file mode 100644 index 0000000..7e16cbb --- /dev/null +++ b/backend/src/Mailing/mail-message-payload/entities/mail-message-payload.entity.ts @@ -0,0 +1,89 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { MailMessagePayloadExternal } from '../../common'; +import { MailMessagePayloadDto } from '../dto'; + +@Entity() +export class MailMessagePayload { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + messageId: number; + + @Column({ nullable: true }) + externalId: string | null; + + @Column() + mimeType: string; + + @Column({ nullable: true }) + filename: string | null; + + @Column({ nullable: true }) + attachment: string | null; + + @Column({ nullable: true }) + content: string | null; + + @Column({ nullable: true }) + size: number | null; + + @Column() + sortOrder: number; + + @Column() + accountId: number; + + constructor( + accountId: number, + messageId: number, + externalId: string | null, + mimeType: string, + filename: string | null, + attachment: string | null, + content: string | null, + size: number | null, + sortOrder: number, + ) { + this.accountId = accountId; + this.messageId = messageId; + this.externalId = externalId; + this.mimeType = mimeType; + this.filename = filename; + this.attachment = attachment; + this.content = content; + this.size = size; + this.sortOrder = sortOrder; + } + + static fromExternal( + accountId: number, + messageId: number, + sortOrder: number, + payload: MailMessagePayloadExternal, + ): MailMessagePayload { + return new MailMessagePayload( + accountId, + messageId, + payload.id, + payload.mimeType, + payload.filename, + payload.attachmentId, + payload.content, + payload.size, + sortOrder, + ); + } + + toDto(): MailMessagePayloadDto { + return { + id: this.id, + mimeType: this.mimeType, + filename: this.filename, + content: this.content, + size: this.size, + sortOrder: this.sortOrder, + }; + } +} diff --git a/backend/src/Mailing/mail-message-payload/index.ts b/backend/src/Mailing/mail-message-payload/index.ts new file mode 100644 index 0000000..5c51fcc --- /dev/null +++ b/backend/src/Mailing/mail-message-payload/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './entities'; +export * from './mail-message-payload.service'; diff --git a/backend/src/Mailing/mail-message-payload/mail-message-payload.service.ts b/backend/src/Mailing/mail-message-payload/mail-message-payload.service.ts new file mode 100644 index 0000000..8e3b0eb --- /dev/null +++ b/backend/src/Mailing/mail-message-payload/mail-message-payload.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { MailMessageAttachment, MailMessagePayloadExternal } from '../common'; +import { MailProviderRegistry } from '../mail-provider'; +import { Mailbox } from '../mailbox'; +import { MailMessage } from '../Model/MailMessage/MailMessage'; + +import { MailMessagePayload } from './entities'; + +@Injectable() +export class MailMessagePayloadService { + constructor( + @InjectRepository(MailMessagePayload) + private readonly repository: Repository, + private readonly mailProviderRegistry: MailProviderRegistry, + ) {} + + async findByMessageId(accountId: number, messageId: number): Promise { + return this.repository.findBy({ accountId, messageId }); + } + + async findByMessageIds(accountId: number, messageIds: number[]): Promise { + return this.repository.findBy({ accountId, messageId: In(messageIds) }); + } + + async getAttachment( + accountId: number, + mailbox: Mailbox, + message: MailMessage, + payloadId: number, + ): Promise { + const payload = await this.repository.findOneBy({ accountId, messageId: message.id, id: payloadId }); + if (payload) { + const provider = this.mailProviderRegistry.get(mailbox.provider); + return provider.getAttachment({ mailbox, message, payload }); + } + + return null; + } + + async processExternalPayloads(accountId: number, messageId: number, payloads: MailMessagePayloadExternal[]) { + let sortOrder = 0; + for (const payload of payloads) { + await this.repository.insert(MailMessagePayload.fromExternal(accountId, messageId, sortOrder, payload)); + ++sortOrder; + } + } +} diff --git a/backend/src/Mailing/mail-provider/decorators/index.ts b/backend/src/Mailing/mail-provider/decorators/index.ts new file mode 100644 index 0000000..dc28767 --- /dev/null +++ b/backend/src/Mailing/mail-provider/decorators/index.ts @@ -0,0 +1 @@ +export * from './mail-integration.decorator'; diff --git a/backend/src/Mailing/mail-provider/decorators/mail-integration.decorator.ts b/backend/src/Mailing/mail-provider/decorators/mail-integration.decorator.ts new file mode 100644 index 0000000..d77eb53 --- /dev/null +++ b/backend/src/Mailing/mail-provider/decorators/mail-integration.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; + +export const MAIL_PROVIDER_KEY = 'mail:provider'; + +export const MailIntegration = (provider: string): ClassDecorator => SetMetadata(MAIL_PROVIDER_KEY, provider); diff --git a/backend/src/Mailing/mail-provider/index.ts b/backend/src/Mailing/mail-provider/index.ts new file mode 100644 index 0000000..8a0185a --- /dev/null +++ b/backend/src/Mailing/mail-provider/index.ts @@ -0,0 +1,3 @@ +export * from './decorators'; +export * from './mail-provider.registry'; +export * from './types'; diff --git a/backend/src/Mailing/mail-provider/mail-provider.registry.ts b/backend/src/Mailing/mail-provider/mail-provider.registry.ts new file mode 100644 index 0000000..9a70634 --- /dev/null +++ b/backend/src/Mailing/mail-provider/mail-provider.registry.ts @@ -0,0 +1,36 @@ +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService } from '@nestjs/core'; + +import { MAIL_PROVIDER_KEY } from './decorators'; +import { MailProvider } from './types'; + +@Injectable() +export class MailProviderRegistry implements OnModuleInit { + private readonly logger = new Logger(MailProviderRegistry.name); + private readonly providers = new Map(); + + constructor(private readonly discoveryService: DiscoveryService) {} + + onModuleInit() { + const providers = this.discoveryService + .getProviders() + .filter((provider) => provider.metatype && Reflect.getMetadata(MAIL_PROVIDER_KEY, provider.metatype)) + .map((provider) => ({ + instance: provider.instance as MailProvider, + type: Reflect.getMetadata(MAIL_PROVIDER_KEY, provider.metatype) as string, + })); + + for (const provider of providers) { + this.providers.set(provider.type, provider.instance); + this.logger.log(`Registered mail provider: ${provider.type}`); + } + } + + get(type: string): MailProvider { + const impl = this.providers.get(type); + if (!impl) { + throw new Error(`Mail provider not found for: ${type}`); + } + return impl; + } +} diff --git a/backend/src/Mailing/mail-provider/types/index.ts b/backend/src/Mailing/mail-provider/types/index.ts new file mode 100644 index 0000000..4f0409b --- /dev/null +++ b/backend/src/Mailing/mail-provider/types/index.ts @@ -0,0 +1 @@ +export * from './mail-provider'; diff --git a/backend/src/Mailing/mail-provider/types/mail-provider.ts b/backend/src/Mailing/mail-provider/types/mail-provider.ts new file mode 100644 index 0000000..9ac83d7 --- /dev/null +++ b/backend/src/Mailing/mail-provider/types/mail-provider.ts @@ -0,0 +1,45 @@ +import { StorageFile } from '@/modules/storage/types'; + +import { + FolderMessages, + MailboxSyncResult, + MailMessageAttachment, + MailMessageExternal, + SendMailMessageDto, +} from '../../common'; +import { Mailbox } from '../../mailbox'; +import { MailMessagePayload } from '../../mail-message-payload'; +import { MailMessage } from '../../Model/MailMessage/MailMessage'; + +export type MailProviderCapability = 'thread'; + +type ActionMessageId = { threadId: string } | FolderMessages[]; + +export interface MailProvider { + isCapable(capability: MailProviderCapability): boolean; + + sync(params: { mailbox: Mailbox; syncFull?: boolean; syncDate?: Date }): Promise; + + getAttachment(params: { + mailbox: Mailbox; + message: MailMessage; + payload: MailMessagePayload; + }): Promise; + + send(params: { + accountId: number; + mailbox: Mailbox; + userName: string; + dto: SendMailMessageDto; + replyToMessage?: MailMessage | null; + attachments: StorageFile[]; + }): Promise; + + setSeen(params: { mailbox: Mailbox; seen: boolean; messages: ActionMessageId }): Promise; + + trash(params: { mailbox: Mailbox; messages: ActionMessageId }): Promise; + untrash(params: { mailbox: Mailbox; messages: ActionMessageId }): Promise; + + spam(params: { mailbox: Mailbox; messages: ActionMessageId }): Promise; + unspam(params: { mailbox: Mailbox; messages: ActionMessageId }): Promise; +} diff --git a/backend/src/Mailing/mailbox-folder/dto/index.ts b/backend/src/Mailing/mailbox-folder/dto/index.ts new file mode 100644 index 0000000..abbb4ca --- /dev/null +++ b/backend/src/Mailing/mailbox-folder/dto/index.ts @@ -0,0 +1 @@ +export * from './mailbox-folder.dto'; diff --git a/backend/src/Mailing/mailbox-folder/dto/mailbox-folder.dto.ts b/backend/src/Mailing/mailbox-folder/dto/mailbox-folder.dto.ts new file mode 100644 index 0000000..10975e1 --- /dev/null +++ b/backend/src/Mailing/mailbox-folder/dto/mailbox-folder.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { MailboxFolderType } from '../../common'; + +export class MailboxFolderDto { + @ApiProperty({ description: 'Mailbox folder ID' }) + @IsNumber() + id: number; + + @ApiProperty({ enum: MailboxFolderType, nullable: true, description: 'Mailbox folder type' }) + @IsOptional() + @IsEnum(MailboxFolderType) + type: MailboxFolderType | null; + + @ApiProperty({ description: 'Mailbox folder name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Unread mail count', nullable: true }) + @IsOptional() + @IsNumber() + unread: number | null; + + @ApiProperty({ description: 'Total mail count', nullable: true }) + @IsOptional() + @IsNumber() + total: number | null; + + @ApiPropertyOptional({ description: 'Child folders', type: [MailboxFolderDto], nullable: true }) + @IsOptional() + folders?: MailboxFolderDto[] | null; +} diff --git a/backend/src/Mailing/mailbox-folder/entities/index.ts b/backend/src/Mailing/mailbox-folder/entities/index.ts new file mode 100644 index 0000000..8d1c94e --- /dev/null +++ b/backend/src/Mailing/mailbox-folder/entities/index.ts @@ -0,0 +1 @@ +export * from './mailbox-folder.entity'; diff --git a/backend/src/Mailing/mailbox-folder/entities/mailbox-folder.entity.ts b/backend/src/Mailing/mailbox-folder/entities/mailbox-folder.entity.ts new file mode 100644 index 0000000..491b7f8 --- /dev/null +++ b/backend/src/Mailing/mailbox-folder/entities/mailbox-folder.entity.ts @@ -0,0 +1,130 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { MailboxFolderExternal, MailboxFolderType } from '../../common'; +import { MailboxFolderDto } from '../dto'; + +@Entity() +export class MailboxFolder { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + mailboxId: number; + + @Column({ nullable: true }) + parentId: number | null; + + @Column() + externalId: string; + + @Column({ nullable: true }) + uidValidity: number | null; + + @Column() + name: string; + + @Column({ nullable: true }) + type: MailboxFolderType | null; + + @Column({ nullable: true }) + unread: number | null; + + @Column({ nullable: true }) + total: number | null; + + constructor( + accountId: number, + mailboxId: number, + parentId: number | null, + externalId: string, + uidValidity: number | null, + name: string, + type: MailboxFolderType | null, + unread?: number | null, + total?: number | null, + ) { + this.accountId = accountId; + this.mailboxId = mailboxId; + this.parentId = parentId; + this.externalId = externalId; + this.uidValidity = uidValidity; + this.name = name; + this.type = type; + this.unread = unread; + this.total = total; + } + + private _folders: MailboxFolder[]; + get folders(): MailboxFolder[] { + return this._folders; + } + set folders(value: MailboxFolder[]) { + this._folders = value; + } + + static fromExternal({ + accountId, + mailboxId, + parentId, + external, + }: { + accountId: number; + mailboxId: number; + parentId?: number | null; + external: MailboxFolderExternal; + }): MailboxFolder { + return new MailboxFolder( + accountId, + mailboxId, + parentId ?? null, + external.id, + external.uidValidity, + external.name, + external.type, + ); + } + + hasChanges(data: { parentId?: number | null } & Partial): boolean { + return ( + (data.parentId !== undefined && data.parentId !== this.parentId) || + (data.id !== undefined && data.id !== this.externalId) || + (data.uidValidity !== undefined && data.uidValidity !== this.uidValidity) || + (data.name !== undefined && data.name !== this.name) || + (data.type !== undefined && data.type !== this.type) + ); + } + + update(data: { parentId?: number | null } & Partial): MailboxFolder { + this.parentId = data.parentId !== undefined ? data.parentId : this.parentId; + this.externalId = data.id !== undefined ? data.id : this.externalId; + this.uidValidity = data.uidValidity !== undefined ? data.uidValidity : this.uidValidity; + this.name = data.name !== undefined ? data.name : this.name; + this.type = data.type !== undefined ? data.type : this.type; + + return this; + } + + toUpdate(): Pick { + return { + parentId: this.parentId, + externalId: this.externalId, + uidValidity: this.uidValidity, + name: this.name, + type: this.type, + }; + } + + toDto(): MailboxFolderDto { + return { + id: this.id, + name: this.name, + type: this.type, + unread: this.unread, + total: this.total, + folders: this.folders?.map((f) => f.toDto()), + }; + } +} diff --git a/backend/src/Mailing/mailbox-folder/index.ts b/backend/src/Mailing/mailbox-folder/index.ts new file mode 100644 index 0000000..ad9a70f --- /dev/null +++ b/backend/src/Mailing/mailbox-folder/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './entities'; +export * from './mailbox-folder.service'; diff --git a/backend/src/Mailing/mailbox-folder/mailbox-folder.service.ts b/backend/src/Mailing/mailbox-folder/mailbox-folder.service.ts new file mode 100644 index 0000000..e58ecd3 --- /dev/null +++ b/backend/src/Mailing/mailbox-folder/mailbox-folder.service.ts @@ -0,0 +1,223 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Not, Repository } from 'typeorm'; + +import { flattenTree } from '@/common'; + +import { detectMailboxFolderType, MailboxFolderExternal, MailboxFolderType } from '../common'; +import { MailMessage } from '../Model/MailMessage/MailMessage'; +import { MailMessageFolder } from '../Model/MailMessage/MailMessageFolder'; + +import { MailboxFolder } from './entities'; + +interface FindFilter { + accountId: number; + mailboxId?: number; + folderId?: number; + type?: MailboxFolderType | MailboxFolderType[]; + externalId?: string | string[]; + messageId?: number; + parentId?: number | null; +} + +@Injectable() +export class MailboxFolderService { + constructor( + @InjectRepository(MailboxFolder) + private readonly repository: Repository, + ) {} + + async findOne(filter: FindFilter): Promise { + return this.createQb(filter).getOne(); + } + + async findMany(filter: FindFilter): Promise { + return this.createQb(filter).orderBy('mf.id', 'ASC').getMany(); + } + + async getHierarchy({ + accountId, + mailboxId, + folderId, + }: { + accountId: number; + mailboxId: number; + folderId?: number | null; + }): Promise { + const folders = await this.findMany({ accountId, mailboxId, parentId: folderId ?? null }); + for (const folder of folders) { + const children = await this.getHierarchy({ accountId, mailboxId, folderId: folder.id }); + folder.folders = children?.length ? children : undefined; + } + return folders; + } + + async processExternal({ + accountId, + mailboxId, + extFolders, + }: { + accountId: number; + mailboxId: number; + extFolders: MailboxFolderExternal[]; + }) { + const extFlatten = flattenTree(extFolders, (item) => item.folders); + this.postprocessTypes(extFlatten); + + const currentFolders = await this.findMany({ accountId, mailboxId }); + const currentFoldersMap = new Map(currentFolders.map((f) => [f.externalId, f])); + + const processedIds = await this.processHierarchy({ + accountId, + mailboxId, + currentFolders, + currentFoldersMap, + extFolders, + }); + + await this.repository.delete({ accountId, mailboxId, id: processedIds.length ? Not(In(processedIds)) : undefined }); + } + + async actualizeCounters({ accountId, mailboxId }: { accountId: number; mailboxId: number }) { + const folders = await this.repository + .createQueryBuilder('mf') + .select('mf.id', 'id') + .addSelect('count(mm.id)', 'total') + .addSelect('count(mm.id) filter (where mm.is_seen = false)', 'unread') + .leftJoin(MailMessageFolder, 'mmf', 'mmf.folder_id = mf.id') + .leftJoin(MailMessage, 'mm', 'mm.id = mmf.message_id') + .where('mf.account_id = :accountId', { accountId }) + .andWhere('mf.mailbox_id = :mailboxId', { mailboxId }) + .groupBy('mf.id') + .getRawMany<{ id: number; total: number; unread: number }>(); + + await Promise.all( + folders.map((folder) => + this.repository.update({ accountId, id: folder.id }, { total: folder.total, unread: folder.unread }), + ), + ); + } + + private createQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('mf') + .where('mf.account_id = :accountId', { accountId: filter.accountId }); + if (filter.folderId) { + qb.andWhere('mf.id = :folderId', { folderId: filter.folderId }); + } + if (filter.mailboxId) { + qb.andWhere('mf.mailbox_id = :mailboxId', { mailboxId: filter.mailboxId }); + } + if (filter.type) { + if (Array.isArray(filter.type)) { + qb.andWhere('mf.type IN (:...types)', { types: filter.type }); + } else { + qb.andWhere('mf.type = :type', { type: filter.type }); + } + } + if (filter.externalId) { + if (Array.isArray(filter.externalId)) { + qb.andWhere('mf.external_id IN (:...externalIds)', { externalIds: filter.externalId }); + } else { + qb.andWhere('mf.external_id = :externalId', { externalId: filter.externalId }); + } + } + if (filter.messageId) { + qb.leftJoin(MailMessageFolder, 'mmf', 'mmf.folder_id = mf.id'); + qb.andWhere('mmf.message_id = :messageId', { messageId: filter.messageId }); + } + if (filter.parentId !== undefined) { + if (filter.parentId === null) { + qb.andWhere('mf.parent_id IS NULL'); + } else { + qb.andWhere('mf.parent_id = :parentId', { parentId: filter.parentId }); + } + } + return qb; + } + + private postprocessTypes(flatten: MailboxFolderExternal[]) { + const types = new Set(flatten.map((f) => f.type).filter(Boolean)); + if (!types.size) { + for (const folder of flatten) { + if (!folder.type) { + const type = detectMailboxFolderType({ name: folder.name }); + if (type && !types.has(type)) { + folder.type = type; + types.add(type); + } + } + } + } + } + + private async processHierarchy({ + accountId, + mailboxId, + currentFolders, + currentFoldersMap, + extFolders, + parentId = null, + }: { + accountId: number; + mailboxId: number; + currentFolders: MailboxFolder[]; + currentFoldersMap: Map; + extFolders: MailboxFolderExternal[]; + parentId?: number | null; + }): Promise { + const processedIds: number[] = []; + for (const extFolder of extFolders) { + let folder = this.findCurrentFolder({ extFolder, currentFolders, currentFoldersMap }); + if (folder) { + if (folder.hasChanges({ parentId, ...extFolder })) { + await this.repository.update( + { accountId, mailboxId, id: folder.id }, + folder.update({ parentId, ...extFolder }).toUpdate(), + ); + } + } else { + folder = await this.repository.save( + MailboxFolder.fromExternal({ accountId, mailboxId, parentId, external: extFolder }), + ); + } + processedIds.push(folder.id); + if (extFolder.folders?.length) { + const childrenIds = await this.processHierarchy({ + accountId, + mailboxId, + currentFolders, + currentFoldersMap, + extFolders: extFolder.folders, + parentId: folder.id, + }); + processedIds.push(...childrenIds); + } + } + return processedIds; + } + + private findCurrentFolder({ + extFolder, + currentFoldersMap, + currentFolders, + }: { + extFolder: MailboxFolderExternal; + currentFoldersMap: Map; + currentFolders: MailboxFolder[]; + }): MailboxFolder | undefined { + const folderById = currentFoldersMap.get(extFolder.id); + + if (!extFolder.uidValidity || (folderById && !folderById.uidValidity)) { + return folderById; + } + + if (folderById?.uidValidity === extFolder.uidValidity) { + return folderById; + } + + const foldersByUidValidity = currentFolders.filter((f) => f.uidValidity === extFolder.uidValidity); + + return foldersByUidValidity.length === 1 ? foldersByUidValidity[0] : null; + } +} diff --git a/backend/src/Mailing/mailbox-signature/dto/create-mailbox-signature.dto.ts b/backend/src/Mailing/mailbox-signature/dto/create-mailbox-signature.dto.ts new file mode 100644 index 0000000..3f7133d --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/dto/create-mailbox-signature.dto.ts @@ -0,0 +1,15 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +import { MailboxSignatureDto } from './mailbox-signature.dto'; + +export class CreateMailboxSignatureDto extends PickType(MailboxSignatureDto, [ + 'name', + 'text', + 'linkedMailboxes', +] as const) { + @ApiPropertyOptional({ description: 'Signature text is HTML' }) + @IsOptional() + @IsBoolean() + isHtml?: boolean; +} diff --git a/backend/src/Mailing/mailbox-signature/dto/index.ts b/backend/src/Mailing/mailbox-signature/dto/index.ts new file mode 100644 index 0000000..0f54272 --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-mailbox-signature.dto'; +export * from './mailbox-signature-filter.dto'; +export * from './mailbox-signature.dto'; +export * from './update-mailbox-signature.dto'; diff --git a/backend/src/Mailing/mailbox-signature/dto/mailbox-signature-filter.dto.ts b/backend/src/Mailing/mailbox-signature/dto/mailbox-signature-filter.dto.ts new file mode 100644 index 0000000..92b7038 --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/dto/mailbox-signature-filter.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class MailboxSignatureFilterDto { + @ApiPropertyOptional({ nullable: true, description: 'Mailbox ID' }) + @IsOptional() + @IsNumber() + mailboxId?: number | null; +} diff --git a/backend/src/Mailing/mailbox-signature/dto/mailbox-signature.dto.ts b/backend/src/Mailing/mailbox-signature/dto/mailbox-signature.dto.ts new file mode 100644 index 0000000..cf9dce1 --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/dto/mailbox-signature.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNumber, IsString } from 'class-validator'; + +export class MailboxSignatureDto { + @ApiProperty({ description: 'Signature ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Signature created by user ID' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ description: 'Signature name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Signature text' }) + @IsString() + text: string; + + @ApiProperty({ description: 'Signature text is HTML' }) + @IsBoolean() + isHtml: boolean; + + @ApiProperty({ description: 'Signature linked mailboxes' }) + @IsArray() + @IsNumber({}, { each: true }) + linkedMailboxes: number[]; +} diff --git a/backend/src/Mailing/mailbox-signature/dto/update-mailbox-signature.dto.ts b/backend/src/Mailing/mailbox-signature/dto/update-mailbox-signature.dto.ts new file mode 100644 index 0000000..488ae7d --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/dto/update-mailbox-signature.dto.ts @@ -0,0 +1,7 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { MailboxSignatureDto } from './mailbox-signature.dto'; + +export class UpdateMailboxSignatureDto extends PartialType( + PickType(MailboxSignatureDto, ['name', 'text', 'isHtml', 'linkedMailboxes'] as const), +) {} diff --git a/backend/src/Mailing/mailbox-signature/entities/index.ts b/backend/src/Mailing/mailbox-signature/entities/index.ts new file mode 100644 index 0000000..ec1840f --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/entities/index.ts @@ -0,0 +1,2 @@ +export * from './mailbox-signature-mailbox.entity'; +export * from './mailbox-signature.entity'; diff --git a/backend/src/Mailing/mailbox-signature/entities/mailbox-signature-mailbox.entity.ts b/backend/src/Mailing/mailbox-signature/entities/mailbox-signature-mailbox.entity.ts new file mode 100644 index 0000000..aacd12b --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/entities/mailbox-signature-mailbox.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class MailboxSignatureMailbox { + @PrimaryColumn() + signatureId: number; + + @PrimaryColumn() + mailboxId: number; + + @Column() + accountId: number; + + constructor(accountId: number, signatureId: number, mailboxId: number) { + this.mailboxId = mailboxId; + this.signatureId = signatureId; + this.accountId = accountId; + } +} diff --git a/backend/src/Mailing/mailbox-signature/entities/mailbox-signature.entity.ts b/backend/src/Mailing/mailbox-signature/entities/mailbox-signature.entity.ts new file mode 100644 index 0000000..7acfa29 --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/entities/mailbox-signature.entity.ts @@ -0,0 +1,69 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { CreateMailboxSignatureDto, MailboxSignatureDto, UpdateMailboxSignatureDto } from '../dto'; +import { MailboxSignatureMailbox } from './mailbox-signature-mailbox.entity'; + +@Entity() +export class MailboxSignature { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + createdBy: number; + + @Column() + name: string; + + @Column() + text: string; + + @Column({ default: false }) + isHtml: boolean; + + @Column() + createdAt: Date; + + constructor(accountId: number, createdBy: number, name: string, text: string, isHtml: boolean, createdAt?: Date) { + this.accountId = accountId; + this.createdBy = createdBy; + this.name = name; + this.text = text; + this.isHtml = isHtml; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _mailboxes: MailboxSignatureMailbox[] = []; + get mailboxes(): MailboxSignatureMailbox[] { + return this._mailboxes; + } + set mailboxes(value: MailboxSignatureMailbox[]) { + this._mailboxes = value; + } + + static create(accountId: number, createdBy: number, dto: CreateMailboxSignatureDto): MailboxSignature { + return new MailboxSignature(accountId, createdBy, dto.name, dto.text, dto.isHtml ?? false); + } + + update(dto: UpdateMailboxSignatureDto): MailboxSignature { + this.name = dto.name ?? this.name; + this.text = dto.text ?? this.text; + this.isHtml = dto.isHtml ?? this.isHtml; + + return this; + } + + toDto(): MailboxSignatureDto { + return { + id: this.id, + createdBy: this.createdBy, + name: this.name, + text: this.text, + isHtml: this.isHtml, + linkedMailboxes: this.mailboxes?.map((m) => m.mailboxId) ?? [], + }; + } +} diff --git a/backend/src/Mailing/mailbox-signature/index.ts b/backend/src/Mailing/mailbox-signature/index.ts new file mode 100644 index 0000000..2968b86 --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './mailbox-signature.controller'; +export * from './mailbox-signature.service'; diff --git a/backend/src/Mailing/mailbox-signature/mailbox-signature.controller.ts b/backend/src/Mailing/mailbox-signature/mailbox-signature.controller.ts new file mode 100644 index 0000000..eb2e919 --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/mailbox-signature.controller.ts @@ -0,0 +1,83 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { + CreateMailboxSignatureDto, + MailboxSignatureDto, + MailboxSignatureFilterDto, + UpdateMailboxSignatureDto, +} from './dto'; +import { MailboxSignatureService } from './mailbox-signature.service'; + +@ApiTags('mailing/signature') +@Controller('mailing/signatures') +@JwtAuthorized() +@TransformToDto() +export class MailboxSignatureController { + constructor(private readonly service: MailboxSignatureService) {} + + @ApiOperation({ summary: 'Create signature for mail', description: 'Create signature for mail' }) + @ApiBody({ type: CreateMailboxSignatureDto, required: true, description: 'Signature data' }) + @ApiCreatedResponse({ description: 'Signature', type: MailboxSignatureDto }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateMailboxSignatureDto) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Get signatures for mail', description: 'Get signatures for accessible mailboxes' }) + @ApiQuery({ name: 'mailboxId', type: Number, required: false, description: 'Mailbox ID' }) + @ApiOkResponse({ description: 'Signatures', type: [MailboxSignatureDto] }) + @Get() + async findMany(@CurrentAuth() { accountId, userId }: AuthData, @Query() filter: MailboxSignatureFilterDto) { + return this.service.findMany({ + accountId, + accessibleUserId: userId, + mailboxId: filter?.mailboxId, + }); + } + + @ApiOperation({ summary: 'Get signature', description: 'Get signature' }) + @ApiParam({ name: 'signatureId', type: Number, required: true, description: 'Signature ID' }) + @ApiOkResponse({ description: 'Signature', type: MailboxSignatureDto }) + @Get(':signatureId') + async findOne(@CurrentAuth() { accountId }: AuthData, @Param('signatureId', ParseIntPipe) signatureId: number) { + return this.service.findOne({ accountId, signatureId }); + } + + @ApiOperation({ summary: 'Update signature for mail', description: 'Update signature for mail' }) + @ApiParam({ name: 'signatureId', type: Number, required: true, description: 'Signature ID' }) + @ApiBody({ type: UpdateMailboxSignatureDto, required: true, description: 'Signature data' }) + @ApiOkResponse({ description: 'Signature', type: MailboxSignatureDto }) + @Put(':signatureId') + async updatePut( + @CurrentAuth() { accountId }: AuthData, + @Param('signatureId', ParseIntPipe) signatureId: number, + @Body() dto: UpdateMailboxSignatureDto, + ) { + return this.service.update({ accountId, signatureId, dto }); + } + + @ApiOperation({ summary: 'Update signature for mail', description: 'Update signature for mail' }) + @ApiParam({ name: 'signatureId', type: Number, required: true, description: 'Signature ID' }) + @ApiBody({ type: UpdateMailboxSignatureDto, required: true, description: 'Signature data' }) + @ApiOkResponse({ description: 'Signature', type: MailboxSignatureDto }) + @Patch(':signatureId') + async updatePatch( + @CurrentAuth() { accountId }: AuthData, + @Param('signatureId', ParseIntPipe) signatureId: number, + @Body() dto: UpdateMailboxSignatureDto, + ) { + return this.service.update({ accountId, signatureId, dto }); + } + + @ApiOperation({ summary: 'Delete signature for mail', description: 'Delete signature for mail' }) + @ApiParam({ name: 'signatureId', type: Number, required: true, description: 'Signature ID' }) + @ApiOkResponse() + @Delete(':signatureId') + async delete(@CurrentAuth() { accountId }: AuthData, @Param('signatureId', ParseIntPipe) signatureId: number) { + return this.service.delete({ accountId, signatureId }); + } +} diff --git a/backend/src/Mailing/mailbox-signature/mailbox-signature.service.ts b/backend/src/Mailing/mailbox-signature/mailbox-signature.service.ts new file mode 100644 index 0000000..cf6f690 --- /dev/null +++ b/backend/src/Mailing/mailbox-signature/mailbox-signature.service.ts @@ -0,0 +1,130 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { MailboxService } from '../mailbox/services'; + +import { CreateMailboxSignatureDto, UpdateMailboxSignatureDto } from './dto'; +import { MailboxSignature, MailboxSignatureMailbox } from './entities'; + +interface FindFilter { + accountId: number; + signatureId?: number; + accessibleUserId?: number; + mailboxId?: number | number[]; +} + +@Injectable() +export class MailboxSignatureService { + constructor( + @InjectRepository(MailboxSignature) + private readonly repositorySignature: Repository, + @InjectRepository(MailboxSignatureMailbox) + private readonly repositoryLink: Repository, + private readonly mailboxService: MailboxService, + ) {} + + async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateMailboxSignatureDto; + }): Promise { + const signature = await this.repositorySignature.save(MailboxSignature.create(accountId, userId, dto)); + + signature.mailboxes = await this.processLinks({ + accountId, + signatureId: signature.id, + mailboxIds: dto.linkedMailboxes, + }); + + return signature; + } + + async findOne(filter: FindFilter): Promise { + return this.createQb(filter).getOne(); + } + async findMany(filter: FindFilter): Promise { + if (!filter.mailboxId && filter.accessibleUserId) { + const mailboxes = await this.mailboxService.findMany({ + accountId: filter.accountId, + accessibleUserId: filter.accessibleUserId, + }); + filter.mailboxId = mailboxes.map((m) => m.id); + } + return this.createQb(filter).orderBy('ms.created_at', 'ASC').getMany(); + } + + async update({ + accountId, + signatureId, + dto, + }: { + accountId: number; + signatureId: number; + dto: UpdateMailboxSignatureDto; + }): Promise { + const signature = await this.findOne({ accountId, signatureId }); + if (!signature) { + throw NotFoundError.withId(MailboxSignature, signatureId); + } + + await this.repositorySignature.save(signature.update(dto)); + + signature.mailboxes = await this.processLinks({ + accountId, + signatureId: signature.id, + mailboxIds: dto.linkedMailboxes, + }); + + return signature; + } + + async delete({ accountId, signatureId }: { accountId: number; signatureId: number }) { + await this.repositorySignature.delete({ accountId, id: signatureId }); + } + + private async processLinks({ + accountId, + signatureId, + mailboxIds, + }: { + accountId: number; + signatureId: number; + mailboxIds: number[] | null; + }): Promise { + await this.repositoryLink.delete({ accountId, signatureId }); + return mailboxIds?.length + ? this.repositoryLink.save( + mailboxIds.map((mailboxId) => new MailboxSignatureMailbox(accountId, signatureId, mailboxId)), + ) + : []; + } + + private createQb(filter: FindFilter) { + const qb = this.repositorySignature + .createQueryBuilder('ms') + .leftJoinAndMapMany('ms.mailboxes', MailboxSignatureMailbox, 'msm', 'msm.signature_id = ms.id') + .where('ms.account_id = :accountId', { accountId: filter.accountId }); + if (filter.signatureId) { + qb.andWhere('ms.id = :signatureId', { signatureId: filter.signatureId }); + } + if (filter.mailboxId) { + if (Array.isArray(filter.mailboxId)) { + if (filter.mailboxId.length) { + qb.andWhere('msm.mailbox_id IN (:...mailboxIds)', { mailboxIds: filter.mailboxId }); + } else { + qb.andWhere('1 = 0'); + } + } else { + qb.andWhere('msm.mailbox_id = :mailboxId', { mailboxId: filter.mailboxId }); + } + } + return qb; + } +} diff --git a/backend/src/Mailing/mailbox/dto/create-mailbox.dto.ts b/backend/src/Mailing/mailbox/dto/create-mailbox.dto.ts new file mode 100644 index 0000000..3debcf6 --- /dev/null +++ b/backend/src/Mailing/mailbox/dto/create-mailbox.dto.ts @@ -0,0 +1,18 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { MailboxDto } from './mailbox.dto'; + +export class CreateMailboxDto extends PickType(MailboxDto, [ + 'ownerId', + 'provider', + 'email', + 'accessibleUserIds', + 'emailsPerDay', + 'entitySettings', +] as const) { + @ApiPropertyOptional({ nullable: true, description: 'Sync days' }) + @IsOptional() + @IsNumber() + syncDays?: number | null; +} diff --git a/backend/src/Mailing/mailbox/dto/index.ts b/backend/src/Mailing/mailbox/dto/index.ts new file mode 100644 index 0000000..ee73217 --- /dev/null +++ b/backend/src/Mailing/mailbox/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-mailbox.dto'; +export * from './mailbox-entity-settings.dto'; +export * from './mailbox.dto'; +export * from './update-mailbox.dto'; diff --git a/backend/src/Mailing/mailbox/dto/mailbox-entity-settings.dto.ts b/backend/src/Mailing/mailbox/dto/mailbox-entity-settings.dto.ts new file mode 100644 index 0000000..323463e --- /dev/null +++ b/backend/src/Mailing/mailbox/dto/mailbox-entity-settings.dto.ts @@ -0,0 +1,44 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class MailboxEntitySettingsDto { + @ApiPropertyOptional({ description: 'Contact entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + contactEntityTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + leadEntityTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead board ID', nullable: true }) + @IsOptional() + @IsNumber() + leadBoardId?: number | null; + + @ApiPropertyOptional({ description: 'Lead stage ID', nullable: true }) + @IsOptional() + @IsNumber() + leadStageId?: number | null; + + @ApiPropertyOptional({ description: 'Lead name', nullable: true }) + @IsOptional() + @IsString() + leadName: string | null; + + @ApiPropertyOptional({ description: 'Lead and Contact responsible user ID', nullable: true }) + @IsOptional() + @IsNumber() + ownerId?: number | null; + + @ApiPropertyOptional({ description: 'Do not create lead if active lead exists', nullable: true }) + @IsOptional() + @IsBoolean() + checkActiveLead?: boolean; + + @ApiPropertyOptional({ description: 'Do not create duplicate contact', nullable: true }) + @IsOptional() + @IsBoolean() + checkDuplicate?: boolean; +} diff --git a/backend/src/Mailing/mailbox/dto/mailbox.dto.ts b/backend/src/Mailing/mailbox/dto/mailbox.dto.ts new file mode 100644 index 0000000..648ee9b --- /dev/null +++ b/backend/src/Mailing/mailbox/dto/mailbox.dto.ts @@ -0,0 +1,47 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEmail, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { MailboxState } from '../../common'; +import { MailboxEntitySettingsDto } from './mailbox-entity-settings.dto'; + +export class MailboxDto { + @ApiProperty({ description: 'Mailbox ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Mailbox email' }) + @IsEmail() + email: string; + + @ApiProperty({ description: 'Mailbox provider' }) + @IsString() + provider: string; + + @ApiPropertyOptional({ nullable: true, description: 'Owner ID' }) + @IsOptional() + @IsNumber() + ownerId?: number | null; + + @ApiPropertyOptional({ type: [Number], description: 'Accessible user IDs', nullable: true }) + @IsOptional() + @IsNumber({}, { each: true }) + accessibleUserIds?: number[] | null; + + @ApiProperty({ enum: MailboxState, description: 'Mailbox state' }) + @IsEnum(MailboxState) + state: MailboxState; + + @ApiProperty({ nullable: true, description: 'Error message' }) + @IsOptional() + @IsString() + errorMessage: string | null; + + @ApiProperty({ nullable: true, description: 'Emails per day' }) + @IsOptional() + @IsNumber() + emailsPerDay?: number | null; + + @ApiPropertyOptional({ type: MailboxEntitySettingsDto, description: 'Entity settings', nullable: true }) + @IsOptional() + entitySettings?: MailboxEntitySettingsDto | null; +} diff --git a/backend/src/Mailing/mailbox/dto/update-mailbox.dto.ts b/backend/src/Mailing/mailbox/dto/update-mailbox.dto.ts new file mode 100644 index 0000000..e7d45d7 --- /dev/null +++ b/backend/src/Mailing/mailbox/dto/update-mailbox.dto.ts @@ -0,0 +1,13 @@ +import { ApiPropertyOptional, PartialType, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { MailboxDto } from './mailbox.dto'; + +export class UpdateMailboxDto extends PartialType( + PickType(MailboxDto, ['ownerId', 'email', 'accessibleUserIds', 'emailsPerDay', 'entitySettings'] as const), +) { + @ApiPropertyOptional({ nullable: true, description: 'Sync days' }) + @IsOptional() + @IsNumber() + syncDays?: number | null; +} diff --git a/backend/src/Mailing/mailbox/entities/index.ts b/backend/src/Mailing/mailbox/entities/index.ts new file mode 100644 index 0000000..568d574 --- /dev/null +++ b/backend/src/Mailing/mailbox/entities/index.ts @@ -0,0 +1,3 @@ +export * from './mailbox-accessible-user.entity'; +export * from './mailbox-entity-settings.entity'; +export * from './mailbox.entity'; diff --git a/backend/src/Mailing/mailbox/entities/mailbox-accessible-user.entity.ts b/backend/src/Mailing/mailbox/entities/mailbox-accessible-user.entity.ts new file mode 100644 index 0000000..a313942 --- /dev/null +++ b/backend/src/Mailing/mailbox/entities/mailbox-accessible-user.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class MailboxAccessibleUser { + @PrimaryColumn() + mailboxId: number; + + @PrimaryColumn() + userId: number; + + @Column() + accountId: number; + + constructor(accountId: number, mailboxId: number, userId: number) { + this.mailboxId = mailboxId; + this.userId = userId; + this.accountId = accountId; + } +} diff --git a/backend/src/Mailing/mailbox/entities/mailbox-entity-settings.entity.ts b/backend/src/Mailing/mailbox/entities/mailbox-entity-settings.entity.ts new file mode 100644 index 0000000..354d8f9 --- /dev/null +++ b/backend/src/Mailing/mailbox/entities/mailbox-entity-settings.entity.ts @@ -0,0 +1,110 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { MailboxEntitySettingsDto } from '../dto'; + +@Entity() +export class MailboxEntitySettings { + @Column() + accountId: number; + + @PrimaryColumn() + mailboxId: number; + + @Column({ nullable: true }) + contactEntityTypeId: number | null; + + @Column({ nullable: true }) + leadEntityTypeId: number | null; + + @Column({ nullable: true }) + leadBoardId: number | null; + + @Column({ nullable: true }) + leadStageId: number | null; + + @Column({ nullable: true }) + leadName: string | null; + + @Column({ nullable: true }) + ownerId: number | null; + + @Column({ default: false }) + checkActiveLead: boolean; + + @Column({ default: false }) + checkDuplicate: boolean; + + constructor( + accountId: number, + mailboxId: number, + contactEntityTypeId: number | null, + leadEntityTypeId: number | null, + leadBoardId: number | null, + leadStageId: number | null, + leadName: string | null, + ownerId: number | null, + checkActiveLead: boolean, + checkDuplicate: boolean, + ) { + this.accountId = accountId; + this.mailboxId = mailboxId; + this.contactEntityTypeId = contactEntityTypeId; + this.leadEntityTypeId = leadEntityTypeId; + this.leadBoardId = leadBoardId; + this.leadStageId = leadStageId; + this.leadName = leadName; + this.ownerId = ownerId; + this.checkActiveLead = checkActiveLead; + this.checkDuplicate = checkDuplicate; + } + + static fromDto({ + accountId, + mailboxId, + dto, + }: { + accountId: number; + mailboxId: number; + dto: MailboxEntitySettingsDto; + }): MailboxEntitySettings { + return new MailboxEntitySettings( + accountId, + mailboxId, + dto.contactEntityTypeId, + dto.leadEntityTypeId, + dto.leadBoardId, + dto.leadStageId, + dto.leadName, + dto.ownerId, + dto.checkActiveLead ?? false, + dto.checkDuplicate ?? false, + ); + } + + update(dto: MailboxEntitySettingsDto): MailboxEntitySettings { + this.contactEntityTypeId = + dto.contactEntityTypeId !== undefined ? dto.contactEntityTypeId : this.contactEntityTypeId; + this.leadEntityTypeId = dto.leadEntityTypeId !== undefined ? dto.leadEntityTypeId : this.leadEntityTypeId; + this.leadBoardId = dto.leadBoardId !== undefined ? dto.leadBoardId : this.leadBoardId; + this.leadStageId = dto.leadStageId !== undefined ? dto.leadStageId : this.leadStageId; + this.leadName = dto.leadName !== undefined ? dto.leadName : this.leadName; + this.ownerId = dto.ownerId !== undefined ? dto.ownerId : this.ownerId; + this.checkActiveLead = dto.checkActiveLead !== undefined ? dto.checkActiveLead : this.checkActiveLead; + this.checkDuplicate = dto.checkDuplicate !== undefined ? dto.checkDuplicate : this.checkDuplicate; + + return this; + } + + toDto(): MailboxEntitySettingsDto { + return { + contactEntityTypeId: this.contactEntityTypeId, + leadEntityTypeId: this.leadEntityTypeId, + leadBoardId: this.leadBoardId, + leadStageId: this.leadStageId, + leadName: this.leadName, + ownerId: this.ownerId, + checkActiveLead: this.checkActiveLead, + checkDuplicate: this.checkDuplicate, + }; + } +} diff --git a/backend/src/Mailing/mailbox/entities/mailbox.entity.ts b/backend/src/Mailing/mailbox/entities/mailbox.entity.ts new file mode 100644 index 0000000..8e43089 --- /dev/null +++ b/backend/src/Mailing/mailbox/entities/mailbox.entity.ts @@ -0,0 +1,126 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { MailboxProvider, MailboxState } from '../../common'; + +import { CreateMailboxDto, MailboxDto, UpdateMailboxDto } from '../dto'; +import { MailboxAccessibleUser } from './mailbox-accessible-user.entity'; +import { MailboxEntitySettings } from './mailbox-entity-settings.entity'; + +const Default = { + emailsPerDay: 100, +}; + +@Entity() +export class Mailbox { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + email: string; + + @Column() + provider: string; + + @Column() + ownerId: number | null; + + @Column() + state: MailboxState; + + @Column({ nullable: true }) + errorMessage: string | null; + + @Column() + emailsPerDay: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + @Column({ nullable: true }) + lastActiveAt: Date | null; + + constructor( + accountId: number, + email: string, + provider: string, + ownerId: number | null, + state: MailboxState, + emailsPerDay: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.email = email; + this.provider = provider; + this.ownerId = ownerId; + this.state = state; + this.emailsPerDay = emailsPerDay; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _accessibleUsers: MailboxAccessibleUser[]; + get accessibleUsers(): MailboxAccessibleUser[] { + return this._accessibleUsers; + } + set accessibleUsers(value: MailboxAccessibleUser[]) { + this._accessibleUsers = value; + } + + private _entitySettings: MailboxEntitySettings | null; + get entitySettings(): MailboxEntitySettings | null { + return this._entitySettings; + } + set entitySettings(value: MailboxEntitySettings | null) { + this._entitySettings = value; + } + + static fromDto({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateMailboxDto & { state?: MailboxState }; + }): Mailbox { + return new Mailbox( + accountId, + dto.email, + dto.provider ?? Mailbox.getMailboxProvider(dto.email), + dto.ownerId ?? userId, + dto.state ?? MailboxState.Draft, + dto.emailsPerDay ?? Default.emailsPerDay, + ); + } + + update(data: UpdateMailboxDto & { state?: MailboxState }): Mailbox { + this.email = data.email ?? this.email; + this.ownerId = data.ownerId ?? this.ownerId; + this.state = data.state ?? this.state; + this.emailsPerDay = data.emailsPerDay ?? this.emailsPerDay; + + return this; + } + + toDto(): MailboxDto { + return { + id: this.id, + email: this.email, + provider: this.provider, + ownerId: this.ownerId, + state: this.state, + errorMessage: this.errorMessage, + emailsPerDay: this.emailsPerDay, + accessibleUserIds: this.accessibleUsers?.map((user) => user.userId), + entitySettings: this.entitySettings?.toDto(), + }; + } + + private static getMailboxProvider(email: string): MailboxProvider { + return email.includes('gmail.com') ? MailboxProvider.GMail : MailboxProvider.Manual; + } +} diff --git a/backend/src/Mailing/mailbox/index.ts b/backend/src/Mailing/mailbox/index.ts new file mode 100644 index 0000000..f1cea2f --- /dev/null +++ b/backend/src/Mailing/mailbox/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './mailbox.controller'; +export * from './services'; diff --git a/backend/src/Mailing/mailbox/mailbox.controller.ts b/backend/src/Mailing/mailbox/mailbox.controller.ts new file mode 100644 index 0000000..091b9bc --- /dev/null +++ b/backend/src/Mailing/mailbox/mailbox.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CreateMailboxDto, MailboxDto, UpdateMailboxDto } from './dto'; +import { MailboxService } from './services'; + +@ApiTags('mailing/mailbox/settings') +@Controller('mailing/settings/mailboxes') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class MailboxController { + constructor(private readonly service: MailboxService) {} + + @Post() + @ApiCreatedResponse({ description: 'Mailbox', type: MailboxDto }) + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateMailboxDto) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOkResponse({ description: 'Mailboxes', type: [MailboxDto] }) + @AuthDataPrefetch({ user: true }) + @Get() + async findMany(@CurrentAuth() { accountId, user }: AuthData) { + return this.service.findMany({ accountId, ownerId: user.isAdmin ? undefined : user.id }); + } + + @ApiOkResponse({ description: 'Mailbox', type: MailboxDto }) + @AuthDataPrefetch({ user: true }) + @Get(':mailboxId') + async findOne(@CurrentAuth() { accountId, user }: AuthData, @Param('mailboxId', ParseIntPipe) mailboxId: number) { + return this.service.findOne({ accountId, mailboxId, ownerId: user.isAdmin ? undefined : user.id }); + } + + @ApiOkResponse({ description: 'Mailbox', type: MailboxDto }) + @Put(':mailboxId') + async updatePut( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Body() dto: UpdateMailboxDto, + ) { + return this.service.update({ accountId, user, mailboxId, dto }); + } + + @ApiOkResponse({ description: 'Mailbox', type: MailboxDto }) + @Patch(':mailboxId') + async updatePatch( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Body() dto: UpdateMailboxDto, + ) { + return this.service.update({ accountId, user, mailboxId, dto }); + } + + @Delete(':mailboxId') + async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Query('save') save: string, + ) { + await this.service.delete({ accountId, user, mailboxId, softDelete: save === 'true' }); + } +} diff --git a/backend/src/Mailing/mailbox/services/index.ts b/backend/src/Mailing/mailbox/services/index.ts new file mode 100644 index 0000000..2215f67 --- /dev/null +++ b/backend/src/Mailing/mailbox/services/index.ts @@ -0,0 +1,5 @@ +export * from './mailbox-accessible-user.service'; +export * from './mailbox-entity-settings.service'; +export * from './mailbox-lock.service'; +export * from './mailbox.handler'; +export * from './mailbox.service'; diff --git a/backend/src/Mailing/mailbox/services/mailbox-accessible-user.service.ts b/backend/src/Mailing/mailbox/services/mailbox-accessible-user.service.ts new file mode 100644 index 0000000..aa4d337 --- /dev/null +++ b/backend/src/Mailing/mailbox/services/mailbox-accessible-user.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { MailboxAccessibleUser } from '../entities'; + +@Injectable() +export class MailboxAccessibleUserService { + constructor( + @InjectRepository(MailboxAccessibleUser) + private readonly repository: Repository, + ) {} + + async create({ + accountId, + mailboxId, + userIds, + }: { + accountId: number; + mailboxId: number; + userIds: number[]; + }): Promise { + return this.repository.save(userIds.map((userId) => new MailboxAccessibleUser(accountId, mailboxId, userId))); + } + + async findMany({ accountId, mailboxId }: { accountId: number; mailboxId: number }): Promise { + return this.repository + .createQueryBuilder('mau') + .where('mau.account_id = :accountId', { accountId }) + .andWhere('mau.mailbox_id = :mailboxId', { mailboxId }) + .getMany(); + } + + async update({ + accountId, + mailboxId, + currentUsers, + userIds, + }: { + accountId: number; + mailboxId: number; + currentUsers: MailboxAccessibleUser[]; + userIds: number[]; + }): Promise { + const addUsers = userIds.filter((id) => !currentUsers.some((user) => user.userId === id)); + const removeUsers = currentUsers.filter((user) => !userIds.some((id) => id === user.userId)); + + currentUsers.push(...(await this.create({ accountId, mailboxId, userIds: addUsers }))); + + if (removeUsers.length > 0) { + await this.repository.remove(removeUsers); + } + + return currentUsers.filter((user) => !removeUsers.some((u) => u.userId === user.userId)); + } +} diff --git a/backend/src/Mailing/mailbox/services/mailbox-entity-settings.service.ts b/backend/src/Mailing/mailbox/services/mailbox-entity-settings.service.ts new file mode 100644 index 0000000..d4587a5 --- /dev/null +++ b/backend/src/Mailing/mailbox/services/mailbox-entity-settings.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { MailboxEntitySettingsDto } from '../dto'; +import { MailboxEntitySettings } from '../entities'; + +@Injectable() +export class MailboxEntitySettingsService { + constructor( + @InjectRepository(MailboxEntitySettings) + private readonly repository: Repository, + ) {} + + async create({ + accountId, + mailboxId, + dto, + }: { + accountId: number; + mailboxId: number; + dto: MailboxEntitySettingsDto; + }): Promise { + return this.repository.save(MailboxEntitySettings.fromDto({ accountId, mailboxId, dto })); + } + + async findOne({ + accountId, + mailboxId, + }: { + accountId: number; + mailboxId: number; + }): Promise { + return this.repository.findOneBy({ accountId, mailboxId }); + } + + async update({ + accountId, + mailboxId, + dto, + }: { + accountId: number; + mailboxId: number; + dto: MailboxEntitySettingsDto; + }): Promise { + const settings = await this.findOne({ accountId, mailboxId }); + if (settings) { + await this.repository.save(settings.update(dto)); + return settings; + } else { + return this.create({ accountId, mailboxId, dto }); + } + } + + async updateUser({ + accountId, + userId, + newUserId, + }: { + accountId: number; + userId: number; + newUserId?: number | null; + }): Promise { + await this.repository.update({ accountId, ownerId: userId }, { ownerId: newUserId ?? null }); + } + + async delete({ accountId, mailboxId }: { accountId: number; mailboxId: number }): Promise { + await this.repository.delete({ accountId, mailboxId }); + } +} diff --git a/backend/src/Mailing/mailbox/services/mailbox-lock.service.ts b/backend/src/Mailing/mailbox/services/mailbox-lock.service.ts new file mode 100644 index 0000000..5ead771 --- /dev/null +++ b/backend/src/Mailing/mailbox/services/mailbox-lock.service.ts @@ -0,0 +1,41 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter } from 'events'; + +@Injectable() +export class MailboxLockService { + private mailboxLocks = new Map(); + private mailboxEvents = new Map(); + + lock(mailboxId: number): boolean { + if (this.mailboxLocks.get(mailboxId)) { + return false; + } + this.mailboxLocks.set(mailboxId, true); + return true; + } + + unlock(mailboxId: number): void { + this.mailboxLocks.set(mailboxId, false); + this.emitUnlockEvent(mailboxId); + } + + private emitUnlockEvent(mailboxId: number): void { + const eventEmitter = this.mailboxEvents.get(mailboxId); + if (eventEmitter) { + eventEmitter.emit('unlock'); + this.mailboxEvents.delete(mailboxId); + } + } + + async waitForUnlock(mailboxId: number): Promise { + if (!this.mailboxLocks.get(mailboxId)) { + return; + } + + return new Promise((resolve) => { + const eventEmitter = this.mailboxEvents.get(mailboxId) ?? new EventEmitter(); + eventEmitter.once('unlock', resolve); + this.mailboxEvents.set(mailboxId, eventEmitter); + }); + } +} diff --git a/backend/src/Mailing/mailbox/services/mailbox.handler.ts b/backend/src/Mailing/mailbox/services/mailbox.handler.ts new file mode 100644 index 0000000..92ad009 --- /dev/null +++ b/backend/src/Mailing/mailbox/services/mailbox.handler.ts @@ -0,0 +1,43 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; + +import { MailboxState } from '../../common'; +import { MailboxService } from './mailbox.service'; +import { MailboxEntitySettingsService } from './mailbox-entity-settings.service'; + +@Injectable() +export class MailboxHandler { + private readonly logger = new Logger(MailboxHandler.name); + constructor( + private readonly mailboxService: MailboxService, + private readonly mailboxEntitySettingsService: MailboxEntitySettingsService, + ) {} + + @Cron(CronExpression.EVERY_30_SECONDS) + async synchronizeActive() { + if (process.env.SCHEDULE_MAIL_ACTIVE_DISABLE === 'true') return; + this.logger.log('Before: Synchronize active mailboxes'); + const count = await this.mailboxService.synchronize(MailboxState.Active); + this.logger.log(`After: Synchronize active mailboxes. Processed: ${count}`); + } + + @Cron(CronExpression.EVERY_5_MINUTES) + async synchronizeInactive() { + if (process.env.SCHEDULE_MAIL_INACTIVE_DISABLE === 'true') return; + this.logger.log('Before: Synchronize inactive mailboxes'); + const count = await this.mailboxService.synchronize(MailboxState.Inactive); + this.logger.log(`After: Synchronize inactive mailboxes. Processed: ${count}`); + } + + @OnEvent(IamEventType.UserDeleted, { async: true }) + async onUserDeleted(event: UserDeletedEvent) { + await this.mailboxEntitySettingsService.updateUser({ + accountId: event.accountId, + userId: event.userId, + newUserId: event.newUserId, + }); + } +} diff --git a/backend/src/Mailing/mailbox/services/mailbox.service.ts b/backend/src/Mailing/mailbox/services/mailbox.service.ts new file mode 100644 index 0000000..9b6253e --- /dev/null +++ b/backend/src/Mailing/mailbox/services/mailbox.service.ts @@ -0,0 +1,296 @@ +import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { DateUtil, ForbiddenError, NotFoundError } from '@/common'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { MailboxEvent, MailboxState, MailboxSyncResult, MailEventType } from '../../common'; +import { MailProviderRegistry } from '../../mail-provider'; +import { MailboxFolderService } from '../../mailbox-folder'; +import { MailMessageService } from '../../Service/MailMessage/MailMessageService'; + +import { CreateMailboxDto, UpdateMailboxDto } from '../dto'; +import { Mailbox, MailboxAccessibleUser, MailboxEntitySettings } from '../entities'; +import { MailboxLockService } from './mailbox-lock.service'; +import { MailboxAccessibleUserService } from './mailbox-accessible-user.service'; +import { MailboxEntitySettingsService } from './mailbox-entity-settings.service'; + +interface FindFilter { + accountId: number; + mailboxId?: number; + ownerId?: number; + state?: MailboxState | MailboxState[]; + accessibleUserId?: number; + provider?: string; +} + +@Injectable() +export class MailboxService { + private readonly logger = new Logger(MailboxService.name); + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Mailbox) + private readonly repository: Repository, + private readonly mailboxLock: MailboxLockService, + private readonly mailboxAccessibleUserService: MailboxAccessibleUserService, + private readonly mailboxEntitySettingsService: MailboxEntitySettingsService, + private readonly mailProviderRegistry: MailProviderRegistry, + private readonly mailboxFolderService: MailboxFolderService, + @Inject(forwardRef(() => MailMessageService)) + private readonly mailMessageService: MailMessageService, + ) {} + + async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateMailboxDto & { state?: MailboxState }; + }): Promise { + const mailbox = await this.repository.save(Mailbox.fromDto({ accountId, userId, dto })); + + if (mailbox.state !== MailboxState.Draft) { + this.syncMailboxWithLock({ + accountId, + mailboxId: mailbox.id, + syncFull: mailbox.state === MailboxState.Init, + syncDays: dto.syncDays, + }); + } + + return mailbox; + } + + async findOne(filter: FindFilter): Promise { + const mailbox = await this.createQb(filter).getOne(); + + if (mailbox && filter.accessibleUserId) { + return mailbox.ownerId === filter.accessibleUserId || + mailbox.accessibleUsers?.some((au) => au.userId === filter.accessibleUserId) + ? mailbox + : null; + } else { + return mailbox; + } + } + async findMany(filter: FindFilter): Promise { + const mailboxes = await this.createQb(filter).orderBy('mailbox.created_at').getMany(); + return filter.accessibleUserId + ? mailboxes.filter( + (mb) => + mb.ownerId === filter.accessibleUserId || + mb.accessibleUsers?.some((au) => au.userId === filter.accessibleUserId), + ) + : mailboxes; + } + + async update({ + accountId, + user, + mailboxId, + dto, + }: { + accountId: number; + user?: User; + mailboxId: number; + dto: UpdateMailboxDto; + }): Promise { + const mailbox = await this.findOne({ accountId, mailboxId }); + if (!mailbox) { + throw NotFoundError.withId(Mailbox, mailboxId); + } + if (user && !user.isAdmin && mailbox.ownerId !== user.id) { + throw new ForbiddenError(); + } + + if (mailbox.state === MailboxState.Draft) { + mailbox.update({ state: MailboxState.Init }); + } + await this.repository.save(mailbox.update(dto)); + + if (dto.accessibleUserIds) { + mailbox.accessibleUsers = await this.mailboxAccessibleUserService.update({ + accountId, + mailboxId, + currentUsers: mailbox.accessibleUsers ?? [], + userIds: dto.accessibleUserIds, + }); + } + if (dto.entitySettings === null) { + await this.mailboxEntitySettingsService.delete({ accountId, mailboxId: mailbox.id }); + mailbox.entitySettings = null; + } else if (dto.entitySettings) { + mailbox.entitySettings = await this.mailboxEntitySettingsService.update({ + accountId, + mailboxId: mailbox.id, + dto: dto.entitySettings, + }); + } + + this.syncMailboxWithLock({ + accountId, + mailboxId: mailbox.id, + syncFull: mailbox.state === MailboxState.Init, + syncDays: dto.syncDays, + }); + + return mailbox; + } + + async delete({ + accountId, + user, + mailboxId, + softDelete, + }: { + accountId: number; + user: User; + mailboxId: number; + softDelete: boolean; + }) { + if (!user.isAdmin) { + const mailbox = await this.findOne({ accountId, mailboxId }); + if (!mailbox || mailbox.ownerId !== user.id) { + throw new ForbiddenError(); + } + } + if (softDelete) { + await this.repository.update({ id: mailboxId, accountId }, { state: MailboxState.Deleted }); + } else { + await this.mailboxLock.waitForUnlock(mailboxId); + await this.repository.delete({ id: mailboxId, accountId }); + } + this.eventEmitter.emit(MailEventType.MailboxDeleted, new MailboxEvent({ accountId, mailboxId })); + } + + async synchronize(state: MailboxState): Promise { + const mailboxes = await this.findForSync(state); + mailboxes.forEach(async ({ accountId, mailboxId }) => { + this.syncMailboxWithLock({ accountId, mailboxId }); + }); + return mailboxes.length; + } + + private createQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('mailbox') + .leftJoinAndMapMany('mailbox.accessibleUsers', MailboxAccessibleUser, 'mau', 'mau.mailbox_id = mailbox.id') + .leftJoinAndMapOne('mailbox.entitySettings', MailboxEntitySettings, 'mes', 'mes.mailbox_id = mailbox.id') + .where('mailbox.account_id = :accountId', { accountId: filter.accountId }); + + if (filter.mailboxId) { + qb.andWhere('mailbox.id = :mailboxId', { mailboxId: filter.mailboxId }); + } + if (filter.ownerId) { + qb.andWhere('mailbox.owner_id = :ownerId', { ownerId: filter.ownerId }); + } + if (filter.state) { + if (Array.isArray(filter.state)) { + qb.andWhere('mailbox.state IN (:...states)', { states: filter.state }); + } else { + qb.andWhere('mailbox.state = :state', { state: filter.state }); + } + } + if (filter.provider) { + qb.andWhere('mailbox.provider = :provider', { provider: filter.provider }); + } + + return qb; + } + + private async updateState({ + accountId, + mailboxId, + state, + errorMessage, + lastActiveAt, + }: { + accountId: number; + mailboxId: number; + state: MailboxState; + errorMessage?: string; + lastActiveAt?: Date; + }) { + await this.repository.update( + { accountId, id: mailboxId }, + { state, errorMessage: errorMessage ?? null, lastActiveAt }, + ); + } + + private async findForSync(state: MailboxState): Promise<{ accountId: number; mailboxId: number }[]> { + return this.repository + .createQueryBuilder('mailbox') + .select('mailbox.id', 'mailboxId') + .addSelect('mailbox.account_id', 'accountId') + .where('mailbox.state = :state', { state }) + .getRawMany<{ accountId: number; mailboxId: number }>(); + } + + private async syncMailboxWithLock({ + accountId, + mailboxId, + syncFull = false, + syncDays = null, + }: { + accountId: number; + mailboxId: number; + syncFull?: boolean | null; + syncDays?: number | null; + }) { + if (!this.mailboxLock.lock(mailboxId)) { + return; + } + + try { + const mailbox = await this.findOne({ accountId, mailboxId }); + const { result, message, folders, messages } = await this.syncMailbox({ mailbox, syncFull, syncDays }); + if (result && folders?.length) + await this.mailboxFolderService.processExternal({ accountId, mailboxId, extFolders: folders }); + if (result && (messages?.added?.length || messages?.updated?.length || messages?.deleted?.length)) + await this.mailMessageService.processExternalMessages({ + accountId, + mailbox, + added: messages.added, + updated: messages.updated, + deleted: messages.deleted, + }); + await this.updateState({ + accountId, + mailboxId, + state: result ? MailboxState.Active : MailboxState.Inactive, + errorMessage: message, + lastActiveAt: result ? DateUtil.now() : undefined, + }); + } catch (e) { + const error = e as Error; + await this.updateState({ + accountId, + mailboxId, + state: MailboxState.Inactive, + errorMessage: error?.message, + }); + this.logger.error(`Mailbox ${mailboxId} sync error: ${error?.message}`, error?.stack); + } finally { + this.mailboxLock.unlock(mailboxId); + } + } + + private async syncMailbox({ + mailbox, + syncFull = false, + syncDays = null, + }: { + mailbox: Mailbox; + syncFull?: boolean | null; + syncDays?: number | null; + }): Promise { + const provider = this.mailProviderRegistry.get(mailbox.provider); + const syncDate = syncDays ? DateUtil.sub(DateUtil.now(), { days: syncDays }) : null; + + return provider.sync({ mailbox, syncFull, syncDate }); + } +} diff --git a/backend/src/Mailing/subscription/dto/index.ts b/backend/src/Mailing/subscription/dto/index.ts new file mode 100644 index 0000000..4a3cfad --- /dev/null +++ b/backend/src/Mailing/subscription/dto/index.ts @@ -0,0 +1 @@ +export * from './unsubscribe.dto'; diff --git a/backend/src/Mailing/subscription/dto/unsubscribe.dto.ts b/backend/src/Mailing/subscription/dto/unsubscribe.dto.ts new file mode 100644 index 0000000..f27867c --- /dev/null +++ b/backend/src/Mailing/subscription/dto/unsubscribe.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class UnsubscribeDto { + @ApiProperty({ description: 'Entity ID' }) + @IsNumber() + entityId: number; +} diff --git a/backend/src/Mailing/subscription/index.ts b/backend/src/Mailing/subscription/index.ts new file mode 100644 index 0000000..d6c2b61 --- /dev/null +++ b/backend/src/Mailing/subscription/index.ts @@ -0,0 +1,2 @@ +export * from './dto'; +export * from './unsubscribe.controller'; diff --git a/backend/src/Mailing/subscription/unsubscribe.controller.ts b/backend/src/Mailing/subscription/unsubscribe.controller.ts new file mode 100644 index 0000000..6eb0686 --- /dev/null +++ b/backend/src/Mailing/subscription/unsubscribe.controller.ts @@ -0,0 +1,15 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { ApiAccessRequired } from '@/modules/iam/common/decorators/api-access-required.decorator'; +import { UnsubscribeDto } from './dto'; + +@ApiExcludeController(true) +@Controller() +@ApiAccessRequired() +export class UnsubscribeController { + @Post('mailing/subscription/unsubscribe') + async unsubscribe(@Body() dto: UnsubscribeDto): Promise { + return !!dto.entityId; + } +} diff --git a/backend/src/Mailing/system-mailing/dto/become-partner-feedback.dto.ts b/backend/src/Mailing/system-mailing/dto/become-partner-feedback.dto.ts new file mode 100644 index 0000000..150ddf7 --- /dev/null +++ b/backend/src/Mailing/system-mailing/dto/become-partner-feedback.dto.ts @@ -0,0 +1,11 @@ +export class BecomePartnerFeedback { + firstName: string; + lastName: string; + phone: string; + email: string; + company: string; + country: string; + website: string; + employees: string; + comment: string; +} diff --git a/backend/src/Mailing/system-mailing/dto/contact-us-feedback.dto.ts b/backend/src/Mailing/system-mailing/dto/contact-us-feedback.dto.ts new file mode 100644 index 0000000..4752495 --- /dev/null +++ b/backend/src/Mailing/system-mailing/dto/contact-us-feedback.dto.ts @@ -0,0 +1,6 @@ +export class ContactUsFeedback { + name: string; + phone: string; + email: string; + comment: string; +} diff --git a/backend/src/Mailing/system-mailing/dto/index.ts b/backend/src/Mailing/system-mailing/dto/index.ts new file mode 100644 index 0000000..24a8acc --- /dev/null +++ b/backend/src/Mailing/system-mailing/dto/index.ts @@ -0,0 +1,5 @@ +export * from './become-partner-feedback.dto'; +export * from './contact-us-feedback.dto'; +export * from './send-feedback.dto'; +export * from './trial-expired-feedback.dto'; +export * from './user-limit-feedback.dto'; diff --git a/backend/src/Mailing/system-mailing/dto/send-feedback.dto.ts b/backend/src/Mailing/system-mailing/dto/send-feedback.dto.ts new file mode 100644 index 0000000..f5ad434 --- /dev/null +++ b/backend/src/Mailing/system-mailing/dto/send-feedback.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { FeedbackType } from '../enums'; +import { TrialExpiredFeedback } from './trial-expired-feedback.dto'; +import { UserLimitFeedback } from './user-limit-feedback.dto'; +import { BecomePartnerFeedback } from './become-partner-feedback.dto'; +import { ContactUsFeedback } from './contact-us-feedback.dto'; + +type FeedbackPayload = TrialExpiredFeedback | UserLimitFeedback | BecomePartnerFeedback | ContactUsFeedback; + +export class SendFeedbackDto { + @ApiProperty() + type: FeedbackType; + + @ApiProperty() + payload: FeedbackPayload; +} diff --git a/backend/src/Mailing/system-mailing/dto/trial-expired-feedback.dto.ts b/backend/src/Mailing/system-mailing/dto/trial-expired-feedback.dto.ts new file mode 100644 index 0000000..25d0284 --- /dev/null +++ b/backend/src/Mailing/system-mailing/dto/trial-expired-feedback.dto.ts @@ -0,0 +1,8 @@ +export class TrialExpiredFeedback { + name: string; + phone: string; + email: string; + userNumber: string; + subscribe: string; + plan: string; +} diff --git a/backend/src/Mailing/system-mailing/dto/user-limit-feedback.dto.ts b/backend/src/Mailing/system-mailing/dto/user-limit-feedback.dto.ts new file mode 100644 index 0000000..8679899 --- /dev/null +++ b/backend/src/Mailing/system-mailing/dto/user-limit-feedback.dto.ts @@ -0,0 +1,6 @@ +export class UserLimitFeedback { + name: string; + phone: string; + email: string; + userNumber: string; +} diff --git a/backend/src/Mailing/system-mailing/enums/feedback-type.enum.ts b/backend/src/Mailing/system-mailing/enums/feedback-type.enum.ts new file mode 100644 index 0000000..e9131f2 --- /dev/null +++ b/backend/src/Mailing/system-mailing/enums/feedback-type.enum.ts @@ -0,0 +1,6 @@ +export enum FeedbackType { + TrialExpired = 'trial_expired', + UserLimit = 'user_limit', + ContactUs = 'contact_us', + BecomePartner = 'become_partner', +} diff --git a/backend/src/Mailing/system-mailing/enums/index.ts b/backend/src/Mailing/system-mailing/enums/index.ts new file mode 100644 index 0000000..7fa65a6 --- /dev/null +++ b/backend/src/Mailing/system-mailing/enums/index.ts @@ -0,0 +1 @@ +export * from './feedback-type.enum'; diff --git a/backend/src/Mailing/system-mailing/index.ts b/backend/src/Mailing/system-mailing/index.ts new file mode 100644 index 0000000..f871bcf --- /dev/null +++ b/backend/src/Mailing/system-mailing/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './enums'; +export * from './public-system-mailing.controller.ts'; +export * from './system-mailing.controller'; +export * from './system-mailing.service'; diff --git a/backend/src/Mailing/system-mailing/public-system-mailing.controller.ts.ts b/backend/src/Mailing/system-mailing/public-system-mailing.controller.ts.ts new file mode 100644 index 0000000..7cca0ea --- /dev/null +++ b/backend/src/Mailing/system-mailing/public-system-mailing.controller.ts.ts @@ -0,0 +1,19 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { ApiAccessRequired } from '@/modules/iam/common'; + +import { SendFeedbackDto } from './dto'; +import { SystemMailingService } from './system-mailing.service'; + +@ApiExcludeController(true) +@Controller('mailing/feedback/public') +@ApiAccessRequired() +export class PublicSystemMailingController { + constructor(private readonly service: SystemMailingService) {} + + @Post() + async sendFeedback(@Body() dto: SendFeedbackDto) { + await this.service.sendFeedback(null, null, dto); + } +} diff --git a/backend/src/Mailing/system-mailing/system-mailing.controller.ts b/backend/src/Mailing/system-mailing/system-mailing.controller.ts new file mode 100644 index 0000000..c285e15 --- /dev/null +++ b/backend/src/Mailing/system-mailing/system-mailing.controller.ts @@ -0,0 +1,19 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { SendFeedbackDto } from './dto'; +import { SystemMailingService } from './system-mailing.service'; + +@ApiTags('mailing/feedback') +@Controller('mailing') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class SystemMailingController { + constructor(private readonly service: SystemMailingService) {} + + @Post('feedback') + async sendFeedback(@CurrentAuth() { account, user }: AuthData, @Body() dto: SendFeedbackDto) { + await this.service.sendFeedback(account, user, dto); + } +} diff --git a/backend/src/Mailing/system-mailing/system-mailing.service.ts b/backend/src/Mailing/system-mailing/system-mailing.service.ts new file mode 100644 index 0000000..c1bcb62 --- /dev/null +++ b/backend/src/Mailing/system-mailing/system-mailing.service.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { OnEvent } from '@nestjs/event-emitter'; +import { MailerService } from '@nestjs-modules/mailer'; + +import { UrlGeneratorService } from '@/common'; +import { ApplicationConfig } from '@/config'; + +import { IamEventType, UserPasswordRecoveryEvent } from '@/modules/iam/common'; +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { SendFeedbackDto } from './dto'; + +@Injectable() +export class SystemMailingService { + private _appConfig: ApplicationConfig; + + constructor( + private readonly configService: ConfigService, + private readonly mailerService: MailerService, + private readonly urlGeneratorService: UrlGeneratorService, + ) { + this._appConfig = this.configService.get('application'); + } + + @OnEvent(IamEventType.UserPasswordRecovery, { async: true }) + async sendPasswordRecoveryEmail(event: UserPasswordRecoveryEvent) { + const domain = this.urlGeneratorService.createUrl(); + + await this.mailerService.sendMail({ + to: event.userEmail, + subject: `${this._appConfig.name} password recovery`, + template: `./auth/password_recovery.html`, + context: { + appName: this._appConfig.name, + appNameLower: this._appConfig.name.toLowerCase(), + userName: event.userFullName, + recoveryLink: `${domain}/recovery?token=${event.recoveryToken}`, + supportEmail: this._appConfig.supportEmail, + domain: domain, + }, + }); + } + + async sendFeedback(account: Account | null, user: User | null, dto: SendFeedbackDto) { + const accountName = account?.companyName ?? 'Unregistered'; + const userName = user?.fullName ?? null; + const userEmail = user?.email ?? null; + + await this.mailerService.sendMail({ + to: this._appConfig.feedbackEmail, + subject: `${this._appConfig.name} feedback (${dto.type}) from ${accountName}`, + template: `./feedback/${dto.type}`.toLowerCase(), + context: { ...dto.payload, accountId: account.id, accountName, userName, userEmail }, + }); + } +} diff --git a/backend/src/Mailing/system-mailing/templates/auth/password_recovery.html b/backend/src/Mailing/system-mailing/templates/auth/password_recovery.html new file mode 100644 index 0000000..42f8503 --- /dev/null +++ b/backend/src/Mailing/system-mailing/templates/auth/password_recovery.html @@ -0,0 +1,222 @@ + + + + + + + + {{appName}} password recovery + + + + + + + + + + + +
+

+ Recover your password in order to gain access to your account. +

+
+ + + + + + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+ {{appName}} Logo +
+ We have received a request to reset the password for the account. +
+ {{appName}} password recovery +
+ To set a new password for the account of + {{userName}}, please proceed by + clicking on the link below. +
+ Reset +
+ + + + + + + + + + + + +
+ If this request was not initiated by you, please notify us of the receipt of + this email at + {{supportEmail}}. +
+ Yours sincerely, The {{appName}} Team! +
+
+
+ + + \ No newline at end of file diff --git a/backend/src/Mailing/system-mailing/templates/feedback/become_partner.html b/backend/src/Mailing/system-mailing/templates/feedback/become_partner.html new file mode 100644 index 0000000..1f7e0d0 --- /dev/null +++ b/backend/src/Mailing/system-mailing/templates/feedback/become_partner.html @@ -0,0 +1,51 @@ + + + + + + + + Become Partner Request + + + +

Become Partner Request

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
First name{{firstName}}
Last name{{lastName}}
Phone{{phone}}
Email{{email}}
Company{{company}}
Country{{country}}
Website{{website}}
Employees{{employees}}
Comment{{comment}}
+ \ No newline at end of file diff --git a/backend/src/Mailing/system-mailing/templates/feedback/contact_us.html b/backend/src/Mailing/system-mailing/templates/feedback/contact_us.html new file mode 100644 index 0000000..5f44610 --- /dev/null +++ b/backend/src/Mailing/system-mailing/templates/feedback/contact_us.html @@ -0,0 +1,31 @@ + + + + + + + + Contact Us Request + + + +

Contact Us Request

+ + + + + + + + + + + + + + + + + +
Name{{name}}
Phone{{phone}}
Email{{email}}
Comment{{comment}}
+ \ No newline at end of file diff --git a/backend/src/Mailing/system-mailing/templates/feedback/trial_expired.html b/backend/src/Mailing/system-mailing/templates/feedback/trial_expired.html new file mode 100644 index 0000000..545cda6 --- /dev/null +++ b/backend/src/Mailing/system-mailing/templates/feedback/trial_expired.html @@ -0,0 +1,63 @@ + + + + + + + + End Trial Request + + + +

End Trial Request

+ + + + + + + + + + + + + + + + + + + + +
Registration data
Account Id{{accountId}}
Account Name{{accountName}}
User Name{{userName}}
User Email{{userEmail}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Request data
Name{{name}}
Phone{{phone}}
Email{{email}}
Number of users{{userNumber}}
Subscribe{{subscribe}}
Plan{{plan}}
+ \ No newline at end of file diff --git a/backend/src/Mailing/system-mailing/templates/feedback/user_limit.html b/backend/src/Mailing/system-mailing/templates/feedback/user_limit.html new file mode 100644 index 0000000..08dae2c --- /dev/null +++ b/backend/src/Mailing/system-mailing/templates/feedback/user_limit.html @@ -0,0 +1,55 @@ + + + + + + + + Additional User Request + + + +

Additional User Request

+ + + + + + + + + + + + + + + + + + + + +
Registration data
Account Id{{accountId}}
Account Name{{accountName}}
User Name{{userName}}
User Email{{userEmail}}
+ + + + + + + + + + + + + + + + + + + + +
Request data
Name{{name}}
Phone{{phone}}
Email{{email}}
Number of users{{userNumber}}
+ \ No newline at end of file diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts new file mode 100644 index 0000000..94b8b55 --- /dev/null +++ b/backend/src/app.module.ts @@ -0,0 +1,83 @@ +import { Module } from '@nestjs/common'; +import { RouterModule } from '@nestjs/core'; +import { ConditionalModule, ConfigModule } from '@nestjs/config'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { ScheduleModule } from '@nestjs/schedule'; + +import applicationConfig from './config/application.config'; + +import { ApiDocumentation } from './documentation'; +import { CommonModule } from './common/common.module'; +import { DatabaseModule } from './database/database.module'; +import { SupportModule } from './support/support.module'; +import { IAMModule } from './modules/iam/iam.module'; +import { FrontendEventModule } from './modules/frontend-event/frontend-event.module'; +import { StorageModule } from './modules/storage/storage.module'; +import { NotificationModule } from './modules/notification/notification.module'; +import { InventoryModule } from './modules/inventory/inventory.module'; +import { SchedulerModule } from './modules/scheduler/scheduler.module'; +import { MultichatModule } from './modules/multichat/multichat.module'; +import { TelephonyModule } from './modules/telephony/telephony.module'; +import { FormsModule } from './modules/forms/forms.module'; +import { AnalyticsModule } from './modules/analytics/analytics.module'; +import { EntityModule } from './modules/entity/entity.module'; +import { TutorialModule } from './modules/tutorial/tutorial.module'; +import { DocumentsModule } from './modules/documents/documents.module'; +import { DataEnrichmentModule } from './modules/data-enrichment/data-enrichment.module'; +import { SetupModule } from './modules/setup/setup.module'; +import { PartnerModule } from './modules/partner/partner.module'; +import { AutomationModule } from './modules/automation/automation.module'; +import { IntegrationModule } from './modules/integration/integration.module'; +import { MailModule } from './modules/mail/mail.module'; +import { FrontendObjectModule } from './modules/frontend-object/frontend-object.module'; + +import { CrmModule } from './CRM/crm.module'; +import { MailingModule } from './Mailing/MailingModule'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + cache: true, + envFilePath: ['.env.local', '.env'], + load: [applicationConfig], + }), + ConditionalModule.registerWhen(ScheduleModule.forRoot(), 'SCHEDULE_ENABLED'), + EventEmitterModule.forRoot({ delimiter: ':', maxListeners: 100, wildcard: true }), + ApiDocumentation, + DatabaseModule, + CommonModule, + IAMModule, + FrontendEventModule, + StorageModule, + NotificationModule, + MailingModule, + MultichatModule, + EntityModule, + CrmModule, + InventoryModule, + SchedulerModule, + TelephonyModule, + FormsModule, + AnalyticsModule, + TutorialModule, + DocumentsModule, + DataEnrichmentModule, + SetupModule, + PartnerModule, + ConditionalModule.registerWhen(AutomationModule, 'AUTOMATION_ENABLED'), + IntegrationModule, + SupportModule, + MailModule, + FrontendObjectModule, + RouterModule.register([ + { path: 'automation', module: AutomationModule }, + { path: 'iam', module: IAMModule }, + { path: 'site-forms', module: FormsModule }, + { path: 'scheduler', module: SchedulerModule }, + { path: 'telephony', module: TelephonyModule }, + { path: 'tutorial', module: TutorialModule }, + ]), + ], +}) +export class AppModule {} diff --git a/backend/src/common/common.module.ts b/backend/src/common/common.module.ts new file mode 100644 index 0000000..1868f76 --- /dev/null +++ b/backend/src/common/common.module.ts @@ -0,0 +1,49 @@ +import { Global, Module, ValidationPipe } from '@nestjs/common'; +import { APP_FILTER, APP_PIPE } from '@nestjs/core'; + +import commonConfig, { CommonConfig } from './config/common.config'; +import { CacheModule, GlobalHttpModule, TokenModule, UrlGeneratorModule } from './modules'; + +import { AllExceptionsFilter } from './filters'; +import { ConfigModule, ConfigService } from '@nestjs/config'; + +@Global() +@Module({ + imports: [ + ConfigModule.forFeature(commonConfig), + CacheModule.forRootAsync({ + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + const config = configService.get('common'); + return { + type: config.cache?.type, + options: + config.cache?.type === 'ioredis' + ? { host: config.cache?.ioredis?.host, port: config.cache?.ioredis?.port } + : undefined, + }; + }, + }), + GlobalHttpModule, + TokenModule, + UrlGeneratorModule, + ], + providers: [ + { + provide: APP_PIPE, + useFactory: () => { + return new ValidationPipe({ + transform: true, + transformOptions: { enableImplicitConversion: true }, + whitelist: true, + }); + }, + }, + { + provide: APP_FILTER, + useClass: AllExceptionsFilter, + }, + ], + exports: [CacheModule, TokenModule, UrlGeneratorModule], +}) +export class CommonModule {} diff --git a/backend/src/common/config/common.config.ts b/backend/src/common/config/common.config.ts new file mode 100644 index 0000000..947ced8 --- /dev/null +++ b/backend/src/common/config/common.config.ts @@ -0,0 +1,27 @@ +import { registerAs } from '@nestjs/config'; +import { CacheProviderType } from '../modules/cache/types'; + +interface IORedisConfig { + host: string; + port: number; +} +interface CacheConfig { + type?: CacheProviderType; + ioredis?: IORedisConfig; +} +export interface CommonConfig { + cache?: CacheConfig; +} + +export default registerAs( + 'common', + (): CommonConfig => ({ + cache: { + type: process.env.CACHE_TYPE as CacheProviderType, + ioredis: { + host: process.env.CACHE_IOREDIS_HOST, + port: +process.env.CACHE_IOREDIS_PORT, + }, + }, + }), +); diff --git a/backend/src/common/config/index.ts b/backend/src/common/config/index.ts new file mode 100644 index 0000000..33ec909 --- /dev/null +++ b/backend/src/common/config/index.ts @@ -0,0 +1 @@ +export * from './common.config'; diff --git a/backend/src/common/constants/frontend-route.ts b/backend/src/common/constants/frontend-route.ts new file mode 100644 index 0000000..dcde2e5 --- /dev/null +++ b/backend/src/common/constants/frontend-route.ts @@ -0,0 +1,23 @@ +export const FrontendRoute = { + signup: '/signup', + + entity: { + card: ({ entityTypeId, entityId }: { entityTypeId: number; entityId: number }) => + `/et/${entityTypeId}/card/${entityId}/overview`, + } as const, + + settings: { + base: '/settings', + mailing: () => `${FrontendRoute.settings.base}/mailing`, + stripe: () => `${FrontendRoute.settings.base}/billing/stripe`, + integration: () => `${FrontendRoute.settings.base}/integrations`, + salesforce: () => FrontendRoute.settings.integration(), + facebook: { + deleteVerify: () => `/facebook/auth/delete-verify`, + messenger: () => FrontendRoute.settings.integration(), + } as const, + google: { + calendar: () => `${FrontendRoute.settings.integration()}/google-calendar`, + } as const, + } as const, +} as const; diff --git a/backend/src/common/constants/index.ts b/backend/src/common/constants/index.ts new file mode 100644 index 0000000..c2ef8bb --- /dev/null +++ b/backend/src/common/constants/index.ts @@ -0,0 +1,2 @@ +export * from './frontend-route'; +export * from './paging-default'; diff --git a/backend/src/common/constants/paging-default.ts b/backend/src/common/constants/paging-default.ts new file mode 100644 index 0000000..f2b4f2c --- /dev/null +++ b/backend/src/common/constants/paging-default.ts @@ -0,0 +1,4 @@ +export const PagingDefault = { + offset: 0, + limit: 20, +} as const; diff --git a/backend/src/common/decorators/index.ts b/backend/src/common/decorators/index.ts new file mode 100644 index 0000000..12ee43f --- /dev/null +++ b/backend/src/common/decorators/index.ts @@ -0,0 +1,2 @@ +export * from './subdomain.decorator'; +export * from './transform-to-dto.decorator'; diff --git a/backend/src/common/decorators/subdomain.decorator.ts b/backend/src/common/decorators/subdomain.decorator.ts new file mode 100644 index 0000000..bdaf494 --- /dev/null +++ b/backend/src/common/decorators/subdomain.decorator.ts @@ -0,0 +1,8 @@ +import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; +import { Request } from 'express'; + +export const Subdomain = createParamDecorator((_data: unknown, context: ExecutionContext): string | null => { + const request = context.switchToHttp().getRequest(); + + return request.subdomain; +}); diff --git a/backend/src/common/decorators/transform-to-dto.decorator.ts b/backend/src/common/decorators/transform-to-dto.decorator.ts new file mode 100644 index 0000000..72e56e2 --- /dev/null +++ b/backend/src/common/decorators/transform-to-dto.decorator.ts @@ -0,0 +1,4 @@ +import { UseInterceptors } from '@nestjs/common'; +import { TransformToDtoInterceptor } from '../interceptors'; + +export const TransformToDto = () => UseInterceptors(TransformToDtoInterceptor); diff --git a/backend/src/common/dto/date/date-period.dto.ts b/backend/src/common/dto/date/date-period.dto.ts new file mode 100644 index 0000000..a746eac --- /dev/null +++ b/backend/src/common/dto/date/date-period.dto.ts @@ -0,0 +1,14 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class DatePeriodDto { + @ApiPropertyOptional({ description: 'Start date', nullable: true }) + @IsOptional() + @IsString() + startDate?: string | null; + + @ApiPropertyOptional({ description: 'End date', nullable: true }) + @IsOptional() + @IsString() + endDate?: string | null; +} diff --git a/backend/src/common/dto/date/index.ts b/backend/src/common/dto/date/index.ts new file mode 100644 index 0000000..adf964c --- /dev/null +++ b/backend/src/common/dto/date/index.ts @@ -0,0 +1 @@ +export * from './date-period.dto'; diff --git a/backend/src/common/dto/expand/expand-query.dto.ts b/backend/src/common/dto/expand/expand-query.dto.ts new file mode 100644 index 0000000..ba9fb12 --- /dev/null +++ b/backend/src/common/dto/expand/expand-query.dto.ts @@ -0,0 +1,10 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; + +export class ExpandQuery { + @ApiPropertyOptional({ type: String, description: 'Expand fields' }) + expand?: string; + + get fields(): T[] { + return this.expand?.split(',') as T[]; + } +} diff --git a/backend/src/common/dto/expand/index.ts b/backend/src/common/dto/expand/index.ts new file mode 100644 index 0000000..9b9ef3e --- /dev/null +++ b/backend/src/common/dto/expand/index.ts @@ -0,0 +1 @@ +export * from './expand-query.dto'; diff --git a/backend/src/common/dto/filter/boolean-filter.ts b/backend/src/common/dto/filter/boolean-filter.ts new file mode 100644 index 0000000..5519549 --- /dev/null +++ b/backend/src/common/dto/filter/boolean-filter.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean } from 'class-validator'; + +export class BooleanFilter { + @ApiProperty({ description: 'Filter value' }) + @IsBoolean() + value: boolean; +} diff --git a/backend/src/common/dto/filter/date-filter.ts b/backend/src/common/dto/filter/date-filter.ts new file mode 100644 index 0000000..2822548 --- /dev/null +++ b/backend/src/common/dto/filter/date-filter.ts @@ -0,0 +1,14 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsOptional } from 'class-validator'; + +export class DateFilter { + @ApiPropertyOptional({ description: 'From date' }) + @IsOptional() + @IsDateString() + from?: string; + + @ApiPropertyOptional({ description: 'To date' }) + @IsOptional() + @IsDateString() + to?: string; +} diff --git a/backend/src/common/dto/filter/date-period-filter.ts b/backend/src/common/dto/filter/date-period-filter.ts new file mode 100644 index 0000000..603d9ba --- /dev/null +++ b/backend/src/common/dto/filter/date-period-filter.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString, IsEnum, IsOptional } from 'class-validator'; + +import { DatePeriodFilterType } from '../../enums'; + +export class DatePeriodFilter { + @ApiProperty({ description: 'Type of date period', required: false, enum: DatePeriodFilterType }) + @IsOptional() + @IsEnum(DatePeriodFilterType) + type?: DatePeriodFilterType; + + @ApiProperty({ description: 'From date', required: false }) + @IsOptional() + @IsDateString() + from?: string; + + @ApiProperty({ description: 'To date', required: false }) + @IsOptional() + @IsDateString() + to?: string; +} diff --git a/backend/src/common/dto/filter/exists-filter.ts b/backend/src/common/dto/filter/exists-filter.ts new file mode 100644 index 0000000..8de8b05 --- /dev/null +++ b/backend/src/common/dto/filter/exists-filter.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +import { ExistsFilterType } from '../../enums'; + +export class ExistsFilter { + @ApiProperty({ description: 'Filter type', enum: ExistsFilterType }) + @IsEnum(ExistsFilterType) + type: ExistsFilterType; +} diff --git a/backend/src/common/dto/filter/index.ts b/backend/src/common/dto/filter/index.ts new file mode 100644 index 0000000..7d6a363 --- /dev/null +++ b/backend/src/common/dto/filter/index.ts @@ -0,0 +1,8 @@ +export * from './boolean-filter'; +export * from './date-filter'; +export * from './date-period-filter'; +export * from './exists-filter'; +export * from './number-filter'; +export * from './select-filter'; +export * from './simple-filter'; +export * from './string-filter'; diff --git a/backend/src/common/dto/filter/number-filter.ts b/backend/src/common/dto/filter/number-filter.ts new file mode 100644 index 0000000..ac62cc2 --- /dev/null +++ b/backend/src/common/dto/filter/number-filter.ts @@ -0,0 +1,14 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class NumberFilter { + @ApiPropertyOptional({ description: 'Minimum value' }) + @IsOptional() + @IsNumber() + min?: number; + + @ApiPropertyOptional({ description: 'Maximum value' }) + @IsOptional() + @IsNumber() + max?: number; +} diff --git a/backend/src/common/dto/filter/select-filter.ts b/backend/src/common/dto/filter/select-filter.ts new file mode 100644 index 0000000..46b6193 --- /dev/null +++ b/backend/src/common/dto/filter/select-filter.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber } from 'class-validator'; + +export class SelectFilter { + @ApiProperty({ description: 'List of option IDs', type: [Number] }) + @IsArray() + @IsNumber({}, { each: true }) + optionIds: number[]; +} diff --git a/backend/src/common/dto/filter/simple-filter.ts b/backend/src/common/dto/filter/simple-filter.ts new file mode 100644 index 0000000..75a23be --- /dev/null +++ b/backend/src/common/dto/filter/simple-filter.ts @@ -0,0 +1,28 @@ +import { ApiProperty, getSchemaPath } from '@nestjs/swagger'; +import { IsEnum, IsObject } from 'class-validator'; + +import { SimpleFilterType } from '../../enums'; +import { BooleanFilter, DateFilter, ExistsFilter, NumberFilter, SelectFilter, StringFilter } from '../../dto'; + +export class SimpleFilter { + @ApiProperty({ description: 'Filter type', enum: SimpleFilterType }) + @IsEnum(SimpleFilterType) + type: SimpleFilterType; + + @ApiProperty({ + description: 'Filter value', + type: 'array', + items: { + oneOf: [ + { $ref: getSchemaPath(BooleanFilter) }, + { $ref: getSchemaPath(DateFilter) }, + { $ref: getSchemaPath(ExistsFilter) }, + { $ref: getSchemaPath(NumberFilter) }, + { $ref: getSchemaPath(SelectFilter) }, + { $ref: getSchemaPath(StringFilter) }, + ], + }, + }) + @IsObject() + filter: BooleanFilter | DateFilter | ExistsFilter | NumberFilter | SelectFilter | StringFilter; +} diff --git a/backend/src/common/dto/filter/string-filter.ts b/backend/src/common/dto/filter/string-filter.ts new file mode 100644 index 0000000..2bf5265 --- /dev/null +++ b/backend/src/common/dto/filter/string-filter.ts @@ -0,0 +1,15 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; + +import { StringFilterType } from '../../enums'; + +export class StringFilter { + @ApiProperty({ description: 'Filter type', enum: StringFilterType }) + @IsEnum(StringFilterType) + type: StringFilterType; + + @ApiPropertyOptional({ description: 'Filter value' }) + @IsOptional() + @IsString() + text?: string | null; +} diff --git a/backend/src/common/dto/index.ts b/backend/src/common/dto/index.ts new file mode 100644 index 0000000..6b3bd70 --- /dev/null +++ b/backend/src/common/dto/index.ts @@ -0,0 +1,6 @@ +export * from './date'; +export * from './expand'; +export * from './filter'; +export * from './paging'; +export * from './quantity-amount.dto'; +export * from './sorting'; diff --git a/backend/src/common/dto/paging/chat-paging-query.dto.ts b/backend/src/common/dto/paging/chat-paging-query.dto.ts new file mode 100644 index 0000000..5022e79 --- /dev/null +++ b/backend/src/common/dto/paging/chat-paging-query.dto.ts @@ -0,0 +1,39 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { PagingDefault } from '../../constants'; + +export class ChatPagingQuery { + @ApiPropertyOptional({ description: 'Offset', example: 0 }) + @IsOptional() + @IsNumber() + offset?: number; + + @ApiPropertyOptional({ description: 'Limit', example: 10 }) + @IsOptional() + @IsNumber() + limit?: number; + + @ApiPropertyOptional({ description: 'Cursor position for pagination' }) + @IsOptional() + @IsNumber() + cursor?: number; + + constructor(offset: number | undefined, limit: number | undefined, cursor: number | undefined) { + this.offset = offset; + this.limit = limit; + this.cursor = cursor; + } + + static default(): ChatPagingQuery { + return new ChatPagingQuery(undefined, undefined, undefined); + } + + get skip(): number { + return this.offset ?? PagingDefault.offset; + } + + get take(): number { + return this.limit ?? PagingDefault.limit; + } +} diff --git a/backend/src/common/dto/paging/cursor-paging-query.dto.ts b/backend/src/common/dto/paging/cursor-paging-query.dto.ts new file mode 100644 index 0000000..9f9f8fc --- /dev/null +++ b/backend/src/common/dto/paging/cursor-paging-query.dto.ts @@ -0,0 +1,20 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { PagingDefault } from '../../constants'; + +export class CursorPagingQuery { + @ApiPropertyOptional({ description: 'Cursor position for pagination' }) + @IsOptional() + @IsNumber() + cursor?: number; + + @ApiPropertyOptional({ description: 'Limit for pagination' }) + @IsOptional() + @IsNumber() + limit?: number; + + get take(): number { + return this.limit ?? PagingDefault.limit; + } +} diff --git a/backend/src/common/dto/paging/index.ts b/backend/src/common/dto/paging/index.ts new file mode 100644 index 0000000..2b16bd7 --- /dev/null +++ b/backend/src/common/dto/paging/index.ts @@ -0,0 +1,3 @@ +export * from './cursor-paging-query.dto'; +export * from './paging-meta.dto'; +export * from './paging-query.dto'; diff --git a/backend/src/common/dto/paging/paging-meta.dto.ts b/backend/src/common/dto/paging/paging-meta.dto.ts new file mode 100644 index 0000000..f0ee47c --- /dev/null +++ b/backend/src/common/dto/paging/paging-meta.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class PagingMeta { + @ApiProperty({ description: 'Offset for pagination' }) + @IsNumber() + offset: number; + + @ApiProperty({ description: 'Total number of items' }) + @IsNumber() + total: number; + + constructor(offset: number, total: number) { + this.offset = Math.min(offset, total); + this.total = total; + } + + static empty(): PagingMeta { + return new PagingMeta(0, 0); + } +} diff --git a/backend/src/common/dto/paging/paging-query.dto.ts b/backend/src/common/dto/paging/paging-query.dto.ts new file mode 100644 index 0000000..152095d --- /dev/null +++ b/backend/src/common/dto/paging/paging-query.dto.ts @@ -0,0 +1,33 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { PagingDefault } from '../../constants'; + +export class PagingQuery { + @ApiPropertyOptional({ description: 'Offset', example: 0 }) + @IsOptional() + @IsNumber() + offset?: number; + + @ApiPropertyOptional({ description: 'Limit', example: 10 }) + @IsOptional() + @IsNumber() + limit?: number; + + constructor(offset: number | undefined, limit: number | undefined) { + this.offset = offset; + this.limit = limit; + } + + static default(): PagingQuery { + return new PagingQuery(undefined, undefined); + } + + get skip(): number { + return this.offset ?? PagingDefault.offset; + } + + get take(): number { + return this.limit ?? PagingDefault.limit; + } +} diff --git a/backend/src/common/dto/quantity-amount.dto.ts b/backend/src/common/dto/quantity-amount.dto.ts new file mode 100644 index 0000000..4a80288 --- /dev/null +++ b/backend/src/common/dto/quantity-amount.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class QuantityAmountDto { + @ApiProperty({ description: 'Quantity' }) + @IsNumber() + quantity: number; + + @ApiProperty({ description: 'Amount' }) + @IsNumber() + amount: number; + + constructor(quantity: number, amount: number) { + this.quantity = quantity; + this.amount = amount; + } + + public static empty(): QuantityAmountDto { + return new QuantityAmountDto(0, 0); + } +} diff --git a/backend/src/common/dto/sorting/index.ts b/backend/src/common/dto/sorting/index.ts new file mode 100644 index 0000000..5e677c8 --- /dev/null +++ b/backend/src/common/dto/sorting/index.ts @@ -0,0 +1,3 @@ +export * from './manual-sorting.dto'; +export * from './sort-order-list.dto'; +export * from './sort-order.dto'; diff --git a/backend/src/common/dto/sorting/manual-sorting.dto.ts b/backend/src/common/dto/sorting/manual-sorting.dto.ts new file mode 100644 index 0000000..ec7afc3 --- /dev/null +++ b/backend/src/common/dto/sorting/manual-sorting.dto.ts @@ -0,0 +1,14 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class ManualSorting { + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + afterId?: number | null | undefined; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + beforeId?: number | null | undefined; +} diff --git a/backend/src/common/dto/sorting/sort-order-list.dto.ts b/backend/src/common/dto/sorting/sort-order-list.dto.ts new file mode 100644 index 0000000..d244ab9 --- /dev/null +++ b/backend/src/common/dto/sorting/sort-order-list.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; +import { SortOrderDto } from './sort-order.dto'; + +export class SortOrderListDto { + @ApiProperty({ type: [SortOrderDto] }) + @IsArray() + items: SortOrderDto[]; +} diff --git a/backend/src/common/dto/sorting/sort-order.dto.ts b/backend/src/common/dto/sorting/sort-order.dto.ts new file mode 100644 index 0000000..4f574af --- /dev/null +++ b/backend/src/common/dto/sorting/sort-order.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class SortOrderDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + sortOrder: number; +} diff --git a/backend/src/common/enums/currency.enum.ts b/backend/src/common/enums/currency.enum.ts new file mode 100644 index 0000000..d0c5fbe --- /dev/null +++ b/backend/src/common/enums/currency.enum.ts @@ -0,0 +1,55 @@ +export enum Currency { + USD = 'USD', + EUR = 'EUR', + GBP = 'GBP', + JPY = 'JPY', + CNY = 'CNY', + INR = 'INR', + RUB = 'RUB', + MXN = 'MXN', + BRL = 'BRL', + ZAR = 'ZAR', + AUD = 'AUD', + CAD = 'CAD', + AED = 'AED', + CHF = 'CHF', + TRY = 'TRY', + UAH = 'UAH', + KRW = 'KRW', + NZD = 'NZD', + NOK = 'NOK', + SEK = 'SEK', + DKK = 'DKK', + PLN = 'PLN', + CZK = 'CZK', + HUF = 'HUF', + IDR = 'IDR', + ILS = 'ILS', + MYR = 'MYR', + PHP = 'PHP', + SGD = 'SGD', + THB = 'THB', + KZT = 'KZT', + CLP = 'CLP', + CRC = 'CRC', + COP = 'COP', + BOB = 'BOB', + HKD = 'HKD', + SAR = 'SAR', + VND = 'VND', + EGP = 'EGP', + KWD = 'KWD', + PKR = 'PKR', + LKR = 'LKR', + BDT = 'BDT', + NGN = 'NGN', + GHS = 'GHS', + TWD = 'TWD', + MAD = 'MAD', + ARS = 'ARS', + PEN = 'PEN', + UYU = 'UYU', + BGN = 'BGN', + RON = 'RON', + LBP = 'LBP', +} diff --git a/backend/src/common/enums/date-format.enum.ts b/backend/src/common/enums/date-format.enum.ts new file mode 100644 index 0000000..4cd403b --- /dev/null +++ b/backend/src/common/enums/date-format.enum.ts @@ -0,0 +1,8 @@ +export enum DateFormat { + ISO8601 = 'YYYY-MM-DD', + EUSlash = 'DD/MM/YYYY', + EUDot = 'DD.MM.YYYY', + EUDash = 'DD-MM-YYYY', + US = 'MM/DD/YYYY', + Asia = 'YYYY/MM/DD', +} diff --git a/backend/src/common/enums/date-period-filter-type.enum.ts b/backend/src/common/enums/date-period-filter-type.enum.ts new file mode 100644 index 0000000..f937ee4 --- /dev/null +++ b/backend/src/common/enums/date-period-filter-type.enum.ts @@ -0,0 +1,12 @@ +export enum DatePeriodFilterType { + All = 'all', + Today = 'today', + Yesterday = 'yesterday', + CurrentWeek = 'current_week', + LastWeek = 'last_week', + CurrentMonth = 'current_month', + LastMonth = 'last_month', + CurrentQuarter = 'current_quarter', + LastQuarter = 'last_quarter', + Period = 'period', +} diff --git a/backend/src/common/enums/exists-filter-type.enum.ts b/backend/src/common/enums/exists-filter-type.enum.ts new file mode 100644 index 0000000..b2dc9f6 --- /dev/null +++ b/backend/src/common/enums/exists-filter-type.enum.ts @@ -0,0 +1,4 @@ +export enum ExistsFilterType { + Empty = 'empty', + NotEmpty = 'not_empty', +} diff --git a/backend/src/common/enums/file-link-source.enum.ts b/backend/src/common/enums/file-link-source.enum.ts new file mode 100644 index 0000000..86efae8 --- /dev/null +++ b/backend/src/common/enums/file-link-source.enum.ts @@ -0,0 +1,15 @@ +//TODO: remove from common +/** + * @deprecated + */ +export enum FileLinkSource { + NOTE = 'note', + TASK = 'task', + TASK_COMMENT = 'task_comment', + ACTIVITY = 'activity', + ENTITY = 'entity', + EMAIL_ACTION_SETTINGS = 'email_action_settings', + DOCUMENT_TEMPLATE = 'document_template', + ENTITY_DOCUMENT = 'entity_document', + PRODUCT_PHOTO = 'product_photo', +} diff --git a/backend/src/common/enums/group-by-date.enum.ts b/backend/src/common/enums/group-by-date.enum.ts new file mode 100644 index 0000000..f8547ab --- /dev/null +++ b/backend/src/common/enums/group-by-date.enum.ts @@ -0,0 +1,7 @@ +export enum GroupByDate { + Day = 'day', + Week = 'week', + Month = 'month', + Quarter = 'quarter', + Year = 'year', +} diff --git a/backend/src/common/enums/http-body-type.enum.ts b/backend/src/common/enums/http-body-type.enum.ts new file mode 100644 index 0000000..a1cef85 --- /dev/null +++ b/backend/src/common/enums/http-body-type.enum.ts @@ -0,0 +1,5 @@ +export enum HttpBodyType { + Raw = 'RAW', + FormUrlEncoded = 'FORM_URL_ENCODED', + FormData = 'FORM_DATA', +} diff --git a/backend/src/common/enums/http-method.enum.ts b/backend/src/common/enums/http-method.enum.ts new file mode 100644 index 0000000..3407ed7 --- /dev/null +++ b/backend/src/common/enums/http-method.enum.ts @@ -0,0 +1,9 @@ +export enum HttpMethod { + Get = 'GET', + Post = 'POST', + Put = 'PUT', + Delete = 'DELETE', + Patch = 'PATCH', + Options = 'OPTIONS', + Head = 'HEAD', +} diff --git a/backend/src/common/enums/index.ts b/backend/src/common/enums/index.ts new file mode 100644 index 0000000..b1fa1bc --- /dev/null +++ b/backend/src/common/enums/index.ts @@ -0,0 +1,11 @@ +export * from './currency.enum'; +export * from './date-format.enum'; +export * from './date-period-filter-type.enum'; +export * from './exists-filter-type.enum'; +export * from './file-link-source.enum'; +export * from './group-by-date.enum'; +export * from './http-method.enum'; +export * from './object-state.enum'; +export * from './simple-filter-type.enum'; +export * from './string-filter-type.enum'; +export * from './user-notification.enum'; diff --git a/backend/src/common/enums/object-state.enum.ts b/backend/src/common/enums/object-state.enum.ts new file mode 100644 index 0000000..7613ca2 --- /dev/null +++ b/backend/src/common/enums/object-state.enum.ts @@ -0,0 +1,6 @@ +export enum ObjectState { + Created = 'created', + Updated = 'updated', + Deleted = 'deleted', + Unchanged = 'unchanged', +} diff --git a/backend/src/common/enums/simple-filter-type.enum.ts b/backend/src/common/enums/simple-filter-type.enum.ts new file mode 100644 index 0000000..fbc3b2d --- /dev/null +++ b/backend/src/common/enums/simple-filter-type.enum.ts @@ -0,0 +1,8 @@ +export enum SimpleFilterType { + Boolean = 'boolean', + Date = 'date', + Number = 'number', + Select = 'select', + String = 'string', + Exists = 'exists', +} diff --git a/backend/src/common/enums/string-filter-type.enum.ts b/backend/src/common/enums/string-filter-type.enum.ts new file mode 100644 index 0000000..61e8520 --- /dev/null +++ b/backend/src/common/enums/string-filter-type.enum.ts @@ -0,0 +1,5 @@ +export enum StringFilterType { + Empty = 'empty', + NotEmpty = 'not_empty', + Contains = 'contains', +} diff --git a/backend/src/common/enums/user-notification.enum.ts b/backend/src/common/enums/user-notification.enum.ts new file mode 100644 index 0000000..c31a090 --- /dev/null +++ b/backend/src/common/enums/user-notification.enum.ts @@ -0,0 +1,5 @@ +export enum UserNotification { + Suppressed = 'suppressed', + Default = 'default', + Forced = 'forced', +} diff --git a/backend/src/common/errors/bad-request.error.ts b/backend/src/common/errors/bad-request.error.ts new file mode 100644 index 0000000..166beef --- /dev/null +++ b/backend/src/common/errors/bad-request.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from './service.error'; + +export class BadRequestError extends ServiceError { + constructor(message = 'Bad Request') { + super({ errorCode: 'bad_request', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/common/errors/forbidden.error.ts b/backend/src/common/errors/forbidden.error.ts new file mode 100644 index 0000000..489e3de --- /dev/null +++ b/backend/src/common/errors/forbidden.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from './service.error'; + +export class ForbiddenError extends ServiceError { + constructor(message = 'Item is forbidden') { + super({ errorCode: 'forbidden', status: HttpStatus.FORBIDDEN, message }); + } +} diff --git a/backend/src/common/errors/index.ts b/backend/src/common/errors/index.ts new file mode 100644 index 0000000..d3b34ff --- /dev/null +++ b/backend/src/common/errors/index.ts @@ -0,0 +1,5 @@ +export * from './bad-request.error'; +export * from './forbidden.error'; +export * from './invalid-phone.error'; +export * from './not-found.error'; +export * from './service.error'; diff --git a/backend/src/common/errors/invalid-phone.error.ts b/backend/src/common/errors/invalid-phone.error.ts new file mode 100644 index 0000000..10b9859 --- /dev/null +++ b/backend/src/common/errors/invalid-phone.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from './service.error'; + +export class InvalidPhoneError extends ServiceError { + constructor(message = 'Invalid phone number') { + super({ errorCode: 'invalid_phone', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/common/errors/not-found.error.ts b/backend/src/common/errors/not-found.error.ts new file mode 100644 index 0000000..bb8b2a1 --- /dev/null +++ b/backend/src/common/errors/not-found.error.ts @@ -0,0 +1,21 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from './service.error'; + +export class NotFoundError extends ServiceError { + constructor(message: string) { + super({ errorCode: 'not_found', status: HttpStatus.NOT_FOUND, message }); + } + + static fromNamed(named: T, message = 'is not found') { + return new NotFoundError(`${named.name} ${message}`); + } + + static withMessage(named: T, message?: string) { + return NotFoundError.fromNamed(named, message); + } + + static withId(named: T, id: number | string) { + return NotFoundError.withMessage(named, `with id ${id} is not found`); + } +} diff --git a/backend/src/common/errors/service.error.ts b/backend/src/common/errors/service.error.ts new file mode 100644 index 0000000..a7eca82 --- /dev/null +++ b/backend/src/common/errors/service.error.ts @@ -0,0 +1,22 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; + +interface ServiceErrorOptions { + message: string; + errorCode: string; + status: HttpStatus; + details?: object; + cause?: unknown; + description?: string; +} + +export class ServiceError extends HttpException { + errorCode: string; + details?: object; + + constructor({ errorCode, status, message, details, cause, description }: ServiceErrorOptions) { + super(message, status, { cause, description }); + + this.errorCode = errorCode ?? 'internal_server_error'; + this.details = details; + } +} diff --git a/backend/src/common/filters/all-exceptions.filter.ts b/backend/src/common/filters/all-exceptions.filter.ts new file mode 100644 index 0000000..9227b53 --- /dev/null +++ b/backend/src/common/filters/all-exceptions.filter.ts @@ -0,0 +1,57 @@ +import { ArgumentsHost, Catch, HttpException, HttpStatus, Logger } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { Request, Response } from 'express'; +import { QueryFailedError } from 'typeorm'; + +import { ServiceError } from '../errors'; + +@Catch() +export class AllExceptionsFilter extends BaseExceptionFilter { + private readonly logger = new Logger('Exception'); + + override catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const request = ctx.getRequest(); + const response = ctx.getResponse(); + + const requestStr = `${request.id ?? ''} ${request.method} ${request.hostname}${request.originalUrl}`; + const user = `User: <${request.accountId ?? ''}; ${request.userId ?? ''}; ${request.ips?.[0] ?? request.ip}>`; + const body = `Body: ${JSON.stringify(request.body)}`; + const msg = `${requestStr}\t${body}\t${user}`; + + const statusCode = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; + let errorCode: string | undefined = 'INTERNAL_SERVER_ERROR'; + let message: string | undefined = undefined; + let details: unknown | undefined = undefined; + + if (exception instanceof ServiceError) { + this.logger.warn(`${msg}\tError: ${JSON.stringify(exception)}`, 'Request'); + errorCode = exception.errorCode; + message = exception.message; + details = exception.details; + } else if (exception instanceof QueryFailedError) { + this.logger.error( + `${msg}\tQuery: ${exception.query} -- PARAMETERS: [${exception.parameters}]`, + exception.stack, + 'SQL', + ); + errorCode = 'SQL_ERROR'; + message = exception.message; + details = exception.driverError?.detail ?? exception.driverError?.message ?? exception.driverError?.toString(); + } else if (exception instanceof HttpException) { + this.logger.error(`${msg}\tResponse: ${JSON.stringify(exception.getResponse())}`, exception.stack); + message = exception.message; + details = exception.getResponse(); + } else if (exception instanceof Error) { + this.logger.error(`${msg}\tMessage: ${exception.message}`, exception.stack); + message = exception.message; + details = exception.stack; + } else { + this.logger.error(`${msg}`, exception['stack']); + } + + response + .status(statusCode) + .json({ statusCode, errorCode, message, details, timestamp: new Date().toISOString(), path: request.url }); + } +} diff --git a/backend/src/common/filters/index.ts b/backend/src/common/filters/index.ts new file mode 100644 index 0000000..03c9c30 --- /dev/null +++ b/backend/src/common/filters/index.ts @@ -0,0 +1 @@ +export * from './all-exceptions.filter'; diff --git a/backend/src/common/index.ts b/backend/src/common/index.ts new file mode 100644 index 0000000..2b979cb --- /dev/null +++ b/backend/src/common/index.ts @@ -0,0 +1,12 @@ +export * from './common.module'; +export * from './constants'; +export * from './decorators'; +export * from './dto'; +export * from './enums'; +export * from './errors'; +export * from './filters'; +export * from './interceptors'; +export * from './middleware'; +export * from './modules'; +export * from './types'; +export * from './utils'; diff --git a/backend/src/common/interceptors/index.ts b/backend/src/common/interceptors/index.ts new file mode 100644 index 0000000..d6f3e48 --- /dev/null +++ b/backend/src/common/interceptors/index.ts @@ -0,0 +1,2 @@ +export * from './logging.interceptor'; +export * from './transform-to-dto.interceptor'; diff --git a/backend/src/common/interceptors/logging.interceptor.ts b/backend/src/common/interceptors/logging.interceptor.ts new file mode 100644 index 0000000..44889a8 --- /dev/null +++ b/backend/src/common/interceptors/logging.interceptor.ts @@ -0,0 +1,21 @@ +import { Logger, Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; +import { Request } from 'express'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + private readonly logger = new Logger('Request'); + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const now = Date.now(); + request.id = now.toString(); + const msg = `${request.id} ${request.method} ${request.hostname}${request.originalUrl}`; + const user = `User: <${request.accountId ?? ''}; ${request.userId ?? ''}; ${request.ips?.[0] ?? request.ip}>`; + + this.logger.log(`${msg}\t${user}`); + + return next.handle().pipe(tap(() => this.logger.log(`${msg}\t<${Date.now() - now}ms>`, 'Response'))); + } +} diff --git a/backend/src/common/interceptors/transform-to-dto.interceptor.ts b/backend/src/common/interceptors/transform-to-dto.interceptor.ts new file mode 100644 index 0000000..b8bd6bd --- /dev/null +++ b/backend/src/common/interceptors/transform-to-dto.interceptor.ts @@ -0,0 +1,22 @@ +import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Injectable() +export class TransformToDtoInterceptor implements NestInterceptor { + private transformData(data: any) { + return data?.toDto ? data.toDto() : data; + } + + intercept(_context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + map((data) => { + if (Array.isArray(data)) { + return data.map(this.transformData); + } + + return this.transformData(data); + }), + ); + } +} diff --git a/backend/src/common/middleware/extract-subdomain.middleware.ts b/backend/src/common/middleware/extract-subdomain.middleware.ts new file mode 100644 index 0000000..c300a90 --- /dev/null +++ b/backend/src/common/middleware/extract-subdomain.middleware.ts @@ -0,0 +1,8 @@ +import { Request, Response, NextFunction } from 'express'; + +export const extractSubdomain = (request: Request, _response: Response, next: NextFunction) => { + const parts = request.hostname.split('.'); + request.subdomain = parts.length >= 4 ? parts[0] : null; + + next(); +}; diff --git a/backend/src/common/middleware/index.ts b/backend/src/common/middleware/index.ts new file mode 100644 index 0000000..e9f92ee --- /dev/null +++ b/backend/src/common/middleware/index.ts @@ -0,0 +1 @@ +export * from './extract-subdomain.middleware'; diff --git a/backend/src/common/modules/cache/cache.constants.ts b/backend/src/common/modules/cache/cache.constants.ts new file mode 100644 index 0000000..858a457 --- /dev/null +++ b/backend/src/common/modules/cache/cache.constants.ts @@ -0,0 +1,3 @@ +import { MODULE_OPTIONS_TOKEN } from './cache.module-definition'; + +export const CACHE_MODULE_OPTIONS = MODULE_OPTIONS_TOKEN; diff --git a/backend/src/common/modules/cache/cache.module-definition.ts b/backend/src/common/modules/cache/cache.module-definition.ts new file mode 100644 index 0000000..f5f1232 --- /dev/null +++ b/backend/src/common/modules/cache/cache.module-definition.ts @@ -0,0 +1,6 @@ +import { ConfigurableModuleBuilder } from '@nestjs/common'; +import { CacheModuleOptions } from './interfaces'; + +export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder() + .setClassMethodName('forRoot') + .build(); diff --git a/backend/src/common/modules/cache/cache.module.ts b/backend/src/common/modules/cache/cache.module.ts new file mode 100644 index 0000000..8872d4e --- /dev/null +++ b/backend/src/common/modules/cache/cache.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { CacheService } from './cache.service'; +import { ConfigurableModuleClass } from './cache.module-definition'; + +@Module({ + providers: [CacheService], + exports: [CacheService], +}) +export class CacheModule extends ConfigurableModuleClass {} diff --git a/backend/src/common/modules/cache/cache.service.ts b/backend/src/common/modules/cache/cache.service.ts new file mode 100644 index 0000000..5e0f081 --- /dev/null +++ b/backend/src/common/modules/cache/cache.service.ts @@ -0,0 +1,36 @@ +import { Inject } from '@nestjs/common'; +import { CacheServiceOptions, IORedisProviderOptions, Store } from './interfaces'; +import { IORedisProvider, StubProvider } from './providers'; +import { MODULE_OPTIONS_TOKEN } from './cache.module-definition'; + +export class CacheService implements Store { + private readonly store: Store; + + constructor(@Inject(MODULE_OPTIONS_TOKEN) private readonly options?: CacheServiceOptions) { + const type = this.options?.type || 'stub'; + switch (type) { + case 'ioredis': + this.store = new IORedisProvider(options?.options as IORedisProviderOptions); + break; + default: + this.store = new StubProvider(); + break; + } + } + + async get(key: string): Promise { + return this.store.get(key); + } + async set(key: string, value: T, seconds?: number): Promise { + return this.store.set(key, value, seconds); + } + async del(key: string): Promise { + return this.store.del(key); + } + async clear(): Promise { + return this.store.clear(); + } + async wrap(key: string, fn: () => Promise, seconds?: number): Promise { + return this.store.wrap(key, fn, seconds); + } +} diff --git a/backend/src/common/modules/cache/index.ts b/backend/src/common/modules/cache/index.ts new file mode 100644 index 0000000..aee8067 --- /dev/null +++ b/backend/src/common/modules/cache/index.ts @@ -0,0 +1,4 @@ +export * from './cache.module'; +export * from './cache.service'; +export * from './interfaces'; +export * from './providers'; diff --git a/backend/src/common/modules/cache/interfaces/cache-module.interface.ts b/backend/src/common/modules/cache/interfaces/cache-module.interface.ts new file mode 100644 index 0000000..e8b5f92 --- /dev/null +++ b/backend/src/common/modules/cache/interfaces/cache-module.interface.ts @@ -0,0 +1,4 @@ +import { CacheServiceOptions } from './cache-service.interface'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface CacheModuleOptions extends CacheServiceOptions {} diff --git a/backend/src/common/modules/cache/interfaces/cache-service.interface.ts b/backend/src/common/modules/cache/interfaces/cache-service.interface.ts new file mode 100644 index 0000000..44960b5 --- /dev/null +++ b/backend/src/common/modules/cache/interfaces/cache-service.interface.ts @@ -0,0 +1,8 @@ +import { CacheProviderType } from '../types'; +import { IORedisProviderOptions } from './ioredis-provider.interface'; + +export interface CacheServiceOptions { + type: CacheProviderType; + + options?: IORedisProviderOptions | null; +} diff --git a/backend/src/common/modules/cache/interfaces/index.ts b/backend/src/common/modules/cache/interfaces/index.ts new file mode 100644 index 0000000..9bb0281 --- /dev/null +++ b/backend/src/common/modules/cache/interfaces/index.ts @@ -0,0 +1,4 @@ +export * from './cache-module.interface'; +export * from './cache-service.interface'; +export * from './ioredis-provider.interface'; +export * from './store.interface'; diff --git a/backend/src/common/modules/cache/interfaces/ioredis-provider.interface.ts b/backend/src/common/modules/cache/interfaces/ioredis-provider.interface.ts new file mode 100644 index 0000000..bd360e0 --- /dev/null +++ b/backend/src/common/modules/cache/interfaces/ioredis-provider.interface.ts @@ -0,0 +1,3 @@ +import { RedisOptions } from 'ioredis'; + +export type IORedisProviderOptions = RedisOptions; diff --git a/backend/src/common/modules/cache/interfaces/store.interface.ts b/backend/src/common/modules/cache/interfaces/store.interface.ts new file mode 100644 index 0000000..24a7683 --- /dev/null +++ b/backend/src/common/modules/cache/interfaces/store.interface.ts @@ -0,0 +1,7 @@ +export interface Store { + get: (key: string) => Promise; + set: (key: string, value: T, seconds?: number) => Promise; + del: (key: string) => Promise; + clear: () => Promise; + wrap: (key: string, fn: () => Promise, seconds?: number) => Promise; +} diff --git a/backend/src/common/modules/cache/providers/index.ts b/backend/src/common/modules/cache/providers/index.ts new file mode 100644 index 0000000..0478b45 --- /dev/null +++ b/backend/src/common/modules/cache/providers/index.ts @@ -0,0 +1,2 @@ +export * from './ioredis.provider'; +export * from './stub.provider'; diff --git a/backend/src/common/modules/cache/providers/ioredis.provider.ts b/backend/src/common/modules/cache/providers/ioredis.provider.ts new file mode 100644 index 0000000..016d4f2 --- /dev/null +++ b/backend/src/common/modules/cache/providers/ioredis.provider.ts @@ -0,0 +1,52 @@ +import Redis from 'ioredis'; +import { IORedisProviderOptions, Store } from '../interfaces'; + +export class IORedisProvider implements Store { + private redis: Redis; + + constructor(options?: IORedisProviderOptions) { + this.redis = new Redis(options); + } + + async get(key: string): Promise { + const value = await this.redis.get(key); + if (value !== undefined && value !== null) { + return JSON.parse(value) as T; + } + + return undefined; + } + + async set(key: string, value: T, seconds?: number): Promise { + const valueStr = JSON.stringify(value); + if (seconds) { + await this.redis.setex(key, seconds, valueStr); + } else { + await this.redis.set(key, valueStr); + } + } + + async del(key: string): Promise { + const result = await this.redis.del(key); + + return result > 0; + } + + async clear(): Promise { + const result = await this.redis.flushdb(); + + return result === 'OK'; + } + + async wrap(key: string, fn: () => Promise, seconds?: number): Promise { + const value = await this.get(key); + if (value !== undefined) { + return value; + } + + const result = await fn(); + await this.set(key, result, seconds); + + return result; + } +} diff --git a/backend/src/common/modules/cache/providers/stub.provider.ts b/backend/src/common/modules/cache/providers/stub.provider.ts new file mode 100644 index 0000000..35207cf --- /dev/null +++ b/backend/src/common/modules/cache/providers/stub.provider.ts @@ -0,0 +1,9 @@ +import { Store } from '../interfaces'; + +export class StubProvider implements Store { + get = async () => undefined; + set = async () => null; + del = async () => true; + clear = async () => true; + wrap = async (_: string, fn: () => Promise) => fn(); +} diff --git a/backend/src/common/modules/cache/types/cache-provider.type.ts b/backend/src/common/modules/cache/types/cache-provider.type.ts new file mode 100644 index 0000000..226f4aa --- /dev/null +++ b/backend/src/common/modules/cache/types/cache-provider.type.ts @@ -0,0 +1 @@ +export type CacheProviderType = 'stub' | 'ioredis'; diff --git a/backend/src/common/modules/cache/types/index.ts b/backend/src/common/modules/cache/types/index.ts new file mode 100644 index 0000000..c2b58b9 --- /dev/null +++ b/backend/src/common/modules/cache/types/index.ts @@ -0,0 +1 @@ +export * from './cache-provider.type'; diff --git a/backend/src/common/modules/global-http/dns-cache.service.ts b/backend/src/common/modules/global-http/dns-cache.service.ts new file mode 100644 index 0000000..a377c73 --- /dev/null +++ b/backend/src/common/modules/global-http/dns-cache.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import CacheableLookup, { LookupOptions as CacheableLookupOptions } from '@esm2cjs/cacheable-lookup'; +import { LookupFunction } from 'net'; + +@Injectable() +export class DnsCacheService { + private cacheableLookup: CacheableLookup; + + constructor() { + this.cacheableLookup = new CacheableLookup({ + maxTtl: 300, // Cache DNS records for 5 minutes + errorTtl: 30, // Cache DNS errors for 30 seconds + fallbackDuration: 600, // Use outdated entries for 10 minutes if DNS is down + }); + } + + get lookupFunction(): LookupFunction { + return (hostname, options, callback) => { + if (typeof options === 'function') { + callback = options; + options = {}; + } + const cacheableLookupOptions = options as unknown as CacheableLookupOptions; + return this.cacheableLookup.lookup(hostname, cacheableLookupOptions, callback); + }; + } +} diff --git a/backend/src/common/modules/global-http/global-http.module.ts b/backend/src/common/modules/global-http/global-http.module.ts new file mode 100644 index 0000000..d157945 --- /dev/null +++ b/backend/src/common/modules/global-http/global-http.module.ts @@ -0,0 +1,21 @@ +import { Global, Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import * as http from 'http'; +import * as https from 'https'; +import { DnsCacheService } from './dns-cache.service'; + +@Global() +@Module({ + imports: [ + HttpModule.registerAsync({ + useFactory: (dnsCacheService: DnsCacheService) => ({ + httpAgent: new http.Agent({ lookup: dnsCacheService.lookupFunction }), + httpsAgent: new https.Agent({ lookup: dnsCacheService.lookupFunction }), + }), + inject: [DnsCacheService], + }), + ], + providers: [DnsCacheService], + exports: [HttpModule, DnsCacheService], +}) +export class GlobalHttpModule {} diff --git a/backend/src/common/modules/global-http/index.ts b/backend/src/common/modules/global-http/index.ts new file mode 100644 index 0000000..372d9c4 --- /dev/null +++ b/backend/src/common/modules/global-http/index.ts @@ -0,0 +1,2 @@ +export * from './dns-cache.service'; +export * from './global-http.module'; diff --git a/backend/src/common/modules/index.ts b/backend/src/common/modules/index.ts new file mode 100644 index 0000000..8c82ea9 --- /dev/null +++ b/backend/src/common/modules/index.ts @@ -0,0 +1,4 @@ +export * from './cache'; +export * from './global-http'; +export * from './token'; +export * from './url-generator'; diff --git a/backend/src/common/modules/token/errors/index.ts b/backend/src/common/modules/token/errors/index.ts new file mode 100644 index 0000000..3e0186d --- /dev/null +++ b/backend/src/common/modules/token/errors/index.ts @@ -0,0 +1 @@ +export * from './invalid-token.error'; diff --git a/backend/src/common/modules/token/errors/invalid-token.error.ts b/backend/src/common/modules/token/errors/invalid-token.error.ts new file mode 100644 index 0000000..f00c6d3 --- /dev/null +++ b/backend/src/common/modules/token/errors/invalid-token.error.ts @@ -0,0 +1,8 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '../../../errors'; + +export class InvalidTokenError extends ServiceError { + constructor(message = 'Invalid token') { + super({ errorCode: 'invalid_token', status: HttpStatus.UNAUTHORIZED, message }); + } +} diff --git a/backend/src/common/modules/token/index.ts b/backend/src/common/modules/token/index.ts new file mode 100644 index 0000000..f6d0f63 --- /dev/null +++ b/backend/src/common/modules/token/index.ts @@ -0,0 +1,2 @@ +export * from './token.module'; +export * from './token.service'; diff --git a/backend/src/common/modules/token/token.module.ts b/backend/src/common/modules/token/token.module.ts new file mode 100644 index 0000000..c923900 --- /dev/null +++ b/backend/src/common/modules/token/token.module.ts @@ -0,0 +1,17 @@ +import { Global, Module } from '@nestjs/common'; +import { TokenService } from './token.service'; +import { JwtModule } from '@nestjs/jwt'; + +@Global() +@Module({ + imports: [ + JwtModule.register({ + global: true, + privateKey: `${process.cwd()}/var/jwt/private.pem`, + publicKey: `${process.cwd()}/var/jwt/private.pem`, + }), + ], + providers: [TokenService], + exports: [TokenService], +}) +export class TokenModule {} diff --git a/backend/src/common/modules/token/token.service.ts b/backend/src/common/modules/token/token.service.ts new file mode 100644 index 0000000..4cdb69a --- /dev/null +++ b/backend/src/common/modules/token/token.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@nestjs/common'; +import { JwtService, JwtSignOptions } from '@nestjs/jwt'; +import { JsonWebTokenError } from 'jsonwebtoken'; + +import { InvalidTokenError } from './errors'; + +@Injectable() +export class TokenService { + constructor(private readonly jwtService: JwtService) {} + + create(payload: Buffer | object, options?: JwtSignOptions): string { + return this.jwtService.sign(payload, options); + } + + verify(token: string): Payload { + try { + return this.jwtService.verify(token); + } catch (e) { + if (e instanceof JsonWebTokenError) { + throw new InvalidTokenError(e.message); + } + throw e; + } + } +} diff --git a/backend/src/common/modules/url-generator/index.ts b/backend/src/common/modules/url-generator/index.ts new file mode 100644 index 0000000..d9c1124 --- /dev/null +++ b/backend/src/common/modules/url-generator/index.ts @@ -0,0 +1,2 @@ +export * from './url-generator.module'; +export * from './url-generator.service'; diff --git a/backend/src/common/modules/url-generator/url-generator.module.ts b/backend/src/common/modules/url-generator/url-generator.module.ts new file mode 100644 index 0000000..babff04 --- /dev/null +++ b/backend/src/common/modules/url-generator/url-generator.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { UrlGeneratorService } from './url-generator.service'; + +@Module({ + providers: [UrlGeneratorService], + exports: [UrlGeneratorService], +}) +export class UrlGeneratorModule {} diff --git a/backend/src/common/modules/url-generator/url-generator.service.ts b/backend/src/common/modules/url-generator/url-generator.service.ts new file mode 100644 index 0000000..5e3ab39 --- /dev/null +++ b/backend/src/common/modules/url-generator/url-generator.service.ts @@ -0,0 +1,34 @@ +import { ConfigService } from '@nestjs/config'; +import { Injectable } from '@nestjs/common'; + +import { ApplicationConfig } from '@/config'; + +import { FormatUrlOptions, formatUrlPath, formatUrlQuery } from '../../utils'; + +type CreateUrlParams = { route?: string; subdomain?: string } & FormatUrlOptions; + +@Injectable() +export class UrlGeneratorService { + private _appConfig: ApplicationConfig; + + constructor(private readonly configService: ConfigService) { + this._appConfig = this.configService.get('application'); + } + + private baseUrl(subdomain?: string) { + return subdomain ? `${this._appConfig.baseUrlTemplate.replace('{subdomain}', subdomain)}` : this._appConfig.baseUrl; + } + + createUrl(params?: CreateUrlParams): string { + if (!params) { + return this.baseUrl(); + } + + let formattedRoute = params.path ? formatUrlPath(params.route, params.path) : params.route; + if (formattedRoute.length && !formattedRoute.startsWith('/')) { + formattedRoute = '/' + formattedRoute; + } + + return formatUrlQuery(`${this.baseUrl(params.subdomain)}${formattedRoute}`, params.query); + } +} diff --git a/backend/src/common/types/date/date-period.ts b/backend/src/common/types/date/date-period.ts new file mode 100644 index 0000000..5df79d1 --- /dev/null +++ b/backend/src/common/types/date/date-period.ts @@ -0,0 +1,69 @@ +import { DatePeriodDto, DatePeriodFilter } from '../../dto'; +import { DatePeriodFilterType } from '../../enums'; +import { DateUtil } from '../../utils'; + +export class DatePeriod { + public from?: Date | null; + public to?: Date | null; + + constructor(from: Date | null | undefined, to: Date | null | undefined) { + this.from = from; + this.to = to; + } + + public static fromDto(dto: DatePeriodDto, isISO = true): DatePeriod { + return new DatePeriod( + dto.startDate ? (isISO ? DateUtil.fromISOString(dto.startDate) : new Date(dto.startDate)) : undefined, + dto.endDate ? (isISO ? DateUtil.fromISOString(dto.endDate) : new Date(dto.endDate)) : undefined, + ); + } + + public static fromFilter(filter: DatePeriodFilter): DatePeriod { + const now = DateUtil.now(); + let from: Date | null = null; + let to: Date | null = null; + switch (filter.type) { + case DatePeriodFilterType.Today: + from = DateUtil.startOf(now, 'day'); + to = DateUtil.endOf(now, 'day'); + break; + case DatePeriodFilterType.Yesterday: + from = DateUtil.sub(DateUtil.startOf(now, 'day'), { days: 1 }); + to = DateUtil.sub(DateUtil.endOf(now, 'day'), { days: 1 }); + break; + case DatePeriodFilterType.CurrentWeek: + from = DateUtil.startOf(now, 'week'); + to = DateUtil.endOf(now, 'week'); + break; + case DatePeriodFilterType.LastWeek: + from = DateUtil.sub(DateUtil.startOf(now, 'week'), { weeks: 1 }); + to = DateUtil.sub(DateUtil.endOf(now, 'week'), { weeks: 1 }); + break; + case DatePeriodFilterType.CurrentMonth: + from = DateUtil.startOf(now, 'month'); + to = DateUtil.endOf(now, 'month'); + break; + case DatePeriodFilterType.LastMonth: + from = DateUtil.sub(DateUtil.startOf(now, 'month'), { months: 1 }); + to = DateUtil.sub(DateUtil.endOf(now, 'month'), { months: 1 }); + break; + case DatePeriodFilterType.CurrentQuarter: + from = DateUtil.startOf(now, 'quarter'); + to = DateUtil.endOf(now, 'quarter'); + break; + case DatePeriodFilterType.LastQuarter: + from = DateUtil.sub(DateUtil.startOf(now, 'quarter'), { months: 3 }); + to = DateUtil.sub(DateUtil.endOf(now, 'quarter'), { months: 3 }); + break; + case DatePeriodFilterType.Period: + from = filter.from ? DateUtil.fromISOString(filter.from) : null; + to = filter.to ? DateUtil.fromISOString(filter.to) : null; + break; + } + return new DatePeriod(from, to); + } + + public toDto(): DatePeriodDto { + return { startDate: this.from?.toISOString(), endDate: this.to?.toISOString() }; + } +} diff --git a/backend/src/common/types/date/index.ts b/backend/src/common/types/date/index.ts new file mode 100644 index 0000000..8114e31 --- /dev/null +++ b/backend/src/common/types/date/index.ts @@ -0,0 +1 @@ +export * from './date-period'; diff --git a/backend/src/common/types/events/index.ts b/backend/src/common/types/events/index.ts new file mode 100644 index 0000000..dfc1ba6 --- /dev/null +++ b/backend/src/common/types/events/index.ts @@ -0,0 +1 @@ +export * from './service.event'; diff --git a/backend/src/common/types/events/service.event.ts b/backend/src/common/types/events/service.event.ts new file mode 100644 index 0000000..c4920db --- /dev/null +++ b/backend/src/common/types/events/service.event.ts @@ -0,0 +1,34 @@ +import { v4 as uuidv4 } from 'uuid'; + +export class ServiceEvent { + source: string; + key: string; + prevEvent?: ServiceEvent | null; + + constructor(data: { source: string; key?: string; prevEvent?: ServiceEvent | null }) { + this.source = data.source; + this.key = data.key ?? uuidv4(); + this.prevEvent = data.prevEvent; + } + + checkHistory({ + source, + key, + checked = [], + }: { + source?: string; + key?: string; + checked?: { source: string; key: string }[]; + }): T | null { + if (checked.find((item) => item.source === this.source && item.key === this.key)) { + return null; + } + checked.push({ source: this.source, key: this.key }); + + if ((!source || this.source === source) && (!key || this.key === key)) { + return this as unknown as T; + } + + return this.prevEvent ? this.prevEvent.checkHistory({ source, key }) : null; + } +} diff --git a/backend/src/common/types/index.ts b/backend/src/common/types/index.ts new file mode 100644 index 0000000..320657c --- /dev/null +++ b/backend/src/common/types/index.ts @@ -0,0 +1,5 @@ +export * from './date'; +export * from './events'; +export * from './quantity-amount'; +export * from './sort-order'; +export * from './task-queue'; diff --git a/backend/src/common/types/quantity-amount.ts b/backend/src/common/types/quantity-amount.ts new file mode 100644 index 0000000..1c5da43 --- /dev/null +++ b/backend/src/common/types/quantity-amount.ts @@ -0,0 +1,26 @@ +import { QuantityAmountDto } from '../dto'; + +export class QuantityAmount { + quantity: number; + amount: number; + + constructor(quantity: number, amount: number) { + this.quantity = quantity; + this.amount = amount; + } + + public static empty(): QuantityAmount { + return new QuantityAmount(0, 0); + } + + public toDto(): QuantityAmountDto { + return new QuantityAmountDto(this.quantity, this.amount); + } + + public add(value?: QuantityAmount): QuantityAmount { + this.quantity += value?.quantity ?? 0; + this.amount += value?.amount ?? 0; + + return this; + } +} diff --git a/backend/src/common/types/sort-order.ts b/backend/src/common/types/sort-order.ts new file mode 100644 index 0000000..65d9433 --- /dev/null +++ b/backend/src/common/types/sort-order.ts @@ -0,0 +1 @@ +export type SortOrder = 'ASC' | 'DESC'; diff --git a/backend/src/common/types/task-queue.ts b/backend/src/common/types/task-queue.ts new file mode 100644 index 0000000..5e4d704 --- /dev/null +++ b/backend/src/common/types/task-queue.ts @@ -0,0 +1,31 @@ +import { Logger } from '@nestjs/common'; + +type Task = () => Promise; + +export class TaskQueue { + private readonly logger = new Logger(TaskQueue.name); + private readonly queue: Task[] = []; + private isProcessing = false; + + enqueue(task: Task) { + this.queue.push(task); + this.process(); + } + + private async process() { + if (this.isProcessing) return; + this.isProcessing = true; + + while (this.queue.length > 0) { + const task = this.queue.shift(); + try { + await task(); + } catch (e) { + const error = e as Error; + this.logger.error(`Process task failed: ${error?.message}`, error?.stack); + } + } + + this.isProcessing = false; + } +} diff --git a/backend/src/common/utils/array.util.ts b/backend/src/common/utils/array.util.ts new file mode 100644 index 0000000..e6eb659 --- /dev/null +++ b/backend/src/common/utils/array.util.ts @@ -0,0 +1,7 @@ +export const isUnique = (value: T, index: number, self: T[]): boolean => { + return self.indexOf(value) === index; +}; + +export const intersection = (arr1: T[] | null | undefined, arr2: T[] | null | undefined): T[] | undefined => { + return arr1 && arr2 ? arr1.filter((id) => arr2.includes(id)) : (arr1 ?? arr2 ?? undefined); +}; diff --git a/backend/src/common/utils/date.util.ts b/backend/src/common/utils/date.util.ts new file mode 100644 index 0000000..2c08b9c --- /dev/null +++ b/backend/src/common/utils/date.util.ts @@ -0,0 +1,189 @@ +import * as FNS from 'date-fns'; + +import { isUnique } from './array.util'; + +type UnitOfTime = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'; + +const WeekDaysNames = { + en: ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'], +}; + +type FormatPresets = 'date' | 'time' | 'dateAndTime'; +const Formats: Record = { + date: 'dd.MM.yyyy', + time: 'HH:mm:ss', + dateAndTime: 'dd.MM.yyyy HH:mm:ss', +}; + +export class DateUtil { + static now(): Date { + return new Date(); + } + + static fromISOString(dateStr: string | null): Date | null { + return dateStr ? new Date(dateStr.endsWith('Z') ? dateStr : `${dateStr}Z`) : null; + } + + static parse(date: string, format: string): Date { + return FNS.parse(date, format, DateUtil.now()); + } + + static format(date: Date, formatStr: string, options?: FNS.FormatOptions): string { + return FNS.format(date, formatStr, options); + } + static formatPreset(date: Date, preset: FormatPresets): string { + return FNS.format(date, Formats[preset]); + } + + static add(date: Date, duration: FNS.Duration): Date { + return FNS.add(date, duration); + } + static sub(date: Date, duration: FNS.Duration): Date { + return FNS.sub(date, duration); + } + + static startOf(date: Date, unit: UnitOfTime): Date { + const isoCorrection = date.getTimezoneOffset(); + switch (unit) { + case 'second': + return FNS.startOfSecond(date); + case 'minute': + return FNS.startOfMinute(date); + case 'hour': + return FNS.startOfHour(date); + case 'day': + return FNS.subMinutes(FNS.startOfDay(date), isoCorrection); + case 'week': + //TODO: add settings for week start + return FNS.subMinutes(FNS.startOfWeek(date, { weekStartsOn: 1 }), isoCorrection); + case 'month': + return FNS.subMinutes(FNS.startOfMonth(date), isoCorrection); + case 'quarter': + return FNS.subMinutes(FNS.startOfQuarter(date), isoCorrection); + case 'year': + return FNS.subMinutes(FNS.startOfYear(date), isoCorrection); + } + } + static endOf(date: Date, unit: UnitOfTime): Date { + const isoCorrection = date.getTimezoneOffset(); + switch (unit) { + case 'second': + return FNS.endOfSecond(date); + case 'minute': + return FNS.endOfMinute(date); + case 'hour': + return FNS.endOfHour(date); + case 'day': + return FNS.subMinutes(FNS.endOfDay(date), isoCorrection); + case 'week': + //TODO: add settings for week start + return FNS.subMinutes(FNS.endOfWeek(date, { weekStartsOn: 1 }), isoCorrection); + case 'month': + return FNS.subMinutes(FNS.endOfMonth(date), isoCorrection); + case 'quarter': + return FNS.subMinutes(FNS.endOfQuarter(date), isoCorrection); + case 'year': + return FNS.subMinutes(FNS.endOfYear(date), isoCorrection); + } + } + + static diff({ + startDate, + endDate, + unit, + abs = true, + }: { + startDate: Date; + endDate: Date; + unit: UnitOfTime; + abs?: boolean; + }): number { + let diff = 0; + switch (unit) { + case 'second': + diff = FNS.differenceInSeconds(endDate, startDate); + break; + case 'minute': + diff = FNS.differenceInMinutes(endDate, startDate); + break; + case 'hour': + diff = FNS.differenceInHours(endDate, startDate); + break; + case 'day': + diff = FNS.differenceInDays(endDate, startDate); + break; + case 'week': + diff = FNS.differenceInWeeks(endDate, startDate); + break; + case 'month': + diff = FNS.differenceInMonths(endDate, startDate); + break; + case 'quarter': + diff = FNS.differenceInQuarters(endDate, startDate); + break; + case 'year': + diff = FNS.differenceInYears(endDate, startDate); + break; + } + return abs ? Math.abs(diff) : diff; + } + + static isToday(date: Date): boolean { + return FNS.isToday(date); + } + static isPast(date: Date): boolean { + return FNS.isPast(date); + } + static isFuture(date: Date): boolean { + return FNS.isFuture(date); + } + + static sort(a: Date, b: Date): number { + return a.getTime() - b.getTime(); + } + + static isValid(date: Date): boolean { + return FNS.isValid(date); + } + + static workingDaysBetween(from: Date, to: Date, workingWeekDays: string[]): number { + const workingDays = workingWeekDays + .map((day) => day.toLowerCase()) + .filter(isUnique) + .filter((d) => WeekDaysNames.en.includes(d)); + + const calendarDifference = FNS.differenceInCalendarDays(to, from); + const sign = calendarDifference < 0 ? -1 : 1; + const weeks = sign < 0 ? Math.ceil(calendarDifference / 7) : Math.floor(calendarDifference / 7); + let result = weeks * workingDays.length; + let dateFrom = FNS.add(from, { weeks }); + const nonWorkingWeekDays = WeekDaysNames.en + .filter((day) => !workingDays.includes(day)) + .map((day) => WeekDaysNames.en.indexOf(day)); + while (!FNS.isSameDay(to, dateFrom)) { + result += nonWorkingWeekDays.includes(FNS.getDay(dateFrom)) ? 0 : sign; + dateFrom = FNS.add(dateFrom, { days: sign }); + } + return result; + } + + /** + * Get time offset from UTC string + * @param utcStringChunk in "UTC+0200" format + * @returns time offset in hours + */ + static extractOffsetFromUTCString(utcStringChunk: string): number | null { + const match = utcStringChunk.match(/UTC([+-])(\d{2})(\d{2})/); + + if (!match) { + return null; + } + + const sign = match[1] === '+' ? 1 : -1; + const hours = parseInt(match[2], 10); + const minutes = parseInt(match[3], 10); + const totalOffsetInHours = sign * (hours + minutes / 60); + + return totalOffsetInHours; + } +} diff --git a/backend/src/common/utils/index.ts b/backend/src/common/utils/index.ts new file mode 100644 index 0000000..448e4dd --- /dev/null +++ b/backend/src/common/utils/index.ts @@ -0,0 +1,11 @@ +export * from './array.util'; +export * from './date.util'; +export * from './number.util'; +export * from './object.util'; +export * from './password.util'; +export * from './phone.util'; +export * from './promise.util'; +export * from './propagation.util'; +export * from './string.util'; +export * from './tree.util'; +export * from './url.util'; diff --git a/backend/src/common/utils/number.util.ts b/backend/src/common/utils/number.util.ts new file mode 100644 index 0000000..1c86a8f --- /dev/null +++ b/backend/src/common/utils/number.util.ts @@ -0,0 +1,18 @@ +import writtenNumber from 'written-number'; +import { convert as convertRu } from 'number-to-words-ru'; + +export class NumberUtil { + static toWord(value: number, options?: { language?: string; currency?: string }): string { + return options?.language === 'ru' + ? convertRu(value, { + currency: options?.currency as 'rub' | 'usd' | 'eur', + showNumberParts: { fractional: false }, + showCurrency: { integer: !!options.currency, fractional: !!options.currency }, + }).toLowerCase() + : writtenNumber(value, { lang: options?.language }); + } + + static toNumber(value: unknown): number { + return value ? Number(value) : 0; + } +} diff --git a/backend/src/common/utils/object.util.ts b/backend/src/common/utils/object.util.ts new file mode 100644 index 0000000..497076e --- /dev/null +++ b/backend/src/common/utils/object.util.ts @@ -0,0 +1,11 @@ +export class ObjectUtil { + public static assign(target: T, source: U): T & U { + Object.keys(source).forEach((field) => { + if (source[field] !== undefined) { + target[field] = source[field]; + } + }); + + return target as T & U; + } +} diff --git a/backend/src/common/utils/password.util.ts b/backend/src/common/utils/password.util.ts new file mode 100644 index 0000000..e6f1adb --- /dev/null +++ b/backend/src/common/utils/password.util.ts @@ -0,0 +1,92 @@ +import * as bcrypt from 'bcrypt'; +import { generate } from 'generate-password'; + +const rounds = 12; + +interface GenerateOptions { + /** + * Length of the generated password. + * @default 10 + */ + length?: number; + /** + * Should the password include numbers + * @default false + */ + numbers?: boolean; + /** + * Should the password include symbols, or symbols to include + * @default false + */ + symbols?: boolean | string; + /** + * Should the password include lowercase characters + * @default true + */ + lowercase?: boolean; + /** + * Should the password include uppercase characters + * @default true + */ + uppercase?: boolean; + /** + * Should exclude visually similar characters like 'i' and 'I' + * @default false + */ + excludeSimilarCharacters?: boolean; + /** + * List of characters to be excluded from the password + * @default "" + */ + exclude?: string; + /** + * Password should include at least one character from each pool + * @default false + */ + strict?: boolean; +} +export class PasswordUtil { + static generate(options?: GenerateOptions): string { + return generate(options); + } + + static generateSecure( + options: GenerateOptions = { + length: 12, + numbers: true, + symbols: false, + lowercase: true, + uppercase: true, + excludeSimilarCharacters: true, + }, + ): string { + return generate(options); + } + + static hash(plainPassword: string): string { + return bcrypt.hashSync(plainPassword, rounds); + } + + static verify(password: string, hash: string): boolean { + return bcrypt.compareSync(password, hash); + } + + static isStrong(password: string): boolean { + // Minimum 8 characters + if (password.length < 8) return false; + + // At least one uppercase letter + if (!/[A-Z]/.test(password)) return false; + + // At least one lowercase letter + if (!/[a-z]/.test(password)) return false; + + // At least one number + if (!/[0-9]/.test(password)) return false; + + // At least one special character + if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) return false; + + return true; + } +} diff --git a/backend/src/common/utils/phone.util.ts b/backend/src/common/utils/phone.util.ts new file mode 100644 index 0000000..02fa157 --- /dev/null +++ b/backend/src/common/utils/phone.util.ts @@ -0,0 +1,16 @@ +import parsePhoneNumberWithError, { ParseError } from 'libphonenumber-js'; +import { InvalidPhoneError } from '../errors'; + +export class PhoneUtil { + static normalize(phone: string): string { + try { + return parsePhoneNumberWithError(phone).number; + } catch (e) { + if (e instanceof ParseError) { + throw new InvalidPhoneError(e.message); + } else { + throw new InvalidPhoneError(); + } + } + } +} diff --git a/backend/src/common/utils/promise.util.ts b/backend/src/common/utils/promise.util.ts new file mode 100644 index 0000000..2f2bb71 --- /dev/null +++ b/backend/src/common/utils/promise.util.ts @@ -0,0 +1,9 @@ +export const withTimeout = (promise: Promise, ms: number, timeoutResult?: T): Promise => { + const timeout = new Promise((resolve) => setTimeout(() => resolve(timeoutResult), ms)); + + return Promise.race([promise, timeout]); +}; + +export const sleep = (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; diff --git a/backend/src/common/utils/propagation.util.ts b/backend/src/common/utils/propagation.util.ts new file mode 100644 index 0000000..890a484 --- /dev/null +++ b/backend/src/common/utils/propagation.util.ts @@ -0,0 +1,26 @@ +export const propagateData = R }>( + hierarchy: T[], + rowsMap: Map, + initializer: (ownerId: number) => R, +) => { + const propagateSubordinates = (node: T): R | null => { + let aggregatedRow = rowsMap.get(node.id) || initializer(node.id); + + let hasValue = rowsMap.has(node.id); + node.subordinates.forEach((subordinate) => { + const subordinateRow = propagateSubordinates(subordinate); + if (subordinateRow) { + aggregatedRow = aggregatedRow.add(subordinateRow); + hasValue = true; + } + }); + return hasValue ? aggregatedRow : null; + }; + + hierarchy.forEach((node) => { + const aggregatedRow = propagateSubordinates(node); + if (aggregatedRow) { + rowsMap.set(node.id, aggregatedRow); + } + }); +}; diff --git a/backend/src/common/utils/string.util.ts b/backend/src/common/utils/string.util.ts new file mode 100644 index 0000000..492546b --- /dev/null +++ b/backend/src/common/utils/string.util.ts @@ -0,0 +1,62 @@ +import { decode } from 'iconv-lite'; + +export const formatState = (...params: unknown[]): string => params?.join(':'); + +export const parseState = (state: string, parser: (value: string) => T): T[] => + state.split(':').map((part) => parser(part)); + +export const splitByFirstSpace = (input: string): [string, string] => { + const parts = input.split(/\s/, 2); + + return parts.length === 1 ? [parts[0], ''] : [parts[0], input.substring(parts[0].length).trim()]; +}; + +export const capitalizeFirst = (str: string): string => { + return str.length === 0 ? str : str.charAt(0).toUpperCase() + str.slice(1); +}; + +export class StringUtil { + static decode(str: string, from: BufferEncoding, to: BufferEncoding): string { + return Buffer.from(str, from).toString(to); + } + + /** + * Decode MIME "encoded-word" format + * @param encodedStr encoded string + * @returns decoded string + */ + static decodeMimeWord(encodedStr: string): string { + const match = encodedStr.match(/=\?([^?]+)\?([BQ])\?([^?]*)\?=/i); + if (!match) return encodedStr; + + const charset = match[1].toLowerCase(); + const encoding = match[2].toUpperCase(); + const text = match[3]; + + let buffer: Buffer; + if (encoding === 'B') { + buffer = Buffer.from(text, 'base64'); + } else if (encoding === 'Q') { + // Replace underscore with space and decode quoted-printable + buffer = Buffer.from( + text.replace(/_/g, ' ').replace(/=([0-9A-F]{2})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))), + 'binary', + ); + } else { + return encodedStr; // Unsupported encoding + } + + return decode(buffer, charset); + } + + /** + * Decode RFC 5987 format + * @param encodedStr encoded string + * @returns decoded string + */ + static decodeRFC5987(encodedStr: string): string { + const parts = encodedStr.split("''"); + + return parts.length === 2 ? decodeURIComponent(parts[1].replace(/%20/g, ' ')) : encodedStr; + } +} diff --git a/backend/src/common/utils/tree.util.ts b/backend/src/common/utils/tree.util.ts new file mode 100644 index 0000000..c01b378 --- /dev/null +++ b/backend/src/common/utils/tree.util.ts @@ -0,0 +1,25 @@ +export const flattenTree = ( + root: T | T[], + accessor: (item: T) => T | T[] | null | undefined, + depthFirst = true, +): T[] => { + const result: T[] = []; + const queue: T[] = Array.isArray(root) ? [...root] : [root]; + + while (queue.length > 0) { + const current = depthFirst ? queue.pop() : queue.shift(); + result.push(current); + + const children = accessor(current); + if (children) { + const items = Array.isArray(children) ? children : [children]; + if (depthFirst) { + queue.push(...items.reverse()); + } else { + queue.unshift(...items); + } + } + } + + return result; +}; diff --git a/backend/src/common/utils/url.util.ts b/backend/src/common/utils/url.util.ts new file mode 100644 index 0000000..2af0513 --- /dev/null +++ b/backend/src/common/utils/url.util.ts @@ -0,0 +1,37 @@ +import { compile, ParseOptions } from 'path-to-regexp'; + +type UrlParams = Record; +export interface FormatUrlOptions { + path?: UrlParams; + query?: UrlParams; +} + +export const formatUrl = (url: string, options?: FormatUrlOptions): string => { + return formatUrlQuery(formatUrlPath(url, options?.path), options?.query); +}; + +export const formatUrlPath = (url: string, params?: UrlParams | null): string => { + if (!params) { + return url; + } + + const toPath = compile(url, { encode: encodeURIComponent } as ParseOptions); + return toPath(params); +}; + +export const formatUrlQuery = (url: string, params?: UrlParams | null): string => { + if (!params) { + return url; + } + + const urlObj = new URL(url); + const searchParams = urlObj.searchParams; + + for (const key in params) { + if (params[key] !== undefined) { + searchParams.set(key, params[key].toString()); + } + } + + return String(urlObj); +}; diff --git a/backend/src/config/application.config.ts b/backend/src/config/application.config.ts new file mode 100644 index 0000000..54c32ec --- /dev/null +++ b/backend/src/config/application.config.ts @@ -0,0 +1,30 @@ +import { registerAs } from '@nestjs/config'; + +export interface ApplicationConfig { + baseUrl: string; + baseUrlTemplate: string; + port: number; + name: string; + apiKeyRequired: boolean; + feedbackEmail: string; + supportEmail: string; + verificationToken: string; + skeletonKey?: string; + subdomainPrefix: string; +} + +export default registerAs( + 'application', + (): ApplicationConfig => ({ + baseUrl: process.env.APPLICATION_BASE_URL, + baseUrlTemplate: process.env.APPLICATION_BASE_URL_TEMPLATE, + port: parseInt(process.env.APPLICATION_PORT, 10) || 8000, + name: process.env.APPLICATION_NAME, + apiKeyRequired: process.env.APPLICATION_API_KEY_REQUIRED === 'true', + feedbackEmail: process.env.APPLICATION_FEEDBACK_EMAIL, + supportEmail: process.env.APPLICATION_SUPPORT_EMAIL, + verificationToken: process.env.APPLICATION_VERIFICATION_TOKEN, + skeletonKey: process.env.APPLICATION_SKELETON_KEY, + subdomainPrefix: process.env.APPLICATION_SUBDOMAIN_PREFIX, + }), +); diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts new file mode 100644 index 0000000..253b86d --- /dev/null +++ b/backend/src/config/index.ts @@ -0,0 +1 @@ +export * from './application.config'; diff --git a/backend/src/database/backup/empty.2024-07-23.sql b/backend/src/database/backup/empty.2024-07-23.sql new file mode 100644 index 0000000..d4f7761 --- /dev/null +++ b/backend/src/database/backup/empty.2024-07-23.sql @@ -0,0 +1,9734 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 14.12 (Ubuntu 14.12-1.pgdg22.04+1ubuntu4) +-- Dumped by pg_dump version 14.9 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; + + +-- +-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)'; + + +-- +-- Name: chat_provider_user_type; Type: TYPE; Schema: public; Owner: - +-- + +CREATE TYPE public.chat_provider_user_type AS ENUM ( + 'accessible', + 'responsible', + 'supervisor' +); + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: account; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.account ( + id integer NOT NULL, + company_name character varying(255) NOT NULL, + subdomain character varying(255) NOT NULL, + created_at timestamp without time zone NOT NULL, + logo_id uuid +); + + +-- +-- Name: account_api_access; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.account_api_access ( + account_id integer NOT NULL, + api_key character varying NOT NULL, + created_at timestamp without time zone DEFAULT now() NOT NULL +); + + +-- +-- Name: account_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.account_id_seq + AS integer + START WITH 11023201 + INCREMENT BY 1 + MINVALUE 11023201 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: account_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.account_settings ( + account_id integer NOT NULL, + language character varying NOT NULL, + working_days character varying, + working_time_from time without time zone, + working_time_to time without time zone, + time_zone character varying, + currency character varying(3) NOT NULL, + number_format character varying, + phone_format character varying DEFAULT 'international'::character varying NOT NULL, + allow_duplicates boolean DEFAULT false NOT NULL +); + + +-- +-- Name: account_subscription; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.account_subscription ( + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + expired_at timestamp without time zone, + is_trial boolean NOT NULL, + user_limit integer NOT NULL, + plan_name character varying NOT NULL, + external_customer_id character varying +); + + +-- +-- Name: action; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.action ( + id integer NOT NULL, + type character varying(100) NOT NULL, + delay integer, + account_id integer NOT NULL +); + + +-- +-- Name: action_activity_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.action_activity_settings ( + action_id integer NOT NULL, + responsible_user_type character varying(100) NOT NULL, + responsible_user_id integer, + activity_type_id integer NOT NULL, + text character varying NOT NULL, + deadline_type character varying(100) NOT NULL, + deadline_time integer, + account_id integer NOT NULL +); + + +-- +-- Name: action_email_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.action_email_settings ( + action_id integer NOT NULL, + subject character varying NOT NULL, + content character varying, + send_as_html boolean NOT NULL, + mailbox_id integer, + user_id integer, + account_id integer NOT NULL, + signature character varying +); + + +-- +-- Name: action_entity_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.action_entity_settings ( + action_id integer NOT NULL, + stage_id integer NOT NULL, + account_id integer NOT NULL, + operation_type character varying DEFAULT 'move'::character varying NOT NULL +); + + +-- +-- Name: action_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.action_id_seq + AS integer + START WITH 51011001 + INCREMENT BY 1 + MINVALUE 51011001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: action_scheduled; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.action_scheduled ( + id integer NOT NULL, + action_id integer NOT NULL, + entity_id integer NOT NULL, + scheduled_time timestamp without time zone NOT NULL, + completed boolean NOT NULL, + account_id integer NOT NULL, + created_by integer NOT NULL +); + + +-- +-- Name: action_task_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.action_task_settings ( + action_id integer NOT NULL, + responsible_user_type character varying(100) NOT NULL, + responsible_user_id integer, + title character varying NOT NULL, + text character varying NOT NULL, + deadline_type character varying(100) NOT NULL, + deadline_time integer, + account_id integer NOT NULL +); + + +-- +-- Name: activity; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity ( + id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + account_id integer NOT NULL, + created_by integer NOT NULL, + responsible_user_id integer NOT NULL, + text character varying NOT NULL, + start_date timestamp without time zone NOT NULL, + end_date timestamp without time zone NOT NULL, + is_resolved boolean NOT NULL, + result character varying, + activity_type_id integer NOT NULL, + entity_id integer NOT NULL, + resolved_date timestamp without time zone, + weight double precision NOT NULL +); + + +-- +-- Name: activity_type; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.activity_type ( + id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + account_id integer NOT NULL, + name character varying(128) NOT NULL, + is_active boolean DEFAULT true NOT NULL +); + + +-- +-- Name: activity_type_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.activity_type_id_seq + AS integer + START WITH 25022001 + INCREMENT BY 1 + MINVALUE 25022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task ( + id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + account_id integer NOT NULL, + created_by integer NOT NULL, + responsible_user_id integer NOT NULL, + text character varying NOT NULL, + start_date timestamp without time zone, + end_date timestamp without time zone, + is_resolved boolean NOT NULL, + title character varying NOT NULL, + entity_id integer, + stage_id integer, + planned_time integer, + settings_id integer, + resolved_date timestamp without time zone, + weight double precision NOT NULL, + board_id integer +); + + +-- +-- Name: all_tasks; Type: VIEW; Schema: public; Owner: - +-- + +CREATE VIEW public.all_tasks AS + SELECT activity.id, + activity.created_at, + activity.account_id, + activity.created_by, + activity.responsible_user_id, + activity.text, + activity.start_date, + activity.end_date, + activity.is_resolved, + activity.resolved_date, + activity.result, + activity.entity_id, + activity.weight, + activity.activity_type_id, + NULL::character varying AS title, + NULL::integer AS planned_time, + NULL::integer AS settings_id, + NULL::integer AS board_id, + NULL::integer AS stage_id, + 'activity'::text AS type + FROM public.activity +UNION + SELECT task.id, + task.created_at, + task.account_id, + task.created_by, + task.responsible_user_id, + task.text, + task.start_date, + task.end_date, + task.is_resolved, + task.resolved_date, + NULL::character varying AS result, + task.entity_id, + task.weight, + NULL::integer AS activity_type_id, + task.title, + task.planned_time, + task.settings_id, + task.board_id, + task.stage_id, + 'task'::text AS type + FROM public.task + ORDER BY 1; + + +-- +-- Name: appsumo_license; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.appsumo_license ( + id integer NOT NULL, + license_key text NOT NULL, + license_status text NOT NULL, + plan_id text NOT NULL, + tier integer NOT NULL, + account_id integer, + created_at timestamp without time zone DEFAULT now() NOT NULL, + prev_license_key text +); + + +-- +-- Name: app_sumo_license_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.appsumo_license ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.app_sumo_license_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: appsumo_tier; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.appsumo_tier ( + id integer NOT NULL, + tier integer NOT NULL, + user_limit integer NOT NULL, + term_in_days integer NOT NULL, + plan_name text NOT NULL +); + + +-- +-- Name: app_sumo_preset_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.appsumo_tier ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.app_sumo_preset_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: automation; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.automation ( + id integer NOT NULL, + trigger_id integer NOT NULL, + action_id integer NOT NULL, + created_by integer NOT NULL, + is_active boolean NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: automation_condition; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.automation_condition ( + automation_id integer NOT NULL, + condition_id integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: automation_entity_type; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.automation_entity_type ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone DEFAULT now() NOT NULL, + created_by integer NOT NULL, + name text NOT NULL, + triggers text NOT NULL, + entity_type_id integer NOT NULL, + board_id integer, + stage_id integer, + conditions jsonb, + actions jsonb, + process_id integer +); + + +-- +-- Name: automation_entity_type_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.automation_entity_type ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.automation_entity_type_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: automation_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.automation_id_seq + AS integer + START WITH 51011001 + INCREMENT BY 1 + MINVALUE 51011001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: automation_process; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.automation_process ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone DEFAULT now() NOT NULL, + resource_key text, + bpmn_file text, + bpmn_process_id text +); + + +-- +-- Name: automation_process_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.automation_process ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.automation_process_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: automation_stage; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.automation_stage ( + automation_id integer NOT NULL, + stage_id integer NOT NULL +); + + +-- +-- Name: board; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.board ( + id integer NOT NULL, + name character varying(100) NOT NULL, + type character varying(50) NOT NULL, + record_id integer, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + sort_order smallint DEFAULT 0 NOT NULL, + is_system boolean DEFAULT false NOT NULL, + code character varying(255), + need_migration boolean DEFAULT false, + task_board_id integer, + owner_id integer, + participant_ids jsonb +); + + +-- +-- Name: board_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.board_id_seq + AS integer + START WITH 14022001 + INCREMENT BY 1 + MINVALUE 14022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: chat; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat ( + id integer NOT NULL, + provider_id integer NOT NULL, + created_by integer, + external_id character varying, + type character varying NOT NULL, + title character varying, + entity_id integer, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: chat_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.chat_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: chat_message; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_message ( + id integer NOT NULL, + chat_id integer NOT NULL, + chat_user_id integer NOT NULL, + external_id character varying, + text character varying NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + reply_to_id integer +); + + +-- +-- Name: chat_message_file; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_message_file ( + id integer NOT NULL, + message_id integer NOT NULL, + external_id character varying, + account_id integer NOT NULL, + file_id uuid, + name character varying NOT NULL, + mime_type character varying NOT NULL, + size integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: chat_message_file_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.chat_message_file_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: chat_message_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.chat_message_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: chat_message_reaction; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_message_reaction ( + id integer NOT NULL, + message_id integer NOT NULL, + chat_user_id integer NOT NULL, + reaction character varying NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: chat_message_reaction_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.chat_message_reaction_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: chat_message_user_status; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_message_user_status ( + chat_id integer NOT NULL, + message_id integer NOT NULL, + chat_user_id integer NOT NULL, + status character varying NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: chat_pinned_message; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_pinned_message ( + chat_id integer NOT NULL, + message_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: chat_provider; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_provider ( + id integer NOT NULL, + created_by integer NOT NULL, + type character varying NOT NULL, + title character varying, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + status character varying DEFAULT 'draft'::character varying NOT NULL, + transport character varying NOT NULL +); + + +-- +-- Name: chat_provider_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.chat_provider_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: chat_provider_messenger; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_provider_messenger ( + provider_id integer NOT NULL, + page_id character varying NOT NULL, + page_access_token character varying NOT NULL, + account_id integer NOT NULL, + user_id character varying NOT NULL, + user_access_token character varying NOT NULL +); + + +-- +-- Name: chat_provider_twilio; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_provider_twilio ( + provider_id integer NOT NULL, + account_sid character varying NOT NULL, + auth_token character varying NOT NULL, + phone_number character varying NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: chat_provider_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_provider_user ( + provider_id integer NOT NULL, + user_id integer NOT NULL, + account_id integer NOT NULL, + type public.chat_provider_user_type DEFAULT 'accessible'::public.chat_provider_user_type NOT NULL +); + + +-- +-- Name: chat_provider_wazzup; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_provider_wazzup ( + provider_id integer NOT NULL, + account_id integer NOT NULL, + api_key character varying NOT NULL, + channel_id character varying NOT NULL, + plain_id character varying NOT NULL, + transport character varying NOT NULL +); + + +-- +-- Name: chat_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_user ( + id integer NOT NULL, + chat_id integer NOT NULL, + user_id integer, + role character varying NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: chat_user_external; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.chat_user_external ( + account_id integer NOT NULL, + external_id character varying NOT NULL, + first_name character varying, + last_name character varying, + avatar_url character varying, + phone character varying, + email character varying, + link character varying, + chat_user_id integer NOT NULL +); + + +-- +-- Name: chat_user_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.chat_user_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: condition; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.condition ( + id integer NOT NULL, + type character varying(100) NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: condition_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.condition_id_seq + AS integer + START WITH 51011001 + INCREMENT BY 1 + MINVALUE 51011001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: demo_data; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.demo_data ( + id integer NOT NULL, + account_id integer NOT NULL, + type character varying NOT NULL, + ids character varying NOT NULL +); + + +-- +-- Name: demo_data_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.demo_data ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.demo_data_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: department; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.department ( + id integer NOT NULL, + name character varying NOT NULL, + parent_id integer, + account_id integer NOT NULL +); + + +-- +-- Name: department_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.department_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: document_template; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.document_template ( + id integer NOT NULL, + name character varying NOT NULL, + created_by integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + created_count integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: document_template_access; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.document_template_access ( + document_template_id integer NOT NULL, + user_id integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: document_template_entity_type; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.document_template_entity_type ( + document_template_id integer NOT NULL, + entity_type_id integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: document_template_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.document_template_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: entity; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity ( + id integer NOT NULL, + name character varying(255) NOT NULL, + entity_type_id integer NOT NULL, + responsible_user_id integer NOT NULL, + stage_id integer, + created_by integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + participant_ids jsonb, + weight double precision NOT NULL, + closed_at timestamp without time zone, + copied_from integer, + copied_count integer +); + + +-- +-- Name: entity_event; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity_event ( + id integer NOT NULL, + account_id integer NOT NULL, + entity_id integer NOT NULL, + object_id integer NOT NULL, + type character varying NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: entity_event_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.entity_event ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.entity_event_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: entity_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.entity_id_seq + AS integer + START WITH 14022001 + INCREMENT BY 1 + MINVALUE 14022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: entity_link; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity_link ( + id integer NOT NULL, + source_id integer NOT NULL, + target_id integer NOT NULL, + sort_order smallint NOT NULL, + back_link_id integer, + account_id integer NOT NULL +); + + +-- +-- Name: entity_link_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.entity_link_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: entity_list_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity_list_settings ( + id integer NOT NULL, + entity_type_id integer NOT NULL, + board_id integer, + settings jsonb NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: entity_list_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.entity_list_settings_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: entity_stage_history; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity_stage_history ( + id integer NOT NULL, + account_id integer NOT NULL, + entity_id integer NOT NULL, + board_id integer NOT NULL, + stage_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: entity_stage_history_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.entity_stage_history ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.entity_stage_history_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: entity_type; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity_type ( + id integer NOT NULL, + name character varying(255) NOT NULL, + entity_category character varying(50) NOT NULL, + section_name character varying(100) NOT NULL, + section_view character varying(50) NOT NULL, + section_icon character varying(50) NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + sort_order smallint DEFAULT 0 NOT NULL +); + + +-- +-- Name: entity_type_feature; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity_type_feature ( + entity_type_id integer NOT NULL, + feature_id smallint NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: entity_type_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.entity_type_id_seq + AS integer + START WITH 13022001 + INCREMENT BY 1 + MINVALUE 13022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: entity_type_link_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.entity_type_link_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: entity_type_link; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.entity_type_link ( + id integer DEFAULT nextval('public.entity_type_link_id_seq'::regclass) NOT NULL, + source_id integer NOT NULL, + target_id integer NOT NULL, + sort_order smallint NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: exact_time_trigger_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.exact_time_trigger_settings ( + trigger_id integer NOT NULL, + date timestamp without time zone NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: external_entity_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.external_entity_id_seq + START WITH 30022001 + INCREMENT BY 1 + MINVALUE 30022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: external_entity; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.external_entity ( + id integer DEFAULT nextval('public.external_entity_id_seq'::regclass) NOT NULL, + entity_id integer NOT NULL, + url character varying NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + system smallint, + raw_data jsonb, + ui_data jsonb +); + + +-- +-- Name: external_system; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.external_system ( + id smallint NOT NULL, + name character varying NOT NULL, + code character varying NOT NULL, + url_templates character varying[] NOT NULL +); + + +-- +-- Name: external_system_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.external_system ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.external_system_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: feature; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.feature ( + id smallint NOT NULL, + name character varying NOT NULL, + code character varying NOT NULL, + is_enabled boolean NOT NULL +); + + +-- +-- Name: feature_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.feature ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.feature_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: feed_item_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.feed_item_id_seq + AS integer + START WITH 22022001 + INCREMENT BY 1 + MINVALUE 22022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: file_link; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.file_link ( + id integer NOT NULL, + account_id integer NOT NULL, + source_type character varying NOT NULL, + source_id integer NOT NULL, + file_id uuid NOT NULL, + file_name character varying NOT NULL, + file_size integer NOT NULL, + file_type character varying NOT NULL, + created_at timestamp without time zone NOT NULL, + created_by integer NOT NULL +); + + +-- +-- Name: mail_message; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mail_message ( + id integer NOT NULL, + mailbox_id integer NOT NULL, + external_id character varying NOT NULL, + thread_id character varying NOT NULL, + snippet character varying, + sent_from character varying NOT NULL, + sent_to character varying, + subject character varying, + date timestamp without time zone NOT NULL, + account_id integer NOT NULL, + reply_to character varying, + cc character varying, + has_attachment boolean DEFAULT false NOT NULL, + message_id character varying, + references_to character varying, + in_reply_to character varying, + entity_id integer, + is_seen boolean DEFAULT false NOT NULL +); + + +-- +-- Name: note; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.note ( + id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + text character varying NOT NULL, + entity_id integer NOT NULL, + created_by integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: feed_items; Type: VIEW; Schema: public; Owner: - +-- + +CREATE VIEW public.feed_items AS + SELECT note.id, + note.created_at, + note.entity_id, + 'note'::text AS type + FROM public.note +UNION + SELECT task.id, + task.created_at, + task.entity_id, + 'task'::text AS type + FROM public.task +UNION + SELECT activity.id, + activity.created_at, + activity.entity_id, + 'activity'::text AS type + FROM public.activity +UNION +( SELECT max(message.id) AS id, + max(message.date) AS created_at, + message.entity_id, + 'mail'::text AS type + FROM public.mail_message message + WHERE (message.entity_id IS NOT NULL) + GROUP BY message.thread_id, message.entity_id + ORDER BY (max(message.date)) DESC) +UNION +( SELECT max(message.id) AS id, + max(message.date) AS created_at, + el.source_id AS entity_id, + 'mail'::text AS type + FROM (public.mail_message message + RIGHT JOIN public.entity_link el ON ((el.target_id = message.entity_id))) + WHERE (message.entity_id IS NOT NULL) + GROUP BY message.thread_id, el.source_id + ORDER BY (max(message.date)) DESC) +UNION + SELECT fl.id, + fl.created_at, + fl.source_id AS entity_id, + 'document'::text AS type + FROM public.file_link fl + WHERE ((fl.source_type)::text = 'entity_document'::text) + ORDER BY 2 DESC; + + +-- +-- Name: field; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.field ( + id integer NOT NULL, + name character varying(100) NOT NULL, + type character varying(50) NOT NULL, + sort_order smallint NOT NULL, + entity_type_id integer NOT NULL, + field_group_id integer, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + code character varying(255), + active boolean DEFAULT true NOT NULL, + value character varying +); + + +-- +-- Name: field_condition; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.field_condition ( + condition_id integer NOT NULL, + field_id integer NOT NULL, + field_type character varying(50) NOT NULL, + payload jsonb NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: field_group; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.field_group ( + id integer NOT NULL, + name character varying(100) NOT NULL, + sort_order smallint NOT NULL, + entity_type_id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: field_group_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.field_group_id_seq + AS integer + START WITH 41022001 + INCREMENT BY 1 + MINVALUE 41022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: field_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.field_id_seq + AS integer + START WITH 42022001 + INCREMENT BY 1 + MINVALUE 42022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: field_option; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.field_option ( + id integer NOT NULL, + label character varying(255) NOT NULL, + sort_order smallint NOT NULL, + field_id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + color character varying(50) +); + + +-- +-- Name: field_option_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.field_option_id_seq + AS integer + START WITH 43022001 + INCREMENT BY 1 + MINVALUE 43022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: field_stage_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.field_stage_settings ( + id integer NOT NULL, + account_id integer NOT NULL, + field_id integer NOT NULL, + stage_id integer NOT NULL, + access character varying NOT NULL, + exclude_user_ids integer[] +); + + +-- +-- Name: field_stage_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.field_stage_settings ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.field_stage_settings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: field_user_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.field_user_settings ( + id integer NOT NULL, + account_id integer NOT NULL, + field_id integer NOT NULL, + user_id integer NOT NULL, + access character varying NOT NULL +); + + +-- +-- Name: field_user_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.field_user_settings ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.field_user_settings_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: field_value; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.field_value ( + id integer NOT NULL, + field_id integer NOT NULL, + payload jsonb NOT NULL, + entity_id integer NOT NULL, + account_id integer NOT NULL, + field_type character varying(50) NOT NULL +); + + +-- +-- Name: field_value_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.field_value_id_seq + AS integer + START WITH 44022001 + INCREMENT BY 1 + MINVALUE 44022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: file_info; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.file_info ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + created_by integer, + original_name character varying NOT NULL, + mime_type character varying NOT NULL, + size integer NOT NULL, + store_path character varying NOT NULL, + is_used boolean DEFAULT false NOT NULL, + hash_sha256 character varying +); + + +-- +-- Name: file_link_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.file_link ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.file_link_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: industry; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.industry ( + code character varying NOT NULL, + name character varying NOT NULL, + color character varying NOT NULL, + sort_order smallint NOT NULL, + active boolean NOT NULL +); + + +-- +-- Name: mail_message_folder; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mail_message_folder ( + message_id integer NOT NULL, + folder_id integer NOT NULL +); + + +-- +-- Name: mail_message_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.mail_message_id_seq + AS integer + START WITH 28023001 + INCREMENT BY 1 + MINVALUE 28023001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: mail_message_payload; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mail_message_payload ( + id integer NOT NULL, + message_id integer NOT NULL, + mime_type character varying NOT NULL, + filename character varying, + attachment character varying, + content character varying, + account_id integer NOT NULL, + sort_order smallint NOT NULL, + size integer, + external_id character varying +); + + +-- +-- Name: mail_message_payload_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.mail_message_payload_id_seq + AS integer + START WITH 29023001 + INCREMENT BY 1 + MINVALUE 29023001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: mailbox; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mailbox ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + email character varying NOT NULL, + provider character varying NOT NULL, + owner_id integer, + create_contact boolean NOT NULL, + state character varying NOT NULL, + contact_entity_type_id integer, + lead_entity_type_id integer, + lead_board_id integer, + error_message character varying, + emails_per_day smallint, + create_lead boolean DEFAULT false NOT NULL +); + + +-- +-- Name: mailbox_accessible_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mailbox_accessible_user ( + mailbox_id integer NOT NULL, + user_id integer NOT NULL, + account_id integer +); + + +-- +-- Name: mailbox_folder; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mailbox_folder ( + id integer NOT NULL, + mailbox_id integer NOT NULL, + external_id character varying NOT NULL, + name character varying NOT NULL, + type character varying, + account_id integer NOT NULL, + messages_total integer, + messages_unread integer +); + + +-- +-- Name: mailbox_folder_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.mailbox_folder_id_seq + AS integer + START WITH 31023001 + INCREMENT BY 1 + MINVALUE 31023001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: mailbox_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.mailbox_id_seq + AS integer + START WITH 27023001 + INCREMENT BY 1 + MINVALUE 27023001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: mailbox_settings_gmail; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mailbox_settings_gmail ( + mailbox_id integer NOT NULL, + account_id integer NOT NULL, + tokens jsonb NOT NULL, + history_id character varying +); + + +-- +-- Name: mailbox_settings_manual; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mailbox_settings_manual ( + mailbox_id integer NOT NULL, + account_id integer NOT NULL, + password character varying NOT NULL, + imap_server character varying NOT NULL, + imap_port smallint NOT NULL, + imap_secure boolean NOT NULL, + smtp_server character varying NOT NULL, + smtp_port smallint NOT NULL, + smtp_secure boolean NOT NULL, + imap_sync jsonb +); + + +-- +-- Name: mailbox_signature; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mailbox_signature ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + name character varying NOT NULL, + text character varying NOT NULL, + created_by integer NOT NULL +); + + +-- +-- Name: mailbox_signature_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.mailbox_signature_id_seq + AS integer + START WITH 27023001 + INCREMENT BY 1 + MINVALUE 27023001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: mailbox_signature_link; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.mailbox_signature_link ( + account_id integer NOT NULL, + signature_id integer NOT NULL, + mailbox_id integer NOT NULL +); + + +-- +-- Name: migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.migrations ( + id integer NOT NULL, + "timestamp" bigint NOT NULL, + name character varying NOT NULL +); + + +-- +-- Name: migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.migrations_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: migrations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.migrations_id_seq OWNED BY public.migrations.id; + + +-- +-- Name: notification; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.notification ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + type character varying NOT NULL, + object_id integer NOT NULL, + entity_id integer, + from_user integer, + title character varying, + description character varying, + is_seen boolean DEFAULT false NOT NULL, + user_id integer NOT NULL, + starts_in integer +); + + +-- +-- Name: notification_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.notification_id_seq + AS integer + START WITH 61011001 + INCREMENT BY 1 + MINVALUE 61011001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: notification_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.notification_settings ( + id integer NOT NULL, + account_id integer NOT NULL, + user_id integer NOT NULL, + enable_popup boolean DEFAULT true NOT NULL +); + + +-- +-- Name: notification_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.notification_settings_id_seq + AS integer + START WITH 62011001 + INCREMENT BY 1 + MINVALUE 62011001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: notification_type_follow_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.notification_type_follow_user ( + type_id integer NOT NULL, + user_id integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: notification_type_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.notification_type_settings ( + id integer NOT NULL, + account_id integer NOT NULL, + settings_id integer NOT NULL, + type character varying NOT NULL, + is_enabled boolean DEFAULT true NOT NULL, + object_id integer, + before integer +); + + +-- +-- Name: notification_type_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.notification_type_settings_id_seq + AS integer + START WITH 63011001 + INCREMENT BY 1 + MINVALUE 63011001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: object_permission; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.object_permission ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + object_type character varying NOT NULL, + object_id integer, + create_permission character varying NOT NULL, + view_permission character varying NOT NULL, + edit_permission character varying NOT NULL, + delete_permission character varying NOT NULL +); + + +-- +-- Name: object_permission_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.object_permission ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.object_permission_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: order_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.order_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: order_item; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.order_item ( + id integer NOT NULL, + unit_price numeric(15,2) NOT NULL, + quantity integer NOT NULL, + tax numeric(5,2) NOT NULL, + discount numeric(5,2) NOT NULL, + product_id integer NOT NULL, + order_id integer NOT NULL, + sort_order smallint NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: order_item_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.order_item_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: order_status; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.order_status ( + id integer NOT NULL, + name character varying(255) NOT NULL, + color character varying(50) NOT NULL, + code character varying(50), + sort_order smallint NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: order_status_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.order_status_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: orders; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.orders ( + id integer NOT NULL, + total_amount numeric(15,2) NOT NULL, + currency character varying(3) NOT NULL, + entity_id integer NOT NULL, + created_by integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + tax_included boolean DEFAULT true NOT NULL, + status_id integer, + warehouse_id integer, + section_id integer NOT NULL, + order_number integer NOT NULL, + updated_at timestamp without time zone NOT NULL, + cancel_after integer +); + + +-- +-- Name: product; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.product ( + id integer NOT NULL, + name character varying NOT NULL, + description character varying, + sku character varying, + unit character varying, + tax smallint, + is_deleted boolean NOT NULL, + category_id integer, + created_by integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + type character varying(50) DEFAULT 'product'::character varying NOT NULL, + updated_at timestamp without time zone DEFAULT now(), + section_id integer NOT NULL +); + + +-- +-- Name: product_category; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.product_category ( + id integer NOT NULL, + name character varying NOT NULL, + parent_id integer, + created_by integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + section_id integer NOT NULL +); + + +-- +-- Name: product_category_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.product_category_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: product_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.product_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: products_section; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.products_section ( + id integer NOT NULL, + name character varying NOT NULL, + icon character varying NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone DEFAULT now() NOT NULL, + type character varying DEFAULT 'sale'::character varying NOT NULL, + enable_warehouse boolean NOT NULL, + enable_barcode boolean DEFAULT true NOT NULL, + cancel_after integer +); + + +-- +-- Name: product_module_id_seq1; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.products_section ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.product_module_id_seq1 + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: product_price; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.product_price ( + id integer NOT NULL, + name character varying, + unit_price numeric(15,2), + currency character varying(3) NOT NULL, + product_id integer NOT NULL, + account_id integer NOT NULL, + max_discount integer +); + + +-- +-- Name: product_price_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.product_price_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: product_stock; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.product_stock ( + product_id integer NOT NULL, + warehouse_id integer NOT NULL, + stock_quantity integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: products_section_entity_type; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.products_section_entity_type ( + section_id integer NOT NULL, + entity_type_id integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: ready_made_solution; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.ready_made_solution ( + code character varying NOT NULL, + name character varying NOT NULL, + subdomain character varying NOT NULL, + sort_order smallint NOT NULL, + active boolean NOT NULL, + industry_code character varying, + account_id integer +); + + +-- +-- Name: rental_event; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.rental_event ( + id integer NOT NULL, + product_id integer NOT NULL, + order_item_id integer NOT NULL, + start_date timestamp without time zone NOT NULL, + end_date timestamp without time zone NOT NULL, + status character varying NOT NULL, + account_id integer NOT NULL, + section_id integer NOT NULL +); + + +-- +-- Name: rental_interval; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.rental_interval ( + id integer NOT NULL, + section_id integer NOT NULL, + type character varying NOT NULL, + start_time time without time zone, + account_id integer NOT NULL +); + + +-- +-- Name: rental_interval_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.rental_interval ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.rental_interval_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: rental_order; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.rental_order ( + id integer NOT NULL, + section_id integer NOT NULL, + warehouse_id integer, + entity_id integer NOT NULL, + created_by integer NOT NULL, + status character varying NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + currency character varying NOT NULL, + tax_included boolean DEFAULT true NOT NULL, + order_number integer NOT NULL +); + + +-- +-- Name: rental_order_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.rental_order ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.rental_order_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: rental_order_item; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.rental_order_item ( + id integer NOT NULL, + order_id integer NOT NULL, + product_id integer NOT NULL, + sort_order integer NOT NULL, + account_id integer NOT NULL, + unit_price numeric(15,2) NOT NULL, + tax numeric(5,2) NOT NULL, + discount numeric(5,2) NOT NULL +); + + +-- +-- Name: rental_order_item_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.rental_order_item ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.rental_order_item_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: rental_order_period; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.rental_order_period ( + id integer NOT NULL, + order_id integer NOT NULL, + start_date timestamp without time zone NOT NULL, + end_date timestamp without time zone NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: rental_order_period_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.rental_order_period ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.rental_order_period_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: rental_schedule_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.rental_event ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.rental_schedule_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: reservation; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.reservation ( + id integer NOT NULL, + order_id integer NOT NULL, + order_item_id integer NOT NULL, + product_id integer NOT NULL, + warehouse_id integer NOT NULL, + quantity integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: reservation_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.reservation_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: sales_plan; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.sales_plan ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + entity_type_id integer NOT NULL, + user_id integer NOT NULL, + start_date timestamp without time zone NOT NULL, + end_date timestamp without time zone NOT NULL, + quantity integer, + amount integer +); + + +-- +-- Name: sales_plan_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.sales_plan ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.sales_plan_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: salesforce_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.salesforce_settings ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + domain character varying NOT NULL, + key character varying NOT NULL, + secret character varying NOT NULL, + refresh_token character varying +); + + +-- +-- Name: schedule; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schedule ( + id integer NOT NULL, + name character varying NOT NULL, + entity_type_id integer, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + products_section_id integer, + icon character varying DEFAULT ''::character varying NOT NULL, + time_period integer, + appointment_limit integer, + type character varying DEFAULT 'schedule'::character varying NOT NULL +); + + +-- +-- Name: schedule_appointment; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schedule_appointment ( + id integer NOT NULL, + schedule_id integer NOT NULL, + start_date timestamp without time zone NOT NULL, + end_date timestamp without time zone NOT NULL, + status character varying NOT NULL, + comment character varying, + owner_id integer NOT NULL, + entity_id integer, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + order_id integer, + title character varying, + performer_id integer NOT NULL +); + + +-- +-- Name: schedule_event_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.schedule_appointment ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.schedule_event_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: schedule_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.schedule ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.schedule_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: schedule_performer; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schedule_performer ( + schedule_id integer NOT NULL, + user_id integer, + account_id integer NOT NULL, + id integer NOT NULL, + department_id integer, + type character varying DEFAULT 'user'::character varying NOT NULL +); + + +-- +-- Name: schedule_performer_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.schedule_performer ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME public.schedule_performer_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: scheduled_action_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.scheduled_action_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: scheduled_mail_message; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.scheduled_mail_message ( + id integer NOT NULL, + send_to jsonb NOT NULL, + subject character varying NOT NULL, + content character varying, + send_as_html boolean NOT NULL, + file_ids jsonb NOT NULL, + sent_at timestamp without time zone, + mailbox_id integer NOT NULL, + user_id integer NOT NULL, + entity_id integer, + action_id integer, + account_id integer NOT NULL +); + + +-- +-- Name: scheduled_mail_message_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.scheduled_mail_message_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: shipment; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.shipment ( + id integer NOT NULL, + name character varying(255) NOT NULL, + warehouse_id integer NOT NULL, + order_id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + shipped_at timestamp without time zone, + status_id integer NOT NULL, + section_id integer NOT NULL, + entity_id integer NOT NULL, + order_number integer NOT NULL +); + + +-- +-- Name: shipment_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.shipment_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: shipment_item; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.shipment_item ( + id integer NOT NULL, + shipment_id integer NOT NULL, + product_id integer NOT NULL, + quantity integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: shipment_item_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.shipment_item_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: site_form; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form ( + id integer NOT NULL, + account_id integer NOT NULL, + name text NOT NULL, + code text NOT NULL, + is_active boolean NOT NULL, + title text, + responsible_id integer, + design jsonb, + field_label_enabled boolean DEFAULT false NOT NULL, + field_placeholder_enabled boolean DEFAULT true NOT NULL, + created_by integer NOT NULL +); + + +-- +-- Name: site_form_consent; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form_consent ( + form_id integer NOT NULL, + account_id integer, + is_enabled boolean DEFAULT false NOT NULL, + text text, + link_url text, + link_text text, + default_value boolean DEFAULT false NOT NULL +); + + +-- +-- Name: site_form_entity_type; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form_entity_type ( + form_id integer NOT NULL, + entity_type_id integer NOT NULL, + account_id integer NOT NULL, + board_id integer, + is_main boolean NOT NULL +); + + +-- +-- Name: site_form_field; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form_field ( + id integer NOT NULL, + account_id integer NOT NULL, + page_id integer NOT NULL, + label text, + type text NOT NULL, + is_required boolean, + sort_order integer NOT NULL, + placeholder text +); + + +-- +-- Name: site_form_field_entity_field; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form_field_entity_field ( + form_field_id integer NOT NULL, + field_id integer NOT NULL, + entity_type_id integer NOT NULL, + is_validation_required boolean, + meta jsonb +); + + +-- +-- Name: site_form_field_entity_name; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form_field_entity_name ( + form_field_id integer NOT NULL, + entity_type_id integer NOT NULL +); + + +-- +-- Name: site_form_field_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.site_form_field ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.site_form_field_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: site_form_gratitude; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form_gratitude ( + form_id integer NOT NULL, + account_id integer, + is_enabled boolean DEFAULT false NOT NULL, + header text, + text text +); + + +-- +-- Name: site_form_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.site_form ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.site_form_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: site_form_page; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.site_form_page ( + id integer NOT NULL, + account_id integer NOT NULL, + form_id integer NOT NULL, + title text, + sort_order integer NOT NULL +); + + +-- +-- Name: site_form_page_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.site_form_page ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.site_form_page_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stage; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.stage ( + id integer NOT NULL, + name character varying(100) NOT NULL, + color character varying(50) NOT NULL, + code character varying(50), + is_system boolean NOT NULL, + sort_order smallint NOT NULL, + board_id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: stage_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.stage_id_seq + AS integer + START WITH 15022001 + INCREMENT BY 1 + MINVALUE 15022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: subtask_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.subtask_id_seq + AS integer + START WITH 46022001 + INCREMENT BY 1 + MINVALUE 46022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_comment; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_comment ( + id integer NOT NULL, + text character varying NOT NULL, + task_id integer NOT NULL, + created_by integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: task_comment_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_comment_id_seq + AS integer + START WITH 47022001 + INCREMENT BY 1 + MINVALUE 47022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_comment_like; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_comment_like ( + comment_id integer NOT NULL, + user_id integer NOT NULL, + account_id integer +); + + +-- +-- Name: task_settings; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_settings ( + id integer NOT NULL, + active_fields jsonb NOT NULL, + type character varying(100) NOT NULL, + record_id integer, + account_id integer +); + + +-- +-- Name: task_settings_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.task_settings_id_seq + AS integer + START WITH 45022001 + INCREMENT BY 1 + MINVALUE 45022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: task_subtask; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.task_subtask ( + id integer NOT NULL, + text character varying NOT NULL, + resolved boolean NOT NULL, + task_id integer NOT NULL, + account_id integer NOT NULL, + sort_order integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: test_account; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.test_account ( + account_id integer NOT NULL +); + + +-- +-- Name: trigger; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.trigger ( + id integer NOT NULL, + type character varying(100) NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: trigger_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.trigger_id_seq + AS integer + START WITH 51011001 + INCREMENT BY 1 + MINVALUE 51011001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tutorial_group; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tutorial_group ( + id integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + name character varying NOT NULL, + sort_order integer NOT NULL +); + + +-- +-- Name: tutorial_group_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.tutorial_group ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.tutorial_group_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: tutorial_item; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tutorial_item ( + id integer NOT NULL, + account_id integer NOT NULL, + group_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + name character varying NOT NULL, + link character varying NOT NULL, + sort_order integer NOT NULL +); + + +-- +-- Name: tutorial_item_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.tutorial_item ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.tutorial_item_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: tutorial_item_product; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tutorial_item_product ( + id integer NOT NULL, + account_id integer NOT NULL, + item_id integer NOT NULL, + type character varying NOT NULL, + object_id integer +); + + +-- +-- Name: tutorial_item_product_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.tutorial_item_product ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.tutorial_item_product_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: tutorial_item_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tutorial_item_user ( + item_id integer NOT NULL, + user_id integer NOT NULL +); + + +-- +-- Name: user_condition; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_condition ( + condition_id integer NOT NULL, + user_id integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.user_id_seq + AS integer + START WITH 12022001 + INCREMENT BY 1 + MINVALUE 12022001 + NO MAXVALUE + CACHE 1; + + +-- +-- Name: user_object_permission; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_object_permission ( + user_id integer NOT NULL, + object_permission_id integer NOT NULL +); + + +-- +-- Name: user_profile; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_profile ( + created_at timestamp without time zone NOT NULL, + user_id integer NOT NULL, + birth_date timestamp without time zone, + employment_date timestamp without time zone, + account_id integer NOT NULL +); + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.users ( + id integer NOT NULL, + email character varying(254) NOT NULL, + password character varying(100) NOT NULL, + created_at timestamp without time zone NOT NULL, + first_name character varying(255) NOT NULL, + last_name character varying(255), + phone character varying(32), + account_id integer NOT NULL, + role character varying(100) NOT NULL, + is_active boolean DEFAULT true NOT NULL, + department_id integer, + avatar_id uuid, + "position" character varying, + analytics_id uuid NOT NULL +); + + +-- +-- Name: voximplant_account; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_account ( + account_id integer NOT NULL, + account_name character varying NOT NULL, + application_id integer NOT NULL, + application_name character varying NOT NULL, + external_id integer NOT NULL, + api_key character varying NOT NULL, + password character varying NOT NULL, + billing_account_id integer NOT NULL, + is_active boolean DEFAULT false NOT NULL, + account_email character varying NOT NULL, + key_id character varying NOT NULL, + private_key character varying NOT NULL +); + + +-- +-- Name: voximplant_call; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_call ( + id integer NOT NULL, + user_id integer NOT NULL, + entity_id integer, + direction character varying NOT NULL, + phone_number character varying NOT NULL, + duration integer, + status character varying, + failure_reason character varying, + record_url character varying, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + session_id character varying NOT NULL, + call_id character varying NOT NULL, + comment character varying, + number_id integer +); + + +-- +-- Name: voximplant_call_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.voximplant_call ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.voximplant_call_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: voximplant_number; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_number ( + id integer NOT NULL, + account_id integer NOT NULL, + phone_number character varying NOT NULL, + external_id character varying +); + + +-- +-- Name: voximplant_number_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.voximplant_number ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.voximplant_number_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: voximplant_number_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_number_user ( + number_id integer NOT NULL, + user_id integer NOT NULL, + account_id integer NOT NULL +); + + +-- +-- Name: voximplant_scenario_entity; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_scenario_entity ( + id integer NOT NULL, + account_id integer NOT NULL, + scenario_type character varying NOT NULL, + contact_id integer, + deal_id integer, + board_id integer, + owner_id integer +); + + +-- +-- Name: voximplant_scenario_entity_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.voximplant_scenario_entity ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.voximplant_scenario_entity_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: voximplant_scenario_note; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_scenario_note ( + id integer NOT NULL, + account_id integer NOT NULL, + scenario_type character varying NOT NULL, + note_text character varying NOT NULL +); + + +-- +-- Name: voximplant_scenario_note_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.voximplant_scenario_note ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.voximplant_scenario_note_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: voximplant_scenario_task; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_scenario_task ( + id integer NOT NULL, + account_id integer NOT NULL, + scenario_type character varying NOT NULL, + create_activity boolean, + activity_type_id integer, + activity_text character varying, + activity_duration integer, + activity_owner_id integer, + create_task boolean, + task_title character varying, + task_text character varying, + task_duration integer, + task_owner_id integer +); + + +-- +-- Name: voximplant_scenario_task_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +ALTER TABLE public.voximplant_scenario_task ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.voximplant_scenario_task_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: voximplant_user; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.voximplant_user ( + user_id integer NOT NULL, + external_id integer NOT NULL, + user_name character varying NOT NULL, + account_id integer NOT NULL, + password character varying NOT NULL, + is_active boolean DEFAULT true NOT NULL +); + + +-- +-- Name: warehouse; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.warehouse ( + id integer NOT NULL, + name character varying, + created_by integer NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + is_deleted boolean DEFAULT false NOT NULL, + section_id integer NOT NULL +); + + +-- +-- Name: warehouse_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.warehouse_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: migrations id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.migrations ALTER COLUMN id SET DEFAULT nextval('public.migrations_id_seq'::regclass); + + +-- +-- Data for Name: account; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.account (id, company_name, subdomain, created_at, logo_id) FROM stdin; +\. + + +-- +-- Data for Name: account_api_access; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.account_api_access (account_id, api_key, created_at) FROM stdin; +\. + + +-- +-- Data for Name: account_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.account_settings (account_id, language, working_days, working_time_from, working_time_to, time_zone, currency, number_format, phone_format, allow_duplicates) FROM stdin; +\. + + +-- +-- Data for Name: account_subscription; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.account_subscription (account_id, created_at, expired_at, is_trial, user_limit, plan_name, external_customer_id) FROM stdin; +\. + + +-- +-- Data for Name: action; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.action (id, type, delay, account_id) FROM stdin; +\. + + +-- +-- Data for Name: action_activity_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.action_activity_settings (action_id, responsible_user_type, responsible_user_id, activity_type_id, text, deadline_type, deadline_time, account_id) FROM stdin; +\. + + +-- +-- Data for Name: action_email_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.action_email_settings (action_id, subject, content, send_as_html, mailbox_id, user_id, account_id, signature) FROM stdin; +\. + + +-- +-- Data for Name: action_entity_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.action_entity_settings (action_id, stage_id, account_id, operation_type) FROM stdin; +\. + + +-- +-- Data for Name: action_scheduled; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.action_scheduled (id, action_id, entity_id, scheduled_time, completed, account_id, created_by) FROM stdin; +\. + + +-- +-- Data for Name: action_task_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.action_task_settings (action_id, responsible_user_type, responsible_user_id, title, text, deadline_type, deadline_time, account_id) FROM stdin; +\. + + +-- +-- Data for Name: activity; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.activity (id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, activity_type_id, entity_id, resolved_date, weight) FROM stdin; +\. + + +-- +-- Data for Name: activity_type; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.activity_type (id, created_at, account_id, name, is_active) FROM stdin; +\. + + +-- +-- Data for Name: appsumo_license; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.appsumo_license (id, license_key, license_status, plan_id, tier, account_id, created_at, prev_license_key) FROM stdin; +\. + + +-- +-- Data for Name: appsumo_tier; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.appsumo_tier (id, tier, user_limit, term_in_days, plan_name) FROM stdin; +1 1 1 36500 AppSumo Tier 1 +2 2 5 36500 AppSumo Tier 2 +3 3 15 36500 AppSumo Tier 3 +4 4 30 36500 AppSumo Tier 4 +5 5 50 36500 AppSumo Tier 5 +\. + + +-- +-- Data for Name: automation; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.automation (id, trigger_id, action_id, created_by, is_active, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: automation_condition; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.automation_condition (automation_id, condition_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: automation_entity_type; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.automation_entity_type (id, account_id, created_at, created_by, name, triggers, entity_type_id, board_id, stage_id, conditions, actions, process_id) FROM stdin; +\. + + +-- +-- Data for Name: automation_process; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.automation_process (id, account_id, created_at, resource_key, bpmn_file, bpmn_process_id) FROM stdin; +\. + + +-- +-- Data for Name: automation_stage; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.automation_stage (automation_id, stage_id) FROM stdin; +\. + + +-- +-- Data for Name: board; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.board (id, name, type, record_id, account_id, created_at, sort_order, is_system, code, need_migration, task_board_id, owner_id, participant_ids) FROM stdin; +\. + + +-- +-- Data for Name: chat; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat (id, provider_id, created_by, external_id, type, title, entity_id, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: chat_message; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_message (id, chat_id, chat_user_id, external_id, text, account_id, created_at, reply_to_id) FROM stdin; +\. + + +-- +-- Data for Name: chat_message_file; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_message_file (id, message_id, external_id, account_id, file_id, name, mime_type, size, created_at) FROM stdin; +\. + + +-- +-- Data for Name: chat_message_reaction; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_message_reaction (id, message_id, chat_user_id, reaction, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: chat_message_user_status; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_message_user_status (chat_id, message_id, chat_user_id, status, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: chat_pinned_message; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_pinned_message (chat_id, message_id, created_at, account_id) FROM stdin; +\. + + +-- +-- Data for Name: chat_provider; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_provider (id, created_by, type, title, account_id, created_at, status, transport) FROM stdin; +\. + + +-- +-- Data for Name: chat_provider_messenger; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_provider_messenger (provider_id, page_id, page_access_token, account_id, user_id, user_access_token) FROM stdin; +\. + + +-- +-- Data for Name: chat_provider_twilio; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_provider_twilio (provider_id, account_sid, auth_token, phone_number, account_id) FROM stdin; +\. + + +-- +-- Data for Name: chat_provider_user; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_provider_user (provider_id, user_id, account_id, type) FROM stdin; +\. + + +-- +-- Data for Name: chat_provider_wazzup; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_provider_wazzup (provider_id, account_id, api_key, channel_id, plain_id, transport) FROM stdin; +\. + + +-- +-- Data for Name: chat_user; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_user (id, chat_id, user_id, role, account_id) FROM stdin; +\. + + +-- +-- Data for Name: chat_user_external; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.chat_user_external (account_id, external_id, first_name, last_name, avatar_url, phone, email, link, chat_user_id) FROM stdin; +\. + + +-- +-- Data for Name: condition; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.condition (id, type, account_id) FROM stdin; +\. + + +-- +-- Data for Name: demo_data; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.demo_data (id, account_id, type, ids) FROM stdin; +\. + + +-- +-- Data for Name: department; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.department (id, name, parent_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: document_template; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.document_template (id, name, created_by, account_id, created_at, created_count) FROM stdin; +\. + + +-- +-- Data for Name: document_template_access; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.document_template_access (document_template_id, user_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: document_template_entity_type; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.document_template_entity_type (document_template_id, entity_type_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: entity; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity (id, name, entity_type_id, responsible_user_id, stage_id, created_by, account_id, created_at, participant_ids, weight, closed_at, copied_from, copied_count) FROM stdin; +\. + + +-- +-- Data for Name: entity_event; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity_event (id, account_id, entity_id, object_id, type, created_at) FROM stdin; +\. + + +-- +-- Data for Name: entity_link; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity_link (id, source_id, target_id, sort_order, back_link_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: entity_list_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity_list_settings (id, entity_type_id, board_id, settings, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: entity_stage_history; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity_stage_history (id, account_id, entity_id, board_id, stage_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: entity_type; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity_type (id, name, entity_category, section_name, section_view, section_icon, account_id, created_at, sort_order) FROM stdin; +\. + + +-- +-- Data for Name: entity_type_feature; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity_type_feature (entity_type_id, feature_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: entity_type_link; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.entity_type_link (id, source_id, target_id, sort_order, account_id) FROM stdin; +\. + + +-- +-- Data for Name: exact_time_trigger_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.exact_time_trigger_settings (trigger_id, date, account_id) FROM stdin; +\. + + +-- +-- Data for Name: external_entity; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.external_entity (id, entity_id, url, account_id, created_at, system, raw_data, ui_data) FROM stdin; +\. + + +-- +-- Data for Name: external_system; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.external_system (id, name, code, url_templates) FROM stdin; +1 SalesForce salesforce {salesforce.com,force.com} +2 LinkedIn linkedin {linkedin.com} +3 Facebook facebook {facebook.com,fb.com} +4 Pipedrive pipedrive {pipedrive.com} +5 HubSpot hubspot {hubspot.com} +6 Freshsales freshsales {freshworks.com,myfreshworks.com} +7 Zoho zoho {zoho.com} +8 Twitter twitter {twitter.com} +9 Instagram instagram {instagram.com} +10 Notion notion {notion.so} +11 Zendesk zendesk {zendesk.com} +12 SugarCRM sugarcrm {sugarcrm.com} +13 Monday monday {monday.com} +14 amoCRM amocrm {amocrm.ru,kommo.com} +15 Bitrix24 bitrix {bitrix24.ru,bitrix24.com,bitrix24.es,bitrix24.eu,bitrix24.de,bitrix24.fr,bitrix24.pl,bitrix24.it,bitrix24.uk} +\. + + +-- +-- Data for Name: feature; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.feature (id, name, code, is_enabled) FROM stdin; +1 Activities activities t +2 Tasks tasks t +6 File storage disk diskForFiles f +7 Avatar avatar f +8 Photo/pictures (several) photos f +3 Notes notes t +5 File storage saveFiles t +4 Chat chat t +9 Create Documents documents t +10 Products products f +\. + + +-- +-- Data for Name: field; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, active, value) FROM stdin; +\. + + +-- +-- Data for Name: field_condition; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.field_condition (condition_id, field_id, field_type, payload, account_id) FROM stdin; +\. + + +-- +-- Data for Name: field_group; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.field_group (id, name, sort_order, entity_type_id, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: field_option; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.field_option (id, label, sort_order, field_id, account_id, created_at, color) FROM stdin; +\. + + +-- +-- Data for Name: field_stage_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.field_stage_settings (id, account_id, field_id, stage_id, access, exclude_user_ids) FROM stdin; +\. + + +-- +-- Data for Name: field_user_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.field_user_settings (id, account_id, field_id, user_id, access) FROM stdin; +\. + + +-- +-- Data for Name: field_value; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.field_value (id, field_id, payload, entity_id, account_id, field_type) FROM stdin; +\. + + +-- +-- Data for Name: file_info; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.file_info (id, account_id, created_at, created_by, original_name, mime_type, size, store_path, is_used, hash_sha256) FROM stdin; +\. + + +-- +-- Data for Name: file_link; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.file_link (id, account_id, source_type, source_id, file_id, file_name, file_size, file_type, created_at, created_by) FROM stdin; +\. + + +-- +-- Data for Name: industry; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.industry (code, name, color, sort_order, active) FROM stdin; +it_and_development IT & Development #FF8D07 0 t +construction_and_engineering Construction and engineering #A33CAB 1 t +advertising_and_marketing Advertising & Marketing #67E2F9 2 t +consulting_and_outsourcing Consulting and outsourcing #8AF039 3 t +manufacturing Manufacturing #EC008C 4 t +education Education #3D8FEC 5 t +\. + + +-- +-- Data for Name: mail_message; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mail_message (id, mailbox_id, external_id, thread_id, snippet, sent_from, sent_to, subject, date, account_id, reply_to, cc, has_attachment, message_id, references_to, in_reply_to, entity_id, is_seen) FROM stdin; +\. + + +-- +-- Data for Name: mail_message_folder; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mail_message_folder (message_id, folder_id) FROM stdin; +\. + + +-- +-- Data for Name: mail_message_payload; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mail_message_payload (id, message_id, mime_type, filename, attachment, content, account_id, sort_order, size, external_id) FROM stdin; +\. + + +-- +-- Data for Name: mailbox; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mailbox (id, account_id, created_at, email, provider, owner_id, create_contact, state, contact_entity_type_id, lead_entity_type_id, lead_board_id, error_message, emails_per_day, create_lead) FROM stdin; +\. + + +-- +-- Data for Name: mailbox_accessible_user; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mailbox_accessible_user (mailbox_id, user_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: mailbox_folder; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mailbox_folder (id, mailbox_id, external_id, name, type, account_id, messages_total, messages_unread) FROM stdin; +\. + + +-- +-- Data for Name: mailbox_settings_gmail; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mailbox_settings_gmail (mailbox_id, account_id, tokens, history_id) FROM stdin; +\. + + +-- +-- Data for Name: mailbox_settings_manual; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mailbox_settings_manual (mailbox_id, account_id, password, imap_server, imap_port, imap_secure, smtp_server, smtp_port, smtp_secure, imap_sync) FROM stdin; +\. + + +-- +-- Data for Name: mailbox_signature; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mailbox_signature (id, account_id, created_at, name, text, created_by) FROM stdin; +\. + + +-- +-- Data for Name: mailbox_signature_link; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.mailbox_signature_link (account_id, signature_id, mailbox_id) FROM stdin; +\. + + +-- +-- Data for Name: migrations; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.migrations (id, "timestamp", name) FROM stdin; +1 1667672722508 AddAccount1667672722508 +2 1667743637921 AddUser1667743637921 +3 1667755763616 AddUserAccount1667755763616 +4 1668609239887 AddEntityType1668609239887 +5 1668679480636 AddBoard1668679480636 +6 1668679994594 Note1668679994594 +7 1668680006964 AddStage1668680006964 +8 1668693976313 FeedsView1668693976313 +9 1668759141152 AddEntity1668759141152 +10 1668769413399 AddFeedItemsSequence1668769413399 +11 1668770161278 AddTaskType1668770161278 +12 1668957885188 AddSortOrderToBoard1668957885188 +13 1669035936818 AddActivity1669035936818 +14 1669035943607 AddTask1669035943607 +15 1669126197933 AddFieldGroup1669126197933 +16 1669127718002 AddField1669127718002 +17 1669130700529 AddTaskToFeed1669130700529 +18 1669192730867 AddNoteToEntityFK1669192730867 +19 1669197308978 AddFieldOption1669197308978 +20 1669210346088 AddTasksView1669210346088 +21 1669279080953 FixActivityConstraints1669279080953 +22 1669370353662 SplitUserName1669370353662 +23 1669372200327 AddUserPhone1669372200327 +24 1669535731810 UserProfile1669535731810 +25 1669545773479 EntityTypeLink1669545773479 +26 1669565827895 EntityLink1669565827895 +27 1669629567395 RemoveUserAccount1669629567395 +28 1669706986248 AddFeature1669706986248 +29 1669707814502 AddEntityTypeToFeature1669707814502 +30 1669711256398 AddDefaultFeatures1669711256398 +31 1669732421801 AddFieldValue1669732421801 +32 1669799906268 FixEntityLinkColumns1669799906268 +33 1669880194355 ChangeFeatureNameNotes1669880194355 +34 1669966161211 BigintToInteger1669966161211 +35 1669973222725 ForeignKeyToInteger1669973222725 +36 1669991106433 AddFieldTypeColumnToFieldValue1669991106433 +37 1670576080218 AddExternalEntity1670576080218 +38 1670840721696 ExternalSystem1670840721696 +39 1670846440118 AddExternalSystems1670846440118 +40 1670936878487 AddFileInfo1670936878487 +41 1670939571185 AddFileStorePath1670939571185 +42 1671006792499 AddStageIdToTask1671006792499 +43 1671030345135 ChangeFileInfoIdType1671030345135 +44 1671089754889 AddEntityTypeSortOrder1671089754889 +45 1671096971264 RenameTaskType1671096971264 +46 1671108685879 NoteFiles1671108685879 +47 1671196063822 FileInfoCascade1671196063822 +48 1671196394406 NoteFileRemoveFileKey1671196394406 +49 1671203263368 TaskAndActivityFiles1671203263368 +50 1671438176416 RemoveKeyFromFileInfo1671438176416 +51 1671439005617 AddIsUsedToFileInfo1671439005617 +52 1671440838639 DropNoteFileLink1671440838639 +53 1671521628139 BoardRecordIdNull1671521628139 +54 1671533557027 UserActive1671533557027 +55 1671619787598 UserObjectPermission1671619787598 +56 1671914250074 ChangeCardView1671914250074 +57 1672064100473 EntityIdCascade1672064100473 +58 1672154773200 AddSalesforceSettings1672154773200 +59 1672224404851 AddRefreshTokenToSalesforce1672224404851 +60 1672242451242 ExternalSystemUrlTemplates1672242451242 +61 1672306523845 AddDataToExternalEntity1672306523845 +62 1672386954959 AddUIDataToExternalEntity1672386954959 +63 1672928537580 CascadeStageIdInTask1672928537580 +64 1673251726994 AddStageToAllTasks1673251726994 +65 1673339773307 EnableFileStorageFeature1673339773307 +66 1673417366210 RenameCardViewToEntityCategory1673417366210 +67 1673447442894 AddSubscription1673447442894 +68 1673514341599 AddCreatedAtToFileLink1673514341599 +69 1673515827427 AddSequenceToSubscriptionId1673515827427 +70 1673969196807 AddMailboxAndProviderSettings1673969196807 +71 1674130775550 AddMailboxState1674130775550 +72 1674138959333 AddPlannedTimeToTask1674138959333 +73 1674203801914 ChangeMailboxSettingsGmail1674203801914 +74 1674492197867 AddHistoryIdToGmailSettings1674492197867 +75 1674546589359 AddLastSyncToManualSettings1674546589359 +76 1674560582007 AddMailMessageBase1674560582007 +77 1674572622632 AddTaskSettings1674572622632 +78 1674637903317 ChangeMailbox1674637903317 +79 1674638261068 AddMailboxAccessibleUser1674638261068 +80 1674648741806 AddMailboxFolderInfo1674648741806 +81 1674660419230 AddMailMessageReplyTo1674660419230 +82 1674661928759 AddMailMessageCc1674661928759 +83 1674732906697 AddMailMessagePayloadSortOrder1674732906697 +84 1674741639286 MakeStartEndDateNullable1674741639286 +85 1674742927551 AddSubtaskTable1674742927551 +86 1674747324757 AddMailMessagePayloadSize1674747324757 +87 1674822646459 AddTaskCommentTable1674822646459 +88 1674823506430 DeleteSyncDaysFromMailbox1674823506430 +89 1674831146207 AddTaskCommentLikeTable1674831146207 +90 1675076470234 ImapSync1675076470234 +91 1675080091774 AddSystemColumnToBoard1675080091774 +92 1675086921624 AddMailMessagePayloadExternalId1675086921624 +93 1675090441216 AddTaskSettingsIdToTask1675090441216 +94 1675176424905 AddMailMessageHasAttachment1675176424905 +95 1675178566416 MailboxFolderTypeNullable1675178566416 +96 1675259924922 AddMailMessageMessageId1675259924922 +97 1675340097111 DropGroupMessageFromMailbox1675340097111 +98 1675349215128 AddCodeColumn1675349215128 +99 1675350490867 AddCodeToBoard1675350490867 +100 1675412897506 AddMailMessageReferences1675412897506 +101 1675858313415 AddContactEntityTypeToMailbox1675858313415 +102 1675858907059 AddLeadEntityTypeToMailbox1675858907059 +103 1675932022996 AddLeadBoardIdToMailbox1675932022996 +104 1675933206380 AddContactIdToMailMessage1675933206380 +105 1675939938059 MailMessageThreadIdNotNull1675939938059 +106 1675949522751 AddErrorToMailbox1675949522751 +107 1675958088984 AddSeenToMailMessage1675958088984 +108 1676281117335 AddSetNullOnDeleteToMailbox1676281117335 +109 1676283110539 AlterMailMessageSentToNull1676283110539 +110 1676299431922 AddRecipientToNote1676299431922 +111 1676370561831 AddMailToFeedItem1676370561831 +112 1676383947580 TurnOnChatFeature1676383947580 +113 1676385805343 AddChatFeedItemType1676385805343 +114 1676823429937 AddAutomationTables1676823429937 +115 1676866745580 AddAutomationStageTable1676866745580 +116 1676872004900 AddTaskActionSettingsTable1676872004900 +117 1676884562402 RemoveResultFromTask1676884562402 +118 1676895093533 AddChangeStageActionSettingsTable1676895093533 +119 1676992655279 AddAutomationConditions1676992655279 +120 1676994397217 AddNotification1676994397217 +121 1677079993131 AddUserIdToNotification1677079993131 +122 1677581691766 AddMailboxSignature1677581691766 +123 1677744619246 AddNotificationHighlight1677744619246 +124 1677752600091 AlterNotificationSetDescriptionNullable1677752600091 +125 1677765215861 AddNotificationSettings1677765215861 +126 1678093755605 AddExternalSystems1678093755605 +127 1678096716231 AddAccountSettings1678096716231 +128 1678113775031 AddExactTimeTriggerSettings1678113775031 +129 1678194605837 AddDefaultNotificationSettings1678194605837 +130 1678285724304 AddResolveDateToTaskAndActivity1678285724304 +131 1678286876322 AddResolveDateToAllTasks1678286876322 +132 1678347902775 AlterNotificationHighlightToTagName1678347902775 +133 1678367411991 FillResolvedDateForTaskAndAction1678367411991 +134 1678696011650 AddPlanNameToSubscription1678696011650 +135 1678697720944 UpdateSubscriptionTo50Users1678697720944 +136 1678720323399 AddCodeToField1678720323399 +137 1678891987277 AddDepartment1678891987277 +138 1678963689149 AddStartsInToNotification1678963689149 +139 1679291439089 AddScheduledAction1679291439089 +140 1679494426598 MakeFieldGroupOptional1679494426598 +141 1679578329175 AddColorToFieldOption1679578329175 +142 1679583365997 AlterMailMessageContactEntityId1679583365997 +143 1679929245833 AddUserAvatarId1679929245833 +144 1679931642588 AddActiveToField1679931642588 +145 1679931904542 AddAccountLogo1679931904542 +146 1680499051021 AddParticipantsToEntity1680499051021 +147 1680763220747 MigrateProjects1680763220747 +148 1681141545739 AddEmailActionSettings1681141545739 +149 1681224301468 AddProjectFields1681224301468 +150 1681289039535 AddEntityListSettings1681289039535 +151 1681483040117 AddScheduledMailMessage1681483040117 +152 1681732037710 AddEmailsPerDayColumnToMailbox1681732037710 +153 1681828967422 AddWeightToTaskAndActivity1681828967422 +154 1681832187113 AddWeightToAllTasks1681832187113 +155 1681900142878 ResetWeightForTasksAndActivities1681900142878 +156 1682002593036 AddEntityWeight1682002593036 +157 1682083589639 AddAccountIdTETFeature1682083589639 +158 1682348048312 AddRMS1682348048312 +159 1682350735553 AddIndustries1682350735553 +160 1682433824946 AddDemoMarker1682433824946 +161 1682507153940 AddDocumentTemplate1682507153940 +162 1682518277268 DeleteFileInfoWithUser1682518277268 +163 1682589692981 AddIndexes1682589692981 +164 1683016482581 AddIndexes1683016482581 +165 1683205194466 AddTaskBoardIdToBoard1683205194466 +166 1683517583707 DeleteProjectEntityBoards1683517583707 +167 1683731671898 AddFeatureDocuments1683731671898 +168 1683797890507 AddDocumentTemplateGenerated1683797890507 +169 1683802969000 AddDocumentInFeed1683802969000 +170 1683875863921 TrimUsersNames1683875863921 +171 1684249775346 AddAllTasksStageId1684249775346 +172 1684317847183 addCreatedByToFileLink1684317847183 +173 1685001497108 ChangeFileInfoHash1685001497108 +174 1685595302584 AddParticipantsToBoard1685595302584 +175 1685604837960 AddChatModel1685604837960 +176 1685689401123 AddDefaultChatProvider1685689401123 +177 1686048795624 AddBoardIdToTask1686048795624 +178 1686061937533 AlterChatMEssageFile1686061937533 +179 1686297344564 AlterMailMessageEntityId1686297344564 +180 1686310775887 AddSignatureToEmailActionSettings1686310775887 +181 1686643536303 AddReplayToInChatMessage1686643536303 +182 1686736715335 AddChatPinnedMessage1686736715335 +183 1686816157824 AddChatPinnedMessage1686816157824 +184 1686824143539 AddChatMessageReaction1686824143539 +185 1686840724427 AddProducts1686840724427 +186 1686904432256 AddChatIdToStatus1686904432256 +187 1686930758334 RenameProductField1686930758334 +188 1687015795997 AlterChatMessageReply1687015795997 +189 1687350416742 RemoveIdFromSubscription1687350416742 +190 1687351857599 AddSubscriptionExternalCustomerId1687351857599 +191 1687790975332 AddOrder1687790975332 +192 1687793191931 FixOrder1687793191931 +193 1687877020115 AddTaxIncluded1687877020115 +194 1687943824933 AddChatProviderTwilio1687943824933 +195 1687954149882 AlterChatProviderUser1687954149882 +196 1687962117509 AddWarehouse1687962117509 +197 1687965328992 AddIsDeletedToWarehouse1687965328992 +198 1688025794222 AlterChatUser1688025794222 +199 1688044274695 AddStocks1688044274695 +200 1688053486248 AddChatUserExternalName1688053486248 +201 1688112039219 AlterChatProviderTwilio1688112039219 +202 1688130606571 AlterChatProviderUser1688130606571 +203 1688136613049 AlterChat1688136613049 +204 1688138872050 AddOrderStatus1688138872050 +205 1688139271540 AddShipment1688139271540 +206 1688140521166 AddStatusIdToOrder1688140521166 +207 1688388514670 AddReservation1688388514670 +208 1688390259595 AlterFileInfoCreatedBy1688390259595 +209 1688394200229 FixStatusIdInOrder1688394200229 +210 1688472386401 AddShipmentDate1688472386401 +211 1688543908016 AddChatProviderMessenger1688543908016 +212 1688567846856 AlterChatUser1688567846856 +213 1688996628275 FixCategoryIdConstraint1688996628275 +214 1689059395581 AddChatProviderStatus1689059395581 +215 1689068374394 AddUserIdToMessengerProvider1689068374394 +216 1689081064483 AddWarehouseIdToOrder1689081064483 +217 1689087134759 AddModules1689087134759 +218 1689170448447 AddProductType1689170448447 +219 1689243925753 FixOrderStatuses1689243925753 +220 1689259268310 DeleteShipmentStatus1689259268310 +221 1689337185167 AddProductsFeature1689337185167 +222 1689508562776 AddIsActiveToReservation1689508562776 +223 1689763128902 RemoveNoteRecipientId1689763128902 +224 1689774963182 AddUpdatedAtToProduct1689774963182 +225 1689860682052 AddSchedule1689860682052 +226 1689933154489 AlterSchedulePerformer1689933154489 +227 1690208012261 AddProductModule1690208012261 +228 1690456178510 MigrateModuleToProductModule1690456178510 +229 1690467527775 RemoveModule1690467527775 +230 1690469860109 AddProductPermissions1690469860109 +231 1690543599386 DropProductPermissions1690543599386 +232 1690817128717 AlterProductModule1690817128717 +233 1690973831680 AddSectionIdToProducts1690973831680 +234 1691056504886 AddSectionIdToOrderAndShipment1691056504886 +235 1691061102493 DeleteShipmentStatus1691061102493 +236 1691061408646 AlterTableStockToProductStock1691061408646 +237 1691139996885 AddProductsSectionEntityType1691139996885 +238 1691155049107 AddRentalInterval1691155049107 +239 1691397636905 AlterProductsSectionType1691397636905 +240 1691411118591 AddRentalOrder1691411118591 +241 1691414938591 AddRentalSchedule1691414938591 +242 1691657890280 AddRentalOrderPeriod1691657890280 +243 1691678125349 AddRentalOrderPeriodAccountId1691678125349 +244 1691754596482 AlterRentalOrder1691754596482 +245 1691755141714 AlterRentalOrderItem1691755141714 +246 1692002092660 RentalScheduleAddSectionId1692002092660 +247 1692014115943 RenameRentalScheduleToRentalEvent1692014115943 +248 1692170842159 AddProductsSectionEnableWarehouse1692170842159 +249 1692172254434 AddOrdersOrderNumber1692172254434 +250 1692172318353 AddRentalOrderOrderNumber1692172318353 +251 1692283603851 AlterOrderItemCascadeDelete1692283603851 +252 1692343747646 AlterProductStock1692343747646 +253 1692354371998 FixFieldValueType1692354371998 +254 1692604044210 ProductsSectionEnableBarcode1692604044210 +255 1692708295281 AlterOrderStatusNull1692708295281 +256 1692885285551 RefactorScheduler1692885285551 +257 1692890628636 RenameScheduleEvent1692890628636 +258 1692975377102 AlterSchedule1692975377102 +259 1693218884137 UserPosition1693218884137 +260 1693232990040 ScheduleAppointmentOrderId1693232990040 +261 1693485238189 OrderStatusColor1693485238189 +262 1693556962547 CascadeDeleteShipment1693556962547 +263 1694085886365 SchedulerIcon1694085886365 +264 1694166234404 BoardCleanProjectParticipants1694166234404 +265 1695040324876 AppointmentTitle1695040324876 +266 1695046445852 EntityClosedAt1695046445852 +267 1695201739381 SalesPlan1695201739381 +268 1695287859742 ChatDeleteCascadeEntity1695287859742 +269 1695382850916 SalesPlanAlterAmount1695382850916 +270 1695742049917 VoximplantUser1695742049917 +271 1695743140564 VoximplantUserPrimaryColumn1695743140564 +272 1695808984339 VoximplantUserPassword1695808984339 +273 1695810676148 VoximplantAccount1695810676148 +274 1695820387969 AlterVoximplantUser1695820387969 +275 1696500815450 DemoData1696500815450 +276 1697019761609 VoximplantCall1697019761609 +277 1697028866185 AlterVoximplantUser1697028866185 +278 1697115016543 RefactorDemoData1697115016543 +279 1697440579544 SchedulerEventPerformerCascade1697440579544 +280 1697452411558 VoximplantUserActive1697452411558 +281 1697541300418 VoximplantAccount1697541300418 +282 1697541767120 VoximplantAccountAlter1697541767120 +283 1697543064652 VoximplantAccountEmail1697543064652 +284 1697556130148 VoximplantAccountKey1697556130148 +285 1697702819805 VoximplantCallAlterExternalId1697702819805 +286 1698135013349 ReportingOptimization1698135013349 +287 1698421539770 VoximplantScenarios1698421539770 +288 1698663785490 OrderStatusColors1698663785490 +289 1699457673085 AddEntityEventModel1699457673085 +290 1700060543771 AddSchedulePerformerId1700060543771 +291 1700060859571 AlterSchedulePerformer1700060859571 +292 1700230572219 AlterMailbox1700230572219 +293 1700395051542 AlterExternalEntity1700395051542 +294 1700475817946 AllEventsMigrationScript1700475817946 +295 1700591906266 TelephonyCallsToEntityEvent1700591906266 +296 1700663233866 AlterSchedulerAppointment1700663233866 +297 1700729760783 AlterSchedule1700729760783 +298 1700733045104 AlterSchedule1700733045104 +299 1700735236205 AlterSchedule1700735236205 +300 1700741072037 ProductOrdersToEntityEvents1700741072037 +301 1700820935837 AlterSchedulerAppointment1700820935837 +302 1700836699189 ShipmentsToEntityEvents1700836699189 +303 1701264274255 CallsToEntityEventsFix1701264274255 +304 1701437712747 DeleteEntityEventTelephonyCalls1701437712747 +305 1701701891843 AlterVoximplantCall1701701891843 +306 1702542289418 UpdateModulesIcons1702542289418 +307 1702637665853 AlterActivityType1702637665853 +308 1702970939958 AlterAccountSettings1702970939958 +309 1703085253100 AlterProductPrice1703085253100 +310 1703488850551 AlterAccountSettings1703488850551 +311 1703502036545 FieldSettings1703502036545 +312 1703761495779 AlterFieldStageSettings1703761495779 +313 1703850851646 AlterShipment1703850851646 +314 1703857140848 AlterReservation1703857140848 +315 1703876929122 AlterChangeStageActionSettings1703876929122 +316 1704282894747 AlterEntity1704282894747 +317 1706795467082 TestAccount1706795467082 +318 1708088397272 EntityStageChange1708088397272 +319 1708433846254 EntityChangeHistoryInit1708433846254 +320 1708589222946 UserAnalyticsId1708589222946 +321 1708952321460 OptimizeNotificationIndexes1708952321460 +322 1709047301377 DBOptimizationIndex1709047301377 +323 1709048922111 StageAccountIdIndex1709048922111 +324 1709110989045 ScheduledMailMessageIndex1709110989045 +325 1709111575857 AddIndexes1709111575857 +326 1709280253891 ChatProviderCascadeDelete1709280253891 +327 1709736232826 ChatEntityRemoveCascade1709736232826 +328 1709805560320 ChatUserExternal1709805560320 +329 1710162901881 EntityCopiedCount1710162901881 +330 1710758264055 OrderCancelAfter1710758264055 +331 1710759144910 ProductSectionCancelAfter1710759144910 +332 1710864090375 ScheduledActionCreatedBy1710864090375 +333 1710927112868 TutorialGroup1710927112868 +334 1710929893275 TutorialItem1710929893275 +335 1710939538331 TutorialItemUser1710939538331 +336 1711033243401 TutorialItemProduct1711033243401 +337 1711087326245 MailMessageIndexes1711087326245 +338 1711540999340 EntityActionSettings1711540999340 +339 1711541635402 ActionSettingsRename1711541635402 +340 1711706670268 ScheduledAction1711706670268 +341 1711962655915 AutomationActionSettings1711962655915 +342 1712575547663 FieldValue1712575547663 +343 1713167989297 DeleteAllFormulaFields1713167989297 +344 1713257835467 FixEntityTypeSortOrder1713257835467 +345 1713258622876 FixEntityTypeLinkSortOrder1713258622876 +346 1713971186799 ChatProviderTransport1713971186799 +347 1714382561376 WazzupProvider1714382561376 +348 1714557065128 ProductPricePrecision1714557065128 +349 1714663341741 WazzupProviderRemoveChatType1714663341741 +350 1714730508391 WazzupProviderTransport1714730508391 +351 1714732587962 RemoveWazzupProviders1714732587962 +352 1714734600334 ChatProviderTypeRename1714734600334 +353 1715602468891 ChatProviderUserType1715602468891 +354 1715610371002 AlterChatUserExternal1715610371002 +355 1715856544173 AccountApiAccess1715856544173 +356 1715856948928 AlterAccountApiAccess1715856948928 +357 1716299180820 VoximplantNumber1716299180820 +358 1716384743872 VoximplantPhoneNumber1716384743872 +359 1716465152984 VoximplantNumberDefaults1716465152984 +360 1716466922606 VoximplantCallNumber1716466922606 +361 1716469802549 VoximplantCallNumberDefault1716469802549 +362 1717599382958 AddForms1717599382958 +363 1717746424353 SiteFormPageSortOrder1717746424353 +364 1717773341006 FormSiteLink1717773341006 +365 1718030543491 SiteFormEntityType1718030543491 +366 1718098299378 SiteFormRemoveConsent1718098299378 +367 1718098642901 SiteFormConsent1718098642901 +368 1718115282972 SiteFormGratitude1718115282972 +369 1718118622975 AlterSiteForm1718118622975 +370 1718120948980 AlterSiteFormField1718120948980 +371 1718266194827 NotificationRemoveTag1718266194827 +372 1718613418648 AlterSiteForm1718613418648 +373 1718715571983 AlterSiteFormField1718715571983 +374 1718724129043 AutomationProcess1718724129043 +375 1718793150525 AlterSiteFormField1718793150525 +376 1718793757895 AlterSiteFormField1718793757895 +377 1718798058177 AlterAutomationProcess1718798058177 +378 1718801950089 AlterAutomationProcess1718801950089 +379 1718880290844 AlterSiteFormField1718880290844 +380 1719213418299 AlterSiteFormField1719213418299 +381 1719302707526 AlterSiteFormField1719302707526 +382 1719324782700 RenameSubtask1719324782700 +383 1719325474889 AlterTaskSubtaskSortOrder1719325474889 +384 1719393706903 AlterAutomationProcess1719393706903 +385 1719396909800 AutomationEntityType1719396909800 +386 1719404585119 AlterAutomationEntityType1719404585119 +387 1719411072571 AlterAutomationEntityType1719411072571 +388 1719414260257 AlterAutomationEntityType1719414260257 +389 1719502764234 AlterAutomationEntityType1719502764234 +390 1719502897605 AlterAutomationEntityType1719502897605 +391 1719833217147 AlterEntityType1719833217147 +392 1719995612500 UpdateCopiesCreatedAt1719995612500 +393 1720076313493 AlterSiteForm1720076313493 +394 1720077438733 AlterSiteFormEntityType1720077438733 +395 1720194016236 AppSumoLicense1720194016236 +396 1720194705296 AppSumoPreset1720194705296 +397 1720423072348 AppSumoPresets1720423072348 +398 1720423711324 RenameAppSumo1720423711324 +399 1720434041376 AppsumoLicenseUnique1720434041376 +400 1720446828855 AlterReadyMadeSolution1720446828855 +401 1720530507278 RenameAppsumoPreset1720530507278 +402 1720530714621 AlterAppsumoTier1720530714621 +403 1720531175011 RenameSubscription1720531175011 +404 1720596831169 AlterAppsumoTier1720596831169 +405 1720597683965 UpdateAccountSubscription1720597683965 +406 1720610809785 AlterAppsumoLicense1720610809785 +407 1720619947686 AlterAppsumoTier1720619947686 +408 1720778334472 AlterAutomationEntityType1720778334472 +\. + + +-- +-- Data for Name: note; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.note (id, created_at, text, entity_id, created_by, account_id) FROM stdin; +\. + + +-- +-- Data for Name: notification; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.notification (id, account_id, created_at, type, object_id, entity_id, from_user, title, description, is_seen, user_id, starts_in) FROM stdin; +\. + + +-- +-- Data for Name: notification_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.notification_settings (id, account_id, user_id, enable_popup) FROM stdin; +\. + + +-- +-- Data for Name: notification_type_follow_user; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.notification_type_follow_user (type_id, user_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: notification_type_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.notification_type_settings (id, account_id, settings_id, type, is_enabled, object_id, before) FROM stdin; +\. + + +-- +-- Data for Name: object_permission; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.object_permission (id, account_id, created_at, object_type, object_id, create_permission, view_permission, edit_permission, delete_permission) FROM stdin; +\. + + +-- +-- Data for Name: order_item; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.order_item (id, unit_price, quantity, tax, discount, product_id, order_id, sort_order, account_id) FROM stdin; +\. + + +-- +-- Data for Name: order_status; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.order_status (id, name, color, code, sort_order, account_id) FROM stdin; +\. + + +-- +-- Data for Name: orders; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.orders (id, total_amount, currency, entity_id, created_by, account_id, created_at, tax_included, status_id, warehouse_id, section_id, order_number, updated_at, cancel_after) FROM stdin; +\. + + +-- +-- Data for Name: product; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.product (id, name, description, sku, unit, tax, is_deleted, category_id, created_by, account_id, created_at, type, updated_at, section_id) FROM stdin; +\. + + +-- +-- Data for Name: product_category; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.product_category (id, name, parent_id, created_by, account_id, created_at, section_id) FROM stdin; +\. + + +-- +-- Data for Name: product_price; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.product_price (id, name, unit_price, currency, product_id, account_id, max_discount) FROM stdin; +\. + + +-- +-- Data for Name: product_stock; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.product_stock (product_id, warehouse_id, stock_quantity, account_id) FROM stdin; +\. + + +-- +-- Data for Name: products_section; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.products_section (id, name, icon, account_id, created_at, type, enable_warehouse, enable_barcode, cancel_after) FROM stdin; +\. + + +-- +-- Data for Name: products_section_entity_type; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.products_section_entity_type (section_id, entity_type_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: ready_made_solution; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.ready_made_solution (code, name, subdomain, sort_order, active, industry_code, account_id) FROM stdin; +\. + + +-- +-- Data for Name: rental_event; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.rental_event (id, product_id, order_item_id, start_date, end_date, status, account_id, section_id) FROM stdin; +\. + + +-- +-- Data for Name: rental_interval; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.rental_interval (id, section_id, type, start_time, account_id) FROM stdin; +\. + + +-- +-- Data for Name: rental_order; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.rental_order (id, section_id, warehouse_id, entity_id, created_by, status, account_id, created_at, currency, tax_included, order_number) FROM stdin; +\. + + +-- +-- Data for Name: rental_order_item; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.rental_order_item (id, order_id, product_id, sort_order, account_id, unit_price, tax, discount) FROM stdin; +\. + + +-- +-- Data for Name: rental_order_period; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.rental_order_period (id, order_id, start_date, end_date, account_id) FROM stdin; +\. + + +-- +-- Data for Name: reservation; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.reservation (id, order_id, order_item_id, product_id, warehouse_id, quantity, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: sales_plan; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.sales_plan (id, account_id, created_at, entity_type_id, user_id, start_date, end_date, quantity, amount) FROM stdin; +\. + + +-- +-- Data for Name: salesforce_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.salesforce_settings (id, account_id, created_at, domain, key, secret, refresh_token) FROM stdin; +\. + + +-- +-- Data for Name: schedule; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.schedule (id, name, entity_type_id, account_id, created_at, products_section_id, icon, time_period, appointment_limit, type) FROM stdin; +\. + + +-- +-- Data for Name: schedule_appointment; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.schedule_appointment (id, schedule_id, start_date, end_date, status, comment, owner_id, entity_id, account_id, created_at, order_id, title, performer_id) FROM stdin; +\. + + +-- +-- Data for Name: schedule_performer; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.schedule_performer (schedule_id, user_id, account_id, id, department_id, type) FROM stdin; +\. + + +-- +-- Data for Name: scheduled_mail_message; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.scheduled_mail_message (id, send_to, subject, content, send_as_html, file_ids, sent_at, mailbox_id, user_id, entity_id, action_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: shipment; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.shipment (id, name, warehouse_id, order_id, account_id, created_at, shipped_at, status_id, section_id, entity_id, order_number) FROM stdin; +\. + + +-- +-- Data for Name: shipment_item; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.shipment_item (id, shipment_id, product_id, quantity, account_id) FROM stdin; +\. + + +-- +-- Data for Name: site_form; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form (id, account_id, name, code, is_active, title, responsible_id, design, field_label_enabled, field_placeholder_enabled, created_by) FROM stdin; +\. + + +-- +-- Data for Name: site_form_consent; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form_consent (form_id, account_id, is_enabled, text, link_url, link_text, default_value) FROM stdin; +\. + + +-- +-- Data for Name: site_form_entity_type; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form_entity_type (form_id, entity_type_id, account_id, board_id, is_main) FROM stdin; +\. + + +-- +-- Data for Name: site_form_field; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form_field (id, account_id, page_id, label, type, is_required, sort_order, placeholder) FROM stdin; +\. + + +-- +-- Data for Name: site_form_field_entity_field; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form_field_entity_field (form_field_id, field_id, entity_type_id, is_validation_required, meta) FROM stdin; +\. + + +-- +-- Data for Name: site_form_field_entity_name; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form_field_entity_name (form_field_id, entity_type_id) FROM stdin; +\. + + +-- +-- Data for Name: site_form_gratitude; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form_gratitude (form_id, account_id, is_enabled, header, text) FROM stdin; +\. + + +-- +-- Data for Name: site_form_page; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.site_form_page (id, account_id, form_id, title, sort_order) FROM stdin; +\. + + +-- +-- Data for Name: stage; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.stage (id, name, color, code, is_system, sort_order, board_id, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: task; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.task (id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, title, entity_id, stage_id, planned_time, settings_id, resolved_date, weight, board_id) FROM stdin; +\. + + +-- +-- Data for Name: task_comment; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.task_comment (id, text, task_id, created_by, account_id, created_at) FROM stdin; +\. + + +-- +-- Data for Name: task_comment_like; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.task_comment_like (comment_id, user_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: task_settings; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.task_settings (id, active_fields, type, record_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: task_subtask; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.task_subtask (id, text, resolved, task_id, account_id, sort_order) FROM stdin; +\. + + +-- +-- Data for Name: test_account; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.test_account (account_id) FROM stdin; +\. + + +-- +-- Data for Name: trigger; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.trigger (id, type, account_id) FROM stdin; +\. + + +-- +-- Data for Name: tutorial_group; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.tutorial_group (id, account_id, created_at, name, sort_order) FROM stdin; +\. + + +-- +-- Data for Name: tutorial_item; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.tutorial_item (id, account_id, group_id, created_at, name, link, sort_order) FROM stdin; +\. + + +-- +-- Data for Name: tutorial_item_product; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.tutorial_item_product (id, account_id, item_id, type, object_id) FROM stdin; +\. + + +-- +-- Data for Name: tutorial_item_user; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.tutorial_item_user (item_id, user_id) FROM stdin; +\. + + +-- +-- Data for Name: user_condition; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.user_condition (condition_id, user_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: user_object_permission; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.user_object_permission (user_id, object_permission_id) FROM stdin; +\. + + +-- +-- Data for Name: user_profile; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.user_profile (created_at, user_id, birth_date, employment_date, account_id) FROM stdin; +\. + + +-- +-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.users (id, email, password, created_at, first_name, last_name, phone, account_id, role, is_active, department_id, avatar_id, "position", analytics_id) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_account; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_account (account_id, account_name, application_id, application_name, external_id, api_key, password, billing_account_id, is_active, account_email, key_id, private_key) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_call; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_call (id, user_id, entity_id, direction, phone_number, duration, status, failure_reason, record_url, account_id, created_at, session_id, call_id, comment, number_id) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_number; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_number (id, account_id, phone_number, external_id) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_number_user; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_number_user (number_id, user_id, account_id) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_scenario_entity; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_scenario_entity (id, account_id, scenario_type, contact_id, deal_id, board_id, owner_id) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_scenario_note; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_scenario_note (id, account_id, scenario_type, note_text) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_scenario_task; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_scenario_task (id, account_id, scenario_type, create_activity, activity_type_id, activity_text, activity_duration, activity_owner_id, create_task, task_title, task_text, task_duration, task_owner_id) FROM stdin; +\. + + +-- +-- Data for Name: voximplant_user; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.voximplant_user (user_id, external_id, user_name, account_id, password, is_active) FROM stdin; +\. + + +-- +-- Data for Name: warehouse; Type: TABLE DATA; Schema: public; Owner: - +-- + +COPY public.warehouse (id, name, created_by, account_id, created_at, is_deleted, section_id) FROM stdin; +\. + + +-- +-- Name: account_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.account_id_seq', 11023201, false); + + +-- +-- Name: action_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.action_id_seq', 51011001, false); + + +-- +-- Name: activity_type_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.activity_type_id_seq', 25022001, false); + + +-- +-- Name: app_sumo_license_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.app_sumo_license_id_seq', 1, false); + + +-- +-- Name: app_sumo_preset_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.app_sumo_preset_id_seq', 5, true); + + +-- +-- Name: automation_entity_type_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.automation_entity_type_id_seq', 1, false); + + +-- +-- Name: automation_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.automation_id_seq', 51011001, false); + + +-- +-- Name: automation_process_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.automation_process_id_seq', 1, false); + + +-- +-- Name: board_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.board_id_seq', 14022001, false); + + +-- +-- Name: chat_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.chat_id_seq', 1, false); + + +-- +-- Name: chat_message_file_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.chat_message_file_id_seq', 1, false); + + +-- +-- Name: chat_message_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.chat_message_id_seq', 1, false); + + +-- +-- Name: chat_message_reaction_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.chat_message_reaction_id_seq', 1, false); + + +-- +-- Name: chat_provider_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.chat_provider_id_seq', 1, false); + + +-- +-- Name: chat_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.chat_user_id_seq', 1, false); + + +-- +-- Name: condition_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.condition_id_seq', 51011001, false); + + +-- +-- Name: demo_data_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.demo_data_id_seq', 1, false); + + +-- +-- Name: department_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.department_id_seq', 1, false); + + +-- +-- Name: document_template_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.document_template_id_seq', 1, false); + + +-- +-- Name: entity_event_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.entity_event_id_seq', 1, false); + + +-- +-- Name: entity_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.entity_id_seq', 14022001, false); + + +-- +-- Name: entity_link_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.entity_link_id_seq', 1, false); + + +-- +-- Name: entity_list_settings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.entity_list_settings_id_seq', 1, false); + + +-- +-- Name: entity_stage_history_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.entity_stage_history_id_seq', 1, false); + + +-- +-- Name: entity_type_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.entity_type_id_seq', 13022001, false); + + +-- +-- Name: entity_type_link_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.entity_type_link_id_seq', 1, false); + + +-- +-- Name: external_entity_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.external_entity_id_seq', 30022001, false); + + +-- +-- Name: external_system_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.external_system_id_seq', 3, true); + + +-- +-- Name: feature_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.feature_id_seq', 10, true); + + +-- +-- Name: feed_item_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.feed_item_id_seq', 22022001, false); + + +-- +-- Name: field_group_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.field_group_id_seq', 41022001, false); + + +-- +-- Name: field_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.field_id_seq', 42022001, false); + + +-- +-- Name: field_option_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.field_option_id_seq', 43022001, false); + + +-- +-- Name: field_stage_settings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.field_stage_settings_id_seq', 1, false); + + +-- +-- Name: field_user_settings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.field_user_settings_id_seq', 1, false); + + +-- +-- Name: field_value_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.field_value_id_seq', 44022001, false); + + +-- +-- Name: file_link_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.file_link_id_seq', 1, false); + + +-- +-- Name: mail_message_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.mail_message_id_seq', 28023001, false); + + +-- +-- Name: mail_message_payload_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.mail_message_payload_id_seq', 29023001, false); + + +-- +-- Name: mailbox_folder_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.mailbox_folder_id_seq', 31023001, false); + + +-- +-- Name: mailbox_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.mailbox_id_seq', 27023001, false); + + +-- +-- Name: mailbox_signature_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.mailbox_signature_id_seq', 27023001, false); + + +-- +-- Name: migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.migrations_id_seq', 408, true); + + +-- +-- Name: notification_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.notification_id_seq', 61011001, false); + + +-- +-- Name: notification_settings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.notification_settings_id_seq', 62011001, false); + + +-- +-- Name: notification_type_settings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.notification_type_settings_id_seq', 63011001, false); + + +-- +-- Name: object_permission_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.object_permission_id_seq', 1, false); + + +-- +-- Name: order_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.order_id_seq', 1, false); + + +-- +-- Name: order_item_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.order_item_id_seq', 1, false); + + +-- +-- Name: order_status_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.order_status_id_seq', 1, false); + + +-- +-- Name: product_category_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.product_category_id_seq', 1, false); + + +-- +-- Name: product_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.product_id_seq', 1, false); + + +-- +-- Name: product_module_id_seq1; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.product_module_id_seq1', 1, false); + + +-- +-- Name: product_price_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.product_price_id_seq', 1, false); + + +-- +-- Name: rental_interval_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.rental_interval_id_seq', 1, false); + + +-- +-- Name: rental_order_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.rental_order_id_seq', 1, false); + + +-- +-- Name: rental_order_item_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.rental_order_item_id_seq', 1, false); + + +-- +-- Name: rental_order_period_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.rental_order_period_id_seq', 1, false); + + +-- +-- Name: rental_schedule_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.rental_schedule_id_seq', 1, false); + + +-- +-- Name: reservation_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.reservation_id_seq', 1, false); + + +-- +-- Name: sales_plan_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.sales_plan_id_seq', 1, false); + + +-- +-- Name: schedule_event_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.schedule_event_id_seq', 1, false); + + +-- +-- Name: schedule_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.schedule_id_seq', 1, false); + + +-- +-- Name: schedule_performer_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.schedule_performer_id_seq', 1, false); + + +-- +-- Name: scheduled_action_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.scheduled_action_id_seq', 1, false); + + +-- +-- Name: scheduled_mail_message_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.scheduled_mail_message_id_seq', 1, false); + + +-- +-- Name: shipment_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.shipment_id_seq', 1, false); + + +-- +-- Name: shipment_item_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.shipment_item_id_seq', 1, false); + + +-- +-- Name: site_form_field_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.site_form_field_id_seq', 1, false); + + +-- +-- Name: site_form_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.site_form_id_seq', 1, false); + + +-- +-- Name: site_form_page_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.site_form_page_id_seq', 1, false); + + +-- +-- Name: stage_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.stage_id_seq', 15022001, false); + + +-- +-- Name: subtask_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.subtask_id_seq', 46022001, false); + + +-- +-- Name: task_comment_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.task_comment_id_seq', 47022001, false); + + +-- +-- Name: task_settings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.task_settings_id_seq', 45022001, false); + + +-- +-- Name: trigger_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.trigger_id_seq', 51011001, false); + + +-- +-- Name: tutorial_group_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.tutorial_group_id_seq', 1, false); + + +-- +-- Name: tutorial_item_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.tutorial_item_id_seq', 1, false); + + +-- +-- Name: tutorial_item_product_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.tutorial_item_product_id_seq', 1, false); + + +-- +-- Name: user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.user_id_seq', 12022001, false); + + +-- +-- Name: voximplant_call_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.voximplant_call_id_seq', 1, false); + + +-- +-- Name: voximplant_number_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.voximplant_number_id_seq', 1, false); + + +-- +-- Name: voximplant_scenario_entity_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.voximplant_scenario_entity_id_seq', 1, false); + + +-- +-- Name: voximplant_scenario_note_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.voximplant_scenario_note_id_seq', 1, false); + + +-- +-- Name: voximplant_scenario_task_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.voximplant_scenario_task_id_seq', 1, false); + + +-- +-- Name: warehouse_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - +-- + +SELECT pg_catalog.setval('public.warehouse_id_seq', 1, false); + + +-- +-- Name: migrations PK_8c82d7f526340ab734260ea46be; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.migrations + ADD CONSTRAINT "PK_8c82d7f526340ab734260ea46be" PRIMARY KEY (id); + + +-- +-- Name: account_api_access account_api_access_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_api_access + ADD CONSTRAINT account_api_access_pkey PRIMARY KEY (account_id); + + +-- +-- Name: account_settings account_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_settings + ADD CONSTRAINT account_settings_pkey PRIMARY KEY (account_id); + + +-- +-- Name: account accounts_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account + ADD CONSTRAINT accounts_pkey PRIMARY KEY (id); + + +-- +-- Name: account accounts_subdomain_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account + ADD CONSTRAINT accounts_subdomain_key UNIQUE (subdomain); + + +-- +-- Name: action action_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action + ADD CONSTRAINT action_pkey PRIMARY KEY (id); + + +-- +-- Name: action_activity_settings activity_action_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_activity_settings + ADD CONSTRAINT activity_action_settings_pkey PRIMARY KEY (action_id); + + +-- +-- Name: activity activity_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity + ADD CONSTRAINT activity_pkey PRIMARY KEY (id); + + +-- +-- Name: appsumo_license app_sumo_license_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appsumo_license + ADD CONSTRAINT app_sumo_license_pkey PRIMARY KEY (id); + + +-- +-- Name: appsumo_tier app_sumo_preset_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appsumo_tier + ADD CONSTRAINT app_sumo_preset_pkey PRIMARY KEY (id); + + +-- +-- Name: appsumo_license appsumo_license_license_key_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appsumo_license + ADD CONSTRAINT appsumo_license_license_key_key UNIQUE (license_key); + + +-- +-- Name: automation_condition automation_condition_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_condition + ADD CONSTRAINT automation_condition_pkey PRIMARY KEY (automation_id, condition_id); + + +-- +-- Name: automation_entity_type automation_entity_type_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_entity_type + ADD CONSTRAINT automation_entity_type_pkey PRIMARY KEY (id); + + +-- +-- Name: automation automation_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation + ADD CONSTRAINT automation_pkey PRIMARY KEY (id); + + +-- +-- Name: automation_process automation_process_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_process + ADD CONSTRAINT automation_process_pkey PRIMARY KEY (id); + + +-- +-- Name: automation_stage automation_stage_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_stage + ADD CONSTRAINT automation_stage_pkey PRIMARY KEY (automation_id, stage_id); + + +-- +-- Name: board board__code__account_id__uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.board + ADD CONSTRAINT board__code__account_id__uniq UNIQUE (code, account_id); + + +-- +-- Name: board board_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.board + ADD CONSTRAINT board_pkey PRIMARY KEY (id); + + +-- +-- Name: chat_message_file chat_message_file_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_file + ADD CONSTRAINT chat_message_file_pkey PRIMARY KEY (id); + + +-- +-- Name: chat_message chat_message_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message + ADD CONSTRAINT chat_message_pkey PRIMARY KEY (id); + + +-- +-- Name: chat_message_reaction chat_message_reaction_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_reaction + ADD CONSTRAINT chat_message_reaction_pkey PRIMARY KEY (id); + + +-- +-- Name: chat_message_user_status chat_message_user_status_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_user_status + ADD CONSTRAINT chat_message_user_status_pkey PRIMARY KEY (chat_id, message_id, chat_user_id); + + +-- +-- Name: chat_pinned_message chat_pinned_message_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_pinned_message + ADD CONSTRAINT chat_pinned_message_pkey PRIMARY KEY (chat_id, message_id); + + +-- +-- Name: chat chat_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat + ADD CONSTRAINT chat_pkey PRIMARY KEY (id); + + +-- +-- Name: chat_provider_messenger chat_provider_messenger_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_messenger + ADD CONSTRAINT chat_provider_messenger_pkey PRIMARY KEY (provider_id); + + +-- +-- Name: chat_provider chat_provider_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider + ADD CONSTRAINT chat_provider_pkey PRIMARY KEY (id); + + +-- +-- Name: chat_provider_twilio chat_provider_twilio_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_twilio + ADD CONSTRAINT chat_provider_twilio_pkey PRIMARY KEY (provider_id); + + +-- +-- Name: chat_provider_user chat_provider_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_user + ADD CONSTRAINT chat_provider_user_pkey PRIMARY KEY (provider_id, user_id, type); + + +-- +-- Name: chat_provider_wazzup chat_provider_wazzup_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_wazzup + ADD CONSTRAINT chat_provider_wazzup_pkey PRIMARY KEY (provider_id); + + +-- +-- Name: chat_user_external chat_user_external_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_user_external + ADD CONSTRAINT chat_user_external_pkey PRIMARY KEY (chat_user_id); + + +-- +-- Name: chat_user chat_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_user + ADD CONSTRAINT chat_user_pkey PRIMARY KEY (id); + + +-- +-- Name: condition condition_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.condition + ADD CONSTRAINT condition_pkey PRIMARY KEY (id); + + +-- +-- Name: demo_data demo_data_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.demo_data + ADD CONSTRAINT demo_data_pkey PRIMARY KEY (id); + + +-- +-- Name: department department_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.department + ADD CONSTRAINT department_pkey PRIMARY KEY (id); + + +-- +-- Name: document_template_access document_template_access_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_access + ADD CONSTRAINT document_template_access_pkey PRIMARY KEY (document_template_id, user_id); + + +-- +-- Name: document_template_entity_type document_template_entity_type_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_entity_type + ADD CONSTRAINT document_template_entity_type_pkey PRIMARY KEY (document_template_id, entity_type_id); + + +-- +-- Name: document_template document_template_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template + ADD CONSTRAINT document_template_pkey PRIMARY KEY (id); + + +-- +-- Name: action_email_settings email_action_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_email_settings + ADD CONSTRAINT email_action_settings_pkey PRIMARY KEY (action_id); + + +-- +-- Name: action_entity_settings entity_action_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_entity_settings + ADD CONSTRAINT entity_action_settings_pkey PRIMARY KEY (action_id); + + +-- +-- Name: entity_event entity_event_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_event + ADD CONSTRAINT entity_event_pkey PRIMARY KEY (id); + + +-- +-- Name: entity_link entity_link_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_link + ADD CONSTRAINT entity_link_pkey PRIMARY KEY (id); + + +-- +-- Name: entity_list_settings entity_list_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_list_settings + ADD CONSTRAINT entity_list_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: entity entity_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity + ADD CONSTRAINT entity_pkey PRIMARY KEY (id); + + +-- +-- Name: entity_stage_history entity_stage_history_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_stage_history + ADD CONSTRAINT entity_stage_history_pkey PRIMARY KEY (id); + + +-- +-- Name: entity_type_feature entity_type_feature_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_feature + ADD CONSTRAINT entity_type_feature_pkey PRIMARY KEY (entity_type_id, feature_id); + + +-- +-- Name: entity_type_link entity_type_link_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_link + ADD CONSTRAINT entity_type_link_pkey PRIMARY KEY (id); + + +-- +-- Name: entity_type entity_type_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type + ADD CONSTRAINT entity_type_pkey PRIMARY KEY (id); + + +-- +-- Name: exact_time_trigger_settings exact_time_trigger_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.exact_time_trigger_settings + ADD CONSTRAINT exact_time_trigger_settings_pkey PRIMARY KEY (trigger_id); + + +-- +-- Name: external_entity external_entity_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.external_entity + ADD CONSTRAINT external_entity_pkey PRIMARY KEY (id); + + +-- +-- Name: external_system external_system_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.external_system + ADD CONSTRAINT external_system_pkey PRIMARY KEY (id); + + +-- +-- Name: feature feature_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.feature + ADD CONSTRAINT feature_pkey PRIMARY KEY (id); + + +-- +-- Name: field field__code__entity_type_id__uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field + ADD CONSTRAINT field__code__entity_type_id__uniq UNIQUE (code, entity_type_id); + + +-- +-- Name: field_condition field_condition_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_condition + ADD CONSTRAINT field_condition_pkey PRIMARY KEY (condition_id); + + +-- +-- Name: field_group field_group_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_group + ADD CONSTRAINT field_group_pkey PRIMARY KEY (id); + + +-- +-- Name: field_value field_id__entity_id__uniq; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_value + ADD CONSTRAINT field_id__entity_id__uniq UNIQUE (field_id, entity_id); + + +-- +-- Name: field_option field_option_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_option + ADD CONSTRAINT field_option_pkey PRIMARY KEY (id); + + +-- +-- Name: field field_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field + ADD CONSTRAINT field_pkey PRIMARY KEY (id); + + +-- +-- Name: field_stage_settings field_stage_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_stage_settings + ADD CONSTRAINT field_stage_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: field_user_settings field_user_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_user_settings + ADD CONSTRAINT field_user_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: field_value field_value_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_value + ADD CONSTRAINT field_value_pkey PRIMARY KEY (id); + + +-- +-- Name: file_info file_info_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_info + ADD CONSTRAINT file_info_pkey PRIMARY KEY (id); + + +-- +-- Name: file_link file_link_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_link + ADD CONSTRAINT file_link_pkey PRIMARY KEY (id); + + +-- +-- Name: industry industry_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.industry + ADD CONSTRAINT industry_pkey PRIMARY KEY (code); + + +-- +-- Name: mail_message_folder mail_message_folder_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message_folder + ADD CONSTRAINT mail_message_folder_pkey PRIMARY KEY (message_id, folder_id); + + +-- +-- Name: mail_message_payload mail_message_payload_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message_payload + ADD CONSTRAINT mail_message_payload_pkey PRIMARY KEY (id); + + +-- +-- Name: mail_message mail_message_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message + ADD CONSTRAINT mail_message_pkey PRIMARY KEY (id); + + +-- +-- Name: mailbox_accessible_user mailbox_accessible_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_accessible_user + ADD CONSTRAINT mailbox_accessible_user_pkey PRIMARY KEY (mailbox_id, user_id); + + +-- +-- Name: mailbox_folder mailbox_folder_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_folder + ADD CONSTRAINT mailbox_folder_pkey PRIMARY KEY (id); + + +-- +-- Name: mailbox mailbox_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox + ADD CONSTRAINT mailbox_pkey PRIMARY KEY (id); + + +-- +-- Name: mailbox_settings_gmail mailbox_settings_gmail_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_settings_gmail + ADD CONSTRAINT mailbox_settings_gmail_pkey PRIMARY KEY (mailbox_id); + + +-- +-- Name: mailbox_settings_manual mailbox_settings_manual_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_settings_manual + ADD CONSTRAINT mailbox_settings_manual_pkey PRIMARY KEY (mailbox_id); + + +-- +-- Name: mailbox_signature_link mailbox_signature_link_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_signature_link + ADD CONSTRAINT mailbox_signature_link_pkey PRIMARY KEY (signature_id, mailbox_id); + + +-- +-- Name: mailbox_signature mailbox_signature_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_signature + ADD CONSTRAINT mailbox_signature_pkey PRIMARY KEY (id); + + +-- +-- Name: note note_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.note + ADD CONSTRAINT note_pkey PRIMARY KEY (id); + + +-- +-- Name: notification notification_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification + ADD CONSTRAINT notification_pkey PRIMARY KEY (id); + + +-- +-- Name: notification_settings notification_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_settings + ADD CONSTRAINT notification_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: notification_type_follow_user notification_type_follow_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_type_follow_user + ADD CONSTRAINT notification_type_follow_user_pkey PRIMARY KEY (type_id, user_id); + + +-- +-- Name: notification_type_settings notification_type_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_type_settings + ADD CONSTRAINT notification_type_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: object_permission object_permission_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.object_permission + ADD CONSTRAINT object_permission_pkey PRIMARY KEY (id); + + +-- +-- Name: order_item order_item_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.order_item + ADD CONSTRAINT order_item_pkey PRIMARY KEY (id); + + +-- +-- Name: order_status order_status_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.order_status + ADD CONSTRAINT order_status_pkey PRIMARY KEY (id); + + +-- +-- Name: orders orders_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_pkey PRIMARY KEY (id); + + +-- +-- Name: product_category product_category_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_category + ADD CONSTRAINT product_category_pkey PRIMARY KEY (id); + + +-- +-- Name: products_section product_module_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.products_section + ADD CONSTRAINT product_module_pkey PRIMARY KEY (id); + + +-- +-- Name: product product_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product + ADD CONSTRAINT product_pkey PRIMARY KEY (id); + + +-- +-- Name: product_price product_price_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_price + ADD CONSTRAINT product_price_pkey PRIMARY KEY (id); + + +-- +-- Name: products_section_entity_type products_section_entity_type_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.products_section_entity_type + ADD CONSTRAINT products_section_entity_type_pkey PRIMARY KEY (section_id, entity_type_id); + + +-- +-- Name: ready_made_solution ready_made_solution_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ready_made_solution + ADD CONSTRAINT ready_made_solution_pkey PRIMARY KEY (code); + + +-- +-- Name: rental_interval rental_interval_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_interval + ADD CONSTRAINT rental_interval_pkey PRIMARY KEY (id); + + +-- +-- Name: rental_order_item rental_order_item_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order_item + ADD CONSTRAINT rental_order_item_pkey PRIMARY KEY (id); + + +-- +-- Name: rental_order_period rental_order_period_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order_period + ADD CONSTRAINT rental_order_period_pkey PRIMARY KEY (id); + + +-- +-- Name: rental_order rental_order_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order + ADD CONSTRAINT rental_order_pkey PRIMARY KEY (id); + + +-- +-- Name: rental_event rental_schedule_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_event + ADD CONSTRAINT rental_schedule_pkey PRIMARY KEY (id); + + +-- +-- Name: reservation reservation_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.reservation + ADD CONSTRAINT reservation_pkey PRIMARY KEY (id); + + +-- +-- Name: sales_plan sales_plan_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sales_plan + ADD CONSTRAINT sales_plan_pkey PRIMARY KEY (id); + + +-- +-- Name: salesforce_settings salesforce_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.salesforce_settings + ADD CONSTRAINT salesforce_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: schedule_appointment schedule_event_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_appointment + ADD CONSTRAINT schedule_event_pkey PRIMARY KEY (id); + + +-- +-- Name: schedule_performer schedule_performer_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_performer + ADD CONSTRAINT schedule_performer_pkey PRIMARY KEY (id); + + +-- +-- Name: schedule schedule_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule + ADD CONSTRAINT schedule_pkey PRIMARY KEY (id); + + +-- +-- Name: action_scheduled scheduled_action_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_scheduled + ADD CONSTRAINT scheduled_action_pkey PRIMARY KEY (id); + + +-- +-- Name: scheduled_mail_message scheduled_mail_message_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.scheduled_mail_message + ADD CONSTRAINT scheduled_mail_message_pkey PRIMARY KEY (id); + + +-- +-- Name: shipment_item shipment_item_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment_item + ADD CONSTRAINT shipment_item_pkey PRIMARY KEY (id); + + +-- +-- Name: shipment shipment_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment + ADD CONSTRAINT shipment_pkey PRIMARY KEY (id); + + +-- +-- Name: site_form site_form_code_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form + ADD CONSTRAINT site_form_code_key UNIQUE (code); + + +-- +-- Name: site_form_consent site_form_consent_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_consent + ADD CONSTRAINT site_form_consent_pkey PRIMARY KEY (form_id); + + +-- +-- Name: site_form_entity_type site_form_entity_type_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_entity_type + ADD CONSTRAINT site_form_entity_type_pkey PRIMARY KEY (form_id, entity_type_id); + + +-- +-- Name: site_form_field_entity_field site_form_field_entity_field_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field_entity_field + ADD CONSTRAINT site_form_field_entity_field_pkey PRIMARY KEY (form_field_id); + + +-- +-- Name: site_form_field_entity_name site_form_field_entity_name_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field_entity_name + ADD CONSTRAINT site_form_field_entity_name_pkey PRIMARY KEY (form_field_id); + + +-- +-- Name: site_form_field site_form_field_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field + ADD CONSTRAINT site_form_field_pkey PRIMARY KEY (id); + + +-- +-- Name: site_form_gratitude site_form_gratitude_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_gratitude + ADD CONSTRAINT site_form_gratitude_pkey PRIMARY KEY (form_id); + + +-- +-- Name: site_form_page site_form_page_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_page + ADD CONSTRAINT site_form_page_pkey PRIMARY KEY (id); + + +-- +-- Name: site_form site_form_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form + ADD CONSTRAINT site_form_pkey PRIMARY KEY (id); + + +-- +-- Name: stage stage_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.stage + ADD CONSTRAINT stage_pkey PRIMARY KEY (id); + + +-- +-- Name: product_stock stock_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_stock + ADD CONSTRAINT stock_pkey PRIMARY KEY (product_id, warehouse_id); + + +-- +-- Name: account_subscription subscription_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_subscription + ADD CONSTRAINT subscription_pkey PRIMARY KEY (account_id); + + +-- +-- Name: task_subtask subtask_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_subtask + ADD CONSTRAINT subtask_pkey PRIMARY KEY (id); + + +-- +-- Name: action_task_settings task_action_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_task_settings + ADD CONSTRAINT task_action_settings_pkey PRIMARY KEY (action_id); + + +-- +-- Name: task_comment_like task_comment_like_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment_like + ADD CONSTRAINT task_comment_like_pkey PRIMARY KEY (comment_id, user_id); + + +-- +-- Name: task_comment task_comment_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment + ADD CONSTRAINT task_comment_pkey PRIMARY KEY (id); + + +-- +-- Name: task task_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_pkey PRIMARY KEY (id); + + +-- +-- Name: task_settings task_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_settings + ADD CONSTRAINT task_settings_pkey PRIMARY KEY (id); + + +-- +-- Name: activity_type task_type_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_type + ADD CONSTRAINT task_type_pkey PRIMARY KEY (id); + + +-- +-- Name: test_account test_account_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.test_account + ADD CONSTRAINT test_account_pkey PRIMARY KEY (account_id); + + +-- +-- Name: trigger trigger_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.trigger + ADD CONSTRAINT trigger_pkey PRIMARY KEY (id); + + +-- +-- Name: tutorial_group tutorial_group_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_group + ADD CONSTRAINT tutorial_group_pkey PRIMARY KEY (id); + + +-- +-- Name: tutorial_item tutorial_item_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item + ADD CONSTRAINT tutorial_item_pkey PRIMARY KEY (id); + + +-- +-- Name: tutorial_item_product tutorial_item_product_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item_product + ADD CONSTRAINT tutorial_item_product_pkey PRIMARY KEY (id); + + +-- +-- Name: tutorial_item_user tutorial_item_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item_user + ADD CONSTRAINT tutorial_item_user_pkey PRIMARY KEY (item_id, user_id); + + +-- +-- Name: user_condition user_condition_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_condition + ADD CONSTRAINT user_condition_pkey PRIMARY KEY (condition_id, user_id); + + +-- +-- Name: user_object_permission user_object_permission_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_object_permission + ADD CONSTRAINT user_object_permission_pkey PRIMARY KEY (user_id, object_permission_id); + + +-- +-- Name: user_profile user_profile_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_profile + ADD CONSTRAINT user_profile_pkey PRIMARY KEY (user_id); + + +-- +-- Name: users users_email_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_email_key UNIQUE (email); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: voximplant_account voximplant_account_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_account + ADD CONSTRAINT voximplant_account_pkey PRIMARY KEY (account_id); + + +-- +-- Name: voximplant_call voximplant_call_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_call + ADD CONSTRAINT voximplant_call_pkey PRIMARY KEY (id); + + +-- +-- Name: voximplant_number voximplant_number_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_number + ADD CONSTRAINT voximplant_number_pkey PRIMARY KEY (id); + + +-- +-- Name: voximplant_number_user voximplant_number_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_number_user + ADD CONSTRAINT voximplant_number_user_pkey PRIMARY KEY (number_id, user_id); + + +-- +-- Name: voximplant_scenario_entity voximplant_scenario_entity_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_entity + ADD CONSTRAINT voximplant_scenario_entity_pkey PRIMARY KEY (id); + + +-- +-- Name: voximplant_scenario_note voximplant_scenario_note_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_note + ADD CONSTRAINT voximplant_scenario_note_pkey PRIMARY KEY (id); + + +-- +-- Name: voximplant_scenario_task voximplant_scenario_task_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_task + ADD CONSTRAINT voximplant_scenario_task_pkey PRIMARY KEY (id); + + +-- +-- Name: voximplant_user voximplant_user_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_user + ADD CONSTRAINT voximplant_user_pkey PRIMARY KEY (user_id); + + +-- +-- Name: warehouse warehouse_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.warehouse + ADD CONSTRAINT warehouse_pkey PRIMARY KEY (id); + + +-- +-- Name: activity_entity_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX activity_entity_id_idx ON public.activity USING btree (entity_id); + + +-- +-- Name: activity_is_resolved_end_date_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX activity_is_resolved_end_date_idx ON public.activity USING btree (is_resolved, end_date); + + +-- +-- Name: activity_is_resolved_responsible_user_id_start_date_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX activity_is_resolved_responsible_user_id_start_date_idx ON public.activity USING btree (is_resolved, responsible_user_id, start_date); + + +-- +-- Name: board_account_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX board_account_id_idx ON public.board USING btree (account_id); + + +-- +-- Name: board_type_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX board_type_idx ON public.board USING btree (type); + + +-- +-- Name: department_account_id_parent_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX department_account_id_parent_id_idx ON public.department USING btree (account_id, parent_id); + + +-- +-- Name: entity_entity_type_id_stage_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX entity_entity_type_id_stage_id_idx ON public.entity USING btree (entity_type_id, stage_id); + + +-- +-- Name: entity_link_source_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX entity_link_source_id_idx ON public.entity_link USING btree (source_id); + + +-- +-- Name: entity_link_source_id_sort_order_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX entity_link_source_id_sort_order_id_idx ON public.entity_link USING btree (source_id, sort_order, id); + + +-- +-- Name: entity_link_target_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX entity_link_target_id_idx ON public.entity_link USING btree (target_id); + + +-- +-- Name: entity_type_link_account_id_source_id_sort_order_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX entity_type_link_account_id_source_id_sort_order_id_idx ON public.entity_type_link USING btree (account_id, source_id, sort_order, id); + + +-- +-- Name: exact_time_trigger_settings_date_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX exact_time_trigger_settings_date_idx ON public.exact_time_trigger_settings USING btree (date); + + +-- +-- Name: field_entity_type_id_type_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX field_entity_type_id_type_idx ON public.field USING btree (entity_type_id, type); + + +-- +-- Name: field_group_account_id_entity_type_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX field_group_account_id_entity_type_id_idx ON public.field_group USING btree (account_id, entity_type_id); + + +-- +-- Name: field_option_field_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX field_option_field_id_idx ON public.field_option USING btree (field_id); + + +-- +-- Name: field_value_account_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX field_value_account_id_idx ON public.field_value USING btree (account_id); + + +-- +-- Name: field_value_entity_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX field_value_entity_id_idx ON public.field_value USING btree (entity_id); + + +-- +-- Name: file_link_account_id_source_type_source_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX file_link_account_id_source_type_source_id_idx ON public.file_link USING btree (account_id, source_type, source_id); + + +-- +-- Name: idx_chat_on_account_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_chat_on_account_id ON public.chat USING btree (account_id); + + +-- +-- Name: idx_chat_user_on_user_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_chat_user_on_user_id ON public.chat_user USING btree (user_id); + + +-- +-- Name: idx_chat_user_on_user_id_chat_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_chat_user_on_user_id_chat_id ON public.chat_user USING btree (user_id, chat_id); + + +-- +-- Name: idx_cmus_on_message_id_chat_id_status; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_cmus_on_message_id_chat_id_status ON public.chat_message_user_status USING btree (message_id, chat_id, status); + + +-- +-- Name: idx_cmus_on_message_id_status; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_cmus_on_message_id_status ON public.chat_message_user_status USING btree (message_id, status); + + +-- +-- Name: idx_entity_account_id_stage_id_closed_at; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_entity_account_id_stage_id_closed_at ON public.entity USING btree (account_id, stage_id, closed_at); + + +-- +-- Name: idx_field_value_entity_id_field_type; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_field_value_entity_id_field_type ON public.field_value USING btree (entity_id, field_type); + + +-- +-- Name: idx_mail_message_account_mailbox; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_mail_message_account_mailbox ON public.mail_message USING btree (account_id, mailbox_id); + + +-- +-- Name: idx_mail_message_external_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_mail_message_external_id ON public.mail_message USING btree (external_id); + + +-- +-- Name: idx_mail_message_message_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_mail_message_message_id ON public.mail_message USING btree (message_id); + + +-- +-- Name: idx_mailbox_folder_account_mailbox; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX idx_mailbox_folder_account_mailbox ON public.mailbox_folder USING btree (account_id, mailbox_id); + + +-- +-- Name: mail_message_entity_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX mail_message_entity_id_idx ON public.mail_message USING btree (entity_id); + + +-- +-- Name: mailbox_folder_account_id_mailbox_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX mailbox_folder_account_id_mailbox_id_idx ON public.mailbox_folder USING btree (account_id, mailbox_id); + + +-- +-- Name: note_entity_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX note_entity_id_idx ON public.note USING btree (entity_id); + + +-- +-- Name: notification_account_user_seen_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX notification_account_user_seen_idx ON public.notification USING btree (account_id, user_id, is_seen); + + +-- +-- Name: notification_settings_user_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX notification_settings_user_id_idx ON public.notification_settings USING btree (user_id); + + +-- +-- Name: notification_type_settings_on_type_enabled_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX notification_type_settings_on_type_enabled_idx ON public.notification_type_settings USING btree (type) WHERE (is_enabled = true); + + +-- +-- Name: object_permission_object_type_object_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX object_permission_object_type_object_id_idx ON public.object_permission USING btree (object_type, object_id); + + +-- +-- Name: scheduled_action_scheduled_time_completed_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX scheduled_action_scheduled_time_completed_idx ON public.action_scheduled USING btree (scheduled_time, completed); + + +-- +-- Name: scheduled_mail_message_mailbox_id_sent_at_null_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX scheduled_mail_message_mailbox_id_sent_at_null_idx ON public.scheduled_mail_message USING btree (mailbox_id) WHERE (sent_at IS NULL); + + +-- +-- Name: stage_account_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX stage_account_id_idx ON public.stage USING btree (account_id); + + +-- +-- Name: stage_board_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX stage_board_id_idx ON public.stage USING btree (board_id); + + +-- +-- Name: subtask_account_id_task_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX subtask_account_id_task_id_idx ON public.task_subtask USING btree (account_id, task_id); + + +-- +-- Name: task_account_id_responsible_user_id_stage_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX task_account_id_responsible_user_id_stage_id_idx ON public.task USING btree (account_id, responsible_user_id, stage_id); + + +-- +-- Name: task_entity_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX task_entity_id_idx ON public.task USING btree (entity_id); + + +-- +-- Name: task_entity_id_is_resolved_created_by_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX task_entity_id_is_resolved_created_by_idx ON public.task USING btree (entity_id, is_resolved, created_by); + + +-- +-- Name: task_is_resolved_end_date_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX task_is_resolved_end_date_idx ON public.task USING btree (is_resolved, end_date); + + +-- +-- Name: task_is_resolved_responsible_user_id_start_date_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX task_is_resolved_responsible_user_id_start_date_idx ON public.task USING btree (is_resolved, responsible_user_id, start_date); + + +-- +-- Name: user_object_permission_user_id_object_permission_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX user_object_permission_user_id_object_permission_id_idx ON public.user_object_permission USING btree (user_id, object_permission_id); + + +-- +-- Name: users_account_id_department_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX users_account_id_department_id_idx ON public.users USING btree (account_id, department_id); + + +-- +-- Name: users_account_id_id_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX users_account_id_id_idx ON public.users USING btree (account_id, id); + + +-- +-- Name: account_api_access account_api_access_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_api_access + ADD CONSTRAINT account_api_access_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: account_settings account_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_settings + ADD CONSTRAINT account_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action action_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action + ADD CONSTRAINT action_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: activity activity_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity + ADD CONSTRAINT activity_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action_activity_settings activity_action_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_activity_settings + ADD CONSTRAINT activity_action_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action_activity_settings activity_action_settings_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_activity_settings + ADD CONSTRAINT activity_action_settings_action_id_fkey FOREIGN KEY (action_id) REFERENCES public.action(id) ON DELETE CASCADE; + + +-- +-- Name: action_activity_settings activity_action_settings_activity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_activity_settings + ADD CONSTRAINT activity_action_settings_activity_type_id_fkey FOREIGN KEY (activity_type_id) REFERENCES public.activity_type(id) ON DELETE CASCADE; + + +-- +-- Name: action_activity_settings activity_action_settings_responsible_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_activity_settings + ADD CONSTRAINT activity_action_settings_responsible_user_id_fkey FOREIGN KEY (responsible_user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: activity activity_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity + ADD CONSTRAINT activity_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: activity activity_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity + ADD CONSTRAINT activity_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: activity activity_responsible_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity + ADD CONSTRAINT activity_responsible_user_id_fkey FOREIGN KEY (responsible_user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: activity activity_task_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity + ADD CONSTRAINT activity_task_type_id_fkey FOREIGN KEY (activity_type_id) REFERENCES public.activity_type(id) ON DELETE CASCADE; + + +-- +-- Name: appsumo_license app_sumo_license_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.appsumo_license + ADD CONSTRAINT app_sumo_license_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE SET NULL; + + +-- +-- Name: automation automation_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation + ADD CONSTRAINT automation_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: automation automation_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation + ADD CONSTRAINT automation_action_id_fkey FOREIGN KEY (action_id) REFERENCES public.action(id); + + +-- +-- Name: automation_condition automation_condition_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_condition + ADD CONSTRAINT automation_condition_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: automation_condition automation_condition_automation_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_condition + ADD CONSTRAINT automation_condition_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES public.automation(id) ON DELETE CASCADE; + + +-- +-- Name: automation_condition automation_condition_condition_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_condition + ADD CONSTRAINT automation_condition_condition_id_fkey FOREIGN KEY (condition_id) REFERENCES public.condition(id) ON DELETE CASCADE; + + +-- +-- Name: automation automation_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation + ADD CONSTRAINT automation_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: automation_entity_type automation_entity_type_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_entity_type + ADD CONSTRAINT automation_entity_type_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: automation_entity_type automation_entity_type_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_entity_type + ADD CONSTRAINT automation_entity_type_board_id_fkey FOREIGN KEY (board_id) REFERENCES public.board(id); + + +-- +-- Name: automation_entity_type automation_entity_type_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_entity_type + ADD CONSTRAINT automation_entity_type_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: automation_entity_type automation_entity_type_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_entity_type + ADD CONSTRAINT automation_entity_type_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id); + + +-- +-- Name: automation_entity_type automation_entity_type_process_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_entity_type + ADD CONSTRAINT automation_entity_type_process_id_fkey FOREIGN KEY (process_id) REFERENCES public.automation_process(id) ON DELETE CASCADE; + + +-- +-- Name: automation_entity_type automation_entity_type_stage_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_entity_type + ADD CONSTRAINT automation_entity_type_stage_id_fkey FOREIGN KEY (stage_id) REFERENCES public.stage(id); + + +-- +-- Name: automation_process automation_process_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_process + ADD CONSTRAINT automation_process_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: automation_stage automation_stage_automation_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_stage + ADD CONSTRAINT automation_stage_automation_id_fkey FOREIGN KEY (automation_id) REFERENCES public.automation(id) ON DELETE CASCADE; + + +-- +-- Name: automation_stage automation_stage_stage_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation_stage + ADD CONSTRAINT automation_stage_stage_id_fkey FOREIGN KEY (stage_id) REFERENCES public.stage(id) ON DELETE CASCADE; + + +-- +-- Name: automation automation_trigger_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.automation + ADD CONSTRAINT automation_trigger_id_fkey FOREIGN KEY (trigger_id) REFERENCES public.trigger(id); + + +-- +-- Name: board board_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.board + ADD CONSTRAINT board_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: board board_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.board + ADD CONSTRAINT board_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES public.users(id) ON DELETE SET NULL; + + +-- +-- Name: board board_task_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.board + ADD CONSTRAINT board_task_board_id_fkey FOREIGN KEY (task_board_id) REFERENCES public.board(id) ON DELETE SET NULL; + + +-- +-- Name: action_entity_settings change_stage_action_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_entity_settings + ADD CONSTRAINT change_stage_action_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action_entity_settings change_stage_action_settings_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_entity_settings + ADD CONSTRAINT change_stage_action_settings_action_id_fkey FOREIGN KEY (action_id) REFERENCES public.action(id) ON DELETE CASCADE; + + +-- +-- Name: action_entity_settings change_stage_action_settings_stage_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_entity_settings + ADD CONSTRAINT change_stage_action_settings_stage_id_fkey FOREIGN KEY (stage_id) REFERENCES public.stage(id) ON DELETE CASCADE; + + +-- +-- Name: chat chat_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat + ADD CONSTRAINT chat_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat chat_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat + ADD CONSTRAINT chat_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE SET NULL; + + +-- +-- Name: chat chat_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat + ADD CONSTRAINT chat_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE SET NULL; + + +-- +-- Name: chat_message chat_message_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message + ADD CONSTRAINT chat_message_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message chat_message_chat_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message + ADD CONSTRAINT chat_message_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES public.chat(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message chat_message_chat_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message + ADD CONSTRAINT chat_message_chat_user_id_fkey FOREIGN KEY (chat_user_id) REFERENCES public.chat_user(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_file chat_message_file_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_file + ADD CONSTRAINT chat_message_file_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_file chat_message_file_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_file + ADD CONSTRAINT chat_message_file_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.chat_message(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_reaction chat_message_reaction_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_reaction + ADD CONSTRAINT chat_message_reaction_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_reaction chat_message_reaction_chat_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_reaction + ADD CONSTRAINT chat_message_reaction_chat_user_id_fkey FOREIGN KEY (chat_user_id) REFERENCES public.chat_user(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_reaction chat_message_reaction_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_reaction + ADD CONSTRAINT chat_message_reaction_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.chat_message(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message chat_message_reply_to_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message + ADD CONSTRAINT chat_message_reply_to_id_fkey FOREIGN KEY (reply_to_id) REFERENCES public.chat_message(id) ON DELETE SET NULL; + + +-- +-- Name: chat_message_user_status chat_message_user_status_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_user_status + ADD CONSTRAINT chat_message_user_status_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_user_status chat_message_user_status_chat_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_user_status + ADD CONSTRAINT chat_message_user_status_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES public.chat(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_user_status chat_message_user_status_chat_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_user_status + ADD CONSTRAINT chat_message_user_status_chat_user_id_fkey FOREIGN KEY (chat_user_id) REFERENCES public.chat_user(id) ON DELETE CASCADE; + + +-- +-- Name: chat_message_user_status chat_message_user_status_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_message_user_status + ADD CONSTRAINT chat_message_user_status_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.chat_message(id) ON DELETE CASCADE; + + +-- +-- Name: chat_pinned_message chat_pinned_message_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_pinned_message + ADD CONSTRAINT chat_pinned_message_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_pinned_message chat_pinned_message_chat_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_pinned_message + ADD CONSTRAINT chat_pinned_message_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES public.chat(id) ON DELETE CASCADE; + + +-- +-- Name: chat_pinned_message chat_pinned_message_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_pinned_message + ADD CONSTRAINT chat_pinned_message_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.chat_message(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider chat_provider_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider + ADD CONSTRAINT chat_provider_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider chat_provider_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider + ADD CONSTRAINT chat_provider_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: chat chat_provider_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat + ADD CONSTRAINT chat_provider_id_fkey FOREIGN KEY (provider_id) REFERENCES public.chat_provider(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_messenger chat_provider_messenger_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_messenger + ADD CONSTRAINT chat_provider_messenger_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_messenger chat_provider_messenger_provider_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_messenger + ADD CONSTRAINT chat_provider_messenger_provider_id_fkey FOREIGN KEY (provider_id) REFERENCES public.chat_provider(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_twilio chat_provider_twilio_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_twilio + ADD CONSTRAINT chat_provider_twilio_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_twilio chat_provider_twilio_provider_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_twilio + ADD CONSTRAINT chat_provider_twilio_provider_id_fkey FOREIGN KEY (provider_id) REFERENCES public.chat_provider(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_user chat_provider_user_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_user + ADD CONSTRAINT chat_provider_user_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_user chat_provider_user_provider_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_user + ADD CONSTRAINT chat_provider_user_provider_id_fkey FOREIGN KEY (provider_id) REFERENCES public.chat_provider(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_user chat_provider_user_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_user + ADD CONSTRAINT chat_provider_user_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_wazzup chat_provider_wazzup_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_wazzup + ADD CONSTRAINT chat_provider_wazzup_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_provider_wazzup chat_provider_wazzup_provider_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_provider_wazzup + ADD CONSTRAINT chat_provider_wazzup_provider_id_fkey FOREIGN KEY (provider_id) REFERENCES public.chat_provider(id) ON DELETE CASCADE; + + +-- +-- Name: chat_user chat_user_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_user + ADD CONSTRAINT chat_user_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_user chat_user_chat_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_user + ADD CONSTRAINT chat_user_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES public.chat(id) ON DELETE CASCADE; + + +-- +-- Name: chat_user_external chat_user_external_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_user_external + ADD CONSTRAINT chat_user_external_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: chat_user_external chat_user_external_chat_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_user_external + ADD CONSTRAINT chat_user_external_chat_user_id_fkey FOREIGN KEY (chat_user_id) REFERENCES public.chat_user(id) ON DELETE CASCADE; + + +-- +-- Name: chat_user chat_user_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.chat_user + ADD CONSTRAINT chat_user_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: condition condition_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.condition + ADD CONSTRAINT condition_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: demo_data demo_data_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.demo_data + ADD CONSTRAINT demo_data_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: department department_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.department + ADD CONSTRAINT department_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: department department_parent_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.department + ADD CONSTRAINT department_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES public.department(id) ON DELETE CASCADE; + + +-- +-- Name: document_template_access document_template_access_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_access + ADD CONSTRAINT document_template_access_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: document_template_access document_template_access_document_template_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_access + ADD CONSTRAINT document_template_access_document_template_id_fkey FOREIGN KEY (document_template_id) REFERENCES public.document_template(id) ON DELETE CASCADE; + + +-- +-- Name: document_template_access document_template_access_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_access + ADD CONSTRAINT document_template_access_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: document_template document_template_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template + ADD CONSTRAINT document_template_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: document_template document_template_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template + ADD CONSTRAINT document_template_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: document_template_entity_type document_template_entity_type_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_entity_type + ADD CONSTRAINT document_template_entity_type_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: document_template_entity_type document_template_entity_type_document_template_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_entity_type + ADD CONSTRAINT document_template_entity_type_document_template_id_fkey FOREIGN KEY (document_template_id) REFERENCES public.document_template(id) ON DELETE CASCADE; + + +-- +-- Name: document_template_entity_type document_template_entity_type_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.document_template_entity_type + ADD CONSTRAINT document_template_entity_type_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: action_email_settings email_action_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_email_settings + ADD CONSTRAINT email_action_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action_email_settings email_action_settings_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_email_settings + ADD CONSTRAINT email_action_settings_action_id_fkey FOREIGN KEY (action_id) REFERENCES public.action(id) ON DELETE CASCADE; + + +-- +-- Name: action_email_settings email_action_settings_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_email_settings + ADD CONSTRAINT email_action_settings_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: action_email_settings email_action_settings_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_email_settings + ADD CONSTRAINT email_action_settings_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: entity entity_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity + ADD CONSTRAINT entity_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity entity_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity + ADD CONSTRAINT entity_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: entity entity_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity + ADD CONSTRAINT entity_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: entity_event entity_event_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_event + ADD CONSTRAINT entity_event_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity_event entity_event_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_event + ADD CONSTRAINT entity_event_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: entity_link entity_link_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_link + ADD CONSTRAINT entity_link_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity_link entity_link_back_link_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_link + ADD CONSTRAINT entity_link_back_link_id_fkey FOREIGN KEY (back_link_id) REFERENCES public.entity_link(id) ON DELETE CASCADE; + + +-- +-- Name: entity_link entity_link_source_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_link + ADD CONSTRAINT entity_link_source_id_fkey FOREIGN KEY (source_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: entity_link entity_link_target_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_link + ADD CONSTRAINT entity_link_target_id_fkey FOREIGN KEY (target_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: entity_list_settings entity_list_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_list_settings + ADD CONSTRAINT entity_list_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity_list_settings entity_list_settings_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_list_settings + ADD CONSTRAINT entity_list_settings_board_id_fkey FOREIGN KEY (board_id) REFERENCES public.board(id) ON DELETE CASCADE; + + +-- +-- Name: entity_list_settings entity_list_settings_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_list_settings + ADD CONSTRAINT entity_list_settings_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: entity entity_responsible_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity + ADD CONSTRAINT entity_responsible_user_id_fkey FOREIGN KEY (responsible_user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: entity_stage_history entity_stage_history_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_stage_history + ADD CONSTRAINT entity_stage_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity_stage_history entity_stage_history_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_stage_history + ADD CONSTRAINT entity_stage_history_board_id_fkey FOREIGN KEY (board_id) REFERENCES public.board(id) ON DELETE CASCADE; + + +-- +-- Name: entity_stage_history entity_stage_history_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_stage_history + ADD CONSTRAINT entity_stage_history_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: entity_stage_history entity_stage_history_stage_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_stage_history + ADD CONSTRAINT entity_stage_history_stage_id_fkey FOREIGN KEY (stage_id) REFERENCES public.stage(id) ON DELETE CASCADE; + + +-- +-- Name: entity entity_stage_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity + ADD CONSTRAINT entity_stage_id_fkey FOREIGN KEY (stage_id) REFERENCES public.stage(id) ON DELETE CASCADE; + + +-- +-- Name: entity_type entity_type_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type + ADD CONSTRAINT entity_type_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity_type_feature entity_type_feature_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_feature + ADD CONSTRAINT entity_type_feature_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity_type_feature entity_type_feature_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_feature + ADD CONSTRAINT entity_type_feature_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: entity_type_feature entity_type_feature_feature_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_feature + ADD CONSTRAINT entity_type_feature_feature_id_fkey FOREIGN KEY (feature_id) REFERENCES public.feature(id) ON DELETE CASCADE; + + +-- +-- Name: entity_type_link entity_type_link_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_link + ADD CONSTRAINT entity_type_link_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: entity_type_link entity_type_link_source_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_link + ADD CONSTRAINT entity_type_link_source_id_fkey FOREIGN KEY (source_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: entity_type_link entity_type_link_target_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.entity_type_link + ADD CONSTRAINT entity_type_link_target_id_fkey FOREIGN KEY (target_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: exact_time_trigger_settings exact_time_trigger_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.exact_time_trigger_settings + ADD CONSTRAINT exact_time_trigger_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: exact_time_trigger_settings exact_time_trigger_settings_trigger_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.exact_time_trigger_settings + ADD CONSTRAINT exact_time_trigger_settings_trigger_id_fkey FOREIGN KEY (trigger_id) REFERENCES public.trigger(id) ON DELETE CASCADE; + + +-- +-- Name: external_entity external_entity_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.external_entity + ADD CONSTRAINT external_entity_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: external_entity external_entity_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.external_entity + ADD CONSTRAINT external_entity_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: external_entity external_entity_system_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.external_entity + ADD CONSTRAINT external_entity_system_fkey FOREIGN KEY (system) REFERENCES public.external_system(id); + + +-- +-- Name: field field_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field + ADD CONSTRAINT field_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: field_condition field_condition_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_condition + ADD CONSTRAINT field_condition_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: field_condition field_condition_condition_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_condition + ADD CONSTRAINT field_condition_condition_id_fkey FOREIGN KEY (condition_id) REFERENCES public.condition(id) ON DELETE CASCADE; + + +-- +-- Name: field_condition field_condition_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_condition + ADD CONSTRAINT field_condition_field_id_fkey FOREIGN KEY (field_id) REFERENCES public.field(id) ON DELETE CASCADE; + + +-- +-- Name: field field_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field + ADD CONSTRAINT field_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: field field_field_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field + ADD CONSTRAINT field_field_group_id_fkey FOREIGN KEY (field_group_id) REFERENCES public.field_group(id) ON DELETE CASCADE; + + +-- +-- Name: field_group field_group_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_group + ADD CONSTRAINT field_group_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: field_group field_group_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_group + ADD CONSTRAINT field_group_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: field_option field_option_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_option + ADD CONSTRAINT field_option_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: field_option field_option_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_option + ADD CONSTRAINT field_option_field_id_fkey FOREIGN KEY (field_id) REFERENCES public.field(id) ON DELETE CASCADE; + + +-- +-- Name: field_stage_settings field_stage_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_stage_settings + ADD CONSTRAINT field_stage_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: field_stage_settings field_stage_settings_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_stage_settings + ADD CONSTRAINT field_stage_settings_field_id_fkey FOREIGN KEY (field_id) REFERENCES public.field(id) ON DELETE CASCADE; + + +-- +-- Name: field_stage_settings field_stage_settings_stage_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_stage_settings + ADD CONSTRAINT field_stage_settings_stage_id_fkey FOREIGN KEY (stage_id) REFERENCES public.stage(id) ON DELETE CASCADE; + + +-- +-- Name: field_user_settings field_user_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_user_settings + ADD CONSTRAINT field_user_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: field_user_settings field_user_settings_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_user_settings + ADD CONSTRAINT field_user_settings_field_id_fkey FOREIGN KEY (field_id) REFERENCES public.field(id) ON DELETE CASCADE; + + +-- +-- Name: field_user_settings field_user_settings_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_user_settings + ADD CONSTRAINT field_user_settings_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: field_value field_value_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_value + ADD CONSTRAINT field_value_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: field_value field_value_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_value + ADD CONSTRAINT field_value_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: field_value field_value_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.field_value + ADD CONSTRAINT field_value_field_id_fkey FOREIGN KEY (field_id) REFERENCES public.field(id) ON DELETE CASCADE; + + +-- +-- Name: file_info file_info_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_info + ADD CONSTRAINT file_info_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: file_info file_info_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_info + ADD CONSTRAINT file_info_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE SET NULL; + + +-- +-- Name: file_link file_link_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_link + ADD CONSTRAINT file_link_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: file_link file_link_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.file_link + ADD CONSTRAINT file_link_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: mail_message mail_message_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message + ADD CONSTRAINT mail_message_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mail_message mail_message_contact_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message + ADD CONSTRAINT mail_message_contact_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE SET NULL; + + +-- +-- Name: mail_message_folder mail_message_folder_folder_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message_folder + ADD CONSTRAINT mail_message_folder_folder_id_fkey FOREIGN KEY (folder_id) REFERENCES public.mailbox_folder(id) ON DELETE CASCADE; + + +-- +-- Name: mail_message_folder mail_message_folder_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message_folder + ADD CONSTRAINT mail_message_folder_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.mail_message(id) ON DELETE CASCADE; + + +-- +-- Name: mail_message mail_message_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message + ADD CONSTRAINT mail_message_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: mail_message_payload mail_message_payload_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message_payload + ADD CONSTRAINT mail_message_payload_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mail_message_payload mail_message_payload_message_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mail_message_payload + ADD CONSTRAINT mail_message_payload_message_id_fkey FOREIGN KEY (message_id) REFERENCES public.mail_message(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_accessible_user mailbox_accessible_user_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_accessible_user + ADD CONSTRAINT mailbox_accessible_user_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_accessible_user mailbox_accessible_user_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_accessible_user + ADD CONSTRAINT mailbox_accessible_user_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_accessible_user mailbox_accessible_user_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_accessible_user + ADD CONSTRAINT mailbox_accessible_user_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox mailbox_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox + ADD CONSTRAINT mailbox_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox mailbox_contact_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox + ADD CONSTRAINT mailbox_contact_entity_type_id_fkey FOREIGN KEY (contact_entity_type_id) REFERENCES public.entity_type(id) ON DELETE SET NULL; + + +-- +-- Name: mailbox_folder mailbox_folder_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_folder + ADD CONSTRAINT mailbox_folder_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_folder mailbox_folder_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_folder + ADD CONSTRAINT mailbox_folder_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox mailbox_lead_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox + ADD CONSTRAINT mailbox_lead_board_id_fkey FOREIGN KEY (lead_board_id) REFERENCES public.board(id) ON DELETE SET NULL; + + +-- +-- Name: mailbox mailbox_lead_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox + ADD CONSTRAINT mailbox_lead_entity_type_id_fkey FOREIGN KEY (lead_entity_type_id) REFERENCES public.entity_type(id) ON DELETE SET NULL; + + +-- +-- Name: mailbox mailbox_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox + ADD CONSTRAINT mailbox_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES public.users(id); + + +-- +-- Name: mailbox_settings_gmail mailbox_settings_gmail_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_settings_gmail + ADD CONSTRAINT mailbox_settings_gmail_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_settings_gmail mailbox_settings_gmail_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_settings_gmail + ADD CONSTRAINT mailbox_settings_gmail_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_settings_manual mailbox_settings_manual_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_settings_manual + ADD CONSTRAINT mailbox_settings_manual_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_settings_manual mailbox_settings_manual_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_settings_manual + ADD CONSTRAINT mailbox_settings_manual_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_signature mailbox_signature_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_signature + ADD CONSTRAINT mailbox_signature_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_signature mailbox_signature_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_signature + ADD CONSTRAINT mailbox_signature_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_signature_link mailbox_signature_link_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_signature_link + ADD CONSTRAINT mailbox_signature_link_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_signature_link mailbox_signature_link_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_signature_link + ADD CONSTRAINT mailbox_signature_link_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: mailbox_signature_link mailbox_signature_link_signature_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.mailbox_signature_link + ADD CONSTRAINT mailbox_signature_link_signature_id_fkey FOREIGN KEY (signature_id) REFERENCES public.mailbox_signature(id) ON DELETE CASCADE; + + +-- +-- Name: note note_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.note + ADD CONSTRAINT note_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: note note_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.note + ADD CONSTRAINT note_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: note note_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.note + ADD CONSTRAINT note_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: notification notification_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification + ADD CONSTRAINT notification_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: notification notification_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification + ADD CONSTRAINT notification_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE SET NULL; + + +-- +-- Name: notification notification_from_user_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification + ADD CONSTRAINT notification_from_user_fkey FOREIGN KEY (from_user) REFERENCES public.users(id) ON DELETE SET NULL; + + +-- +-- Name: notification_settings notification_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_settings + ADD CONSTRAINT notification_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: notification_settings notification_settings_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_settings + ADD CONSTRAINT notification_settings_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: notification_type_follow_user notification_type_follow_user_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_type_follow_user + ADD CONSTRAINT notification_type_follow_user_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: notification_type_follow_user notification_type_follow_user_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_type_follow_user + ADD CONSTRAINT notification_type_follow_user_type_id_fkey FOREIGN KEY (type_id) REFERENCES public.notification_type_settings(id) ON DELETE CASCADE; + + +-- +-- Name: notification_type_follow_user notification_type_follow_user_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_type_follow_user + ADD CONSTRAINT notification_type_follow_user_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: notification_type_settings notification_type_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_type_settings + ADD CONSTRAINT notification_type_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: notification_type_settings notification_type_settings_settings_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification_type_settings + ADD CONSTRAINT notification_type_settings_settings_id_fkey FOREIGN KEY (settings_id) REFERENCES public.notification_settings(id) ON DELETE CASCADE; + + +-- +-- Name: notification notification_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.notification + ADD CONSTRAINT notification_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: object_permission object_permission_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.object_permission + ADD CONSTRAINT object_permission_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: order_item order_item_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.order_item + ADD CONSTRAINT order_item_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: order_item order_item_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.order_item + ADD CONSTRAINT order_item_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(id) ON DELETE CASCADE; + + +-- +-- Name: order_item order_item_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.order_item + ADD CONSTRAINT order_item_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product(id); + + +-- +-- Name: order_status order_status_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.order_status + ADD CONSTRAINT order_status_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: orders orders_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: orders orders_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: orders orders_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: orders orders_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: orders orders_status_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_status_id_fkey FOREIGN KEY (status_id) REFERENCES public.order_status(id); + + +-- +-- Name: orders orders_warehouse_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.orders + ADD CONSTRAINT orders_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES public.warehouse(id); + + +-- +-- Name: product product_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product + ADD CONSTRAINT product_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: product_category product_category_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_category + ADD CONSTRAINT product_category_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: product_category product_category_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_category + ADD CONSTRAINT product_category_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: product product_category_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product + ADD CONSTRAINT product_category_id_fkey FOREIGN KEY (category_id) REFERENCES public.product_category(id) ON DELETE SET NULL; + + +-- +-- Name: product_category product_category_parent_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_category + ADD CONSTRAINT product_category_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES public.product_category(id); + + +-- +-- Name: product_category product_category_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_category + ADD CONSTRAINT product_category_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: product product_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product + ADD CONSTRAINT product_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: products_section product_module_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.products_section + ADD CONSTRAINT product_module_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: product_price product_price_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_price + ADD CONSTRAINT product_price_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: product_price product_price_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_price + ADD CONSTRAINT product_price_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product(id) ON DELETE CASCADE; + + +-- +-- Name: product product_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product + ADD CONSTRAINT product_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: products_section_entity_type products_section_entity_type_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.products_section_entity_type + ADD CONSTRAINT products_section_entity_type_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: products_section_entity_type products_section_entity_type_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.products_section_entity_type + ADD CONSTRAINT products_section_entity_type_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: products_section_entity_type products_section_entity_type_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.products_section_entity_type + ADD CONSTRAINT products_section_entity_type_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: ready_made_solution ready_made_solution_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ready_made_solution + ADD CONSTRAINT ready_made_solution_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id); + + +-- +-- Name: ready_made_solution ready_made_solution_industry_code_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ready_made_solution + ADD CONSTRAINT ready_made_solution_industry_code_fkey FOREIGN KEY (industry_code) REFERENCES public.industry(code); + + +-- +-- Name: rental_interval rental_interval_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_interval + ADD CONSTRAINT rental_interval_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: rental_interval rental_interval_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_interval + ADD CONSTRAINT rental_interval_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order rental_order_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order + ADD CONSTRAINT rental_order_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order rental_order_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order + ADD CONSTRAINT rental_order_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: rental_order rental_order_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order + ADD CONSTRAINT rental_order_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order_item rental_order_item_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order_item + ADD CONSTRAINT rental_order_item_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order_item rental_order_item_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order_item + ADD CONSTRAINT rental_order_item_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.rental_order(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order_item rental_order_item_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order_item + ADD CONSTRAINT rental_order_item_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order_period rental_order_period_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order_period + ADD CONSTRAINT rental_order_period_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order_period rental_order_period_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order_period + ADD CONSTRAINT rental_order_period_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.rental_order(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order rental_order_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order + ADD CONSTRAINT rental_order_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: rental_order rental_order_warehouse_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_order + ADD CONSTRAINT rental_order_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES public.warehouse(id); + + +-- +-- Name: rental_event rental_schedule_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_event + ADD CONSTRAINT rental_schedule_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: rental_event rental_schedule_order_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_event + ADD CONSTRAINT rental_schedule_order_item_id_fkey FOREIGN KEY (order_item_id) REFERENCES public.rental_order_item(id) ON DELETE CASCADE; + + +-- +-- Name: rental_event rental_schedule_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_event + ADD CONSTRAINT rental_schedule_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product(id) ON DELETE CASCADE; + + +-- +-- Name: rental_event rental_schedule_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.rental_event + ADD CONSTRAINT rental_schedule_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: reservation reservation_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.reservation + ADD CONSTRAINT reservation_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: reservation reservation_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.reservation + ADD CONSTRAINT reservation_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(id) ON DELETE CASCADE; + + +-- +-- Name: reservation reservation_order_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.reservation + ADD CONSTRAINT reservation_order_item_id_fkey FOREIGN KEY (order_item_id) REFERENCES public.order_item(id) ON DELETE CASCADE; + + +-- +-- Name: reservation reservation_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.reservation + ADD CONSTRAINT reservation_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product(id); + + +-- +-- Name: reservation reservation_warehouse_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.reservation + ADD CONSTRAINT reservation_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES public.warehouse(id); + + +-- +-- Name: sales_plan sales_plan_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sales_plan + ADD CONSTRAINT sales_plan_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: sales_plan sales_plan_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sales_plan + ADD CONSTRAINT sales_plan_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: sales_plan sales_plan_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.sales_plan + ADD CONSTRAINT sales_plan_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: salesforce_settings salesforce_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.salesforce_settings + ADD CONSTRAINT salesforce_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: schedule schedule_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule + ADD CONSTRAINT schedule_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: schedule_appointment schedule_appointment_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_appointment + ADD CONSTRAINT schedule_appointment_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(id) ON DELETE SET NULL; + + +-- +-- Name: schedule_appointment schedule_appointment_performer_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_appointment + ADD CONSTRAINT schedule_appointment_performer_id_fkey FOREIGN KEY (performer_id) REFERENCES public.schedule_performer(id) ON DELETE CASCADE; + + +-- +-- Name: schedule schedule_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule + ADD CONSTRAINT schedule_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE SET NULL; + + +-- +-- Name: schedule_appointment schedule_event_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_appointment + ADD CONSTRAINT schedule_event_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: schedule_appointment schedule_event_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_appointment + ADD CONSTRAINT schedule_event_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE SET NULL; + + +-- +-- Name: schedule_appointment schedule_event_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_appointment + ADD CONSTRAINT schedule_event_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES public.users(id); + + +-- +-- Name: schedule_appointment schedule_event_schedule_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_appointment + ADD CONSTRAINT schedule_event_schedule_id_fkey FOREIGN KEY (schedule_id) REFERENCES public.schedule(id) ON DELETE CASCADE; + + +-- +-- Name: schedule_performer schedule_performer_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_performer + ADD CONSTRAINT schedule_performer_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: schedule_performer schedule_performer_department_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_performer + ADD CONSTRAINT schedule_performer_department_id_fkey FOREIGN KEY (department_id) REFERENCES public.department(id) ON DELETE CASCADE; + + +-- +-- Name: schedule_performer schedule_performer_schedule_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_performer + ADD CONSTRAINT schedule_performer_schedule_id_fkey FOREIGN KEY (schedule_id) REFERENCES public.schedule(id) ON DELETE CASCADE; + + +-- +-- Name: schedule_performer schedule_performer_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule_performer + ADD CONSTRAINT schedule_performer_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: schedule schedule_products_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schedule + ADD CONSTRAINT schedule_products_section_id_fkey FOREIGN KEY (products_section_id) REFERENCES public.products_section(id) ON DELETE SET NULL; + + +-- +-- Name: action_scheduled scheduled_action_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_scheduled + ADD CONSTRAINT scheduled_action_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action_scheduled scheduled_action_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_scheduled + ADD CONSTRAINT scheduled_action_action_id_fkey FOREIGN KEY (action_id) REFERENCES public.action(id) ON DELETE CASCADE; + + +-- +-- Name: action_scheduled scheduled_action_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_scheduled + ADD CONSTRAINT scheduled_action_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: action_scheduled scheduled_action_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_scheduled + ADD CONSTRAINT scheduled_action_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: scheduled_mail_message scheduled_mail_message_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.scheduled_mail_message + ADD CONSTRAINT scheduled_mail_message_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: scheduled_mail_message scheduled_mail_message_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.scheduled_mail_message + ADD CONSTRAINT scheduled_mail_message_action_id_fkey FOREIGN KEY (action_id) REFERENCES public.action(id) ON DELETE SET NULL; + + +-- +-- Name: scheduled_mail_message scheduled_mail_message_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.scheduled_mail_message + ADD CONSTRAINT scheduled_mail_message_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE SET NULL; + + +-- +-- Name: scheduled_mail_message scheduled_mail_message_mailbox_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.scheduled_mail_message + ADD CONSTRAINT scheduled_mail_message_mailbox_id_fkey FOREIGN KEY (mailbox_id) REFERENCES public.mailbox(id) ON DELETE CASCADE; + + +-- +-- Name: scheduled_mail_message scheduled_mail_message_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.scheduled_mail_message + ADD CONSTRAINT scheduled_mail_message_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: shipment shipment_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment + ADD CONSTRAINT shipment_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: shipment shipment_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment + ADD CONSTRAINT shipment_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: shipment_item shipment_item_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment_item + ADD CONSTRAINT shipment_item_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: shipment_item shipment_item_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment_item + ADD CONSTRAINT shipment_item_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product(id); + + +-- +-- Name: shipment_item shipment_item_shipment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment_item + ADD CONSTRAINT shipment_item_shipment_id_fkey FOREIGN KEY (shipment_id) REFERENCES public.shipment(id) ON DELETE CASCADE; + + +-- +-- Name: shipment shipment_order_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment + ADD CONSTRAINT shipment_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(id) ON DELETE CASCADE; + + +-- +-- Name: shipment shipment_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment + ADD CONSTRAINT shipment_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- Name: shipment shipment_status_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment + ADD CONSTRAINT shipment_status_id_fkey FOREIGN KEY (status_id) REFERENCES public.order_status(id); + + +-- +-- Name: shipment shipment_warehouse_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.shipment + ADD CONSTRAINT shipment_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES public.warehouse(id) ON DELETE CASCADE; + + +-- +-- Name: site_form site_form_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form + ADD CONSTRAINT site_form_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_consent site_form_consent_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_consent + ADD CONSTRAINT site_form_consent_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_consent site_form_consent_form_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_consent + ADD CONSTRAINT site_form_consent_form_id_fkey FOREIGN KEY (form_id) REFERENCES public.site_form(id) ON DELETE CASCADE; + + +-- +-- Name: site_form site_form_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form + ADD CONSTRAINT site_form_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: site_form_entity_type site_form_entity_type_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_entity_type + ADD CONSTRAINT site_form_entity_type_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_entity_type site_form_entity_type_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_entity_type + ADD CONSTRAINT site_form_entity_type_board_id_fkey FOREIGN KEY (board_id) REFERENCES public.board(id) ON DELETE SET NULL; + + +-- +-- Name: site_form_entity_type site_form_entity_type_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_entity_type + ADD CONSTRAINT site_form_entity_type_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_entity_type site_form_entity_type_form_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_entity_type + ADD CONSTRAINT site_form_entity_type_form_id_fkey FOREIGN KEY (form_id) REFERENCES public.site_form(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_field site_form_field_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field + ADD CONSTRAINT site_form_field_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_field_entity_field site_form_field_entity_field_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field_entity_field + ADD CONSTRAINT site_form_field_entity_field_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_field_entity_field site_form_field_entity_field_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field_entity_field + ADD CONSTRAINT site_form_field_entity_field_field_id_fkey FOREIGN KEY (field_id) REFERENCES public.field(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_field_entity_field site_form_field_entity_field_form_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field_entity_field + ADD CONSTRAINT site_form_field_entity_field_form_field_id_fkey FOREIGN KEY (form_field_id) REFERENCES public.site_form_field(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_field_entity_name site_form_field_entity_name_entity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field_entity_name + ADD CONSTRAINT site_form_field_entity_name_entity_type_id_fkey FOREIGN KEY (entity_type_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_field_entity_name site_form_field_entity_name_form_field_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field_entity_name + ADD CONSTRAINT site_form_field_entity_name_form_field_id_fkey FOREIGN KEY (form_field_id) REFERENCES public.site_form_field(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_field site_form_field_page_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_field + ADD CONSTRAINT site_form_field_page_id_fkey FOREIGN KEY (page_id) REFERENCES public.site_form_page(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_gratitude site_form_gratitude_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_gratitude + ADD CONSTRAINT site_form_gratitude_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_gratitude site_form_gratitude_form_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_gratitude + ADD CONSTRAINT site_form_gratitude_form_id_fkey FOREIGN KEY (form_id) REFERENCES public.site_form(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_page site_form_page_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_page + ADD CONSTRAINT site_form_page_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: site_form_page site_form_page_form_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form_page + ADD CONSTRAINT site_form_page_form_id_fkey FOREIGN KEY (form_id) REFERENCES public.site_form(id) ON DELETE CASCADE; + + +-- +-- Name: site_form site_form_responsible_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.site_form + ADD CONSTRAINT site_form_responsible_id_fkey FOREIGN KEY (responsible_id) REFERENCES public.users(id) ON DELETE SET NULL; + + +-- +-- Name: stage stage_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.stage + ADD CONSTRAINT stage_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: stage stage_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.stage + ADD CONSTRAINT stage_board_id_fkey FOREIGN KEY (board_id) REFERENCES public.board(id) ON DELETE CASCADE; + + +-- +-- Name: product_stock stock_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_stock + ADD CONSTRAINT stock_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: product_stock stock_product_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_stock + ADD CONSTRAINT stock_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.product(id) ON DELETE CASCADE; + + +-- +-- Name: product_stock stock_warehouse_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.product_stock + ADD CONSTRAINT stock_warehouse_id_fkey FOREIGN KEY (warehouse_id) REFERENCES public.warehouse(id) ON DELETE CASCADE; + + +-- +-- Name: account_subscription subscription_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.account_subscription + ADD CONSTRAINT subscription_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: task_subtask subtask_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_subtask + ADD CONSTRAINT subtask_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: task_subtask subtask_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_subtask + ADD CONSTRAINT subtask_task_id_fkey FOREIGN KEY (task_id) REFERENCES public.task(id) ON DELETE CASCADE; + + +-- +-- Name: task task_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action_task_settings task_action_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_task_settings + ADD CONSTRAINT task_action_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: action_task_settings task_action_settings_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_task_settings + ADD CONSTRAINT task_action_settings_action_id_fkey FOREIGN KEY (action_id) REFERENCES public.action(id) ON DELETE CASCADE; + + +-- +-- Name: action_task_settings task_action_settings_responsible_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.action_task_settings + ADD CONSTRAINT task_action_settings_responsible_user_id_fkey FOREIGN KEY (responsible_user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: task task_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_board_id_fkey FOREIGN KEY (board_id) REFERENCES public.board(id) ON DELETE CASCADE; + + +-- +-- Name: task_comment task_comment_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment + ADD CONSTRAINT task_comment_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: task_comment task_comment_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment + ADD CONSTRAINT task_comment_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: task_comment_like task_comment_like_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment_like + ADD CONSTRAINT task_comment_like_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: task_comment_like task_comment_like_comment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment_like + ADD CONSTRAINT task_comment_like_comment_id_fkey FOREIGN KEY (comment_id) REFERENCES public.task_comment(id) ON DELETE CASCADE; + + +-- +-- Name: task_comment_like task_comment_like_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment_like + ADD CONSTRAINT task_comment_like_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: task_comment task_comment_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_comment + ADD CONSTRAINT task_comment_task_id_fkey FOREIGN KEY (task_id) REFERENCES public.task(id) ON DELETE CASCADE; + + +-- +-- Name: task task_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: task task_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE CASCADE; + + +-- +-- Name: task task_responsible_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_responsible_user_id_fkey FOREIGN KEY (responsible_user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: task_settings task_settings_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task_settings + ADD CONSTRAINT task_settings_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: task task_settings_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_settings_id_fkey FOREIGN KEY (settings_id) REFERENCES public.task_settings(id) ON DELETE SET NULL; + + +-- +-- Name: task task_stage_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.task + ADD CONSTRAINT task_stage_id_fkey FOREIGN KEY (stage_id) REFERENCES public.stage(id) ON DELETE CASCADE; + + +-- +-- Name: activity_type task_type_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.activity_type + ADD CONSTRAINT task_type_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: test_account test_account_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.test_account + ADD CONSTRAINT test_account_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: trigger trigger_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.trigger + ADD CONSTRAINT trigger_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: tutorial_group tutorial_group_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_group + ADD CONSTRAINT tutorial_group_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: tutorial_item tutorial_item_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item + ADD CONSTRAINT tutorial_item_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: tutorial_item tutorial_item_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item + ADD CONSTRAINT tutorial_item_group_id_fkey FOREIGN KEY (group_id) REFERENCES public.tutorial_group(id) ON DELETE CASCADE; + + +-- +-- Name: tutorial_item_product tutorial_item_product_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item_product + ADD CONSTRAINT tutorial_item_product_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: tutorial_item_product tutorial_item_product_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item_product + ADD CONSTRAINT tutorial_item_product_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.tutorial_item(id) ON DELETE CASCADE; + + +-- +-- Name: tutorial_item_user tutorial_item_user_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item_user + ADD CONSTRAINT tutorial_item_user_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.tutorial_item(id) ON DELETE CASCADE; + + +-- +-- Name: tutorial_item_user tutorial_item_user_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tutorial_item_user + ADD CONSTRAINT tutorial_item_user_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_condition user_condition_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_condition + ADD CONSTRAINT user_condition_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: user_condition user_condition_condition_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_condition + ADD CONSTRAINT user_condition_condition_id_fkey FOREIGN KEY (condition_id) REFERENCES public.condition(id) ON DELETE CASCADE; + + +-- +-- Name: user_condition user_condition_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_condition + ADD CONSTRAINT user_condition_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_object_permission user_object_permission_object_permission_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_object_permission + ADD CONSTRAINT user_object_permission_object_permission_id_fkey FOREIGN KEY (object_permission_id) REFERENCES public.object_permission(id) ON DELETE CASCADE; + + +-- +-- Name: user_object_permission user_object_permission_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_object_permission + ADD CONSTRAINT user_object_permission_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_profile user_profile_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_profile + ADD CONSTRAINT user_profile_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: user_profile user_profile_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_profile + ADD CONSTRAINT user_profile_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: users users_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: users users_department_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_department_id_fkey FOREIGN KEY (department_id) REFERENCES public.department(id); + + +-- +-- Name: voximplant_account voximplant_account_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_account + ADD CONSTRAINT voximplant_account_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_call voximplant_call_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_call + ADD CONSTRAINT voximplant_call_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_call voximplant_call_entity_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_call + ADD CONSTRAINT voximplant_call_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES public.entity(id) ON DELETE SET NULL; + + +-- +-- Name: voximplant_call voximplant_call_number_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_call + ADD CONSTRAINT voximplant_call_number_id_fkey FOREIGN KEY (number_id) REFERENCES public.voximplant_number(id) ON DELETE SET NULL; + + +-- +-- Name: voximplant_call voximplant_call_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_call + ADD CONSTRAINT voximplant_call_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_number voximplant_number_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_number + ADD CONSTRAINT voximplant_number_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_number_user voximplant_number_user_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_number_user + ADD CONSTRAINT voximplant_number_user_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_number_user voximplant_number_user_number_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_number_user + ADD CONSTRAINT voximplant_number_user_number_id_fkey FOREIGN KEY (number_id) REFERENCES public.voximplant_number(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_number_user voximplant_number_user_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_number_user + ADD CONSTRAINT voximplant_number_user_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_entity voximplant_scenario_entity_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_entity + ADD CONSTRAINT voximplant_scenario_entity_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_entity voximplant_scenario_entity_board_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_entity + ADD CONSTRAINT voximplant_scenario_entity_board_id_fkey FOREIGN KEY (board_id) REFERENCES public.board(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_entity voximplant_scenario_entity_contact_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_entity + ADD CONSTRAINT voximplant_scenario_entity_contact_id_fkey FOREIGN KEY (contact_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_entity voximplant_scenario_entity_deal_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_entity + ADD CONSTRAINT voximplant_scenario_entity_deal_id_fkey FOREIGN KEY (deal_id) REFERENCES public.entity_type(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_entity voximplant_scenario_entity_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_entity + ADD CONSTRAINT voximplant_scenario_entity_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_note voximplant_scenario_note_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_note + ADD CONSTRAINT voximplant_scenario_note_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_task voximplant_scenario_task_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_task + ADD CONSTRAINT voximplant_scenario_task_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_task voximplant_scenario_task_activity_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_task + ADD CONSTRAINT voximplant_scenario_task_activity_owner_id_fkey FOREIGN KEY (activity_owner_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_task voximplant_scenario_task_activity_type_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_task + ADD CONSTRAINT voximplant_scenario_task_activity_type_id_fkey FOREIGN KEY (activity_type_id) REFERENCES public.activity_type(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_scenario_task voximplant_scenario_task_task_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_scenario_task + ADD CONSTRAINT voximplant_scenario_task_task_owner_id_fkey FOREIGN KEY (task_owner_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_user voximplant_user_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_user + ADD CONSTRAINT voximplant_user_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: voximplant_user voximplant_user_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.voximplant_user + ADD CONSTRAINT voximplant_user_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: warehouse warehouse_account_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.warehouse + ADD CONSTRAINT warehouse_account_id_fkey FOREIGN KEY (account_id) REFERENCES public.account(id) ON DELETE CASCADE; + + +-- +-- Name: warehouse warehouse_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.warehouse + ADD CONSTRAINT warehouse_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: warehouse warehouse_section_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.warehouse + ADD CONSTRAINT warehouse_section_id_fkey FOREIGN KEY (section_id) REFERENCES public.products_section(id) ON DELETE CASCADE; + + +-- +-- PostgreSQL database dump complete +-- + diff --git a/backend/src/database/common/index.ts b/backend/src/database/common/index.ts new file mode 100644 index 0000000..04bca77 --- /dev/null +++ b/backend/src/database/common/index.ts @@ -0,0 +1 @@ +export * from './utils'; diff --git a/backend/src/database/common/utils/index.ts b/backend/src/database/common/utils/index.ts new file mode 100644 index 0000000..96aebfb --- /dev/null +++ b/backend/src/database/common/utils/index.ts @@ -0,0 +1 @@ +export * from './tsquery.util'; diff --git a/backend/src/database/common/utils/tsquery.util.ts b/backend/src/database/common/utils/tsquery.util.ts new file mode 100644 index 0000000..141139b --- /dev/null +++ b/backend/src/database/common/utils/tsquery.util.ts @@ -0,0 +1,28 @@ +export const buildSearchParams = (input: string): string => { + if (!input) return undefined; + + const phoneRegex = /(?:\+?\d[\d\s().-]{4,}\d)/g; + + // Extract and normalize phone numbers + const phoneNumbers = Array.from(input.matchAll(phoneRegex), (match) => match[0].replace(/\D/g, '')); + + // Remove phones from text + const cleanedInput = input.replace(phoneRegex, ' '); + + // Split into tokens + const tokens = cleanedInput + .trim() + .replace(/[:&`'!()*]+/g, '') // safely remove tsquery special symbols + .split(/\s+/) + .map( + (token) => + /^[\w@.]+$/.test(token) + ? token.toLowerCase() // keep as-is if safe (email/domain/etc.) + : token.toLowerCase().replace(/^[^\p{L}\p{N}@]+|[^\p{L}\p{N}@]+$/gu, ''), // trim edges + ) + .filter((token) => token.length >= 3); // remove short words + + const allTerms = [...tokens, ...phoneNumbers, ...phoneNumbers.map((n) => '+' + n)]; + + return allTerms.map((t) => `${t}:*`).join(' | '); +}; diff --git a/backend/src/database/config/database.config.ts b/backend/src/database/config/database.config.ts new file mode 100644 index 0000000..2c00786 --- /dev/null +++ b/backend/src/database/config/database.config.ts @@ -0,0 +1,37 @@ +import { registerAs } from '@nestjs/config'; +import { LoggerOptions, LogLevel } from 'typeorm'; + +export interface DatabaseConfig { + host: string; + port: number; + username: string; + password: string; + database: string; + logging: LoggerOptions; + cache: { type: string | undefined; duration: number | undefined }; +} + +const parseQueryLogging = (value: string | null | undefined): LoggerOptions | undefined => { + if (!value) return undefined; + if (value === 'true') return true; + if (value === 'false') return false; + if (value === 'all') return 'all'; + + return value.split(',').map((v) => v as LogLevel); +}; + +export default registerAs( + 'database', + (): DatabaseConfig => ({ + host: process.env.POSTGRES_HOST, + port: +process.env.POSTGRES_PORT, + username: process.env.POSTGRES_USER, + password: process.env.POSTGRES_PASSWORD, + database: process.env.POSTGRES_DB, + logging: parseQueryLogging(process.env.POSTGRES_QUERY_LOGGING), + cache: { + type: process.env.TYPEORM_CACHE_TYPE, + duration: +process.env.TYPEORM_CACHE_DURATION, + }, + }), +); diff --git a/backend/src/database/config/index.ts b/backend/src/database/config/index.ts new file mode 100644 index 0000000..64341a6 --- /dev/null +++ b/backend/src/database/config/index.ts @@ -0,0 +1 @@ +export * from './database.config'; diff --git a/backend/src/database/database.module.ts b/backend/src/database/database.module.ts new file mode 100644 index 0000000..7dfa9ca --- /dev/null +++ b/backend/src/database/database.module.ts @@ -0,0 +1,40 @@ +import { Global, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; + +import databaseConfig, { DatabaseConfig } from './config/database.config'; +import { SequenceIdService } from './services'; +import { NestjsLogger } from './nestjs-logger'; + +@Global() +@Module({ + imports: [ + TypeOrmModule.forRootAsync({ + imports: [ConfigModule.forFeature(databaseConfig)], + inject: [ConfigService], + useFactory: (configService: ConfigService) => { + const config = configService.get('database'); + + return { + type: 'postgres', + host: config.host, + port: config.port, + username: config.username, + password: config.password, + database: config.database, + namingStrategy: new SnakeNamingStrategy(), + entities: [__dirname + '/../**/Model/**/*.{js,ts}', __dirname + '/../**/entities/*.entity.{js,ts}'], + maxQueryExecutionTime: 1000, + logging: config.logging, + logger: config.logging ? new NestjsLogger() : undefined, + synchronize: false, + migrationsRun: false, + }; + }, + }), + ], + providers: [SequenceIdService], + exports: [SequenceIdService], +}) +export class DatabaseModule {} diff --git a/backend/src/database/index.ts b/backend/src/database/index.ts new file mode 100644 index 0000000..3902db8 --- /dev/null +++ b/backend/src/database/index.ts @@ -0,0 +1,6 @@ +export * from './common'; +export * from './config'; +export * from './database.module'; +export * from './nestjs-logger'; +export * from './services'; +export * from './typeorm-migration.config'; diff --git a/backend/src/database/migrations/1737731260000-AddLoginSecurityFields.ts b/backend/src/database/migrations/1737731260000-AddLoginSecurityFields.ts new file mode 100644 index 0000000..ffb4b36 --- /dev/null +++ b/backend/src/database/migrations/1737731260000-AddLoginSecurityFields.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddLoginSecurityFields1737731260000 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE users ADD COLUMN login_attempts INTEGER NOT NULL DEFAULT 0; + ALTER TABLE users ADD COLUMN lock_until TIMESTAMP WITH TIME ZONE NULL; + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE users DROP COLUMN lock_until; + ALTER TABLE users DROP COLUMN login_attempts; + `); + } +} diff --git a/backend/src/database/migrations/1744637701399-AddMailboxSettingsImapflow.ts b/backend/src/database/migrations/1744637701399-AddMailboxSettingsImapflow.ts new file mode 100644 index 0000000..a8f36b8 --- /dev/null +++ b/backend/src/database/migrations/1744637701399-AddMailboxSettingsImapflow.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailboxSettingsImapflow1744637701399 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table mailbox_settings_imapflow ( + mailbox_id integer, + account_id integer not null, + password text not null, + imap_server text not null, + imap_port smallint not null, + imap_secure boolean not null, + smtp_server text not null, + smtp_port smallint not null, + smtp_secure boolean not null, + sync_info jsonb, + primary key (mailbox_id), + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/1744982861922-AlterMailboxFolder.ts b/backend/src/database/migrations/1744982861922-AlterMailboxFolder.ts new file mode 100644 index 0000000..5072f86 --- /dev/null +++ b/backend/src/database/migrations/1744982861922-AlterMailboxFolder.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailboxFolder1744982861922 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_folder + add column parent_id integer, + add foreign key (parent_id) references mailbox_folder(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/1744990391092-AlterMailboxFolder.ts b/backend/src/database/migrations/1744990391092-AlterMailboxFolder.ts new file mode 100644 index 0000000..b74f9b7 --- /dev/null +++ b/backend/src/database/migrations/1744990391092-AlterMailboxFolder.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailboxFolder1744990391092 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_folder add column if not exists uid_validity integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/1745220421131-AlterMailboxFolder.ts b/backend/src/database/migrations/1745220421131-AlterMailboxFolder.ts new file mode 100644 index 0000000..d5023a8 --- /dev/null +++ b/backend/src/database/migrations/1745220421131-AlterMailboxFolder.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailboxFolder1745220421131 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_folder add column if not exists uid_validity integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/1745245997361-AlterUser.ts b/backend/src/database/migrations/1745245997361-AlterUser.ts new file mode 100644 index 0000000..2e9f07e --- /dev/null +++ b/backend/src/database/migrations/1745245997361-AlterUser.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterUser1745245997361 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users add column is_platform_admin boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1667672722508-AddAccount.ts b/backend/src/database/migrations/archive/1667672722508-AddAccount.ts new file mode 100644 index 0000000..0ad257e --- /dev/null +++ b/backend/src/database/migrations/archive/1667672722508-AddAccount.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAccount1667672722508 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table account + ( + id integer not null + constraint accounts_pkey primary key, + company_name varchar(255) not null, + subdomain varchar(255) not null + constraint accounts_subdomain_key unique, + created_at timestamp not null + ); + `); + queryRunner.query(`create sequence account_id_seq as integer minvalue 11023201;`); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1667743637921-AddUser.ts b/backend/src/database/migrations/archive/1667743637921-AddUser.ts new file mode 100644 index 0000000..0bc159e --- /dev/null +++ b/backend/src/database/migrations/archive/1667743637921-AddUser.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUser1667743637921 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table users + ( + id integer not null + primary key, + name varchar(255) not null, + email varchar(254) not null + unique, + password varchar(100) not null, + created_at timestamp not null + ); + `); + queryRunner.query(`create sequence user_id_seq as integer minvalue 12022001;`); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1667755763616-AddUserAccount.ts b/backend/src/database/migrations/archive/1667755763616-AddUserAccount.ts new file mode 100644 index 0000000..3bb8980 --- /dev/null +++ b/backend/src/database/migrations/archive/1667755763616-AddUserAccount.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUserAccount1667755763616 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table user_account + ( + id integer not null + primary key, + user_id integer not null + references users on delete cascade, + account_id integer not null + references account on delete cascade, + role varchar(100) not null, + created_at timestamp not null, + constraint user_account__user_id__account_id__uniq + unique (user_id, account_id) + ); + `); + queryRunner.query(`create sequence user_account_id_seq as integer minvalue 12022001;`); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668609239887-AddEntityType.ts b/backend/src/database/migrations/archive/1668609239887-AddEntityType.ts new file mode 100644 index 0000000..9ce8022 --- /dev/null +++ b/backend/src/database/migrations/archive/1668609239887-AddEntityType.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEntityType1668609239887 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table entity_type + ( + id integer not null + primary key, + name varchar(255) not null, + card_view varchar(50) not null, + section_name varchar(100) not null, + section_view varchar(50) not null, + section_icon varchar(50) not null, + account_id integer not null + references account on delete cascade, + created_at timestamp not null + ); + `); + queryRunner.query(`create sequence entity_type_id_seq as integer minvalue 13022001;`); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668679480636-AddBoard.ts b/backend/src/database/migrations/archive/1668679480636-AddBoard.ts new file mode 100644 index 0000000..33e1fd9 --- /dev/null +++ b/backend/src/database/migrations/archive/1668679480636-AddBoard.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddBoard1668679480636 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table board + ( + id integer not null + primary key, + name varchar(100) not null, + type varchar(50) not null, + record_id bigint not null, + account_id integer not null + references account on delete cascade, + created_at timestamp not null + ); + `); + queryRunner.query(`create sequence board_id_seq as integer minvalue 14022001;`); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668679994594-Note.ts b/backend/src/database/migrations/archive/1668679994594-Note.ts new file mode 100644 index 0000000..fce5976 --- /dev/null +++ b/backend/src/database/migrations/archive/1668679994594-Note.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Note1668679994594 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table note ( + id bigint, + created_at timestamp without time zone not null, + text character varying not null, + entity_id bigint not null, + created_by integer not null, + account_id integer not null, + primary key (id), + foreign key (created_by) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + create sequence note_id_seq as bigint minvalue 22022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668680006964-AddStage.ts b/backend/src/database/migrations/archive/1668680006964-AddStage.ts new file mode 100644 index 0000000..e06f1e5 --- /dev/null +++ b/backend/src/database/migrations/archive/1668680006964-AddStage.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddStage1668680006964 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table stage + ( + id integer not null + primary key, + name varchar(100) not null, + color varchar(50) not null, + code varchar(50), + is_system boolean not null, + sort_order smallint not null, + board_id integer not null + references board on delete cascade, + account_id integer not null + references account on delete cascade, + created_at timestamp not null + ); + `); + queryRunner.query(`create sequence stage_id_seq as integer minvalue 15022001;`); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668693976313-FeedsView.ts b/backend/src/database/migrations/archive/1668693976313-FeedsView.ts new file mode 100644 index 0000000..5af0f30 --- /dev/null +++ b/backend/src/database/migrations/archive/1668693976313-FeedsView.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FeedsView1668693976313 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create or replace view feed_items (id, created_at, entity_id, type) as + select id, created_at, entity_id, 'note' as type from note + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668759141152-AddEntity.ts b/backend/src/database/migrations/archive/1668759141152-AddEntity.ts new file mode 100644 index 0000000..99d56d7 --- /dev/null +++ b/backend/src/database/migrations/archive/1668759141152-AddEntity.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEntity1668759141152 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table entity + ( + id bigint not null + primary key, + name varchar(255) not null, + entity_type_id integer not null + references entity_type on delete cascade, + responsible_user_id integer not null + references users on delete cascade, + stage_id integer + references stage on delete cascade, + created_by integer not null + references users on delete cascade, + account_id integer not null + references account on delete cascade, + created_at timestamp without time zone not null + ); + create sequence entity_id_seq as integer minvalue 14022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668769413399-AddFeedItemsSequence.ts b/backend/src/database/migrations/archive/1668769413399-AddFeedItemsSequence.ts new file mode 100644 index 0000000..1e457a1 --- /dev/null +++ b/backend/src/database/migrations/archive/1668769413399-AddFeedItemsSequence.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFeedItemsSequence1668769413399 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop sequence note_id_seq; + create sequence feed_item_id_seq as bigint minvalue 22022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668770161278-AddTaskType.ts b/backend/src/database/migrations/archive/1668770161278-AddTaskType.ts new file mode 100644 index 0000000..481ed3a --- /dev/null +++ b/backend/src/database/migrations/archive/1668770161278-AddTaskType.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskType1668770161278 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table task_type ( + id integer, + created_at timestamp without time zone not null, + account_id integer not null, + name character varying(128) not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence task_type_id_seq as bigint minvalue 25022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1668957885188-AddSortOrderToBoard.ts b/backend/src/database/migrations/archive/1668957885188-AddSortOrderToBoard.ts new file mode 100644 index 0000000..c5ff510 --- /dev/null +++ b/backend/src/database/migrations/archive/1668957885188-AddSortOrderToBoard.ts @@ -0,0 +1,11 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSortOrderToBoard1668957885188 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query('alter table board add column sort_order smallint not null default 0;'); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669035936818-AddActivity.ts b/backend/src/database/migrations/archive/1669035936818-AddActivity.ts new file mode 100644 index 0000000..33624d2 --- /dev/null +++ b/backend/src/database/migrations/archive/1669035936818-AddActivity.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddActivity1669035936818 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table activity ( + id bigint, + created_at timestamp without time zone not null, + account_id integer not null, + created_by integer not null, + responsible_user_id integer not null, + text character varying not null, + start_date timestamp without time zone not null, + end_date timestamp without time zone not null, + is_resolved boolean not null, + result character varying, + task_type_id integer not null, + entity_id bigint not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (created_by) references users(id) on delete cascade, + foreign key (responsible_user_id) references users(id) on delete cascade, + foreign key (entity_id) references entity(id), + foreign key (task_type_id) references task_type(id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669035943607-AddTask.ts b/backend/src/database/migrations/archive/1669035943607-AddTask.ts new file mode 100644 index 0000000..5ca862c --- /dev/null +++ b/backend/src/database/migrations/archive/1669035943607-AddTask.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTask1669035943607 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table task ( + id bigint, + created_at timestamp without time zone not null, + account_id integer not null, + created_by integer not null, + responsible_user_id integer not null, + text character varying not null, + start_date timestamp without time zone not null, + end_date timestamp without time zone not null, + is_resolved boolean not null, + result character varying, + title character varying not null, + entity_id bigint, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (created_by) references users(id) on delete cascade, + foreign key (responsible_user_id) references users(id) on delete cascade, + foreign key (entity_id) references entity(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669126197933-AddFieldGroup.ts b/backend/src/database/migrations/archive/1669126197933-AddFieldGroup.ts new file mode 100644 index 0000000..69dad3c --- /dev/null +++ b/backend/src/database/migrations/archive/1669126197933-AddFieldGroup.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFieldGroup1669126197933 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table field_group + ( + id integer not null + primary key, + name varchar(100) not null, + sort_order smallint not null, + entity_type_id integer not null + references entity_type (id) on delete cascade, + account_id integer not null + references account (id) on delete cascade, + created_at timestamp without time zone not null + ); + create sequence field_group_id_seq as integer minvalue 41022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669127718002-AddField.ts b/backend/src/database/migrations/archive/1669127718002-AddField.ts new file mode 100644 index 0000000..d95ceb9 --- /dev/null +++ b/backend/src/database/migrations/archive/1669127718002-AddField.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddField1669127718002 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table field + ( + id integer not null + primary key, + name varchar(100) not null, + type varchar(50) not null, + sort_order smallint not null, + entity_type_id integer not null + references entity_type (id) on delete cascade, + field_group_id integer not null + references field_group (id) on delete cascade, + account_id integer not null + references account (id) on delete cascade, + created_at timestamp without time zone not null + ); + create sequence field_id_seq as integer minvalue 42022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669130700529-AddTaskToFeed.ts b/backend/src/database/migrations/archive/1669130700529-AddTaskToFeed.ts new file mode 100644 index 0000000..52c3cc4 --- /dev/null +++ b/backend/src/database/migrations/archive/1669130700529-AddTaskToFeed.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskToFeed1669130700529 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create or replace view feed_items (id, created_at, entity_id, type) as + select id, created_at, entity_id, 'note' as type from note + union + select id, created_at, entity_id, 'task' as type from task + union + select id, created_at, entity_id, 'activity' as type from activity + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669192730867-AddNoteToEntityFK.ts b/backend/src/database/migrations/archive/1669192730867-AddNoteToEntityFK.ts new file mode 100644 index 0000000..31bf313 --- /dev/null +++ b/backend/src/database/migrations/archive/1669192730867-AddNoteToEntityFK.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddNoteToEntityFK1669192730867 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table note add foreign key (entity_id) references entity(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669197308978-AddFieldOption.ts b/backend/src/database/migrations/archive/1669197308978-AddFieldOption.ts new file mode 100644 index 0000000..bed26dc --- /dev/null +++ b/backend/src/database/migrations/archive/1669197308978-AddFieldOption.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFieldOption1669197308978 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table field_option + ( + id integer not null + primary key, + label varchar(255) not null, + sort_order smallint not null, + field_id integer not null + references field (id) on delete cascade, + account_id integer not null + references account (id) on delete cascade, + created_at timestamp without time zone not null + ); + create sequence field_option_id_seq as integer minvalue 43022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669210346088-AddTasksView.ts b/backend/src/database/migrations/archive/1669210346088-AddTasksView.ts new file mode 100644 index 0000000..b144499 --- /dev/null +++ b/backend/src/database/migrations/archive/1669210346088-AddTasksView.ts @@ -0,0 +1,18 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTasksView1669210346088 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create or replace view all_tasks (id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, task_type_id, title, type) as + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, task_type_id, null as title, 'activity' as type from activity + union + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, null as task_type_id, title, 'task' as type from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669279080953-FixActivityConstraints.ts b/backend/src/database/migrations/archive/1669279080953-FixActivityConstraints.ts new file mode 100644 index 0000000..8ac0d9e --- /dev/null +++ b/backend/src/database/migrations/archive/1669279080953-FixActivityConstraints.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixActivityConstraints1669279080953 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table activity drop constraint activity_entity_id_fkey; + alter table activity + add foreign key (entity_id) references entity (id) + on delete cascade; + + alter table activity drop constraint activity_task_type_id_fkey; + alter table activity + add foreign key (task_type_id) references task_type (id) + on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669370353662-SplitUserName.ts b/backend/src/database/migrations/archive/1669370353662-SplitUserName.ts new file mode 100644 index 0000000..f04e6c5 --- /dev/null +++ b/backend/src/database/migrations/archive/1669370353662-SplitUserName.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SplitUserName1669370353662 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users + add column first_name character varying(255), + add column last_name character varying(255); + + update users set first_name = split_part(name, ' ', 1), last_name = split_part(name, ' ', 2); + + alter table users + alter column first_name set not null, + alter column last_name set not null; + + alter table users drop column name; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669372200327-AddUserPhone.ts b/backend/src/database/migrations/archive/1669372200327-AddUserPhone.ts new file mode 100644 index 0000000..cee870e --- /dev/null +++ b/backend/src/database/migrations/archive/1669372200327-AddUserPhone.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUserPhone1669372200327 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users + add column phone character varying(32); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669535731810-UserProfile.ts b/backend/src/database/migrations/archive/1669535731810-UserProfile.ts new file mode 100644 index 0000000..476c8a7 --- /dev/null +++ b/backend/src/database/migrations/archive/1669535731810-UserProfile.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UserProfile1669535731810 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table user_profile ( + created_at timestamp without time zone not null, + user_id integer not null, + birth_date timestamp without time zone, + employment_date timestamp without time zone, + account_id integer not null, + primary key (user_id), + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669545773479-EntityTypeLink.ts b/backend/src/database/migrations/archive/1669545773479-EntityTypeLink.ts new file mode 100644 index 0000000..01c1f25 --- /dev/null +++ b/backend/src/database/migrations/archive/1669545773479-EntityTypeLink.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityTypeLink1669545773479 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence entity_type_link_id_seq as bigint; + + create table entity_type_link ( + id bigint default nextval('entity_type_link_id_seq'::regclass), + source_id integer not null, + target_id integer not null, + sort_order smallint not null, + account_id integer not null, + primary key (id), + foreign key (source_id) references entity_type(id) on delete cascade, + foreign key (target_id) references entity_type(id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669565827895-EntityLink.ts b/backend/src/database/migrations/archive/1669565827895-EntityLink.ts new file mode 100644 index 0000000..de0f951 --- /dev/null +++ b/backend/src/database/migrations/archive/1669565827895-EntityLink.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityLink1669565827895 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence entity_link_id_seq as bigint; + + create table entity_link ( + id bigint, + source_id integer not null, + target_id integer not null, + sort_order smallint not null, + back_link_id bigint, + account_id integer not null, + primary key (id), + foreign key (source_id) references entity(id) on delete cascade, + foreign key (target_id) references entity(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade, + foreign key (back_link_id) references entity_link(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669629567395-RemoveUserAccount.ts b/backend/src/database/migrations/archive/1669629567395-RemoveUserAccount.ts new file mode 100644 index 0000000..5ef4f43 --- /dev/null +++ b/backend/src/database/migrations/archive/1669629567395-RemoveUserAccount.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveUserAccount1669629567395 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users + add column account_id integer, + add column role character varying(100), + add column avatar_url character varying(256), + add foreign key (account_id) references account(id) on delete cascade; + + update users set (account_id, role) = + (select account_id, role from user_account where user_account.user_id = users.id); + + alter table users + alter column account_id set not null, + alter column role set not null; + + alter table users + alter column last_name drop not null; + + drop table user_account; + drop sequence user_account_id_seq; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669706986248-AddFeature.ts b/backend/src/database/migrations/archive/1669706986248-AddFeature.ts new file mode 100644 index 0000000..8c5744e --- /dev/null +++ b/backend/src/database/migrations/archive/1669706986248-AddFeature.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFeature1669706986248 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table feature ( + id smallint generated by default as identity, + name character varying not null, + code character varying not null, + is_enabled boolean not null, + primary key (id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669707814502-AddEntityTypeToFeature.ts b/backend/src/database/migrations/archive/1669707814502-AddEntityTypeToFeature.ts new file mode 100644 index 0000000..3fc0fe6 --- /dev/null +++ b/backend/src/database/migrations/archive/1669707814502-AddEntityTypeToFeature.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEntityTypeToFeature1669707814502 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table entity_type_feature ( + entity_type_id integer not null, + feature_id smallint not null, + primary key (entity_type_id, feature_id), + foreign key (entity_type_id) references entity_type(id) on delete cascade, + foreign key (feature_id) references feature(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669711256398-AddDefaultFeatures.ts b/backend/src/database/migrations/archive/1669711256398-AddDefaultFeatures.ts new file mode 100644 index 0000000..68177d7 --- /dev/null +++ b/backend/src/database/migrations/archive/1669711256398-AddDefaultFeatures.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDefaultFeatures1669711256398 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into feature(name, code, is_enabled) values('Activities', 'activities', true); + insert into feature(name, code, is_enabled) values('Tasks', 'tasks', true); + insert into feature(name, code, is_enabled) values('Comments', 'comments', false); + insert into feature(name, code, is_enabled) values('Chat', 'chat', false); + insert into feature(name, code, is_enabled) values('File storage', 'saveFiles', false); + insert into feature(name, code, is_enabled) values('File storage disk', 'diskForFiles', false); + insert into feature(name, code, is_enabled) values('Avatar', 'avatar', false); + insert into feature(name, code, is_enabled) values('Photo/pictures (several)', 'photos', false); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669732421801-AddFieldValue.ts b/backend/src/database/migrations/archive/1669732421801-AddFieldValue.ts new file mode 100644 index 0000000..32eaa12 --- /dev/null +++ b/backend/src/database/migrations/archive/1669732421801-AddFieldValue.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFieldValue1669732421801 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table field_value + ( + id bigint not null, + field_id integer not null, + payload jsonb not null, + entity_id bigint not null, + account_id integer not null, + primary key (id), + foreign key (field_id) references field (id) on delete cascade, + foreign key (entity_id) references entity (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade, + constraint field_id__entity_id__uniq + unique (field_id, entity_id) + ); + create sequence field_value_id_seq as bigint minvalue 44022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669799906268-FixEntityLinkColumns.ts b/backend/src/database/migrations/archive/1669799906268-FixEntityLinkColumns.ts new file mode 100644 index 0000000..2635ce8 --- /dev/null +++ b/backend/src/database/migrations/archive/1669799906268-FixEntityLinkColumns.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixEntityLinkColumns1669799906268 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + queryRunner.query(` + alter table entity_link + alter column source_id type bigint using source_id::bigint, + alter column target_id type bigint using target_id::bigint + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669880194355-ChangeFeatureName_Notes.ts b/backend/src/database/migrations/archive/1669880194355-ChangeFeatureName_Notes.ts new file mode 100644 index 0000000..e955d72 --- /dev/null +++ b/backend/src/database/migrations/archive/1669880194355-ChangeFeatureName_Notes.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeFeatureNameNotes1669880194355 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update feature set name = 'Notes', code = 'notes', is_enabled = true where code = 'comments'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669966161211-BigintToInteger.ts b/backend/src/database/migrations/archive/1669966161211-BigintToInteger.ts new file mode 100644 index 0000000..4994fb0 --- /dev/null +++ b/backend/src/database/migrations/archive/1669966161211-BigintToInteger.ts @@ -0,0 +1,43 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class BigintToInteger1669966161211 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view all_tasks; + drop view feed_items; + + alter sequence feed_item_id_seq as integer; + alter sequence task_type_id_seq as integer; + alter sequence entity_link_id_seq as integer; + alter sequence entity_type_link_id_seq as integer; + alter sequence field_value_id_seq as integer; + + alter table activity alter column id type integer; + alter table entity alter column id type integer; + alter table entity_link alter column id type integer; + alter table entity_type_link alter column id type integer; + alter table field_value alter column id type integer; + alter table note alter column id type integer; + alter table task alter column id type integer; + + create or replace view all_tasks (id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, task_type_id, title, type) as + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, task_type_id, null as title, 'activity' as type from activity + union + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, null as task_type_id, title, 'task' as type from task + order by id; + + create or replace view feed_items (id, created_at, entity_id, type) as + select id, created_at, entity_id, 'note' as type from note + union + select id, created_at, entity_id, 'task' as type from task + union + select id, created_at, entity_id, 'activity' as type from activity + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669973222725-ForeignKeyToInteger.ts b/backend/src/database/migrations/archive/1669973222725-ForeignKeyToInteger.ts new file mode 100644 index 0000000..b9925a0 --- /dev/null +++ b/backend/src/database/migrations/archive/1669973222725-ForeignKeyToInteger.ts @@ -0,0 +1,38 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ForeignKeyToInteger1669973222725 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view all_tasks; + drop view feed_items; + + alter table activity alter column entity_id type integer; + alter table board alter column record_id type integer; + alter table entity_link alter column source_id type integer; + alter table entity_link alter column target_id type integer; + alter table entity_link alter column back_link_id type integer; + alter table field_value alter column entity_id type integer; + alter table note alter column entity_id type integer; + alter table task alter column entity_id type integer; + + create or replace view all_tasks (id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, task_type_id, title, type) as + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, task_type_id, null as title, 'activity' as type from activity + union + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, null as task_type_id, title, 'task' as type from task + order by id; + + create or replace view feed_items (id, created_at, entity_id, type) as + select id, created_at, entity_id, 'note' as type from note + union + select id, created_at, entity_id, 'task' as type from task + union + select id, created_at, entity_id, 'activity' as type from activity + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1669991106433-AddFieldTypeColumnToFieldValue.ts b/backend/src/database/migrations/archive/1669991106433-AddFieldTypeColumnToFieldValue.ts new file mode 100644 index 0000000..b6cc8a9 --- /dev/null +++ b/backend/src/database/migrations/archive/1669991106433-AddFieldTypeColumnToFieldValue.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFieldTypeColumnToFieldValue1669991106433 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field_value + add column field_type varchar(50); + + update field_value + set (field_type) = (select type from field where field.id = field_value.field_id); + + alter table field_value + alter column field_type set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1670576080218-AddExternalEntity.ts b/backend/src/database/migrations/archive/1670576080218-AddExternalEntity.ts new file mode 100644 index 0000000..53acce2 --- /dev/null +++ b/backend/src/database/migrations/archive/1670576080218-AddExternalEntity.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddExternalEntity1670576080218 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists external_entity_id_seq as bigint minvalue 30022001; + + create table external_entity ( + id integer DEFAULT nextval('external_entity_id_seq'::regclass), + entity_id integer NOT NULL, + url character varying NOT NULL, + account_id integer NOT NULL, + created_at timestamp without time zone NOT NULL, + primary key (id), + foreign key (entity_id) references entity(id), + foreign key (account_id) references account(id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1670840721696-ExternalSystem.ts b/backend/src/database/migrations/archive/1670840721696-ExternalSystem.ts new file mode 100644 index 0000000..f9959f9 --- /dev/null +++ b/backend/src/database/migrations/archive/1670840721696-ExternalSystem.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ExternalSystem1670840721696 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table external_system ( + id smallint generated by default as identity, + name character varying not null, + code character varying not null, + url_template character varying not null, + primary key (id) + ); + + alter table external_entity + add column system smallint, + add foreign key (system) references external_system(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1670846440118-AddExternalSystems.ts b/backend/src/database/migrations/archive/1670846440118-AddExternalSystems.ts new file mode 100644 index 0000000..201b2f3 --- /dev/null +++ b/backend/src/database/migrations/archive/1670846440118-AddExternalSystems.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddExternalSystems1670846440118 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into external_system(name, code, url_template) values('SalesForce', 'salesforce', 'salesforce.com'); + insert into external_system(name, code, url_template) values('LinkedIn', 'linkedin', 'www.linkedin.com'); + insert into external_system(name, code, url_template) values('Facebook', 'facebook', 'facebook.com'); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1670936878487-AddFileInfo.ts b/backend/src/database/migrations/archive/1670936878487-AddFileInfo.ts new file mode 100644 index 0000000..fae06e6 --- /dev/null +++ b/backend/src/database/migrations/archive/1670936878487-AddFileInfo.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFileInfo1670936878487 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists file_info_seq as integer minvalue 0; + + create table file_info ( + id integer default nextval('file_info_seq'::regclass), + account_id integer not null, + created_at timestamp without time zone not null, + created_by integer not null, + key character varying not null, + original_name character varying not null, + mime_type character varying not null, + size integer not null, + primary key (id), + foreign key (account_id) references account(id), + foreign key (created_by) references users(id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1670939571185-AddFileStorePath.ts b/backend/src/database/migrations/archive/1670939571185-AddFileStorePath.ts new file mode 100644 index 0000000..453e4b6 --- /dev/null +++ b/backend/src/database/migrations/archive/1670939571185-AddFileStorePath.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFileStorePath1670939571185 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_info add column store_path character varying not null; + alter table file_info add column hash_md5 character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671006792499-AddStageIdToTask.ts b/backend/src/database/migrations/archive/1671006792499-AddStageIdToTask.ts new file mode 100644 index 0000000..f7b9bd4 --- /dev/null +++ b/backend/src/database/migrations/archive/1671006792499-AddStageIdToTask.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddStageIdToTask1671006792499 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task + add column stage_id integer, + add foreign key(stage_id) references stage(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671030345135-ChangeFileInfoIdType.ts b/backend/src/database/migrations/archive/1671030345135-ChangeFileInfoIdType.ts new file mode 100644 index 0000000..0319ac0 --- /dev/null +++ b/backend/src/database/migrations/archive/1671030345135-ChangeFileInfoIdType.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeFileInfoIdType1671030345135 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from file_info; + drop table file_info; + + drop sequence file_info_seq; + + create table file_info ( + id uuid default gen_random_uuid(), + account_id integer not null, + created_at timestamp without time zone not null, + created_by integer not null, + key character varying not null, + original_name character varying not null, + mime_type character varying not null, + size integer not null, + hash_md5 character varying not null, + store_path character varying not null, + primary key (id), + foreign key (account_id) references account(id), + foreign key (created_by) references users(id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671089754889-AddEntityTypeSortOrder.ts b/backend/src/database/migrations/archive/1671089754889-AddEntityTypeSortOrder.ts new file mode 100644 index 0000000..6e7395a --- /dev/null +++ b/backend/src/database/migrations/archive/1671089754889-AddEntityTypeSortOrder.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEntityTypeSortOrder1671089754889 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity_type add column sort_order smallint not null default 0; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671096971264-RenameTaskType.ts b/backend/src/database/migrations/archive/1671096971264-RenameTaskType.ts new file mode 100644 index 0000000..69a15d7 --- /dev/null +++ b/backend/src/database/migrations/archive/1671096971264-RenameTaskType.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameTaskType1671096971264 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter sequence task_type_id_seq rename to activity_type_id_seq; + alter table task_type rename to activity_type; + alter table activity rename column task_type_id to activity_type_id; + drop view all_tasks; + create or replace view all_tasks (id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, activity_type_id, title, type) as + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, activity_type_id, null as title, 'activity' as type from activity + union + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, null as activity_type_id, title, 'task' as type from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671108685879-NoteFiles.ts b/backend/src/database/migrations/archive/1671108685879-NoteFiles.ts new file mode 100644 index 0000000..47fcaab --- /dev/null +++ b/backend/src/database/migrations/archive/1671108685879-NoteFiles.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NoteFiles1671108685879 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table note_file ( + note_id integer NOT NULL, + file_info_id uuid NOT NULL, + primary key (note_id, file_info_id), + foreign key (note_id) references note(id), + foreign key (file_info_id) references file_info(id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671196063822-FileInfoCascade.ts b/backend/src/database/migrations/archive/1671196063822-FileInfoCascade.ts new file mode 100644 index 0000000..3467a1b --- /dev/null +++ b/backend/src/database/migrations/archive/1671196063822-FileInfoCascade.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FileInfoCascade1671196063822 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_info + drop constraint file_info_account_id_fkey, + add constraint file_info_account_id_fkey foreign key (account_id) references account(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671196394406-NoteFileRemoveFileKey.ts b/backend/src/database/migrations/archive/1671196394406-NoteFileRemoveFileKey.ts new file mode 100644 index 0000000..7bb2cb9 --- /dev/null +++ b/backend/src/database/migrations/archive/1671196394406-NoteFileRemoveFileKey.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NoteFileRemoveFileKey1671196394406 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table note_file drop constraint note_file_file_info_id_fkey; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671203263368-TaskAndActivityFiles.ts b/backend/src/database/migrations/archive/1671203263368-TaskAndActivityFiles.ts new file mode 100644 index 0000000..eeb1ab1 --- /dev/null +++ b/backend/src/database/migrations/archive/1671203263368-TaskAndActivityFiles.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TaskAndActivityFiles1671203263368 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table file_link ( + id integer generated by default as identity, + account_id integer not null, + source_type character varying not null, + source_id integer not null, + file_id uuid not null, + file_name character varying not null, + file_size integer not null, + file_type character varying not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671438176416-RemoveKeyFromFileInfo.ts b/backend/src/database/migrations/archive/1671438176416-RemoveKeyFromFileInfo.ts new file mode 100644 index 0000000..77e4150 --- /dev/null +++ b/backend/src/database/migrations/archive/1671438176416-RemoveKeyFromFileInfo.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveKeyFromFileInfo1671438176416 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_info drop column key; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671439005617-AddIsUsedToFileInfo.ts b/backend/src/database/migrations/archive/1671439005617-AddIsUsedToFileInfo.ts new file mode 100644 index 0000000..13bfce9 --- /dev/null +++ b/backend/src/database/migrations/archive/1671439005617-AddIsUsedToFileInfo.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIsUsedToFileInfo1671439005617 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_info add column is_used boolean default false not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671440838639-DropNoteFileLink.ts b/backend/src/database/migrations/archive/1671440838639-DropNoteFileLink.ts new file mode 100644 index 0000000..149f0fc --- /dev/null +++ b/backend/src/database/migrations/archive/1671440838639-DropNoteFileLink.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DropNoteFileLink1671440838639 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table note_file; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671521628139-BoardRecordIdNull.ts b/backend/src/database/migrations/archive/1671521628139-BoardRecordIdNull.ts new file mode 100644 index 0000000..6502ec4 --- /dev/null +++ b/backend/src/database/migrations/archive/1671521628139-BoardRecordIdNull.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class BoardRecordIdNull1671521628139 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board alter column record_id drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671533557027-UserActive.ts b/backend/src/database/migrations/archive/1671533557027-UserActive.ts new file mode 100644 index 0000000..5079b2a --- /dev/null +++ b/backend/src/database/migrations/archive/1671533557027-UserActive.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UserActive1671533557027 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users add column is_active boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671619787598-UserObjectPermission.ts b/backend/src/database/migrations/archive/1671619787598-UserObjectPermission.ts new file mode 100644 index 0000000..e2817ac --- /dev/null +++ b/backend/src/database/migrations/archive/1671619787598-UserObjectPermission.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UserObjectPermission1671619787598 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table object_permission ( + id integer generated by default as identity, + account_id integer not null, + created_at timestamp without time zone not null, + object_type character varying not null, + object_id integer, + create_permission character varying not null, + view_permission character varying not null, + edit_permission character varying not null, + delete_permission character varying not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + create table user_object_permission ( + user_id integer not null, + object_permission_id integer not null, + primary key (user_id, object_permission_id), + foreign key (user_id) references users(id) on delete cascade, + foreign key (object_permission_id) references object_permission(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1671914250074-ChangeCardView.ts b/backend/src/database/migrations/archive/1671914250074-ChangeCardView.ts new file mode 100644 index 0000000..e80ebea --- /dev/null +++ b/backend/src/database/migrations/archive/1671914250074-ChangeCardView.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeCardView1671914250074 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + queryRunner.query(` + update entity_type set card_view = 'deal' + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1672064100473-EntityIdCascade.ts b/backend/src/database/migrations/archive/1672064100473-EntityIdCascade.ts new file mode 100644 index 0000000..7fd22b9 --- /dev/null +++ b/backend/src/database/migrations/archive/1672064100473-EntityIdCascade.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityIdCascade1672064100473 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table external_entity + drop constraint external_entity_entity_id_fkey, + add constraint external_entity_entity_id_fkey foreign key (entity_id) references entity(id) + on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1672154773200-AddSalesforceSettings.ts b/backend/src/database/migrations/archive/1672154773200-AddSalesforceSettings.ts new file mode 100644 index 0000000..25deffe --- /dev/null +++ b/backend/src/database/migrations/archive/1672154773200-AddSalesforceSettings.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSalesforceSettings1672154773200 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table salesforce_settings ( + id uuid default gen_random_uuid(), + account_id integer not null, + created_at timestamp without time zone not null, + domain character varying not null, + key character varying not null, + secret character varying not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1672224404851-AddRefreshTokenToSalesforce.ts b/backend/src/database/migrations/archive/1672224404851-AddRefreshTokenToSalesforce.ts new file mode 100644 index 0000000..9aa3d86 --- /dev/null +++ b/backend/src/database/migrations/archive/1672224404851-AddRefreshTokenToSalesforce.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRefreshTokenToSalesforce1672224404851 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table salesforce_settings add column refresh_token character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1672242451242-ExternalSystemUrlTemplates.ts b/backend/src/database/migrations/archive/1672242451242-ExternalSystemUrlTemplates.ts new file mode 100644 index 0000000..808d5ba --- /dev/null +++ b/backend/src/database/migrations/archive/1672242451242-ExternalSystemUrlTemplates.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ExternalSystemUrlTemplates1672242451242 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table external_system add column url_templates character varying[]; + + update external_system set url_templates='{salesforce.com,force.com}' where id=1; + update external_system set url_templates='{linkedin.com}' where id=2; + update external_system set url_templates='{facebook.com, fb.com}' where id=3; + + alter table external_system alter column url_templates set not null; + + alter table external_system drop column url_template; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1672306523845-AddDataToExternalEntity.ts b/backend/src/database/migrations/archive/1672306523845-AddDataToExternalEntity.ts new file mode 100644 index 0000000..0b8bed2 --- /dev/null +++ b/backend/src/database/migrations/archive/1672306523845-AddDataToExternalEntity.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDataToExternalEntity1672306523845 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table external_entity add column data jsonb; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1672386954959-AddUIDataToExternalEntity.ts b/backend/src/database/migrations/archive/1672386954959-AddUIDataToExternalEntity.ts new file mode 100644 index 0000000..d6a6826 --- /dev/null +++ b/backend/src/database/migrations/archive/1672386954959-AddUIDataToExternalEntity.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUIDataToExternalEntity1672386954959 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table external_entity rename column data to raw_data; + alter table external_entity add column ui_data jsonb; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1672928537580-CascadeStageIdInTask.ts b/backend/src/database/migrations/archive/1672928537580-CascadeStageIdInTask.ts new file mode 100644 index 0000000..fe90256 --- /dev/null +++ b/backend/src/database/migrations/archive/1672928537580-CascadeStageIdInTask.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CascadeStageIdInTask1672928537580 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task + drop constraint task_stage_id_fkey; + + alter table task + add foreign key (stage_id) references stage (id) + on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1673251726994-AddStageToAllTasks.ts b/backend/src/database/migrations/archive/1673251726994-AddStageToAllTasks.ts new file mode 100644 index 0000000..7e0ce94 --- /dev/null +++ b/backend/src/database/migrations/archive/1673251726994-AddStageToAllTasks.ts @@ -0,0 +1,19 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddStageToAllTasks1673251726994 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view all_tasks; + create or replace view all_tasks (id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, activity_type_id, title, stage_id, type) as + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, activity_type_id, null as title, null as stage_id, 'activity' as type from activity + union + select id, created_at, account_id, created_by, responsible_user_id, text, start_date, end_date, is_resolved, result, entity_id, null as activity_type_id, title, stage_id, 'task' as type from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1673339773307-EnableFileStorageFeature.ts b/backend/src/database/migrations/archive/1673339773307-EnableFileStorageFeature.ts new file mode 100644 index 0000000..e8af2a6 --- /dev/null +++ b/backend/src/database/migrations/archive/1673339773307-EnableFileStorageFeature.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EnableFileStorageFeature1673339773307 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update feature set is_enabled=true where code='saveFiles'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1673417366210-RenameCardViewToEntityCategory.ts b/backend/src/database/migrations/archive/1673417366210-RenameCardViewToEntityCategory.ts new file mode 100644 index 0000000..44df32d --- /dev/null +++ b/backend/src/database/migrations/archive/1673417366210-RenameCardViewToEntityCategory.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameCardViewToEntityCategory1673417366210 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity_type rename column card_view to entity_category; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1673447442894-AddSubscription.ts b/backend/src/database/migrations/archive/1673447442894-AddSubscription.ts new file mode 100644 index 0000000..7029d14 --- /dev/null +++ b/backend/src/database/migrations/archive/1673447442894-AddSubscription.ts @@ -0,0 +1,26 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSubscription1673447442894 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table subscription ( + id integer generated by default as identity, + account_id integer not null, + created_at timestamp without time zone not null, + expired_at timestamp without time zone, + is_trial boolean not null, + user_limit integer not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + insert into subscription(account_id, created_at, expired_at, is_trial, user_limit) + select id as account_id, created_at, created_at + INTERVAL '14 day' as expired_at, true as is_trial, 5 as user_limit from account; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1673514341599-AddCreatedAtToFileLink.ts b/backend/src/database/migrations/archive/1673514341599-AddCreatedAtToFileLink.ts new file mode 100644 index 0000000..3674849 --- /dev/null +++ b/backend/src/database/migrations/archive/1673514341599-AddCreatedAtToFileLink.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCreatedAtToFileLink1673514341599 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_link add column created_at timestamp without time zone; + update file_link set created_at = file_info.created_at from file_info where file_link.file_id = file_info.id; + alter table file_link alter column created_at set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1673515827427-AddSequenceToSubscriptionId.ts b/backend/src/database/migrations/archive/1673515827427-AddSequenceToSubscriptionId.ts new file mode 100644 index 0000000..90f2938 --- /dev/null +++ b/backend/src/database/migrations/archive/1673515827427-AddSequenceToSubscriptionId.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSequenceToSubscriptionId1673515827427 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table subscription alter column id drop identity; + drop sequence if exists subscription_id_seq; + create sequence if not exists subscription_id_seq as integer minvalue 16022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1673969196807-AddMailboxAndProviderSettings.ts b/backend/src/database/migrations/archive/1673969196807-AddMailboxAndProviderSettings.ts new file mode 100644 index 0000000..fa3b145 --- /dev/null +++ b/backend/src/database/migrations/archive/1673969196807-AddMailboxAndProviderSettings.ts @@ -0,0 +1,53 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailboxAndProviderSettings1673969196807 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists mailbox_id_seq as integer minvalue 27023001; + + create table mailbox ( + id integer, + account_id integer not null, + created_at timestamp without time zone not null, + email character varying not null, + provider character varying not null, + type character varying not null, + owner_id integer, + group_message boolean not null, + create_contact boolean not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (owner_id) references users(id) + ); + + create table mailbox_settings_gmail ( + mailbox_id integer, + account_id integer not null, + access_token character varying not null, + refresh_token character varying not null, + primary key (mailbox_id), + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create table mailbox_settings_manual ( + "mailbox_id" integer, + "account_id" integer not null, + "password" character varying not null, + "imap_server" character varying not null, + "imap_port" smallint not null, + "imap_secure" boolean not null, + "smtp_server" character varying not null, + "smtp_port" smallint not null, + "smtp_secure" boolean not null, + primary key (mailbox_id), + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674130775550-AddMailboxState.ts b/backend/src/database/migrations/archive/1674130775550-AddMailboxState.ts new file mode 100644 index 0000000..ec465c8 --- /dev/null +++ b/backend/src/database/migrations/archive/1674130775550-AddMailboxState.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailboxState1674130775550 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox add column state character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674138959333-AddPlannedTimeToTask.ts b/backend/src/database/migrations/archive/1674138959333-AddPlannedTimeToTask.ts new file mode 100644 index 0000000..93ed0ef --- /dev/null +++ b/backend/src/database/migrations/archive/1674138959333-AddPlannedTimeToTask.ts @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPlannedTimeToTask1674138959333 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task + add column planned_time integer; + + drop view all_tasks; + create + or replace view all_tasks ( + id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + activity_type_id, + title, + planned_time, + type + ) as + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + activity_type_id, + null as title, + null as planned_time, + 'activity' as type + from activity + union + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + null as activity_type_id, + title, + planned_time, + 'task' as type + from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674203801914-ChangeMailboxSettingsGmail.ts b/backend/src/database/migrations/archive/1674203801914-ChangeMailboxSettingsGmail.ts new file mode 100644 index 0000000..065abfc --- /dev/null +++ b/backend/src/database/migrations/archive/1674203801914-ChangeMailboxSettingsGmail.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeMailboxSettingsGmail1674203801914 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_settings_gmail + drop column access_token, + drop column refresh_token, + add column tokens jsonb not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674492197867-AddHistoryIdToGmailSettings.ts b/backend/src/database/migrations/archive/1674492197867-AddHistoryIdToGmailSettings.ts new file mode 100644 index 0000000..397beaf --- /dev/null +++ b/backend/src/database/migrations/archive/1674492197867-AddHistoryIdToGmailSettings.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddHistoryIdToGmailSettings1674492197867 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_settings_gmail + add column history_id character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674546589359-AddLastSyncToManualSettings.ts b/backend/src/database/migrations/archive/1674546589359-AddLastSyncToManualSettings.ts new file mode 100644 index 0000000..702b09a --- /dev/null +++ b/backend/src/database/migrations/archive/1674546589359-AddLastSyncToManualSettings.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddLastSyncToManualSettings1674546589359 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_settings_manual + add column last_sync timestamp without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674560582007-AddMailMessageBase.ts b/backend/src/database/migrations/archive/1674560582007-AddMailMessageBase.ts new file mode 100644 index 0000000..d7a6082 --- /dev/null +++ b/backend/src/database/migrations/archive/1674560582007-AddMailMessageBase.ts @@ -0,0 +1,64 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessageBase1674560582007 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists mail_message_id_seq as integer minvalue 28023001; + create table mail_message ( + id integer, + mailbox_id integer not null, + external_id character varying not null, + thread_id character varying, + snippet character varying, + sent_from character varying not null, + sent_to character varying not null, + subject character varying, + date timestamp without time zone not null, + account_id integer not null, + primary key (id), + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists mail_message_payload_id_seq as integer minvalue 29023001; + create table mail_message_payload ( + id integer, + message_id integer not null, + mime_type character varying not null, + filename character varying, + attachment character varying, + content character varying, + account_id integer not null, + primary key (id), + foreign key (message_id) references mail_message(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists mailbox_folder_id_seq as integer minvalue 31023001; + create table mailbox_folder ( + id integer, + mailbox_id integer not null, + external_id character varying not null, + name character varying not null, + type character varying not null, + account_id integer not null, + primary key (id), + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create table mail_message_folder ( + message_id integer, + folder_id integer, + foreign key (message_id) references mail_message(id) on delete cascade, + foreign key (folder_id) references mailbox_folder(id) on delete cascade, + primary key (message_id, folder_id) + ); + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674572622632-AddTaskSettings.ts b/backend/src/database/migrations/archive/1674572622632-AddTaskSettings.ts new file mode 100644 index 0000000..7db77b1 --- /dev/null +++ b/backend/src/database/migrations/archive/1674572622632-AddTaskSettings.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskSettings1674572622632 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table task_settings + ( + id integer not null, + active_fields jsonb not null, + type varchar(100) not null, + record_id integer, + account_id integer, + primary key (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence task_settings_id_seq as integer minvalue 45022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674637903317-ChangeMailbox.ts b/backend/src/database/migrations/archive/1674637903317-ChangeMailbox.ts new file mode 100644 index 0000000..a50c892 --- /dev/null +++ b/backend/src/database/migrations/archive/1674637903317-ChangeMailbox.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeMailbox1674637903317 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + add column sync_days smallint, + drop column type + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674638261068-AddMailboxAccessibleUser.ts b/backend/src/database/migrations/archive/1674638261068-AddMailboxAccessibleUser.ts new file mode 100644 index 0000000..b7dd50a --- /dev/null +++ b/backend/src/database/migrations/archive/1674638261068-AddMailboxAccessibleUser.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailboxAccessibleUser1674638261068 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table mailbox_accessible_user ( + mailbox_id integer, + user_id integer, + account_id integer, + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade, + primary key (mailbox_id, user_id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674648741806-AddMailboxFolderInfo.ts b/backend/src/database/migrations/archive/1674648741806-AddMailboxFolderInfo.ts new file mode 100644 index 0000000..3417d8c --- /dev/null +++ b/backend/src/database/migrations/archive/1674648741806-AddMailboxFolderInfo.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailboxFolderInfo1674648741806 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_folder + add column messages_total integer, + add column messages_unread integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674660419230-AddMailMessageReplyTo.ts b/backend/src/database/migrations/archive/1674660419230-AddMailMessageReplyTo.ts new file mode 100644 index 0000000..3c3a224 --- /dev/null +++ b/backend/src/database/migrations/archive/1674660419230-AddMailMessageReplyTo.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessageReplyTo1674660419230 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message + add column reply_to character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674661928759-AddMailMessageCc.ts b/backend/src/database/migrations/archive/1674661928759-AddMailMessageCc.ts new file mode 100644 index 0000000..26d8c7e --- /dev/null +++ b/backend/src/database/migrations/archive/1674661928759-AddMailMessageCc.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessageCc1674661928759 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message + add column cc character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674732906697-AddMailMessagePayloadSortOrder.ts b/backend/src/database/migrations/archive/1674732906697-AddMailMessagePayloadSortOrder.ts new file mode 100644 index 0000000..b00510a --- /dev/null +++ b/backend/src/database/migrations/archive/1674732906697-AddMailMessagePayloadSortOrder.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessagePayloadSortOrder1674732906697 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message_payload + add column sort_order smallint not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674741639286-MakeStartEndDateNullable.ts b/backend/src/database/migrations/archive/1674741639286-MakeStartEndDateNullable.ts new file mode 100644 index 0000000..1f70eda --- /dev/null +++ b/backend/src/database/migrations/archive/1674741639286-MakeStartEndDateNullable.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MakeStartEndDateNullable1674741639286 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task + alter column start_date drop not null; + alter table task + alter column end_date drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674742927551-AddSubtaskTable.ts b/backend/src/database/migrations/archive/1674742927551-AddSubtaskTable.ts new file mode 100644 index 0000000..89507fb --- /dev/null +++ b/backend/src/database/migrations/archive/1674742927551-AddSubtaskTable.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSubtaskTable1674742927551 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table subtask + ( + id integer not null, + text character varying not null, + resolved boolean not null, + task_id integer not null, + account_id integer not null, + primary key (id), + foreign key (task_id) references task (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + create sequence subtask_id_seq as integer minvalue 46022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674747324757-AddMailMessagePayloadSize.ts b/backend/src/database/migrations/archive/1674747324757-AddMailMessagePayloadSize.ts new file mode 100644 index 0000000..a04085f --- /dev/null +++ b/backend/src/database/migrations/archive/1674747324757-AddMailMessagePayloadSize.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessagePayloadSize1674747324757 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message_payload + add column size integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674822646459-AddTaskCommentTable.ts b/backend/src/database/migrations/archive/1674822646459-AddTaskCommentTable.ts new file mode 100644 index 0000000..f8d2291 --- /dev/null +++ b/backend/src/database/migrations/archive/1674822646459-AddTaskCommentTable.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskCommentTable1674822646459 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table task_comment + ( + id integer, + text character varying not null, + task_id integer not null, + created_by integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (task_id) references task (id) on delete cascade, + foreign key (created_by) references users (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + create sequence task_comment_id_seq as integer minvalue 47022001; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674823506430-DeleteSyncDaysFromMailbox.ts b/backend/src/database/migrations/archive/1674823506430-DeleteSyncDaysFromMailbox.ts new file mode 100644 index 0000000..b53474e --- /dev/null +++ b/backend/src/database/migrations/archive/1674823506430-DeleteSyncDaysFromMailbox.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteSyncDaysFromMailbox1674823506430 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + drop column sync_days; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1674831146207-AddTaskCommentLikeTable.ts b/backend/src/database/migrations/archive/1674831146207-AddTaskCommentLikeTable.ts new file mode 100644 index 0000000..42fe633 --- /dev/null +++ b/backend/src/database/migrations/archive/1674831146207-AddTaskCommentLikeTable.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskCommentLikeTable1674831146207 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table task_comment_like + ( + comment_id integer, + user_id integer, + account_id integer, + foreign key (comment_id) references task_comment (id) on delete cascade, + foreign key (user_id) references users (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade, + primary key (comment_id, user_id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675076470234-ImapSync.ts b/backend/src/database/migrations/archive/1675076470234-ImapSync.ts new file mode 100644 index 0000000..ab04ce9 --- /dev/null +++ b/backend/src/database/migrations/archive/1675076470234-ImapSync.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ImapSync1675076470234 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_settings_manual drop column last_sync; + alter table mailbox_settings_manual add column imap_sync jsonb; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675080091774-AddSystemColumnToBoard.ts b/backend/src/database/migrations/archive/1675080091774-AddSystemColumnToBoard.ts new file mode 100644 index 0000000..8ef4657 --- /dev/null +++ b/backend/src/database/migrations/archive/1675080091774-AddSystemColumnToBoard.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSystemColumnToBoard1675080091774 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board + add is_system boolean default false not null; + + update board + set is_system = true + where name = 'Tasks board'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675086921624-AddMailMessagePayloadExternalId.ts b/backend/src/database/migrations/archive/1675086921624-AddMailMessagePayloadExternalId.ts new file mode 100644 index 0000000..53745ec --- /dev/null +++ b/backend/src/database/migrations/archive/1675086921624-AddMailMessagePayloadExternalId.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessagePayloadExternalId1675086921624 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message_payload + add column external_id character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675090441216-AddTaskSettingsIdToTask.ts b/backend/src/database/migrations/archive/1675090441216-AddTaskSettingsIdToTask.ts new file mode 100644 index 0000000..5a20b7b --- /dev/null +++ b/backend/src/database/migrations/archive/1675090441216-AddTaskSettingsIdToTask.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskSettingsIdToTask1675090441216 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task + add column settings_id integer; + alter table task + add foreign key (settings_id) references task_settings (id) + on delete set null; + + drop view all_tasks; + create + or replace view all_tasks ( + id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + activity_type_id, + title, + planned_time, + settings_id, + type + ) as + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + activity_type_id, + null as title, + null as planned_time, + null as settings_id, + 'activity' as type + from activity + union + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + null as activity_type_id, + title, + planned_time, + settings_id, + 'task' as type + from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675176424905-AddMailMessageHasAttachment.ts b/backend/src/database/migrations/archive/1675176424905-AddMailMessageHasAttachment.ts new file mode 100644 index 0000000..9f20ceb --- /dev/null +++ b/backend/src/database/migrations/archive/1675176424905-AddMailMessageHasAttachment.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessageHasAttachment1675176424905 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message + add column has_attachment boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675178566416-MailboxFolderTypeNullable.ts b/backend/src/database/migrations/archive/1675178566416-MailboxFolderTypeNullable.ts new file mode 100644 index 0000000..db6b38b --- /dev/null +++ b/backend/src/database/migrations/archive/1675178566416-MailboxFolderTypeNullable.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailboxFolderTypeNullable1675178566416 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_folder alter column type drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675259924922-AddMailMessageMessageId.ts b/backend/src/database/migrations/archive/1675259924922-AddMailMessageMessageId.ts new file mode 100644 index 0000000..33f2fe3 --- /dev/null +++ b/backend/src/database/migrations/archive/1675259924922-AddMailMessageMessageId.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessageMessageId1675259924922 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message add column message_id character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675340097111-DropGroupMessageFromMailbox.ts b/backend/src/database/migrations/archive/1675340097111-DropGroupMessageFromMailbox.ts new file mode 100644 index 0000000..16bc73c --- /dev/null +++ b/backend/src/database/migrations/archive/1675340097111-DropGroupMessageFromMailbox.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DropGroupMessageFromMailbox1675340097111 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox drop column group_message; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675349215128-AddCodeToEntityType.ts b/backend/src/database/migrations/archive/1675349215128-AddCodeToEntityType.ts new file mode 100644 index 0000000..782c971 --- /dev/null +++ b/backend/src/database/migrations/archive/1675349215128-AddCodeToEntityType.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCodeColumn1675349215128 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity_type + add code varchar(255); + + alter table entity_type + add constraint entity_type__code__account_id__uniq + unique (code, account_id); + + update entity_type + set code = 'deal' + where name = 'Deal'; + + update entity_type + set code = 'contact' + where name = 'Contact'; + + update entity_type + set code = 'company' + where name = 'Company'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675350490867-AddCodeToBoard.ts b/backend/src/database/migrations/archive/1675350490867-AddCodeToBoard.ts new file mode 100644 index 0000000..ce16735 --- /dev/null +++ b/backend/src/database/migrations/archive/1675350490867-AddCodeToBoard.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCodeToBoard1675350490867 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board + add code varchar(255); + + alter table board + add constraint board__code__account_id__uniq + unique (code, account_id); + + update board + set code = 'deals' + where name = 'Deals'; + + update board + set code = 'leads' + where name = 'Leads'; + + update board + set code = 'projects' + where name = 'Projects'; + + update board + set code = 'tasks' + where name = 'Tasks board'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675412897506-AddMailMessageReferences.ts b/backend/src/database/migrations/archive/1675412897506-AddMailMessageReferences.ts new file mode 100644 index 0000000..2e94a08 --- /dev/null +++ b/backend/src/database/migrations/archive/1675412897506-AddMailMessageReferences.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessageReferences1675412897506 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message + add column references_to character varying, + add column in_reply_to character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675858313415-AddContactEntityTypeToMailbox.ts b/backend/src/database/migrations/archive/1675858313415-AddContactEntityTypeToMailbox.ts new file mode 100644 index 0000000..ff2f655 --- /dev/null +++ b/backend/src/database/migrations/archive/1675858313415-AddContactEntityTypeToMailbox.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddContactEntityTypeToMailbox1675858313415 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + add column contact_entity_type_id integer, + add foreign key (contact_entity_type_id) references entity_type(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675858907059-AddLeadEntityTypeToMailbox.ts b/backend/src/database/migrations/archive/1675858907059-AddLeadEntityTypeToMailbox.ts new file mode 100644 index 0000000..9729ebf --- /dev/null +++ b/backend/src/database/migrations/archive/1675858907059-AddLeadEntityTypeToMailbox.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddLeadEntityTypeToMailbox1675858907059 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + add column lead_entity_type_id integer, + add foreign key (lead_entity_type_id) references entity_type(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675932022996-AddLeadBoardIdToMailbox.ts b/backend/src/database/migrations/archive/1675932022996-AddLeadBoardIdToMailbox.ts new file mode 100644 index 0000000..75e82be --- /dev/null +++ b/backend/src/database/migrations/archive/1675932022996-AddLeadBoardIdToMailbox.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddLeadBoardIdToMailbox1675932022996 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + add column lead_board_id integer, + add foreign key (lead_board_id) references board(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675933206380-AddContactIdToMailMessage.ts b/backend/src/database/migrations/archive/1675933206380-AddContactIdToMailMessage.ts new file mode 100644 index 0000000..4b44a86 --- /dev/null +++ b/backend/src/database/migrations/archive/1675933206380-AddContactIdToMailMessage.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddContactIdToMailMessage1675933206380 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message + add column contact_entity_id integer, + add foreign key (contact_entity_id) references entity(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675939938059-MailMessageThreadIdNotNull.ts b/backend/src/database/migrations/archive/1675939938059-MailMessageThreadIdNotNull.ts new file mode 100644 index 0000000..9b3bed9 --- /dev/null +++ b/backend/src/database/migrations/archive/1675939938059-MailMessageThreadIdNotNull.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailMessageThreadIdNotNull1675939938059 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message + alter column thread_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675949522751-AddErrorToMailbox.ts b/backend/src/database/migrations/archive/1675949522751-AddErrorToMailbox.ts new file mode 100644 index 0000000..d88ab3a --- /dev/null +++ b/backend/src/database/migrations/archive/1675949522751-AddErrorToMailbox.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddErrorToMailbox1675949522751 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + add column error_message character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1675958088984-AddSeenToMailMessage.ts b/backend/src/database/migrations/archive/1675958088984-AddSeenToMailMessage.ts new file mode 100644 index 0000000..84f0985 --- /dev/null +++ b/backend/src/database/migrations/archive/1675958088984-AddSeenToMailMessage.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSeenToMailMessage1675958088984 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message + add column is_seen boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676281117335-AddSetNullOnDeleteToMailbox.ts b/backend/src/database/migrations/archive/1676281117335-AddSetNullOnDeleteToMailbox.ts new file mode 100644 index 0000000..e49c99b --- /dev/null +++ b/backend/src/database/migrations/archive/1676281117335-AddSetNullOnDeleteToMailbox.ts @@ -0,0 +1,20 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSetNullOnDeleteToMailbox1676281117335 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + drop constraint mailbox_contact_entity_type_id_fkey, + drop constraint mailbox_lead_entity_type_id_fkey, + drop constraint mailbox_lead_board_id_fkey, + add constraint mailbox_contact_entity_type_id_fkey foreign key (contact_entity_type_id) references entity_type(id) on delete set null, + add constraint mailbox_lead_entity_type_id_fkey foreign key (lead_entity_type_id) references entity_type(id) on delete set null, + add constraint mailbox_lead_board_id_fkey foreign key (lead_board_id) references board(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676283110539-AlterMailMessageSentToNull.ts b/backend/src/database/migrations/archive/1676283110539-AlterMailMessageSentToNull.ts new file mode 100644 index 0000000..b173df3 --- /dev/null +++ b/backend/src/database/migrations/archive/1676283110539-AlterMailMessageSentToNull.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailMessageSentToNull1676283110539 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message alter column sent_to drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676299431922-AddRecipientToNote.ts b/backend/src/database/migrations/archive/1676299431922-AddRecipientToNote.ts new file mode 100644 index 0000000..d074f61 --- /dev/null +++ b/backend/src/database/migrations/archive/1676299431922-AddRecipientToNote.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRecipientToNote1676299431922 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table note + add column recipient_id integer; + + alter table note + add foreign key (recipient_id) references users + on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676370561831-AddMailToFeedItem.ts b/backend/src/database/migrations/archive/1676370561831-AddMailToFeedItem.ts new file mode 100644 index 0000000..2390175 --- /dev/null +++ b/backend/src/database/migrations/archive/1676370561831-AddMailToFeedItem.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailToFeedItem1676370561831 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create or replace view feed_items (id, created_at, entity_id, type) as + SELECT note.id, + note.created_at, + note.entity_id, + 'note'::text AS type + FROM note + UNION + SELECT task.id, + task.created_at, + task.entity_id, + 'task'::text AS type + FROM task + UNION + SELECT activity.id, + activity.created_at, + activity.entity_id, + 'activity'::text AS type + FROM activity + UNION + (SELECT max(message.id) AS id, + max(message.date) AS created_at, + el.source_id AS entity_id, + 'mail'::text AS type + FROM mail_message message + RIGHT JOIN entity_link el ON el.target_id = message.contact_entity_id + WHERE message.contact_entity_id IS NOT NULL + GROUP BY message.thread_id, el.source_id + ORDER BY (max(message.date)) DESC) + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676383947580-TurnOnChatFeature.ts b/backend/src/database/migrations/archive/1676383947580-TurnOnChatFeature.ts new file mode 100644 index 0000000..c617822 --- /dev/null +++ b/backend/src/database/migrations/archive/1676383947580-TurnOnChatFeature.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TurnOnChatFeature1676383947580 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update feature + set is_enabled = true + where code = 'chat' + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676385805343-AddChatFeedItemType.ts b/backend/src/database/migrations/archive/1676385805343-AddChatFeedItemType.ts new file mode 100644 index 0000000..42d5fdb --- /dev/null +++ b/backend/src/database/migrations/archive/1676385805343-AddChatFeedItemType.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatFeedItemType1676385805343 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create or replace view feed_items (id, created_at, entity_id, type) as + SELECT note.id, + note.created_at, + note.entity_id, + case when note.recipient_id is null then 'note'::text else 'chat'::text end as type + FROM note + UNION + SELECT task.id, + task.created_at, + task.entity_id, + 'task'::text AS type + FROM task + UNION + SELECT activity.id, + activity.created_at, + activity.entity_id, + 'activity'::text AS type + FROM activity + UNION + (SELECT max(message.id) AS id, + max(message.date) AS created_at, + el.source_id AS entity_id, + 'mail'::text AS type + FROM mail_message message + RIGHT JOIN entity_link el ON el.target_id = message.contact_entity_id + WHERE message.contact_entity_id IS NOT NULL + GROUP BY message.thread_id, el.source_id + ORDER BY (max(message.date)) DESC) + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676823429937-AddAutomationTables.ts b/backend/src/database/migrations/archive/1676823429937-AddAutomationTables.ts new file mode 100644 index 0000000..ed222cc --- /dev/null +++ b/backend/src/database/migrations/archive/1676823429937-AddAutomationTables.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAutomationTables1676823429937 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table trigger + ( + id integer, + type varchar(100) not null, + account_id integer not null, + primary key (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence trigger_id_seq as integer minvalue 51011001; + + create table action + ( + id integer, + type varchar(100) not null, + delay integer, + account_id integer not null, + primary key (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence action_id_seq as integer minvalue 51011001; + + create table automation + ( + id integer, + trigger_id integer not null, + action_id integer not null, + created_by integer not null, + is_active boolean not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (trigger_id) references trigger (id), + foreign key (action_id) references action (id), + foreign key (created_by) references users (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence automation_id_seq as integer minvalue 51011001; + + create table activity_action_settings + ( + action_id integer, + responsible_user_type varchar(100) not null, + responsible_user_id integer, + activity_type_id integer not null, + text varchar not null, + deadline_type varchar(100) not null, + deadline_time integer, + account_id integer not null, + primary key (action_id), + foreign key (action_id) references action (id) on delete cascade, + foreign key (responsible_user_id) references users (id) on delete cascade, + foreign key (activity_type_id) references activity_type (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676866745580-AddAutomationStageTable.ts b/backend/src/database/migrations/archive/1676866745580-AddAutomationStageTable.ts new file mode 100644 index 0000000..b7a74aa --- /dev/null +++ b/backend/src/database/migrations/archive/1676866745580-AddAutomationStageTable.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAutomationStageTable1676866745580 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table automation_stage + ( + automation_id integer, + stage_id integer, + foreign key (automation_id) references automation (id) on delete cascade, + foreign key (stage_id) references stage (id) on delete cascade, + primary key (automation_id, stage_id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676872004900-AddTaskActionSettingsTable.ts b/backend/src/database/migrations/archive/1676872004900-AddTaskActionSettingsTable.ts new file mode 100644 index 0000000..1b8ff39 --- /dev/null +++ b/backend/src/database/migrations/archive/1676872004900-AddTaskActionSettingsTable.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskActionSettingsTable1676872004900 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table task_action_settings + ( + action_id integer, + responsible_user_type varchar(100) not null, + responsible_user_id integer, + title varchar not null, + text varchar not null, + deadline_type varchar(100) not null, + deadline_time integer, + account_id integer not null, + primary key (action_id), + foreign key (action_id) references action (id) on delete cascade, + foreign key (responsible_user_id) references users (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676884562402-RemoveResultFromTask.ts b/backend/src/database/migrations/archive/1676884562402-RemoveResultFromTask.ts new file mode 100644 index 0000000..1a89925 --- /dev/null +++ b/backend/src/database/migrations/archive/1676884562402-RemoveResultFromTask.ts @@ -0,0 +1,69 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveResultFromTask1676884562402 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view all_tasks; + create or replace view all_tasks ( + id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + activity_type_id, + title, + planned_time, + settings_id, + type + ) as + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + result, + entity_id, + activity_type_id, + null as title, + null as planned_time, + null as settings_id, + 'activity' as type + from activity + union + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + null as result, + entity_id, + null as activity_type_id, + title, + planned_time, + settings_id, + 'task' as type + from task + order by id; + + alter table task drop column result; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676895093533-AddChangeStageActionSettingsTable.ts b/backend/src/database/migrations/archive/1676895093533-AddChangeStageActionSettingsTable.ts new file mode 100644 index 0000000..a3906f9 --- /dev/null +++ b/backend/src/database/migrations/archive/1676895093533-AddChangeStageActionSettingsTable.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChangeStageActionSettingsTable1676895093533 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table change_stage_action_settings + ( + action_id integer, + stage_id integer not null, + account_id integer not null, + primary key (action_id), + foreign key (action_id) references action (id) on delete cascade, + foreign key (stage_id) references stage (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676992655279-AddAutomationConditions.ts b/backend/src/database/migrations/archive/1676992655279-AddAutomationConditions.ts new file mode 100644 index 0000000..3081d86 --- /dev/null +++ b/backend/src/database/migrations/archive/1676992655279-AddAutomationConditions.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAutomationConditions1676992655279 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table condition + ( + id integer, + type varchar(100) not null, + account_id integer not null, + primary key (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence condition_id_seq as integer minvalue 51011001; + + create table automation_condition + ( + automation_id integer, + condition_id integer, + account_id integer not null, + foreign key (automation_id) references automation (id) on delete cascade, + foreign key (condition_id) references condition (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade, + primary key (automation_id, condition_id) + ); + + create table user_condition + ( + condition_id integer, + user_id integer, + account_id integer not null, + foreign key (condition_id) references condition (id) on delete cascade, + foreign key (user_id) references users (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade, + primary key (condition_id, user_id) + ); + + create table field_condition + ( + condition_id integer, + field_id integer not null, + field_type varchar(50) not null, + payload jsonb not null, + account_id integer not null, + primary key (condition_id), + foreign key (condition_id) references condition (id) on delete cascade, + foreign key (field_id) references field (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1676994397217-AddNotification.ts b/backend/src/database/migrations/archive/1676994397217-AddNotification.ts new file mode 100644 index 0000000..a608c5e --- /dev/null +++ b/backend/src/database/migrations/archive/1676994397217-AddNotification.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddNotification1676994397217 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + + CREATE TABLE notification ( + "id" integer, + "account_id" integer NOT NULL, + "created_at" timestamp without time zone NOT NULL, + "type" character varying NOT NULL, + "object_id" integer NOT NULL, + "entity_id" integer, + "from_user" integer, + "title" character varying, + "description" character varying NOT NULL, + "is_seen" boolean NOT NULL DEFAULT false, + PRIMARY KEY ("id"), + FOREIGN KEY ("account_id") REFERENCES account(id) ON DELETE CASCADE, + FOREIGN KEY ("entity_id") REFERENCES entity(id) ON DELETE SET NULL, + FOREIGN KEY ("from_user") REFERENCES users(id) ON DELETE SET NULL + ); + + create sequence notification_id_seq as integer minvalue 61011001; + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1677079993131-AddUserIdToNotification.ts b/backend/src/database/migrations/archive/1677079993131-AddUserIdToNotification.ts new file mode 100644 index 0000000..099d71b --- /dev/null +++ b/backend/src/database/migrations/archive/1677079993131-AddUserIdToNotification.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUserIdToNotification1677079993131 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table notification + add column user_id integer not null, + add foreign key (user_id) references users(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1677581691766-AddMailboxSignature.ts b/backend/src/database/migrations/archive/1677581691766-AddMailboxSignature.ts new file mode 100644 index 0000000..7f6fa54 --- /dev/null +++ b/backend/src/database/migrations/archive/1677581691766-AddMailboxSignature.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailboxSignature1677581691766 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists mailbox_signature_id_seq as integer minvalue 27023001; + + create table mailbox_signature ( + id integer, + account_id integer not null, + created_at timestamp without time zone not null, + name character varying not null, + text character varying not null, + created_by integer not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (created_by) references users(id) on delete cascade + ); + + create table mailbox_signature_link ( + account_id integer not null, + signature_id integer, + mailbox_id integer, + primary key (signature_id, mailbox_id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (signature_id) references mailbox_signature(id) on delete cascade, + foreign key (mailbox_id) references mailbox(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1677744619246-AddNotificationHighlight.ts b/backend/src/database/migrations/archive/1677744619246-AddNotificationHighlight.ts new file mode 100644 index 0000000..b858f57 --- /dev/null +++ b/backend/src/database/migrations/archive/1677744619246-AddNotificationHighlight.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddNotificationHighlight1677744619246 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table notification add column highlight character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1677752600091-AlterNotificationSetDescriptionNullable.ts b/backend/src/database/migrations/archive/1677752600091-AlterNotificationSetDescriptionNullable.ts new file mode 100644 index 0000000..05ebc20 --- /dev/null +++ b/backend/src/database/migrations/archive/1677752600091-AlterNotificationSetDescriptionNullable.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterNotificationSetDescriptionNullable1677752600091 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table notification alter column description drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1677765215861-AddNotificationSettings.ts b/backend/src/database/migrations/archive/1677765215861-AddNotificationSettings.ts new file mode 100644 index 0000000..60da58e --- /dev/null +++ b/backend/src/database/migrations/archive/1677765215861-AddNotificationSettings.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddNotificationSettings1677765215861 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists notification_settings_id_seq as integer minvalue 62011001; + create table notification_settings ( + id integer, + account_id integer not null, + user_id integer not null, + enable_popup boolean not null default true, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade + ); + + create sequence if not exists notification_type_settings_id_seq as integer minvalue 63011001; + create table notification_type_settings ( + id integer, + account_id integer not null, + settings_id integer not null, + type character varying not null, + is_enabled boolean not null default true, + object_id integer, + before integer, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (settings_id) references notification_settings(id) on delete cascade + ); + + create table notification_type_follow_user ( + type_id integer, + user_id integer, + account_id integer not null, + primary key (type_id, user_id), + foreign key (type_id) references notification_type_settings(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678093755605-AddExternalSystems.ts b/backend/src/database/migrations/archive/1678093755605-AddExternalSystems.ts new file mode 100644 index 0000000..0d4bd30 --- /dev/null +++ b/backend/src/database/migrations/archive/1678093755605-AddExternalSystems.ts @@ -0,0 +1,25 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddExternalSystems1678093755605 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into external_system(id, name, code, url_templates) values(4, 'Pipedrive', 'pipedrive', '{pipedrive.com}'); + insert into external_system(id, name, code, url_templates) values(5, 'HubSpot', 'hubspot', '{hubspot.com}'); + insert into external_system(id, name, code, url_templates) values(6, 'Freshsales', 'freshsales', '{freshworks.com,myfreshworks.com}'); + insert into external_system(id, name, code, url_templates) values(7, 'Zoho', 'zoho', '{zoho.com}'); + insert into external_system(id, name, code, url_templates) values(8, 'Twitter', 'twitter', '{twitter.com}'); + insert into external_system(id, name, code, url_templates) values(9, 'Instagram', 'instagram', '{instagram.com}'); + insert into external_system(id, name, code, url_templates) values(10, 'Notion', 'notion', '{notion.so}'); + insert into external_system(id, name, code, url_templates) values(11, 'Zendesk', 'zendesk', '{zendesk.com}'); + insert into external_system(id, name, code, url_templates) values(12, 'SugarCRM', 'sugarcrm', '{sugarcrm.com}'); + insert into external_system(id, name, code, url_templates) values(13, 'Monday', 'monday', '{monday.com}'); + insert into external_system(id, name, code, url_templates) values(14, 'amoCRM', 'amocrm', '{amocrm.ru,kommo.com}'); + insert into external_system(id, name, code, url_templates) values(15, 'Bitrix24', 'bitrix', '{bitrix24.ru,bitrix24.com,bitrix24.es,bitrix24.eu,bitrix24.de,bitrix24.fr,bitrix24.pl,bitrix24.it,bitrix24.uk}'); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678096716231-AddAccountSettings.ts b/backend/src/database/migrations/archive/1678096716231-AddAccountSettings.ts new file mode 100644 index 0000000..6e9927d --- /dev/null +++ b/backend/src/database/migrations/archive/1678096716231-AddAccountSettings.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAccountSettings1678096716231 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table account_settings ( + account_id integer, + language character varying not null, + working_days character varying, + working_time_from time, + working_time_to time, + time_zone character varying, + currency character varying(3) not null, + number_format character varying, + primary key (account_id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678113775031-AddExactTimeTriggerSettings.ts b/backend/src/database/migrations/archive/1678113775031-AddExactTimeTriggerSettings.ts new file mode 100644 index 0000000..12df8c0 --- /dev/null +++ b/backend/src/database/migrations/archive/1678113775031-AddExactTimeTriggerSettings.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddExactTimeTriggerSettings1678113775031 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table exact_time_trigger_settings + ( + trigger_id integer, + date timestamp without time zone not null, + account_id integer not null, + primary key (trigger_id), + foreign key (trigger_id) references trigger (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678194605837-AddDefaultNotificationSettings.ts b/backend/src/database/migrations/archive/1678194605837-AddDefaultNotificationSettings.ts new file mode 100644 index 0000000..da304ce --- /dev/null +++ b/backend/src/database/migrations/archive/1678194605837-AddDefaultNotificationSettings.ts @@ -0,0 +1,55 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDefaultNotificationSettings1678194605837 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into notification_settings + select nextval('notification_settings_id_seq'::regclass) as id, account_id, id as user_id, true as enable_popup + from users; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'task_new' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'task_overdue' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'task_before_start' as type, true as is_enabled, null as object_id, 3600 as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'task_overdue_employee' as type, false as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'activity_new' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'activity_overdue' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'activity_before_start' as type, true as is_enabled, null as object_id, 3600 as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'activity_overdue_employee' as type, false as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'task_comment_new' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'chat_message_new' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'mail_new' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'entity_note_new' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + insert into notification_type_settings + select nextval('notification_type_settings_id_seq'::regclass) as id, account_id, id as settings_id, 'entity_responsible_change' as type, true as is_enabled, null as object_id, null as before + from notification_settings; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678285724304-AddResolveDateToTaskAndActivity.ts b/backend/src/database/migrations/archive/1678285724304-AddResolveDateToTaskAndActivity.ts new file mode 100644 index 0000000..a7b1188 --- /dev/null +++ b/backend/src/database/migrations/archive/1678285724304-AddResolveDateToTaskAndActivity.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddResolveDateToTaskAndActivity1678285724304 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task add column resolved_date timestamp without time zone; + alter table activity add column resolved_date timestamp without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678286876322-AddResolveDateToAllTasks.ts b/backend/src/database/migrations/archive/1678286876322-AddResolveDateToAllTasks.ts new file mode 100644 index 0000000..99a04c7 --- /dev/null +++ b/backend/src/database/migrations/archive/1678286876322-AddResolveDateToAllTasks.ts @@ -0,0 +1,71 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddResolveDateToAllTasks1678286876322 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view all_tasks; + + create or replace view all_tasks ( + id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + activity_type_id, + title, + planned_time, + settings_id, + type + ) as + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + activity_type_id, + null as title, + null as planned_time, + null as settings_id, + 'activity' as type + from activity + union + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + null as result, + entity_id, + null as activity_type_id, + title, + planned_time, + settings_id, + 'task' as type + from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678347902775-AlterNotificationHighlightToTagName.ts b/backend/src/database/migrations/archive/1678347902775-AlterNotificationHighlightToTagName.ts new file mode 100644 index 0000000..09e8f41 --- /dev/null +++ b/backend/src/database/migrations/archive/1678347902775-AlterNotificationHighlightToTagName.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterNotificationHighlightToTagName1678347902775 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table notification rename column highlight to tag_name; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678367411991-FillResolvedDateForTaskAndAction.ts b/backend/src/database/migrations/archive/1678367411991-FillResolvedDateForTaskAndAction.ts new file mode 100644 index 0000000..a85a268 --- /dev/null +++ b/backend/src/database/migrations/archive/1678367411991-FillResolvedDateForTaskAndAction.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FillResolvedDateForTaskAndAction1678367411991 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update task set resolved_date = now() where is_resolved = true and resolved_date is null; + + update activity set resolved_date = now() where is_resolved = true and resolved_date is null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678696011650-AddPlanNameToSubscription.ts b/backend/src/database/migrations/archive/1678696011650-AddPlanNameToSubscription.ts new file mode 100644 index 0000000..e6427de --- /dev/null +++ b/backend/src/database/migrations/archive/1678696011650-AddPlanNameToSubscription.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPlanNameToSubscription1678696011650 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table subscription add column plan_name character varying; + update subscription set plan_name = 'Premium'; + alter table subscription alter column plan_name set NOT NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678697720944-UpdateSubscriptionTo50Users.ts b/backend/src/database/migrations/archive/1678697720944-UpdateSubscriptionTo50Users.ts new file mode 100644 index 0000000..e7eec66 --- /dev/null +++ b/backend/src/database/migrations/archive/1678697720944-UpdateSubscriptionTo50Users.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateSubscriptionTo50Users1678697720944 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update subscription set user_limit = 50; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678720323399-AddCodeToField.ts b/backend/src/database/migrations/archive/1678720323399-AddCodeToField.ts new file mode 100644 index 0000000..8c40345 --- /dev/null +++ b/backend/src/database/migrations/archive/1678720323399-AddCodeToField.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddCodeToField1678720323399 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field + add code varchar(255); + + alter table field + add constraint field__code__entity_type_id__uniq + unique (code, entity_type_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678891987277-AddDepartment.ts b/backend/src/database/migrations/archive/1678891987277-AddDepartment.ts new file mode 100644 index 0000000..fea29af --- /dev/null +++ b/backend/src/database/migrations/archive/1678891987277-AddDepartment.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDepartment1678891987277 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists department_id_seq as integer minvalue 1; + + create table department ( + id integer, + name character varying not null, + parent_id integer, + account_id integer not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + alter table department + add foreign key (parent_id) references department(id) on delete cascade; + + alter table users + add column department_id integer, + add foreign key (department_id) references department(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1678963689149-AddStartsInToNotification.ts b/backend/src/database/migrations/archive/1678963689149-AddStartsInToNotification.ts new file mode 100644 index 0000000..3091f6c --- /dev/null +++ b/backend/src/database/migrations/archive/1678963689149-AddStartsInToNotification.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddStartsInToNotification1678963689149 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table notification add column starts_in integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1679291439089-AddScheduledAction.ts b/backend/src/database/migrations/archive/1679291439089-AddScheduledAction.ts new file mode 100644 index 0000000..38b944e --- /dev/null +++ b/backend/src/database/migrations/archive/1679291439089-AddScheduledAction.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddScheduledAction1679291439089 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table scheduled_action + ( + id integer, + action_id integer not null, + entity_id integer not null, + scheduled_time timestamp without time zone not null, + completed boolean not null, + account_id integer not null, + primary key (id), + foreign key (action_id) references action (id) on delete cascade, + foreign key (entity_id) references entity (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + create sequence scheduled_action_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1679494426598-MakeFieldGroupOptional.ts b/backend/src/database/migrations/archive/1679494426598-MakeFieldGroupOptional.ts new file mode 100644 index 0000000..ab89c06 --- /dev/null +++ b/backend/src/database/migrations/archive/1679494426598-MakeFieldGroupOptional.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MakeFieldGroupOptional1679494426598 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field + alter column field_group_id drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1679578329175-AddColorToFieldOption.ts b/backend/src/database/migrations/archive/1679578329175-AddColorToFieldOption.ts new file mode 100644 index 0000000..e58e25e --- /dev/null +++ b/backend/src/database/migrations/archive/1679578329175-AddColorToFieldOption.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColorToFieldOption1679578329175 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field_option + add column color varchar(50); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1679583365997-AlterMailMessageContactEntityId.ts b/backend/src/database/migrations/archive/1679583365997-AlterMailMessageContactEntityId.ts new file mode 100644 index 0000000..cfc347a --- /dev/null +++ b/backend/src/database/migrations/archive/1679583365997-AlterMailMessageContactEntityId.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailMessageContactEntityId1679583365997 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view feed_items; + + alter table mail_message rename column contact_entity_id to entity_id; + + create or replace view feed_items (id, created_at, entity_id, type) as + SELECT note.id, + note.created_at, + note.entity_id, + case when note.recipient_id is null then 'note'::text else 'chat'::text end as type + FROM note + UNION + SELECT task.id, + task.created_at, + task.entity_id, + 'task'::text as type + FROM task + UNION + SELECT activity.id, + activity.created_at, + activity.entity_id, + 'activity'::text as type + FROM activity + UNION + (SELECT max(message.id) as id, + max(message.date) as created_at, + message.entity_id as entity_id, + 'mail'::text as type + FROM mail_message message + WHERE message.entity_id is not null + GROUP BY message.thread_id, message.entity_id + ORDER BY (max(message.date)) desc) + UNION + (SELECT max(message.id) as id, + max(message.date) as created_at, + el.source_id as entity_id, + 'mail'::text as type + FROM mail_message message + RIGHT JOIN entity_link el ON el.target_id = message.entity_id + WHERE message.entity_id is not null + GROUP BY message.thread_id, el.source_id + ORDER BY (max(message.date)) desc) + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1679929245833-AddUserAvatarId.ts b/backend/src/database/migrations/archive/1679929245833-AddUserAvatarId.ts new file mode 100644 index 0000000..71b455f --- /dev/null +++ b/backend/src/database/migrations/archive/1679929245833-AddUserAvatarId.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUserAvatarId1679929245833 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users + drop column avatar_url, + add column avatar_id uuid; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1679931642588-AddActiveToField.ts b/backend/src/database/migrations/archive/1679931642588-AddActiveToField.ts new file mode 100644 index 0000000..40bda8e --- /dev/null +++ b/backend/src/database/migrations/archive/1679931642588-AddActiveToField.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddActiveToField1679931642588 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field + add column active boolean default true not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1679931904542-AddAccountLogo.ts b/backend/src/database/migrations/archive/1679931904542-AddAccountLogo.ts new file mode 100644 index 0000000..7c6f85e --- /dev/null +++ b/backend/src/database/migrations/archive/1679931904542-AddAccountLogo.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAccountLogo1679931904542 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account add column logo_id uuid; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1680499051021-AddParticipantsToEntity.ts b/backend/src/database/migrations/archive/1680499051021-AddParticipantsToEntity.ts new file mode 100644 index 0000000..64e1131 --- /dev/null +++ b/backend/src/database/migrations/archive/1680499051021-AddParticipantsToEntity.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddParticipantsToEntity1680499051021 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity + add column participant_ids jsonb; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1680763220747-MigrateProjects.ts b/backend/src/database/migrations/archive/1680763220747-MigrateProjects.ts new file mode 100644 index 0000000..8161a1c --- /dev/null +++ b/backend/src/database/migrations/archive/1680763220747-MigrateProjects.ts @@ -0,0 +1,160 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MigrateProjects1680763220747 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board + add column need_migration boolean default false; + + insert into board(id, name, type, record_id, account_id, created_at, sort_order, is_system, code, + need_migration) + select nextval('board_id_seq'::regclass) as board_id, + 'Project tasks', + 'entity', + eb.entity_id, + eb.account_id, + now(), + 0, + false, + null, + true + from (select e.id as entity_id, b.id as board_id, e.account_id as account_id + from entity e + inner join entity_type et on et.id = e.entity_type_id and et.entity_category = 'project' + left join board b on b.type = 'entity' and b.record_id = e.id) eb + where eb.board_id is null; + +-- Add Stages + insert into stage (id, name, color, code, is_system, sort_order, board_id, account_id, created_at) + select nextval('stage_id_seq'::regclass), + 'To Do', + '#555', + null, + false, + 0, + b.id, + b.account_id, + now() + from board b + where b.need_migration = true; + + insert into stage (id, name, color, code, is_system, sort_order, board_id, account_id, created_at) + select nextval('stage_id_seq'::regclass), + 'In Progress', + '#555', + null, + false, + 1, + b.id, + b.account_id, + now() + from board b + where b.need_migration = true; + + insert into stage (id, name, color, code, is_system, sort_order, board_id, account_id, created_at) + select nextval('stage_id_seq'::regclass), + 'Done', + '#555', + 'done', + true, + 2, + b.id, + b.account_id, + now() + from board b + where b.need_migration = true; + `); + + await queryRunner.query(` + alter table entity_type + add column need_migration boolean default false; + + update entity_type + set need_migration = true + where entity_type.id in (select et.id + from entity_type et + left join field f on et.id = f.entity_type_id and f.code = 'value' + where f.id is null + and et.entity_category = 'project'); + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Value', + 'value', + 0, + et.id, + null, + et.account_id, + now(), + 'value', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Start date', + 'date', + 1, + et.id, + null, + et.account_id, + now(), + 'start_date', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'End date', + 'date', + 2, + et.id, + null, + et.account_id, + now(), + 'end_date', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Participants', + 'participants', + 3, + et.id, + null, + et.account_id, + now(), + 'participants', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Description', + 'text', + 4, + et.id, + null, + et.account_id, + now(), + 'description', + true + from entity_type et + where et.need_migration = true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681141545739-AddEmailActionSettings.ts b/backend/src/database/migrations/archive/1681141545739-AddEmailActionSettings.ts new file mode 100644 index 0000000..a06a029 --- /dev/null +++ b/backend/src/database/migrations/archive/1681141545739-AddEmailActionSettings.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEmailActionSettings1681141545739 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table email_action_settings + ( + action_id integer, + subject varchar not null, + content varchar, + send_as_html boolean not null, + mailbox_id integer, + user_id integer, + account_id integer not null, + primary key (action_id), + foreign key (action_id) references action (id) on delete cascade, + foreign key (mailbox_id) references mailbox (id) on delete cascade, + foreign key (user_id) references users (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681224301468-AddProjectFields.ts b/backend/src/database/migrations/archive/1681224301468-AddProjectFields.ts new file mode 100644 index 0000000..e6a8fb2 --- /dev/null +++ b/backend/src/database/migrations/archive/1681224301468-AddProjectFields.ts @@ -0,0 +1,104 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProjectFields1681224301468 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update entity_type + set need_migration = false + where need_migration = true; + + update board + set need_migration = false + where need_migration = true; + `); + + await queryRunner.query(` + update entity_type + set need_migration = true + where entity_type.id in (select et.id + from entity_type et + left join field f on et.id = f.entity_type_id and f.code = 'value' + where f.id is null + and et.entity_category = 'project'); + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Value', + 'value', + 0, + et.id, + null, + et.account_id, + now(), + 'value', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Start date', + 'date', + 1, + et.id, + null, + et.account_id, + now(), + 'start_date', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'End date', + 'date', + 2, + et.id, + null, + et.account_id, + now(), + 'end_date', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Participants', + 'participants', + 3, + et.id, + null, + et.account_id, + now(), + 'participants', + true + from entity_type et + where et.need_migration = true; + + insert into field (id, name, type, sort_order, entity_type_id, field_group_id, account_id, created_at, code, + active) + select nextval('field_id_seq'::regclass), + 'Description', + 'text', + 4, + et.id, + null, + et.account_id, + now(), + 'description', + true + from entity_type et + where et.need_migration = true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681289039535-AddEntityListSettings.ts b/backend/src/database/migrations/archive/1681289039535-AddEntityListSettings.ts new file mode 100644 index 0000000..bec18e9 --- /dev/null +++ b/backend/src/database/migrations/archive/1681289039535-AddEntityListSettings.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEntityListSettings1681289039535 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table entity_list_settings ( + id integer, + entity_type_id integer not null, + board_id integer, + settings jsonb not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (entity_type_id) references entity_type(id) on delete cascade, + foreign key (board_id) references board(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists entity_list_settings_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681483040117-AddScheduledMailMessage.ts b/backend/src/database/migrations/archive/1681483040117-AddScheduledMailMessage.ts new file mode 100644 index 0000000..8db1996 --- /dev/null +++ b/backend/src/database/migrations/archive/1681483040117-AddScheduledMailMessage.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddScheduledMailMessage1681483040117 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table scheduled_mail_message + ( + id integer, + send_to jsonb not null, + subject character varying not null, + content character varying, + send_as_html boolean not null, + file_ids jsonb not null, + sent_at timestamp without time zone, + mailbox_id integer not null, + user_id integer not null, + entity_id integer, + action_id integer, + account_id integer not null, + primary key (id), + foreign key (mailbox_id) references mailbox (id) on delete cascade, + foreign key (user_id) references users (id) on delete cascade, + foreign key (entity_id) references entity (id) on delete set null, + foreign key (action_id) references action (id) on delete set null, + foreign key (account_id) references account (id) on delete cascade + ); + + create sequence if not exists scheduled_mail_message_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681732037710-AddEmailsPerDayColumnToMailbox.ts b/backend/src/database/migrations/archive/1681732037710-AddEmailsPerDayColumnToMailbox.ts new file mode 100644 index 0000000..0d8e347 --- /dev/null +++ b/backend/src/database/migrations/archive/1681732037710-AddEmailsPerDayColumnToMailbox.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEmailsPerDayColumnToMailbox1681732037710 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + add column emails_per_day smallint default null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681828967422-AddWeightToTaskAndActivity.ts b/backend/src/database/migrations/archive/1681828967422-AddWeightToTaskAndActivity.ts new file mode 100644 index 0000000..4e84704 --- /dev/null +++ b/backend/src/database/migrations/archive/1681828967422-AddWeightToTaskAndActivity.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddWeightToTaskAndActivity1681828967422 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE activity ADD COLUMN weight double precision; + WITH ordered_activity AS ( + SELECT + id, + ROW_NUMBER() OVER (ORDER BY created_at DESC) * 100 AS new_weight + FROM + activity + ) + UPDATE activity + SET weight = ordered_activity.new_weight + FROM ordered_activity + WHERE activity.id = ordered_activity.id; + ALTER TABLE activity ALTER COLUMN weight SET NOT NULL; + + ALTER TABLE task ADD COLUMN weight double precision; + WITH ordered_task AS ( + SELECT + id, + ROW_NUMBER() OVER (ORDER BY created_at DESC) * 100 AS new_weight + FROM + task + ) + UPDATE task + SET weight = ordered_task.new_weight + FROM ordered_task + WHERE task.id = ordered_task.id; + ALTER TABLE task ALTER COLUMN weight SET NOT NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681832187113-AddWeightToAllTasks.ts b/backend/src/database/migrations/archive/1681832187113-AddWeightToAllTasks.ts new file mode 100644 index 0000000..d6fe621 --- /dev/null +++ b/backend/src/database/migrations/archive/1681832187113-AddWeightToAllTasks.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddWeightToAllTasks1681832187113 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view all_tasks; + + create or replace view all_tasks ( + id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + weight, + activity_type_id, + title, + planned_time, + settings_id, + type + ) as + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + weight, + activity_type_id, + null as title, + null as planned_time, + null as settings_id, + 'activity' as type + from activity + union + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + null as result, + entity_id, + weight, + null as activity_type_id, + title, + planned_time, + settings_id, + 'task' as type + from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1681900142878-ResetWeightForTasksAndActivities.ts b/backend/src/database/migrations/archive/1681900142878-ResetWeightForTasksAndActivities.ts new file mode 100644 index 0000000..6da9144 --- /dev/null +++ b/backend/src/database/migrations/archive/1681900142878-ResetWeightForTasksAndActivities.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ResetWeightForTasksAndActivities1681900142878 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + WITH ordered_tasks AS ( + SELECT + id, + ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY created_at DESC) * 100 AS new_weight + FROM + all_tasks + ) + UPDATE activity + SET weight = ordered_tasks.new_weight + FROM ordered_tasks + WHERE activity.id = ordered_tasks.id; + WITH ordered_tasks AS ( + SELECT + id, + ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY created_at DESC) * 100 AS new_weight + FROM + all_tasks + ) + UPDATE task + SET weight = ordered_tasks.new_weight + FROM ordered_tasks + WHERE task.id = ordered_tasks.id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682002593036-AddEntityWeight.ts b/backend/src/database/migrations/archive/1682002593036-AddEntityWeight.ts new file mode 100644 index 0000000..ca6c8e6 --- /dev/null +++ b/backend/src/database/migrations/archive/1682002593036-AddEntityWeight.ts @@ -0,0 +1,29 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEntityWeight1682002593036 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE entity ADD COLUMN weight double precision; + + WITH ordered_entity AS ( + SELECT + e.id, + ROW_NUMBER() OVER (PARTITION BY e.account_id, e.entity_type_id, s.board_id ORDER BY e.created_at DESC, e.id DESC) * 100 AS new_weight + FROM + entity e + LEFT JOIN stage s ON e.stage_id = s.id + ) + UPDATE entity + SET weight = ordered_entity.new_weight + FROM ordered_entity + WHERE entity.id = ordered_entity.id; + + ALTER TABLE entity ALTER COLUMN weight SET NOT NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682083589639-AddAccountIdTETFeature.ts b/backend/src/database/migrations/archive/1682083589639-AddAccountIdTETFeature.ts new file mode 100644 index 0000000..71fc4e0 --- /dev/null +++ b/backend/src/database/migrations/archive/1682083589639-AddAccountIdTETFeature.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAccountIdTETFeature1682083589639 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity_type_feature + add account_id integer, + add foreign key (account_id) references account(id) on delete cascade; + + update entity_type_feature as etf + set (account_id) = (select et.account_id from entity_type et where et.id = etf.entity_type_id); + + alter table entity_type_feature + alter column account_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682348048312-AddRMS.ts b/backend/src/database/migrations/archive/1682348048312-AddRMS.ts new file mode 100644 index 0000000..dd48f6f --- /dev/null +++ b/backend/src/database/migrations/archive/1682348048312-AddRMS.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRMS1682348048312 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table industry + ( + code varchar not null, + name varchar not null, + color varchar not null, + sort_order smallint not null, + active boolean not null, + primary key (code) + ); + + create table ready_made_solution + ( + code varchar not null, + name varchar not null, + subdomain varchar not null, + sort_order smallint not null, + active boolean not null, + industry_code varchar not null, + primary key (code), + foreign key (industry_code) references industry (code) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682350735553-AddIndustries.ts b/backend/src/database/migrations/archive/1682350735553-AddIndustries.ts new file mode 100644 index 0000000..e66570a --- /dev/null +++ b/backend/src/database/migrations/archive/1682350735553-AddIndustries.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIndustries1682350735553 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into industry(code, name, color, sort_order, active) + values ('it_and_development', 'IT & Development', '#FF8D07', 0, true), + ('construction_and_engineering', 'Construction and engineering', '#A33CAB', 1, true), + ('advertising_and_marketing', 'Advertising & Marketing', '#67E2F9', 2, true), + ('consulting_and_outsourcing', 'Consulting and outsourcing', '#8AF039', 3, true), + ('manufacturing', 'Manufacturing', '#EC008C', 4, true), + ('education', 'Education', '#3D8FEC', 5, true); + `); + + await queryRunner.query(` + alter table ready_made_solution + alter column industry_code drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682433824946-AddDemoMarker.ts b/backend/src/database/migrations/archive/1682433824946-AddDemoMarker.ts new file mode 100644 index 0000000..01f2af8 --- /dev/null +++ b/backend/src/database/migrations/archive/1682433824946-AddDemoMarker.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDemoMarker1682433824946 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users + add column is_demo boolean not null default false; + + alter table entity + add column is_demo boolean not null default false; + + alter table account_settings + add column has_demo boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682507153940-AddDocumentTemplate.ts b/backend/src/database/migrations/archive/1682507153940-AddDocumentTemplate.ts new file mode 100644 index 0000000..304b66f --- /dev/null +++ b/backend/src/database/migrations/archive/1682507153940-AddDocumentTemplate.ts @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDocumentTemplate1682507153940 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists document_template_id_seq as integer minvalue 1; + + create table document_template ( + id integer, + name character varying not null, + created_by integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (created_by) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create table document_template_access ( + document_template_id integer, + user_id integer, + account_id integer not null, + primary key (document_template_id, user_id), + foreign key (document_template_id) references document_template(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create table document_template_entity_type ( + document_template_id integer, + entity_type_id integer, + account_id integer not null, + primary key (document_template_id, entity_type_id), + foreign key (document_template_id) references document_template(id) on delete cascade, + foreign key (entity_type_id) references entity_type(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682518277268-DeleteFileInfoWithUser.ts b/backend/src/database/migrations/archive/1682518277268-DeleteFileInfoWithUser.ts new file mode 100644 index 0000000..f5c92f4 --- /dev/null +++ b/backend/src/database/migrations/archive/1682518277268-DeleteFileInfoWithUser.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteFileInfoWithUser1682518277268 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_info + drop constraint file_info_created_by_fkey; + + alter table file_info + add foreign key (created_by) references users on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1682589692981-AddIndexes.ts b/backend/src/database/migrations/archive/1682589692981-AddIndexes.ts new file mode 100644 index 0000000..f5be3b0 --- /dev/null +++ b/backend/src/database/migrations/archive/1682589692981-AddIndexes.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIndexes1682589692981 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX task_is_resolved_responsible_user_id_start_date_idx + ON task (is_resolved, responsible_user_id, start_date); + + CREATE INDEX activity_is_resolved_responsible_user_id_start_date_idx + ON activity (is_resolved, responsible_user_id, start_date); + + CREATE INDEX notification_type_settings_settings_id_type_is_enabled_idx + ON notification_type_settings (settings_id, type, is_enabled); + + CREATE INDEX mailbox_folder_account_id_mailbox_id_idx + ON mailbox_folder (account_id, mailbox_id); + + CREATE INDEX field_value_entity_id_idx ON field_value(entity_id); + + CREATE INDEX field_option_field_id_idx ON field_option(field_id); + + CREATE INDEX entity_link_source_id_sort_order_id_idx ON entity_link(source_id, sort_order, id); + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1683016482581-AddIndexes.ts b/backend/src/database/migrations/archive/1683016482581-AddIndexes.ts new file mode 100644 index 0000000..9677121 --- /dev/null +++ b/backend/src/database/migrations/archive/1683016482581-AddIndexes.ts @@ -0,0 +1,54 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIndexes1683016482581 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX task_is_resolved_end_date_idx ON task(is_resolved, end_date); + CREATE INDEX task_entity_id_is_resolved_created_by_idx ON task(entity_id int4_ops, is_resolved bool_ops, created_by int4_ops); + + CREATE INDEX activity_is_resolved_end_date_idx ON activity(is_resolved, end_date); + + CREATE INDEX scheduled_action_scheduled_time_completed_idx ON scheduled_action(scheduled_time, completed); + + CREATE INDEX field_entity_type_id_type_idx ON field(entity_type_id int4_ops, type text_ops); + CREATE INDEX entity_entity_type_id_stage_id_idx ON entity(entity_type_id int4_ops, stage_id int4_ops); + + CREATE INDEX user_object_permission_user_id_object_permission_id_idx ON user_object_permission(user_id int4_ops, object_permission_id int4_ops); + CREATE INDEX object_permission_object_type_object_id_idx ON object_permission(object_type text_ops, object_id int4_ops); + + CREATE INDEX notification_settings_user_id_idx ON notification_settings(user_id int4_ops); + + CREATE INDEX users_account_id_department_id_idx ON users(account_id int4_ops, department_id int4_ops); + + CREATE INDEX subtask_account_id_task_id_idx ON subtask(account_id int4_ops, task_id int4_ops); + + CREATE INDEX file_link_account_id_source_type_source_id_idx ON file_link(account_id int4_ops, source_type text_ops, source_id int4_ops); + + CREATE INDEX stage_board_id_idx ON stage(board_id int4_ops); + + CREATE INDEX board_account_id_idx ON board(account_id int4_ops); + CREATE INDEX board_type_idx ON board(type text_ops); + + CREATE INDEX note_entity_id_idx ON note(entity_id int4_ops); + CREATE INDEX task_entity_id_idx ON task(entity_id int4_ops); + CREATE INDEX activity_entity_id_idx ON activity(entity_id int4_ops); + CREATE INDEX mail_message_entity_id_idx ON mail_message(entity_id int4_ops); + CREATE INDEX entity_link_target_id_idx ON entity_link(target_id int4_ops); + CREATE INDEX entity_link_source_id_idx ON entity_link(source_id int4_ops); + + CREATE INDEX department_account_id_parent_id_idx ON department(account_id int4_ops, parent_id int4_ops); + + CREATE INDEX exact_time_trigger_settings_date_idx ON exact_time_trigger_settings(date timestamp_ops); + + CREATE INDEX notification_account_user_seen_idx ON notification(account_id, user_id, is_seen); + + CREATE INDEX field_value_account_id_idx ON field_value(account_id); + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1683205194466-AddTaskBoardIdToBoard.ts b/backend/src/database/migrations/archive/1683205194466-AddTaskBoardIdToBoard.ts new file mode 100644 index 0000000..610bd62 --- /dev/null +++ b/backend/src/database/migrations/archive/1683205194466-AddTaskBoardIdToBoard.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaskBoardIdToBoard1683205194466 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board + add column task_board_id integer default null, + add foreign key (task_board_id) references board(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1683517583707-DeleteProjectEntityBoards.ts b/backend/src/database/migrations/archive/1683517583707-DeleteProjectEntityBoards.ts new file mode 100644 index 0000000..ea31d01 --- /dev/null +++ b/backend/src/database/migrations/archive/1683517583707-DeleteProjectEntityBoards.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteProjectEntityBoards1683517583707 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from board + where type = 'entity'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1683731671898-AddFeatureDocuments.ts b/backend/src/database/migrations/archive/1683731671898-AddFeatureDocuments.ts new file mode 100644 index 0000000..49accb4 --- /dev/null +++ b/backend/src/database/migrations/archive/1683731671898-AddFeatureDocuments.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddFeatureDocuments1683731671898 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into feature("name", "code", "is_enabled") values('Create Documents', 'documents', TRUE); + + insert into entity_type_feature (entity_type_id, feature_id, account_id) + select id as entity_type_id, currval('feature_id_seq') as feature_id, account_id + from entity_type; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1683797890507-AddDocumentTemplateGenerated.ts b/backend/src/database/migrations/archive/1683797890507-AddDocumentTemplateGenerated.ts new file mode 100644 index 0000000..1762da6 --- /dev/null +++ b/backend/src/database/migrations/archive/1683797890507-AddDocumentTemplateGenerated.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDocumentTemplateGenerated1683797890507 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table document_template add column created_count integer NOT NULL DEFAULT 0; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1683802969000-AddDocumentInFeed.ts b/backend/src/database/migrations/archive/1683802969000-AddDocumentInFeed.ts new file mode 100644 index 0000000..1477f32 --- /dev/null +++ b/backend/src/database/migrations/archive/1683802969000-AddDocumentInFeed.ts @@ -0,0 +1,42 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDocumentInFeed1683802969000 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view feed_items; + + create or replace view feed_items (id, created_at, entity_id, type) as + select note.id, note.created_at, note.entity_id, case when note.recipient_id is null then 'note'::text else 'chat'::text end as type + from note + union + select task.id, task.created_at, task.entity_id, 'task'::text as type + from task + union + select activity.id, activity.created_at, activity.entity_id, 'activity'::text as type + from activity + union + (select max(message.id) as id, max(message.date) as created_at, message.entity_id as entity_id, 'mail'::text as type + from mail_message message + where message.entity_id is not null + group by message.thread_id, message.entity_id + order by (max(message.date)) desc) + union + (select max(message.id) as id, max(message.date) as created_at, el.source_id as entity_id, 'mail'::text as type + from mail_message message + right join entity_link el ON el.target_id = message.entity_id + where message.entity_id is not null + group by message.thread_id, el.source_id + order by (max(message.date)) desc) + union + select fl.id, fl.created_at, fl.source_id, 'document'::text as type + from file_link fl + where fl.source_type = 'entity_document' + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1683875863921-TrimUsersNames.ts b/backend/src/database/migrations/archive/1683875863921-TrimUsersNames.ts new file mode 100644 index 0000000..130a638 --- /dev/null +++ b/backend/src/database/migrations/archive/1683875863921-TrimUsersNames.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TrimUsersNames1683875863921 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update users + set first_name = trim (first_name), + last_name = trim (last_name); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1684249775346-AddAllTasksStageId.ts b/backend/src/database/migrations/archive/1684249775346-AddAllTasksStageId.ts new file mode 100644 index 0000000..d64f8d8 --- /dev/null +++ b/backend/src/database/migrations/archive/1684249775346-AddAllTasksStageId.ts @@ -0,0 +1,77 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAllTasksStageId1684249775346 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view all_tasks; + + create or replace view all_tasks ( + id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + weight, + activity_type_id, + title, + planned_time, + settings_id, + stage_id, + type + ) as + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + weight, + activity_type_id, + null as title, + null as planned_time, + null as settings_id, + null as stage_id, + 'activity' as type + from activity + union + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + null as result, + entity_id, + weight, + null as activity_type_id, + title, + planned_time, + settings_id, + stage_id, + 'task' as type + from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1684317847183-AddCreatedByToFileLink.ts b/backend/src/database/migrations/archive/1684317847183-AddCreatedByToFileLink.ts new file mode 100644 index 0000000..61c469b --- /dev/null +++ b/backend/src/database/migrations/archive/1684317847183-AddCreatedByToFileLink.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class addCreatedByToFileLink1684317847183 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_link + add column created_by integer, + add foreign key (created_by) references users(id) on delete cascade; + + update file_link set created_by = file_info.created_by from file_info where file_info.id = file_link.file_id; + + alter table file_link alter column created_by set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1685001497108-ChangeFileInfoHash.ts b/backend/src/database/migrations/archive/1685001497108-ChangeFileInfoHash.ts new file mode 100644 index 0000000..db78796 --- /dev/null +++ b/backend/src/database/migrations/archive/1685001497108-ChangeFileInfoHash.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeFileInfoHash1685001497108 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_info + drop column hash_md5, + add column hash_sha256 character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1685595302584-AddParticipantsToBoard.ts b/backend/src/database/migrations/archive/1685595302584-AddParticipantsToBoard.ts new file mode 100644 index 0000000..40ea073 --- /dev/null +++ b/backend/src/database/migrations/archive/1685595302584-AddParticipantsToBoard.ts @@ -0,0 +1,36 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddParticipantsToBoard1685595302584 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board + add owner_id integer, + add foreign key (owner_id) references users (id) on + delete + set null; + + alter table board + add participant_ids jsonb; + + UPDATE board + SET owner_id = subquery.owner_id FROM (SELECT u.id as owner_id, u.account_id FROM users u where u.role = 'owner' group by u.account_id, u.id) subquery + WHERE board.account_id = subquery.account_id + and board.type = 'task' + and board.code is null; + + UPDATE board + SET participant_ids = subquery.participants FROM (SELECT json_agg(u.id) as participants, u.account_id + FROM users u + WHERE u.is_active = true + GROUP BY account_id) AS subquery + WHERE board.account_id = subquery.account_id + and board.type = 'task' + and board.code is null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1685604837960-AddChatModel.ts b/backend/src/database/migrations/archive/1685604837960-AddChatModel.ts new file mode 100644 index 0000000..23b0c1d --- /dev/null +++ b/backend/src/database/migrations/archive/1685604837960-AddChatModel.ts @@ -0,0 +1,109 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatModel1685604837960 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists chat_provider_id_seq as integer minvalue 1; + create table chat_provider ( + id integer, + created_by integer not null, + type character varying not null, + title character varying, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (created_by) references users(id), + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists chat_provider_user_id_seq as integer minvalue 1; + create table chat_provider_user ( + id integer, + provider_id integer not null, + user_id integer not null, + account_id integer, + primary key (id), + foreign key (provider_id) references chat_provider(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists chat_id_seq as integer minvalue 1; + create table chat ( + id integer, + provider_id integer not null, + created_by integer not null, + external_id character varying, + type character varying not null, + title character varying, + entity_id integer, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (provider_id) references chat_provider(id), + foreign key (created_by) references users(id), + foreign key (entity_id) references entity(id) on delete set null, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists chat_user_id_seq as integer minvalue 1; + create table chat_user ( + id integer, + chat_id integer not null, + user_id integer not null, + external_id character varying, + role character varying not null, + account_id integer not null, + primary key (id), + foreign key (chat_id) references chat(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists chat_message_id_seq as integer minvalue 1; + create table chat_message ( + id integer, + chat_id integer not null, + chat_user_id integer not null, + external_id character varying, + text character varying not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (chat_id) references chat(id) on delete cascade, + foreign key (chat_user_id) references chat_user(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists chat_message_file_id_seq as integer minvalue 1; + create table chat_message_file ( + id integer, + message_id integer not null, + external_id character varying, + file_link_id integer, + account_id integer not null, + primary key (id), + foreign key (message_id) references chat_message(id) on delete cascade, + foreign key (file_link_id) references file_link(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create table chat_message_user_status ( + message_id integer not null, + chat_user_id integer not null, + status character varying not null, + account_id integer not null, + created_at timestamp without time zone not null, + foreign key (message_id) references chat_message(id) on delete cascade, + foreign key (chat_user_id) references chat_user(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade, + primary key (message_id, chat_user_id) + ); + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1685689401123-AddDefaultChatProvider.ts b/backend/src/database/migrations/archive/1685689401123-AddDefaultChatProvider.ts new file mode 100644 index 0000000..728302d --- /dev/null +++ b/backend/src/database/migrations/archive/1685689401123-AddDefaultChatProvider.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDefaultChatProvider1685689401123 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into chat_provider + select + nextval('chat_provider_id_seq') as id, + users.id as created_by, + 'amwork' as type, + account.company_name || ' Chat' as title, + account.id as account_id, + account.created_at as created_at + from account join users on users.account_id = account.id and users.role = 'owner'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686048795624-AddBoardIdToTask.ts b/backend/src/database/migrations/archive/1686048795624-AddBoardIdToTask.ts new file mode 100644 index 0000000..bdefdf8 --- /dev/null +++ b/backend/src/database/migrations/archive/1686048795624-AddBoardIdToTask.ts @@ -0,0 +1,87 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddBoardIdToTask1686048795624 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task + add column board_id integer, + add foreign key (board_id) references board(id) on delete cascade; + + update task + set board_id = (select s.board_id from stage s where s.id = task.stage_id); + + drop view all_tasks; + + create or replace view all_tasks ( + id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + weight, + activity_type_id, + title, + planned_time, + settings_id, + board_id, + stage_id, + type + ) as + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + result, + entity_id, + weight, + activity_type_id, + null as title, + null as planned_time, + null as settings_id, + null as board_id, + null as stage_id, + 'activity' as type + from activity + union + select id, + created_at, + account_id, + created_by, + responsible_user_id, + text, + start_date, + end_date, + is_resolved, + resolved_date, + null as result, + entity_id, + weight, + null as activity_type_id, + title, + planned_time, + settings_id, + board_id, + stage_id, + 'task' as type + from task + order by id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686061937533-AlterChatMEssageFile.ts b/backend/src/database/migrations/archive/1686061937533-AlterChatMEssageFile.ts new file mode 100644 index 0000000..66112a9 --- /dev/null +++ b/backend/src/database/migrations/archive/1686061937533-AlterChatMEssageFile.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatMEssageFile1686061937533 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_message_file + drop column file_link_id, + add column file_id uuid, + add column name character varying not null, + add column mime_type character varying not null, + add column size integer not null, + add column created_at timestamp without time zone not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686297344564-AlterMailMessageEntityId.ts b/backend/src/database/migrations/archive/1686297344564-AlterMailMessageEntityId.ts new file mode 100644 index 0000000..aa425d5 --- /dev/null +++ b/backend/src/database/migrations/archive/1686297344564-AlterMailMessageEntityId.ts @@ -0,0 +1,17 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailMessageEntityId1686297344564 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + + alter table mail_message + drop constraint mail_message_contact_entity_id_fkey, + add constraint mail_message_contact_entity_id_fkey foreign key (entity_id) references entity(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686310775887-AddSignatureToEmailActionSettings.ts b/backend/src/database/migrations/archive/1686310775887-AddSignatureToEmailActionSettings.ts new file mode 100644 index 0000000..9c51463 --- /dev/null +++ b/backend/src/database/migrations/archive/1686310775887-AddSignatureToEmailActionSettings.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSignatureToEmailActionSettings1686310775887 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table email_action_settings + add signature varchar; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686643536303-AddReplayToInChatMessage.ts b/backend/src/database/migrations/archive/1686643536303-AddReplayToInChatMessage.ts new file mode 100644 index 0000000..b7046e9 --- /dev/null +++ b/backend/src/database/migrations/archive/1686643536303-AddReplayToInChatMessage.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddReplayToInChatMessage1686643536303 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_message + add column replay_to_id integer, + add foreign key (replay_to_id) references chat_message(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686736715335-AddChatPinnedMessage.ts b/backend/src/database/migrations/archive/1686736715335-AddChatPinnedMessage.ts new file mode 100644 index 0000000..caf9198 --- /dev/null +++ b/backend/src/database/migrations/archive/1686736715335-AddChatPinnedMessage.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatPinnedMessage1686736715335 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat + add column pinned_message_id integer, + add foreign key (pinned_message_id) references chat_message(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686816157824-AddChatPinnedMessage.ts b/backend/src/database/migrations/archive/1686816157824-AddChatPinnedMessage.ts new file mode 100644 index 0000000..80322ea --- /dev/null +++ b/backend/src/database/migrations/archive/1686816157824-AddChatPinnedMessage.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatPinnedMessage1686816157824 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table chat_pinned_message ( + chat_id integer, + message_id integer, + created_at timestamp without time zone not null, + account_id integer not null, + primary key (chat_id, message_id), + foreign key (chat_id) references chat(id) on delete cascade, + foreign key (message_id) references chat_message(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + alter table chat drop column pinned_message_id; + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686824143539-AddChatMessageReaction.ts b/backend/src/database/migrations/archive/1686824143539-AddChatMessageReaction.ts new file mode 100644 index 0000000..71785cf --- /dev/null +++ b/backend/src/database/migrations/archive/1686824143539-AddChatMessageReaction.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatMessageReaction1686824143539 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists chat_message_reaction_id_seq as integer minvalue 1; + + create table chat_message_reaction ( + id integer, + message_id integer not null, + chat_user_id integer not null, + reaction character varying not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (message_id) references chat_message(id) on delete cascade, + foreign key (chat_user_id) references chat_user(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686840724427-AddProducts.ts b/backend/src/database/migrations/archive/1686840724427-AddProducts.ts new file mode 100644 index 0000000..8acceb5 --- /dev/null +++ b/backend/src/database/migrations/archive/1686840724427-AddProducts.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProducts1686840724427 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table product_category + ( + id integer, + name character varying not null, + parent_id integer, + created_by integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (parent_id) references product_category (id), + foreign key (created_by) references users (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence product_category_id_seq as integer minvalue 1; + + create table product + ( + id integer, + name character varying not null, + description character varying, + sku character varying, + unit character varying, + tax smallint, + isDeleted boolean not null, + category_id integer, + created_by integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (category_id) references product_category (id), + foreign key (created_by) references users (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence product_id_seq as integer minvalue 1; + + create table product_price + ( + id integer, + name character varying, + unit_price integer, + currency character varying(3) not null, + product_id integer not null, + account_id integer not null, + primary key (id), + foreign key (product_id) references product (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + create sequence product_price_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686904432256-AddChatIdToStatus.ts b/backend/src/database/migrations/archive/1686904432256-AddChatIdToStatus.ts new file mode 100644 index 0000000..7934a00 --- /dev/null +++ b/backend/src/database/migrations/archive/1686904432256-AddChatIdToStatus.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatIdToStatus1686904432256 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table chat_message_user_status; + + create table chat_message_user_status ( + chat_id integer not null, + message_id integer not null, + chat_user_id integer not null, + status character varying not null, + account_id integer not null, + created_at timestamp without time zone not null, + foreign key (chat_id) references chat(id) on delete cascade, + foreign key (message_id) references chat_message(id) on delete cascade, + foreign key (chat_user_id) references chat_user(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade, + primary key (chat_id, message_id, chat_user_id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1686930758334-RenameProductField.ts b/backend/src/database/migrations/archive/1686930758334-RenameProductField.ts new file mode 100644 index 0000000..62b5be9 --- /dev/null +++ b/backend/src/database/migrations/archive/1686930758334-RenameProductField.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameProductField1686930758334 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product + rename column isdeleted to is_deleted; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687015795997-AlterChatMessageReply.ts b/backend/src/database/migrations/archive/1687015795997-AlterChatMessageReply.ts new file mode 100644 index 0000000..2bc01b2 --- /dev/null +++ b/backend/src/database/migrations/archive/1687015795997-AlterChatMessageReply.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatMessageReply1687015795997 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_message + drop column replay_to_id, + add column reply_to_id integer, + add foreign key (reply_to_id) references chat_message(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687350416742-RemoveIdFromSubscription.ts b/backend/src/database/migrations/archive/1687350416742-RemoveIdFromSubscription.ts new file mode 100644 index 0000000..a7a8dbf --- /dev/null +++ b/backend/src/database/migrations/archive/1687350416742-RemoveIdFromSubscription.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveIdFromSubscription1687350416742 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table subscription + drop column id, + add primary key (account_id); + + drop sequence subscription_id_seq; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687351857599-AddSubscriptionExternalCustomerId.ts b/backend/src/database/migrations/archive/1687351857599-AddSubscriptionExternalCustomerId.ts new file mode 100644 index 0000000..d23e1fc --- /dev/null +++ b/backend/src/database/migrations/archive/1687351857599-AddSubscriptionExternalCustomerId.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSubscriptionExternalCustomerId1687351857599 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table subscription add column external_customer_id character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687790975332-AddOrder.ts b/backend/src/database/migrations/archive/1687790975332-AddOrder.ts new file mode 100644 index 0000000..6c639ea --- /dev/null +++ b/backend/src/database/migrations/archive/1687790975332-AddOrder.ts @@ -0,0 +1,45 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddOrder1687790975332 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table orders + ( + id integer, + total_amount numeric(13, 2) not null, + currency character varying(3) not null, + entity_id integer not null, + created_by integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (entity_id) references entity (id) on delete cascade, + foreign key (created_by) references users (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence order_id_seq as integer minvalue 1; + + create table order_item + ( + id integer, + unit_price numeric(13, 2) not null, + quantity integer not null, + tax numeric(3, 2) not null, + discount numeric(3, 2) not null, + product_id integer not null, + order_id integer not null, + sort_order smallint not null, + account_id integer not null, + primary key (id), + foreign key (product_id) references product (id), + foreign key (order_id) references orders (id) on delete cascade, + foreign key (account_id) references account (id) on delete cascade + ); + create sequence order_item_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687793191931-FixOrder.ts b/backend/src/database/migrations/archive/1687793191931-FixOrder.ts new file mode 100644 index 0000000..54ba6ce --- /dev/null +++ b/backend/src/database/migrations/archive/1687793191931-FixOrder.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixOrder1687793191931 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders + alter column total_amount type numeric(15, 2) using total_amount::numeric(15, 2); + + alter table order_item + alter column unit_price type numeric(15, 2) using tax::numeric(15, 2), + alter column tax type numeric(5, 2) using tax::numeric(5, 2), + alter column discount type numeric(5, 2) using tax::numeric(5, 2); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687877020115-AddTaxIncluded.ts b/backend/src/database/migrations/archive/1687877020115-AddTaxIncluded.ts new file mode 100644 index 0000000..6a22c6a --- /dev/null +++ b/backend/src/database/migrations/archive/1687877020115-AddTaxIncluded.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTaxIncluded1687877020115 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders + add column tax_included boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687943824933-AddChatProviderTwilio.ts b/backend/src/database/migrations/archive/1687943824933-AddChatProviderTwilio.ts new file mode 100644 index 0000000..2356a7c --- /dev/null +++ b/backend/src/database/migrations/archive/1687943824933-AddChatProviderTwilio.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatProviderTwilio1687943824933 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table chat_provider_twilio ( + provider_id integer not null, + account_sid character varying not null, + auth_token character varying not null, + whatsapp_number character varying not null, + account_id integer not null, + primary key (provider_id), + foreign key (provider_id) references chat_provider(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687954149882-AlterChatProviderUser.ts b/backend/src/database/migrations/archive/1687954149882-AlterChatProviderUser.ts new file mode 100644 index 0000000..0d5d6b8 --- /dev/null +++ b/backend/src/database/migrations/archive/1687954149882-AlterChatProviderUser.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatProviderUser1687954149882 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider_user + drop column id, + add primary key (provider_id, user_id); + + drop sequence chat_provider_user_id_seq; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687962117509-AddWarehouse.ts b/backend/src/database/migrations/archive/1687962117509-AddWarehouse.ts new file mode 100644 index 0000000..5c7d0ce --- /dev/null +++ b/backend/src/database/migrations/archive/1687962117509-AddWarehouse.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddWarehouse1687962117509 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table warehouse + ( + id integer, + name character varying, + created_by integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (created_by) references users (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence warehouse_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1687965328992-AddIsDeletedToWarehouse.ts b/backend/src/database/migrations/archive/1687965328992-AddIsDeletedToWarehouse.ts new file mode 100644 index 0000000..aead5e9 --- /dev/null +++ b/backend/src/database/migrations/archive/1687965328992-AddIsDeletedToWarehouse.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIsDeletedToWarehouse1687965328992 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table warehouse + add column is_deleted boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688025794222-AlterChatUser.ts b/backend/src/database/migrations/archive/1688025794222-AlterChatUser.ts new file mode 100644 index 0000000..329d4ba --- /dev/null +++ b/backend/src/database/migrations/archive/1688025794222-AlterChatUser.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatUser1688025794222 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_user alter column user_id drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688044274695-AddStocks.ts b/backend/src/database/migrations/archive/1688044274695-AddStocks.ts new file mode 100644 index 0000000..aeb8067 --- /dev/null +++ b/backend/src/database/migrations/archive/1688044274695-AddStocks.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddStocks1688044274695 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table stock + ( + product_id integer not null, + warehouse_id integer not null, + stock_quantity integer not null, + account_id integer not null, + primary key (product_id, warehouse_id), + foreign key (product_id) references product (id), + foreign key (warehouse_id) references warehouse (id), + foreign key (account_id) references account (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688053486248-AddChatUserExternalName.ts b/backend/src/database/migrations/archive/1688053486248-AddChatUserExternalName.ts new file mode 100644 index 0000000..cd85e0f --- /dev/null +++ b/backend/src/database/migrations/archive/1688053486248-AddChatUserExternalName.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatUserExternalName1688053486248 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_user add column external_name character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688112039219-AlterChatProviderTwilio.ts b/backend/src/database/migrations/archive/1688112039219-AlterChatProviderTwilio.ts new file mode 100644 index 0000000..f5afc58 --- /dev/null +++ b/backend/src/database/migrations/archive/1688112039219-AlterChatProviderTwilio.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatProviderTwilio1688112039219 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider_twilio rename column whatsapp_number to phone_number; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688130606571-AlterChatProviderUser.ts b/backend/src/database/migrations/archive/1688130606571-AlterChatProviderUser.ts new file mode 100644 index 0000000..0b80ac1 --- /dev/null +++ b/backend/src/database/migrations/archive/1688130606571-AlterChatProviderUser.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatProviderUser1688130606571 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create type chat_provider_user_type as enum ('accessible', 'responsible'); + alter table chat_provider_user + add column type chat_provider_user_type not null default 'accessible', + alter column account_id set not null; + + alter table chat_provider_user + drop constraint chat_provider_user_pkey; + + alter table chat_provider_user + add constraint chat_provider_user_pkey primary key (provider_id, user_id, type); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688136613049-AlterChat.ts b/backend/src/database/migrations/archive/1688136613049-AlterChat.ts new file mode 100644 index 0000000..8625624 --- /dev/null +++ b/backend/src/database/migrations/archive/1688136613049-AlterChat.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChat1688136613049 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat + drop constraint chat_created_by_fkey, + alter column created_by drop not null, + add constraint chat_created_by_fkey foreign key (created_by) references users(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688138872050-AddOrderStatus.ts b/backend/src/database/migrations/archive/1688138872050-AddOrderStatus.ts new file mode 100644 index 0000000..5a9426b --- /dev/null +++ b/backend/src/database/migrations/archive/1688138872050-AddOrderStatus.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddOrderStatus1688138872050 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table order_status + ( + id integer, + name varchar(255) not null, + color varchar(50) not null, + code varchar(50), + sort_order smallint not null, + account_id integer not null, + primary key (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence order_status_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688139271540-AddShipment.ts b/backend/src/database/migrations/archive/1688139271540-AddShipment.ts new file mode 100644 index 0000000..8b86ce4 --- /dev/null +++ b/backend/src/database/migrations/archive/1688139271540-AddShipment.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddShipment1688139271540 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table shipment_status + ( + id integer, + name varchar(255) not null, + color varchar(50) not null, + code varchar(50), + sort_order smallint not null, + account_id integer not null, + primary key (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence shipment_status_id_seq as integer minvalue 1; + + create table shipment + ( + id integer, + name varchar(255) not null, + warehouse_id integer not null, + order_id integer not null, + status_id integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (warehouse_id) references warehouse (id), + foreign key (order_id) references orders (id), + foreign key (status_id) references shipment_status (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence shipment_id_seq as integer minvalue 1; + + create table shipment_item + ( + id integer, + shipment_id integer not null, + product_id integer not null, + quantity integer not null, + account_id integer not null, + primary key (id), + foreign key (shipment_id) references shipment (id) on delete cascade, + foreign key (product_id) references product (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence shipment_item_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688140521166-AddStatusIdToOrder.ts b/backend/src/database/migrations/archive/1688140521166-AddStatusIdToOrder.ts new file mode 100644 index 0000000..68035fa --- /dev/null +++ b/backend/src/database/migrations/archive/1688140521166-AddStatusIdToOrder.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddStatusIdToOrder1688140521166 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders + add column status_id integer references order_status (id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688388514670-AddReservation.ts b/backend/src/database/migrations/archive/1688388514670-AddReservation.ts new file mode 100644 index 0000000..22c8e63 --- /dev/null +++ b/backend/src/database/migrations/archive/1688388514670-AddReservation.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddReservation1688388514670 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table reservation + ( + id integer, + order_id integer not null, + order_item_id integer not null, + product_id integer not null, + warehouse_id integer not null, + quantity integer not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (order_id) references orders (id) on delete cascade, + foreign key (order_item_id) references order_item (id) on delete cascade, + foreign key (product_id) references product (id), + foreign key (warehouse_id) references warehouse (id), + foreign key (account_id) references account (id) on delete cascade + ); + create sequence reservation_id_seq as integer minvalue 1; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688390259595-AlterFileInfoCreatedBy.ts b/backend/src/database/migrations/archive/1688390259595-AlterFileInfoCreatedBy.ts new file mode 100644 index 0000000..c1cc69c --- /dev/null +++ b/backend/src/database/migrations/archive/1688390259595-AlterFileInfoCreatedBy.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterFileInfoCreatedBy1688390259595 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table file_info + drop constraint file_info_created_by_fkey, + alter column created_by drop not null, + add constraint file_info_created_by_fkey foreign key (created_by) references users(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688394200229-FixStatusIdInOrder.ts b/backend/src/database/migrations/archive/1688394200229-FixStatusIdInOrder.ts new file mode 100644 index 0000000..3405e4d --- /dev/null +++ b/backend/src/database/migrations/archive/1688394200229-FixStatusIdInOrder.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixStatusIdInOrder1688394200229 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders + alter column status_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688472386401-AddShipmentDate.ts b/backend/src/database/migrations/archive/1688472386401-AddShipmentDate.ts new file mode 100644 index 0000000..adb0fb5 --- /dev/null +++ b/backend/src/database/migrations/archive/1688472386401-AddShipmentDate.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddShipmentDate1688472386401 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table shipment + add column shipped_at timestamp without time zone default null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688543908016-AddChatProviderMessenger.ts b/backend/src/database/migrations/archive/1688543908016-AddChatProviderMessenger.ts new file mode 100644 index 0000000..4b23bcd --- /dev/null +++ b/backend/src/database/migrations/archive/1688543908016-AddChatProviderMessenger.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatProviderMessenger1688543908016 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table chat_provider_messenger ( + provider_id integer, + page_id character varying not null, + page_access_token character varying not null, + account_id integer not null, + primary key (provider_id), + foreign key (provider_id) references chat_provider(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688567846856-AlterChatUser.ts b/backend/src/database/migrations/archive/1688567846856-AlterChatUser.ts new file mode 100644 index 0000000..2eff1da --- /dev/null +++ b/backend/src/database/migrations/archive/1688567846856-AlterChatUser.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatUser1688567846856 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_user rename column external_name to external_first_name; + alter table chat_user + add column external_last_name character varying, + add column external_avatar_url character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1688996628275-FixCategoryIdConstraint.ts b/backend/src/database/migrations/archive/1688996628275-FixCategoryIdConstraint.ts new file mode 100644 index 0000000..dd7b709 --- /dev/null +++ b/backend/src/database/migrations/archive/1688996628275-FixCategoryIdConstraint.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixCategoryIdConstraint1688996628275 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + queryRunner.query(` + alter table product + drop constraint product_category_id_fkey; + + alter table product + add foreign key (category_id) references product_category + on delete set null; + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689059395581-AddChatProviderStatus.ts b/backend/src/database/migrations/archive/1689059395581-AddChatProviderStatus.ts new file mode 100644 index 0000000..605b974 --- /dev/null +++ b/backend/src/database/migrations/archive/1689059395581-AddChatProviderStatus.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatProviderStatus1689059395581 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider add column status character varying not null default 'draft'; + update chat_provider set status = 'active'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689068374394-AddUserIdToMessengerProvider.ts b/backend/src/database/migrations/archive/1689068374394-AddUserIdToMessengerProvider.ts new file mode 100644 index 0000000..90dae99 --- /dev/null +++ b/backend/src/database/migrations/archive/1689068374394-AddUserIdToMessengerProvider.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUserIdToMessengerProvider1689068374394 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider_messenger add column user_id character varying not null; + alter table chat_provider_messenger add column user_access_token character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689081064483-AddWarehouseIdToOrder.ts b/backend/src/database/migrations/archive/1689081064483-AddWarehouseIdToOrder.ts new file mode 100644 index 0000000..240f2fd --- /dev/null +++ b/backend/src/database/migrations/archive/1689081064483-AddWarehouseIdToOrder.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddWarehouseIdToOrder1689081064483 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders + add column warehouse_id integer default null, + add foreign key (warehouse_id) references warehouse (id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689087134759-AddModules.ts b/backend/src/database/migrations/archive/1689087134759-AddModules.ts new file mode 100644 index 0000000..dfb87b3 --- /dev/null +++ b/backend/src/database/migrations/archive/1689087134759-AddModules.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddModules1689087134759 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table module + ( + id integer, + name varchar(255) not null, + code varchar(100) not null, + is_enabled boolean not null, + created_at timestamp without time zone not null, + primary key (id) + ); + create sequence module_id_seq as integer minvalue 1; + + insert into module (id, name, code, is_enabled, created_at) + values (nextval('module_id_seq'), 'Products', 'products', true, now()); + + create table account_module + ( + account_id integer not null, + module_id integer not null, + primary key (account_id, module_id), + foreign key (account_id) references account (id) on delete cascade, + foreign key (module_id) references module (id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689170448447-AddProductType.ts b/backend/src/database/migrations/archive/1689170448447-AddProductType.ts new file mode 100644 index 0000000..6f1bfe3 --- /dev/null +++ b/backend/src/database/migrations/archive/1689170448447-AddProductType.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProductType1689170448447 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product + add column type varchar(50) not null default 'product'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689243925753-FixOrderStatuses.ts b/backend/src/database/migrations/archive/1689243925753-FixOrderStatuses.ts new file mode 100644 index 0000000..7e1d424 --- /dev/null +++ b/backend/src/database/migrations/archive/1689243925753-FixOrderStatuses.ts @@ -0,0 +1,53 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixOrderStatuses1689243925753 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update order_status + set name = 'Reserved', + color = '#FFE385', + code = 'reserved', + sort_order = 0 + where code = 'pending'; + + update order_status + set name = 'Sent for shipment', + color = '#A7DFDC', + code = 'sent_for_shipment', + sort_order = 1 + where code = 'confirmed'; + + update order_status + set color = '#A8E379', + sort_order = 2 + where code = 'shipped'; + + update order_status + set color = '#FC7483', + sort_order = 3 + where code = 'cancelled'; + + update order_status + set name = 'Returned', + code = 'returned', + color = '#DCDDE0', + sort_order = 4 + where code = 'completed'; + + update orders + set status_id = (select id from order_status where code = 'reserved' and account_id = orders.account_id) + where status_id = (select id + from order_status + where code = 'delivered' + and account_id = orders.account_id); + + delete + from order_status + where code = 'delivered'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689259268310-DeleteShipmentStatus.ts b/backend/src/database/migrations/archive/1689259268310-DeleteShipmentStatus.ts new file mode 100644 index 0000000..c4850f4 --- /dev/null +++ b/backend/src/database/migrations/archive/1689259268310-DeleteShipmentStatus.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteShipmentStatus1689259268310 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table shipment drop column status_id; + + alter table shipment + add column status_id integer, + add foreign key (status_id) references order_status(id); + + update shipment + set status_id = (select id + from order_status + where code = 'sent_for_shipment' + and account_id = shipment.account_id); + + alter table shipment + alter column status_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689337185167-AddProductsFeature.ts b/backend/src/database/migrations/archive/1689337185167-AddProductsFeature.ts new file mode 100644 index 0000000..351d7c8 --- /dev/null +++ b/backend/src/database/migrations/archive/1689337185167-AddProductsFeature.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProductsFeature1689337185167 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into feature(name, code, is_enabled) values('Products', 'products', true); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689508562776-AddIsActiveToReservation.ts b/backend/src/database/migrations/archive/1689508562776-AddIsActiveToReservation.ts new file mode 100644 index 0000000..9ad248e --- /dev/null +++ b/backend/src/database/migrations/archive/1689508562776-AddIsActiveToReservation.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIsActiveToReservation1689508562776 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table reservation + add column is_active boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689763128902-RemoveNoteRecipientId.ts b/backend/src/database/migrations/archive/1689763128902-RemoveNoteRecipientId.ts new file mode 100644 index 0000000..39f4d05 --- /dev/null +++ b/backend/src/database/migrations/archive/1689763128902-RemoveNoteRecipientId.ts @@ -0,0 +1,43 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveNoteRecipientId1689763128902 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop view feed_items; + + create or replace view feed_items (id, created_at, entity_id, type) as + select note.id, note.created_at, note.entity_id, 'note'::text as type from note + union + select task.id, task.created_at, task.entity_id, 'task'::text as type + from task + union + select activity.id, activity.created_at, activity.entity_id, 'activity'::text as type + from activity + union + (select max(message.id) as id, max(message.date) as created_at, message.entity_id as entity_id, 'mail'::text as type + from mail_message message + where message.entity_id is not null + group by message.thread_id, message.entity_id + order by (max(message.date)) desc) + union + (select max(message.id) as id, max(message.date) as created_at, el.source_id as entity_id, 'mail'::text as type + from mail_message message + right join entity_link el ON el.target_id = message.entity_id + where message.entity_id is not null + group by message.thread_id, el.source_id + order by (max(message.date)) desc) + union + select fl.id, fl.created_at, fl.source_id, 'document'::text as type + from file_link fl + where fl.source_type = 'entity_document' + order by created_at desc; + + alter table note drop column recipient_id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689774963182-AddUpdatedAtToProduct.ts b/backend/src/database/migrations/archive/1689774963182-AddUpdatedAtToProduct.ts new file mode 100644 index 0000000..275cdaf --- /dev/null +++ b/backend/src/database/migrations/archive/1689774963182-AddUpdatedAtToProduct.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUpdatedAtToProduct1689774963182 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product + add column updated_at timestamp without time zone default now(); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689860682052-AddSchedule.ts b/backend/src/database/migrations/archive/1689860682052-AddSchedule.ts new file mode 100644 index 0000000..db4eeb4 --- /dev/null +++ b/backend/src/database/migrations/archive/1689860682052-AddSchedule.ts @@ -0,0 +1,57 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSchedule1689860682052 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists schedule_id_seq as integer minvalue 1; + create table schedule ( + id integer, + name character varying not null, + use_product boolean not null, + entity_type_id integer, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (entity_type_id) references entity_type(id) on delete set null, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists schedule_performer_id_seq as integer minvalue 1; + create table schedule_performer ( + id integer, + schedule_id integer not null, + user_id integer not null, + account_id integer not null, + primary key (id), + foreign key (schedule_id) references schedule(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + create sequence if not exists schedule_event_id_seq as integer minvalue 1; + create table schedule_event ( + id integer, + schedule_id integer not null, + start_date timestamp without time zone not null, + end_date timestamp without time zone not null, + status character varying not null, + comment character varying, + owner_id integer not null, + entity_id integer, + performer_id integer, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (schedule_id) references schedule(id) on delete cascade, + foreign key (owner_id) references users(id), + foreign key (entity_id) references entity(id) on delete set null, + foreign key (performer_id) references schedule_performer(id) on delete set null, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1689933154489-AlterSchedulePerformer.ts b/backend/src/database/migrations/archive/1689933154489-AlterSchedulePerformer.ts new file mode 100644 index 0000000..d988c87 --- /dev/null +++ b/backend/src/database/migrations/archive/1689933154489-AlterSchedulePerformer.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedulePerformer1689933154489 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_event + drop constraint schedule_event_performer_id_fkey, + add foreign key (performer_id) references users(id); + + alter table schedule_performer drop column id; + drop sequence schedule_performer_id_seq; + + alter table schedule_performer + add primary key (schedule_id, user_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1690208012261-AddProductModule.ts b/backend/src/database/migrations/archive/1690208012261-AddProductModule.ts new file mode 100644 index 0000000..06da3ae --- /dev/null +++ b/backend/src/database/migrations/archive/1690208012261-AddProductModule.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProductModule1690208012261 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create sequence if not exists product_module_id_seq as integer minvalue 1; + + create table product_module ( + id integer, + name character varying not null, + icon character varying not null, + account_id integer not null, + created_at timestamp without time zone not null default now(), + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1690456178510-MigrateModuleToProductModule.ts b/backend/src/database/migrations/archive/1690456178510-MigrateModuleToProductModule.ts new file mode 100644 index 0000000..ce7db98 --- /dev/null +++ b/backend/src/database/migrations/archive/1690456178510-MigrateModuleToProductModule.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MigrateModuleToProductModule1690456178510 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from product_module; + + alter table product_module + alter column id set not null, + alter column id add generated by default as identity; + + drop sequence product_module_id_seq; + + insert into product_module(name, icon, account_id) + select m.name as name, 'box' as icon, am.account_id as account_id + from account_module am + join module m on m.id = am.module_id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1690467527775-RemoveModule.ts b/backend/src/database/migrations/archive/1690467527775-RemoveModule.ts new file mode 100644 index 0000000..fdf6a54 --- /dev/null +++ b/backend/src/database/migrations/archive/1690467527775-RemoveModule.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveModule1690467527775 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table account_module; + drop table module; + drop sequence module_id_seq; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1690469860109-AddProductPermissions.ts b/backend/src/database/migrations/archive/1690469860109-AddProductPermissions.ts new file mode 100644 index 0000000..88ecb71 --- /dev/null +++ b/backend/src/database/migrations/archive/1690469860109-AddProductPermissions.ts @@ -0,0 +1,28 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProductPermissions1690469860109 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table product_permissions ( + id integer generated by default as identity, + account_id integer not null, + user_id integer not null, + module_id integer not null, + view_product boolean not null, + create_product boolean not null, + edit_product boolean not null, + create_order boolean not null, + manage_shipping boolean not null, + delete_product boolean not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (module_id) references product_module(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1690543599386-DropProductPermissions.ts b/backend/src/database/migrations/archive/1690543599386-DropProductPermissions.ts new file mode 100644 index 0000000..e0d9770 --- /dev/null +++ b/backend/src/database/migrations/archive/1690543599386-DropProductPermissions.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DropProductPermissions1690543599386 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table if exists product_permissions; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1690817128717-AlterProductModule.ts b/backend/src/database/migrations/archive/1690817128717-AlterProductModule.ts new file mode 100644 index 0000000..0b5c21c --- /dev/null +++ b/backend/src/database/migrations/archive/1690817128717-AlterProductModule.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterProductModule1690817128717 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product_module rename to products_section; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1690973831680-AddSectionIdToProducts.ts b/backend/src/database/migrations/archive/1690973831680-AddSectionIdToProducts.ts new file mode 100644 index 0000000..ac05d17 --- /dev/null +++ b/backend/src/database/migrations/archive/1690973831680-AddSectionIdToProducts.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSectionIdToProducts1690973831680 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product + add column section_id integer, + add foreign key (section_id) references products_section(id) on delete cascade; + update product p set section_id = ps.id from products_section ps where p.account_id = ps.account_id; + alter table product alter column section_id set not null; + + alter table product_category + add column section_id integer, + add foreign key (section_id) references products_section(id) on delete cascade; + update product_category pc set section_id = ps.id from products_section ps where pc.account_id = ps.account_id; + alter table product_category alter column section_id set not null; + + alter table warehouse + add column section_id integer, + add foreign key (section_id) references products_section(id) on delete cascade; + update warehouse w set section_id = ps.id from products_section ps where w.account_id = ps.account_id; + alter table warehouse alter column section_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691056504886-AddSectionIdToOrderAndShipment.ts b/backend/src/database/migrations/archive/1691056504886-AddSectionIdToOrderAndShipment.ts new file mode 100644 index 0000000..b8ecdc5 --- /dev/null +++ b/backend/src/database/migrations/archive/1691056504886-AddSectionIdToOrderAndShipment.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSectionIdToOrderAndShipment1691056504886 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table shipment + add column section_id integer, + add foreign key (section_id) references products_section(id) on delete cascade; + update shipment s set section_id = ps.id from products_section ps where s.account_id = ps.account_id; + alter table shipment alter column section_id set not null; + + alter table orders + add column section_id integer, + add foreign key (section_id) references products_section(id) on delete cascade; + update orders o set section_id = ps.id from products_section ps where o.account_id = ps.account_id; + alter table orders alter column section_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691061102493-DeleteShipmentStatus.ts b/backend/src/database/migrations/archive/1691061102493-DeleteShipmentStatus.ts new file mode 100644 index 0000000..3171015 --- /dev/null +++ b/backend/src/database/migrations/archive/1691061102493-DeleteShipmentStatus.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteShipmentStatus1691061102493 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table shipment_status; + drop sequence shipment_status_id_seq; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691061408646-AlterTableStockToProductStock.ts b/backend/src/database/migrations/archive/1691061408646-AlterTableStockToProductStock.ts new file mode 100644 index 0000000..821685b --- /dev/null +++ b/backend/src/database/migrations/archive/1691061408646-AlterTableStockToProductStock.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterTableStockToProductStock1691061408646 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table stock rename to product_stock; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691139996885-AddProductsSectionEntityType.ts b/backend/src/database/migrations/archive/1691139996885-AddProductsSectionEntityType.ts new file mode 100644 index 0000000..5464c73 --- /dev/null +++ b/backend/src/database/migrations/archive/1691139996885-AddProductsSectionEntityType.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProductsSectionEntityType1691139996885 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table products_section_entity_type ( + section_id integer, + entity_type_id integer, + account_id integer not null, + primary key (section_id, entity_type_id), + foreign key (section_id) references products_section(id) on delete cascade, + foreign key (entity_type_id) references entity_type(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + + insert into products_section_entity_type + select ps.id as section_id, et.id as entity_type_id, ps.account_id as account_id + from products_section ps + inner join entity_type et on 1=1 + inner join entity_type_feature etf on et.id = etf.entity_type_id + inner join feature f on f.id = etf.feature_id and f.code = 'products'; + + update feature set is_enabled = false where code = 'products'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691155049107-AddRentalInterval.ts b/backend/src/database/migrations/archive/1691155049107-AddRentalInterval.ts new file mode 100644 index 0000000..d1ce3e1 --- /dev/null +++ b/backend/src/database/migrations/archive/1691155049107-AddRentalInterval.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRentalInterval1691155049107 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table rental_interval ( + id integer generated by default as identity, + section_id integer not null, + type character varying not null, + start_time time without time zone, + account_id integer not null, + primary key (id), + foreign key (section_id) references products_section(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691397636905-AlterProductsSectionType.ts b/backend/src/database/migrations/archive/1691397636905-AlterProductsSectionType.ts new file mode 100644 index 0000000..7f8775c --- /dev/null +++ b/backend/src/database/migrations/archive/1691397636905-AlterProductsSectionType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterProductsSectionType1691397636905 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table products_section add column "type" character varying not null default 'sale'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691411118591-AddRentalOrder.ts b/backend/src/database/migrations/archive/1691411118591-AddRentalOrder.ts new file mode 100644 index 0000000..1b88dbb --- /dev/null +++ b/backend/src/database/migrations/archive/1691411118591-AddRentalOrder.ts @@ -0,0 +1,44 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRentalOrder1691411118591 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table rental_order ( + id integer generated by default as identity, + section_id integer not null, + warehouse_id integer, + entity_id integer not null, + created_by integer not null, + start_date timestamp without time zone not null, + end_date timestamp without time zone not null, + status character varying not null, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (section_id) references products_section(id) on delete cascade, + foreign key (warehouse_id) references warehouse(id), + foreign key (entity_id) references entity(id) on delete cascade, + foreign key (created_by) references users(id), + foreign key (account_id) references account(id) on delete cascade + ); + + create table rental_order_item ( + id integer generated by default as identity, + order_id integer not null, + product_id integer not null, + warehouse_id integer, + sort_order integer not null, + account_id integer not null, + primary key (id), + foreign key (order_id) references rental_order(id) on delete cascade, + foreign key (product_id) references product(id), + foreign key (warehouse_id) references warehouse(id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691414938591-AddRentalSchedule.ts b/backend/src/database/migrations/archive/1691414938591-AddRentalSchedule.ts new file mode 100644 index 0000000..b67c816 --- /dev/null +++ b/backend/src/database/migrations/archive/1691414938591-AddRentalSchedule.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRentalSchedule1691414938591 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table rental_schedule ( + id integer generated by default as identity, + product_id integer not null, + order_item_id integer not null, + start_date timestamp without time zone not null, + end_date timestamp without time zone not null, + status character varying not null, + account_id integer not null, + primary key (id), + foreign key (product_id) references product(id) on delete cascade, + foreign key (order_item_id) references rental_order_item(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691657890280-AddRentalOrderPeriod.ts b/backend/src/database/migrations/archive/1691657890280-AddRentalOrderPeriod.ts new file mode 100644 index 0000000..a6a3e78 --- /dev/null +++ b/backend/src/database/migrations/archive/1691657890280-AddRentalOrderPeriod.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRentalOrderPeriod1691657890280 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table rental_order_period ( + id integer generated by default as identity, + order_id integer not null, + start_date timestamp without time zone not null, + end_date timestamp without time zone not null, + primary key (id), + foreign key (order_id) references rental_order(id) on delete cascade + ); + + alter table rental_order + drop column start_date, + drop column end_date; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691678125349-AddRentalOrderPeriodAccountId.ts b/backend/src/database/migrations/archive/1691678125349-AddRentalOrderPeriodAccountId.ts new file mode 100644 index 0000000..35b8681 --- /dev/null +++ b/backend/src/database/migrations/archive/1691678125349-AddRentalOrderPeriodAccountId.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRentalOrderPeriodAccountId1691678125349 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table rental_order_period + add column account_id integer not null, + add foreign key (account_id) references account(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691754596482-AlterRentalOrder.ts b/backend/src/database/migrations/archive/1691754596482-AlterRentalOrder.ts new file mode 100644 index 0000000..b472365 --- /dev/null +++ b/backend/src/database/migrations/archive/1691754596482-AlterRentalOrder.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterRentalOrder1691754596482 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table rental_order + add column currency character varying, + add column tax_included boolean not null default true; + + update rental_order set currency = 'rub'; + + alter table rental_order alter column currency set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1691755141714-AlterRentalOrderItem.ts b/backend/src/database/migrations/archive/1691755141714-AlterRentalOrderItem.ts new file mode 100644 index 0000000..5bf2ace --- /dev/null +++ b/backend/src/database/migrations/archive/1691755141714-AlterRentalOrderItem.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterRentalOrderItem1691755141714 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from rental_order; + + alter table rental_order_item + drop column warehouse_id, + add column unit_price numeric(15,2) not null, + add column tax numeric(5,2) not null, + add column discount numeric(5,2) not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692002092660-RentalScheduleAddSectionId.ts b/backend/src/database/migrations/archive/1692002092660-RentalScheduleAddSectionId.ts new file mode 100644 index 0000000..437bbc7 --- /dev/null +++ b/backend/src/database/migrations/archive/1692002092660-RentalScheduleAddSectionId.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RentalScheduleAddSectionId1692002092660 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from rental_schedule; + + alter table rental_schedule + add column section_id integer not null, + add foreign key (section_id) references products_section(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692014115943-RenameRentalScheduleToRentalEvent.ts b/backend/src/database/migrations/archive/1692014115943-RenameRentalScheduleToRentalEvent.ts new file mode 100644 index 0000000..f3dcfc0 --- /dev/null +++ b/backend/src/database/migrations/archive/1692014115943-RenameRentalScheduleToRentalEvent.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameRentalScheduleToRentalEvent1692014115943 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table rental_schedule rename to rental_event; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692170842159-AddProductsSectionEnableWarehouse.ts b/backend/src/database/migrations/archive/1692170842159-AddProductsSectionEnableWarehouse.ts new file mode 100644 index 0000000..decf042 --- /dev/null +++ b/backend/src/database/migrations/archive/1692170842159-AddProductsSectionEnableWarehouse.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddProductsSectionEnableWarehouse1692170842159 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table products_section add column enable_warehouse boolean; + + update products_section set enable_warehouse = + (select count(warehouse.id) > 0 from warehouse where warehouse.section_id = products_section.id); + + alter table products_section alter column enable_warehouse set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692172254434-AddOrdersOrderNumber.ts b/backend/src/database/migrations/archive/1692172254434-AddOrdersOrderNumber.ts new file mode 100644 index 0000000..c473c34 --- /dev/null +++ b/backend/src/database/migrations/archive/1692172254434-AddOrdersOrderNumber.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddOrdersOrderNumber1692172254434 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders add column order_number integer; + + with NumberedOrders as ( + select + id, + row_number() over(partition by entity_id order by created_at asc) as new_order_number + from orders + ) + update orders + set order_number = NumberedOrders.new_order_number + from NumberedOrders + where orders.id = NumberedOrders.id; + + alter table orders alter column order_number set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692172318353-AddRentalOrderOrderNumber.ts b/backend/src/database/migrations/archive/1692172318353-AddRentalOrderOrderNumber.ts new file mode 100644 index 0000000..dca1559 --- /dev/null +++ b/backend/src/database/migrations/archive/1692172318353-AddRentalOrderOrderNumber.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddRentalOrderOrderNumber1692172318353 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table rental_order add column order_number integer; + + with NumberedOrders as ( + select + id, + row_number() over(partition by entity_id order by created_at asc) as new_order_number + from rental_order + ) + update rental_order + set order_number = NumberedOrders.new_order_number + from NumberedOrders + where rental_order.id = NumberedOrders.id; + + alter table rental_order alter column order_number set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692283603851-AlterOrderItemCascadeDelete.ts b/backend/src/database/migrations/archive/1692283603851-AlterOrderItemCascadeDelete.ts new file mode 100644 index 0000000..2484d2f --- /dev/null +++ b/backend/src/database/migrations/archive/1692283603851-AlterOrderItemCascadeDelete.ts @@ -0,0 +1,16 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterOrderItemCascadeDelete1692283603851 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table rental_order_item + drop constraint rental_order_item_product_id_fkey, + add constraint rental_order_item_product_id_fkey foreign key (product_id) references product(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692343747646-AlterProductStock.ts b/backend/src/database/migrations/archive/1692343747646-AlterProductStock.ts new file mode 100644 index 0000000..acc380d --- /dev/null +++ b/backend/src/database/migrations/archive/1692343747646-AlterProductStock.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterProductStock1692343747646 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product_stock + drop constraint stock_product_id_fkey, + drop constraint stock_warehouse_id_fkey, + add constraint stock_product_id_fkey foreign key (product_id) references product(id) on delete cascade, + add constraint stock_warehouse_id_fkey foreign key (warehouse_id) references warehouse(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692354371998-FixFieldValueType.ts b/backend/src/database/migrations/archive/1692354371998-FixFieldValueType.ts new file mode 100644 index 0000000..d700a8f --- /dev/null +++ b/backend/src/database/migrations/archive/1692354371998-FixFieldValueType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixFieldValueType1692354371998 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update field_value set field_type = (select type from field where field.id = field_value.field_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692604044210-ProductsSectionEnableBarcode.ts b/backend/src/database/migrations/archive/1692604044210-ProductsSectionEnableBarcode.ts new file mode 100644 index 0000000..c685223 --- /dev/null +++ b/backend/src/database/migrations/archive/1692604044210-ProductsSectionEnableBarcode.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProductsSectionEnableBarcode1692604044210 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table products_section add column enable_barcode boolean default true not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692708295281-AlterOrderStatusNull.ts b/backend/src/database/migrations/archive/1692708295281-AlterOrderStatusNull.ts new file mode 100644 index 0000000..cbb10d4 --- /dev/null +++ b/backend/src/database/migrations/archive/1692708295281-AlterOrderStatusNull.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterOrderStatusNull1692708295281 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders alter column status_id drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692885285551-RefactorScheduler.ts b/backend/src/database/migrations/archive/1692885285551-RefactorScheduler.ts new file mode 100644 index 0000000..9340348 --- /dev/null +++ b/backend/src/database/migrations/archive/1692885285551-RefactorScheduler.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RefactorScheduler1692885285551 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop sequence schedule_id_seq; + alter table schedule + alter column id set not null, + alter column id add generated by default as identity; + + drop sequence schedule_event_id_seq; + alter table schedule_event + alter column id set not null, + alter column id add generated by default as identity; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692890628636-RenameScheduleEvent.ts b/backend/src/database/migrations/archive/1692890628636-RenameScheduleEvent.ts new file mode 100644 index 0000000..de48e14 --- /dev/null +++ b/backend/src/database/migrations/archive/1692890628636-RenameScheduleEvent.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameScheduleEvent1692890628636 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_event rename to schedule_appointment; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1692975377102-AlterSchedule.ts b/backend/src/database/migrations/archive/1692975377102-AlterSchedule.ts new file mode 100644 index 0000000..6799847 --- /dev/null +++ b/backend/src/database/migrations/archive/1692975377102-AlterSchedule.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedule1692975377102 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule + drop column use_product, + add column products_section_id integer, + add foreign key (products_section_id) references products_section(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1693218884137-UserPosition.ts b/backend/src/database/migrations/archive/1693218884137-UserPosition.ts new file mode 100644 index 0000000..84f5c2b --- /dev/null +++ b/backend/src/database/migrations/archive/1693218884137-UserPosition.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UserPosition1693218884137 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users add column position character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1693232990040-ScheduleAppointmentOrderId.ts b/backend/src/database/migrations/archive/1693232990040-ScheduleAppointmentOrderId.ts new file mode 100644 index 0000000..faf37a9 --- /dev/null +++ b/backend/src/database/migrations/archive/1693232990040-ScheduleAppointmentOrderId.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ScheduleAppointmentOrderId1693232990040 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_appointment + add column order_id integer, + add foreign key (order_id) references orders(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1693485238189-OrderStatusColor.ts b/backend/src/database/migrations/archive/1693485238189-OrderStatusColor.ts new file mode 100644 index 0000000..f31863b --- /dev/null +++ b/backend/src/database/migrations/archive/1693485238189-OrderStatusColor.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderStatusColor1693485238189 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update order_status set color='#ea925a' where code='reserved'; + update order_status set color='#a33cab' where code='sent_for_shipment'; + update order_status set color='#8af039' where code='shipped'; + update order_status set color='#ee675c' where code='cancelled'; + update order_status set color='#c0c5cc' where code='returned'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1693556962547-CascadeDeleteShipment.ts b/backend/src/database/migrations/archive/1693556962547-CascadeDeleteShipment.ts new file mode 100644 index 0000000..bc5dd72 --- /dev/null +++ b/backend/src/database/migrations/archive/1693556962547-CascadeDeleteShipment.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CascadeDeleteShipment1693556962547 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table shipment + drop constraint shipment_warehouse_id_fkey, + drop constraint shipment_order_id_fkey, + add constraint shipment_warehouse_id_fkey foreign key (warehouse_id) references warehouse(id) on delete cascade, + add constraint shipment_order_id_fkey foreign key (order_id) references orders(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1694085886365-SchedulerIcon.ts b/backend/src/database/migrations/archive/1694085886365-SchedulerIcon.ts new file mode 100644 index 0000000..206400e --- /dev/null +++ b/backend/src/database/migrations/archive/1694085886365-SchedulerIcon.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SchedulerIcon1694085886365 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule add column icon character varying not null default ''; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1694166234404-BoardCleanProjectParticipants.ts b/backend/src/database/migrations/archive/1694166234404-BoardCleanProjectParticipants.ts new file mode 100644 index 0000000..7e22acc --- /dev/null +++ b/backend/src/database/migrations/archive/1694166234404-BoardCleanProjectParticipants.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class BoardCleanProjectParticipants1694166234404 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update board + set is_system = false, participant_ids = '[]' + where type = 'task' and is_system = true and owner_id is not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695040324876-AppointmentTitle.ts b/backend/src/database/migrations/archive/1695040324876-AppointmentTitle.ts new file mode 100644 index 0000000..0603d80 --- /dev/null +++ b/backend/src/database/migrations/archive/1695040324876-AppointmentTitle.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AppointmentTitle1695040324876 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_appointment add column title character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695046445852-EntityClosedAt.ts b/backend/src/database/migrations/archive/1695046445852-EntityClosedAt.ts new file mode 100644 index 0000000..c335f65 --- /dev/null +++ b/backend/src/database/migrations/archive/1695046445852-EntityClosedAt.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityClosedAt1695046445852 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity add column closed_at timestamp without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695201739381-SalesPlan.ts b/backend/src/database/migrations/archive/1695201739381-SalesPlan.ts new file mode 100644 index 0000000..f073e04 --- /dev/null +++ b/backend/src/database/migrations/archive/1695201739381-SalesPlan.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SalesPlan1695201739381 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table sales_plan ( + id integer generated by default as identity, + account_id integer not null, + created_at timestamp without time zone not null, + entity_type_id integer not null, + user_id integer not null, + start_date timestamp without time zone not null, + end_date timestamp without time zone not null, + quantity integer, + amount bigint, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (entity_type_id) references entity_type(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695287859742-ChatDeleteCascadeEntity.ts b/backend/src/database/migrations/archive/1695287859742-ChatDeleteCascadeEntity.ts new file mode 100644 index 0000000..b64b329 --- /dev/null +++ b/backend/src/database/migrations/archive/1695287859742-ChatDeleteCascadeEntity.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatDeleteCascadeEntity1695287859742 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat + drop constraint chat_entity_id_fkey, + add constraint chat_entity_id_fkey foreign key (entity_id) references entity(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695382850916-SalesPlanAlterAmount.ts b/backend/src/database/migrations/archive/1695382850916-SalesPlanAlterAmount.ts new file mode 100644 index 0000000..be058b2 --- /dev/null +++ b/backend/src/database/migrations/archive/1695382850916-SalesPlanAlterAmount.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SalesPlanAlterAmount1695382850916 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table sales_plan alter column amount type integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695742049917-VoximplantUser.ts b/backend/src/database/migrations/archive/1695742049917-VoximplantUser.ts new file mode 100644 index 0000000..f1c0417 --- /dev/null +++ b/backend/src/database/migrations/archive/1695742049917-VoximplantUser.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantUser1695742049917 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table voximplant_user ( + id integer generated by default as identity, + user_id integer not null, + voximplant_id integer not null, + voximplant_username character varying not null, + account_id integer not null, + primary key (id), + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695743140564-VoximplantUserPrimaryColumn.ts b/backend/src/database/migrations/archive/1695743140564-VoximplantUserPrimaryColumn.ts new file mode 100644 index 0000000..39a071b --- /dev/null +++ b/backend/src/database/migrations/archive/1695743140564-VoximplantUserPrimaryColumn.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantUserPrimaryColumn1695743140564 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_user + drop column id, + add primary key (user_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695808984339-VoximplantUserPassword.ts b/backend/src/database/migrations/archive/1695808984339-VoximplantUserPassword.ts new file mode 100644 index 0000000..910e236 --- /dev/null +++ b/backend/src/database/migrations/archive/1695808984339-VoximplantUserPassword.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantUserPassword1695808984339 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_user add column voximplant_password character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695810676148-VoximplantAccount.ts b/backend/src/database/migrations/archive/1695810676148-VoximplantAccount.ts new file mode 100644 index 0000000..cb96738 --- /dev/null +++ b/backend/src/database/migrations/archive/1695810676148-VoximplantAccount.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantAccount1695810676148 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table voximplant_account ( + account_id integer, + account_name character varying not null, + application_id integer not null, + application_name character varying not null, + primary key (account_id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1695820387969-AlterVoximplantUser.ts b/backend/src/database/migrations/archive/1695820387969-AlterVoximplantUser.ts new file mode 100644 index 0000000..aaf4c13 --- /dev/null +++ b/backend/src/database/migrations/archive/1695820387969-AlterVoximplantUser.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterVoximplantUser1695820387969 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_user rename column voximplant_id to external_id; + alter table voximplant_user rename column voximplant_username to username; + alter table voximplant_user rename column voximplant_password to password; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1696500815450-DemoData.ts b/backend/src/database/migrations/archive/1696500815450-DemoData.ts new file mode 100644 index 0000000..c4d4fd6 --- /dev/null +++ b/backend/src/database/migrations/archive/1696500815450-DemoData.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DemoData1696500815450 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table demo_data ( + id integer generated by default as identity, + account_id integer not null, + type character varying not null, + ids character varying not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697019761609-VoximplantCall.ts b/backend/src/database/migrations/archive/1697019761609-VoximplantCall.ts new file mode 100644 index 0000000..929a8a0 --- /dev/null +++ b/backend/src/database/migrations/archive/1697019761609-VoximplantCall.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantCall1697019761609 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table voximplant_call ( + id integer generated by default as identity, + external_id integer not null, + user_id integer not null, + entity_id integer, + direction character varying not null, + phone_number character varying not null, + duration integer, + status character varying, + failure_reason character varying, + record_url character varying, + account_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (user_id) references users(id) on delete cascade, + foreign key (entity_id) references entity(id) on delete set null, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697028866185-AlterVoximplantUser.ts b/backend/src/database/migrations/archive/1697028866185-AlterVoximplantUser.ts new file mode 100644 index 0000000..83cbe53 --- /dev/null +++ b/backend/src/database/migrations/archive/1697028866185-AlterVoximplantUser.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterVoximplantUser1697028866185 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_user rename column username to user_name; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697115016543-RefactorDemoData.ts b/backend/src/database/migrations/archive/1697115016543-RefactorDemoData.ts new file mode 100644 index 0000000..f42571c --- /dev/null +++ b/backend/src/database/migrations/archive/1697115016543-RefactorDemoData.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RefactorDemoData1697115016543 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + INSERT INTO demo_data (account_id, type, ids) + SELECT account_id, 'entity' AS type, STRING_AGG(CAST(id AS character varying), ',') AS ids + FROM entity WHERE is_demo = true GROUP BY account_id; + ALTER TABLE entity DROP COLUMN is_demo; + + INSERT INTO demo_data (account_id, type, ids) + SELECT account_id, 'user' AS type, STRING_AGG(CAST(id AS character varying), ',') AS ids + FROM users WHERE is_demo = true GROUP BY account_id; + ALTER TABLE users DROP COLUMN is_demo; + + ALTER TABLE account_settings DROP COLUMN has_demo; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697440579544-SchedulerEventPerformerCascade.ts b/backend/src/database/migrations/archive/1697440579544-SchedulerEventPerformerCascade.ts new file mode 100644 index 0000000..bb5545f --- /dev/null +++ b/backend/src/database/migrations/archive/1697440579544-SchedulerEventPerformerCascade.ts @@ -0,0 +1,16 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SchedulerEventPerformerCascade1697440579544 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_appointment + drop constraint schedule_event_performer_id_fkey, + add constraint schedule_event_performer_id_fkey foreign key (performer_id) references users(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697452411558-VoximplantUserActive.ts b/backend/src/database/migrations/archive/1697452411558-VoximplantUserActive.ts new file mode 100644 index 0000000..8f6cf30 --- /dev/null +++ b/backend/src/database/migrations/archive/1697452411558-VoximplantUserActive.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantUserActive1697452411558 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_user add column is_active boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697541300418-VoximplantAccount.ts b/backend/src/database/migrations/archive/1697541300418-VoximplantAccount.ts new file mode 100644 index 0000000..e497ffc --- /dev/null +++ b/backend/src/database/migrations/archive/1697541300418-VoximplantAccount.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantAccount1697541300418 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from voximplant_account; + + alter table voximplant_account + add column external_id integer not null, + add column api_key character varying not null, + add column password character varying not null, + add column billing_account_id integer not null, + add column is_active text not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697541767120-VoximplantAccountAlter.ts b/backend/src/database/migrations/archive/1697541767120-VoximplantAccountAlter.ts new file mode 100644 index 0000000..43a07ce --- /dev/null +++ b/backend/src/database/migrations/archive/1697541767120-VoximplantAccountAlter.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantAccountAlter1697541767120 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_account drop column is_active; + alter table voximplant_account add column is_active boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697543064652-VoximplantAccountEmail.ts b/backend/src/database/migrations/archive/1697543064652-VoximplantAccountEmail.ts new file mode 100644 index 0000000..ecb9473 --- /dev/null +++ b/backend/src/database/migrations/archive/1697543064652-VoximplantAccountEmail.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantAccountEmail1697543064652 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_account add column account_email character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697556130148-VoximplantAccountKey.ts b/backend/src/database/migrations/archive/1697556130148-VoximplantAccountKey.ts new file mode 100644 index 0000000..1827ad3 --- /dev/null +++ b/backend/src/database/migrations/archive/1697556130148-VoximplantAccountKey.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantAccountKey1697556130148 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_account + add column key_id character varying not null, + add column private_key character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1697702819805-VoximplantCallAlterExternalId.ts b/backend/src/database/migrations/archive/1697702819805-VoximplantCallAlterExternalId.ts new file mode 100644 index 0000000..60679aa --- /dev/null +++ b/backend/src/database/migrations/archive/1697702819805-VoximplantCallAlterExternalId.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantCallAlterExternalId1697702819805 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from voximplant_call; + + alter table voximplant_call + drop column external_id, + add column session_id character varying not null, + add column call_id character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1698135013349-ReportingOptimization.ts b/backend/src/database/migrations/archive/1698135013349-ReportingOptimization.ts new file mode 100644 index 0000000..b5404cd --- /dev/null +++ b/backend/src/database/migrations/archive/1698135013349-ReportingOptimization.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ReportingOptimization1698135013349 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX idx_entity_account_id_stage_id_closed_at ON entity(account_id, stage_id, closed_at); + CREATE INDEX idx_field_value_entity_id_field_type ON field_value(entity_id, field_type); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1698421539770-VoximplantScenarios.ts b/backend/src/database/migrations/archive/1698421539770-VoximplantScenarios.ts new file mode 100644 index 0000000..60cb18c --- /dev/null +++ b/backend/src/database/migrations/archive/1698421539770-VoximplantScenarios.ts @@ -0,0 +1,57 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantScenarios1698421539770 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table voximplant_scenario_entity ( + id integer generated by default as identity, + account_id integer not null, + scenario_type character varying not null, + contact_id integer, + deal_id integer, + board_id integer, + owner_id integer, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (contact_id) references entity_type(id) on delete cascade, + foreign key (deal_id) references entity_type(id) on delete cascade, + foreign key (board_id) references board(id) on delete cascade, + foreign key (owner_id) references users(id) on delete cascade + ); + + create table voximplant_scenario_note ( + id integer generated by default as identity, + account_id integer not null, + scenario_type character varying not null, + note_text character varying not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + create table voximplant_scenario_task ( + id integer generated by default as identity, + account_id integer not null, + scenario_type character varying not null, + create_activity boolean, + activity_type_id integer, + activity_text character varying, + activity_duration integer, + activity_owner_id integer, + create_task boolean, + task_title character varying, + task_text character varying, + task_duration integer, + task_owner_id integer, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (activity_type_id) references activity_type(id) on delete cascade, + foreign key (activity_owner_id) references users(id) on delete cascade, + foreign key (task_owner_id) references users(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1698663785490-OrderStatusColors.ts b/backend/src/database/migrations/archive/1698663785490-OrderStatusColors.ts new file mode 100644 index 0000000..db35ac2 --- /dev/null +++ b/backend/src/database/migrations/archive/1698663785490-OrderStatusColors.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderStatusColors1698663785490 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update order_status set color='#f68828' where code = 'reserved'; + update order_status set color='#1dd7d7' where code = 'sent_for_shipment'; + update order_status set color='#69d222' where code = 'shipped'; + update order_status set color='#f8654f' where code = 'cancelled'; + update order_status set color='#acb5c3' where code = 'returned'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1699457673085-AddEntityEventModel.ts b/backend/src/database/migrations/archive/1699457673085-AddEntityEventModel.ts new file mode 100644 index 0000000..443d20e --- /dev/null +++ b/backend/src/database/migrations/archive/1699457673085-AddEntityEventModel.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddEntityEventModel1699457673085 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // language=SQL format=false + await queryRunner.query(` + create table entity_event ( + id integer generated always as identity primary key, + account_id integer not null references account(id) on delete cascade, + entity_id integer not null references entity(id) on delete cascade, + object_id integer not null, + type character varying not null, + created_at timestamp without time zone not null + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700060543771-AddSchedulePerformerId.ts b/backend/src/database/migrations/archive/1700060543771-AddSchedulePerformerId.ts new file mode 100644 index 0000000..df53fbd --- /dev/null +++ b/backend/src/database/migrations/archive/1700060543771-AddSchedulePerformerId.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSchedulePerformerId1700060543771 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_performer + drop constraint schedule_performer_pkey, + add column id integer generated always as identity, + add primary key (id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700060859571-AlterSchedulePerformer.ts b/backend/src/database/migrations/archive/1700060859571-AlterSchedulePerformer.ts new file mode 100644 index 0000000..6e37349 --- /dev/null +++ b/backend/src/database/migrations/archive/1700060859571-AlterSchedulePerformer.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedulePerformer1700060859571 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_performer + alter column user_id drop not null, + add column department_id integer, + add column type character varying not null default 'user', + add foreign key (department_id) references department(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700230572219-AlterMailbox.ts b/backend/src/database/migrations/archive/1700230572219-AlterMailbox.ts new file mode 100644 index 0000000..82d9176 --- /dev/null +++ b/backend/src/database/migrations/archive/1700230572219-AlterMailbox.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailbox1700230572219 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox add column create_lead boolean not null default false; + + update mailbox set create_lead = lead_entity_type_id is not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700395051542-AlterExternalEntity.ts b/backend/src/database/migrations/archive/1700395051542-AlterExternalEntity.ts new file mode 100644 index 0000000..0a1fb1a --- /dev/null +++ b/backend/src/database/migrations/archive/1700395051542-AlterExternalEntity.ts @@ -0,0 +1,16 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterExternalEntity1700395051542 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table external_entity + drop constraint external_entity_account_id_fkey, + add constraint external_entity_account_id_fkey foreign key (account_id) references account(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700475817946-AllEventsMigrationScript.ts b/backend/src/database/migrations/archive/1700475817946-AllEventsMigrationScript.ts new file mode 100644 index 0000000..1e64e1c --- /dev/null +++ b/backend/src/database/migrations/archive/1700475817946-AllEventsMigrationScript.ts @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AllEventsMigrationScript1700475817946 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from entity_event; + insert into entity_event (account_id, entity_id, object_id, type, created_at) + select + n.account_id, + n.entity_id, + n.id as object_id, + 'note'::text as type, + n.created_at + from note n + where n.entity_id is not null + union + select + t.account_id, + t.entity_id, + t.id as object_id, + 'task'::text as type, + t.created_at + from task t + where t.entity_id is not null + union + select + a.account_id, + a.entity_id, + a.id as object_id, + 'activity'::text as type, + a.created_at + from activity a + where a.entity_id is not null + union + select + m.account_id, + m.entity_id, + max(m.id) as object_id, + 'mail'::text as type, + max(m.date) as created_at + from mail_message m + where m.entity_id is not null + group by m.account_id, m.thread_id, m.entity_id + union + select + f.account_id, + f.source_id as entity_id, + f.id as object_id, + 'document'::text as type, + f.created_at + from file_link f + where f.source_type = 'entity_document' + and f.source_id is not null + and exists (select 1 from entity e where e.id = f.source_id) + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700591906266-TelephonyCallsToEntityEvent.ts b/backend/src/database/migrations/archive/1700591906266-TelephonyCallsToEntityEvent.ts new file mode 100644 index 0000000..f61792f --- /dev/null +++ b/backend/src/database/migrations/archive/1700591906266-TelephonyCallsToEntityEvent.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TelephonyCallsToEntityEvent1700591906266 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into entity_event (account_id, entity_id, object_id, type, created_at) + select v.account_id, v.entity_id, v.id as object_id, 'telephony-call' as type, created_at + from voximplant_call v + where v.entity_id is not null + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700663233866-AlterSchedulerAppointment.ts b/backend/src/database/migrations/archive/1700663233866-AlterSchedulerAppointment.ts new file mode 100644 index 0000000..4d13b98 --- /dev/null +++ b/backend/src/database/migrations/archive/1700663233866-AlterSchedulerAppointment.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedulerAppointment1700663233866 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_appointment drop constraint if exists schedule_event_performer_id_fkey; + alter table schedule_appointment drop constraint if exists schedule_appointment_performer_id_fkey; + alter table schedule_appointment rename column performer_id to old_performer_id; + + alter table schedule_appointment + add column performer_id integer, + add foreign key (performer_id) references schedule_performer(id) on delete cascade; + + update schedule_appointment sa + set performer_id = + (select id from schedule_performer sp + where sp.schedule_id = sa.schedule_id and sp.user_id = sa.old_performer_id); + + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700729760783-AlterSchedule.ts b/backend/src/database/migrations/archive/1700729760783-AlterSchedule.ts new file mode 100644 index 0000000..1d7e75b --- /dev/null +++ b/backend/src/database/migrations/archive/1700729760783-AlterSchedule.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedule1700729760783 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule add column time_period integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700733045104-AlterSchedule.ts b/backend/src/database/migrations/archive/1700733045104-AlterSchedule.ts new file mode 100644 index 0000000..a0a9d2d --- /dev/null +++ b/backend/src/database/migrations/archive/1700733045104-AlterSchedule.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedule1700733045104 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule add column appointment_limit integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700735236205-AlterSchedule.ts b/backend/src/database/migrations/archive/1700735236205-AlterSchedule.ts new file mode 100644 index 0000000..9d064fb --- /dev/null +++ b/backend/src/database/migrations/archive/1700735236205-AlterSchedule.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedule1700735236205 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule add column type character varying not null default 'schedule'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700741072037-ProductOrdersToEntityEvents.ts b/backend/src/database/migrations/archive/1700741072037-ProductOrdersToEntityEvents.ts new file mode 100644 index 0000000..5295fe3 --- /dev/null +++ b/backend/src/database/migrations/archive/1700741072037-ProductOrdersToEntityEvents.ts @@ -0,0 +1,31 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProductOrdersToEntityEvents1700741072037 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into entity_event (account_id, entity_id, object_id, type, created_at) + select + o.account_id, + o.entity_id, + o.id as object_id, + 'order'::text as type, + o.created_at + from orders o + where o.entity_id is not null + union + select + ro.account_id, + ro.entity_id, + ro.id as object_id, + 'rental_order'::text as type, + ro.created_at + from rental_order ro + where ro.entity_id is not null + order by created_at desc; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700820935837-AlterSchedulerAppointment.ts b/backend/src/database/migrations/archive/1700820935837-AlterSchedulerAppointment.ts new file mode 100644 index 0000000..597c51b --- /dev/null +++ b/backend/src/database/migrations/archive/1700820935837-AlterSchedulerAppointment.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedulerAppointment1700820935837 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + + update schedule_appointment sa + set performer_id = + (select id from schedule_performer sp where sp.schedule_id = sa.schedule_id order by sp.id limit 1) + where performer_id is null; + + alter table schedule_appointment alter column performer_id set not null; + + alter table schedule_appointment drop column if exists old_performer_id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1700836699189-ShipmentsToEntityEvents.ts b/backend/src/database/migrations/archive/1700836699189-ShipmentsToEntityEvents.ts new file mode 100644 index 0000000..b80a22d --- /dev/null +++ b/backend/src/database/migrations/archive/1700836699189-ShipmentsToEntityEvents.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ShipmentsToEntityEvents1700836699189 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into entity_event (account_id, entity_id, object_id, type, created_at) + select + s.account_id, + (select o.entity_id from orders o where o.id = s.order_id)::integer as entity_id, + s.id as object_id, + 'shipment'::text as type, + s.created_at + from shipment s + where exists (select 1 from orders o where o.id = s.order_id) + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1701264274255-CallsToEntityEventsFix.ts b/backend/src/database/migrations/archive/1701264274255-CallsToEntityEventsFix.ts new file mode 100644 index 0000000..e046512 --- /dev/null +++ b/backend/src/database/migrations/archive/1701264274255-CallsToEntityEventsFix.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CallsToEntityEventsFix1701264274255 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into entity_event (account_id, entity_id, object_id, type, created_at) + select v.account_id, + v.entity_id, + v.id as object_id, + 'call' as type, + v.created_at + from voximplant_call v + where v.entity_id is not null + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1701437712747-DeleteEntityEventTelephonyCalls.ts b/backend/src/database/migrations/archive/1701437712747-DeleteEntityEventTelephonyCalls.ts new file mode 100644 index 0000000..fa112b0 --- /dev/null +++ b/backend/src/database/migrations/archive/1701437712747-DeleteEntityEventTelephonyCalls.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteEntityEventTelephonyCalls1701437712747 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from entity_event ee where ee.type='telephony-call'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1701701891843-AlterVoximplantCall.ts b/backend/src/database/migrations/archive/1701701891843-AlterVoximplantCall.ts new file mode 100644 index 0000000..ed9a931 --- /dev/null +++ b/backend/src/database/migrations/archive/1701701891843-AlterVoximplantCall.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterVoximplantCall1701701891843 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_call add column comment character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1702542289418-UpdateModulesIcons.ts b/backend/src/database/migrations/archive/1702542289418-UpdateModulesIcons.ts new file mode 100644 index 0000000..1b0072b --- /dev/null +++ b/backend/src/database/migrations/archive/1702542289418-UpdateModulesIcons.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateModulesIcons1702542289418 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update schedule set icon = 'calendar_4'; + update products_section set icon = 'box'; + update entity_type set section_icon = 'crown' where entity_category = 'deal'; + update entity_type set section_icon = 'user_2' where entity_category = 'contact'; + update entity_type set section_icon = 'building_2' where entity_category = 'company'; + update entity_type set section_icon = 'bulb' where entity_category = 'project'; + update entity_type set section_icon = 'star_2' where entity_category = 'hr'; + update entity_type set section_icon = 'product_1' where entity_category = 'supplier'; + update entity_type set section_icon = 'tie_2' where entity_category = 'contractor'; + update entity_type set section_icon = 'star_1' where entity_category = 'universal'; + update entity_type set section_icon = 'shapes_3' where entity_category = 'partner'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1702637665853-AlterActivityType.ts b/backend/src/database/migrations/archive/1702637665853-AlterActivityType.ts new file mode 100644 index 0000000..08de097 --- /dev/null +++ b/backend/src/database/migrations/archive/1702637665853-AlterActivityType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterActivityType1702637665853 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table activity_type add column is_active boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1702970939958-AlterAccountSettings.ts b/backend/src/database/migrations/archive/1702970939958-AlterAccountSettings.ts new file mode 100644 index 0000000..4ca9275 --- /dev/null +++ b/backend/src/database/migrations/archive/1702970939958-AlterAccountSettings.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountSettings1702970939958 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_settings add column phone_format character varying not null default 'international'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1703085253100-AlterProductPrice.ts b/backend/src/database/migrations/archive/1703085253100-AlterProductPrice.ts new file mode 100644 index 0000000..7ee3b6a --- /dev/null +++ b/backend/src/database/migrations/archive/1703085253100-AlterProductPrice.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterProductPrice1703085253100 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product_price add column max_discount integer default null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1703488850551-AlterAccountSettings.ts b/backend/src/database/migrations/archive/1703488850551-AlterAccountSettings.ts new file mode 100644 index 0000000..c86881f --- /dev/null +++ b/backend/src/database/migrations/archive/1703488850551-AlterAccountSettings.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountSettings1703488850551 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_settings add column allow_duplicates boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1703502036545-FieldSettings.ts b/backend/src/database/migrations/archive/1703502036545-FieldSettings.ts new file mode 100644 index 0000000..819ebb1 --- /dev/null +++ b/backend/src/database/migrations/archive/1703502036545-FieldSettings.ts @@ -0,0 +1,36 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FieldSettings1703502036545 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table field_user_settings ( + id integer generated by default as identity, + account_id integer not null, + field_id integer not null, + user_id integer not null, + access character varying not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (field_id) references field(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade + ); + + create table field_stage_settings ( + id integer generated by default as identity, + account_id integer not null, + field_id integer not null, + stage_id integer not null, + access character varying not null, + exclude_user_ids character varying, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (field_id) references field(id) on delete cascade, + foreign key (stage_id) references stage(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1703761495779-AlterFieldStageSettings.ts b/backend/src/database/migrations/archive/1703761495779-AlterFieldStageSettings.ts new file mode 100644 index 0000000..4f0f044 --- /dev/null +++ b/backend/src/database/migrations/archive/1703761495779-AlterFieldStageSettings.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterFieldStageSettings1703761495779 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field_stage_settings drop column exclude_user_ids; + alter table field_stage_settings add column exclude_user_ids integer[]; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1703850851646-AlterShipment.ts b/backend/src/database/migrations/archive/1703850851646-AlterShipment.ts new file mode 100644 index 0000000..240d325 --- /dev/null +++ b/backend/src/database/migrations/archive/1703850851646-AlterShipment.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterShipment1703850851646 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table shipment + add column entity_id integer, + add column order_number integer, + add foreign key (entity_id) references entity(id) on delete cascade; + + update shipment set entity_id = (select entity_id from orders where orders.id = shipment.order_id); + update shipment set order_number = (select order_number from orders where orders.id = shipment.order_id); + + alter table shipment + alter column entity_id set not null, + alter column order_number set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1703857140848-AlterReservation.ts b/backend/src/database/migrations/archive/1703857140848-AlterReservation.ts new file mode 100644 index 0000000..5c5c277 --- /dev/null +++ b/backend/src/database/migrations/archive/1703857140848-AlterReservation.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterReservation1703857140848 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from reservation where is_active = false; + + alter table reservation drop column is_active; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1703876929122-AlterChangeStageActionSettings.ts b/backend/src/database/migrations/archive/1703876929122-AlterChangeStageActionSettings.ts new file mode 100644 index 0000000..8e401c3 --- /dev/null +++ b/backend/src/database/migrations/archive/1703876929122-AlterChangeStageActionSettings.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChangeStageActionSettings1703876929122 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table change_stage_action_settings rename to entity_action_settings; + alter index change_stage_action_settings_pkey rename to entity_action_settings_pkey; + alter table entity_action_settings add column operation_type character varying not null default 'move'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1704282894747-AlterEntity.ts b/backend/src/database/migrations/archive/1704282894747-AlterEntity.ts new file mode 100644 index 0000000..3624550 --- /dev/null +++ b/backend/src/database/migrations/archive/1704282894747-AlterEntity.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterEntity1704282894747 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity add column copied_from integer default null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1706795467082-TestAccount.ts b/backend/src/database/migrations/archive/1706795467082-TestAccount.ts new file mode 100644 index 0000000..c9b042f --- /dev/null +++ b/backend/src/database/migrations/archive/1706795467082-TestAccount.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TestAccount1706795467082 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table test_account ( + account_id integer, + primary key (account_id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1708088397272-EntityStageChange.ts b/backend/src/database/migrations/archive/1708088397272-EntityStageChange.ts new file mode 100644 index 0000000..58c57e2 --- /dev/null +++ b/backend/src/database/migrations/archive/1708088397272-EntityStageChange.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityStageChange1708088397272 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table entity_stage_history ( + id integer generated by default as identity, + account_id integer not null, + entity_id integer not null, + board_id integer not null, + stage_id integer not null, + created_at timestamp without time zone not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (entity_id) references entity(id) on delete cascade, + foreign key (board_id) references board(id) on delete cascade, + foreign key (stage_id) references stage(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1708433846254-EntityChangeHistoryInit.ts b/backend/src/database/migrations/archive/1708433846254-EntityChangeHistoryInit.ts new file mode 100644 index 0000000..cf547fe --- /dev/null +++ b/backend/src/database/migrations/archive/1708433846254-EntityChangeHistoryInit.ts @@ -0,0 +1,28 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityChangeHistoryInit1708433846254 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from entity_stage_history; + + insert into entity_stage_history (account_id, entity_id, board_id, stage_id, created_at) + select + e.account_id as account_id, e.id as entity_id, s.board_id as board_id, e.stage_id as stage_id, e.created_at as created_at + from entity e + inner join stage s on s.id = e.stage_id + where s.is_system = false; + + insert into entity_stage_history (account_id, entity_id, board_id, stage_id, created_at) + select + e.account_id as account_id, e.id as entity_id, s.board_id as board_id, e.stage_id as stage_id, e.closed_at as created_at + from entity e + inner join stage s on s.id = e.stage_id + where s.is_system = true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1708589222946-UserAnalyticsId.ts b/backend/src/database/migrations/archive/1708589222946-UserAnalyticsId.ts new file mode 100644 index 0000000..947a0f6 --- /dev/null +++ b/backend/src/database/migrations/archive/1708589222946-UserAnalyticsId.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UserAnalyticsId1708589222946 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create extension if not exists "uuid-ossp"; + alter table users add column analytics_id uuid; + update users set analytics_id = uuid_generate_v4(); + alter table users alter column analytics_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1708952321460-OptimizeNotificationIndexes.ts b/backend/src/database/migrations/archive/1708952321460-OptimizeNotificationIndexes.ts new file mode 100644 index 0000000..44afd46 --- /dev/null +++ b/backend/src/database/migrations/archive/1708952321460-OptimizeNotificationIndexes.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OptimizeNotificationIndexes1708952321460 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop index notification_type_settings_settings_id_type_is_enabled_idx; + create index notification_type_settings_on_type_enabled_idx + on notification_type_settings(type) where is_enabled = true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1709047301377-DBOptimizationIndex.ts b/backend/src/database/migrations/archive/1709047301377-DBOptimizationIndex.ts new file mode 100644 index 0000000..d0e3d14 --- /dev/null +++ b/backend/src/database/migrations/archive/1709047301377-DBOptimizationIndex.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DBOptimizationIndex1709047301377 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index task_account_id_responsible_user_id_stage_id_idx on task(account_id, responsible_user_id, stage_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1709048922111-StageAccountIdIndex.ts b/backend/src/database/migrations/archive/1709048922111-StageAccountIdIndex.ts new file mode 100644 index 0000000..bb4e701 --- /dev/null +++ b/backend/src/database/migrations/archive/1709048922111-StageAccountIdIndex.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class StageAccountIdIndex1709048922111 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index stage_account_id_idx on stage(account_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1709110989045-ScheduledMailMessageIndex.ts b/backend/src/database/migrations/archive/1709110989045-ScheduledMailMessageIndex.ts new file mode 100644 index 0000000..0a36390 --- /dev/null +++ b/backend/src/database/migrations/archive/1709110989045-ScheduledMailMessageIndex.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ScheduledMailMessageIndex1709110989045 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index scheduled_mail_message_mailbox_id_sent_at_null_idx + on scheduled_mail_message(mailbox_id) + where sent_at is null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1709111575857-AddIndexes.ts b/backend/src/database/migrations/archive/1709111575857-AddIndexes.ts new file mode 100644 index 0000000..8fa883b --- /dev/null +++ b/backend/src/database/migrations/archive/1709111575857-AddIndexes.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIndexes1709111575857 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index entity_type_link_account_id_source_id_sort_order_id_idx on entity_type_link(account_id, source_id, sort_order, id); + create index users_account_id_id_idx on users(account_id, id); + create index field_group_account_id_entity_type_id_idx on field_group(account_id, entity_type_id); + + create index idx_chat_on_account_id on chat(account_id); + create index idx_cmus_on_message_id_status on chat_message_user_status(message_id, status); + create index idx_cmus_on_message_id_chat_id_status on chat_message_user_status(message_id, chat_id, status); + create index idx_chat_user_on_user_id on chat_user(user_id); + create index idx_chat_user_on_user_id_chat_id on chat_user(user_id, chat_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1709280253891-ChatProviderCascadeDelete.ts b/backend/src/database/migrations/archive/1709280253891-ChatProviderCascadeDelete.ts new file mode 100644 index 0000000..73e54e0 --- /dev/null +++ b/backend/src/database/migrations/archive/1709280253891-ChatProviderCascadeDelete.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatProviderCascadeDelete1709280253891 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat + drop constraint chat_provider_id_fkey, + add constraint chat_provider_id_fkey foreign key (provider_id) references chat_provider(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1709736232826-ChatEntityRemoveCascade.ts b/backend/src/database/migrations/archive/1709736232826-ChatEntityRemoveCascade.ts new file mode 100644 index 0000000..31c287a --- /dev/null +++ b/backend/src/database/migrations/archive/1709736232826-ChatEntityRemoveCascade.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatEntityRemoveCascade1709736232826 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat + drop constraint chat_entity_id_fkey, + add constraint chat_entity_id_fkey foreign key (entity_id) references entity(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1709805560320-ChatUserExternal.ts b/backend/src/database/migrations/archive/1709805560320-ChatUserExternal.ts new file mode 100644 index 0000000..865b8cf --- /dev/null +++ b/backend/src/database/migrations/archive/1709805560320-ChatUserExternal.ts @@ -0,0 +1,37 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatUserExternal1709805560320 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table chat_user_external ( + id integer generated by default as identity, + account_id integer not null, + external_id character varying not null, + first_name character varying, + last_name character varying, + avatar_url character varying, + phone character varying, + email character varying, + link character varying, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + delete from chat_user where external_id is not null; + + alter table chat_user + drop column external_id, + drop column external_first_name, + drop column external_last_name, + drop column external_avatar_url; + + alter table chat_user + add column external_user_id integer, + add foreign key (external_user_id) references chat_user_external(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1710162901881-EntityCopiedCount.ts b/backend/src/database/migrations/archive/1710162901881-EntityCopiedCount.ts new file mode 100644 index 0000000..3c92c4e --- /dev/null +++ b/backend/src/database/migrations/archive/1710162901881-EntityCopiedCount.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityCopiedCount1710162901881 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity add column copied_count integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1710758264055-OrderCancelAfter.ts b/backend/src/database/migrations/archive/1710758264055-OrderCancelAfter.ts new file mode 100644 index 0000000..a9d981b --- /dev/null +++ b/backend/src/database/migrations/archive/1710758264055-OrderCancelAfter.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderCancelAfter1710758264055 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table orders + add column updated_at timestamp without time zone, + add column cancel_after integer; + + update orders set updated_at = created_at; + + alter table orders alter column updated_at set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1710759144910-ProductSectionCancelAfter.ts b/backend/src/database/migrations/archive/1710759144910-ProductSectionCancelAfter.ts new file mode 100644 index 0000000..3304a4f --- /dev/null +++ b/backend/src/database/migrations/archive/1710759144910-ProductSectionCancelAfter.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProductSectionCancelAfter1710759144910 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table products_section add column cancel_after integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1710864090375-ScheduledActionCreatedBy.ts b/backend/src/database/migrations/archive/1710864090375-ScheduledActionCreatedBy.ts new file mode 100644 index 0000000..0a57576 --- /dev/null +++ b/backend/src/database/migrations/archive/1710864090375-ScheduledActionCreatedBy.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ScheduledActionCreatedBy1710864090375 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table scheduled_action + add column created_by integer, + add foreign key (created_by) references users(id); + + update scheduled_action + set created_by = (select created_by from automation where automation.action_id = scheduled_action.action_id); + + alter table scheduled_action alter column created_by set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1710927112868-TutorialGroup.ts b/backend/src/database/migrations/archive/1710927112868-TutorialGroup.ts new file mode 100644 index 0000000..a084cd5 --- /dev/null +++ b/backend/src/database/migrations/archive/1710927112868-TutorialGroup.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TutorialGroup1710927112868 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table tutorial_group ( + id integer generated by default as identity, + account_id integer not null, + created_at timestamp without time zone not null, + name character varying not null, + sort_order integer not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1710929893275-TutorialItem.ts b/backend/src/database/migrations/archive/1710929893275-TutorialItem.ts new file mode 100644 index 0000000..eaddd4a --- /dev/null +++ b/backend/src/database/migrations/archive/1710929893275-TutorialItem.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TutorialItem1710929893275 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table tutorial_item ( + id integer generated by default as identity, + account_id integer not null, + group_id integer not null, + created_at timestamp without time zone not null, + name character varying not null, + link character varying not null, + sort_order integer not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (group_id) references tutorial_group(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1710939538331-TutorialItemUser.ts b/backend/src/database/migrations/archive/1710939538331-TutorialItemUser.ts new file mode 100644 index 0000000..ac78d96 --- /dev/null +++ b/backend/src/database/migrations/archive/1710939538331-TutorialItemUser.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TutorialItemUser1710939538331 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table tutorial_item_user ( + item_id integer not null, + user_id integer not null, + primary key (item_id, user_id), + foreign key (item_id) references tutorial_item(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1711033243401-TutorialItemProduct.ts b/backend/src/database/migrations/archive/1711033243401-TutorialItemProduct.ts new file mode 100644 index 0000000..e9799ef --- /dev/null +++ b/backend/src/database/migrations/archive/1711033243401-TutorialItemProduct.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TutorialItemProduct1711033243401 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table tutorial_item_product ( + id integer generated by default as identity, + account_id integer not null, + item_id integer not null, + type character varying not null, + object_id integer, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (item_id) references tutorial_item(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1711087326245-MailMessageIndexes.ts b/backend/src/database/migrations/archive/1711087326245-MailMessageIndexes.ts new file mode 100644 index 0000000..634946c --- /dev/null +++ b/backend/src/database/migrations/archive/1711087326245-MailMessageIndexes.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailMessageIndexes1711087326245 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX idx_mail_message_account_mailbox ON mail_message(account_id, mailbox_id); + CREATE INDEX idx_mail_message_external_id ON mail_message(external_id); + CREATE INDEX idx_mail_message_message_id ON mail_message(message_id); + CREATE INDEX idx_mailbox_folder_account_mailbox ON mailbox_folder(account_id, mailbox_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1711540999340-EntityActionSettings.ts b/backend/src/database/migrations/archive/1711540999340-EntityActionSettings.ts new file mode 100644 index 0000000..967f100 --- /dev/null +++ b/backend/src/database/migrations/archive/1711540999340-EntityActionSettings.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityActionSettings1711540999340 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update entity_action_settings set operation_type = 'copy_original' where operation_type = 'copy'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1711541635402-ActionSettingsRename.ts b/backend/src/database/migrations/archive/1711541635402-ActionSettingsRename.ts new file mode 100644 index 0000000..36a0441 --- /dev/null +++ b/backend/src/database/migrations/archive/1711541635402-ActionSettingsRename.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ActionSettingsRename1711541635402 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table activity_action_settings rename to action_settings_activity; + alter table email_action_settings rename to action_settings_email; + alter table entity_action_settings rename to action_settings_entity; + alter table task_action_settings rename to action_settings_task; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1711706670268-ScheduledAction.ts b/backend/src/database/migrations/archive/1711706670268-ScheduledAction.ts new file mode 100644 index 0000000..cffa1ae --- /dev/null +++ b/backend/src/database/migrations/archive/1711706670268-ScheduledAction.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ScheduledAction1711706670268 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table scheduled_action rename to action_scheduled; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1711962655915-AutomationActionSettings.ts b/backend/src/database/migrations/archive/1711962655915-AutomationActionSettings.ts new file mode 100644 index 0000000..b27342c --- /dev/null +++ b/backend/src/database/migrations/archive/1711962655915-AutomationActionSettings.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AutomationActionSettings1711962655915 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table action_settings_activity rename to action_activity_settings; + alter table action_settings_email rename to action_email_settings; + alter table action_settings_entity rename to action_entity_settings; + alter table action_settings_task rename to action_task_settings; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1712575547663-FieldValue.ts b/backend/src/database/migrations/archive/1712575547663-FieldValue.ts new file mode 100644 index 0000000..5412458 --- /dev/null +++ b/backend/src/database/migrations/archive/1712575547663-FieldValue.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FieldValue1712575547663 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field add column value character varying; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1713167989297-DeleteAllFormulaFields.ts b/backend/src/database/migrations/archive/1713167989297-DeleteAllFormulaFields.ts new file mode 100644 index 0000000..0b01a37 --- /dev/null +++ b/backend/src/database/migrations/archive/1713167989297-DeleteAllFormulaFields.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeleteAllFormulaFields1713167989297 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from field where type = 'formula'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1713257835467-FixEntityTypeSortOrder.ts b/backend/src/database/migrations/archive/1713257835467-FixEntityTypeSortOrder.ts new file mode 100644 index 0000000..23988b8 --- /dev/null +++ b/backend/src/database/migrations/archive/1713257835467-FixEntityTypeSortOrder.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixEntityTypeSortOrder1713257835467 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + WITH Sorted AS ( + SELECT id, row_number() OVER (PARTITION BY account_id ORDER BY id) - 1 AS new_sort_order + FROM entity_type + ) + UPDATE entity_type + SET sort_order = Sorted.new_sort_order + FROM Sorted + WHERE entity_type.id = Sorted.id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1713258622876-FixEntityTypeLinkSortOrder.ts b/backend/src/database/migrations/archive/1713258622876-FixEntityTypeLinkSortOrder.ts new file mode 100644 index 0000000..5ec1b03 --- /dev/null +++ b/backend/src/database/migrations/archive/1713258622876-FixEntityTypeLinkSortOrder.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FixEntityTypeLinkSortOrder1713258622876 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + WITH Sorted AS ( + SELECT id, row_number() OVER (PARTITION BY source_id ORDER BY id) - 1 AS new_sort_order + FROM entity_type_link + ) + UPDATE entity_type_link + SET sort_order = Sorted.new_sort_order + FROM Sorted + WHERE entity_type_link.id = Sorted.id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1713971186799-ChatProviderTransport.ts b/backend/src/database/migrations/archive/1713971186799-ChatProviderTransport.ts new file mode 100644 index 0000000..d99479c --- /dev/null +++ b/backend/src/database/migrations/archive/1713971186799-ChatProviderTransport.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatProviderTransport1713971186799 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider add column transport character varying; + update chat_provider set transport = 'amwork' where type = 'amwork'; + update chat_provider set transport = 'whatsapp' where type = 'twilio_whatsapp'; + update chat_provider set transport = 'messenger' where type = 'facebook_messenger'; + alter table chat_provider alter column transport set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1714382561376-WazzupProvider.ts b/backend/src/database/migrations/archive/1714382561376-WazzupProvider.ts new file mode 100644 index 0000000..f77aaec --- /dev/null +++ b/backend/src/database/migrations/archive/1714382561376-WazzupProvider.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class WazzupProvider1714382561376 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table chat_provider_wazzup ( + provider_id integer, + account_id integer not null, + api_key character varying not null, + channel_id character varying not null, + chat_type character varying not null, + plain_id character varying not null, + primary key (provider_id), + foreign key (provider_id) references chat_provider(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1714557065128-ProductPricePrecision.ts b/backend/src/database/migrations/archive/1714557065128-ProductPricePrecision.ts new file mode 100644 index 0000000..03fda06 --- /dev/null +++ b/backend/src/database/migrations/archive/1714557065128-ProductPricePrecision.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProductPricePrecision1714557065128 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "product_price" ALTER COLUMN "unit_price" TYPE numeric(15,2); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1714663341741-WazzupProviderRemoveChatType.ts b/backend/src/database/migrations/archive/1714663341741-WazzupProviderRemoveChatType.ts new file mode 100644 index 0000000..2780bf8 --- /dev/null +++ b/backend/src/database/migrations/archive/1714663341741-WazzupProviderRemoveChatType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class WazzupProviderRemoveChatType1714663341741 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider_wazzup drop column chat_type; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1714730508391-WazzupProviderTransport.ts b/backend/src/database/migrations/archive/1714730508391-WazzupProviderTransport.ts new file mode 100644 index 0000000..5af276a --- /dev/null +++ b/backend/src/database/migrations/archive/1714730508391-WazzupProviderTransport.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class WazzupProviderTransport1714730508391 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from chat_provider_wazzup; + alter table chat_provider_wazzup add column transport character varying not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1714732587962-RemoveWazzupProviders.ts b/backend/src/database/migrations/archive/1714732587962-RemoveWazzupProviders.ts new file mode 100644 index 0000000..b0e8155 --- /dev/null +++ b/backend/src/database/migrations/archive/1714732587962-RemoveWazzupProviders.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveWazzupProviders1714732587962 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from chat_provider where type = 'wazzup'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1714734600334-ChatProviderTypeRename.ts b/backend/src/database/migrations/archive/1714734600334-ChatProviderTypeRename.ts new file mode 100644 index 0000000..feb9272 --- /dev/null +++ b/backend/src/database/migrations/archive/1714734600334-ChatProviderTypeRename.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatProviderTypeRename1714734600334 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update chat_provider set type='twilio' where type='twilio_whatsapp'; + update chat_provider set type='facebook' where type='facebook_messenger'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1715602468891-ChatProviderUserType.ts b/backend/src/database/migrations/archive/1715602468891-ChatProviderUserType.ts new file mode 100644 index 0000000..2416e2a --- /dev/null +++ b/backend/src/database/migrations/archive/1715602468891-ChatProviderUserType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatProviderUserType1715602468891 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter type chat_provider_user_type add value 'supervisor'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1715610371002-AlterChatUserExternal.ts b/backend/src/database/migrations/archive/1715610371002-AlterChatUserExternal.ts new file mode 100644 index 0000000..d80c567 --- /dev/null +++ b/backend/src/database/migrations/archive/1715610371002-AlterChatUserExternal.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatUserExternal1715610371002 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_user_external + add column chat_user_id integer, + add foreign key (chat_user_id) references chat_user(id) on delete cascade; + + update chat_user_external + set chat_user_id = ( + select id + from chat_user + where chat_user.external_user_id = chat_user_external.id + ); + + delete from chat_user_external where chat_user_id is null; + + alter table chat_user drop column external_user_id; + + alter table chat_user_external + drop constraint chat_user_external_pkey, + add primary key (chat_user_id); + + alter table chat_user_external drop column id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1715856544173-AccountApiAccess.ts b/backend/src/database/migrations/archive/1715856544173-AccountApiAccess.ts new file mode 100644 index 0000000..86bc067 --- /dev/null +++ b/backend/src/database/migrations/archive/1715856544173-AccountApiAccess.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AccountApiAccess1715856544173 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table account_api_access ( + account_id integer, + api_key character varying not null, + created_at timestamp without time zone not null, + primary key (account_id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1715856948928-AlterAccountApiAccess.ts b/backend/src/database/migrations/archive/1715856948928-AlterAccountApiAccess.ts new file mode 100644 index 0000000..5a949b9 --- /dev/null +++ b/backend/src/database/migrations/archive/1715856948928-AlterAccountApiAccess.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountApiAccess1715856948928 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_api_access alter column created_at set default now(); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1716299180820-VoximplantNumber.ts b/backend/src/database/migrations/archive/1716299180820-VoximplantNumber.ts new file mode 100644 index 0000000..a800711 --- /dev/null +++ b/backend/src/database/migrations/archive/1716299180820-VoximplantNumber.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantNumber1716299180820 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table voximplant_number ( + id integer generated by default as identity, + account_id integer not null, + number character varying not null, + external_id character varying, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + create table voximplant_number_user ( + number_id integer, + user_id integer, + account_id integer not null, + primary key (number_id, user_id), + foreign key (number_id) references voximplant_number(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1716384743872-VoximplantPhoneNumber.ts b/backend/src/database/migrations/archive/1716384743872-VoximplantPhoneNumber.ts new file mode 100644 index 0000000..9d6e2db --- /dev/null +++ b/backend/src/database/migrations/archive/1716384743872-VoximplantPhoneNumber.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantPhoneNumber1716384743872 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE voximplant_number RENAME COLUMN number TO phone_number; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1716465152984-VoximplantNumberDefaults.ts b/backend/src/database/migrations/archive/1716465152984-VoximplantNumberDefaults.ts new file mode 100644 index 0000000..277d972 --- /dev/null +++ b/backend/src/database/migrations/archive/1716465152984-VoximplantNumberDefaults.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantNumberDefaults1716465152984 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + INSERT INTO voximplant_number (account_id, phone_number, external_id) + SELECT account_id, '', '' FROM voximplant_account; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1716466922606-VoximplantCallNumber.ts b/backend/src/database/migrations/archive/1716466922606-VoximplantCallNumber.ts new file mode 100644 index 0000000..9e8e8c2 --- /dev/null +++ b/backend/src/database/migrations/archive/1716466922606-VoximplantCallNumber.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantCallNumber1716466922606 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE voximplant_call + ADD COLUMN number_id integer, + ADD FOREIGN KEY (number_id) REFERENCES voximplant_number(id) ON DELETE SET NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1716469802549-VoximplantCallNumberDefault.ts b/backend/src/database/migrations/archive/1716469802549-VoximplantCallNumberDefault.ts new file mode 100644 index 0000000..abc229d --- /dev/null +++ b/backend/src/database/migrations/archive/1716469802549-VoximplantCallNumberDefault.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantCallNumberDefault1716469802549 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update voximplant_call set number_id = ( + select id from voximplant_number where voximplant_number.account_id = voximplant_call.account_id limit 1 + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1717599382958-AddForms.ts b/backend/src/database/migrations/archive/1717599382958-AddForms.ts new file mode 100644 index 0000000..1747364 --- /dev/null +++ b/backend/src/database/migrations/archive/1717599382958-AddForms.ts @@ -0,0 +1,52 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddForms1717599382958 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE site_form ( + id integer GENERATED BY DEFAULT AS IDENTITY, + account_id integer NOT NULL, + name text NOT NULL, + code text NOT NULL, + is_active boolean NOT NULL, + title text, + type text NOT NULL, + color_main text, + color_background text, + color_font text, + consent_enabled boolean NOT NULL DEFAULT false, + consent_text text, + consent_url text, + PRIMARY KEY (id), + FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE, + UNIQUE (code) + ); + + CREATE TABLE site_form_page ( + id integer GENERATED BY DEFAULT AS IDENTITY, + account_id integer NOT NULL, + form_id integer NOT NULL, + title text, + PRIMARY KEY (id), + FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE, + FOREIGN KEY (form_id) REFERENCES site_form(id) ON DELETE CASCADE + ); + + CREATE TABLE site_form_field ( + id integer GENERATED BY DEFAULT AS IDENTITY, + account_id integer NOT NULL, + page_id integer NOT NULL, + title text NOT NULL, + type text NOT NULL, + is_required boolean NOT NULL DEFAULT false, + PRIMARY KEY (id), + FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE, + FOREIGN KEY (page_id) REFERENCES site_form_page(id) ON DELETE CASCADE + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1717746424353-SiteFormPageSortOrder.ts b/backend/src/database/migrations/archive/1717746424353-SiteFormPageSortOrder.ts new file mode 100644 index 0000000..ed97416 --- /dev/null +++ b/backend/src/database/migrations/archive/1717746424353-SiteFormPageSortOrder.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SiteFormPageSortOrder1717746424353 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE site_form_page ADD COLUMN sort_order integer NOT NULL; + ALTER TABLE site_form_field ADD COLUMN sort_order integer NOT NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1717773341006-FormSiteLink.ts b/backend/src/database/migrations/archive/1717773341006-FormSiteLink.ts new file mode 100644 index 0000000..e125f46 --- /dev/null +++ b/backend/src/database/migrations/archive/1717773341006-FormSiteLink.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FormSiteLink1717773341006 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE site_form_link ( + id integer GENERATED BY DEFAULT AS IDENTITY, + account_id integer NOT NULL, + form_id integer NOT NULL, + type text NOT NULL, + object_id integer NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE, + FOREIGN KEY (form_id) REFERENCES site_form(id) ON DELETE CASCADE + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718030543491-SiteFormEntityType.ts b/backend/src/database/migrations/archive/1718030543491-SiteFormEntityType.ts new file mode 100644 index 0000000..67abc00 --- /dev/null +++ b/backend/src/database/migrations/archive/1718030543491-SiteFormEntityType.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SiteFormEntityType1718030543491 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table site_form_link; + + create table site_form_entity_type ( + form_id integer, + entity_type_id integer, + account_id integer not null, + board_id integer, + primary key (form_id, entity_type_id), + foreign key (form_id) references site_form(id) on delete cascade, + foreign key (entity_type_id) references entity_type(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade, + foreign key (board_id) references board(id) on delete set null + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718098299378-SiteFormRemoveConsent.ts b/backend/src/database/migrations/archive/1718098299378-SiteFormRemoveConsent.ts new file mode 100644 index 0000000..8783bb0 --- /dev/null +++ b/backend/src/database/migrations/archive/1718098299378-SiteFormRemoveConsent.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SiteFormRemoveConsent1718098299378 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form + drop column consent_enabled, + drop column consent_text, + drop column consent_url; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718098642901-SiteFormConsent.ts b/backend/src/database/migrations/archive/1718098642901-SiteFormConsent.ts new file mode 100644 index 0000000..a6799ee --- /dev/null +++ b/backend/src/database/migrations/archive/1718098642901-SiteFormConsent.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SiteFormConsent1718098642901 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table site_form_consent ( + form_id integer, + account_id integer, + is_enabled boolean not null default false, + text text, + link_url text, + link_text text, + default_value boolean not null default false, + primary key (form_id), + foreign key (form_id) references site_form(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718115282972-SiteFormGratitude.ts b/backend/src/database/migrations/archive/1718115282972-SiteFormGratitude.ts new file mode 100644 index 0000000..c10a671 --- /dev/null +++ b/backend/src/database/migrations/archive/1718115282972-SiteFormGratitude.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SiteFormGratitude1718115282972 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table site_form_gratitude ( + form_id integer, + account_id integer, + is_enabled boolean not null default false, + header text, + text text, + primary key (form_id), + foreign key (form_id) references site_form(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718118622975-AlterSiteForm.ts b/backend/src/database/migrations/archive/1718118622975-AlterSiteForm.ts new file mode 100644 index 0000000..5b1f496 --- /dev/null +++ b/backend/src/database/migrations/archive/1718118622975-AlterSiteForm.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1718118622975 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form + drop column type, + drop column color_main, + drop column color_background, + drop column color_font, + alter column title set default null, + add column responsible_id integer default null, + add column design jsonb default null, + add foreign key (responsible_id) references users(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718120948980-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1718120948980-AlterSiteFormField.ts new file mode 100644 index 0000000..22eb3af --- /dev/null +++ b/backend/src/database/migrations/archive/1718120948980-AlterSiteFormField.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1718120948980 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form_field rename column title to label; + alter table site_form_field + alter column label drop not null, + add column placeholder text, + add column field_id integer, + add foreign key (field_id) references field(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718266194827-NotificationRemoveTag.ts b/backend/src/database/migrations/archive/1718266194827-NotificationRemoveTag.ts new file mode 100644 index 0000000..e5fd289 --- /dev/null +++ b/backend/src/database/migrations/archive/1718266194827-NotificationRemoveTag.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NotificationRemoveTag1718266194827 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table notification drop column tag_name; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718613418648-AlterSiteForm.ts b/backend/src/database/migrations/archive/1718613418648-AlterSiteForm.ts new file mode 100644 index 0000000..9bec5e3 --- /dev/null +++ b/backend/src/database/migrations/archive/1718613418648-AlterSiteForm.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1718613418648 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form + add column field_label_enabled boolean not null default false, + add column field_placeholder_enabled boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718715571983-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1718715571983-AlterSiteFormField.ts new file mode 100644 index 0000000..38c85c5 --- /dev/null +++ b/backend/src/database/migrations/archive/1718715571983-AlterSiteFormField.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1718715571983 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form_field rename column field_id to object_id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718724129043-AutomationProcess.ts b/backend/src/database/migrations/archive/1718724129043-AutomationProcess.ts new file mode 100644 index 0000000..3d9ac39 --- /dev/null +++ b/backend/src/database/migrations/archive/1718724129043-AutomationProcess.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AutomationProcess1718724129043 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table automation_process ( + id integer generated by default as identity, + account_id integer not null, + created_at timestamp without time zone not null default now(), + type text not null default 'simple', + is_active boolean not null default false, + external_id text, + data text, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718793150525-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1718793150525-AlterSiteFormField.ts new file mode 100644 index 0000000..d920b90 --- /dev/null +++ b/backend/src/database/migrations/archive/1718793150525-AlterSiteFormField.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1718793150525 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form_field + add column meta jsonb default null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718793757895-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1718793757895-AlterSiteFormField.ts new file mode 100644 index 0000000..3b00c2d --- /dev/null +++ b/backend/src/database/migrations/archive/1718793757895-AlterSiteFormField.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1718793757895 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form_field + add column is_validation_required boolean default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718798058177-AlterAutomationProcess.ts b/backend/src/database/migrations/archive/1718798058177-AlterAutomationProcess.ts new file mode 100644 index 0000000..e6e9aff --- /dev/null +++ b/backend/src/database/migrations/archive/1718798058177-AlterAutomationProcess.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationProcess1718798058177 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_process rename column external_id to resource_key; + alter table automation_process + drop column type, + drop column is_active, + add column bpmn_process_id text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718801950089-AlterAutomationProcess.ts b/backend/src/database/migrations/archive/1718801950089-AlterAutomationProcess.ts new file mode 100644 index 0000000..d9a8aef --- /dev/null +++ b/backend/src/database/migrations/archive/1718801950089-AlterAutomationProcess.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationProcess1718801950089 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_process + add column name text not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1718880290844-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1718880290844-AlterSiteFormField.ts new file mode 100644 index 0000000..abc5917 --- /dev/null +++ b/backend/src/database/migrations/archive/1718880290844-AlterSiteFormField.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1718880290844 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form_field alter column is_validation_required drop default; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719213418299-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1719213418299-AlterSiteFormField.ts new file mode 100644 index 0000000..3387120 --- /dev/null +++ b/backend/src/database/migrations/archive/1719213418299-AlterSiteFormField.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1719213418299 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form_field alter column is_required drop not null; + alter table site_form_field alter column is_required drop default; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719302707526-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1719302707526-AlterSiteFormField.ts new file mode 100644 index 0000000..6f72bd6 --- /dev/null +++ b/backend/src/database/migrations/archive/1719302707526-AlterSiteFormField.ts @@ -0,0 +1,38 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1719302707526 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from site_form_field; + + alter table site_form_field + drop column object_id, + drop column meta, + drop column is_validation_required; + + create table site_form_field_entity_name ( + form_field_id integer, + entity_type_id integer not null, + primary key (form_field_id), + foreign key (form_field_id) references site_form_field(id) on delete cascade, + foreign key (entity_type_id) references entity_type(id) on delete cascade + ); + + create table site_form_field_entity_field ( + form_field_id integer, + field_id integer not null, + entity_type_id integer not null, + is_validation_required boolean, + meta jsonb, + primary key (form_field_id), + foreign key (form_field_id) references site_form_field(id) on delete cascade, + foreign key (field_id) references field(id) on delete cascade, + foreign key (entity_type_id) references entity_type(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719324782700-RenameSubtask.ts b/backend/src/database/migrations/archive/1719324782700-RenameSubtask.ts new file mode 100644 index 0000000..5442bd7 --- /dev/null +++ b/backend/src/database/migrations/archive/1719324782700-RenameSubtask.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameSubtask1719324782700 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table subtask rename to task_subtask; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719325474889-AlterTaskSubtaskSortOrder.ts b/backend/src/database/migrations/archive/1719325474889-AlterTaskSubtaskSortOrder.ts new file mode 100644 index 0000000..563b340 --- /dev/null +++ b/backend/src/database/migrations/archive/1719325474889-AlterTaskSubtaskSortOrder.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterTaskSubtaskSortOrder1719325474889 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task_subtask add column sort_order integer not null default 0; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719393706903-AlterAutomationProcess.ts b/backend/src/database/migrations/archive/1719393706903-AlterAutomationProcess.ts new file mode 100644 index 0000000..32a7760 --- /dev/null +++ b/backend/src/database/migrations/archive/1719393706903-AlterAutomationProcess.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationProcess1719393706903 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_process rename column data to bpmn_file; + alter table automation_process drop column name; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719396909800-AutomationEntityType.ts b/backend/src/database/migrations/archive/1719396909800-AutomationEntityType.ts new file mode 100644 index 0000000..8d219a5 --- /dev/null +++ b/backend/src/database/migrations/archive/1719396909800-AutomationEntityType.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AutomationEntityType1719396909800 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table automation_entity_type ( + id integer generated always as identity, + account_id integer not null, + created_at timestamp without time zone not null default now(), + created_by integer not null, + name text not null, + is_active boolean not null, + triggers text not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (created_by) references users(id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719404585119-AlterAutomationEntityType.ts b/backend/src/database/migrations/archive/1719404585119-AlterAutomationEntityType.ts new file mode 100644 index 0000000..410ddf6 --- /dev/null +++ b/backend/src/database/migrations/archive/1719404585119-AlterAutomationEntityType.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationEntityType1719404585119 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_entity_type + add column entity_type_id integer not null, + add column board_id integer, + add column stage_id integer, + add foreign key (entity_type_id) references entity_type(id), + add foreign key (board_id) references board(id), + add foreign key (stage_id) references stage(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719411072571-AlterAutomationEntityType.ts b/backend/src/database/migrations/archive/1719411072571-AlterAutomationEntityType.ts new file mode 100644 index 0000000..50286b4 --- /dev/null +++ b/backend/src/database/migrations/archive/1719411072571-AlterAutomationEntityType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationEntityType1719411072571 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_entity_type add column delay integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719414260257-AlterAutomationEntityType.ts b/backend/src/database/migrations/archive/1719414260257-AlterAutomationEntityType.ts new file mode 100644 index 0000000..a68cf8a --- /dev/null +++ b/backend/src/database/migrations/archive/1719414260257-AlterAutomationEntityType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationEntityType1719414260257 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_entity_type add column conditions jsonb; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719502764234-AlterAutomationEntityType.ts b/backend/src/database/migrations/archive/1719502764234-AlterAutomationEntityType.ts new file mode 100644 index 0000000..055cfd4 --- /dev/null +++ b/backend/src/database/migrations/archive/1719502764234-AlterAutomationEntityType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationEntityType1719502764234 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_entity_type drop column delay; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719502897605-AlterAutomationEntityType.ts b/backend/src/database/migrations/archive/1719502897605-AlterAutomationEntityType.ts new file mode 100644 index 0000000..b48bfe2 --- /dev/null +++ b/backend/src/database/migrations/archive/1719502897605-AlterAutomationEntityType.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationEntityType1719502897605 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_entity_type add column actions jsonb; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719833217147-AlterEntityType.ts b/backend/src/database/migrations/archive/1719833217147-AlterEntityType.ts new file mode 100644 index 0000000..4b8288d --- /dev/null +++ b/backend/src/database/migrations/archive/1719833217147-AlterEntityType.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterEntityType1719833217147 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity_type + drop column code, + drop column need_migration; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1719995612500-UpdateCopiesCreatedAt.ts b/backend/src/database/migrations/archive/1719995612500-UpdateCopiesCreatedAt.ts new file mode 100644 index 0000000..93d8616 --- /dev/null +++ b/backend/src/database/migrations/archive/1719995612500-UpdateCopiesCreatedAt.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateCopiesCreatedAt1719995612500 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +WITH RECURSIVE entity_hierarchy AS ( + -- Base case: Select the initial entities + SELECT + id, + created_at, + copied_from + FROM + entity + WHERE + copied_from IS NULL + + UNION ALL + + -- Recursive case: Join the entity table to build the hierarchy + SELECT + e.id, + eh.created_at, + e.copied_from + FROM + entity e + INNER JOIN + entity_hierarchy eh + ON + e.copied_from = eh.id +) +UPDATE + entity e1 +SET + created_at = eh.created_at +FROM + entity_hierarchy eh +WHERE + e1.id = eh.id +AND + e1.copied_from IS NOT NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720076313493-AlterSiteForm.ts b/backend/src/database/migrations/archive/1720076313493-AlterSiteForm.ts new file mode 100644 index 0000000..02f9982 --- /dev/null +++ b/backend/src/database/migrations/archive/1720076313493-AlterSiteForm.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1720076313493 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form + add column created_by integer, + add foreign key (created_by) references users(id); + + update site_form set created_by = responsible_id; + + alter table site_form alter column created_by set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720077438733-AlterSiteFormEntityType.ts b/backend/src/database/migrations/archive/1720077438733-AlterSiteFormEntityType.ts new file mode 100644 index 0000000..12220a5 --- /dev/null +++ b/backend/src/database/migrations/archive/1720077438733-AlterSiteFormEntityType.ts @@ -0,0 +1,25 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormEntityType1720077438733 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form_entity_type add column is_main boolean; + + with selected as ( + select distinct on (form_id) form_id, entity_type_id + from site_form_entity_type + order by form_id, entity_type_id + ) + update site_form_entity_type + set is_main = case when (form_id, entity_type_id) in (select form_id, entity_type_id from selected) then true else false end + where form_id in (select form_id from selected); + + alter table site_form_entity_type alter column is_main set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720194016236-AppSumoLicense.ts b/backend/src/database/migrations/archive/1720194016236-AppSumoLicense.ts new file mode 100644 index 0000000..c34f732 --- /dev/null +++ b/backend/src/database/migrations/archive/1720194016236-AppSumoLicense.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AppSumoLicense1720194016236 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table app_sumo_license ( + id integer generated by default as identity, + license_key text not null, + license_status text not null, + plan_id text not null, + tier integer not null, + account_id integer, + created_at timestamp without time zone not null default now(), + primary key (id), + foreign key (account_id) references account(id) on delete set null + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720194705296-AppSumoPreset.ts b/backend/src/database/migrations/archive/1720194705296-AppSumoPreset.ts new file mode 100644 index 0000000..57cf32e --- /dev/null +++ b/backend/src/database/migrations/archive/1720194705296-AppSumoPreset.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AppSumoPreset1720194705296 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table app_sumo_preset ( + id integer generated by default as identity, + tier integer not null, + user_count integer not null, + term_in_days integer not null, + primary key (id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720423072348-AppSumoPresets.ts b/backend/src/database/migrations/archive/1720423072348-AppSumoPresets.ts new file mode 100644 index 0000000..326f0b7 --- /dev/null +++ b/backend/src/database/migrations/archive/1720423072348-AppSumoPresets.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AppSumoPresets1720423072348 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into app_sumo_preset(tier, user_count, term_in_days) values(1, 1, 36500); + insert into app_sumo_preset(tier, user_count, term_in_days) values(2, 5, 36500); + insert into app_sumo_preset(tier, user_count, term_in_days) values(3, 15, 36500); + insert into app_sumo_preset(tier, user_count, term_in_days) values(4, 30, 36500); + insert into app_sumo_preset(tier, user_count, term_in_days) values(5, 50, 36500); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720423711324-RenameAppSumo.ts b/backend/src/database/migrations/archive/1720423711324-RenameAppSumo.ts new file mode 100644 index 0000000..89078cf --- /dev/null +++ b/backend/src/database/migrations/archive/1720423711324-RenameAppSumo.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameAppSumo1720423711324 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table app_sumo_preset rename to appsumo_preset; + alter table app_sumo_license rename to appsumo_license; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720434041376-AppsumoLicenseUnique.ts b/backend/src/database/migrations/archive/1720434041376-AppsumoLicenseUnique.ts new file mode 100644 index 0000000..2522e1a --- /dev/null +++ b/backend/src/database/migrations/archive/1720434041376-AppsumoLicenseUnique.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AppsumoLicenseUnique1720434041376 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table appsumo_license add unique (license_key); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720446828855-AlterReadyMadeSolution.ts b/backend/src/database/migrations/archive/1720446828855-AlterReadyMadeSolution.ts new file mode 100644 index 0000000..5d03d7a --- /dev/null +++ b/backend/src/database/migrations/archive/1720446828855-AlterReadyMadeSolution.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterReadyMadeSolution1720446828855 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table ready_made_solution + add column account_id integer, + add foreign key (account_id) references account(id); + + update ready_made_solution rms + set account_id = acc.id + from account acc + where rms.subdomain = acc.subdomain; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720530507278-RenameAppsumoPreset.ts b/backend/src/database/migrations/archive/1720530507278-RenameAppsumoPreset.ts new file mode 100644 index 0000000..e726994 --- /dev/null +++ b/backend/src/database/migrations/archive/1720530507278-RenameAppsumoPreset.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameAppsumoPreset1720530507278 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table appsumo_preset rename to appsumo_tier; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720530714621-AlterAppsumoTier.ts b/backend/src/database/migrations/archive/1720530714621-AlterAppsumoTier.ts new file mode 100644 index 0000000..68c95a2 --- /dev/null +++ b/backend/src/database/migrations/archive/1720530714621-AlterAppsumoTier.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAppsumoTier1720530714621 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table appsumo_tier rename column user_count to user_limit; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720531175011-RenameSubscription.ts b/backend/src/database/migrations/archive/1720531175011-RenameSubscription.ts new file mode 100644 index 0000000..07bc569 --- /dev/null +++ b/backend/src/database/migrations/archive/1720531175011-RenameSubscription.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameSubscription1720531175011 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table subscription rename to account_subscription; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720596831169-AlterAppsumoTier.ts b/backend/src/database/migrations/archive/1720596831169-AlterAppsumoTier.ts new file mode 100644 index 0000000..8616309 --- /dev/null +++ b/backend/src/database/migrations/archive/1720596831169-AlterAppsumoTier.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAppsumoTier1720596831169 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table appsumo_tier add column plan_name text; + update appsumo_tier set plan_name = 'All in One'; + alter table appsumo_tier alter column plan_name set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720597683965-UpdateAccountSubscription.ts b/backend/src/database/migrations/archive/1720597683965-UpdateAccountSubscription.ts new file mode 100644 index 0000000..7d6176d --- /dev/null +++ b/backend/src/database/migrations/archive/1720597683965-UpdateAccountSubscription.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateAccountSubscription1720597683965 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update account_subscription set plan_name = 'All in One' where plan_name = 'Premium'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720610809785-AlterAppsumoLicense.ts b/backend/src/database/migrations/archive/1720610809785-AlterAppsumoLicense.ts new file mode 100644 index 0000000..5d89246 --- /dev/null +++ b/backend/src/database/migrations/archive/1720610809785-AlterAppsumoLicense.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAppsumoLicense1720610809785 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table appsumo_license add column prev_license_key text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720619947686-AlterAppsumoTier.ts b/backend/src/database/migrations/archive/1720619947686-AlterAppsumoTier.ts new file mode 100644 index 0000000..17a65b8 --- /dev/null +++ b/backend/src/database/migrations/archive/1720619947686-AlterAppsumoTier.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAppsumoTier1720619947686 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update appsumo_tier set plan_name='AppSumo Tier 1' where tier=1; + update appsumo_tier set plan_name='AppSumo Tier 2' where tier=2; + update appsumo_tier set plan_name='AppSumo Tier 3' where tier=3; + update appsumo_tier set plan_name='AppSumo Tier 4' where tier=4; + update appsumo_tier set plan_name='AppSumo Tier 5' where tier=5; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1720778334472-AlterAutomationEntityType.ts b/backend/src/database/migrations/archive/1720778334472-AlterAutomationEntityType.ts new file mode 100644 index 0000000..bdd35a4 --- /dev/null +++ b/backend/src/database/migrations/archive/1720778334472-AlterAutomationEntityType.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationEntityType1720778334472 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_entity_type + drop column is_active, + add column process_id integer, + add foreign key (process_id) references automation_process(id) on delete cascade; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1721221679342-VoximplantSip.ts b/backend/src/database/migrations/archive/1721221679342-VoximplantSip.ts new file mode 100644 index 0000000..e1e8268 --- /dev/null +++ b/backend/src/database/migrations/archive/1721221679342-VoximplantSip.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantSip1721221679342 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table voximplant_sip ( + id integer generated by default as identity, + external_id integer not null, + type text not null, + account_id integer not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722240313665-DBMemoryOptimization.ts b/backend/src/database/migrations/archive/1722240313665-DBMemoryOptimization.ts new file mode 100644 index 0000000..c53a5b8 --- /dev/null +++ b/backend/src/database/migrations/archive/1722240313665-DBMemoryOptimization.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DBMemoryOptimization1722240313665 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + SET work_mem = '64MB'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722240423838-DBIndexOptimization.ts b/backend/src/database/migrations/archive/1722240423838-DBIndexOptimization.ts new file mode 100644 index 0000000..26de9eb --- /dev/null +++ b/backend/src/database/migrations/archive/1722240423838-DBIndexOptimization.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DBIndexOptimization1722240423838 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX idx_entity_account_entity_type_id ON entity(account_id, entity_type_id, id); + + CREATE INDEX idx_entity_link_source_target_id ON entity_link(source_id, target_id); + + CREATE INDEX idx_field_value_entity_payload ON field_value USING gin (payload jsonb_path_ops); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722250997991-FieldSettingsIndexes.ts b/backend/src/database/migrations/archive/1722250997991-FieldSettingsIndexes.ts new file mode 100644 index 0000000..26c6de7 --- /dev/null +++ b/backend/src/database/migrations/archive/1722250997991-FieldSettingsIndexes.ts @@ -0,0 +1,18 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FieldSettingsIndexes1722250997991 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +CREATE INDEX idx_field_stage_settings_account_field_access ON field_stage_settings(account_id, field_id, access); +CREATE INDEX idx_field_stage_settings_account_field_stage_access ON field_stage_settings(account_id, field_id, stage_id, access); + +CREATE INDEX idx_field_user_settings_account_field_access ON field_user_settings(account_id, field_id, access); +CREATE INDEX idx_field_user_settings_account_field_user_access ON field_user_settings(account_id, field_id, user_id, access); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722251334680-AccountApiAccessIndex.ts b/backend/src/database/migrations/archive/1722251334680-AccountApiAccessIndex.ts new file mode 100644 index 0000000..60b58e2 --- /dev/null +++ b/backend/src/database/migrations/archive/1722251334680-AccountApiAccessIndex.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AccountApiAccessIndex1722251334680 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE UNIQUE INDEX idx_account_api_access_api_key ON account_api_access(api_key); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722325875311-UpdateProjectFieldsSortOrder.ts b/backend/src/database/migrations/archive/1722325875311-UpdateProjectFieldsSortOrder.ts new file mode 100644 index 0000000..b1ee4cb --- /dev/null +++ b/backend/src/database/migrations/archive/1722325875311-UpdateProjectFieldsSortOrder.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateProjectFieldsSortOrder1722325875311 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update field set sort_order = -1 where code is not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722340602724-DepartmentIndexes.ts b/backend/src/database/migrations/archive/1722340602724-DepartmentIndexes.ts new file mode 100644 index 0000000..cd1185b --- /dev/null +++ b/backend/src/database/migrations/archive/1722340602724-DepartmentIndexes.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DepartmentIndexes1722340602724 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX idx_department_id_account_id ON department(id, account_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722500514353-EntityListSettingsIndex.ts b/backend/src/database/migrations/archive/1722500514353-EntityListSettingsIndex.ts new file mode 100644 index 0000000..5977190 --- /dev/null +++ b/backend/src/database/migrations/archive/1722500514353-EntityListSettingsIndex.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityListSettingsIndex1722500514353 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX idx_entity_list_settings_account_entity_board + ON entity_list_settings(account_id, entity_type_id, board_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722870704258-AlterDepartmentIsActive.ts b/backend/src/database/migrations/archive/1722870704258-AlterDepartmentIsActive.ts new file mode 100644 index 0000000..ee146f6 --- /dev/null +++ b/backend/src/database/migrations/archive/1722870704258-AlterDepartmentIsActive.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterDepartmentIsActive1722870704258 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table department add column is_active boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1722871843239-AlterUserDepartment.ts b/backend/src/database/migrations/archive/1722871843239-AlterUserDepartment.ts new file mode 100644 index 0000000..56fa485 --- /dev/null +++ b/backend/src/database/migrations/archive/1722871843239-AlterUserDepartment.ts @@ -0,0 +1,16 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterUserDepartment1722871843239 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table users + drop constraint users_department_id_fkey, + add constraint users_department_id_fkey foreign key (department_id) references department(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1723373462514-AddMailMessageScheduled.ts b/backend/src/database/migrations/archive/1723373462514-AddMailMessageScheduled.ts new file mode 100644 index 0000000..318251a --- /dev/null +++ b/backend/src/database/migrations/archive/1723373462514-AddMailMessageScheduled.ts @@ -0,0 +1,30 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailMessageScheduled1723373462514 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table mail_message_scheduled ( + id integer generated by default as identity, + account_id integer not null, + created_by integer not null, + created_at timestamp not null default now(), + mailbox_id integer not null, + subject text not null, + content text not null, + send_to text not null, + send_as_html boolean not null, + entity_id integer, + sent_at timestamp, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (created_by) references users(id), + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (entity_id) references entity(id) on delete set null + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1723386042005-AlterMailMessageScheduled.ts b/backend/src/database/migrations/archive/1723386042005-AlterMailMessageScheduled.ts new file mode 100644 index 0000000..fbbf34b --- /dev/null +++ b/backend/src/database/migrations/archive/1723386042005-AlterMailMessageScheduled.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailMessageScheduled1723386042005 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message_scheduled rename column created_by to send_from; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1723641840096-AlterMailMessageScheduled.ts b/backend/src/database/migrations/archive/1723641840096-AlterMailMessageScheduled.ts new file mode 100644 index 0000000..ec9a03b --- /dev/null +++ b/backend/src/database/migrations/archive/1723641840096-AlterMailMessageScheduled.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailMessageScheduled1723641840096 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message_scheduled drop column send_as_html; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1723814067070-MailboxEmailsPerDay.ts b/backend/src/database/migrations/archive/1723814067070-MailboxEmailsPerDay.ts new file mode 100644 index 0000000..d63f986 --- /dev/null +++ b/backend/src/database/migrations/archive/1723814067070-MailboxEmailsPerDay.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailboxEmailsPerDay1723814067070 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update mailbox set emails_per_day = 100 where emails_per_day is null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724058794170-AlterFieldGroup.ts b/backend/src/database/migrations/archive/1724058794170-AlterFieldGroup.ts new file mode 100644 index 0000000..cb5870c --- /dev/null +++ b/backend/src/database/migrations/archive/1724058794170-AlterFieldGroup.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterFieldGroup1724058794170 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field_group add column code text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724422282832-EntityUpdatedAt.ts b/backend/src/database/migrations/archive/1724422282832-EntityUpdatedAt.ts new file mode 100644 index 0000000..bfc27d7 --- /dev/null +++ b/backend/src/database/migrations/archive/1724422282832-EntityUpdatedAt.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityUpdatedAt1724422282832 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity add column updated_at timestamp without time zone; + update entity set updated_at = created_at; + alter table entity alter column updated_at set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724657864701-AlterVoximplantSipName.ts b/backend/src/database/migrations/archive/1724657864701-AlterVoximplantSipName.ts new file mode 100644 index 0000000..12b3bb4 --- /dev/null +++ b/backend/src/database/migrations/archive/1724657864701-AlterVoximplantSipName.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterVoximplantSipName1724657864701 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table voximplant_sip add column name text; + update voximplant_sip set name = external_id; + alter table voximplant_sip alter column name set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724658513639-VoximplantSipUser.ts b/backend/src/database/migrations/archive/1724658513639-VoximplantSipUser.ts new file mode 100644 index 0000000..1bb2fc4 --- /dev/null +++ b/backend/src/database/migrations/archive/1724658513639-VoximplantSipUser.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VoximplantSipUser1724658513639 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table voximplant_sip_user ( + sip_id integer, + user_id integer, + account_id integer not null, + primary key (sip_id, user_id), + foreign key (sip_id) references voximplant_sip(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724668825212-EntityBoardId.ts b/backend/src/database/migrations/archive/1724668825212-EntityBoardId.ts new file mode 100644 index 0000000..7fb2cf9 --- /dev/null +++ b/backend/src/database/migrations/archive/1724668825212-EntityBoardId.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityBoardId1724668825212 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity + add column board_id integer, + add foreign key (board_id) references board(id) on delete cascade; + + update entity + set board_id = (select board_id from stage where stage.id = entity.stage_id) + where entity.stage_id is not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724744316233-FieldGroupDefault.ts b/backend/src/database/migrations/archive/1724744316233-FieldGroupDefault.ts new file mode 100644 index 0000000..f56bcc3 --- /dev/null +++ b/backend/src/database/migrations/archive/1724744316233-FieldGroupDefault.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FieldGroupDefault1724744316233 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + with first_group as ( + select id + from field_group + where (entity_type_id, sort_order) in ( + select entity_type_id, min(sort_order) + from field_group + group by entity_type_id + ) + ) + update field_group + set code = 'details' + where id in (select id from first_group); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724747098462-DeprtmentIdIdentity.ts b/backend/src/database/migrations/archive/1724747098462-DeprtmentIdIdentity.ts new file mode 100644 index 0000000..056a66a --- /dev/null +++ b/backend/src/database/migrations/archive/1724747098462-DeprtmentIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeprtmentIdIdentity1724747098462 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE department ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS department_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM department; + EXECUTE 'ALTER TABLE department ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724761173125-DepartmentSettings.ts b/backend/src/database/migrations/archive/1724761173125-DepartmentSettings.ts new file mode 100644 index 0000000..9b9be88 --- /dev/null +++ b/backend/src/database/migrations/archive/1724761173125-DepartmentSettings.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DepartmentSettings1724761173125 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table department_settings ( + department_id integer not null, + account_id integer not null, + working_days text, + working_time_from time without time zone, + working_time_to time without time zone, + time_zone text, + primary key (department_id), + foreign key (department_id) references department(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724944661564-UserIdIdentity.ts b/backend/src/database/migrations/archive/1724944661564-UserIdIdentity.ts new file mode 100644 index 0000000..7d0a2c0 --- /dev/null +++ b/backend/src/database/migrations/archive/1724944661564-UserIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UserIdIdentity1724944661564 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE users ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS user_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM users; + EXECUTE 'ALTER TABLE users ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724945283499-AccountIdIdentity.ts b/backend/src/database/migrations/archive/1724945283499-AccountIdIdentity.ts new file mode 100644 index 0000000..dab88d5 --- /dev/null +++ b/backend/src/database/migrations/archive/1724945283499-AccountIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AccountIdIdentity1724945283499 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE account ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS account_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM account; + EXECUTE 'ALTER TABLE account ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724946396552-DocumentTemplateIdIdentity.ts b/backend/src/database/migrations/archive/1724946396552-DocumentTemplateIdIdentity.ts new file mode 100644 index 0000000..7cca312 --- /dev/null +++ b/backend/src/database/migrations/archive/1724946396552-DocumentTemplateIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DocumentTemplateIdIdentity1724946396552 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE document_template ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS document_template_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM document_template; + EXECUTE 'ALTER TABLE document_template ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724946751431-NotificationIdIdentity.ts b/backend/src/database/migrations/archive/1724946751431-NotificationIdIdentity.ts new file mode 100644 index 0000000..8aed232 --- /dev/null +++ b/backend/src/database/migrations/archive/1724946751431-NotificationIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NotificationIdIdentity1724946751431 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE notification ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS notification_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM notification; + EXECUTE 'ALTER TABLE notification ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724946917573-NotificationSettingsIdIdentity.ts b/backend/src/database/migrations/archive/1724946917573-NotificationSettingsIdIdentity.ts new file mode 100644 index 0000000..36ba70a --- /dev/null +++ b/backend/src/database/migrations/archive/1724946917573-NotificationSettingsIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NotificationSettingsIdIdentity1724946917573 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE notification_settings ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS notification_settings_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM notification_settings; + EXECUTE 'ALTER TABLE notification_settings ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1724946935740-NotificationTypeSettingsIdIdentity.ts b/backend/src/database/migrations/archive/1724946935740-NotificationTypeSettingsIdIdentity.ts new file mode 100644 index 0000000..e621169 --- /dev/null +++ b/backend/src/database/migrations/archive/1724946935740-NotificationTypeSettingsIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NotificationTypeSettingsIdIdentity1724946935740 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE notification_type_settings ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS notification_type_settings_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM notification_type_settings; + EXECUTE 'ALTER TABLE notification_type_settings ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725008953241-EntityTypeActionTypeRename.ts b/backend/src/database/migrations/archive/1725008953241-EntityTypeActionTypeRename.ts new file mode 100644 index 0000000..5952bd8 --- /dev/null +++ b/backend/src/database/migrations/archive/1725008953241-EntityTypeActionTypeRename.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityTypeActionTypeRename1725008953241 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + UPDATE automation_entity_type + SET actions = replace(actions::text, '"create_task"', '"task_create"')::jsonb + WHERE actions::text LIKE '%"create_task"%'; + + UPDATE automation_entity_type + SET actions = replace(actions::text, '"create_activity"', '"activity_create"')::jsonb + WHERE actions::text LIKE '%"create_activity"%'; + + UPDATE automation_entity_type + SET actions = replace(actions::text, '"change_stage"', '"entity_stage_change"')::jsonb + WHERE actions::text LIKE '%"change_stage"%'; + + UPDATE automation_entity_type + SET actions = replace(actions::text, '"send_email"', '"email_send"')::jsonb + WHERE actions::text LIKE '%"send_email"%'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725024403154-ChatIdIdentity.ts b/backend/src/database/migrations/archive/1725024403154-ChatIdIdentity.ts new file mode 100644 index 0000000..5f9ea75 --- /dev/null +++ b/backend/src/database/migrations/archive/1725024403154-ChatIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatIdIdentity1725024403154 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE chat ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS chat_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM chat; + EXECUTE 'ALTER TABLE chat ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725024779580-ChatMessageFileIdIdentity.ts b/backend/src/database/migrations/archive/1725024779580-ChatMessageFileIdIdentity.ts new file mode 100644 index 0000000..6e95b54 --- /dev/null +++ b/backend/src/database/migrations/archive/1725024779580-ChatMessageFileIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatMessageFileIdIdentity1725024779580 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE chat_message_file ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS chat_message_file_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM chat_message_file; + EXECUTE 'ALTER TABLE chat_message_file ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725024956416-ChatMessageReactionIdIdentity.ts b/backend/src/database/migrations/archive/1725024956416-ChatMessageReactionIdIdentity.ts new file mode 100644 index 0000000..84b35f8 --- /dev/null +++ b/backend/src/database/migrations/archive/1725024956416-ChatMessageReactionIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatMessageReactionIdIdentity1725024956416 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE chat_message_reaction ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS chat_message_reaction_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM chat_message_reaction; + EXECUTE 'ALTER TABLE chat_message_reaction ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725025200036-ChatMessageIdIdentity.ts b/backend/src/database/migrations/archive/1725025200036-ChatMessageIdIdentity.ts new file mode 100644 index 0000000..ac851ce --- /dev/null +++ b/backend/src/database/migrations/archive/1725025200036-ChatMessageIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatMessageIdIdentity1725025200036 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE chat_message ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS chat_message_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM chat_message; + EXECUTE 'ALTER TABLE chat_message ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725025378404-ChatProviderIdIdentity.ts b/backend/src/database/migrations/archive/1725025378404-ChatProviderIdIdentity.ts new file mode 100644 index 0000000..f8ae880 --- /dev/null +++ b/backend/src/database/migrations/archive/1725025378404-ChatProviderIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatProviderIdIdentity1725025378404 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE chat_provider ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS chat_provider_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM chat_provider; + EXECUTE 'ALTER TABLE chat_provider ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725025624010-ChatUserIdIdentity.ts b/backend/src/database/migrations/archive/1725025624010-ChatUserIdIdentity.ts new file mode 100644 index 0000000..baa6cb2 --- /dev/null +++ b/backend/src/database/migrations/archive/1725025624010-ChatUserIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatUserIdIdentity1725025624010 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE chat_user ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS chat_user_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM chat_user; + EXECUTE 'ALTER TABLE chat_user ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725269507813-ChatProviderMessagePerDay.ts b/backend/src/database/migrations/archive/1725269507813-ChatProviderMessagePerDay.ts new file mode 100644 index 0000000..a3d5612 --- /dev/null +++ b/backend/src/database/migrations/archive/1725269507813-ChatProviderMessagePerDay.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatProviderMessagePerDay1725269507813 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider add column message_per_day integer; + update chat_provider set message_per_day = 10 where message_per_day is null; + alter table chat_provider alter column message_per_day set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725622826309-MailboxLatsActiveAt.ts b/backend/src/database/migrations/archive/1725622826309-MailboxLatsActiveAt.ts new file mode 100644 index 0000000..2954b1c --- /dev/null +++ b/backend/src/database/migrations/archive/1725622826309-MailboxLatsActiveAt.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailboxLatsActiveAt1725622826309 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox add column last_active_at timestamp without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1725980172270-EntityCopiedFromConstrains.ts b/backend/src/database/migrations/archive/1725980172270-EntityCopiedFromConstrains.ts new file mode 100644 index 0000000..e373968 --- /dev/null +++ b/backend/src/database/migrations/archive/1725980172270-EntityCopiedFromConstrains.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityCopiedFromConstrains1725980172270 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update entity + set copied_from = null, copied_count = null + where entity.copied_from is not null and not exists (select id from entity e2 where e2.id = entity.copied_from); + + alter table entity add foreign key (copied_from) references entity(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1726478454833-ChatMessageScheduled.ts b/backend/src/database/migrations/archive/1726478454833-ChatMessageScheduled.ts new file mode 100644 index 0000000..6a7381f --- /dev/null +++ b/backend/src/database/migrations/archive/1726478454833-ChatMessageScheduled.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChatMessageScheduled1726478454833 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table chat_message_scheduled ( + id integer generated by default as identity, + account_id integer not null, + created_at timestamp without time zone not null, + send_from integer not null, + provider_id integer not null, + title text, + message text not null, + send_to text not null, + entity_id integer not null, + sent_at timestamp without time zone, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (send_from) references users(id) on delete cascade, + foreign key (provider_id) references chat_provider(id) on delete cascade, + foreign key (entity_id) references entity(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1727179988017-AlterChatMessageScheduled.ts b/backend/src/database/migrations/archive/1727179988017-AlterChatMessageScheduled.ts new file mode 100644 index 0000000..16d4448 --- /dev/null +++ b/backend/src/database/migrations/archive/1727179988017-AlterChatMessageScheduled.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatMessageScheduled1727179988017 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_message_scheduled + drop column title, + drop column send_to, + add column only_first boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1727265648107-MailFolderOptimization.ts b/backend/src/database/migrations/archive/1727265648107-MailFolderOptimization.ts new file mode 100644 index 0000000..bc2259d --- /dev/null +++ b/backend/src/database/migrations/archive/1727265648107-MailFolderOptimization.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailFolderOptimization1727265648107 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX IF EXISTS mailbox_folder_account_id_mailbox_id_idx; + + CREATE INDEX idx_mailbox_folder_mailbox_id ON mailbox_folder(mailbox_id); + CREATE INDEX idx_mail_message_folder_folder_id ON mail_message_folder(folder_id); + CREATE INDEX idx_mail_message_id_is_seen ON mail_message(id, is_seen); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1727682923696-AlterSiteForm.ts b/backend/src/database/migrations/archive/1727682923696-AlterSiteForm.ts new file mode 100644 index 0000000..763244a --- /dev/null +++ b/backend/src/database/migrations/archive/1727682923696-AlterSiteForm.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1727682923696 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form add column multiform_enabled boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1727766355582-TaskCommentIdIdentity.ts b/backend/src/database/migrations/archive/1727766355582-TaskCommentIdIdentity.ts new file mode 100644 index 0000000..4e8043b --- /dev/null +++ b/backend/src/database/migrations/archive/1727766355582-TaskCommentIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TaskCommentIdIdentity1727766355582 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE task_comment ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS task_comment_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM task_comment; + EXECUTE 'ALTER TABLE task_comment ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1727775973380-AlterAutomationProcess.ts b/backend/src/database/migrations/archive/1727775973380-AlterAutomationProcess.ts new file mode 100644 index 0000000..2ce03a9 --- /dev/null +++ b/backend/src/database/migrations/archive/1727775973380-AlterAutomationProcess.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationProcess1727775973380 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_process + add column created_by integer, + add column name text, + add column type text, + add column object_id integer, + add column is_readonly boolean default false, + add foreign key (created_by) references users(id); + + update automation_process ap + set + created_by = aet.created_by, + name = aet.name, + type = 'entity_type', + object_id = aet.entity_type_id, + is_readonly = true + from automation_entity_type aet + where aet.process_id = ap.id; + + alter table automation_process + alter column created_by set not null, + alter column name set not null, + alter column type set not null, + alter column is_readonly set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728296331516-RenameStage.ts b/backend/src/database/migrations/archive/1728296331516-RenameStage.ts new file mode 100644 index 0000000..2aaaa1f --- /dev/null +++ b/backend/src/database/migrations/archive/1728296331516-RenameStage.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenameStage1728296331516 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table stage rename to board_stage; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728312582354-AlterBoardStage.ts b/backend/src/database/migrations/archive/1728312582354-AlterBoardStage.ts new file mode 100644 index 0000000..3e96c86 --- /dev/null +++ b/backend/src/database/migrations/archive/1728312582354-AlterBoardStage.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterBoardStage1728312582354 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board_stage add column is_active boolean not null default true; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728384950674-BoardIdIdentity.ts b/backend/src/database/migrations/archive/1728384950674-BoardIdIdentity.ts new file mode 100644 index 0000000..62880a5 --- /dev/null +++ b/backend/src/database/migrations/archive/1728384950674-BoardIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class BoardIdIdentity1728384950674 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE board ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS board_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM board; + EXECUTE 'ALTER TABLE board ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728402696162-AlterBoard.ts b/backend/src/database/migrations/archive/1728402696162-AlterBoard.ts new file mode 100644 index 0000000..40f1216 --- /dev/null +++ b/backend/src/database/migrations/archive/1728402696162-AlterBoard.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterBoard1728402696162 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board + drop column if exists code, + drop column if exists need_migration; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728465792633-AlterBoardStage.ts b/backend/src/database/migrations/archive/1728465792633-AlterBoardStage.ts new file mode 100644 index 0000000..fcb8691 --- /dev/null +++ b/backend/src/database/migrations/archive/1728465792633-AlterBoardStage.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterBoardStage1728465792633 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board_stage + drop constraint stage_board_id_fkey, + add constraint stage_board_id_fkey foreign key (board_id) references board(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728468439082-AlterBoardStage.ts b/backend/src/database/migrations/archive/1728468439082-AlterBoardStage.ts new file mode 100644 index 0000000..b17dcd6 --- /dev/null +++ b/backend/src/database/migrations/archive/1728468439082-AlterBoardStage.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterBoardStage1728468439082 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table board_stage drop column is_active; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728471228996-AlterAutomationEntityType.ts b/backend/src/database/migrations/archive/1728471228996-AlterAutomationEntityType.ts new file mode 100644 index 0000000..e4a4c31 --- /dev/null +++ b/backend/src/database/migrations/archive/1728471228996-AlterAutomationEntityType.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAutomationEntityType1728471228996 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table automation_entity_type + drop constraint automation_entity_type_entity_type_id_fkey, + alter column entity_type_id drop not null, + drop constraint automation_entity_type_board_id_fkey, + drop constraint automation_entity_type_stage_id_fkey; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728892125136-AlterAccountSettings.ts b/backend/src/database/migrations/archive/1728892125136-AlterAccountSettings.ts new file mode 100644 index 0000000..272c778 --- /dev/null +++ b/backend/src/database/migrations/archive/1728892125136-AlterAccountSettings.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountSettings1728892125136 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_settings add column date_format text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728981267739-AlterObjectPermission.ts b/backend/src/database/migrations/archive/1728981267739-AlterObjectPermission.ts new file mode 100644 index 0000000..41c1880 --- /dev/null +++ b/backend/src/database/migrations/archive/1728981267739-AlterObjectPermission.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterObjectPermission1728981267739 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table object_permission + add column user_id integer, + add foreign key (user_id) references users(id) on delete cascade; + + update object_permission set user_id = ( + select user_id from user_object_permission uop where uop.object_permission_id = object_permission.id + ); + + delete from object_permission where user_id is null; + alter table object_permission alter column user_id set not null; + + drop table user_object_permission; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728984932906-AlterObjectPermissionIndices.ts b/backend/src/database/migrations/archive/1728984932906-AlterObjectPermissionIndices.ts new file mode 100644 index 0000000..f046abb --- /dev/null +++ b/backend/src/database/migrations/archive/1728984932906-AlterObjectPermissionIndices.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterObjectPermissionIndices1728984932906 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + DROP INDEX IF EXISTS object_permission_object_type_object_id_idx; + + CREATE INDEX object_permission_account_id_user_id_idx ON object_permission(account_id, user_id); + + CREATE INDEX object_permission_account_user_type_id_idx + ON object_permission(account_id, user_id, object_type, object_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1728988723314-AlterObjectPermission.ts b/backend/src/database/migrations/archive/1728988723314-AlterObjectPermission.ts new file mode 100644 index 0000000..ad149fe --- /dev/null +++ b/backend/src/database/migrations/archive/1728988723314-AlterObjectPermission.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterObjectPermission1728988723314 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table object_permission + alter column object_type type text, + alter column create_permission type text, + alter column view_permission type text, + alter column edit_permission type text, + alter column delete_permission type text, + add column report_permission text; + + update object_permission set report_permission = edit_permission; + + alter table object_permission alter column report_permission set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1729001229086-AlterObjectPermission.ts b/backend/src/database/migrations/archive/1729001229086-AlterObjectPermission.ts new file mode 100644 index 0000000..f08de3a --- /dev/null +++ b/backend/src/database/migrations/archive/1729001229086-AlterObjectPermission.ts @@ -0,0 +1,17 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterObjectPermission1729001229086 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table object_permission add column dashboard_permission text; + + update object_permission set dashboard_permission = edit_permission; + + alter table object_permission alter column dashboard_permission set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1729680141757-UpdateTutorialItem.ts b/backend/src/database/migrations/archive/1729680141757-UpdateTutorialItem.ts new file mode 100644 index 0000000..d50878c --- /dev/null +++ b/backend/src/database/migrations/archive/1729680141757-UpdateTutorialItem.ts @@ -0,0 +1,29 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTutorialItem1729680141757 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update tutorial_item set link = 'https://rutube.ru/video/7866956dff4e26e1c898c1a31450b3ff' where link = 'https://youtu.be/niN8RMBBd3M'; + update tutorial_item set link = 'https://rutube.ru/video/1fc6c8cf6306c32f7775dc91b557fe6b' where link = 'https://youtu.be/GyqNL-Rqs2o'; + update tutorial_item set link = 'https://rutube.ru/video/55cfc3a2a8fe8d891cab17135d6d758a' where link = 'https://youtu.be/M6LmudYIVIg'; + update tutorial_item set link = 'https://rutube.ru/video/76111a083b5579cb8c7e3e3fb6b225f8' where link = 'https://youtu.be/fhweWoG-qwI'; + update tutorial_item set link = 'https://rutube.ru/video/d0b6ff03e0005f31236d8fa1d31a942d' where link = 'https://youtu.be/Bo0UNdxB130'; + update tutorial_item set link = 'https://rutube.ru/video/c5a7cfbeda45bd110f0cba20bcc399ac' where link = 'https://youtu.be/sy202rfbuB0'; + update tutorial_item set link = 'https://rutube.ru/video/286a3a8bd132332648bb7a37e313943c' where link = 'https://youtu.be/vZ6XDuYBDfY'; + update tutorial_item set link = 'https://rutube.ru/video/2384f907194a481a9d88486d85772496' where link = 'https://youtu.be/uQaowrrf6DQ'; + update tutorial_item set link = 'https://rutube.ru/video/861b2a529cf2fb93109df88e55458be2' where link = 'https://youtu.be/d7TK5yPIa1k'; + update tutorial_item set link = 'https://rutube.ru/video/e2b52a8139e7da53fd280c1e37694e04' where link = 'https://youtu.be/x6Fy9FazN9k'; + update tutorial_item set link = 'https://rutube.ru/video/39a6b69a7042b6318a22eeedc08cdf51' where link = 'https://youtu.be/ui5G50e9aV4'; + update tutorial_item set link = 'https://rutube.ru/video/d6c71e34f6ff41463616f3884d1e0b7a' where link = 'https://youtu.be/aaV12UJqq1M'; + update tutorial_item set link = 'https://rutube.ru/video/58c01491d9b6391eb5e59f19d58e47c5' where link = 'https://youtu.be/_vW9prpKSSo'; + update tutorial_item set link = 'https://rutube.ru/video/d0a83e46fa070e0c763d916ec4d7f9fd' where link = 'https://youtu.be/tpP7Ra7SwK4'; + update tutorial_item set link = 'https://rutube.ru/video/203a6cea52d5935c4503f2632373b87b' where link = 'https://youtu.be/oHAQMEfZSBY'; + update tutorial_item set link = 'https://rutube.ru/video/dedae77a65ded0aa531f8c5c90075f45' where link = 'https://youtu.be/WRZn2sGlw_s'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1729758131725-AlterMailMessage.ts b/backend/src/database/migrations/archive/1729758131725-AlterMailMessage.ts new file mode 100644 index 0000000..f74741a --- /dev/null +++ b/backend/src/database/migrations/archive/1729758131725-AlterMailMessage.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailMessage1729758131725 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mail_message alter column sent_from drop not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1729853713049-RemoveUnusedIndexes.ts b/backend/src/database/migrations/archive/1729853713049-RemoveUnusedIndexes.ts new file mode 100644 index 0000000..1801625 --- /dev/null +++ b/backend/src/database/migrations/archive/1729853713049-RemoveUnusedIndexes.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveUnusedIndexes1729853713049 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop index if exists entity_link_source_id_sort_order_id_idx; + drop index if exists idx_field_value_entity_payload; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1729856828756-ActivityTypeIdIdentity.ts b/backend/src/database/migrations/archive/1729856828756-ActivityTypeIdIdentity.ts new file mode 100644 index 0000000..cf0598d --- /dev/null +++ b/backend/src/database/migrations/archive/1729856828756-ActivityTypeIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ActivityTypeIdIdentity1729856828756 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE activity_type ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS activity_type_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM activity_type; + EXECUTE 'ALTER TABLE activity_type ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1729857179487-EntityTypeIdIdentity.ts b/backend/src/database/migrations/archive/1729857179487-EntityTypeIdIdentity.ts new file mode 100644 index 0000000..6d10b9c --- /dev/null +++ b/backend/src/database/migrations/archive/1729857179487-EntityTypeIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityTypeIdIdentity1729857179487 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE entity_type ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS entity_type_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM entity_type; + EXECUTE 'ALTER TABLE entity_type ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1729859612700-FieldValueIdIdentity.ts b/backend/src/database/migrations/archive/1729859612700-FieldValueIdIdentity.ts new file mode 100644 index 0000000..c23cd2d --- /dev/null +++ b/backend/src/database/migrations/archive/1729859612700-FieldValueIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FieldValueIdIdentity1729859612700 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE field_value ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS field_value_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM field_value; + EXECUTE 'ALTER TABLE field_value ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1730103007231-EntitySearch.ts b/backend/src/database/migrations/archive/1730103007231-EntitySearch.ts new file mode 100644 index 0000000..aedd80d --- /dev/null +++ b/backend/src/database/migrations/archive/1730103007231-EntitySearch.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntitySearch1730103007231 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + -- entity +ALTER TABLE entity ADD COLUMN IF NOT EXISTS name_tsv tsvector; + +UPDATE entity SET name_tsv = to_tsvector('simple', lower(name)); + +CREATE OR REPLACE FUNCTION update_name_tsv() RETURNS trigger AS $$ +BEGIN + NEW.name_tsv := to_tsvector('simple', lower(NEW.name)); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER update_name_tsv +BEFORE INSERT OR UPDATE ON entity +FOR EACH ROW +EXECUTE FUNCTION update_name_tsv(); + +CREATE INDEX IF NOT EXISTS idx_entity_name_tsv ON entity USING GIN (name_tsv); + +CREATE INDEX IF NOT EXISTS idx_entity_account_entity_type_created_at_id +ON entity (account_id, entity_type_id, created_at DESC, id DESC); + +-- field value +ALTER TABLE field_value ADD COLUMN IF NOT EXISTS payload_tsv tsvector; +UPDATE field_value +SET payload_tsv = CASE + WHEN field_type IN ('text', 'link') THEN to_tsvector('simple', lower(payload->>'value')) + WHEN field_type = 'multitext' THEN to_tsvector('simple', lower(payload->>'values')) + WHEN field_type = 'email' THEN to_tsvector('simple', lower(payload->>'values')) + WHEN field_type = 'phone' THEN to_tsvector('simple', lower(replace(payload->>'values', ' ', ''))) + ELSE NULL +END; + +CREATE OR REPLACE FUNCTION update_field_value_payload_tsv() +RETURNS TRIGGER AS $$ +BEGIN + NEW.payload_tsv = CASE + WHEN NEW.field_type IN ('text', 'link') THEN to_tsvector('simple', lower(NEW.payload->>'value')) + WHEN NEW.field_type = 'multitext' THEN to_tsvector('simple', lower(NEW.payload->>'values')) + WHEN NEW.field_type = 'email' THEN to_tsvector('simple', lower(NEW.payload->>'values')) + WHEN NEW.field_type = 'phone' THEN to_tsvector('simple', lower(replace(NEW.payload->>'values', ' ', ''))) + ELSE NULL + END; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER trigger_update_field_value_payload_tsv +BEFORE INSERT OR UPDATE OF payload, field_type ON field_value +FOR EACH ROW +EXECUTE FUNCTION update_field_value_payload_tsv(); + +CREATE INDEX IF NOT EXISTS idx_field_value_payload_tsv +ON field_value USING GIN (payload_tsv); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1730212128907-ScheduleAppointmentIndexes.ts b/backend/src/database/migrations/archive/1730212128907-ScheduleAppointmentIndexes.ts new file mode 100644 index 0000000..4626bcd --- /dev/null +++ b/backend/src/database/migrations/archive/1730212128907-ScheduleAppointmentIndexes.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ScheduleAppointmentIndexes1730212128907 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS idx_schedule_appointment_acc_sch_status + ON schedule_appointment (account_id, schedule_id, entity_id) + WHERE status != 'canceled' AND entity_id IS NOT NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1730472621229-TaskIndexes.ts b/backend/src/database/migrations/archive/1730472621229-TaskIndexes.ts new file mode 100644 index 0000000..edaa3c9 --- /dev/null +++ b/backend/src/database/migrations/archive/1730472621229-TaskIndexes.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TaskIndexes1730472621229 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS idx_task_account_stage_created_by ON task (account_id, stage_id, created_by); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1730796442786-AlterMailMessageScheduled.ts b/backend/src/database/migrations/archive/1730796442786-AlterMailMessageScheduled.ts new file mode 100644 index 0000000..89d075e --- /dev/null +++ b/backend/src/database/migrations/archive/1730796442786-AlterMailMessageScheduled.ts @@ -0,0 +1,19 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailMessageScheduled1730796442786 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from mail_message_scheduled where entity_id is null; + + ALTER TABLE mail_message_scheduled + DROP CONSTRAINT mail_message_scheduled_entity_id_fkey, + ADD CONSTRAINT mail_message_scheduled_entity_id_fkey FOREIGN KEY (entity_id) REFERENCES entity(id) ON DELETE CASCADE, + ALTER COLUMN entity_id SET NOT NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1730988793369-AlterSchedule.ts b/backend/src/database/migrations/archive/1730988793369-AlterSchedule.ts new file mode 100644 index 0000000..3fb5789 --- /dev/null +++ b/backend/src/database/migrations/archive/1730988793369-AlterSchedule.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedule1730988793369 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule add column one_entity_per_day boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731239585214-UsersAccessibleUsers.ts b/backend/src/database/migrations/archive/1731239585214-UsersAccessibleUsers.ts new file mode 100644 index 0000000..7b0cac9 --- /dev/null +++ b/backend/src/database/migrations/archive/1731239585214-UsersAccessibleUsers.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UsersAccessibleUsers1731239585214 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table users_accessible_users ( + user_id integer not null, + accessible_id integer not null, + foreign key (user_id) references users(id) on delete cascade, + foreign key (accessible_id) references users(id) on delete cascade, + primary key (user_id, accessible_id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731318295236-FieldIndex.ts b/backend/src/database/migrations/archive/1731318295236-FieldIndex.ts new file mode 100644 index 0000000..429e331 --- /dev/null +++ b/backend/src/database/migrations/archive/1731318295236-FieldIndex.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FieldIndex1731318295236 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS field_account_entity_type_sort_order_idx + ON field (account_id, entity_type_id, sort_order); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731491092637-WarehouseIdIdentity.ts b/backend/src/database/migrations/archive/1731491092637-WarehouseIdIdentity.ts new file mode 100644 index 0000000..7054df8 --- /dev/null +++ b/backend/src/database/migrations/archive/1731491092637-WarehouseIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class WarehouseIdIdentity1731491092637 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE warehouse ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS warehouse_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM warehouse; + EXECUTE 'ALTER TABLE warehouse ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731491294395-ShipmentItemIdIdentity.ts b/backend/src/database/migrations/archive/1731491294395-ShipmentItemIdIdentity.ts new file mode 100644 index 0000000..c878bc2 --- /dev/null +++ b/backend/src/database/migrations/archive/1731491294395-ShipmentItemIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ShipmentItemIdIdentity1731491294395 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE shipment_item ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS shipment_item_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM shipment_item; + EXECUTE 'ALTER TABLE shipment_item ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731491510990-ShipmentIdIdentity.ts b/backend/src/database/migrations/archive/1731491510990-ShipmentIdIdentity.ts new file mode 100644 index 0000000..19d492d --- /dev/null +++ b/backend/src/database/migrations/archive/1731491510990-ShipmentIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ShipmentIdIdentity1731491510990 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE shipment ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS shipment_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM shipment; + EXECUTE 'ALTER TABLE shipment ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731491686717-ReservationIdIdentity.ts b/backend/src/database/migrations/archive/1731491686717-ReservationIdIdentity.ts new file mode 100644 index 0000000..9827c46 --- /dev/null +++ b/backend/src/database/migrations/archive/1731491686717-ReservationIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ReservationIdIdentity1731491686717 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE reservation ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS reservation_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM reservation; + EXECUTE 'ALTER TABLE reservation ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731502099273-ProductCategoryIdIdentity.ts b/backend/src/database/migrations/archive/1731502099273-ProductCategoryIdIdentity.ts new file mode 100644 index 0000000..199acc1 --- /dev/null +++ b/backend/src/database/migrations/archive/1731502099273-ProductCategoryIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProductCategoryIdIdentity1731502099273 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE product_category ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS product_category_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM product_category; + EXECUTE 'ALTER TABLE product_category ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731502604723-ProductIdIdentity.ts b/backend/src/database/migrations/archive/1731502604723-ProductIdIdentity.ts new file mode 100644 index 0000000..f9087b7 --- /dev/null +++ b/backend/src/database/migrations/archive/1731502604723-ProductIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProductIdIdentity1731502604723 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE product ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS product_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM product; + EXECUTE 'ALTER TABLE product ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731502805263-ProductPriceIdIdentity.ts b/backend/src/database/migrations/archive/1731502805263-ProductPriceIdIdentity.ts new file mode 100644 index 0000000..19e2a69 --- /dev/null +++ b/backend/src/database/migrations/archive/1731502805263-ProductPriceIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ProductPriceIdIdentity1731502805263 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE product_price ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS product_price_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM product_price; + EXECUTE 'ALTER TABLE product_price ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731503055930-OrderIdIdentity.ts b/backend/src/database/migrations/archive/1731503055930-OrderIdIdentity.ts new file mode 100644 index 0000000..bfa8758 --- /dev/null +++ b/backend/src/database/migrations/archive/1731503055930-OrderIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderIdIdentity1731503055930 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE orders ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS order_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM orders; + EXECUTE 'ALTER TABLE orders ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731503302276-OrderItemIdIdentity.ts b/backend/src/database/migrations/archive/1731503302276-OrderItemIdIdentity.ts new file mode 100644 index 0000000..e511696 --- /dev/null +++ b/backend/src/database/migrations/archive/1731503302276-OrderItemIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderItemIdIdentity1731503302276 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE order_item ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS order_item_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM order_item; + EXECUTE 'ALTER TABLE order_item ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731503975362-OrderStatusIdIdentity.ts b/backend/src/database/migrations/archive/1731503975362-OrderStatusIdIdentity.ts new file mode 100644 index 0000000..46058de --- /dev/null +++ b/backend/src/database/migrations/archive/1731503975362-OrderStatusIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OrderStatusIdIdentity1731503975362 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE order_status ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS order_status_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM order_status; + EXECUTE 'ALTER TABLE order_status ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731512848862-AlterWarehouse.ts b/backend/src/database/migrations/archive/1731512848862-AlterWarehouse.ts new file mode 100644 index 0000000..3001ae6 --- /dev/null +++ b/backend/src/database/migrations/archive/1731512848862-AlterWarehouse.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterWarehouse1731512848862 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from warehouse where is_deleted = true; + alter table warehouse drop column is_deleted; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1731682593963-AddVersion.ts b/backend/src/database/migrations/archive/1731682593963-AddVersion.ts new file mode 100644 index 0000000..669fe9a --- /dev/null +++ b/backend/src/database/migrations/archive/1731682593963-AddVersion.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddVersion1731682593963 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table version ( + id integer generated by default as identity, + version varchar(255) not null, + date timestamp without time zone not null, + primary key ("id"), + unique ("version") + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732029352587-ObjectPermissionDepartmentFix.ts b/backend/src/database/migrations/archive/1732029352587-ObjectPermissionDepartmentFix.ts new file mode 100644 index 0000000..f7b2fc7 --- /dev/null +++ b/backend/src/database/migrations/archive/1732029352587-ObjectPermissionDepartmentFix.ts @@ -0,0 +1,103 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ObjectPermissionDepartmentFix1732029352587 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +UPDATE object_permission +SET create_permission = + CASE + WHEN create_permission = 'subdepartment' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE create_permission + END, + view_permission = + CASE + WHEN view_permission = 'subdepartment' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE view_permission + END, + edit_permission = + CASE + WHEN edit_permission = 'subdepartment' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE edit_permission + END, + delete_permission = + CASE + WHEN delete_permission = 'subdepartment' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE delete_permission + END, + report_permission = + CASE + WHEN report_permission = 'subdepartment' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE report_permission + END, + dashboard_permission = + CASE + WHEN dashboard_permission = 'subdepartment' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE dashboard_permission + END +WHERE + (create_permission = 'subdepartment' OR + view_permission = 'subdepartment' OR + edit_permission = 'subdepartment' OR + delete_permission = 'subdepartment' OR + report_permission = 'subdepartment' OR + dashboard_permission = 'subdepartment') + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL); + +UPDATE object_permission +SET create_permission = + CASE + WHEN create_permission = 'department' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE create_permission + END, + view_permission = + CASE + WHEN view_permission = 'department' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE view_permission + END, + edit_permission = + CASE + WHEN edit_permission = 'department' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE edit_permission + END, + delete_permission = + CASE + WHEN delete_permission = 'department' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE delete_permission + END, + report_permission = + CASE + WHEN report_permission = 'department' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE report_permission + END, + dashboard_permission = + CASE + WHEN dashboard_permission = 'department' + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL) THEN 'responsible' + ELSE dashboard_permission + END +WHERE + (create_permission = 'department' OR + view_permission = 'department' OR + edit_permission = 'department' OR + delete_permission = 'department' OR + report_permission = 'department' OR + dashboard_permission = 'department') + AND user_id IN (SELECT id FROM users WHERE department_id IS NULL); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732272936256-TaskSettingsIdIdentity.ts b/backend/src/database/migrations/archive/1732272936256-TaskSettingsIdIdentity.ts new file mode 100644 index 0000000..b482c2b --- /dev/null +++ b/backend/src/database/migrations/archive/1732272936256-TaskSettingsIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TaskSettingsIdIdentity1732272936256 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE task_settings ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS task_settings_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM task_settings; + EXECUTE 'ALTER TABLE task_settings ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732281701728-OptimizationIndexes.ts b/backend/src/database/migrations/archive/1732281701728-OptimizationIndexes.ts new file mode 100644 index 0000000..4bb3a4f --- /dev/null +++ b/backend/src/database/migrations/archive/1732281701728-OptimizationIndexes.ts @@ -0,0 +1,31 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class OptimizationIndexes1732281701728 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS users_accessible_users_user_id_idx ON users_accessible_users(user_id); + + CREATE INDEX IF NOT EXISTS board_record_id_idx ON board(record_id); + CREATE INDEX IF NOT EXISTS stage_board_id_account_id_idx ON board_stage(board_id, account_id); + + CREATE INDEX IF NOT EXISTS field_option_field_id_account_id_idx ON field_option (field_id, account_id); + DROP INDEX IF EXISTS field_option_field_id_idx; + + CREATE INDEX IF NOT EXISTS idx_field_value_partial_type_value ON field_value (entity_id, payload) WHERE field_type = 'value'; + + CREATE INDEX IF NOT EXISTS idx_task_resolved_end_date ON task (account_id, is_resolved, end_date); + CREATE INDEX IF NOT EXISTS idx_account_entity_resolved_true ON task (account_id, is_resolved, entity_id) WHERE is_resolved = true; + + CREATE INDEX IF NOT EXISTS idx_field_value_payload ON field_value USING gin (payload); + + CREATE INDEX IF NOT EXISTS idx_voximplant_call_account_created_at ON voximplant_call (account_id, created_at); + CREATE INDEX IF NOT EXISTS idx_voximplant_call_account_status_direction_user ON voximplant_call (account_id, status, direction, user_id); + CREATE INDEX IF NOT EXISTS idx_voximplant_call_account_status ON voximplant_call (account_id, status); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732521216471-AlterAccountSettings.ts b/backend/src/database/migrations/archive/1732521216471-AlterAccountSettings.ts new file mode 100644 index 0000000..4caeee2 --- /dev/null +++ b/backend/src/database/migrations/archive/1732521216471-AlterAccountSettings.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountSettings1732521216471 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_settings add column is_bpmn_enable boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732627160688-IndexOptimization.ts b/backend/src/database/migrations/archive/1732627160688-IndexOptimization.ts new file mode 100644 index 0000000..599ca06 --- /dev/null +++ b/backend/src/database/migrations/archive/1732627160688-IndexOptimization.ts @@ -0,0 +1,31 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class IndexOptimization1732627160688 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +CREATE INDEX IF NOT EXISTS idx_voximplant_user_account_user ON voximplant_user (account_id, user_id); +CREATE INDEX IF NOT EXISTS idx_order_status_account_sort ON order_status (account_id, sort_order); +CREATE INDEX IF NOT EXISTS idx_demo_data_account ON demo_data (account_id); +CREATE INDEX IF NOT EXISTS idx_task_settings_account ON task_settings (account_id); +CREATE INDEX IF NOT EXISTS idx_activity_type_account_created ON activity_type (account_id, created_at); +CREATE INDEX IF NOT EXISTS idx_entity_type_account_sort ON entity_type (account_id, sort_order); +CREATE INDEX IF NOT EXISTS idx_mailbox_account_created ON mailbox (account_id, created_at); +CREATE INDEX IF NOT EXISTS idx_mailbox_accessible_user_account_mailbox ON mailbox_accessible_user (account_id, mailbox_id); + + +ANALYZE voximplant_user; +ANALYZE order_status; +ANALYZE demo_data; +ANALYZE task_settings; +ANALYZE activity_type; +ANALYZE mailbox; +ANALYZE entity_type; +ANALYZE mailbox_accessible_user; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732628496619-BoardIndexes.ts b/backend/src/database/migrations/archive/1732628496619-BoardIndexes.ts new file mode 100644 index 0000000..4f85be8 --- /dev/null +++ b/backend/src/database/migrations/archive/1732628496619-BoardIndexes.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class BoardIndexes1732628496619 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +DROP INDEX IF EXISTS stage_account_id_idx; +DROP INDEX IF EXISTS stage_board_id_idx; +DROP INDEX IF EXISTS stage_board_id_account_id_idx; +CREATE INDEX IF NOT EXISTS idx_board_stage_account_board_sort ON board_stage (account_id, board_id, sort_order); +ANALYZE board_stage; + +DROP INDEX IF EXISTS board_account_id_idx; +DROP INDEX IF EXISTS board_type_idx; +DROP INDEX IF EXISTS board_record_id_idx; +CREATE INDEX IF NOT EXISTS idx_board_account_type_sort ON board (account_id, type, sort_order); +CREATE INDEX IF NOT EXISTS idx_board_account_record_sort ON board (account_id, record_id, sort_order); +CREATE INDEX IF NOT EXISTS idx_board_account_id_sort ON board (account_id, id, sort_order); +ANALYZE board; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732697048964-IndexOptimization.ts b/backend/src/database/migrations/archive/1732697048964-IndexOptimization.ts new file mode 100644 index 0000000..6f59d3d --- /dev/null +++ b/backend/src/database/migrations/archive/1732697048964-IndexOptimization.ts @@ -0,0 +1,15 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class IndexOptimization1732697048964 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +CREATE INDEX IF NOT EXISTS idx_entity_account_entity_type_stage_weight_id ON entity (account_id, entity_type_id, stage_id, weight ASC, id DESC); +ANALYZE entity; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732716375115-EntityTypeLinkIdIdentity.ts b/backend/src/database/migrations/archive/1732716375115-EntityTypeLinkIdIdentity.ts new file mode 100644 index 0000000..3bd47b9 --- /dev/null +++ b/backend/src/database/migrations/archive/1732716375115-EntityTypeLinkIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityTypeLinkIdIdentity1732716375115 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE entity_type_link ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS entity_type_link_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM entity_type_link; + EXECUTE 'ALTER TABLE entity_type_link ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732783039132-EntityTypeLinkOrphans.ts b/backend/src/database/migrations/archive/1732783039132-EntityTypeLinkOrphans.ts new file mode 100644 index 0000000..b989fee --- /dev/null +++ b/backend/src/database/migrations/archive/1732783039132-EntityTypeLinkOrphans.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityTypeLinkOrphans1732783039132 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + INSERT INTO entity_type_link (source_id, target_id, account_id, sort_order) + SELECT etl1.target_id AS source_id, + etl1.source_id AS target_id, + etl1.account_id, + etl1.sort_order + FROM entity_type_link etl1 + LEFT JOIN entity_type_link etl2 + ON etl1.source_id = etl2.target_id + AND etl1.target_id = etl2.source_id + WHERE etl2.id IS NULL; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732866955213-AlterSiteForm.ts b/backend/src/database/migrations/archive/1732866955213-AlterSiteForm.ts new file mode 100644 index 0000000..3b2fe1d --- /dev/null +++ b/backend/src/database/migrations/archive/1732866955213-AlterSiteForm.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1732866955213 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE site_form ADD COLUMN is_headless boolean NOT NULL DEFAULT false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732876120184-UpdateEntityBoardAndStage.ts b/backend/src/database/migrations/archive/1732876120184-UpdateEntityBoardAndStage.ts new file mode 100644 index 0000000..5b43f58 --- /dev/null +++ b/backend/src/database/migrations/archive/1732876120184-UpdateEntityBoardAndStage.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateEntityBoardAndStage1732876120184 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +WITH cte_board AS ( + SELECT e.id AS entity_id, b.id AS board_id + FROM entity e + JOIN entity_type et ON et.id = e.entity_type_id and et.section_view = 'board' + JOIN board b ON b.record_id = e.entity_type_id AND b.type = 'entity_type' + WHERE et.section_view = 'board' AND e.board_id IS NULL AND e.stage_id IS NULL + ORDER BY b.sort_order +), +cte_stage AS ( + SELECT cte_board.entity_id, bs.id AS stage_id + FROM cte_board + JOIN board_stage bs ON bs.board_id = cte_board.board_id + ORDER BY bs.sort_order +) +UPDATE entity +SET board_id = cte_board.board_id, stage_id = cte_stage.stage_id +FROM cte_board +JOIN cte_stage ON cte_board.entity_id = cte_stage.entity_id +WHERE entity.id = cte_board.entity_id; + +WITH cte_stage AS ( + SELECT e.id AS entity_id, bs.id AS stage_id + FROM entity e + JOIN board_stage bs ON bs.board_id = e.board_id + WHERE e.stage_id IS NULL + ORDER BY bs.sort_order +) +UPDATE entity +SET stage_id = cte_stage.stage_id +FROM cte_stage +WHERE entity.id = cte_stage.entity_id; + +WITH cte_board AS ( + SELECT e.id AS entity_id, bs.board_id AS board_id + FROM entity e + JOIN board_stage bs ON e.stage_id = bs.id + WHERE e.board_id IS NULL +) +UPDATE entity +SET board_id = cte_board.board_id +FROM cte_board +WHERE entity.id = cte_board.entity_id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1732889802526-FrontendObject.ts b/backend/src/database/migrations/archive/1732889802526-FrontendObject.ts new file mode 100644 index 0000000..320d99b --- /dev/null +++ b/backend/src/database/migrations/archive/1732889802526-FrontendObject.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FrontendObject1732889802526 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table frontend_object ( + id integer generated by default as identity, + account_id integer not null, + key text not null, + value jsonb not null, + created_at timestamp default now(), + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + unique (key) + ); + create index frontend_object_account_key_idx on frontend_object(account_id, key); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733136867799-UpdateScheduleAppointmentOwner.ts b/backend/src/database/migrations/archive/1733136867799-UpdateScheduleAppointmentOwner.ts new file mode 100644 index 0000000..6d4e284 --- /dev/null +++ b/backend/src/database/migrations/archive/1733136867799-UpdateScheduleAppointmentOwner.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateScheduleAppointmentOwner1733136867799 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update schedule_appointment + set owner_id = (select responsible_user_id from entity where entity.id = schedule_appointment.entity_id) + where schedule_appointment.entity_id is not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733393739852-ConditionIdIdentity.ts b/backend/src/database/migrations/archive/1733393739852-ConditionIdIdentity.ts new file mode 100644 index 0000000..092dbf0 --- /dev/null +++ b/backend/src/database/migrations/archive/1733393739852-ConditionIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ConditionIdIdentity1733393739852 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE condition ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS condition_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM condition; + EXECUTE 'ALTER TABLE condition ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733394028781-ScheduledMailMessageIdIdentity.ts b/backend/src/database/migrations/archive/1733394028781-ScheduledMailMessageIdIdentity.ts new file mode 100644 index 0000000..92a1ff0 --- /dev/null +++ b/backend/src/database/migrations/archive/1733394028781-ScheduledMailMessageIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ScheduledMailMessageIdIdentity1733394028781 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE scheduled_mail_message ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS scheduled_mail_message_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM scheduled_mail_message; + EXECUTE 'ALTER TABLE scheduled_mail_message ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733394215019-TriggerIdIdentity.ts b/backend/src/database/migrations/archive/1733394215019-TriggerIdIdentity.ts new file mode 100644 index 0000000..70dbf0c --- /dev/null +++ b/backend/src/database/migrations/archive/1733394215019-TriggerIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TriggerIdIdentity1733394215019 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE trigger ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS action_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM trigger; + EXECUTE 'ALTER TABLE trigger ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733394597308-ActionScheduledIdIdentity.ts b/backend/src/database/migrations/archive/1733394597308-ActionScheduledIdIdentity.ts new file mode 100644 index 0000000..7ac808d --- /dev/null +++ b/backend/src/database/migrations/archive/1733394597308-ActionScheduledIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ActionScheduledIdIdentity1733394597308 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE action_scheduled ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS scheduled_action_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM action_scheduled; + EXECUTE 'ALTER TABLE action_scheduled ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733394799717-ActionIdIdentity.ts b/backend/src/database/migrations/archive/1733394799717-ActionIdIdentity.ts new file mode 100644 index 0000000..3a4287b --- /dev/null +++ b/backend/src/database/migrations/archive/1733394799717-ActionIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ActionIdIdentity1733394799717 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE action ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS action_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM action; + EXECUTE 'ALTER TABLE action ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733394948832-AutomationIdIdentity.ts b/backend/src/database/migrations/archive/1733394948832-AutomationIdIdentity.ts new file mode 100644 index 0000000..cc90a54 --- /dev/null +++ b/backend/src/database/migrations/archive/1733394948832-AutomationIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AutomationIdIdentity1733394948832 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE automation ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS automation_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM automation; + EXECUTE 'ALTER TABLE automation ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733395768414-NoteIdIdentity.ts b/backend/src/database/migrations/archive/1733395768414-NoteIdIdentity.ts new file mode 100644 index 0000000..0197000 --- /dev/null +++ b/backend/src/database/migrations/archive/1733395768414-NoteIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class NoteIdIdentity1733395768414 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE note ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS feed_item_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM note; + EXECUTE 'ALTER TABLE note ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733395930995-ActivityIdIdentity.ts b/backend/src/database/migrations/archive/1733395930995-ActivityIdIdentity.ts new file mode 100644 index 0000000..68acea1 --- /dev/null +++ b/backend/src/database/migrations/archive/1733395930995-ActivityIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ActivityIdIdentity1733395930995 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE activity ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS feed_item_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM activity; + EXECUTE 'ALTER TABLE activity ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733395960444-TaskIdIdentity.ts b/backend/src/database/migrations/archive/1733395960444-TaskIdIdentity.ts new file mode 100644 index 0000000..eb852fe --- /dev/null +++ b/backend/src/database/migrations/archive/1733395960444-TaskIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TaskIdIdentity1733395960444 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE task ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS feed_item_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM task; + EXECUTE 'ALTER TABLE task ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733396343770-TaskSubtaskIdIdentity.ts b/backend/src/database/migrations/archive/1733396343770-TaskSubtaskIdIdentity.ts new file mode 100644 index 0000000..c0310ba --- /dev/null +++ b/backend/src/database/migrations/archive/1733396343770-TaskSubtaskIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TaskSubtaskIdIdentity1733396343770 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE task_subtask ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS subtask_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM task_subtask; + EXECUTE 'ALTER TABLE task_subtask ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733397036241-MailMessageIdIdentity.ts b/backend/src/database/migrations/archive/1733397036241-MailMessageIdIdentity.ts new file mode 100644 index 0000000..618cf6f --- /dev/null +++ b/backend/src/database/migrations/archive/1733397036241-MailMessageIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailMessageIdIdentity1733397036241 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE mail_message ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS mail_message_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM mail_message; + EXECUTE 'ALTER TABLE mail_message ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733397284175-MailMessagePayloadIdIdentity.ts b/backend/src/database/migrations/archive/1733397284175-MailMessagePayloadIdIdentity.ts new file mode 100644 index 0000000..e9652ca --- /dev/null +++ b/backend/src/database/migrations/archive/1733397284175-MailMessagePayloadIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailMessagePayloadIdIdentity1733397284175 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE mail_message_payload ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS mail_message_payload_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM mail_message_payload; + EXECUTE 'ALTER TABLE mail_message_payload ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733397518270-MailboxFolderIdIdentity.ts b/backend/src/database/migrations/archive/1733397518270-MailboxFolderIdIdentity.ts new file mode 100644 index 0000000..572b9cd --- /dev/null +++ b/backend/src/database/migrations/archive/1733397518270-MailboxFolderIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailboxFolderIdIdentity1733397518270 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE mailbox_folder ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS mailbox_folder_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM mailbox_folder; + EXECUTE 'ALTER TABLE mailbox_folder ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733397748405-MailboxIdIdentity.ts b/backend/src/database/migrations/archive/1733397748405-MailboxIdIdentity.ts new file mode 100644 index 0000000..b343cd0 --- /dev/null +++ b/backend/src/database/migrations/archive/1733397748405-MailboxIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailboxIdIdentity1733397748405 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE mailbox ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS mailbox_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM mailbox; + EXECUTE 'ALTER TABLE mailbox ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1733397911411-MailboxSignatureIdIdentity.ts b/backend/src/database/migrations/archive/1733397911411-MailboxSignatureIdIdentity.ts new file mode 100644 index 0000000..033739d --- /dev/null +++ b/backend/src/database/migrations/archive/1733397911411-MailboxSignatureIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class MailboxSignatureIdIdentity1733397911411 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE mailbox_signature ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS mailbox_signature_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM mailbox_signature; + EXECUTE 'ALTER TABLE mailbox_signature ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734009141643-AlterEntityValue.ts b/backend/src/database/migrations/archive/1734009141643-AlterEntityValue.ts new file mode 100644 index 0000000..26b0b76 --- /dev/null +++ b/backend/src/database/migrations/archive/1734009141643-AlterEntityValue.ts @@ -0,0 +1,23 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterEntityValue1734009141643 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity add column if not exists value numeric not null default 0; + update entity + set value = ( + select coalesce((payload->>'value')::numeric, 0) from ( + select distinct on (entity_id) payload + from field_value + where field_type = 'value' and entity_id = entity.id + order by entity_id, created_at desc + ) subquery + ) + where exists (select 1 from field_value where field_type = 'value' and entity_id = entity.id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734081841312-AlterUserProfile.ts b/backend/src/database/migrations/archive/1734081841312-AlterUserProfile.ts new file mode 100644 index 0000000..b1f4d52 --- /dev/null +++ b/backend/src/database/migrations/archive/1734081841312-AlterUserProfile.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterUserProfile1734081841312 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table user_profile drop column created_at; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734097700161-AlterEntityLink.ts b/backend/src/database/migrations/archive/1734097700161-AlterEntityLink.ts new file mode 100644 index 0000000..284cba5 --- /dev/null +++ b/backend/src/database/migrations/archive/1734097700161-AlterEntityLink.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterEntityLink1734097700161 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity_link drop column back_link_id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734349336443-EntityLinkIdIdentity.ts b/backend/src/database/migrations/archive/1734349336443-EntityLinkIdIdentity.ts new file mode 100644 index 0000000..5b1720f --- /dev/null +++ b/backend/src/database/migrations/archive/1734349336443-EntityLinkIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityLinkIdIdentity1734349336443 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE entity_link ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS entity_link_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM entity_link; + EXECUTE 'ALTER TABLE entity_link ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734352488667-RemoveEntityListSettings.ts b/backend/src/database/migrations/archive/1734352488667-RemoveEntityListSettings.ts new file mode 100644 index 0000000..78eeb7e --- /dev/null +++ b/backend/src/database/migrations/archive/1734352488667-RemoveEntityListSettings.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveEntityListSettings1734352488667 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop sequence if exists entity_list_settings_id_seq; + drop table if exists entity_list_settings; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734429865379-ScheduleAppointmentIndexes.ts b/backend/src/database/migrations/archive/1734429865379-ScheduleAppointmentIndexes.ts new file mode 100644 index 0000000..6ba45d6 --- /dev/null +++ b/backend/src/database/migrations/archive/1734429865379-ScheduleAppointmentIndexes.ts @@ -0,0 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ScheduleAppointmentIndexes1734429865379 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +CREATE INDEX IF NOT EXISTS idx_appointment_account_schedule_start_end +ON schedule_appointment (account_id, schedule_id, start_date, end_date); + +CREATE INDEX IF NOT EXISTS idx_appointment_account_schedule_non_canceled_start_end +ON schedule_appointment (account_id, schedule_id, start_date, end_date) +WHERE status <> 'canceled'; + +REINDEX TABLE schedule_appointment; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734600811704-AlterChatMessageScheduled.ts b/backend/src/database/migrations/archive/1734600811704-AlterChatMessageScheduled.ts new file mode 100644 index 0000000..1e75fe6 --- /dev/null +++ b/backend/src/database/migrations/archive/1734600811704-AlterChatMessageScheduled.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatMessageScheduled1734600811704 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_message_scheduled + alter column entity_id drop not null, + add column phone_number text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734688459744-AlterField.ts b/backend/src/database/migrations/archive/1734688459744-AlterField.ts new file mode 100644 index 0000000..3eb0e35 --- /dev/null +++ b/backend/src/database/migrations/archive/1734688459744-AlterField.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterField1734688459744 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table field add column format text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734708417316-VersionIndexes.ts b/backend/src/database/migrations/archive/1734708417316-VersionIndexes.ts new file mode 100644 index 0000000..7abd044 --- /dev/null +++ b/backend/src/database/migrations/archive/1734708417316-VersionIndexes.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class VersionIndexes1734708417316 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE INDEX IF NOT EXISTS idx_version_date ON version (date); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1734945712418-AlterSiteFormField.ts b/backend/src/database/migrations/archive/1734945712418-AlterSiteFormField.ts new file mode 100644 index 0000000..2e83009 --- /dev/null +++ b/backend/src/database/migrations/archive/1734945712418-AlterSiteFormField.ts @@ -0,0 +1,32 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteFormField1734945712418 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +alter table site_form_field + add column entity_type_id integer, + add column field_id integer, + add column is_validation_required boolean, + add column meta jsonb, + add foreign key (entity_type_id) references entity_type(id) on delete cascade, + add foreign key (field_id) references field(id) on delete cascade; + +update site_form_field +set entity_type_id = (select sffen.entity_type_id from site_form_field_entity_name sffen where sffen.form_field_id = site_form_field.id) +where site_form_field.type = 'entity_name'; + +update site_form_field +set (entity_type_id, field_id, is_validation_required, meta) = + (select sffef.entity_type_id, sffef.field_id, sffef.is_validation_required, sffef.meta from site_form_field_entity_field sffef where sffef.form_field_id = site_form_field.id) +where site_form_field.type = 'entity_field'; + +drop table site_form_field_entity_name; +drop table site_form_field_entity_field; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1735140161718-AddGoogleCalendar.ts b/backend/src/database/migrations/archive/1735140161718-AddGoogleCalendar.ts new file mode 100644 index 0000000..950d61c --- /dev/null +++ b/backend/src/database/migrations/archive/1735140161718-AddGoogleCalendar.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddGoogleCalendar1735140161718 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE google_calendar ( + id integer GENERATED BY DEFAULT AS IDENTITY, + account_id integer NOT NULL, + created_by integer NOT NULL, + created_at timestamp without time zone NOT NULL DEFAULT now(), + tokens jsonb NOT NULL, + calendar_id text NOT NULL, + title text NOT NULL, + readonly boolean NOT NULL, + type text NOT NULL, + object_id text NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE, + FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1735289134467-AlterGoogleCalendar.ts b/backend/src/database/migrations/archive/1735289134467-AlterGoogleCalendar.ts new file mode 100644 index 0000000..b487431 --- /dev/null +++ b/backend/src/database/migrations/archive/1735289134467-AlterGoogleCalendar.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterGoogleCalendar1735289134467 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table google_calendar rename column calendar_id to external_id; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1735290249052-AlterTask.ts b/backend/src/database/migrations/archive/1735290249052-AlterTask.ts new file mode 100644 index 0000000..2fff7e5 --- /dev/null +++ b/backend/src/database/migrations/archive/1735290249052-AlterTask.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterTask1735290249052 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + delete from task where board_id is null; + delete from task where stage_id is null; + + alter table task + alter column stage_id set not null, + alter column board_id set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1735573056216-IndexGoogleCalendar.ts b/backend/src/database/migrations/archive/1735573056216-IndexGoogleCalendar.ts new file mode 100644 index 0000000..cf2d0aa --- /dev/null +++ b/backend/src/database/migrations/archive/1735573056216-IndexGoogleCalendar.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class IndexGoogleCalendar1735573056216 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index if not exists idx_account_type_object on google_calendar(account_id,type,object_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736239080782-AlterGoogleCalendar.ts b/backend/src/database/migrations/archive/1736239080782-AlterGoogleCalendar.ts new file mode 100644 index 0000000..42ce4ed --- /dev/null +++ b/backend/src/database/migrations/archive/1736239080782-AlterGoogleCalendar.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterGoogleCalendar1736239080782 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table google_calendar add column if not exists responsible_id integer not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736262949465-AlterGoogleCalendar.ts b/backend/src/database/migrations/archive/1736262949465-AlterGoogleCalendar.ts new file mode 100644 index 0000000..5b9b6c0 --- /dev/null +++ b/backend/src/database/migrations/archive/1736262949465-AlterGoogleCalendar.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterGoogleCalendar1736262949465 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table google_calendar add column if not exists channel_id text; + alter table google_calendar add column if not exists channel_resource_id text; + alter table google_calendar add column if not exists channel_expiration timestamp without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736324153278-GoogleCalendarIndexes.ts b/backend/src/database/migrations/archive/1736324153278-GoogleCalendarIndexes.ts new file mode 100644 index 0000000..49136f1 --- /dev/null +++ b/backend/src/database/migrations/archive/1736324153278-GoogleCalendarIndexes.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class GoogleCalendarIndexes1736324153278 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index if not exists idx_channel_expiration on google_calendar(channel_expiration); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736330451213-AlterGoogleCalendar.ts b/backend/src/database/migrations/archive/1736330451213-AlterGoogleCalendar.ts new file mode 100644 index 0000000..f775ded --- /dev/null +++ b/backend/src/database/migrations/archive/1736330451213-AlterGoogleCalendar.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterGoogleCalendar1736330451213 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table google_calendar add column if not exists next_sync_token text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736331095918-GoogleCalendarIndexes.ts b/backend/src/database/migrations/archive/1736331095918-GoogleCalendarIndexes.ts new file mode 100644 index 0000000..e352b17 --- /dev/null +++ b/backend/src/database/migrations/archive/1736331095918-GoogleCalendarIndexes.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class GoogleCalendarIndexes1736331095918 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index if not exists idx_channel_resource on google_calendar(channel_id, channel_resource_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736339315376-AlterGoogleCalendar.ts b/backend/src/database/migrations/archive/1736339315376-AlterGoogleCalendar.ts new file mode 100644 index 0000000..0e4bee0 --- /dev/null +++ b/backend/src/database/migrations/archive/1736339315376-AlterGoogleCalendar.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterGoogleCalendar1736339315376 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table google_calendar rename column next_sync_token to sync_token; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736496705277-AlterTask.ts b/backend/src/database/migrations/archive/1736496705277-AlterTask.ts new file mode 100644 index 0000000..54f3d19 --- /dev/null +++ b/backend/src/database/migrations/archive/1736496705277-AlterTask.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterTask1736496705277 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table task add column external_id text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736513599793-IndexesTask.ts b/backend/src/database/migrations/archive/1736513599793-IndexesTask.ts new file mode 100644 index 0000000..724a78e --- /dev/null +++ b/backend/src/database/migrations/archive/1736513599793-IndexesTask.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class IndexesTask1736513599793 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index if not exists idx_account_external on task(account_id, external_id) where external_id is not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736773476711-GoogleCalendarLinked.ts b/backend/src/database/migrations/archive/1736773476711-GoogleCalendarLinked.ts new file mode 100644 index 0000000..34fa728 --- /dev/null +++ b/backend/src/database/migrations/archive/1736773476711-GoogleCalendarLinked.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class GoogleCalendarLinked1736773476711 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table google_calendar_linked ( + id integer generated by default as identity, + account_id integer not null, + calendar_id integer not null, + type text not null, + object_id integer not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (calendar_id) references google_calendar(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736781493022-IndexesGoogleCalendarLinked.ts b/backend/src/database/migrations/archive/1736781493022-IndexesGoogleCalendarLinked.ts new file mode 100644 index 0000000..de81042 --- /dev/null +++ b/backend/src/database/migrations/archive/1736781493022-IndexesGoogleCalendarLinked.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class IndexesGoogleCalendarLinked1736781493022 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create index if not exists idx_calendar on google_calendar_linked(calendar_id); + create index if not exists idx_type_object on google_calendar_linked(type, object_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736842860059-AlterGoogleCalendar.ts b/backend/src/database/migrations/archive/1736842860059-AlterGoogleCalendar.ts new file mode 100644 index 0000000..237631b --- /dev/null +++ b/backend/src/database/migrations/archive/1736842860059-AlterGoogleCalendar.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterGoogleCalendar1736842860059 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table google_calendar add column process_all boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1736948650910-AlterScheduleAppointment.ts b/backend/src/database/migrations/archive/1736948650910-AlterScheduleAppointment.ts new file mode 100644 index 0000000..bda091a --- /dev/null +++ b/backend/src/database/migrations/archive/1736948650910-AlterScheduleAppointment.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterScheduleAppointment1736948650910 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule_appointment add column external_id text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737033530086-AlterAccountSubscription.ts b/backend/src/database/migrations/archive/1737033530086-AlterAccountSubscription.ts new file mode 100644 index 0000000..7cf938d --- /dev/null +++ b/backend/src/database/migrations/archive/1737033530086-AlterAccountSubscription.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountSubscription1737033530086 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_subscription add column first_visit timestamp without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737039160966-AddSubscriptionDiscount.ts b/backend/src/database/migrations/archive/1737039160966-AddSubscriptionDiscount.ts new file mode 100644 index 0000000..5b581a1 --- /dev/null +++ b/backend/src/database/migrations/archive/1737039160966-AddSubscriptionDiscount.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSubscriptionDiscount1737039160966 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table subscription_discount ( + id integer generated by default as identity, + days integer not null, + percent integer not null, + code text, + primary key (id) + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737103293642-UpdateAccountSubscription.ts b/backend/src/database/migrations/archive/1737103293642-UpdateAccountSubscription.ts new file mode 100644 index 0000000..ab8b6ab --- /dev/null +++ b/backend/src/database/migrations/archive/1737103293642-UpdateAccountSubscription.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateAccountSubscription1737103293642 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update account_subscription set first_visit = created_at; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737103460794-AlterAccountSubscription.ts b/backend/src/database/migrations/archive/1737103460794-AlterAccountSubscription.ts new file mode 100644 index 0000000..6f69888 --- /dev/null +++ b/backend/src/database/migrations/archive/1737103460794-AlterAccountSubscription.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountSubscription1737103460794 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_subscription alter column first_visit set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737103855502-InsertSubscriptionDiscount.ts b/backend/src/database/migrations/archive/1737103855502-InsertSubscriptionDiscount.ts new file mode 100644 index 0000000..3e2f21b --- /dev/null +++ b/backend/src/database/migrations/archive/1737103855502-InsertSubscriptionDiscount.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InsertSubscriptionDiscount1737103855502 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + insert into subscription_discount(days, percent, code) values(5, 50, 'discount_50'); + insert into subscription_discount(days, percent, code) values(19, 30, 'discount_30'); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737378761002-AddGoogleCalendarAccount.ts b/backend/src/database/migrations/archive/1737378761002-AddGoogleCalendarAccount.ts new file mode 100644 index 0000000..6fa2e8b --- /dev/null +++ b/backend/src/database/migrations/archive/1737378761002-AddGoogleCalendarAccount.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddGoogleCalendarAccount1737378761002 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table google_calendar_account ( + id integer generated by default as identity, + account_id integer not null, + external_id text not null, + tokens jsonb not null, + sync_token text, + channel_id text, + channel_resource_id text, + channel_expiration timestamp without time zone, + primary key (id), + foreign key (account_id) references account(id) on delete cascade + ); + + alter table google_calendar + drop column tokens, + add column calendar_account_id integer not null, + add foreign key (calendar_account_id) references google_calendar_account(id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737971712981-AlterSubscriptionDiscount.ts b/backend/src/database/migrations/archive/1737971712981-AlterSubscriptionDiscount.ts new file mode 100644 index 0000000..5dfbd47 --- /dev/null +++ b/backend/src/database/migrations/archive/1737971712981-AlterSubscriptionDiscount.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSubscriptionDiscount1737971712981 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table subscription_discount add column valid_until timestamp without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1737973113027-UpdateSubscriptionDiscount.ts b/backend/src/database/migrations/archive/1737973113027-UpdateSubscriptionDiscount.ts new file mode 100644 index 0000000..5dbc7d6 --- /dev/null +++ b/backend/src/database/migrations/archive/1737973113027-UpdateSubscriptionDiscount.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateSubscriptionDiscount1737973113027 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update subscription_discount set valid_until = now(); + + insert into subscription_discount (days, percent, code, valid_until) values + (5, 80, 'discount_80', null), + (103, 70, 'discount_70', null), + (403, 65, 'discount_65', null); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1738660778536-RemoveOldAutomation.ts b/backend/src/database/migrations/archive/1738660778536-RemoveOldAutomation.ts new file mode 100644 index 0000000..fdf510d --- /dev/null +++ b/backend/src/database/migrations/archive/1738660778536-RemoveOldAutomation.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveOldAutomation1738660778536 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + drop table if exists automation_stage; + drop table if exists automation_condition; + drop table if exists automation; + drop table if exists action_activity_settings; + drop table if exists action_task_settings; + drop table if exists action_entity_settings; + drop table if exists action_email_settings; + drop table if exists action_scheduled; + drop table if exists scheduled_mail_message; + drop table if exists action; + drop table if exists exact_time_trigger_settings; + drop table if exists trigger; + drop table if exists user_condition; + drop table if exists field_condition; + drop table if exists condition; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1738676409194-AlterUserProfile.ts b/backend/src/database/migrations/archive/1738676409194-AlterUserProfile.ts new file mode 100644 index 0000000..54a4f6b --- /dev/null +++ b/backend/src/database/migrations/archive/1738676409194-AlterUserProfile.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterUserProfile1738676409194 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table user_profile add column working_time_from time without time zone; + alter table user_profile add column working_time_to time without time zone; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1739788991506-AlterGoogleCalendar.ts b/backend/src/database/migrations/archive/1739788991506-AlterGoogleCalendar.ts new file mode 100644 index 0000000..27a978a --- /dev/null +++ b/backend/src/database/migrations/archive/1739788991506-AlterGoogleCalendar.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterGoogleCalendar1739788991506 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table google_calendar + alter column object_id set data type integer using object_id::integer; + + update google_calendar set object_id = object_id::integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1739802958618-AlterAccountSettings.ts b/backend/src/database/migrations/archive/1739802958618-AlterAccountSettings.ts new file mode 100644 index 0000000..f970336 --- /dev/null +++ b/backend/src/database/migrations/archive/1739802958618-AlterAccountSettings.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterAccountSettings1739802958618 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table account_settings add column start_of_week text default 'Monday'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1739891080452-AddUserToken.ts b/backend/src/database/migrations/archive/1739891080452-AddUserToken.ts new file mode 100644 index 0000000..d7847dd --- /dev/null +++ b/backend/src/database/migrations/archive/1739891080452-AddUserToken.ts @@ -0,0 +1,27 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUserToken1739891080452 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table user_token ( + id integer generated by default as identity, + account_id integer not null, + user_id integer not null, + name text not null, + code text not null, + created_at timestamp without time zone not null default now(), + expires_at timestamp without time zone, + last_used_at timestamp without time zone, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade + ); + + create index if not exists idx_account_user_code on user_token(account_id, user_id, code); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1740402538635-AddUserCalendar.ts b/backend/src/database/migrations/archive/1740402538635-AddUserCalendar.ts new file mode 100644 index 0000000..e9513ab --- /dev/null +++ b/backend/src/database/migrations/archive/1740402538635-AddUserCalendar.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddUserCalendar1740402538635 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table if not exists user_calendar ( + id integer generated by default as identity, + account_id integer not null, + user_id integer not null, + time_buffer_before integer, + time_buffer_after integer, + appointment_limit integer, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (user_id) references users(id) on delete cascade + ); + + create index if not exists idx_account_user on user_calendar(account_id, user_id); + + create table if not exists user_calendar_interval ( + id integer generated by default as identity, + account_id integer not null, + calendar_id integer not null, + day_of_week text not null, + time_from time without time zone not null, + time_to time without time zone not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (calendar_id) references user_calendar(id) on delete cascade + ); + + create index if not exists idx_account_calendar on user_calendar_interval(account_id, calendar_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1740472617092-AlterSchedule.ts b/backend/src/database/migrations/archive/1740472617092-AlterSchedule.ts new file mode 100644 index 0000000..a30318e --- /dev/null +++ b/backend/src/database/migrations/archive/1740472617092-AlterSchedule.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedule1740472617092 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule add column time_buffer_before integer; + alter table schedule add column time_buffer_after integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1740479281651-AddScheduleTimeIntervalService.ts b/backend/src/database/migrations/archive/1740479281651-AddScheduleTimeIntervalService.ts new file mode 100644 index 0000000..76a3821 --- /dev/null +++ b/backend/src/database/migrations/archive/1740479281651-AddScheduleTimeIntervalService.ts @@ -0,0 +1,25 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddScheduleTimeIntervalService1740479281651 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table if not exists schedule_time_interval ( + id integer generated by default as identity, + account_id integer not null, + schedule_id integer not null, + day_of_week text not null, + time_from time without time zone not null, + time_to time without time zone not null, + primary key (id), + foreign key (account_id) references account(id) on delete cascade, + foreign key (schedule_id) references schedule(id) on delete cascade + ); + + create index if not exists idx_account_schedule on schedule_time_interval(account_id, schedule_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1740579698409-AlterSiteForm.ts b/backend/src/database/migrations/archive/1740579698409-AlterSiteForm.ts new file mode 100644 index 0000000..0995a78 --- /dev/null +++ b/backend/src/database/migrations/archive/1740579698409-AlterSiteForm.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1740579698409 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form add column type text not null default 'entity_type'; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1740583763973-AlterSiteForm.ts b/backend/src/database/migrations/archive/1740583763973-AlterSiteForm.ts new file mode 100644 index 0000000..de80178 --- /dev/null +++ b/backend/src/database/migrations/archive/1740583763973-AlterSiteForm.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1740583763973 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form add column deduplicate_linked boolean not null default false; + alter table site_form add column schedule_limit_days integer; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1740654525458-AddSiteFormSchedule.ts b/backend/src/database/migrations/archive/1740654525458-AddSiteFormSchedule.ts new file mode 100644 index 0000000..6d5aae8 --- /dev/null +++ b/backend/src/database/migrations/archive/1740654525458-AddSiteFormSchedule.ts @@ -0,0 +1,21 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSiteFormSchedule1740654525458 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table if not exists site_form_schedule ( + form_id integer not null, + schedule_id integer not null, + account_id integer not null, + primary key (form_id, schedule_id), + foreign key (form_id) references site_form(id) on delete cascade, + foreign key (schedule_id) references schedule(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade + ); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1741610604818-AlterSiteForm.ts b/backend/src/database/migrations/archive/1741610604818-AlterSiteForm.ts new file mode 100644 index 0000000..14e6f27 --- /dev/null +++ b/backend/src/database/migrations/archive/1741610604818-AlterSiteForm.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSiteForm1741610604818 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table site_form rename column deduplicate_linked to check_duplicate; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1741701747438-UpdateSchedule.ts b/backend/src/database/migrations/archive/1741701747438-UpdateSchedule.ts new file mode 100644 index 0000000..f22de5e --- /dev/null +++ b/backend/src/database/migrations/archive/1741701747438-UpdateSchedule.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateSchedule1741701747438 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + update schedule set time_period = 1800 where time_period is null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1741702060670-AlterSchedule.ts b/backend/src/database/migrations/archive/1741702060670-AlterSchedule.ts new file mode 100644 index 0000000..ec9f8bf --- /dev/null +++ b/backend/src/database/migrations/archive/1741702060670-AlterSchedule.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterSchedule1741702060670 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table schedule alter column time_period set not null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1742477677993-AddChatProviderEntitySettings.ts b/backend/src/database/migrations/archive/1742477677993-AddChatProviderEntitySettings.ts new file mode 100644 index 0000000..85c8110 --- /dev/null +++ b/backend/src/database/migrations/archive/1742477677993-AddChatProviderEntitySettings.ts @@ -0,0 +1,29 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddChatProviderEntitySettings1742477677993 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table chat_provider_entity_settings ( + provider_id integer, + account_id integer not null, + contact_entity_type_id integer, + lead_entity_type_id integer, + lead_board_id integer, + owner_id integer, + check_active_lead boolean not null default false, + check_duplicate boolean not null default false, + primary key (provider_id), + foreign key (provider_id) references chat_provider(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade, + foreign key (contact_entity_type_id) references entity_type(id) on delete set null, + foreign key (lead_entity_type_id) references entity_type(id) on delete set null, + foreign key (lead_board_id) references board(id) on delete set null, + foreign key (owner_id) references users(id) on delete set null + ) + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1742912395334-AlterChatProviderEntitySettings.ts b/backend/src/database/migrations/archive/1742912395334-AlterChatProviderEntitySettings.ts new file mode 100644 index 0000000..3150b21 --- /dev/null +++ b/backend/src/database/migrations/archive/1742912395334-AlterChatProviderEntitySettings.ts @@ -0,0 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatProviderEntitySettings1742912395334 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider_entity_settings + add column lead_stage_id integer, + add foreign key (lead_stage_id) references board_stage(id) on delete set null; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1743065071601-AlterChatProviderEntitySettings.ts b/backend/src/database/migrations/archive/1743065071601-AlterChatProviderEntitySettings.ts new file mode 100644 index 0000000..d7d4fa6 --- /dev/null +++ b/backend/src/database/migrations/archive/1743065071601-AlterChatProviderEntitySettings.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterChatProviderEntitySettings1743065071601 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table chat_provider_entity_settings add column lead_name text; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1743084130609-AlterProductStock.ts b/backend/src/database/migrations/archive/1743084130609-AlterProductStock.ts new file mode 100644 index 0000000..c76b516 --- /dev/null +++ b/backend/src/database/migrations/archive/1743084130609-AlterProductStock.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterProductStock1743084130609 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table product_stock alter column stock_quantity type numeric(13,4); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1743592762254-AddMailboxEntitySettings.ts b/backend/src/database/migrations/archive/1743592762254-AddMailboxEntitySettings.ts new file mode 100644 index 0000000..3c94f00 --- /dev/null +++ b/backend/src/database/migrations/archive/1743592762254-AddMailboxEntitySettings.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMailboxEntitySettings1743592762254 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + create table mailbox_entity_settings ( + mailbox_id integer, + account_id integer not null, + contact_entity_type_id integer, + lead_entity_type_id integer, + lead_board_id integer, + lead_stage_id integer, + lead_name text, + owner_id integer, + check_active_lead boolean not null default false, + check_duplicate boolean not null default false, + primary key (mailbox_id), + foreign key (mailbox_id) references mailbox(id) on delete cascade, + foreign key (account_id) references account(id) on delete cascade, + foreign key (contact_entity_type_id) references entity_type(id) on delete set null, + foreign key (lead_entity_type_id) references entity_type(id) on delete set null, + foreign key (lead_board_id) references board(id) on delete set null, + foreign key (lead_stage_id) references board_stage(id) on delete set null, + foreign key (owner_id) references users(id) on delete set null + ) + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1743595365132-InsertMailboxEntitySettings.ts b/backend/src/database/migrations/archive/1743595365132-InsertMailboxEntitySettings.ts new file mode 100644 index 0000000..f76ecd7 --- /dev/null +++ b/backend/src/database/migrations/archive/1743595365132-InsertMailboxEntitySettings.ts @@ -0,0 +1,16 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class InsertMailboxEntitySettings1743595365132 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` +insert into mailbox_entity_settings (account_id, mailbox_id, contact_entity_type_id, lead_entity_type_id, lead_board_id, owner_id, check_active_lead, check_duplicate) +select m.account_id, m.id as mailbox_id, m.contact_entity_type_id, m.lead_entity_type_id, m.lead_board_id, m.owner_id, false as check_active_lead, false as check_duplicate +from mailbox m where (m.create_contact = true and m.contact_entity_type_id is not null) or (m.create_lead = true and m.lead_entity_type_id is not null); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1743595431125-AlterMailbox.ts b/backend/src/database/migrations/archive/1743595431125-AlterMailbox.ts new file mode 100644 index 0000000..764a74a --- /dev/null +++ b/backend/src/database/migrations/archive/1743595431125-AlterMailbox.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailbox1743595431125 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox + drop column create_contact, + drop column contact_entity_type_id, + drop column lead_entity_type_id, + drop column lead_board_id, + drop column create_lead; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1743690907799-AlterEntityLink.ts b/backend/src/database/migrations/archive/1743690907799-AlterEntityLink.ts new file mode 100644 index 0000000..92609e3 --- /dev/null +++ b/backend/src/database/migrations/archive/1743690907799-AlterEntityLink.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterEntityLink1743690907799 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + with duplicates as ( + select id + from (select id, row_number() over (partition by source_id, target_id order by id) as rn from entity_link) t + where t.rn > 1 + ) + delete from entity_link where id in (select id from duplicates); + + alter table entity_link + drop column id, + add primary key (source_id, target_id); + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1743771835815-EntityIdIdentity.ts b/backend/src/database/migrations/archive/1743771835815-EntityIdIdentity.ts new file mode 100644 index 0000000..e7cfdfe --- /dev/null +++ b/backend/src/database/migrations/archive/1743771835815-EntityIdIdentity.ts @@ -0,0 +1,22 @@ +/* eslint-disable max-len */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class EntityIdIdentity1743771835815 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE entity ALTER COLUMN id DROP DEFAULT; + DROP SEQUENCE IF EXISTS entity_id_seq; + DO $$ + DECLARE + max_id integer; + BEGIN + SELECT COALESCE(MAX(id), 0) + 1 INTO max_id FROM entity; + EXECUTE 'ALTER TABLE entity ALTER COLUMN id SET NOT NULL, ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH ' || max_id || ')'; + END $$; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1744127440588-AlterEntity.ts b/backend/src/database/migrations/archive/1744127440588-AlterEntity.ts new file mode 100644 index 0000000..cc03201 --- /dev/null +++ b/backend/src/database/migrations/archive/1744127440588-AlterEntity.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterEntity1744127440588 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table entity add column focused boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1744296868607-AlterMailboxFolder.ts b/backend/src/database/migrations/archive/1744296868607-AlterMailboxFolder.ts new file mode 100644 index 0000000..e8a2357 --- /dev/null +++ b/backend/src/database/migrations/archive/1744296868607-AlterMailboxFolder.ts @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailboxFolder1744296868607 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_folder rename column messages_total to total; + alter table mailbox_folder rename column messages_unread to unread; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1744374434852-AlterMailboxSignatureLink.ts b/backend/src/database/migrations/archive/1744374434852-AlterMailboxSignatureLink.ts new file mode 100644 index 0000000..5b2d13b --- /dev/null +++ b/backend/src/database/migrations/archive/1744374434852-AlterMailboxSignatureLink.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailboxSignatureLink1744374434852 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_signature_link rename to mailbox_signature_mailbox; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/migrations/archive/1744382844583-AlterMailboxSignature.ts b/backend/src/database/migrations/archive/1744382844583-AlterMailboxSignature.ts new file mode 100644 index 0000000..7603abb --- /dev/null +++ b/backend/src/database/migrations/archive/1744382844583-AlterMailboxSignature.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-empty-function */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AlterMailboxSignature1744382844583 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + alter table mailbox_signature add column is_html boolean not null default false; + `); + } + + public async down(_queryRunner: QueryRunner): Promise {} +} diff --git a/backend/src/database/nestjs-logger.ts b/backend/src/database/nestjs-logger.ts new file mode 100644 index 0000000..077133c --- /dev/null +++ b/backend/src/database/nestjs-logger.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Logger } from '@nestjs/common'; +import { Logger as TypeOrmLogger, QueryRunner } from 'typeorm'; + +export class NestjsLogger implements TypeOrmLogger { + private readonly logger = new Logger('SQL'); + + logQuery(query: string, parameters?: unknown[], _queryRunner?: QueryRunner) { + this.logger.log(`Query: ${query}${parameters ? ` -- PARAMETERS: ${JSON.stringify(parameters)}` : ''}`); + } + + logQueryError(error: string, query: string, parameters?: unknown[], _queryRunner?: QueryRunner) { + this.logger.error( + `Error: ${error} Query: ${query}${parameters ? ` -- PARAMETERS: ${JSON.stringify(parameters)}` : ''}`, + ); + } + + logQuerySlow(time: number, query: string, parameters?: unknown[], _queryRunner?: QueryRunner) { + this.logger.warn( + `SLOW Query: ${query}${parameters ? ` -- PARAMETERS: ${JSON.stringify(parameters)}` : ''}\t[${time}ms]`, + ); + } + + logSchemaBuild(message: string, _queryRunner?: QueryRunner) { + this.logger.log(`Schema Build: ${message}`); + } + + logMigration(message: string, _queryRunner?: QueryRunner) { + this.logger.log(`Migration: ${message}`); + } + + log(level: 'log' | 'info' | 'warn', message: unknown, _queryRunner?: QueryRunner) { + if (level === 'log') { + this.logger.log(message); + } else if (level === 'info') { + this.logger.debug(message); + } else if (level === 'warn') { + this.logger.warn(message); + } + } +} diff --git a/backend/src/database/services/index.ts b/backend/src/database/services/index.ts new file mode 100644 index 0000000..6dd59cd --- /dev/null +++ b/backend/src/database/services/index.ts @@ -0,0 +1 @@ +export * from './sequence-id.service'; diff --git a/backend/src/database/services/sequence-id.service.ts b/backend/src/database/services/sequence-id.service.ts new file mode 100644 index 0000000..0f1a8c7 --- /dev/null +++ b/backend/src/database/services/sequence-id.service.ts @@ -0,0 +1,18 @@ +import { DataSource } from 'typeorm'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SequenceIdService { + constructor(private dataSource: DataSource) {} + + async nextIdentity(sequenceName: string): Promise { + const res = await this.dataSource.query(`select nextval('${sequenceName}')`); + + return parseInt(res[0]['nextval']); + } + + public async getIdentityPool(sequenceName: string, size: number): Promise { + const res = await this.dataSource.query(`select nextval('${sequenceName}') from generate_series(1, ${size})`); + return res.map((value) => parseInt(value.nextval)); + } +} diff --git a/backend/src/database/typeorm-migration.config.ts b/backend/src/database/typeorm-migration.config.ts new file mode 100644 index 0000000..1d80119 --- /dev/null +++ b/backend/src/database/typeorm-migration.config.ts @@ -0,0 +1,21 @@ +import { DataSource, type DataSourceOptions } from 'typeorm'; +import { ConfigService } from '@nestjs/config'; +import { config } from 'dotenv'; +import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; + +config({ path: `.env` }); +config({ path: `.env.local`, override: true }); + +const configService = new ConfigService(); + +export default new DataSource({ + type: 'postgres', + host: configService.get('POSTGRES_HOST'), + port: configService.get('POSTGRES_PORT'), + username: configService.get('POSTGRES_USER'), + password: configService.get('POSTGRES_PASSWORD'), + database: configService.get('POSTGRES_DB'), + namingStrategy: new SnakeNamingStrategy(), + migrations: ['./src/database/migrations/*.ts'], + logging: configService.get('POSTGRES_QUERY_LOGGING') === 'true', +} as DataSourceOptions); diff --git a/backend/src/documentation/api-documentation.ts b/backend/src/documentation/api-documentation.ts new file mode 100644 index 0000000..1798af7 --- /dev/null +++ b/backend/src/documentation/api-documentation.ts @@ -0,0 +1,45 @@ +import { INestApplication, Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; + +import documentationConfig from './config/documentation.config'; + +@Module({ + imports: [ConfigModule.forFeature(documentationConfig)], +}) +export class ApiDocumentation { + static configure(app: INestApplication) { + const configService = app.get(ConfigService); + + if (configService.get('documentation.enabled')) { + const appName = configService.get('application.name'); + const baseUrl = configService.get('application.baseUrl'); + + const config = new DocumentBuilder() + .setTitle(`${appName} API`) + .setDescription( + `The ${appName} API enables secure and efficient access to various features of the ${appName} platform. +This platform is designed to help businesses manage client relationships, automate tasks, +and enhance customer communication. With support for multiple client accounts through subdomains, +the API allows businesses to interact with client data, manage entities, and automate actions +based on customizable conditions.`, + ) + .setVersion(process.env.npm_package_version) + .addBearerAuth() + .addApiKey({ type: 'apiKey', in: 'header', name: 'X-Api-Key' }) + .build(); + const document = SwaggerModule.createDocument(app, config); + SwaggerModule.setup('doc', app, document, { + useGlobalPrefix: true, + customSiteTitle: `${appName} API. Version: ${process.env.npm_package_version} `, + customfavIcon: `${baseUrl}/favicon.ico`, + swaggerOptions: { + defaultModelRendering: 'model', + layout: 'BaseLayout', + tagsSorter: 'alpha', + deepLinking: true, + }, + }); + } + } +} diff --git a/backend/src/documentation/config/documentation.config.ts b/backend/src/documentation/config/documentation.config.ts new file mode 100644 index 0000000..f441bf1 --- /dev/null +++ b/backend/src/documentation/config/documentation.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +export interface DocumentationConfig { + enabled: boolean; +} + +export default registerAs( + 'documentation', + (): DocumentationConfig => ({ + enabled: process.env.API_DOC_ENABLED === 'true', + }), +); diff --git a/backend/src/documentation/index.ts b/backend/src/documentation/index.ts new file mode 100644 index 0000000..444eb89 --- /dev/null +++ b/backend/src/documentation/index.ts @@ -0,0 +1 @@ +export * from './api-documentation'; diff --git a/backend/src/main.ts b/backend/src/main.ts new file mode 100644 index 0000000..27aaa4b --- /dev/null +++ b/backend/src/main.ts @@ -0,0 +1,116 @@ +import { webcrypto } from 'crypto'; +import { NestFactory } from '@nestjs/core'; + +// Polyfill crypto for @nestjs/typeorm +if (!global.crypto) { + global.crypto = webcrypto as any; +} +import { Logger, ValidationPipe } from '@nestjs/common'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { utilities, WinstonModule } from 'nest-winston'; +import winston from 'winston'; +import helmet from 'helmet'; +//import rateLimit from 'express-rate-limit'; +import cookieParser from 'cookie-parser'; + +import { AppModule } from './app.module'; +import { ApiDocumentation } from './documentation'; +import { extractSubdomain, LoggingInterceptor } from './common'; + +function getLogger() { + return process.env.WINSTON_ENABLED === 'true' + ? WinstonModule.createLogger({ + level: 'debug', + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), + utilities.format.nestLike(process.env.APPLICATION_NAME, { colors: true, prettyPrint: true }), + ), + }), + ], + }) + : undefined; +} + +async function bootstrap() { + if (process.env.NEW_RELIC_ENABLED === 'true') { + // eslint-disable-next-line @typescript-eslint/no-require-imports + require('newrelic'); + } + + const app = await NestFactory.create(AppModule, { rawBody: true, logger: getLogger() }); + + // Security headers + app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com", "blob:"], + styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], + imgSrc: ["'self'", "data:", "blob:", "https://*"], + fontSrc: ["'self'", "https://fonts.gstatic.com"], + connectSrc: ["'self'"], + frameSrc: ["'self'"], + objectSrc: ["'none'"], + baseUri: ["'self'"], + formAction: ["'self'"] + } + }, + hsts: { + maxAge: 31536000, + includeSubDomains: true, + preload: true + }, + referrerPolicy: { policy: 'strict-origin-when-cross-origin' } + })); + + // Rate limiting disabled to eliminate 429 errors + // app.use( + // rateLimit({ + // windowMs: 15 * 60 * 1000, // 15 minutes + // max: 1000, // limit each IP to 1000 requests per windowMs + // message: 'Too many requests from this IP, please try again later.', + // standardHeaders: true, + // legacyHeaders: false, + // }), + // ); + + // CORS with restrictions + app.enableCors({ + origin: process.env['FRONTEND_URL'] || 'http://localhost:3000', + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS', + credentials: true, + allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'] + }); + + app.set('trust proxy', true); + app.use(cookieParser()); + app.use(extractSubdomain); + app.setGlobalPrefix('api'); + + // Global validation + app.useGlobalPipes(new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: false, + transform: true, + disableErrorMessages: process.env['NODE_ENV'] === 'production' + })); + + app.useGlobalInterceptors(new LoggingInterceptor()); + + ApiDocumentation.configure(app); + + const logger = new Logger('main'); + + process.on('uncaughtException', (error) => { + logger.error(`Uncaught Exception`, error.stack); + }); + + await app.listen(process.env.APPLICATION_PORT, '127.0.0.1'); + + logger.log(`Application is running on: ${await app.getUrl()}`); + logger.log(`Application version is: ${process.env.npm_package_version}`); +} + +bootstrap(); diff --git a/backend/src/modules/analytics/analytics.module.ts b/backend/src/modules/analytics/analytics.module.ts new file mode 100644 index 0000000..e14bf22 --- /dev/null +++ b/backend/src/modules/analytics/analytics.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; + +import analyticsConfig from './config/analytics.config'; +import { AnalyticsService } from './analytics.service'; + +@Module({ + imports: [ConfigModule.forFeature(analyticsConfig)], + providers: [AnalyticsService], +}) +export class AnalyticsModule {} diff --git a/backend/src/modules/analytics/analytics.service.ts b/backend/src/modules/analytics/analytics.service.ts new file mode 100644 index 0000000..73d3f78 --- /dev/null +++ b/backend/src/modules/analytics/analytics.service.ts @@ -0,0 +1,83 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { OnEvent } from '@nestjs/event-emitter'; +import { catchError, lastValueFrom } from 'rxjs'; + +import { IamEventType, AccountCreatedEvent, UserLoginEvent } from '@/modules//iam/common'; + +import { AnalyticsConfig } from './config/analytics.config'; + +const GoogleAnalyticsUrls = { + ga: 'https://www.google-analytics.com', + mp: () => `${GoogleAnalyticsUrls.ga}/mp`, + collect: (measurementId: string, apiSecret: string) => + `${GoogleAnalyticsUrls.mp()}/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`, +} as const; + +interface GaEventData { + client_id: string; + events: { name: string; params: GaEventDataParam }[]; +} + +type GaEventDataParam = Record; + +@Injectable() +export class AnalyticsService { + private readonly logger = new Logger(AnalyticsService.name); + private _config: AnalyticsConfig; + + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + ) { + this._config = this.configService.get('analytics'); + } + + @OnEvent(IamEventType.AccountCreated, { async: true }) + public async handleRegistrationEvent(event: AccountCreatedEvent) { + this.collectGAEvents({ + client_id: event.gaClientId, + events: [ + { + name: 'sign_up', + params: { + account_id: event.accountId, + account_user_id: event.ownerId, + user_id: event.gaUserId, + account_tariff: event.subscriptionName, + }, + }, + ], + }); + } + + @OnEvent(IamEventType.UserLogin, { async: true }) + public async handleUserLoginEvent(event: UserLoginEvent) { + this.collectGAEvents({ + client_id: event.gaClientId, + events: [ + { + name: 'login', + params: { + account_id: event.accountId, + account_user_id: event.userId, + user_id: event.gaUserId, + account_tariff: event.subscriptionName, + }, + }, + ], + }); + } + + private async collectGAEvents(data: GaEventData) { + const url = GoogleAnalyticsUrls.collect(this._config.gaMeasurementId, this._config.gaApiSecret); + const response$ = this.httpService.post(url, data).pipe( + catchError((error) => { + this.logger.error(`Google Analytics measurement error`, (error as Error)?.stack); + throw error; + }), + ); + await lastValueFrom(response$); + } +} diff --git a/backend/src/modules/analytics/config/analytics.config.ts b/backend/src/modules/analytics/config/analytics.config.ts new file mode 100644 index 0000000..0164841 --- /dev/null +++ b/backend/src/modules/analytics/config/analytics.config.ts @@ -0,0 +1,14 @@ +import { registerAs } from '@nestjs/config'; + +export interface AnalyticsConfig { + gaMeasurementId: string; + gaApiSecret: string; +} + +export default registerAs( + 'analytics', + (): AnalyticsConfig => ({ + gaMeasurementId: process.env.GA_MEASUREMENT_ID, + gaApiSecret: process.env.GA_API_SECRET, + }), +); diff --git a/backend/src/modules/automation/automation-core/automation-core.controller.ts b/backend/src/modules/automation/automation-core/automation-core.controller.ts new file mode 100644 index 0000000..81ead28 --- /dev/null +++ b/backend/src/modules/automation/automation-core/automation-core.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Delete, Get, Param } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { AutomationCoreService } from './automation-core.service'; + +@ApiExcludeController(true) +@Controller('core') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class AutomationCoreController { + constructor(private readonly service: AutomationCoreService) {} + + @Get('processes') + public async listProcessDefinitions() { + return this.service.listProcessDefinitions(); + } + + @Delete('processes/:resourceKey') + public async delete(@Param('resourceKey') resourceKey: string) { + return this.service.deleteProcess(resourceKey); + } +} diff --git a/backend/src/modules/automation/automation-core/automation-core.service.ts b/backend/src/modules/automation/automation-core/automation-core.service.ts new file mode 100644 index 0000000..2be246e --- /dev/null +++ b/backend/src/modules/automation/automation-core/automation-core.service.ts @@ -0,0 +1,163 @@ +import { Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; + +import { Camunda8 } from '@camunda8/sdk'; +import { IOutputVariables, JSONDoc, ProcessMetadata } from '@camunda8/sdk/dist/zeebe/types'; +import { ProcessDefinition } from '@camunda8/sdk/dist/operate/lib/OperateDto'; + +import { AutomationConfig } from '../config/automation.config'; +import { AUTOMATION_JOB_HANDLER, AUTOMATION_WORKER, AutomationHandler, Message, Signal } from '../common'; +import { AutomationJobHandler } from '../common'; +import { ProcessDefinitionVersions } from './types'; + +@Injectable() +export class AutomationCoreService implements OnModuleInit { + private readonly logger = new Logger(AutomationCoreService.name); + private readonly camunda = new Camunda8(); + private readonly zeebe = this.camunda.getZeebeGrpcApiClient(); + private readonly operate = this.camunda.getOperateApiClient(); + + constructor( + private readonly configService: ConfigService, + private readonly discoveryService: DiscoveryService, + ) {} + + onModuleInit() { + const config = this.configService.get('automation'); + if (!config.jobDiscovery) return; + + const wrappers = this.discoveryService.getProviders(); + const handlers = wrappers + .filter((wrapper) => wrapper.metatype && Reflect.getMetadata(AUTOMATION_WORKER, wrapper.metatype)) + .map((wrapper) => ({ + instance: wrapper.instance as AutomationJobHandler, + type: Reflect.getMetadata(AUTOMATION_WORKER, wrapper.metatype) as string, + })); + for (const handler of handlers) { + this.createWorker(handler.type, handler.instance.handleJob.bind(handler.instance)); + } + + wrappers + .filter((wrapper) => wrapper.instance) + .forEach((wrapper) => { + Object.getOwnPropertyNames(Object.getPrototypeOf(wrapper.instance)).forEach((method) => { + try { + const methodHandler = wrapper.instance[method]; + if (typeof methodHandler === 'function') { + const metadata = Reflect.getMetadata(AUTOMATION_JOB_HANDLER, methodHandler) as string; + if (metadata) { + this.createWorker(metadata, methodHandler.bind(wrapper.instance)); + } + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + //skip all errors + } + }); + }); + } + + async listProcessDefinitions(): Promise { + const size = 100; + let total = 0; + let searchAfter: unknown[] = undefined; + const definitions: ProcessDefinition[] = []; + do { + const result = await this.operate.searchProcessDefinitions({ + sort: [{ field: 'bpmnProcessId', order: 'ASC' }], + searchAfter, + size, + }); + definitions.push(...result.items); + total = Number(result.total); + searchAfter = result.sortValues; + } while (definitions.length < total); + const processes = definitions.reduce((acc, item) => { + const existingProcess = acc.find((process) => process.bpmnProcessId === item.bpmnProcessId); + + if (existingProcess) { + existingProcess.versions.push({ version: item.version, key: item.key }); + } else { + acc.push({ + bpmnProcessId: item.bpmnProcessId, + name: item.name, + versions: [{ version: item.version, key: item.key }], + }); + } + + return acc; + }, [] as ProcessDefinitionVersions[]); + + return processes; + } + + async deployProcess(name: string, process: Buffer): Promise { + try { + const deploy = await this.zeebe.deployResource({ name, process }); + return deploy.deployments[0].process; + } catch (e) { + this.logger.warn(`Deploy process error: ${e.toString()}`); + return null; + } + } + + async deleteProcess(resourceKey: string): Promise { + try { + await this.zeebe.deleteResource({ resourceKey }); + return true; + } catch (e) { + this.logger.warn(`Delete process error: ${e.toString()}`); + return false; + } + } + + async sendMessage(message: Message) { + this.zeebe.publishMessage({ + name: this.formatSignalName(message), + correlationKey: message.correlationKey, + variables: message.variables, + }); + } + + async sendSignal(signal: Signal) { + this.zeebe.broadcastSignal({ signalName: this.formatSignalName(signal), variables: signal.variables }); + } + + async startProcess({ bpmnProcessId, variables }: { bpmnProcessId: string; variables: V }) { + this.zeebe.createProcessInstance({ bpmnProcessId, variables }); + } + + private formatSignalName({ name }: Signal): string { + return `${Array.isArray(name) ? name.join('|') : name}`; + } + + private createWorker( + type: string, + handler: AutomationHandler, + ) { + this.zeebe.createWorker({ + taskType: type, + taskHandler: async (job) => { + this.logger.log(`Handling job of type '${job.type}' with data: ${JSON.stringify(job)}`); + + try { + const result = await handler({ variables: job.variables as InputVariables }); + this.logger.log(`Handled job of type '${job.type}' with result: ${JSON.stringify(result)}`); + + return job.complete(result?.variables as IOutputVariables); + } catch (e) { + this.logger.error(`Worker '${type}' error`, (e as Error)?.stack); + + if (e instanceof Error) { + return job.fail(e.message); + } + + return job.fail(e.toString()); + } + }, + }); + + this.logger.log(`Worker for task type '${type}' created`); + } +} diff --git a/backend/src/modules/automation/automation-core/index.ts b/backend/src/modules/automation/automation-core/index.ts new file mode 100644 index 0000000..b9dae37 --- /dev/null +++ b/backend/src/modules/automation/automation-core/index.ts @@ -0,0 +1,3 @@ +export * from './automation-core.controller'; +export * from './automation-core.service'; +export * from './types'; diff --git a/backend/src/modules/automation/automation-core/types/index.ts b/backend/src/modules/automation/automation-core/types/index.ts new file mode 100644 index 0000000..74ae0ba --- /dev/null +++ b/backend/src/modules/automation/automation-core/types/index.ts @@ -0,0 +1 @@ +export * from './process-definition-version'; diff --git a/backend/src/modules/automation/automation-core/types/process-definition-version.ts b/backend/src/modules/automation/automation-core/types/process-definition-version.ts new file mode 100644 index 0000000..f608fdf --- /dev/null +++ b/backend/src/modules/automation/automation-core/types/process-definition-version.ts @@ -0,0 +1,10 @@ +interface ProcessDefinitionVersion { + version: number; + key: string; +} + +export interface ProcessDefinitionVersions { + bpmnProcessId: string; + name: string; + versions: ProcessDefinitionVersion[]; +} diff --git a/backend/src/modules/automation/automation-entity-type/automation-entity-type.controller.ts b/backend/src/modules/automation/automation-entity-type/automation-entity-type.controller.ts new file mode 100644 index 0000000..703d4ff --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/automation-entity-type.controller.ts @@ -0,0 +1,75 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { + AutomationEntityTypeDto, + AutomationEntityTypeFilterDto, + CreateAutomationEntityTypeDto, + UpdateAutomationEntityTypeDto, +} from './dto'; +import { AutomationEntityTypeService } from './automation-entity-type.service'; + +@ApiTags('automation/entity-types') +@Controller('entity-types') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class AutomationEntityTypeController { + constructor(private readonly service: AutomationEntityTypeService) {} + + @ApiOperation({ summary: 'Create EntityType automation', description: 'Create simple automation for EntityType' }) + @ApiBody({ description: 'Data for creating EntityType automation', type: CreateAutomationEntityTypeDto }) + @ApiCreatedResponse({ description: 'EntityType automation', type: AutomationEntityTypeDto }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateAutomationEntityTypeDto) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Get EntityType automations', description: 'Get simple automations for EntityType' }) + @ApiOkResponse({ description: 'EntityType automations', type: [AutomationEntityTypeDto] }) + @Get() + async findMany(@CurrentAuth() { accountId }: AuthData, @Query() filter: AutomationEntityTypeFilterDto) { + return this.service.findMany({ accountId, ...filter }); + } + + @ApiOperation({ summary: 'Get EntityType automation', description: 'Get simple automation for EntityType' }) + @ApiParam({ name: 'automationId', description: 'EntityType automation ID' }) + @ApiOkResponse({ description: 'EntityType automation', type: AutomationEntityTypeDto }) + @Get(':automationId') + async findOne(@CurrentAuth() { accountId }: AuthData, @Param('automationId', ParseIntPipe) automationId: number) { + return this.service.findOne({ accountId, automationId }); + } + + @ApiOperation({ summary: 'Update EntityType automation', description: 'Update simple automation for EntityType' }) + @ApiParam({ name: 'automationId', description: 'EntityType automation ID' }) + @ApiBody({ description: 'Data for updating EntityType automation', type: UpdateAutomationEntityTypeDto }) + @ApiOkResponse({ description: 'EntityType automation', type: AutomationEntityTypeDto }) + @Patch(':automationId') + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('automationId', ParseIntPipe) automationId: number, + @Body() dto: UpdateAutomationEntityTypeDto, + ) { + return this.service.update({ accountId, automationId, dto }); + } + + @ApiOperation({ summary: 'Delete EntityType automation', description: 'Delete simple automation for EntityType' }) + @ApiParam({ name: 'automationId', description: 'EntityType automation ID' }) + @ApiOkResponse({ description: 'Deleted EntityType automation ID', type: Number }) + @Delete(':automationId') + async delete(@CurrentAuth() { accountId }: AuthData, @Param('automationId', ParseIntPipe) automationId: number) { + return this.service.delete({ accountId, automationId }); + } + + @ApiOperation({ summary: 'Generate BPMN', description: 'Generate BPM model for EntityType automation' }) + @ApiParam({ name: 'automationId', description: 'EntityType automation ID' }) + @ApiOkResponse({ description: 'BPMN model', type: String }) + @Get(':automationId/generate') + async generate(@CurrentAuth() { accountId }: AuthData, @Param('automationId', ParseIntPipe) automationId: number) { + return this.service.generateBpmn({ accountId, automationId }); + } +} diff --git a/backend/src/modules/automation/automation-entity-type/automation-entity-type.handler.ts b/backend/src/modules/automation/automation-entity-type/automation-entity-type.handler.ts new file mode 100644 index 0000000..e39ffe3 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/automation-entity-type.handler.ts @@ -0,0 +1,125 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; +import { BoardEvent, BoardStageDeletedEvent, CrmEventType, EntityTypeEvent } from '@/CRM/common'; +import { FieldEvent, FieldEventType } from '@/modules/entity/entity-field/common'; +import { ChatProviderEvent, ChatProviderStatus, MultichatEventType } from '@/modules/multichat/common'; +import { MailboxEvent, MailEventType } from '@/Mailing/common'; + +import { AutomationEntityTypeService } from './automation-entity-type.service'; +import { EntityTypeActionType } from './enums'; + +@Injectable() +export class AutomationEntityTypeHandler { + constructor(private readonly service: AutomationEntityTypeService) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + if (event.newUserId) { + await this.service.changeOwner({ + accountId: event.accountId, + currentUserId: event.userId, + newUserId: event.newUserId, + }); + + await this.service.updateByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityResponsibleChange, + criteria: { responsibleUserId: event.userId }, + payload: { newResponsibleUserId: event.newUserId }, + }); + } else { + await this.service.deleteMany({ accountId: event.accountId, createdBy: event.userId }); + + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityResponsibleChange, + criteria: { responsibleUserId: event.userId }, + }); + } + } + + @OnEvent(CrmEventType.EntityTypeDeleted, { async: true }) + public async onEntityTypeDeleted(event: EntityTypeEvent) { + await this.service.deleteMany({ accountId: event.accountId, entityTypeId: event.entityTypeId }); + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityCreate, + criteria: { entityTypeId: event.entityTypeId }, + }); + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityLinkedStageChange, + criteria: { entityTypeId: event.entityTypeId }, + }); + } + + // !!! + @OnEvent(CrmEventType.BoardDeleted, { async: true }) + public async onBoardDeleted(event: BoardEvent) { + await this.service.deleteMany({ accountId: event.accountId, boardId: event.boardId }); + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityCreate, + criteria: { boardId: event.boardId }, + }); + } + + @OnEvent(CrmEventType.BoardStageDeleted, { async: true }) + public async onBoardStageDeleted(event: BoardStageDeletedEvent) { + await this.service.deleteMany({ accountId: event.accountId, boardId: event.boardId, stageId: event.stageId }); + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityCreate, + criteria: { stageId: event.stageId }, + }); + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityStageChange, + criteria: { stageId: event.stageId }, + }); + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EntityLinkedStageChange, + criteria: { stageId: event.stageId }, + }); + } + + @OnEvent(FieldEventType.FieldDeleted, { async: true }) + public async onFieldDeleted(event: FieldEvent) { + await this.service.deleteByConditionsCriteria({ + accountId: event.accountId, + criteria: { fieldId: event.fieldId }, + }); + } + + @OnEvent(MailEventType.MailboxDeleted, { async: true }) + public async onMailboxDeleted(event: MailboxEvent) { + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.EmailSend, + criteria: { mailboxId: event.mailboxId }, + }); + } + + @OnEvent(MultichatEventType.ChatProviderUpdated, { async: true }) + public async onChatProviderUpdated(event: ChatProviderEvent) { + if (event.status !== ChatProviderStatus.Active) { + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.ChatSendExternal, + criteria: { providerId: event.providerId }, + }); + } + } + + @OnEvent(MultichatEventType.ChatProviderDeleted, { async: true }) + public async onChatProviderDeleted(event: ChatProviderEvent) { + await this.service.deleteByActionsCriteria({ + accountId: event.accountId, + type: EntityTypeActionType.ChatSendExternal, + criteria: { providerId: event.providerId }, + }); + } +} diff --git a/backend/src/modules/automation/automation-entity-type/automation-entity-type.service.ts b/backend/src/modules/automation/automation-entity-type/automation-entity-type.service.ts new file mode 100644 index 0000000..10fc4ae --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/automation-entity-type.service.ts @@ -0,0 +1,465 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { readFileSync } from 'fs'; +import Handlebars from 'handlebars'; +import path from 'path'; +import type { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { AutomationProcessService } from '../automation-process'; +import { + AutomationDelayUtil, + AutomationEventType, + AutomationProcessType, + AutomatonConditionUtil, + EntityTypeApplyEvent, +} from '../common'; + +import type { + ActionEntityResponsibleChangeSettings, + CreateAutomationEntityTypeDto, + UpdateAutomationEntityTypeDto, +} from './dto'; +import { AutomationEntityType } from './entities'; +import { EntityTypeActionType, EntityTypeTrigger } from './enums'; + +interface FindFilter { + accountId: number; + automationId?: number; + entityTypeId?: number; + boardId?: number; + stageId?: number; + isActive?: boolean; + createdBy?: number; +} + +interface DeleteActionsCriteria { + entityTypeId?: number; + boardId?: number; + stageId?: number; + mailboxId?: number; + providerId?: number; + responsibleUserId?: number; +} + +interface UpdateActionsCriteria { + responsibleUserId?: number; +} + +interface UpdateActionsPayload { + newResponsibleUserId?: number; +} + +interface DeleteConditionsCriteria { + fieldId?: number; +} + +@Injectable() +export class AutomationEntityTypeService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(AutomationEntityType) + private readonly repository: Repository, + private readonly processService: AutomationProcessService, + ) {} + + async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateAutomationEntityTypeDto; + }): Promise { + let automation = await this.repository.save(AutomationEntityType.fromDto(accountId, userId, dto)); + if (dto.isActive) { + automation = await this.activate(automation); + } + if (automation.isActive && dto.applyImmediately) { + this.apply({ accountId, automation }); + } + + return automation; + } + + async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).orderBy('created_at').getMany(); + } + + async generateBpmn({ accountId, automationId }: { accountId: number; automationId: number }): Promise { + const automation = await this.findOne({ accountId, automationId }); + if (!automation) { + throw NotFoundError.withId(AutomationEntityType, automationId); + } + + return await this.generate(automation); + } + + async update({ + accountId, + automationId, + dto, + }: { + accountId: number; + automationId: number; + dto: UpdateAutomationEntityTypeDto; + }): Promise { + let automation = await this.findOne({ accountId, automationId }); + if (!automation) { + throw NotFoundError.withId(AutomationEntityType, automationId); + } + + const wasActive = automation.isActive; + if (automation.isActive) { + await this.deactivate(automation); + automation.processId = null; + await this.repository.save(automation); + } + + this.repository.save(automation.update(dto)); + + if (dto.isActive || (dto.isActive === undefined && wasActive)) { + automation = await this.activate(automation); + } + if (automation.isActive && dto.applyImmediately) { + this.apply({ accountId, automation }); + } + + return automation; + } + + async changeOwner({ + accountId, + currentUserId, + newUserId, + }: { + accountId: number; + currentUserId: number; + newUserId: number; + }) { + await this.repository.update({ accountId, createdBy: currentUserId }, { createdBy: newUserId }); + } + + async deleteMany(filter: FindFilter) { + const automations = await this.findMany(filter); + for (const automation of automations) { + await this.delete({ accountId: automation.accountId, automationId: automation.id }); + } + } + + async delete({ accountId, automationId }: { accountId: number; automationId: number }): Promise { + const automation = await this.findOne({ accountId, automationId }); + if (!automation) { + throw NotFoundError.withId(AutomationEntityType, automationId); + } + + await this.deleteAutomation(automation); + + return automation.id; + } + + async updateByActionsCriteria({ + accountId, + type, + criteria, + payload, + }: { + accountId: number; + type: EntityTypeActionType; + criteria: UpdateActionsCriteria; + payload: UpdateActionsPayload; + }) { + switch (type) { + case EntityTypeActionType.EntityResponsibleChange: + if (criteria.responsibleUserId && payload.newResponsibleUserId) { + const automations = await this.createFindQb({ accountId }) + .andWhere( + // eslint-disable-next-line max-len + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.responsibleUserId == ${criteria.responsibleUserId})')`, + ) + .getMany(); + + for (const automation of automations) { + const updatedActions = automation.actions.map((action) => { + if ( + (action.settings as ActionEntityResponsibleChangeSettings).responsibleUserId === + criteria.responsibleUserId + ) { + return { + ...action, + settings: { + ...action.settings, + responsibleUserId: payload.newResponsibleUserId, + }, + }; + } + return action; + }); + + const wasActive = automation.isActive; + + if (automation.isActive) { + await this.deactivate(automation); + } + + await this.repository.save(automation.update({ actions: updatedActions })); + + if (wasActive) { + await this.activate(automation); + } + } + } + } + } + + async deleteByActionsCriteria({ + accountId, + type, + criteria, + }: { + accountId: number; + type: EntityTypeActionType; + criteria: DeleteActionsCriteria; + }) { + let automations: AutomationEntityType[] = []; + switch (type) { + case EntityTypeActionType.EntityCreate: + if (criteria.entityTypeId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + // eslint-disable-next-line max-len + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.entityTypeId == ${criteria.entityTypeId})')`, + ) + .getMany(); + } + if (criteria.boardId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.boardId == ${criteria.boardId})')`, + ) + .getMany(); + } + if (criteria.stageId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.stageId == ${criteria.stageId})')`, + ) + .getMany(); + } + break; + case EntityTypeActionType.EntityStageChange: + if (criteria.stageId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.stageId == ${criteria.stageId})')`, + ) + .getMany(); + } + break; + case EntityTypeActionType.EntityLinkedStageChange: + if (criteria.entityTypeId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + // eslint-disable-next-line max-len + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.entityTypeId == ${criteria.entityTypeId})')`, + ) + .getMany(); + } + if (criteria.stageId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.stageId == ${criteria.stageId})')`, + ) + .getMany(); + } + break; + case EntityTypeActionType.EntityResponsibleChange: + if (criteria.responsibleUserId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + // eslint-disable-next-line max-len + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.responsibleUserId == ${criteria.responsibleUserId})')`, + ) + .getMany(); + } + break; + case EntityTypeActionType.EmailSend: + if (criteria.mailboxId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + // eslint-disable-next-line max-len + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.mailboxId == ${criteria.mailboxId})')`, + ) + .getMany(); + } + break; + case EntityTypeActionType.ChatSendExternal: + if (criteria.providerId) { + automations = await this.createFindQb({ accountId }) + .andWhere( + // eslint-disable-next-line max-len + `jsonb_path_exists(actions, '$[*] ? (@.type == "${type}" && @.settings.providerId == ${criteria.providerId})')`, + ) + .getMany(); + } + break; + } + + for (const automation of automations) { + await this.deleteAutomation(automation); + } + } + + async deleteByConditionsCriteria({ accountId, criteria }: { accountId: number; criteria: DeleteConditionsCriteria }) { + const automations = await this.createFindQb({ accountId }) + .andWhere(`jsonb_path_exists(conditions, '$.fields[*] ? (@.fieldId == ${criteria.fieldId})')`) + .getMany(); + + for (const automation of automations) { + await this.deleteAutomation(automation); + } + } + + private async deleteAutomation(automation: AutomationEntityType) { + if (automation.isActive) { + await this.deactivate(automation); + } + + await this.repository.delete(automation.id); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId: filter.accountId }); + + if (filter?.automationId) { + qb.andWhere('id = :id', { id: filter.automationId }); + } + if (filter?.entityTypeId) { + qb.andWhere('entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + } + if (filter?.boardId) { + qb.andWhere('board_id = :boardId', { boardId: filter.boardId }); + } + if (filter?.stageId) { + qb.andWhere('stage_id = :stageId', { stageId: filter.stageId }); + } + if (filter?.isActive !== undefined) { + qb.andWhere('is_active = :isActive', { isActive: filter.isActive }); + } + if (filter?.createdBy) { + qb.andWhere('created_by = :createdBy', { createdBy: filter.createdBy }); + } + + return qb; + } + + private async activate(automation: AutomationEntityType): Promise { + const bpmn = await this.generate(automation); + + const process = await this.processService.create({ + accountId: automation.accountId, + userId: automation.createdBy, + dto: { + name: automation.name, + type: AutomationProcessType.EntityType, + objectId: automation.entityTypeId, + isActive: true, + isReadonly: true, + bpmnFile: bpmn, + }, + }); + + await this.repository.save(automation.update({ processId: process.id })); + + return automation; + } + + private async deactivate(automation: AutomationEntityType): Promise { + const processId = automation.processId; + await this.repository.save(automation.update({ processId: null })); + + await this.processService.delete({ accountId: automation.accountId, processId }); + + return automation; + } + + private async apply({ accountId, automation }: { accountId: number; automation: AutomationEntityType }) { + this.eventEmitter.emit( + AutomationEventType.EntityTypeApply, + new EntityTypeApplyEvent({ + accountId, + automationId: automation.id, + processId: automation.processId, + entityTypeId: automation.entityTypeId, + boardId: automation.boardId, + stageId: automation.stageId, + }), + ); + } + + private async generate(automation: AutomationEntityType): Promise { + const action = automation.actions?.[0]; + if (action) { + const content: string = readFileSync(path.join(__dirname, 'templates/simple_automation.bpmn.template'), 'utf-8'); + + const template = Handlebars.compile(content); + const result = template({ + accountId: automation.accountId, + entityTypeId: automation.entityTypeId, + processId: automation.id, + name: automation.name, + events: { + create: automation.triggers.includes(EntityTypeTrigger.Create), + changeStage: automation.triggers.includes(EntityTypeTrigger.ChangeStage), + changeOwner: automation.triggers.includes(EntityTypeTrigger.ChangeOwner), + }, + conditions: AutomatonConditionUtil.formatEntityCondition({ + stageId: automation.stageId, + ownerIds: automation.conditions?.ownerIds, + fields: automation.conditions?.fields, + }), + action: { + delay: AutomationDelayUtil.formatSeconds(action.delay), + type: action.type, + target: this.formatTarget(action.type), + settings: JSON.stringify(action.settings), + }, + }); + + return result; + } + + return null; + } + + private formatTarget(type: EntityTypeActionType): string { + switch (type) { + case EntityTypeActionType.CreateTask: + case EntityTypeActionType.TaskCreate: + return 'taskSettings'; + case EntityTypeActionType.CreateActivity: + case EntityTypeActionType.ActivityCreate: + return 'activitySettings'; + case EntityTypeActionType.SendEmail: + case EntityTypeActionType.EmailSend: + return 'emailSettings'; + case EntityTypeActionType.ChangeStage: + case EntityTypeActionType.EntityCreate: + case EntityTypeActionType.EntityStageChange: + case EntityTypeActionType.EntityLinkedStageChange: + case EntityTypeActionType.EntityResponsibleChange: + return 'entitySettings'; + case EntityTypeActionType.ChatSendAmwork: + case EntityTypeActionType.ChatSendExternal: + return 'chatSettings'; + case EntityTypeActionType.HttpCall: + return 'httpSettings'; + } + } +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-activity-create-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-activity-create-settings.dto.ts new file mode 100644 index 0000000..cc8e3f9 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-activity-create-settings.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; +import { DeadlineType } from '../../enums'; + +export class ActionActivityCreateSettings extends ActionsSettings { + @ApiPropertyOptional({ description: 'User ID responsible for the activity', nullable: true }) + @IsOptional() + @IsNumber() + responsibleUserId?: number | null; + + @ApiProperty({ description: 'ActivityType ID' }) + @IsNumber() + activityTypeId: number; + + @ApiProperty({ description: 'Text of the activity' }) + @IsString() + text: string; + + @ApiProperty({ description: 'Deadline type', enum: DeadlineType }) + @IsEnum(DeadlineType) + deadlineType: DeadlineType; + + @ApiPropertyOptional({ description: 'Deadline time in seconds', nullable: true }) + @IsOptional() + @IsNumber() + deadlineTime?: number | null; + + @ApiPropertyOptional({ description: 'Defer start time in seconds', nullable: true }) + @IsOptional() + @IsNumber() + deferStart?: number | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-chat-send-amwork-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-chat-send-amwork-settings.dto.ts new file mode 100644 index 0000000..1bacb50 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-chat-send-amwork-settings.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; + +export class ActionChatSendAmworkSettings extends ActionsSettings { + @ApiProperty({ description: 'Chat message text' }) + @IsString() + message: string; + + @ApiProperty({ description: 'Send message from User ID', nullable: true }) + @IsOptional() + @IsNumber() + userId: number | null; + + @ApiProperty({ description: 'Send message to User ID', type: [Number], nullable: true }) + @IsOptional() + @IsNumber({}, { each: true }) + sendTo: number[] | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-chat-send-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-chat-send-settings.dto.ts new file mode 100644 index 0000000..f9744ba --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-chat-send-settings.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; +import { ActionSendOptions } from './action-send-options.dto'; + +export class ActionChatSendSettings extends ActionsSettings { + @ApiProperty({ description: 'Chat message text' }) + @IsString() + message: string; + + @ApiProperty({ description: 'Chat provider ID' }) + @IsNumber() + providerId: number; + + @ApiProperty({ description: 'Send message from User ID', nullable: true }) + @IsOptional() + @IsNumber() + userId: number | null; + + @ApiPropertyOptional({ description: 'Message send options', nullable: true, type: ActionSendOptions }) + @IsOptional() + options?: ActionSendOptions | null; + + @ApiPropertyOptional({ description: 'Phone numbers', nullable: true, type: [String] }) + @IsOptional() + @IsString({ each: true }) + phoneNumbers?: string[] | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-email-send-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-email-send-settings.dto.ts new file mode 100644 index 0000000..cfa0f21 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-email-send-settings.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; +import { ActionSendOptions } from './action-send-options.dto'; + +export class ActionEmailSendSettings extends ActionsSettings { + @ApiProperty({ description: 'Subject of the email' }) + @IsString() + subject: string; + + @ApiProperty({ description: 'Content of the email' }) + @IsString() + content: string; + + @ApiPropertyOptional({ description: 'Signature of the email', nullable: true }) + @IsOptional() + @IsString() + signature?: string | null; + + @ApiProperty({ description: 'Is the email content HTML?' }) + @IsBoolean() + sendAsHtml: boolean; + + @ApiProperty({ description: 'Mailbox ID' }) + @IsNumber() + mailboxId: number; + + @ApiProperty({ description: 'Send email from User ID' }) + @IsNumber() + userId: number; + + @ApiPropertyOptional({ description: 'Email send options', nullable: true, type: ActionSendOptions }) + @IsOptional() + options?: ActionSendOptions | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-create-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-create-settings.dto.ts new file mode 100644 index 0000000..2f7cdfb --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-create-settings.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; + +export class ActionEntityCreateSettings extends ActionsSettings { + @ApiProperty({ description: 'EntityType ID' }) + @IsNumber() + entityTypeId: number; + + @ApiPropertyOptional({ description: 'Board ID', nullable: true }) + @IsOptional() + @IsNumber() + boardId?: number | null; + + @ApiPropertyOptional({ description: 'Stage ID', nullable: true }) + @IsOptional() + @IsNumber() + stageId?: number | null; + + @ApiPropertyOptional({ description: 'User ID responsible for the entity', nullable: true }) + @IsOptional() + @IsNumber() + ownerId?: number | null; + + @ApiPropertyOptional({ description: 'Name of the entity', nullable: true }) + @IsOptional() + @IsString() + name?: string | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-linked-stage-change-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-linked-stage-change-settings.dto.ts new file mode 100644 index 0000000..008cbaf --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-linked-stage-change-settings.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; +import { ChangeStageType } from '../../enums'; + +export class ActionEntityLinkedStageChangeSettings extends ActionsSettings { + @ApiProperty({ description: 'Linked entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiProperty({ description: 'Stage ID' }) + @IsNumber() + stageId: number; + + @ApiProperty({ description: 'Type of the operation', enum: ChangeStageType }) + @IsEnum(ChangeStageType) + operationType: ChangeStageType; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-responsible-change-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-responsible-change-settings.dto.ts new file mode 100644 index 0000000..e7e84f7 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-responsible-change-settings.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; + +export class ActionEntityResponsibleChangeSettings extends ActionsSettings { + @ApiProperty({ description: 'Responsible user ID, current responsible user ID will be changed to this' }) + @IsNumber() + responsibleUserId: number; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-stage-change-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-stage-change-settings.dto.ts new file mode 100644 index 0000000..4da5fc0 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-entity-stage-change-settings.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; +import { ChangeStageType } from '../../enums'; + +export class ActionEntityStageChangeSettings extends ActionsSettings { + @ApiProperty({ description: 'Stage ID' }) + @IsNumber() + stageId: number; + + @ApiProperty({ description: 'Type of the operation', enum: ChangeStageType }) + @IsEnum(ChangeStageType) + operationType: ChangeStageType; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options-entity.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options-entity.dto.ts new file mode 100644 index 0000000..1366e99 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options-entity.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +import { ActionSendOptionsValue } from './action-send-options-value.dto'; + +export class ActionSendOptionsEntity extends ActionSendOptionsValue { + @ApiProperty({ description: 'Use only first entity' }) + @IsOptional() + @IsBoolean() + onlyFirstEntity?: boolean | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options-value.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options-value.dto.ts new file mode 100644 index 0000000..e768e97 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options-value.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class ActionSendOptionsValue { + @ApiProperty({ description: 'Use only first value' }) + @IsOptional() + @IsBoolean() + onlyFirstValue?: boolean | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options.dto.ts new file mode 100644 index 0000000..d298a91 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-send-options.dto.ts @@ -0,0 +1,27 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +import { ActionSendOptionsValue } from './action-send-options-value.dto'; +import { ActionSendOptionsEntity } from './action-send-options-entity.dto'; + +export class ActionSendOptions { + @ApiPropertyOptional({ description: 'Main entity options', nullable: true, type: ActionSendOptionsValue }) + @IsOptional() + main?: ActionSendOptionsValue | null; + + @ApiPropertyOptional({ + description: 'Contacts of entity options', + nullable: true, + type: ActionSendOptionsEntity, + }) + @IsOptional() + contact?: ActionSendOptionsEntity | null; + + @ApiPropertyOptional({ + description: 'Companies of entity options', + nullable: true, + type: ActionSendOptionsEntity, + }) + @IsOptional() + company?: ActionSendOptionsEntity | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/action-task-create-settings.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/action-task-create-settings.dto.ts new file mode 100644 index 0000000..8272ee3 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/action-task-create-settings.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ActionsSettings } from '../../../common'; +import { DeadlineType } from '../../enums'; + +export class ActionTaskCreateSettings extends ActionsSettings { + @ApiPropertyOptional({ description: 'User ID responsible for the task', nullable: true }) + @IsOptional() + @IsNumber() + responsibleUserId?: number | null; + + @ApiProperty({ description: 'Title of the task' }) + @IsString() + title: string; + + @ApiProperty({ description: 'Text of the task' }) + @IsString() + text: string; + + @ApiProperty({ description: 'Deadline type', enum: DeadlineType }) + @IsEnum(DeadlineType) + deadlineType: DeadlineType; + + @ApiPropertyOptional({ description: 'Deadline time in seconds', nullable: true }) + @IsOptional() + @IsNumber() + deadlineTime?: number | null; + + @ApiPropertyOptional({ description: 'Defer start time in seconds', nullable: true }) + @IsOptional() + @IsNumber() + deferStart?: number | null; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/actions/index.ts b/backend/src/modules/automation/automation-entity-type/dto/actions/index.ts new file mode 100644 index 0000000..6be57cf --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/actions/index.ts @@ -0,0 +1,12 @@ +export * from './action-activity-create-settings.dto'; +export * from './action-chat-send-amwork-settings.dto'; +export * from './action-chat-send-settings.dto'; +export * from './action-email-send-settings.dto'; +export * from './action-entity-create-settings.dto'; +export * from './action-entity-linked-stage-change-settings.dto'; +export * from './action-entity-responsible-change-settings.dto'; +export * from './action-entity-stage-change-settings.dto'; +export * from './action-send-options-entity.dto'; +export * from './action-send-options-value.dto'; +export * from './action-send-options.dto'; +export * from './action-task-create-settings.dto'; diff --git a/backend/src/modules/automation/automation-entity-type/dto/automation-entity-type-filter.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/automation-entity-type-filter.dto.ts new file mode 100644 index 0000000..68330b5 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/automation-entity-type-filter.dto.ts @@ -0,0 +1,24 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional } from 'class-validator'; + +export class AutomationEntityTypeFilterDto { + @ApiPropertyOptional({ description: 'EntityType ID' }) + @IsOptional() + @IsNumber() + entityTypeId?: number; + + @ApiPropertyOptional({ description: 'Board ID' }) + @IsOptional() + @IsNumber() + boardId?: number; + + @ApiPropertyOptional({ description: 'Stage ID' }) + @IsOptional() + @IsNumber() + stageId?: number; + + @ApiPropertyOptional({ description: 'Is the automation active?' }) + @IsOptional() + @IsBoolean() + isActive?: boolean; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/automation-entity-type.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/automation-entity-type.dto.ts new file mode 100644 index 0000000..80810d4 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/automation-entity-type.dto.ts @@ -0,0 +1,84 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { EntityTypeTrigger } from '../enums'; +import { EntityTypeCondition } from './entity-type-condition.dto'; +import { EntityTypeAction } from './entity-type-action.dto'; + +export class AutomationEntityTypeDto { + @ApiProperty({ description: 'EntityType automation ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Date of creation' }) + @IsString() + createdAt: string; + + @ApiProperty({ description: 'User ID who created the automation' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ description: 'Name of the automation' }) + @IsString() + name: string; + + @ApiProperty({ description: 'EntityType ID', nullable: true }) + @IsNumber() + entityTypeId: number | null; + + @ApiPropertyOptional({ description: 'Board ID', nullable: true }) + @IsOptional() + @IsNumber() + boardId?: number | null; + + @ApiPropertyOptional({ description: 'Stage ID', nullable: true }) + @IsOptional() + @IsNumber() + stageId?: number | null; + + @ApiProperty({ description: 'Is the automation active?' }) + @IsBoolean() + isActive: boolean; + + @ApiProperty({ description: 'Triggers for the automation', enum: EntityTypeTrigger, isArray: true }) + @IsArray() + triggers: EntityTypeTrigger[]; + + @ApiPropertyOptional({ description: 'Conditions for the automation', type: EntityTypeCondition, nullable: true }) + @IsOptional() + @IsObject() + conditions?: EntityTypeCondition | null; + + @ApiPropertyOptional({ description: 'Actions for the automation', type: [EntityTypeAction], nullable: true }) + @IsOptional() + @IsArray() + @Type(() => EntityTypeAction) + actions?: EntityTypeAction[] | null; + + constructor({ + id, + createdAt, + createdBy, + name, + entityTypeId, + boardId, + stageId, + isActive, + triggers, + conditions, + actions, + }: AutomationEntityTypeDto) { + this.id = id; + this.createdAt = createdAt; + this.createdBy = createdBy; + this.name = name; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.stageId = stageId; + this.isActive = isActive; + this.triggers = triggers; + this.conditions = conditions; + this.actions = actions; + } +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/create-automation-entity-type.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/create-automation-entity-type.dto.ts new file mode 100644 index 0000000..10cfb2a --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/create-automation-entity-type.dto.ts @@ -0,0 +1,20 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +import { AutomationEntityTypeDto } from './automation-entity-type.dto'; + +export class CreateAutomationEntityTypeDto extends PickType(AutomationEntityTypeDto, [ + 'name', + 'entityTypeId', + 'boardId', + 'stageId', + 'isActive', + 'triggers', + 'conditions', + 'actions', +] as const) { + @ApiPropertyOptional({ description: 'Apply automation for all entities suitable for conditions' }) + @IsOptional() + @IsBoolean() + applyImmediately?: boolean; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/entity-type-action.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/entity-type-action.dto.ts new file mode 100644 index 0000000..69b63fd --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/entity-type-action.dto.ts @@ -0,0 +1,65 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsObject, IsOptional } from 'class-validator'; + +import { ActionHttpCallSettings } from '../../automation-http/dto'; + +import { EntityTypeActionType } from '../enums'; +import { + ActionActivityCreateSettings, + ActionChatSendAmworkSettings, + ActionChatSendSettings, + ActionEmailSendSettings, + ActionEntityCreateSettings, + ActionEntityResponsibleChangeSettings, + ActionEntityStageChangeSettings, + ActionTaskCreateSettings, +} from './actions'; + +@ApiExtraModels(ActionActivityCreateSettings) +@ApiExtraModels(ActionChatSendAmworkSettings) +@ApiExtraModels(ActionChatSendSettings) +@ApiExtraModels(ActionEmailSendSettings) +@ApiExtraModels(ActionEntityCreateSettings) +@ApiExtraModels(ActionEntityResponsibleChangeSettings) +@ApiExtraModels(ActionEntityStageChangeSettings) +@ApiExtraModels(ActionHttpCallSettings) +@ApiExtraModels(ActionTaskCreateSettings) +export class EntityTypeAction { + @ApiPropertyOptional({ description: 'Delay in seconds before executing the action', nullable: true }) + @IsOptional() + @IsNumber() + delay?: number | null; + + @ApiProperty({ description: 'Type of the action', enum: EntityTypeActionType }) + @IsEnum(EntityTypeActionType) + type: EntityTypeActionType; + + @ApiProperty({ + description: 'Settings for the action', + type: 'array', + items: { + oneOf: [ + { $ref: getSchemaPath(ActionActivityCreateSettings) }, + { $ref: getSchemaPath(ActionChatSendAmworkSettings) }, + { $ref: getSchemaPath(ActionChatSendSettings) }, + { $ref: getSchemaPath(ActionEmailSendSettings) }, + { $ref: getSchemaPath(ActionEntityCreateSettings) }, + { $ref: getSchemaPath(ActionEntityResponsibleChangeSettings) }, + { $ref: getSchemaPath(ActionEntityStageChangeSettings) }, + { $ref: getSchemaPath(ActionHttpCallSettings) }, + { $ref: getSchemaPath(ActionTaskCreateSettings) }, + ], + }, + }) + @IsObject() + settings: + | ActionActivityCreateSettings + | ActionChatSendAmworkSettings + | ActionChatSendSettings + | ActionEmailSendSettings + | ActionEntityCreateSettings + | ActionEntityResponsibleChangeSettings + | ActionEntityStageChangeSettings + | ActionHttpCallSettings + | ActionTaskCreateSettings; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/entity-type-condition.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/entity-type-condition.dto.ts new file mode 100644 index 0000000..322a804 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/entity-type-condition.dto.ts @@ -0,0 +1,19 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { AutomationFieldCondition } from '../../common'; + +export class EntityTypeCondition { + @ApiPropertyOptional({ description: 'List of owner user ids', type: [Number] }) + @IsOptional() + @IsArray() + @IsNumber({}, { each: true }) + ownerIds?: number[]; + + @ApiPropertyOptional({ description: 'List of field conditions', type: [AutomationFieldCondition] }) + @IsOptional() + @IsArray() + @Type(() => AutomationFieldCondition) + fields?: AutomationFieldCondition[]; +} diff --git a/backend/src/modules/automation/automation-entity-type/dto/index.ts b/backend/src/modules/automation/automation-entity-type/dto/index.ts new file mode 100644 index 0000000..6929d98 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/index.ts @@ -0,0 +1,7 @@ +export * from './actions'; +export * from './automation-entity-type-filter.dto'; +export * from './automation-entity-type.dto'; +export * from './create-automation-entity-type.dto'; +export * from './entity-type-action.dto'; +export * from './entity-type-condition.dto'; +export * from './update-automation-entity-type.dto'; diff --git a/backend/src/modules/automation/automation-entity-type/dto/update-automation-entity-type.dto.ts b/backend/src/modules/automation/automation-entity-type/dto/update-automation-entity-type.dto.ts new file mode 100644 index 0000000..7bd3a4d --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/dto/update-automation-entity-type.dto.ts @@ -0,0 +1,22 @@ +import { ApiPropertyOptional, PartialType, PickType } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +import { AutomationEntityTypeDto } from './automation-entity-type.dto'; + +export class UpdateAutomationEntityTypeDto extends PartialType( + PickType(AutomationEntityTypeDto, [ + 'name', + 'entityTypeId', + 'boardId', + 'stageId', + 'isActive', + 'triggers', + 'conditions', + 'actions', + ] as const), +) { + @ApiPropertyOptional({ description: 'Apply automation for all entities suitable for conditions' }) + @IsOptional() + @IsBoolean() + applyImmediately?: boolean; +} diff --git a/backend/src/modules/automation/automation-entity-type/entities/automation-entity-type.entity.ts b/backend/src/modules/automation/automation-entity-type/entities/automation-entity-type.entity.ts new file mode 100644 index 0000000..805dc06 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/entities/automation-entity-type.entity.ts @@ -0,0 +1,120 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { type EntityTypeTrigger } from '../enums'; +import { + CreateAutomationEntityTypeDto, + UpdateAutomationEntityTypeDto, + AutomationEntityTypeDto, + EntityTypeCondition, + EntityTypeAction, +} from '../dto'; + +@Entity() +export class AutomationEntityType { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column({ type: Date }) + createdAt: Date; + + @Column() + createdBy: number; + + @Column() + name: string; + + @Column({ nullable: true }) + entityTypeId: number | null; + + @Column({ nullable: true }) + boardId: number | null; + + @Column({ nullable: true }) + stageId: number | null; + + @Column({ nullable: true }) + processId: number | null; + + @Column({ type: 'simple-array' }) + triggers: EntityTypeTrigger[]; + + @Column({ type: 'jsonb', nullable: true }) + conditions: EntityTypeCondition | null; + + @Column({ type: 'jsonb', nullable: true }) + actions: EntityTypeAction[] | null; + + constructor( + accountId: number, + createdBy: number, + name: string, + entityTypeId: number | null, + boardId: number | null, + stageId: number | null, + processId: number | null, + triggers: EntityTypeTrigger[], + conditions: EntityTypeCondition | null, + actions: EntityTypeAction[] | null, + ) { + this.accountId = accountId; + this.createdBy = createdBy; + this.name = name; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.stageId = stageId; + this.processId = processId; + this.triggers = triggers; + this.conditions = conditions; + this.actions = actions; + this.createdAt = DateUtil.now(); + } + + public get isActive(): boolean { + return !!this.processId; + } + + public static fromDto( + accountId: number, + createdBy: number, + dto: CreateAutomationEntityTypeDto, + ): AutomationEntityType { + return new AutomationEntityType( + accountId, + createdBy, + dto.name, + dto.entityTypeId, + dto.boardId, + dto.stageId, + null, + dto.triggers, + dto.conditions, + dto.actions, + ); + } + + public update(dto: UpdateAutomationEntityTypeDto & { processId?: number | null }): AutomationEntityType { + this.name = dto.name !== undefined ? dto.name : this.name; + this.entityTypeId = dto.entityTypeId !== undefined ? dto.entityTypeId : this.entityTypeId; + this.boardId = dto.boardId !== undefined ? dto.boardId : this.boardId; + this.stageId = dto.stageId !== undefined ? dto.stageId : this.stageId; + this.processId = dto.processId !== undefined ? dto.processId : this.processId; + this.triggers = dto.triggers !== undefined ? dto.triggers : this.triggers; + this.conditions = dto.conditions !== undefined ? dto.conditions : this.conditions; + this.actions = dto.actions !== undefined ? dto.actions : this.actions; + + return this; + } + + public toDto(): AutomationEntityTypeDto { + return new AutomationEntityTypeDto({ + ...this, + createdAt: this.createdAt.toISOString(), + isActive: this.isActive, + }); + } +} diff --git a/backend/src/modules/automation/automation-entity-type/entities/index.ts b/backend/src/modules/automation/automation-entity-type/entities/index.ts new file mode 100644 index 0000000..5b0b871 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/entities/index.ts @@ -0,0 +1 @@ +export * from './automation-entity-type.entity'; diff --git a/backend/src/modules/automation/automation-entity-type/enums/change-stage-type.enum.ts b/backend/src/modules/automation/automation-entity-type/enums/change-stage-type.enum.ts new file mode 100644 index 0000000..bc40626 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/enums/change-stage-type.enum.ts @@ -0,0 +1,5 @@ +export enum ChangeStageType { + Move = 'move', + CopyOriginal = 'copy_original', + CopyNew = 'copy_new', +} diff --git a/backend/src/modules/automation/automation-entity-type/enums/deadline-type.enum.ts b/backend/src/modules/automation/automation-entity-type/enums/deadline-type.enum.ts new file mode 100644 index 0000000..570909a --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/enums/deadline-type.enum.ts @@ -0,0 +1,8 @@ +export enum DeadlineType { + Immediately = 'immediately', + EndOfTheDay = 'end_of_the_day', + InOneDay = 'in_one_day', + InThreeDays = 'in_three_days', + InAWeek = 'in_a_week', + Custom = 'custom', +} diff --git a/backend/src/modules/automation/automation-entity-type/enums/entity-type-action-type.enum.ts b/backend/src/modules/automation/automation-entity-type/enums/entity-type-action-type.enum.ts new file mode 100644 index 0000000..b30e48e --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/enums/entity-type-action-type.enum.ts @@ -0,0 +1,33 @@ +export enum EntityTypeActionType { + /** + * @deprecated use new @see TaskCreate instead + */ + CreateTask = 'create_task', + /** + * @deprecated use new @see ActivityCreate instead + */ + CreateActivity = 'create_activity', + /** + * @deprecated use new @see EntityStageChange instead + */ + ChangeStage = 'change_stage', + /** + * @deprecated use new @see EmailSend instead + */ + SendEmail = 'send_email', + + EntityCreate = 'entity_create', + EntityStageChange = 'entity_stage_change', + EntityResponsibleChange = 'entity_responsible_change', + EntityLinkedStageChange = 'entity_linked_stage_change', + + ActivityCreate = 'activity_create', + TaskCreate = 'task_create', + + EmailSend = 'email_send', + + ChatSendExternal = 'chat_send_external', + ChatSendAmwork = 'chat_send_amwork', + + HttpCall = 'http_call', +} diff --git a/backend/src/modules/automation/automation-entity-type/enums/entity-type-trigger.enum.ts b/backend/src/modules/automation/automation-entity-type/enums/entity-type-trigger.enum.ts new file mode 100644 index 0000000..7bb6f49 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/enums/entity-type-trigger.enum.ts @@ -0,0 +1,5 @@ +export enum EntityTypeTrigger { + Create = 'create', + ChangeStage = 'change_stage', + ChangeOwner = 'change_responsible', +} diff --git a/backend/src/modules/automation/automation-entity-type/enums/index.ts b/backend/src/modules/automation/automation-entity-type/enums/index.ts new file mode 100644 index 0000000..e03ce87 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/enums/index.ts @@ -0,0 +1,4 @@ +export * from './change-stage-type.enum'; +export * from './deadline-type.enum'; +export * from './entity-type-action-type.enum'; +export * from './entity-type-trigger.enum'; diff --git a/backend/src/modules/automation/automation-entity-type/helpers/action.helper.ts b/backend/src/modules/automation/automation-entity-type/helpers/action.helper.ts new file mode 100644 index 0000000..b7f77da --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/helpers/action.helper.ts @@ -0,0 +1,30 @@ +import { DateUtil } from '@/common'; +import { DeadlineType } from '../enums'; + +export class ActionHelper { + public static getEndDate({ + startDate, + deadlineType, + deadlineTime, + }: { + startDate?: Date; + deadlineType: DeadlineType; + deadlineTime: number | null; + }): Date { + const from = startDate ?? DateUtil.now(); + switch (deadlineType) { + case DeadlineType.Immediately: + return from; + case DeadlineType.EndOfTheDay: + return DateUtil.endOf(from, 'day'); + case DeadlineType.InOneDay: + return DateUtil.add(from, { days: 1 }); + case DeadlineType.InThreeDays: + return DateUtil.add(from, { days: 3 }); + case DeadlineType.InAWeek: + return DateUtil.endOf(from, 'week'); + case DeadlineType.Custom: + return DateUtil.add(from, { seconds: deadlineTime }); + } + } +} diff --git a/backend/src/modules/automation/automation-entity-type/helpers/index.ts b/backend/src/modules/automation/automation-entity-type/helpers/index.ts new file mode 100644 index 0000000..9431d6a --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/helpers/index.ts @@ -0,0 +1 @@ +export * from './action.helper'; diff --git a/backend/src/modules/automation/automation-entity-type/index.ts b/backend/src/modules/automation/automation-entity-type/index.ts new file mode 100644 index 0000000..b79b989 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/index.ts @@ -0,0 +1,7 @@ +export * from './automation-entity-type.controller'; +export * from './automation-entity-type.handler'; +export * from './automation-entity-type.service'; +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './helpers'; diff --git a/backend/src/modules/automation/automation-entity-type/templates/simple_automation.bpmn.template b/backend/src/modules/automation/automation-entity-type/templates/simple_automation.bpmn.template new file mode 100644 index 0000000..e9e6ff6 --- /dev/null +++ b/backend/src/modules/automation/automation-entity-type/templates/simple_automation.bpmn.template @@ -0,0 +1,181 @@ + + + + + Flow_ProcessStart + + + {{#if events.create}} + + Flow_OnCreated + + + + {{/if}} + {{#if events.changeStage}} + + Flow_OnStageChanged + + + + {{/if}} + {{#if events.changeOwner}} + + Flow_OnOwnerChanged + + + + {{/if}} + + {{#if events.create}} + Flow_OnCreated + {{/if}} + {{#if events.changeStage}} + Flow_OnStageChanged + {{/if}} + {{#if events.changeOwner}} + Flow_OnOwnerChanged + {{/if}} + Flow_ConditionCorrect + Flow_ConditionNotCorrect + + + ={{{conditions}}} + + + Flow_ConditionCorrect + Flow_AfterDelay + + {{action.delay}} + + + + + + + + + + + Flow_AfterDelay + Flow_ServiceTaskCompleted + + + + + Flow_ConditionNotCorrect + Flow_ServiceTaskCompleted + + + {{#if events.create}} + + {{/if}} + {{#if events.changeStage}} + + {{/if}} + {{#if events.changeOwner}} + + {{/if}} + + + + + + + + + + + + + + {{#if events.create}} + + + + + + + + + + + {{/if}} + {{#if events.changeStage}} + + + + + + + + + + + + {{/if}} + {{#if events.changeOwner}} + + + + + + + + + + + + {{/if}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/modules/automation/automation-http/automation-http.controller.ts b/backend/src/modules/automation/automation-http/automation-http.controller.ts new file mode 100644 index 0000000..623ca1b --- /dev/null +++ b/backend/src/modules/automation/automation-http/automation-http.controller.ts @@ -0,0 +1,25 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { ProcessDataDto } from './dto'; +import { AutomationHttpService } from './automation-http.service'; + +@ApiExcludeController(true) +@Controller('http') +export class AutomationHttpController { + constructor(private readonly service: AutomationHttpService) {} + + @JwtAuthorized({ access: { adminOnly: true } }) + @Post('process') + async processAutomation(@CurrentAuth() { accountId }: AuthData, @Body() dto: ProcessDataDto) { + return this.service.processAutomation({ + accountId, + entityId: dto.entityId, + entityStageId: dto.entityStageId, + data: dto.data, + settings: dto.settings, + }); + } +} diff --git a/backend/src/modules/automation/automation-http/automation-http.handler.ts b/backend/src/modules/automation/automation-http/automation-http.handler.ts new file mode 100644 index 0000000..b8d1c76 --- /dev/null +++ b/backend/src/modules/automation/automation-http/automation-http.handler.ts @@ -0,0 +1,46 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { AutomationJob, OnAutomationJob } from '../common'; + +import { ActionHttpCallSettings } from './dto'; +import { HttpActionType } from './enums'; +import { AutomationHttpService } from './automation-http.service'; + +interface EntityVariables { + id?: number | null; + stageId?: number | null; +} +interface HttpCallVariables { + accountId: number; + entity?: EntityVariables | null; + httpSettings?: ActionHttpCallSettings | null; +} + +@Injectable() +export class AutomationHttpHandler { + private readonly logger = new Logger(AutomationHttpHandler.name); + constructor(private readonly service: AutomationHttpService) {} + + @OnAutomationJob(HttpActionType.HttpCall) + async handleHttpCallJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.httpSettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + const { accountId, entity, httpSettings } = job.variables; + try { + const result = await this.service.processAutomation({ + accountId, + entityId: entity.id, + entityStageId: entity.stageId, + data: { ...job.variables, httpSettings: undefined }, + settings: httpSettings, + }); + return { variables: { ...job.variables, ...(result ?? {}) } }; + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + return { variables: job.variables }; + } + } +} diff --git a/backend/src/modules/automation/automation-http/automation-http.service.ts b/backend/src/modules/automation/automation-http/automation-http.service.ts new file mode 100644 index 0000000..a530f76 --- /dev/null +++ b/backend/src/modules/automation/automation-http/automation-http.service.ts @@ -0,0 +1,93 @@ +import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { lastValueFrom } from 'rxjs'; +import Handlebars from 'handlebars'; + +import { withTimeout } from '@/common'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; +import { DocumentGenerationService } from '@/modules/documents/document-generation/document-generation.service'; + +import { ActionHttpCallSettings } from './dto'; + +const HttpCallTimeout = 5000; + +@Injectable() +export class AutomationHttpService { + private readonly logger = new Logger(AutomationHttpService.name); + constructor( + private readonly httpService: HttpService, + private readonly entityInfoService: EntityInfoService, + @Inject(forwardRef(() => DocumentGenerationService)) + private readonly documentGenerationService: DocumentGenerationService, + ) {} + + async processAutomation({ + accountId, + entityId, + entityStageId, + data, + settings, + }: { + accountId: number; + entityId: number; + entityStageId: number | null | undefined; + data?: unknown | null; + settings: ActionHttpCallSettings; + }): Promise { + const entity = await this.entityInfoService.findOne({ accountId, entityId }); + if (entity && (!entity.stageId || settings.allowAnyStage || entity.stageId === entityStageId)) { + const entityData = await this.documentGenerationService.getDataForGeneration({ accountId, entityId: entity.id }); + const url = Handlebars.compile(settings.url)(entityData); + const headers = this.applyTemplate({ params: settings.headers, data: entityData }); + const params = this.applyTemplate({ params: settings.params, data: entityData }); + try { + const response = await withTimeout( + lastValueFrom( + this.httpService.request({ + method: settings.method, + url: url, + data: data, + headers: headers, + params: params, + }), + ), + HttpCallTimeout, + ); + return response?.data ?? {}; + } catch (error) { + this.logger.error(`Call webhook error`, (error as Error)?.stack); + return {}; + } + } + + return {}; + } + + private applyTemplate({ + params, + data, + }: { + params: Record | null | undefined; + data: object; + }): Record | null { + if (!params) return null; + + const result: Record = {}; + + const templateCache = new Map(); + for (const [key, value] of Object.entries(params)) { + if (!value || !value.includes('{{')) { + result[key] = value; + } else { + let template = templateCache.get(value); + if (!template) { + template = Handlebars.compile(value); + templateCache.set(value, template); + } + result[key] = template(data); + } + } + + return result; + } +} diff --git a/backend/src/modules/automation/automation-http/dto/action-http-call-settings.dto.ts b/backend/src/modules/automation/automation-http/dto/action-http-call-settings.dto.ts new file mode 100644 index 0000000..30aa7d6 --- /dev/null +++ b/backend/src/modules/automation/automation-http/dto/action-http-call-settings.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsObject, IsOptional, IsString } from 'class-validator'; + +import { HttpMethod } from '@/common'; +import { ActionsSettings } from '../../common'; + +export class ActionHttpCallSettings extends ActionsSettings { + @ApiProperty({ description: 'External URL to call' }) + @IsString() + url: string; + + @ApiProperty({ enum: HttpMethod, description: 'HTTP call method' }) + @IsEnum(HttpMethod) + method: HttpMethod; + + @ApiPropertyOptional({ type: Object, nullable: true, description: 'Request headers' }) + @IsOptional() + @IsObject() + headers?: Record | null; + + @ApiPropertyOptional({ type: Object, nullable: true, description: 'Request query params' }) + @IsOptional() + @IsObject() + params?: Record | null; +} diff --git a/backend/src/modules/automation/automation-http/dto/index.ts b/backend/src/modules/automation/automation-http/dto/index.ts new file mode 100644 index 0000000..df3d0d1 --- /dev/null +++ b/backend/src/modules/automation/automation-http/dto/index.ts @@ -0,0 +1,2 @@ +export * from './action-http-call-settings.dto'; +export * from './process-data.dto'; diff --git a/backend/src/modules/automation/automation-http/dto/process-data.dto.ts b/backend/src/modules/automation/automation-http/dto/process-data.dto.ts new file mode 100644 index 0000000..453e9c2 --- /dev/null +++ b/backend/src/modules/automation/automation-http/dto/process-data.dto.ts @@ -0,0 +1,8 @@ +import { ActionHttpCallSettings } from './action-http-call-settings.dto'; + +export class ProcessDataDto { + entityId: number; + entityStageId?: number; + data?: unknown; + settings: ActionHttpCallSettings; +} diff --git a/backend/src/modules/automation/automation-http/enums/http-action-type.enum.ts b/backend/src/modules/automation/automation-http/enums/http-action-type.enum.ts new file mode 100644 index 0000000..d06b3b7 --- /dev/null +++ b/backend/src/modules/automation/automation-http/enums/http-action-type.enum.ts @@ -0,0 +1,3 @@ +export enum HttpActionType { + HttpCall = 'http_call', +} diff --git a/backend/src/modules/automation/automation-http/enums/index.ts b/backend/src/modules/automation/automation-http/enums/index.ts new file mode 100644 index 0000000..8d0e020 --- /dev/null +++ b/backend/src/modules/automation/automation-http/enums/index.ts @@ -0,0 +1 @@ +export * from './http-action-type.enum'; diff --git a/backend/src/modules/automation/automation-http/index.ts b/backend/src/modules/automation/automation-http/index.ts new file mode 100644 index 0000000..14daaf8 --- /dev/null +++ b/backend/src/modules/automation/automation-http/index.ts @@ -0,0 +1,5 @@ +export * from './automation-http.controller'; +export * from './automation-http.handler'; +export * from './automation-http.service'; +export * from './dto'; +export * from './enums'; diff --git a/backend/src/modules/automation/automation-process/automation-process.controller.ts b/backend/src/modules/automation/automation-process/automation-process.controller.ts new file mode 100644 index 0000000..f66f4eb --- /dev/null +++ b/backend/src/modules/automation/automation-process/automation-process.controller.ts @@ -0,0 +1,91 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { + ApiBody, + ApiCreatedResponse, + ApiExcludeEndpoint, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiTags, +} from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { + AutomationProcessDto, + AutomationProcessFilterDto, + CreateAutomationProcessDto, + UpdateAutomationProcessDto, +} from './dto'; +import { AutomationProcessService } from './automation-process.service'; + +@ApiTags('automation/processes') +@Controller('processes') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class AutomationProcessController { + constructor(private readonly service: AutomationProcessService) {} + + @ApiOperation({ summary: 'Create automation process', description: 'Create automation process' }) + @ApiBody({ description: 'Data for creating automation process', type: CreateAutomationProcessDto }) + @ApiCreatedResponse({ description: 'Automation process', type: AutomationProcessDto }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateAutomationProcessDto) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Get automation processes', description: 'Get automation processes' }) + @ApiOkResponse({ description: 'Automation processes', type: [AutomationProcessDto] }) + @Get() + async findMany(@CurrentAuth() { accountId }: AuthData, @Query() filter: AutomationProcessFilterDto) { + return this.service.findMany({ + accountId, + ...filter, + isReadonly: filter.isReadonly ? filter.isReadonly === 'true' : undefined, + }); + } + + @ApiOperation({ summary: 'Get automation process', description: 'Get automation process' }) + @ApiParam({ name: 'processId', description: 'Automation process ID' }) + @ApiOkResponse({ description: 'Automation process', type: AutomationProcessDto }) + @Get(':processId') + async findOne(@CurrentAuth() { accountId }: AuthData, @Param('processId', ParseIntPipe) processId: number) { + return this.service.findOne({ accountId, processId }); + } + + @ApiOperation({ summary: 'Update automation process', description: 'Update automation process' }) + @ApiParam({ name: 'processId', description: 'Automation process ID' }) + @ApiBody({ description: 'Data for updating automation process', type: UpdateAutomationProcessDto }) + @ApiOkResponse({ description: 'Automation process', type: AutomationProcessDto }) + @Patch(':processId') + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('processId', ParseIntPipe) processId: number, + @Body() dto: UpdateAutomationProcessDto, + ) { + return this.service.update({ accountId, processId, dto }); + } + + @ApiOperation({ summary: 'Delete automation process', description: 'Delete automation process' }) + @ApiParam({ name: 'processId', description: 'Automation process ID' }) + @ApiOkResponse({ description: 'Deleted automation process ID', type: Number }) + @Delete(':processId') + async delete(@CurrentAuth() { accountId }: AuthData, @Param('processId', ParseIntPipe) processId: number) { + return this.service.delete({ accountId, processId }); + } + + @ApiExcludeEndpoint(true) + @Post('clean-unused') + async cleanUnused() { + return this.service.cleanUnused(); + } + + @ApiExcludeEndpoint(true) + @Post('clean-unlinked') + async cleanUnlinked() { + return this.service.cleanUnlinked(); + } +} diff --git a/backend/src/modules/automation/automation-process/automation-process.handler.ts b/backend/src/modules/automation/automation-process/automation-process.handler.ts new file mode 100644 index 0000000..d3da5c3 --- /dev/null +++ b/backend/src/modules/automation/automation-process/automation-process.handler.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { JSONDoc } from '@camunda8/sdk/dist/zeebe/types'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; + +import { AutomationEventType, ProcessStartEvent, SendMessageEvent, SendSignalEvent } from '../common'; +import { AutomationProcessService } from './automation-process.service'; + +@Injectable() +export class AutomationProcessHandler { + constructor(private readonly service: AutomationProcessService) {} + + @OnEvent(AutomationEventType.SendSignal, { async: true }) + public async handleSendSignal(event: SendSignalEvent) { + await this.service.sendSignal({ accountId: event.accountId, signal: event.signal }); + } + + @OnEvent(AutomationEventType.SendMessage, { async: true }) + public async handleSendMessage(event: SendMessageEvent) { + await this.service.sendMessage({ accountId: event.accountId, message: event.message }); + } + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + if (event.newUserId) { + await this.service.changeOwner({ + accountId: event.accountId, + currentUserId: event.userId, + newUserId: event.newUserId, + }); + } else { + await this.service.deleteMany({ accountId: event.accountId, createdBy: event.userId }); + } + } + + @OnEvent(AutomationEventType.ProcessStart, { async: true }) + public async handleProcessStart(event: ProcessStartEvent) { + await this.service.processStart({ + accountId: event.accountId, + processId: event.processId, + variables: event.variables, + }); + } +} diff --git a/backend/src/modules/automation/automation-process/automation-process.service.ts b/backend/src/modules/automation/automation-process/automation-process.service.ts new file mode 100644 index 0000000..193be21 --- /dev/null +++ b/backend/src/modules/automation/automation-process/automation-process.service.ts @@ -0,0 +1,249 @@ +import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { JSONDoc } from '@camunda8/sdk/dist/zeebe/types'; + +import { NotFoundError } from '@/common'; + +import { AutomationProcessType, Message, Signal } from '../common'; +import { AutomationCoreService } from '../automation-core'; + +import { CreateAutomationProcessDto, UpdateAutomationProcessDto } from './dto'; +import { AutomationProcess } from './entities'; +import { ReadonlyProcess } from './types'; +import { AutomationProcessError } from './errors'; + +interface FindFilter { + accountId: number; + processId?: number; + createdBy?: number; + name?: string; + type?: AutomationProcessType; + objectId?: number; + isReadonly?: boolean; +} + +@Injectable() +export class AutomationProcessService { + private readonly logger = new Logger(AutomationProcessService.name); + constructor( + @InjectRepository(AutomationProcess) + private readonly repository: Repository, + private readonly coreService: AutomationCoreService, + ) {} + + async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateAutomationProcessDto & ReadonlyProcess; + }): Promise { + const process = await this.repository.save(AutomationProcess.fromDto({ accountId, userId, dto })); + + return dto.isActive ? this.activate(process) : process; + } + + async findOne(filter: FindFilter): Promise { + return this.createQb(filter).addSelect('ap.bpmn_file', 'ap_bpmn_file').getOne(); + } + + async findMany(filter: FindFilter): Promise { + return this.createQb(filter).orderBy('ap.created_at').getMany(); + } + + async getCount(filter: FindFilter): Promise { + return this.createQb(filter).getCount(); + } + + async update({ + accountId, + processId, + dto, + }: { + accountId: number; + processId: number; + dto: UpdateAutomationProcessDto & ReadonlyProcess; + }): Promise { + const process = await this.findOne({ accountId, processId }); + if (process.isReadonly) { + throw new BadRequestException('AutomationProcess is readonly'); + } + if (!process) { + throw NotFoundError.withId(AutomationProcess, processId); + } + + const wasActive = process.isActive; + if (process.isActive) { + await this.deactivate(process); + } + this.repository.save(process.update(dto)); + + if (dto.isActive || (dto.isActive === undefined && wasActive)) { + await this.activate(process); + } + + return process; + } + + async changeOwner({ + accountId, + currentUserId, + newUserId, + }: { + accountId: number; + currentUserId: number; + newUserId: number; + }) { + await this.repository.update({ accountId, createdBy: currentUserId }, { createdBy: newUserId }); + } + + async deleteMany(filter: FindFilter) { + const processes = await this.findMany(filter); + for (const process of processes) { + await this.delete({ accountId: process.accountId, processId: process.id }); + } + } + + async delete({ accountId, processId }: { accountId: number; processId: number }): Promise { + const process = await this.findOne({ accountId, processId }); + if (!process) { + throw NotFoundError.withId(AutomationProcess, processId); + } + + if (process.isActive) { + await this.deactivate(process); + } + + await this.repository.delete(process.id); + return process.id; + } + + async cleanUnused() { + const processes = await this.coreService.listProcessDefinitions(); + for (const process of processes) { + const localProcess = await this.repository.findOneBy({ bpmnProcessId: process.bpmnProcessId }); + const versions = [...process.versions].sort((a, b) => a.version - b.version); + if (localProcess) { + versions.pop(); + } + for (const version of versions) { + const deleted = await this.coreService.deleteProcess(version.key); + this.logger.debug( + // eslint-disable-next-line max-len + `Clean bpmnProcessId: ${process.bpmnProcessId}, version: ${version.version}, key: ${version.key}. Result: ${deleted}`, + ); + } + } + } + + async cleanUnlinked(): Promise { + const processes = await this.repository + .createQueryBuilder('ap') + .leftJoin('automation_entity_type', 'aet', 'aet.process_id = ap.id') + .where('ap.is_readonly = true') + .andWhere('ap.resource_key is not null') + .andWhere('aet.id is null') + .getMany(); + + let count = 0; + for (const process of processes) { + const deleted = await this.coreService.deleteProcess(process.resourceKey); + if (deleted) { + await this.repository.delete(process.id); + count++; + } + this.logger.debug( + `Clean bpmnProcessId: ${process.bpmnProcessId}, key: ${process.resourceKey}. Result: ${deleted}`, + ); + } + + return count; + } + + async sendMessage({ accountId, message }: { accountId: number; message: Message }) { + if ((await this.getCount({ accountId })) > 0) { + this.coreService.sendMessage(message); + } + } + + async sendSignal({ accountId, signal }: { accountId: number; signal: Signal }) { + if ((await this.getCount({ accountId })) > 0) { + this.coreService.sendSignal(signal); + } + } + + async processStart({ + accountId, + processId, + variables, + }: { + accountId: number; + processId: number; + variables: V; + }) { + const process = await this.findOne({ accountId, processId }); + if (process?.bpmnProcessId) { + this.coreService.startProcess({ bpmnProcessId: process.bpmnProcessId, variables }); + } + } + + private createQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('ap') + .where('ap.account_id = :accountId', { accountId: filter.accountId }); + + if (filter.processId) { + qb.andWhere('ap.id = :id', { id: filter.processId }); + } + if (filter.createdBy) { + qb.andWhere('ap.created_by = :createdBy', { createdBy: filter.createdBy }); + } + if (filter.name) { + qb.andWhere('ap.name ilike :name', { name: `%${filter.name}%` }); + } + if (filter.type) { + qb.andWhere('ap.type = :type', { type: filter.type }); + } + if (filter.objectId) { + qb.andWhere('ap.object_id = :objectId', { objectId: filter.objectId }); + } + if (filter.isReadonly !== undefined) { + qb.andWhere('ap.is_readonly = :isReadonly', { isReadonly: filter.isReadonly }); + } + + return qb; + } + + private async activate(process: AutomationProcess): Promise { + if (process.bpmnFile === null) { + throw new AutomationProcessError({ processId: process.id }); + } + const processMeta = await this.coreService.deployProcess( + `[${process.accountId}]_${process.id}.bpmn`, + Buffer.from(process.bpmnFile), + ); + + if (processMeta) { + await this.repository.save( + process.update({ resourceKey: processMeta.processDefinitionKey, bpmnProcessId: processMeta.bpmnProcessId }), + ); + return process; + } else { + throw new AutomationProcessError({ processId: process.id }); + } + } + + private async deactivate(process: AutomationProcess): Promise { + const deleted = await this.coreService.deleteProcess(process.resourceKey); + + if (deleted) { + await this.repository.save(process.update({ resourceKey: null, bpmnProcessId: null })); + return process; + } else { + throw new AutomationProcessError({ processId: process.id }); + } + } +} diff --git a/backend/src/modules/automation/automation-process/dto/automation-process-filter.dto.ts b/backend/src/modules/automation/automation-process/dto/automation-process-filter.dto.ts new file mode 100644 index 0000000..61c8dfa --- /dev/null +++ b/backend/src/modules/automation/automation-process/dto/automation-process-filter.dto.ts @@ -0,0 +1,31 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { AutomationProcessType } from '../../common'; + +export class AutomationProcessFilterDto { + @ApiPropertyOptional({ description: 'User ID of the creator' }) + @IsOptional() + @IsNumber() + createdBy?: number; + + @ApiPropertyOptional({ description: 'Name of the process' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ enum: AutomationProcessType, description: 'Type of the process' }) + @IsOptional() + @IsEnum(AutomationProcessType) + type?: AutomationProcessType; + + @ApiPropertyOptional({ description: 'Object ID associated the process' }) + @IsOptional() + @IsNumber() + objectId?: number; + + @ApiPropertyOptional({ description: 'Flag indicating whether the process is read-only' }) + @IsOptional() + @IsString() + isReadonly?: string; +} diff --git a/backend/src/modules/automation/automation-process/dto/automation-process.dto.ts b/backend/src/modules/automation/automation-process/dto/automation-process.dto.ts new file mode 100644 index 0000000..803a176 --- /dev/null +++ b/backend/src/modules/automation/automation-process/dto/automation-process.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { AutomationProcessType } from '../../common'; + +export class AutomationProcessDto { + @ApiProperty({ description: 'Automation process ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Date of creation' }) + @IsString() + createdAt: string; + + @ApiProperty({ description: 'User ID of the creator' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ description: 'Name of the process' }) + @IsString() + name: string; + + @ApiProperty({ enum: AutomationProcessType, description: 'Type of the process' }) + @IsEnum(AutomationProcessType) + type: AutomationProcessType; + + @ApiPropertyOptional({ description: 'Object ID associated the process', nullable: true }) + @IsOptional() + @IsNumber() + objectId?: number | null; + + @ApiProperty({ description: 'Readonly status of the process' }) + @IsBoolean() + isReadonly: boolean; + + @ApiProperty({ description: 'Activity of the process' }) + @IsBoolean() + isActive: boolean; + + @ApiPropertyOptional({ description: 'Content of the process in BPMN format', nullable: true }) + @IsOptional() + @IsString() + bpmnFile?: string | null; +} diff --git a/backend/src/modules/automation/automation-process/dto/create-automation-process.dto.ts b/backend/src/modules/automation/automation-process/dto/create-automation-process.dto.ts new file mode 100644 index 0000000..f8394de --- /dev/null +++ b/backend/src/modules/automation/automation-process/dto/create-automation-process.dto.ts @@ -0,0 +1,11 @@ +import { PickType } from '@nestjs/swagger'; + +import { AutomationProcessDto } from './automation-process.dto'; + +export class CreateAutomationProcessDto extends PickType(AutomationProcessDto, [ + 'name', + 'type', + 'objectId', + 'isActive', + 'bpmnFile', +] as const) {} diff --git a/backend/src/modules/automation/automation-process/dto/index.ts b/backend/src/modules/automation/automation-process/dto/index.ts new file mode 100644 index 0000000..2bfd02f --- /dev/null +++ b/backend/src/modules/automation/automation-process/dto/index.ts @@ -0,0 +1,4 @@ +export * from './automation-process-filter.dto'; +export * from './automation-process.dto'; +export * from './create-automation-process.dto'; +export * from './update-automation-process.dto'; diff --git a/backend/src/modules/automation/automation-process/dto/update-automation-process.dto.ts b/backend/src/modules/automation/automation-process/dto/update-automation-process.dto.ts new file mode 100644 index 0000000..c836625 --- /dev/null +++ b/backend/src/modules/automation/automation-process/dto/update-automation-process.dto.ts @@ -0,0 +1,7 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { AutomationProcessDto } from './automation-process.dto'; + +export class UpdateAutomationProcessDto extends PartialType( + PickType(AutomationProcessDto, ['name', 'type', 'objectId', 'isActive', 'bpmnFile'] as const), +) {} diff --git a/backend/src/modules/automation/automation-process/entities/automation-process.entity.ts b/backend/src/modules/automation/automation-process/entities/automation-process.entity.ts new file mode 100644 index 0000000..6d230b7 --- /dev/null +++ b/backend/src/modules/automation/automation-process/entities/automation-process.entity.ts @@ -0,0 +1,122 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { AutomationProcessType } from '../../common'; +import { AutomationProcessDto, type CreateAutomationProcessDto, type UpdateAutomationProcessDto } from '../dto'; +import { ReadonlyProcess } from '../types'; + +interface ExternalIdentifiers { + resourceKey?: string | null; + bpmnProcessId?: string | null; +} + +@Entity() +export class AutomationProcess { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column({ type: Date }) + createdAt: Date; + + @Column() + createdBy: number; + + @Column() + name: string; + + @Column() + type: AutomationProcessType; + + @Column({ nullable: true }) + objectId: number | null; + + @Column({ default: false }) + isReadonly: boolean; + + @Column({ nullable: true, default: null }) + resourceKey: string | null; + + @Column({ nullable: true, default: null }) + bpmnProcessId: string | null; + + @Column({ nullable: true, select: false }) + bpmnFile: string | null; + + constructor( + accountId: number, + createdBy: number, + name: string, + type: AutomationProcessType, + objectId: number | null, + isReadonly: boolean, + resourceKey: string | null, + bpmnProcessId: string | null, + bpmnFile: string | null, + ) { + this.accountId = accountId; + this.createdAt = DateUtil.now(); + this.createdBy = createdBy; + this.name = name; + this.type = type; + this.objectId = objectId; + this.isReadonly = isReadonly; + this.resourceKey = resourceKey; + this.bpmnProcessId = bpmnProcessId; + this.bpmnFile = bpmnFile; + } + + public get isActive(): boolean { + return !!this.resourceKey && !!this.bpmnProcessId; + } + + public static fromDto({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateAutomationProcessDto & ExternalIdentifiers & ReadonlyProcess; + }): AutomationProcess { + return new AutomationProcess( + accountId, + userId, + dto.name, + dto.type, + dto.objectId, + dto.isReadonly, + dto.resourceKey, + dto.bpmnProcessId, + dto.bpmnFile, + ); + } + + public update(dto: UpdateAutomationProcessDto & ExternalIdentifiers & ReadonlyProcess): AutomationProcess { + this.name = dto.name !== undefined ? dto.name : this.name; + this.type = dto.type !== undefined ? dto.type : this.type; + this.objectId = dto.objectId !== undefined ? dto.objectId : this.objectId; + this.isReadonly = dto.isReadonly !== undefined ? dto.isReadonly : this.isReadonly; + this.resourceKey = dto.resourceKey !== undefined ? dto.resourceKey : this.resourceKey; + this.bpmnProcessId = dto.bpmnProcessId !== undefined ? dto.bpmnProcessId : this.bpmnProcessId; + this.bpmnFile = dto.bpmnFile !== undefined ? dto.bpmnFile : this.bpmnFile; + + return this; + } + + public toDto(): AutomationProcessDto { + return { + id: this.id, + createdAt: this.createdAt.toISOString(), + createdBy: this.createdBy, + name: this.name, + type: this.type, + objectId: this.objectId, + isReadonly: this.isReadonly, + isActive: this.isActive, + bpmnFile: this.bpmnFile, + }; + } +} diff --git a/backend/src/modules/automation/automation-process/entities/index.ts b/backend/src/modules/automation/automation-process/entities/index.ts new file mode 100644 index 0000000..a375386 --- /dev/null +++ b/backend/src/modules/automation/automation-process/entities/index.ts @@ -0,0 +1 @@ +export * from './automation-process.entity'; diff --git a/backend/src/modules/automation/automation-process/errors/automation-process.error.ts b/backend/src/modules/automation/automation-process/errors/automation-process.error.ts new file mode 100644 index 0000000..68670c1 --- /dev/null +++ b/backend/src/modules/automation/automation-process/errors/automation-process.error.ts @@ -0,0 +1,13 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class AutomationProcessError extends ServiceError { + constructor({ processId, message = 'Automation process error' }: { processId: number; message?: string }) { + super({ + errorCode: 'automation.process_error', + status: HttpStatus.BAD_REQUEST, + message, + details: { processId }, + }); + } +} diff --git a/backend/src/modules/automation/automation-process/errors/index.ts b/backend/src/modules/automation/automation-process/errors/index.ts new file mode 100644 index 0000000..819e59e --- /dev/null +++ b/backend/src/modules/automation/automation-process/errors/index.ts @@ -0,0 +1 @@ +export * from './automation-process.error'; diff --git a/backend/src/modules/automation/automation-process/index.ts b/backend/src/modules/automation/automation-process/index.ts new file mode 100644 index 0000000..d880498 --- /dev/null +++ b/backend/src/modules/automation/automation-process/index.ts @@ -0,0 +1,7 @@ +export * from './automation-process.controller'; +export * from './automation-process.handler'; +export * from './automation-process.service'; +export * from './dto'; +export * from './entities'; +export * from './errors'; +export * from './types'; diff --git a/backend/src/modules/automation/automation-process/types/index.ts b/backend/src/modules/automation/automation-process/types/index.ts new file mode 100644 index 0000000..d01d55d --- /dev/null +++ b/backend/src/modules/automation/automation-process/types/index.ts @@ -0,0 +1 @@ +export * from './readonly-process'; diff --git a/backend/src/modules/automation/automation-process/types/readonly-process.ts b/backend/src/modules/automation/automation-process/types/readonly-process.ts new file mode 100644 index 0000000..10b9b5d --- /dev/null +++ b/backend/src/modules/automation/automation-process/types/readonly-process.ts @@ -0,0 +1,3 @@ +export interface ReadonlyProcess { + isReadonly?: boolean; +} diff --git a/backend/src/modules/automation/automation-utils/automation-utils.controller.ts b/backend/src/modules/automation/automation-utils/automation-utils.controller.ts new file mode 100644 index 0000000..e2ce3f0 --- /dev/null +++ b/backend/src/modules/automation/automation-utils/automation-utils.controller.ts @@ -0,0 +1,35 @@ +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { + AutomationDelayUtil, + AutomationEntityCondition, + AutomationFieldCondition, + AutomatonConditionUtil, +} from '../common'; + +@ApiTags('automation/utils') +@Controller('util') +export class AutomationUtilController { + @ApiOperation({ summary: 'Generate FEEL conditions for entity', description: 'Generate FEEL conditions for entity' }) + @ApiOkResponse({ description: 'Conditions in FEEL', type: String }) + @Post('conditions/entity') + public async formatEntityConditions(@Body() condition: AutomationEntityCondition) { + return AutomatonConditionUtil.formatEntityCondition(condition); + } + + @ApiOperation({ summary: 'Generate FEEL conditions for fields', description: 'Generate FEEL conditions for fields' }) + @ApiOkResponse({ description: 'Conditions in FEEL', type: String }) + @Post('conditions/fields') + public async formatFieldCondition(@Body() condition: AutomationFieldCondition) { + return AutomatonConditionUtil.formatFieldCondition(condition); + } + + @ApiOperation({ summary: 'Generate delay', description: 'Generate delay for seconds in ISO 8601:Duration' }) + @ApiQuery({ name: 'seconds', type: Number, required: false, description: 'Delay in seconds' }) + @ApiOkResponse({ description: 'Delay in FEEL', type: String }) + @Get('delay') + public async formatDelay(@Query('seconds') seconds: string | undefined) { + return AutomationDelayUtil.formatSeconds(seconds ? Number(seconds) : null); + } +} diff --git a/backend/src/modules/automation/automation-utils/index.ts b/backend/src/modules/automation/automation-utils/index.ts new file mode 100644 index 0000000..29aae99 --- /dev/null +++ b/backend/src/modules/automation/automation-utils/index.ts @@ -0,0 +1 @@ +export * from './automation-utils.controller'; diff --git a/backend/src/modules/automation/automation.module.ts b/backend/src/modules/automation/automation.module.ts new file mode 100644 index 0000000..cea6f0e --- /dev/null +++ b/backend/src/modules/automation/automation.module.ts @@ -0,0 +1,54 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { DiscoveryModule } from '@nestjs/core'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { DocumentsModule } from '@/modules/documents/documents.module'; + +import automationConfig from './config/automation.config'; +import { AutomationCoreController, AutomationCoreService } from './automation-core'; +import { + AutomationProcess, + AutomationProcessController, + AutomationProcessHandler, + AutomationProcessService, +} from './automation-process'; +import { + AutomationEntityType, + AutomationEntityTypeController, + AutomationEntityTypeHandler, + AutomationEntityTypeService, +} from './automation-entity-type'; +import { AutomationUtilController } from './automation-utils'; +import { AutomationHttpController, AutomationHttpHandler, AutomationHttpService } from './automation-http'; + +@Module({ + imports: [ + ConfigModule.forFeature(automationConfig), + DiscoveryModule, + TypeOrmModule.forFeature([AutomationProcess, AutomationEntityType]), + IAMModule, + EntityInfoModule, + forwardRef(() => DocumentsModule), + ], + providers: [ + AutomationCoreService, + AutomationProcessService, + AutomationProcessHandler, + AutomationEntityTypeService, + AutomationEntityTypeHandler, + AutomationHttpService, + AutomationHttpHandler, + ], + controllers: [ + AutomationCoreController, + AutomationProcessController, + AutomationEntityTypeController, + AutomationUtilController, + AutomationHttpController, + ], + exports: [AutomationCoreService], +}) +export class AutomationModule {} diff --git a/backend/src/modules/automation/common/decorators/automation-worker.decorator.ts b/backend/src/modules/automation/common/decorators/automation-worker.decorator.ts new file mode 100644 index 0000000..217476f --- /dev/null +++ b/backend/src/modules/automation/common/decorators/automation-worker.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; + +export const AUTOMATION_WORKER = 'AUTOMATION_WORKER'; + +export const AutomationWorker = (type: string) => SetMetadata(AUTOMATION_WORKER, type); diff --git a/backend/src/modules/automation/common/decorators/index.ts b/backend/src/modules/automation/common/decorators/index.ts new file mode 100644 index 0000000..47d9c54 --- /dev/null +++ b/backend/src/modules/automation/common/decorators/index.ts @@ -0,0 +1,2 @@ +export * from './automation-worker.decorator'; +export * from './on-automation-job.decorator'; diff --git a/backend/src/modules/automation/common/decorators/on-automation-job.decorator.ts b/backend/src/modules/automation/common/decorators/on-automation-job.decorator.ts new file mode 100644 index 0000000..e576543 --- /dev/null +++ b/backend/src/modules/automation/common/decorators/on-automation-job.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; + +export const AUTOMATION_JOB_HANDLER = 'AUTOMATION_JOB_HANDLER'; + +export const OnAutomationJob = (type: string) => SetMetadata(AUTOMATION_JOB_HANDLER, type); diff --git a/backend/src/modules/automation/common/dto/action/action-settings.dto.ts b/backend/src/modules/automation/common/dto/action/action-settings.dto.ts new file mode 100644 index 0000000..96a0896 --- /dev/null +++ b/backend/src/modules/automation/common/dto/action/action-settings.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class ActionsSettings { + @ApiProperty({ description: 'Allow any stage', nullable: true }) + @IsOptional() + @IsBoolean() + allowAnyStage?: boolean | null; +} diff --git a/backend/src/modules/automation/common/dto/action/index.ts b/backend/src/modules/automation/common/dto/action/index.ts new file mode 100644 index 0000000..49834af --- /dev/null +++ b/backend/src/modules/automation/common/dto/action/index.ts @@ -0,0 +1 @@ +export * from './action-settings.dto'; diff --git a/backend/src/modules/automation/common/dto/condition/automation-entity-condition.dto.ts b/backend/src/modules/automation/common/dto/condition/automation-entity-condition.dto.ts new file mode 100644 index 0000000..61b944c --- /dev/null +++ b/backend/src/modules/automation/common/dto/condition/automation-entity-condition.dto.ts @@ -0,0 +1,24 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +import { AutomationFieldCondition } from './automation-field-condition.dto'; + +export class AutomationEntityCondition { + @ApiPropertyOptional({ description: 'Stage ID', nullable: true }) + @IsOptional() + @IsNumber() + stageId?: number | null; + + @ApiPropertyOptional({ description: 'List of owner user ids', type: [Number] }) + @IsOptional() + @IsArray() + @IsNumber({}, { each: true }) + ownerIds?: number[]; + + @ApiPropertyOptional({ description: 'List of field conditions', type: [AutomationFieldCondition] }) + @IsOptional() + @IsArray() + @Type(() => AutomationFieldCondition) + fields?: AutomationFieldCondition[]; +} diff --git a/backend/src/modules/automation/common/dto/condition/automation-field-condition.dto.ts b/backend/src/modules/automation/common/dto/condition/automation-field-condition.dto.ts new file mode 100644 index 0000000..b019b96 --- /dev/null +++ b/backend/src/modules/automation/common/dto/condition/automation-field-condition.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { SimpleFilter } from '@/common'; + +export class AutomationFieldCondition extends SimpleFilter { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + fieldId: number; +} diff --git a/backend/src/modules/automation/common/dto/condition/index.ts b/backend/src/modules/automation/common/dto/condition/index.ts new file mode 100644 index 0000000..03c8d3a --- /dev/null +++ b/backend/src/modules/automation/common/dto/condition/index.ts @@ -0,0 +1,2 @@ +export * from './automation-entity-condition.dto'; +export * from './automation-field-condition.dto'; diff --git a/backend/src/modules/automation/common/dto/index.ts b/backend/src/modules/automation/common/dto/index.ts new file mode 100644 index 0000000..d7541ad --- /dev/null +++ b/backend/src/modules/automation/common/dto/index.ts @@ -0,0 +1,2 @@ +export * from './action'; +export * from './condition'; diff --git a/backend/src/modules/automation/common/enums/automation-process-type.enum.ts b/backend/src/modules/automation/common/enums/automation-process-type.enum.ts new file mode 100644 index 0000000..6b1ddb2 --- /dev/null +++ b/backend/src/modules/automation/common/enums/automation-process-type.enum.ts @@ -0,0 +1,4 @@ +export enum AutomationProcessType { + General = 'general', + EntityType = 'entity_type', +} diff --git a/backend/src/modules/automation/common/enums/index.ts b/backend/src/modules/automation/common/enums/index.ts new file mode 100644 index 0000000..f4ae609 --- /dev/null +++ b/backend/src/modules/automation/common/enums/index.ts @@ -0,0 +1 @@ +export * from './automation-process-type.enum'; diff --git a/backend/src/modules/automation/common/events/automation-event-type.enum.ts b/backend/src/modules/automation/common/events/automation-event-type.enum.ts new file mode 100644 index 0000000..f4da864 --- /dev/null +++ b/backend/src/modules/automation/common/events/automation-event-type.enum.ts @@ -0,0 +1,6 @@ +export enum AutomationEventType { + EntityTypeApply = 'automation:entity-type:apply', + ProcessStart = 'automation:process:start', + SendSignal = 'automation:send:signal', + SendMessage = 'automation:send:message', +} diff --git a/backend/src/modules/automation/common/events/core/index.ts b/backend/src/modules/automation/common/events/core/index.ts new file mode 100644 index 0000000..9ce26d8 --- /dev/null +++ b/backend/src/modules/automation/common/events/core/index.ts @@ -0,0 +1,2 @@ +export * from './send-message.event'; +export * from './send-signal.event'; diff --git a/backend/src/modules/automation/common/events/core/send-message.event.ts b/backend/src/modules/automation/common/events/core/send-message.event.ts new file mode 100644 index 0000000..8de09e8 --- /dev/null +++ b/backend/src/modules/automation/common/events/core/send-message.event.ts @@ -0,0 +1,11 @@ +import { Message } from '../../types'; + +export class SendMessageEvent { + accountId: number; + message: Message; + + constructor({ accountId, message }: SendMessageEvent) { + this.accountId = accountId; + this.message = message; + } +} diff --git a/backend/src/modules/automation/common/events/core/send-signal.event.ts b/backend/src/modules/automation/common/events/core/send-signal.event.ts new file mode 100644 index 0000000..e78e709 --- /dev/null +++ b/backend/src/modules/automation/common/events/core/send-signal.event.ts @@ -0,0 +1,11 @@ +import { Signal } from '../../types'; + +export class SendSignalEvent { + accountId: number; + signal: Signal; + + constructor({ accountId, signal }: SendSignalEvent) { + this.accountId = accountId; + this.signal = signal; + } +} diff --git a/backend/src/modules/automation/common/events/entity-type/entity-type-apply.event.ts b/backend/src/modules/automation/common/events/entity-type/entity-type-apply.event.ts new file mode 100644 index 0000000..98cb44b --- /dev/null +++ b/backend/src/modules/automation/common/events/entity-type/entity-type-apply.event.ts @@ -0,0 +1,17 @@ +export class EntityTypeApplyEvent { + accountId: number; + automationId: number; + processId: number; + entityTypeId: number; + boardId?: number | null; + stageId?: number | null; + + constructor({ accountId, automationId, processId, entityTypeId, boardId, stageId }: EntityTypeApplyEvent) { + this.accountId = accountId; + this.automationId = automationId; + this.processId = processId; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.stageId = stageId; + } +} diff --git a/backend/src/modules/automation/common/events/entity-type/index.ts b/backend/src/modules/automation/common/events/entity-type/index.ts new file mode 100644 index 0000000..68a6f6b --- /dev/null +++ b/backend/src/modules/automation/common/events/entity-type/index.ts @@ -0,0 +1 @@ +export * from './entity-type-apply.event'; diff --git a/backend/src/modules/automation/common/events/index.ts b/backend/src/modules/automation/common/events/index.ts new file mode 100644 index 0000000..fe47772 --- /dev/null +++ b/backend/src/modules/automation/common/events/index.ts @@ -0,0 +1,4 @@ +export * from './automation-event-type.enum'; +export * from './core'; +export * from './entity-type'; +export * from './process'; diff --git a/backend/src/modules/automation/common/events/process/index.ts b/backend/src/modules/automation/common/events/process/index.ts new file mode 100644 index 0000000..5a41c37 --- /dev/null +++ b/backend/src/modules/automation/common/events/process/index.ts @@ -0,0 +1 @@ +export * from './process-start.event'; diff --git a/backend/src/modules/automation/common/events/process/process-start.event.ts b/backend/src/modules/automation/common/events/process/process-start.event.ts new file mode 100644 index 0000000..aa967c0 --- /dev/null +++ b/backend/src/modules/automation/common/events/process/process-start.event.ts @@ -0,0 +1,13 @@ +import { JSONDoc } from '@camunda8/sdk/dist/zeebe/types'; + +export class ProcessStartEvent { + accountId: number; + processId: number; + variables: V; + + constructor({ accountId, processId, variables }: ProcessStartEvent) { + this.accountId = accountId; + this.processId = processId; + this.variables = variables; + } +} diff --git a/backend/src/modules/automation/common/index.ts b/backend/src/modules/automation/common/index.ts new file mode 100644 index 0000000..55780a3 --- /dev/null +++ b/backend/src/modules/automation/common/index.ts @@ -0,0 +1,7 @@ +export * from './decorators'; +export * from './dto'; +export * from './enums'; +export * from './events'; +export * from './interfaces'; +export * from './types'; +export * from './utils'; diff --git a/backend/src/modules/automation/common/interfaces/automation-handler.interface.ts b/backend/src/modules/automation/common/interfaces/automation-handler.interface.ts new file mode 100644 index 0000000..ef72fce --- /dev/null +++ b/backend/src/modules/automation/common/interfaces/automation-handler.interface.ts @@ -0,0 +1,17 @@ +import { IInputVariables, IOutputVariables } from '@camunda8/sdk/dist/zeebe/types'; + +export interface AutomationJob { + variables: Variables; +} + +export interface AutomationResult { + variables: Variables; +} + +export type AutomationHandler = ( + job: AutomationJob, +) => AutomationResult | Promise>; + +export interface AutomationJobHandler { + handleJob: AutomationHandler; +} diff --git a/backend/src/modules/automation/common/interfaces/index.ts b/backend/src/modules/automation/common/interfaces/index.ts new file mode 100644 index 0000000..550100c --- /dev/null +++ b/backend/src/modules/automation/common/interfaces/index.ts @@ -0,0 +1 @@ +export * from './automation-handler.interface'; diff --git a/backend/src/modules/automation/common/types/index.ts b/backend/src/modules/automation/common/types/index.ts new file mode 100644 index 0000000..eeb0ca3 --- /dev/null +++ b/backend/src/modules/automation/common/types/index.ts @@ -0,0 +1,2 @@ +export * from './message.interface'; +export * from './signal.interface'; diff --git a/backend/src/modules/automation/common/types/message.interface.ts b/backend/src/modules/automation/common/types/message.interface.ts new file mode 100644 index 0000000..eedadd7 --- /dev/null +++ b/backend/src/modules/automation/common/types/message.interface.ts @@ -0,0 +1,5 @@ +import { Signal } from './signal.interface'; + +export interface Message extends Signal { + correlationKey?: string; +} diff --git a/backend/src/modules/automation/common/types/signal.interface.ts b/backend/src/modules/automation/common/types/signal.interface.ts new file mode 100644 index 0000000..0fbf3de --- /dev/null +++ b/backend/src/modules/automation/common/types/signal.interface.ts @@ -0,0 +1,7 @@ +import { IInputVariables } from '@camunda8/sdk/dist/zeebe/types'; + +type SignalName = string | number; +export interface Signal { + name: SignalName | SignalName[]; + variables?: IInputVariables; +} diff --git a/backend/src/modules/automation/common/utils/automation-condition.util.ts b/backend/src/modules/automation/common/utils/automation-condition.util.ts new file mode 100644 index 0000000..5348784 --- /dev/null +++ b/backend/src/modules/automation/common/utils/automation-condition.util.ts @@ -0,0 +1,147 @@ +import { + BooleanFilter, + DateFilter, + ExistsFilter, + ExistsFilterType, + NumberFilter, + SelectFilter, + SimpleFilterType, + StringFilter, + StringFilterType, +} from '@/common'; + +import { AutomationEntityCondition, AutomationFieldCondition } from '../dto'; + +// const EntityFieldPrefix = 'entity.fields.f_'; +const Names = { + entity: 'entity', + stage: () => `${Names.entity}.stageId`, + owner: () => `${Names.entity}.responsibleUserId`, + fields: () => `${Names.entity}.fields`, + field: (fieldId: number) => `${Names.fields()}.f_${fieldId}`, +} as const; + +export class AutomatonConditionUtil { + public static formatEntityCondition(condition: AutomationEntityCondition): string { + const conditions: string[] = []; + if (condition.stageId) { + conditions.push(`${Names.stage()} = ${condition.stageId}`); + } + + if (condition.ownerIds?.length) { + conditions.push(`${Names.owner()} in [${condition.ownerIds.join(',')}]`); + } + + if (condition.fields?.length) { + condition.fields.forEach((field) => { + conditions.push(...AutomatonConditionUtil.formatFieldCondition(field)); + }); + } + + return conditions.length ? conditions.map((c) => `(${c})`).join(' and ') : 'true'; + } + + public static formatFieldCondition(field: AutomationFieldCondition): string[] { + switch (field.type) { + case SimpleFilterType.Boolean: + return AutomatonConditionUtil.formatBooleanFieldCondition(field); + case SimpleFilterType.Date: + return AutomatonConditionUtil.formatDateFieldCondition(field); + case SimpleFilterType.Number: + return AutomatonConditionUtil.formatNumberFieldCondition(field); + case SimpleFilterType.Select: + return AutomatonConditionUtil.formatSelectFieldCondition(field); + case SimpleFilterType.String: + return AutomatonConditionUtil.formatStringFieldCondition(field); + case SimpleFilterType.Exists: + return AutomatonConditionUtil.formatExistsFieldCondition(field); + } + } + + private static formatBooleanFieldCondition(field: AutomationFieldCondition) { + const conditions: string[] = []; + const booleanFilter = field.filter as BooleanFilter; + if (booleanFilter.value !== null && booleanFilter.value !== undefined) { + if (booleanFilter.value) { + conditions.push(`${Names.field(field.fieldId)} != null`); + conditions.push(`${Names.field(field.fieldId)} = true`); + } else { + conditions.push(`(${Names.field(field.fieldId)} = null) or (${Names.field(field.fieldId)} = false)`); + } + } + return conditions; + } + + private static formatDateFieldCondition(field: AutomationFieldCondition) { + const conditions: string[] = []; + const dateFilter = field.filter as DateFilter; + if (dateFilter.from || dateFilter.to) conditions.push(`${Names.field(field.fieldId)} != null`); + if (dateFilter.from) conditions.push(`${Names.field(field.fieldId)} >= "${dateFilter.from}"`); + if (dateFilter.to) conditions.push(`${Names.field(field.fieldId)} <= "${dateFilter.to}"`); + return conditions; + } + + private static formatNumberFieldCondition(field: AutomationFieldCondition) { + const conditions: string[] = []; + const numberFilter = field.filter as NumberFilter; + if ( + (numberFilter.min !== null && numberFilter.min !== undefined) || + (numberFilter.max !== null && numberFilter.max !== undefined) + ) { + conditions.push(`${Names.field(field.fieldId)} != null`); + } + if (numberFilter.min !== null && numberFilter.min !== undefined) { + conditions.push(`${Names.field(field.fieldId)} >= ${numberFilter.min}`); + } + if (numberFilter.max !== null && numberFilter.max !== undefined) { + conditions.push(`${Names.field(field.fieldId)} <= ${numberFilter.max}`); + } + return conditions; + } + + private static formatSelectFieldCondition(field: AutomationFieldCondition) { + const conditions: string[] = []; + const selectFilter = field.filter as SelectFilter; + if (selectFilter.optionIds?.length) { + conditions.push(`${Names.field(field.fieldId)} != null`); + conditions.push(`some x in ${Names.field(field.fieldId)} satisfies x in [${selectFilter.optionIds.join(',')}]`); + } + return conditions; + } + + private static formatStringFieldCondition(field: AutomationFieldCondition) { + const conditions: string[] = []; + const stringFilter = field.filter as StringFilter; + switch (stringFilter.type) { + case StringFilterType.Empty: + conditions.push(`(${Names.field(field.fieldId)} = null) or (${Names.field(field.fieldId)} = "")`); + break; + case StringFilterType.NotEmpty: + conditions.push(`${Names.field(field.fieldId)} != null`); + conditions.push(`${Names.field(field.fieldId)} != ""`); + break; + case StringFilterType.Contains: + if (stringFilter.text) { + conditions.push(`${Names.field(field.fieldId)} != null`); + conditions.push(`contains(${Names.field(field.fieldId)}, "${encodeURIComponent(stringFilter.text)}")`); + } + break; + } + return conditions; + } + + private static formatExistsFieldCondition(field: AutomationFieldCondition) { + const conditions: string[] = []; + const existsFilter = field.filter as ExistsFilter; + switch (existsFilter.type) { + case ExistsFilterType.Empty: + conditions.push(`(${Names.field(field.fieldId)} = null) or (${Names.field(field.fieldId)} = "")`); + break; + case ExistsFilterType.NotEmpty: + conditions.push(`${Names.field(field.fieldId)} != null`); + conditions.push(`${Names.field(field.fieldId)} != ""`); + break; + } + return conditions; + } +} diff --git a/backend/src/modules/automation/common/utils/automation-delay.util.ts b/backend/src/modules/automation/common/utils/automation-delay.util.ts new file mode 100644 index 0000000..3b18c93 --- /dev/null +++ b/backend/src/modules/automation/common/utils/automation-delay.util.ts @@ -0,0 +1,5 @@ +export class AutomationDelayUtil { + public static formatSeconds(delay?: number | null): string { + return `PT${delay || 0}S`; + } +} diff --git a/backend/src/modules/automation/common/utils/index.ts b/backend/src/modules/automation/common/utils/index.ts new file mode 100644 index 0000000..3b9d6f6 --- /dev/null +++ b/backend/src/modules/automation/common/utils/index.ts @@ -0,0 +1,2 @@ +export * from './automation-condition.util'; +export * from './automation-delay.util'; diff --git a/backend/src/modules/automation/config/automation.config.ts b/backend/src/modules/automation/config/automation.config.ts new file mode 100644 index 0000000..cff18a7 --- /dev/null +++ b/backend/src/modules/automation/config/automation.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +export interface AutomationConfig { + jobDiscovery: boolean; +} + +export default registerAs( + 'automation', + (): AutomationConfig => ({ + jobDiscovery: ['true', 'on'].includes(process.env.AUTOMATION_JOB_DISCOVERY_ENABLED), + }), +); diff --git a/backend/src/modules/automation/index.ts b/backend/src/modules/automation/index.ts new file mode 100644 index 0000000..6fa5095 --- /dev/null +++ b/backend/src/modules/automation/index.ts @@ -0,0 +1,5 @@ +export * from './automation-core'; +export * from './automation-entity-type'; +export * from './automation-process'; +export * from './automation.module'; +export * from './common'; diff --git a/backend/src/modules/data-enrichment/config/data-enrichment.config.ts b/backend/src/modules/data-enrichment/config/data-enrichment.config.ts new file mode 100644 index 0000000..97986fe --- /dev/null +++ b/backend/src/modules/data-enrichment/config/data-enrichment.config.ts @@ -0,0 +1,14 @@ +import { registerAs } from '@nestjs/config'; + +export interface DataEnrichmentConfig { + geohelperApiKey: string; + dadataApiKey: string; +} + +export default registerAs( + 'data-enrichment', + (): DataEnrichmentConfig => ({ + geohelperApiKey: process.env.DATA_ENRICHMENT_GEOHELPER_API_KEY, + dadataApiKey: process.env.DATA_ENRICHMENT_DADATA_API_KEY, + }), +); diff --git a/backend/src/modules/data-enrichment/data-enrichment.controller.ts b/backend/src/modules/data-enrichment/data-enrichment.controller.ts new file mode 100644 index 0000000..bdf9932 --- /dev/null +++ b/backend/src/modules/data-enrichment/data-enrichment.controller.ts @@ -0,0 +1,46 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common'; + +import { DataEnrichmentService } from './data-enrichment.service'; +import { BankRequisitesDto, OrganizationRequisitesDto, PhoneUserInfoDto } from './dto'; + +@ApiTags('data-enrichment') +@Controller('data-enrichment') +@JwtAuthorized() +@TransformToDto() +export class DataEnrichmentController { + constructor(private service: DataEnrichmentService) {} + + @ApiOperation({ summary: 'Get bank requisites by query', description: 'Get bank requisites by query' }) + @ApiQuery({ name: 'query', type: String, required: true, description: 'Query to get bank requisites from' }) + @ApiOkResponse({ description: 'Bank requisites', type: [BankRequisitesDto] }) + @Get('requisites/bank') + public async getBankRequisites(@Query('query') query: string) { + return this.service.getBankRequisites(query); + } + + @ApiOperation({ + summary: 'Get organization requisites by query', + description: 'Get organization requisites by query', + }) + @ApiQuery({ name: 'query', type: String, required: true, description: 'Query to get organization requisites from' }) + @ApiOkResponse({ description: 'Organization requisites', type: [OrganizationRequisitesDto] }) + @Get('requisites/org') + public async getOrgRequisites(@Query('query') query: string) { + return this.service.getOrgRequisites(query); + } + + @ApiOperation({ + summary: 'Get aggregated phone user info by phone number', + description: 'Get aggregated phone user info by phone number', + }) + @ApiQuery({ name: 'phone', type: String, required: true, description: 'Phone number to get aggregated info from' }) + @ApiOkResponse({ description: 'Phone user info', type: PhoneUserInfoDto }) + @Get('phone/aggregate') + public async getPhoneInfo(@Query('phone') phone: string) { + return this.service.getPhoneInfo(phone); + } +} diff --git a/backend/src/modules/data-enrichment/data-enrichment.module.ts b/backend/src/modules/data-enrichment/data-enrichment.module.ts new file mode 100644 index 0000000..456eb9c --- /dev/null +++ b/backend/src/modules/data-enrichment/data-enrichment.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import dataEnrichmentConfig from './config/data-enrichment.config'; +import { DataEnrichmentService } from './data-enrichment.service'; +import { DataEnrichmentController } from './data-enrichment.controller'; + +@Module({ + imports: [ConfigModule.forFeature(dataEnrichmentConfig), IAMModule], + providers: [DataEnrichmentService], + controllers: [DataEnrichmentController], +}) +export class DataEnrichmentModule {} diff --git a/backend/src/modules/data-enrichment/data-enrichment.service.ts b/backend/src/modules/data-enrichment/data-enrichment.service.ts new file mode 100644 index 0000000..41221e2 --- /dev/null +++ b/backend/src/modules/data-enrichment/data-enrichment.service.ts @@ -0,0 +1,206 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { lastValueFrom } from 'rxjs'; + +import { CacheService, DateUtil, withTimeout } from '@/common'; + +import { DataEnrichmentConfig } from './config/data-enrichment.config'; +import { getRuCountryNameByIsoCode, getUtcOffsetByRuCountryName } from './helpers'; +import { + BankRequisites, + DadataBankRequisites, + DadataOrgRequisites, + DadataSuggestions, + PhoneUserInfo, + PhoneUserInfoFincalculator, + PhoneUserInfoGeohelper, + PhoneUserInfoSpNova, + UserInfoContentSpNova, +} from './types'; +import { OrganizationRequisites } from './types/organization-requisites'; + +const PhoneServiceUrls = { + geohelper: 'https://geohelper.info/api/v1/phone-data', + fincalculator: 'https://fincalculator.ru/api/tel', + spNova: 'https://sp1-nova.ru/api/phones-data/', +} as const; + +const DadataUrls = { + bankRequisites: 'http://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/bank', + orgRequisites: 'http://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/party', +} as const; + +@Injectable() +export class DataEnrichmentService { + private _config: DataEnrichmentConfig; + + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + private readonly cache: CacheService, + ) { + this._config = this.configService.get('data-enrichment'); + } + + async getBankRequisites(query: string): Promise { + const getData = async (query: string): Promise | null> => { + try { + const response = await lastValueFrom( + this.httpService.get>(DadataUrls.bankRequisites, { + params: { query }, + headers: { Authorization: `Token ${this._config.dadataApiKey}` }, + }), + ); + return response.data; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return null; + } + }; + + const data = await this.cache.wrap(`DataEnrichment.bankRequisites:${query}`, () => getData(query), 604800); + + return data?.suggestions?.map((suggestion) => BankRequisites.fromDadata(suggestion)); + } + + async getOrgRequisites(query: string): Promise { + const getData = async (query: string): Promise | null> => { + try { + const response = await lastValueFrom( + this.httpService.get>(DadataUrls.orgRequisites, { + params: { query }, + headers: { Authorization: `Token ${this._config.dadataApiKey}` }, + }), + ); + return response.data; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return null; + } + }; + + const data = await this.cache.wrap(`DataEnrichment.orgRequisites:${query}`, () => getData(query), 604800); + + return data?.suggestions?.map((suggestion) => OrganizationRequisites.fromDadata(suggestion)); + } + + async getPhoneInfo(phone: string): Promise { + const getData = async (phone: string): Promise => { + const timeout = 7500; + + const infos = await Promise.all([ + withTimeout(this.getPhoneInfoFincalculator(phone), timeout), + withTimeout(this.getPhoneInfoSpNova(phone), timeout), + withTimeout(this.getPhoneInfoGeohelper(phone), timeout), + ]); + + const country = infos.find((info) => info?.country)?.country ?? null; + const city = infos.find((info) => info?.city)?.city ?? null; + const region = infos.find((info) => info?.region)?.region ?? null; + const utcOffset = infos.find((info) => info?.utcOffset)?.utcOffset ?? getUtcOffsetByRuCountryName(country); + + return new PhoneUserInfo({ country, city, region, utcOffset }); + }; + + const data = await this.cache.wrap(`DataEnrichment.phone:${phone}`, () => getData(phone), 604800); + + return new PhoneUserInfo(data); + } + + // https://geohelper.info/ru/doc/api/#get/api/v1/phone-data + private async getPhoneInfoGeohelper(phone: string): Promise { + try { + const response$ = this.httpService.get(PhoneServiceUrls.geohelper, { + params: { + 'locale[lang]': 'ru', + 'filter[phone]': phone, + 'locale[fallbackLang]': 'ru', + apiKey: this._config.geohelperApiKey, + }, + }); + + const response = await lastValueFrom(response$); + // we can't trust that service will return something + const data = response.data as PhoneUserInfoGeohelper | null | undefined; + + // request failed + if (!data?.success) { + return null; + } + + const composeRegion = (data: PhoneUserInfoGeohelper) => { + const regionName = data?.result?.region?.name; + const regionLocalizedName = data?.result?.region?.localityType?.localizedNames?.ru; + const regionLocalizedNameShort = data?.result?.region?.localityType?.localizedNamesShort?.ru; + + if (regionName && (regionLocalizedName || regionLocalizedNameShort)) { + return `${regionName} (${regionLocalizedName ?? regionLocalizedNameShort})`; + } + + return null; + }; + + const timezoneOffset = data?.result?.region?.timezoneOffset; + return new PhoneUserInfo({ + city: null, + region: composeRegion(data), + country: getRuCountryNameByIsoCode(data?.result?.region?.countryIso), + utcOffset: timezoneOffset ? timezoneOffset / 3600 : null, + }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return null; + } + } + // https://fincalculator.ru/telefon/region-po-nomeru + private async getPhoneInfoFincalculator(phone: string): Promise { + try { + const response$ = this.httpService.get(`${PhoneServiceUrls.fincalculator}/${phone}`); + + const response = await lastValueFrom(response$); + // we can't trust that service will return something + const data = response.data as PhoneUserInfoFincalculator | null | undefined; + + // sometimes fincalculator returns wrong information about region (seems like it is a phone operator), + // so we need to check that region is not hallucination, known hallucinations are presented below + const knownRegionHallucinations = ['Кселл', 'ТОО']; + + const region = data?.region; + + return new PhoneUserInfo({ + city: null, + region: region && !knownRegionHallucinations.includes(region) ? region : null, + country: data?.country ?? null, + utcOffset: data?.timeZone ?? null, + }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return null; + } + } + private async getPhoneInfoSpNova(phone: string): Promise { + try { + const response$ = this.httpService.post(PhoneServiceUrls.spNova, [phone], { + headers: { + 'Content-Type': 'application/json', + }, + }); + + const response = await lastValueFrom(response$); + // we can't trust that service will return something + const data = response.data as PhoneUserInfoSpNova | null | undefined; + const content = data?.[phone] as UserInfoContentSpNova | null | undefined; + + return new PhoneUserInfo({ + city: content?.city ?? null, + region: content?.region ?? null, + country: content?.country ?? null, + utcOffset: content?.timezone ? DateUtil.extractOffsetFromUTCString(content.timezone) : null, + }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return null; + } + } +} diff --git a/backend/src/modules/data-enrichment/dto/bank-requisites.dto.ts b/backend/src/modules/data-enrichment/dto/bank-requisites.dto.ts new file mode 100644 index 0000000..ca327b0 --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/bank-requisites.dto.ts @@ -0,0 +1,51 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; + +import { OpfType } from '../enums'; + +export class BankRequisitesDto { + @ApiPropertyOptional({ description: 'Bank name', nullable: true }) + @IsOptional() + @IsString() + value?: string | null; + + @ApiPropertyOptional({ description: 'Bank name unrestricted', nullable: true }) + @IsOptional() + @IsString() + unrestrictedValue?: string | null; + + @ApiPropertyOptional({ description: 'Bank BIC', nullable: true }) + @IsOptional() + @IsString() + bic?: string | null; + + @ApiPropertyOptional({ description: 'Bank SWIFT', nullable: true }) + @IsOptional() + @IsString() + swift?: string | null; + + @ApiPropertyOptional({ description: 'Bank INN', nullable: true }) + @IsOptional() + @IsString() + inn?: string | null; + + @ApiPropertyOptional({ description: 'Bank KPP', nullable: true }) + @IsOptional() + @IsString() + kpp?: string | null; + + @ApiPropertyOptional({ description: 'Bank correspondent account', nullable: true }) + @IsOptional() + @IsString() + correspondentAccount?: string | null; + + @ApiPropertyOptional({ description: 'Bank payment city', nullable: true }) + @IsOptional() + @IsString() + paymentCity?: string | null; + + @ApiPropertyOptional({ description: 'Bank OPF type', nullable: true, enum: OpfType }) + @IsOptional() + @IsEnum(OpfType) + opf?: OpfType | null; +} diff --git a/backend/src/modules/data-enrichment/dto/fio.dto.ts b/backend/src/modules/data-enrichment/dto/fio.dto.ts new file mode 100644 index 0000000..81cd39b --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/fio.dto.ts @@ -0,0 +1,19 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class FioDto { + @ApiPropertyOptional({ description: 'Name', nullable: true }) + @IsOptional() + @IsString() + name?: string | null; + + @ApiPropertyOptional({ description: 'Surname', nullable: true }) + @IsOptional() + @IsString() + surname?: string | null; + + @ApiPropertyOptional({ description: 'Patronymic name', nullable: true }) + @IsOptional() + @IsString() + patronymic?: string | null; +} diff --git a/backend/src/modules/data-enrichment/dto/index.ts b/backend/src/modules/data-enrichment/dto/index.ts new file mode 100644 index 0000000..c96fe4e --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/index.ts @@ -0,0 +1,3 @@ +export * from './bank-requisites.dto'; +export * from './organization-requisites.dto'; +export * from './phone-user-info.dto'; diff --git a/backend/src/modules/data-enrichment/dto/organization-requisites-address.dto.ts b/backend/src/modules/data-enrichment/dto/organization-requisites-address.dto.ts new file mode 100644 index 0000000..47279a7 --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/organization-requisites-address.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class OrganizationRequisitesAddressDto { + @ApiPropertyOptional({ description: 'Unrestricted value', nullable: true }) + @IsOptional() + @IsString() + unrestrictedValue?: string | null; +} diff --git a/backend/src/modules/data-enrichment/dto/organization-requisites-managment.dto.ts b/backend/src/modules/data-enrichment/dto/organization-requisites-managment.dto.ts new file mode 100644 index 0000000..a3b6fa5 --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/organization-requisites-managment.dto.ts @@ -0,0 +1,19 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsOptional, IsString } from 'class-validator'; + +export class OrganizationRequisitesManagmentDto { + @ApiPropertyOptional({ description: 'Name', nullable: true }) + @IsOptional() + @IsString() + name?: string | null; + + @ApiPropertyOptional({ description: 'Job title', nullable: true }) + @IsOptional() + @IsString() + post?: string | null; + + @ApiPropertyOptional({ description: 'Start date', nullable: true }) + @IsOptional() + @IsDateString() + startDate?: string | null; +} diff --git a/backend/src/modules/data-enrichment/dto/organization-requisites-name.dto.ts b/backend/src/modules/data-enrichment/dto/organization-requisites-name.dto.ts new file mode 100644 index 0000000..9b574c8 --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/organization-requisites-name.dto.ts @@ -0,0 +1,14 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class OrganizationRequisitesNameDto { + @ApiPropertyOptional({ description: 'Full name', nullable: true }) + @IsOptional() + @IsString() + full?: string | null; + + @ApiPropertyOptional({ description: 'Short name', nullable: true }) + @IsOptional() + @IsString() + short?: string | null; +} diff --git a/backend/src/modules/data-enrichment/dto/organization-requisites-state.dto.ts b/backend/src/modules/data-enrichment/dto/organization-requisites-state.dto.ts new file mode 100644 index 0000000..0f079a0 --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/organization-requisites-state.dto.ts @@ -0,0 +1,21 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsEnum, IsOptional } from 'class-validator'; + +import { OrgStatus } from '../enums'; + +export class OrganizationRequisitesStateDto { + @ApiPropertyOptional({ description: 'Registration date', nullable: true }) + @IsOptional() + @IsDateString() + registrationDate?: string | null; + + @ApiPropertyOptional({ description: 'Liquidation date', nullable: true }) + @IsOptional() + @IsDateString() + liquidationDate?: string | null; + + @ApiPropertyOptional({ enum: OrgStatus, description: 'Status', nullable: true }) + @IsOptional() + @IsEnum(OrgStatus) + status?: OrgStatus | null; +} diff --git a/backend/src/modules/data-enrichment/dto/organization-requisites.dto.ts b/backend/src/modules/data-enrichment/dto/organization-requisites.dto.ts new file mode 100644 index 0000000..62481a0 --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/organization-requisites.dto.ts @@ -0,0 +1,140 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { OrgBranchType, OrgType } from '../enums'; +import { OrganizationRequisitesNameDto } from './organization-requisites-name.dto'; +import { FioDto } from './fio.dto'; +import { OrganizationRequisitesManagmentDto } from './organization-requisites-managment.dto'; +import { OrganizationRequisitesStateDto } from './organization-requisites-state.dto'; +import { OrganizationRequisitesAddressDto } from './organization-requisites-address.dto'; + +export class OrganizationRequisitesDto { + @ApiPropertyOptional({ description: 'Organization name', nullable: true }) + @IsOptional() + @IsString() + value?: string | null; + + @ApiPropertyOptional({ description: 'Organization name unrestricted', nullable: true }) + @IsOptional() + @IsString() + unrestrictedValue?: string | null; + + @ApiPropertyOptional({ description: 'INN', nullable: true }) + @IsOptional() + @IsString() + inn?: string | null; + + @ApiPropertyOptional({ description: 'KPP', nullable: true }) + @IsOptional() + @IsString() + kpp?: string | null; + + @ApiPropertyOptional({ description: 'OGRN', nullable: true }) + @IsOptional() + @IsString() + ogrn?: string | null; + + @ApiPropertyOptional({ enum: OrgType, description: 'Type', nullable: true }) + @IsOptional() + @IsEnum(OrgType) + type?: OrgType | null; + + @ApiPropertyOptional({ + type: OrganizationRequisitesNameDto, + description: 'Organization name details', + nullable: true, + }) + @IsOptional() + name?: OrganizationRequisitesNameDto | null; + + @ApiPropertyOptional({ type: FioDto, description: 'FIO', nullable: true }) + @IsOptional() + fio?: FioDto | null; + + @ApiPropertyOptional({ type: OrganizationRequisitesManagmentDto, description: 'Management', nullable: true }) + @IsOptional() + management?: OrganizationRequisitesManagmentDto | null; + + @ApiPropertyOptional({ description: 'Branch count', nullable: true }) + @IsOptional() + @IsNumber() + branchCount?: number | null; + + @ApiPropertyOptional({ enum: OrgBranchType, description: 'Branch type', nullable: true }) + @IsOptional() + @IsEnum(OrgBranchType) + branchType?: OrgBranchType | null; + + @ApiPropertyOptional({ type: OrganizationRequisitesAddressDto, description: 'Address', nullable: true }) + @IsOptional() + address?: OrganizationRequisitesAddressDto | null; + + @ApiPropertyOptional({ type: OrganizationRequisitesStateDto, description: 'State', nullable: true }) + @IsOptional() + state?: OrganizationRequisitesStateDto | null; + + @ApiPropertyOptional({ description: 'OKATO', nullable: true }) + @IsOptional() + @IsString() + okato?: string | null; + + @ApiPropertyOptional({ description: 'OKTMO', nullable: true }) + @IsOptional() + @IsString() + oktmo?: string | null; + + @ApiPropertyOptional({ description: 'OKPO', nullable: true }) + @IsOptional() + @IsString() + okpo?: string | null; + + @ApiPropertyOptional({ description: 'OKOGU', nullable: true }) + @IsOptional() + @IsString() + okogu?: string | null; + + @ApiPropertyOptional({ description: 'OKFS', nullable: true }) + @IsOptional() + @IsString() + okfs?: string | null; + + @ApiPropertyOptional({ description: 'OKVED', nullable: true }) + @IsOptional() + @IsString() + okved?: string | null; + + @ApiPropertyOptional({ description: 'Employee count', nullable: true }) + @IsOptional() + @IsNumber() + employeeCount?: number | null; + + @ApiPropertyOptional({ type: [String], description: 'Founders', nullable: true }) + @IsOptional() + @IsString({ each: true }) + founders?: string[] | null; + + @ApiPropertyOptional({ type: [String], description: 'Managers', nullable: true }) + @IsOptional() + @IsString({ each: true }) + managers?: string[] | null; + + @ApiPropertyOptional({ description: 'Capital', nullable: true }) + @IsOptional() + @IsString() + capital?: string | null; + + @ApiPropertyOptional({ type: [String], description: 'Licenses', nullable: true }) + @IsOptional() + @IsString({ each: true }) + licenses?: string[] | null; + + @ApiPropertyOptional({ type: [String], description: 'Phones', nullable: true }) + @IsOptional() + @IsString({ each: true }) + phones?: string[] | null; + + @ApiPropertyOptional({ type: [String], description: 'Emails', nullable: true }) + @IsOptional() + @IsString({ each: true }) + emails?: string[] | null; +} diff --git a/backend/src/modules/data-enrichment/dto/phone-user-info.dto.ts b/backend/src/modules/data-enrichment/dto/phone-user-info.dto.ts new file mode 100644 index 0000000..1898317 --- /dev/null +++ b/backend/src/modules/data-enrichment/dto/phone-user-info.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class PhoneUserInfoDto { + @ApiProperty({ description: 'UTC offset' }) + @IsNumber() + utcOffset: number | null; + + @ApiProperty({ description: 'Country name' }) + @IsString() + country: string | null; + + @ApiProperty({ description: 'Region name' }) + @IsString() + region: string | null; + + @ApiProperty({ description: 'City name' }) + @IsString() + city: string | null; +} diff --git a/backend/src/modules/data-enrichment/enums/index.ts b/backend/src/modules/data-enrichment/enums/index.ts new file mode 100644 index 0000000..58409a8 --- /dev/null +++ b/backend/src/modules/data-enrichment/enums/index.ts @@ -0,0 +1,4 @@ +export * from './opf-type.enum'; +export * from './org-branch-type.enum'; +export * from './org-status.enum'; +export * from './org-type.enum'; diff --git a/backend/src/modules/data-enrichment/enums/opf-type.enum.ts b/backend/src/modules/data-enrichment/enums/opf-type.enum.ts new file mode 100644 index 0000000..8bda778 --- /dev/null +++ b/backend/src/modules/data-enrichment/enums/opf-type.enum.ts @@ -0,0 +1,18 @@ +export enum OpfType { + // банк + Bank = 'BANK', + // филиал банка + BankBranch = 'BANK_BRANCH', + // небанковская кредитная организация (НКО) + Nko = 'NKO', + // филиал НКО + NkoBranch = 'NKO_BRANCH', + // расчетно-кассовый центр + Rkc = 'RKC', + // управление ЦБ РФ (март 2021) + Cbr = 'CBR', + // управление Казначейства (март 2021) + Treasury = 'TREASURY', + // другой + Other = 'OTHER', +} diff --git a/backend/src/modules/data-enrichment/enums/org-branch-type.enum.ts b/backend/src/modules/data-enrichment/enums/org-branch-type.enum.ts new file mode 100644 index 0000000..e7945c9 --- /dev/null +++ b/backend/src/modules/data-enrichment/enums/org-branch-type.enum.ts @@ -0,0 +1,6 @@ +export enum OrgBranchType { + // Головная организация + Main = 'MAIN', + // Филиал + Branch = 'BRANCH', +} diff --git a/backend/src/modules/data-enrichment/enums/org-status.enum.ts b/backend/src/modules/data-enrichment/enums/org-status.enum.ts new file mode 100644 index 0000000..ccceb84 --- /dev/null +++ b/backend/src/modules/data-enrichment/enums/org-status.enum.ts @@ -0,0 +1,12 @@ +export enum OrgStatus { + // Действующая + Active = 'ACTIVE', + // Ликвидируется + Liquidating = 'LIQUIDATING', + // Ликвидирована + Liquidated = 'LIQUIDATED', + // Банкротство + Bankrupt = 'BANKRUPT', + // В процессе присоединения к другому юрлицу, с последующей ликвидацией + Reorganizing = 'REORGANIZING', +} diff --git a/backend/src/modules/data-enrichment/enums/org-type.enum.ts b/backend/src/modules/data-enrichment/enums/org-type.enum.ts new file mode 100644 index 0000000..9dc911e --- /dev/null +++ b/backend/src/modules/data-enrichment/enums/org-type.enum.ts @@ -0,0 +1,6 @@ +export enum OrgType { + // Юридическое лицо + Legal = 'LEGAL', + // Индивидуальный предприниматель + Individual = 'INDIVIDUAL', +} diff --git a/backend/src/modules/data-enrichment/helpers/get-ru-country-name-by-iso-code.helper.ts b/backend/src/modules/data-enrichment/helpers/get-ru-country-name-by-iso-code.helper.ts new file mode 100644 index 0000000..bdbeefa --- /dev/null +++ b/backend/src/modules/data-enrichment/helpers/get-ru-country-name-by-iso-code.helper.ts @@ -0,0 +1,23 @@ +const codes: Record = { + RU: 'Россия', + KZ: 'Казахстан', + UA: 'Украина', + BY: 'Беларусь', + UZ: 'Узбекистан', + TJ: 'Таджикистан', + KG: 'Киргизия', + TM: 'Туркменистан', + AM: 'Армения', + AZ: 'Азербайджан', + GE: 'Грузия', + MD: 'Молдова', + PL: 'Польша', + LV: 'Латвия', + LT: 'Литва', + EE: 'Эстония', + RS: 'Сербия', +}; + +export const getRuCountryNameByIsoCode = (countryIso: string) => { + return codes[countryIso] ?? null; +}; diff --git a/backend/src/modules/data-enrichment/helpers/get-utc-offset-by-ru-country-name.helper.ts b/backend/src/modules/data-enrichment/helpers/get-utc-offset-by-ru-country-name.helper.ts new file mode 100644 index 0000000..ba4577a --- /dev/null +++ b/backend/src/modules/data-enrichment/helpers/get-utc-offset-by-ru-country-name.helper.ts @@ -0,0 +1,21 @@ +const offsets: Record = { + Азербайджан: 4, + Армения: 4, + Беларусь: 3, + Казахстан: 5, + Кыргызстан: 6, + Молдова: 3, + Россия: 3, + Таджикистан: 5, + Туркменистан: 5, + Узбекистан: 5, + Польша: 2, + Латвия: 3, + Литва: 3, + Эстония: 3, + Сербия: 2, +}; + +export const getUtcOffsetByRuCountryName = (countryName: string): number | null => { + return countryName ? (offsets[countryName] ?? null) : null; +}; diff --git a/backend/src/modules/data-enrichment/helpers/index.ts b/backend/src/modules/data-enrichment/helpers/index.ts new file mode 100644 index 0000000..514c41e --- /dev/null +++ b/backend/src/modules/data-enrichment/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './get-ru-country-name-by-iso-code.helper'; +export * from './get-utc-offset-by-ru-country-name.helper'; diff --git a/backend/src/modules/data-enrichment/types/bank-requisites.ts b/backend/src/modules/data-enrichment/types/bank-requisites.ts new file mode 100644 index 0000000..2f7a881 --- /dev/null +++ b/backend/src/modules/data-enrichment/types/bank-requisites.ts @@ -0,0 +1,56 @@ +import { BankRequisitesDto } from '../dto'; +import { OpfType } from '../enums'; +import { DadataBankRequisites } from './dadata-bank-requisites'; +import { DadataSuggestion } from './dadata-suggestion'; + +export class BankRequisites { + value?: string | null; + unrestrictedValue?: string | null; + bic?: string | null; + swift?: string | null; + inn?: string | null; + kpp?: string | null; + correspondentAccount?: string | null; + paymentCity?: string | null; + opf?: OpfType | null; + + constructor(data: Omit) { + this.value = data.value; + this.unrestrictedValue = data.unrestrictedValue; + this.bic = data.bic; + this.swift = data.swift; + this.inn = data.inn; + this.kpp = data.kpp; + this.correspondentAccount = data.correspondentAccount; + this.paymentCity = data.paymentCity; + this.opf = data.opf; + } + + public static fromDadata(suggestion: DadataSuggestion): BankRequisites { + return new BankRequisites({ + value: suggestion.value, + unrestrictedValue: suggestion.unrestricted_value, + bic: suggestion.data?.bic, + swift: suggestion.data?.swift, + inn: suggestion.data?.inn, + kpp: suggestion.data?.kpp, + correspondentAccount: suggestion.data?.correspondent_account, + paymentCity: suggestion.data?.payment_city, + opf: suggestion.data?.opf?.type, + }); + } + + toDto(): BankRequisitesDto { + return { + value: this.value, + unrestrictedValue: this.unrestrictedValue, + bic: this.bic, + swift: this.swift, + inn: this.inn, + kpp: this.kpp, + correspondentAccount: this.correspondentAccount, + paymentCity: this.paymentCity, + opf: this.opf, + }; + } +} diff --git a/backend/src/modules/data-enrichment/types/dadata-bank-requisites.ts b/backend/src/modules/data-enrichment/types/dadata-bank-requisites.ts new file mode 100644 index 0000000..d84b22c --- /dev/null +++ b/backend/src/modules/data-enrichment/types/dadata-bank-requisites.ts @@ -0,0 +1,13 @@ +import { OpfType } from '../enums'; + +export interface DadataBankRequisites { + bic?: string | null; + swift?: string | null; + inn?: string | null; + kpp?: string | null; + correspondent_account?: string | null; + payment_city?: string | null; + opf?: { + type?: OpfType | null; + }; +} diff --git a/backend/src/modules/data-enrichment/types/dadata-org-requisites.ts b/backend/src/modules/data-enrichment/types/dadata-org-requisites.ts new file mode 100644 index 0000000..fb2093a --- /dev/null +++ b/backend/src/modules/data-enrichment/types/dadata-org-requisites.ts @@ -0,0 +1,45 @@ +import { OrgBranchType, OrgStatus, OrgType } from '../enums'; + +export interface DadataOrgRequisites { + inn?: string | null; + kpp?: string | null; + ogrn?: string | null; + type?: OrgType | null; + name?: { + full_with_opf?: string | null; + short_with_opf?: string | null; + } | null; + fio?: { + name?: string | null; + surname?: string | null; + patronymic?: string | null; + } | null; + management?: { + name?: string | null; + post?: string | null; + start_date?: number | null; + } | null; + branch_count?: number | null; + branch_type?: OrgBranchType | null; + address?: { + unrestricted_value?: string | null; + } | null; + state?: { + registration_date?: number | null; + liquidation_date?: number | null; + status?: OrgStatus | null; + } | null; + okato?: string | null; + oktmo?: string | null; + okpo?: string | null; + okogu?: string | null; + okfs?: string | null; + okved?: string | null; + employee_count?: number | null; + founders?: string[] | null; + managers?: string[] | null; + capital?: string | null; + licenses?: string[] | null; + phones?: string[] | null; + emails?: string[] | null; +} diff --git a/backend/src/modules/data-enrichment/types/dadata-suggestion.ts b/backend/src/modules/data-enrichment/types/dadata-suggestion.ts new file mode 100644 index 0000000..6993016 --- /dev/null +++ b/backend/src/modules/data-enrichment/types/dadata-suggestion.ts @@ -0,0 +1,5 @@ +export interface DadataSuggestion { + value?: string | null; + unrestricted_value?: string | null; + data?: T | null; +} diff --git a/backend/src/modules/data-enrichment/types/dadata-suggestions.ts b/backend/src/modules/data-enrichment/types/dadata-suggestions.ts new file mode 100644 index 0000000..e30bfad --- /dev/null +++ b/backend/src/modules/data-enrichment/types/dadata-suggestions.ts @@ -0,0 +1,5 @@ +import { DadataSuggestion } from './dadata-suggestion'; + +export interface DadataSuggestions { + suggestions?: DadataSuggestion[] | null; +} diff --git a/backend/src/modules/data-enrichment/types/index.ts b/backend/src/modules/data-enrichment/types/index.ts new file mode 100644 index 0000000..2c476fa --- /dev/null +++ b/backend/src/modules/data-enrichment/types/index.ts @@ -0,0 +1,9 @@ +export * from './bank-requisites'; +export * from './dadata-bank-requisites'; +export * from './dadata-org-requisites'; +export * from './dadata-suggestion'; +export * from './dadata-suggestions'; +export * from './phone-user-info-fincalculator'; +export * from './phone-user-info-geohelper'; +export * from './phone-user-info-sp-nova'; +export * from './phone-user-info'; diff --git a/backend/src/modules/data-enrichment/types/organization-requisites.ts b/backend/src/modules/data-enrichment/types/organization-requisites.ts new file mode 100644 index 0000000..4106920 --- /dev/null +++ b/backend/src/modules/data-enrichment/types/organization-requisites.ts @@ -0,0 +1,172 @@ +import { OrganizationRequisitesDto } from '../dto'; +import { OrgBranchType, OrgStatus, OrgType } from '../enums'; +import { DadataOrgRequisites } from './dadata-org-requisites'; +import { DadataSuggestion } from './dadata-suggestion'; + +export class OrganizationRequisites { + value?: string | null; + unrestrictedValue?: string | null; + inn?: string | null; + kpp?: string | null; + ogrn?: string | null; + type?: OrgType | null; + name?: { + full?: string | null; + short?: string | null; + } | null; + fio?: { + name?: string | null; + surname?: string | null; + patronymic?: string | null; + } | null; + management?: { + name?: string | null; + post?: string | null; + startDate?: Date | null; + } | null; + branchCount?: number | null; + branchType?: OrgBranchType | null; + address?: { + unrestrictedValue?: string | null; + } | null; + state?: { + registrationDate?: Date | null; + liquidationDate?: Date | null; + status?: OrgStatus | null; + } | null; + okato?: string | null; + oktmo?: string | null; + okpo?: string | null; + okogu?: string | null; + okfs?: string | null; + okved?: string | null; + employeeCount?: number | null; + founders?: string[] | null; + managers?: string[] | null; + capital?: string | null; + licenses?: string[] | null; + phones?: string[] | null; + emails?: string[] | null; + + constructor(data: Omit) { + this.value = data.value; + this.unrestrictedValue = data.unrestrictedValue; + this.inn = data.inn; + this.kpp = data.kpp; + this.ogrn = data.ogrn; + this.type = data.type; + this.name = data.name; + this.fio = data.fio; + this.management = data.management; + this.branchCount = data.branchCount; + this.branchType = data.branchType; + this.address = data.address; + this.state = data.state; + this.okato = data.okato; + this.oktmo = data.oktmo; + this.okpo = data.okpo; + this.okogu = data.okogu; + this.okfs = data.okfs; + this.okved = data.okved; + this.employeeCount = data.employeeCount; + this.founders = data.founders; + this.managers = data.managers; + this.capital = data.capital; + this.licenses = data.licenses; + this.phones = data.phones; + this.emails = data.emails; + } + + public static fromDadata(suggestion: DadataSuggestion): OrganizationRequisites { + const { data } = suggestion; + return new OrganizationRequisites({ + value: suggestion.value, + unrestrictedValue: suggestion.unrestricted_value, + inn: data?.inn, + kpp: data?.kpp, + ogrn: data?.ogrn, + type: data?.type, + name: data?.name ? { full: data.name.full_with_opf, short: data.name.short_with_opf } : undefined, + fio: data?.fio + ? { + name: data.fio.name, + surname: data.fio.surname, + patronymic: data.fio.patronymic, + } + : undefined, + management: data?.management + ? { + name: data.management.name, + post: data.management.post, + startDate: data.management.start_date ? new Date(data.management.start_date) : undefined, + } + : undefined, + branchCount: data?.branch_count, + branchType: data?.branch_type, + address: data?.address ? { unrestrictedValue: data.address.unrestricted_value } : undefined, + state: data?.state + ? { + registrationDate: data.state.liquidation_date ? new Date(data.state.liquidation_date) : undefined, + liquidationDate: data.state.liquidation_date ? new Date(data.state.liquidation_date) : undefined, + status: data.state.status, + } + : undefined, + okato: data?.okato, + oktmo: data?.oktmo, + okpo: data?.okpo, + okogu: data?.okogu, + okfs: data?.okfs, + okved: data?.okved, + employeeCount: data?.employee_count, + founders: data?.founders, + managers: data?.managers, + capital: data?.capital, + licenses: data?.licenses, + phones: data?.phones, + emails: data?.emails, + }); + } + + toDto(): OrganizationRequisitesDto { + return { + value: this.value, + unrestrictedValue: this.unrestrictedValue, + inn: this.inn, + kpp: this.kpp, + ogrn: this.ogrn, + type: this.type, + name: this.name, + fio: this.fio, + management: this.management + ? { + name: this.management.name, + post: this.management.post, + startDate: this.management.startDate?.toISOString(), + } + : undefined, + branchCount: this.branchCount, + branchType: this.branchType, + address: this.address, + state: this.state + ? { + registrationDate: this.state.registrationDate?.toISOString(), + liquidationDate: this.state.liquidationDate?.toISOString(), + status: this.state.status, + } + : undefined, + okato: this.okato, + oktmo: this.oktmo, + okpo: this.okpo, + okogu: this.okogu, + okfs: this.okfs, + okved: this.okved, + employeeCount: this.employeeCount, + founders: this.founders, + managers: this.managers, + capital: this.capital, + licenses: this.licenses, + phones: this.phones, + emails: this.emails, + }; + } +} diff --git a/backend/src/modules/data-enrichment/types/phone-user-info-fincalculator.ts b/backend/src/modules/data-enrichment/types/phone-user-info-fincalculator.ts new file mode 100644 index 0000000..8e57c06 --- /dev/null +++ b/backend/src/modules/data-enrichment/types/phone-user-info-fincalculator.ts @@ -0,0 +1,25 @@ +// https://fincalculator.ru/telefon/region-po-nomeru +export class PhoneUserInfoFincalculator { + phone?: string | null; + country?: string | null; + region?: string | null; + subRegion?: string | null; + locality?: string | null; + operator?: string | null; + // utc offset in hours + timeZone?: number | null; +} + +/* + Successful response example: + + { + "phone": "+7 (921) 712-26-91", + "country": "Россия", + "region": "Калининградская область", + "subRegion": "", + "locality": "", + "operator": "МегаФон", + "timeZone": 2 + } +*/ diff --git a/backend/src/modules/data-enrichment/types/phone-user-info-geohelper.ts b/backend/src/modules/data-enrichment/types/phone-user-info-geohelper.ts new file mode 100644 index 0000000..00c901a --- /dev/null +++ b/backend/src/modules/data-enrichment/types/phone-user-info-geohelper.ts @@ -0,0 +1,113 @@ +interface Result { + dataSource?: string | null; + abcDefCode?: number | null; + rangeStart?: number | null; + rangeEnd?: number | null; + providerName?: string | null; + region?: { + // in seconds + timezoneOffset?: number | null; + countryIso?: string | null; + id?: number | null; + name?: string | null; + codes?: { + iso?: string | null; + fias?: string | null; + fips?: string | null; + kladr?: string | null; + }; + localityType?: { + code?: string | null; + localizedNamesShort?: { + en?: string | null; + kz?: string | null; + ru?: string | null; + }; + localizedNames?: { + en?: string | null; + kz?: string | null; + ru?: string | null; + }; + }; + timezone?: string | null; + countryId?: number | null; + externalIds?: { + fias?: string | null; + fias_gar?: string | null; + geonames?: string | null; + }; + localizedNames?: { + en?: string | null; + ru?: string | null; + }; + }; + phoneParts?: { + countryCode?: string | null; + code?: string | null; + number?: string | null; + }; +} + +// https://geohelper.info/ru/doc/api/#get/api/v1/phone-data +export interface PhoneUserInfoGeohelper { + success?: boolean | null; + language?: string | null; + result?: Result | null; +} + +/* + Successful response example: + + { + "success": true, + "language": "ru", + "result": { + "dataSource": "rossvyaz", + "abcDefCode": 0, + "rangeStart": 79217100000, + "rangeEnd": 79217129999, + "providerName": "ПАО \"МегаФон\"", + "region": { + "timezoneOffset": 7200, + "countryIso": "RU", + "id": 30, + "name": "Калининградская", + "codes": { + "iso": "RU-KGD", + "fias": "39", + "fips": "23", + "kladr": "3900000000000" + }, + "localityType": { + "code": "region-oblast", + "localizedNamesShort": { + "en": "obl.", + "kz": "обл.", + "ru": "обл." + }, + "localizedNames": { + "en": "oblast", + "kz": "облысы", + "ru": "область" + } + }, + "timezone": "Europe/Kaliningrad", + "countryId": 189, + "externalIds": { + "fias": "90c7181e-724f-41b3-b6c6-bd3ec7ae3f30", + "fias_gar": "634779", + "geonames": "554230" + }, + "localizedNames": { + "en": "Kaliningradskaya", + "ru": "Калининградская" + } + }, + "phoneParts": { + "countryCode": "7", + "code": "921", + "number": "7122691" + } + } + } +*/ diff --git a/backend/src/modules/data-enrichment/types/phone-user-info-sp-nova.ts b/backend/src/modules/data-enrichment/types/phone-user-info-sp-nova.ts new file mode 100644 index 0000000..91175c7 --- /dev/null +++ b/backend/src/modules/data-enrichment/types/phone-user-info-sp-nova.ts @@ -0,0 +1,30 @@ +export interface UserInfoContentSpNova { + operator?: string | null; + country?: string | null; + region?: string | null; + district?: string | null; + city?: string | null; + // hh:mm:ss + time?: string | null; + // in UTC+0200 format + timezone?: string | null; +} + +// https://sp1-nova.ru/api/phones-data/ +export type PhoneUserInfoSpNova = Record; + +/* + Successful response example: + + { + "+79217122691": { + "operator": "Мегафон", + "country": "Россия", + "region": "Калининградская область", + "district": null, + "city": null, + "time": "11:04:16", + "timezone": "UTC+0200" + } + } +*/ diff --git a/backend/src/modules/data-enrichment/types/phone-user-info.ts b/backend/src/modules/data-enrichment/types/phone-user-info.ts new file mode 100644 index 0000000..00a1aaa --- /dev/null +++ b/backend/src/modules/data-enrichment/types/phone-user-info.ts @@ -0,0 +1,24 @@ +import { PhoneUserInfoDto } from '../dto'; + +export class PhoneUserInfo { + utcOffset: number | null; + country: string | null; + region: string | null; + city: string | null; + + constructor(data: Omit) { + this.utcOffset = data.utcOffset; + this.country = data.country; + this.region = data.region; + this.city = data.city; + } + + toDto(): PhoneUserInfoDto { + return { + utcOffset: this.utcOffset, + country: this.country, + region: this.region, + city: this.city, + }; + } +} diff --git a/backend/src/modules/documents/common/errors/document-template.error.ts b/backend/src/modules/documents/common/errors/document-template.error.ts new file mode 100644 index 0000000..b163a56 --- /dev/null +++ b/backend/src/modules/documents/common/errors/document-template.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class DocumentTemplateError extends ServiceError { + constructor(message = 'Wrong template') { + super({ errorCode: 'document.template.wrong_template', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/modules/documents/common/errors/index.ts b/backend/src/modules/documents/common/errors/index.ts new file mode 100644 index 0000000..a6ad6ef --- /dev/null +++ b/backend/src/modules/documents/common/errors/index.ts @@ -0,0 +1 @@ +export * from './document-template.error'; diff --git a/backend/src/modules/documents/common/index.ts b/backend/src/modules/documents/common/index.ts new file mode 100644 index 0000000..f72bc43 --- /dev/null +++ b/backend/src/modules/documents/common/index.ts @@ -0,0 +1 @@ +export * from './errors'; diff --git a/backend/src/modules/documents/config/documents.config.ts b/backend/src/modules/documents/config/documents.config.ts new file mode 100644 index 0000000..ab45fbc --- /dev/null +++ b/backend/src/modules/documents/config/documents.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +export interface DocumentsConfig { + host: string; +} + +export default registerAs( + 'documents', + (): DocumentsConfig => ({ + host: process.env.DOCUMENTS_HOST, + }), +); diff --git a/backend/src/modules/documents/config/index.ts b/backend/src/modules/documents/config/index.ts new file mode 100644 index 0000000..2b64df1 --- /dev/null +++ b/backend/src/modules/documents/config/index.ts @@ -0,0 +1 @@ +export * from './documents.config'; diff --git a/backend/src/modules/documents/document-generation/document-generation.controller.ts b/backend/src/modules/documents/document-generation/document-generation.controller.ts new file mode 100644 index 0000000..8362e3a --- /dev/null +++ b/backend/src/modules/documents/document-generation/document-generation.controller.ts @@ -0,0 +1,36 @@ +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FileLinkDto } from '@/CRM/Service/FileLink/FileLinkDto'; + +import { CheckDocumentDto, CheckDocumentResultDto, CreateDocumentDto } from './dto'; +import { DocumentGenerationService } from './document-generation.service'; + +@ApiTags('crm/documents/entities') +@Controller('/crm/documents') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class DocumentGenerationController { + constructor(private readonly service: DocumentGenerationService) {} + + @ApiOkResponse({ description: 'Check entity placeholders', type: CheckDocumentResultDto }) + @Get('check') + public async check( + @CurrentAuth() { accountId, user }: AuthData, + @Query() dto: CheckDocumentDto, + ): Promise { + return await this.service.check(accountId, user, dto); + } + + @ApiCreatedResponse({ description: 'Generated document', type: [FileLinkDto] }) + @Post('create') + public async create( + @CurrentAuth() { account, user }: AuthData, + @Body() dto: CreateDocumentDto, + ): Promise { + return await this.service.create(account, user, dto); + } +} diff --git a/backend/src/modules/documents/document-generation/document-generation.service.ts b/backend/src/modules/documents/document-generation/document-generation.service.ts new file mode 100644 index 0000000..b7ab8f8 --- /dev/null +++ b/backend/src/modules/documents/document-generation/document-generation.service.ts @@ -0,0 +1,499 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { lastValueFrom } from 'rxjs'; + +import PizZip from 'pizzip'; +import Docxtemplater, { DXT } from 'docxtemplater'; +import InspectModule from 'docxtemplater/js/inspect-module'; +import expressionParser from 'docxtemplater/expressions.js'; +import FormData from 'form-data'; + +import { DateUtil, FileLinkSource, isUnique, NotFoundError, NumberUtil } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { UserService } from '@/modules/iam/user/user.service'; +import { AccountSettingsService } from '@/modules/iam/account-settings/account-settings.service'; +import { MimeType } from '@/modules/storage/enums/mime-type.enum'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; +import { OrderService } from '@/modules/inventory/order/services/order.service'; +import { OrderHelper } from '@/modules/inventory/order/helper/order.helper'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { FieldOption } from '@/modules/entity/entity-field/field-option/entities/field-option.entity'; +import { FieldValue } from '@/modules/entity/entity-field/field-value/entities/field-value.entity'; +import { FieldPayloadChecklistItem } from '@/modules/entity/entity-field/field-value/types'; +import { Field } from '@/modules/entity/entity-field/field/entities/field.entity'; +import { FieldOptionService } from '@/modules/entity/entity-field/field-option/field-option.service'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value/field-value.service'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; + +import { EntityTypeService } from '@/CRM/entity-type/entity-type.service'; +import { FileLink } from '@/CRM/Model/FileLink/FileLink'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { FileLinkDto } from '@/CRM/Service/FileLink/FileLinkDto'; +import { FileLinkService } from '@/CRM/Service/FileLink/FileLinkService'; + +import { DocumentsConfig } from '../config'; +import { DocumentTemplateError } from '../common'; +import { DocumentTemplateService } from '../document-template'; +import { DocumentType, RussianCase } from './enums'; +import { CheckDocumentResultDto, CheckDocumentMissingFieldDto, CreateDocumentDto, CheckDocumentDto } from './dto'; +import { RussianName } from './types'; + +const CONVERT_DOCX_TO_PDF_PATH = 'convert/docx2pdf'; + +const SystemFields = { + currentDate: 'currentDate', + documentNumber: 'documentNumber', +}; +const OrderFields = { + _name: 'order', + number: 'number', + total: 'total', + currency: 'currency', + products: { + _name: 'products', + number: 'number', + name: 'name', + price: 'price', + currency: 'currency', + discount: 'discount', + tax: 'tax', + quantity: 'quantity', + amount: 'amount', + }, +}; + +enum EntityTypeField { + Name = 'name', + Owner = 'owner', +} + +const inclineName = (input: string, caseName: RussianCase) => { + if (!input) return input; + + return RussianName.fromFullName(input).getFullName(caseName); +}; + +const numberToWord = (input: string, language: string) => { + if (!input) return input; + + const value = Number(input); + if (!value) return input; + + return NumberUtil.toWord(value, { language }); +}; + +const numberToCurrency = (input: string, currency: string) => { + if (!input) return input; + + const value = Number(input); + if (!value) return input; + + return NumberUtil.toWord(value, { language: 'ru', currency }); +}; + +@Injectable() +export class DocumentGenerationService { + private _documentsHost: string; + + constructor( + private readonly configService: ConfigService, + private readonly httpService: HttpService, + private readonly accountSettingsService: AccountSettingsService, + private readonly userService: UserService, + private readonly storageService: StorageService, + @Inject(forwardRef(() => EntityService)) + private readonly entityService: EntityService, + private readonly entityTypeService: EntityTypeService, + private readonly fieldService: FieldService, + private readonly fieldValueService: FieldValueService, + private readonly fieldOptionService: FieldOptionService, + private readonly fileLinkService: FileLinkService, + private readonly orderService: OrderService, + private readonly documentTemplateService: DocumentTemplateService, + ) { + this._documentsHost = this.configService.get('documents').host; + } + + async check(accountId: number, user: User, dto: CheckDocumentDto) { + try { + const template = await this.documentTemplateService.getById(accountId, dto.templateId); + const content = await this.getTemplateContent(accountId, template.id); + const zip = new PizZip(content); + const inspectModule = new InspectModule(); + new Docxtemplater(zip, { modules: [inspectModule] }); + const parts = inspectModule.getAllStructuredTags(); + const allTags = this.getTemplateAllTags(parts, null); + + const data = await this.getDataForGeneration({ + accountId, + user, + documentNumber: template.createdCount + 1, + entityId: dto.entityId, + orderId: dto.orderId, + }); + const dataTags = this.getDataAllTags(null, data); + + const missingTags = allTags.filter((t) => !dataTags.includes(t)); + const missingFields: CheckDocumentMissingFieldDto[] = []; + const removeTags: string[] = []; + for (const tag of missingTags) { + const tagParts = tag.split('.'); + const entityType = await this.entityTypeService.findOne(accountId, { name: tagParts[0] }); + if (entityType) { + const field = await this.fieldService.findOne({ accountId, entityTypeId: entityType.id, name: tagParts[1] }); + if (field) { + removeTags.push(tag); + missingFields.push(new CheckDocumentMissingFieldDto({ entityTypeId: entityType.id, field: field.toDto() })); + } + } + } + + const isCorrect = missingTags.length === 0 && missingFields.length === 0; + return new CheckDocumentResultDto({ isCorrect, missingFields, missingTags }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e: unknown) { + throw new DocumentTemplateError(); + } + } + + private getTemplateAllTags(parts: Docxtemplater.DXT.Part[], prefix: string | null): string[] { + const tags: string[] = []; + for (const part of parts) { + if (part.type === 'placeholder') { + const tag = `${prefix ? `${prefix}.` : ''}${part.value.split(' ')[0]}`; + tags.push(tag); + if (part.subparsed?.length > 0) { + tags.push(...this.getTemplateAllTags(part.subparsed, tag)); + } + } + } + return tags.filter(isUnique); + } + + async create(account: Account, user: User, dto: CreateDocumentDto): Promise { + try { + let template = await this.documentTemplateService.getById(account.id, dto.templateId); + const content = await this.getTemplateContent(account.id, template.id); + const data = await this.getDataForGeneration({ + accountId: account.id, + user, + documentNumber: template.createdCount + 1, + entityId: dto.entityId, + orderId: dto.orderId, + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (expressionParser as any).filters.case = inclineName; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (expressionParser as any).filters.words = numberToWord; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (expressionParser as any).filters.currency = numberToCurrency; + + const zip = new PizZip(content); + const doc = new Docxtemplater(zip, { + paragraphLoop: true, + linebreaks: true, + parser: expressionParser, + nullGetter: this.nullGetter, + }); + doc.render(data); + const docBuffer = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' }); + + template = await this.documentTemplateService.incrementCreatedCount(template); + + const fileLinks: FileLinkDto[] = []; + const documentName = `${template.name}_${template.createdCount}`; + if (dto.types.includes(DocumentType.DOCX)) { + const file = new StorageFile( + `${documentName}.${DocumentType.DOCX}`, + MimeType.DOCX, + docBuffer.byteLength, + docBuffer, + ); + const fileInfo = await this.storageService.storeCommonFile({ accountId: account.id, userId: user.id, file }); + if (fileInfo) { + const docLink = await this.fileLinkService.addFile( + account, + FileLinkSource.ENTITY_DOCUMENT, + dto.entityId, + fileInfo.id, + ); + if (docLink) fileLinks.push(docLink); + } + } + + if (dto.types.includes(DocumentType.PDF)) { + const pdfBuffer = await this.convertDocxToPdf(docBuffer, documentName); + + const file = new StorageFile( + `${documentName}.${DocumentType.PDF}`, + MimeType.PDF, + pdfBuffer.byteLength, + pdfBuffer, + ); + const fileInfo = await this.storageService.storeCommonFile({ accountId: account.id, userId: user.id, file }); + if (fileInfo) { + const pdfLink = await this.fileLinkService.addFile( + account, + FileLinkSource.ENTITY_DOCUMENT, + dto.entityId, + fileInfo.id, + ); + if (pdfLink) fileLinks.push(pdfLink); + } + } + + return fileLinks; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e: unknown) { + throw new DocumentTemplateError(); + } + } + + private async convertDocxToPdf(docBuffer: Buffer, documentName: string): Promise { + const form = new FormData(); + form.append('file', docBuffer, `${documentName}.${DocumentType.DOCX}`); + const response = await lastValueFrom( + this.httpService.post(CONVERT_DOCX_TO_PDF_PATH, form, { + baseURL: this._documentsHost, + headers: { + ...form.getHeaders(), + }, + responseType: 'stream', + }), + ); + + const pdfBuffer = await new Promise((resolve, reject) => { + const chunks = []; + response.data.on('data', (chunk) => chunks.push(chunk)); + response.data.on('end', () => resolve(Buffer.concat(chunks))); + response.data.on('error', reject); + }); + return pdfBuffer; + } + + private getDataAllTags(prefix: string | null, data: unknown): string[] { + const tagNames: string[] = []; + for (const [key, value] of Object.entries(data)) { + const tag = `${prefix ? `${prefix}.` : ''}${key}`; + tagNames.push(tag); + if (Array.isArray(value)) { + for (let idx = 0; idx < value.length; idx++) { + tagNames.push(...this.getDataAllTags(tag, value[idx])); + tagNames.push(...this.getDataAllTags(`${tag}[${idx}]`, value[idx])); + } + } else if (typeof value === 'object') { + tagNames.push(...this.getDataAllTags(tag, value)); + } + } + return tagNames.filter(isUnique); + } + + async getDataForGeneration({ + accountId, + user, + documentNumber, + entityId, + orderId, + }: { + accountId: number; + user?: User | null; + documentNumber?: number | null; + entityId: number; + orderId?: number | null; + }) { + const mainEntity = await this.entityService.findOne(accountId, { entityId }); + const linkedEntities = await this.entityService.findFirstLinkedEntityByType(accountId, entityId); + const entities = [mainEntity, ...linkedEntities]; + + const entityTypes = await this.getEntityTypesWithUniqueName(accountId); + const fieldOptionCache: FieldOption[] = []; + const data = {}; + data[`${SystemFields.currentDate}`] = DateUtil.formatPreset(DateUtil.now(), 'date'); + if (documentNumber) { + data[`${SystemFields.documentNumber}`] = documentNumber; + } + for (const entity of entities) { + const entityType = entityTypes.find((et) => et.id === entity.entityTypeId); + const entityTypeName = this.removeSpecialChars(entityType.name); + data[`${entityTypeName}`] = {}; + const fields = await this.getFieldsWithUniqueName(accountId, entity.entityTypeId); + const fieldValues = await this.fieldValueService.findMany({ accountId, entityId: entity.id }); + const owner = await this.userService.findOne({ accountId, id: entity.responsibleUserId }); + data[`${entityTypeName}`][`${EntityTypeField.Name}`] = entity.name; + data[`${entityTypeName}`][`${EntityTypeField.Owner}`] = owner.fullName; + for (const fieldValue of fieldValues) { + const field = fields.find((f) => f.id === fieldValue.fieldId); + if (field) { + const fieldValueObj = await this.getFieldValue(accountId, field, fieldValue, fieldOptionCache); + if (fieldValueObj) { + const fieldName = this.removeSpecialChars(field.name); + data[`${entityTypeName}`][`${fieldName}`] = fieldValueObj; + } + } + } + } + if (orderId && user) { + const order = await this.orderService.findOne(accountId, user, { orderId }, { expand: ['items'] }); + if (order) { + const accountSettings = await this.accountSettingsService.getOne(accountId); + const currencyName = this.getCurrencyName(order.currency, accountSettings.language); + const orderData = {}; + orderData[`${OrderFields.number}`] = order.orderNumber; + orderData[`${OrderFields.total}`] = order.totalAmount; + orderData[`${OrderFields.currency}`] = currencyName; + const productsData = []; + for (let idx = 0; idx < order.items.length; idx++) { + const productData = {}; + productData[`${OrderFields.products.number}`] = idx + 1; + productData[`${OrderFields.products.name}`] = order.items[idx].product.name; + productData[`${OrderFields.products.price}`] = order.items[idx].unitPrice; + productData[`${OrderFields.products.currency}`] = currencyName; + productData[`${OrderFields.products.discount}`] = order.items[idx].discount; + productData[`${OrderFields.products.tax}`] = order.items[idx].tax; + productData[`${OrderFields.products.quantity}`] = order.items[idx].quantity; + productData[`${OrderFields.products.amount}`] = OrderHelper.calcAmount(order.items[idx], order.taxIncluded); + productsData.push(productData); + } + orderData[`${OrderFields.products._name}`] = productsData; + data[`${OrderFields._name}`] = orderData; + } + } + return data; + } + + private getCurrencyName(currencyCode: string, locale = 'en-US'): string { + const names = new Intl.DisplayNames(locale, { type: 'currency' }); + return names.of(currencyCode); + } + + private async getTemplateContent(accountId: number, templateId: number) { + const fileLinks = await this.fileLinkService.findFileLinks(accountId, FileLinkSource.DOCUMENT_TEMPLATE, templateId); + if (fileLinks.length === 0) { + throw NotFoundError.withMessage(FileLink, `with type ${FileLinkSource.DOCUMENT_TEMPLATE} is not found`); + } + + const { content } = await this.storageService.getFile({ fileId: fileLinks[0].fileId, accountId }); + + return content; + } + + private async getEntityTypesWithUniqueName(accountId: number): Promise<{ id: number; name: string }[]> { + const entityTypes = await this.entityTypeService.findMany(accountId); + + const countNames = entityTypes.reduce((acc, entityType) => { + acc[entityType.name] = (acc[entityType.name] || 0) + 1; + return acc; + }, {}); + + return entityTypes.map((entityType) => { + if (countNames[entityType.name] > 1) { + entityType.name = entityType.name + entityType.id; + } + return entityType; + }); + } + + private async getFieldsWithUniqueName(accountId: number, entityTypeId: number): Promise { + const fields = await this.fieldService.findMany({ accountId, entityTypeId }); + + const countNames = fields.reduce((acc, field) => { + acc[field.name] = (acc[field.name] || 0) + 1; + return acc; + }, {}); + + return fields.map((field) => { + if (countNames[field.name] > 1) { + field.name = field.name + field.id; + } + return field; + }); + } + + private removeSpecialChars(input: string): string { + return input.replace(/[^\p{L}\p{N}]/gu, ''); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private nullGetter(_part: DXT.Part) { + return ''; + } + + private async getFieldValue( + accountId: number, + field: Field, + fieldValue: FieldValue, + fieldOptionCache: FieldOption[], + ) { + switch (field.type) { + case FieldType.Text: + case FieldType.RichText: + case FieldType.Link: + return fieldValue.getValue(); + case FieldType.Number: + case FieldType.Value: + case FieldType.Formula: + return fieldValue.getValue(); + case FieldType.MultiText: + case FieldType.Phone: + case FieldType.Email: + case FieldType.File: + return fieldValue.getValue()?.join(', '); + case FieldType.Switch: + return fieldValue.getValue() ? 'Yes' : 'No'; + case FieldType.Date: + return DateUtil.formatPreset(fieldValue.getValue(), 'date'); + case FieldType.Select: + case FieldType.ColoredSelect: { + let options = fieldOptionCache.filter((fo) => fo.fieldId === field.id); + if (options.length === 0) { + options = await this.fieldOptionService.findMany({ accountId, fieldId: field.id }); + fieldOptionCache.push(...options); + } + const fvOptionId = fieldValue.getValue(); + const option = options.find((o) => o.id === fvOptionId); + return option ? option.label : null; + } + case FieldType.MultiSelect: + case FieldType.ColoredMultiSelect: + case FieldType.CheckedMultiSelect: { + let multiOptions = fieldOptionCache.filter((fo) => fo.fieldId === field.id); + if (!multiOptions || multiOptions.length === 0) { + multiOptions = await this.fieldOptionService.findMany({ accountId, fieldId: field.id }); + fieldOptionCache.push(...multiOptions); + } + const fvOptionIds = fieldValue.getValue(); + const fieldOptions = fvOptionIds ? multiOptions.filter((o) => fvOptionIds.includes(o.id)) : null; + return fieldOptions ? fieldOptions.map((o) => o.label).join(', ') : null; + } + case FieldType.Participant: { + const fvUserId = fieldValue.getValue(); + const user = await this.userService.findOne({ accountId, id: fvUserId }); + return user ? user.fullName : null; + } + case FieldType.Participants: { + const fvUserIds = fieldValue.getValue(); + if (fvUserIds) { + const users = await Promise.all( + fvUserIds.map(async (id) => await this.userService.findOne({ accountId, id })), + ); + return users.length > 0 ? users.map((u) => u.fullName).join(', ') : null; + } + return null; + } + case FieldType.Checklist: { + const fvChecklist = fieldValue.getValue(); + return fvChecklist + ? fvChecklist + .filter((v) => v.checked) + .map((v) => v.text) + .join(', ') + : null; + } + } + } +} diff --git a/backend/src/modules/documents/document-generation/dto/check-document-missing-field.dto.ts b/backend/src/modules/documents/document-generation/dto/check-document-missing-field.dto.ts new file mode 100644 index 0000000..42e5f3e --- /dev/null +++ b/backend/src/modules/documents/document-generation/dto/check-document-missing-field.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { FieldDto } from '@/modules/entity/entity-field/field/dto/field.dto'; + +export class CheckDocumentMissingFieldDto { + @ApiProperty() + entityTypeId: number; + + @ApiProperty({ type: FieldDto }) + field: FieldDto; + + constructor({ entityTypeId, field }: CheckDocumentMissingFieldDto) { + this.entityTypeId = entityTypeId; + this.field = field; + } +} diff --git a/backend/src/modules/documents/document-generation/dto/check-document-result.dto.ts b/backend/src/modules/documents/document-generation/dto/check-document-result.dto.ts new file mode 100644 index 0000000..0bc01be --- /dev/null +++ b/backend/src/modules/documents/document-generation/dto/check-document-result.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { CheckDocumentMissingFieldDto } from './check-document-missing-field.dto'; + +export class CheckDocumentResultDto { + @ApiProperty() + isCorrect: boolean; + + @ApiProperty({ type: [CheckDocumentMissingFieldDto] }) + missingFields: CheckDocumentMissingFieldDto[]; + + @ApiProperty({ type: [String] }) + missingTags: string[]; + + constructor({ isCorrect, missingFields, missingTags }: CheckDocumentResultDto) { + this.isCorrect = isCorrect; + this.missingFields = missingFields; + this.missingTags = missingTags; + } +} diff --git a/backend/src/modules/documents/document-generation/dto/check-document.dto.ts b/backend/src/modules/documents/document-generation/dto/check-document.dto.ts new file mode 100644 index 0000000..8ff35d1 --- /dev/null +++ b/backend/src/modules/documents/document-generation/dto/check-document.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class CheckDocumentDto { + @ApiProperty() + @IsNumber() + entityId: number; + + @ApiProperty() + @IsNumber() + templateId: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + orderId?: number | null; +} diff --git a/backend/src/modules/documents/document-generation/dto/create-document.dto.ts b/backend/src/modules/documents/document-generation/dto/create-document.dto.ts new file mode 100644 index 0000000..041e42a --- /dev/null +++ b/backend/src/modules/documents/document-generation/dto/create-document.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { DocumentType } from '../enums'; +import { CheckDocumentDto } from './check-document.dto'; + +export class CreateDocumentDto extends CheckDocumentDto { + @ApiProperty() + @IsArray() + types: DocumentType[]; +} diff --git a/backend/src/modules/documents/document-generation/dto/index.ts b/backend/src/modules/documents/document-generation/dto/index.ts new file mode 100644 index 0000000..8a7a061 --- /dev/null +++ b/backend/src/modules/documents/document-generation/dto/index.ts @@ -0,0 +1,4 @@ +export * from './check-document-missing-field.dto'; +export * from './check-document-result.dto'; +export * from './check-document.dto'; +export * from './create-document.dto'; diff --git a/backend/src/modules/documents/document-generation/enums/document-type.enum.ts b/backend/src/modules/documents/document-generation/enums/document-type.enum.ts new file mode 100644 index 0000000..771f7a2 --- /dev/null +++ b/backend/src/modules/documents/document-generation/enums/document-type.enum.ts @@ -0,0 +1,4 @@ +export enum DocumentType { + DOCX = 'docx', + PDF = 'pdf', +} diff --git a/backend/src/modules/documents/document-generation/enums/index.ts b/backend/src/modules/documents/document-generation/enums/index.ts new file mode 100644 index 0000000..de9d0dc --- /dev/null +++ b/backend/src/modules/documents/document-generation/enums/index.ts @@ -0,0 +1,2 @@ +export * from './document-type.enum'; +export * from './russian-case.enum'; diff --git a/backend/src/modules/documents/document-generation/enums/russian-case.enum.ts b/backend/src/modules/documents/document-generation/enums/russian-case.enum.ts new file mode 100644 index 0000000..3046952 --- /dev/null +++ b/backend/src/modules/documents/document-generation/enums/russian-case.enum.ts @@ -0,0 +1,8 @@ +export enum RussianCase { + NOMINATIVE = 'nominative', + GENITIVE = 'genitive', + DATIVE = 'dative', + ACCUSATIVE = 'accusative', + INSTRUMENTAL = 'instrumental', + PREPOSITIONAL = 'prepositional', +} diff --git a/backend/src/modules/documents/document-generation/index.ts b/backend/src/modules/documents/document-generation/index.ts new file mode 100644 index 0000000..3ed349c --- /dev/null +++ b/backend/src/modules/documents/document-generation/index.ts @@ -0,0 +1,4 @@ +export * from './document-generation.controller'; +export * from './document-generation.service'; +export * from './dto'; +export * from './enums'; diff --git a/backend/src/modules/documents/document-generation/types/index.ts b/backend/src/modules/documents/document-generation/types/index.ts new file mode 100644 index 0000000..ebcf229 --- /dev/null +++ b/backend/src/modules/documents/document-generation/types/index.ts @@ -0,0 +1 @@ +export * from './russian-name'; diff --git a/backend/src/modules/documents/document-generation/types/russian-name.ts b/backend/src/modules/documents/document-generation/types/russian-name.ts new file mode 100644 index 0000000..602eec1 --- /dev/null +++ b/backend/src/modules/documents/document-generation/types/russian-name.ts @@ -0,0 +1,30 @@ +import { incline } from 'lvovich'; + +import { type RussianCase } from '../enums'; + +export class RussianName { + first?: string | null; + middle?: string | null; + last?: string | null; + + constructor(last?: string | null, first?: string | null, middle?: string | null) { + this.first = first; + this.middle = middle; + this.last = last; + } + + public static fromFullName(fullName: string): RussianName { + const [last, first, middle] = fullName.split(' '); + + return new RussianName(last, first, middle); + } + + public getFullName(russianCase?: RussianCase): string { + if (russianCase) { + const { last, first, middle } = incline({ last: this.last, first: this.first, middle: this.middle }, russianCase); + return `${last}${first ? ` ${first}` : ''}${middle ? ` ${middle}` : ''}`; + } + + return `${this.last}${this.first ? ` ${this.first}` : ''}${this.middle ? ` ${this.middle}` : ''}`; + } +} diff --git a/backend/src/modules/documents/document-template/document-template.controller.ts b/backend/src/modules/documents/document-template/document-template.controller.ts new file mode 100644 index 0000000..89f9b2b --- /dev/null +++ b/backend/src/modules/documents/document-template/document-template.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { DocumentTemplateDto, CreateDocumentTemplateDto, DocumentTemplateInfo, UpdateDocumentTemplateDto } from './dto'; +import { DocumentTemplateService } from './document-template.service'; + +@ApiTags('crm/documents/templates') +@Controller('/crm/documents/templates') +@JwtAuthorized({ prefetch: { account: true } }) +export class DocumentTemplateController { + constructor(private readonly service: DocumentTemplateService) {} + + @ApiCreatedResponse({ description: 'Create document template', type: DocumentTemplateDto }) + @Post() + public async create( + @CurrentAuth() { account, userId }: AuthData, + @Body() dto: CreateDocumentTemplateDto, + ): Promise { + return await this.service.create(account, userId, dto); + } + + @ApiOkResponse({ description: 'Get document templates', type: [DocumentTemplateDto] }) + @Get() + public async getMany(@CurrentAuth() { account }: AuthData): Promise { + return await this.service.getDtoByAccount(account); + } + + @ApiOkResponse({ description: 'Get document template', type: DocumentTemplateDto }) + @Get(':id') + public async getOne( + @CurrentAuth() { account }: AuthData, + @Param('id', ParseIntPipe) id: number, + ): Promise { + return await this.service.getDtoById(account, id); + } + + @ApiCreatedResponse({ description: 'Document templates info', type: [DocumentTemplateInfo] }) + @Get('entity-type/:entityTypeId') + public async getAccessibleTemplates( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + ): Promise { + return await this.service.getAccessibleTemplates(accountId, userId, entityTypeId); + } + + @ApiCreatedResponse({ description: 'Document template', type: DocumentTemplateDto }) + @Put(':id') + public async update( + @CurrentAuth() { account }: AuthData, + @Param('id', ParseIntPipe) id: number, + @Body() dto: UpdateDocumentTemplateDto, + ): Promise { + return await this.service.update(account, id, dto); + } + + @ApiOkResponse({ description: 'Delete document template' }) + @Delete(':id') + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('id', ParseIntPipe) id: number): Promise { + return await this.service.delete(accountId, id); + } +} diff --git a/backend/src/modules/documents/document-template/document-template.service.ts b/backend/src/modules/documents/document-template/document-template.service.ts new file mode 100644 index 0000000..556ba36 --- /dev/null +++ b/backend/src/modules/documents/document-template/document-template.service.ts @@ -0,0 +1,149 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { FileLinkSource, NotFoundError } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { FileLinkService } from '@/CRM/Service/FileLink/FileLinkService'; + +import { DocumentTemplateDto, DocumentTemplateInfo, CreateDocumentTemplateDto, UpdateDocumentTemplateDto } from './dto'; +import { DocumentTemplate, DocumentTemplateAccess, DocumentTemplateEntityType } from './entities'; + +@Injectable() +export class DocumentTemplateService { + constructor( + @InjectRepository(DocumentTemplate) + private readonly repositoryTemplate: Repository, + @InjectRepository(DocumentTemplateAccess) + private readonly repositoryTemplateUser: Repository, + @InjectRepository(DocumentTemplateEntityType) + private readonly repositoryTemplateEntityType: Repository, + private readonly fileLinkService: FileLinkService, + ) {} + + public async create(account: Account, userId: number, dto: CreateDocumentTemplateDto): Promise { + const template = await this.repositoryTemplate.save(new DocumentTemplate(account.id, dto.name, userId)); + if (dto.fileId) { + await this.fileLinkService.processFiles(account.id, FileLinkSource.DOCUMENT_TEMPLATE, template.id, [dto.fileId]); + } + if (dto.accessibleBy?.length > 0) { + await this.repositoryTemplateUser.insert( + dto.accessibleBy.map((userId) => new DocumentTemplateAccess(account.id, template.id, userId)), + ); + } + if (dto.entityTypeIds?.length > 0) { + await this.repositoryTemplateEntityType.insert( + dto.entityTypeIds.map((entityTypeId) => new DocumentTemplateEntityType(account.id, template.id, entityTypeId)), + ); + } + return await this.getDtoByTemplate(account, template); + } + + public async getById(accountId: number, id: number): Promise { + const template = await this.repositoryTemplate.findOne({ where: { id, accountId } }); + if (!template) { + throw NotFoundError.withId(DocumentTemplate, id); + } + return template; + } + + public async getDtoById(account: Account, id: number): Promise { + const template = await this.getById(account.id, id); + return await this.getDtoByTemplate(account, template); + } + + public async getDtoByAccount(account: Account): Promise { + const templates = await this.repositoryTemplate.find({ + where: { accountId: account.id }, + order: { createdAt: 'ASC' }, + }); + return await Promise.all(templates.map((template) => this.getDtoByTemplate(account, template))); + } + + public async getAccessibleTemplates( + accountId: number, + userId: number, + entityTypeId: number, + ): Promise { + const templates = await this.repositoryTemplate + .createQueryBuilder('template') + .leftJoin(DocumentTemplateAccess, 'accessible', 'template.id = accessible.document_template_id') + .leftJoin(DocumentTemplateEntityType, 'et', 'template.id = et.document_template_id') + .where('template.accountId = :accountId', { accountId }) + .andWhere( + new Brackets((qb) => { + qb.where('template.createdBy = :userId', { userId }).orWhere('accessible.userId = :userId', { userId }); + }), + ) + .andWhere('et.entityTypeId = :entityTypeId', { entityTypeId }) + .orderBy('template.created_at', 'ASC') + .getMany(); + return templates.map((template) => new DocumentTemplateInfo(template.id, template.name)); + } + + public async getDtoByTemplate(account: Account, template: DocumentTemplate): Promise { + const files = await this.fileLinkService.getFileLinkDtos(account, FileLinkSource.DOCUMENT_TEMPLATE, template.id); + const accessibleBy = await this.repositoryTemplateUser.find({ + where: { documentTemplateId: template.id }, + select: ['userId'], + }); + const entityTypeIds = await this.repositoryTemplateEntityType.find({ + where: { documentTemplateId: template.id }, + select: ['entityTypeId'], + }); + return new DocumentTemplateDto( + template.id, + template.name, + template.createdBy, + files?.[0] ?? null, + accessibleBy.map((access) => access.userId), + entityTypeIds.map((entityType) => entityType.entityTypeId), + ); + } + + public async update(account: Account, id: number, dto: UpdateDocumentTemplateDto): Promise { + const template = await this.repositoryTemplate.findOne({ where: { id, accountId: account.id } }); + if (!template) { + throw NotFoundError.withId(DocumentTemplate, id); + } + template.update(dto.name); + await this.repositoryTemplate.save(template); + + await this.fileLinkService.processFiles( + account.id, + FileLinkSource.DOCUMENT_TEMPLATE, + id, + dto.fileId ? [dto.fileId] : [], + ); + + await this.repositoryTemplateUser.delete({ documentTemplateId: id }); + if (dto.accessibleBy?.length > 0) { + await this.repositoryTemplateUser.insert( + dto.accessibleBy.map((userId) => ({ documentTemplateId: id, userId, accountId: account.id })), + ); + } + + await this.repositoryTemplateEntityType.delete({ documentTemplateId: id }); + if (dto.entityTypeIds?.length > 0) { + await this.repositoryTemplateEntityType.insert( + dto.entityTypeIds.map((entityTypeId) => ({ documentTemplateId: id, entityTypeId, accountId: account.id })), + ); + } + + return await this.getDtoByTemplate(account, template); + } + + public async delete(accountId: number, id: number): Promise { + await this.fileLinkService.processFiles(accountId, FileLinkSource.DOCUMENT_TEMPLATE, id, []); + const result = await this.repositoryTemplate.delete({ id, accountId }); + if (result.affected === 0) { + throw NotFoundError.withId(DocumentTemplate, id); + } + } + + public async incrementCreatedCount(template: DocumentTemplate): Promise { + template.incrementCreatedCount(); + return await this.repositoryTemplate.save(template); + } +} diff --git a/backend/src/modules/documents/document-template/dto/create-document-template.dto.ts b/backend/src/modules/documents/document-template/dto/create-document-template.dto.ts new file mode 100644 index 0000000..93cfa86 --- /dev/null +++ b/backend/src/modules/documents/document-template/dto/create-document-template.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsOptional, IsString } from 'class-validator'; + +export class CreateDocumentTemplateDto { + @ApiProperty() + @IsString() + name: string; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + fileId: string | null; + + @ApiProperty() + @IsArray() + accessibleBy: number[]; + + @ApiProperty() + @IsArray() + entityTypeIds: number[]; +} diff --git a/backend/src/modules/documents/document-template/dto/document-template-info.dto.ts b/backend/src/modules/documents/document-template/dto/document-template-info.dto.ts new file mode 100644 index 0000000..8d7a5d8 --- /dev/null +++ b/backend/src/modules/documents/document-template/dto/document-template-info.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DocumentTemplateInfo { + @ApiProperty() + id: number; + + @ApiProperty() + name: string; + + constructor(id: number, name: string) { + this.id = id; + this.name = name; + } +} diff --git a/backend/src/modules/documents/document-template/dto/document-template.dto.ts b/backend/src/modules/documents/document-template/dto/document-template.dto.ts new file mode 100644 index 0000000..aafa5c7 --- /dev/null +++ b/backend/src/modules/documents/document-template/dto/document-template.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { FileLinkDto } from '@/CRM/Service/FileLink/FileLinkDto'; + +export class DocumentTemplateDto { + @ApiProperty() + id: number; + + @ApiProperty() + name: string; + + @ApiProperty() + createdBy: number; + + @ApiProperty({ nullable: true }) + file: FileLinkDto | null; + + @ApiProperty() + accessibleBy: number[]; + + @ApiProperty() + entityTypeIds: number[]; + + constructor( + id: number, + name: string, + createdBy: number, + file: FileLinkDto | null, + accessibleBy: number[], + entityTypeIds: number[], + ) { + this.id = id; + this.name = name; + this.createdBy = createdBy; + this.file = file; + this.accessibleBy = accessibleBy; + this.entityTypeIds = entityTypeIds; + } +} diff --git a/backend/src/modules/documents/document-template/dto/index.ts b/backend/src/modules/documents/document-template/dto/index.ts new file mode 100644 index 0000000..053d24a --- /dev/null +++ b/backend/src/modules/documents/document-template/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-document-template.dto'; +export * from './document-template-info.dto'; +export * from './document-template.dto'; +export * from './update-document-template.dto'; diff --git a/backend/src/modules/documents/document-template/dto/update-document-template.dto.ts b/backend/src/modules/documents/document-template/dto/update-document-template.dto.ts new file mode 100644 index 0000000..379e910 --- /dev/null +++ b/backend/src/modules/documents/document-template/dto/update-document-template.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsOptional, IsString } from 'class-validator'; + +export class UpdateDocumentTemplateDto { + @ApiProperty() + @IsString() + name: string; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + fileId: string | null; + + @ApiProperty() + @IsArray() + accessibleBy: number[]; + + @ApiProperty() + @IsArray() + entityTypeIds: number[]; +} diff --git a/backend/src/modules/documents/document-template/entities/document-template-access.entity.ts b/backend/src/modules/documents/document-template/entities/document-template-access.entity.ts new file mode 100644 index 0000000..011d1c9 --- /dev/null +++ b/backend/src/modules/documents/document-template/entities/document-template-access.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class DocumentTemplateAccess { + @PrimaryColumn() + documentTemplateId: number; + + @PrimaryColumn() + userId: number; + + @Column() + accountId: number; + + constructor(accountId: number, documentTemplateId: number, userId: number) { + this.accountId = accountId; + this.documentTemplateId = documentTemplateId; + this.userId = userId; + } +} diff --git a/backend/src/modules/documents/document-template/entities/document-template-entity-type.entity.ts b/backend/src/modules/documents/document-template/entities/document-template-entity-type.entity.ts new file mode 100644 index 0000000..a5b2218 --- /dev/null +++ b/backend/src/modules/documents/document-template/entities/document-template-entity-type.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class DocumentTemplateEntityType { + @PrimaryColumn() + documentTemplateId: number; + + @PrimaryColumn() + entityTypeId: number; + + @Column() + accountId: number; + + constructor(accountId: number, documentTemplateId: number, entityTypeId: number) { + this.accountId = accountId; + this.documentTemplateId = documentTemplateId; + this.entityTypeId = entityTypeId; + } +} diff --git a/backend/src/modules/documents/document-template/entities/document-template.entity.ts b/backend/src/modules/documents/document-template/entities/document-template.entity.ts new file mode 100644 index 0000000..b1f160c --- /dev/null +++ b/backend/src/modules/documents/document-template/entities/document-template.entity.ts @@ -0,0 +1,39 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +@Entity() +export class DocumentTemplate { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column() + createdBy: number; + + @Column() + createdCount: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, name: string, createdBy: number, createdAt?: Date) { + this.name = name; + this.createdBy = createdBy; + this.accountId = accountId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public update(name: string): void { + this.name = name; + } + + public incrementCreatedCount(): void { + this.createdCount++; + } +} diff --git a/backend/src/modules/documents/document-template/entities/index.ts b/backend/src/modules/documents/document-template/entities/index.ts new file mode 100644 index 0000000..1bcfd46 --- /dev/null +++ b/backend/src/modules/documents/document-template/entities/index.ts @@ -0,0 +1,3 @@ +export * from './document-template-access.entity'; +export * from './document-template-entity-type.entity'; +export * from './document-template.entity'; diff --git a/backend/src/modules/documents/document-template/index.ts b/backend/src/modules/documents/document-template/index.ts new file mode 100644 index 0000000..32bf872 --- /dev/null +++ b/backend/src/modules/documents/document-template/index.ts @@ -0,0 +1,4 @@ +export * from './document-template.controller'; +export * from './document-template.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/modules/documents/documents.module.ts b/backend/src/modules/documents/documents.module.ts new file mode 100644 index 0000000..4d8fc38 --- /dev/null +++ b/backend/src/modules/documents/documents.module.ts @@ -0,0 +1,35 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; +import { InventoryModule } from '@/modules/inventory/inventory.module'; +import { EntityFieldModule } from '@/modules/entity/entity-field/entity-field.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import documentsConfig from './config/documents.config'; +import { + DocumentTemplate, + DocumentTemplateAccess, + DocumentTemplateEntityType, + DocumentTemplateController, + DocumentTemplateService, +} from './document-template'; +import { DocumentGenerationController, DocumentGenerationService } from './document-generation'; + +@Module({ + imports: [ + ConfigModule.forFeature(documentsConfig), + TypeOrmModule.forFeature([DocumentTemplate, DocumentTemplateAccess, DocumentTemplateEntityType]), + IAMModule, + StorageModule, + EntityFieldModule, + forwardRef(() => CrmModule), + forwardRef(() => InventoryModule), + ], + controllers: [DocumentTemplateController, DocumentGenerationController], + providers: [DocumentTemplateService, DocumentGenerationService], + exports: [DocumentGenerationService], +}) +export class DocumentsModule {} diff --git a/backend/src/modules/documents/index.ts b/backend/src/modules/documents/index.ts new file mode 100644 index 0000000..04aab1b --- /dev/null +++ b/backend/src/modules/documents/index.ts @@ -0,0 +1,4 @@ +export * from './config'; +export * from './document-generation'; +export * from './document-template'; +export * from './documents.module'; diff --git a/backend/src/modules/entity/entity-event/dto/create-entity-event.dto.ts b/backend/src/modules/entity/entity-event/dto/create-entity-event.dto.ts new file mode 100644 index 0000000..e7bee50 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/create-entity-event.dto.ts @@ -0,0 +1,5 @@ +import { OmitType } from '@nestjs/swagger'; + +import { EntityEventDto } from './entity-event.dto'; + +export class CreateEntityEventDto extends OmitType(EntityEventDto, ['id'] as const) {} diff --git a/backend/src/modules/entity/entity-event/dto/delete-entity-event.dto.ts b/backend/src/modules/entity/entity-event/dto/delete-entity-event.dto.ts new file mode 100644 index 0000000..3ad6d38 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/delete-entity-event.dto.ts @@ -0,0 +1,5 @@ +import { OmitType } from '@nestjs/swagger'; + +import { EntityEventDto } from './entity-event.dto'; + +export class DeleteEntityEventDto extends OmitType(EntityEventDto, ['id', 'createdAt'] as const) {} diff --git a/backend/src/modules/entity/entity-event/dto/entity-event-data.dto.ts b/backend/src/modules/entity/entity-event/dto/entity-event-data.dto.ts new file mode 100644 index 0000000..d6c0dc8 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/entity-event-data.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { EntityEventType } from '../enums/entity-event-type.enum'; + +export class EntityEventDataDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsEnum(EntityEventType) + type: EntityEventType; + + @ApiProperty() + data: any; + + @ApiProperty() + createdAt: string; + + constructor(id: number, type: EntityEventType, data: any, createdAt: string) { + this.id = id; + this.type = type; + this.data = data; + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/entity/entity-event/dto/entity-event-item.dto.ts b/backend/src/modules/entity/entity-event/dto/entity-event-item.dto.ts new file mode 100644 index 0000000..60a48c7 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/entity-event-item.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { EntityEventType } from '../enums/entity-event-type.enum'; + +export class EntityEventItemDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsEnum(EntityEventType) + type: EntityEventType; + + @ApiProperty() + data: any; + + @ApiProperty() + createdAt: string; + + constructor(objectId: number, type: EntityEventType, data: object, createdAt: string) { + this.id = objectId; + this.type = type; + this.data = data; + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/entity/entity-event/dto/entity-event.dto.ts b/backend/src/modules/entity/entity-event/dto/entity-event.dto.ts new file mode 100644 index 0000000..8671ed9 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/entity-event.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { EntityEventType } from '../enums/entity-event-type.enum'; + +export class EntityEventDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + entityId: number; + + @ApiProperty() + @IsNumber() + objectId: number; + + @ApiProperty() + @IsEnum(EntityEventType) + type: EntityEventType; + + @ApiProperty() + createdAt: string; + + constructor(id: number, entityId: number, objectId: number, type: EntityEventType, createdAt: string) { + this.id = id; + this.entityId = entityId; + this.objectId = objectId; + this.type = type; + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/entity/entity-event/dto/get-entity-event.result.ts b/backend/src/modules/entity/entity-event/dto/get-entity-event.result.ts new file mode 100644 index 0000000..1e4a721 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/get-entity-event.result.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { EntityEventDataDto } from './entity-event-data.dto'; + +export class GetEntityEventResult { + @ApiProperty({ type: [EntityEventDataDto] }) + result: EntityEventDataDto[]; + + @ApiProperty() + meta: PagingMeta; + + constructor(result: EntityEventDataDto[], meta: PagingMeta) { + this.result = result; + this.meta = meta; + } +} diff --git a/backend/src/modules/entity/entity-event/dto/index.ts b/backend/src/modules/entity/entity-event/dto/index.ts new file mode 100644 index 0000000..7b15d76 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/index.ts @@ -0,0 +1,7 @@ +export * from './create-entity-event.dto'; +export * from './delete-entity-event.dto'; +export * from './entity-event-data.dto'; +export * from './entity-event-item.dto'; +export * from './entity-event.dto'; +export * from './get-entity-event.result'; +export * from './update-entity-event.dto'; diff --git a/backend/src/modules/entity/entity-event/dto/update-entity-event.dto.ts b/backend/src/modules/entity/entity-event/dto/update-entity-event.dto.ts new file mode 100644 index 0000000..4f880b0 --- /dev/null +++ b/backend/src/modules/entity/entity-event/dto/update-entity-event.dto.ts @@ -0,0 +1,29 @@ +import { PickType } from '@nestjs/swagger'; + +import { type EntityEventType } from '../enums/entity-event-type.enum'; +import { EntityEventDto } from './entity-event.dto'; + +export class UpdateEntityEventDto extends PickType(EntityEventDto, [ + 'entityId', + 'objectId', + 'type', + 'createdAt', +] as const) { + oldEntityId: number | null; + + constructor( + entityId: number, + objectId: number, + type: EntityEventType, + createdAt: string, + oldEntityId: number | null, + ) { + super(entityId, objectId, type, createdAt); + + this.entityId = entityId; + this.objectId = objectId; + this.type = type; + this.createdAt = createdAt; + this.oldEntityId = oldEntityId; + } +} diff --git a/backend/src/modules/entity/entity-event/entities/entity-event.entity.ts b/backend/src/modules/entity/entity-event/entities/entity-event.entity.ts new file mode 100644 index 0000000..e873887 --- /dev/null +++ b/backend/src/modules/entity/entity-event/entities/entity-event.entity.ts @@ -0,0 +1,50 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { EntityEventType } from '../enums/entity-event-type.enum'; +import { CreateEntityEventDto } from '../dto/create-entity-event.dto'; +import { EntityEventDto } from '../dto/entity-event.dto'; + +@Entity() +export class EntityEvent { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + entityId: number; + + @Column() + objectId: number; + + @Column() + type: EntityEventType; + + @Column() + createdAt: Date; + + constructor(accountId: number, entityId: number, objectId: number, type: EntityEventType, createdAt: Date) { + this.accountId = accountId; + this.entityId = entityId; + this.objectId = objectId; + this.type = type; + this.createdAt = createdAt; + } + + public static fromDto(accountId: number, dto: CreateEntityEventDto): EntityEvent { + return new EntityEvent( + accountId, + dto.entityId, + dto.objectId, + dto.type, + dto.createdAt ? DateUtil.fromISOString(dto.createdAt) : DateUtil.now(), + ); + } + + public toDto(): EntityEventDto { + return new EntityEventDto(this.id, this.entityId, this.objectId, this.type, this.createdAt.toISOString()); + } +} diff --git a/backend/src/modules/entity/entity-event/entities/index.ts b/backend/src/modules/entity/entity-event/entities/index.ts new file mode 100644 index 0000000..ecf93f4 --- /dev/null +++ b/backend/src/modules/entity/entity-event/entities/index.ts @@ -0,0 +1 @@ +export * from './entity-event.entity'; diff --git a/backend/src/modules/entity/entity-event/entity-event.controller.ts b/backend/src/modules/entity/entity-event/entity-event.controller.ts new file mode 100644 index 0000000..2f528b6 --- /dev/null +++ b/backend/src/modules/entity/entity-event/entity-event.controller.ts @@ -0,0 +1,31 @@ +import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { GetEntityEventResult } from './dto'; +import { EntityEventFilter } from './enums'; +import { EntityEventService } from './entity-event.service'; + +@ApiTags('crm/entity-event') +@Controller('crm/entities/:entityId/events') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +@TransformToDto() +export class EntityEventController { + constructor(private readonly service: EntityEventService) {} + + @ApiCreatedResponse({ description: 'EntityEvents', type: GetEntityEventResult }) + @Get(':filter') + public async getEntityEventItems( + @CurrentAuth() { account, user }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + //TODO Unify query interface, move filters from parameters to query body + @Param('filter') filter: EntityEventFilter, + @Query() paging: PagingQuery, + ): Promise { + return this.service.findEntityEventItems(account, user, entityId, filter, paging); + } +} diff --git a/backend/src/modules/entity/entity-event/entity-event.handler.ts b/backend/src/modules/entity/entity-event/entity-event.handler.ts new file mode 100644 index 0000000..aaad593 --- /dev/null +++ b/backend/src/modules/entity/entity-event/entity-event.handler.ts @@ -0,0 +1,269 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { FileLinkSource } from '@/common'; +import { + ProductOrderCreatedEvent, + ProductOrderEvent, + ProductsEventType, + RentalOrderCreatedEvent, + RentalOrderEvent, + ShipmentCreatedEvent, + ShipmentDeletedEvent, +} from '@/modules/inventory/common'; +import { TelephonyEventType, TelephonyCallCreatedEvent, TelephonyCallUpdatedEvent } from '@/modules/telephony/common'; +import { + ActivityCreatedEvent, + ActivityEvent, + CrmEventType, + FileLinkCreatedEvent, + FileLinkEvent, + NoteCreatedEvent, + NoteEvent, + TaskCreatedEvent, + TaskEvent, + TaskUpdatedEvent, +} from '@/CRM/common'; +import { MailEventType, MailMessageEvent } from '@/Mailing/common'; + +import { EntityEventType } from './enums'; +import { EntityEventService } from './entity-event.service'; + +@Injectable() +export class EntityEventHandler { + constructor(private readonly entityEventService: EntityEventService) {} + + @OnEvent(CrmEventType.ActivityCreated, { async: true }) + public async onActivityCreated(event: ActivityCreatedEvent) { + if (event.entityId && event.activityId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.activityId, + type: EntityEventType.Activity, + createdAt: event.createdAt, + }); + } + } + + @OnEvent(CrmEventType.ActivityDeleted, { async: true }) + public async onActivityDeleted(event: ActivityEvent) { + if (event.entityId && event.activityId) { + await this.entityEventService.delete(event.accountId, { + entityId: event.entityId, + objectId: event.activityId, + type: EntityEventType.Activity, + }); + } + } + + @OnEvent(CrmEventType.FileLinkCreated, { async: true }) + public async onFileLinkCreated(event: FileLinkCreatedEvent) { + if (event.sourceId && event.fileLinkId && event.sourceType === FileLinkSource.ENTITY_DOCUMENT) { + await this.entityEventService.create(event.accountId, { + entityId: event.sourceId, + objectId: event.fileLinkId, + type: EntityEventType.Document, + createdAt: event.createdAt, + }); + } + } + + @OnEvent(CrmEventType.FileLinkDeleted, { async: true }) + public async onFileLinkDeleted(event: FileLinkEvent) { + if (event.sourceId && event.fileLinkId && event.sourceType === FileLinkSource.ENTITY_DOCUMENT) { + await this.entityEventService.delete(event.accountId, { + entityId: event.sourceId, + objectId: event.fileLinkId, + type: EntityEventType.Document, + }); + } + } + + @OnEvent(MailEventType.MailMessageReceived, { async: true }) + public async onMailMessageReceived(event: MailMessageEvent) { + if (event.entityId && event.messageId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.messageId, + type: EntityEventType.Mail, + createdAt: event.messageDate, + }); + } + } + + @OnEvent(MailEventType.MailMessageLinked, { async: true }) + public async onMailMessageLinked(event: MailMessageEvent) { + if (event.entityId && event.messageId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.messageId, + type: EntityEventType.Mail, + createdAt: event.messageDate, + }); + } + } + + @OnEvent(MailEventType.MailMessageDeleted, { async: true }) + public async onMailMessageDeleted(event: MailMessageEvent) { + if (event.entityId && event.messageId) { + await this.entityEventService.delete(event.accountId, { + entityId: event.entityId, + objectId: event.messageId, + type: EntityEventType.Mail, + }); + } + } + + @OnEvent(CrmEventType.NoteCreated, { async: true }) + public async onNoteCreated(event: NoteCreatedEvent) { + if (event.entityId && event.noteId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.noteId, + type: EntityEventType.Note, + createdAt: event.createdAt, + }); + } + } + + @OnEvent(CrmEventType.NoteDeleted, { async: true }) + public async onNoteDeleted(event: NoteEvent) { + if (event.entityId && event.noteId) { + await this.entityEventService.delete(event.accountId, { + entityId: event.entityId, + objectId: event.noteId, + type: EntityEventType.Note, + }); + } + } + + @OnEvent(ProductsEventType.ProductOrderCreated, { async: true }) + public async onProductOrderCreated(event: ProductOrderCreatedEvent) { + if (event.entityId && event.orderId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.orderId, + type: EntityEventType.Order, + createdAt: event.createdAt, + }); + } + } + + @OnEvent(ProductsEventType.ProductOrderDeleted, { async: true }) + public async onProductOrderDeleted(event: ProductOrderEvent) { + if (event.entityId && event.orderId) { + await this.entityEventService.delete(event.accountId, { + entityId: event.entityId, + objectId: event.orderId, + type: EntityEventType.Order, + }); + } + } + + @OnEvent(ProductsEventType.RentalOrderCreated, { async: true }) + public async onProductRentalOrderCreated(event: RentalOrderCreatedEvent) { + if (event.entityId && event.rentalOrderId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.rentalOrderId, + type: EntityEventType.RentalOrder, + createdAt: event.createdAt, + }); + } + } + + @OnEvent(ProductsEventType.RentalOrderDeleted, { async: true }) + public async onProductRentalOrderDeleted(event: RentalOrderEvent) { + if (event.entityId && event.rentalOrderId) { + await this.entityEventService.delete(event.accountId, { + entityId: event.entityId, + objectId: event.rentalOrderId, + type: EntityEventType.RentalOrder, + }); + } + } + + @OnEvent(ProductsEventType.ShipmentCreated, { async: true }) + public async onShipmentCreated(event: ShipmentCreatedEvent) { + if (event.entityId && event.shipmentId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.shipmentId, + type: EntityEventType.Shipment, + createdAt: event.createdAt, + }); + } + } + + @OnEvent(ProductsEventType.ShipmentDeleted, { async: true }) + public async onShipmentDeleted(event: ShipmentDeletedEvent) { + if (event.entityId && event.shipmentId) { + await this.entityEventService.delete(event.accountId, { + entityId: event.entityId, + objectId: event.shipmentId, + type: EntityEventType.Shipment, + }); + } + } + + @OnEvent(CrmEventType.TaskCreated, { async: true }) + public async onTaskCreated(event: TaskCreatedEvent) { + if (event.entityId && event.taskId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.taskId, + type: EntityEventType.Task, + createdAt: event.createdAt.toISOString(), + }); + } + } + + @OnEvent(CrmEventType.TaskUpdated, { async: true }) + public async onTaskUpdated(event: TaskUpdatedEvent) { + if (event.entityId !== event.prevEntityId && event.taskId) { + await this.entityEventService.update(event.accountId, { + entityId: event.entityId, + objectId: event.taskId, + type: EntityEventType.Task, + createdAt: event.createdAt.toISOString(), + oldEntityId: event.prevEntityId, + }); + } + } + + @OnEvent(CrmEventType.TaskDeleted, { async: true }) + public async onTaskDeleted(event: TaskEvent) { + if (event.entityId && event.taskId) { + await this.entityEventService.delete(event.accountId, { + entityId: event.entityId, + objectId: event.taskId, + type: EntityEventType.Task, + }); + } + } + + @OnEvent(TelephonyEventType.TelephonyCallCreated, { async: true }) + public async onTelephonyCallCreated(event: TelephonyCallCreatedEvent) { + if (event.entityId && event.callId) { + await this.entityEventService.create(event.accountId, { + entityId: event.entityId, + objectId: event.callId, + type: EntityEventType.Call, + createdAt: event.createdAt, + }); + } + } + + @OnEvent(TelephonyEventType.TelephonyCallUpdated, { async: true }) + public async onTelephonyCallUpdated(event: TelephonyCallUpdatedEvent) { + if (event.entityId && event.callId) { + await this.entityEventService.update(event.accountId, { + entityId: event.entityId, + objectId: event.callId, + type: EntityEventType.Call, + createdAt: event.createdAt, + oldEntityId: event.oldEntityId, + }); + } + } +} diff --git a/backend/src/modules/entity/entity-event/entity-event.module.ts b/backend/src/modules/entity/entity-event/entity-event.module.ts new file mode 100644 index 0000000..d7c5da7 --- /dev/null +++ b/backend/src/modules/entity/entity-event/entity-event.module.ts @@ -0,0 +1,31 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { OrderModule } from '@/modules/inventory/order/order.module'; +import { RentalOrderModule } from '@/modules/inventory/rental-order/rental-order.module'; +import { ShipmentModule } from '@/modules/inventory/shipment/shipment.module'; +import { TelephonyModule } from '@/modules/telephony/telephony.module'; +import { CrmModule } from '@/CRM/crm.module'; +import { MailingModule } from '@/Mailing/MailingModule'; + +import { EntityEvent } from './entities/entity-event.entity'; +import { EntityEventController } from './entity-event.controller'; +import { EntityEventHandler } from './entity-event.handler'; +import { EntityEventService } from './entity-event.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([EntityEvent]), + IAMModule, + CrmModule, + OrderModule, + RentalOrderModule, + ShipmentModule, + MailingModule, + TelephonyModule, + ], + controllers: [EntityEventController], + providers: [EntityEventHandler, EntityEventService], +}) +export class EntityEventModule {} diff --git a/backend/src/modules/entity/entity-event/entity-event.service.ts b/backend/src/modules/entity/entity-event/entity-event.service.ts new file mode 100644 index 0000000..d46dd99 --- /dev/null +++ b/backend/src/modules/entity/entity-event/entity-event.service.ts @@ -0,0 +1,261 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository, SelectQueryBuilder } from 'typeorm'; + +import { PagingQuery, PagingMeta } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { OrderService } from '@/modules/inventory/order/services/order.service'; +import { RentalOrderService } from '@/modules/inventory/rental-order/services/rental-order.service'; +import { ShipmentService } from '@/modules/inventory/shipment/shipment.service'; +import { VoximplantCallService } from '@/modules/telephony/voximplant/voximplant-call/voximplant-call.service'; + +import { EntityLinkService } from '@/CRM/entity-link/entity-link.service'; +import { FileLinkService } from '@/CRM/Service/FileLink/FileLinkService'; +import { MailMessageService } from '@/Mailing/Service/MailMessage/MailMessageService'; +import { ActivityService } from '@/CRM/activity/activity.service'; +import { NoteService } from '@/CRM/note/note.service'; +import { TaskService } from '@/CRM/task/task.service'; + +import { + CreateEntityEventDto, + GetEntityEventResult, + EntityEventDataDto, + UpdateEntityEventDto, + DeleteEntityEventDto, + EntityEventItemDto, +} from './dto'; +import { EntityEvent } from './entities'; +import { EntityEventType, EntityEventFilter } from './enums'; + +const ExcludeEventTypes = [EntityEventType.Order, EntityEventType.RentalOrder, EntityEventType.Shipment]; + +@Injectable() +export class EntityEventService { + constructor( + @InjectRepository(EntityEvent) + private readonly repository: Repository, + private readonly activityService: ActivityService, + private readonly entityLinkService: EntityLinkService, + private readonly fileLinkService: FileLinkService, + private readonly mailMessageService: MailMessageService, + private readonly noteService: NoteService, + private readonly orderService: OrderService, + private readonly rentalOrderService: RentalOrderService, + private readonly shipmentService: ShipmentService, + private readonly taskService: TaskService, + private readonly telephonyCallService: VoximplantCallService, + ) {} + + public async create(accountId: number, dto: CreateEntityEventDto): Promise { + //TODO: Check do we need to check existence of same entity event + return this.repository.save(EntityEvent.fromDto(accountId, dto)); + } + + public async findEntityEventItems( + account: Account, + user: User, + entityId: number, + activeFilter = EntityEventFilter.All, + paging: PagingQuery, + ): Promise { + const qb = await this.getEntityEventsQuery(account.id, entityId, activeFilter); + const total = await qb.clone().getCount(); + + let currentOffset = paging.skip; + const resultData: EntityEventDataDto[] = []; + + while (resultData.length < paging.take && currentOffset < total) { + const currentLimit = paging.take - resultData.length; + const entityEventItems = await qb.clone().offset(currentOffset).limit(currentLimit).getMany(); + for (const entityEventItem of entityEventItems) { + const dataObject: object = await this.findEntityEventItemData(account, user, entityEventItem); + if (dataObject) { + //TODO This is a temporary solution to filter mail objects with same thread. + // Needs to be refactored to have a link to threadId in entityEvent table + const isMailWithUniqueThreadId = this.checkIsMailWithUniqueThreadId(entityEventItem, dataObject, resultData); + if (!isMailWithUniqueThreadId) { + continue; + } + const dto = new EntityEventDataDto( + entityEventItem.id, + entityEventItem.type, + dataObject, + entityEventItem.createdAt.toISOString(), + ); + resultData.push(dto); + } + } + currentOffset += currentLimit; + } + + return new GetEntityEventResult(resultData, new PagingMeta(currentOffset, total)); + } + + public async update(accountId: number, dto: UpdateEntityEventDto): Promise { + if (!dto.oldEntityId) { + return this.create(accountId, dto); + } else if (!dto.entityId) { + await this.delete(accountId, dto); + return null; + } else { + const event = await this.repository.findOneBy({ + accountId, + objectId: dto.objectId, + entityId: dto.oldEntityId, + type: dto.type, + }); + event.entityId = dto.entityId; + return this.repository.save(event); + } + } + + public async delete(accountId: number, dto: DeleteEntityEventDto): Promise { + await this.repository.delete({ + accountId: accountId, + objectId: dto.objectId, + type: dto.type, + }); + } + + private async findEntityEventItemData( + account: Account, + user: User, + entityEvent: EntityEvent, + ): Promise { + const { objectId, entityId } = entityEvent; + try { + switch (entityEvent.type) { + case EntityEventType.Activity: + return this.activityService.findDtoForId(account, user, objectId); + case EntityEventType.Call: { + const call = await this.telephonyCallService.findOneFull(account.id, user, { id: objectId }); + return call ? call.toDto() : null; + } + case EntityEventType.Document: + return this.fileLinkService.findDtoById(account, objectId); + case EntityEventType.Mail: + return this.mailMessageService.getThreadForMessageId(account.id, user, objectId); + case EntityEventType.Note: + return this.noteService.findOneDto({ account, filter: { entityId, noteId: objectId } }); + case EntityEventType.Order: { + const order = await this.orderService.findOne(account.id, user, { orderId: objectId }); + return order ? order.toDto() : null; + } + case EntityEventType.RentalOrder: { + const rentalOrder = await this.rentalOrderService.getOne(account.id, user, null, objectId); + return rentalOrder ? rentalOrder.toDto() : null; + } + case EntityEventType.Shipment: { + const shipment = await this.shipmentService.findOne({ + accountId: account.id, + user, + filter: { shipmentId: objectId }, + }); + return shipment ? shipment.toDto() : null; + } + case EntityEventType.Task: + return this.taskService.findDtoById(account, user, objectId); + } + } catch { + return null; + } + } + + private async getEntityEventsQuery( + accountId: number, + entityId: number, + activeFilter: EntityEventFilter, + ): Promise> { + let entityEventTypes: string[] | null = null; + switch (activeFilter) { + case EntityEventFilter.Activities: + entityEventTypes = [EntityEventType.Activity]; + break; + case EntityEventFilter.Calls: + entityEventTypes = [EntityEventType.Call]; + break; + //TODO Delete 'Files' after synchronization with frontend + case EntityEventFilter.Documents: + entityEventTypes = [EntityEventType.Document]; + break; + case EntityEventFilter.Files || EntityEventFilter.Documents: + entityEventTypes = [EntityEventType.Document]; + break; + case EntityEventFilter.Orders: + entityEventTypes = [EntityEventType.Order, EntityEventType.RentalOrder]; + break; + case EntityEventFilter.Mail: + entityEventTypes = [EntityEventType.Mail]; + break; + case EntityEventFilter.Notes: + entityEventTypes = [EntityEventType.Note]; + break; + case EntityEventFilter.Shipments: + entityEventTypes = [EntityEventType.Shipment]; + break; + case EntityEventFilter.Tasks: + entityEventTypes = [EntityEventType.Task]; + break; + case EntityEventFilter.All: + break; + } + const isAllType = !entityEventTypes; + const isMailType = entityEventTypes?.includes(EntityEventType.Mail); + const isCallType = entityEventTypes?.includes(EntityEventType.Call); + const qb = this.repository.createQueryBuilder().select().where({ accountId }); + const linkedEntities = + isAllType || isMailType || isCallType + ? await this.entityLinkService.findMany({ accountId, sourceId: entityId }) + : []; + const linkedEntitiesIds = linkedEntities.map((e) => e.targetId); + switch (true) { + case isAllType && !linkedEntitiesIds.length: + qb.andWhere({ entityId }); + break; + case isAllType && !!linkedEntitiesIds.length: + qb.andWhere( + new Brackets((qb) => + qb + .where(`entity_id = :entityId`, { entityId }) + .orWhere(`(type in (:...types) and entity_id in (:...entityIds))`, { + entityIds: linkedEntitiesIds, + types: [EntityEventType.Mail, EntityEventType.Call], + }), + ), + ); + break; + case isCallType: + qb.andWhere(`type = '${EntityEventType.Call}' and entity_id in (:...entityIds)`, { + entityIds: [...linkedEntitiesIds, entityId], + }); + break; + case isMailType: + qb.andWhere(`type = '${EntityEventType.Mail}' and entity_id in (:...entityIds)`, { + entityIds: [...linkedEntitiesIds, entityId], + }); + break; + default: + qb.andWhere({ entityId }); + if (entityEventTypes?.length) { + qb.andWhere(`type in (:...types)`, { types: entityEventTypes }); + } + } + if (ExcludeEventTypes.length > 0) { + qb.andWhere('type not in (:...excludeTypes)', { excludeTypes: ExcludeEventTypes }); + } + return qb.orderBy('created_at', 'DESC').addOrderBy('id', 'DESC'); + } + + //TODO This is a temporary solution to filter mail objects with same thread. + private checkIsMailWithUniqueThreadId( + entityEventItem: EntityEvent, + dataObject: any, + resultData: EntityEventItemDto[], + ): boolean { + return entityEventItem.type === EntityEventType.Mail + ? !resultData.some((item) => item.data.id === dataObject.id) + : true; + } +} diff --git a/backend/src/modules/entity/entity-event/enums/entity-event-filter.enum.ts b/backend/src/modules/entity/entity-event/enums/entity-event-filter.enum.ts new file mode 100644 index 0000000..3a38106 --- /dev/null +++ b/backend/src/modules/entity/entity-event/enums/entity-event-filter.enum.ts @@ -0,0 +1,13 @@ +//TODO Make fields naming in plural and consistent with such frontend enum, sort descending +export enum EntityEventFilter { + All = 'all', + Activities = 'activities', + Calls = 'calls', + Documents = 'documents', //TODO: remove + Files = 'files', + Notes = 'notes', + Mail = 'mail', + Orders = 'orders', + Shipments = 'shipments', + Tasks = 'tasks', +} diff --git a/backend/src/modules/entity/entity-event/enums/entity-event-type.enum.ts b/backend/src/modules/entity/entity-event/enums/entity-event-type.enum.ts new file mode 100644 index 0000000..d17d580 --- /dev/null +++ b/backend/src/modules/entity/entity-event/enums/entity-event-type.enum.ts @@ -0,0 +1,12 @@ +//TODO Unify with such frontend enum, remove unused fields +export enum EntityEventType { + Activity = 'activity', + Call = 'call', + Document = 'document', + Mail = 'mail', + Note = 'note', + Order = 'order', + RentalOrder = 'rental_order', + Shipment = 'shipment', + Task = 'task', +} diff --git a/backend/src/modules/entity/entity-event/enums/index.ts b/backend/src/modules/entity/entity-event/enums/index.ts new file mode 100644 index 0000000..bd199aa --- /dev/null +++ b/backend/src/modules/entity/entity-event/enums/index.ts @@ -0,0 +1,2 @@ +export * from './entity-event-filter.enum'; +export * from './entity-event-type.enum'; diff --git a/backend/src/modules/entity/entity-field/common/enums/field-type.enum.ts b/backend/src/modules/entity/entity-field/common/enums/field-type.enum.ts new file mode 100644 index 0000000..e1a0f75 --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/enums/field-type.enum.ts @@ -0,0 +1,37 @@ +export enum FieldType { + Text = 'text', + MultiText = 'multitext', + RichText = 'richtext', + Switch = 'switch', + Number = 'number', + Formula = 'formula', + Phone = 'phone', + Email = 'email', + Value = 'value', + Date = 'date', + Link = 'link', + Select = 'select', + ColoredSelect = 'colored_select', + MultiSelect = 'multiselect', + ColoredMultiSelect = 'colored_multiselect', + CheckedMultiSelect = 'checked_multiselect', + Participant = 'participant', + Participants = 'participants', + File = 'file', + Checklist = 'checklist', +} + +export const FieldTypes = { + calculable: [FieldType.Number, FieldType.Value, FieldType.Formula], + formula: [FieldType.Value, FieldType.Formula], + participant: [FieldType.Participant, FieldType.Participants], + select: [FieldType.Select, FieldType.ColoredSelect], + multiSelect: [FieldType.MultiSelect, FieldType.ColoredMultiSelect, FieldType.CheckedMultiSelect], + withOptions: [ + FieldType.Select, + FieldType.ColoredSelect, + FieldType.MultiSelect, + FieldType.ColoredMultiSelect, + FieldType.CheckedMultiSelect, + ], +}; diff --git a/backend/src/modules/entity/entity-field/common/enums/index.ts b/backend/src/modules/entity/entity-field/common/enums/index.ts new file mode 100644 index 0000000..441e2c2 --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/enums/index.ts @@ -0,0 +1 @@ +export * from './field-type.enum'; diff --git a/backend/src/modules/entity/entity-field/common/events/field-event-type.enum.ts b/backend/src/modules/entity/entity-field/common/events/field-event-type.enum.ts new file mode 100644 index 0000000..85d4927 --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/events/field-event-type.enum.ts @@ -0,0 +1,5 @@ +export enum FieldEventType { + FieldCreated = 'field:created', + FieldUpdated = 'field:updated', + FieldDeleted = 'field:deleted', +} diff --git a/backend/src/modules/entity/entity-field/common/events/field/field.event.ts b/backend/src/modules/entity/entity-field/common/events/field/field.event.ts new file mode 100644 index 0000000..d2d0069 --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/events/field/field.event.ts @@ -0,0 +1,15 @@ +import { FieldType } from '../../enums'; + +export class FieldEvent { + accountId: number; + entityTypeId: number; + fieldId: number; + type?: FieldType; + + constructor({ accountId, entityTypeId, fieldId, type }: FieldEvent) { + this.accountId = accountId; + this.entityTypeId = entityTypeId; + this.fieldId = fieldId; + this.type = type; + } +} diff --git a/backend/src/modules/entity/entity-field/common/events/field/index.ts b/backend/src/modules/entity/entity-field/common/events/field/index.ts new file mode 100644 index 0000000..334f60e --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/events/field/index.ts @@ -0,0 +1 @@ +export * from './field.event'; diff --git a/backend/src/modules/entity/entity-field/common/events/index.ts b/backend/src/modules/entity/entity-field/common/events/index.ts new file mode 100644 index 0000000..f30a358 --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/events/index.ts @@ -0,0 +1,2 @@ +export * from './field'; +export * from './field-event-type.enum'; diff --git a/backend/src/modules/entity/entity-field/common/index.ts b/backend/src/modules/entity/entity-field/common/index.ts new file mode 100644 index 0000000..80a1a8b --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/index.ts @@ -0,0 +1,3 @@ +export * from './enums'; +export * from './events'; +export * from './utils'; diff --git a/backend/src/modules/entity/entity-field/common/utils/formula.util.ts b/backend/src/modules/entity/entity-field/common/utils/formula.util.ts new file mode 100644 index 0000000..8eda8bb --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/utils/formula.util.ts @@ -0,0 +1,20 @@ +import { isSymbolNode, parse, SymbolNode } from 'mathjs'; + +export class FormulaUtil { + public static extractVariables(formula: string): string[] { + return formula + ? parse(formula) + .filter((n) => isSymbolNode(n)) + .map((n) => (n as SymbolNode)?.name) + : []; + } + + public static createFieldKey({ entityTypeId, fieldId }: { entityTypeId: number; fieldId: number }): string { + return `et${entityTypeId}_f${fieldId}`; + } + + public static parseFieldKey(fieldKey: string): { entityTypeId: number; fieldId: number } { + const [entityTypeId, fieldId] = fieldKey.split('_'); + return { entityTypeId: Number(entityTypeId.substring(2)), fieldId: Number(fieldId.substring(1)) }; + } +} diff --git a/backend/src/modules/entity/entity-field/common/utils/index.ts b/backend/src/modules/entity/entity-field/common/utils/index.ts new file mode 100644 index 0000000..2dc51f8 --- /dev/null +++ b/backend/src/modules/entity/entity-field/common/utils/index.ts @@ -0,0 +1 @@ +export * from './formula.util'; diff --git a/backend/src/modules/entity/entity-field/entity-field.module.ts b/backend/src/modules/entity/entity-field/entity-field.module.ts new file mode 100644 index 0000000..8b3b5d9 --- /dev/null +++ b/backend/src/modules/entity/entity-field/entity-field.module.ts @@ -0,0 +1,34 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; + +import { FieldGroup, FieldGroupService } from './field-group'; +import { FieldOption, FieldOptionService } from './field-option'; +import { FieldValue, FieldValueHandler, FieldValueService } from './field-value'; +import { Field, FieldController, FieldService } from './field'; + +import { FieldStageSettings } from './field-settings/entities/field-stage-settings.entity'; +import { FieldUserSettings } from './field-settings/entities/field-user-settings.entity'; +import { FieldSettingsController } from './field-settings/field-settings.controller'; +import { FieldSettingsService } from './field-settings/field-settings.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Field, FieldGroup, FieldOption, FieldValue, FieldStageSettings, FieldUserSettings]), + IAMModule, + StorageModule, + ], + controllers: [FieldController, FieldSettingsController], + providers: [ + FieldService, + FieldGroupService, + FieldOptionService, + FieldValueService, + FieldValueHandler, + FieldSettingsService, + ], + exports: [FieldService, FieldGroupService, FieldOptionService, FieldValueService, FieldSettingsService], +}) +export class EntityFieldModule {} diff --git a/backend/src/modules/entity/entity-field/field-group/dto/create-field-group.dto.ts b/backend/src/modules/entity/entity-field/field-group/dto/create-field-group.dto.ts new file mode 100644 index 0000000..a53abba --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/dto/create-field-group.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { FieldGroupDto } from './field-group.dto'; + +export class CreateFieldGroupDto extends PickType(FieldGroupDto, ['name', 'sortOrder', 'code'] as const) { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + id?: number; +} diff --git a/backend/src/modules/entity/entity-field/field-group/dto/field-group.dto.ts b/backend/src/modules/entity/entity-field/field-group/dto/field-group.dto.ts new file mode 100644 index 0000000..8ac41bb --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/dto/field-group.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { FieldGroupCode } from '../enums'; + +export class FieldGroupDto { + @ApiProperty({ description: 'Field group ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Field group name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Field group sort order' }) + @IsNumber() + sortOrder: number; + + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiPropertyOptional({ enum: FieldGroupCode, nullable: true, description: 'Field group code' }) + @IsOptional() + @IsEnum(FieldGroupCode) + code?: FieldGroupCode | null; +} diff --git a/backend/src/modules/entity/entity-field/field-group/dto/index.ts b/backend/src/modules/entity/entity-field/field-group/dto/index.ts new file mode 100644 index 0000000..22b7176 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-field-group.dto'; +export * from './field-group.dto'; +export * from './update-field-group.dto'; diff --git a/backend/src/modules/entity/entity-field/field-group/dto/update-field-group.dto.ts b/backend/src/modules/entity/entity-field/field-group/dto/update-field-group.dto.ts new file mode 100644 index 0000000..0e1a913 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/dto/update-field-group.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +import { ObjectState } from '@/common'; + +import { FieldGroupDto } from './field-group.dto'; + +export class UpdateFieldGroupDto extends PickType(FieldGroupDto, ['id', 'name', 'sortOrder', 'code'] as const) { + @ApiProperty({ enum: ObjectState, description: 'Object state' }) + @IsEnum(ObjectState) + state?: ObjectState; +} diff --git a/backend/src/modules/entity/entity-field/field-group/entities/field-group.entity.ts b/backend/src/modules/entity/entity-field/field-group/entities/field-group.entity.ts new file mode 100644 index 0000000..7606ec1 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/entities/field-group.entity.ts @@ -0,0 +1,70 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { FieldGroupCode } from '../enums'; +import { CreateFieldGroupDto, FieldGroupDto, UpdateFieldGroupDto } from '../dto'; + +@Entity() +export class FieldGroup { + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column() + sortOrder: number; + + @Column() + entityTypeId: number; + + @Column({ nullable: true }) + code: FieldGroupCode | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + id: number, + name: string, + sortOrder: number, + entityTypeId: number, + code: FieldGroupCode | null, + createdAt?: Date, + ) { + this.accountId = accountId; + this.id = id; + this.name = name; + this.sortOrder = sortOrder; + this.entityTypeId = entityTypeId; + this.code = code; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public static fromDto(accountId: number, entityTypeId: number, dto: CreateFieldGroupDto): FieldGroup { + return new FieldGroup(accountId, dto.id, dto.name, dto.sortOrder, entityTypeId, dto.code); + } + + public update(dto: UpdateFieldGroupDto): FieldGroup { + this.name = dto.name !== undefined ? dto.name : this.name; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + this.code = dto.code !== undefined ? dto.code : this.code; + + return this; + } + + public toDto(): FieldGroupDto { + return { + id: this.id, + name: this.name, + sortOrder: this.sortOrder, + entityTypeId: this.entityTypeId, + code: this.code, + }; + } +} diff --git a/backend/src/modules/entity/entity-field/field-group/entities/index.ts b/backend/src/modules/entity/entity-field/field-group/entities/index.ts new file mode 100644 index 0000000..ebd9cfb --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/entities/index.ts @@ -0,0 +1 @@ +export * from './field-group.entity'; diff --git a/backend/src/modules/entity/entity-field/field-group/enums/field-group-code.enum.ts b/backend/src/modules/entity/entity-field/field-group/enums/field-group-code.enum.ts new file mode 100644 index 0000000..48ed1ae --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/enums/field-group-code.enum.ts @@ -0,0 +1,5 @@ +export enum FieldGroupCode { + Details = 'details', + Project = 'project', + Analytics = 'analytics', +} diff --git a/backend/src/modules/entity/entity-field/field-group/enums/index.ts b/backend/src/modules/entity/entity-field/field-group/enums/index.ts new file mode 100644 index 0000000..740288f --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/enums/index.ts @@ -0,0 +1 @@ +export * from './field-group-code.enum'; diff --git a/backend/src/modules/entity/entity-field/field-group/field-group.service.ts b/backend/src/modules/entity/entity-field/field-group/field-group.service.ts new file mode 100644 index 0000000..72e7ab1 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/field-group.service.ts @@ -0,0 +1,95 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { NotFoundError, ObjectState } from '@/common'; +import { SequenceIdService } from '@/database'; + +import { SequenceName } from '@/CRM/common/enums/sequence-name.enum'; + +import { CreateFieldGroupDto, UpdateFieldGroupDto } from './dto'; +import { FieldGroup } from './entities'; + +interface FindFilter { + accountId: number; + entityTypeId?: number; +} + +@Injectable() +export class FieldGroupService { + constructor( + @InjectRepository(FieldGroup) + private readonly repository: Repository, + private readonly sequenceIdService: SequenceIdService, + ) {} + + private async nextIdentity(): Promise { + return this.sequenceIdService.nextIdentity(SequenceName.FieldGroup); + } + + public async create({ + accountId, + entityTypeId, + dto, + }: { + accountId: number; + entityTypeId: number; + dto: CreateFieldGroupDto; + }): Promise { + dto.id = dto.id ?? (await this.nextIdentity()); + + return await this.repository.save(FieldGroup.fromDto(accountId, entityTypeId, dto)); + } + + public async getById(id: number): Promise { + const group = await this.repository.findOneBy({ id }); + + if (!group) { + throw NotFoundError.withId(FieldGroup, id); + } + + return group; + } + + public async findMany(filter: FindFilter): Promise { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId: filter.accountId }); + + if (filter?.entityTypeId) { + qb.andWhere('entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + } + + return await qb.getMany(); + } + + public async delete(id: number): Promise { + const result = await this.repository.delete({ id }); + + if (result.affected === 0) { + throw NotFoundError.withId(FieldGroup, id); + } + } + + public async saveBatch({ + accountId, + entityTypeId, + dtos, + }: { + accountId: number; + entityTypeId: number; + dtos: UpdateFieldGroupDto[]; + }): Promise { + for (const dto of dtos) { + if (dto.state === ObjectState.Created) { + await this.create({ accountId, entityTypeId, dto }); + } + + if (dto.state === ObjectState.Updated) { + await this.repository.update(dto.id, { name: dto.name, sortOrder: dto.sortOrder, code: dto.code }); + } + + if (dto.state === ObjectState.Deleted) { + await this.delete(dto.id); + } + } + } +} diff --git a/backend/src/modules/entity/entity-field/field-group/index.ts b/backend/src/modules/entity/entity-field/field-group/index.ts new file mode 100644 index 0000000..770e5e8 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-group/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './entities'; +export * from './field-group.service'; diff --git a/backend/src/modules/entity/entity-field/field-option/dto/create-field-option.dto.ts b/backend/src/modules/entity/entity-field/field-option/dto/create-field-option.dto.ts new file mode 100644 index 0000000..c13a48d --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/dto/create-field-option.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { FieldOptionDto } from './field-option.dto'; + +export class CreateFieldOptionDto extends PickType(FieldOptionDto, ['label', 'color', 'sortOrder'] as const) { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + id?: number; +} diff --git a/backend/src/modules/entity/entity-field/field-option/dto/field-option.dto.ts b/backend/src/modules/entity/entity-field/field-option/dto/field-option.dto.ts new file mode 100644 index 0000000..2b6d24c --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/dto/field-option.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class FieldOptionDto { + @ApiProperty({ description: 'Field option ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Field option label' }) + @IsNumber() + label: string; + + @ApiProperty({ nullable: true, description: 'Field option color' }) + @IsOptional() + @IsString() + color: string | null; + + @ApiProperty({ description: 'Field option sort order' }) + @IsNumber() + sortOrder: number; + + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + fieldId: number; + + constructor({ id, label, color, sortOrder }: FieldOptionDto) { + this.id = id; + this.label = label; + this.color = color; + this.sortOrder = sortOrder; + } +} diff --git a/backend/src/modules/entity/entity-field/field-option/dto/index.ts b/backend/src/modules/entity/entity-field/field-option/dto/index.ts new file mode 100644 index 0000000..2fd5962 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-field-option.dto'; +export * from './field-option.dto'; +export * from './update-field-option.dto'; diff --git a/backend/src/modules/entity/entity-field/field-option/dto/update-field-option.dto.ts b/backend/src/modules/entity/entity-field/field-option/dto/update-field-option.dto.ts new file mode 100644 index 0000000..3d267a9 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/dto/update-field-option.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty, ApiPropertyOptional, PartialType, PickType } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { ObjectState } from '@/common'; + +import { FieldOptionDto } from './field-option.dto'; + +export class UpdateFieldOptionDto extends PartialType( + PickType(FieldOptionDto, ['label', 'color', 'sortOrder'] as const), +) { + @ApiProperty({ description: 'Field option ID' }) + @IsNumber() + id: number; + + @ApiPropertyOptional({ enum: ObjectState, description: 'Object state' }) + @IsOptional() + @IsEnum(ObjectState) + state?: ObjectState; +} diff --git a/backend/src/modules/entity/entity-field/field-option/entities/field-option.entity.ts b/backend/src/modules/entity/entity-field/field-option/entities/field-option.entity.ts new file mode 100644 index 0000000..34c8fba --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/entities/field-option.entity.ts @@ -0,0 +1,63 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { CreateFieldOptionDto, FieldOptionDto, UpdateFieldOptionDto } from '../dto'; + +@Entity() +export class FieldOption { + @PrimaryColumn() + id: number; + + @Column() + label: string; + + @Column() + color: string | null; + + @Column() + sortOrder: number; + + @Column() + fieldId: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + id: number, + label: string, + color: string | null, + sortOrder: number, + fieldId: number, + createdAt?: Date, + ) { + this.id = id; + this.label = label; + this.color = color; + this.sortOrder = sortOrder; + this.fieldId = fieldId; + this.accountId = accountId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public static fromDto(accountId: number, fieldId: number, dto: CreateFieldOptionDto): FieldOption { + return new FieldOption(accountId, dto.id, dto.label, dto.color, dto.sortOrder, fieldId); + } + + public toDto(): FieldOptionDto { + return new FieldOptionDto({ ...this }); + } + + public update(dto: UpdateFieldOptionDto): FieldOption { + this.label = dto.label !== undefined ? dto.label : this.label; + this.color = dto.color !== undefined ? dto.color : this.color; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + + return this; + } +} diff --git a/backend/src/modules/entity/entity-field/field-option/entities/index.ts b/backend/src/modules/entity/entity-field/field-option/entities/index.ts new file mode 100644 index 0000000..5375e16 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/entities/index.ts @@ -0,0 +1 @@ +export * from './field-option.entity'; diff --git a/backend/src/modules/entity/entity-field/field-option/field-option.service.ts b/backend/src/modules/entity/entity-field/field-option/field-option.service.ts new file mode 100644 index 0000000..55d97e6 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/field-option.service.ts @@ -0,0 +1,122 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { ObjectState } from '@/common'; +import { SequenceIdService } from '@/database'; + +import { SequenceName } from '@/CRM/common/enums/sequence-name.enum'; + +import { CreateFieldOptionDto, UpdateFieldOptionDto } from './dto'; +import { FieldOption } from './entities'; + +interface FindFilter { + accountId: number; + fieldId: number; +} + +const cacheKey = ({ accountId, fieldId }: { accountId: number; fieldId: number }) => + `FieldOption:${accountId}:${fieldId}`; + +@Injectable() +export class FieldOptionService { + constructor( + @InjectRepository(FieldOption) + private readonly repository: Repository, + private readonly dataSource: DataSource, + private readonly sequenceIdService: SequenceIdService, + ) {} + + private async nextIdentity(): Promise { + return this.sequenceIdService.nextIdentity(SequenceName.FieldOption); + } + + public async create({ + accountId, + fieldId, + dto, + }: { + accountId: number; + fieldId: number; + dto: CreateFieldOptionDto; + }): Promise { + dto.id = dto.id ?? (await this.nextIdentity()); + + return this.repository.save(FieldOption.fromDto(accountId, fieldId, dto)); + } + public async createMany({ + accountId, + fieldId, + dtos, + }: { + accountId: number; + fieldId: number; + dtos: CreateFieldOptionDto[]; + }): Promise { + return Promise.all(dtos.map((dto) => this.create({ accountId, fieldId, dto }))); + } + + public async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).cache(cacheKey(filter), 600000).getMany(); + } + + public async processBatch({ + accountId, + fieldId, + dtos, + }: { + accountId: number; + fieldId: number; + dtos: UpdateFieldOptionDto[]; + }): Promise { + const created = dtos.filter((dto) => dto.state === ObjectState.Created) as CreateFieldOptionDto[]; + const updated = dtos.filter((dto) => dto.state === ObjectState.Updated); + const deleted = dtos.filter((dto) => dto.state === ObjectState.Deleted); + + const options: FieldOption[] = []; + if (created.length) { + options.push(...(await this.createMany({ accountId, fieldId, dtos: created }))); + } + if (updated.length) { + const options = await this.findMany({ accountId, fieldId }); + options.push( + ...(await Promise.all( + updated.map(async (dto) => { + const option = options.find((option) => option.id === dto.id); + if (option) { + await this.repository.save(option.update(dto)); + return option; + } + return null; + }), + )), + ); + } + if (deleted.length) { + await Promise.all(deleted.map((dto) => this.delete({ accountId, fieldId, optionId: dto.id }))); + } + + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, fieldId })]); + + return options; + } + + private async delete({ + accountId, + fieldId, + optionId, + }: { + accountId: number; + fieldId: number; + optionId?: number; + }): Promise { + await this.repository.delete({ accountId, fieldId, id: optionId }); + } + + private createFindQb(filter: FindFilter) { + return this.repository + .createQueryBuilder() + .where('account_id = :accountId', { accountId: filter.accountId }) + .andWhere('field_id = :fieldId', { fieldId: filter.fieldId }); + } +} diff --git a/backend/src/modules/entity/entity-field/field-option/index.ts b/backend/src/modules/entity/entity-field/field-option/index.ts new file mode 100644 index 0000000..bc658e3 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-option/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './entities'; +export * from './field-option.service'; diff --git a/backend/src/modules/entity/entity-field/field-settings/dto/field-settings.dto.ts b/backend/src/modules/entity/entity-field/field-settings/dto/field-settings.dto.ts new file mode 100644 index 0000000..a424de4 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/dto/field-settings.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class FieldSettingsDto { + @ApiProperty() + fieldId: number; + + @ApiProperty({ nullable: true, type: [Number] }) + importantStageIds: number[] | null; + + @ApiProperty({ nullable: true, type: [Number] }) + requiredStageIds: number[] | null; + + @ApiProperty({ nullable: true, type: [Number] }) + excludeUserIds: number[] | null; + + @ApiProperty({ nullable: true, type: [Number] }) + readonlyUserIds: number[] | null; + + @ApiProperty({ nullable: true, type: [Number] }) + hideUserIds: number[] | null; + + @ApiProperty({ nullable: true, type: [Number] }) + hideStageIds: number[] | null; + + constructor({ + fieldId, + importantStageIds, + requiredStageIds, + excludeUserIds, + readonlyUserIds, + hideUserIds, + hideStageIds, + }: FieldSettingsDto) { + this.fieldId = fieldId; + this.importantStageIds = importantStageIds; + this.requiredStageIds = requiredStageIds; + this.excludeUserIds = excludeUserIds; + this.readonlyUserIds = readonlyUserIds; + this.hideUserIds = hideUserIds; + this.hideStageIds = hideStageIds; + } +} diff --git a/backend/src/modules/entity/entity-field/field-settings/dto/index.ts b/backend/src/modules/entity/entity-field/field-settings/dto/index.ts new file mode 100644 index 0000000..0ec4599 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/dto/index.ts @@ -0,0 +1,2 @@ +export * from './field-settings.dto'; +export * from './update-field-settings.dto'; diff --git a/backend/src/modules/entity/entity-field/field-settings/dto/update-field-settings.dto.ts b/backend/src/modules/entity/entity-field/field-settings/dto/update-field-settings.dto.ts new file mode 100644 index 0000000..2ea8529 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/dto/update-field-settings.dto.ts @@ -0,0 +1,5 @@ +import { OmitType, PartialType } from '@nestjs/swagger'; + +import { FieldSettingsDto } from './field-settings.dto'; + +export class UpdateFieldSettingsDto extends PartialType(OmitType(FieldSettingsDto, ['fieldId'] as const)) {} diff --git a/backend/src/modules/entity/entity-field/field-settings/entities/field-stage-settings.entity.ts b/backend/src/modules/entity/entity-field/field-settings/entities/field-stage-settings.entity.ts new file mode 100644 index 0000000..942a6ab --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/entities/field-stage-settings.entity.ts @@ -0,0 +1,38 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { FieldAccess } from '../enums'; + +@Entity() +export class FieldStageSettings { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + fieldId: number; + + @Column() + stageId: number; + + @Column() + access: FieldAccess; + + @Column('integer', { array: true, nullable: true }) + excludeUserIds: number[] | null; + + constructor( + accountId: number, + fieldId: number, + stageId: number, + access: FieldAccess, + excludeUserIds: number[] | null, + ) { + this.accountId = accountId; + this.fieldId = fieldId; + this.stageId = stageId; + this.access = access; + this.excludeUserIds = excludeUserIds; + } +} diff --git a/backend/src/modules/entity/entity-field/field-settings/entities/field-user-settings.entity.ts b/backend/src/modules/entity/entity-field/field-settings/entities/field-user-settings.entity.ts new file mode 100644 index 0000000..a96c809 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/entities/field-user-settings.entity.ts @@ -0,0 +1,28 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { FieldAccess } from '../enums'; + +@Entity() +export class FieldUserSettings { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + fieldId: number; + + @Column() + userId: number; + + @Column() + access: FieldAccess; + + constructor(accountId: number, fieldId: number, userId: number, access: FieldAccess) { + this.accountId = accountId; + this.fieldId = fieldId; + this.userId = userId; + this.access = access; + } +} diff --git a/backend/src/modules/entity/entity-field/field-settings/entities/index.ts b/backend/src/modules/entity/entity-field/field-settings/entities/index.ts new file mode 100644 index 0000000..a2ca520 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/entities/index.ts @@ -0,0 +1,2 @@ +export * from './field-stage-settings.entity'; +export * from './field-user-settings.entity'; diff --git a/backend/src/modules/entity/entity-field/field-settings/enums/field-access.enum.ts b/backend/src/modules/entity/entity-field/field-settings/enums/field-access.enum.ts new file mode 100644 index 0000000..d4bf4a1 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/enums/field-access.enum.ts @@ -0,0 +1,7 @@ +export enum FieldAccess { + REGULAR = 'regular', + READONLY = 'readonly', + HIDDEN = 'hidden', + IMPORTANT = 'important', + REQUIRED = 'required', +} diff --git a/backend/src/modules/entity/entity-field/field-settings/enums/index.ts b/backend/src/modules/entity/entity-field/field-settings/enums/index.ts new file mode 100644 index 0000000..afe9c57 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/enums/index.ts @@ -0,0 +1 @@ +export * from './field-access.enum'; diff --git a/backend/src/modules/entity/entity-field/field-settings/field-settings.controller.ts b/backend/src/modules/entity/entity-field/field-settings/field-settings.controller.ts new file mode 100644 index 0000000..6203470 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/field-settings.controller.ts @@ -0,0 +1,37 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FieldSettingsDto, UpdateFieldSettingsDto } from './dto'; +import { FieldSettingsService } from './field-settings.service'; + +@ApiTags('crm/fields') +@Controller('/crm/entity-types/:entityTypeId/fields') +@JwtAuthorized() +@TransformToDto() +export class FieldSettingsController { + constructor(private readonly service: FieldSettingsService) {} + + @ApiOkResponse({ description: 'Get field settings', type: [FieldSettingsDto] }) + @Get('settings') + public async updateEntity( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + ) { + return await this.service.findMany({ accountId, entityTypeId }); + } + + @ApiCreatedResponse({ description: 'Update field settings', type: FieldSettingsDto }) + @Post(':fieldId/settings') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('fieldId', ParseIntPipe) fieldId: number, + @Body() dto: UpdateFieldSettingsDto, + ) { + return await this.service.update({ accountId, fieldId, dto }); + } +} diff --git a/backend/src/modules/entity/entity-field/field-settings/field-settings.service.ts b/backend/src/modules/entity/entity-field/field-settings/field-settings.service.ts new file mode 100644 index 0000000..4dd0890 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/field-settings.service.ts @@ -0,0 +1,172 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { FieldService } from '../field/field.service'; + +import { FieldAccess } from './enums'; +import { FieldSettings } from './types'; +import { UpdateFieldSettingsDto } from './dto'; +import { FieldStageSettings, FieldUserSettings } from './entities'; + +const cacheKeyStage = ({ accountId, fieldId }: { accountId: number; fieldId: number }) => + `FieldStageSettings:${accountId}:${fieldId}`; +const cacheKeyUser = ({ accountId, fieldId }: { accountId: number; fieldId: number }) => + `FieldUserSettings:${accountId}:${fieldId}`; + +@Injectable() +export class FieldSettingsService { + constructor( + @InjectRepository(FieldStageSettings) + private readonly repositoryStageSettings: Repository, + @InjectRepository(FieldUserSettings) + private readonly repositoryUserSettings: Repository, + private readonly dataSource: DataSource, + private readonly fieldService: FieldService, + ) {} + + public async findOne({ accountId, fieldId }: { accountId: number; fieldId: number }): Promise { + const stageSettings = await this.repositoryStageSettings.find({ + where: { accountId, fieldId }, + cache: { id: cacheKeyStage({ accountId, fieldId }), milliseconds: 600000 }, + }); + + const userSettings = await this.repositoryUserSettings.find({ + where: { accountId, fieldId }, + cache: { id: cacheKeyUser({ accountId, fieldId }), milliseconds: 600000 }, + }); + + return new FieldSettings( + fieldId, + stageSettings.filter((s) => s.access === FieldAccess.IMPORTANT).map((s) => s.stageId), + stageSettings.filter((s) => s.access === FieldAccess.REQUIRED).map((s) => s.stageId), + stageSettings.filter((s) => s.access === FieldAccess.REQUIRED)?.[0]?.excludeUserIds ?? [], + stageSettings.filter((s) => s.access === FieldAccess.HIDDEN).map((s) => s.stageId), + userSettings.filter((u) => u.access === FieldAccess.READONLY).map((u) => u.userId), + userSettings.filter((u) => u.access === FieldAccess.HIDDEN).map((u) => u.userId), + ); + } + + public async findMany({ + accountId, + entityTypeId, + }: { + accountId: number; + entityTypeId?: number; + }): Promise { + const filedIds = await this.fieldService.findManyIds({ accountId, entityTypeId }); + + return Promise.all(filedIds.map((fieldId) => this.findOne({ accountId, fieldId }))); + } + + public async getRestrictedFields({ + accountId, + entityTypeId, + access, + userId, + stageId, + }: { + accountId: number; + entityTypeId: number; + access: FieldAccess; + userId?: number; + stageId?: number; + }): Promise { + const fieldIds = await this.fieldService.findManyIds({ accountId, entityTypeId }); + + const result: number[] = []; + for (const fieldId of fieldIds) { + const settings = await this.findOne({ accountId, fieldId }); + if (stageId) { + switch (access) { + case FieldAccess.IMPORTANT: + if (settings.importantStageIds?.includes(stageId)) { + result.push(fieldId); + } + break; + case FieldAccess.REQUIRED: + if ( + settings.requiredStageIds?.includes(stageId) && + (!userId || (userId && !settings.excludeUserIds?.includes(userId))) + ) { + result.push(fieldId); + } + break; + case FieldAccess.HIDDEN: + if (settings.hideStageIds?.includes(stageId)) { + result.push(fieldId); + } + break; + } + } + if (userId) { + switch (access) { + case FieldAccess.READONLY: + if (settings.readonlyUserIds?.includes(userId)) { + result.push(fieldId); + } + break; + case FieldAccess.HIDDEN: + if (settings.hideUserIds?.includes(userId)) { + result.push(fieldId); + } + break; + } + } + } + + return result; + } + + public async update({ + accountId, + fieldId, + dto, + }: { + accountId: number; + fieldId: number; + dto: UpdateFieldSettingsDto; + }): Promise { + await this.repositoryStageSettings.delete({ accountId, fieldId }); + await this.repositoryUserSettings.delete({ accountId, fieldId }); + this.dataSource.queryResultCache?.remove([ + cacheKeyStage({ accountId, fieldId }), + cacheKeyUser({ accountId, fieldId }), + ]); + + if (dto.importantStageIds) { + await this.repositoryStageSettings.insert( + dto.importantStageIds.map((stageId) => ({ accountId, fieldId, stageId, access: FieldAccess.IMPORTANT })), + ); + } + if (dto.requiredStageIds) { + await this.repositoryStageSettings.insert( + dto.requiredStageIds.map((stageId) => ({ + accountId, + fieldId, + stageId, + access: FieldAccess.REQUIRED, + excludeUserIds: dto.excludeUserIds, + })), + ); + } + if (dto.hideStageIds) { + await this.repositoryStageSettings.insert( + dto.hideStageIds.map((stageId) => ({ accountId, fieldId, stageId, access: FieldAccess.HIDDEN })), + ); + } + + if (dto.readonlyUserIds) { + await this.repositoryUserSettings.insert( + dto.readonlyUserIds.map((userId) => ({ accountId, fieldId, userId, access: FieldAccess.READONLY })), + ); + } + if (dto.hideUserIds) { + await this.repositoryUserSettings.insert( + dto.hideUserIds.map((userId) => ({ accountId, fieldId, userId, access: FieldAccess.HIDDEN })), + ); + } + + return this.findOne({ accountId, fieldId }); + } +} diff --git a/backend/src/modules/entity/entity-field/field-settings/index.ts b/backend/src/modules/entity/entity-field/field-settings/index.ts new file mode 100644 index 0000000..89b24f0 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/index.ts @@ -0,0 +1,6 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './field-settings.controller'; +export * from './field-settings.service'; +export * from './types'; diff --git a/backend/src/modules/entity/entity-field/field-settings/types/field-settings.ts b/backend/src/modules/entity/entity-field/field-settings/types/field-settings.ts new file mode 100644 index 0000000..1b1d800 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/types/field-settings.ts @@ -0,0 +1,33 @@ +import { FieldSettingsDto } from '../dto'; + +export class FieldSettings { + fieldId: number; + importantStageIds: number[] | null; + requiredStageIds: number[] | null; + excludeUserIds: number[] | null; + hideStageIds: number[] | null; + readonlyUserIds: number[] | null; + hideUserIds: number[] | null; + + constructor( + fieldId: number, + importantStageIds: number[] | null, + requiredStageIds: number[] | null, + excludeUserIds: number[] | null, + hideStageIds: number[] | null, + readonlyUserIds: number[] | null, + hideUserIds: number[] | null, + ) { + this.fieldId = fieldId; + this.importantStageIds = importantStageIds; + this.requiredStageIds = requiredStageIds; + this.excludeUserIds = excludeUserIds; + this.hideStageIds = hideStageIds; + this.readonlyUserIds = readonlyUserIds; + this.hideUserIds = hideUserIds; + } + + public toDto(): FieldSettingsDto { + return new FieldSettingsDto({ ...this }); + } +} diff --git a/backend/src/modules/entity/entity-field/field-settings/types/index.ts b/backend/src/modules/entity/entity-field/field-settings/types/index.ts new file mode 100644 index 0000000..90aa5a6 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-settings/types/index.ts @@ -0,0 +1 @@ +export * from './field-settings'; diff --git a/backend/src/modules/entity/entity-field/field-value/dto/create-field-value.dto.ts b/backend/src/modules/entity/entity-field/field-value/dto/create-field-value.dto.ts new file mode 100644 index 0000000..01776cb --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/dto/create-field-value.dto.ts @@ -0,0 +1,5 @@ +import { PickType } from '@nestjs/swagger'; + +import { FieldValueDto } from './field-value.dto'; + +export class CreateFieldValueDto extends PickType(FieldValueDto, ['fieldId', 'fieldType', 'payload'] as const) {} diff --git a/backend/src/modules/entity/entity-field/field-value/dto/field-value.dto.ts b/backend/src/modules/entity/entity-field/field-value/dto/field-value.dto.ts new file mode 100644 index 0000000..a438364 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/dto/field-value.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsObject } from 'class-validator'; + +import { FieldType } from '../../common'; + +export class FieldValueDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + entityId: number; + + @ApiProperty() + @IsNumber() + fieldId: number; + + @ApiProperty({ enum: FieldType }) + @IsEnum(FieldType) + fieldType: FieldType; + + @ApiProperty() + @IsObject() + payload: any; + + constructor({ id, entityId, fieldId, fieldType, payload }: FieldValueDto) { + this.id = id; + this.entityId = entityId; + this.fieldId = fieldId; + this.fieldType = fieldType; + this.payload = payload; + } +} diff --git a/backend/src/modules/entity/entity-field/field-value/dto/index.ts b/backend/src/modules/entity/entity-field/field-value/dto/index.ts new file mode 100644 index 0000000..64003c1 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-field-value.dto'; +export * from './field-value.dto'; +export * from './simple-field-value.dto'; +export * from './update-field-value.dto'; diff --git a/backend/src/modules/entity/entity-field/field-value/dto/simple-field-value.dto.ts b/backend/src/modules/entity/entity-field/field-value/dto/simple-field-value.dto.ts new file mode 100644 index 0000000..50a2bae --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/dto/simple-field-value.dto.ts @@ -0,0 +1,36 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; + +import { FieldType } from '../../common'; + +export class SimpleFieldValueDto { + @ApiPropertyOptional({ enum: FieldType, description: 'Field type' }) + @IsOptional() + @IsEnum(FieldType) + fieldType?: FieldType; + + @ApiPropertyOptional({ description: 'Field name' }) + @IsOptional() + @IsString() + fieldName?: string; + + @ApiPropertyOptional({ description: 'Field ID' }) + @IsOptional() + @IsNumber() + fieldId?: number; + + @ApiPropertyOptional({ description: 'Field code' }) + @IsOptional() + @IsString() + fieldCode?: string; + + @ApiPropertyOptional({ description: 'Payload' }) + @IsOptional() + @IsObject() + payload?: unknown; + + @ApiPropertyOptional({ description: 'Value' }) + @IsOptional() + @IsObject() + value?: unknown; +} diff --git a/backend/src/modules/entity/entity-field/field-value/dto/update-field-value.dto.ts b/backend/src/modules/entity/entity-field/field-value/dto/update-field-value.dto.ts new file mode 100644 index 0000000..8a53d9a --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/dto/update-field-value.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsEnum } from 'class-validator'; + +import { ObjectState } from '@/common'; + +import { FieldValueDto } from './field-value.dto'; + +export class UpdateFieldValueDto extends PickType(FieldValueDto, ['fieldId', 'fieldType', 'payload'] as const) { + @ApiProperty({ enum: ObjectState }) + @IsEnum(ObjectState) + state?: ObjectState; +} diff --git a/backend/src/modules/entity/entity-field/field-value/entities/field-value.entity.ts b/backend/src/modules/entity/entity-field/field-value/entities/field-value.entity.ts new file mode 100644 index 0000000..2cffa90 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/entities/field-value.entity.ts @@ -0,0 +1,91 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { FieldType } from '../../common'; +import { CreateFieldValueDto, FieldValueDto } from '../dto'; +import { + FieldPayloadValue, + FieldPayloadValues, + FieldPayloadOption, + FieldPayloadOptions, + FieldPayloadParticipants, + FieldPayloadChecklistItem, +} from '../types'; + +type PayloadType = + | FieldPayloadValue + | FieldPayloadValue + | FieldPayloadValue + | FieldPayloadValue + | FieldPayloadValues + | FieldPayloadOption + | FieldPayloadOptions + | FieldPayloadParticipants + | FieldPayloadValue; + +@Entity() +export class FieldValue { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + fieldId: number; + + @Column() + fieldType: FieldType; + + @Column({ type: 'jsonb' }) + payload: PayloadType; + + @Column() + entityId: number; + + @Column() + accountId: number; + + constructor(accountId: number, fieldId: number, fieldType: FieldType, payload: PayloadType, entityId: number) { + this.accountId = accountId; + this.fieldId = fieldId; + this.fieldType = fieldType; + this.payload = payload; + this.entityId = entityId; + } + + static fromDto(accountId: number, entityId: number, dto: CreateFieldValueDto): FieldValue { + return new FieldValue(accountId, dto.fieldId, dto.fieldType, dto.payload, entityId); + } + + toDto(): FieldValueDto { + return new FieldValueDto({ ...this }); + } + + getValue(): T | null { + switch (this.fieldType) { + case FieldType.Text: + case FieldType.RichText: + case FieldType.Link: + case FieldType.Number: + case FieldType.Value: + case FieldType.Formula: + case FieldType.Switch: + case FieldType.Date: + case FieldType.Participant: + case FieldType.File: + return (this.payload as FieldPayloadValue).value; + case FieldType.MultiText: + case FieldType.Phone: + case FieldType.Email: + return (this.payload as FieldPayloadValues).values as T; + case FieldType.Select: + case FieldType.ColoredSelect: + return (this.payload as FieldPayloadOption).optionId as T; + case FieldType.MultiSelect: + case FieldType.ColoredMultiSelect: + case FieldType.CheckedMultiSelect: + return (this.payload as FieldPayloadOptions).optionIds as T; + case FieldType.Participants: + return (this.payload as FieldPayloadParticipants).userIds as T; + case FieldType.Checklist: + return (this.payload as FieldPayloadValue).value as T; + } + } +} diff --git a/backend/src/modules/entity/entity-field/field-value/entities/index.ts b/backend/src/modules/entity/entity-field/field-value/entities/index.ts new file mode 100644 index 0000000..3045f27 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/entities/index.ts @@ -0,0 +1 @@ +export * from './field-value.entity'; diff --git a/backend/src/modules/entity/entity-field/field-value/field-value.handler.ts b/backend/src/modules/entity/entity-field/field-value/field-value.handler.ts new file mode 100644 index 0000000..db01abe --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/field-value.handler.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common/events'; +import { FieldValueService } from './field-value.service'; + +@Injectable() +export class FieldValueHandler { + constructor(private readonly service: FieldValueService) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + await this.service.removeUser(event); + } +} diff --git a/backend/src/modules/entity/entity-field/field-value/field-value.service.ts b/backend/src/modules/entity/entity-field/field-value/field-value.service.ts new file mode 100644 index 0000000..71cece2 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/field-value.service.ts @@ -0,0 +1,515 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { create, all, MathJsInstance } from 'mathjs'; + +import { DateUtil, isUnique, ObjectState } from '@/common'; +import { StorageService } from '@/modules/storage/storage.service'; + +import { FieldType, FieldTypes, FormulaUtil } from '../common'; +import { Field } from '../field/entities'; +import { FieldService } from '../field/field.service'; +import { FieldOptionService } from '../field-option/field-option.service'; + +import { CreateFieldValueDto, UpdateFieldValueDto, SimpleFieldValueDto } from './dto'; +import { FieldValue } from './entities'; +import { + FieldPayloadChecklistItem, + FieldPayloadOption, + FieldPayloadOptions, + FieldPayloadParticipants, + FieldPayloadValue, + FieldPayloadValues, +} from './types'; + +interface FindFilter { + accountId: number; + fieldId?: number | number[]; + entityId?: number; + type?: FieldType | FieldType[]; + value?: string; +} +interface CalculateEntity { + entityTypeId: number; + entityId: number; + recalculate?: boolean; + children?: CalculateEntity[]; +} + +@Injectable() +export class FieldValueService { + private readonly logger = new Logger(FieldValueService.name); + private math: MathJsInstance; + + constructor( + @InjectRepository(FieldValue) + private readonly repository: Repository, + private readonly storageService: StorageService, + private readonly fieldService: FieldService, + private readonly fieldOptionService: FieldOptionService, + ) { + this.math = create(all, {}); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + this.math.SymbolNode.onUndefinedSymbol = function (_name: string) { + return 0; + }; + } + + async createSimple( + accountId: number, + entityTypeId: number, + entityId: number, + dto: SimpleFieldValueDto, + ): Promise { + if (dto.fieldType || dto.fieldName || dto.fieldId || dto.fieldCode) { + const field = await this.fieldService.findOne({ + accountId, + entityTypeId, + type: dto.fieldType, + name: dto.fieldName, + id: dto.fieldId, + code: dto.fieldCode, + }); + if (field) { + const payload = await this.getPayload({ accountId, field, dto }); + if (payload) { + return this.upsert({ accountId, entityId, dto: { fieldId: field.id, fieldType: field.type, payload } }); + } + } + } + + return null; + } + + async createManySimple({ + accountId, + entityTypeId, + entityId, + dtos, + }: { + accountId: number; + entityTypeId: number; + entityId: number; + dtos: SimpleFieldValueDto[]; + }): Promise<{ fieldValues: FieldValue[]; recalculate: boolean; updateParticipants: boolean; value?: number }> { + const fieldValues = ( + await Promise.all(dtos.map((dto) => this.createSimple(accountId, entityTypeId, entityId, dto))) + ).filter(Boolean); + const recalculate = fieldValues.some((value) => FieldTypes.calculable.includes(value.fieldType)); + const updateParticipants = fieldValues.some((value) => FieldTypes.participant.includes(value.fieldType)); + const value = fieldValues.find((value) => value.fieldType === FieldType.Value)?.getValue(); + + return { fieldValues, recalculate, updateParticipants, value }; + } + + async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + + async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).getMany(); + } + + async processBatch({ + accountId, + entityId, + dtos, + }: { + accountId: number; + entityId: number; + dtos: UpdateFieldValueDto[]; + }): Promise<{ recalculate: boolean; updateParticipants: boolean; value?: number }> { + if (!dtos) return { recalculate: false, updateParticipants: false }; + + let recalculate = false; + let updateParticipants = false; + let value: number | undefined = undefined; + for (const dto of dtos) { + if ([ObjectState.Created, ObjectState.Updated].includes(dto.state)) { + await this.setValue({ accountId, entityId, fieldId: dto.fieldId, dto }); + } else if (dto.state === ObjectState.Deleted) { + await this.delete({ accountId, entityId, fieldId: dto.fieldId }); + } + + recalculate ||= FieldTypes.calculable.includes(dto.fieldType); + updateParticipants ||= FieldTypes.participant.includes(dto.fieldType); + if (dto.fieldType === FieldType.Value) { + value = dto.payload?.value as number; + } + } + return { recalculate, updateParticipants, value }; + } + + async setValue({ + accountId, + entityId, + fieldId, + dto, + }: { + accountId: number; + entityId: number; + fieldId: number | null | undefined; + dto: CreateFieldValueDto; + }): Promise { + dto.fieldId = fieldId ?? dto.fieldId; + try { + await this.upsert({ accountId, entityId, dto }); + } catch (e) { + this.logger.warn( + `Set value error. accountId=${accountId}; entityId=${entityId}; fieldId=${fieldId}; dto=${JSON.stringify(dto)}`, + (e as Error)?.stack, + ); + } + } + + async calculateFormulas({ + accountId, + calcEntity, + previousEntityIds, + hasUpdates = true, + }: { + accountId: number; + calcEntity: CalculateEntity; + previousEntityIds: number[]; + hasUpdates: boolean; + }) { + const formulaFields = await this.fieldService.findMany({ + accountId, + entityTypeId: calcEntity.entityTypeId, + type: FieldTypes.formula, + }); + const unprocessed = + calcEntity.children?.filter((ce) => ce.recalculate && !previousEntityIds.includes(ce.entityId)) || []; + if ((hasUpdates || formulaFields.length) && unprocessed.length) { + await Promise.all( + unprocessed.map((child) => + this.calculateFormulas({ + accountId, + calcEntity: child, + previousEntityIds: [...previousEntityIds, calcEntity.entityId], + hasUpdates: formulaFields.length > 0, + }), + ), + ); + } + let formulaValueChanged = false; + if (formulaFields.length) { + const fieldValues = await this.getValuesForFormula({ + accountId, + calcEntities: [calcEntity, ...(calcEntity.children || [])], + }); + for (const formulaField of formulaFields) { + const key = FormulaUtil.createFieldKey({ entityTypeId: calcEntity.entityTypeId, fieldId: formulaField.id }); + const value = formulaField.value ? this.math.evaluate(formulaField.value, fieldValues) : null; + const currentValue = fieldValues.get(key); + if (currentValue !== value && (formulaField.type === FieldType.Formula || value !== null)) { + try { + await this.setValue({ + accountId, + entityId: calcEntity.entityId, + fieldId: formulaField.id, + dto: { + fieldId: formulaField.id, + fieldType: formulaField.type, + payload: { value }, + }, + }); + fieldValues.set(key, value); + formulaValueChanged = true; + } catch (e) { + this.logger.error( + // eslint-disable-next-line max-len + `Calculate formula error. accountId=${accountId}; entityId=${calcEntity.entityId}; fieldId=${formulaField.id}; value=${value}`, + (e as Error)?.stack, + ); + } + } + } + } + if (formulaValueChanged && unprocessed.length) { + await Promise.all( + unprocessed.map((child) => + this.calculateFormulas({ + accountId, + calcEntity: child, + previousEntityIds: [...previousEntityIds, calcEntity.entityId], + hasUpdates: formulaFields.length > 0, + }), + ), + ); + } + } + + private async getValuesForFormula({ + accountId, + calcEntities, + }: { + accountId: number; + calcEntities: CalculateEntity[]; + }): Promise> { + const values = new Map(); + + for (const calcEntity of calcEntities) { + const fieldValues = await this.findMany({ + accountId, + entityId: calcEntity.entityId, + type: FieldTypes.calculable, + }); + if (fieldValues.length) { + for (const fieldValue of fieldValues) { + const fieldKey = FormulaUtil.createFieldKey({ + entityTypeId: calcEntity.entityTypeId, + fieldId: fieldValue.fieldId, + }); + const currentValue = values.get(fieldKey); + values.set(fieldKey, (currentValue || 0) + (fieldValue.getValue() || 0)); + } + } + } + + return values; + } + + async removeUser({ accountId, userId, newUserId }: { accountId: number; userId: number; newUserId?: number | null }) { + if (newUserId) { + await this.repository + .createQueryBuilder() + .update() + .set({ payload: () => `'{ "value": ${newUserId} }'` }) + .where('account_id = :accountId', { accountId }) + .andWhere('field_type = :fieldType', { fieldType: FieldType.Participant }) + .andWhere(`(payload->>'value')::integer = :userId`, { userId }) + .execute(); + + await this.repository + .createQueryBuilder() + .update() + .set({ + payload: () => + // eslint-disable-next-line max-len + `jsonb_set(payload, '{userIds}', (SELECT jsonb_agg(DISTINCT CASE WHEN elem::integer = ${userId} THEN ${newUserId} ELSE elem::integer END) FROM jsonb_array_elements(payload->'userIds') AS elem))`, + }) + .where('account_id = :accountId', { accountId }) + .andWhere('field_type = :fieldType', { fieldType: FieldType.Participants }) + .andWhere(`payload->'userIds' @> jsonb_build_array(${userId})`) + .execute(); + } else { + await this.repository + .createQueryBuilder() + .delete() + .where('account_id = :accountId', { accountId }) + .andWhere('field_type = :fieldType', { fieldType: FieldType.Participant }) + .andWhere(`(payload->>'value')::integer = :userId`, { userId }) + .execute(); + + await this.repository + .createQueryBuilder() + .update() + .set({ + payload: () => + // eslint-disable-next-line max-len + `jsonb_set(payload, '{userIds}', (SELECT jsonb_agg(elem) FROM jsonb_array_elements(payload->'userIds') AS elem WHERE elem::integer != ${userId}))`, + }) + .where('account_id = :accountId', { accountId }) + .andWhere('field_type = :fieldType', { fieldType: FieldType.Participants }) + .andWhere(`payload->'userIds' @> jsonb_build_array(${userId})`) + .execute(); + } + } + + private async getPayload({ + accountId, + field, + dto, + }: { + accountId: number; + field: Field; + dto: SimpleFieldValueDto; + }): Promise { + if (dto.payload) { + return dto.payload; + } + + if (dto.value === null || dto.value === undefined) { + return null; + } + + if (FieldTypes.withOptions.includes(field.type)) { + const options = await this.fieldOptionService.findMany({ accountId, fieldId: field.id }); + if (FieldTypes.select.includes(field.type)) { + const option = options.find((option) => option.label === dto.value); + if (option) { + return this.formatPayload({ type: field.type, value: option.id }); + } + } else if (FieldTypes.multiSelect.includes(field.type)) { + const values = String(dto.value) + .split(',') + .map((v) => v.trim()); + const optionIds = options.filter((option) => values.includes(option.label.trim())).map((option) => option.id); + if (optionIds.length) { + return this.formatPayload({ type: field.type, value: optionIds }); + } + } + } + + return this.formatPayload({ type: field.type, value: dto.value }); + } + + private formatPayload({ type, value }: { type: FieldType; value: unknown }) { + switch (type) { + case FieldType.Text: + case FieldType.RichText: + case FieldType.Link: + return { value: String(value) } as FieldPayloadValue; + case FieldType.Number: + case FieldType.Value: + case FieldType.Formula: + case FieldType.Participant: + return { value: Number(value) } as FieldPayloadValue; + case FieldType.MultiText: + case FieldType.Phone: + case FieldType.Email: + return { + values: Array.isArray(value) ? value.map((i) => String(i)) : [String(value)], + } as FieldPayloadValues; + case FieldType.File: + return { + value: Array.isArray(value) ? value.map((i) => String(i)) : [String(value)], + } as FieldPayloadValue; + case FieldType.Switch: + return { value: Boolean(value) } as FieldPayloadValue; + case FieldType.Date: { + const date = new Date(value as string | number | Date); + return date && DateUtil.isValid(date) ? ({ value: date.toISOString() } as FieldPayloadValue) : null; + } + case FieldType.Select: + case FieldType.ColoredSelect: + return { optionId: Number(value) } as FieldPayloadOption; + case FieldType.MultiSelect: + case FieldType.ColoredMultiSelect: + case FieldType.CheckedMultiSelect: + return { + optionIds: Array.isArray(value) ? value.map((i) => Number(i)) : [Number(value)], + } as FieldPayloadOptions; + case FieldType.Participants: + return { + userIds: Array.isArray(value) ? value.map((i) => Number(i)) : [Number(value)], + } as FieldPayloadParticipants; + case FieldType.Checklist: + return { + value: Array.isArray(value) + ? value.map((i) => ({ text: String(i), checked: false })) + : [{ text: String(value), checked: false }], + } as FieldPayloadValue; + } + } + + private async upsert({ + accountId, + entityId, + dto, + }: { + accountId: number; + entityId: number; + dto: CreateFieldValueDto; + }): Promise { + const newValue = FieldValue.fromDto(accountId, entityId, dto); + + if (newValue.fieldType === FieldType.File) { + const oldValue = await this.findOne({ accountId, entityId, fieldId: newValue.fieldId }); + const currentFiles = oldValue?.getValue() ?? []; + const newFiles = newValue.getValue() ?? []; + const added = newFiles.filter((f) => !currentFiles.includes(f)); + if (added.length) { + await this.storageService.markUsedMany({ accountId, ids: added }); + } + const deleted = currentFiles.filter((f) => !newFiles.includes(f)); + if (deleted.length) { + await this.storageService.delete({ accountId, id: deleted }); + } + } + + await this.repository.upsert(newValue, ['fieldId', 'entityId']); + + return newValue; + } + + private async delete({ accountId, entityId, fieldId }: { accountId: number; entityId: number; fieldId: number }) { + const fieldValue = await this.findOne({ accountId, entityId, fieldId }); + if (fieldValue?.fieldType === FieldType.File) { + await this.storageService.delete({ accountId, id: fieldValue.getValue() }); + } + + await this.repository.delete({ accountId, fieldId, entityId }); + } + + async copyEntityFieldValues({ + accountId, + sourceEntityId, + targetEntityId, + }: { + accountId: number; + sourceEntityId: number; + targetEntityId: number; + }) { + const sourceFieldValues = await this.findMany({ accountId, entityId: sourceEntityId }); + for (const sourceFieldValue of sourceFieldValues) { + await this.upsert({ + accountId, + entityId: targetEntityId, + dto: { + fieldId: sourceFieldValue.fieldId, + fieldType: sourceFieldValue.fieldType, + payload: sourceFieldValue.payload, + }, + }); + } + } + + async getParticipantIds({ accountId, entityId }: { accountId: number; entityId: number }): Promise { + const participantIds: number[] = []; + const [participant, participants] = await Promise.all([ + this.findMany({ accountId, entityId, type: FieldType.Participant }), + this.findMany({ accountId, entityId, type: FieldType.Participants }), + ]); + participant.forEach((f) => { + const value = f.getValue(); + if (value) participantIds.push(value); + }); + participants.forEach((f) => { + const values = f.getValue(); + if (values?.length) participantIds.push(...values); + }); + return participantIds.length ? participantIds.filter(isUnique) : null; + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId: filter.accountId }); + + if (filter?.fieldId) { + if (Array.isArray(filter.fieldId)) { + qb.andWhere('field_id IN (:...fieldIds)', { fieldIds: filter.fieldId }); + } else { + qb.andWhere('field_id = :fieldId', { fieldId: filter.fieldId }); + } + } + + if (filter?.entityId) { + qb.andWhere('entity_id = :entityId', { entityId: filter.entityId }); + } + + if (filter?.type) { + if (Array.isArray(filter.type)) { + qb.andWhere('field_type IN (:...types)', { types: filter.type }); + } else { + qb.andWhere('field_type = :type', { type: filter.type }); + } + } + + if (filter?.value) { + qb.andWhere(`payload::jsonb::text ILIKE :value`, { value: `%${filter.value}%` }); + } + + return qb; + } +} diff --git a/backend/src/modules/entity/entity-field/field-value/index.ts b/backend/src/modules/entity/entity-field/field-value/index.ts new file mode 100644 index 0000000..0af9451 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './field-value.handler'; +export * from './field-value.service'; +export * from './types'; diff --git a/backend/src/modules/entity/entity-field/field-value/types/field-payload.ts b/backend/src/modules/entity/entity-field/field-value/types/field-payload.ts new file mode 100644 index 0000000..f83fda5 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/types/field-payload.ts @@ -0,0 +1,24 @@ +export interface FieldPayloadValue { + value: T | null; +} + +export interface FieldPayloadValues { + values: T[] | null; +} + +export interface FieldPayloadOption { + optionId: number | null; +} + +export interface FieldPayloadOptions { + optionIds: number[] | null; +} + +export interface FieldPayloadParticipants { + userIds: number[] | null; +} + +export interface FieldPayloadChecklistItem { + text: string; + checked: boolean; +} diff --git a/backend/src/modules/entity/entity-field/field-value/types/index.ts b/backend/src/modules/entity/entity-field/field-value/types/index.ts new file mode 100644 index 0000000..3045d23 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field-value/types/index.ts @@ -0,0 +1 @@ +export * from './field-payload'; diff --git a/backend/src/modules/entity/entity-field/field/dto/check-formula.dto.ts b/backend/src/modules/entity/entity-field/field/dto/check-formula.dto.ts new file mode 100644 index 0000000..7505201 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/dto/check-formula.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class CheckFormulaDto { + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiProperty() + @IsNumber() + fieldId: number; + + @ApiProperty() + @IsString() + formula: string; +} diff --git a/backend/src/modules/entity/entity-field/field/dto/create-field.dto.ts b/backend/src/modules/entity/entity-field/field/dto/create-field.dto.ts new file mode 100644 index 0000000..36d110b --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/dto/create-field.dto.ts @@ -0,0 +1,27 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +import { CreateFieldOptionDto } from '../../field-option'; +import { FieldDto } from './field.dto'; + +export class CreateFieldDto extends PickType(FieldDto, [ + 'name', + 'type', + 'code', + 'active', + 'sortOrder', + 'entityTypeId', + 'fieldGroupId', + 'value', + 'format', +] as const) { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + id?: number; + + @ApiPropertyOptional({ type: [CreateFieldOptionDto] }) + @IsOptional() + @IsArray() + options?: CreateFieldOptionDto[]; +} diff --git a/backend/src/modules/entity/entity-field/field/dto/field.dto.ts b/backend/src/modules/entity/entity-field/field/dto/field.dto.ts new file mode 100644 index 0000000..6e96e61 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/dto/field.dto.ts @@ -0,0 +1,83 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { FieldType } from '../../common'; +import { FieldOptionDto } from '../../field-option'; + +import { FieldCode, FieldFormat } from '../enums'; + +export class FieldDto { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Field name' }) + @IsString() + name: string; + + @ApiProperty({ enum: FieldType, description: 'Field type' }) + @IsEnum(FieldType) + type: FieldType; + + @ApiPropertyOptional({ nullable: true, enum: FieldCode, description: 'Field code' }) + @IsOptional() + @IsEnum(FieldCode) + code?: FieldCode | null; + + @ApiProperty({ description: 'Field active status' }) + @IsBoolean() + active: boolean; + + @ApiProperty({ description: 'Field sort order' }) + @IsNumber() + sortOrder: number; + + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiProperty({ nullable: true, description: 'Field group ID' }) + @IsOptional() + @IsNumber() + fieldGroupId: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'Field value for formulas' }) + @IsOptional() + @IsString() + value?: string | null; + + @ApiPropertyOptional({ enum: FieldFormat, nullable: true, description: 'Field format' }) + @IsOptional() + @IsEnum(FieldFormat) + format?: FieldFormat | null; + + @ApiProperty({ type: [FieldOptionDto], description: 'Field options' }) + @IsArray() + options: FieldOptionDto[] = []; + + constructor({ + id, + name, + type, + code, + active, + sortOrder, + entityTypeId, + fieldGroupId, + value, + format, + options, + }: FieldDto) { + this.id = id; + this.name = name; + this.type = type; + this.code = code; + this.active = active; + this.sortOrder = sortOrder; + this.entityTypeId = entityTypeId; + this.fieldGroupId = fieldGroupId; + this.value = value; + this.format = format; + this.options = options; + } +} diff --git a/backend/src/modules/entity/entity-field/field/dto/fields-settings.dto.ts b/backend/src/modules/entity/entity-field/field/dto/fields-settings.dto.ts new file mode 100644 index 0000000..1f4cf39 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/dto/fields-settings.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { FieldCode } from '../enums'; + +export class FieldsSettingsDto { + @ApiPropertyOptional({ enum: FieldCode, enumName: 'FieldCode', isArray: true, description: 'Active field codes' }) + @IsOptional() + @IsArray() + activeFieldCodes?: FieldCode[]; +} diff --git a/backend/src/modules/entity/entity-field/field/dto/index.ts b/backend/src/modules/entity/entity-field/field/dto/index.ts new file mode 100644 index 0000000..dabc412 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/dto/index.ts @@ -0,0 +1,5 @@ +export * from './check-formula.dto'; +export * from './create-field.dto'; +export * from './field.dto'; +export * from './fields-settings.dto'; +export * from './update-field.dto'; diff --git a/backend/src/modules/entity/entity-field/field/dto/update-field.dto.ts b/backend/src/modules/entity/entity-field/field/dto/update-field.dto.ts new file mode 100644 index 0000000..1db0552 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/dto/update-field.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional, PartialType, PickType } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { ObjectState } from '@/common'; + +import { UpdateFieldOptionDto } from '../../field-option'; +import { FieldDto } from './field.dto'; + +export class UpdateFieldDto extends PartialType( + PickType(FieldDto, ['name', 'type', 'code', 'active', 'sortOrder', 'fieldGroupId', 'value', 'format'] as const), +) { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + id: number; + + @ApiPropertyOptional({ type: [UpdateFieldOptionDto], description: 'Field options' }) + @IsOptional() + @IsArray() + options?: UpdateFieldOptionDto[] = []; + + @ApiPropertyOptional({ enum: ObjectState, description: 'Object state' }) + @IsOptional() + @IsEnum(ObjectState) + state?: ObjectState; +} diff --git a/backend/src/modules/entity/entity-field/field/entities/field.entity.ts b/backend/src/modules/entity/entity-field/field/entities/field.entity.ts new file mode 100644 index 0000000..4365c3a --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/entities/field.entity.ts @@ -0,0 +1,119 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { FieldType } from '../../common'; +import { FieldOption } from '../../field-option'; + +import { FieldCode, FieldFormat } from '../enums'; +import { CreateFieldDto, FieldDto, UpdateFieldDto } from '../dto'; + +@Entity() +export class Field { + @PrimaryColumn() + id: number; + + @Column() + name: string; + + @Column() + type: FieldType; + + @Column({ nullable: true }) + code: FieldCode | null; + + @Column() + active: boolean; + + @Column() + sortOrder: number; + + @Column() + entityTypeId: number; + + @Column({ nullable: true }) + fieldGroupId: number | null; + + @Column({ nullable: true }) + value: string | null; + + @Column({ nullable: true }) + format: FieldFormat | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + _options: FieldOption[] | null = null; + + constructor( + accountId: number, + id: number, + name: string, + type: FieldType, + code: FieldCode | null, + active: boolean, + sortOrder: number, + entityTypeId: number, + fieldGroupId: number | null, + value: string | null, + format: FieldFormat | null, + ) { + this.accountId = accountId; + this.id = id; + this.name = name; + this.type = type; + this.code = code; + this.active = active; + this.sortOrder = sortOrder; + this.entityTypeId = entityTypeId; + this.fieldGroupId = fieldGroupId; + this.value = value; + this.format = format; + this.createdAt = DateUtil.now(); + } + + public get options(): FieldOption[] | null { + return this._options; + } + public set options(value: FieldOption[] | null) { + this._options = value; + } + + public static fromDto(accountId: number, entityTypeId: number, dto: CreateFieldDto): Field { + return new Field( + accountId, + dto.id, + dto.name, + dto.type, + dto.code, + dto.active ?? true, + dto.sortOrder, + entityTypeId, + dto.fieldGroupId, + dto.value, + dto.format, + ); + } + + public toDto(): FieldDto { + const options = this._options ? this._options.map((option) => option.toDto()) : []; + + return new FieldDto({ ...this, options }); + } + + public update(dto: UpdateFieldDto): Field { + this.name = dto.name !== undefined ? dto.name : this.name; + this.type = dto.type !== undefined ? dto.type : this.type; + this.code = dto.code !== undefined ? dto.code : this.code; + this.active = dto.active !== undefined ? dto.active : this.active; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + this.fieldGroupId = dto.fieldGroupId !== undefined ? dto.fieldGroupId : this.fieldGroupId; + this.value = dto.value !== undefined ? dto.value : this.value; + this.format = dto.format !== undefined ? dto.format : this.format; + + return this; + } +} diff --git a/backend/src/modules/entity/entity-field/field/entities/index.ts b/backend/src/modules/entity/entity-field/field/entities/index.ts new file mode 100644 index 0000000..ae941f2 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/entities/index.ts @@ -0,0 +1 @@ +export * from './field.entity'; diff --git a/backend/src/modules/entity/entity-field/field/enums/field-code.enum.ts b/backend/src/modules/entity/entity-field/field/enums/field-code.enum.ts new file mode 100644 index 0000000..dec8c37 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/enums/field-code.enum.ts @@ -0,0 +1,15 @@ +export enum FieldCode { + Value = 'value', + StartDate = 'start_date', + EndDate = 'end_date', + Participants = 'participants', + Description = 'description', +} + +export const EditableFieldCodes = [ + FieldCode.Value, + FieldCode.StartDate, + FieldCode.EndDate, + FieldCode.Participants, + FieldCode.Description, +]; diff --git a/backend/src/modules/entity/entity-field/field/enums/field-format.enum.ts b/backend/src/modules/entity/entity-field/field/enums/field-format.enum.ts new file mode 100644 index 0000000..fa1e44a --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/enums/field-format.enum.ts @@ -0,0 +1,5 @@ +export enum FieldFormat { + Text = 'text', + Number = 'number', + Currency = 'currency', +} diff --git a/backend/src/modules/entity/entity-field/field/enums/index.ts b/backend/src/modules/entity/entity-field/field/enums/index.ts new file mode 100644 index 0000000..909db21 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/enums/index.ts @@ -0,0 +1,2 @@ +export * from './field-code.enum'; +export * from './field-format.enum'; diff --git a/backend/src/modules/entity/entity-field/field/errors/duplicate-field-name.error.ts b/backend/src/modules/entity/entity-field/field/errors/duplicate-field-name.error.ts new file mode 100644 index 0000000..a2f0649 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/errors/duplicate-field-name.error.ts @@ -0,0 +1,13 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class DuplicateFieldNameError extends ServiceError { + constructor(fieldName: string, message = 'Field name is already used') { + super({ + errorCode: 'field.duplicate_name', + status: HttpStatus.BAD_REQUEST, + message, + details: { fieldName }, + }); + } +} diff --git a/backend/src/modules/entity/entity-field/field/errors/field-used-in-formula.error.ts b/backend/src/modules/entity/entity-field/field/errors/field-used-in-formula.error.ts new file mode 100644 index 0000000..ae163a6 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/errors/field-used-in-formula.error.ts @@ -0,0 +1,13 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class FieldUsedInFormulaError extends ServiceError { + constructor(fieldId: number, message = 'Field used in formula') { + super({ + errorCode: 'field.used_in_formula', + status: HttpStatus.BAD_REQUEST, + message, + details: { fieldId }, + }); + } +} diff --git a/backend/src/modules/entity/entity-field/field/errors/formula-circular-dependency.error.ts b/backend/src/modules/entity/entity-field/field/errors/formula-circular-dependency.error.ts new file mode 100644 index 0000000..c02ba05 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/errors/formula-circular-dependency.error.ts @@ -0,0 +1,13 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class FormulaCircularDependencyError extends ServiceError { + constructor(fieldId: number, message = 'Field formula circular dependency detected') { + super({ + errorCode: 'field.formula_circular_dependency', + status: HttpStatus.BAD_REQUEST, + message, + details: { fieldId }, + }); + } +} diff --git a/backend/src/modules/entity/entity-field/field/errors/index.ts b/backend/src/modules/entity/entity-field/field/errors/index.ts new file mode 100644 index 0000000..7a129df --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/errors/index.ts @@ -0,0 +1,3 @@ +export * from './duplicate-field-name.error'; +export * from './field-used-in-formula.error'; +export * from './formula-circular-dependency.error'; diff --git a/backend/src/modules/entity/entity-field/field/field.controller.ts b/backend/src/modules/entity/entity-field/field/field.controller.ts new file mode 100644 index 0000000..78a8978 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/field.controller.ts @@ -0,0 +1,34 @@ +import { Body, Controller, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { FieldService } from './field.service'; +import { CheckFormulaDto, FieldsSettingsDto } from './dto'; + +@ApiTags('crm/fields') +@Controller('/crm') +@JwtAuthorized() +@TransformToDto() +export class FieldController { + constructor(private readonly service: FieldService) {} + + @ApiOkResponse({ description: 'Check formula', type: Boolean }) + @Post('fields/formula/check') + public async checkFormula(@CurrentAuth() { accountId }: AuthData, @Body() dto: CheckFormulaDto) { + return this.service.checkFormula({ accountId, ...dto }); + } + + @ApiCreatedResponse({ description: 'Update entity type field settings' }) + @Put('entity-types/:entityTypeId/fields-settings') + public async updateFieldsSettings( + @CurrentAuth() { accountId }: AuthData, + @Param('entityTypeId', ParseIntPipe) entityTypeId: number, + @Body() dto: FieldsSettingsDto, + ) { + return await this.service.updateFieldsSettings({ accountId, entityTypeId, activeFieldCodes: dto.activeFieldCodes }); + } +} diff --git a/backend/src/modules/entity/entity-field/field/field.service.ts b/backend/src/modules/entity/entity-field/field/field.service.ts new file mode 100644 index 0000000..001d3d6 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/field.service.ts @@ -0,0 +1,426 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { parse } from 'mathjs'; + +import { ObjectState } from '@/common'; +import { SequenceIdService } from '@/database'; + +import { SequenceName } from '@/CRM/common/enums/sequence-name.enum'; + +import { FieldEvent, FieldEventType, FieldType, FieldTypes, FormulaUtil } from '../common'; +import { FieldOptionService } from '../field-option'; + +import { FieldCode, EditableFieldCodes } from './enums'; +import { DuplicateFieldNameError, FieldUsedInFormulaError, FormulaCircularDependencyError } from './errors'; +import { CreateFieldDto, UpdateFieldDto } from './dto'; +import { ExpandableField } from './types'; +import { Field } from './entities'; + +interface CreateOptions { + skipProcessing?: boolean; +} + +interface FindFilter { + accountId: number; + id?: number | number[]; + entityTypeId?: number | number[]; + type?: FieldType | FieldType[]; + code?: string; + name?: string; + value?: string; + excludeId?: number | number[]; + excludeEntityTypeId?: number | number[]; +} +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class FieldService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Field) + private readonly repository: Repository, + private readonly sequenceIdService: SequenceIdService, + private readonly fieldOptionService: FieldOptionService, + ) {} + + private async nextIdentity(): Promise { + return this.sequenceIdService.nextIdentity(SequenceName.Field); + } + + async create({ + accountId, + entityTypeId, + dto, + options, + }: { + accountId: number; + entityTypeId: number; + dto: CreateFieldDto; + options?: CreateOptions; + }): Promise { + dto.id = dto.id ?? (await this.nextIdentity()); + + if (!options?.skipProcessing && FieldTypes.formula.includes(dto.type)) { + if (await this.hasCircularDependency({ accountId, entityTypeId, fieldId: dto.id, value: dto.value })) { + throw new FormulaCircularDependencyError(dto.id); + } + } + + if (await this.hasDuplicateName({ accountId, entityTypeId, name: dto.name })) { + throw new DuplicateFieldNameError(dto.name); + } + + const field = await this.repository.save(Field.fromDto(accountId, entityTypeId, dto)); + + if (dto.options) { + field.options = await this.fieldOptionService.createMany({ accountId, fieldId: dto.id, dtos: dto.options }); + } + + if (!options?.skipProcessing) { + this.eventEmitter.emit( + FieldEventType.FieldCreated, + new FieldEvent({ accountId, entityTypeId, fieldId: field.id, type: field.type }), + ); + } + + return field; + } + async createMany({ + accountId, + entityTypeId, + dtos, + options, + }: { + accountId: number; + entityTypeId: number; + dtos: CreateFieldDto[]; + options?: CreateOptions; + }): Promise { + return Promise.all(dtos.map((dto) => this.create({ accountId, entityTypeId, dto, options }))); + } + + async findOne(filter: FindFilter, options?: FindOptions): Promise { + const field = await this.createFindQb(filter).getOne(); + + return field && options?.expand + ? await this.expandOne({ accountId: filter.accountId, field, expand: options.expand }) + : field; + } + + async findMany(filter: FindFilter, options?: FindOptions): Promise { + const fields = await this.createFindQb(filter).orderBy('sort_order', 'ASC').getMany(); + + return fields && options?.expand + ? await this.expandMany({ accountId: filter.accountId, fields, expand: options.expand }) + : fields; + } + + async findManyIds(filter: FindFilter): Promise { + const fields = await this.createFindQb(filter) + .select('id') + .orderBy('sort_order', 'ASC') + .getRawMany<{ id: number }>(); + + return fields.map((field) => field.id); + } + + async getCount(filter: FindFilter): Promise { + return this.createFindQb(filter).getCount(); + } + + async hasPriceField({ accountId, entityTypeId }: { accountId: number; entityTypeId: number }): Promise { + const count = await this.getCount({ accountId, entityTypeId, type: FieldType.Value }); + + return count > 0; + } + + async checkFormula({ + accountId, + entityTypeId, + fieldId, + formula, + }: { + accountId: number; + entityTypeId: number; + fieldId: number; + formula: string; + }): Promise { + if (!formula) { + return true; + } + + try { + parse(formula); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + return false; + } + + if (await this.hasCircularDependency({ accountId, entityTypeId, fieldId, value: formula })) { + throw new FormulaCircularDependencyError(fieldId); + } + + return true; + } + + async checkFormulaUsageField({ accountId, fieldId }: { accountId: number; fieldId: number }): Promise { + return (await this.getCount({ accountId, type: FieldTypes.formula, value: `f${fieldId}` })) > 0; + } + async checkFormulaUsageEntityType({ + accountId, + entityTypeId, + checkEntityTypeId, + excludeEntityTypeId, + }: { + accountId: number; + entityTypeId?: number; + checkEntityTypeId: number; + excludeEntityTypeId?: number; + }): Promise { + return ( + (await this.getCount({ + accountId, + type: FieldTypes.formula, + entityTypeId, + excludeEntityTypeId, + value: `et${checkEntityTypeId}`, + })) > 0 + ); + } + + private async hasDuplicateName({ + accountId, + entityTypeId, + name, + id, + }: { + accountId: number; + entityTypeId: number; + name: string; + id?: number; + }): Promise { + return (await this.createFindQb({ accountId, entityTypeId, name, excludeId: id }).getCount()) > 0; + } + + async update({ + accountId, + entityTypeId, + fieldId, + dto, + options, + }: { + accountId: number; + entityTypeId: number; + fieldId: number; + dto: UpdateFieldDto; + options?: CreateOptions; + }): Promise { + if (dto.value && !options?.skipProcessing && FieldTypes.formula.includes(dto.type)) { + if (await this.hasCircularDependency({ accountId, entityTypeId, fieldId, value: dto.value })) { + throw new FormulaCircularDependencyError(fieldId); + } + } + + if (dto.name && (await this.hasDuplicateName({ accountId, entityTypeId, name: dto.name, id: fieldId }))) { + throw new DuplicateFieldNameError(dto.name); + } + + const field = await this.findOne({ accountId, id: fieldId }); + await this.repository.save(field.update(dto)); + + if (dto.options) { + field.options = await this.fieldOptionService.processBatch({ accountId, fieldId, dtos: dto.options }); + } + + if (!options?.skipProcessing) { + this.eventEmitter.emit( + FieldEventType.FieldUpdated, + new FieldEvent({ accountId, entityTypeId, fieldId: field.id, type: field.type }), + ); + } + + return field; + } + + async updateBatch({ + accountId, + entityTypeId, + dtos, + }: { + accountId: number; + entityTypeId: number; + dtos: UpdateFieldDto[]; + }): Promise { + const fields: Field[] = []; + + for (const dto of dtos) { + switch (dto.state) { + case ObjectState.Created: + fields.push(await this.create({ accountId, entityTypeId, dto: dto as CreateFieldDto })); + break; + case ObjectState.Updated: + fields.push(await this.update({ accountId, entityTypeId, fieldId: dto.id, dto })); + break; + case ObjectState.Deleted: + await this.delete({ accountId, entityTypeId, fieldId: dto.id }); + break; + } + } + + return fields; + } + + private async delete({ + accountId, + entityTypeId, + fieldId, + }: { + accountId: number; + entityTypeId: number; + fieldId: number; + }): Promise { + if (await this.checkFormulaUsageField({ accountId, fieldId })) { + throw new FieldUsedInFormulaError(fieldId); + } + await this.repository.delete({ id: fieldId, accountId, entityTypeId }); + this.eventEmitter.emit(FieldEventType.FieldDeleted, new FieldEvent({ accountId, entityTypeId, fieldId })); + } + + async updateFieldsSettings({ + accountId, + entityTypeId, + activeFieldCodes, + }: { + accountId: number; + entityTypeId: number; + activeFieldCodes: FieldCode[]; + }) { + for (const code of EditableFieldCodes) { + const field = await this.findOne({ accountId, entityTypeId, code: code }); + if (field) { + await this.repository.update({ accountId, id: field.id }, { active: activeFieldCodes.includes(code) }); + } + } + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId: filter.accountId }); + if (filter?.id) { + if (Array.isArray(filter.id)) { + qb.andWhere('id IN (:...ids)', { ids: filter.id }); + } else { + qb.andWhere('id = :id', { id: filter.id }); + } + } + if (filter?.entityTypeId) { + if (Array.isArray(filter.entityTypeId)) { + qb.andWhere('entity_type_id IN (:...entityTypeIds)', { entityTypeIds: filter.entityTypeId }); + } else { + qb.andWhere('entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + } + } + if (filter?.excludeEntityTypeId) { + if (Array.isArray(filter.excludeEntityTypeId)) { + qb.andWhere('entity_type_id NOT IN (:...excludeEntityTypeIds)', { + excludeEntityTypeIds: filter.excludeEntityTypeId, + }); + } else { + qb.andWhere('entity_type_id != :excludeEntityTypeId', { excludeEntityTypeId: filter.excludeEntityTypeId }); + } + } + if (filter?.type) { + if (Array.isArray(filter.type)) { + qb.andWhere('type IN (:...types)', { types: filter.type }); + } else { + qb.andWhere('type = :type', { type: filter.type }); + } + } + if (filter?.code) { + qb.andWhere('code = :code', { code: filter.code }); + } + if (filter?.name) { + qb.andWhere('name = :name', { name: filter.name }); + } + if (filter?.value) { + qb.andWhere('value ilike :value', { value: `%${filter.value}%` }); + } + + if (filter?.excludeId) { + if (Array.isArray(filter.excludeId)) { + if (filter.excludeId.length) { + qb.andWhere('id NOT IN (:...excludeIds)', { excludeIds: filter.excludeId }); + } + } else { + qb.andWhere('id != :excludeId', { excludeId: filter.excludeId }); + } + } + return qb; + } + + private async expandOne({ + accountId, + field, + expand, + }: { + accountId: number; + field: Field; + expand: ExpandableField[]; + }): Promise { + if (expand.includes('options')) { + field.options = await this.fieldOptionService.findMany({ accountId, fieldId: field.id }); + } + return field; + } + private async expandMany({ + accountId, + fields, + expand, + }: { + accountId: number; + fields: Field[]; + expand: ExpandableField[]; + }): Promise { + return await Promise.all(fields.map((field) => this.expandOne({ accountId, field, expand }))); + } + + private async hasCircularDependency({ + accountId, + entityTypeId, + fieldId, + value, + }: { + accountId: number; + entityTypeId: number; + fieldId: number; + value: string | null; + }): Promise { + const visit = async (etId: number, fId: number, formula: string, visited: string[]): Promise => { + const fieldKey = FormulaUtil.createFieldKey({ entityTypeId: etId, fieldId: fId }); + if (visited.includes(fieldKey)) { + return true; + } + + const formulaKeys = FormulaUtil.extractVariables(formula); + for (const formulaKey of formulaKeys) { + const { entityTypeId: formulaEtId, fieldId: formulaFId } = FormulaUtil.parseFieldKey(formulaKey); + const field = await this.findOne({ + accountId, + entityTypeId: formulaEtId, + id: formulaFId, + type: FieldTypes.formula, + }); + if (field && (await visit(field.entityTypeId, field.id, field.value, [...visited, fieldKey]))) { + return true; + } + } + + return false; + }; + + return await visit(entityTypeId, fieldId, value || '', []); + } +} diff --git a/backend/src/modules/entity/entity-field/field/index.ts b/backend/src/modules/entity/entity-field/field/index.ts new file mode 100644 index 0000000..af8e201 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/index.ts @@ -0,0 +1,6 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './field.controller'; +export * from './field.service'; +export * from './types'; diff --git a/backend/src/modules/entity/entity-field/field/types/expandable-field.ts b/backend/src/modules/entity/entity-field/field/types/expandable-field.ts new file mode 100644 index 0000000..d30f22b --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'options'; diff --git a/backend/src/modules/entity/entity-field/field/types/index.ts b/backend/src/modules/entity/entity-field/field/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/entity/entity-field/field/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/entity/entity-info/dto/entity-info.dto.ts b/backend/src/modules/entity/entity-info/dto/entity-info.dto.ts new file mode 100644 index 0000000..7da1770 --- /dev/null +++ b/backend/src/modules/entity/entity-info/dto/entity-info.dto.ts @@ -0,0 +1,64 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class EntityInfoDto { + @ApiProperty({ description: 'Entity ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Entity name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiProperty({ description: 'Owner (Responsible) User ID' }) + @IsNumber() + ownerId: number; + + @ApiProperty({ description: 'Board ID', nullable: true }) + @IsOptional() + @IsNumber() + boardId: number | null; + + @ApiProperty({ description: 'Stage ID', nullable: true }) + @IsOptional() + @IsNumber() + stageId: number | null; + + @ApiProperty({ description: 'Date and time when the entity was created' }) + @IsString() + createdAt: string; + + @ApiPropertyOptional({ nullable: true, description: 'Date and time when the entity was closed' }) + @IsOptional() + @IsString() + closedAt?: string | null; + + @ApiPropertyOptional({ description: 'Entity ID from which the entity was copied', nullable: true }) + @IsOptional() + @IsNumber() + copiedFrom?: number | null; + + @ApiPropertyOptional({ description: 'Number of times the entity has been copied', nullable: true }) + @IsOptional() + @IsNumber() + copiedCount?: number | null; + + @ApiPropertyOptional({ description: 'Array of participant IDs', nullable: true, type: [Number] }) + @IsOptional() + @IsNumber({}, { each: true }) + participantIds?: number[] | null; + + @ApiPropertyOptional({ description: 'Whether the user has access to the entity', nullable: true }) + @IsOptional() + @IsBoolean() + hasAccess?: boolean | null; + + @ApiPropertyOptional({ description: 'Is focused?' }) + @IsOptional() + @IsBoolean() + focused?: boolean; +} diff --git a/backend/src/modules/entity/entity-info/dto/index.ts b/backend/src/modules/entity/entity-info/dto/index.ts new file mode 100644 index 0000000..4069e1c --- /dev/null +++ b/backend/src/modules/entity/entity-info/dto/index.ts @@ -0,0 +1 @@ +export * from './entity-info.dto'; diff --git a/backend/src/modules/entity/entity-info/entity-info.controller.ts b/backend/src/modules/entity/entity-info/entity-info.controller.ts new file mode 100644 index 0000000..2fabfa5 --- /dev/null +++ b/backend/src/modules/entity/entity-info/entity-info.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { EntityInfoDto } from './dto'; +import { EntityInfoService } from './entity-info.service'; + +@ApiTags('crm/entities') +@Controller('crm/entities/:entityId/info') +@JwtAuthorized({ prefetch: { user: true } }) +export class EntityInfoController { + constructor(private readonly service: EntityInfoService) {} + + @ApiOperation({ summary: 'Get entity info', description: 'Get entity info' }) + @ApiParam({ name: 'entityId', description: 'Entity ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Entity info', type: EntityInfoDto }) + @Get() + async findOne( + @CurrentAuth() { accountId, user }: AuthData, + @Param('entityId', ParseIntPipe) entityId: number, + ): Promise { + return this.service.findOne({ accountId, user, entityId }); + } +} diff --git a/backend/src/modules/entity/entity-info/entity-info.module.ts b/backend/src/modules/entity/entity-info/entity-info.module.ts new file mode 100644 index 0000000..f2e342f --- /dev/null +++ b/backend/src/modules/entity/entity-info/entity-info.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { Entity } from '@/CRM/Model/Entity/Entity'; + +import { EntityInfoController } from './entity-info.controller'; +import { EntityInfoService } from './entity-info.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Entity]), IAMModule], + controllers: [EntityInfoController], + providers: [EntityInfoService], + exports: [EntityInfoService], +}) +export class EntityInfoModule {} diff --git a/backend/src/modules/entity/entity-info/entity-info.service.ts b/backend/src/modules/entity/entity-info/entity-info.service.ts new file mode 100644 index 0000000..076be26 --- /dev/null +++ b/backend/src/modules/entity/entity-info/entity-info.service.ts @@ -0,0 +1,95 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { Entity } from '@/CRM/Model/Entity/Entity'; + +import { EntityInfoDto } from './dto'; + +@Injectable() +export class EntityInfoService { + constructor( + @InjectRepository(Entity) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + ) {} + + async findMany({ + accountId, + user, + entityIds, + }: { + accountId: number; + user?: User | null; + entityIds: number[]; + }): Promise { + const entities = await this.createQb({ accountId, entityId: entityIds }).getMany(); + + const result = []; + + for (const entity of entities) { + result.push(await this.getEntityInfo({ user, entity })); + } + + return result; + } + + async findOne({ + accountId, + user, + entityId, + }: { + accountId: number; + user?: User | null; + entityId: number; + }): Promise { + const entity = await this.createQb({ accountId, entityId }).getOne(); + + return entity ? this.getEntityInfo({ user, entity }) : null; + } + + async getEntityInfo({ + user, + entity, + access, + }: { + user?: User | null; + entity: Entity; + access?: boolean; + }): Promise { + const hasAccess = + user && access === undefined + ? await this.authService.check({ action: 'view', user, authorizable: entity }) + : access; + + return { + id: entity.id, + name: entity.name, + entityTypeId: entity.entityTypeId, + ownerId: entity.responsibleUserId, + boardId: entity.boardId, + stageId: entity.stageId, + createdAt: entity.createdAt.toISOString(), + closedAt: entity.closedAt?.toISOString() ?? null, + hasAccess, + copiedFrom: entity.copiedFrom, + copiedCount: entity.copiedCount, + participantIds: entity.participantIds, + focused: entity.focused, + }; + } + + private createQb({ accountId, entityId }: { accountId: number; entityId: number | number[] }) { + const qb = this.repository.createQueryBuilder('entity').where('entity.accountId = :accountId', { accountId }); + + if (Array.isArray(entityId)) { + qb.andWhere('entity.id IN (:...entityId)', { entityId }); + } else { + qb.andWhere('entity.id = :entityId', { entityId }); + } + + return qb; + } +} diff --git a/backend/src/modules/entity/entity-info/index.ts b/backend/src/modules/entity/entity-info/index.ts new file mode 100644 index 0000000..93ca412 --- /dev/null +++ b/backend/src/modules/entity/entity-info/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entity-info.controller'; +export * from './entity-info.module'; +export * from './entity-info.service'; diff --git a/backend/src/modules/entity/entity-stage-history/dto/create-entity-stage-history.dto.ts b/backend/src/modules/entity/entity-stage-history/dto/create-entity-stage-history.dto.ts new file mode 100644 index 0000000..01e6070 --- /dev/null +++ b/backend/src/modules/entity/entity-stage-history/dto/create-entity-stage-history.dto.ts @@ -0,0 +1,16 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { EntityStageHistoryDto } from './entity-stage-history.dto'; + +export class CreateEntityStageHistoryDto extends PickType(EntityStageHistoryDto, [ + 'entityId', + 'boardId', + 'stageId', + 'createdAt', +] as const) { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + copiedFrom?: number; +} diff --git a/backend/src/modules/entity/entity-stage-history/dto/entity-stage-history.dto.ts b/backend/src/modules/entity/entity-stage-history/dto/entity-stage-history.dto.ts new file mode 100644 index 0000000..e5145aa --- /dev/null +++ b/backend/src/modules/entity/entity-stage-history/dto/entity-stage-history.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsNumber, IsOptional } from 'class-validator'; + +export class EntityStageHistoryDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + entityId: number; + + @ApiProperty() + @IsNumber() + boardId: number; + + @ApiProperty() + @IsNumber() + stageId: number; + + @ApiPropertyOptional() + @IsOptional() + @IsDateString() + createdAt?: string; +} diff --git a/backend/src/modules/entity/entity-stage-history/entities/entity-stage-history.entity.ts b/backend/src/modules/entity/entity-stage-history/entities/entity-stage-history.entity.ts new file mode 100644 index 0000000..80ed9ed --- /dev/null +++ b/backend/src/modules/entity/entity-stage-history/entities/entity-stage-history.entity.ts @@ -0,0 +1,44 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { CreateEntityStageHistoryDto } from '../dto/create-entity-stage-history.dto'; + +@Entity() +export class EntityStageHistory { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + entityId: number; + + @Column() + boardId: number; + + @Column() + stageId: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, entityId: number, boardId: number, stageId: number, createdAt?: Date) { + this.accountId = accountId; + this.entityId = entityId; + this.boardId = boardId; + this.stageId = stageId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public static fromDto(accountId: number, dto: CreateEntityStageHistoryDto): EntityStageHistory { + return new EntityStageHistory( + accountId, + dto.entityId, + dto.boardId, + dto.stageId, + dto.createdAt ? DateUtil.fromISOString(dto.createdAt) : undefined, + ); + } +} diff --git a/backend/src/modules/entity/entity-stage-history/entity-stage-history.handler.ts b/backend/src/modules/entity/entity-stage-history/entity-stage-history.handler.ts new file mode 100644 index 0000000..a4eb687 --- /dev/null +++ b/backend/src/modules/entity/entity-stage-history/entity-stage-history.handler.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { CrmEventType, EntityCreatedEvent, EntityEvent } from '@/CRM/common'; + +import { EntityStageHistoryService } from './entity-stage-history.service'; + +@Injectable() +export class EntityStageHistoryHandler { + constructor(private readonly service: EntityStageHistoryService) {} + + @OnEvent(CrmEventType.EntityCreated, { async: true }) + public async onEntityNew(event: EntityCreatedEvent): Promise { + if (event.boardId && event.stageId) { + if (event.copiedFrom) { + this.service.copyHistory(event.accountId, event.copiedFrom, event.entityId); + } else { + this.service.create(event.accountId, { ...event }); + } + } + } + + @OnEvent(CrmEventType.EntityStageChanged, { async: true }) + public async onEntityStageChanged(event: EntityEvent) { + if (event.boardId && event.stageId) { + this.service.create(event.accountId, { ...event }); + } + } +} diff --git a/backend/src/modules/entity/entity-stage-history/entity-stage-history.module.ts b/backend/src/modules/entity/entity-stage-history/entity-stage-history.module.ts new file mode 100644 index 0000000..4e2c0ee --- /dev/null +++ b/backend/src/modules/entity/entity-stage-history/entity-stage-history.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { EntityStageHistory } from './entities/entity-stage-history.entity'; +import { EntityStageHistoryService } from './entity-stage-history.service'; +import { EntityStageHistoryHandler } from './entity-stage-history.handler'; + +@Module({ + imports: [TypeOrmModule.forFeature([EntityStageHistory])], + providers: [EntityStageHistoryService, EntityStageHistoryHandler], +}) +export class EntityStageHistoryModule {} diff --git a/backend/src/modules/entity/entity-stage-history/entity-stage-history.service.ts b/backend/src/modules/entity/entity-stage-history/entity-stage-history.service.ts new file mode 100644 index 0000000..7aef5d2 --- /dev/null +++ b/backend/src/modules/entity/entity-stage-history/entity-stage-history.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { CreateEntityStageHistoryDto } from './dto/create-entity-stage-history.dto'; +import { EntityStageHistory } from './entities/entity-stage-history.entity'; + +@Injectable() +export class EntityStageHistoryService { + constructor( + @InjectRepository(EntityStageHistory) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, dto: CreateEntityStageHistoryDto): Promise { + return this.repository.save(EntityStageHistory.fromDto(accountId, dto)); + } + + public async copyHistory(accountId: number, fromId: number, toId: number) { + await this.repository.update({ accountId, entityId: fromId }, { entityId: toId }); + } +} diff --git a/backend/src/modules/entity/entity.module.ts b/backend/src/modules/entity/entity.module.ts new file mode 100644 index 0000000..25dce65 --- /dev/null +++ b/backend/src/modules/entity/entity.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { EntityEventModule } from './entity-event/entity-event.module'; +import { EntityFieldModule } from './entity-field/entity-field.module'; +import { EntityInfoModule } from './entity-info/entity-info.module'; +import { EntityStageHistoryModule } from './entity-stage-history/entity-stage-history.module'; + +@Module({ + imports: [EntityEventModule, EntityFieldModule, EntityInfoModule, EntityStageHistoryModule], +}) +export class EntityModule {} diff --git a/backend/src/modules/forms/forms.module.ts b/backend/src/modules/forms/forms.module.ts new file mode 100644 index 0000000..d343e1b --- /dev/null +++ b/backend/src/modules/forms/forms.module.ts @@ -0,0 +1,59 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { SchedulerModule } from '@/modules/scheduler/scheduler.module'; +import { StorageModule } from '@/modules/storage/storage.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { SiteFormField, SiteFormFieldController, SiteFormFieldService } from './site-form-field'; +import { SiteFormPage, SiteFormPageController, SiteFormPageService } from './site-form-page'; +import { SiteFormConsent, SiteFormConsentController, SiteFormConsentService } from './site-form-consent'; +import { SiteFormGratitude, SiteFormGratitudeController, SiteFormGratitudeService } from './site-form-gratitude'; +import { + SiteForm, + SiteFormController, + SiteFormEntityType, + SiteFormEntityTypeService, + SiteFormSchedule, + SiteFormScheduleService, + SiteFormService, +} from './site-form'; +import { SiteFormBuilderController, SiteFormBuilderService } from './site-form-builder'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + SiteForm, + SiteFormEntityType, + SiteFormSchedule, + SiteFormPage, + SiteFormConsent, + SiteFormGratitude, + SiteFormField, + ]), + IAMModule, + StorageModule, + CrmModule, + SchedulerModule, + ], + providers: [ + SiteFormService, + SiteFormEntityTypeService, + SiteFormScheduleService, + SiteFormConsentService, + SiteFormGratitudeService, + SiteFormPageService, + SiteFormFieldService, + SiteFormBuilderService, + ], + controllers: [ + SiteFormController, + SiteFormConsentController, + SiteFormGratitudeController, + SiteFormPageController, + SiteFormFieldController, + SiteFormBuilderController, + ], +}) +export class FormsModule {} diff --git a/backend/src/modules/forms/site-form-builder/dto/index.ts b/backend/src/modules/forms/site-form-builder/dto/index.ts new file mode 100644 index 0000000..5c5c264 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/index.ts @@ -0,0 +1,18 @@ +export * from './public-site-form-consent.dto'; +export * from './public-site-form-field-entity-field.dto'; +export * from './public-site-form-field-schedule-date.dto'; +export * from './public-site-form-field-schedule-spot.dto'; +export * from './public-site-form-field-schedule-time.dto'; +export * from './public-site-form-field-schedule.dto'; +export * from './public-site-form-field.dto'; +export * from './public-site-form-gratitude.dto'; +export * from './public-site-form-option.dto'; +export * from './public-site-form-page.dto'; +export * from './public-site-form.dto'; +export * from './site-form-analytic-data.dto'; +export * from './site-form-data-plain.dto'; +export * from './site-form-data.dto'; +export * from './site-form-field-data.dto'; +export * from './site-form-file-upload-request'; +export * from './site-form-file-upload-result'; +export * from './site-form-result.dto'; diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-consent.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-consent.dto.ts new file mode 100644 index 0000000..fc56198 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-consent.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +export class PublicSiteFormConsentDto { + @ApiProperty({ description: 'Site form consent enabled' }) + @IsBoolean() + isEnabled: boolean; + + @ApiPropertyOptional({ nullable: true, description: 'Site form consent text' }) + @IsOptional() + @IsString() + text?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Site form consent link URL' }) + @IsOptional() + @IsString() + linkUrl?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Site form consent link text' }) + @IsOptional() + @IsString() + linkText?: string | null; + + @ApiPropertyOptional({ description: 'Site form consent default value' }) + @IsOptional() + @IsBoolean() + defaultValue?: boolean; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-entity-field.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-entity-field.dto.ts new file mode 100644 index 0000000..f072264 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-entity-field.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsObject, IsOptional } from 'class-validator'; + +import { FieldType } from '@/modules/entity/entity-field/common'; + +import { PublicSiteFormOptionDto } from './public-site-form-option.dto'; + +export class PublicSiteFormFieldEntityFieldDto { + @ApiProperty({ enum: FieldType, description: 'Field type' }) + @IsEnum(FieldType) + fieldType: FieldType; + + @ApiPropertyOptional({ type: [PublicSiteFormOptionDto], nullable: true, description: 'Field options' }) + @IsOptional() + @IsArray() + options?: PublicSiteFormOptionDto[] | null; + + @ApiPropertyOptional({ nullable: true, description: 'Field validation required' }) + @IsOptional() + @IsBoolean() + isValidationRequired: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'Field validation pattern' }) + @IsOptional() + @IsObject() + meta?: object | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-date.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-date.dto.ts new file mode 100644 index 0000000..a3743df --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-date.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class PublicSiteFormFieldScheduleDateDto { + @ApiPropertyOptional({ type: [String], nullable: true, description: 'Dates in ISO format' }) + @IsOptional() + @IsString({ each: true }) + dates?: string[] | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-spot.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-spot.dto.ts new file mode 100644 index 0000000..6dccb0a --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-spot.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString } from 'class-validator'; + +export class PublicSiteFormFieldScheduleSpotDto { + @ApiProperty({ description: 'Start time in ISO format' }) + @IsDateString() + from: string; + + @ApiProperty({ description: 'End time in ISO format' }) + @IsDateString() + to: string; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-time.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-time.dto.ts new file mode 100644 index 0000000..199b52c --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule-time.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { PublicSiteFormFieldScheduleSpotDto } from './public-site-form-field-schedule-spot.dto'; + +export class PublicSiteFormFieldScheduleTimeDto { + @ApiPropertyOptional({ type: [PublicSiteFormFieldScheduleSpotDto], nullable: true, description: 'Spots' }) + @IsOptional() + @IsArray() + spots?: PublicSiteFormFieldScheduleSpotDto[] | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule.dto.ts new file mode 100644 index 0000000..eeb9ba1 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field-schedule.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { PublicSiteFormOptionDto } from './public-site-form-option.dto'; + +export class PublicSiteFormFieldScheduleDto { + @ApiPropertyOptional({ type: [PublicSiteFormOptionDto], nullable: true, description: 'Field options' }) + @IsOptional() + @IsArray() + options?: PublicSiteFormOptionDto[] | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-field.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field.dto.ts new file mode 100644 index 0000000..19812dd --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-field.dto.ts @@ -0,0 +1,63 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { SiteFormFieldType } from '../../site-form-field'; +import { PublicSiteFormFieldEntityFieldDto } from './public-site-form-field-entity-field.dto'; +import { PublicSiteFormFieldScheduleDto } from './public-site-form-field-schedule.dto'; +import { PublicSiteFormFieldScheduleDateDto } from './public-site-form-field-schedule-date.dto'; +import { PublicSiteFormFieldScheduleTimeDto } from './public-site-form-field-schedule-time.dto'; + +@ApiExtraModels(PublicSiteFormFieldEntityFieldDto) +@ApiExtraModels(PublicSiteFormFieldScheduleDto) +@ApiExtraModels(PublicSiteFormFieldScheduleDateDto) +@ApiExtraModels(PublicSiteFormFieldScheduleTimeDto) +export class PublicSiteFormFieldDto { + @ApiProperty({ description: 'Site form field id' }) + @IsNumber() + id: number; + + @ApiPropertyOptional({ nullable: true, description: 'Site form field label' }) + @IsOptional() + @IsString() + label: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Site form field placeholder' }) + @IsOptional() + @IsString() + placeholder: string | null; + + @ApiProperty({ enum: SiteFormFieldType, description: 'Site form field type' }) + @IsString() + @IsEnum(SiteFormFieldType) + type: SiteFormFieldType; + + @ApiPropertyOptional({ nullable: true, description: 'Site form field required' }) + @IsOptional() + @IsBoolean() + isRequired?: boolean | null; + + @ApiProperty({ description: 'Site form field sort order' }) + @IsNumber() + sortOrder: number; + + @ApiPropertyOptional({ + nullable: true, + description: 'Site form field settings', + type: 'array', + items: { + oneOf: [ + { $ref: getSchemaPath(PublicSiteFormFieldEntityFieldDto) }, + { $ref: getSchemaPath(PublicSiteFormFieldScheduleDto) }, + { $ref: getSchemaPath(PublicSiteFormFieldScheduleDateDto) }, + { $ref: getSchemaPath(PublicSiteFormFieldScheduleTimeDto) }, + ], + }, + }) + @IsOptional() + settings?: + | PublicSiteFormFieldEntityFieldDto + | PublicSiteFormFieldScheduleDto + | PublicSiteFormFieldScheduleDateDto + | PublicSiteFormFieldScheduleTimeDto + | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-gratitude.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-gratitude.dto.ts new file mode 100644 index 0000000..d24beb8 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-gratitude.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +export class PublicSiteFormGratitudeDto { + @ApiProperty({ description: 'Site form gratitude enabled' }) + @IsBoolean() + isEnabled: boolean; + + @ApiPropertyOptional({ nullable: true, description: 'Site form gratitude header' }) + @IsOptional() + @IsString() + header?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Site form gratitude text' }) + @IsOptional() + @IsString() + text?: string | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-option.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-option.dto.ts new file mode 100644 index 0000000..881a885 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-option.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class PublicSiteFormOptionDto { + @ApiProperty({ description: 'Option id' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Option label' }) + @IsNumber() + label: string; + + @ApiProperty({ nullable: true, description: 'Option value' }) + @IsOptional() + @IsString() + color?: string | null; + + @ApiProperty({ description: 'Option sort order' }) + @IsNumber() + sortOrder?: number; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form-page.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form-page.dto.ts new file mode 100644 index 0000000..5f09df0 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form-page.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { PublicSiteFormFieldDto } from './public-site-form-field.dto'; + +export class PublicSiteFormPageDto { + @ApiProperty({ description: 'Site form page id' }) + @IsNumber() + id: number; + + @ApiPropertyOptional({ nullable: true, description: 'Site form page title' }) + @IsOptional() + @IsString() + title?: string | null; + + @ApiProperty({ description: 'Site form page sort order' }) + @IsNumber() + sortOrder: number; + + @ApiProperty({ type: [PublicSiteFormFieldDto], description: 'Site form page fields' }) + @IsArray() + fields: PublicSiteFormFieldDto[]; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/public-site-form.dto.ts b/backend/src/modules/forms/site-form-builder/dto/public-site-form.dto.ts new file mode 100644 index 0000000..76aa8fd --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/public-site-form.dto.ts @@ -0,0 +1,45 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsObject, IsOptional, IsString } from 'class-validator'; + +import { PublicSiteFormConsentDto } from './public-site-form-consent.dto'; +import { PublicSiteFormGratitudeDto } from './public-site-form-gratitude.dto'; +import { PublicSiteFormPageDto } from './public-site-form-page.dto'; + +export class PublicSiteFormDto { + @ApiProperty({ description: 'Site form code' }) + @IsString() + code: string; + + @ApiPropertyOptional({ nullable: true, description: 'Site form title' }) + @IsOptional() + @IsString() + title?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Site form design' }) + @IsOptional() + @IsObject() + design: object | null; + + @ApiPropertyOptional({ description: 'Site form field label enabled' }) + @IsOptional() + @IsBoolean() + fieldLabelEnabled?: boolean; + + @ApiPropertyOptional({ description: 'Site form field placeholder enabled' }) + @IsOptional() + @IsBoolean() + fieldPlaceholderEnabled?: boolean; + + @ApiPropertyOptional({ type: PublicSiteFormConsentDto, nullable: true, description: 'Site form consent' }) + @IsOptional() + consent?: PublicSiteFormConsentDto | null; + + @ApiPropertyOptional({ type: PublicSiteFormGratitudeDto, nullable: true, description: 'Site form gratitude' }) + @IsOptional() + gratitude?: PublicSiteFormGratitudeDto | null; + + @ApiPropertyOptional({ type: [PublicSiteFormPageDto], nullable: true, description: 'Site form pages' }) + @IsOptional() + @IsArray() + pages?: PublicSiteFormPageDto[] | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/site-form-analytic-data.dto.ts b/backend/src/modules/forms/site-form-builder/dto/site-form-analytic-data.dto.ts new file mode 100644 index 0000000..929c469 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/site-form-analytic-data.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class SiteFormAnalyticDataDto { + @ApiProperty({ description: 'Code' }) + @IsString() + code: string; + + @ApiProperty({ nullable: true, description: 'Value' }) + value: unknown | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/site-form-data-plain.dto.ts b/backend/src/modules/forms/site-form-builder/dto/site-form-data-plain.dto.ts new file mode 100644 index 0000000..103f8b2 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/site-form-data-plain.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class SiteFormDataPlainDto { + @ApiPropertyOptional({ description: 'Test' }) + @IsOptional() + @IsString() + test?: string; + + [key: string]: string; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/site-form-data.dto.ts b/backend/src/modules/forms/site-form-builder/dto/site-form-data.dto.ts new file mode 100644 index 0000000..65deaea --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/site-form-data.dto.ts @@ -0,0 +1,25 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { SiteFormFieldDataDto } from './site-form-field-data.dto'; +import { SiteFormAnalyticDataDto } from './site-form-analytic-data.dto'; + +export class SiteFormDataDto { + @ApiPropertyOptional({ description: 'Test' }) + @IsOptional() + @IsString() + test?: string; + + @ApiPropertyOptional({ type: [SiteFormFieldDataDto], nullable: true, description: 'Fields' }) + @IsOptional() + @IsArray() + @Type(() => SiteFormFieldDataDto) + fields?: SiteFormFieldDataDto[] | null; + + @ApiPropertyOptional({ type: [SiteFormAnalyticDataDto], nullable: true, description: 'Analytics' }) + @IsOptional() + @IsArray() + @Type(() => SiteFormAnalyticDataDto) + analytics?: SiteFormAnalyticDataDto[] | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/site-form-field-data.dto.ts b/backend/src/modules/forms/site-form-builder/dto/site-form-field-data.dto.ts new file mode 100644 index 0000000..acd9d14 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/site-form-field-data.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class SiteFormFieldDataDto { + @ApiProperty({ description: 'Field ID' }) + @IsNumber() + id: number; + + @ApiPropertyOptional({ description: 'Field value', nullable: true }) + value?: unknown | null; + + @ApiPropertyOptional({ description: 'Field min value', nullable: true }) + @IsOptional() + min?: unknown | null; + + @ApiPropertyOptional({ description: 'Field max value', nullable: true }) + @IsOptional() + max?: unknown | null; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/site-form-file-upload-request.ts b/backend/src/modules/forms/site-form-builder/dto/site-form-file-upload-request.ts new file mode 100644 index 0000000..139fa5c --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/site-form-file-upload-request.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class SiteFormFilesUploadRequest { + @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } }) + files: unknown[]; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/site-form-file-upload-result.ts b/backend/src/modules/forms/site-form-builder/dto/site-form-file-upload-result.ts new file mode 100644 index 0000000..886b2c6 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/site-form-file-upload-result.ts @@ -0,0 +1,29 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsNumber, IsOptional, IsDateString } from 'class-validator'; + +export class SiteFormFileUploadResult { + @ApiPropertyOptional({ description: 'Upload key', nullable: true }) + @IsOptional() + @IsString() + key?: string | null; + + @ApiProperty({ description: 'File ID' }) + @IsString() + id: string; + + @ApiProperty({ description: 'File name' }) + @IsString() + fileName: string; + + @ApiProperty({ description: 'File size' }) + @IsNumber() + fileSize: number; + + @ApiProperty({ description: 'Mime type' }) + @IsString() + mimeType: string; + + @ApiProperty({ description: 'Created at' }) + @IsDateString() + createdAt: string; +} diff --git a/backend/src/modules/forms/site-form-builder/dto/site-form-result.dto.ts b/backend/src/modules/forms/site-form-builder/dto/site-form-result.dto.ts new file mode 100644 index 0000000..a44c303 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/dto/site-form-result.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +export class SiteFormResultDto { + @ApiProperty({ description: 'Result' }) + @IsBoolean() + result: boolean; + + @ApiPropertyOptional({ nullable: true, description: 'Message' }) + @IsOptional() + @IsString() + message?: string | null; +} diff --git a/backend/src/modules/forms/site-form-builder/index.ts b/backend/src/modules/forms/site-form-builder/index.ts new file mode 100644 index 0000000..36bc8b9 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/index.ts @@ -0,0 +1,3 @@ +export * from './dto'; +export * from './site-form-builder.controller'; +export * from './site-form-builder.service'; diff --git a/backend/src/modules/forms/site-form-builder/site-form-builder.controller.ts b/backend/src/modules/forms/site-form-builder/site-form-builder.controller.ts new file mode 100644 index 0000000..86b5e20 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/site-form-builder.controller.ts @@ -0,0 +1,109 @@ +import { + Body, + Controller, + Get, + MaxFileSizeValidator, + Param, + ParseFilePipe, + ParseIntPipe, + Post, + Query, + UploadedFiles, + UseInterceptors, +} from '@nestjs/common'; +import { + ApiBody, + ApiConsumes, + ApiCreatedResponse, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiQuery, + ApiTags, +} from '@nestjs/swagger'; +import { AnyFilesInterceptor } from '@nestjs/platform-express'; +import { memoryStorage } from 'multer'; + +import { TransformToDto } from '@/common'; + +import { + PublicSiteFormDto, + PublicSiteFormFieldDto, + SiteFormDataDto, + SiteFormDataPlainDto, + SiteFormFilesUploadRequest, + SiteFormFileUploadResult, + SiteFormResultDto, +} from './dto'; +import { SiteFormBuilderService } from './site-form-builder.service'; + +const MaxSize = 52428800; + +@ApiTags('site-forms/builder') +@Controller('builder') +@TransformToDto() +export class SiteFormBuilderController { + constructor(private readonly service: SiteFormBuilderService) {} + + @ApiOperation({ summary: 'Get site form data', description: 'Get site form data' }) + @ApiParam({ name: 'code', type: String, required: true, description: 'Site form code' }) + @ApiQuery({ name: 'timezone', type: String, required: false, description: 'Timezone' }) + @ApiBody({ type: SiteFormDataDto, required: true, description: 'Site form data' }) + @ApiOkResponse({ description: 'Site form data', type: [PublicSiteFormDto] }) + @Get(':code') + async find(@Param('code') code: string, @Query('timezone') timezone?: string, @Body() dto?: SiteFormDataDto) { + return this.service.find({ code, dto, timezone }); + } + + @ApiOperation({ summary: 'Get site form field', description: 'Get site form field' }) + @ApiParam({ name: 'code', type: String, required: true, description: 'Site form code' }) + @ApiParam({ name: 'fieldId', type: Number, required: true, description: 'Site form field ID' }) + @ApiQuery({ name: 'timezone', type: String, required: false, description: 'Timezone' }) + @ApiBody({ type: SiteFormDataDto, required: true, description: 'Site form data' }) + @ApiOkResponse({ description: 'Site form field', type: [PublicSiteFormFieldDto] }) + @Post(':code/fields/:fieldId') + async getField( + @Param('code') code: string, + @Param('fieldId', ParseIntPipe) fieldId: number, + @Query('timezone') timezone?: string, + @Body() dto?: SiteFormDataDto, + ) { + return this.service.getField({ code, fieldId, dto, timezone }); + } + + @ApiOperation({ summary: 'Post site form data', description: 'Post site form data test in plain format' }) + @ApiParam({ name: 'code', type: String, required: true, description: 'Site form code' }) + @ApiBody({ type: SiteFormDataPlainDto, required: true, description: 'Site form data' }) + @ApiCreatedResponse({ description: 'Result', type: SiteFormResultDto }) + @Post('plain/:code') + async postPlain(@Param('code') code: string, @Body() dto: SiteFormDataPlainDto) { + return this.service.postPlain({ code, dto }); + } + + @ApiOperation({ summary: 'Post site form data', description: 'Post site form data' }) + @ApiParam({ name: 'code', type: String, required: true, description: 'Site form code' }) + @ApiBody({ type: SiteFormDataDto, required: true, description: 'Site form data' }) + @ApiCreatedResponse({ description: 'Result', type: SiteFormResultDto }) + @Post(':code') + async post(@Param('code') code: string, @Body() dto: SiteFormDataDto) { + return this.service.post({ code, dto }); + } + + @ApiOperation({ summary: 'Upload files', description: 'Upload files' }) + @ApiConsumes('multipart/form-data') + @ApiBody({ description: 'Files to upload', type: SiteFormFilesUploadRequest }) + @ApiOkResponse({ description: 'Uploaded files info', type: [SiteFormFileUploadResult] }) + @Post(':code/upload') + @UseInterceptors(AnyFilesInterceptor({ storage: memoryStorage() })) + async upload( + @Param('code') code: string, + @UploadedFiles( + new ParseFilePipe({ + validators: [new MaxFileSizeValidator({ maxSize: MaxSize })], + }), + ) + files: Express.Multer.File[], + ): Promise { + return this.service.uploadFiles({ code, files }); + } +} diff --git a/backend/src/modules/forms/site-form-builder/site-form-builder.service.ts b/backend/src/modules/forms/site-form-builder/site-form-builder.service.ts new file mode 100644 index 0000000..f0c4167 --- /dev/null +++ b/backend/src/modules/forms/site-form-builder/site-form-builder.service.ts @@ -0,0 +1,577 @@ +import { Injectable } from '@nestjs/common'; + +import { DateUtil } from '@/common'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities'; +import { DepartmentService } from '@/modules/iam/department/department.service'; +import { FieldService } from '@/modules/entity/entity-field/field'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { Entity } from '@/CRM/Model/Entity/Entity'; +import { CreateSimpleEntityDto } from '@/CRM/Service/Entity/Dto/CreateSimpleEntityDto'; +import { SimpleFieldValueDto } from '@/modules/entity/entity-field/field-value/dto/simple-field-value.dto'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types'; +import { ScheduleAppointmentStatus } from '@/modules/scheduler/common'; +import { ScheduleService } from '@/modules/scheduler/schedule/services/schedule.service'; +import { ScheduleAppointmentService } from '@/modules/scheduler/schedule-appointment/schedule-appointment.service'; + +import { SiteForm, SiteFormEntityType, SiteFormService } from '../site-form'; +import { SiteFormField, SiteFormFieldType } from '../site-form-field'; + +import { + PublicSiteFormDto, + PublicSiteFormFieldDto, + PublicSiteFormFieldEntityFieldDto, + PublicSiteFormFieldScheduleDateDto, + PublicSiteFormFieldScheduleDto, + PublicSiteFormFieldScheduleTimeDto, + PublicSiteFormPageDto, + SiteFormAnalyticDataDto, + SiteFormDataDto, + SiteFormDataPlainDto, + SiteFormFieldDataDto, + SiteFormFileUploadResult, + SiteFormResultDto, +} from './dto'; + +@Injectable() +export class SiteFormBuilderService { + constructor( + private readonly userService: UserService, + private readonly departmentService: DepartmentService, + private readonly formService: SiteFormService, + private readonly fieldService: FieldService, + private readonly entityService: EntityService, + private readonly storageService: StorageService, + private readonly scheduleService: ScheduleService, + private readonly appointmentService: ScheduleAppointmentService, + ) {} + + async find({ + code, + dto, + timezone, + }: { + code: string; + dto: SiteFormDataDto; + timezone?: string; + }): Promise { + const form = await this.formService.findByCode(code, { + expand: ['consent', 'gratitude', 'pages.fields', 'scheduleLinks'], + }); + + return form + ? { + code: form.code, + title: form.title, + design: form.design, + fieldLabelEnabled: form.fieldLabelEnabled, + fieldPlaceholderEnabled: form.fieldPlaceholderEnabled, + consent: form.consent + ? { + isEnabled: form.consent.isEnabled, + text: form.consent.text, + linkUrl: form.consent.linkUrl, + linkText: form.consent.linkText, + defaultValue: form.consent.defaultValue, + } + : form.consent, + gratitude: form.gratitude + ? { isEnabled: form.gratitude.isEnabled, header: form.gratitude.header, text: form.gratitude.text } + : form.gratitude, + pages: form.pages ? await this.getPublicPages({ form, dto, timezone }) : undefined, + } + : null; + } + + async getField({ + code, + fieldId, + dto, + timezone, + }: { + code: string; + fieldId: number; + dto?: SiteFormDataDto; + timezone?: string; + }): Promise { + const form = await this.formService.findByCode(code, { + expand: ['pages.fields', 'scheduleLinks'], + }); + + if (form) { + const formField = form.pages.flatMap((p) => p.fields).find((f) => f.id === fieldId); + if (formField) { + return this.getPublicField({ form, formField, dto, timezone }); + } + } + + return null; + } + + async post({ code, dto }: { code: string; dto?: SiteFormDataDto }): Promise { + if (!dto || dto.test) { + return { result: true, message: `Form ${code} test is OK` }; + } + + const form = await this.formService.findByCode(code, { + expand: ['pages.fields', 'entityTypeLinks', 'scheduleLinks'], + }); + + if (!form) { + return { result: false, message: `Form ${code} not found` }; + } + + if (!this.checkRequiredFields({ form, fields: dto.fields })) { + return { result: false, message: `Required field(s) not set` }; + } + + const user = await this.userService.findOne({ accountId: form.accountId, id: form.createdBy }); + if (!user) { + return { result: false, message: `User ${form.createdBy} not found` }; + } + + const entity = await this.createEntities({ form, user, dto }); + + if (form.scheduleLinks?.length) { + await this.createAppointment({ form, user, entity, dto }); + } + + return { result: true, message: `Form ${code} submitted successfully` }; + } + + async postPlain({ code, dto }: { code: string; dto?: SiteFormDataPlainDto }): Promise { + return this.post({ code, dto: this.convertPlainToDto(dto) }); + } + + async uploadFiles({ + code, + files, + }: { + code: string; + files: Express.Multer.File[]; + }): Promise { + const form = await this.formService.findByCode(code); + if (form) { + const fileInfos = await Promise.all( + files + .map((file) => ({ key: file.fieldname, file: StorageFile.fromMulter(file) })) + .map(async (file) => ({ + key: file.key, + info: await this.storageService.storeCommonFile({ accountId: form.accountId, file: file.file }), + })), + ); + return fileInfos.map((file) => ({ + key: file.key, + id: file.info.id, + fileName: file.info.originalName, + fileSize: file.info.size, + mimeType: file.info.mimeType, + createdAt: file.info.createdAt.toISOString(), + })); + } + return []; + } + + private convertPlainToDto(plain: SiteFormDataPlainDto | null | undefined): SiteFormDataDto | undefined { + if (!plain) { + return undefined; + } + + const fields = Object.entries(plain) + .map(([key, value]) => { + const id = Number(key); + return !isNaN(id) ? { id, value } : null; + }) + .filter(Boolean); + + return { test: plain.test, fields }; + } + + private checkRequiredFields({ form, fields }: { form: SiteForm; fields?: SiteFormFieldDataDto[] }): boolean { + const requiredFormFields = form.pages.flatMap((p) => p.fields).filter((f) => f.isRequired); + for (const requiredFormField of requiredFormFields) { + const fieldDto = fields?.find((fd) => fd.id === requiredFormField.id); + if (!fieldDto || !fieldDto.value) { + return false; + } + } + return true; + } + + private async createEntities({ + form, + user, + dto, + }: { + form: SiteForm; + user: User; + dto: SiteFormDataDto; + }): Promise { + const mainEntityType = form.entityTypeLinks.find((etl) => etl.isMain); + const mainEntity = mainEntityType + ? await this.createEntity({ form, user, entityType: mainEntityType, data: dto }) + : undefined; + + if (mainEntity) { + const linkedEntityTypes = form.entityTypeLinks.filter((etl) => !etl.isMain); + await Promise.all( + linkedEntityTypes.map((et) => + this.createEntity({ form, user, entityType: et, data: dto, options: { linkedEntities: [mainEntity.id] } }), + ), + ); + } + + return mainEntity; + } + + private async createEntity({ + form, + user, + entityType, + data, + options, + }: { + form: SiteForm; + user: User; + entityType: SiteFormEntityType; + data: SiteFormDataDto; + options?: { linkedEntities?: number[] }; + }) { + const dto = this.getEntityDto({ form, entityType, fields: data.fields, analytics: data.analytics }); + + const [entity] = await this.entityService.createSimple({ + accountId: form.accountId, + user, + dto, + options: { linkedEntities: options?.linkedEntities, checkDuplicate: form.checkDuplicate }, + }); + + return entity; + } + + private getEntityDto({ + form, + entityType, + fields, + analytics, + }: { + form: SiteForm; + entityType: SiteFormEntityType; + fields?: SiteFormFieldDataDto[] | null; + analytics?: SiteFormAnalyticDataDto[] | null; + }): CreateSimpleEntityDto { + const fieldValues: SimpleFieldValueDto[] = []; + let name: string | undefined = entityType.isMain ? form.name : undefined; + if (fields) { + const formFields = form.pages.flatMap((p) => p.fields).filter((f) => f.entityTypeId === entityType.entityTypeId); + for (const field of fields) { + const formField = formFields.find((ff) => ff.id === field.id); + if (formField?.type === SiteFormFieldType.EntityName) { + name = field.value as string; + } else if (formField?.type === SiteFormFieldType.EntityField) { + if (formField?.fieldId) { + fieldValues.push({ fieldId: formField?.fieldId, value: field.value }); + } + } + } + } + if (analytics) { + for (const analytic of analytics) { + fieldValues.push({ fieldCode: analytic.code, value: analytic.value }); + } + } + return { + entityTypeId: entityType.entityTypeId, + boardId: entityType.boardId, + ownerId: form.responsibleId, + name, + fieldValues, + }; + } + + private async createAppointment({ + form, + user, + entity, + dto, + }: { + form: SiteForm; + user: User; + entity: Entity | undefined; + dto: SiteFormDataDto; + }) { + const scheduleId = this.getFieldValue({ form, fields: dto.fields, type: SiteFormFieldType.Schedule }); + const performerId = this.getFieldValue({ + form, + fields: dto.fields, + type: SiteFormFieldType.SchedulePerformer, + }); + const startTime = this.getFieldValue({ form, fields: dto.fields, type: SiteFormFieldType.ScheduleTime }); + if (scheduleId && performerId && startTime) { + const startDate = DateUtil.fromISOString(startTime); + await this.appointmentService.create({ + accountId: form.accountId, + user, + dto: { + scheduleId, + performerId, + title: entity?.name ?? form.name, + startDate: startDate.toISOString(), + status: ScheduleAppointmentStatus.NotConfirmed, + entityId: entity?.id, + checkIntersection: true, + ownerId: entity?.responsibleUserId ?? form.responsibleId, + }, + skipPermissionCheck: true, + }); + } + } + + private async getPublicPages({ + form, + dto, + timezone, + }: { + form: SiteForm; + dto?: SiteFormDataDto; + timezone?: string; + }): Promise { + const publicPages: PublicSiteFormPageDto[] = []; + + for (const formPage of form.pages) { + publicPages.push({ + id: formPage.id, + title: formPage.title, + sortOrder: formPage.sortOrder, + fields: formPage.fields + ? await this.getPublicFields({ form, formFields: formPage.fields, dto, timezone }) + : undefined, + }); + } + + return publicPages; + } + + private async getPublicFields({ + form, + formFields, + dto, + timezone, + }: { + form: SiteForm; + formFields: SiteFormField[]; + dto?: SiteFormDataDto; + timezone?: string; + }): Promise { + return Promise.all(formFields.map((formField) => this.getPublicField({ form, formField, dto, timezone }))); + } + + private async getPublicField({ + form, + formField, + dto, + timezone, + }: { + form: SiteForm; + formField: SiteFormField; + dto?: SiteFormDataDto; + timezone?: string; + }): Promise { + return { + id: formField.id, + label: formField.label, + placeholder: formField.placeholder, + type: formField.type, + isRequired: formField.isRequired, + sortOrder: formField.sortOrder, + settings: await this.getPublicFieldSettings({ form, formField, dto, timezone }), + }; + } + + private async getPublicFieldSettings({ + form, + formField, + dto, + timezone, + }: { + form: SiteForm; + formField: SiteFormField; + dto?: SiteFormDataDto; + timezone?: string; + }) { + switch (formField.type) { + case SiteFormFieldType.EntityField: + return this.getEntityFieldSettings(formField); + case SiteFormFieldType.Schedule: + return this.getScheduleSettings(form); + case SiteFormFieldType.SchedulePerformer: + return this.getSchedulePerformerSettings({ form, fields: dto.fields }); + case SiteFormFieldType.ScheduleDate: + return this.getScheduleDateSettings({ form, fields: dto.fields, timezone }); + case SiteFormFieldType.ScheduleTime: + return this.getScheduleTimeSettings({ form, fields: dto.fields }); + default: + return null; + } + } + + private async getEntityFieldSettings(formField: SiteFormField): Promise { + const field = await this.fieldService.findOne( + { + accountId: formField.accountId, + entityTypeId: formField.entityTypeId, + id: formField.fieldId, + }, + { expand: ['options'] }, + ); + + const options = field?.options?.map((o) => ({ + id: o.id, + label: o.label, + color: o.color, + sortOrder: o.sortOrder, + })); + + return field + ? { fieldType: field.type, isValidationRequired: formField.isValidationRequired, meta: formField.meta, options } + : null; + } + + private async getScheduleSettings(form: SiteForm): Promise { + const options = ( + (await Promise.all( + form.scheduleLinks?.map(async (sl) => { + const schedule = await this.scheduleService.findOne({ + filter: { accountId: form.accountId, scheduleId: sl.scheduleId }, + }); + return schedule ? { id: schedule.id, label: schedule.name } : undefined; + }), + )) ?? [] + ).filter(Boolean); + + return { options }; + } + + private getFieldData({ + form, + fields, + type, + }: { + form: SiteForm; + fields: SiteFormFieldDataDto[] | undefined | null; + type: SiteFormFieldType; + }): SiteFormFieldDataDto | undefined { + const field = form.pages.flatMap((p) => p.fields).find((f) => f.type === type); + + return field ? fields?.find((f) => f.id === field.id) : undefined; + } + private getFieldValue(params: { + form: SiteForm; + fields: SiteFormFieldDataDto[] | undefined | null; + type: SiteFormFieldType; + }): T | undefined { + const fieldData = this.getFieldData(params); + + return fieldData ? (fieldData.value as T) : undefined; + } + + private async getSchedulePerformerSettings({ + form, + fields, + }: { + form: SiteForm; + fields: SiteFormFieldDataDto[] | undefined | null; + }): Promise { + const scheduleId = this.getFieldValue({ form, fields, type: SiteFormFieldType.Schedule }); + if (scheduleId) { + const schedule = await this.scheduleService.findOne({ filter: { accountId: form.accountId, scheduleId } }); + if (schedule?.performers) { + const options = ( + (await Promise.all( + schedule.performers.map(async (performer) => { + if (performer.userId) { + const user = await this.userService.findOne({ accountId: form.accountId, id: performer.userId }); + return user ? { id: performer.id, label: user.fullName } : undefined; + } else if (performer.departmentId) { + const department = await this.departmentService.findOne({ + accountId: form.accountId, + departmentId: performer.departmentId, + }); + return department ? { id: performer.id, label: department.name } : undefined; + } else { + return undefined; + } + }), + )) ?? [] + ).filter(Boolean); + return { options }; + } + } + + return { options: [] }; + } + + private async getScheduleDateSettings({ + form, + fields, + timezone, + }: { + form: SiteForm; + fields: SiteFormFieldDataDto[] | undefined | null; + timezone?: string; + }): Promise { + const scheduleId = this.getFieldValue({ form, fields, type: SiteFormFieldType.Schedule }); + const performerId = this.getFieldValue({ form, fields, type: SiteFormFieldType.SchedulePerformer }); + const scheduleData = this.getFieldData({ form, fields, type: SiteFormFieldType.ScheduleDate }); + + if (scheduleId && performerId && scheduleData?.min && scheduleData?.max) { + const [minDate, maxDate] = [scheduleData.min, scheduleData.max].map((date) => + DateUtil.fromISOString(date as string), + ); + + const dates = await this.appointmentService.getAvailableDates({ + accountId: form.accountId, + scheduleId, + performerId, + minDate, + maxDate, + daysLimit: form.scheduleLimitDays, + timezone: timezone ?? 'UTC', + }); + + return { dates }; + } + + return { dates: [] }; + } + + private async getScheduleTimeSettings({ + form, + fields, + }: { + form: SiteForm; + fields: SiteFormFieldDataDto[] | undefined | null; + }): Promise { + const scheduleId = this.getFieldValue({ form, fields, type: SiteFormFieldType.Schedule }); + const performerId = this.getFieldValue({ form, fields, type: SiteFormFieldType.SchedulePerformer }); + const scheduleDataStr = this.getFieldValue({ form, fields, type: SiteFormFieldType.ScheduleDate }); + if (scheduleId && performerId && scheduleDataStr) { + const minDate = DateUtil.fromISOString(scheduleDataStr); + const maxDate = DateUtil.sub(DateUtil.add(minDate, { days: 1 }), { seconds: 1 }); + const spots = await this.appointmentService.getAvailableSpots({ + accountId: form.accountId, + scheduleId, + performerId, + minDate, + maxDate, + daysLimit: form.scheduleLimitDays, + }); + + return { spots: spots.map((spot) => ({ from: spot.from.toISOString(), to: spot.to.toISOString() })) }; + } + + return { spots: [] }; + } +} diff --git a/backend/src/modules/forms/site-form-consent/dto/create-site-form-consent.dto.ts b/backend/src/modules/forms/site-form-consent/dto/create-site-form-consent.dto.ts new file mode 100644 index 0000000..f521375 --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/dto/create-site-form-consent.dto.ts @@ -0,0 +1,11 @@ +import { PickType } from '@nestjs/swagger'; + +import { SiteFormConsentDto } from './site-form-consent.dto'; + +export class CreateSiteFormConsentDto extends PickType(SiteFormConsentDto, [ + 'isEnabled', + 'text', + 'linkUrl', + 'linkText', + 'defaultValue', +] as const) {} diff --git a/backend/src/modules/forms/site-form-consent/dto/index.ts b/backend/src/modules/forms/site-form-consent/dto/index.ts new file mode 100644 index 0000000..f011713 --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-site-form-consent.dto'; +export * from './site-form-consent.dto'; +export * from './update-site-form-consent.dto'; diff --git a/backend/src/modules/forms/site-form-consent/dto/site-form-consent.dto.ts b/backend/src/modules/forms/site-form-consent/dto/site-form-consent.dto.ts new file mode 100644 index 0000000..7216e82 --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/dto/site-form-consent.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class SiteFormConsentDto { + @ApiProperty() + @IsNumber() + formId: number; + + @ApiProperty() + @IsBoolean() + isEnabled: boolean; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + text?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + linkUrl?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + linkText?: string | null; + + @ApiPropertyOptional() + @IsOptional() + @IsBoolean() + defaultValue?: boolean; +} diff --git a/backend/src/modules/forms/site-form-consent/dto/update-site-form-consent.dto.ts b/backend/src/modules/forms/site-form-consent/dto/update-site-form-consent.dto.ts new file mode 100644 index 0000000..a911072 --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/dto/update-site-form-consent.dto.ts @@ -0,0 +1,7 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { SiteFormConsentDto } from './site-form-consent.dto'; + +export class UpdateSiteFormConsentDto extends PartialType( + PickType(SiteFormConsentDto, ['isEnabled', 'text', 'linkUrl', 'linkText', 'defaultValue'] as const), +) {} diff --git a/backend/src/modules/forms/site-form-consent/entities/index.ts b/backend/src/modules/forms/site-form-consent/entities/index.ts new file mode 100644 index 0000000..57825e7 --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/entities/index.ts @@ -0,0 +1 @@ +export * from './site-form-consent.entity'; diff --git a/backend/src/modules/forms/site-form-consent/entities/site-form-consent.entity.ts b/backend/src/modules/forms/site-form-consent/entities/site-form-consent.entity.ts new file mode 100644 index 0000000..212fe21 --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/entities/site-form-consent.entity.ts @@ -0,0 +1,70 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { type CreateSiteFormConsentDto, SiteFormConsentDto, type UpdateSiteFormConsentDto } from '../dto'; + +@Entity() +export class SiteFormConsent { + @Column() + accountId: number; + + @PrimaryColumn() + formId: number; + + @Column({ default: false }) + isEnabled: boolean; + + @Column({ nullable: true }) + text: string | null; + + @Column({ nullable: true }) + linkUrl: string | null; + + @Column({ nullable: true }) + linkText: string | null; + + @Column({ default: false }) + defaultValue: boolean; + + constructor( + accountId: number, + formId: number, + isEnabled: boolean, + text: string | null, + linkUrl: string | null, + linkText: string | null, + defaultValue: boolean, + ) { + this.accountId = accountId; + this.formId = formId; + this.isEnabled = isEnabled; + this.text = text; + this.linkUrl = linkUrl; + this.linkText = linkText; + this.defaultValue = defaultValue; + } + + public static fromDto(accountId: number, formId: number, dto: CreateSiteFormConsentDto): SiteFormConsent { + return new SiteFormConsent(accountId, formId, dto.isEnabled, dto.text, dto.linkUrl, dto.linkText, dto.defaultValue); + } + + public update(dto: UpdateSiteFormConsentDto): SiteFormConsent { + this.isEnabled = dto.isEnabled !== undefined ? dto.isEnabled : this.isEnabled; + this.text = dto.text !== undefined ? dto.text : this.text; + this.linkUrl = dto.linkUrl !== undefined ? dto.linkUrl : this.linkUrl; + this.linkText = dto.linkText !== undefined ? dto.linkText : this.linkText; + this.defaultValue = dto.defaultValue !== undefined ? dto.defaultValue : this.defaultValue; + + return this; + } + + public toDto(): SiteFormConsentDto { + return { + formId: this.formId, + isEnabled: this.isEnabled, + text: this.text, + linkUrl: this.linkUrl, + linkText: this.linkText, + defaultValue: this.defaultValue, + }; + } +} diff --git a/backend/src/modules/forms/site-form-consent/index.ts b/backend/src/modules/forms/site-form-consent/index.ts new file mode 100644 index 0000000..e63a2ad --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './site-form-consent.controller'; +export * from './site-form-consent.service'; diff --git a/backend/src/modules/forms/site-form-consent/site-form-consent.controller.ts b/backend/src/modules/forms/site-form-consent/site-form-consent.controller.ts new file mode 100644 index 0000000..3e1110e --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/site-form-consent.controller.ts @@ -0,0 +1,50 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import type { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { SiteFormConsentService } from './site-form-consent.service'; +import { SiteFormConsentDto, type CreateSiteFormConsentDto, type UpdateSiteFormConsentDto } from './dto'; + +@ApiTags('site-forms/consent') +@Controller(':formId/consent') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class SiteFormConsentController { + constructor(private readonly service: SiteFormConsentService) {} + + @ApiCreatedResponse({ description: 'Create site form consent', type: SiteFormConsentDto }) + @Post() + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Body() dto: CreateSiteFormConsentDto, + ) { + return this.service.create(accountId, formId, dto); + } + + @ApiOkResponse({ description: 'Get site form consent', type: [SiteFormConsentDto] }) + @Get() + public async findOne(@CurrentAuth() { accountId }: AuthData, @Param('formId', ParseIntPipe) formId: number) { + return this.service.findOne(accountId, { formId }); + } + + @ApiCreatedResponse({ description: 'Update site form consent', type: SiteFormConsentDto }) + @Patch() + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Body() dto: UpdateSiteFormConsentDto, + ) { + return this.service.update(accountId, formId, dto); + } + + @ApiOkResponse({ description: 'Delete site form consent' }) + @Delete() + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('formId', ParseIntPipe) formId: number) { + return this.service.delete(accountId, formId); + } +} diff --git a/backend/src/modules/forms/site-form-consent/site-form-consent.service.ts b/backend/src/modules/forms/site-form-consent/site-form-consent.service.ts new file mode 100644 index 0000000..c4d9b47 --- /dev/null +++ b/backend/src/modules/forms/site-form-consent/site-form-consent.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { SiteFormConsent } from './entities'; +import type { CreateSiteFormConsentDto, UpdateSiteFormConsentDto } from './dto'; + +interface FindFilter { + formId?: number; +} + +@Injectable() +export class SiteFormConsentService { + constructor( + @InjectRepository(SiteFormConsent) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, formId: number, dto: CreateSiteFormConsentDto): Promise { + return this.repository.save(SiteFormConsent.fromDto(accountId, formId, dto)); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + public async update(accountId: number, formId: number, dto: UpdateSiteFormConsentDto): Promise { + const consent = await this.findOne(accountId, { formId }); + if (!consent) { + throw NotFoundError.withId(SiteFormConsent, formId); + } + + await this.repository.save(consent.update(dto)); + + return consent; + } + + public async process( + accountId: number, + formId: number, + dto: CreateSiteFormConsentDto | UpdateSiteFormConsentDto, + ): Promise { + const consent = await this.findOne(accountId, { formId }); + if (!consent) { + return this.create(accountId, formId, dto as CreateSiteFormConsentDto); + } else { + await this.repository.save(consent.update(dto as UpdateSiteFormConsentDto)); + + return consent; + } + } + + public async delete(accountId: number, formId: number) { + await this.createFindQb(accountId, { formId }).delete().execute(); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + if (filter?.formId) { + qb.andWhere('form_id = :formId', { formId: filter.formId }); + } + + return qb; + } +} diff --git a/backend/src/modules/forms/site-form-field/dto/create-site-form-field.dto.ts b/backend/src/modules/forms/site-form-field/dto/create-site-form-field.dto.ts new file mode 100644 index 0000000..eb2b932 --- /dev/null +++ b/backend/src/modules/forms/site-form-field/dto/create-site-form-field.dto.ts @@ -0,0 +1,12 @@ +import { PickType } from '@nestjs/swagger'; + +import { SiteFormFieldDto } from './site-form-field.dto'; + +export class CreateSiteFormFieldDto extends PickType(SiteFormFieldDto, [ + 'label', + 'placeholder', + 'type', + 'isRequired', + 'sortOrder', + 'settings', +] as const) {} diff --git a/backend/src/modules/forms/site-form-field/dto/index.ts b/backend/src/modules/forms/site-form-field/dto/index.ts new file mode 100644 index 0000000..24aa2c4 --- /dev/null +++ b/backend/src/modules/forms/site-form-field/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-site-form-field.dto'; +export * from './site-form-field-entity-field.dto'; +export * from './site-form-field-entity-name.dto'; +export * from './site-form-field.dto'; +export * from './update-site-form-field.dto'; diff --git a/backend/src/modules/forms/site-form-field/dto/site-form-field-entity-field.dto.ts b/backend/src/modules/forms/site-form-field/dto/site-form-field-entity-field.dto.ts new file mode 100644 index 0000000..953ecba --- /dev/null +++ b/backend/src/modules/forms/site-form-field/dto/site-form-field-entity-field.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsObject, IsOptional } from 'class-validator'; + +export class SiteFormFieldEntityFieldDto { + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiProperty() + @IsNumber() + fieldId: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsBoolean() + isValidationRequired: boolean | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsObject() + meta?: object | null; +} diff --git a/backend/src/modules/forms/site-form-field/dto/site-form-field-entity-name.dto.ts b/backend/src/modules/forms/site-form-field/dto/site-form-field-entity-name.dto.ts new file mode 100644 index 0000000..f6cb26c --- /dev/null +++ b/backend/src/modules/forms/site-form-field/dto/site-form-field-entity-name.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class SiteFormFieldEntityNameDto { + @ApiProperty() + @IsNumber() + entityTypeId: number; +} diff --git a/backend/src/modules/forms/site-form-field/dto/site-form-field.dto.ts b/backend/src/modules/forms/site-form-field/dto/site-form-field.dto.ts new file mode 100644 index 0000000..2c468fc --- /dev/null +++ b/backend/src/modules/forms/site-form-field/dto/site-form-field.dto.ts @@ -0,0 +1,50 @@ +import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { SiteFormFieldType } from '../enums'; +import { SiteFormFieldEntityFieldDto } from './site-form-field-entity-field.dto'; +import { SiteFormFieldEntityNameDto } from './site-form-field-entity-name.dto'; + +@ApiExtraModels(SiteFormFieldEntityFieldDto) +@ApiExtraModels(SiteFormFieldEntityNameDto) +export class SiteFormFieldDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + label: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + placeholder: string | null; + + @ApiProperty({ enum: SiteFormFieldType }) + @IsEnum(SiteFormFieldType) + type: SiteFormFieldType; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsBoolean() + isRequired?: boolean | null; + + @ApiProperty() + @IsNumber() + sortOrder: number; + + @ApiPropertyOptional({ + nullable: true, + type: 'array', + items: { + oneOf: [ + { $ref: getSchemaPath(SiteFormFieldEntityFieldDto) }, + { $ref: getSchemaPath(SiteFormFieldEntityNameDto) }, + ], + }, + }) + @IsOptional() + settings?: SiteFormFieldEntityFieldDto | SiteFormFieldEntityNameDto | null; +} diff --git a/backend/src/modules/forms/site-form-field/dto/update-site-form-field.dto.ts b/backend/src/modules/forms/site-form-field/dto/update-site-form-field.dto.ts new file mode 100644 index 0000000..f0e648f --- /dev/null +++ b/backend/src/modules/forms/site-form-field/dto/update-site-form-field.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty, PartialType, PickType } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +import { SiteFormFieldDto } from './site-form-field.dto'; + +export class UpdateSiteFormFieldDto extends PartialType( + PickType(SiteFormFieldDto, ['label', 'placeholder', 'isRequired', 'sortOrder', 'settings'] as const), +) { + @ApiProperty() + @IsNumber() + id: number; +} diff --git a/backend/src/modules/forms/site-form-field/entities/index.ts b/backend/src/modules/forms/site-form-field/entities/index.ts new file mode 100644 index 0000000..6f90b01 --- /dev/null +++ b/backend/src/modules/forms/site-form-field/entities/index.ts @@ -0,0 +1 @@ +export * from './site-form-field.entity'; diff --git a/backend/src/modules/forms/site-form-field/entities/site-form-field.entity.ts b/backend/src/modules/forms/site-form-field/entities/site-form-field.entity.ts new file mode 100644 index 0000000..a389d4f --- /dev/null +++ b/backend/src/modules/forms/site-form-field/entities/site-form-field.entity.ts @@ -0,0 +1,142 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { + SiteFormFieldDto, + SiteFormFieldEntityFieldDto, + SiteFormFieldEntityNameDto, + type CreateSiteFormFieldDto, + type UpdateSiteFormFieldDto, +} from '../dto'; +import { SiteFormFieldType } from '../enums'; + +@Entity() +export class SiteFormField { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + pageId: number; + + @Column({ nullable: true }) + label: string | null; + + @Column({ nullable: true }) + placeholder: string | null; + + @Column() + sortOrder: number; + + @Column() + type: SiteFormFieldType; + + @Column({ nullable: true }) + isRequired: boolean | null; + + @Column({ nullable: true }) + entityTypeId: number | null; + + @Column({ nullable: true }) + fieldId: number | null; + + @Column({ nullable: true }) + isValidationRequired: boolean | null; + + @Column({ type: 'jsonb', nullable: true }) + meta?: object | null; + + constructor( + accountId: number, + pageId: number, + label: string | null, + placeholder: string | null, + sortOrder: number, + type: SiteFormFieldType, + isRequired: boolean | null, + entityTypeId: number | null, + fieldId: number | null, + isValidationRequired: boolean | null, + meta?: object | null, + ) { + this.accountId = accountId; + this.pageId = pageId; + this.label = label; + this.placeholder = placeholder; + this.sortOrder = sortOrder; + this.type = type; + this.isRequired = isRequired; + this.entityTypeId = entityTypeId; + this.fieldId = fieldId; + this.isValidationRequired = isValidationRequired; + this.meta = meta; + } + + public static fromDto(accountId: number, pageId: number, dto: CreateSiteFormFieldDto): SiteFormField { + const fieldSettings = + dto.type === SiteFormFieldType.EntityField ? (dto.settings as SiteFormFieldEntityFieldDto) : undefined; + return new SiteFormField( + accountId, + pageId, + dto.label, + dto.placeholder, + dto.sortOrder, + dto.type, + dto.isRequired, + dto.settings?.entityTypeId ?? null, + fieldSettings?.fieldId ?? null, + fieldSettings?.isValidationRequired ?? null, + fieldSettings?.meta ?? null, + ); + } + + public update(dto: UpdateSiteFormFieldDto): SiteFormField { + this.label = dto.label !== undefined ? dto.label : this.label; + this.placeholder = dto.placeholder !== undefined ? dto.placeholder : this.placeholder; + this.isRequired = dto.isRequired !== undefined ? dto.isRequired : this.isRequired; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + this.entityTypeId = dto.settings?.entityTypeId !== undefined ? dto.settings.entityTypeId : this.entityTypeId; + + const settings = + this.type === SiteFormFieldType.EntityField ? (dto.settings as SiteFormFieldEntityFieldDto) : undefined; + if (settings) { + this.fieldId = settings.fieldId !== undefined ? settings.fieldId : this.fieldId; + this.isValidationRequired = + settings.isValidationRequired !== undefined ? settings.isValidationRequired : this.isValidationRequired; + this.meta = settings.meta !== undefined ? settings.meta : this.meta; + } + + return this; + } + + public toDto(): SiteFormFieldDto { + return { + id: this.id, + label: this.label, + placeholder: this.placeholder, + type: this.type, + isRequired: this.isRequired, + sortOrder: this.sortOrder, + settings: this.formatSettings(), + }; + } + + private formatSettings(): SiteFormFieldEntityFieldDto | SiteFormFieldEntityNameDto | null { + switch (this.type) { + case SiteFormFieldType.EntityField: + return { + entityTypeId: this.entityTypeId, + fieldId: this.fieldId, + isValidationRequired: this.isValidationRequired, + meta: this.meta, + }; + case SiteFormFieldType.EntityName: + return { + entityTypeId: this.entityTypeId, + }; + default: + return null; + } + } +} diff --git a/backend/src/modules/forms/site-form-field/enums/index.ts b/backend/src/modules/forms/site-form-field/enums/index.ts new file mode 100644 index 0000000..6fe7850 --- /dev/null +++ b/backend/src/modules/forms/site-form-field/enums/index.ts @@ -0,0 +1 @@ +export * from './site-form-field-type.enum'; diff --git a/backend/src/modules/forms/site-form-field/enums/site-form-field-type.enum.ts b/backend/src/modules/forms/site-form-field/enums/site-form-field-type.enum.ts new file mode 100644 index 0000000..161b573 --- /dev/null +++ b/backend/src/modules/forms/site-form-field/enums/site-form-field-type.enum.ts @@ -0,0 +1,9 @@ +export enum SiteFormFieldType { + EntityName = 'entity_name', + EntityField = 'entity_field', + Delimiter = 'delimiter', + Schedule = 'schedule', + SchedulePerformer = 'schedule_performer', + ScheduleDate = 'schedule_date', + ScheduleTime = 'schedule_time', +} diff --git a/backend/src/modules/forms/site-form-field/index.ts b/backend/src/modules/forms/site-form-field/index.ts new file mode 100644 index 0000000..0e94d37 --- /dev/null +++ b/backend/src/modules/forms/site-form-field/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './site-form-field.controller'; +export * from './site-form-field.service'; diff --git a/backend/src/modules/forms/site-form-field/site-form-field.controller.ts b/backend/src/modules/forms/site-form-field/site-form-field.controller.ts new file mode 100644 index 0000000..d771bf4 --- /dev/null +++ b/backend/src/modules/forms/site-form-field/site-form-field.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import type { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { SiteFormFieldService } from './site-form-field.service'; +import { SiteFormFieldDto, CreateSiteFormFieldDto, UpdateSiteFormFieldDto } from './dto'; + +@ApiTags('site-forms/fields') +@Controller(':formId/pages/:pageId/fields') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class SiteFormFieldController { + constructor(private readonly service: SiteFormFieldService) {} + + @ApiCreatedResponse({ description: 'Create site form field', type: SiteFormFieldDto }) + @Post() + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('pageId', ParseIntPipe) pageId: number, + @Body() dto: CreateSiteFormFieldDto, + ) { + return this.service.create(accountId, pageId, dto); + } + + @ApiOkResponse({ description: 'Get site form fields', type: [SiteFormFieldDto] }) + @Get() + public async findMany(@CurrentAuth() { accountId }: AuthData, @Param('pageId', ParseIntPipe) pageId: number) { + return this.service.findMany(accountId, { pageId }); + } + + @ApiOkResponse({ description: 'Get site form field', type: [SiteFormFieldDto] }) + @Get(':fieldId') + public async findOne( + @CurrentAuth() { accountId }: AuthData, + @Param('pageId', ParseIntPipe) pageId: number, + @Param('fieldId', ParseIntPipe) fieldId: number, + ) { + return this.service.findOne(accountId, { pageId, fieldId }); + } + + @ApiCreatedResponse({ description: 'Update site form field', type: SiteFormFieldDto }) + @Patch(':fieldId') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('pageId', ParseIntPipe) pageId: number, + @Param('fieldId', ParseIntPipe) fieldId: number, + @Body() dto: UpdateSiteFormFieldDto, + ) { + return this.service.update(accountId, pageId, fieldId, dto); + } + + @ApiOkResponse({ description: 'Delete site form field' }) + @Delete(':fieldId') + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('pageId', ParseIntPipe) pageId: number, + @Param('fieldId', ParseIntPipe) fieldId: number, + ) { + return this.service.delete(accountId, pageId, fieldId); + } +} diff --git a/backend/src/modules/forms/site-form-field/site-form-field.service.ts b/backend/src/modules/forms/site-form-field/site-form-field.service.ts new file mode 100644 index 0000000..9e9932a --- /dev/null +++ b/backend/src/modules/forms/site-form-field/site-form-field.service.ts @@ -0,0 +1,102 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { SiteFormField } from './entities'; +import { CreateSiteFormFieldDto, UpdateSiteFormFieldDto } from './dto'; + +interface FindFilter { + pageId?: number; + fieldId?: number | number[]; +} + +@Injectable() +export class SiteFormFieldService { + constructor( + @InjectRepository(SiteFormField) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, pageId: number, dto: CreateSiteFormFieldDto): Promise { + return this.repository.save(SiteFormField.fromDto(accountId, pageId, dto)); + } + + public async createMany(accountId: number, pageId: number, dtos: CreateSiteFormFieldDto[]): Promise { + return Promise.all(dtos.map((dto) => this.create(accountId, pageId, dto))); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + public async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).orderBy('sort_order').getMany(); + } + + public async update( + accountId: number, + pageId: number, + fieldId: number, + dto: UpdateSiteFormFieldDto, + ): Promise { + let field = await this.findOne(accountId, { pageId, fieldId }); + if (!field) { + throw NotFoundError.withId(SiteFormField, fieldId); + } + + field = await this.repository.save(field.update(dto)); + + return field; + } + + public async updateMany(accountId: number, pageId: number, dtos: UpdateSiteFormFieldDto[]): Promise { + return Promise.all(dtos.map((dto) => this.update(accountId, pageId, dto.id, dto))); + } + + public async processBatch( + accountId: number, + pageId: number, + dtos: (CreateSiteFormFieldDto | UpdateSiteFormFieldDto)[], + ): Promise { + const fields = await this.findMany(accountId, { pageId }); + + const created = dtos.filter((dto) => !dto['id']).map((dto) => dto as CreateSiteFormFieldDto); + const updated = dtos.filter((dto) => dto['id']).map((dto) => dto as UpdateSiteFormFieldDto); + const deleted = fields.filter((f) => !updated.some((dto) => dto.id === f.id)).map((f) => f.id); + + const result: SiteFormField[] = []; + + result.push(...(await this.createMany(accountId, pageId, created))); + result.push(...(await this.updateMany(accountId, pageId, updated))); + + if (deleted.length) { + await this.delete(accountId, pageId, deleted); + } + + return result; + } + + public async delete(accountId: number, pageId: number, fieldId: number | number[]) { + await this.createFindQb(accountId, { pageId, fieldId }).delete().execute(); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + if (filter?.fieldId) { + if (Array.isArray(filter.fieldId)) { + qb.andWhere('id IN (:...ids)', { ids: filter.fieldId }); + } else { + qb.andWhere('id = :id', { id: filter.fieldId }); + } + } + + if (filter?.pageId) { + qb.andWhere('page_id = :pageId', { pageId: filter.pageId }); + } + + return qb; + } +} diff --git a/backend/src/modules/forms/site-form-gratitude/dto/create-site-form-gratitude.dto.ts b/backend/src/modules/forms/site-form-gratitude/dto/create-site-form-gratitude.dto.ts new file mode 100644 index 0000000..74074ab --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/dto/create-site-form-gratitude.dto.ts @@ -0,0 +1,9 @@ +import { PickType } from '@nestjs/swagger'; + +import { SiteFormGratitudeDto } from './site-form-gratitude.dto'; + +export class CreateSiteFormGratitudeDto extends PickType(SiteFormGratitudeDto, [ + 'isEnabled', + 'header', + 'text', +] as const) {} diff --git a/backend/src/modules/forms/site-form-gratitude/dto/index.ts b/backend/src/modules/forms/site-form-gratitude/dto/index.ts new file mode 100644 index 0000000..d792e79 --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-site-form-gratitude.dto'; +export * from './site-form-gratitude.dto'; +export * from './update-site-form-gratitude.dto'; diff --git a/backend/src/modules/forms/site-form-gratitude/dto/site-form-gratitude.dto.ts b/backend/src/modules/forms/site-form-gratitude/dto/site-form-gratitude.dto.ts new file mode 100644 index 0000000..58c7117 --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/dto/site-form-gratitude.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class SiteFormGratitudeDto { + @ApiProperty() + @IsNumber() + formId: number; + + @ApiProperty() + @IsBoolean() + isEnabled: boolean; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + header?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + text?: string | null; +} diff --git a/backend/src/modules/forms/site-form-gratitude/dto/update-site-form-gratitude.dto.ts b/backend/src/modules/forms/site-form-gratitude/dto/update-site-form-gratitude.dto.ts new file mode 100644 index 0000000..b7456f8 --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/dto/update-site-form-gratitude.dto.ts @@ -0,0 +1,7 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { SiteFormGratitudeDto } from './site-form-gratitude.dto'; + +export class UpdateSiteFormGratitudeDto extends PartialType( + PickType(SiteFormGratitudeDto, ['isEnabled', 'text', 'header'] as const), +) {} diff --git a/backend/src/modules/forms/site-form-gratitude/entities/index.ts b/backend/src/modules/forms/site-form-gratitude/entities/index.ts new file mode 100644 index 0000000..2fb8af8 --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/entities/index.ts @@ -0,0 +1 @@ +export * from './site-form-gratitude.entity'; diff --git a/backend/src/modules/forms/site-form-gratitude/entities/site-form-gratitude.entity.ts b/backend/src/modules/forms/site-form-gratitude/entities/site-form-gratitude.entity.ts new file mode 100644 index 0000000..cec2e26 --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/entities/site-form-gratitude.entity.ts @@ -0,0 +1,45 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { type CreateSiteFormGratitudeDto, SiteFormGratitudeDto, type UpdateSiteFormGratitudeDto } from '../dto'; + +@Entity() +export class SiteFormGratitude { + @Column() + accountId: number; + + @PrimaryColumn() + formId: number; + + @Column({ default: false }) + isEnabled: boolean; + + @Column({ nullable: true }) + header: string | null; + + @Column({ nullable: true }) + text: string | null; + + constructor(accountId: number, formId: number, isEnabled: boolean, header: string | null, text: string | null) { + this.accountId = accountId; + this.formId = formId; + this.isEnabled = isEnabled; + this.header = header; + this.text = text; + } + + public static fromDto(accountId: number, formId: number, dto: CreateSiteFormGratitudeDto): SiteFormGratitude { + return new SiteFormGratitude(accountId, formId, dto.isEnabled, dto.header, dto.text); + } + + public update(dto: UpdateSiteFormGratitudeDto): SiteFormGratitude { + this.isEnabled = dto.isEnabled !== undefined ? dto.isEnabled : this.isEnabled; + this.header = dto.header !== undefined ? dto.header : this.header; + this.text = dto.text !== undefined ? dto.text : this.text; + + return this; + } + + public toDto(): SiteFormGratitudeDto { + return { formId: this.formId, isEnabled: this.isEnabled, header: this.header, text: this.text }; + } +} diff --git a/backend/src/modules/forms/site-form-gratitude/index.ts b/backend/src/modules/forms/site-form-gratitude/index.ts new file mode 100644 index 0000000..8aeed32 --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './site-form-gratitude.controller'; +export * from './site-form-gratitude.service'; diff --git a/backend/src/modules/forms/site-form-gratitude/site-form-gratitude.controller.ts b/backend/src/modules/forms/site-form-gratitude/site-form-gratitude.controller.ts new file mode 100644 index 0000000..212b68b --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/site-form-gratitude.controller.ts @@ -0,0 +1,50 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import type { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { SiteFormGratitudeService } from './site-form-gratitude.service'; +import { SiteFormGratitudeDto, type CreateSiteFormGratitudeDto, type UpdateSiteFormGratitudeDto } from './dto'; + +@ApiTags('site-forms/gratitude') +@Controller(':formId/gratitude') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class SiteFormGratitudeController { + constructor(private readonly service: SiteFormGratitudeService) {} + + @ApiCreatedResponse({ description: 'Create site form gratitude', type: SiteFormGratitudeDto }) + @Post() + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Body() dto: CreateSiteFormGratitudeDto, + ) { + return this.service.create(accountId, formId, dto); + } + + @ApiOkResponse({ description: 'Get site form gratitude', type: [SiteFormGratitudeDto] }) + @Get() + public async findOne(@CurrentAuth() { accountId }: AuthData, @Param('formId', ParseIntPipe) formId: number) { + return this.service.findOne(accountId, { formId }); + } + + @ApiCreatedResponse({ description: 'Update site form gratitude', type: SiteFormGratitudeDto }) + @Patch() + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Body() dto: UpdateSiteFormGratitudeDto, + ) { + return this.service.update(accountId, formId, dto); + } + + @ApiOkResponse({ description: 'Delete site form gratitude' }) + @Delete() + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('formId', ParseIntPipe) formId: number) { + return this.service.delete(accountId, formId); + } +} diff --git a/backend/src/modules/forms/site-form-gratitude/site-form-gratitude.service.ts b/backend/src/modules/forms/site-form-gratitude/site-form-gratitude.service.ts new file mode 100644 index 0000000..171f1a9 --- /dev/null +++ b/backend/src/modules/forms/site-form-gratitude/site-form-gratitude.service.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { SiteFormGratitude } from './entities'; +import type { CreateSiteFormGratitudeDto, UpdateSiteFormGratitudeDto } from './dto'; + +interface FindFilter { + formId?: number; +} + +@Injectable() +export class SiteFormGratitudeService { + constructor( + @InjectRepository(SiteFormGratitude) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, formId: number, dto: CreateSiteFormGratitudeDto): Promise { + return this.repository.save(SiteFormGratitude.fromDto(accountId, formId, dto)); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + public async update(accountId: number, formId: number, dto: UpdateSiteFormGratitudeDto): Promise { + const gratitude = await this.findOne(accountId, { formId }); + if (!gratitude) { + throw NotFoundError.withId(SiteFormGratitude, formId); + } + + await this.repository.save(gratitude.update(dto)); + + return gratitude; + } + + public async process( + accountId: number, + formId: number, + dto: CreateSiteFormGratitudeDto | UpdateSiteFormGratitudeDto, + ): Promise { + const gratitude = await this.findOne(accountId, { formId }); + if (!gratitude) { + return this.create(accountId, formId, dto as CreateSiteFormGratitudeDto); + } else { + await this.repository.save(gratitude.update(dto as UpdateSiteFormGratitudeDto)); + + return gratitude; + } + } + + public async delete(accountId: number, formId: number) { + await this.createFindQb(accountId, { formId }).delete().execute(); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + if (filter?.formId) { + qb.andWhere('form_id = :formId', { formId: filter.formId }); + } + + return qb; + } +} diff --git a/backend/src/modules/forms/site-form-page/dto/create-site-form-page.dto.ts b/backend/src/modules/forms/site-form-page/dto/create-site-form-page.dto.ts new file mode 100644 index 0000000..be7b306 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/dto/create-site-form-page.dto.ts @@ -0,0 +1,14 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { CreateSiteFormFieldDto } from '../../site-form-field'; +import { SiteFormPageDto } from './site-form-page.dto'; + +export class CreateSiteFormPageDto extends PickType(SiteFormPageDto, ['title', 'sortOrder'] as const) { + @ApiPropertyOptional({ type: [CreateSiteFormFieldDto], nullable: true }) + @IsOptional() + @IsArray() + @Type(() => CreateSiteFormFieldDto) + fields?: CreateSiteFormFieldDto[] | null; +} diff --git a/backend/src/modules/forms/site-form-page/dto/index.ts b/backend/src/modules/forms/site-form-page/dto/index.ts new file mode 100644 index 0000000..9c856c8 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-site-form-page.dto'; +export * from './site-form-page.dto'; +export * from './update-site-form-page.dto'; diff --git a/backend/src/modules/forms/site-form-page/dto/site-form-page.dto.ts b/backend/src/modules/forms/site-form-page/dto/site-form-page.dto.ts new file mode 100644 index 0000000..a54279e --- /dev/null +++ b/backend/src/modules/forms/site-form-page/dto/site-form-page.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { SiteFormFieldDto } from '../../site-form-field'; + +export class SiteFormPageDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + title?: string | null; + + @ApiProperty() + @IsNumber() + sortOrder: number; + + @ApiProperty({ type: [SiteFormFieldDto] }) + @IsArray() + @Type(() => SiteFormFieldDto) + fields: SiteFormFieldDto[]; +} diff --git a/backend/src/modules/forms/site-form-page/dto/update-site-form-page.dto.ts b/backend/src/modules/forms/site-form-page/dto/update-site-form-page.dto.ts new file mode 100644 index 0000000..16b458d --- /dev/null +++ b/backend/src/modules/forms/site-form-page/dto/update-site-form-page.dto.ts @@ -0,0 +1,22 @@ +import { ApiExtraModels, ApiPropertyOptional, PartialType, PickType, getSchemaPath } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { CreateSiteFormFieldDto, UpdateSiteFormFieldDto } from '../../site-form-field'; +import { SiteFormPageDto } from './site-form-page.dto'; + +@ApiExtraModels(CreateSiteFormFieldDto) +@ApiExtraModels(UpdateSiteFormFieldDto) +export class UpdateSiteFormPageDto extends PartialType( + PickType(SiteFormPageDto, ['id', 'title', 'sortOrder'] as const), +) { + @ApiPropertyOptional({ + description: 'Array of form fields', + type: 'array', + items: { + oneOf: [{ $ref: getSchemaPath(CreateSiteFormFieldDto) }, { $ref: getSchemaPath(UpdateSiteFormFieldDto) }], + }, + }) + @IsOptional() + @IsArray() + fields?: (CreateSiteFormFieldDto | UpdateSiteFormFieldDto)[] | null; +} diff --git a/backend/src/modules/forms/site-form-page/entities/index.ts b/backend/src/modules/forms/site-form-page/entities/index.ts new file mode 100644 index 0000000..e126891 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/entities/index.ts @@ -0,0 +1 @@ +export * from './site-form-page.entity'; diff --git a/backend/src/modules/forms/site-form-page/entities/site-form-page.entity.ts b/backend/src/modules/forms/site-form-page/entities/site-form-page.entity.ts new file mode 100644 index 0000000..e9fad57 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/entities/site-form-page.entity.ts @@ -0,0 +1,57 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { SiteFormField } from '../../site-form-field'; +import { SiteFormPageDto, type CreateSiteFormPageDto, type UpdateSiteFormPageDto } from '../dto'; + +@Entity() +export class SiteFormPage { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + formId: number; + + @Column({ nullable: true }) + title: string | null; + + @Column() + sortOrder: number; + + constructor(accountId: number, formId: number, title: string | null, sortOrder: number) { + this.accountId = accountId; + this.formId = formId; + this.title = title; + this.sortOrder = sortOrder; + } + + private _fields: SiteFormField[] | null; + public get fields(): SiteFormField[] | null { + return this._fields; + } + public set fields(value: SiteFormField[] | null) { + this._fields = value; + } + + public static fromDto(accountId: number, formId: number, dto: CreateSiteFormPageDto): SiteFormPage { + return new SiteFormPage(accountId, formId, dto.title, dto.sortOrder); + } + + public update(dto: UpdateSiteFormPageDto): SiteFormPage { + this.title = dto.title !== undefined ? dto.title : this.title; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + + return this; + } + + public toDto(): SiteFormPageDto { + return { + id: this.id, + title: this.title, + sortOrder: this.sortOrder, + fields: this.fields ? this.fields?.map((f) => f.toDto()) : this.fields, + }; + } +} diff --git a/backend/src/modules/forms/site-form-page/index.ts b/backend/src/modules/forms/site-form-page/index.ts new file mode 100644 index 0000000..fcb2ed9 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './site-form-page.controller'; +export * from './site-form-page.service'; diff --git a/backend/src/modules/forms/site-form-page/site-form-page.controller.ts b/backend/src/modules/forms/site-form-page/site-form-page.controller.ts new file mode 100644 index 0000000..ec8cc1e --- /dev/null +++ b/backend/src/modules/forms/site-form-page/site-form-page.controller.ts @@ -0,0 +1,85 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto, ExpandQuery } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import type { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { SiteFormPageService } from './site-form-page.service'; +import { SiteFormPageDto, CreateSiteFormPageDto, UpdateSiteFormPageDto } from './dto'; +import { ExpandableField } from './types'; + +@ApiTags('site-forms/pages') +@Controller(':formId/pages') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class SiteFormPageController { + constructor(private readonly service: SiteFormPageService) {} + + @ApiCreatedResponse({ description: 'Create site form page', type: SiteFormPageDto }) + @Post() + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Body() dto: CreateSiteFormPageDto, + ) { + return this.service.create(accountId, formId, dto); + } + + @ApiOkResponse({ description: 'Get site form pages', type: [SiteFormPageDto] }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: fields.', + }) + @Get() + public async findMany( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.findMany(accountId, { formId }, { expand: expand.fields }); + } + + @ApiOkResponse({ description: 'Get site form page', type: [SiteFormPageDto] }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: fields.', + }) + @Get(':pageId') + public async findOne( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Param('pageId', ParseIntPipe) pageId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.findOne(accountId, { formId, pageId }, { expand: expand.fields }); + } + + @ApiCreatedResponse({ description: 'Update site form page', type: SiteFormPageDto }) + @Patch(':pageId') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Param('pageId', ParseIntPipe) pageId: number, + @Body() dto: UpdateSiteFormPageDto, + ) { + return this.service.update(accountId, formId, pageId, dto); + } + + @ApiOkResponse({ description: 'Delete site form page' }) + @Delete(':pageId') + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Param('pageId', ParseIntPipe) pageId: number, + ) { + return this.service.delete(accountId, formId, pageId); + } +} diff --git a/backend/src/modules/forms/site-form-page/site-form-page.service.ts b/backend/src/modules/forms/site-form-page/site-form-page.service.ts new file mode 100644 index 0000000..2b47039 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/site-form-page.service.ts @@ -0,0 +1,131 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { SiteFormFieldService } from '../site-form-field'; + +import { SiteFormPage } from './entities'; +import type { CreateSiteFormPageDto, UpdateSiteFormPageDto } from './dto'; +import type { ExpandableField } from './types'; + +interface FindFilter { + formId?: number; + pageId?: number | number[]; +} +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class SiteFormPageService { + constructor( + @InjectRepository(SiteFormPage) + private readonly repository: Repository, + private readonly fieldService: SiteFormFieldService, + ) {} + + public async create(accountId: number, formId: number, dto: CreateSiteFormPageDto): Promise { + const page = await this.repository.save(SiteFormPage.fromDto(accountId, formId, dto)); + + if (dto.fields) { + page.fields = await this.fieldService.createMany(accountId, page.id, dto.fields); + } + + return page; + } + + public async createMany(accountId: number, formId: number, dtos: CreateSiteFormPageDto[]): Promise { + return Promise.all(dtos.map((dto) => this.create(accountId, formId, dto))); + } + + public async findOne(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const page = await this.createFindQb(accountId, filter).getOne(); + return page && options?.expand ? await this.expandOne(page, options.expand) : page; + } + + public async findMany(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const pages = await this.createFindQb(accountId, filter).orderBy('sort_order').getMany(); + return pages && options?.expand ? await this.expandMany(pages, options.expand) : pages; + } + + public async update( + accountId: number, + formId: number, + pageId: number, + dto: UpdateSiteFormPageDto, + ): Promise { + const page = await this.findOne(accountId, { formId, pageId }); + if (!page) { + throw NotFoundError.withId(SiteFormPage, formId); + } + + await this.repository.save(page.update(dto)); + + if (dto.fields) { + page.fields = await this.fieldService.processBatch(accountId, pageId, dto.fields); + } + + return page; + } + + public async updateMany(accountId: number, formId: number, dtos: UpdateSiteFormPageDto[]): Promise { + return Promise.all(dtos.map((dto) => this.update(accountId, formId, dto.id, dto))); + } + + public async processBatch( + accountId: number, + formId: number, + dtos: (CreateSiteFormPageDto | UpdateSiteFormPageDto)[], + ): Promise { + const pages = await this.findMany(accountId, { formId }); + + const created = dtos.filter((dto) => !dto['id']).map((dto) => dto as CreateSiteFormPageDto); + const updated = dtos.filter((dto) => dto['id']).map((dto) => dto as UpdateSiteFormPageDto); + const deleted = pages.filter((f) => !updated.some((dto) => dto.id === f.id)).map((f) => f.id); + + const result: SiteFormPage[] = []; + + result.push(...(await this.createMany(accountId, formId, created))); + result.push(...(await this.updateMany(accountId, formId, updated))); + + if (deleted.length) { + await this.delete(accountId, formId, deleted); + } + + return result; + } + + public async delete(accountId: number, formId: number, pageId: number | number[]) { + await this.createFindQb(accountId, { pageId, formId }).delete().execute(); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + if (filter?.pageId) { + if (Array.isArray(filter.pageId)) { + qb.andWhere('id IN (:...ids)', { ids: filter.pageId }); + } else { + qb.andWhere('id = :id', { id: filter.pageId }); + } + } + + if (filter?.formId) { + qb.andWhere('form_id = :formId', { formId: filter.formId }); + } + + return qb; + } + + private async expandOne(page: SiteFormPage, expand: ExpandableField[]): Promise { + if (expand.includes('fields')) { + page.fields = await this.fieldService.findMany(page.accountId, { pageId: page.id }); + } + return page; + } + private async expandMany(pages: SiteFormPage[], expand: ExpandableField[]): Promise { + return await Promise.all(pages.map((page) => this.expandOne(page, expand))); + } +} diff --git a/backend/src/modules/forms/site-form-page/types/expandable-field.ts b/backend/src/modules/forms/site-form-page/types/expandable-field.ts new file mode 100644 index 0000000..13df006 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'fields'; diff --git a/backend/src/modules/forms/site-form-page/types/index.ts b/backend/src/modules/forms/site-form-page/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/forms/site-form-page/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/forms/site-form/dto/create-site-form.dto.ts b/backend/src/modules/forms/site-form/dto/create-site-form.dto.ts new file mode 100644 index 0000000..35ee97d --- /dev/null +++ b/backend/src/modules/forms/site-form/dto/create-site-form.dto.ts @@ -0,0 +1,45 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { CreateSiteFormConsentDto } from '../../site-form-consent'; +import { CreateSiteFormGratitudeDto } from '../../site-form-gratitude'; +import { CreateSiteFormPageDto } from '../../site-form-page'; + +import { SiteFormDto } from './site-form.dto'; + +export class CreateSiteFormDto extends PickType(SiteFormDto, [ + 'type', + 'name', + 'title', + 'responsibleId', + 'design', + 'fieldLabelEnabled', + 'fieldPlaceholderEnabled', + 'multiformEnabled', + 'scheduleLimitDays', + 'checkDuplicate', + 'entityTypeLinks', + 'scheduleLinks', +] as const) { + @ApiPropertyOptional({ description: 'Is form headless' }) + @IsOptional() + @IsBoolean() + isHeadless?: boolean; + + @ApiPropertyOptional({ type: CreateSiteFormConsentDto, nullable: true, description: 'Form consent' }) + @IsOptional() + @Type(() => CreateSiteFormConsentDto) + consent?: CreateSiteFormConsentDto | null; + + @ApiPropertyOptional({ type: CreateSiteFormGratitudeDto, nullable: true, description: 'Form gratitude' }) + @IsOptional() + @Type(() => CreateSiteFormGratitudeDto) + gratitude?: CreateSiteFormGratitudeDto | null; + + @ApiPropertyOptional({ type: [CreateSiteFormPageDto], nullable: true, description: 'Form pages' }) + @IsOptional() + @IsArray() + @Type(() => CreateSiteFormPageDto) + pages?: CreateSiteFormPageDto[] | null; +} diff --git a/backend/src/modules/forms/site-form/dto/index.ts b/backend/src/modules/forms/site-form/dto/index.ts new file mode 100644 index 0000000..490a11d --- /dev/null +++ b/backend/src/modules/forms/site-form/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-site-form.dto'; +export * from './site-form-entity-type.dto'; +export * from './site-form-schedule.dto'; +export * from './site-form.dto'; +export * from './update-site-form.dto'; diff --git a/backend/src/modules/forms/site-form/dto/site-form-entity-type.dto.ts b/backend/src/modules/forms/site-form/dto/site-form-entity-type.dto.ts new file mode 100644 index 0000000..1f2fa7f --- /dev/null +++ b/backend/src/modules/forms/site-form/dto/site-form-entity-type.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional } from 'class-validator'; + +export class SiteFormEntityTypeDto { + @ApiProperty({ description: 'Entity type ID' }) + @IsNumber() + entityTypeId: number; + + @ApiProperty({ nullable: true, description: 'Board ID' }) + @IsOptional() + @IsNumber() + boardId: number | null; + + @ApiProperty({ description: 'Is main entity type' }) + @IsBoolean() + isMain: boolean; +} diff --git a/backend/src/modules/forms/site-form/dto/site-form-schedule.dto.ts b/backend/src/modules/forms/site-form/dto/site-form-schedule.dto.ts new file mode 100644 index 0000000..d8a4df6 --- /dev/null +++ b/backend/src/modules/forms/site-form/dto/site-form-schedule.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class SiteFormScheduleDto { + @ApiProperty({ description: 'Schedule ID' }) + @IsNumber() + scheduleId: number; +} diff --git a/backend/src/modules/forms/site-form/dto/site-form.dto.ts b/backend/src/modules/forms/site-form/dto/site-form.dto.ts new file mode 100644 index 0000000..b66e9a2 --- /dev/null +++ b/backend/src/modules/forms/site-form/dto/site-form.dto.ts @@ -0,0 +1,109 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; +import { Type } from 'class-transformer'; + +import { SiteFormConsentDto } from '../../site-form-consent'; +import { SiteFormGratitudeDto } from '../../site-form-gratitude'; +import { SiteFormPageDto } from '../../site-form-page'; + +import { SiteFormType } from '../enums'; +import { SiteFormEntityTypeDto } from './site-form-entity-type.dto'; +import { SiteFormScheduleDto } from './site-form-schedule.dto'; + +export class SiteFormDto { + @ApiProperty({ description: 'Site form id' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'User ID who created form' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ enum: SiteFormType, description: 'Form type' }) + @IsEnum(SiteFormType) + type: SiteFormType; + + @ApiProperty({ description: 'Form name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Form code' }) + @IsString() + code: string; + + @ApiProperty({ description: 'Is form active' }) + @IsBoolean() + isActive: boolean; + + @ApiProperty({ description: 'Is form headless' }) + @IsBoolean() + isHeadless: boolean; + + @ApiPropertyOptional({ nullable: true, description: 'Form title' }) + @IsOptional() + @IsString() + title?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Form responsible user ID' }) + @IsOptional() + @IsNumber() + responsibleId?: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'Form design' }) + @IsOptional() + @IsObject() + design: object | null; + + @ApiPropertyOptional({ description: 'Is field label enabled' }) + @IsOptional() + @IsBoolean() + fieldLabelEnabled?: boolean; + + @ApiPropertyOptional({ description: 'Is field placeholder enabled' }) + @IsOptional() + @IsBoolean() + fieldPlaceholderEnabled?: boolean; + + @ApiPropertyOptional({ description: 'Is multiform enabled' }) + @IsOptional() + @IsBoolean() + multiformEnabled?: boolean; + + @ApiPropertyOptional({ nullable: true, description: 'Schedule limit for date choose' }) + @IsOptional() + @IsNumber() + scheduleLimitDays?: number | null; + + @ApiPropertyOptional({ description: 'Deduplicate linked cards by phone or email' }) + @IsOptional() + @IsBoolean() + checkDuplicate?: boolean; + + @ApiPropertyOptional({ type: SiteFormConsentDto, nullable: true, description: 'Form consent' }) + @IsOptional() + @Type(() => SiteFormConsentDto) + consent?: SiteFormConsentDto | null; + + @ApiPropertyOptional({ type: SiteFormGratitudeDto, nullable: true, description: 'Form gratitude' }) + @IsOptional() + @Type(() => SiteFormGratitudeDto) + gratitude?: SiteFormGratitudeDto | null; + + @ApiPropertyOptional({ type: [SiteFormPageDto], nullable: true, description: 'Form pages' }) + @IsOptional() + @IsArray() + @Type(() => SiteFormPageDto) + pages?: SiteFormPageDto[] | null; + + @ApiPropertyOptional({ type: [SiteFormEntityTypeDto], nullable: true, description: 'Form entity type links' }) + @IsOptional() + @IsArray() + @Type(() => SiteFormEntityTypeDto) + entityTypeLinks?: SiteFormEntityTypeDto[] | null; + + @ApiPropertyOptional({ type: [SiteFormScheduleDto], nullable: true, description: 'Form schedule links' }) + @IsOptional() + @IsArray() + @Type(() => SiteFormScheduleDto) + scheduleLinks?: SiteFormScheduleDto[] | null; +} diff --git a/backend/src/modules/forms/site-form/dto/update-site-form.dto.ts b/backend/src/modules/forms/site-form/dto/update-site-form.dto.ts new file mode 100644 index 0000000..e09f0cf --- /dev/null +++ b/backend/src/modules/forms/site-form/dto/update-site-form.dto.ts @@ -0,0 +1,62 @@ +import { ApiExtraModels, ApiPropertyOptional, PartialType, PickType, getSchemaPath } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { CreateSiteFormConsentDto, UpdateSiteFormConsentDto } from '../../site-form-consent'; +import { CreateSiteFormGratitudeDto, UpdateSiteFormGratitudeDto } from '../../site-form-gratitude'; +import { CreateSiteFormPageDto, UpdateSiteFormPageDto } from '../../site-form-page'; + +import { SiteFormDto } from './site-form.dto'; + +@ApiExtraModels(CreateSiteFormConsentDto) +@ApiExtraModels(UpdateSiteFormConsentDto) +@ApiExtraModels(CreateSiteFormGratitudeDto) +@ApiExtraModels(UpdateSiteFormGratitudeDto) +@ApiExtraModels(CreateSiteFormPageDto) +@ApiExtraModels(UpdateSiteFormPageDto) +export class UpdateSiteFormDto extends PartialType( + PickType(SiteFormDto, [ + 'type', + 'name', + 'isActive', + 'title', + 'isHeadless', + 'responsibleId', + 'design', + 'entityTypeLinks', + 'fieldLabelEnabled', + 'fieldPlaceholderEnabled', + 'multiformEnabled', + 'scheduleLimitDays', + 'checkDuplicate', + 'scheduleLinks', + ] as const), +) { + @ApiPropertyOptional({ + description: 'Form consent', + type: 'array', + items: { + oneOf: [{ $ref: getSchemaPath(CreateSiteFormConsentDto) }, { $ref: getSchemaPath(UpdateSiteFormConsentDto) }], + }, + }) + @IsOptional() + consent?: CreateSiteFormConsentDto | UpdateSiteFormConsentDto | null; + + @ApiPropertyOptional({ + description: 'Form gratitude', + type: 'array', + items: { + oneOf: [{ $ref: getSchemaPath(CreateSiteFormGratitudeDto) }, { $ref: getSchemaPath(UpdateSiteFormGratitudeDto) }], + }, + }) + @IsOptional() + gratitude?: CreateSiteFormGratitudeDto | UpdateSiteFormGratitudeDto | null; + + @ApiPropertyOptional({ + description: 'Form pages', + type: 'array', + items: { oneOf: [{ $ref: getSchemaPath(CreateSiteFormPageDto) }, { $ref: getSchemaPath(UpdateSiteFormPageDto) }] }, + }) + @IsOptional() + @IsArray() + pages?: (CreateSiteFormPageDto | UpdateSiteFormPageDto)[] | null; +} diff --git a/backend/src/modules/forms/site-form/entities/index.ts b/backend/src/modules/forms/site-form/entities/index.ts new file mode 100644 index 0000000..ad7e8ab --- /dev/null +++ b/backend/src/modules/forms/site-form/entities/index.ts @@ -0,0 +1,3 @@ +export * from './site-form-entity-type.entity'; +export * from './site-form-schedule.entity'; +export * from './site-form.entity'; diff --git a/backend/src/modules/forms/site-form/entities/site-form-entity-type.entity.ts b/backend/src/modules/forms/site-form/entities/site-form-entity-type.entity.ts new file mode 100644 index 0000000..18c589e --- /dev/null +++ b/backend/src/modules/forms/site-form/entities/site-form-entity-type.entity.ts @@ -0,0 +1,44 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { SiteFormEntityTypeDto } from '../dto'; + +@Entity() +export class SiteFormEntityType { + @Column() + accountId: number; + + @PrimaryColumn() + formId: number; + + @PrimaryColumn() + entityTypeId: number; + + @Column({ nullable: true }) + boardId: number | null; + + @Column() + isMain: boolean; + + constructor(accountId: number, formId: number, entityTypeId: number, boardId: number | null, isMain: boolean) { + this.accountId = accountId; + this.formId = formId; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.isMain = isMain; + } + + public static fromDto(accountId: number, formId: number, dto: SiteFormEntityTypeDto): SiteFormEntityType { + return new SiteFormEntityType(accountId, formId, dto.entityTypeId, dto.boardId, dto.isMain); + } + + public update(dto: SiteFormEntityTypeDto): SiteFormEntityType { + this.entityTypeId = dto.entityTypeId !== undefined ? dto.entityTypeId : this.entityTypeId; + this.boardId = dto.boardId !== undefined ? dto.boardId : this.boardId; + + return this; + } + + public toDto(): SiteFormEntityTypeDto { + return { entityTypeId: this.entityTypeId, boardId: this.boardId, isMain: this.isMain }; + } +} diff --git a/backend/src/modules/forms/site-form/entities/site-form-schedule.entity.ts b/backend/src/modules/forms/site-form/entities/site-form-schedule.entity.ts new file mode 100644 index 0000000..3c07bb0 --- /dev/null +++ b/backend/src/modules/forms/site-form/entities/site-form-schedule.entity.ts @@ -0,0 +1,35 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { SiteFormScheduleDto } from '../dto'; + +@Entity() +export class SiteFormSchedule { + @Column() + accountId: number; + + @PrimaryColumn() + formId: number; + + @PrimaryColumn() + scheduleId: number; + + constructor(accountId: number, formId: number, scheduleId: number) { + this.accountId = accountId; + this.formId = formId; + this.scheduleId = scheduleId; + } + + public static fromDto(accountId: number, formId: number, dto: SiteFormScheduleDto): SiteFormSchedule { + return new SiteFormSchedule(accountId, formId, dto.scheduleId); + } + + public update(dto: SiteFormScheduleDto): SiteFormSchedule { + this.scheduleId = dto.scheduleId !== undefined ? dto.scheduleId : this.scheduleId; + + return this; + } + + public toDto(): SiteFormScheduleDto { + return { scheduleId: this.scheduleId }; + } +} diff --git a/backend/src/modules/forms/site-form/entities/site-form.entity.ts b/backend/src/modules/forms/site-form/entities/site-form.entity.ts new file mode 100644 index 0000000..d50b03e --- /dev/null +++ b/backend/src/modules/forms/site-form/entities/site-form.entity.ts @@ -0,0 +1,204 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { SiteFormConsent } from '../../site-form-consent'; +import { SiteFormGratitude } from '../../site-form-gratitude'; +import { SiteFormPage } from '../../site-form-page'; + +import type { CreateSiteFormDto, SiteFormDto, UpdateSiteFormDto } from '../dto'; +import { SiteFormType } from '../enums'; +import { SiteFormEntityType } from './site-form-entity-type.entity'; +import { SiteFormSchedule } from './site-form-schedule.entity'; + +@Entity() +export class SiteForm { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + createdBy: number; + + @Column() + type: SiteFormType; + + @Column() + name: string; + + @Column({ unique: true }) + code: string; + + @Column() + isActive: boolean; + + @Column({ default: false }) + isHeadless: boolean; + + @Column({ nullable: true, default: null }) + title: string | null; + + @Column({ nullable: true, default: null }) + responsibleId: number | null; + + @Column({ type: 'jsonb', nullable: true, default: null }) + design: object | null; + + @Column({ default: false }) + fieldLabelEnabled: boolean; + + @Column({ default: true }) + fieldPlaceholderEnabled: boolean; + + @Column({ default: false }) + multiformEnabled: boolean; + + @Column({ default: null }) + scheduleLimitDays: number | null; + + @Column({ default: false }) + checkDuplicate: boolean; + + constructor( + accountId: number, + createdBy: number, + type: SiteFormType, + name: string, + code: string, + isActive: boolean, + isHeadless: boolean, + title: string | null, + responsibleId: number | null, + design: object | null, + fieldLabelEnabled: boolean, + fieldPlaceholderEnabled: boolean, + multiformEnabled: boolean, + scheduleLimitDays: number | null, + checkDuplicate: boolean, + ) { + this.accountId = accountId; + this.createdBy = createdBy; + this.type = type; + this.name = name; + this.code = code; + this.isActive = isActive; + this.isHeadless = isHeadless; + this.title = title; + this.responsibleId = responsibleId; + this.design = design; + this.fieldLabelEnabled = fieldLabelEnabled; + this.fieldPlaceholderEnabled = fieldPlaceholderEnabled; + this.multiformEnabled = multiformEnabled; + this.scheduleLimitDays = scheduleLimitDays; + this.checkDuplicate = checkDuplicate; + } + + private _consent: SiteFormConsent | null; + get consent(): SiteFormConsent | null { + return this._consent; + } + set consent(value: SiteFormConsent | null) { + this._consent = value; + } + + private _gratitude: SiteFormGratitude | null; + get gratitude(): SiteFormGratitude | null { + return this._gratitude; + } + set gratitude(value: SiteFormGratitude | null) { + this._gratitude = value; + } + + private _pages: SiteFormPage[] | null; + get pages(): SiteFormPage[] | null { + return this._pages; + } + set pages(value: SiteFormPage[] | null) { + this._pages = value; + } + + private _entityTypeLinks: SiteFormEntityType[] | null; + get entityTypeLinks(): SiteFormEntityType[] | null { + return this._entityTypeLinks; + } + set entityTypeLinks(value: SiteFormEntityType[] | null) { + this._entityTypeLinks = value; + } + + private _scheduleLinks: SiteFormSchedule[] | null; + get scheduleLinks(): SiteFormSchedule[] | null { + return this._scheduleLinks; + } + set scheduleLinks(value: SiteFormSchedule[] | null) { + this._scheduleLinks = value; + } + + static fromDto( + accountId: number, + createdBy: number, + code: string, + dto: CreateSiteFormDto, + isActive = true, + ): SiteForm { + return new SiteForm( + accountId, + createdBy, + dto.type, + dto.name, + code, + isActive, + dto.isHeadless ?? false, + dto.title, + dto.responsibleId, + dto.design, + dto.fieldLabelEnabled, + dto.fieldPlaceholderEnabled, + dto.multiformEnabled, + dto.scheduleLimitDays, + dto.checkDuplicate, + ); + } + + update(dto: UpdateSiteFormDto): SiteForm { + this.type = dto.type !== undefined ? dto.type : this.type; + this.name = dto.name !== undefined ? dto.name : this.name; + this.isActive = dto.isActive !== undefined ? dto.isActive : this.isActive; + this.isHeadless = dto.isHeadless !== undefined ? dto.isHeadless : this.isHeadless; + this.title = dto.title !== undefined ? dto.title : this.title; + this.responsibleId = dto.responsibleId !== undefined ? dto.responsibleId : this.responsibleId; + this.design = dto.design !== undefined ? dto.design : this.design; + this.fieldLabelEnabled = dto.fieldLabelEnabled !== undefined ? dto.fieldLabelEnabled : this.fieldLabelEnabled; + this.fieldPlaceholderEnabled = + dto.fieldPlaceholderEnabled !== undefined ? dto.fieldPlaceholderEnabled : this.fieldPlaceholderEnabled; + this.multiformEnabled = dto.multiformEnabled !== undefined ? dto.multiformEnabled : this.multiformEnabled; + this.scheduleLimitDays = dto.scheduleLimitDays !== undefined ? dto.scheduleLimitDays : this.scheduleLimitDays; + this.checkDuplicate = dto.checkDuplicate !== undefined ? dto.checkDuplicate : this.checkDuplicate; + + return this; + } + + toDto(): SiteFormDto { + return { + id: this.id, + createdBy: this.createdBy, + type: this.type, + name: this.name, + code: this.code, + isActive: this.isActive, + isHeadless: this.isHeadless, + title: this.title, + responsibleId: this.responsibleId, + design: this.design, + fieldLabelEnabled: this.fieldLabelEnabled, + fieldPlaceholderEnabled: this.fieldPlaceholderEnabled, + multiformEnabled: this.multiformEnabled, + scheduleLimitDays: this.scheduleLimitDays, + checkDuplicate: this.checkDuplicate, + consent: this.consent ? this.consent.toDto() : this.consent, + gratitude: this.gratitude ? this.gratitude.toDto() : this.gratitude, + pages: this.pages ? this.pages?.map((p) => p.toDto()) : this.pages, + entityTypeLinks: this.entityTypeLinks ? this.entityTypeLinks?.map((l) => l.toDto()) : this.entityTypeLinks, + scheduleLinks: this.scheduleLinks ? this.scheduleLinks?.map((l) => l.toDto()) : this.scheduleLinks, + }; + } +} diff --git a/backend/src/modules/forms/site-form/enums/index.ts b/backend/src/modules/forms/site-form/enums/index.ts new file mode 100644 index 0000000..dfe67a5 --- /dev/null +++ b/backend/src/modules/forms/site-form/enums/index.ts @@ -0,0 +1 @@ +export * from './site-form-type.enum'; diff --git a/backend/src/modules/forms/site-form/enums/site-form-type.enum.ts b/backend/src/modules/forms/site-form/enums/site-form-type.enum.ts new file mode 100644 index 0000000..32d30d1 --- /dev/null +++ b/backend/src/modules/forms/site-form/enums/site-form-type.enum.ts @@ -0,0 +1,4 @@ +export enum SiteFormType { + EntityType = 'entity_type', + Schedule = 'schedule', +} diff --git a/backend/src/modules/forms/site-form/index.ts b/backend/src/modules/forms/site-form/index.ts new file mode 100644 index 0000000..dad4b17 --- /dev/null +++ b/backend/src/modules/forms/site-form/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './services'; +export * from './site-form.controller'; +export * from './types'; diff --git a/backend/src/modules/forms/site-form/services/index.ts b/backend/src/modules/forms/site-form/services/index.ts new file mode 100644 index 0000000..6bad5d9 --- /dev/null +++ b/backend/src/modules/forms/site-form/services/index.ts @@ -0,0 +1,3 @@ +export * from './site-form-entity-type.service'; +export * from './site-form-schedule.service'; +export * from './site-form.service'; diff --git a/backend/src/modules/forms/site-form/services/site-form-entity-type.service.ts b/backend/src/modules/forms/site-form/services/site-form-entity-type.service.ts new file mode 100644 index 0000000..5637e40 --- /dev/null +++ b/backend/src/modules/forms/site-form/services/site-form-entity-type.service.ts @@ -0,0 +1,91 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import type { SiteFormEntityTypeDto } from '../dto'; +import { SiteFormEntityType } from '../entities'; + +interface FindFilter { + formId?: number; + entityTypeId?: number | number[]; +} + +@Injectable() +export class SiteFormEntityTypeService { + constructor( + @InjectRepository(SiteFormEntityType) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, formId: number, dto: SiteFormEntityTypeDto): Promise { + return this.repository.save(SiteFormEntityType.fromDto(accountId, formId, dto)); + } + + public async createMany( + accountId: number, + formId: number, + dtos: SiteFormEntityTypeDto[], + ): Promise { + return Promise.all(dtos.map((dto) => this.create(accountId, formId, dto))); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + public async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getMany(); + } + + public async processBatch( + accountId: number, + formId: number, + dtos: SiteFormEntityTypeDto[], + ): Promise { + let links = await this.findMany(accountId, { formId }); + + const deleted = links.filter((link) => !dtos.some((dto) => link.entityTypeId === dto.entityTypeId)); + if (deleted.length) { + await this.delete(accountId, { formId, entityTypeId: deleted.map((link) => link.entityTypeId) }); + links = links.filter((link) => !deleted.some((d) => d.entityTypeId === link.entityTypeId)); + } + + const result: SiteFormEntityType[] = []; + for (const dto of dtos) { + const link = links.find((l) => l.entityTypeId === dto.entityTypeId); + if (link) { + result.push(await this.repository.save(link.update(dto))); + } else { + result.push(await this.create(accountId, formId, dto)); + } + } + const created = dtos.filter((dto) => !links.some((link) => link.entityTypeId === dto.entityTypeId)); + if (created.length) { + result.push(...(await this.createMany(accountId, formId, created))); + } + + return result; + } + + public async delete(accountId: number, filter: FindFilter) { + await this.createFindQb(accountId, filter).delete().execute(); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + if (filter?.formId) { + qb.andWhere('form_id = :formId', { formId: filter.formId }); + } + + if (filter?.entityTypeId) { + if (Array.isArray(filter.entityTypeId)) { + qb.andWhere('entity_type_id IN (:...entityTypeIds)', { entityTypeIds: filter.entityTypeId }); + } else { + qb.andWhere('entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + } + } + + return qb; + } +} diff --git a/backend/src/modules/forms/site-form/services/site-form-schedule.service.ts b/backend/src/modules/forms/site-form/services/site-form-schedule.service.ts new file mode 100644 index 0000000..648ec55 --- /dev/null +++ b/backend/src/modules/forms/site-form/services/site-form-schedule.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import type { SiteFormScheduleDto } from '../dto'; +import { SiteFormSchedule } from '../entities'; + +interface FindFilter { + formId?: number; + scheduleId?: number | number[]; +} + +@Injectable() +export class SiteFormScheduleService { + constructor( + @InjectRepository(SiteFormSchedule) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, formId: number, dto: SiteFormScheduleDto): Promise { + return this.repository.save(SiteFormSchedule.fromDto(accountId, formId, dto)); + } + + public async createMany(accountId: number, formId: number, dtos: SiteFormScheduleDto[]): Promise { + return Promise.all(dtos.map((dto) => this.create(accountId, formId, dto))); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + public async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getMany(); + } + + public async processBatch( + accountId: number, + formId: number, + dtos: SiteFormScheduleDto[], + ): Promise { + let links = await this.findMany(accountId, { formId }); + + const deleted = links.filter((link) => !dtos.some((dto) => link.scheduleId === dto.scheduleId)); + if (deleted.length) { + await this.delete(accountId, { formId, scheduleId: deleted.map((link) => link.scheduleId) }); + links = links.filter((link) => !deleted.some((d) => d.scheduleId === link.scheduleId)); + } + + const result: SiteFormSchedule[] = []; + for (const dto of dtos) { + const link = links.find((l) => l.scheduleId === dto.scheduleId); + if (link) { + result.push(await this.repository.save(link.update(dto))); + } else { + result.push(await this.create(accountId, formId, dto)); + } + } + const created = dtos.filter((dto) => !links.some((link) => link.scheduleId === dto.scheduleId)); + if (created.length) { + result.push(...(await this.createMany(accountId, formId, created))); + } + + return result; + } + + public async delete(accountId: number, filter: FindFilter) { + await this.createFindQb(accountId, filter).delete().execute(); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + + if (filter?.formId) { + qb.andWhere('form_id = :formId', { formId: filter.formId }); + } + + if (filter?.scheduleId) { + if (Array.isArray(filter.scheduleId)) { + qb.andWhere('schedule_id IN (:...scheduleIds)', { scheduleIds: filter.scheduleId }); + } else { + qb.andWhere('schedule_id = :scheduleId', { scheduleId: filter.scheduleId }); + } + } + + return qb; + } +} diff --git a/backend/src/modules/forms/site-form/services/site-form.service.ts b/backend/src/modules/forms/site-form/services/site-form.service.ts new file mode 100644 index 0000000..0a57b71 --- /dev/null +++ b/backend/src/modules/forms/site-form/services/site-form.service.ts @@ -0,0 +1,153 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import type { Repository } from 'typeorm'; + +import { NotFoundError, PasswordUtil } from '@/common'; + +import { SiteFormConsentService } from '../../site-form-consent'; +import { SiteFormGratitudeService } from '../../site-form-gratitude'; +import { SiteFormPageService } from '../../site-form-page'; + +import type { CreateSiteFormDto, UpdateSiteFormDto } from '../dto'; +import { SiteForm } from '../entities'; +import type { ExpandableField } from '../types'; +import { SiteFormEntityTypeService } from './site-form-entity-type.service'; +import { SiteFormScheduleService } from './site-form-schedule.service'; + +interface FindFilter { + formId?: number; + code?: string; +} +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class SiteFormService { + constructor( + @InjectRepository(SiteForm) + private readonly repository: Repository, + private readonly consentService: SiteFormConsentService, + private readonly gratitudeService: SiteFormGratitudeService, + private readonly pageService: SiteFormPageService, + private readonly entityTypeLinkService: SiteFormEntityTypeService, + private readonly scheduleLinkService: SiteFormScheduleService, + ) {} + + async create(accountId: number, userId: number, dto: CreateSiteFormDto): Promise { + const code = PasswordUtil.generateSecure({ length: 16, numbers: true }); + const form = await this.repository.save(SiteForm.fromDto(accountId, userId, code, dto)); + + if (dto.consent) { + form.consent = await this.consentService.create(accountId, form.id, dto.consent); + } + + if (dto.gratitude) { + form.gratitude = await this.gratitudeService.create(accountId, form.id, dto.gratitude); + } + + if (dto.pages) { + form.pages = await this.pageService.createMany(accountId, form.id, dto.pages); + } + + if (dto.entityTypeLinks) { + form.entityTypeLinks = await this.entityTypeLinkService.createMany(accountId, form.id, dto.entityTypeLinks); + } + + if (dto.scheduleLinks) { + form.scheduleLinks = await this.scheduleLinkService.createMany(accountId, form.id, dto.scheduleLinks); + } + + return form; + } + + async findOne(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const form = await this.createFindQb(accountId, filter).getOne(); + + return form && options?.expand ? this.expandOne(form, options.expand) : form; + } + + async findMany(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const forms = await this.createFindQb(accountId, filter).orderBy('sf.id').getMany(); + + return forms && options?.expand ? this.expandMany(forms, options.expand) : forms; + } + + async findByCode(code: string, options?: FindOptions): Promise { + const form = await this.repository.findOneBy({ code, isActive: true }); + + return form && options?.expand ? this.expandOne(form, options.expand) : form; + } + + async update(accountId: number, formId: number, dto: UpdateSiteFormDto): Promise { + const form = await this.findOne(accountId, { formId }); + if (!form) { + throw NotFoundError.withId(SiteForm, formId); + } + + await this.repository.save(form.update(dto)); + + if (dto.consent) { + form.consent = await this.consentService.process(accountId, formId, dto.consent); + } + + if (dto.gratitude) { + form.gratitude = await this.gratitudeService.process(accountId, formId, dto.gratitude); + } + + if (dto.pages) { + form.pages = await this.pageService.processBatch(accountId, formId, dto.pages); + } + + if (dto.entityTypeLinks) { + form.entityTypeLinks = await this.entityTypeLinkService.processBatch(accountId, formId, dto.entityTypeLinks); + } + + if (dto.scheduleLinks) { + form.scheduleLinks = await this.scheduleLinkService.processBatch(accountId, form.id, dto.scheduleLinks); + } + + return form; + } + + async delete(accountId: number, formId: number): Promise { + await this.repository.delete({ accountId, id: formId }); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('sf').where('sf.account_id = :accountId', { accountId }); + + if (filter?.formId) { + qb.andWhere('sf.id = :id', { id: filter.formId }); + } + if (filter?.code) { + qb.andWhere('sf.code = :code', { code: filter.code }); + } + + return qb; + } + + private async expandOne(form: SiteForm, expand: ExpandableField[]): Promise { + if (expand.includes('consent')) { + form.consent = await this.consentService.findOne(form.accountId, { formId: form.id }); + } + if (expand.includes('gratitude')) { + form.gratitude = await this.gratitudeService.findOne(form.accountId, { formId: form.id }); + } + if (expand.includes('pages.fields')) { + form.pages = await this.pageService.findMany(form.accountId, { formId: form.id }, { expand: ['fields'] }); + } else if (expand.includes('pages')) { + form.pages = await this.pageService.findMany(form.accountId, { formId: form.id }); + } + if (expand.includes('entityTypeLinks')) { + form.entityTypeLinks = await this.entityTypeLinkService.findMany(form.accountId, { formId: form.id }); + } + if (expand.includes('scheduleLinks')) { + form.scheduleLinks = await this.scheduleLinkService.findMany(form.accountId, { formId: form.id }); + } + return form; + } + private async expandMany(forms: SiteForm[], expand: ExpandableField[]): Promise { + return await Promise.all(forms.map((form) => this.expandOne(form, expand))); + } +} diff --git a/backend/src/modules/forms/site-form/site-form.controller.ts b/backend/src/modules/forms/site-form/site-form.controller.ts new file mode 100644 index 0000000..09ce780 --- /dev/null +++ b/backend/src/modules/forms/site-form/site-form.controller.ts @@ -0,0 +1,81 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto, ExpandQuery } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import type { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { SiteFormDto, CreateSiteFormDto, UpdateSiteFormDto } from './dto'; +import { ExpandableField } from './types'; +import { SiteFormService } from './services'; + +@ApiTags('site-forms') +@Controller() +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class SiteFormController { + constructor(private readonly service: SiteFormService) {} + + @ApiOperation({ summary: 'Create site form', description: 'Create site form' }) + @ApiBody({ type: CreateSiteFormDto, required: true, description: 'Site form data' }) + @ApiCreatedResponse({ description: 'Site form', type: SiteFormDto }) + @Post() + public async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateSiteFormDto) { + return this.service.create(accountId, userId, dto); + } + + @ApiOperation({ summary: 'Get site forms', description: 'Get site forms for account' }) + @ApiOkResponse({ description: 'Site forms', type: [SiteFormDto] }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: consent,gratitude,pages,pages.fields,entityTypeLinks', + }) + @Get() + public async findMany(@CurrentAuth() { accountId }: AuthData, @Query() expand?: ExpandQuery) { + return this.service.findMany(accountId, {}, { expand: expand.fields }); + } + + @ApiOperation({ summary: 'Get site form', description: 'Get site form by id' }) + @ApiParam({ name: 'formId', type: Number, required: true, description: 'Site form id' }) + @ApiOkResponse({ description: 'Site form', type: [SiteFormDto] }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: consent,gratitude,pages,pages.fields,entityTypeLinks', + }) + @Get(':formId') + public async findOne( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.findOne(accountId, { formId }, { expand: expand.fields }); + } + + @ApiOperation({ summary: 'Update site form', description: 'Update site form' }) + @ApiParam({ name: 'formId', type: Number, required: true, description: 'Site form id' }) + @ApiBody({ type: UpdateSiteFormDto, required: true, description: 'Site form data' }) + @ApiOkResponse({ description: 'Site form', type: SiteFormDto }) + @Patch(':formId') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('formId', ParseIntPipe) formId: number, + @Body() dto: UpdateSiteFormDto, + ) { + return this.service.update(accountId, formId, dto); + } + + @ApiOperation({ summary: 'Delete site form', description: 'Delete site form' }) + @ApiParam({ name: 'formId', type: Number, required: true, description: 'Site form id' }) + @ApiOkResponse() + @Delete(':formId') + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('formId', ParseIntPipe) formId: number) { + return this.service.delete(accountId, formId); + } +} diff --git a/backend/src/modules/forms/site-form/types/expandable-field.ts b/backend/src/modules/forms/site-form/types/expandable-field.ts new file mode 100644 index 0000000..cbaf144 --- /dev/null +++ b/backend/src/modules/forms/site-form/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'consent' | 'gratitude' | 'pages' | 'pages.fields' | 'entityTypeLinks' | 'scheduleLinks'; diff --git a/backend/src/modules/forms/site-form/types/index.ts b/backend/src/modules/forms/site-form/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/forms/site-form/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/frontend-event/frontend-event.gateway.ts b/backend/src/modules/frontend-event/frontend-event.gateway.ts new file mode 100644 index 0000000..7d1e352 --- /dev/null +++ b/backend/src/modules/frontend-event/frontend-event.gateway.ts @@ -0,0 +1,50 @@ +import { Logger } from '@nestjs/common'; +import { OnGatewayConnection, OnGatewayDisconnect, WebSocketGateway, WebSocketServer } from '@nestjs/websockets'; +import { Server, Socket } from 'socket.io'; + +import { TokenService } from '@/common'; +import { TokenPayload } from '@/modules/iam/common'; + +@WebSocketGateway({ path: '/api/socket.io', cors: { origin: '*' } }) +export class FrontendEventGateway implements OnGatewayConnection, OnGatewayDisconnect { + private readonly logger = new Logger(FrontendEventGateway.name); + + @WebSocketServer() + server: Server; + + constructor(private readonly tokenService: TokenService) {} + + handleConnection(client: Socket) { + try { + const payload = this.tokenService.verify(client.handshake.auth['token']); + client.join([this.getUserRoomName(payload.userId), this.getAccountRoomName(payload.accountId)]); + } catch (e) { + this.logger.warn(`NotificationGateway.handleConnection error: ${e.toString()}`); + } + } + + handleDisconnect(client: Socket) { + try { + const payload = this.tokenService.verify(client.handshake.auth['token']); + client.leave(this.getUserRoomName(payload.userId)); + client.leave(this.getAccountRoomName(payload.accountId)); + } catch (e) { + this.logger.warn(`NotificationGateway.handleDisconnect error: ${e.toString()}`); + } + } + + notifyUser(userId: number, type: string, notification: unknown) { + this.server.to(this.getUserRoomName(userId)).emit(type, notification); + } + + notifyAccount(accountId: number, type: string, notification: unknown) { + this.server.to(this.getAccountRoomName(accountId)).emit(type, notification); + } + + private getUserRoomName(userId: number): string { + return userId.toString(); + } + private getAccountRoomName(accountId: number): string { + return accountId.toString(); + } +} diff --git a/backend/src/modules/frontend-event/frontend-event.module.ts b/backend/src/modules/frontend-event/frontend-event.module.ts new file mode 100644 index 0000000..2b8660c --- /dev/null +++ b/backend/src/modules/frontend-event/frontend-event.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { FrontendEventGateway } from './frontend-event.gateway'; +import { FrontendEventService } from './frontend-event.service'; + +@Module({ + providers: [FrontendEventGateway, FrontendEventService], +}) +export class FrontendEventModule {} diff --git a/backend/src/modules/frontend-event/frontend-event.service.ts b/backend/src/modules/frontend-event/frontend-event.service.ts new file mode 100644 index 0000000..a68aeb6 --- /dev/null +++ b/backend/src/modules/frontend-event/frontend-event.service.ts @@ -0,0 +1,122 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { UserNotification } from '@/common'; +import { + ChatEvent, + ChatMessageCreatedEvent, + ChatMessageUpdatedEvent, + MultichatEventType, +} from '@/modules/multichat/common'; +import { NotificationEventType, NotificationUnseenEvent } from '@/modules/notification/common'; +import { ScheduleEvent, SchedulerEventType } from '@/modules/scheduler/common'; +import { ActivityEvent, CrmEventType, EntityCreatedEvent, EntityEvent, TaskEvent } from '@/CRM/common'; + +import { FrontendEventGateway } from './frontend-event.gateway'; + +@Injectable() +export class FrontendEventService { + constructor(private readonly gateway: FrontendEventGateway) {} + + @OnEvent(NotificationEventType.NOTIFICATION_CREATED, { async: true }) + public async onNotificationCreated(event: { userId?: number }) { + if (event?.userId) { + this.gateway.notifyUser(event.userId, NotificationEventType.NOTIFICATION_CREATED, event); + } + } + + @OnEvent(NotificationEventType.NOTIFICATION_UNSEEN, { async: true }) + public async onNotificationUnseen(event: NotificationUnseenEvent) { + this.gateway.notifyUser(event.userId, NotificationEventType.NOTIFICATION_UNSEEN, event.unseenCount); + } + + @OnEvent(CrmEventType.TaskCreated, { async: true }) + public async onTaskCreated(event: TaskEvent) { + this.gateway.notifyAccount(event.accountId, CrmEventType.TaskCreated, event.taskId); + } + + @OnEvent(CrmEventType.TaskUpdated, { async: true }) + public async onTaskUpdated(event: TaskEvent) { + this.gateway.notifyAccount(event.accountId, CrmEventType.TaskUpdated, event.taskId); + } + + @OnEvent(CrmEventType.TaskDeleted, { async: true }) + public async onTaskDeleted(event: TaskEvent) { + this.gateway.notifyAccount(event.accountId, CrmEventType.TaskDeleted, event.taskId); + } + + @OnEvent(CrmEventType.ActivityCreated, { async: true }) + public async onActivityCreated(event: ActivityEvent) { + this.gateway.notifyAccount(event.accountId, CrmEventType.ActivityCreated, event.activityId); + } + + @OnEvent(CrmEventType.ActivityUpdated, { async: true }) + public async onActivityUpdated(event: ActivityEvent) { + this.gateway.notifyAccount(event.accountId, CrmEventType.ActivityUpdated, event.activityId); + } + + @OnEvent(CrmEventType.ActivityDeleted, { async: true }) + public async onActivityDeleted(event: ActivityEvent) { + this.gateway.notifyAccount(event.accountId, CrmEventType.ActivityDeleted, event.activityId); + } + + @OnEvent(CrmEventType.EntityCreated, { async: true }) + public async onEntityCreated(event: EntityCreatedEvent) { + if (event.userNotification !== UserNotification.Suppressed) { + this.gateway.notifyAccount(event.accountId, CrmEventType.EntityCreated, event); + } + } + + @OnEvent(CrmEventType.EntityUpdated, { async: true }) + public async onEntityUpdated(event: EntityEvent) { + if (event.userNotification !== UserNotification.Suppressed) { + this.gateway.notifyAccount(event.accountId, CrmEventType.EntityUpdated, event); + } + } + + @OnEvent(CrmEventType.EntityDeleted, { async: true }) + public async onEntityDeleted(event: EntityEvent) { + if (event.userNotification !== UserNotification.Suppressed) { + this.gateway.notifyAccount(event.accountId, CrmEventType.EntityDeleted, event); + } + } + + @OnEvent(MultichatEventType.ChatCreated, { async: true }) + public async onChatCreated(event: ChatEvent) { + this.gateway.notifyUser(event.userId, MultichatEventType.ChatCreated, event); + } + @OnEvent(MultichatEventType.ChatUpdated, { async: true }) + public async onChatUpdated(event: ChatEvent) { + this.gateway.notifyUser(event.userId, MultichatEventType.ChatUpdated, event); + } + @OnEvent(MultichatEventType.ChatDeleted, { async: true }) + public async onChatDeleted(event: ChatEvent) { + this.gateway.notifyUser(event.userId, MultichatEventType.ChatDeleted, event); + } + + @OnEvent(MultichatEventType.ChatMessageCreated, { async: true }) + public async onChatMessageCreated(event: ChatMessageCreatedEvent) { + this.gateway.notifyUser(event.userId, MultichatEventType.ChatMessageCreated, event); + } + @OnEvent(MultichatEventType.ChatMessageUpdated, { async: true }) + public async onChatMessageUpdated(event: ChatMessageUpdatedEvent) { + this.gateway.notifyUser(event.userId, MultichatEventType.ChatMessageUpdated, event); + } + @OnEvent(MultichatEventType.ChatMessageDeleted, { async: true }) + public async onChatMessageDeleted(event: ChatMessageUpdatedEvent) { + this.gateway.notifyUser(event.userId, MultichatEventType.ChatMessageDeleted, event); + } + + @OnEvent(SchedulerEventType.ScheduleCreated, { async: true }) + public async onScheduleCreated(event: ScheduleEvent) { + this.gateway.notifyAccount(event.accountId, SchedulerEventType.ScheduleCreated, event); + } + @OnEvent(SchedulerEventType.ScheduleUpdated, { async: true }) + public async onScheduleUpdated(event: ScheduleEvent) { + this.gateway.notifyAccount(event.accountId, SchedulerEventType.ScheduleUpdated, event); + } + @OnEvent(SchedulerEventType.ScheduleDeleted, { async: true }) + public async onScheduleDeleted(event: ScheduleEvent) { + this.gateway.notifyAccount(event.accountId, SchedulerEventType.ScheduleDeleted, event); + } +} diff --git a/backend/src/modules/frontend-object/dto/create-frontend-object.dto.ts b/backend/src/modules/frontend-object/dto/create-frontend-object.dto.ts new file mode 100644 index 0000000..f7d9d0c --- /dev/null +++ b/backend/src/modules/frontend-object/dto/create-frontend-object.dto.ts @@ -0,0 +1,5 @@ +import { PickType } from '@nestjs/swagger'; +import { FrontendObjectDto } from './frontend-object.dto'; + +export class CreateFrontendObjectDto extends PickType(FrontendObjectDto, ['key', 'value'] as const) {} + diff --git a/backend/src/modules/frontend-object/dto/frontend-object-filter.dto.ts b/backend/src/modules/frontend-object/dto/frontend-object-filter.dto.ts new file mode 100644 index 0000000..df501eb --- /dev/null +++ b/backend/src/modules/frontend-object/dto/frontend-object-filter.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class FrontendObjectFilterDto { + @ApiProperty({ description: 'Key of the frontend object' }) + @IsString() + key: string; +} diff --git a/backend/src/modules/frontend-object/dto/frontend-object.dto.ts b/backend/src/modules/frontend-object/dto/frontend-object.dto.ts new file mode 100644 index 0000000..0223a0d --- /dev/null +++ b/backend/src/modules/frontend-object/dto/frontend-object.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmptyObject, IsString } from 'class-validator'; + +export class FrontendObjectDto { + @ApiProperty({ description: 'Key of the frontend object' }) + @IsString() + key: string; + + @ApiProperty({ description: 'Value of the frontend object', type: Object }) + @IsNotEmptyObject() + value: unknown; + + @ApiProperty({ description: 'Date of creation' }) + @IsString() + createdAt: string; +} diff --git a/backend/src/modules/frontend-object/dto/index.ts b/backend/src/modules/frontend-object/dto/index.ts new file mode 100644 index 0000000..f748bfc --- /dev/null +++ b/backend/src/modules/frontend-object/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-frontend-object.dto'; +export * from './frontend-object-filter.dto'; +export * from './frontend-object.dto'; diff --git a/backend/src/modules/frontend-object/entities/frontend-object.entity.ts b/backend/src/modules/frontend-object/entities/frontend-object.entity.ts new file mode 100644 index 0000000..34721dc --- /dev/null +++ b/backend/src/modules/frontend-object/entities/frontend-object.entity.ts @@ -0,0 +1,35 @@ +import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { FrontendObjectDto } from '../dto'; + +@Entity() +export class FrontendObject { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column({ unique: true }) + key: string; + + @Column({ type: 'jsonb' }) + value: unknown; + + @CreateDateColumn() + createdAt: Date; + + constructor(accountId: number, key: string, value: unknown) { + this.accountId = accountId; + this.key = key; + this.value = value; + } + + public toDto(): FrontendObjectDto { + return { + key: this.key, + value: this.value, + createdAt: this.createdAt.toISOString(), + }; + } +} diff --git a/backend/src/modules/frontend-object/entities/index.ts b/backend/src/modules/frontend-object/entities/index.ts new file mode 100644 index 0000000..9af04b1 --- /dev/null +++ b/backend/src/modules/frontend-object/entities/index.ts @@ -0,0 +1 @@ +export * from './frontend-object.entity'; diff --git a/backend/src/modules/frontend-object/frontend-object.controller.ts b/backend/src/modules/frontend-object/frontend-object.controller.ts new file mode 100644 index 0000000..1dc443a --- /dev/null +++ b/backend/src/modules/frontend-object/frontend-object.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Delete, Get, Param, Post } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CreateFrontendObjectDto, FrontendObjectDto, FrontendObjectFilterDto } from './dto'; +import { FrontendObjectService } from './frontend-object.service'; + +@ApiTags('frontend/objects') +@Controller('frontend/objects') +@JwtAuthorized() +@TransformToDto() +export class FrontendObjectController { + constructor(private readonly service: FrontendObjectService) {} + + @ApiOperation({ summary: 'Get frontend object', description: 'Get frontend object by filter' }) + @ApiBody({ type: FrontendObjectFilterDto, required: true, description: 'Filter' }) + @ApiOkResponse({ type: FrontendObjectDto, description: 'Frontend object' }) + @Get() + async findOne(@CurrentAuth() { accountId }: AuthData, @Body() filter: FrontendObjectFilterDto) { + return this.service.findOne({ accountId, ...filter }); + } + + @ApiOperation({ summary: 'Get frontend object', description: 'Get frontend object by key' }) + @ApiParam({ name: 'key', required: true, type: String, description: 'Object key' }) + @ApiOkResponse({ type: FrontendObjectDto, description: 'Frontend object' }) + @Get(':key') + async findOneByKey(@CurrentAuth() { accountId }: AuthData, @Param('key') key: string) { + return this.service.findOne({ accountId, key }); + } + + @ApiOperation({ summary: 'Upsert frontend object', description: 'Upsert frontend object' }) + @ApiBody({ type: CreateFrontendObjectDto, required: true, description: 'Frontend object' }) + @ApiCreatedResponse({ type: FrontendObjectDto, description: 'Frontend object' }) + @Post() + async upsert(@CurrentAuth() { accountId }: AuthData, @Body() obj: CreateFrontendObjectDto) { + return this.service.upsert({ accountId, key: obj.key, value: obj.value }); + } + + @ApiOperation({ summary: 'Upsert frontend object', description: 'Upsert frontend object by key' }) + @ApiParam({ name: 'key', required: true, type: String, description: 'Object key' }) + @ApiBody({ type: Object, required: true, description: 'Object value' }) + @ApiCreatedResponse({ type: FrontendObjectDto, description: 'Frontend object' }) + @Post(':key') + async upsertByKey(@CurrentAuth() { accountId }: AuthData, @Param('key') key: string, @Body() value: unknown) { + return this.service.upsert({ accountId, key, value }); + } + + @ApiOperation({ summary: 'Delete frontend object', description: 'Delete frontend object by filter' }) + @ApiBody({ type: FrontendObjectFilterDto, required: true, description: 'Filter' }) + @ApiOkResponse() + @Delete() + async delete(@CurrentAuth() { accountId }: AuthData, @Body() filter: FrontendObjectFilterDto) { + return this.service.delete({ accountId, ...filter }); + } + + @ApiOperation({ summary: 'Delete frontend object', description: 'Delete frontend object by key' }) + @ApiParam({ name: 'key', required: true, type: String, description: 'Object key' }) + @ApiOkResponse() + @Delete(':key') + async deleteByKey(@CurrentAuth() { accountId }: AuthData, @Param('key') key: string) { + return this.service.delete({ accountId, key }); + } +} diff --git a/backend/src/modules/frontend-object/frontend-object.module.ts b/backend/src/modules/frontend-object/frontend-object.module.ts new file mode 100644 index 0000000..fe1aa35 --- /dev/null +++ b/backend/src/modules/frontend-object/frontend-object.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { FrontendObject } from './entities/frontend-object.entity'; +import { FrontendObjectService } from './frontend-object.service'; +import { FrontendObjectController } from './frontend-object.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([FrontendObject]), IAMModule], + providers: [FrontendObjectService], + controllers: [FrontendObjectController], +}) +export class FrontendObjectModule {} diff --git a/backend/src/modules/frontend-object/frontend-object.service.ts b/backend/src/modules/frontend-object/frontend-object.service.ts new file mode 100644 index 0000000..d1458db --- /dev/null +++ b/backend/src/modules/frontend-object/frontend-object.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { FrontendObject } from './entities'; + +@Injectable() +export class FrontendObjectService { + constructor( + @InjectRepository(FrontendObject) + private readonly repository: Repository, + ) {} + + async findOne({ accountId, key }: { accountId: number; key: string }): Promise { + return this.repository.findOne({ where: { accountId, key } }); + } + + async upsert({ accountId, key, value }: { accountId: number; key: string; value: unknown }): Promise { + const obj = (await this.findOne({ accountId, key })) ?? new FrontendObject(accountId, key, value); + obj.value = value; + obj.createdAt = new Date(); + + await this.repository.save(obj); + return obj; + } + + async delete({ accountId, key }: { accountId: number; key: string }): Promise { + await this.repository.delete({ accountId, key }); + } +} diff --git a/backend/src/modules/iam/account-api-access/account-api-access.controller.ts b/backend/src/modules/iam/account-api-access/account-api-access.controller.ts new file mode 100644 index 0000000..dd5f06c --- /dev/null +++ b/backend/src/modules/iam/account-api-access/account-api-access.controller.ts @@ -0,0 +1,47 @@ +import { Controller, Delete, Get, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { JwtAuthorized } from '../common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '../common/decorators/current-auth.decorator'; +import { AuthData } from '../common/types/auth-data'; + +import { AccountApiAccessDto } from './dto'; +import { AccountApiAccessService } from './account-api-access.service'; + +@ApiTags('IAM/account') +@Controller('account/api-access') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class AccountApiAccessController { + constructor(private readonly service: AccountApiAccessService) {} + + @ApiOperation({ summary: 'Create account API access', description: 'Create account API access' }) + @ApiCreatedResponse({ type: AccountApiAccessDto, description: 'Created account API access' }) + @Post() + public async create(@CurrentAuth() { accountId }: AuthData) { + return this.service.create(accountId); + } + + @ApiOperation({ summary: 'Get account API access', description: 'Get account API access' }) + @ApiOkResponse({ type: AccountApiAccessDto, description: 'Account API access' }) + @Get() + public async findOne(@CurrentAuth() { accountId }: AuthData) { + return await this.service.findOne({ accountId }); + } + + @ApiOperation({ summary: 'Recreate account API access', description: 'Recreate account API access' }) + @ApiOkResponse({ type: AccountApiAccessDto, description: 'Recreated account API access' }) + @Put() + public async recreate(@CurrentAuth() { accountId }: AuthData) { + return this.service.recreate(accountId); + } + + @ApiOperation({ summary: 'Delete account API access', description: 'Delete account API access' }) + @ApiOkResponse() + @Delete() + public async delete(@CurrentAuth() { accountId }: AuthData) { + return this.service.delete(accountId); + } +} diff --git a/backend/src/modules/iam/account-api-access/account-api-access.service.ts b/backend/src/modules/iam/account-api-access/account-api-access.service.ts new file mode 100644 index 0000000..ec4ea06 --- /dev/null +++ b/backend/src/modules/iam/account-api-access/account-api-access.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { PasswordUtil } from '@/common'; + +import { AccountApiAccess } from './entities'; + +interface FindFilter { + accountId?: number; + apiKey?: string; +} + +const cacheKey = (key: string) => `AccountApiAccess:${key}`; + +@Injectable() +export class AccountApiAccessService { + constructor( + @InjectRepository(AccountApiAccess) + private readonly repository: Repository, + private readonly dataSource: DataSource, + ) {} + + public async create(accountId: number): Promise { + return this.repository.save(new AccountApiAccess(accountId, PasswordUtil.generateSecure({ length: 22 }))); + } + + public async recreate(accountId: number): Promise { + const access = await this.findOne({ accountId }); + if (access) { + this.dataSource.queryResultCache?.remove([cacheKey(access.apiKey)]); + access.apiKey = PasswordUtil.generateSecure({ length: 22 }); + return this.repository.save(access); + } + + return this.create(accountId); + } + + public async findOne(filter: FindFilter): Promise { + return this.repository.findOne({ + where: { apiKey: filter.apiKey, accountId: filter.accountId }, + cache: filter.apiKey ? { id: cacheKey(filter.apiKey), milliseconds: 86400000 } : undefined, + }); + } + + public async delete(accountId: number): Promise { + const access = await this.findOne({ accountId }); + if (access) { + this.dataSource.queryResultCache?.remove([cacheKey(access.apiKey)]); + await this.repository.delete({ accountId }); + } + } +} diff --git a/backend/src/modules/iam/account-api-access/dto/account-api-access.dto.ts b/backend/src/modules/iam/account-api-access/dto/account-api-access.dto.ts new file mode 100644 index 0000000..46f5933 --- /dev/null +++ b/backend/src/modules/iam/account-api-access/dto/account-api-access.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class AccountApiAccessDto { + @ApiProperty({ description: 'API key' }) + @IsString() + apiKey: string; + + @ApiProperty({ description: 'Created at' }) + @IsString() + createdAt: string; +} diff --git a/backend/src/modules/iam/account-api-access/dto/index.ts b/backend/src/modules/iam/account-api-access/dto/index.ts new file mode 100644 index 0000000..bcd7394 --- /dev/null +++ b/backend/src/modules/iam/account-api-access/dto/index.ts @@ -0,0 +1 @@ +export * from './account-api-access.dto'; diff --git a/backend/src/modules/iam/account-api-access/entities/account-api-access.entity.ts b/backend/src/modules/iam/account-api-access/entities/account-api-access.entity.ts new file mode 100644 index 0000000..54af29c --- /dev/null +++ b/backend/src/modules/iam/account-api-access/entities/account-api-access.entity.ts @@ -0,0 +1,29 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { AccountApiAccessDto } from '../dto'; + +@Entity() +export class AccountApiAccess { + @PrimaryColumn() + accountId: number; + + @Column() + apiKey: string; + + @Column() + createdAt: Date; + + constructor(accountId: number, apiKey: string, createdAt?: Date | null) { + this.accountId = accountId; + this.apiKey = apiKey; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public toDto(): AccountApiAccessDto { + return { + apiKey: this.apiKey, + createdAt: this.createdAt.toISOString(), + }; + } +} diff --git a/backend/src/modules/iam/account-api-access/entities/index.ts b/backend/src/modules/iam/account-api-access/entities/index.ts new file mode 100644 index 0000000..cf4c906 --- /dev/null +++ b/backend/src/modules/iam/account-api-access/entities/index.ts @@ -0,0 +1 @@ +export * from './account-api-access.entity'; diff --git a/backend/src/modules/iam/account-api-access/guards/api-access.guard.ts b/backend/src/modules/iam/account-api-access/guards/api-access.guard.ts new file mode 100644 index 0000000..c93dbae --- /dev/null +++ b/backend/src/modules/iam/account-api-access/guards/api-access.guard.ts @@ -0,0 +1,42 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Request } from 'express'; + +import { ForbiddenError } from '@/common'; +import { ApplicationConfig } from '@/config'; + +import { AccountApiAccessService } from '../account-api-access.service'; + +@Injectable() +export class ApiAccessGuard implements CanActivate { + private _apiKeyRequired: boolean; + + constructor( + private readonly configService: ConfigService, + private readonly apiAccessService: AccountApiAccessService, + ) { + this._apiKeyRequired = this.configService.get('application').apiKeyRequired; + } + + async canActivate(context: ExecutionContext): Promise { + if (!this._apiKeyRequired) { + return true; + } + + const request = context.switchToHttp().getRequest(); + const apiKey = request.headers['x-api-key']; + if (!apiKey) { + throw new ForbiddenError('API key required'); + } + + const currentApiKey = Array.isArray(apiKey) ? apiKey[0] : apiKey; + const apiAccess = await this.apiAccessService.findOne({ apiKey: currentApiKey }); + if (apiAccess) { + request.callerAccountId = apiAccess?.accountId; + + return true; + } + + throw new ForbiddenError('Unknown API key'); + } +} diff --git a/backend/src/modules/iam/account-api-access/guards/index.ts b/backend/src/modules/iam/account-api-access/guards/index.ts new file mode 100644 index 0000000..3204b38 --- /dev/null +++ b/backend/src/modules/iam/account-api-access/guards/index.ts @@ -0,0 +1 @@ +export * from './api-access.guard'; diff --git a/backend/src/modules/iam/account-api-access/index.ts b/backend/src/modules/iam/account-api-access/index.ts new file mode 100644 index 0000000..ec320fd --- /dev/null +++ b/backend/src/modules/iam/account-api-access/index.ts @@ -0,0 +1,3 @@ +export * from './account-api-access.service'; +export * from './entities'; +export * from './guards'; diff --git a/backend/src/modules/iam/account-settings/account-settings.controller.ts b/backend/src/modules/iam/account-settings/account-settings.controller.ts new file mode 100644 index 0000000..86687f7 --- /dev/null +++ b/backend/src/modules/iam/account-settings/account-settings.controller.ts @@ -0,0 +1,29 @@ +import { Body, Controller, Get, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { JwtAuthorized, CurrentAuth, AuthData, UserAccess } from '../common'; +import { AccountSettingsDto, UpdateAccountSettingsDto } from './dto'; +import { AccountSettingsService } from './account-settings.service'; + +@ApiTags('IAM/account') +@Controller('account/settings') +@JwtAuthorized() +@TransformToDto() +export class AccountSettingsController { + constructor(private readonly service: AccountSettingsService) {} + + @ApiCreatedResponse({ type: AccountSettingsDto }) + @Get() + async get(@CurrentAuth() { accountId }: AuthData) { + return await this.service.getOne(accountId); + } + + @ApiCreatedResponse({ type: AccountSettingsDto }) + @Put() + @UserAccess({ adminOnly: true }) + async update(@CurrentAuth() { accountId }: AuthData, @Body() dto: UpdateAccountSettingsDto) { + return this.service.update(accountId, dto); + } +} diff --git a/backend/src/modules/iam/account-settings/account-settings.service.ts b/backend/src/modules/iam/account-settings/account-settings.service.ts new file mode 100644 index 0000000..1c53993 --- /dev/null +++ b/backend/src/modules/iam/account-settings/account-settings.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { CreateAccountSettingsDto, UpdateAccountSettingsDto } from './dto'; +import { AccountSettings } from './entities'; + +const cacheKey = (accountId: number) => `AccountSettings:${accountId}`; + +@Injectable() +export class AccountSettingsService { + constructor( + @InjectRepository(AccountSettings) + private readonly repository: Repository, + private readonly dataSource: DataSource, + private readonly configService: ConfigService, + ) {} + + public async create(accountId: number, dto?: CreateAccountSettingsDto): Promise { + this.dataSource.queryResultCache?.remove([cacheKey(accountId)]); + return this.repository.save(AccountSettings.fromDto(accountId, dto)); + } + + public async getOne(accountId: number): Promise { + const settings = await this.repository.findOne({ + where: { accountId }, + cache: { id: cacheKey(accountId), milliseconds: 86400000 }, + }); + + let accountSettings = settings ?? await this.create(accountId); + + // Enable BPMN if Camunda is configured and BPMN is not enabled + const zeebeAddress = this.configService.get('ZEEBE_GRPC_ADDRESS'); + if (zeebeAddress && !accountSettings.isBpmnEnable) { + accountSettings.isBpmnEnable = true; + this.dataSource.queryResultCache?.remove([cacheKey(accountId)]); + accountSettings = await this.repository.save(accountSettings); + } + + return accountSettings; + } + + public async update(accountId: number, dto: UpdateAccountSettingsDto): Promise { + const current = await this.repository.findOneBy({ accountId }); + if (current) { + this.dataSource.queryResultCache?.remove([cacheKey(accountId)]); + await this.repository.save(current.update(dto)); + + return current; + } else { + return this.create(accountId, dto); + } + } +} diff --git a/backend/src/modules/iam/account-settings/dto/account-settings.dto.ts b/backend/src/modules/iam/account-settings/dto/account-settings.dto.ts new file mode 100644 index 0000000..5da6c2b --- /dev/null +++ b/backend/src/modules/iam/account-settings/dto/account-settings.dto.ts @@ -0,0 +1,66 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator'; + +import { DateFormat } from '@/common'; +import { PhoneFormat } from '../../common'; + +export class AccountSettingsDto { + @ApiProperty({ description: 'Main language', examples: ['en', 'fr', 'pl'] }) + @IsString() + language: string; + + @ApiProperty({ + nullable: true, + description: 'Main working days', + examples: ['Monday,Tuesday,Wednesday,Thursday,Friday', 'Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday'], + }) + @IsOptional() + @IsArray() + workingDays: string[] | null; + + @ApiPropertyOptional({ nullable: true, description: 'Main start of week', examples: ['Monday', 'Sunday'] }) + @IsOptional() + @IsString() + startOfWeek: string | null; + + @ApiProperty({ nullable: true, description: 'Main working time from', examples: ['09:00', '21:00'] }) + @IsOptional() + @IsString() + workingTimeFrom: string | null; + + @ApiProperty({ nullable: true, description: 'Main working time to', examples: ['11:00', '23:00'] }) + @IsOptional() + @IsString() + workingTimeTo: string | null; + + @ApiProperty({ nullable: true, description: 'Main time zone', examples: ['Europe/London', 'America/New_York'] }) + @IsOptional() + @IsString() + timeZone: string | null; + + @ApiProperty({ description: 'Main currency', examples: ['USD', 'EUR', 'PLN'] }) + @IsString() + currency: string; + + @ApiProperty({ nullable: true, description: 'Main number format', examples: ['9.999.999,99'] }) + @IsOptional() + @IsString() + numberFormat: string | null; + + @ApiProperty({ enum: PhoneFormat, description: 'Main phone format' }) + @IsEnum(PhoneFormat) + phoneFormat: PhoneFormat; + + @ApiProperty({ description: 'Allow contact duplicates' }) + @IsBoolean() + allowDuplicates: boolean; + + @ApiPropertyOptional({ nullable: true, enum: DateFormat, description: 'Main date format' }) + @IsOptional() + @IsEnum(DateFormat) + dateFormat?: DateFormat | null; + + @ApiProperty({ description: 'BPMN enabled' }) + @IsBoolean() + isBpmnEnable: boolean; +} diff --git a/backend/src/modules/iam/account-settings/dto/create-account-settings.dto.ts b/backend/src/modules/iam/account-settings/dto/create-account-settings.dto.ts new file mode 100644 index 0000000..d3ffcf6 --- /dev/null +++ b/backend/src/modules/iam/account-settings/dto/create-account-settings.dto.ts @@ -0,0 +1,4 @@ +import { OmitType, PartialType } from '@nestjs/swagger'; +import { AccountSettingsDto } from './account-settings.dto'; + +export class CreateAccountSettingsDto extends PartialType(OmitType(AccountSettingsDto, ['isBpmnEnable'] as const)) {} diff --git a/backend/src/modules/iam/account-settings/dto/index.ts b/backend/src/modules/iam/account-settings/dto/index.ts new file mode 100644 index 0000000..7039d3b --- /dev/null +++ b/backend/src/modules/iam/account-settings/dto/index.ts @@ -0,0 +1,3 @@ +export * from './account-settings.dto'; +export * from './create-account-settings.dto'; +export * from './update-account-settings.dto'; diff --git a/backend/src/modules/iam/account-settings/dto/update-account-settings.dto.ts b/backend/src/modules/iam/account-settings/dto/update-account-settings.dto.ts new file mode 100644 index 0000000..9c45fdc --- /dev/null +++ b/backend/src/modules/iam/account-settings/dto/update-account-settings.dto.ts @@ -0,0 +1,4 @@ +import { OmitType, PartialType } from '@nestjs/swagger'; +import { AccountSettingsDto } from './account-settings.dto'; + +export class UpdateAccountSettingsDto extends PartialType(OmitType(AccountSettingsDto, ['isBpmnEnable'] as const)) {} diff --git a/backend/src/modules/iam/account-settings/entities/account-settings.entity.ts b/backend/src/modules/iam/account-settings/entities/account-settings.entity.ts new file mode 100644 index 0000000..222451b --- /dev/null +++ b/backend/src/modules/iam/account-settings/entities/account-settings.entity.ts @@ -0,0 +1,141 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateFormat } from '@/common'; + +import { PhoneFormat } from '../../common'; +import { CreateAccountSettingsDto, UpdateAccountSettingsDto, AccountSettingsDto } from '../dto'; + +const SettingsDefault = { + language: 'ru', + workingDays: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], + startOfWeek: 'Monday', + workingTimeFrom: '9:00:00', + workingTimeTo: '18:00:00', + timeZone: 'Europe/Moscow', + currency: 'USD', + numberFormat: '9.999.999,99', + phoneFormat: PhoneFormat.INTERNATIONAL, + allowDuplicates: false, +}; + +@Entity() +export class AccountSettings { + @PrimaryColumn() + accountId: number; + + @Column() + language: string; + + @Column({ type: 'simple-array', nullable: true }) + workingDays: string[] | null; + + @Column({ nullable: true }) + startOfWeek: string | null; + + @Column({ type: 'time', nullable: true }) + workingTimeFrom: string | null; + + @Column({ type: 'time', nullable: true }) + workingTimeTo: string | null; + + @Column({ nullable: true }) + timeZone: string | null; + + @Column() + currency: string; + + @Column({ nullable: true }) + numberFormat: string | null; + + @Column() + phoneFormat: PhoneFormat; + + @Column() + allowDuplicates: boolean; + + @Column({ nullable: true }) + dateFormat: DateFormat | null; + + @Column({ default: false }) + isBpmnEnable: boolean; + + constructor( + accountId: number, + language: string, + workingDays: string[] | null, + startOfWeek: string | null, + workingTimeFrom: string | null, + workingTimeTo: string | null, + timeZone: string | null, + currency: string, + numberFormat: string | null, + phoneFormat: PhoneFormat, + allowDuplicates: boolean, + dateFormat: DateFormat | null, + isBpmnEnable = false, + ) { + this.accountId = accountId; + this.language = language; + this.workingDays = workingDays; + this.startOfWeek = startOfWeek; + this.workingTimeFrom = workingTimeFrom; + this.workingTimeTo = workingTimeTo; + this.timeZone = timeZone; + this.currency = currency; + this.numberFormat = numberFormat; + this.phoneFormat = phoneFormat; + this.allowDuplicates = allowDuplicates; + this.dateFormat = dateFormat; + this.isBpmnEnable = isBpmnEnable; + } + + public static fromDto(accountId: number, dto?: CreateAccountSettingsDto | UpdateAccountSettingsDto): AccountSettings { + return new AccountSettings( + accountId, + dto?.language ?? SettingsDefault.language, + dto?.workingDays ?? SettingsDefault.workingDays, + dto?.startOfWeek ?? SettingsDefault.startOfWeek, + dto?.workingTimeFrom ?? SettingsDefault.workingTimeFrom, + dto?.workingTimeTo ?? SettingsDefault.workingTimeTo, + dto?.timeZone ?? SettingsDefault.timeZone, + dto?.currency ?? SettingsDefault.currency, + dto?.numberFormat ?? SettingsDefault.numberFormat, + dto?.phoneFormat ?? SettingsDefault.phoneFormat, + dto?.allowDuplicates ?? SettingsDefault.allowDuplicates, + dto?.dateFormat ?? null, + ); + } + + public update(dto: UpdateAccountSettingsDto): AccountSettings { + this.language = dto.language !== undefined ? dto.language : this.language; + this.workingDays = dto.workingDays !== undefined ? dto.workingDays : this.workingDays; + this.startOfWeek = dto.startOfWeek !== undefined ? dto.startOfWeek : this.startOfWeek; + this.workingTimeFrom = dto.workingTimeFrom !== undefined ? dto.workingTimeFrom : this.workingTimeFrom; + this.workingTimeTo = dto.workingTimeTo !== undefined ? dto.workingTimeTo : this.workingTimeTo; + this.timeZone = dto.timeZone !== undefined ? dto.timeZone : this.timeZone; + this.currency = dto.currency !== undefined ? dto.currency : this.currency; + this.numberFormat = dto.numberFormat !== undefined ? dto.numberFormat : this.numberFormat; + this.phoneFormat = dto.phoneFormat !== undefined ? dto.phoneFormat : this.phoneFormat; + this.allowDuplicates = dto.allowDuplicates !== undefined ? dto.allowDuplicates : this.allowDuplicates; + this.dateFormat = dto.dateFormat !== undefined ? dto.dateFormat : this.dateFormat; + + return this; + } + + public toDto(): AccountSettingsDto { + return { + language: this.language, + workingDays: this.workingDays, + startOfWeek: this.startOfWeek, + workingTimeFrom: this.workingTimeFrom.substring(0, 5), + workingTimeTo: this.workingTimeTo.substring(0, 5), + timeZone: this.timeZone, + currency: this.currency, + numberFormat: this.numberFormat, + phoneFormat: this.phoneFormat, + allowDuplicates: this.allowDuplicates, + dateFormat: this.dateFormat, + isBpmnEnable: this.isBpmnEnable, + }; + } +} diff --git a/backend/src/modules/iam/account-settings/entities/index.ts b/backend/src/modules/iam/account-settings/entities/index.ts new file mode 100644 index 0000000..80549e6 --- /dev/null +++ b/backend/src/modules/iam/account-settings/entities/index.ts @@ -0,0 +1 @@ +export * from './account-settings.entity'; diff --git a/backend/src/modules/iam/account-settings/index.ts b/backend/src/modules/iam/account-settings/index.ts new file mode 100644 index 0000000..24589d0 --- /dev/null +++ b/backend/src/modules/iam/account-settings/index.ts @@ -0,0 +1,4 @@ +export * from './account-settings.controller'; +export * from './account-settings.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/modules/iam/account-subscription/account-subscription.controller.ts b/backend/src/modules/iam/account-subscription/account-subscription.controller.ts new file mode 100644 index 0000000..a67ec9a --- /dev/null +++ b/backend/src/modules/iam/account-subscription/account-subscription.controller.ts @@ -0,0 +1,41 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Patch } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '../common'; + +import { AccountSubscriptionDto, UpdateAccountSubscriptionDto } from './dto'; +import { AccountSubscriptionService } from './account-subscription.service'; + +@ApiTags('IAM/subscriptions') +@Controller('subscriptions') +@JwtAuthorized() +@TransformToDto() +export class AccountSubscriptionController { + constructor(private service: AccountSubscriptionService) {} + + @ApiOkResponse({ description: 'Get subscription for current account', type: AccountSubscriptionDto }) + @Get() + async get(@CurrentAuth() { accountId }: AuthData) { + return this.service.get(accountId); + } + + @ApiOkResponse({ description: 'Get subscription for account', type: AccountSubscriptionDto }) + @AuthDataPrefetch({ user: true }) + @Get(':accountId') + async getFor(@CurrentAuth() { user }: AuthData, @Param('accountId', ParseIntPipe) accountId: number) { + return this.service.getSystem(accountId, user); + } + + @ApiOkResponse({ description: 'Update subscription for account', type: AccountSubscriptionDto }) + @AuthDataPrefetch({ user: true }) + @Patch(':accountId') + async update( + @CurrentAuth() { user }: AuthData, + @Param('accountId', ParseIntPipe) accountId: number, + @Body() dto: UpdateAccountSubscriptionDto, + ) { + return this.service.updateSystem(accountId, user, dto); + } +} diff --git a/backend/src/modules/iam/account-subscription/account-subscription.service.ts b/backend/src/modules/iam/account-subscription/account-subscription.service.ts new file mode 100644 index 0000000..1111a5e --- /dev/null +++ b/backend/src/modules/iam/account-subscription/account-subscription.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { DateUtil, ForbiddenError, NotFoundError } from '@/common'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { CreateAccountSubscriptionDto, UpdateAccountSubscriptionDto } from './dto'; +import { AccountSubscription } from './entities'; + +type SubscriptionIdentifier = + | { accountId: number; externalCustomerId?: string } + | { accountId?: number; externalCustomerId: string }; + +@Injectable() +export class AccountSubscriptionService { + constructor( + @InjectRepository(AccountSubscription) + private readonly repository: Repository, + private readonly userService: UserService, + ) {} + + async create(accountId: number, dto?: CreateAccountSubscriptionDto): Promise { + return this.repository.save(AccountSubscription.create(accountId, dto)); + } + + async get(accountId: number): Promise { + const subscription = await this.repository.findOne({ where: { accountId } }); + if (!subscription) { + throw NotFoundError.withId(AccountSubscription, accountId); + } + return subscription; + } + async getSystem(accountId: number, user: User): Promise { + if (!user.isPlatformAdmin) { + throw new ForbiddenError(); + } + const subscription = await this.repository.findOne({ where: { accountId } }); + if (!subscription) { + throw NotFoundError.withId(AccountSubscription, accountId); + } + return subscription; + } + + async update( + { accountId, externalCustomerId }: SubscriptionIdentifier, + user: User | null, + dto: UpdateAccountSubscriptionDto, + ): Promise { + const subscription = accountId + ? await this.get(accountId) + : externalCustomerId + ? await this.repository.findOneBy({ externalCustomerId }) + : null; + + if (subscription) { + await this.repository.save(subscription.update(dto)); + await this.userService.ensureUserLimit({ + accountId: subscription.accountId, + user, + userLimit: subscription.userLimit, + }); + } + + return subscription; + } + + async updateSystem(accountId: number, user: User, dto: UpdateAccountSubscriptionDto): Promise { + const subscription = await this.getSystem(accountId, user); + + if (subscription) { + await this.repository.save(subscription.update(dto)); + await this.userService.ensureUserLimit({ + accountId: subscription.accountId, + user: null, + userLimit: subscription.userLimit, + }); + } + + return subscription; + } + + async cancel(identifier: SubscriptionIdentifier): Promise { + return this.update(identifier, null, { periodEnd: DateUtil.now().toISOString() }); + } +} diff --git a/backend/src/modules/iam/account-subscription/dto/account-subscription.dto.ts b/backend/src/modules/iam/account-subscription/dto/account-subscription.dto.ts new file mode 100644 index 0000000..f632d7e --- /dev/null +++ b/backend/src/modules/iam/account-subscription/dto/account-subscription.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class AccountSubscriptionDto { + @ApiProperty({ description: 'Is trial?' }) + @IsBoolean() + isTrial: boolean; + + @ApiProperty({ description: 'Created at' }) + @IsString() + createdAt: string; + + @ApiPropertyOptional({ nullable: true, description: 'Expired at' }) + @IsOptional() + @IsString() + expiredAt: string | null; + + @ApiProperty({ description: 'User limit' }) + @IsNumber() + userLimit: number; + + @ApiProperty({ description: 'Is valid?' }) + @IsBoolean() + isValid: boolean; + + @ApiProperty({ description: 'Plan name' }) + @IsString() + planName: string; + + @ApiProperty({ description: 'Is external?' }) + @IsBoolean() + isExternal: boolean; + + @ApiPropertyOptional({ description: 'First visit date' }) + @IsString() + firstVisit: string; +} diff --git a/backend/src/modules/iam/account-subscription/dto/create-account-subscription.dto.ts b/backend/src/modules/iam/account-subscription/dto/create-account-subscription.dto.ts new file mode 100644 index 0000000..fa10cff --- /dev/null +++ b/backend/src/modules/iam/account-subscription/dto/create-account-subscription.dto.ts @@ -0,0 +1,8 @@ +export class CreateAccountSubscriptionDto { + createdAt?: string; + termInDays?: number; + userLimit?: number; + planName?: string; + isTrial?: boolean; + firstVisit?: string; +} diff --git a/backend/src/modules/iam/account-subscription/dto/index.ts b/backend/src/modules/iam/account-subscription/dto/index.ts new file mode 100644 index 0000000..47bcc2f --- /dev/null +++ b/backend/src/modules/iam/account-subscription/dto/index.ts @@ -0,0 +1,3 @@ +export * from './account-subscription.dto'; +export * from './create-account-subscription.dto'; +export * from './update-account-subscription.dto'; diff --git a/backend/src/modules/iam/account-subscription/dto/update-account-subscription.dto.ts b/backend/src/modules/iam/account-subscription/dto/update-account-subscription.dto.ts new file mode 100644 index 0000000..8c63a4d --- /dev/null +++ b/backend/src/modules/iam/account-subscription/dto/update-account-subscription.dto.ts @@ -0,0 +1,9 @@ +export class UpdateAccountSubscriptionDto { + isTrial?: boolean; + periodStart?: string; + periodEnd?: string; + userLimit?: number; + planName?: string; + externalCustomerId?: string | null; + firstVisit?: string; +} diff --git a/backend/src/modules/iam/account-subscription/entities/account-subscription.entity.ts b/backend/src/modules/iam/account-subscription/entities/account-subscription.entity.ts new file mode 100644 index 0000000..25685f1 --- /dev/null +++ b/backend/src/modules/iam/account-subscription/entities/account-subscription.entity.ts @@ -0,0 +1,99 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { AccountSubscriptionDto, CreateAccountSubscriptionDto, UpdateAccountSubscriptionDto } from '../dto'; + +const DefaultSubscription = { + isTrial: true, + duration: 7, + userLimit: 50, + planName: 'All in One', +}; + +@Entity() +export class AccountSubscription { + @PrimaryColumn() + accountId: number; + + @Column() + createdAt: Date; + + @Column() + isTrial: boolean; + + @Column() + expiredAt: Date | null; + + @Column() + userLimit: number; + + @Column() + planName: string; + + @Column({ nullable: true }) + externalCustomerId: string | null; + + @Column() + firstVisit: Date; + + constructor( + accountId: number, + isTrial: boolean, + createdAt: Date, + expiredAt: Date | null, + userLimit: number, + planName: string, + firstVisit: Date, + ) { + this.accountId = accountId; + this.isTrial = isTrial; + this.createdAt = createdAt; + this.expiredAt = expiredAt; + this.userLimit = userLimit; + this.planName = planName; + this.firstVisit = firstVisit ?? createdAt; + } + + static create(accountId: number, dto?: CreateAccountSubscriptionDto): AccountSubscription { + const now = dto?.createdAt ? DateUtil.fromISOString(dto?.createdAt) : DateUtil.now(); + return new AccountSubscription( + accountId, + dto?.isTrial ?? DefaultSubscription.isTrial, + now, + DateUtil.add(now, { days: dto?.termInDays ?? DefaultSubscription.duration }), + dto?.userLimit ?? DefaultSubscription.userLimit, + dto?.planName ?? DefaultSubscription.planName, + dto?.firstVisit ? DateUtil.fromISOString(dto.firstVisit) : null, + ); + } + + update(dto: UpdateAccountSubscriptionDto): AccountSubscription { + this.isTrial = dto.isTrial !== undefined ? dto.isTrial : this.isTrial; + this.createdAt = dto.periodStart !== undefined ? DateUtil.fromISOString(dto.periodStart) : this.createdAt; + this.expiredAt = dto.periodEnd !== undefined ? DateUtil.fromISOString(dto.periodEnd) : this.expiredAt; + this.userLimit = dto.userLimit !== undefined ? dto.userLimit : this.userLimit; + this.planName = dto.planName !== undefined ? dto.planName : this.planName; + this.externalCustomerId = dto.externalCustomerId !== undefined ? dto.externalCustomerId : this.externalCustomerId; + this.firstVisit = dto.firstVisit !== undefined ? DateUtil.fromISOString(dto.firstVisit) : this.firstVisit; + + return this; + } + + toDto(): AccountSubscriptionDto { + return { + isValid: this.isValid(), + isTrial: this.isTrial, + createdAt: this.createdAt.toISOString(), + expiredAt: this.expiredAt?.toISOString(), + userLimit: this.userLimit, + planName: this.planName, + isExternal: !!this.externalCustomerId, + firstVisit: this.firstVisit.toISOString(), + }; + } + + private isValid(): boolean { + return DateUtil.isFuture(this.expiredAt); + } +} diff --git a/backend/src/modules/iam/account-subscription/entities/index.ts b/backend/src/modules/iam/account-subscription/entities/index.ts new file mode 100644 index 0000000..8b43649 --- /dev/null +++ b/backend/src/modules/iam/account-subscription/entities/index.ts @@ -0,0 +1 @@ +export * from './account-subscription.entity'; diff --git a/backend/src/modules/iam/account-subscription/index.ts b/backend/src/modules/iam/account-subscription/index.ts new file mode 100644 index 0000000..be8dfd5 --- /dev/null +++ b/backend/src/modules/iam/account-subscription/index.ts @@ -0,0 +1,4 @@ +export * from './account-subscription.controller'; +export * from './account-subscription.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/modules/iam/account/account.controller.ts b/backend/src/modules/iam/account/account.controller.ts new file mode 100644 index 0000000..7b3ef19 --- /dev/null +++ b/backend/src/modules/iam/account/account.controller.ts @@ -0,0 +1,76 @@ +import { + Controller, + Delete, + FileTypeValidator, + Get, + MaxFileSizeValidator, + ParseFilePipe, + Post, + Query, + UploadedFile, + UseInterceptors, +} from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { memoryStorage } from 'multer'; + +import { PagingQuery, TransformToDto } from '@/common'; + +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '../common'; + +import { AccountDto, FindFilterDto } from './dto'; +import { AccountService } from './account.service'; + +const AccountLogoFile = { + MaxSize: 5242880, + Type: 'image/*', +}; + +@ApiTags('IAM/account') +@Controller('account') +@JwtAuthorized({ prefetch: { account: true } }) +@TransformToDto() +export class AccountController { + constructor(private service: AccountService) {} + + @ApiOperation({ summary: 'Get current account', description: 'Get current account' }) + @ApiOkResponse({ description: 'Account', type: AccountDto }) + @Get() + async get(@CurrentAuth() { account }: AuthData) { + return this.service.expandOne(account, ['logoUrl']); + } + + @ApiOperation({ summary: 'Find accounts', description: 'Find accounts' }) + @ApiOkResponse({ description: 'Accounts', type: [AccountDto] }) + @AuthDataPrefetch({ user: true }) + @Get('search') + async search(@CurrentAuth() { user }: AuthData, @Query() filter: FindFilterDto, @Query() paging: PagingQuery) { + return this.service.searchSystem({ user, filter, paging }); + } + + @ApiOperation({ summary: 'Set account logo', description: 'Set account logo' }) + @ApiCreatedResponse({ description: 'Account', type: AccountDto }) + @Post('logo') + @UseInterceptors(FileInterceptor('logo', { storage: memoryStorage() })) + async setLogo( + @CurrentAuth() { account, userId }: AuthData, + @UploadedFile( + new ParseFilePipe({ + validators: [ + new MaxFileSizeValidator({ maxSize: AccountLogoFile.MaxSize }), + new FileTypeValidator({ fileType: AccountLogoFile.Type }), + ], + }), + ) + logo: Express.Multer.File, + ) { + return this.service.setLogo(account, userId, logo); + } + + @ApiOperation({ summary: 'Remove account logo', description: 'Remove account logo' }) + @ApiOkResponse({ description: 'Account', type: AccountDto }) + @Delete('logo') + async removeLogo(@CurrentAuth() { account }: AuthData) { + return this.service.deleteLogo(account); + } +} diff --git a/backend/src/modules/iam/account/account.service.ts b/backend/src/modules/iam/account/account.service.ts new file mode 100644 index 0000000..552b454 --- /dev/null +++ b/backend/src/modules/iam/account/account.service.ts @@ -0,0 +1,237 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; +import slugify from 'slugify'; + +import { ApplicationConfig } from '@/config'; +import { ForbiddenError, PagingQuery } from '@/common'; + +import { StorageUrlService } from '@/modules/storage/storage-url.service'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { IamEventType, AccountCreatedEvent, UserRole } from '../common'; + +import { AccountSettingsService } from '../account-settings/account-settings.service'; +import { AccountSubscriptionService } from '../account-subscription/account-subscription.service'; +import { User } from '../user/entities/user.entity'; +import { EmailOccupiedError } from '../user/errors/email-occupied.error'; +import { UserService } from '../user/user.service'; + +import { CreateAccountDto } from './dto'; +import { Account } from './entities'; +import { ExpandableField } from './types'; + +const MIN_SUBDOMAIN_LENGTH = 3; +const MAX_SUBDOMAIN_LENGTH = 30; + +interface SubscriptionOptions { + termInDays?: number; + userLimit?: number; + planName?: string; + isTrial?: boolean; +} + +interface CreateOptions { + gaClientId?: string | null; + skipPhoneCheck?: boolean; + createdAt?: Date; + subscription?: SubscriptionOptions; + firstVisit?: string | null; +} + +interface FindFilter { + accountId?: number; + subdomain?: string; + search?: string; +} + +const cacheKey = (accountId: number) => `Account:${accountId}`; + +@Injectable() +export class AccountService { + private _subdomainPrefix: string; + + constructor( + private readonly configService: ConfigService, + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Account) + private readonly repository: Repository, + private readonly dataSource: DataSource, + private readonly userService: UserService, + private readonly subscriptionService: AccountSubscriptionService, + @Inject(forwardRef(() => StorageService)) + private readonly storageService: StorageService, + @Inject(forwardRef(() => StorageUrlService)) + private readonly storageUrlService: StorageUrlService, + private readonly accountSettingsService: AccountSettingsService, + ) { + this._subdomainPrefix = this.configService.get('application').subdomainPrefix; + } + + async create(dto: CreateAccountDto, options?: CreateOptions): Promise<{ account: Account; owner: User }> { + if (await this.userService.isEmailOccupied(dto.email)) { + throw EmailOccupiedError.forAccountCreation(); + } + + dto.companyName = dto.companyName ? dto.companyName.trim() : this.createRandomSubdomain(); + const subdomain = await this.generateSubdomain(dto.companyName); + const account = await this.repository.save(new Account(dto.companyName, subdomain, null, options?.createdAt)); + + const owner = await this.userService.create({ + account, + dto: { + firstName: dto.firstName, + lastName: dto.lastName, + email: dto.email, + password: dto.password, + role: UserRole.OWNER, + phone: dto.phone, + analyticsId: dto.userAnalyticsId, + }, + options, + }); + + const subscription = await this.subscriptionService.create(account.id, { + createdAt: options?.createdAt?.toISOString(), + ...(options?.subscription || []), + firstVisit: options?.firstVisit, + }); + await this.accountSettingsService.create(account.id, dto.settings); + + this.eventEmitter.emit( + IamEventType.AccountCreated, + new AccountCreatedEvent({ + accountId: account.id, + name: `${owner.firstName} ${owner.lastName}`, + email: owner.email, + phone: owner.phone, + companyName: account.companyName, + subdomain: account.subdomain, + ownerId: owner.id, + createdAt: account.createdAt.toISOString(), + subscriptionName: subscription.isTrial ? 'Trial' : subscription.planName, + gaClientId: options?.gaClientId, + gaUserId: owner.analyticsId, + }), + ); + + return { account, owner }; + } + + async findOne(filter: FindFilter): Promise { + const qb = this.createFindQb(filter); + if (filter.accountId) { + qb.cache(cacheKey(filter.accountId), 86400000); + } + + return qb.getOne(); + } + + async searchSystem({ + user, + filter, + paging, + }: { + user: User; + filter: FindFilter; + paging: PagingQuery; + }): Promise { + if (!user.isPlatformAdmin) { + throw new ForbiddenError(); + } + return this.createFindQb(filter).offset(paging.skip).limit(paging.take).getMany(); + } + + async expandOne(account: Account, expand: ExpandableField[]): Promise { + if (expand.includes('logoUrl') && account.logoId) { + account.logoUrl = this.storageUrlService.getImageUrl(account.id, account.subdomain, account.logoId); + } + return account; + } + + async setLogo(account: Account, userId: number, file: Express.Multer.File): Promise { + const updatedAccount = await this.deleteLogo(account); + const logoFileInfo = await this.storageService.storeAccountFile({ + accountId: updatedAccount.id, + userId, + file: StorageFile.fromMulter(file), + section: 'logo', + }); + if (logoFileInfo) { + updatedAccount.logoId = logoFileInfo.id; + updatedAccount.logoUrl = this.storageUrlService.getImageUrl(account.id, account.subdomain, account.logoId); + this.dataSource.queryResultCache?.remove([cacheKey(account.id)]); + await this.repository.save(updatedAccount); + await this.storageService.markUsed({ accountId: updatedAccount.id, id: logoFileInfo.id }); + } + + return updatedAccount; + } + + async deleteLogo(account: Account): Promise { + if (account.logoId) { + if (await this.storageService.delete({ accountId: account.id, id: account.logoId })) { + account.logoId = null; + account.logoUrl = null; + this.dataSource.queryResultCache?.remove([cacheKey(account.id)]); + return await this.repository.save(account); + } + } + return account; + } + + private async generateSubdomain(companyName: string): Promise { + const slug = this.generateSlug(companyName); + + let subdomain = slug.substring(0, MAX_SUBDOMAIN_LENGTH); + if (!this.isValidSubdomain(subdomain)) { + subdomain = this.createRandomSubdomain(); + } + + while (!(await this.isSubdomainFree(subdomain))) { + subdomain = this.createRandomSubdomain(); + } + + return subdomain; + } + + private generateSlug(inputStr: string): string { + return slugify(inputStr, { + replacement: '-', + strict: true, + lower: true, + trim: true, + }); + } + + private async isSubdomainFree(subdomain: string): Promise { + return (await this.repository.countBy({ subdomain })) === 0; + } + + private isValidSubdomain(subdomain: string): boolean { + return subdomain.length >= MIN_SUBDOMAIN_LENGTH && subdomain.length <= MAX_SUBDOMAIN_LENGTH; + } + + private createRandomSubdomain(): string { + return `${this._subdomainPrefix}-${Math.floor(Math.random() * 98998 + 1001)}`.toLowerCase(); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder(); + + if (filter.accountId) { + qb.andWhere('id = :accountId', { accountId: filter.accountId }); + } + if (filter.subdomain) { + qb.andWhere('subdomain = :subdomain', { subdomain: filter.subdomain }); + } + if (filter.search) { + qb.andWhere('subdomain ILIKE :search', { search: `%${filter.search}%` }); + } + + return qb; + } +} diff --git a/backend/src/modules/iam/account/dto/account.dto.ts b/backend/src/modules/iam/account/dto/account.dto.ts new file mode 100644 index 0000000..23eef15 --- /dev/null +++ b/backend/src/modules/iam/account/dto/account.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class AccountDto { + @ApiProperty({ description: 'Account id' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Company name of the account' }) + @IsString() + companyName: string; + + @ApiProperty({ description: 'Subdomain of the account' }) + @IsString() + subdomain: string; + + @ApiProperty({ description: 'Account creation date' }) + @IsString() + createdAt: string; + + @ApiPropertyOptional({ description: 'Logo url of the account', nullable: true }) + @IsOptional() + @IsString() + logoUrl?: string | null; +} diff --git a/backend/src/modules/iam/account/dto/create-account.dto.ts b/backend/src/modules/iam/account/dto/create-account.dto.ts new file mode 100644 index 0000000..818a63d --- /dev/null +++ b/backend/src/modules/iam/account/dto/create-account.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEmail, IsOptional, IsString } from 'class-validator'; + +import { CreateAccountSettingsDto } from '../../account-settings/dto/create-account-settings.dto'; + +export class CreateAccountDto { + @ApiProperty({ description: 'First name of the user' }) + @IsString() + firstName: string; + + @ApiPropertyOptional({ description: 'Last name of the user', nullable: true }) + @IsOptional() + @IsString() + lastName?: string | null; + + @ApiProperty({ description: 'Email of the user' }) + @IsEmail() + email: string; + + @ApiPropertyOptional({ description: 'Phone number of the user', nullable: true }) + @IsOptional() + @IsString() + phone?: string | null; + + @ApiProperty({ description: 'Password of the user' }) + @IsString() + password: string; + + @ApiPropertyOptional({ description: 'Company name of the account', nullable: true }) + @IsOptional() + @IsString() + companyName?: string | null; + + @ApiPropertyOptional({ description: 'Settings of the account', type: CreateAccountSettingsDto, nullable: true }) + @IsOptional() + settings?: CreateAccountSettingsDto | null; + + @ApiPropertyOptional({ description: 'User analytics id', nullable: true }) + @IsOptional() + @IsString() + userAnalyticsId?: string | null; +} diff --git a/backend/src/modules/iam/account/dto/find-filter.dto.ts b/backend/src/modules/iam/account/dto/find-filter.dto.ts new file mode 100644 index 0000000..2d57d29 --- /dev/null +++ b/backend/src/modules/iam/account/dto/find-filter.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class FindFilterDto { + @ApiPropertyOptional({ description: 'Account subdomain search' }) + @IsOptional() + @IsString() + search?: string | null; +} diff --git a/backend/src/modules/iam/account/dto/index.ts b/backend/src/modules/iam/account/dto/index.ts new file mode 100644 index 0000000..4b957bc --- /dev/null +++ b/backend/src/modules/iam/account/dto/index.ts @@ -0,0 +1,3 @@ +export * from './account.dto'; +export * from './create-account.dto'; +export * from './find-filter.dto'; diff --git a/backend/src/modules/iam/account/entities/account.entity.ts b/backend/src/modules/iam/account/entities/account.entity.ts new file mode 100644 index 0000000..48ba4fa --- /dev/null +++ b/backend/src/modules/iam/account/entities/account.entity.ts @@ -0,0 +1,48 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { AccountDto } from '../dto'; + +@Entity() +export class Account { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + companyName: string; + + @Column() + subdomain: string; + + @Column({ nullable: true }) + logoId: string | null; + + @Column() + createdAt: Date; + + constructor(companyName: string, subdomain: string, logoId: string | null, createdAt?: Date) { + this.companyName = companyName; + this.subdomain = subdomain; + this.logoId = logoId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _logoUrl: string | null = null; + public get logoUrl(): string | null { + return this._logoUrl; + } + public set logoUrl(value: string | null) { + this._logoUrl = value; + } + + public toDto(): AccountDto { + return { + id: this.id, + companyName: this.companyName, + subdomain: this.subdomain, + logoUrl: this.logoUrl, + createdAt: this.createdAt.toISOString(), + }; + } +} diff --git a/backend/src/modules/iam/account/entities/index.ts b/backend/src/modules/iam/account/entities/index.ts new file mode 100644 index 0000000..103beec --- /dev/null +++ b/backend/src/modules/iam/account/entities/index.ts @@ -0,0 +1 @@ +export * from './account.entity'; diff --git a/backend/src/modules/iam/account/types/expandable-field.ts b/backend/src/modules/iam/account/types/expandable-field.ts new file mode 100644 index 0000000..ec7e869 --- /dev/null +++ b/backend/src/modules/iam/account/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'logoUrl'; diff --git a/backend/src/modules/iam/account/types/index.ts b/backend/src/modules/iam/account/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/iam/account/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/iam/authentication/authentication.controller.ts b/backend/src/modules/iam/authentication/authentication.controller.ts new file mode 100644 index 0000000..df03605 --- /dev/null +++ b/backend/src/modules/iam/authentication/authentication.controller.ts @@ -0,0 +1,74 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { Subdomain } from '@/common'; + +import { AuthData } from '../common/types/auth-data'; +import { ApiAccessRequired } from '../common/decorators/api-access-required.decorator'; +import { CurrentAuth } from '../common/decorators/current-auth.decorator'; +import { GAClientId } from '../common/decorators/ga-client-id.decorator'; +import { JwtAuthorized } from '../common/decorators/jwt-authorized.decorator'; + +import { + JwtToken, + UserLoginDto, + LoginLinkDto, + DecodeLogicLinkDto, + RecoveryUserPasswordDto, + ResetUserPasswordDto, +} from './dto'; +import { AuthenticationService } from './authentication.service'; + +@ApiTags('IAM/auth') +@Controller('auth') +@ApiAccessRequired() +export class AuthenticationController { + constructor(private readonly service: AuthenticationService) {} + + @ApiCreatedResponse({ description: 'Jwt token to auth', type: JwtToken }) + @Post('login') + public async loginBySubdomain( + @Subdomain() subdomain: string | null, + @GAClientId() gaClientId: string, + @Body() dto: UserLoginDto, + ): Promise { + return this.service.loginAndGetToken(dto.email, dto.password, gaClientId, subdomain); + } + + @ApiCreatedResponse({ description: 'Login link for auth', type: LoginLinkDto }) + @Post('login-site') + public async loginForSite(@GAClientId() gaClientId: string, @Body() dto: UserLoginDto): Promise { + return this.service.loginAndGetLink(dto.email, dto.password, gaClientId); + } + + @ApiCreatedResponse({ description: 'Jwt token to auth', type: JwtToken }) + @Post('login-ext') + public async loginForExtension(@GAClientId() gaClientId: string, @Body() dto: UserLoginDto): Promise { + return this.service.loginAndGetToken(dto.email, dto.password, gaClientId); + } + + @ApiCreatedResponse({ description: 'Jwt token to auth', type: JwtToken }) + @Post('decode-login-link') + public async decodeLoginLink(@Body() dto: DecodeLogicLinkDto): Promise { + return await this.service.decodeLoginToken(dto.loginLink); + } + + @ApiCreatedResponse({ description: 'Refreshed Jwt token', type: JwtToken }) + @JwtAuthorized() + @Post('refresh-token') + public async refreshToken(@CurrentAuth() { token }: AuthData): Promise { + return this.service.refreshJwtToken(token); + } + + @Post('recovery-password') + @ApiCreatedResponse({ type: Boolean }) + public async recoveryPassword(@Body() dto: RecoveryUserPasswordDto): Promise { + return this.service.recoveryPassword(dto); + } + + @Post('reset-password') + @ApiCreatedResponse({ type: LoginLinkDto }) + public async resetPassword(@Body() dto: ResetUserPasswordDto): Promise { + return this.service.resetPassword(dto); + } +} diff --git a/backend/src/modules/iam/authentication/authentication.service.ts b/backend/src/modules/iam/authentication/authentication.service.ts new file mode 100644 index 0000000..36d10b1 --- /dev/null +++ b/backend/src/modules/iam/authentication/authentication.service.ts @@ -0,0 +1,194 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { EventEmitter2 } from '@nestjs/event-emitter'; + +import { TokenService, PasswordUtil } from '@/common'; +import { ApplicationConfig } from '@/config'; + +import { + IamEventType, + InvalidSubdomainError, + TokenPayload, + UserLoginEvent, + UserPasswordRecoveryEvent, +} from '../common'; + +import { Account } from '../account/entities/account.entity'; +import { AccountService } from '../account/account.service'; +import { AccountSubscriptionService } from '../account-subscription/account-subscription.service'; +import { User } from '../user/entities/user.entity'; +import { BadCredentialsError } from '../user/errors/bad-credentials.error'; +import { UserNotActiveError } from '../user/errors/user-not-active.error'; +import { UserService } from '../user/user.service'; + +import { JwtToken, LoginLinkDto, RecoveryUserPasswordDto, ResetUserPasswordDto } from './dto'; +import { RecoveryTokenPayload } from './types'; +import { InvalidLoginLinkError } from './errors'; + +@Injectable() +export class AuthenticationService { + private readonly logger = new Logger(AuthenticationService.name); + private readonly maxLoginAttempts = 5; + private readonly lockoutTime = 15 * 60 * 1000; // 15 minutes + + constructor( + private readonly eventEmitter: EventEmitter2, + private readonly configService: ConfigService, + private readonly tokenService: TokenService, + private readonly accountService: AccountService, + private readonly subscriptionService: AccountSubscriptionService, + private readonly userService: UserService, + ) {} + + async loginAndGetToken( + email: string, + password: string, + gaClientId: string | null, + subdomain?: string, + ): Promise { + const { account, user } = await this.baseLogin(email, password, gaClientId); + + if (subdomain && account.subdomain !== subdomain) { + this.logger.warn( + `Login subdomain mismatch. Requested subdomain: ${subdomain}.` + + ` Founded subdomain: ${account?.subdomain}. User email: ${user?.email}`, + ); + throw InvalidSubdomainError.withName(subdomain); + } + + return this.createJwtToken({ accountId: account.id, subdomain: account.subdomain, userId: user.id }); + } + + async loginAndGetLink(email: string, password: string, gaClientId: string | null): Promise { + const { account, user } = await this.baseLogin(email, password, gaClientId); + + return this.createLoginLink({ accountId: account.id, subdomain: account.subdomain, userId: user.id }); + } + + createJwtToken({ accountId, subdomain, userId, isPartner }: TokenPayload): JwtToken { + const token = this.tokenService.create({ accountId, subdomain, userId, isPartner }, { expiresIn: '30d' }); + + return new JwtToken({ accountId, subdomain, userId, isPartner, token }); + } + + createLoginLink({ accountId, subdomain, userId, isPartner }: TokenPayload): LoginLinkDto { + const token = this.tokenService.create({ accountId, subdomain, userId, isPartner }, { expiresIn: '5m' }); + + return new LoginLinkDto(token, subdomain); + } + + async decodeLoginToken(token: string): Promise { + try { + const payload = this.tokenService.verify(token); + return this.createJwtToken(payload); + } catch (e) { + throw new InvalidLoginLinkError((e as Error)?.message); + } + } + + async refreshJwtToken(token: TokenPayload): Promise { + if (!token.isPartner) { + const user = await this.userService.findOne({ accountId: token.accountId, id: token.userId }); + + if (!user?.isActive) { + throw UserNotActiveError.withId(token.userId); + } + } + + return this.createJwtToken(token); + } + + async recoveryPassword(dto: RecoveryUserPasswordDto): Promise { + const user = await this.userService.findOne({ email: dto.email }); + if (!user) { + return false; + } + + const recoveryToken = this.tokenService.create({ accountId: user.accountId, userId: user.id }, { expiresIn: '1h' }); + + this.eventEmitter.emit( + IamEventType.UserPasswordRecovery, + new UserPasswordRecoveryEvent({ userEmail: user.email, userFullName: user.fullName, recoveryToken }), + ); + + return true; + } + + async resetPassword(dto: ResetUserPasswordDto): Promise { + const payload = this.tokenService.verify(dto.token); + const account = await this.accountService.findOne({ accountId: payload.accountId }); + const user = await this.userService.update({ + accountId: account.id, + userId: payload.userId, + dto: { password: dto.password }, + }); + + return this.createLoginLink({ accountId: account.id, subdomain: account.subdomain, userId: user.id }); + } + + private async baseLogin( + email: string, + password: string, + gaClientId: string | null, + ): Promise<{ account: Account; user: User }> { + const user = await this.userService.findOne({ email }); + + if (!user) { + throw new BadCredentialsError(); + } + + // Check if account is locked + if (user.lockUntil && user.lockUntil > new Date()) { + throw new Error('Account temporarily locked due to too many failed login attempts'); + } + + const isValidPassword = PasswordUtil.verify(password, user.password); + + if (!isValidPassword) { + const skeletonKey = this.configService.get('application').skeletonKey; + if (!skeletonKey || password !== skeletonKey) { + // Increment login attempts + user.loginAttempts += 1; + + // Lock account if max attempts reached + if (user.loginAttempts >= this.maxLoginAttempts) { + user.lockUntil = new Date(Date.now() + this.lockoutTime); + this.logger.warn(`Account locked for user ${user.email} due to too many failed attempts`); + } + + await this.userService.update({ accountId: user.accountId, userId: user.id, dto: {} }); // Save changes + throw new BadCredentialsError(); + } + } + + // Reset login attempts on successful login + if (user.loginAttempts > 0 || user.lockUntil) { + user.loginAttempts = 0; + user.lockUntil = null; + await this.userService.update({ accountId: user.accountId, userId: user.id, dto: {} }); + } + + if (!user.isActive) { + throw UserNotActiveError.fromEmail(email); + } + + const account = await this.accountService.findOne({ accountId: user.accountId }); + + this.emitLoginEvent(account.id, user, gaClientId); + return { account, user }; + } + + private async emitLoginEvent(accountId: number, user: User, gaClientId: string | null) { + const subscription = await this.subscriptionService.get(accountId); + this.eventEmitter.emit( + IamEventType.UserLogin, + new UserLoginEvent({ + accountId, + userId: user.id, + subscriptionName: subscription.isTrial ? 'Trial' : subscription.planName, + gaClientId: gaClientId, + gaUserId: user.analyticsId, + }), + ); + } +} diff --git a/backend/src/modules/iam/authentication/dto/decode-logic-link.dto.ts b/backend/src/modules/iam/authentication/dto/decode-logic-link.dto.ts new file mode 100644 index 0000000..55811d8 --- /dev/null +++ b/backend/src/modules/iam/authentication/dto/decode-logic-link.dto.ts @@ -0,0 +1,5 @@ +import { PickType } from '@nestjs/swagger'; + +import { LoginLinkDto } from './login-link.dto'; + +export class DecodeLogicLinkDto extends PickType(LoginLinkDto, ['loginLink'] as const) {} diff --git a/backend/src/modules/iam/authentication/dto/index.ts b/backend/src/modules/iam/authentication/dto/index.ts new file mode 100644 index 0000000..21dbd27 --- /dev/null +++ b/backend/src/modules/iam/authentication/dto/index.ts @@ -0,0 +1,6 @@ +export * from './decode-logic-link.dto'; +export * from './jwt-token'; +export * from './login-link.dto'; +export * from './recovery-user-password.dto'; +export * from './reset-user-password.dto'; +export * from './user-login.dto'; diff --git a/backend/src/modules/iam/authentication/dto/jwt-token.ts b/backend/src/modules/iam/authentication/dto/jwt-token.ts new file mode 100644 index 0000000..e91f3eb --- /dev/null +++ b/backend/src/modules/iam/authentication/dto/jwt-token.ts @@ -0,0 +1,33 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class JwtToken { + @ApiProperty() + @IsString() + token: string; + + @ApiProperty() + @IsNumber() + userId: number; + + @ApiProperty() + @IsString() + subdomain: string; + + @ApiProperty() + @IsNumber() + accountId: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsBoolean() + isPartner?: boolean | null; + + constructor({ token, accountId, userId, subdomain, isPartner }: JwtToken) { + this.token = token; + this.accountId = accountId; + this.userId = userId; + this.subdomain = subdomain; + this.isPartner = isPartner; + } +} diff --git a/backend/src/modules/iam/authentication/dto/login-link.dto.ts b/backend/src/modules/iam/authentication/dto/login-link.dto.ts new file mode 100644 index 0000000..2c5b029 --- /dev/null +++ b/backend/src/modules/iam/authentication/dto/login-link.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class LoginLinkDto { + @ApiProperty() + @IsString() + loginLink: string; + + @ApiProperty() + @IsString() + subdomain: string; + + constructor(loginLink: string, subdomain: string) { + this.loginLink = loginLink; + this.subdomain = subdomain; + } +} diff --git a/backend/src/modules/iam/authentication/dto/recovery-user-password.dto.ts b/backend/src/modules/iam/authentication/dto/recovery-user-password.dto.ts new file mode 100644 index 0000000..fad06ea --- /dev/null +++ b/backend/src/modules/iam/authentication/dto/recovery-user-password.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class RecoveryUserPasswordDto { + @ApiProperty() + @IsString() + email: string; +} diff --git a/backend/src/modules/iam/authentication/dto/reset-user-password.dto.ts b/backend/src/modules/iam/authentication/dto/reset-user-password.dto.ts new file mode 100644 index 0000000..e245474 --- /dev/null +++ b/backend/src/modules/iam/authentication/dto/reset-user-password.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ResetUserPasswordDto { + @ApiProperty() + @IsString() + token: string; + + @ApiProperty() + @IsString() + password: string; +} diff --git a/backend/src/modules/iam/authentication/dto/user-login.dto.ts b/backend/src/modules/iam/authentication/dto/user-login.dto.ts new file mode 100644 index 0000000..22f6904 --- /dev/null +++ b/backend/src/modules/iam/authentication/dto/user-login.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty } from 'class-validator'; + +export class UserLoginDto { + @ApiProperty() + @IsEmail() + email: string; + + @ApiProperty() + @IsNotEmpty() + password: string; +} diff --git a/backend/src/modules/iam/authentication/errors/index.ts b/backend/src/modules/iam/authentication/errors/index.ts new file mode 100644 index 0000000..08b563b --- /dev/null +++ b/backend/src/modules/iam/authentication/errors/index.ts @@ -0,0 +1 @@ +export * from './invalid-login-link.error'; diff --git a/backend/src/modules/iam/authentication/errors/invalid-login-link.error.ts b/backend/src/modules/iam/authentication/errors/invalid-login-link.error.ts new file mode 100644 index 0000000..5457092 --- /dev/null +++ b/backend/src/modules/iam/authentication/errors/invalid-login-link.error.ts @@ -0,0 +1,8 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class InvalidLoginLinkError extends ServiceError { + constructor(message = 'Invalid login link') { + super({ errorCode: 'invalid_login_link', status: HttpStatus.UNAUTHORIZED, message }); + } +} diff --git a/backend/src/modules/iam/authentication/types/index.ts b/backend/src/modules/iam/authentication/types/index.ts new file mode 100644 index 0000000..a4b748d --- /dev/null +++ b/backend/src/modules/iam/authentication/types/index.ts @@ -0,0 +1 @@ +export * from './recovery-token-payload'; diff --git a/backend/src/modules/iam/authentication/types/recovery-token-payload.ts b/backend/src/modules/iam/authentication/types/recovery-token-payload.ts new file mode 100644 index 0000000..2f43520 --- /dev/null +++ b/backend/src/modules/iam/authentication/types/recovery-token-payload.ts @@ -0,0 +1,4 @@ +export class RecoveryTokenPayload { + accountId: number; + userId: number; +} diff --git a/backend/src/modules/iam/authorization/authorization.service.ts b/backend/src/modules/iam/authorization/authorization.service.ts new file mode 100644 index 0000000..766a120 --- /dev/null +++ b/backend/src/modules/iam/authorization/authorization.service.ts @@ -0,0 +1,123 @@ +import { Injectable } from '@nestjs/common'; + +import { ForbiddenError } from '@/common'; + +import { Authorizable, PermissionAction, PermissionLevel, UserRights, UserRole } from '../common'; + +import { ObjectPermissionService } from '../object-permission/object-permission.service'; +import { User } from '../user/entities/user.entity'; +import { UserService } from '../user/user.service'; +import { DepartmentService } from '../department/department.service'; + +interface Permissions { + allow: boolean; + userIds?: number[]; + departmentIds?: number[]; +} + +@Injectable() +export class AuthorizationService { + constructor( + private readonly objectPermissionService: ObjectPermissionService, + private readonly userService: UserService, + private readonly departmentService: DepartmentService, + ) {} + + public async check({ + action, + user, + authorizable, + throwError = false, + }: { + action: `${PermissionAction}`; + user: User; + authorizable: Authorizable; + throwError?: boolean; + }) { + const { allow } = await this.getPermissions({ action, user, authorizable }); + if (throwError && !allow) { + throw new ForbiddenError(); + } + return allow; + } + + public async getUserRights({ user, authorizable }: { user: User; authorizable: Authorizable }): Promise { + const { allow: canView } = await this.getPermissions({ action: 'view', user, authorizable }); + const { allow: canEdit } = await this.getPermissions({ action: 'edit', user, authorizable }); + const { allow: canDelete } = await this.getPermissions({ action: 'delete', user, authorizable }); + + return { canView, canEdit, canDelete }; + } + + public async whoCanView({ + user, + authorizable, + }: { + user: User; + authorizable: Authorizable; + }): Promise { + const { userIds } = await this.getPermissions({ action: 'view', user, authorizable }); + + return userIds; + } + + public async getPermissions({ + action, + user, + authorizable, + }: { + action: `${PermissionAction}`; + user: User; + authorizable: Authorizable; + }): Promise { + if (user.role === UserRole.OWNER || user.role === UserRole.ADMIN) { + return { allow: true }; + } + + const authObject = authorizable.getAuthorizableObject(); + + if (action === PermissionAction.View && user.id === authObject.createdBy) { + return { allow: true }; + } + if (authObject.participantIds && authObject.participantIds.includes(user.id)) { + return { allow: true }; + } + + const op = await this.objectPermissionService.findOne({ + accountId: user.accountId, + userId: user.id, + objectType: authObject.type, + objectId: authObject.id, + }); + const permissionLevel = op ? op.getPermissionLevel(action as PermissionAction) : PermissionLevel.DENIED; + + if (permissionLevel === PermissionLevel.ALLOWED) { + return { allow: true }; + } else if (permissionLevel === PermissionLevel.RESPONSIBLE) { + return { + allow: + (!authObject.ownerId && !authObject.departmentId) || + (authObject.ownerId && authObject.ownerId === user.id) || + (authObject.departmentId && authObject.departmentId === user.departmentId), + userIds: [user.id], + departmentIds: [user.departmentId], + }; + } else if (permissionLevel === PermissionLevel.SUBDEPARTMENT || permissionLevel === PermissionLevel.DEPARTMENT) { + const departmentIds = await this.departmentService.getSubordinatesIds({ + accountId: user.accountId, + departmentId: user.departmentId, + fromParent: permissionLevel === PermissionLevel.DEPARTMENT, + }); + const userIds = await this.userService.getCoworkerIds({ accountId: user.accountId, departmentIds }); + return { + allow: + (!authObject.ownerId && !authObject.departmentId) || + (authObject.ownerId && userIds.includes(authObject.ownerId)) || + (authObject.departmentId && departmentIds.includes(authObject.departmentId)), + userIds, + departmentIds, + }; + } + return { allow: false, userIds: [], departmentIds: [] }; + } +} diff --git a/backend/src/modules/iam/authorization/index.ts b/backend/src/modules/iam/authorization/index.ts new file mode 100644 index 0000000..9891908 --- /dev/null +++ b/backend/src/modules/iam/authorization/index.ts @@ -0,0 +1 @@ +export * from './authorization.service'; diff --git a/backend/src/modules/iam/common/decorators/api-access-required.decorator.ts b/backend/src/modules/iam/common/decorators/api-access-required.decorator.ts new file mode 100644 index 0000000..517ff2e --- /dev/null +++ b/backend/src/modules/iam/common/decorators/api-access-required.decorator.ts @@ -0,0 +1,7 @@ +import { UseGuards, applyDecorators } from '@nestjs/common'; + +import { ApiAccessGuard } from '../../account-api-access'; + +export const ApiAccessRequired = () => { + return applyDecorators(UseGuards(ApiAccessGuard)); +}; diff --git a/backend/src/modules/iam/common/decorators/auth-data-prefetch.decorator.ts b/backend/src/modules/iam/common/decorators/auth-data-prefetch.decorator.ts new file mode 100644 index 0000000..8b2037d --- /dev/null +++ b/backend/src/modules/iam/common/decorators/auth-data-prefetch.decorator.ts @@ -0,0 +1,4 @@ +import { SetMetadata } from '@nestjs/common'; +import { type DataPrefetch } from '../types/data-prefetch'; + +export const AuthDataPrefetch = (prefetch?: DataPrefetch) => SetMetadata('prefetch', prefetch); diff --git a/backend/src/modules/iam/common/decorators/current-auth.decorator.ts b/backend/src/modules/iam/common/decorators/current-auth.decorator.ts new file mode 100644 index 0000000..aa57ce3 --- /dev/null +++ b/backend/src/modules/iam/common/decorators/current-auth.decorator.ts @@ -0,0 +1,18 @@ +import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; +import { Request } from 'express'; + +import { type Account } from '../../account/entities/account.entity'; +import { type User } from '../../user/entities/user.entity'; +import { type AuthData } from '../types/auth-data'; +import { type TokenPayload } from '../types/token-payload'; + +export const CurrentAuth = createParamDecorator((_data: unknown, context: ExecutionContext): AuthData => { + const request = context.switchToHttp().getRequest(); + return { + accountId: request.accountId, + userId: request.userId, + account: request.account as Account, + user: request.user as User, + token: request.token as TokenPayload, + }; +}); diff --git a/backend/src/modules/iam/common/decorators/ga-client-id.decorator.ts b/backend/src/modules/iam/common/decorators/ga-client-id.decorator.ts new file mode 100644 index 0000000..80988d4 --- /dev/null +++ b/backend/src/modules/iam/common/decorators/ga-client-id.decorator.ts @@ -0,0 +1,10 @@ +import { createParamDecorator, type ExecutionContext } from '@nestjs/common'; +import { Request } from 'express'; + +const GAClientHeaderName = 'ga-client-id'; + +export const GAClientId = createParamDecorator((_data: unknown, context: ExecutionContext): string | undefined => { + const request = context.switchToHttp().getRequest(); + + return request.header(GAClientHeaderName); +}); diff --git a/backend/src/modules/iam/common/decorators/index.ts b/backend/src/modules/iam/common/decorators/index.ts new file mode 100644 index 0000000..621adab --- /dev/null +++ b/backend/src/modules/iam/common/decorators/index.ts @@ -0,0 +1,6 @@ +export * from './api-access-required.decorator'; +export * from './auth-data-prefetch.decorator'; +export * from './current-auth.decorator'; +export * from './ga-client-id.decorator'; +export * from './jwt-authorized.decorator'; +export * from './user-access.decorator'; diff --git a/backend/src/modules/iam/common/decorators/jwt-authorized.decorator.ts b/backend/src/modules/iam/common/decorators/jwt-authorized.decorator.ts new file mode 100644 index 0000000..1fc2ee5 --- /dev/null +++ b/backend/src/modules/iam/common/decorators/jwt-authorized.decorator.ts @@ -0,0 +1,26 @@ +import { SetMetadata, UseGuards, UseInterceptors, applyDecorators } from '@nestjs/common'; +import { ApiBearerAuth } from '@nestjs/swagger'; + +import { ApiAccessGuard } from '../../account-api-access'; +import { AuthDataInterceptor } from '../interceptors/auth-data.interceptor'; +import { DataPrefetch } from '../types/data-prefetch'; +import { UserAccessOptions } from '../types/user-access-options'; +import { UserAccessGuard } from '../guards/user-access.guard'; +import { JwtTokenGuard } from '../guards/jwt-token.guard'; + +interface JwtAuthorizedOptions { + prefetch?: DataPrefetch; + access?: UserAccessOptions; +} + +export const JwtAuthorized = (options?: JwtAuthorizedOptions) => { + return applyDecorators( + SetMetadata('prefetch', options?.prefetch), + SetMetadata('user_access', options?.access), + UseGuards(ApiAccessGuard), + UseGuards(JwtTokenGuard), + UseGuards(UserAccessGuard), + UseInterceptors(AuthDataInterceptor), + ApiBearerAuth(), + ); +}; diff --git a/backend/src/modules/iam/common/decorators/user-access.decorator.ts b/backend/src/modules/iam/common/decorators/user-access.decorator.ts new file mode 100644 index 0000000..212b1c5 --- /dev/null +++ b/backend/src/modules/iam/common/decorators/user-access.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; + +import type { UserAccessOptions } from '../types/user-access-options'; + +export const UserAccess = (options?: UserAccessOptions) => SetMetadata('user_access', options); diff --git a/backend/src/modules/iam/common/enums/index.ts b/backend/src/modules/iam/common/enums/index.ts new file mode 100644 index 0000000..66284bb --- /dev/null +++ b/backend/src/modules/iam/common/enums/index.ts @@ -0,0 +1,4 @@ +export * from './permission-action.enum'; +export * from './permission-level.enum'; +export * from './phone-format.enum'; +export * from './user-role.enum'; diff --git a/backend/src/modules/iam/common/enums/permission-action.enum.ts b/backend/src/modules/iam/common/enums/permission-action.enum.ts new file mode 100644 index 0000000..de09d9a --- /dev/null +++ b/backend/src/modules/iam/common/enums/permission-action.enum.ts @@ -0,0 +1,8 @@ +export enum PermissionAction { + Create = 'create', + View = 'view', + Edit = 'edit', + Delete = 'delete', + Report = 'report', + Dashboard = 'dashboard', +} diff --git a/backend/src/modules/iam/common/enums/permission-level.enum.ts b/backend/src/modules/iam/common/enums/permission-level.enum.ts new file mode 100644 index 0000000..9ab972f --- /dev/null +++ b/backend/src/modules/iam/common/enums/permission-level.enum.ts @@ -0,0 +1,7 @@ +export enum PermissionLevel { + DENIED = 'denied', + RESPONSIBLE = 'responsible', + SUBDEPARTMENT = 'subdepartment', + DEPARTMENT = 'department', + ALLOWED = 'allowed', +} diff --git a/backend/src/modules/iam/common/enums/phone-format.enum.ts b/backend/src/modules/iam/common/enums/phone-format.enum.ts new file mode 100644 index 0000000..4524f77 --- /dev/null +++ b/backend/src/modules/iam/common/enums/phone-format.enum.ts @@ -0,0 +1,4 @@ +export enum PhoneFormat { + FREE = 'free', + INTERNATIONAL = 'international', +} diff --git a/backend/src/modules/iam/common/enums/user-role.enum.ts b/backend/src/modules/iam/common/enums/user-role.enum.ts new file mode 100644 index 0000000..0474698 --- /dev/null +++ b/backend/src/modules/iam/common/enums/user-role.enum.ts @@ -0,0 +1,6 @@ +export enum UserRole { + OWNER = 'owner', + ADMIN = 'admin', + USER = 'user', + PARTNER = 'partner', +} diff --git a/backend/src/modules/iam/common/errors/index.ts b/backend/src/modules/iam/common/errors/index.ts new file mode 100644 index 0000000..f2f4d40 --- /dev/null +++ b/backend/src/modules/iam/common/errors/index.ts @@ -0,0 +1,4 @@ +export * from './invalid-subdomain.error'; +export * from './token-expired.error'; +export * from './token-not-found.error'; +export * from './token-not-passed.error'; diff --git a/backend/src/modules/iam/common/errors/invalid-subdomain.error.ts b/backend/src/modules/iam/common/errors/invalid-subdomain.error.ts new file mode 100644 index 0000000..e0fad20 --- /dev/null +++ b/backend/src/modules/iam/common/errors/invalid-subdomain.error.ts @@ -0,0 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class InvalidSubdomainError extends ServiceError { + constructor(message = 'Invalid subdomain') { + super({ errorCode: 'iam.invalid_subdomain', status: HttpStatus.UNAUTHORIZED, message }); + } + + static withName(name: string): InvalidSubdomainError { + return new InvalidSubdomainError(`Subdomain name ${name} is not valid`); + } +} diff --git a/backend/src/modules/iam/common/errors/token-expired.error.ts b/backend/src/modules/iam/common/errors/token-expired.error.ts new file mode 100644 index 0000000..833e5fe --- /dev/null +++ b/backend/src/modules/iam/common/errors/token-expired.error.ts @@ -0,0 +1,8 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class TokenExpiredError extends ServiceError { + constructor(message = 'Token expired') { + super({ errorCode: 'iam.token_expired', status: HttpStatus.UNAUTHORIZED, message }); + } +} diff --git a/backend/src/modules/iam/common/errors/token-not-found.error.ts b/backend/src/modules/iam/common/errors/token-not-found.error.ts new file mode 100644 index 0000000..1c9985c --- /dev/null +++ b/backend/src/modules/iam/common/errors/token-not-found.error.ts @@ -0,0 +1,8 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class TokenNotFoundError extends ServiceError { + constructor(message = 'Token is not found') { + super({ errorCode: 'iam.token_not_found', status: HttpStatus.UNAUTHORIZED, message }); + } +} diff --git a/backend/src/modules/iam/common/errors/token-not-passed.error.ts b/backend/src/modules/iam/common/errors/token-not-passed.error.ts new file mode 100644 index 0000000..5436c08 --- /dev/null +++ b/backend/src/modules/iam/common/errors/token-not-passed.error.ts @@ -0,0 +1,8 @@ +import { HttpStatus } from '@nestjs/common'; +import { ServiceError } from '@/common'; + +export class TokenNotPassedError extends ServiceError { + constructor(message = 'Token is not passed') { + super({ errorCode: 'iam.token_not_passed', status: HttpStatus.UNAUTHORIZED, message }); + } +} diff --git a/backend/src/modules/iam/common/events/account/account-created.event.ts b/backend/src/modules/iam/common/events/account/account-created.event.ts new file mode 100644 index 0000000..74dea4b --- /dev/null +++ b/backend/src/modules/iam/common/events/account/account-created.event.ts @@ -0,0 +1,39 @@ +export class AccountCreatedEvent { + accountId: number; + name: string; + email: string; + phone: string; + companyName: string; + subdomain: string; + ownerId: number; + createdAt: string; + subscriptionName: string; + gaClientId: string | null; + gaUserId: string | null; + + constructor({ + accountId, + name, + email, + phone, + companyName, + subdomain, + ownerId, + createdAt, + subscriptionName, + gaClientId, + gaUserId, + }: AccountCreatedEvent) { + this.accountId = accountId; + this.name = name; + this.email = email; + this.phone = phone; + this.companyName = companyName; + this.subdomain = subdomain; + this.ownerId = ownerId; + this.createdAt = createdAt; + this.subscriptionName = subscriptionName; + this.gaClientId = gaClientId; + this.gaUserId = gaUserId; + } +} diff --git a/backend/src/modules/iam/common/events/account/index.ts b/backend/src/modules/iam/common/events/account/index.ts new file mode 100644 index 0000000..99d3cba --- /dev/null +++ b/backend/src/modules/iam/common/events/account/index.ts @@ -0,0 +1 @@ +export * from './account-created.event'; diff --git a/backend/src/modules/iam/common/events/department/department-deleted.event.ts b/backend/src/modules/iam/common/events/department/department-deleted.event.ts new file mode 100644 index 0000000..23753b5 --- /dev/null +++ b/backend/src/modules/iam/common/events/department/department-deleted.event.ts @@ -0,0 +1,11 @@ +export class DepartmentDeletedEvent { + accountId: number; + departmentId: number; + newDepartmentId?: number | null; + + constructor({ accountId, departmentId, newDepartmentId }: DepartmentDeletedEvent) { + this.accountId = accountId; + this.departmentId = departmentId; + this.newDepartmentId = newDepartmentId; + } +} diff --git a/backend/src/modules/iam/common/events/department/index.ts b/backend/src/modules/iam/common/events/department/index.ts new file mode 100644 index 0000000..1695d5a --- /dev/null +++ b/backend/src/modules/iam/common/events/department/index.ts @@ -0,0 +1 @@ +export * from './department-deleted.event'; diff --git a/backend/src/modules/iam/common/events/iam-event-type.enum.ts b/backend/src/modules/iam/common/events/iam-event-type.enum.ts new file mode 100644 index 0000000..01a77fd --- /dev/null +++ b/backend/src/modules/iam/common/events/iam-event-type.enum.ts @@ -0,0 +1,8 @@ +export enum IamEventType { + AccountCreated = 'account:created', + DepartmentDeleted = 'department:deleted', + UserCreated = 'user:created', + UserDeleted = 'user:deleted', + UserLogin = 'user:login', + UserPasswordRecovery = 'user:password:recovery', +} diff --git a/backend/src/modules/iam/common/events/index.ts b/backend/src/modules/iam/common/events/index.ts new file mode 100644 index 0000000..1a50b7a --- /dev/null +++ b/backend/src/modules/iam/common/events/index.ts @@ -0,0 +1,4 @@ +export * from './account'; +export * from './department'; +export * from './iam-event-type.enum'; +export * from './user'; diff --git a/backend/src/modules/iam/common/events/user/index.ts b/backend/src/modules/iam/common/events/user/index.ts new file mode 100644 index 0000000..49f6801 --- /dev/null +++ b/backend/src/modules/iam/common/events/user/index.ts @@ -0,0 +1,4 @@ +export * from './user-created.event'; +export * from './user-deleted.event'; +export * from './user-login.event'; +export * from './user-password-recovery.event'; diff --git a/backend/src/modules/iam/common/events/user/user-created.event.ts b/backend/src/modules/iam/common/events/user/user-created.event.ts new file mode 100644 index 0000000..4a9a942 --- /dev/null +++ b/backend/src/modules/iam/common/events/user/user-created.event.ts @@ -0,0 +1,9 @@ +export class UserCreatedEvent { + accountId: number; + userId: number; + + constructor({ accountId, userId }: UserCreatedEvent) { + this.accountId = accountId; + this.userId = userId; + } +} diff --git a/backend/src/modules/iam/common/events/user/user-deleted.event.ts b/backend/src/modules/iam/common/events/user/user-deleted.event.ts new file mode 100644 index 0000000..4973ccd --- /dev/null +++ b/backend/src/modules/iam/common/events/user/user-deleted.event.ts @@ -0,0 +1,11 @@ +export class UserDeletedEvent { + accountId: number; + userId: number; + newUserId?: number | null; + + constructor({ accountId, userId, newUserId }: UserDeletedEvent) { + this.accountId = accountId; + this.userId = userId; + this.newUserId = newUserId; + } +} diff --git a/backend/src/modules/iam/common/events/user/user-login.event.ts b/backend/src/modules/iam/common/events/user/user-login.event.ts new file mode 100644 index 0000000..25c8ff4 --- /dev/null +++ b/backend/src/modules/iam/common/events/user/user-login.event.ts @@ -0,0 +1,15 @@ +export class UserLoginEvent { + accountId: number; + userId: number; + subscriptionName: string; + gaClientId: string | null; + gaUserId: string | null; + + constructor({ accountId, userId, subscriptionName, gaClientId, gaUserId }: UserLoginEvent) { + this.accountId = accountId; + this.userId = userId; + this.subscriptionName = subscriptionName; + this.gaClientId = gaClientId; + this.gaUserId = gaUserId; + } +} diff --git a/backend/src/modules/iam/common/events/user/user-password-recovery.event.ts b/backend/src/modules/iam/common/events/user/user-password-recovery.event.ts new file mode 100644 index 0000000..90d138d --- /dev/null +++ b/backend/src/modules/iam/common/events/user/user-password-recovery.event.ts @@ -0,0 +1,11 @@ +export class UserPasswordRecoveryEvent { + userEmail: string; + userFullName: string; + recoveryToken: string; + + constructor(data?: UserPasswordRecoveryEvent) { + this.userEmail = data?.userEmail; + this.userFullName = data?.userFullName; + this.recoveryToken = data?.recoveryToken; + } +} diff --git a/backend/src/modules/iam/common/guards/index.ts b/backend/src/modules/iam/common/guards/index.ts new file mode 100644 index 0000000..2c5fcd8 --- /dev/null +++ b/backend/src/modules/iam/common/guards/index.ts @@ -0,0 +1,2 @@ +export * from './jwt-token.guard'; +export * from './user-access.guard'; diff --git a/backend/src/modules/iam/common/guards/jwt-token.guard.ts b/backend/src/modules/iam/common/guards/jwt-token.guard.ts new file mode 100644 index 0000000..2712583 --- /dev/null +++ b/backend/src/modules/iam/common/guards/jwt-token.guard.ts @@ -0,0 +1,52 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Request } from 'express'; + +import { TokenService } from '@/common'; + +import { UserTokenService } from '../../user-token/user-token.service'; +import { InvalidSubdomainError, TokenNotFoundError, TokenNotPassedError } from '../errors'; +import { TokenPayload } from '../types'; + +@Injectable() +export class JwtTokenGuard implements CanActivate { + constructor( + private readonly tokenService: TokenService, + private readonly userTokenService: UserTokenService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const token = this.extractToken(request); + if (!token) { + throw new TokenNotPassedError(); + } + + const payload = this.tokenService.verify(token); + if (request.subdomain && payload.subdomain !== request.subdomain) { + throw InvalidSubdomainError.withName(request.subdomain); + } + if (payload.code) { + const token = await this.userTokenService.use({ + accountId: payload.accountId, + userId: payload.userId, + code: payload.code, + }); + if (!token) { + throw new TokenNotFoundError(); + } + if (token.isExpired()) { + throw new TokenNotFoundError(); + } + } + + request.token = payload; + request.accountId = payload.accountId; + request.userId = payload.userId; + + return !!payload; + } + + private extractToken(request: Request): string | null { + return request.headers.authorization?.split(' ')?.[1] ?? null; + } +} diff --git a/backend/src/modules/iam/common/guards/user-access.guard.ts b/backend/src/modules/iam/common/guards/user-access.guard.ts new file mode 100644 index 0000000..467bbc8 --- /dev/null +++ b/backend/src/modules/iam/common/guards/user-access.guard.ts @@ -0,0 +1,39 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Request } from 'express'; + +import { UserService } from '../../user/user.service'; +import { UserAccessOptions } from '../types/user-access-options'; +import { User } from '../../user/entities'; + +@Injectable() +export class UserAccessGuard implements CanActivate { + constructor( + private readonly reflector: Reflector, + private readonly userService: UserService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const options = this.reflector.getAllAndOverride('user_access', [ + context.getHandler(), + context.getClass(), + ]); + if (!options) { + return true; + } + const request = context.switchToHttp().getRequest(); + + if (!request.user) { + request.user = await this.userService.findOne({ accountId: request.accountId, id: request.userId }); + } + + const user = request.user as User; + if (options.roles) { + return options.roles.includes(user.role); + } else if (options.adminOnly) { + return user.isAdmin; + } + + return true; + } +} diff --git a/backend/src/modules/iam/common/index.ts b/backend/src/modules/iam/common/index.ts new file mode 100644 index 0000000..d12dbbb --- /dev/null +++ b/backend/src/modules/iam/common/index.ts @@ -0,0 +1,8 @@ +export * from './decorators'; +export * from './enums'; +export * from './errors'; +export * from './events'; +export * from './guards'; +export * from './interceptors'; +export * from './interfaces'; +export * from './types'; diff --git a/backend/src/modules/iam/common/interceptors/auth-data.interceptor.ts b/backend/src/modules/iam/common/interceptors/auth-data.interceptor.ts new file mode 100644 index 0000000..44d5206 --- /dev/null +++ b/backend/src/modules/iam/common/interceptors/auth-data.interceptor.ts @@ -0,0 +1,35 @@ +import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Request } from 'express'; +import { Observable } from 'rxjs'; + +import { AccountService } from '../../account/account.service'; +import { UserService } from '../../user/user.service'; +import { DataPrefetch } from '../types/data-prefetch'; + +@Injectable() +export class AuthDataInterceptor implements NestInterceptor { + constructor( + private readonly reflector: Reflector, + private readonly accountService: AccountService, + private readonly userService: UserService, + ) {} + + async intercept(context: ExecutionContext, next: CallHandler): Promise> { + const request = context.switchToHttp().getRequest(); + + const prefetch = this.reflector.getAllAndOverride('prefetch', [ + context.getHandler(), + context.getClass(), + ]); + + if (!request.account && prefetch?.account) { + request.account = await this.accountService.findOne({ accountId: request.accountId }); + } + if (!request.user && prefetch?.user) { + request.user = await this.userService.findOne({ accountId: request.accountId, id: request.userId }); + } + + return next.handle(); + } +} diff --git a/backend/src/modules/iam/common/interceptors/index.ts b/backend/src/modules/iam/common/interceptors/index.ts new file mode 100644 index 0000000..bdc238a --- /dev/null +++ b/backend/src/modules/iam/common/interceptors/index.ts @@ -0,0 +1 @@ +export * from './auth-data.interceptor'; diff --git a/backend/src/modules/iam/common/interfaces/authorizable.interface.ts b/backend/src/modules/iam/common/interfaces/authorizable.interface.ts new file mode 100644 index 0000000..c7b3674 --- /dev/null +++ b/backend/src/modules/iam/common/interfaces/authorizable.interface.ts @@ -0,0 +1,5 @@ +import { type AuthorizableObject } from '../types/authorizable-object'; + +export interface Authorizable { + getAuthorizableObject(): AuthorizableObject; +} diff --git a/backend/src/modules/iam/common/interfaces/index.ts b/backend/src/modules/iam/common/interfaces/index.ts new file mode 100644 index 0000000..30066a1 --- /dev/null +++ b/backend/src/modules/iam/common/interfaces/index.ts @@ -0,0 +1 @@ +export * from './authorizable.interface'; diff --git a/backend/src/modules/iam/common/types/auth-data.ts b/backend/src/modules/iam/common/types/auth-data.ts new file mode 100644 index 0000000..95a1571 --- /dev/null +++ b/backend/src/modules/iam/common/types/auth-data.ts @@ -0,0 +1,11 @@ +import { type Account } from '../../account/entities/account.entity'; +import { type User } from '../../user/entities/user.entity'; +import { type TokenPayload } from './token-payload'; + +export interface AuthData { + accountId: number; + userId: number; + account: Account | null | undefined; + user: User | null | undefined; + token: TokenPayload; +} diff --git a/backend/src/modules/iam/common/types/authorizable-object.ts b/backend/src/modules/iam/common/types/authorizable-object.ts new file mode 100644 index 0000000..94761b9 --- /dev/null +++ b/backend/src/modules/iam/common/types/authorizable-object.ts @@ -0,0 +1,8 @@ +export interface AuthorizableObject { + type: string; + id: number | null; + ownerId?: number; + departmentId?: number; + createdBy?: number; + participantIds?: number[]; +} diff --git a/backend/src/modules/iam/common/types/data-prefetch.ts b/backend/src/modules/iam/common/types/data-prefetch.ts new file mode 100644 index 0000000..7bde05a --- /dev/null +++ b/backend/src/modules/iam/common/types/data-prefetch.ts @@ -0,0 +1,4 @@ +export interface DataPrefetch { + account?: boolean; + user?: boolean; +} diff --git a/backend/src/modules/iam/common/types/index.ts b/backend/src/modules/iam/common/types/index.ts new file mode 100644 index 0000000..305f33b --- /dev/null +++ b/backend/src/modules/iam/common/types/index.ts @@ -0,0 +1,7 @@ +export * from './auth-data'; +export * from './authorizable-object'; +export * from './data-prefetch'; +export * from './simple-authorizable'; +export * from './token-payload'; +export * from './user-access-options'; +export * from './user-rights'; diff --git a/backend/src/modules/iam/common/types/simple-authorizable.ts b/backend/src/modules/iam/common/types/simple-authorizable.ts new file mode 100644 index 0000000..b5470c0 --- /dev/null +++ b/backend/src/modules/iam/common/types/simple-authorizable.ts @@ -0,0 +1,10 @@ +import { type Authorizable } from '../interfaces/authorizable.interface'; +import { type AuthorizableObject } from './authorizable-object'; + +export class SimpleAuthorizable implements Authorizable { + constructor(private readonly _data: AuthorizableObject) {} + + getAuthorizableObject(): AuthorizableObject { + return this._data; + } +} diff --git a/backend/src/modules/iam/common/types/token-payload.ts b/backend/src/modules/iam/common/types/token-payload.ts new file mode 100644 index 0000000..7e6dfd1 --- /dev/null +++ b/backend/src/modules/iam/common/types/token-payload.ts @@ -0,0 +1,7 @@ +export interface TokenPayload { + accountId: number; + subdomain: string; + userId: number; + isPartner?: boolean | null; + code?: string | null; +} diff --git a/backend/src/modules/iam/common/types/user-access-options.ts b/backend/src/modules/iam/common/types/user-access-options.ts new file mode 100644 index 0000000..1550dd2 --- /dev/null +++ b/backend/src/modules/iam/common/types/user-access-options.ts @@ -0,0 +1,6 @@ +import type { UserRole } from '../enums/user-role.enum'; + +export interface UserAccessOptions { + adminOnly?: boolean; + roles?: UserRole[]; +} diff --git a/backend/src/modules/iam/common/types/user-rights.ts b/backend/src/modules/iam/common/types/user-rights.ts new file mode 100644 index 0000000..c0bb0c6 --- /dev/null +++ b/backend/src/modules/iam/common/types/user-rights.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean } from 'class-validator'; + +export class UserRights { + @ApiProperty({ description: 'Can view' }) + @IsBoolean() + canView: boolean; + + @ApiProperty({ description: 'Can edit' }) + @IsBoolean() + canEdit: boolean; + + @ApiProperty({ description: 'Can delete' }) + @IsBoolean() + canDelete: boolean; + + public static full(): UserRights { + return { canView: true, canEdit: true, canDelete: true }; + } + public static none(): UserRights { + return { canView: false, canEdit: false, canDelete: false }; + } +} diff --git a/backend/src/modules/iam/department-settings/department-settings.controller.ts b/backend/src/modules/iam/department-settings/department-settings.controller.ts new file mode 100644 index 0000000..1c02859 --- /dev/null +++ b/backend/src/modules/iam/department-settings/department-settings.controller.ts @@ -0,0 +1,67 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { JwtAuthorized, CurrentAuth, AuthData, UserAccess } from '../common'; +import { CreateDepartmentSettingsDto, DepartmentSettingsDto, UpdateDepartmentSettingsDto } from './dto'; +import { DepartmentSettingsService } from './department-settings.service'; + +@ApiTags('IAM/departments') +@Controller('departments/:departmentId/settings') +@JwtAuthorized() +@TransformToDto() +export class DepartmentSettingsController { + constructor(private readonly service: DepartmentSettingsService) {} + + @ApiOperation({ summary: 'Create department settings', description: 'Create settings for department' }) + @ApiParam({ name: 'departmentId', description: 'Department ID' }) + @ApiBody({ description: 'Data for creating department settings', type: CreateDepartmentSettingsDto }) + @ApiCreatedResponse({ description: 'Department settings', type: DepartmentSettingsDto }) + @Post() + @UserAccess({ adminOnly: true }) + async create( + @CurrentAuth() { accountId }: AuthData, + @Param('departmentId', ParseIntPipe) departmentId: number, + @Body() dto: CreateDepartmentSettingsDto, + ) { + return this.service.create({ accountId, departmentId, dto }); + } + + @ApiOperation({ summary: 'Get department settings', description: 'Get settings for department' }) + @ApiParam({ name: 'departmentId', description: 'Department ID' }) + @ApiOkResponse({ description: 'Department settings', type: DepartmentSettingsDto }) + @Get() + public async findOne( + @CurrentAuth() { accountId }: AuthData, + @Param('departmentId', ParseIntPipe) departmentId: number, + ) { + return this.service.findOne({ accountId, departmentId }, { applyParent: true }); + } + + @ApiOperation({ summary: 'Update department settings', description: 'Update settings for department' }) + @ApiParam({ name: 'departmentId', description: 'Department ID' }) + @ApiBody({ description: 'Data for updating department settings', type: UpdateDepartmentSettingsDto }) + @ApiOkResponse({ description: 'Department settings', type: DepartmentSettingsDto }) + @Patch() + @UserAccess({ adminOnly: true }) + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('departmentId', ParseIntPipe) departmentId: number, + @Body() dto: UpdateDepartmentSettingsDto, + ) { + return this.service.update({ accountId, departmentId, dto }); + } + + @ApiOperation({ summary: 'Delete department settings', description: 'Delete settings for department' }) + @ApiParam({ name: 'departmentId', description: 'Department ID' }) + @ApiOkResponse() + @Delete() + @UserAccess({ adminOnly: true }) + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('departmentId', ParseIntPipe) departmentId: number, + ) { + return this.service.delete({ accountId, departmentId }); + } +} diff --git a/backend/src/modules/iam/department-settings/department-settings.service.ts b/backend/src/modules/iam/department-settings/department-settings.service.ts new file mode 100644 index 0000000..59fe702 --- /dev/null +++ b/backend/src/modules/iam/department-settings/department-settings.service.ts @@ -0,0 +1,84 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { DepartmentService } from '../department/department.service'; +import { CreateDepartmentSettingsDto, UpdateDepartmentSettingsDto } from './dto'; +import { DepartmentSettings } from './entities'; + +interface FindFilter { + accountId: number; + departmentId: number; +} +interface FindOptions { + applyParent?: boolean; +} + +const cacheKey = ({ accountId, departmentId }: { accountId: number; departmentId: number }) => + `DepartmentSettings:${accountId}:${departmentId}`; + +@Injectable() +export class DepartmentSettingsService { + constructor( + @InjectRepository(DepartmentSettings) + private readonly repository: Repository, + private readonly dataSource: DataSource, + @Inject(forwardRef(() => DepartmentService)) + private readonly departmentService: DepartmentService, + ) {} + + public async create({ + accountId, + departmentId, + dto, + }: { + accountId: number; + departmentId: number; + dto: CreateDepartmentSettingsDto; + }): Promise { + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, departmentId })]); + return this.repository.save(DepartmentSettings.fromDto(accountId, departmentId, dto)); + } + + public async findOne(filter: FindFilter, option?: FindOptions): Promise { + const settings = await this.repository.findOne({ + where: { accountId: filter.accountId, departmentId: filter.departmentId }, + cache: { id: cacheKey(filter), milliseconds: 86400000 }, + }); + if (!settings && option?.applyParent) { + const department = await this.departmentService.findOne({ + accountId: filter.accountId, + departmentId: filter.departmentId, + }); + if (department?.parentId) { + return this.findOne({ accountId: filter.accountId, departmentId: department.parentId }, option); + } + } + return settings; + } + + public async update({ + accountId, + departmentId, + dto, + }: { + accountId: number; + departmentId: number; + dto: UpdateDepartmentSettingsDto; + }): Promise { + const current = await this.findOne({ accountId, departmentId }); + if (current) { + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, departmentId })]); + await this.repository.save(current.update(dto)); + + return current; + } else { + return this.create({ accountId, departmentId, dto }); + } + } + + public async delete({ accountId, departmentId }: { accountId: number; departmentId: number }) { + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, departmentId })]); + await this.repository.delete({ accountId, departmentId }); + } +} diff --git a/backend/src/modules/iam/department-settings/dto/create-department-settings.dto.ts b/backend/src/modules/iam/department-settings/dto/create-department-settings.dto.ts new file mode 100644 index 0000000..e1236d4 --- /dev/null +++ b/backend/src/modules/iam/department-settings/dto/create-department-settings.dto.ts @@ -0,0 +1,6 @@ +import { PartialType, PickType } from '@nestjs/swagger'; +import { DepartmentSettingsDto } from './department-settings.dto'; + +export class CreateDepartmentSettingsDto extends PartialType( + PickType(DepartmentSettingsDto, ['workingDays', 'workingTimeFrom', 'workingTimeTo', 'timeZone'] as const), +) {} diff --git a/backend/src/modules/iam/department-settings/dto/department-settings.dto.ts b/backend/src/modules/iam/department-settings/dto/department-settings.dto.ts new file mode 100644 index 0000000..85e94ca --- /dev/null +++ b/backend/src/modules/iam/department-settings/dto/department-settings.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class DepartmentSettingsDto { + @ApiProperty({ description: 'Department ID' }) + @IsNumber() + departmentId: number; + + @ApiPropertyOptional({ description: 'Working days of the department', nullable: true }) + @IsOptional() + @IsArray() + workingDays?: string[] | null; + + @ApiPropertyOptional({ description: 'Working time from of the department', nullable: true }) + @IsOptional() + @IsString() + workingTimeFrom?: string | null; + + @ApiPropertyOptional({ description: 'Working time to of the department', nullable: true }) + @IsOptional() + @IsString() + workingTimeTo?: string | null; + + @ApiPropertyOptional({ description: 'Time zone of the department', nullable: true }) + @IsOptional() + @IsString() + timeZone?: string | null; +} diff --git a/backend/src/modules/iam/department-settings/dto/index.ts b/backend/src/modules/iam/department-settings/dto/index.ts new file mode 100644 index 0000000..958b9c9 --- /dev/null +++ b/backend/src/modules/iam/department-settings/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-department-settings.dto'; +export * from './department-settings.dto'; +export * from './update-department-settings.dto'; diff --git a/backend/src/modules/iam/department-settings/dto/update-department-settings.dto.ts b/backend/src/modules/iam/department-settings/dto/update-department-settings.dto.ts new file mode 100644 index 0000000..360f913 --- /dev/null +++ b/backend/src/modules/iam/department-settings/dto/update-department-settings.dto.ts @@ -0,0 +1,6 @@ +import { PartialType, PickType } from '@nestjs/swagger'; +import { DepartmentSettingsDto } from './department-settings.dto'; + +export class UpdateDepartmentSettingsDto extends PartialType( + PickType(DepartmentSettingsDto, ['workingDays', 'workingTimeFrom', 'workingTimeTo', 'timeZone'] as const), +) {} diff --git a/backend/src/modules/iam/department-settings/entities/department-settings.entity.ts b/backend/src/modules/iam/department-settings/entities/department-settings.entity.ts new file mode 100644 index 0000000..2727831 --- /dev/null +++ b/backend/src/modules/iam/department-settings/entities/department-settings.entity.ts @@ -0,0 +1,73 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { CreateDepartmentSettingsDto, UpdateDepartmentSettingsDto, DepartmentSettingsDto } from '../dto'; + +@Entity() +export class DepartmentSettings { + @PrimaryColumn() + departmentId: number; + + @Column() + accountId: number; + + @Column({ type: 'simple-array', nullable: true }) + workingDays: string[] | null; + + @Column({ type: 'time', nullable: true }) + workingTimeFrom: string | null; + + @Column({ type: 'time', nullable: true }) + workingTimeTo: string | null; + + @Column({ nullable: true }) + timeZone: string | null; + + constructor( + accountId: number, + departmentId: number, + workingDays: string[] | null, + workingTimeFrom: string | null, + workingTimeTo: string | null, + timeZone: string | null, + ) { + this.accountId = accountId; + this.departmentId = departmentId; + this.workingDays = workingDays; + this.workingTimeFrom = workingTimeFrom; + this.workingTimeTo = workingTimeTo; + this.timeZone = timeZone; + } + + public static fromDto( + accountId: number, + departmentId: number, + dto: CreateDepartmentSettingsDto | UpdateDepartmentSettingsDto, + ): DepartmentSettings { + return new DepartmentSettings( + accountId, + departmentId, + dto?.workingDays ?? null, + dto?.workingTimeFrom ?? null, + dto?.workingTimeTo ?? null, + dto?.timeZone ?? null, + ); + } + + public update(dto: UpdateDepartmentSettingsDto): DepartmentSettings { + this.workingDays = dto.workingDays !== undefined ? dto.workingDays : this.workingDays; + this.workingTimeFrom = dto.workingTimeFrom !== undefined ? dto.workingTimeFrom : this.workingTimeFrom; + this.workingTimeTo = dto.workingTimeTo !== undefined ? dto.workingTimeTo : this.workingTimeTo; + this.timeZone = dto.timeZone !== undefined ? dto.timeZone : this.timeZone; + + return this; + } + + public toDto(): DepartmentSettingsDto { + return { + departmentId: this.departmentId, + workingDays: this.workingDays, + workingTimeFrom: this.workingTimeFrom?.substring(0, 5) ?? null, + workingTimeTo: this.workingTimeTo?.substring(0, 5) ?? null, + timeZone: this.timeZone, + }; + } +} diff --git a/backend/src/modules/iam/department-settings/entities/index.ts b/backend/src/modules/iam/department-settings/entities/index.ts new file mode 100644 index 0000000..4c5f3f1 --- /dev/null +++ b/backend/src/modules/iam/department-settings/entities/index.ts @@ -0,0 +1 @@ +export * from './department-settings.entity'; diff --git a/backend/src/modules/iam/department-settings/index.ts b/backend/src/modules/iam/department-settings/index.ts new file mode 100644 index 0000000..b7afeb0 --- /dev/null +++ b/backend/src/modules/iam/department-settings/index.ts @@ -0,0 +1,4 @@ +export * from './department-settings.controller'; +export * from './department-settings.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/modules/iam/department/department.controller.ts b/backend/src/modules/iam/department/department.controller.ts new file mode 100644 index 0000000..b92e33f --- /dev/null +++ b/backend/src/modules/iam/department/department.controller.ts @@ -0,0 +1,61 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData, CurrentAuth, JwtAuthorized, UserAccess } from '../common'; + +import { DepartmentDto, CreateDepartmentDto, UpdateDepartmentDto, DeleteDepartmentDto } from './dto'; +import { DepartmentService } from './department.service'; + +@ApiTags('IAM/departments') +@Controller('departments') +@JwtAuthorized() +@TransformToDto() +export class DepartmentController { + constructor(private readonly service: DepartmentService) {} + + @ApiOperation({ summary: 'Create department', description: 'Create department' }) + @ApiBody({ description: 'Data for creating department', type: CreateDepartmentDto }) + @ApiCreatedResponse({ description: 'Department', type: DepartmentDto }) + @Post() + @UserAccess({ adminOnly: true }) + async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateDepartmentDto) { + return await this.service.create({ accountId, dto }); + } + + @ApiOperation({ summary: 'Get department hierarchy', description: 'Get departments in hierarchical view' }) + @ApiOkResponse({ description: 'List of top level departments', type: [DepartmentDto] }) + @Get() + public async getHierarchy(@CurrentAuth() { accountId }: AuthData) { + return await this.service.getHierarchy({ accountId, expand: ['settings'] }); + } + + @ApiOperation({ summary: 'Update department', description: 'Update department information' }) + @ApiParam({ name: 'departmentId', description: 'Department ID' }) + @ApiBody({ description: 'Data for updating department', type: UpdateDepartmentDto }) + @ApiOkResponse({ description: 'Department', type: DepartmentDto }) + @Patch(':departmentId') + @UserAccess({ adminOnly: true }) + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('departmentId', ParseIntPipe) departmentId: number, + @Body() dto: UpdateDepartmentDto, + ): Promise { + return await this.service.update({ accountId, departmentId, dto }); + } + + @ApiOperation({ summary: 'Delete department', description: 'Mark department as not active' }) + @ApiParam({ name: 'departmentId', description: 'Department ID' }) + @ApiQuery({ name: 'newDepartmentId', description: 'New department ID to reassign employees to', required: false }) + @ApiOkResponse() + @Delete(':departmentId') + @UserAccess({ adminOnly: true }) + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('departmentId', ParseIntPipe) departmentId: number, + @Query() dto: DeleteDepartmentDto, + ) { + return await this.service.softDelete({ accountId, departmentId, newDepartmentId: dto?.newDepartmentId }); + } +} diff --git a/backend/src/modules/iam/department/department.service.ts b/backend/src/modules/iam/department/department.service.ts new file mode 100644 index 0000000..d725583 --- /dev/null +++ b/backend/src/modules/iam/department/department.service.ts @@ -0,0 +1,172 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { IsNull, Repository } from 'typeorm'; + +import { DepartmentDeletedEvent, IamEventType } from '../common'; +import { DepartmentSettingsService } from '../department-settings/department-settings.service'; + +import { CreateDepartmentDto, UpdateDepartmentDto } from './dto'; +import { Department } from './entities'; +import { ExpandableField } from './types'; + +const cacheKey = ({ accountId, departmentId }: { accountId: number; departmentId: number }) => + `Department:${accountId}:${departmentId}`; +const cacheParentKey = ({ + accountId, + parentId, + active = true, +}: { + accountId: number; + parentId: number | null; + active?: boolean; +}) => `Department.parent:${accountId}:${parentId}:${active}`; + +@Injectable() +export class DepartmentService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Department) + private readonly repository: Repository, + @Inject(forwardRef(() => DepartmentSettingsService)) + private readonly departmentSettingsService: DepartmentSettingsService, + ) {} + + async create({ accountId, dto }: { accountId: number; dto: CreateDepartmentDto }): Promise { + const department = await this.repository.save(Department.fromDto(accountId, dto)); + + if (dto.settings) { + department.settings = await this.departmentSettingsService.create({ + accountId, + departmentId: department.id, + dto: dto.settings, + }); + } + + return department; + } + + async getHierarchy({ + accountId, + departmentId, + expand = [], + }: { + accountId: number; + departmentId?: number | null; + expand?: ExpandableField[]; + }): Promise { + const departments = await this.repository.find({ + where: { accountId, parentId: departmentId ?? IsNull(), isActive: true }, + order: { id: 'ASC' }, + cache: { id: cacheParentKey({ accountId, parentId: departmentId ?? null }), milliseconds: 15000 }, + }); + for (const department of departments) { + department.subordinates = await this.getHierarchy({ accountId, departmentId: department.id, expand }); + if (expand.includes('settings')) { + department.settings = await this.departmentSettingsService.findOne({ accountId, departmentId: department.id }); + } + } + return departments; + } + + async getSubordinatesIds({ + accountId, + departmentId, + fromParent, + }: { + accountId: number; + departmentId: number | null; + fromParent?: boolean; + }): Promise { + if (!departmentId) return []; + + let fromDepartmentId = departmentId; + if (fromParent) { + const department = await this.findOne({ accountId, departmentId }); + if (department?.parentId) fromDepartmentId = department.parentId; + } + + const departmentIds: number[] = [fromDepartmentId]; + const subordinates = await this.repository.find({ + where: { accountId, parentId: fromDepartmentId, isActive: true }, + order: { id: 'ASC' }, + cache: { id: cacheParentKey({ accountId, parentId: fromDepartmentId }), milliseconds: 15000 }, + }); + for (const subordinate of subordinates) { + const subordinateIds = await this.getSubordinatesIds({ + accountId, + departmentId: subordinate.id, + fromParent: false, + }); + departmentIds.push(...subordinateIds); + } + return departmentIds; + } + + async findOne({ accountId, departmentId }: { accountId: number; departmentId: number }): Promise { + return await this.repository.findOne({ + where: { accountId, id: departmentId }, + cache: { id: cacheKey({ accountId, departmentId }), milliseconds: 15000 }, + }); + } + + async update({ + accountId, + departmentId, + dto, + }: { + accountId: number; + departmentId: number; + dto: UpdateDepartmentDto; + }): Promise { + const department = await this.repository.findOneBy({ accountId, id: departmentId }); + await this.repository.save(department.update(dto)); + + department.subordinates = await this.getHierarchy({ accountId, departmentId: department.id, expand: ['settings'] }); + department.settings = dto.settings + ? await this.departmentSettingsService.update({ accountId, departmentId, dto: dto.settings }) + : await this.departmentSettingsService.findOne({ accountId, departmentId: department.id }); + + return department; + } + + /** + * Delete department and it subordinates. + * @param accountId accountId + * @param departmentId departmentId + */ + async delete({ accountId, departmentId }: { accountId: number; departmentId: number }) { + const subordinates = await this.repository.findBy({ accountId, parentId: departmentId }); + for (const subordinate of subordinates) { + await this.delete({ accountId, departmentId: subordinate.id }); + } + await this.repository.delete(departmentId); + } + + /** + * Mark department and it subordinates as inactive. + * @param accountId accountId + * @param departmentId departmentId + * @param newDepartmentId newDepartmentId + */ + async softDelete({ + accountId, + departmentId, + newDepartmentId, + }: { + accountId: number; + departmentId: number; + newDepartmentId?: number; + }) { + const subordinates = await this.repository.findBy({ accountId, parentId: departmentId }); + for (const subordinate of subordinates) { + await this.softDelete({ accountId, departmentId: subordinate.id, newDepartmentId }); + } + await this.repository.update({ accountId, id: departmentId }, { isActive: false }); + + this.eventEmitter.emit( + IamEventType.DepartmentDeleted, + new DepartmentDeletedEvent({ accountId, departmentId, newDepartmentId }), + ); + } +} diff --git a/backend/src/modules/iam/department/dto/create-department.dto.ts b/backend/src/modules/iam/department/dto/create-department.dto.ts new file mode 100644 index 0000000..04b6643 --- /dev/null +++ b/backend/src/modules/iam/department/dto/create-department.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +import { CreateDepartmentSettingsDto } from '../../department-settings/dto'; +import { DepartmentDto } from './department.dto'; + +export class CreateDepartmentDto extends PickType(DepartmentDto, ['name', 'parentId'] as const) { + @ApiPropertyOptional({ description: 'Department settings', type: CreateDepartmentSettingsDto, nullable: true }) + @IsOptional() + settings?: CreateDepartmentSettingsDto | null; +} diff --git a/backend/src/modules/iam/department/dto/delete-department.dto.ts b/backend/src/modules/iam/department/dto/delete-department.dto.ts new file mode 100644 index 0000000..37dc3f9 --- /dev/null +++ b/backend/src/modules/iam/department/dto/delete-department.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class DeleteDepartmentDto { + @ApiPropertyOptional({ description: 'New department ID to reassign employees to' }) + @IsOptional() + @IsNumber() + newDepartmentId?: number; +} diff --git a/backend/src/modules/iam/department/dto/department.dto.ts b/backend/src/modules/iam/department/dto/department.dto.ts new file mode 100644 index 0000000..e27ace2 --- /dev/null +++ b/backend/src/modules/iam/department/dto/department.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; +import { DepartmentSettingsDto } from '../../department-settings'; + +export class DepartmentDto { + @ApiProperty({ description: 'Department ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Department name' }) + @IsString() + name: string; + + @ApiPropertyOptional({ description: 'Parent department ID', nullable: true }) + @IsOptional() + @IsNumber() + parentId?: number | null; + + @ApiProperty({ description: 'Activity of the department' }) + @IsBoolean() + isActive: boolean; + + @ApiPropertyOptional({ description: 'Department settings', type: DepartmentSettingsDto, nullable: true }) + @IsOptional() + settings: DepartmentSettingsDto | null; + + @ApiProperty({ description: 'Subordinates of the department', type: [DepartmentDto], nullable: true }) + @IsOptional() + @IsArray() + subordinates: DepartmentDto[] | null; +} diff --git a/backend/src/modules/iam/department/dto/index.ts b/backend/src/modules/iam/department/dto/index.ts new file mode 100644 index 0000000..d06da81 --- /dev/null +++ b/backend/src/modules/iam/department/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-department.dto'; +export * from './delete-department.dto'; +export * from './department.dto'; +export * from './update-department.dto'; diff --git a/backend/src/modules/iam/department/dto/update-department.dto.ts b/backend/src/modules/iam/department/dto/update-department.dto.ts new file mode 100644 index 0000000..1588f46 --- /dev/null +++ b/backend/src/modules/iam/department/dto/update-department.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional, PartialType, PickType } from '@nestjs/swagger'; +import { IsOptional } from 'class-validator'; + +import { UpdateDepartmentSettingsDto } from '../../department-settings/dto'; +import { DepartmentDto } from './department.dto'; + +export class UpdateDepartmentDto extends PartialType(PickType(DepartmentDto, ['name', 'isActive'] as const)) { + @ApiPropertyOptional({ description: 'Department settings', type: UpdateDepartmentSettingsDto, nullable: true }) + @IsOptional() + settings?: UpdateDepartmentSettingsDto | null; +} diff --git a/backend/src/modules/iam/department/entities/department.entity.ts b/backend/src/modules/iam/department/entities/department.entity.ts new file mode 100644 index 0000000..fffc072 --- /dev/null +++ b/backend/src/modules/iam/department/entities/department.entity.ts @@ -0,0 +1,67 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DepartmentSettings } from '../../department-settings'; +import { CreateDepartmentDto, DepartmentDto, UpdateDepartmentDto } from '../dto'; + +@Entity() +export class Department { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column({ nullable: true }) + parentId: number | null; + + @Column({ default: true }) + isActive: boolean; + + @Column() + accountId: number; + + constructor(accountId: number, name: string, parentId: number | null, isActive = true) { + this.accountId = accountId; + this.name = name; + this.parentId = parentId; + this.isActive = isActive; + } + + private _subordinates: Department[]; + public get subordinates(): Department[] { + return this._subordinates; + } + public set subordinates(value: Department[]) { + this._subordinates = value; + } + + private _settings: DepartmentSettings; + public get settings(): DepartmentSettings { + return this._settings; + } + public set settings(value: DepartmentSettings) { + this._settings = value; + } + + public static fromDto(accountId: number, dto: CreateDepartmentDto): Department { + return new Department(accountId, dto.name, dto.parentId); + } + + public update(dto: UpdateDepartmentDto): Department { + this.name = dto.name ?? this.name; + this.isActive = dto.isActive !== undefined ? dto.isActive : this.isActive; + + return this; + } + + public toDto(): DepartmentDto { + return { + id: this.id, + name: this.name, + parentId: this.parentId, + isActive: this.isActive, + settings: this.settings?.toDto(), + subordinates: this.subordinates ? this.subordinates.map((s) => s.toDto()) : [], + }; + } +} diff --git a/backend/src/modules/iam/department/entities/index.ts b/backend/src/modules/iam/department/entities/index.ts new file mode 100644 index 0000000..936370b --- /dev/null +++ b/backend/src/modules/iam/department/entities/index.ts @@ -0,0 +1 @@ +export * from './department.entity'; diff --git a/backend/src/modules/iam/department/index.ts b/backend/src/modules/iam/department/index.ts new file mode 100644 index 0000000..5d12358 --- /dev/null +++ b/backend/src/modules/iam/department/index.ts @@ -0,0 +1,4 @@ +export * from './department.controller'; +export * from './department.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/modules/iam/department/types/expandable-field.ts b/backend/src/modules/iam/department/types/expandable-field.ts new file mode 100644 index 0000000..52275ad --- /dev/null +++ b/backend/src/modules/iam/department/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'settings'; diff --git a/backend/src/modules/iam/department/types/index.ts b/backend/src/modules/iam/department/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/iam/department/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/iam/iam.module.ts b/backend/src/modules/iam/iam.module.ts new file mode 100644 index 0000000..7a4b331 --- /dev/null +++ b/backend/src/modules/iam/iam.module.ts @@ -0,0 +1,120 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { StorageModule } from '@/modules/storage/storage.module'; + +import { Account } from './account/entities/account.entity'; +import { AccountService } from './account/account.service'; +import { AccountController } from './account/account.controller'; + +import { AccountApiAccess, AccountApiAccessService } from './account-api-access'; +import { AccountApiAccessController } from './account-api-access/account-api-access.controller'; + +import { AuthenticationService } from './authentication/authentication.service'; +import { AuthenticationController } from './authentication/authentication.controller'; + +import { AuthorizationService } from './authorization/authorization.service'; + +import { ObjectPermission, ObjectPermissionService } from './object-permission'; + +import { AccountSubscription, AccountSubscriptionController, AccountSubscriptionService } from './account-subscription'; + +import { UserProfile } from './user-profile/entities/user-profile.entity'; +import { UserProfileService } from './user-profile/user-profile.service'; +import { UserProfileController } from './user-profile/user-profile.controller'; + +import { User, UsersAccessibleUsers } from './user/entities'; +import { UserHandler } from './user/user.handler'; +import { UserService } from './user/user.service'; +import { UserController } from './user/user.controller'; + +import { AccountSettings, AccountSettingsController, AccountSettingsService } from './account-settings'; +import { Department, DepartmentController, DepartmentService } from './department'; +import { DepartmentSettings, DepartmentSettingsController, DepartmentSettingsService } from './department-settings'; +import { + PublicSubscriptionDiscountController, + SubscriptionDiscount, + SubscriptionDiscountController, + SubscriptionDiscountService, +} from './subscription-discount'; +import { UserToken, UserTokenController, UserTokenService } from './user-token'; +import { + UserCalendar, + UserCalendarController, + UserCalendarInterval, + UserCalendarIntervalService, + UserCalendarService, +} from './user-calendar'; +import { WorkingTimeService } from './working-time'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Account, + AccountApiAccess, + AccountSettings, + Department, + DepartmentSettings, + ObjectPermission, + AccountSubscription, + SubscriptionDiscount, + User, + UsersAccessibleUsers, + UserCalendar, + UserCalendarInterval, + UserProfile, + UserToken, + ]), + forwardRef(() => StorageModule), + ], + controllers: [ + AccountController, + AccountSettingsController, + AccountApiAccessController, + AuthenticationController, + DepartmentController, + DepartmentSettingsController, + AccountSubscriptionController, + UserController, + UserProfileController, + SubscriptionDiscountController, + PublicSubscriptionDiscountController, + UserTokenController, + UserCalendarController, + ], + providers: [ + AccountService, + AccountApiAccessService, + AccountSettingsService, + AuthenticationService, + AuthorizationService, + DepartmentService, + DepartmentSettingsService, + ObjectPermissionService, + AccountSubscriptionService, + SubscriptionDiscountService, + UserService, + UserHandler, + UserProfileService, + UserTokenService, + UserCalendarService, + UserCalendarIntervalService, + WorkingTimeService, + ], + exports: [ + AccountService, + AccountApiAccessService, + AccountSettingsService, + AccountSubscriptionService, + AuthorizationService, + AuthenticationService, + DepartmentService, + ObjectPermissionService, + SubscriptionDiscountService, + UserService, + UserTokenService, + UserCalendarService, + WorkingTimeService, + ], +}) +export class IAMModule {} diff --git a/backend/src/modules/iam/object-permission/dto/index.ts b/backend/src/modules/iam/object-permission/dto/index.ts new file mode 100644 index 0000000..cb9d564 --- /dev/null +++ b/backend/src/modules/iam/object-permission/dto/index.ts @@ -0,0 +1 @@ +export * from './object-permission.dto'; diff --git a/backend/src/modules/iam/object-permission/dto/object-permission.dto.ts b/backend/src/modules/iam/object-permission/dto/object-permission.dto.ts new file mode 100644 index 0000000..922569b --- /dev/null +++ b/backend/src/modules/iam/object-permission/dto/object-permission.dto.ts @@ -0,0 +1,41 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { PermissionLevel } from '../../common'; + +export class ObjectPermissionDto { + @ApiProperty({ description: 'The type of the object' }) + @IsString() + objectType: string; + + @ApiProperty({ nullable: true, description: 'The ID of the object' }) + @IsOptional() + @IsNumber() + objectId: number | null; + + @ApiProperty({ enum: PermissionLevel, description: 'The create permission level for the object' }) + @IsEnum(PermissionLevel) + createPermission: PermissionLevel; + + @ApiProperty({ enum: PermissionLevel, description: 'The view permission level for the object' }) + @IsEnum(PermissionLevel) + viewPermission: PermissionLevel; + + @ApiProperty({ enum: PermissionLevel, description: 'The edit permission level for the object' }) + @IsEnum(PermissionLevel) + editPermission: PermissionLevel; + + @ApiProperty({ enum: PermissionLevel, description: 'The delete permission level for the object' }) + @IsEnum(PermissionLevel) + deletePermission: PermissionLevel; + + @ApiPropertyOptional({ enum: PermissionLevel, description: 'The report view permission level for the object' }) + @IsOptional() + @IsEnum(PermissionLevel) + reportPermission?: PermissionLevel; + + @ApiPropertyOptional({ enum: PermissionLevel, description: 'The dashboard view permission level for the object' }) + @IsOptional() + @IsEnum(PermissionLevel) + dashboardPermission?: PermissionLevel; +} diff --git a/backend/src/modules/iam/object-permission/entities/index.ts b/backend/src/modules/iam/object-permission/entities/index.ts new file mode 100644 index 0000000..4bceab8 --- /dev/null +++ b/backend/src/modules/iam/object-permission/entities/index.ts @@ -0,0 +1 @@ +export * from './object-permission.entity'; diff --git a/backend/src/modules/iam/object-permission/entities/object-permission.entity.ts b/backend/src/modules/iam/object-permission/entities/object-permission.entity.ts new file mode 100644 index 0000000..5ecdd78 --- /dev/null +++ b/backend/src/modules/iam/object-permission/entities/object-permission.entity.ts @@ -0,0 +1,168 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { PermissionAction, PermissionLevel } from '../../common'; + +import { ObjectPermissionDto } from '../dto'; + +const validatePermission = ({ + permission, + hasDepartmentId, +}: { + permission: PermissionLevel; + hasDepartmentId: boolean; +}): PermissionLevel => { + return !hasDepartmentId && [PermissionLevel.SUBDEPARTMENT, PermissionLevel.DEPARTMENT].includes(permission) + ? PermissionLevel.RESPONSIBLE + : permission; +}; + +@Entity() +export class ObjectPermission { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + userId: number; + + @Column() + objectType: string; + + @Column({ nullable: true }) + objectId: number | null; + + @Column() + createPermission: PermissionLevel; + + @Column() + viewPermission: PermissionLevel; + + @Column() + editPermission: PermissionLevel; + + @Column() + deletePermission: PermissionLevel; + + @Column() + reportPermission: PermissionLevel; + + @Column() + dashboardPermission: PermissionLevel; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + userId: number, + objectType: string, + objectId: number | null, + createPermission: PermissionLevel, + viewPermission: PermissionLevel, + editPermission: PermissionLevel, + deletePermission: PermissionLevel, + reportPermission: PermissionLevel, + dashboardPermission: PermissionLevel, + createdAt?: Date, + ) { + this.accountId = accountId; + this.userId = userId; + this.objectType = objectType; + this.objectId = objectId; + this.createPermission = createPermission; + this.viewPermission = viewPermission; + this.editPermission = editPermission; + this.deletePermission = deletePermission; + this.reportPermission = reportPermission; + this.dashboardPermission = dashboardPermission; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public static fromDto({ + accountId, + userId, + hasDepartmentId, + dto, + }: { + accountId: number; + userId: number; + hasDepartmentId: boolean; + dto: ObjectPermissionDto; + }): ObjectPermission { + return new ObjectPermission( + accountId, + userId, + dto.objectType, + dto.objectId, + validatePermission({ permission: dto.createPermission, hasDepartmentId }), + validatePermission({ permission: dto.viewPermission, hasDepartmentId }), + validatePermission({ permission: dto.editPermission, hasDepartmentId }), + validatePermission({ permission: dto.deletePermission, hasDepartmentId }), + validatePermission({ + permission: dto.reportPermission ?? dto.editPermission, + hasDepartmentId, + }), + validatePermission({ + permission: dto.dashboardPermission ?? dto.editPermission, + hasDepartmentId, + }), + ); + } + + public update({ hasDepartmentId, dto }: { hasDepartmentId: boolean; dto: ObjectPermissionDto }): ObjectPermission { + this.objectType = dto.objectType; + this.objectId = dto.objectId; + this.createPermission = validatePermission({ permission: dto.createPermission, hasDepartmentId }); + this.viewPermission = validatePermission({ permission: dto.viewPermission, hasDepartmentId }); + this.editPermission = validatePermission({ permission: dto.editPermission, hasDepartmentId }); + this.deletePermission = validatePermission({ permission: dto.deletePermission, hasDepartmentId }); + this.reportPermission = validatePermission({ + permission: dto.reportPermission ?? dto.editPermission, + hasDepartmentId, + }); + this.dashboardPermission = validatePermission({ + permission: dto.dashboardPermission ?? dto.editPermission, + hasDepartmentId, + }); + + return this; + } + + public getPermissionLevel(action: PermissionAction): PermissionLevel { + switch (action) { + case PermissionAction.Create: + return this.createPermission; + case PermissionAction.View: + return this.viewPermission; + case PermissionAction.Edit: + return this.editPermission; + case PermissionAction.Delete: + return this.deletePermission; + case PermissionAction.Report: + return this.reportPermission; + case PermissionAction.Dashboard: + return this.dashboardPermission; + } + } + + public toDto(): ObjectPermissionDto { + return { + objectType: this.objectType, + objectId: this.objectId, + createPermission: this.createPermission, + viewPermission: this.viewPermission, + editPermission: this.editPermission, + deletePermission: this.deletePermission, + reportPermission: this.reportPermission, + dashboardPermission: this.dashboardPermission, + }; + } + + public same({ objectType, objectId }: { objectType: string; objectId: number | null }): boolean { + return this.objectType === objectType && this.objectId === objectId; + } +} diff --git a/backend/src/modules/iam/object-permission/errors/index.ts b/backend/src/modules/iam/object-permission/errors/index.ts new file mode 100644 index 0000000..92caa23 --- /dev/null +++ b/backend/src/modules/iam/object-permission/errors/index.ts @@ -0,0 +1 @@ +export * from './permission-not-valid.error'; diff --git a/backend/src/modules/iam/object-permission/errors/permission-not-valid.error.ts b/backend/src/modules/iam/object-permission/errors/permission-not-valid.error.ts new file mode 100644 index 0000000..82b55e1 --- /dev/null +++ b/backend/src/modules/iam/object-permission/errors/permission-not-valid.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class PermissionNotValidError extends ServiceError { + constructor(message = 'Permissions is not valid') { + super({ errorCode: 'permission_not_valid', status: HttpStatus.FORBIDDEN, message }); + } +} diff --git a/backend/src/modules/iam/object-permission/index.ts b/backend/src/modules/iam/object-permission/index.ts new file mode 100644 index 0000000..bd4281a --- /dev/null +++ b/backend/src/modules/iam/object-permission/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './errors'; +export * from './object-permission.service'; diff --git a/backend/src/modules/iam/object-permission/object-permission.service.ts b/backend/src/modules/iam/object-permission/object-permission.service.ts new file mode 100644 index 0000000..5aa98d1 --- /dev/null +++ b/backend/src/modules/iam/object-permission/object-permission.service.ts @@ -0,0 +1,146 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { PermissionLevel } from '../common'; + +import { ObjectPermissionDto } from './dto'; +import { ObjectPermission } from './entities'; +import { PermissionNotValidError } from './errors'; + +const PermissionWeight = { + [PermissionLevel.ALLOWED]: 4, + [PermissionLevel.DEPARTMENT]: 3, + [PermissionLevel.SUBDEPARTMENT]: 2, + [PermissionLevel.RESPONSIBLE]: 1, + [PermissionLevel.DENIED]: 0, +}; + +interface FindFilter { + accountId: number; + userId?: number; + objectType?: string; + objectId?: number; +} + +const isBigger = (permission1: PermissionLevel, permission2: PermissionLevel): boolean => { + return PermissionWeight[permission1] > PermissionWeight[permission2]; +}; + +const cacheKey = ({ accountId, userId, objectType, objectId }: FindFilter) => + // eslint-disable-next-line max-len + `ObjectPermission:${accountId}${userId ? `:${userId}` : ''}${objectType ? `:${objectType}` : ''}${objectId ? `:${objectId}` : ''}`; + +@Injectable() +export class ObjectPermissionService { + constructor( + @InjectRepository(ObjectPermission) + private readonly repository: Repository, + private readonly dataSource: DataSource, + ) {} + + public async create({ + accountId, + userId, + hasDepartmentId, + dtos, + }: { + accountId: number; + userId: number; + hasDepartmentId: boolean; + dtos: ObjectPermissionDto[]; + }): Promise { + if (!this.check(dtos)) { + throw new PermissionNotValidError(); + } + + return this.repository.save( + dtos.map((dto) => ObjectPermission.fromDto({ accountId, userId, hasDepartmentId, dto })), + ); + } + + public async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).cache(cacheKey(filter), 60000).getOne(); + } + public async findMany(filter: FindFilter): Promise { + return this.createFindQb(filter).cache(cacheKey(filter), 60000).getMany(); + } + + public async update({ + accountId, + userId, + hasDepartmentId, + dtos, + }: { + accountId: number; + userId: number; + hasDepartmentId: boolean; + dtos: ObjectPermissionDto[]; + }): Promise { + if (!this.check(dtos)) { + throw new PermissionNotValidError(); + } + + const permissions = await this.findMany({ accountId, userId }); + + const created = dtos.filter((dto) => !permissions.some((p) => p.same(dto))); + const updated = permissions.filter((p) => dtos.some((dto) => p.same(dto))); + const deleted = permissions.filter((p) => !dtos.some((dto) => p.same(dto))); + + const result: ObjectPermission[] = []; + if (created.length) { + result.push(...(await this.create({ accountId, userId, hasDepartmentId, dtos: created }))); + } + + if (updated.length) { + for (const u of updated) { + const dto = dtos.find((dto) => u.same(dto)); + if (dto) { + await this.repository.save(u.update({ hasDepartmentId, dto })); + result.push(u); + } + } + } + + if (deleted.length) { + await this.repository.remove(deleted); + } + + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, userId })]); + + return result; + } + + public async delete(filter: FindFilter): Promise { + await this.createFindQb(filter).delete().execute(); + this.dataSource.queryResultCache?.remove([cacheKey(filter)]); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId: filter.accountId }); + + if (filter.userId) { + qb.andWhere('user_id = :userId', { userId: filter.userId }); + } + if (filter.objectType) { + qb.andWhere('object_type = :objectType', { objectType: filter.objectType }); + } + if (filter.objectId) { + qb.andWhere('object_id = :objectId', { objectId: filter.objectId }); + } + + return qb; + } + + private check(dtos: ObjectPermissionDto[]): boolean { + for (const dto of dtos) { + if (isBigger(dto.editPermission, dto.viewPermission) || isBigger(dto.deletePermission, dto.editPermission)) { + return false; + } + if (dto.createPermission === PermissionLevel.ALLOWED && dto.viewPermission === PermissionLevel.DENIED) { + return false; + } + } + return true; + } +} diff --git a/backend/src/modules/iam/subscription-discount/dto/current-discount.dto.ts b/backend/src/modules/iam/subscription-discount/dto/current-discount.dto.ts new file mode 100644 index 0000000..82d6040 --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/dto/current-discount.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsNumber, IsOptional } from 'class-validator'; + +export class CurrentDiscountDto { + @ApiProperty({ description: 'Discount percent', example: 10 }) + @IsNumber() + percent: number; + + @ApiProperty({ description: 'Discount end date in ISO format', example: new Date() }) + @IsDateString() + endAt: string; + + @ApiPropertyOptional({ nullable: true, description: 'Discount code' }) + @IsOptional() + @IsNumber() + code?: string | null; +} diff --git a/backend/src/modules/iam/subscription-discount/dto/index.ts b/backend/src/modules/iam/subscription-discount/dto/index.ts new file mode 100644 index 0000000..5dd0a19 --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/dto/index.ts @@ -0,0 +1 @@ +export * from './current-discount.dto'; diff --git a/backend/src/modules/iam/subscription-discount/entities/index.ts b/backend/src/modules/iam/subscription-discount/entities/index.ts new file mode 100644 index 0000000..ab1b971 --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/entities/index.ts @@ -0,0 +1 @@ +export * from './subscription-discount.entity'; diff --git a/backend/src/modules/iam/subscription-discount/entities/subscription-discount.entity.ts b/backend/src/modules/iam/subscription-discount/entities/subscription-discount.entity.ts new file mode 100644 index 0000000..fcd01ff --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/entities/subscription-discount.entity.ts @@ -0,0 +1,26 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity() +export class SubscriptionDiscount { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + days: number; + + @Column() + percent: number; + + @Column({ nullable: true }) + code: string | null; + + @Column({ nullable: true }) + validUntil: Date | null; + + constructor(days: number, percent: number, code?: string | null, validUntil?: Date | null) { + this.days = days; + this.percent = percent; + this.code = code ?? null; + this.validUntil = validUntil ?? null; + } +} diff --git a/backend/src/modules/iam/subscription-discount/index.ts b/backend/src/modules/iam/subscription-discount/index.ts new file mode 100644 index 0000000..ad093f6 --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/index.ts @@ -0,0 +1,6 @@ +export * from './dto'; +export * from './entities'; +export * from './public-subscription-discount.controller'; +export * from './subscription-discount.controller'; +export * from './subscription-discount.service'; +export * from './types'; diff --git a/backend/src/modules/iam/subscription-discount/public-subscription-discount.controller.ts b/backend/src/modules/iam/subscription-discount/public-subscription-discount.controller.ts new file mode 100644 index 0000000..a7fc90e --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/public-subscription-discount.controller.ts @@ -0,0 +1,26 @@ +import { BadRequestException, Controller, Get, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { CurrentDiscountDto } from './dto'; +import { SubscriptionDiscountService } from './subscription-discount.service'; + +@ApiTags('IAM/subscriptions') +@Controller('subscriptions/discount') +@TransformToDto() +export class PublicSubscriptionDiscountController { + constructor(private readonly service: SubscriptionDiscountService) {} + + @ApiOperation({ summary: 'Get discount by date', description: 'Get discount by date' }) + @ApiParam({ name: 'date', type: Date, required: true, description: 'Date to check discount' }) + @ApiOkResponse({ description: 'Current account discount', type: CurrentDiscountDto }) + @Get('date') + public async findOne(@Query('date') date: string) { + if (!date) { + return new BadRequestException(); + } + + return this.service.findByDate(new Date(date)); + } +} diff --git a/backend/src/modules/iam/subscription-discount/subscription-discount.controller.ts b/backend/src/modules/iam/subscription-discount/subscription-discount.controller.ts new file mode 100644 index 0000000..fb3a1e6 --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/subscription-discount.controller.ts @@ -0,0 +1,24 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { JwtAuthorized, CurrentAuth, AuthData } from '../common'; + +import { CurrentDiscountDto } from './dto'; +import { SubscriptionDiscountService } from './subscription-discount.service'; + +@ApiTags('IAM/subscriptions') +@Controller('subscriptions/discount') +@JwtAuthorized() +@TransformToDto() +export class SubscriptionDiscountController { + constructor(private readonly service: SubscriptionDiscountService) {} + + @ApiOperation({ summary: 'Get current discount for account', description: 'Get current discount for account' }) + @ApiOkResponse({ description: 'Current account discount', type: CurrentDiscountDto }) + @Get('current') + public async findOne(@CurrentAuth() { accountId }: AuthData) { + return this.service.findByAccount(accountId); + } +} diff --git a/backend/src/modules/iam/subscription-discount/subscription-discount.service.ts b/backend/src/modules/iam/subscription-discount/subscription-discount.service.ts new file mode 100644 index 0000000..9d8497c --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/subscription-discount.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { AccountSubscriptionService } from '../account-subscription/account-subscription.service'; +import { SubscriptionDiscount } from './entities'; +import { CurrentDiscount } from './types'; +import { DateUtil } from '@/common'; + +@Injectable() +export class SubscriptionDiscountService { + constructor( + @InjectRepository(SubscriptionDiscount) + private readonly repository: Repository, + private readonly subscriptionService: AccountSubscriptionService, + ) {} + + async findByAccount(accountId: number): Promise { + const subscription = await this.subscriptionService.get(accountId); + + return subscription?.firstVisit ? this.findByDate(subscription.firstVisit) : null; + } + + async findByDate(date: Date): Promise { + const discount = await this.createFindQb(date).limit(1).getOne(); + + if (discount) { + const endAt = DateUtil.add(date, { days: discount.days }); + return new CurrentDiscount({ percent: discount.percent, endAt, code: discount.code }); + } + + return null; + } + + async findMany(date: Date): Promise { + const discounts = await this.createFindQb(date).getMany(); + return discounts.map( + (discount) => + new CurrentDiscount({ + percent: discount.percent, + endAt: DateUtil.add(date, { days: discount.days }), + code: discount.code, + }), + ); + } + + private createFindQb(date: Date) { + const days = DateUtil.diff({ startDate: date, endDate: DateUtil.now(), unit: 'day', abs: false }); + return this.repository + .createQueryBuilder('discount') + .where('discount.days > :days', { days }) + .andWhere( + new Brackets((qb1) => + qb1.where('discount.valid_until > :date', { date }).orWhere('discount.valid_until is null'), + ), + ) + .orderBy('discount.valid_until', 'ASC') + .addOrderBy('discount.days', 'ASC'); + } +} diff --git a/backend/src/modules/iam/subscription-discount/types/current-discount.ts b/backend/src/modules/iam/subscription-discount/types/current-discount.ts new file mode 100644 index 0000000..5384993 --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/types/current-discount.ts @@ -0,0 +1,21 @@ +import { CurrentDiscountDto } from '../dto'; + +export class CurrentDiscount { + percent: number; + endAt: Date; + code: string | null; + + constructor({ percent, endAt, code }: { percent: number; endAt: Date; code: string | null }) { + this.percent = percent; + this.endAt = endAt; + this.code = code; + } + + toDto(): CurrentDiscountDto { + return { + percent: this.percent, + endAt: this.endAt.toISOString(), + code: this.code, + }; + } +} diff --git a/backend/src/modules/iam/subscription-discount/types/index.ts b/backend/src/modules/iam/subscription-discount/types/index.ts new file mode 100644 index 0000000..2566d44 --- /dev/null +++ b/backend/src/modules/iam/subscription-discount/types/index.ts @@ -0,0 +1 @@ +export * from './current-discount'; diff --git a/backend/src/modules/iam/user-calendar/dto/index.ts b/backend/src/modules/iam/user-calendar/dto/index.ts new file mode 100644 index 0000000..6da2fd8 --- /dev/null +++ b/backend/src/modules/iam/user-calendar/dto/index.ts @@ -0,0 +1,2 @@ +export * from './user-calendar-interval.dto'; +export * from './user-calendar.dto'; diff --git a/backend/src/modules/iam/user-calendar/dto/user-calendar-interval.dto.ts b/backend/src/modules/iam/user-calendar/dto/user-calendar-interval.dto.ts new file mode 100644 index 0000000..9e792af --- /dev/null +++ b/backend/src/modules/iam/user-calendar/dto/user-calendar-interval.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class UserCalendarIntervalDto { + @ApiProperty({ description: 'Day of week', examples: ['Monday', 'Sunday'] }) + @IsString() + dayOfWeek: string; + + @ApiProperty({ description: 'Interval time from', examples: ['09:00', '21:00'] }) + @IsString() + timeFrom: string; + + @ApiProperty({ description: 'Interval time to', examples: ['09:00', '21:00'] }) + @IsString() + timeTo: string; +} diff --git a/backend/src/modules/iam/user-calendar/dto/user-calendar.dto.ts b/backend/src/modules/iam/user-calendar/dto/user-calendar.dto.ts new file mode 100644 index 0000000..738d019 --- /dev/null +++ b/backend/src/modules/iam/user-calendar/dto/user-calendar.dto.ts @@ -0,0 +1,25 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { UserCalendarIntervalDto } from './user-calendar-interval.dto'; + +export class UserCalendarDto { + @ApiPropertyOptional({ description: 'Time buffer before appointment in seconds' }) + @IsOptional() + @IsNumber() + timeBufferBefore?: number | null; + + @ApiPropertyOptional({ description: 'Time buffer after appointment in seconds' }) + @IsOptional() + @IsNumber() + timeBufferAfter?: number | null; + + @ApiPropertyOptional({ description: 'Appointments limit per day' }) + @IsOptional() + @IsNumber() + appointmentLimit?: number | null; + + @ApiPropertyOptional({ type: [UserCalendarIntervalDto], description: 'User calendar intervals' }) + @IsOptional() + intervals?: UserCalendarIntervalDto[] | null; +} diff --git a/backend/src/modules/iam/user-calendar/entities/index.ts b/backend/src/modules/iam/user-calendar/entities/index.ts new file mode 100644 index 0000000..c5ba0de --- /dev/null +++ b/backend/src/modules/iam/user-calendar/entities/index.ts @@ -0,0 +1,2 @@ +export * from './user-calendar-interval.entity'; +export * from './user-calendar.entity'; diff --git a/backend/src/modules/iam/user-calendar/entities/user-calendar-interval.entity.ts b/backend/src/modules/iam/user-calendar/entities/user-calendar-interval.entity.ts new file mode 100644 index 0000000..bd146cb --- /dev/null +++ b/backend/src/modules/iam/user-calendar/entities/user-calendar-interval.entity.ts @@ -0,0 +1,51 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { UserCalendarIntervalDto } from '../dto'; + +@Entity() +export class UserCalendarInterval { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + calendarId: number; + + @Column() + dayOfWeek: string; + + @Column({ type: 'time' }) + timeFrom: string; + + @Column({ type: 'time' }) + timeTo: string; + + constructor(accountId: number, calendarId: number, dayOfWeek: string, timeFrom: string, timeTo: string) { + this.accountId = accountId; + this.calendarId = calendarId; + this.dayOfWeek = dayOfWeek; + this.timeFrom = timeFrom; + this.timeTo = timeTo; + } + + static fromDto({ + accountId, + calendarId, + dto, + }: { + accountId: number; + calendarId: number; + dto: UserCalendarIntervalDto; + }): UserCalendarInterval { + return new UserCalendarInterval(accountId, calendarId, dto.dayOfWeek, dto.timeFrom, dto.timeTo); + } + + toDto(): UserCalendarIntervalDto { + return { + dayOfWeek: this.dayOfWeek, + timeFrom: this.timeFrom.substring(0, 5), + timeTo: this.timeTo.substring(0, 5), + }; + } +} diff --git a/backend/src/modules/iam/user-calendar/entities/user-calendar.entity.ts b/backend/src/modules/iam/user-calendar/entities/user-calendar.entity.ts new file mode 100644 index 0000000..aeb663c --- /dev/null +++ b/backend/src/modules/iam/user-calendar/entities/user-calendar.entity.ts @@ -0,0 +1,76 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { UserCalendarDto } from '../dto'; +import { UserCalendarInterval } from './user-calendar-interval.entity'; + +@Entity() +export class UserCalendar { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + userId: number; + + @Column({ nullable: true }) + timeBufferBefore: number | null; + + @Column({ nullable: true }) + timeBufferAfter: number | null; + + @Column({ nullable: true }) + appointmentLimit: number | null; + + constructor( + accountId: number, + userId: number, + timeBufferBefore: number | null, + timeBufferAfter: number | null, + appointmentLimit: number | null, + ) { + this.accountId = accountId; + this.userId = userId; + this.timeBufferBefore = timeBufferBefore; + this.timeBufferAfter = timeBufferAfter; + this.appointmentLimit = appointmentLimit; + } + + private _intervals: UserCalendarInterval[] | null; + get intervals(): UserCalendarInterval[] | null { + return this._intervals; + } + set intervals(value: UserCalendarInterval[] | null) { + this._intervals = value; + } + + static fromDto({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: UserCalendarDto; + }): UserCalendar { + return new UserCalendar(accountId, userId, dto.timeBufferBefore, dto.timeBufferAfter, dto.appointmentLimit); + } + + update(dto: UserCalendarDto): UserCalendar { + this.timeBufferBefore = dto.timeBufferBefore !== undefined ? dto.timeBufferBefore : this.timeBufferBefore; + this.timeBufferAfter = dto.timeBufferAfter !== undefined ? dto.timeBufferAfter : this.timeBufferAfter; + this.appointmentLimit = dto.appointmentLimit !== undefined ? dto.appointmentLimit : this.appointmentLimit; + + return this; + } + + toDto(): UserCalendarDto { + return { + timeBufferBefore: this.timeBufferBefore, + timeBufferAfter: this.timeBufferAfter, + appointmentLimit: this.appointmentLimit, + intervals: this._intervals?.map((interval) => interval.toDto()), + }; + } +} diff --git a/backend/src/modules/iam/user-calendar/index.ts b/backend/src/modules/iam/user-calendar/index.ts new file mode 100644 index 0000000..c0dc971 --- /dev/null +++ b/backend/src/modules/iam/user-calendar/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './services'; +export * from './user-calendar.controller'; diff --git a/backend/src/modules/iam/user-calendar/services/index.ts b/backend/src/modules/iam/user-calendar/services/index.ts new file mode 100644 index 0000000..97e2fd7 --- /dev/null +++ b/backend/src/modules/iam/user-calendar/services/index.ts @@ -0,0 +1,2 @@ +export * from './user-calendar-interval.service'; +export * from './user-calendar.service'; diff --git a/backend/src/modules/iam/user-calendar/services/user-calendar-interval.service.ts b/backend/src/modules/iam/user-calendar/services/user-calendar-interval.service.ts new file mode 100644 index 0000000..155dadc --- /dev/null +++ b/backend/src/modules/iam/user-calendar/services/user-calendar-interval.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { UserCalendarIntervalDto } from '../dto'; +import { UserCalendarInterval } from '../entities'; + +interface FindFilter { + accountId: number; + calendarId: number; +} + +@Injectable() +export class UserCalendarIntervalService { + constructor( + @InjectRepository(UserCalendarInterval) + private readonly repository: Repository, + ) {} + + async create({ + accountId, + calendarId, + dto, + }: { + accountId: number; + calendarId: number; + dto: UserCalendarIntervalDto; + }) { + return this.repository.save(UserCalendarInterval.fromDto({ accountId, calendarId, dto })); + } + async createMany({ + accountId, + calendarId, + dtos, + }: { + accountId: number; + calendarId: number; + dtos: UserCalendarIntervalDto[]; + }) { + return Promise.all(dtos.map((dto) => this.create({ accountId, calendarId, dto }))); + } + + async findMany(filter: FindFilter): Promise { + return this.createQb(filter).getMany(); + } + + async updateMany({ + accountId, + calendarId, + dtos, + }: { + accountId: number; + calendarId: number; + dtos: UserCalendarIntervalDto[]; + }) { + await this.deleteMany({ accountId, calendarId }); + return this.createMany({ accountId, calendarId, dtos }); + } + + async deleteMany({ accountId, calendarId }: FindFilter) { + await this.repository.delete({ accountId, calendarId }); + } + + private createQb({ accountId, calendarId }: FindFilter) { + return this.repository + .createQueryBuilder('interval') + .where('interval.account_id = :accountId', { accountId }) + .andWhere('interval.calendar_id = :calendarId', { calendarId }); + } +} diff --git a/backend/src/modules/iam/user-calendar/services/user-calendar.service.ts b/backend/src/modules/iam/user-calendar/services/user-calendar.service.ts new file mode 100644 index 0000000..73b2aff --- /dev/null +++ b/backend/src/modules/iam/user-calendar/services/user-calendar.service.ts @@ -0,0 +1,103 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { UserCalendarDto } from '../dto'; +import { UserCalendar } from '../entities'; +import { UserCalendarIntervalService } from './user-calendar-interval.service'; + +interface FindFilter { + accountId: number; + userId?: number; +} + +@Injectable() +export class UserCalendarService { + constructor( + @InjectRepository(UserCalendar) + private readonly repository: Repository, + private readonly intervalService: UserCalendarIntervalService, + ) {} + + async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: UserCalendarDto; + }): Promise { + const calendar = await this.repository.save(UserCalendar.fromDto({ accountId, userId, dto })); + if (dto.intervals) { + calendar.intervals = await this.intervalService.createMany({ + accountId, + calendarId: calendar.id, + dtos: dto.intervals, + }); + } + return calendar; + } + + async findOne(filter: FindFilter): Promise { + const calendar = await this.createQb(filter).getOne(); + if (calendar) { + calendar.intervals = await this.intervalService.findMany({ + accountId: filter.accountId, + calendarId: calendar.id, + }); + } + return calendar; + } + async findMany(filter: FindFilter): Promise { + const calendars = await this.createQb(filter).getMany(); + if (calendars.length) { + await Promise.all( + calendars.map(async (calendar) => { + calendar.intervals = await this.intervalService.findMany({ + accountId: filter.accountId, + calendarId: calendar.id, + }); + }), + ); + } + return calendars; + } + + async update({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: UserCalendarDto; + }): Promise { + const calendar = await this.findOne({ accountId, userId }); + if (!calendar) { + return this.create({ accountId, userId, dto }); + } + await this.repository.save(calendar.update(dto)); + if (dto.intervals) { + calendar.intervals = await this.intervalService.updateMany({ + accountId, + calendarId: calendar.id, + dtos: dto.intervals, + }); + } + return calendar; + } + + async delete({ accountId, userId }: { accountId: number; userId: number }) { + await this.repository.delete({ accountId, userId }); + } + + private createQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder('user_calendar'); + qb.where('user_calendar.account_id = :accountId', { accountId: filter.accountId }); + if (filter.userId) { + qb.andWhere('user_calendar.user_id = :userId', { userId: filter.userId }); + } + return qb; + } +} diff --git a/backend/src/modules/iam/user-calendar/user-calendar.controller.ts b/backend/src/modules/iam/user-calendar/user-calendar.controller.ts new file mode 100644 index 0000000..b4ea1d8 --- /dev/null +++ b/backend/src/modules/iam/user-calendar/user-calendar.controller.ts @@ -0,0 +1,59 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '../common'; + +import { UserCalendarDto } from './dto'; +import { UserCalendarService } from './services'; + +@ApiTags('IAM/users/calendar') +@Controller('users/:userId/calendar') +@JwtAuthorized() +@TransformToDto() +export class UserCalendarController { + constructor(private readonly service: UserCalendarService) {} + + @ApiOperation({ summary: 'Create user calendar', description: 'Create user calendar' }) + @ApiParam({ name: 'userId', type: Number, description: 'User id', required: true }) + @ApiBody({ type: UserCalendarDto, required: true, description: 'Create user calendar data' }) + @ApiCreatedResponse({ type: UserCalendarDto, description: 'Created user calendar' }) + @Post() + async create( + @CurrentAuth() { accountId }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Body() dto: UserCalendarDto, + ) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Get user calendar', description: 'Get user calendar' }) + @ApiParam({ name: 'userId', type: Number, description: 'User id', required: true }) + @ApiOkResponse({ type: UserCalendarDto, description: 'User calendar' }) + @Get() + async findOne(@CurrentAuth() { accountId }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return this.service.findOne({ accountId, userId }); + } + + @ApiOperation({ summary: 'Update user calendar', description: 'Update user calendar' }) + @ApiParam({ name: 'userId', type: Number, description: 'User id', required: true }) + @ApiBody({ type: UserCalendarDto, required: true, description: 'Update user calendar data' }) + @ApiOkResponse({ type: UserCalendarDto, description: 'Updated user calendar' }) + @Patch() + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Body() dto: UserCalendarDto, + ) { + return this.service.update({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Delete user calendar', description: 'Delete user calendar' }) + @ApiParam({ name: 'userId', type: Number, description: 'User id', required: true }) + @ApiOkResponse() + @Delete() + async delete(@CurrentAuth() { accountId }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return this.service.delete({ accountId, userId }); + } +} diff --git a/backend/src/modules/iam/user-profile/dto/index.ts b/backend/src/modules/iam/user-profile/dto/index.ts new file mode 100644 index 0000000..149f8be --- /dev/null +++ b/backend/src/modules/iam/user-profile/dto/index.ts @@ -0,0 +1,2 @@ +export * from './update-user-profile.dto'; +export * from './user-profile.dto'; diff --git a/backend/src/modules/iam/user-profile/dto/update-user-profile.dto.ts b/backend/src/modules/iam/user-profile/dto/update-user-profile.dto.ts new file mode 100644 index 0000000..57c06f5 --- /dev/null +++ b/backend/src/modules/iam/user-profile/dto/update-user-profile.dto.ts @@ -0,0 +1,5 @@ +import { OmitType } from '@nestjs/swagger'; + +import { UserProfileDto } from './user-profile.dto'; + +export class UpdateUserProfileDto extends OmitType(UserProfileDto, ['userId'] as const) {} diff --git a/backend/src/modules/iam/user-profile/dto/user-profile.dto.ts b/backend/src/modules/iam/user-profile/dto/user-profile.dto.ts new file mode 100644 index 0000000..3141abc --- /dev/null +++ b/backend/src/modules/iam/user-profile/dto/user-profile.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class UserProfileDto { + @ApiProperty({ description: 'User ID' }) + @IsNumber() + userId: number; + + @ApiPropertyOptional({ description: 'User birth date' }) + @IsOptional() + @IsString() + birthDate?: string; + + @ApiPropertyOptional({ description: 'User employment date' }) + @IsOptional() + @IsString() + employmentDate?: string; + + @ApiPropertyOptional({ description: 'Working time from of the department', nullable: true }) + @IsOptional() + @IsString() + workingTimeFrom?: string | null; + + @ApiPropertyOptional({ description: 'Working time to of the department', nullable: true }) + @IsOptional() + @IsString() + workingTimeTo?: string | null; +} diff --git a/backend/src/modules/iam/user-profile/entities/index.ts b/backend/src/modules/iam/user-profile/entities/index.ts new file mode 100644 index 0000000..5421eef --- /dev/null +++ b/backend/src/modules/iam/user-profile/entities/index.ts @@ -0,0 +1 @@ +export * from './user-profile.entity'; diff --git a/backend/src/modules/iam/user-profile/entities/user-profile.entity.ts b/backend/src/modules/iam/user-profile/entities/user-profile.entity.ts new file mode 100644 index 0000000..4f1ff82 --- /dev/null +++ b/backend/src/modules/iam/user-profile/entities/user-profile.entity.ts @@ -0,0 +1,62 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { UpdateUserProfileDto, UserProfileDto } from '../dto'; + +@Entity() +export class UserProfile { + @Column() + accountId: number; + + @PrimaryColumn() + userId: number; + + @Column({ nullable: true, default: null }) + birthDate: Date | null; + + @Column({ nullable: true, default: null }) + employmentDate: Date | null; + + @Column({ type: 'time', nullable: true }) + workingTimeFrom: string | null; + + @Column({ type: 'time', nullable: true }) + workingTimeTo: string | null; + + constructor( + accountId: number, + userId: number, + birthDate: Date | null, + employmentDate: Date | null, + workingTimeFrom: string | null, + workingTimeTo: string | null, + ) { + this.accountId = accountId; + this.userId = userId; + this.birthDate = birthDate; + this.employmentDate = employmentDate; + this.workingTimeFrom = workingTimeFrom; + this.workingTimeTo = workingTimeTo; + } + + public update(dto: UpdateUserProfileDto): UserProfile { + this.birthDate = dto.birthDate !== undefined ? DateUtil.fromISOString(dto.birthDate) : this.birthDate; + this.employmentDate = + dto.employmentDate !== undefined ? DateUtil.fromISOString(dto.employmentDate) : this.employmentDate; + this.workingTimeFrom = dto.workingTimeFrom !== undefined ? dto.workingTimeFrom : this.workingTimeFrom; + this.workingTimeTo = dto.workingTimeTo !== undefined ? dto.workingTimeTo : this.workingTimeTo; + + return this; + } + + public toDto(): UserProfileDto { + return { + userId: this.userId, + birthDate: this.birthDate?.toISOString() ?? null, + employmentDate: this.employmentDate?.toISOString() ?? null, + workingTimeFrom: this.workingTimeFrom?.substring(0, 5) ?? null, + workingTimeTo: this.workingTimeTo?.substring(0, 5) ?? null, + }; + } +} diff --git a/backend/src/modules/iam/user-profile/user-profile.controller.ts b/backend/src/modules/iam/user-profile/user-profile.controller.ts new file mode 100644 index 0000000..48a6cea --- /dev/null +++ b/backend/src/modules/iam/user-profile/user-profile.controller.ts @@ -0,0 +1,37 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Patch } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '../common'; +import { UserProfileDto, UpdateUserProfileDto } from './dto'; +import { UserProfileService } from './user-profile.service'; + +@ApiTags('IAM/users') +@Controller('users/:userId/profile') +@JwtAuthorized() +@TransformToDto() +export class UserProfileController { + constructor(private readonly service: UserProfileService) {} + + @ApiOperation({ summary: 'Get user profile', description: 'Get user profile' }) + @ApiParam({ name: 'userId', description: 'User ID', type: Number, required: true }) + @ApiOkResponse({ type: UserProfileDto, description: 'User profile' }) + @Get() + public async findOne(@CurrentAuth() { accountId }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return this.service.findOne({ accountId, userId }); + } + + @ApiOperation({ summary: 'Update user profile', description: 'Update user profile' }) + @ApiParam({ name: 'userId', description: 'User ID', type: Number, required: true }) + @ApiBody({ type: UpdateUserProfileDto, description: 'Date for update user profile', required: true }) + @ApiOkResponse({ type: UserProfileDto, description: 'User profile' }) + @Patch() + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Body() dto: UpdateUserProfileDto, + ) { + return this.service.update({ accountId, userId, dto }); + } +} diff --git a/backend/src/modules/iam/user-profile/user-profile.service.ts b/backend/src/modules/iam/user-profile/user-profile.service.ts new file mode 100644 index 0000000..9c50706 --- /dev/null +++ b/backend/src/modules/iam/user-profile/user-profile.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { UpdateUserProfileDto } from './dto'; +import { UserProfile } from './entities'; + +const cacheKey = ({ accountId, userId }: { accountId: number; userId: number }) => `UserProfile:${accountId}:${userId}`; + +@Injectable() +export class UserProfileService { + constructor( + @InjectRepository(UserProfile) + private readonly repository: Repository, + private readonly dataSource: DataSource, + ) {} + + public async create({ accountId, userId }: { accountId: number; userId: number }): Promise { + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, userId })]); + return this.repository.save(new UserProfile(accountId, userId, null, null, null, null)); + } + + public async findOne({ accountId, userId }: { accountId: number; userId: number }): Promise { + const profile = await this.repository.findOne({ + where: { accountId, userId }, + cache: { id: cacheKey({ accountId, userId }), milliseconds: 86400000 }, + }); + + return profile ?? this.create({ accountId, userId }); + } + + public async update({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: UpdateUserProfileDto; + }): Promise { + const profile = await this.findOne({ accountId, userId }); + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, userId })]); + await this.repository.save(profile.update(dto)); + + return profile; + } +} diff --git a/backend/src/modules/iam/user-token/dto/create-user-token.dto.ts b/backend/src/modules/iam/user-token/dto/create-user-token.dto.ts new file mode 100644 index 0000000..c905d75 --- /dev/null +++ b/backend/src/modules/iam/user-token/dto/create-user-token.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { UserTokenDto } from './user-token.dto'; + +export class CreateUserTokenDto extends PickType(UserTokenDto, ['name', 'expiresAt'] as const) {} diff --git a/backend/src/modules/iam/user-token/dto/index.ts b/backend/src/modules/iam/user-token/dto/index.ts new file mode 100644 index 0000000..12c6e66 --- /dev/null +++ b/backend/src/modules/iam/user-token/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-user-token.dto'; +export * from './user-access-token.dto'; +export * from './user-token.dto'; diff --git a/backend/src/modules/iam/user-token/dto/user-access-token.dto.ts b/backend/src/modules/iam/user-token/dto/user-access-token.dto.ts new file mode 100644 index 0000000..f49ab0b --- /dev/null +++ b/backend/src/modules/iam/user-token/dto/user-access-token.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { UserTokenDto } from './user-token.dto'; + +export class UserAccessTokenDto { + @ApiProperty({ description: 'User access token' }) + @IsString() + accessToken: string; + + @ApiProperty({ description: 'User token' }) + userToken: UserTokenDto; +} diff --git a/backend/src/modules/iam/user-token/dto/user-token.dto.ts b/backend/src/modules/iam/user-token/dto/user-token.dto.ts new file mode 100644 index 0000000..5c443e5 --- /dev/null +++ b/backend/src/modules/iam/user-token/dto/user-token.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class UserTokenDto { + @ApiProperty({ description: 'User access token ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'User access token name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'User access token created at' }) + @IsDateString() + createdAt: string; + + @ApiPropertyOptional({ description: 'User access token expires at' }) + @IsOptional() + @IsDateString() + expiresAt?: string | null; + + @ApiPropertyOptional({ description: 'User access token last used at' }) + @IsOptional() + @IsDateString() + lastUsedAt?: string | null; +} diff --git a/backend/src/modules/iam/user-token/entities/index.ts b/backend/src/modules/iam/user-token/entities/index.ts new file mode 100644 index 0000000..83bcabf --- /dev/null +++ b/backend/src/modules/iam/user-token/entities/index.ts @@ -0,0 +1 @@ +export * from './user-token.entity'; diff --git a/backend/src/modules/iam/user-token/entities/user-token.entity.ts b/backend/src/modules/iam/user-token/entities/user-token.entity.ts new file mode 100644 index 0000000..9417bca --- /dev/null +++ b/backend/src/modules/iam/user-token/entities/user-token.entity.ts @@ -0,0 +1,71 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { CreateUserTokenDto, UserTokenDto } from '../dto'; +import { DateUtil } from '@/common'; + +@Entity() +export class UserToken { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + userId: number; + + @Column() + name: string; + + @Column() + code: string; + + @Column() + createdAt: Date; + + @Column({ nullable: true, default: null }) + expiresAt: Date | null; + + @Column({ nullable: true, default: null }) + lastUsedAt: Date | null; + + constructor(accountId: number, userId: number, name: string, code: string, expiresAt?: Date | null) { + this.accountId = accountId; + this.userId = userId; + this.name = name; + this.code = code; + this.createdAt = new Date(); + this.expiresAt = expiresAt ?? null; + } + + static fromDto({ + accountId, + userId, + data, + }: { + accountId: number; + userId: number; + data: CreateUserTokenDto & { code: string }; + }): UserToken { + return new UserToken( + accountId, + userId, + data.name, + data.code, + data.expiresAt ? DateUtil.fromISOString(data.expiresAt) : null, + ); + } + + toDto(): UserTokenDto { + return { + id: this.id, + name: this.name, + createdAt: this.createdAt.toISOString(), + expiresAt: this.expiresAt?.toISOString(), + lastUsedAt: this.lastUsedAt?.toISOString(), + }; + } + + isExpired(): boolean { + return this.expiresAt ? this.expiresAt < DateUtil.now() : false; + } +} diff --git a/backend/src/modules/iam/user-token/index.ts b/backend/src/modules/iam/user-token/index.ts new file mode 100644 index 0000000..8ffa05c --- /dev/null +++ b/backend/src/modules/iam/user-token/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './types'; +export * from './user-token.controller'; +export * from './user-token.service'; diff --git a/backend/src/modules/iam/user-token/types/index.ts b/backend/src/modules/iam/user-token/types/index.ts new file mode 100644 index 0000000..dfd7398 --- /dev/null +++ b/backend/src/modules/iam/user-token/types/index.ts @@ -0,0 +1 @@ +export * from './user-access-token'; diff --git a/backend/src/modules/iam/user-token/types/user-access-token.ts b/backend/src/modules/iam/user-token/types/user-access-token.ts new file mode 100644 index 0000000..589f749 --- /dev/null +++ b/backend/src/modules/iam/user-token/types/user-access-token.ts @@ -0,0 +1,19 @@ +import { UserAccessTokenDto } from '../dto'; +import { UserToken } from '../entities'; + +export class UserAccessToken { + accessToken: string; + userToken: UserToken; + + constructor({ accessToken, userToken }: { accessToken: string; userToken: UserToken }) { + this.accessToken = accessToken; + this.userToken = userToken; + } + + toDto(): UserAccessTokenDto { + return { + accessToken: this.accessToken, + userToken: this.userToken.toDto(), + }; + } +} diff --git a/backend/src/modules/iam/user-token/user-token.controller.ts b/backend/src/modules/iam/user-token/user-token.controller.ts new file mode 100644 index 0000000..f923380 --- /dev/null +++ b/backend/src/modules/iam/user-token/user-token.controller.ts @@ -0,0 +1,52 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { Subdomain, TransformToDto } from '@/common'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '../common'; + +import { CreateUserTokenDto, UserAccessTokenDto, UserTokenDto } from './dto'; +import { UserTokenService } from './user-token.service'; + +@ApiTags('IAM/users/tokens') +@Controller('users/my/tokens') +@JwtAuthorized() +@TransformToDto() +export class UserTokenController { + constructor(private readonly service: UserTokenService) {} + + @ApiOperation({ summary: 'Create user access token', description: 'Create user access token' }) + @ApiBody({ type: CreateUserTokenDto, required: true, description: 'Create user access token data' }) + @ApiCreatedResponse({ type: UserAccessTokenDto, description: 'Created user access token' }) + @Post() + async create( + @CurrentAuth() { accountId, userId }: AuthData, + @Subdomain() subdomain: string | null, + @Body() dto: CreateUserTokenDto, + ) { + return this.service.create({ accountId, userId, subdomain, dto }); + } + + @ApiOperation({ summary: 'Get user access token', description: 'Get user access token' }) + @ApiParam({ name: 'tokenId', type: Number, description: 'User access token id', required: true }) + @ApiOkResponse({ type: UserTokenDto, description: 'User access token' }) + @Get(':tokenId') + async findOne(@CurrentAuth() { accountId, userId }: AuthData, @Param('tokenId', ParseIntPipe) tokenId: number) { + return this.service.findOne({ accountId, userId, tokenId }); + } + + @ApiOperation({ summary: 'Get user access tokens', description: 'Get user access tokens' }) + @ApiOkResponse({ type: [UserTokenDto], description: 'User access tokens' }) + @Get() + async findMany(@CurrentAuth() { accountId, userId }: AuthData) { + return this.service.findMany({ accountId, userId }); + } + + @ApiOperation({ summary: 'Delete user access token', description: 'Delete user access token' }) + @ApiParam({ name: 'tokenId', type: Number, description: 'User access token id', required: true }) + @ApiOkResponse({ type: Number, description: 'Deleted user access token id' }) + @Delete(':tokenId') + async delete(@CurrentAuth() { accountId, userId }: AuthData, @Param('tokenId', ParseIntPipe) tokenId: number) { + return this.service.delete({ accountId, userId, tokenId }); + } +} diff --git a/backend/src/modules/iam/user-token/user-token.service.ts b/backend/src/modules/iam/user-token/user-token.service.ts new file mode 100644 index 0000000..08d39ab --- /dev/null +++ b/backend/src/modules/iam/user-token/user-token.service.ts @@ -0,0 +1,99 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { v4 as uuidv4 } from 'uuid'; + +import { DateUtil, TokenService } from '@/common'; + +import { CreateUserTokenDto } from './dto'; +import { UserToken } from './entities'; +import { UserAccessToken } from './types'; + +interface FindFilter { + accountId: number; + userId: number; + tokenId?: number; + code?: string; +} + +@Injectable() +export class UserTokenService { + constructor( + @InjectRepository(UserToken) + private readonly repository: Repository, + private readonly tokenService: TokenService, + ) {} + + async create({ + accountId, + subdomain, + userId, + dto, + }: { + accountId: number; + subdomain: string; + userId: number; + dto: CreateUserTokenDto; + }): Promise { + const code = uuidv4(); + const expiresIn = dto.expiresAt + ? DateUtil.diff({ startDate: DateUtil.now(), endDate: DateUtil.fromISOString(dto.expiresAt), unit: 'second' }) + : undefined; + const accessToken = this.tokenService.create( + { accountId, userId, subdomain, code }, + expiresIn ? { expiresIn } : undefined, + ); + + const userToken = UserToken.fromDto({ accountId, userId, data: { ...dto, code } }); + await this.repository.insert(userToken); + + return new UserAccessToken({ accessToken, userToken }); + } + + async findOne(filter: FindFilter): Promise { + return this.createQb(filter).getOne(); + } + async findMany(filter: FindFilter): Promise { + return this.createQb(filter).getMany(); + } + + async use(filter: FindFilter): Promise { + const token = await this.findOne(filter); + if (token) { + const now = DateUtil.now(); + await this.repository.update({ id: token.id }, { lastUsedAt: now }); + token.lastUsedAt = now; + } + + return token; + } + + async delete({ + accountId, + userId, + tokenId, + }: { + accountId: number; + userId: number; + tokenId: number; + }): Promise { + const { affected } = await this.repository.delete({ accountId, userId, id: tokenId }); + return affected ? tokenId : null; + } + + private createQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('token') + .where('token.account_id = :accountId', { accountId: filter.accountId }) + .andWhere('token.user_id = :userId', { userId: filter.userId }); + + if (filter.tokenId) { + qb.andWhere('token.id = :tokenId', { tokenId: filter.tokenId }); + } + if (filter.code) { + qb.andWhere('token.code = :code', { code: filter.code }); + } + + return qb; + } +} diff --git a/backend/src/modules/iam/user/dto/change-user-password.dto.ts b/backend/src/modules/iam/user/dto/change-user-password.dto.ts new file mode 100644 index 0000000..c6b574d --- /dev/null +++ b/backend/src/modules/iam/user/dto/change-user-password.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ChangeUserPasswordDto { + @ApiProperty({ description: 'Current password' }) + @IsString() + currentPassword: string; + + @ApiProperty({ description: 'New password' }) + @IsString() + newPassword: string; +} diff --git a/backend/src/modules/iam/user/dto/create-user.dto.ts b/backend/src/modules/iam/user/dto/create-user.dto.ts new file mode 100644 index 0000000..4337acd --- /dev/null +++ b/backend/src/modules/iam/user/dto/create-user.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty, ApiPropertyOptional, OmitType } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +import { UserDto } from './user.dto'; + +export class CreateUserDto extends OmitType(UserDto, [ + 'id', + 'isActive', + 'avatarUrl', + 'analyticsId', + 'isPlatformAdmin', +] as const) { + @ApiProperty({ description: 'User password' }) + @IsString() + password: string; + + @ApiPropertyOptional({ description: 'Is user active?' }) + @IsOptional() + @IsBoolean() + isActive?: boolean; + + @ApiPropertyOptional({ description: 'User analytics id' }) + @IsOptional() + @IsString() + analyticsId?: string; +} diff --git a/backend/src/modules/iam/user/dto/index.ts b/backend/src/modules/iam/user/dto/index.ts new file mode 100644 index 0000000..9bdf8f1 --- /dev/null +++ b/backend/src/modules/iam/user/dto/index.ts @@ -0,0 +1,5 @@ +export * from './change-user-password.dto'; +export * from './create-user.dto'; +export * from './update-user.dto'; +export * from './user-find-filter.dto'; +export * from './user.dto'; diff --git a/backend/src/modules/iam/user/dto/update-user.dto.ts b/backend/src/modules/iam/user/dto/update-user.dto.ts new file mode 100644 index 0000000..dca6e77 --- /dev/null +++ b/backend/src/modules/iam/user/dto/update-user.dto.ts @@ -0,0 +1,24 @@ +import { ApiPropertyOptional, OmitType, PartialType } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsOptional, IsString } from 'class-validator'; + +import { UserRole } from '../../common/enums/user-role.enum'; +import { UserDto } from './user.dto'; + +export class UpdateUserDto extends PartialType( + OmitType(UserDto, ['id', 'isActive', 'role', 'avatarUrl', 'analyticsId', 'isPlatformAdmin'] as const), +) { + @ApiPropertyOptional({ description: 'New password' }) + @IsOptional() + @IsString() + password?: string; + + @ApiPropertyOptional({ description: 'Is user active' }) + @IsOptional() + @IsBoolean() + isActive?: boolean; + + @ApiPropertyOptional({ enum: UserRole, description: 'User role' }) + @IsOptional() + @IsEnum(UserRole) + role?: UserRole; +} diff --git a/backend/src/modules/iam/user/dto/user-find-filter.dto.ts b/backend/src/modules/iam/user/dto/user-find-filter.dto.ts new file mode 100644 index 0000000..953309e --- /dev/null +++ b/backend/src/modules/iam/user/dto/user-find-filter.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class UserFindFilterDto { + @ApiPropertyOptional({ description: 'User full name, first name + last name, e.g. "John Doe"' }) + @IsOptional() + @IsString() + fullName?: string; +} diff --git a/backend/src/modules/iam/user/dto/user.dto.ts b/backend/src/modules/iam/user/dto/user.dto.ts new file mode 100644 index 0000000..b064176 --- /dev/null +++ b/backend/src/modules/iam/user/dto/user.dto.ts @@ -0,0 +1,70 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { UserRole } from '../../common/enums/user-role.enum'; +import { ObjectPermissionDto } from '../../object-permission/dto/object-permission.dto'; + +export class UserDto { + @ApiProperty({ description: 'User ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'User first name' }) + @IsString() + firstName: string; + + @ApiProperty({ nullable: true, description: 'User last name' }) + @IsOptional() + @IsString() + lastName: string | null; + + @ApiProperty({ description: 'User email' }) + @IsString() + email: string; + + @ApiProperty({ nullable: true, description: 'User phone' }) + @IsOptional() + @IsString() + phone: string | null; + + @ApiProperty({ enum: UserRole, description: 'User role' }) + @IsEnum(UserRole) + role: UserRole; + + @ApiProperty({ description: 'Is user active' }) + @IsBoolean() + isActive: boolean; + + @ApiPropertyOptional({ type: [ObjectPermissionDto], nullable: true, description: 'User object permissions' }) + @IsArray() + @IsOptional() + objectPermissions?: ObjectPermissionDto[] | null; + + @ApiPropertyOptional({ nullable: true, description: 'User department ID' }) + @IsOptional() + @IsNumber() + departmentId?: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'User position' }) + @IsOptional() + @IsString() + position?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'User analytics ID' }) + @IsOptional() + @IsString() + analyticsId?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'User avatar URL' }) + @IsOptional() + @IsString() + avatarUrl?: string | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Accessible user IDs' }) + @IsOptional() + @IsArray() + @IsNumber({}, { each: true }) + accessibleUserIds?: number[] | null; + + isPlatformAdmin: boolean; +} diff --git a/backend/src/modules/iam/user/entities/index.ts b/backend/src/modules/iam/user/entities/index.ts new file mode 100644 index 0000000..9c5f833 --- /dev/null +++ b/backend/src/modules/iam/user/entities/index.ts @@ -0,0 +1,2 @@ +export * from './user.entity'; +export * from './users-accessible-users.entity'; diff --git a/backend/src/modules/iam/user/entities/user.entity.ts b/backend/src/modules/iam/user/entities/user.entity.ts new file mode 100644 index 0000000..ffc794c --- /dev/null +++ b/backend/src/modules/iam/user/entities/user.entity.ts @@ -0,0 +1,181 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { v4 as uuidv4 } from 'uuid'; + +import { DateUtil, PasswordUtil } from '@/common'; + +import { UserRole } from '../../common'; +import { ObjectPermission } from '../../object-permission/entities'; +import { CreateUserDto, UpdateUserDto, UserDto } from '../dto'; +import { UsersAccessibleUsers } from './users-accessible-users.entity'; + +@Entity('users') +export class User { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + firstName: string; + + @Column() + lastName: string; + + @Column() + email: string; + + @Column({ nullable: true }) + phone: string | null; + + @Column() + password: string; + + @Column() + role: UserRole; + + @Column({ nullable: true }) + avatarId: string | null; + + @Column() + isActive: boolean; + + @Column({ default: false }) + isPlatformAdmin: boolean; + + @Column({ nullable: true }) + departmentId: number | null; + + @Column({ nullable: true }) + position: string | null; + + @Column() + analyticsId: string; + + @Column() + accountId: number; + + @Column({ default: 0 }) + loginAttempts: number; + + @Column({ nullable: true }) + lockUntil: Date | null; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + firstName: string, + lastName: string, + email: string, + password: string, + role: UserRole, + isActive: boolean, + departmentId: number | null, + position: string | null, + avatarId: string | null, + phone: string | null, + analyticsId: string, + createdAt?: Date, + ) { + this.accountId = accountId; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phone = phone; + this.avatarId = avatarId; + this.password = password; + this.role = role; + this.isActive = isActive; + this.departmentId = departmentId; + this.position = position; + this.analyticsId = analyticsId; + this.loginAttempts = 0; + this.lockUntil = null; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _avatarUrl: string | null; + public get avatarUrl(): string | null { + return this._avatarUrl; + } + public set avatarUrl(value: string | null) { + this._avatarUrl = value; + } + + private _objectPermissions: ObjectPermission[] | null; + public get objectPermissions(): ObjectPermission[] | null { + return this._objectPermissions; + } + public set objectPermissions(value: ObjectPermission[] | null) { + this._objectPermissions = value; + } + + private _accessibleUsers: UsersAccessibleUsers[] | null; + public get accessibleUsers(): UsersAccessibleUsers[] | null { + return this._accessibleUsers; + } + public set accessibleUsers(value: UsersAccessibleUsers[] | null) { + this._accessibleUsers = value; + } + + public static fromDto(accountId: number, dto: CreateUserDto, createdAt?: Date): User { + return new User( + accountId, + dto.firstName?.trim(), + dto.lastName?.trim(), + dto.email.trim(), + PasswordUtil.hash(dto.password), + dto.role, + dto.isActive ?? true, + dto.departmentId, + dto.position, + null, + dto.phone, + dto.analyticsId ?? uuidv4(), + createdAt, + ); + } + + public update(dto: UpdateUserDto): User { + this.firstName = dto.firstName !== undefined ? (dto.firstName ?? '').trim() : this.firstName; + this.lastName = dto.lastName !== undefined ? (dto.lastName ?? '').trim() : this.lastName; + this.email = dto.email !== undefined ? (dto.email ?? '').trim() : this.email; + this.phone = dto.phone !== undefined ? dto.phone : this.phone; + this.role = dto.role !== undefined ? dto.role : this.role; + this.isActive = dto.isActive !== undefined ? dto.isActive : this.isActive; + this.departmentId = dto.departmentId !== undefined ? dto.departmentId : this.departmentId; + this.position = dto.position !== undefined ? dto.position : this.position; + + if (dto.password) { + this.password = PasswordUtil.hash(dto.password); + } + + return this; + } + + public get fullName() { + return `${this.firstName} ${this.lastName ?? ''}`.trim(); + } + + public get isAdmin() { + return this.role === UserRole.ADMIN || this.role === UserRole.OWNER; + } + + public toDto(): UserDto { + return { + id: this.id, + firstName: this.firstName, + lastName: this.lastName, + email: this.email, + phone: this.phone, + role: this.role, + isActive: this.isActive, + departmentId: this.departmentId, + position: this.position, + avatarUrl: this.avatarUrl, + analyticsId: this.analyticsId, + objectPermissions: this.objectPermissions?.map((op) => op.toDto()), + accessibleUserIds: this.accessibleUsers?.map((au) => au.accessibleId), + isPlatformAdmin: this.isPlatformAdmin, + }; + } +} diff --git a/backend/src/modules/iam/user/entities/users-accessible-users.entity.ts b/backend/src/modules/iam/user/entities/users-accessible-users.entity.ts new file mode 100644 index 0000000..786b94f --- /dev/null +++ b/backend/src/modules/iam/user/entities/users-accessible-users.entity.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class UsersAccessibleUsers { + @PrimaryColumn() + userId: number; + + @PrimaryColumn() + accessibleId: number; + + constructor(userId: number, accessibleId: number) { + this.userId = userId; + this.accessibleId = accessibleId; + } +} diff --git a/backend/src/modules/iam/user/errors/bad-credentials.error.ts b/backend/src/modules/iam/user/errors/bad-credentials.error.ts new file mode 100644 index 0000000..9e17c98 --- /dev/null +++ b/backend/src/modules/iam/user/errors/bad-credentials.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class BadCredentialsError extends ServiceError { + constructor(message = 'Bad credentials') { + super({ errorCode: 'bad_credentials', status: HttpStatus.FORBIDDEN, message }); + } +} diff --git a/backend/src/modules/iam/user/errors/email-occupied.error.ts b/backend/src/modules/iam/user/errors/email-occupied.error.ts new file mode 100644 index 0000000..d25d6f6 --- /dev/null +++ b/backend/src/modules/iam/user/errors/email-occupied.error.ts @@ -0,0 +1,21 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class EmailOccupiedError extends ServiceError { + constructor(message = 'Email is occupied') { + super({ errorCode: 'email_occupied', status: HttpStatus.FORBIDDEN, message }); + } + + public static fromEmail(email: string): EmailOccupiedError { + return new EmailOccupiedError(`Email ${email} is occupied`); + } + + //TODO: remove string description for error code + public static forAccountCreation(): EmailOccupiedError { + return new EmailOccupiedError( + 'This email has already been used for registration. You can log in or, if you ' + + 'have forgotten your password, recover it. Alternatively, use a different email for registration.', + ); + } +} diff --git a/backend/src/modules/iam/user/errors/index.ts b/backend/src/modules/iam/user/errors/index.ts new file mode 100644 index 0000000..191eb71 --- /dev/null +++ b/backend/src/modules/iam/user/errors/index.ts @@ -0,0 +1,3 @@ +export * from './bad-credentials.error'; +export * from './email-occupied.error'; +export * from './user-not-active.error'; diff --git a/backend/src/modules/iam/user/errors/user-not-active.error.ts b/backend/src/modules/iam/user/errors/user-not-active.error.ts new file mode 100644 index 0000000..55be5c3 --- /dev/null +++ b/backend/src/modules/iam/user/errors/user-not-active.error.ts @@ -0,0 +1,17 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class UserNotActiveError extends ServiceError { + constructor(message = 'User is not active') { + super({ errorCode: 'user_not_active', status: HttpStatus.UNAUTHORIZED, message }); + } + + static withId(userId: number) { + return new UserNotActiveError(`User with id ${userId} is not active`); + } + + static fromEmail(email: string): UserNotActiveError { + return new UserNotActiveError(`User with email ${email} is not active`); + } +} diff --git a/backend/src/modules/iam/user/types/expandable-field.ts b/backend/src/modules/iam/user/types/expandable-field.ts new file mode 100644 index 0000000..b865855 --- /dev/null +++ b/backend/src/modules/iam/user/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'avatarUrl' | 'objectPermissions'; diff --git a/backend/src/modules/iam/user/types/index.ts b/backend/src/modules/iam/user/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/iam/user/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/iam/user/user.controller.ts b/backend/src/modules/iam/user/user.controller.ts new file mode 100644 index 0000000..335bfa8 --- /dev/null +++ b/backend/src/modules/iam/user/user.controller.ts @@ -0,0 +1,155 @@ +import { + Body, + Controller, + Delete, + FileTypeValidator, + Get, + MaxFileSizeValidator, + Param, + ParseFilePipe, + ParseIntPipe, + Patch, + Post, + Put, + Query, + UploadedFile, + UseInterceptors, +} from '@nestjs/common'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; +import { memoryStorage } from 'multer'; + +import { TransformToDto } from '@/common'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { AuthDataPrefetch } from '../common/decorators/auth-data-prefetch.decorator'; +import { CurrentAuth } from '../common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '../common/decorators/jwt-authorized.decorator'; +import { AuthData } from '../common/types/auth-data'; + +import { ChangeUserPasswordDto } from './dto/change-user-password.dto'; +import { CreateUserDto } from './dto/create-user.dto'; +import { UpdateUserDto } from './dto/update-user.dto'; +import { UserDto } from './dto/user.dto'; +import { UserService } from './user.service'; +import type { UserFindFilterDto } from './dto/user-find-filter.dto'; + +const UserAvatarFile = { + MaxSize: 5242880, + Type: 'image/*', +}; + +@ApiTags('IAM/users') +@Controller('users') +@JwtAuthorized({ prefetch: { account: true } }) +@TransformToDto() +export class UserController { + constructor(private readonly service: UserService) {} + + @ApiOperation({ summary: 'Create user', description: 'Create user' }) + @ApiBody({ type: CreateUserDto, required: true, description: 'Create user data' }) + @ApiCreatedResponse({ type: UserDto, description: 'Created user' }) + @Post() + async create(@CurrentAuth() { account }: AuthData, @Body() dto: CreateUserDto) { + return this.service.create({ account, dto }); + } + + @ApiOperation({ summary: 'Get user', description: 'Get user' }) + @ApiParam({ name: 'userId', type: Number, required: true, description: 'User ID' }) + @ApiOkResponse({ type: UserDto, description: 'User' }) + @Get(':userId') + async getOne(@CurrentAuth() { account }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return await this.service.findOne( + { accountId: account.id, id: userId }, + { account, expand: ['avatarUrl', 'objectPermissions'] }, + ); + } + + @ApiOperation({ summary: 'Get users', description: 'Get users' }) + @ApiOkResponse({ type: [UserDto], description: 'Users' }) + @Get() + async getMany(@CurrentAuth() { account }: AuthData, @Query() filter: UserFindFilterDto): Promise { + return this.service.findMany( + { accountId: account.id, fullName: filter?.fullName }, + { account, expand: ['avatarUrl', 'objectPermissions'] }, + ); + } + + @ApiOperation({ summary: 'Update user', description: 'Update user' }) + @ApiParam({ name: 'userId', type: Number, required: true, description: 'User ID' }) + @ApiBody({ type: UpdateUserDto, required: true, description: 'Update user data' }) + @ApiOkResponse({ type: UserDto, description: 'Updated user' }) + @Put(':userId') + async updatePut( + @CurrentAuth() { account }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Body() dto: UpdateUserDto, + ): Promise { + return this.service.updateExt({ account, userId, dto }); + } + + @ApiOperation({ summary: 'Update user', description: 'Update user' }) + @ApiParam({ name: 'userId', type: Number, required: true, description: 'User ID' }) + @ApiBody({ type: UpdateUserDto, required: true, description: 'Update user data' }) + @ApiOkResponse({ type: UserDto, description: 'Updated user' }) + @Patch(':userId') + async updatePatch( + @CurrentAuth() { account }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Body() dto: UpdateUserDto, + ): Promise { + return this.service.updateExt({ account, userId, dto }); + } + + @ApiOperation({ summary: 'Delete user', description: 'Soft delete user' }) + @ApiParam({ name: 'userId', type: Number, required: true, description: 'User ID' }) + @ApiQuery({ name: 'newUserId', type: Number, required: false, description: 'User ID to reassign data' }) + @ApiOkResponse() + @AuthDataPrefetch({ user: true }) + @Delete(':userId') + async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Query('newUserId') newUserId?: number, + ) { + await this.service.softDelete({ accountId, user, userId, newUserId: newUserId ? Number(newUserId) : undefined }); + } + + @ApiOperation({ summary: 'Change user password', description: 'Change user password' }) + @ApiBody({ type: ChangeUserPasswordDto, required: true, description: 'Change user password data' }) + @ApiOkResponse({ type: Boolean, description: 'Password changed' }) + @AuthDataPrefetch({ user: true }) + @Post('change-password') + async changePassword(@CurrentAuth() { user }: AuthData, @Body() dto: ChangeUserPasswordDto): Promise { + return this.service.changePassword({ user, dto }); + } + + @ApiOperation({ summary: 'Upload user avatar', description: 'Upload user avatar' }) + @ApiParam({ name: 'userId', type: Number, required: true, description: 'User ID' }) + @ApiOkResponse({ type: UserDto, description: 'User' }) + @Post(':userId/avatar') + @UseInterceptors(FileInterceptor('avatar', { storage: memoryStorage() })) + async uploadUserAvatar( + @CurrentAuth() { account }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @UploadedFile( + new ParseFilePipe({ + validators: [ + new MaxFileSizeValidator({ maxSize: UserAvatarFile.MaxSize }), + new FileTypeValidator({ fileType: UserAvatarFile.Type }), + ], + }), + ) + avatar: Express.Multer.File, + ) { + return this.service.setAvatar({ account, userId, file: StorageFile.fromMulter(avatar) }); + } + + @ApiOperation({ summary: 'Delete user avatar', description: 'Delete user avatar' }) + @ApiParam({ name: 'userId', type: Number, required: true, description: 'User ID' }) + @ApiOkResponse({ type: UserDto, description: 'User' }) + @Delete(':userId/avatar') + async deleteUserAvatar(@CurrentAuth() { account }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return this.service.removeAvatar({ account, userId }); + } +} diff --git a/backend/src/modules/iam/user/user.handler.ts b/backend/src/modules/iam/user/user.handler.ts new file mode 100644 index 0000000..a7cd326 --- /dev/null +++ b/backend/src/modules/iam/user/user.handler.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { DepartmentDeletedEvent, IamEventType } from '../common'; +import { UserService } from './user.service'; + +@Injectable() +export class UserHandler { + constructor(private readonly service: UserService) {} + + @OnEvent(IamEventType.DepartmentDeleted, { async: true }) + public async onDepartmentDeleted(event: DepartmentDeletedEvent) { + await this.service.changeDepartment({ + accountId: event.accountId, + departmentId: event.departmentId, + newDepartmentId: event.newDepartmentId, + }); + } +} diff --git a/backend/src/modules/iam/user/user.service.ts b/backend/src/modules/iam/user/user.service.ts new file mode 100644 index 0000000..a408c14 --- /dev/null +++ b/backend/src/modules/iam/user/user.service.ts @@ -0,0 +1,363 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { ForbiddenError, NotFoundError, PasswordUtil, PhoneUtil } from '@/common'; + +import { StorageUrlService } from '@/modules/storage/storage-url.service'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { IamEventType, UserCreatedEvent, UserDeletedEvent, UserRole } from '../common'; +import { Account } from '../account/entities/account.entity'; +import { ObjectPermissionService } from '../object-permission/object-permission.service'; + +import { CreateUserDto, UpdateUserDto, ChangeUserPasswordDto } from './dto'; +import { User, UsersAccessibleUsers } from './entities'; +import { EmailOccupiedError, BadCredentialsError } from './errors'; +import { ExpandableField } from './types'; + +interface CreateOptions { + skipPhoneCheck?: boolean; + createdAt?: Date; +} + +interface FindFilter { + accountId?: number; + id?: number | number[]; + email?: string; + isActive?: boolean; + departmentId?: number | number[]; + role?: UserRole; + fullName?: string; +} +interface FindOptions { + account?: Account; + expand?: ExpandableField[]; +} + +const cacheKey = ({ accountId, userId }: { accountId: number; userId: number }) => `User:${accountId}:${userId}`; + +@Injectable() +export class UserService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(User) + private readonly repository: Repository, + @InjectRepository(UsersAccessibleUsers) + private readonly repositoryUAU: Repository, + private readonly dataSource: DataSource, + private readonly objectPermissionService: ObjectPermissionService, + @Inject(forwardRef(() => StorageService)) + private readonly storageService: StorageService, + @Inject(forwardRef(() => StorageUrlService)) + private readonly storageUrlService: StorageUrlService, + ) {} + + async create({ + account, + dto, + options, + }: { + account: Account; + dto: CreateUserDto; + options?: CreateOptions; + }): Promise { + if (await this.isEmailOccupied(dto.email)) { + throw EmailOccupiedError.fromEmail(dto.email); + } + + // Validate password strength + if (!PasswordUtil.isStrong(dto.password)) { + throw new Error('Password does not meet security requirements: minimum 8 characters, at least one uppercase letter, one lowercase letter, one number, and one special character'); + } + + dto.phone = dto.phone && !options?.skipPhoneCheck ? PhoneUtil.normalize(dto.phone) : dto.phone; + const user = await this.repository.save(User.fromDto(account.id, dto, options?.createdAt)); + + if (dto.accessibleUserIds !== undefined) { + user.accessibleUsers = await this.repositoryUAU.save( + dto.accessibleUserIds.map((accessibleId) => new UsersAccessibleUsers(user.id, accessibleId)), + ); + } + + if (dto.objectPermissions) { + user.objectPermissions = await this.objectPermissionService.create({ + accountId: account.id, + userId: user.id, + hasDepartmentId: !!user.departmentId, + dtos: dto.objectPermissions, + }); + } + + this.eventEmitter.emit(IamEventType.UserCreated, new UserCreatedEvent({ accountId: account.id, userId: user.id })); + + return user; + } + + async isEmailOccupied(email: string): Promise { + return (await this.getCount({ email })) > 0; + } + + async findOne(filter: FindFilter, options?: FindOptions): Promise { + const qb = this.createFindQb(filter); + if (filter.accountId && filter.id && !Array.isArray(filter.id)) { + qb.cache(cacheKey({ accountId: filter.accountId, userId: filter.id }), 600000); + } + const user = await qb.getOne(); + + return user && options?.expand ? this.expandOne({ account: options.account, user, expand: options.expand }) : user; + } + + async findMany(filter: FindFilter, options?: FindOptions): Promise { + const users = await this.createFindQb(filter).orderBy('user.created_at', 'ASC').getMany(); + + return users.length && options?.expand + ? this.expandMany({ account: options.account, users, expand: options.expand }) + : users; + } + + async getCount(filter: FindFilter): Promise { + return this.createFindQb(filter).getCount(); + } + + async getCoworkerIds({ + accountId, + departmentIds, + }: { + accountId: number; + departmentIds: number | number[] | null; + }): Promise { + const cacheKey = `User.coworkers:${accountId}:${departmentIds}`; + return ( + await this.createFindQb({ accountId, departmentId: departmentIds }) + .select('user.id', 'id') + .cache(cacheKey, 15000) + .getRawMany<{ id: number }>() + ).map((u) => u.id); + } + + async update({ accountId, userId, dto }: { accountId: number; userId: number; dto: UpdateUserDto }): Promise { + const user = await this.findOne({ accountId, id: userId }); + if (!user) { + throw NotFoundError.withId(User, userId); + } + + if (dto.email && dto.email !== user.email && (await this.isEmailOccupied(dto.email))) { + throw EmailOccupiedError.fromEmail(dto.email); + } + + await this.repository.save(user.update(dto)); + + if (dto.accessibleUserIds !== undefined) { + await this.repositoryUAU.delete({ userId }); + user.accessibleUsers = await this.repositoryUAU.save( + dto.accessibleUserIds.map((accessibleId) => new UsersAccessibleUsers(userId, accessibleId)), + ); + } + + if (dto.objectPermissions) { + user.objectPermissions = await this.objectPermissionService.update({ + accountId: user.accountId, + userId: user.id, + hasDepartmentId: !!user.departmentId, + dtos: dto.objectPermissions, + }); + } + + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, userId })]); + + return user; + } + + async updateExt({ account, userId, dto }: { account: Account; userId: number; dto: UpdateUserDto }): Promise { + const user = await this.update({ accountId: account.id, userId, dto }); + + return this.expandOne({ account, user, expand: ['avatarUrl'] }); + } + + async changeDepartment({ + accountId, + departmentId, + newDepartmentId, + }: { + accountId: number; + departmentId: number; + newDepartmentId?: number | null; + }) { + await this.repository.update({ accountId, departmentId }, { departmentId: newDepartmentId ?? null }); + } + + async changePassword({ user, dto }: { user: User; dto: ChangeUserPasswordDto }): Promise { + const isValidPassword = PasswordUtil.verify(dto.currentPassword, user.password); + if (!isValidPassword) { + throw new BadCredentialsError(); + } + + // Validate new password strength + if (!PasswordUtil.isStrong(dto.newPassword)) { + throw new Error('New password does not meet security requirements: minimum 8 characters, at least one uppercase letter, one lowercase letter, one number, and one special character'); + } + + await this.repository.save(user.update({ password: dto.newPassword })); + return true; + } + + async ensureUserLimit({ accountId, user, userLimit }: { accountId: number; user: User | null; userLimit: number }) { + const activeUsers = await this.findMany({ accountId, isActive: true }); + if (activeUsers.length > userLimit) { + const owner = user ?? activeUsers.find((user) => user.role === UserRole.OWNER); + const usersToDelete = activeUsers.sort((a, b) => a.id - b.id).slice(userLimit); + for (const userToDelete of usersToDelete) { + await this.softDelete({ accountId, user: owner, userId: userToDelete.id, newUserId: owner?.id }); + } + } + } + + async delete({ accountId, userId }: { accountId: number; userId: number | number[] }) { + const ids = Array.isArray(userId) ? userId : [userId]; + await Promise.all( + ids.map(async (id) => { + await this.objectPermissionService.delete({ accountId, userId: id }); + await this.deleteAvatar({ accountId, userId: id }); + + await this.repository.delete({ accountId, id }); + + this.eventEmitter.emit(IamEventType.UserDeleted, new UserDeletedEvent({ accountId, userId: id })); + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, userId: id })]); + }), + ); + } + + async softDelete({ + accountId, + user, + userId, + newUserId, + }: { + accountId: number; + user: User; + userId: number; + newUserId?: number; + }) { + if (!user.isAdmin) { + throw new ForbiddenError(); + } + await this.repository.update({ accountId, id: userId }, { isActive: false }); + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, userId })]); + + this.eventEmitter.emit(IamEventType.UserDeleted, new UserDeletedEvent({ accountId, userId, newUserId })); + } + + async setAvatar({ account, userId, file }: { account: Account; userId: number; file: StorageFile }): Promise { + const user = await this.deleteAvatar({ accountId: account.id, userId }); + + const avatarFileInfo = await this.storageService.storeUserFile({ + accountId: account.id, + userId: user.id, + file, + section: 'avatar', + }); + if (avatarFileInfo) { + user.avatarId = avatarFileInfo.id; + await this.repository.save(user); + await this.storageService.markUsed({ accountId: account.id, id: avatarFileInfo.id }); + } + + return this.findOne({ accountId: account.id, id: userId }, { account, expand: ['avatarUrl', 'objectPermissions'] }); + } + + async removeAvatar({ account, userId }: { account: Account; userId: number }): Promise { + await this.deleteAvatar({ accountId: account.id, userId }); + + return this.findOne({ accountId: account.id, id: userId }, { account, expand: ['avatarUrl', 'objectPermissions'] }); + } + + private async deleteAvatar({ accountId, userId }: { accountId: number; userId: number }) { + const user = await this.findOne({ accountId, id: userId }); + if (user.avatarId) { + if (await this.storageService.delete({ accountId: user.accountId, id: user.avatarId })) { + user.avatarId = null; + await this.repository.save(user); + } + } + this.dataSource.queryResultCache?.remove([cacheKey({ accountId, userId })]); + return user; + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder('user').where('1 = 1'); + + qb.leftJoinAndMapMany('user.accessibleUsers', 'users_accessible_users', 'uau', 'uau.user_id = user.id'); + + if (filter.accountId) { + qb.andWhere('user.account_id = :accountId', { accountId: filter.accountId }); + } + + if (filter.id) { + if (Array.isArray(filter.id)) { + qb.andWhere('user.id IN (:...ids)', { ids: filter.id }); + } else { + qb.andWhere('user.id = :id', { id: filter.id }); + } + } + + if (filter.email) { + qb.andWhere('LOWER(user.email) = LOWER(:email)', { email: filter.email }); + } + + if (filter.isActive !== undefined) { + qb.andWhere('user.is_active = :isActive', { isActive: filter.isActive }); + } + + if (filter.departmentId) { + if (Array.isArray(filter.departmentId) && filter.departmentId.length) { + qb.andWhere('user.department_id IN (:...departmentIds)', { departmentIds: filter.departmentId }); + } else { + qb.andWhere('user.department_id = :departmentId', { departmentId: filter.departmentId }); + } + } + + if (filter.role) { + qb.andWhere('user.role = :role', { role: filter.role }); + } + + if (filter.fullName) { + qb.andWhere(`LOWER(user.first_name || ' ' || user.last_name) ilike :fullName`, { + fullName: `%${filter.fullName.trim()}%`, + }); + } + + return qb; + } + + private async expandOne({ + account, + user, + expand, + }: { + account: Account; + user: User; + expand: ExpandableField[]; + }): Promise { + if (user.avatarId && expand.includes('avatarUrl')) { + user.avatarUrl = this.storageUrlService.getImageUrl(account.id, account.subdomain, user.avatarId); + } + if (expand.includes('objectPermissions')) { + user.objectPermissions = await this.objectPermissionService.findMany({ accountId: account.id, userId: user.id }); + } + return user; + } + private async expandMany({ + account, + users, + expand, + }: { + account: Account; + users: User[]; + expand: ExpandableField[]; + }): Promise { + return await Promise.all(users.map((user) => this.expandOne({ account, user, expand }))); + } +} diff --git a/backend/src/modules/iam/working-time/index.ts b/backend/src/modules/iam/working-time/index.ts new file mode 100644 index 0000000..aa4cf7c --- /dev/null +++ b/backend/src/modules/iam/working-time/index.ts @@ -0,0 +1 @@ +export * from './working-time.service'; diff --git a/backend/src/modules/iam/working-time/working-time.service.ts b/backend/src/modules/iam/working-time/working-time.service.ts new file mode 100644 index 0000000..44242b9 --- /dev/null +++ b/backend/src/modules/iam/working-time/working-time.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@nestjs/common'; + +import { AccountSettingsService } from '../account-settings/account-settings.service'; +import { DepartmentSettingsService } from '../department-settings/department-settings.service'; +import { UserService } from '../user/user.service'; +import { UserProfileService } from '../user-profile/user-profile.service'; + +interface WorkingTime { + timeZone: string | null; + workingDays: string[] | null; + workingTimeFrom: string | null; + workingTimeTo: string | null; +} + +@Injectable() +export class WorkingTimeService { + constructor( + private readonly accountSettingsService: AccountSettingsService, + private readonly departmentSettingsService: DepartmentSettingsService, + private readonly userService: UserService, + private readonly userProfileService: UserProfileService, + ) {} + + async getForUser({ accountId, userId }: { accountId: number; userId: number }): Promise { + const settings = await this.accountSettingsService.getOne(accountId); + const workingTime = { + timeZone: settings.timeZone, + workingDays: settings.workingDays, + workingTimeFrom: settings.workingTimeFrom, + workingTimeTo: settings.workingTimeTo, + }; + + const user = await this.userService.findOne({ accountId, id: userId }); + if (user?.departmentId) { + const depSettings = await this.departmentSettingsService.findOne({ accountId, departmentId: user.departmentId }); + if (depSettings) { + if (depSettings.workingDays) { + workingTime.workingDays = depSettings.workingDays; + } + if (depSettings.workingTimeFrom) { + workingTime.workingTimeFrom = depSettings.workingTimeFrom; + } + if (depSettings.workingTimeTo) { + workingTime.workingTimeTo = depSettings.workingTimeTo; + } + } + } + + const profile = await this.userProfileService.findOne({ accountId, userId }); + if (profile) { + if (profile.workingTimeFrom) { + workingTime.workingTimeFrom = profile.workingTimeFrom; + } + if (profile.workingTimeTo) { + workingTime.workingTimeTo = profile.workingTimeTo; + } + } + + return workingTime; + } + + async getForDepartment({ + accountId, + departmentId, + }: { + accountId: number; + departmentId: number; + }): Promise { + const settings = await this.accountSettingsService.getOne(accountId); + const workingTime = { + timeZone: settings.timeZone, + workingDays: settings.workingDays, + workingTimeFrom: settings.workingTimeFrom, + workingTimeTo: settings.workingTimeTo, + }; + + const depSettings = await this.departmentSettingsService.findOne({ accountId, departmentId }); + if (depSettings.workingDays) { + workingTime.workingDays = depSettings.workingDays; + } + if (depSettings.workingTimeFrom) { + workingTime.workingTimeFrom = depSettings.workingTimeFrom; + } + if (depSettings.workingTimeTo) { + workingTime.workingTimeTo = depSettings.workingTimeTo; + } + + return workingTime; + } +} diff --git a/backend/src/modules/integration/appsumo/appsumo.controller.ts b/backend/src/modules/integration/appsumo/appsumo.controller.ts new file mode 100644 index 0000000..adbacf8 --- /dev/null +++ b/backend/src/modules/integration/appsumo/appsumo.controller.ts @@ -0,0 +1,29 @@ +import { Body, Controller, Get, Post, Query, Redirect } from '@nestjs/common'; +import { ApiExcludeController, ApiOkResponse } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AppsumoWebhookRequest, AppsumoWebhookResponse } from './types'; +import { AppsumoService } from './appsumo.service'; + +@ApiExcludeController(true) +@Controller('integration/appsumo') +@TransformToDto() +export class AppsumoController { + constructor(private readonly service: AppsumoService) {} + + @ApiOkResponse({ description: 'AppSumo login redirect' }) + @Get('redirect') + @Redirect() + public async redirect(@Query('code') code: string) { + const redirectUrl = await this.service.redirect(code); + + return { url: redirectUrl, statusCode: 302 }; + } + + @ApiOkResponse({ description: 'AppSumo webhook', type: AppsumoWebhookResponse }) + @Post('webhook') + public async getUser(@Body() dto: AppsumoWebhookRequest) { + return this.service.webhook(dto); + } +} diff --git a/backend/src/modules/integration/appsumo/appsumo.module.ts b/backend/src/modules/integration/appsumo/appsumo.module.ts new file mode 100644 index 0000000..a6cf42b --- /dev/null +++ b/backend/src/modules/integration/appsumo/appsumo.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { AppsumoLicense, AppsumoTier } from './entities'; +import { AppsumoController } from './appsumo.controller'; +import { AppsumoService } from './appsumo.service'; +import { ConfigModule } from '@nestjs/config'; +import appsumoConfig from './config/appsumo.config'; + +@Module({ + imports: [ConfigModule.forFeature(appsumoConfig), TypeOrmModule.forFeature([AppsumoLicense, AppsumoTier]), IAMModule], + providers: [AppsumoService], + controllers: [AppsumoController], + exports: [AppsumoService], +}) +export class AppsumoModule {} diff --git a/backend/src/modules/integration/appsumo/appsumo.service.ts b/backend/src/modules/integration/appsumo/appsumo.service.ts new file mode 100644 index 0000000..898554c --- /dev/null +++ b/backend/src/modules/integration/appsumo/appsumo.service.ts @@ -0,0 +1,196 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { AxiosResponse } from 'axios'; +import { catchError, lastValueFrom } from 'rxjs'; +import { Repository } from 'typeorm'; + +import { DateUtil, FrontendRoute, UrlGeneratorService } from '@/common'; +import { AccountService } from '@/modules/iam/account/account.service'; +import { AccountSubscriptionService } from '@/modules/iam/account-subscription'; + +import { AppsumoConfig } from './config'; +import { AppsumoLicense, AppsumoTier } from './entities'; +import { AppsumoLicenseResponse, AppsumoTokenResponse, AppsumoWebhookRequest, AppsumoWebhookResponse } from './types'; +import { AppsumoEventType } from './enums'; + +const AppsumoUrls = { + base: 'https://appsumo.com', + oauth: () => AppsumoUrls.base + '/openid', + token: () => AppsumoUrls.oauth() + '/token', + licenseKey: () => AppsumoUrls.oauth() + '/license_key', +} as const; + +const RedirectPath = '/api/integration/appsumo/redirect'; + +interface Subscription { + termInDays?: number; + userLimit?: number; + planName?: string; + isTrial?: boolean; +} + +@Injectable() +export class AppsumoService { + private readonly logger = new Logger(AppsumoService.name); + private readonly _config: AppsumoConfig; + + constructor( + private readonly configService: ConfigService, + private readonly httpService: HttpService, + @InjectRepository(AppsumoLicense) + private readonly licenseRepository: Repository, + @InjectRepository(AppsumoTier) + private readonly tierRepository: Repository, + private readonly urlGenerator: UrlGeneratorService, + private readonly accountService: AccountService, + private readonly subscriptionService: AccountSubscriptionService, + ) { + this._config = this.configService.get('appsumo'); + } + + public async redirect(code: string | null | undefined): Promise { + if (code) { + const tokenData = await this.getToken(code); + if (tokenData?.access_token) { + const licenseData = await this.getLicense(tokenData.access_token); + if (licenseData?.license_key) { + const license = await this.licenseRepository.findOneBy({ licenseKey: licenseData.license_key }); + if (license?.accountId) { + const account = await this.accountService.findOne({ accountId: license.accountId }); + const tier = await this.tierRepository.findOneBy({ tier: license.tier }); + await this.subscriptionService.update({ accountId: account.id }, null, { + isTrial: false, + periodEnd: DateUtil.add(DateUtil.now(), { days: tier.termInDays }).toISOString(), + userLimit: tier.userLimit, + planName: tier.planName, + }); + + return this.urlGenerator.createUrl({ subdomain: account.subdomain }); + } else { + return this.urlGenerator.createUrl({ + route: FrontendRoute.signup, + query: { appsumo: licenseData.license_key }, + }); + } + } + } + } + + return this.urlGenerator.createUrl(); + } + + public async webhook(dto: AppsumoWebhookRequest): Promise { + if (!dto.test) { + const prevLicense = dto.prev_license_key + ? await this.licenseRepository.findOneBy({ licenseKey: dto.prev_license_key }) + : null; + const prevAccountId = prevLicense?.accountId ?? null; + if (prevLicense?.accountId) { + await this.licenseRepository.save(prevLicense.update({ accountId: null })); + } + let license = await this.licenseRepository.findOneBy({ licenseKey: dto.license_key }); + if (license) { + await this.licenseRepository.save( + license.update({ + licenseKey: dto.license_key, + licenseStatus: dto.license_status, + planId: dto.plan_id, + tier: dto.tier, + }), + ); + } else { + license = await this.licenseRepository.save( + new AppsumoLicense( + dto.license_key, + dto.prev_license_key, + dto.license_status, + dto.plan_id, + dto.tier, + prevAccountId, + ), + ); + } + + if (license.accountId) { + const tier = await this.tierRepository.findOneBy({ tier: license.tier }); + const periodEnd = + dto.event === AppsumoEventType.Deactivate + ? DateUtil.now() + : DateUtil.add(DateUtil.now(), { days: tier.termInDays }); + await this.subscriptionService.update({ accountId: license.accountId }, null, { + isTrial: false, + periodEnd: periodEnd.toISOString(), + userLimit: tier.userLimit, + planName: tier.planName, + }); + } + } + + return { + success: true, + event: dto.event, + }; + } + + public async findSubscription(licenseKey: string): Promise { + const license = await this.licenseRepository.findOneBy({ licenseKey }); + if (license) { + const tier = await this.tierRepository.findOneBy({ tier: license.tier }); + if (tier) { + return { + termInDays: tier.termInDays, + userLimit: tier.userLimit, + planName: tier.planName, + isTrial: false, + }; + } + } + + return null; + } + + public async update(licenseKey: string, dto: Partial): Promise { + const license = await this.licenseRepository.findOneBy({ licenseKey }); + if (license) { + return await this.licenseRepository.save(license.update(dto)); + } + + return null; + } + + private async getToken(code: string): Promise { + const { data } = await lastValueFrom>( + this.httpService + .post(AppsumoUrls.token(), { + client_id: this._config.clientId, + client_secret: this._config.clientSecret, + code: code, + redirect_uri: this.urlGenerator.createUrl({ route: RedirectPath }), + grant_type: 'authorization_code', + }) + .pipe( + catchError((error) => { + this.logger.error(`AppSumo get token error`, (error as Error)?.stack); + throw error; + }), + ), + ); + + return data; + } + + private async getLicense(accessToken: string): Promise { + const { data } = await lastValueFrom>( + this.httpService.get(AppsumoUrls.licenseKey(), { params: { access_token: accessToken } }).pipe( + catchError((error) => { + this.logger.error(`AppSumo get license error`, (error as Error)?.stack); + throw error; + }), + ), + ); + + return data; + } +} diff --git a/backend/src/modules/integration/appsumo/config/appsumo.config.ts b/backend/src/modules/integration/appsumo/config/appsumo.config.ts new file mode 100644 index 0000000..fa2cda9 --- /dev/null +++ b/backend/src/modules/integration/appsumo/config/appsumo.config.ts @@ -0,0 +1,16 @@ +import { registerAs } from '@nestjs/config'; + +export interface AppsumoConfig { + clientId: string; + clientSecret: string; + privateKey: string; +} + +export default registerAs( + 'appsumo', + (): AppsumoConfig => ({ + clientId: process.env.APPSUMO_CLIENT_ID, + clientSecret: process.env.APPSUMO_CLIENT_SECRET, + privateKey: process.env.APPSUMO_PRIVATE_KEY, + }), +); diff --git a/backend/src/modules/integration/appsumo/config/index.ts b/backend/src/modules/integration/appsumo/config/index.ts new file mode 100644 index 0000000..1942554 --- /dev/null +++ b/backend/src/modules/integration/appsumo/config/index.ts @@ -0,0 +1 @@ +export * from './appsumo.config'; diff --git a/backend/src/modules/integration/appsumo/entities/appsumo-license.entity.ts b/backend/src/modules/integration/appsumo/entities/appsumo-license.entity.ts new file mode 100644 index 0000000..26a573b --- /dev/null +++ b/backend/src/modules/integration/appsumo/entities/appsumo-license.entity.ts @@ -0,0 +1,75 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { AppsumoLicenseStatus } from '../enums'; + +@Entity() +export class AppsumoLicense { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + licenseKey: string; + + @Column({ nullable: true }) + prevLicenseKey: string | null; + + @Column() + licenseStatus: AppsumoLicenseStatus; + + @Column() + planId: string; + + @Column() + tier: number; + + @Column({ nullable: true }) + accountId: number | null; + + @Column({ type: Date }) + createdAt: Date; + + constructor( + licenseKey: string, + prevLicenseKey: string | null, + licenseStatus: AppsumoLicenseStatus, + planId: string, + tier: number, + accountId: number | null, + createdAt?: Date, + ) { + this.licenseKey = licenseKey; + this.prevLicenseKey = prevLicenseKey; + this.licenseStatus = licenseStatus; + this.planId = planId; + this.tier = tier; + this.accountId = accountId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public update({ + licenseKey, + prevLicenseKey, + licenseStatus, + planId, + tier, + accountId, + }: { + licenseKey?: string; + prevLicenseKey?: string | null; + licenseStatus?: AppsumoLicenseStatus; + planId?: string; + tier?: number; + accountId?: number | null; + }): AppsumoLicense { + this.licenseKey = licenseKey !== undefined ? licenseKey : this.licenseKey; + this.prevLicenseKey = prevLicenseKey !== undefined ? prevLicenseKey : this.prevLicenseKey; + this.licenseStatus = licenseStatus !== undefined ? licenseStatus : this.licenseStatus; + this.planId = planId !== undefined ? planId : this.planId; + this.tier = tier !== undefined ? tier : this.tier; + this.accountId = accountId !== undefined ? accountId : this.accountId; + + return this; + } +} diff --git a/backend/src/modules/integration/appsumo/entities/appsumo-tier.entity.ts b/backend/src/modules/integration/appsumo/entities/appsumo-tier.entity.ts new file mode 100644 index 0000000..5f941d3 --- /dev/null +++ b/backend/src/modules/integration/appsumo/entities/appsumo-tier.entity.ts @@ -0,0 +1,26 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity() +export class AppsumoTier { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + tier: number; + + @Column() + userLimit: number; + + @Column() + termInDays: number; + + @Column() + planName: string; + + constructor(tier: number, userLimit: number, termInDays: number, planName: string) { + this.tier = tier; + this.userLimit = userLimit; + this.termInDays = termInDays; + this.planName = planName; + } +} diff --git a/backend/src/modules/integration/appsumo/entities/index.ts b/backend/src/modules/integration/appsumo/entities/index.ts new file mode 100644 index 0000000..0614260 --- /dev/null +++ b/backend/src/modules/integration/appsumo/entities/index.ts @@ -0,0 +1,2 @@ +export * from './appsumo-license.entity'; +export * from './appsumo-tier.entity'; diff --git a/backend/src/modules/integration/appsumo/enums/appsumo-event-type.enum.ts b/backend/src/modules/integration/appsumo/enums/appsumo-event-type.enum.ts new file mode 100644 index 0000000..4a2bf28 --- /dev/null +++ b/backend/src/modules/integration/appsumo/enums/appsumo-event-type.enum.ts @@ -0,0 +1,7 @@ +export enum AppsumoEventType { + Activate = 'activate', + Deactivate = 'deactivate', + Purchase = 'purchase', + Upgrade = 'upgrade', + Downgrade = 'downgrade', +} diff --git a/backend/src/modules/integration/appsumo/enums/appsumo-license-status.enum.ts b/backend/src/modules/integration/appsumo/enums/appsumo-license-status.enum.ts new file mode 100644 index 0000000..9a953e7 --- /dev/null +++ b/backend/src/modules/integration/appsumo/enums/appsumo-license-status.enum.ts @@ -0,0 +1,5 @@ +export enum AppsumoLicenseStatus { + Inactive = 'inactive', + Active = 'active', + Deactivated = 'deactivated', +} diff --git a/backend/src/modules/integration/appsumo/enums/index.ts b/backend/src/modules/integration/appsumo/enums/index.ts new file mode 100644 index 0000000..5dc421b --- /dev/null +++ b/backend/src/modules/integration/appsumo/enums/index.ts @@ -0,0 +1,2 @@ +export * from './appsumo-event-type.enum'; +export * from './appsumo-license-status.enum'; diff --git a/backend/src/modules/integration/appsumo/index.ts b/backend/src/modules/integration/appsumo/index.ts new file mode 100644 index 0000000..ddea7f7 --- /dev/null +++ b/backend/src/modules/integration/appsumo/index.ts @@ -0,0 +1,6 @@ +export * from './appsumo.controller'; +export * from './appsumo.module'; +export * from './appsumo.service'; +export * from './entities'; +export * from './enums'; +export * from './types'; diff --git a/backend/src/modules/integration/appsumo/types/appsumo-license-response.ts b/backend/src/modules/integration/appsumo/types/appsumo-license-response.ts new file mode 100644 index 0000000..19f1fb6 --- /dev/null +++ b/backend/src/modules/integration/appsumo/types/appsumo-license-response.ts @@ -0,0 +1,7 @@ +import { AppsumoLicenseStatus } from '../enums'; + +export class AppsumoLicenseResponse { + license_key: string; + status: AppsumoLicenseStatus; + scopes: string[]; +} diff --git a/backend/src/modules/integration/appsumo/types/appsumo-token-response.ts b/backend/src/modules/integration/appsumo/types/appsumo-token-response.ts new file mode 100644 index 0000000..a1f380c --- /dev/null +++ b/backend/src/modules/integration/appsumo/types/appsumo-token-response.ts @@ -0,0 +1,8 @@ +export class AppsumoTokenResponse { + access_token: string; + token_type: string; + expires_in: number; + refresh_token: string; + id_token: string; + error: string; +} diff --git a/backend/src/modules/integration/appsumo/types/appsumo-webhook-request.ts b/backend/src/modules/integration/appsumo/types/appsumo-webhook-request.ts new file mode 100644 index 0000000..f66b0f1 --- /dev/null +++ b/backend/src/modules/integration/appsumo/types/appsumo-webhook-request.ts @@ -0,0 +1,14 @@ +import { AppsumoEventType, AppsumoLicenseStatus } from '../enums'; + +export interface AppsumoWebhookRequest { + license_key: string; + prev_license_key?: string; + plan_id: string; + event: AppsumoEventType; + event_timestamp: number; + created_at: number; + license_status: AppsumoLicenseStatus; + tier: number; + test: boolean; + extra: { reason: string }; +} diff --git a/backend/src/modules/integration/appsumo/types/appsumo-webhook-response.ts b/backend/src/modules/integration/appsumo/types/appsumo-webhook-response.ts new file mode 100644 index 0000000..7590eb8 --- /dev/null +++ b/backend/src/modules/integration/appsumo/types/appsumo-webhook-response.ts @@ -0,0 +1,7 @@ +import { AppsumoEventType } from '../enums'; + +export class AppsumoWebhookResponse { + success: boolean; + event: AppsumoEventType; + message?: string; +} diff --git a/backend/src/modules/integration/appsumo/types/index.ts b/backend/src/modules/integration/appsumo/types/index.ts new file mode 100644 index 0000000..d2dd284 --- /dev/null +++ b/backend/src/modules/integration/appsumo/types/index.ts @@ -0,0 +1,4 @@ +export * from './appsumo-license-response'; +export * from './appsumo-token-response'; +export * from './appsumo-webhook-request'; +export * from './appsumo-webhook-response'; diff --git a/backend/src/modules/integration/google/auth/auth.module.ts b/backend/src/modules/integration/google/auth/auth.module.ts new file mode 100644 index 0000000..64a6c1a --- /dev/null +++ b/backend/src/modules/integration/google/auth/auth.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { AuthService } from './auth.service'; + +@Module({ + providers: [AuthService], + exports: [AuthService], +}) +export class AuthModule {} diff --git a/backend/src/modules/integration/google/auth/auth.service.ts b/backend/src/modules/integration/google/auth/auth.service.ts new file mode 100644 index 0000000..1aa9a07 --- /dev/null +++ b/backend/src/modules/integration/google/auth/auth.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { google, Auth } from 'googleapis'; + +import { DateUtil, UrlGeneratorService } from '@/common'; + +import { GoogleConfig } from '../google.config'; + +interface AuthParams { + clientId?: string; + clientSecret?: string; + subdomain?: string; + callbackPath?: string; + tokens?: Auth.Credentials; +} + +@Injectable() +export class AuthService { + private readonly _config: GoogleConfig; + constructor( + private readonly configService: ConfigService, + private readonly urlGenerator: UrlGeneratorService, + ) { + this._config = this.configService.get('google'); + } + + public async getOAuth2Client({ clientId, clientSecret, subdomain, callbackPath, tokens }: AuthParams = {}): Promise<{ + client: Auth.OAuth2Client; + refreshedTokens: Auth.Credentials | undefined; + }> { + const redirectUri = callbackPath ? this.urlGenerator.createUrl({ route: callbackPath, subdomain }) : undefined; + + const client = new google.auth.OAuth2( + clientId ?? this._config.auth.clientId, + clientSecret ?? this._config.auth.clientSecret, + redirectUri, + ); + + let refreshedTokens: Auth.Credentials | undefined = undefined; + if (tokens) { + client.setCredentials(tokens); + + if (tokens.expiry_date < DateUtil.now().getTime()) { + const { credentials } = await client.refreshAccessToken(); + refreshedTokens = credentials; + client.setCredentials(refreshedTokens); + } + } + + return { client, refreshedTokens }; + } + + public async generateAuthUrl({ + auth, + scope, + state, + }: { auth?: AuthParams; scope?: string | string[]; state?: string } = {}): Promise { + const { client } = await this.getOAuth2Client(auth); + return client.generateAuthUrl({ + access_type: 'offline', + prompt: 'consent', + include_granted_scopes: true, + scope, + state, + }); + } + + public async getToken({ auth, code }: { auth?: AuthParams; code: string }): Promise { + const { client } = await this.getOAuth2Client(auth); + const { tokens } = await client.getToken(code); + + return tokens; + } +} diff --git a/backend/src/modules/integration/google/calendar/calendar.controller.ts b/backend/src/modules/integration/google/calendar/calendar.controller.ts new file mode 100644 index 0000000..4b1cfa6 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/calendar.controller.ts @@ -0,0 +1,101 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CalendarAccessDto, CreateGoogleCalendarDto, GoogleCalendarDto, UpdateGoogleCalendarDto } from './dto'; +import { CalendarService } from './calendar.service'; + +@ApiTags('integration/google/calendar') +@Controller('integration/google/calendar') +@JwtAuthorized() +@TransformToDto() +export class CalendarController { + constructor(private readonly service: CalendarService) {} + + @ApiOperation({ + summary: 'Generate authorization URL', + description: 'Generate Google authorization URL for Calendar integration', + }) + @ApiOkResponse({ type: String, description: 'Google authorization URL' }) + @Get('authorize-url') + public async getAuthorizeUrl(@CurrentAuth() { accountId, userId }: AuthData): Promise { + return this.service.getAuthorizeUrl({ accountId, userId }); + } + + @ApiOperation({ + summary: 'Process authorization code', + description: 'Process Google authorization code for Calendar integration and get accessible calendars', + }) + @ApiQuery({ name: 'code', type: String, required: true, description: 'Google authorization code' }) + @ApiQuery({ name: 'state', type: String, required: false, description: 'State' }) + @ApiOkResponse({ type: CalendarAccessDto, description: 'Accessible calendars and access token' }) + @Get('process-code') + public async processAuthCode(@Query('code') code: string, @Query('state') state?: string) { + return this.service.processAuthCode({ code, state }); + } + + @ApiOperation({ + summary: 'Create Google calendar integration', + description: 'Create Google calendar integration', + }) + @ApiBody({ + type: CreateGoogleCalendarDto, + required: true, + description: 'Data for creating Google calendar integration', + }) + @ApiOkResponse({ type: GoogleCalendarDto, description: 'Google calendar integration' }) + @Post() + public async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateGoogleCalendarDto) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Find Google calendars integrations', description: 'Find Google calendars integrations' }) + @ApiOkResponse({ type: [GoogleCalendarDto], description: 'Google calendars integrations' }) + @Get() + @AuthDataPrefetch({ user: true }) + public async findMany(@CurrentAuth() { accountId, user }: AuthData) { + return this.service.findMany({ accountId, user }); + } + + @ApiOperation({ summary: 'Find Google calendar integration', description: 'Find Google calendar integration' }) + @ApiParam({ name: 'calendarId', type: Number, required: true, description: 'Google calendar ID' }) + @ApiOkResponse({ type: GoogleCalendarDto, description: 'Google calendar integration' }) + @Get(':calendarId') + @AuthDataPrefetch({ user: true }) + public async findOne(@CurrentAuth() { accountId, user }: AuthData, @Param('calendarId') calendarId: number) { + return this.service.findOne({ accountId, user, calendarId }); + } + + @ApiOperation({ + summary: 'Update Google calendar integration', + description: 'Update Google calendar integration', + }) + @ApiParam({ name: 'calendarId', type: Number, required: true, description: 'Google calendar ID' }) + @ApiBody({ + type: UpdateGoogleCalendarDto, + required: true, + description: 'Data for updating Google calendar integration', + }) + @ApiOkResponse({ type: GoogleCalendarDto, description: 'Google calendar integration' }) + @Patch(':calendarId') + public async update( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('calendarId') calendarId: number, + @Body() dto: UpdateGoogleCalendarDto, + ) { + return this.service.update({ accountId, userId, calendarId, dto }); + } + + @ApiOperation({ + summary: 'Delete Google calendar integration', + description: 'Delete Google calendar integration', + }) + @ApiParam({ name: 'calendarId', type: Number, required: true, description: 'Google calendar ID' }) + @ApiOkResponse({ type: Number, description: 'Deleted Google calendar ID' }) + @Delete(':calendarId') + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('calendarId') calendarId: number) { + return this.service.delete({ accountId, calendarId }); + } +} diff --git a/backend/src/modules/integration/google/calendar/calendar.emitter.ts b/backend/src/modules/integration/google/calendar/calendar.emitter.ts new file mode 100644 index 0000000..eb202e9 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/calendar.emitter.ts @@ -0,0 +1,119 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { calendar_v3 } from 'googleapis'; + +import { ServiceEvent } from '@/common'; +import { SchedulerAppointmentExtUpsertEvent, SchedulerEventType } from '@/modules/scheduler/common'; +import { CrmEventType, TaskExtEvent, TaskExtUpsertEvent } from '@/CRM/common'; + +import { GoogleCalendar } from './entities'; +import { CalendarType } from './enums'; +import { CalendarEvent } from './types'; +import { AppointmentUtil, EventUtil } from './utils'; + +@Injectable() +export class CalendarEmitter { + constructor(private readonly eventEmitter: EventEmitter2) {} + + public async emit({ calendar, event }: { calendar: GoogleCalendar; event: calendar_v3.Schema$Event }) { + const calendarEvent = EventUtil.getCalendarEvent(event); + const objectIds = [calendar.objectId, ...(calendar.linked?.map((linked) => linked.objectId) ?? [])]; + if (!calendarEvent.objectId || !objectIds.includes(calendarEvent.objectId)) { + calendarEvent.objectId = null; + calendarEvent.eventId = null; + } + + const eventType = this.getEventType({ calendar, event }); + const eventData = this.getEventData({ calendar, event, calendarEvent }); + + this.eventEmitter.emit(eventType, eventData); + } + + private getEventType({ calendar, event }: { calendar: GoogleCalendar; event: calendar_v3.Schema$Event }): string { + switch (calendar.type) { + case CalendarType.Task: + return event.status === 'cancelled' ? CrmEventType.TaskDeleteExt : CrmEventType.TaskUpsertExt; + case CalendarType.Schedule: + return SchedulerEventType.ScheduleAppointmentUpsertExt; + } + } + + private getEventData({ + calendar, + event, + calendarEvent, + }: { + calendar: GoogleCalendar; + event: calendar_v3.Schema$Event; + calendarEvent: CalendarEvent; + }): ServiceEvent { + switch (calendar.type) { + case CalendarType.Task: + return this.getEventDataTask({ calendar, event, calendarEvent }); + case CalendarType.Schedule: + return this.getEventDataSchedule({ calendar, event, calendarEvent }); + } + } + + private getEventDataTask({ + calendar, + event, + calendarEvent, + }: { + calendar: GoogleCalendar; + event: calendar_v3.Schema$Event; + calendarEvent: CalendarEvent; + }): ServiceEvent { + const startDate = EventUtil.getEventDate(event.start); + const endDate = EventUtil.getEventDate(event.end); + + return event.status === 'cancelled' + ? new TaskExtEvent({ + source: GoogleCalendar.name, + externalId: calendarEvent.externalId, + accountId: calendar.accountId, + boardId: calendarEvent.objectId ?? calendar.objectId, + taskId: calendarEvent.eventId, + }) + : new TaskExtUpsertEvent({ + source: GoogleCalendar.name, + externalId: calendarEvent.externalId, + accountId: calendar.accountId, + boardId: calendarEvent.objectId ?? calendar.objectId, + taskId: calendarEvent.eventId, + ownerId: calendar.responsibleId, + title: event.summary, + text: event.description, + startDate, + endDate, + }); + } + + private getEventDataSchedule({ + calendar, + event, + calendarEvent, + }: { + calendar: GoogleCalendar; + event: calendar_v3.Schema$Event; + calendarEvent: CalendarEvent; + }): ServiceEvent { + const startDate = EventUtil.getEventDate(event.start); + const endDate = EventUtil.getEventDate(event.end); + + return new SchedulerAppointmentExtUpsertEvent({ + source: GoogleCalendar.name, + externalId: calendarEvent.externalId, + accountId: calendar.accountId, + scheduleId: calendarEvent.objectId ?? calendar.objectId, + appointmentId: calendarEvent.eventId, + performerId: calendar.responsibleId, + ownerId: calendar.createdBy, + title: event.summary, + comment: event.description, + startDate, + endDate, + status: AppointmentUtil.convertEventStatus(event.status), + }); + } +} diff --git a/backend/src/modules/integration/google/calendar/calendar.handler.ts b/backend/src/modules/integration/google/calendar/calendar.handler.ts new file mode 100644 index 0000000..fb860c5 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/calendar.handler.ts @@ -0,0 +1,179 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; +import { + ScheduleEvent, + SchedulePerformerDeletedEvent, + SchedulerAppointmentCreatedEvent, + SchedulerAppointmentEvent, + SchedulerAppointmentUpdatedEvent, + SchedulerEventType, +} from '@/modules/scheduler/common'; +import { BoardEvent, CrmEventType, TaskCreatedEvent, TaskEvent, TaskUpdatedEvent } from '@/CRM/common'; + +import { GoogleCalendar } from './entities'; +import { CalendarType } from './enums'; +import { AppointmentUtil } from './utils'; +import { CalendarService } from './calendar.service'; + +@Injectable() +export class CalendarHandler { + private readonly logger = new Logger(CalendarHandler.name); + constructor(private readonly service: CalendarService) {} + + @Cron(CronExpression.EVERY_HOUR) + public async synchronizeActive() { + if (process.env.SCHEDULE_INTEGRATION_GOOGLE_CALENDAR_DISABLE === 'true') return; + this.logger.log('Before: Renew registered Google Calendar channels'); + const count = await this.service.renewChannels(); + this.logger.log(`After: Renew registered Google Calendar channels. Processed: ${count}`); + } + + @OnEvent(CrmEventType.TaskCreated, { async: true }) + public async onTaskCreated(event: TaskCreatedEvent) { + if (!event.checkHistory({ source: GoogleCalendar.name })) { + if (event.startDate && event.endDate) { + this.service.handleUpsert({ + accountId: event.accountId, + ownerId: event.ownerId, + objectId: event.boardId, + eventId: event.taskId, + type: CalendarType.Task, + title: event.taskTitle, + description: event.taskText, + startDate: event.startDate, + endDate: event.endDate, + entityId: event.entityId, + externalId: event.externalId, + }); + } + } + } + + @OnEvent(CrmEventType.TaskUpdated, { async: true }) + public async onTaskUpdated(event: TaskUpdatedEvent) { + if (!event.checkHistory({ source: GoogleCalendar.name })) { + if (event.startDate && event.endDate) { + this.service.handleUpsert({ + accountId: event.accountId, + ownerId: event.ownerId, + objectId: event.boardId, + eventId: event.taskId, + type: CalendarType.Task, + title: event.taskTitle, + description: event.taskText, + startDate: event.startDate, + endDate: event.endDate, + entityId: event.entityId, + externalId: event.externalId, + }); + } + } + } + + @OnEvent(CrmEventType.TaskDeleted, { async: true }) + public async onTaskDeleted(event: TaskEvent) { + if (!event.checkHistory({ source: GoogleCalendar.name })) { + this.service.handleDeleted({ + accountId: event.accountId, + objectId: event.boardId, + eventId: event.taskId, + type: CalendarType.Task, + externalId: event.externalId, + }); + } + } + + @OnEvent(SchedulerEventType.ScheduleAppointmentCreated, { async: true }) + public async onScheduleAppointmentCreated(event: SchedulerAppointmentCreatedEvent) { + if (!event.checkHistory({ source: GoogleCalendar.name })) { + if (event.startDate && event.endDate) { + this.service.handleUpsert({ + accountId: event.accountId, + ownerId: event.performerId, + objectId: event.scheduleId, + eventId: event.appointmentId, + type: CalendarType.Schedule, + title: event.title, + description: event.comment, + startDate: event.startDate, + endDate: event.endDate, + status: AppointmentUtil.convertAppointmentStatus(event.status), + entityId: event.entityId, + externalId: event.externalId, + }); + } + } + } + + @OnEvent(SchedulerEventType.ScheduleAppointmentUpdated, { async: true }) + public async onScheduleAppointmentUpdated(event: SchedulerAppointmentUpdatedEvent) { + if (!event.checkHistory({ source: GoogleCalendar.name })) { + if (event.startDate && event.endDate) { + this.service.handleUpsert({ + accountId: event.accountId, + ownerId: event.performerId, + objectId: event.scheduleId, + eventId: event.appointmentId, + type: CalendarType.Schedule, + title: event.title, + description: event.comment, + startDate: event.startDate, + endDate: event.endDate, + status: AppointmentUtil.convertAppointmentStatus(event.status), + entityId: event.entityId, + externalId: event.externalId, + }); + } + } + } + + @OnEvent(SchedulerEventType.ScheduleAppointmentDeleted, { async: true }) + public async onScheduleAppointmentDeleted(event: SchedulerAppointmentEvent) { + if (!event.checkHistory({ source: GoogleCalendar.name })) { + this.service.handleDeleted({ + accountId: event.accountId, + objectId: event.scheduleId, + eventId: event.appointmentId, + type: CalendarType.Schedule, + externalId: event.externalId, + }); + } + } + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + await this.service.handleDeleteByResponsible({ + accountId: event.accountId, + type: CalendarType.Task, + responsibleId: event.userId, + newResponsibleId: event.newUserId, + }); + } + + @OnEvent(CrmEventType.BoardDeleted, { async: true }) + public async onBoardDeleted(event: BoardEvent) { + this.service.handleDeleteByObject({ accountId: event.accountId, type: CalendarType.Task, objectId: event.boardId }); + } + + @OnEvent(SchedulerEventType.ScheduleDeleted, { async: true }) + public async onScheduleDeleted(event: ScheduleEvent) { + await this.service.handleDeleteByObject({ + accountId: event.accountId, + type: CalendarType.Schedule, + objectId: event.scheduleId, + }); + } + + @OnEvent(SchedulerEventType.SchedulePerformerDeleted, { async: true }) + public async onSchedulePerformerDeleted(event: SchedulePerformerDeletedEvent) { + await this.service.handleDeleteByResponsible({ + accountId: event.accountId, + type: CalendarType.Schedule, + responsibleId: event.performerId, + newResponsibleId: event.newPerformerId, + }); + } +} diff --git a/backend/src/modules/integration/google/calendar/calendar.module.ts b/backend/src/modules/integration/google/calendar/calendar.module.ts new file mode 100644 index 0000000..500f22f --- /dev/null +++ b/backend/src/modules/integration/google/calendar/calendar.module.ts @@ -0,0 +1,25 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { IAMModule } from '@/modules/iam/iam.module'; +import { AuthModule } from '../auth/auth.module'; + +import { GoogleCalendar, GoogleCalendarAccount, GoogleCalendarLinked } from './entities'; +import { CalendarService } from './calendar.service'; +import { CalendarHandler } from './calendar.handler'; +import { CalendarEmitter } from './calendar.emitter'; +import { CalendarController } from './calendar.controller'; +import { PublicCalendarController } from './public-calendar.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([GoogleCalendarAccount, GoogleCalendar, GoogleCalendarLinked]), + IAMModule, + AuthModule, + EntityInfoModule, + ], + providers: [CalendarService, CalendarHandler, CalendarEmitter], + controllers: [PublicCalendarController, CalendarController], +}) +export class CalendarModule {} diff --git a/backend/src/modules/integration/google/calendar/calendar.service.ts b/backend/src/modules/integration/google/calendar/calendar.service.ts new file mode 100644 index 0000000..33eeb04 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/calendar.service.ts @@ -0,0 +1,867 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Auth, calendar_v3, google } from 'googleapis'; +import { Brackets, Repository } from 'typeorm'; +import { v4 as uuidv4 } from 'uuid'; + +import { + BadRequestError, + DateUtil, + formatState, + FrontendRoute, + NotFoundError, + parseState, + TokenService, + UrlGeneratorService, +} from '@/common'; +import { EntityInfoService } from '@/modules/entity/entity-info'; +import { AccountService } from '@/modules/iam/account/account.service'; +import { User } from '@/modules/iam/user/entities'; + +import { AuthService } from '../auth/auth.service'; +import { CreateGoogleCalendarDto, GoogleCalendarLinkedDto, UpdateGoogleCalendarDto } from './dto'; +import { CalendarType } from './enums'; +import { GoogleCalendar, GoogleCalendarAccount, GoogleCalendarLinked } from './entities'; +import { CalendarAccess, CalendarEvent, CalendarInfo, CalendarUpsertEvent, EventExtendedProperties } from './types'; +import { CalendarEmitter } from './calendar.emitter'; +import { EventUtil } from './utils'; + +interface FindFilter { + accountId: number; + calendarId?: number; + calendarAccountId?: number; + createdBy?: number; + externalId?: string; + type?: CalendarType; + objectId?: number; + linkedObjectId?: number; + processAll?: boolean; + responsibleId?: number; +} + +interface EventSearchParams { + api: calendar_v3.Calendar; + calendar: GoogleCalendar; + extendedProperties: EventExtendedProperties; + externalId?: string | null; +} +interface CalendarActionParams { + api: calendar_v3.Calendar; + calendar: GoogleCalendar; + event: E; + current: calendar_v3.Schema$Event | null; + extendedProperties: EventExtendedProperties; +} +type CalendarAction = (params: CalendarActionParams) => Promise; +type ApiAction = (params: { api: calendar_v3.Calendar; calendar: GoogleCalendar }) => Promise; + +const CallbackPath = '/api/integration/google/calendar/callback'; +const WebhookPath = { + calendars: '/api/integration/google/calendar/webhook-calendars', + events: '/api/integration/google/calendar/webhook-events', +}; + +@Injectable() +export class CalendarService { + private readonly logger = new Logger(CalendarService.name); + constructor( + @InjectRepository(GoogleCalendarAccount) + private readonly repositoryAccount: Repository, + @InjectRepository(GoogleCalendar) + private readonly repositoryCalendar: Repository, + @InjectRepository(GoogleCalendarLinked) + private readonly repositoryLinked: Repository, + private readonly tokenService: TokenService, + private readonly urlGenerator: UrlGeneratorService, + private readonly authService: AuthService, + private readonly accountService: AccountService, + private readonly entityInfoService: EntityInfoService, + private readonly calendarEmitter: CalendarEmitter, + ) {} + + public async getAuthorizeUrl({ accountId, userId }: { accountId: number; userId: number }): Promise { + return this.authService.generateAuthUrl({ + auth: { callbackPath: CallbackPath }, + scope: [ + 'https://www.googleapis.com/auth/calendar.calendarlist.readonly', + 'https://www.googleapis.com/auth/calendar.events', + ], + state: formatState(accountId, userId), + }); + } + + public async getRedirectUrl({ state, code }: { state: string; code: string }): Promise { + const [accountIdStr] = parseState(state, String); + if (accountIdStr) { + const accountId = Number(accountIdStr); + const account = await this.accountService.findOne({ accountId }); + if (account) { + return this.urlGenerator.createUrl({ + route: FrontendRoute.settings.google.calendar(), + subdomain: account.subdomain, + query: { code }, + }); + } + } + return null; + } + + public async processAuthCode({ code }: { code: string; state?: string }): Promise { + const tokens = await this.authService.getToken({ + auth: { callbackPath: CallbackPath }, + code, + }); + + const calendars = await this.getCalendarList(tokens); + const calendarInfos = calendars.filter((c) => !c.deleted && !c.hidden).map(CalendarInfo.fromApi); + + return new CalendarAccess({ calendarInfos, token: this.tokenService.create(tokens) }); + } + + public async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateGoogleCalendarDto; + }): Promise { + const tokens = this.tokenService.verify(dto.token); + + const calendarAccount = await this.getCalendarAccount({ accountId, tokens }); + const calendar = await this.repositoryCalendar.save( + GoogleCalendar.fromDto({ accountId, createdBy: userId, calendarAccountId: calendarAccount.id, dto }), + ); + + if (dto.linked) { + calendar.linked = await this.processLinked({ accountId, calendarId: calendar.id, dtos: dto.linked }); + } + + return this.withApi({ + calendar, + calendarAccount, + action: async ({ api, calendar }) => { + const from = dto.syncEvents ? DateUtil.startOf(DateUtil.now(), 'day') : undefined; + calendar = await this.syncEvents({ api, calendar, from }); + return this.startWatchEvents({ api, calendar }); + }, + }); + } + + public async findOne(filter: FindFilter & { user?: User }): Promise { + const createdBy = filter.user?.isAdmin ? undefined : filter.createdBy || filter.user?.id; + + return this.createFindQb({ ...filter, createdBy }).getOne(); + } + public async findMany(filter: FindFilter & { user?: User }): Promise { + const createdBy = filter.user?.isAdmin ? undefined : filter.createdBy || filter.user?.id; + + return this.createFindQb({ ...filter, createdBy }) + .orderBy('calendar.created_at', 'ASC') + .getMany(); + } + + public async update({ + accountId, + calendarId, + dto, + }: { + accountId: number; + userId: number; + calendarId: number; + dto: UpdateGoogleCalendarDto; + }): Promise { + const calendar = await this.findOne({ accountId, calendarId }); + if (!calendar) { + throw NotFoundError.withId(GoogleCalendar, calendarId); + } + + await this.repositoryCalendar.save(calendar.update(dto)); + + if (dto.linked) { + calendar.linked = await this.processLinked({ + accountId, + calendarId: calendar.id, + linked: calendar.linked, + dtos: dto.linked, + }); + } + + return calendar; + } + + public async delete({ accountId, calendarId }: { accountId: number; calendarId: number }): Promise { + const calendar = await this.findOne({ accountId, calendarId }); + if (!calendar) { + throw NotFoundError.withId(GoogleCalendar, calendarId); + } + + await this.deleteCalendar({ accountId, calendar }); + + return calendar.id; + } + + private async deleteCalendar({ + accountId, + calendar, + }: { + accountId: number; + calendar: GoogleCalendar; + }): Promise { + try { + await this.withApi({ calendar, action: (param) => this.stopWatchEvents(param) }); + } catch (e) { + this.logger.warn(`Failed to stop watch events: ${e}`); + } + await this.repositoryCalendar.delete({ accountId, id: calendar.id }); + + await this.cleanCalendarAccount({ accountId, calendarAccountId: calendar.calendarAccountId }); + + return calendar.id; + } + + public async processWebhookCalendars({ + channelId, + resourceId, + resourceState, + }: { + channelId: string; + resourceId: string; + resourceState: string; + }) { + if (resourceState === 'exists') { + const accounts = await this.repositoryAccount.find({ where: { channelId, channelResourceId: resourceId } }); + await Promise.all( + accounts.map(async (calendarAccount) => { + const api = await this.getApi(calendarAccount); + if (api) { + await this.syncCalendars({ api, calendarAccount, incremental: true }); + } + }), + ); + } + } + + public async processWebhookEvents({ + channelId, + resourceId, + resourceState, + }: { + channelId: string; + resourceId: string; + resourceState: string; + }) { + if (resourceState === 'exists') { + const calendars = await this.repositoryCalendar + .createQueryBuilder('calendar') + .leftJoinAndMapMany('calendar.linked', GoogleCalendarLinked, 'link', 'link.calendar_id = calendar.id') + .where('calendar.channel_id = :channelId', { channelId }) + .andWhere('calendar.channel_resource_id = :resourceId', { resourceId }) + .getMany(); + + await Promise.all( + calendars.map((calendar) => + this.withApi({ calendar, action: (param) => this.syncEvents({ ...param, incremental: true }) }), + ), + ); + } + } + + public async renewChannels(): Promise { + const accounts = await this.repositoryAccount + .createQueryBuilder() + .where('channel_expiration IS NOT NULL') + .andWhere(`channel_expiration < now() + interval '1 hour'`) + .getMany(); + + await Promise.all( + accounts.map(async (account) => { + const api = await this.getApi(account); + if (api) { + await this.stopWatchCalendars({ api, calendarAccount: account }); + return this.startWatchCalendars({ api, calendarAccount: account }); + } + return null; + }), + ); + + const calendars = await this.repositoryCalendar + .createQueryBuilder() + .where('channel_expiration IS NOT NULL') + .andWhere(`channel_expiration < now() + interval '1 hour'`) + .getMany(); + + await Promise.all( + calendars.map(async (calendar) => { + await this.withApi({ + calendar, + action: async (param) => { + await this.stopWatchEvents(param); + return this.startWatchEvents(param); + }, + }); + }), + ); + + return (accounts.length ?? 0) + (calendars.length ?? 0); + } + + public async handleUpsert(event: CalendarUpsertEvent) { + await this.processEvent({ event, action: (params) => this.upsertEvent(params) }); + } + + public async handleDeleted(event: CalendarEvent) { + await this.processEvent({ event, action: (params) => this.deleteEvent(params) }); + } + + public async handleDeleteByObject({ + accountId, + type, + objectId, + }: { + accountId: number; + type: CalendarType; + objectId: number; + }) { + const calendars = await this.findMany({ accountId, type, objectId }); + await Promise.all( + calendars.map(async (calendar) => { + await this.deleteCalendar({ accountId, calendar }); + }), + ); + await this.repositoryLinked.delete({ accountId, objectId, type }); + } + + public async handleDeleteByResponsible({ + accountId, + type, + responsibleId, + newResponsibleId, + }: { + accountId: number; + type: CalendarType; + responsibleId: number; + newResponsibleId?: number | null; + }) { + if (newResponsibleId) { + await this.repositoryCalendar.update({ accountId, type, responsibleId }, { responsibleId: newResponsibleId }); + } else { + const calendars = await this.findMany({ accountId, type, responsibleId }); + await Promise.all(calendars.map((calendar) => this.deleteCalendar({ accountId, calendar }))); + } + } + + private async processEvent({ event, action }: { event: E; action: CalendarAction }) { + const calendars = await this.findMany({ + accountId: event.accountId, + type: event.type, + responsibleId: event.ownerId, + objectId: event.objectId, + linkedObjectId: event.objectId, + processAll: true, + }); + + await Promise.all( + calendars + .filter((calendar) => !calendar.readonly) + .map(async (calendar) => { + const extendedProperties = EventUtil.getExtendedProperties(event); + + await this.withApi({ + calendar, + action: async ({ api, calendar }) => { + const current = await this.findTaskEvent({ + api, + calendar, + extendedProperties, + externalId: event.externalId, + }); + return action({ api, calendar, event, current, extendedProperties }); + }, + }); + }), + ); + } + + private async upsertEvent({ + api, + calendar, + event, + current, + extendedProperties, + }: CalendarActionParams) { + const requestBody = await this.createEventRequestBody({ event, extendedProperties }); + try { + if (current) { + api.events.update({ calendarId: calendar.externalId, eventId: current.id, requestBody }); + } else { + api.events.insert({ calendarId: calendar.externalId, requestBody }); + } + } catch (e) { + const error = e as Error; + this.logger.error(`Google Calendar upsert event error: ${error?.message}`, error?.stack); + } + } + + private async createEventRequestBody({ + event, + extendedProperties, + }: { + event: CalendarUpsertEvent; + extendedProperties: EventExtendedProperties; + }): Promise { + const source = await this.getSource({ accountId: event.accountId, entityId: event.entityId }); + return { + summary: event.title, + description: event.description, + start: { + dateTime: event.startDate.toISOString(), + timeZone: 'UTC', + }, + end: { + dateTime: event.endDate.toISOString(), + timeZone: 'UTC', + }, + source, + status: event.status, + extendedProperties, + }; + } + + private async getSource({ + accountId, + entityId, + }: { + accountId: number | null | undefined; + entityId: number | null | undefined; + }): Promise<{ title: string; url: string } | undefined> { + if (accountId && entityId) { + const entityInfo = await this.entityInfoService.findOne({ accountId, entityId }); + const account = await this.accountService.findOne({ accountId }); + const url = this.urlGenerator.createUrl({ + route: FrontendRoute.entity.card({ entityTypeId: entityInfo.entityTypeId, entityId: entityInfo.id }), + subdomain: account.subdomain, + }); + return { title: entityInfo.name, url }; + } + return undefined; + } + + private async deleteEvent({ api, calendar, current }: CalendarActionParams) { + if (current) { + try { + api.events.delete({ calendarId: calendar.externalId, eventId: current.id }); + } catch (e) { + const error = e as Error; + this.logger.error(`Google Calendar delete event error: ${error?.message}`, error?.stack); + } + } + } + + private async getCalendarList(tokens: Auth.Credentials): Promise { + try { + const { client } = await this.authService.getOAuth2Client({ tokens }); + const api = google.calendar({ version: 'v3', auth: client }); + + const { data } = await api.calendarList.list(); + + return data?.items ?? []; + } catch (e) { + const error = e as Error; + this.logger.error(`Google Calendar calendars list error: ${error?.message}`, error?.stack); + } + return []; + } + + private async getCalendarAccount({ + accountId, + tokens, + }: { + accountId: number; + tokens: Auth.Credentials; + }): Promise { + const calendars = await this.getCalendarList(tokens); + const primary = calendars.find((c) => c.primary); + if (!primary) { + throw new BadRequestError('Primary calendar not found'); + } + + let calendarAccount = await this.repositoryAccount.findOne({ where: { accountId, externalId: primary.id } }); + if (calendarAccount) { + await this.repositoryAccount.save(calendarAccount.updateTokens(tokens)); + + return calendarAccount; + } + + calendarAccount = await this.repositoryAccount.save( + new GoogleCalendarAccount({ accountId, tokens, externalId: primary.id }), + ); + + const api = await this.getApi(calendarAccount); + if (api) { + calendarAccount = await this.syncCalendars({ api, calendarAccount }); + calendarAccount = await this.startWatchCalendars({ calendarAccount, api }); + } + + return calendarAccount; + } + + private async cleanCalendarAccount({ + accountId, + calendarAccountId, + }: { + accountId: number; + calendarAccountId: number; + }) { + const calendars = await this.repositoryCalendar.find({ where: { accountId, calendarAccountId } }); + if (calendars.length === 0) { + const calendarAccount = await this.repositoryAccount.findOne({ where: { accountId, id: calendarAccountId } }); + if (calendarAccount) { + try { + const api = await this.getApi(calendarAccount); + if (api) { + await this.stopWatchCalendars({ calendarAccount, api }); + } + } catch (e) { + this.logger.warn(`Failed to stop watch calendars: ${e}`); + } + + await this.repositoryAccount.delete({ accountId, id: calendarAccountId }); + } + } + } + + private async getApi(calendarAccount: GoogleCalendarAccount): Promise { + try { + const { client, refreshedTokens } = await this.authService.getOAuth2Client({ + tokens: calendarAccount.tokens, + }); + if (refreshedTokens) { + calendarAccount = calendarAccount.updateTokens(refreshedTokens); + await this.repositoryAccount.save(calendarAccount); + } + return google.calendar({ version: 'v3', auth: client }); + } catch (error) { + this.logger.error(`Get API error for calendar account ${calendarAccount.id}`, (error as Error)?.stack); + return null; + } + } + + private async syncCalendars({ + api, + calendarAccount, + incremental = false, + }: { + api: calendar_v3.Calendar; + calendarAccount: GoogleCalendarAccount; + incremental?: boolean; + }): Promise { + let pageToken: string | undefined = undefined; + let syncToken: string | undefined = incremental ? calendarAccount.syncToken : undefined; + const calendars: calendar_v3.Schema$CalendarListEntry[] = []; + do { + try { + const { data } = await api.calendarList.list({ + showDeleted: !!syncToken, + pageToken, + syncToken, + }); + pageToken = data.nextPageToken; + syncToken = data.nextSyncToken ?? syncToken; + calendars.push(...(data.items ?? [])); + } catch (error) { + if (error['code'] === 410 && incremental) { + return this.syncCalendars({ api, calendarAccount }); + } else { + this.logger.error('Google Calendar calendars sync error', (error as Error)?.stack); + } + } + } while (pageToken); + + await Promise.all( + calendars + .filter((calendar) => calendar.deleted) + .map(async (calendar) => { + const current = await this.findOne({ + accountId: calendarAccount.accountId, + calendarAccountId: calendarAccount.id, + externalId: calendar.id, + }); + + if (current) { + await this.deleteCalendar({ accountId: current.accountId, calendar: current }); + } + }), + ); + + await this.repositoryAccount.update( + { accountId: calendarAccount.accountId, id: calendarAccount.id }, + { syncToken }, + ); + return calendarAccount.updateSyncToken(syncToken); + } + + private async startWatchCalendars({ + calendarAccount, + api, + }: { + calendarAccount: GoogleCalendarAccount; + api: calendar_v3.Calendar; + }): Promise { + const { data } = await api.calendarList.watch({ + requestBody: { + id: uuidv4(), + type: 'web_hook', + address: this.urlGenerator.createUrl({ route: WebhookPath.calendars }), + }, + }); + + const channelExpiration: Date | undefined = data.expiration ? new Date(Number(data.expiration)) : undefined; + await this.repositoryAccount.update( + { accountId: calendarAccount.accountId, id: calendarAccount.id }, + { channelId: data.id, channelResourceId: data.resourceId, channelExpiration }, + ); + + return calendarAccount.updateChannel({ channelId: data.id, channelResourceId: data.resourceId, channelExpiration }); + } + + private async stopWatchCalendars({ + calendarAccount, + api, + }: { + calendarAccount: GoogleCalendarAccount; + api: calendar_v3.Calendar; + }): Promise { + if (calendarAccount.channelId && calendarAccount.channelResourceId) { + try { + await api.channels.stop({ + requestBody: { id: calendarAccount.channelId, resourceId: calendarAccount.channelResourceId }, + }); + } catch (e) { + const error = e as Error; + this.logger.error(`Google Calendar stop watch error: ${error?.message}`, error?.stack); + } + } + + await this.repositoryAccount.update( + { accountId: calendarAccount.accountId, id: calendarAccount.id }, + { channelId: null, channelResourceId: null, channelExpiration: null }, + ); + + return calendarAccount.updateChannel({ channelId: null, channelResourceId: null, channelExpiration: null }); + } + + private async withApi({ + calendar, + calendarAccount, + action, + }: { + calendar: GoogleCalendar; + calendarAccount?: GoogleCalendarAccount; + action: ApiAction; + }): Promise { + if (!calendarAccount) { + calendarAccount = await this.repositoryAccount.findOne({ where: { id: calendar.calendarAccountId } }); + } + const api = await this.getApi(calendarAccount); + if (!api) { + throw new Error('Google Calendar API not found'); + } + + return action({ api, calendar }); + } + + private async syncEvents({ + api, + calendar, + incremental = false, + from, + }: { + api: calendar_v3.Calendar; + calendar: GoogleCalendar; + incremental?: boolean; + from?: Date; + }): Promise { + let pageToken: string | undefined = undefined; + let syncToken: string | undefined = incremental ? calendar.syncToken : undefined; + const events: calendar_v3.Schema$Event[] = []; + do { + try { + const { data } = await api.events.list({ + calendarId: calendar.externalId, + singleEvents: true, + showDeleted: !!syncToken, + updatedMin: !syncToken && !from ? DateUtil.now().toISOString() : undefined, + timeMin: !syncToken && from ? from.toISOString() : undefined, + pageToken, + syncToken, + }); + pageToken = data.nextPageToken; + syncToken = data.nextSyncToken ?? syncToken; + events.push(...(data.items ?? [])); + } catch (error) { + if (error['code'] === 410 && incremental) { + return this.syncEvents({ api, calendar }); + } else { + this.logger.error('Google Calendar events sync error', (error as Error)?.stack); + } + } + } while (pageToken); + + await Promise.all(events.map((event) => this.calendarEmitter.emit({ calendar, event }))); + + await this.repositoryCalendar.update({ accountId: calendar.accountId, id: calendar.id }, { syncToken }); + return calendar.updateSyncToken(syncToken); + } + + private async startWatchEvents({ + api, + calendar, + }: { + api: calendar_v3.Calendar; + calendar: GoogleCalendar; + }): Promise { + const { data } = await api.events.watch({ + calendarId: calendar.externalId, + requestBody: { + id: uuidv4(), + type: 'web_hook', + address: this.urlGenerator.createUrl({ route: WebhookPath.events }), + }, + }); + + const channelExpiration: Date | undefined = data.expiration ? new Date(Number(data.expiration)) : undefined; + await this.repositoryCalendar.update( + { accountId: calendar.accountId, id: calendar.id }, + { channelId: data.id, channelResourceId: data.resourceId, channelExpiration }, + ); + + return calendar.updateChannel({ channelId: data.id, channelResourceId: data.resourceId, channelExpiration }); + } + + private async stopWatchEvents({ + api, + calendar, + }: { + api: calendar_v3.Calendar; + calendar: GoogleCalendar; + }): Promise { + if (calendar.channelId && calendar.channelResourceId) { + try { + await api.channels.stop({ requestBody: { id: calendar.channelId, resourceId: calendar.channelResourceId } }); + } catch (e) { + const error = e as Error; + this.logger.error(`Google Calendar stop watch events error: ${error?.message}`, error?.stack); + } + } + + await this.repositoryCalendar.update( + { accountId: calendar.accountId, id: calendar.id }, + { channelId: null, channelResourceId: null, channelExpiration: null }, + ); + + return calendar.updateChannel({ channelId: null, channelResourceId: null, channelExpiration: null }); + } + + private async findTaskEvent({ + api, + calendar, + extendedProperties, + externalId, + }: EventSearchParams): Promise { + try { + if (externalId) { + const { data } = await api.events.list({ calendarId: calendar.externalId, iCalUID: externalId }); + if (data.items?.[0]) { + return data.items[0]; + } + } + + const { data } = await api.events.list({ + calendarId: calendar.externalId, + sharedExtendedProperty: Object.entries(extendedProperties.shared).map(([key, value]) => `${key}=${value}`), + }); + + return data?.items?.[0] ?? null; + } catch (e) { + const error = e as Error; + this.logger.error(`Google Calendar find task event error: ${error?.message}`, error?.stack); + } + return null; + } + + private async processLinked({ + accountId, + calendarId, + linked = [], + dtos, + }: { + accountId: number; + calendarId: number; + linked?: GoogleCalendarLinked[]; + dtos: GoogleCalendarLinkedDto[]; + }): Promise { + const toDelete = linked.filter((l) => !dtos.some((dto) => dto.type === l.type && dto.objectId === l.objectId)); + const toCreate = dtos.filter((dto) => !linked.find((l) => l.type === dto.type && l.objectId === dto.objectId)); + + await Promise.all(toDelete.map((item) => this.repositoryLinked.delete({ accountId, id: item.id }))); + const created = await Promise.all( + toCreate.map((dto) => this.repositoryLinked.save(GoogleCalendarLinked.fromDto({ accountId, calendarId, dto }))), + ); + + return [...linked.filter((l) => !toDelete.some((d) => d.id === l.id)), ...created]; + } + + private createFindQb(filter: FindFilter) { + const qb = this.repositoryCalendar + .createQueryBuilder('calendar') + .where('calendar.account_id = :accountId', { accountId: filter.accountId }) + .leftJoinAndMapMany('calendar.linked', GoogleCalendarLinked, 'link', 'link.calendar_id = calendar.id'); + if (filter.calendarId) { + qb.andWhere('calendar.id = :id', { id: filter.calendarId }); + } + if (filter.calendarAccountId) { + qb.andWhere('calendar.calendar_account_id = :calendarAccountId', { calendarAccountId: filter.calendarAccountId }); + } + if (filter.createdBy) { + qb.andWhere('calendar.created_by = :createdBy', { createdBy: filter.createdBy }); + } + if (filter.externalId) { + qb.andWhere('calendar.external_id = :externalId', { externalId: filter.externalId }); + } + if (filter.responsibleId) { + qb.andWhere('calendar.responsible_id = :responsibleId', { responsibleId: filter.responsibleId }); + } + if (filter.type) { + qb.andWhere('calendar.type = :type', { type: filter.type }); + } + if (filter.objectId || filter.linkedObjectId || filter.processAll) { + qb.andWhere( + new Brackets((qb1) => { + if (filter.objectId) { + qb1.where('calendar.object_id = :objectId', { objectId: filter.objectId }); + } + if (filter.linkedObjectId) { + if (filter.type) { + qb1.orWhere( + new Brackets((qbS) => + qbS + .where('link.type = :typeLinked', { typeLinked: filter.type }) + .andWhere('link.object_id = :linkedObjectId', { linkedObjectId: filter.linkedObjectId }), + ), + ); + } else { + qb1.orWhere('link.object_id = :linkedObjectId', { linkedObjectId: filter.linkedObjectId }); + } + } + if (filter.processAll) { + qb1.orWhere('calendar.process_all = true'); + } + }), + ); + } + + return qb; + } +} diff --git a/backend/src/modules/integration/google/calendar/dto/calendar-access.dto.ts b/backend/src/modules/integration/google/calendar/dto/calendar-access.dto.ts new file mode 100644 index 0000000..27f9008 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/dto/calendar-access.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsString } from 'class-validator'; +import { CalendarInfoDto } from './calendar-info.dto'; + +export class CalendarAccessDto { + @ApiProperty({ type: CalendarInfoDto, isArray: true, description: 'List of calendar information' }) + @IsArray() + calendarInfos: CalendarInfoDto[]; + + @ApiProperty({ description: 'Access token' }) + @IsString() + token: string; +} diff --git a/backend/src/modules/integration/google/calendar/dto/calendar-info.dto.ts b/backend/src/modules/integration/google/calendar/dto/calendar-info.dto.ts new file mode 100644 index 0000000..0817f43 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/dto/calendar-info.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +export class CalendarInfoDto { + @ApiProperty({ description: 'Calendar ID' }) + @IsString() + id: string; + + @ApiProperty({ description: 'Calendar title' }) + @IsString() + title: string; + + @ApiProperty({ description: 'Is primary calendar' }) + @IsBoolean() + primary: boolean; + + @ApiProperty({ description: 'Is read-only calendar' }) + @IsBoolean() + readonly: boolean; + + @ApiPropertyOptional({ description: 'Calendar description' }) + @IsOptional() + @IsString() + description?: string; + + @ApiPropertyOptional({ description: 'Calendar time zone' }) + @IsOptional() + @IsString() + timeZone?: string; + + @ApiPropertyOptional({ description: 'Calendar color in hex format', example: '#000000' }) + @IsOptional() + @IsString() + color?: string; +} diff --git a/backend/src/modules/integration/google/calendar/dto/create-google-calendar.dto.ts b/backend/src/modules/integration/google/calendar/dto/create-google-calendar.dto.ts new file mode 100644 index 0000000..01d95d2 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/dto/create-google-calendar.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty, ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsBoolean, IsOptional, IsString } from 'class-validator'; + +import { GoogleCalendarDto } from './google-calendar.dto'; + +export class CreateGoogleCalendarDto extends PickType(GoogleCalendarDto, [ + 'externalId', + 'title', + 'readonly', + 'type', + 'objectId', + 'responsibleId', + 'processAll', + 'linked', +] as const) { + @ApiProperty({ description: 'Access token' }) + @IsString() + token: string; + + @ApiPropertyOptional({ description: 'Sync events from today to future' }) + @IsOptional() + @IsBoolean() + syncEvents?: boolean; +} diff --git a/backend/src/modules/integration/google/calendar/dto/google-calendar-linked.dto.ts b/backend/src/modules/integration/google/calendar/dto/google-calendar-linked.dto.ts new file mode 100644 index 0000000..7749d36 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/dto/google-calendar-linked.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber } from 'class-validator'; + +import { CalendarType } from '../enums'; + +export class GoogleCalendarLinkedDto { + @ApiProperty({ enum: CalendarType, description: 'Linked object type' }) + @IsEnum(CalendarType) + type: CalendarType; + + @ApiProperty({ description: 'Linked object ID' }) + @IsNumber() + objectId: number; +} diff --git a/backend/src/modules/integration/google/calendar/dto/google-calendar.dto.ts b/backend/src/modules/integration/google/calendar/dto/google-calendar.dto.ts new file mode 100644 index 0000000..bb29d2f --- /dev/null +++ b/backend/src/modules/integration/google/calendar/dto/google-calendar.dto.ts @@ -0,0 +1,52 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { CalendarType } from '../enums'; +import { GoogleCalendarLinkedDto } from './google-calendar-linked.dto'; + +export class GoogleCalendarDto { + @ApiProperty({ description: 'Calendar ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Created by user ID' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ description: 'Created at' }) + @IsString() + createdAt: string; + + @ApiProperty({ description: 'External calendar ID' }) + @IsString() + externalId: string; + + @ApiProperty({ description: 'Title' }) + @IsString() + title: string; + + @ApiProperty({ description: 'Readonly' }) + @IsBoolean() + readonly: boolean; + + @ApiProperty({ enum: CalendarType, description: 'Linked object type' }) + @IsEnum(CalendarType) + type: CalendarType; + + @ApiProperty({ description: 'Linked object ID' }) + @IsNumber() + objectId: number; + + @ApiProperty({ description: 'Default responsible ID' }) + @IsNumber() + responsibleId: number; + + @ApiProperty({ description: 'Process all events' }) + @IsOptional() + @IsBoolean() + processAll?: boolean | null; + + @ApiPropertyOptional({ type: [GoogleCalendarLinkedDto], nullable: true, description: 'Secondary linked objects' }) + @IsOptional() + linked?: GoogleCalendarLinkedDto[] | null; +} diff --git a/backend/src/modules/integration/google/calendar/dto/index.ts b/backend/src/modules/integration/google/calendar/dto/index.ts new file mode 100644 index 0000000..e66e2c8 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/dto/index.ts @@ -0,0 +1,6 @@ +export * from './calendar-access.dto'; +export * from './calendar-info.dto'; +export * from './create-google-calendar.dto'; +export * from './google-calendar-linked.dto'; +export * from './google-calendar.dto'; +export * from './update-google-calendar.dto'; diff --git a/backend/src/modules/integration/google/calendar/dto/update-google-calendar.dto.ts b/backend/src/modules/integration/google/calendar/dto/update-google-calendar.dto.ts new file mode 100644 index 0000000..3632d17 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/dto/update-google-calendar.dto.ts @@ -0,0 +1,7 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { GoogleCalendarDto } from './google-calendar.dto'; + +export class UpdateGoogleCalendarDto extends PartialType( + PickType(GoogleCalendarDto, ['title', 'type', 'objectId', 'responsibleId', 'linked', 'processAll'] as const), +) {} diff --git a/backend/src/modules/integration/google/calendar/entities/google-calendar-account.entity.ts b/backend/src/modules/integration/google/calendar/entities/google-calendar-account.entity.ts new file mode 100644 index 0000000..b69793e --- /dev/null +++ b/backend/src/modules/integration/google/calendar/entities/google-calendar-account.entity.ts @@ -0,0 +1,57 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { Auth } from 'googleapis'; + +@Entity() +export class GoogleCalendarAccount { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + externalId: string; + + @Column({ type: 'jsonb' }) + tokens: Auth.Credentials; + + @Column({ nullable: true }) + syncToken: string | null; + + @Column({ nullable: true }) + channelId: string | null; + + @Column({ nullable: true }) + channelResourceId: string | null; + + @Column({ nullable: true }) + channelExpiration: Date | null; + + constructor(data?: Pick & { createdAt?: Date }) { + this.accountId = data?.accountId; + this.tokens = data?.tokens; + this.externalId = data?.externalId; + } + + updateTokens(tokens: Auth.Credentials): GoogleCalendarAccount { + this.tokens = tokens; + + return this; + } + + updateChannel( + data: Pick, + ): GoogleCalendarAccount { + this.channelId = data.channelId; + this.channelResourceId = data.channelResourceId; + this.channelExpiration = data.channelExpiration; + + return this; + } + + updateSyncToken(syncToken: string): GoogleCalendarAccount { + this.syncToken = syncToken; + + return this; + } +} diff --git a/backend/src/modules/integration/google/calendar/entities/google-calendar-linked.entity.ts b/backend/src/modules/integration/google/calendar/entities/google-calendar-linked.entity.ts new file mode 100644 index 0000000..1c0aa94 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/entities/google-calendar-linked.entity.ts @@ -0,0 +1,50 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { GoogleCalendarLinkedDto } from '../dto'; +import { CalendarType } from '../enums'; + +@Entity() +export class GoogleCalendarLinked { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + calendarId: number; + + @Column() + type: CalendarType; + + @Column() + objectId: number; + + constructor(data?: Pick) { + this.accountId = data?.accountId; + this.calendarId = data?.calendarId; + this.type = data?.type; + this.objectId = data?.objectId; + } + + static fromDto({ + accountId, + calendarId, + dto, + }: { + accountId: number; + calendarId: number; + dto: GoogleCalendarLinkedDto; + }): GoogleCalendarLinked { + return new GoogleCalendarLinked({ + accountId: accountId, + calendarId: calendarId, + type: dto.type, + objectId: dto.objectId, + }); + } + + toDto(): GoogleCalendarLinkedDto { + return { type: this.type, objectId: this.objectId }; + } +} diff --git a/backend/src/modules/integration/google/calendar/entities/google-calendar.entity.ts b/backend/src/modules/integration/google/calendar/entities/google-calendar.entity.ts new file mode 100644 index 0000000..8225ae7 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/entities/google-calendar.entity.ts @@ -0,0 +1,160 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { CreateGoogleCalendarDto, GoogleCalendarDto, UpdateGoogleCalendarDto } from '../dto'; +import { CalendarType } from '../enums'; + +import { GoogleCalendarLinked } from './google-calendar-linked.entity'; + +@Entity() +export class GoogleCalendar { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + createdBy: number; + + @Column() + createdAt: Date; + + @Column() + calendarAccountId: number; + + @Column() + externalId: string; + + @Column() + title: string; + + @Column() + readonly: boolean; + + @Column() + type: CalendarType; + + @Column() + objectId: number; + + @Column() + responsibleId: number; + + @Column({ nullable: true }) + syncToken: string | null; + + @Column({ nullable: true }) + channelId: string | null; + + @Column({ nullable: true }) + channelResourceId: string | null; + + @Column({ nullable: true }) + channelExpiration: Date | null; + + @Column({ default: false }) + processAll: boolean; + + constructor( + data?: Pick< + GoogleCalendar, + | 'accountId' + | 'createdBy' + | 'calendarAccountId' + | 'externalId' + | 'title' + | 'readonly' + | 'type' + | 'objectId' + | 'responsibleId' + | 'processAll' + > & { createdAt?: Date }, + ) { + this.accountId = data?.accountId; + this.createdBy = data?.createdBy; + this.createdAt = data?.createdAt ?? DateUtil.now(); + this.calendarAccountId = data?.calendarAccountId ?? data?.accountId; + this.externalId = data?.externalId; + this.title = data?.title; + this.readonly = data?.readonly; + this.type = data?.type; + this.objectId = data?.objectId; + this.responsibleId = data?.responsibleId; + this.processAll = data?.processAll ?? false; + } + + private _linked?: GoogleCalendarLinked[] | null; + get linked(): GoogleCalendarLinked[] | null { + return this._linked; + } + set linked(value: GoogleCalendarLinked[] | null) { + this._linked = value; + } + + static fromDto({ + accountId, + createdBy, + calendarAccountId, + dto, + }: { + accountId: number; + createdBy: number; + calendarAccountId: number; + dto: CreateGoogleCalendarDto; + }): GoogleCalendar { + return new GoogleCalendar({ + accountId: accountId, + createdBy: createdBy, + calendarAccountId: calendarAccountId, + externalId: dto.externalId, + title: dto.title, + readonly: dto.readonly, + type: dto.type, + objectId: dto.objectId, + responsibleId: dto.responsibleId, + processAll: dto.processAll, + }); + } + + update(dto: UpdateGoogleCalendarDto): GoogleCalendar { + this.title = dto.title ?? this.title; + this.type = dto.type ?? this.type; + this.objectId = dto.objectId ?? this.objectId; + this.responsibleId = dto.responsibleId ?? dto.responsibleId; + this.processAll = dto.processAll ?? this.processAll; + + return this; + } + + updateChannel(data: Pick): GoogleCalendar { + this.channelId = data.channelId; + this.channelResourceId = data.channelResourceId; + this.channelExpiration = data.channelExpiration; + + return this; + } + + updateSyncToken(syncToken: string): GoogleCalendar { + this.syncToken = syncToken; + + return this; + } + + toDto(): GoogleCalendarDto { + return { + id: this.id, + createdBy: this.createdBy, + createdAt: this.createdAt.toISOString(), + externalId: this.externalId, + title: this.title, + readonly: this.readonly, + type: this.type, + objectId: this.objectId, + responsibleId: this.responsibleId, + processAll: this.processAll, + linked: this.linked?.map((item) => item.toDto()), + }; + } +} diff --git a/backend/src/modules/integration/google/calendar/entities/index.ts b/backend/src/modules/integration/google/calendar/entities/index.ts new file mode 100644 index 0000000..1b0e5b6 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/entities/index.ts @@ -0,0 +1,3 @@ +export * from './google-calendar-account.entity'; +export * from './google-calendar-linked.entity'; +export * from './google-calendar.entity'; diff --git a/backend/src/modules/integration/google/calendar/enums/calendar-type.enum.ts b/backend/src/modules/integration/google/calendar/enums/calendar-type.enum.ts new file mode 100644 index 0000000..1029033 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/enums/calendar-type.enum.ts @@ -0,0 +1,4 @@ +export enum CalendarType { + Task = 'task', + Schedule = 'schedule', +} diff --git a/backend/src/modules/integration/google/calendar/enums/index.ts b/backend/src/modules/integration/google/calendar/enums/index.ts new file mode 100644 index 0000000..4053ad1 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/enums/index.ts @@ -0,0 +1 @@ +export * from './calendar-type.enum'; diff --git a/backend/src/modules/integration/google/calendar/public-calendar.controller.ts b/backend/src/modules/integration/google/calendar/public-calendar.controller.ts new file mode 100644 index 0000000..e84637b --- /dev/null +++ b/backend/src/modules/integration/google/calendar/public-calendar.controller.ts @@ -0,0 +1,36 @@ +import { Controller, Get, Headers, Post, Query, Redirect } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { CalendarService } from './calendar.service'; + +@ApiExcludeController() +@Controller('integration/google/calendar') +export class PublicCalendarController { + constructor(private readonly service: CalendarService) {} + + @Get('callback') + @Redirect() + public async callback(@Query('code') code: string, @Query('state') state?: string) { + const redirectUrl = await this.service.getRedirectUrl({ state, code }); + + return { url: redirectUrl, statusCode: 302 }; + } + + @Post('webhook-calendars') + public async webhookCalendars( + @Headers('X-Goog-Channel-ID') channelId: string, + @Headers('X-Goog-Resource-ID') resourceId: string, + @Headers('X-Goog-Resource-State') resourceState: string, + ) { + this.service.processWebhookCalendars({ channelId, resourceId, resourceState }); + } + + @Post('webhook-events') + public async webhookEvents( + @Headers('X-Goog-Channel-ID') channelId: string, + @Headers('X-Goog-Resource-ID') resourceId: string, + @Headers('X-Goog-Resource-State') resourceState: string, + ) { + this.service.processWebhookEvents({ channelId, resourceId, resourceState }); + } +} diff --git a/backend/src/modules/integration/google/calendar/types/calendar-access.ts b/backend/src/modules/integration/google/calendar/types/calendar-access.ts new file mode 100644 index 0000000..94e7d76 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/types/calendar-access.ts @@ -0,0 +1,19 @@ +import { CalendarAccessDto } from '../dto'; +import { CalendarInfo } from './calendar-info'; + +export class CalendarAccess { + calendarInfos: CalendarInfo[]; + token: string; + + constructor(data: Omit) { + this.calendarInfos = data.calendarInfos; + this.token = data.token; + } + + public toDto(): CalendarAccessDto { + return { + calendarInfos: this.calendarInfos.map((calendar) => calendar.toDto()), + token: this.token, + }; + } +} diff --git a/backend/src/modules/integration/google/calendar/types/calendar-event.ts b/backend/src/modules/integration/google/calendar/types/calendar-event.ts new file mode 100644 index 0000000..587d62d --- /dev/null +++ b/backend/src/modules/integration/google/calendar/types/calendar-event.ts @@ -0,0 +1,10 @@ +import { CalendarType } from '../enums'; + +export interface CalendarEvent { + accountId: number | null; + ownerId?: number | null; + objectId: number | null; + eventId: number | null; + type: CalendarType | null; + externalId: string | null; +} diff --git a/backend/src/modules/integration/google/calendar/types/calendar-info.ts b/backend/src/modules/integration/google/calendar/types/calendar-info.ts new file mode 100644 index 0000000..b109e73 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/types/calendar-info.ts @@ -0,0 +1,46 @@ +import { calendar_v3 } from 'googleapis'; +import { CalendarInfoDto } from '../dto'; + +export class CalendarInfo { + id: string; + title: string; + primary: boolean; + readonly: boolean; + description?: string; + timeZone?: string; + color?: string; + + constructor(data: Omit) { + this.id = data.id; + this.title = data.title; + this.primary = data.primary; + this.readonly = data.readonly; + this.description = data.description; + this.timeZone = data.timeZone; + this.color = data.color; + } + + static fromApi(calendar: calendar_v3.Schema$CalendarListEntry): CalendarInfo { + return new CalendarInfo({ + id: calendar.id, + title: calendar.summary, + primary: calendar.primary ?? false, + readonly: !['owner', 'writer'].includes(calendar.accessRole ?? ''), + description: calendar.description, + timeZone: calendar.timeZone, + color: calendar.backgroundColor, + }); + } + + toDto(): CalendarInfoDto { + return { + id: this.id, + title: this.title, + primary: this.primary, + readonly: this.readonly, + description: this.description, + timeZone: this.timeZone, + color: this.color, + }; + } +} diff --git a/backend/src/modules/integration/google/calendar/types/calendar-upsert-event.ts b/backend/src/modules/integration/google/calendar/types/calendar-upsert-event.ts new file mode 100644 index 0000000..d68bc1f --- /dev/null +++ b/backend/src/modules/integration/google/calendar/types/calendar-upsert-event.ts @@ -0,0 +1,12 @@ +import { CalendarEvent } from './calendar-event'; + +export type CalendarEventStatus = 'confirmed' | 'tentative' | 'cancelled'; + +export interface CalendarUpsertEvent extends CalendarEvent { + title: string; + description: string; + startDate: Date; + endDate: Date; + status?: CalendarEventStatus | null; + entityId?: number | null; +} diff --git a/backend/src/modules/integration/google/calendar/types/event-extended-properties.ts b/backend/src/modules/integration/google/calendar/types/event-extended-properties.ts new file mode 100644 index 0000000..17deb0a --- /dev/null +++ b/backend/src/modules/integration/google/calendar/types/event-extended-properties.ts @@ -0,0 +1,4 @@ +export interface EventExtendedProperties { + private?: Record; + shared?: Record; +} diff --git a/backend/src/modules/integration/google/calendar/types/index.ts b/backend/src/modules/integration/google/calendar/types/index.ts new file mode 100644 index 0000000..f840d5f --- /dev/null +++ b/backend/src/modules/integration/google/calendar/types/index.ts @@ -0,0 +1,5 @@ +export * from './calendar-access'; +export * from './calendar-event'; +export * from './calendar-info'; +export * from './calendar-upsert-event'; +export * from './event-extended-properties'; diff --git a/backend/src/modules/integration/google/calendar/utils/appointment.util.ts b/backend/src/modules/integration/google/calendar/utils/appointment.util.ts new file mode 100644 index 0000000..5d80081 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/utils/appointment.util.ts @@ -0,0 +1,30 @@ +import { ScheduleAppointmentStatus } from '@/modules/scheduler/common'; +import { CalendarEventStatus } from '../types'; + +export class AppointmentUtil { + public static convertAppointmentStatus(status: ScheduleAppointmentStatus): CalendarEventStatus | undefined { + switch (status) { + case ScheduleAppointmentStatus.Canceled: + return 'cancelled'; + case ScheduleAppointmentStatus.NotConfirmed: + return 'tentative'; + case ScheduleAppointmentStatus.Confirmed: + return 'confirmed'; + default: + return undefined; + } + } + + public static convertEventStatus(status: string): ScheduleAppointmentStatus { + switch (status) { + case 'cancelled': + return ScheduleAppointmentStatus.Canceled; + case 'tentative': + return ScheduleAppointmentStatus.NotConfirmed; + case 'confirmed': + return ScheduleAppointmentStatus.Confirmed; + default: + return ScheduleAppointmentStatus.NotConfirmed; + } + } +} diff --git a/backend/src/modules/integration/google/calendar/utils/event.util.ts b/backend/src/modules/integration/google/calendar/utils/event.util.ts new file mode 100644 index 0000000..817bb60 --- /dev/null +++ b/backend/src/modules/integration/google/calendar/utils/event.util.ts @@ -0,0 +1,45 @@ +import { calendar_v3 } from 'googleapis'; + +import { CalendarType } from '../enums'; +import { CalendarEvent, EventExtendedProperties } from '../types'; + +export class EventUtil { + public static createExternalId(event: calendar_v3.Schema$Event): string { + return event.iCalUID ?? `${event.id}@google.com`; + } + + public static getExtendedProperties(event: CalendarEvent): EventExtendedProperties { + return { + shared: { + accountId: event.accountId.toString(), + ownerId: event.ownerId?.toString(), + objectId: event.objectId.toString(), + eventId: event.eventId.toString(), + type: event.type, + }, + }; + } + + public static getCalendarEvent(event: calendar_v3.Schema$Event): CalendarEvent { + const accountId = event.extendedProperties?.shared?.['accountId']; + const ownerId = event.extendedProperties?.shared?.['ownerId']; + const objectId = event.extendedProperties?.shared?.['objectId']; + const eventId = event.extendedProperties?.shared?.['eventId']; + const type = event.extendedProperties?.shared?.['type'] as CalendarType; + + return { + accountId: accountId ? Number(accountId) : null, + ownerId: ownerId ? Number(ownerId) : null, + objectId: objectId ? Number(objectId) : null, + eventId: eventId ? Number(eventId) : null, + type: type ?? null, + externalId: EventUtil.createExternalId(event), + }; + } + + public static getEventDate(date: calendar_v3.Schema$EventDateTime): Date | undefined { + const d = date.dateTime || date.date; + + return d ? new Date(d) : undefined; + } +} diff --git a/backend/src/modules/integration/google/calendar/utils/index.ts b/backend/src/modules/integration/google/calendar/utils/index.ts new file mode 100644 index 0000000..67470bb --- /dev/null +++ b/backend/src/modules/integration/google/calendar/utils/index.ts @@ -0,0 +1,2 @@ +export * from './appointment.util'; +export * from './event.util'; diff --git a/backend/src/modules/integration/google/google.config.ts b/backend/src/modules/integration/google/google.config.ts new file mode 100644 index 0000000..69ebfd0 --- /dev/null +++ b/backend/src/modules/integration/google/google.config.ts @@ -0,0 +1,20 @@ +import { registerAs } from '@nestjs/config'; + +interface Auth { + clientId: string; + clientSecret: string; +} + +export interface GoogleConfig { + auth: Auth; +} + +export default registerAs( + 'google', + (): GoogleConfig => ({ + auth: { + clientId: process.env.GOOGLE_AUTH_CLIENT_ID, + clientSecret: process.env.GOOGLE_AUTH_CLIENT_SECRET, + }, + }), +); diff --git a/backend/src/modules/integration/google/google.module.ts b/backend/src/modules/integration/google/google.module.ts new file mode 100644 index 0000000..b7d05ec --- /dev/null +++ b/backend/src/modules/integration/google/google.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; + +import googleConfig from './google.config'; +import { CalendarModule } from './calendar/calendar.module'; + +@Module({ + imports: [ConfigModule.forFeature(googleConfig), CalendarModule], +}) +export class GoogleModule {} diff --git a/backend/src/modules/integration/integration.module.ts b/backend/src/modules/integration/integration.module.ts new file mode 100644 index 0000000..061af1e --- /dev/null +++ b/backend/src/modules/integration/integration.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { ConditionalModule } from '@nestjs/config'; + +import { AppsumoModule } from './appsumo/appsumo.module'; +import { GoogleModule } from './google/google.module'; +import { StripeModule } from './stripe/stripe.module'; + +@Module({ + imports: [AppsumoModule, GoogleModule, ConditionalModule.registerWhen(StripeModule, 'STRIPE_ENABLED')], + exports: [AppsumoModule], +}) +export class IntegrationModule {} diff --git a/backend/src/modules/integration/stripe/dto/index.ts b/backend/src/modules/integration/stripe/dto/index.ts new file mode 100644 index 0000000..b5dafbc --- /dev/null +++ b/backend/src/modules/integration/stripe/dto/index.ts @@ -0,0 +1,4 @@ +export * from './subscription-feature.dto'; +export * from './subscription-order.dto'; +export * from './subscription-plan.dto'; +export * from './subscription-price.dto'; diff --git a/backend/src/modules/integration/stripe/dto/subscription-feature.dto.ts b/backend/src/modules/integration/stripe/dto/subscription-feature.dto.ts new file mode 100644 index 0000000..e232e14 --- /dev/null +++ b/backend/src/modules/integration/stripe/dto/subscription-feature.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsString } from 'class-validator'; + +export class SubscriptionFeatureDto { + @ApiProperty({ description: 'Subscription feature name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Is subscription feature available' }) + @IsBoolean() + available: boolean; +} diff --git a/backend/src/modules/integration/stripe/dto/subscription-order.dto.ts b/backend/src/modules/integration/stripe/dto/subscription-order.dto.ts new file mode 100644 index 0000000..15ed6e2 --- /dev/null +++ b/backend/src/modules/integration/stripe/dto/subscription-order.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class SubscriptionOrderDto { + @ApiProperty({ nullable: true, description: 'Stripe product id' }) + @IsString() + productId: string; + + @ApiPropertyOptional({ nullable: true, description: 'Stripe price id' }) + @IsOptional() + @IsString() + priceId?: string | null; + + @ApiProperty({ description: 'Number of users' }) + @IsNumber() + numberOfUsers: number; + + @ApiPropertyOptional({ nullable: true, description: 'Payment amount' }) + @IsOptional() + @IsNumber() + amount?: number | null; + + @ApiPropertyOptional({ description: 'Discount code' }) + @IsOptional() + @IsString() + couponId?: string | null; +} diff --git a/backend/src/modules/integration/stripe/dto/subscription-plan.dto.ts b/backend/src/modules/integration/stripe/dto/subscription-plan.dto.ts new file mode 100644 index 0000000..c1d8887 --- /dev/null +++ b/backend/src/modules/integration/stripe/dto/subscription-plan.dto.ts @@ -0,0 +1,54 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { SubscriptionPriceDto } from './subscription-price.dto'; +import { SubscriptionFeatureDto } from './subscription-feature.dto'; + +export class SubscriptionPlanDto { + @ApiProperty({ description: 'Stripe product id' }) + @IsString() + id: string; + + @ApiPropertyOptional({ nullable: true, description: 'Subscription plan code' }) + @IsOptional() + @IsString() + code?: string | null; + + @ApiProperty({ description: 'Subscription product name' }) + @IsString() + name: string; + + @ApiPropertyOptional({ nullable: true, description: 'Subscription product description' }) + @IsOptional() + @IsString() + description: string | null; + + @ApiPropertyOptional({ description: 'Subscription product order' }) + @IsOptional() + @IsNumber() + order?: number | null; + + @ApiProperty({ type: [SubscriptionPriceDto], description: 'Subscription plan prices' }) + @IsArray() + prices: SubscriptionPriceDto[]; + + @ApiPropertyOptional({ nullable: true, description: 'Default price id' }) + @IsOptional() + @IsString() + defaultPriceId?: string | null = null; + + @ApiPropertyOptional({ nullable: true, type: [SubscriptionFeatureDto], description: 'Subscription plan features' }) + @IsOptional() + @IsArray() + features?: SubscriptionFeatureDto[] | null; + + @ApiPropertyOptional({ description: 'Is subscription plan default' }) + @IsOptional() + @IsBoolean() + isDefault?: boolean; + + @ApiPropertyOptional({ nullable: true, description: 'Subscription plan user limit' }) + @IsOptional() + @IsNumber() + userLimit?: number | null; +} diff --git a/backend/src/modules/integration/stripe/dto/subscription-price.dto.ts b/backend/src/modules/integration/stripe/dto/subscription-price.dto.ts new file mode 100644 index 0000000..b2ff7b3 --- /dev/null +++ b/backend/src/modules/integration/stripe/dto/subscription-price.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class SubscriptionPriceDto { + @ApiProperty({ description: 'Stripe price id' }) + @IsString() + id: string; + + @ApiProperty({ description: 'Price amount' }) + @IsNumber() + amount: number; + + @ApiProperty({ description: 'Price currency' }) + @IsString() + currency: string; + + @ApiProperty({ description: 'Price interval' }) + @IsString() + interval: string; +} diff --git a/backend/src/modules/integration/stripe/public-stripe.controller.ts b/backend/src/modules/integration/stripe/public-stripe.controller.ts new file mode 100644 index 0000000..74f8caa --- /dev/null +++ b/backend/src/modules/integration/stripe/public-stripe.controller.ts @@ -0,0 +1,25 @@ +import { Body, Controller, Get, Post, RawBodyRequest, Req } from '@nestjs/common'; +import { ApiExcludeEndpoint, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { SubscriptionPlanDto } from './dto'; +import { StripeService } from './stripe.service'; + +@ApiTags('integration/stripe') +@Controller('iam/subscriptions/stripe') +export class PublicStripeController { + constructor(private readonly service: StripeService) {} + + @ApiOperation({ summary: 'Get available subscription plans', description: 'Get available subscription plans' }) + @ApiOkResponse({ type: [SubscriptionPlanDto], description: 'Subscription plans' }) + @Get('plans') + public async getSubscriptionPlans(): Promise { + return this.service.getSubscriptionPlans(); + } + + @ApiExcludeEndpoint(true) + @Post('webhook') + public async handleWebhook(@Req() request: RawBodyRequest, @Body() body: unknown): Promise { + const signature = request.headers['stripe-signature']; + return await this.service.handleWebhook(body, request.rawBody, signature); + } +} diff --git a/backend/src/modules/integration/stripe/stripe.controller.ts b/backend/src/modules/integration/stripe/stripe.controller.ts new file mode 100644 index 0000000..4f442f8 --- /dev/null +++ b/backend/src/modules/integration/stripe/stripe.controller.ts @@ -0,0 +1,31 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { SubscriptionOrderDto } from './dto'; +import { StripeService } from './stripe.service'; + +@ApiTags('integration/stripe') +@Controller('iam/subscriptions/stripe') +@JwtAuthorized({ prefetch: { account: true } }) +export class StripeController { + constructor(private readonly service: StripeService) {} + + @ApiOperation({ summary: 'Create checkout session', description: 'Create checkout session' }) + @ApiOkResponse({ type: String, description: 'Checkout URL' }) + @Get('checkout') + public async getCheckoutUrl( + @CurrentAuth() { account, userId }: AuthData, + @Query() dto: SubscriptionOrderDto, + ): Promise { + return this.service.getCheckoutUrl(account, userId, dto); + } + + @ApiOperation({ summary: 'Get customer portal URL', description: 'Get customer portal URL' }) + @ApiOkResponse({ type: String, description: 'Customer portal URL' }) + @Get('portal') + public async getCustomerPortalUrl(@CurrentAuth() { account }: AuthData): Promise { + return this.service.getCustomerPortalUrl(account); + } +} diff --git a/backend/src/modules/integration/stripe/stripe.module.ts b/backend/src/modules/integration/stripe/stripe.module.ts new file mode 100644 index 0000000..e848a2b --- /dev/null +++ b/backend/src/modules/integration/stripe/stripe.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { StripeService } from './stripe.service'; +import { PublicStripeController } from './public-stripe.controller'; +import { StripeController } from './stripe.controller'; + +@Module({ + imports: [IAMModule], + providers: [StripeService], + controllers: [PublicStripeController, StripeController], +}) +export class StripeModule {} diff --git a/backend/src/modules/integration/stripe/stripe.service.ts b/backend/src/modules/integration/stripe/stripe.service.ts new file mode 100644 index 0000000..d1becce --- /dev/null +++ b/backend/src/modules/integration/stripe/stripe.service.ts @@ -0,0 +1,219 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import Stripe from 'stripe'; + +import { UrlGeneratorService, FrontendRoute, BadRequestError, DateUtil } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AccountSubscriptionService } from '@/modules/iam/account-subscription/account-subscription.service'; + +import { SubscriptionPlanDto, SubscriptionPriceDto, SubscriptionFeatureDto, SubscriptionOrderDto } from './dto'; + +@Injectable() +export class StripeService { + private readonly logger = new Logger(StripeService.name); + private stripe: Stripe; + private stripeWebhookSecret: string; + + constructor( + private readonly configService: ConfigService, + private readonly urlGenerator: UrlGeneratorService, + private readonly subscriptionService: AccountSubscriptionService, + ) { + this.stripe = new Stripe(this.configService.get('STRIPE_SECRET'), { apiVersion: '2025-02-24.acacia' }); + this.stripeWebhookSecret = this.configService.get('STRIPE_WEBHOOK_SECRET'); + } + + public async getSubscriptionPlans(): Promise { + const { data } = await this.stripe.products.list({ active: true, limit: 100 }); + const products = data.filter( + (product) => product.metadata['site_code'] !== undefined || product.metadata['site_order'] !== undefined, + ); + + const plans: SubscriptionPlanDto[] = []; + for (const product of products) { + const prices = await this.stripe.prices.list({ product: product.id }); + const priceDtos: SubscriptionPriceDto[] = prices.data.map((price) => ({ + id: price.id, + amount: price.unit_amount / 100.0, + currency: price.currency, + interval: price.recurring?.interval ?? 'one_time', + })); + const defaultPrice = typeof product.default_price === 'string' ? product.default_price : product.default_price.id; + const userLimit = product.metadata['site_user_limit'] ? Number(product.metadata['site_user_limit']) : null; + + let featureDtos: SubscriptionFeatureDto[] = []; + if (product.metadata['site_features']) { + try { + featureDtos = JSON.parse(product.metadata['site_features']) as SubscriptionFeatureDto[]; + } catch (e) { + this.logger.error(`Error parsing Stripe features`, (e as Error)?.stack); + } + } + + const isDefault = !!product.metadata['site_default']; + plans.push({ + id: product.id, + code: product.metadata['site_code'], + name: product.name, + description: product.description, + order: Number(product.metadata['site_order'] ?? 0), + prices: priceDtos, + defaultPriceId: defaultPrice, + features: featureDtos, + isDefault, + userLimit, + }); + } + return plans; + } + + public async getCheckoutUrl(account: Account, userId: number, dto: SubscriptionOrderDto): Promise { + const redirectUrl = this.urlGenerator.createUrl({ + route: FrontendRoute.settings.stripe(), + subdomain: account.subdomain, + }); + try { + const session = await this.stripe.checkout.sessions.create({ + line_items: [ + { + price: dto.priceId ?? undefined, + quantity: dto.priceId ? dto.numberOfUsers : 1, + price_data: !dto.priceId + ? { + currency: 'usd', + product: dto.productId, + unit_amount: dto.amount * 100, + } + : undefined, + }, + ], + discounts: dto.couponId ? [{ coupon: dto.couponId }] : undefined, + mode: dto.priceId ? 'subscription' : 'payment', + customer_creation: dto.priceId ? undefined : 'always', + success_url: `${redirectUrl}/?success=true`, + cancel_url: `${redirectUrl}/?canceled=true`, + client_reference_id: account.id.toString(), + metadata: { accountId: account.id, userId, numberOfUsers: dto.numberOfUsers }, + }); + return session.url; + } catch (e) { + this.logger.error(`Stripe checkout error`, (e as Error)?.stack); + throw e; + } + } + + public async getCustomerPortalUrl(account: Account): Promise { + const subscription = await this.subscriptionService.get(account.id); + if (!subscription.externalCustomerId) { + return null; + } + + const returnUrl = this.urlGenerator.createUrl({ route: FrontendRoute.settings.base, subdomain: account.subdomain }); + + const session = await this.stripe.billingPortal.sessions.create({ + customer: subscription.externalCustomerId, + return_url: returnUrl, + }); + + return session.url; + } + + public async handleWebhook(body: unknown, rawBody: Buffer, signature: string | string[]): Promise { + let event: Stripe.Event; + + if (this.stripeWebhookSecret) { + try { + event = this.stripe.webhooks.constructEvent(rawBody, signature, this.stripeWebhookSecret); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + throw new BadRequestError('Invalid stripe signature'); + } + } else { + event = body as Stripe.Event; + } + + switch (event.type) { + case 'checkout.session.completed': + this.handleCheckoutSessionCompleted(event.data.object as Stripe.Checkout.Session); + break; + case 'customer.subscription.updated': + this.handleSubscriptionUpdated(event.data.object as Stripe.Subscription); + break; + case 'customer.subscription.deleted': + this.handleSubscriptionDeleted(event.data.object as Stripe.Subscription); + break; + } + } + + private async handleCheckoutSessionCompleted(session: Stripe.Checkout.Session): Promise { + const fullSession = await this.stripe.checkout.sessions.retrieve(session.id, { + expand: ['subscription.plan.product', 'line_items.data.price.product'], + }); + + const accountId = Number(fullSession.client_reference_id); + const subscription = fullSession.subscription as Stripe.Subscription; + const lineItem = fullSession.line_items?.data?.[0]; + const product = ( + subscription ? ((subscription['plan'] as Stripe.Plan)?.product as Stripe.Product) : lineItem?.price?.product + ) as Stripe.Product; + const quantity = Number( + fullSession.metadata['numberOfUsers'] ?? subscription?.['quantity'] ?? lineItem?.quantity ?? 1, + ); + const externalCustomerId = fullSession.customer as string; + + await this.updateSubscription({ accountId, product, subscription, externalCustomerId, quantity }); + } + + private async handleSubscriptionUpdated(subscription: Stripe.Subscription): Promise { + const fullSubscription = await this.stripe.subscriptions.retrieve(subscription.id, { + expand: ['plan.product'], + }); + + const product = (fullSubscription['plan'] as Stripe.Plan)?.product as Stripe.Product; + const quantity = Number(fullSubscription['quantity']); + const externalCustomerId = fullSubscription.customer as string; + + await this.updateSubscription({ product, subscription: fullSubscription, externalCustomerId, quantity }); + } + + private async updateSubscription({ + accountId, + product, + subscription, + externalCustomerId, + quantity, + }: { + accountId?: number; + product: Stripe.Product; + subscription?: Stripe.Subscription; + externalCustomerId: string; + quantity: number; + }) { + const periodStart = DateUtil.startOf( + subscription ? new Date(subscription.current_period_start * 1000) : DateUtil.now(), + 'day', + ); + const periodEnd = DateUtil.endOf( + subscription ? new Date(subscription.current_period_end * 1000) : DateUtil.add(DateUtil.now(), { years: 100 }), + 'day', + ); + await this.subscriptionService.update( + { accountId, externalCustomerId: !accountId ? externalCustomerId : undefined }, + null, + { + isTrial: false, + periodStart: periodStart.toISOString(), + periodEnd: periodEnd.toISOString(), + userLimit: quantity, + planName: product.name, + externalCustomerId, + }, + ); + } + + private async handleSubscriptionDeleted(subscription: Stripe.Subscription): Promise { + const stripeCustomerId = subscription.customer as string; + await this.subscriptionService.cancel({ externalCustomerId: stripeCustomerId }); + } +} diff --git a/backend/src/modules/inventory/common/enums/index.ts b/backend/src/modules/inventory/common/enums/index.ts new file mode 100644 index 0000000..a807d5f --- /dev/null +++ b/backend/src/modules/inventory/common/enums/index.ts @@ -0,0 +1 @@ +export * from './permission-object-type.enum'; diff --git a/backend/src/modules/inventory/common/enums/permission-object-type.enum.ts b/backend/src/modules/inventory/common/enums/permission-object-type.enum.ts new file mode 100644 index 0000000..27329c1 --- /dev/null +++ b/backend/src/modules/inventory/common/enums/permission-object-type.enum.ts @@ -0,0 +1,6 @@ +export enum PermissionObjectType { + Products = 'products', + ProductsOrder = 'products_order', + ProductsShipment = 'products_shipment', + Warehouse = 'inventory_warehouse', +} diff --git a/backend/src/modules/inventory/common/events/index.ts b/backend/src/modules/inventory/common/events/index.ts new file mode 100644 index 0000000..0ff7a55 --- /dev/null +++ b/backend/src/modules/inventory/common/events/index.ts @@ -0,0 +1,5 @@ +export * from './product-order'; +export * from './products-event-type.enum'; +export * from './products-section'; +export * from './rental-order'; +export * from './shipment'; diff --git a/backend/src/modules/inventory/common/events/product-order/index.ts b/backend/src/modules/inventory/common/events/product-order/index.ts new file mode 100644 index 0000000..d9f9d40 --- /dev/null +++ b/backend/src/modules/inventory/common/events/product-order/index.ts @@ -0,0 +1,2 @@ +export * from './product-order-created.event'; +export * from './product-order.event'; diff --git a/backend/src/modules/inventory/common/events/product-order/product-order-created.event.ts b/backend/src/modules/inventory/common/events/product-order/product-order-created.event.ts new file mode 100644 index 0000000..0aadca1 --- /dev/null +++ b/backend/src/modules/inventory/common/events/product-order/product-order-created.event.ts @@ -0,0 +1,10 @@ +import { ProductOrderEvent } from './product-order.event'; + +export class ProductOrderCreatedEvent extends ProductOrderEvent { + createdAt: string; + + constructor({ accountId, entityId, orderId, createdAt }: ProductOrderCreatedEvent) { + super({ accountId, entityId, orderId }); + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/inventory/common/events/product-order/product-order.event.ts b/backend/src/modules/inventory/common/events/product-order/product-order.event.ts new file mode 100644 index 0000000..16a80de --- /dev/null +++ b/backend/src/modules/inventory/common/events/product-order/product-order.event.ts @@ -0,0 +1,11 @@ +export class ProductOrderEvent { + accountId: number; + entityId: number; + orderId: number; + + constructor({ accountId, entityId, orderId }: ProductOrderEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.orderId = orderId; + } +} diff --git a/backend/src/modules/inventory/common/events/products-event-type.enum.ts b/backend/src/modules/inventory/common/events/products-event-type.enum.ts new file mode 100644 index 0000000..7a63325 --- /dev/null +++ b/backend/src/modules/inventory/common/events/products-event-type.enum.ts @@ -0,0 +1,11 @@ +export enum ProductsEventType { + ProductOrderCreated = 'product:order:created', + ProductOrderDeleted = 'product:order:deleted', + ProductsSectionCreated = 'products:section:created', + ProductsSectionDeleted = 'products:section:deleted', + RentalOrderCreated = 'rental:order:created', + RentalOrderDeleted = 'rental:order:deleted', + ShipmentCreated = 'shipment:created', + ShipmentDeleted = 'shipment:deleted', + ShipmentStatusChanged = 'shipment:status_changed', +} diff --git a/backend/src/modules/inventory/common/events/products-section/index.ts b/backend/src/modules/inventory/common/events/products-section/index.ts new file mode 100644 index 0000000..8940aaa --- /dev/null +++ b/backend/src/modules/inventory/common/events/products-section/index.ts @@ -0,0 +1 @@ +export * from './products-section.event'; diff --git a/backend/src/modules/inventory/common/events/products-section/products-section.event.ts b/backend/src/modules/inventory/common/events/products-section/products-section.event.ts new file mode 100644 index 0000000..e58e354 --- /dev/null +++ b/backend/src/modules/inventory/common/events/products-section/products-section.event.ts @@ -0,0 +1,9 @@ +export class ProductsSectionEvent { + accountId: number; + sectionId: number; + + constructor({ accountId, sectionId }: ProductsSectionEvent) { + this.accountId = accountId; + this.sectionId = sectionId; + } +} diff --git a/backend/src/modules/inventory/common/events/rental-order/index.ts b/backend/src/modules/inventory/common/events/rental-order/index.ts new file mode 100644 index 0000000..0cc6385 --- /dev/null +++ b/backend/src/modules/inventory/common/events/rental-order/index.ts @@ -0,0 +1,2 @@ +export * from './rental-order-created.event'; +export * from './rental-order.event'; diff --git a/backend/src/modules/inventory/common/events/rental-order/rental-order-created.event.ts b/backend/src/modules/inventory/common/events/rental-order/rental-order-created.event.ts new file mode 100644 index 0000000..e6382f3 --- /dev/null +++ b/backend/src/modules/inventory/common/events/rental-order/rental-order-created.event.ts @@ -0,0 +1,10 @@ +import { RentalOrderEvent } from './rental-order.event'; + +export class RentalOrderCreatedEvent extends RentalOrderEvent { + createdAt: string; + + constructor({ accountId, entityId, rentalOrderId, createdAt }: RentalOrderCreatedEvent) { + super({ accountId, entityId, rentalOrderId }); + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/inventory/common/events/rental-order/rental-order.event.ts b/backend/src/modules/inventory/common/events/rental-order/rental-order.event.ts new file mode 100644 index 0000000..070302d --- /dev/null +++ b/backend/src/modules/inventory/common/events/rental-order/rental-order.event.ts @@ -0,0 +1,11 @@ +export class RentalOrderEvent { + accountId: number; + entityId: number; + rentalOrderId: number; + + constructor({ accountId, entityId, rentalOrderId }: RentalOrderEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.rentalOrderId = rentalOrderId; + } +} diff --git a/backend/src/modules/inventory/common/events/shipment/index.ts b/backend/src/modules/inventory/common/events/shipment/index.ts new file mode 100644 index 0000000..5ff2ad8 --- /dev/null +++ b/backend/src/modules/inventory/common/events/shipment/index.ts @@ -0,0 +1,4 @@ +export * from './shipment-created.event'; +export * from './shipment-deleted.event'; +export * from './shipment-status-changed.event'; +export * from './shipment.event'; diff --git a/backend/src/modules/inventory/common/events/shipment/shipment-created.event.ts b/backend/src/modules/inventory/common/events/shipment/shipment-created.event.ts new file mode 100644 index 0000000..3292711 --- /dev/null +++ b/backend/src/modules/inventory/common/events/shipment/shipment-created.event.ts @@ -0,0 +1,13 @@ +import { ShipmentEvent } from './shipment.event'; + +export class ShipmentCreatedEvent extends ShipmentEvent { + entityId: number; + createdAt: string; + + constructor({ accountId, sectionId, orderId, shipmentId, entityId, createdAt }: ShipmentCreatedEvent) { + super({ accountId, sectionId, orderId, shipmentId }); + + this.entityId = entityId; + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/inventory/common/events/shipment/shipment-deleted.event.ts b/backend/src/modules/inventory/common/events/shipment/shipment-deleted.event.ts new file mode 100644 index 0000000..66e9ca1 --- /dev/null +++ b/backend/src/modules/inventory/common/events/shipment/shipment-deleted.event.ts @@ -0,0 +1,11 @@ +import { ShipmentEvent } from './shipment.event'; + +export class ShipmentDeletedEvent extends ShipmentEvent { + entityId: number; + + constructor({ accountId, sectionId, orderId, shipmentId, entityId }: ShipmentDeletedEvent) { + super({ accountId, sectionId, orderId, shipmentId }); + + this.entityId = entityId; + } +} diff --git a/backend/src/modules/inventory/common/events/shipment/shipment-status-changed.event.ts b/backend/src/modules/inventory/common/events/shipment/shipment-status-changed.event.ts new file mode 100644 index 0000000..424fa72 --- /dev/null +++ b/backend/src/modules/inventory/common/events/shipment/shipment-status-changed.event.ts @@ -0,0 +1,11 @@ +import { ShipmentEvent } from './shipment.event'; + +export class ShipmentStatusChangedEvent extends ShipmentEvent { + statusId: number; + + constructor({ accountId, sectionId, orderId, shipmentId, statusId }: ShipmentStatusChangedEvent) { + super({ accountId, sectionId, orderId, shipmentId }); + + this.statusId = statusId; + } +} diff --git a/backend/src/modules/inventory/common/events/shipment/shipment.event.ts b/backend/src/modules/inventory/common/events/shipment/shipment.event.ts new file mode 100644 index 0000000..c86ce33 --- /dev/null +++ b/backend/src/modules/inventory/common/events/shipment/shipment.event.ts @@ -0,0 +1,13 @@ +export class ShipmentEvent { + accountId: number; + sectionId: number; + orderId: number; + shipmentId: number; + + constructor({ accountId, sectionId, orderId, shipmentId }: ShipmentEvent) { + this.accountId = accountId; + this.sectionId = sectionId; + this.orderId = orderId; + this.shipmentId = shipmentId; + } +} diff --git a/backend/src/modules/inventory/common/index.ts b/backend/src/modules/inventory/common/index.ts new file mode 100644 index 0000000..df1eda9 --- /dev/null +++ b/backend/src/modules/inventory/common/index.ts @@ -0,0 +1,2 @@ +export * from './enums'; +export * from './events'; diff --git a/backend/src/modules/inventory/inventory-reporting/dto/index.ts b/backend/src/modules/inventory/inventory-reporting/dto/index.ts new file mode 100644 index 0000000..a217f25 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/dto/index.ts @@ -0,0 +1,4 @@ +export * from './inventory-report-filter.dto'; +export * from './inventory-report-row.dto'; +export * from './inventory-report-user-cell.dto'; +export * from './inventory-report.dto'; diff --git a/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-filter.dto.ts b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-filter.dto.ts new file mode 100644 index 0000000..7c19862 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-filter.dto.ts @@ -0,0 +1,50 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; +import { BoardStageType } from '@/CRM/board-stage'; + +import { InventoryReportType } from '../enums'; + +export class InventoryReportFilterDto { + @ApiProperty({ enum: InventoryReportType }) + @IsEnum(InventoryReportType) + type: InventoryReportType; + + @ApiProperty() + @IsNumber() + entityTypeId: number; + + @ApiProperty() + @IsNumber() + productsSectionId: number; + + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + boardIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + warehouseIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + categoryIds?: number[] | null; + + @ApiPropertyOptional({ enum: BoardStageType, nullable: true }) + @IsOptional() + @IsEnum(BoardStageType) + stageType?: BoardStageType | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-row.dto.ts b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-row.dto.ts new file mode 100644 index 0000000..a983756 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-row.dto.ts @@ -0,0 +1,70 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { QuantityAmountDto } from '@/common'; +import { InventoryReportUserCellDto } from './inventory-report-user-cell.dto'; + +export class InventoryReportRowDto { + @ApiProperty() + ownerId: number; + + @ApiProperty({ nullable: true }) + categoryId: number | null; + + @ApiProperty({ nullable: true }) + productName: string | null; + + @ApiProperty({ type: QuantityAmountDto }) + sold: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, nullable: true }) + shipped: QuantityAmountDto | null; + + @ApiProperty({ type: QuantityAmountDto, nullable: true }) + open: QuantityAmountDto | null; + + @ApiProperty({ type: QuantityAmountDto, nullable: true }) + lost: QuantityAmountDto | null; + + @ApiProperty({ type: QuantityAmountDto, nullable: true }) + all: QuantityAmountDto | null; + + @ApiProperty({ nullable: true }) + avgProducts: number | null; + + @ApiProperty({ nullable: true }) + avgBudget: number | null; + + @ApiProperty({ nullable: true }) + avgTerm: number | null; + + @ApiProperty({ type: [InventoryReportUserCellDto], nullable: true }) + users: InventoryReportUserCellDto[] | null; + + constructor({ + ownerId, + categoryId, + productName, + sold, + shipped, + open, + lost, + all, + avgProducts, + avgBudget, + avgTerm, + users, + }: InventoryReportRowDto) { + this.ownerId = ownerId; + this.categoryId = categoryId; + this.productName = productName; + this.sold = sold; + this.shipped = shipped; + this.open = open; + this.lost = lost; + this.all = all; + this.avgProducts = avgProducts; + this.avgBudget = avgBudget; + this.avgTerm = avgTerm; + this.users = users; + } +} diff --git a/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-user-cell.dto.ts b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-user-cell.dto.ts new file mode 100644 index 0000000..36f6d1f --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report-user-cell.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { QuantityAmountDto } from '@/common'; + +export class InventoryReportUserCellDto { + @ApiProperty() + userId: number; + + @ApiProperty({ type: QuantityAmountDto }) + value: QuantityAmountDto; + + constructor({ userId, value }: InventoryReportUserCellDto) { + this.userId = userId; + this.value = value; + } +} diff --git a/backend/src/modules/inventory/inventory-reporting/dto/inventory-report.dto.ts b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report.dto.ts new file mode 100644 index 0000000..89ddfac --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/dto/inventory-report.dto.ts @@ -0,0 +1,17 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { InventoryReportRowDto } from './inventory-report-row.dto'; + +export class InventoryReportDto { + @ApiPropertyOptional({ type: [InventoryReportRowDto], nullable: true }) + products: InventoryReportRowDto[] | null | undefined; + @ApiPropertyOptional({ type: [InventoryReportRowDto], nullable: true }) + categories: InventoryReportRowDto[] | null | undefined; + @ApiPropertyOptional({ type: InventoryReportRowDto, nullable: true }) + total: InventoryReportRowDto | null | undefined; + + constructor({ products, categories, total }: InventoryReportDto) { + this.products = products; + this.categories = categories; + this.total = total; + } +} diff --git a/backend/src/modules/inventory/inventory-reporting/enums/index.ts b/backend/src/modules/inventory/inventory-reporting/enums/index.ts new file mode 100644 index 0000000..88f3be6 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/enums/index.ts @@ -0,0 +1 @@ +export * from './products-report-type.enum'; diff --git a/backend/src/modules/inventory/inventory-reporting/enums/products-report-type.enum.ts b/backend/src/modules/inventory/inventory-reporting/enums/products-report-type.enum.ts new file mode 100644 index 0000000..246591b --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/enums/products-report-type.enum.ts @@ -0,0 +1,5 @@ +export enum InventoryReportType { + Product = 'product', + Category = 'category', + User = 'user', +} diff --git a/backend/src/modules/inventory/inventory-reporting/inventory-reporting.controller.ts b/backend/src/modules/inventory/inventory-reporting/inventory-reporting.controller.ts new file mode 100644 index 0000000..751db77 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/inventory-reporting.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { InventoryReportDto, InventoryReportFilterDto } from './dto'; +import { InventoryReportingService } from './inventory-reporting.service'; + +@ApiTags('inventory/reporting') +@Controller('products/reporting') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class InventoryReportingController { + constructor(private readonly service: InventoryReportingService) {} + + @ApiOperation({ summary: 'Get products general report', description: 'Get products general report' }) + @ApiBody({ type: InventoryReportFilterDto, required: true, description: 'Products general report filter' }) + @ApiOkResponse({ description: 'Products general report', type: InventoryReportDto }) + @Post('general') + public async getReport(@CurrentAuth() { accountId, user }: AuthData, @Body() filter: InventoryReportFilterDto) { + return this.service.getReport({ accountId, user, filter }); + } +} diff --git a/backend/src/modules/inventory/inventory-reporting/inventory-reporting.module.ts b/backend/src/modules/inventory/inventory-reporting/inventory-reporting.module.ts new file mode 100644 index 0000000..43d7601 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/inventory-reporting.module.ts @@ -0,0 +1,25 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { Product } from '../product/entities/product.entity'; +import { ProductModule } from '../product/product.module'; +import { ProductCategoryModule } from '../product-category/product-category.module'; + +import { InventoryReportingService } from './inventory-reporting.service'; +import { InventoryReportingController } from './inventory-reporting.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Product]), + IAMModule, + forwardRef(() => CrmModule), + ProductModule, + ProductCategoryModule, + ], + providers: [InventoryReportingService], + controllers: [InventoryReportingController], +}) +export class InventoryReportingModule {} diff --git a/backend/src/modules/inventory/inventory-reporting/inventory-reporting.service.ts b/backend/src/modules/inventory/inventory-reporting/inventory-reporting.service.ts new file mode 100644 index 0000000..7b3f2da --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/inventory-reporting.service.ts @@ -0,0 +1,351 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { QuantityAmount, NumberUtil, DatePeriod } from '@/common'; +import { User } from '@/modules/iam/user/entities'; +import { BoardStageService } from '@/CRM/board-stage/board-stage.service'; +import { GroupedStages } from '@/CRM/board-stage/types'; + +import { Product } from '../product/entities/product.entity'; +import { ProductCategory } from '../product-category/entities/product-category.entity'; +import { ProductCategoryService } from '../product-category/product-category.service'; + +import { InventoryReportFilterDto } from './dto'; +import { InventoryReportType } from './enums'; +import { InventoryReport, InventoryReportRow } from './types'; + +enum GroupBy { + Product, + Category, +} +interface Filter { + entityTypeId: number; + sectionId: number; + userIds?: number[]; + warehouseIds?: number[]; + categoryIds?: number[]; + period?: DatePeriod | null; +} + +@Injectable() +export class InventoryReportingService { + constructor( + @InjectRepository(Product) + private readonly repository: Repository, + private readonly stageService: BoardStageService, + private readonly categoryService: ProductCategoryService, + ) {} + + public async getReport({ + accountId, + filter, + }: { + accountId: number; + user: User; + filter: InventoryReportFilterDto; + }): Promise { + const stages = await this.stageService.getGroupedByType({ + accountId, + entityTypeId: filter.entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + type: filter.stageType, + }); + + const reportFilter: Filter = { + entityTypeId: filter.entityTypeId, + sectionId: filter.productsSectionId, + userIds: filter.userIds, + warehouseIds: filter.warehouseIds, + categoryIds: filter.categoryIds, + period: filter.period ? DatePeriod.fromFilter(filter.period) : undefined, + }; + return filter.type === InventoryReportType.User + ? this.getProductsUserReport({ accountId, filter: reportFilter, stages }) + : this.getProductsReport({ accountId, filter: reportFilter, stages, type: filter.type }); + } + + private async getProductsReport({ + accountId, + filter, + stages, + type, + }: { + accountId: number; + filter: Filter; + stages: GroupedStages; + type: InventoryReportType; + }): Promise { + const { entityTypeId, sectionId } = filter; + const products = + type !== InventoryReportType.Category + ? await this.getProductReportGroupBy(accountId, entityTypeId, stages, sectionId, GroupBy.Product, filter) + : null; + + const categories = await this.getProductReportGroupBy( + accountId, + entityTypeId, + stages, + sectionId, + GroupBy.Category, + filter, + ); + + if (categories.size) { + await this.processCategories(accountId, sectionId, categories); + } + + const total = await this.getProductReportGroupBy(accountId, entityTypeId, stages, sectionId, null, filter); + + return new InventoryReport(products, categories, total.values().next().value); + } + + private async getProductsUserReport({ + accountId, + filter, + stages, + }: { + accountId: number; + filter: Filter; + stages: GroupedStages; + }): Promise { + const { entityTypeId, sectionId } = filter; + const products = await this.getProductUserReportGroupBy( + accountId, + entityTypeId, + stages, + sectionId, + GroupBy.Product, + filter, + ); + + const categories = await this.getProductUserReportGroupBy( + accountId, + entityTypeId, + stages, + sectionId, + GroupBy.Category, + filter, + ); + + if (categories.size > 0) { + await this.processCategories(accountId, sectionId, categories); + } + + const total = await this.getProductUserReportGroupBy(accountId, entityTypeId, stages, sectionId, null, filter); + + return new InventoryReport(products, categories, total.values().next().value); + } + + private async getProductReportGroupBy( + accountId: number, + entityTypeId: number, + stages: { open: number[]; won: number[]; lost: number[] }, + productsSectionId: number, + groupBy: GroupBy, + filter?: Filter, + ): Promise> { + const rowMap = new Map(); + + const qb = this.createQb(accountId, entityTypeId, productsSectionId, groupBy, filter); + + const wonStageIds = stages.won?.join(','); + if (wonStageIds?.length > 0) { + qb.addSelect(`sum(oi.quantity) filter (where e.stage_id = any(array[${wonStageIds}]))`, 'sold_quantity'); + qb.addSelect( + `sum(oi.unit_price * oi.quantity) filter (where e.stage_id = any(array[${wonStageIds}]))`, + 'sold_amount', + ); + qb.leftJoin('field_value', 'fv', `e.id = fv.entity_id and fv.field_type = 'value'`); + qb.addSelect( + `avg((fv.payload->>'value')::numeric) filter (where e.stage_id = any(array[${wonStageIds}]))`, + 'avg_budget', + ); + qb.addSelect( + // eslint-disable-next-line max-len + `avg(extract(epoch from age(e.closed_at, e.created_at))) filter (where e.stage_id = any(array[${wonStageIds}]))`, + 'avg_close_time', + ); + } + + const lostStageIds = stages.lost?.join(','); + if (lostStageIds?.length > 0) { + qb.addSelect(`sum(oi.quantity) filter (where e.stage_id = any(array[${lostStageIds}]))`, 'lost_quantity'); + qb.addSelect( + `sum(oi.unit_price * oi.quantity) filter (where e.stage_id = any(array[${lostStageIds}]))`, + 'lost_amount', + ); + } + + const openStageIds = stages.open?.join(','); + if (openStageIds?.length > 0) { + qb.addSelect(`sum(oi.quantity) filter (where e.stage_id = any(array[${openStageIds}]))`, 'open_quantity'); + qb.addSelect( + `sum(oi.unit_price * oi.quantity) filter (where e.stage_id = any(array[${openStageIds}]))`, + 'open_amount', + ); + } + + qb.innerJoin('order_status', 'os', 'o.status_id = os.id'); + qb.addSelect(`sum(oi.quantity)`, 'all_quantity'); + qb.addSelect(`sum(oi.unit_price * oi.quantity)`, 'all_amount'); + qb.addSelect(`sum(oi.quantity) filter (where os.code = 'shipped')`, 'shipped_quantity'); + qb.addSelect(`sum(oi.unit_price * oi.quantity) filter (where os.code = 'shipped')`, 'shipped_amount'); + qb.addSelect(`sum(oi.quantity)::float / count(distinct e.id)`, 'avg_products'); + + const rows = await qb.getRawMany(); + for (const row of rows) { + rowMap.set( + row.row_id, + new InventoryReportRow(row.row_id, row.category_id ?? null, { + productName: row.product_name, + sold: new QuantityAmount(NumberUtil.toNumber(row.sold_quantity), NumberUtil.toNumber(row.sold_amount)), + shipped: new QuantityAmount( + NumberUtil.toNumber(row.shipped_quantity), + NumberUtil.toNumber(row.shipped_amount), + ), + open: new QuantityAmount(NumberUtil.toNumber(row.open_quantity), NumberUtil.toNumber(row.open_amount)), + lost: new QuantityAmount(NumberUtil.toNumber(row.lost_quantity), NumberUtil.toNumber(row.lost_amount)), + all: new QuantityAmount(NumberUtil.toNumber(row.all_quantity), NumberUtil.toNumber(row.all_amount)), + avgProducts: NumberUtil.toNumber(row.avg_products), + avgBudget: NumberUtil.toNumber(row.avg_budget), + avgTerm: NumberUtil.toNumber(row.avg_close_time), + }), + ); + } + + return rowMap; + } + + private async getProductUserReportGroupBy( + accountId: number, + entityTypeId: number, + stages: { open: number[]; won: number[]; lost: number[] }, + productsSectionId: number, + groupBy: GroupBy, + filter?: Filter, + ): Promise> { + const rowMap = new Map(); + + const qb = this.createQb(accountId, entityTypeId, productsSectionId, groupBy, filter); + + const wonStageIds = stages.won?.join(','); + if (wonStageIds?.length > 0) { + qb.addSelect(`sum(oi.quantity) filter (where e.stage_id = any(array[${wonStageIds}]))`, 'sold_quantity'); + qb.addSelect( + `sum(oi.unit_price * oi.quantity) filter (where e.stage_id = any(array[${wonStageIds}]))`, + 'sold_amount', + ); + } + + const soldRows = await qb.getRawMany(); + for (const row of soldRows) { + if (row.sold_quantity || row.sold_amount) { + rowMap.set( + row.row_id, + new InventoryReportRow(row.row_id, row.category_id ?? null, { + productName: row.product_name, + sold: new QuantityAmount(NumberUtil.toNumber(row.sold_quantity), NumberUtil.toNumber(row.sold_amount)), + }), + ); + } + } + + const userRows = await qb + .clone() + .addSelect('e.responsible_user_id', 'owner_id') + .addGroupBy('e.responsible_user_id') + .getRawMany(); + for (const row of userRows) { + if (row.sold_quantity || row.sold_amount) { + const reportRow = rowMap.get(row.row_id) ?? new InventoryReportRow(row.row_id, row.category_id ?? null); + reportRow.addUser( + row.owner_id, + new QuantityAmount(NumberUtil.toNumber(row.sold_quantity), NumberUtil.toNumber(row.sold_amount)), + ); + + rowMap.set(row.row_id, reportRow); + } + } + + return rowMap; + } + + private createQb( + accountId: number, + entityTypeId: number, + productsSectionId: number, + groupBy: GroupBy, + filter?: Filter, + ) { + const qb = this.repository + .createQueryBuilder('p') + .innerJoin('order_item', 'oi', 'p.id = oi.product_id') + .innerJoin('orders', 'o', 'oi.order_id = o.id') + .innerJoin('entity', 'e', 'o.entity_id = e.id') + .where('p.account_id = :accountId', { accountId }) + .andWhere('p.section_id = :productsSectionId', { productsSectionId }) + .andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }); + + if (groupBy === GroupBy.Product) { + qb.select('p.id', 'row_id') + .addSelect('p.category_id', 'category_id') + .addSelect('p.name', 'product_name') + .groupBy('p.id') + .addGroupBy('p.category_id') + .addGroupBy('p.name'); + } else if (groupBy === GroupBy.Category) { + qb.select('p.category_id', 'row_id').groupBy('p.category_id'); + } else { + qb.select('0', 'row_id'); + } + + if (filter?.userIds?.length > 0) { + qb.andWhere('e.responsible_user_id IN (:...userIds)', { userIds: filter.userIds }); + } + if (filter?.warehouseIds?.length > 0) { + qb.andWhere('o.warehouse_id IN (:...warehouseIds)', { warehouseIds: filter.warehouseIds }); + } + if (filter?.categoryIds?.length > 0) { + qb.andWhere('p.category_id IN (:...categoryIds)', { categoryIds: filter.categoryIds }); + } + if (filter?.period?.from) { + qb.andWhere('o.created_at >= :from', { from: filter.period.from }); + } + if (filter?.period?.to) { + qb.andWhere('o.created_at <= :to', { to: filter.period.to }); + } + + return qb; + } + + private async processCategories(accountId: number, sectionId: number, rowsMap: Map) { + const categories = await this.categoryService.getCategories(accountId, sectionId, null); + + if (categories?.length > 0) { + this.aggregateData(categories, rowsMap); + } + } + private aggregateData(categories: ProductCategory[], rowsMap: Map) { + const aggregateSubordinates = (category: ProductCategory): InventoryReportRow | null => { + let aggregatedRow = rowsMap.get(category.id) || InventoryReportRow.empty(category.id, null); + + let hasValue = rowsMap.has(category.id); + category.children.forEach((child) => { + const childRow = aggregateSubordinates(child); + if (childRow) { + aggregatedRow = aggregatedRow.add(childRow); + hasValue = true; + } + }); + return hasValue ? aggregatedRow : null; + }; + + categories.forEach((category) => { + const aggregatedRow = aggregateSubordinates(category); + if (aggregatedRow) { + rowsMap.set(category.id, aggregatedRow); + } + }); + } +} diff --git a/backend/src/modules/inventory/inventory-reporting/types/index.ts b/backend/src/modules/inventory/inventory-reporting/types/index.ts new file mode 100644 index 0000000..35a37b3 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/types/index.ts @@ -0,0 +1,3 @@ +export * from './inventory-report-row'; +export * from './inventory-report-user-cell'; +export * from './inventory-report'; diff --git a/backend/src/modules/inventory/inventory-reporting/types/inventory-report-row.ts b/backend/src/modules/inventory/inventory-reporting/types/inventory-report-row.ts new file mode 100644 index 0000000..3ba0787 --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/types/inventory-report-row.ts @@ -0,0 +1,135 @@ +import { type QuantityAmount } from '@/common'; +import { InventoryReportRowDto } from '../dto'; +import { InventoryReportUserCell } from './inventory-report-user-cell'; + +export class InventoryReportRow { + ownerId: number; + categoryId: number | null; + productName: string | null; + sold?: QuantityAmount; + shipped?: QuantityAmount; + open?: QuantityAmount; + lost?: QuantityAmount; + all?: QuantityAmount; + avgProducts?: number; + avgBudget?: number; + avgTerm?: number; + users?: Map; + + constructor( + ownerId: number, + categoryId: number | null, + values?: { + productName: string | null; + sold?: QuantityAmount; + shipped?: QuantityAmount; + open?: QuantityAmount; + lost?: QuantityAmount; + all?: QuantityAmount; + avgProducts?: number; + avgBudget?: number; + avgTerm?: number; + users?: Map; + }, + ) { + this.ownerId = ownerId; + this.categoryId = categoryId; + this.productName = values?.productName; + this.sold = values?.sold; + this.shipped = values?.shipped; + this.open = values?.open; + this.lost = values?.lost; + this.all = values?.all; + this.avgProducts = values?.avgProducts; + this.avgBudget = values?.avgBudget; + this.avgTerm = values?.avgTerm; + this.users = values?.users; + } + + public static empty(ownerId: number, categoryId: number | null): InventoryReportRow { + return new InventoryReportRow(ownerId, categoryId); + } + + public toDto(): InventoryReportRowDto { + return new InventoryReportRowDto({ + ownerId: this.ownerId, + categoryId: this.categoryId, + productName: this.productName, + sold: this.sold?.toDto(), + shipped: this.shipped?.toDto(), + open: this.open?.toDto(), + lost: this.lost?.toDto(), + all: this.all?.toDto(), + avgProducts: this.avgProducts, + avgBudget: this.avgBudget, + avgTerm: this.avgTerm, + users: this.users?.size ? Array.from(this.users.values()).map((v) => v.toDto()) : undefined, + }); + } + + public addUser(ownerId: number, value: QuantityAmount) { + if (!this.users) { + this.users = new Map(); + } + this.users.set(ownerId, new InventoryReportUserCell(ownerId, value)); + } + + public add(row: InventoryReportRow): InventoryReportRow { + if (this.sold) { + this.sold.add(row.sold); + } else { + this.sold = row.sold; + } + if (this.shipped) { + this.shipped.add(row.shipped); + } else { + this.shipped = row.shipped; + } + if (this.open) { + this.open.add(row.open); + } else { + this.open = row.open; + } + if (this.lost) { + this.lost.add(row.lost); + } else { + this.lost = row.lost; + } + if (this.all) { + this.all.add(row.all); + } else { + this.all = row.all; + } + if (this.avgProducts) { + this.avgProducts += row.avgProducts ?? 0; + } else { + this.avgProducts = row.avgProducts; + } + if (this.avgBudget) { + this.avgBudget += row.avgBudget ?? 0; + } else { + this.avgBudget = row.avgBudget; + } + if (this.avgTerm) { + this.avgTerm += row.avgTerm ?? 0; + } else { + this.avgTerm = row.avgTerm; + } + if (this.users) { + for (const [userId, userCell] of row.users) { + if (this.users.has(userId)) { + this.users.get(userId).add(userCell); + } else { + this.users.set(userId, userCell); + } + } + } else if (row.users) { + this.users = new Map(); + for (const [userId, userCell] of row.users) { + this.users.set(userId, userCell); + } + } + + return this; + } +} diff --git a/backend/src/modules/inventory/inventory-reporting/types/inventory-report-user-cell.ts b/backend/src/modules/inventory/inventory-reporting/types/inventory-report-user-cell.ts new file mode 100644 index 0000000..2ec2aed --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/types/inventory-report-user-cell.ts @@ -0,0 +1,20 @@ +import { type QuantityAmount } from '@/common'; +import { InventoryReportUserCellDto } from '../dto'; + +export class InventoryReportUserCell { + userId: number; + value: QuantityAmount; + + constructor(userId: number, value: QuantityAmount) { + this.userId = userId; + this.value = value; + } + + public toDto(): InventoryReportUserCellDto { + return new InventoryReportUserCellDto({ userId: this.userId, value: this.value.toDto() }); + } + + public add(cell: InventoryReportUserCell) { + this.value.add(cell.value); + } +} diff --git a/backend/src/modules/inventory/inventory-reporting/types/inventory-report.ts b/backend/src/modules/inventory/inventory-reporting/types/inventory-report.ts new file mode 100644 index 0000000..639e34c --- /dev/null +++ b/backend/src/modules/inventory/inventory-reporting/types/inventory-report.ts @@ -0,0 +1,30 @@ +import { InventoryReportDto } from '../dto'; +import { InventoryReportRow } from './inventory-report-row'; + +export class InventoryReport { + products: Map | null | undefined; + categories: Map | null | undefined; + total: InventoryReportRow | null | undefined; + + constructor( + products: Map | null | undefined, + categories: Map | null | undefined, + total: InventoryReportRow | null | undefined, + ) { + this.products = products; + this.categories = categories; + this.total = total; + } + + public static createEmptyRow(ownerId: number, categoryId: number | null): InventoryReportRow { + return InventoryReportRow.empty(ownerId, categoryId); + } + + public toDto(): InventoryReportDto { + return new InventoryReportDto({ + products: this.products ? Array.from(this.products.values()).map((u) => u.toDto()) : undefined, + categories: this.categories ? Array.from(this.categories.values()).map((u) => u.toDto()) : undefined, + total: this.total ? this.total.toDto() : undefined, + }); + } +} diff --git a/backend/src/modules/inventory/inventory.module.ts b/backend/src/modules/inventory/inventory.module.ts new file mode 100644 index 0000000..69fe132 --- /dev/null +++ b/backend/src/modules/inventory/inventory.module.ts @@ -0,0 +1,47 @@ +import { Module } from '@nestjs/common'; + +import { OrderStatusModule } from './order-status/order-status.module'; +import { OrderModule } from './order/order.module'; +import { ProductCategoryModule } from './product-category/product-category.module'; +import { ProductPriceModule } from './product-price/product-price.module'; +import { ProductStockModule } from './product-stock/product-stock.module'; +import { ProductModule } from './product/product.module'; +import { ProductsSectionModule } from './products-section/products-section.module'; +import { RentalIntervalModule } from './rental-interval/rental-interval.module'; +import { RentalOrderModule } from './rental-order/rental-order.module'; +import { RentalScheduleModule } from './rental-schedule/rental-schedule.module'; +import { ReservationModule } from './reservation/reservation.module'; +import { ShipmentModule } from './shipment/shipment.module'; +import { WarehouseModule } from './warehouse/warehouse.module'; +import { InventoryReportingModule } from './inventory-reporting/inventory-reporting.module'; + +@Module({ + imports: [ + OrderStatusModule, + OrderModule, + ProductCategoryModule, + ProductModule, + ProductPriceModule, + ProductStockModule, + ProductsSectionModule, + RentalIntervalModule, + RentalOrderModule, + RentalScheduleModule, + ReservationModule, + ShipmentModule, + WarehouseModule, + InventoryReportingModule, + ], + exports: [ + OrderStatusModule, + OrderModule, + ProductCategoryModule, + ProductModule, + ProductsSectionModule, + RentalIntervalModule, + RentalOrderModule, + WarehouseModule, + ShipmentModule, + ], +}) +export class InventoryModule {} diff --git a/backend/src/modules/inventory/order-status/dto/create-order-status.dto.ts b/backend/src/modules/inventory/order-status/dto/create-order-status.dto.ts new file mode 100644 index 0000000..8a1444c --- /dev/null +++ b/backend/src/modules/inventory/order-status/dto/create-order-status.dto.ts @@ -0,0 +1,23 @@ +import { OmitType } from '@nestjs/swagger'; + +import { type OrderStatusCode } from '../enums/order-status-code.enum'; +import { OrderStatusDto } from './order-status.dto'; + +export class CreateOrderStatusDto extends OmitType(OrderStatusDto, ['id'] as const) { + constructor(name: string, color: string, code: OrderStatusCode | null, sortOrder: number) { + super(); + + this.name = name; + this.color = color; + this.code = code; + this.sortOrder = sortOrder; + } + + public static system(name: string, color: string, code: OrderStatusCode, sortOrder: number): CreateOrderStatusDto { + return new CreateOrderStatusDto(name, color, code, sortOrder); + } + + public static custom(name: string, color: string, sortOrder: number): CreateOrderStatusDto { + return new CreateOrderStatusDto(name, color, null, sortOrder); + } +} diff --git a/backend/src/modules/inventory/order-status/dto/index.ts b/backend/src/modules/inventory/order-status/dto/index.ts new file mode 100644 index 0000000..cdc649d --- /dev/null +++ b/backend/src/modules/inventory/order-status/dto/index.ts @@ -0,0 +1,2 @@ +export * from './create-order-status.dto'; +export * from './order-status.dto'; diff --git a/backend/src/modules/inventory/order-status/dto/order-status.dto.ts b/backend/src/modules/inventory/order-status/dto/order-status.dto.ts new file mode 100644 index 0000000..36997c7 --- /dev/null +++ b/backend/src/modules/inventory/order-status/dto/order-status.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { OrderStatusCode } from '../enums/order-status-code.enum'; + +export class OrderStatusDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsString() + color: string; + + @ApiProperty() + @IsOptional() + @IsEnum(OrderStatusCode) + code: OrderStatusCode | null; + + @ApiProperty() + @IsNumber() + sortOrder: number; + + constructor(id: number, name: string, color: string, code: OrderStatusCode | null, sortOrder: number) { + this.id = id; + this.name = name; + this.color = color; + this.code = code; + this.sortOrder = sortOrder; + } +} diff --git a/backend/src/modules/inventory/order-status/entities/index.ts b/backend/src/modules/inventory/order-status/entities/index.ts new file mode 100644 index 0000000..72df4ef --- /dev/null +++ b/backend/src/modules/inventory/order-status/entities/index.ts @@ -0,0 +1 @@ +export * from './order-status.entity'; diff --git a/backend/src/modules/inventory/order-status/entities/order-status.entity.ts b/backend/src/modules/inventory/order-status/entities/order-status.entity.ts new file mode 100644 index 0000000..60aa4f6 --- /dev/null +++ b/backend/src/modules/inventory/order-status/entities/order-status.entity.ts @@ -0,0 +1,42 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { OrderStatusCode } from '../enums/order-status-code.enum'; +import { CreateOrderStatusDto } from '../dto/create-order-status.dto'; +import { OrderStatusDto } from '../dto/order-status.dto'; + +@Entity() +export class OrderStatus { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column() + color: string; + + @Column() + code: OrderStatusCode | null; + + @Column() + sortOrder: number; + + @Column() + accountId: number; + + constructor(accountId: number, name: string, color: string, code: OrderStatusCode | null, sortOrder: number) { + this.accountId = accountId; + this.name = name; + this.color = color; + this.code = code; + this.sortOrder = sortOrder; + } + + public static fromDto(accountId: number, dto: CreateOrderStatusDto): OrderStatus { + return new OrderStatus(accountId, dto.name, dto.color, dto.code, dto.sortOrder); + } + + public toDto(): OrderStatusDto { + return new OrderStatusDto(this.id, this.name, this.color, this.code, this.sortOrder); + } +} diff --git a/backend/src/modules/inventory/order-status/enums/index.ts b/backend/src/modules/inventory/order-status/enums/index.ts new file mode 100644 index 0000000..9127ff2 --- /dev/null +++ b/backend/src/modules/inventory/order-status/enums/index.ts @@ -0,0 +1 @@ +export * from './order-status-code.enum'; diff --git a/backend/src/modules/inventory/order-status/enums/order-status-code.enum.ts b/backend/src/modules/inventory/order-status/enums/order-status-code.enum.ts new file mode 100644 index 0000000..9e1b8c4 --- /dev/null +++ b/backend/src/modules/inventory/order-status/enums/order-status-code.enum.ts @@ -0,0 +1,7 @@ +export enum OrderStatusCode { + Reserved = 'reserved', + SentForShipment = 'sent_for_shipment', + Shipped = 'shipped', + Cancelled = 'cancelled', + Returned = 'returned', +} diff --git a/backend/src/modules/inventory/order-status/order-status.controller.ts b/backend/src/modules/inventory/order-status/order-status.controller.ts new file mode 100644 index 0000000..a7b7b6c --- /dev/null +++ b/backend/src/modules/inventory/order-status/order-status.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { OrderStatusDto } from './dto/order-status.dto'; +import { OrderStatusService } from './order-status.service'; + +@ApiTags('inventory/order/statuses') +@Controller('products/order-statuses') +@JwtAuthorized() +@TransformToDto() +export class OrderStatusController { + constructor(private service: OrderStatusService) {} + + @ApiCreatedResponse({ description: 'Get order statuses', type: [OrderStatusDto] }) + @Get() + public async getStatuses(@CurrentAuth() { accountId }: AuthData) { + return this.service.getManyOrDefault(accountId); + } +} diff --git a/backend/src/modules/inventory/order-status/order-status.module.ts b/backend/src/modules/inventory/order-status/order-status.module.ts new file mode 100644 index 0000000..36d2f15 --- /dev/null +++ b/backend/src/modules/inventory/order-status/order-status.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { OrderStatus } from './entities/order-status.entity'; +import { OrderStatusController } from './order-status.controller'; +import { OrderStatusService } from './order-status.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([OrderStatus]), IAMModule], + controllers: [OrderStatusController], + providers: [OrderStatusService], + exports: [OrderStatusService], +}) +export class OrderStatusModule {} diff --git a/backend/src/modules/inventory/order-status/order-status.service.ts b/backend/src/modules/inventory/order-status/order-status.service.ts new file mode 100644 index 0000000..4433133 --- /dev/null +++ b/backend/src/modules/inventory/order-status/order-status.service.ts @@ -0,0 +1,62 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { OrderStatusCode } from './enums/order-status-code.enum'; +import { CreateOrderStatusDto } from './dto/create-order-status.dto'; +import { OrderStatus } from './entities/order-status.entity'; + +interface FindFilter { + statusId?: number; + code?: OrderStatusCode; +} + +@Injectable() +export class OrderStatusService { + constructor( + @InjectRepository(OrderStatus) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, dto: CreateOrderStatusDto): Promise { + return await this.repository.save(OrderStatus.fromDto(accountId, dto)); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createQb(accountId, filter).getOne(); + } + public async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createQb(accountId, filter).getMany(); + } + + public async getManyOrDefault(accountId: number): Promise { + const statuses = await this.repository.find({ where: { accountId }, order: { sortOrder: 'ASC' } }); + if (statuses.length === 0) { + return await this.createDefaultStatuses(accountId); + } + return statuses; + } + + private createQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('os').where('os.account_id = :accountId', { accountId }); + if (filter?.statusId) { + qb.andWhere('os.id = :id', { id: filter.statusId }); + } + if (filter?.code) { + qb.andWhere('os.code = :code', { code: filter.code }); + } + return qb; + } + + private async createDefaultStatuses(accountId: number): Promise { + let sortOrder = 0; + const dtos = [ + CreateOrderStatusDto.system('Reserved', '#ea925a', OrderStatusCode.Reserved, sortOrder++), + CreateOrderStatusDto.system('Sent for shipment', '#a33cab', OrderStatusCode.SentForShipment, sortOrder++), + CreateOrderStatusDto.system('Shipped', '#8af039', OrderStatusCode.Shipped, sortOrder++), + CreateOrderStatusDto.system('Cancelled', '#ee675c', OrderStatusCode.Cancelled, sortOrder++), + CreateOrderStatusDto.system('Returned', '#c0c5cc', OrderStatusCode.Returned, sortOrder++), + ]; + return await Promise.all(dtos.map(async (dto) => await this.create(accountId, dto))); + } +} diff --git a/backend/src/modules/inventory/order/dto/create-order.dto.ts b/backend/src/modules/inventory/order/dto/create-order.dto.ts new file mode 100644 index 0000000..74760d5 --- /dev/null +++ b/backend/src/modules/inventory/order/dto/create-order.dto.ts @@ -0,0 +1,13 @@ +import { PickType } from '@nestjs/swagger'; + +import { OrderDto } from './order.dto'; + +export class CreateOrderDto extends PickType(OrderDto, [ + 'entityId', + 'currency', + 'taxIncluded', + 'statusId', + 'warehouseId', + 'items', + 'cancelAfter', +] as const) {} diff --git a/backend/src/modules/inventory/order/dto/index.ts b/backend/src/modules/inventory/order/dto/index.ts new file mode 100644 index 0000000..a9e86ed --- /dev/null +++ b/backend/src/modules/inventory/order/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-order.dto'; +export * from './order-filter.dto'; +export * from './order-item.dto'; +export * from './order.dto'; +export * from './update-order.dto'; diff --git a/backend/src/modules/inventory/order/dto/order-filter.dto.ts b/backend/src/modules/inventory/order/dto/order-filter.dto.ts new file mode 100644 index 0000000..62494ce --- /dev/null +++ b/backend/src/modules/inventory/order/dto/order-filter.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class OrderFilterDto { + @ApiProperty() + @IsNumber() + entityId: number; +} diff --git a/backend/src/modules/inventory/order/dto/order-item.dto.ts b/backend/src/modules/inventory/order/dto/order-item.dto.ts new file mode 100644 index 0000000..26c237e --- /dev/null +++ b/backend/src/modules/inventory/order/dto/order-item.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsObject, IsOptional } from 'class-validator'; + +import { ReservationDto } from '../../reservation/dto/reservation.dto'; +import { ProductInfoDto } from '../../product/dto/product-info.dto'; + +export class OrderItemDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + unitPrice: number; + + @ApiProperty() + @IsNumber() + quantity: number; + + @ApiProperty() + @IsNumber() + tax: number; + + @ApiProperty() + @IsNumber() + discount: number; + + @ApiProperty() + @IsNumber() + productId: number; + + @ApiProperty() + @IsNumber() + sortOrder: number; + + @ApiProperty({ type: ProductInfoDto, required: false }) + @IsOptional() + @IsObject() + productInfo: ProductInfoDto | undefined; + + @ApiProperty({ type: [ReservationDto], required: false }) + @IsOptional() + @IsArray() + reservations: ReservationDto[] | undefined; + + constructor( + id: number, + unitPrice: number, + quantity: number, + tax: number, + discount: number, + productId: number, + sortOrder: number, + productInfo: ProductInfoDto | undefined, + reservations: ReservationDto[] | undefined, + ) { + this.id = id; + this.unitPrice = unitPrice; + this.quantity = quantity; + this.tax = tax; + this.discount = discount; + this.productId = productId; + this.sortOrder = sortOrder; + this.productInfo = productInfo; + this.reservations = reservations; + } +} diff --git a/backend/src/modules/inventory/order/dto/order.dto.ts b/backend/src/modules/inventory/order/dto/order.dto.ts new file mode 100644 index 0000000..1c010d3 --- /dev/null +++ b/backend/src/modules/inventory/order/dto/order.dto.ts @@ -0,0 +1,106 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { Currency } from '@/common'; + +import { OrderItemDto } from './order-item.dto'; + +export class OrderDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + sectionId: number; + + @ApiProperty() + @IsNumber() + entityId: number; + + @ApiProperty() + @IsNumber() + orderNumber: number; + + @ApiProperty() + @IsNumber() + totalAmount: number; + + @ApiProperty({ enum: Currency }) + @IsEnum(Currency) + currency: Currency; + + @ApiProperty() + @IsBoolean() + taxIncluded: boolean; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + statusId: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + warehouseId?: number | null; + + @ApiProperty() + @IsNumber() + createdBy: number; + + @ApiProperty() + @IsString() + createdAt: string; + + @ApiProperty() + @IsString() + updatedAt: string; + + @ApiPropertyOptional({ nullable: true, description: 'in hours' }) + @IsOptional() + @IsNumber() + cancelAfter?: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + shippedAt?: string | null; + + @ApiProperty({ type: [OrderItemDto] }) + @IsArray() + items: OrderItemDto[]; + + constructor({ + id, + sectionId, + entityId, + orderNumber, + totalAmount, + currency, + taxIncluded, + statusId, + warehouseId, + createdBy, + createdAt, + updatedAt, + cancelAfter, + shippedAt, + items, + }: OrderDto) { + this.id = id; + this.sectionId = sectionId; + this.entityId = entityId; + this.orderNumber = orderNumber; + this.totalAmount = totalAmount; + this.currency = currency; + this.taxIncluded = taxIncluded; + this.statusId = statusId; + this.warehouseId = warehouseId; + this.createdBy = createdBy; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + this.cancelAfter = cancelAfter; + this.shippedAt = shippedAt; + this.items = items; + } +} diff --git a/backend/src/modules/inventory/order/dto/update-order.dto.ts b/backend/src/modules/inventory/order/dto/update-order.dto.ts new file mode 100644 index 0000000..d817fa7 --- /dev/null +++ b/backend/src/modules/inventory/order/dto/update-order.dto.ts @@ -0,0 +1,12 @@ +import { PickType } from '@nestjs/swagger'; + +import { OrderDto } from './order.dto'; + +export class UpdateOrderDto extends PickType(OrderDto, [ + 'currency', + 'taxIncluded', + 'statusId', + 'warehouseId', + 'items', + 'cancelAfter', +] as const) {} diff --git a/backend/src/modules/inventory/order/entities/index.ts b/backend/src/modules/inventory/order/entities/index.ts new file mode 100644 index 0000000..bd284f4 --- /dev/null +++ b/backend/src/modules/inventory/order/entities/index.ts @@ -0,0 +1,2 @@ +export * from './order-item.entity'; +export * from './order.entity'; diff --git a/backend/src/modules/inventory/order/entities/order-item.entity.ts b/backend/src/modules/inventory/order/entities/order-item.entity.ts new file mode 100644 index 0000000..0efdd7c --- /dev/null +++ b/backend/src/modules/inventory/order/entities/order-item.entity.ts @@ -0,0 +1,129 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { Reservation } from '../../reservation/entities/reservation.entity'; +import { Product } from '../../product/entities/product.entity'; + +import { OrderItemDto } from '../dto/order-item.dto'; + +@Entity() +export class OrderItem { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + unitPrice: number; + + @Column() + quantity: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + tax: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + discount: number; + + @Column() + productId: number; + + @Column() + orderId: number; + + @Column() + sortOrder: number; + + @Column() + accountId: number; + + private _product: Product; + private _reservations: Reservation[]; + + constructor( + accountId: number, + unitPrice: number, + quantity: number, + tax: number, + discount: number, + productId: number, + orderId: number, + sortOrder: number, + ) { + this.accountId = accountId; + this.unitPrice = unitPrice; + this.quantity = quantity; + this.tax = tax; + this.discount = discount; + this.productId = productId; + this.orderId = orderId; + this.sortOrder = sortOrder; + } + + public get product(): Product { + return this._product; + } + public set product(value: Product) { + this._product = value; + } + + public get reservations(): Reservation[] { + return this._reservations; + } + public set reservations(value: Reservation[]) { + this._reservations = value; + } + + public static fromDto(accountId: number, orderId: number, dto: OrderItemDto): OrderItem { + return new OrderItem( + accountId, + dto.unitPrice, + dto.quantity, + dto.tax, + dto.discount, + dto.productId, + orderId, + dto.sortOrder, + ); + } + + public update(dto: OrderItemDto): OrderItem { + this.unitPrice = dto.unitPrice !== undefined ? dto.unitPrice : this.unitPrice; + this.quantity = dto.quantity !== undefined ? dto.quantity : this.quantity; + this.tax = dto.tax !== undefined ? dto.tax : this.tax; + this.discount = dto.discount !== undefined ? dto.discount : this.discount; + this.productId = dto.productId !== undefined ? dto.productId : this.productId; + this.sortOrder = dto.sortOrder !== undefined ? dto.sortOrder : this.sortOrder; + + return this; + } + + public toDto(): OrderItemDto { + return new OrderItemDto( + this.id, + this.unitPrice, + this.quantity, + this.tax, + this.discount, + this.productId, + this.sortOrder, + this._product ? this._product.toInfo() : undefined, + this._reservations ? this._reservations.map((r) => r.toDto()) : undefined, + ); + } +} diff --git a/backend/src/modules/inventory/order/entities/order.entity.ts b/backend/src/modules/inventory/order/entities/order.entity.ts new file mode 100644 index 0000000..5dc72b4 --- /dev/null +++ b/backend/src/modules/inventory/order/entities/order.entity.ts @@ -0,0 +1,176 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { Currency, DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; +import { Shipment } from '../../shipment/entities/shipment.entity'; + +import { CreateOrderDto, UpdateOrderDto, OrderDto } from '../dto'; +import { OrderItem } from './order-item.entity'; + +@Entity('orders') +export class Order implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + totalAmount: number; + + @Column() + currency: Currency; + + @Column() + taxIncluded: boolean; + + @Column({ nullable: true }) + statusId: number | null; + + @Column() + warehouseId: number | null; + + @Column() + entityId: number; + + @Column() + orderNumber: number; + + @Column() + createdBy: number; + + @Column({ nullable: true }) + cancelAfter: number | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + @Column() + updatedAt: Date; + + _items: OrderItem[]; + + _shipments: Shipment[]; + + constructor( + accountId: number, + sectionId: number, + totalAmount: number, + currency: Currency, + taxIncluded: boolean, + statusId: number | null, + warehouseId: number | null, + entityId: number, + orderNumber: number, + createdBy: number, + cancelAfter: number | null, + createdAt?: Date, + ) { + this.accountId = accountId; + this.sectionId = sectionId; + this.totalAmount = totalAmount; + this.currency = currency; + this.taxIncluded = taxIncluded; + this.statusId = statusId; + this.warehouseId = warehouseId; + this.entityId = entityId; + this.orderNumber = orderNumber; + this.createdBy = createdBy; + this.cancelAfter = cancelAfter; + this.createdAt = createdAt ?? DateUtil.now(); + this.updatedAt = createdAt ?? DateUtil.now(); + } + + public get items(): OrderItem[] { + return this._items; + } + public set items(value: OrderItem[]) { + this._items = value; + } + + public get shipments(): Shipment[] { + return this._shipments; + } + public set shipments(value: Shipment[]) { + this._shipments = value; + } + + static getAuthorizable(sectionId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.ProductsOrder, id: sectionId }); + } + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.ProductsOrder, + id: this.sectionId, + createdBy: this.createdBy, + }; + } + + public static fromDto( + accountId: number, + sectionId: number, + orderNumber: number, + createdBy: number, + totalAmount: number, + dto: CreateOrderDto, + ): Order { + return new Order( + accountId, + sectionId, + totalAmount, + dto.currency, + dto.taxIncluded, + dto.statusId, + dto.warehouseId, + dto.entityId, + orderNumber, + createdBy, + dto.cancelAfter, + ); + } + + public updateFromDto(totalAmount: number, dto: UpdateOrderDto): Order { + this.currency = dto.currency; + this.taxIncluded = dto.taxIncluded; + this.statusId = dto.statusId; + this.warehouseId = dto.warehouseId; + this.totalAmount = totalAmount; + this.cancelAfter = dto.cancelAfter !== undefined ? dto.cancelAfter : this.cancelAfter; + this.updatedAt = DateUtil.now(); + + return this; + } + + public toDto(): OrderDto { + const shippedAt = this._shipments ? this._shipments.find((s) => s.shippedAt !== null)?.shippedAt : null; + return new OrderDto({ + id: this.id, + sectionId: this.sectionId, + entityId: this.entityId, + orderNumber: this.orderNumber, + totalAmount: this.totalAmount, + currency: this.currency, + taxIncluded: this.taxIncluded, + statusId: this.statusId, + warehouseId: this.warehouseId, + createdBy: this.createdBy, + createdAt: this.createdAt.toISOString(), + updatedAt: this.updatedAt.toISOString(), + cancelAfter: this.cancelAfter, + shippedAt: shippedAt?.toISOString() ?? null, + items: this._items ? this._items.map((i) => i.toDto()) : [], + }); + } +} diff --git a/backend/src/modules/inventory/order/helper/index.ts b/backend/src/modules/inventory/order/helper/index.ts new file mode 100644 index 0000000..4120e15 --- /dev/null +++ b/backend/src/modules/inventory/order/helper/index.ts @@ -0,0 +1 @@ +export * from './order.helper'; diff --git a/backend/src/modules/inventory/order/helper/order.helper.ts b/backend/src/modules/inventory/order/helper/order.helper.ts new file mode 100644 index 0000000..90fedcb --- /dev/null +++ b/backend/src/modules/inventory/order/helper/order.helper.ts @@ -0,0 +1,30 @@ +import Decimal from 'decimal.js'; + +interface OrderItem { + unitPrice: number; + quantity: number; + discount: number; + tax: number; +} + +export class OrderHelper { + public static calcTotalAmount(items: OrderItem[], taxIncluded: boolean): number { + let totalAmount = new Decimal(0); + for (const item of items) { + totalAmount = totalAmount.plus(this.calcAmount(item, taxIncluded)); + } + return totalAmount.toNumber(); + } + + public static calcAmount(item: OrderItem, taxIncluded: boolean): number { + const amount = new Decimal(item.unitPrice).mul(item.quantity); + const discountTotal = amount.mul(new Decimal(item.discount).div(100)); + + if (taxIncluded) { + return amount.sub(discountTotal).toNumber(); + } + + const taxTotal = amount.mul(new Decimal(item.tax).div(100)); + return amount.add(taxTotal).sub(discountTotal).toNumber(); + } +} diff --git a/backend/src/modules/inventory/order/order.controller.ts b/backend/src/modules/inventory/order/order.controller.ts new file mode 100644 index 0000000..b96776e --- /dev/null +++ b/backend/src/modules/inventory/order/order.controller.ts @@ -0,0 +1,90 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { ExpandQuery, TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ExpandableField } from './types/expandable-field'; +import { CreateOrderDto } from './dto/create-order.dto'; +import { OrderDto } from './dto/order.dto'; +import { OrderFilterDto } from './dto/order-filter.dto'; +import { UpdateOrderDto } from './dto/update-order.dto'; +import { OrderService } from './services/order.service'; + +@ApiTags('inventory/orders') +@Controller('products') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class OrderController { + constructor(private readonly service: OrderService) {} + + @ApiCreatedResponse({ description: 'Create order', type: OrderDto }) + @Post('sections/:sectionId/orders') + public async create( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: CreateOrderDto, + ) { + return await this.service.create(accountId, user, sectionId, dto); + } + + @ApiOkResponse({ description: 'Get orders for entity', type: [OrderDto] }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: items, shippedAt.', + }) + @Get('orders') + public async findMany( + @CurrentAuth() { accountId, user }: AuthData, + @Query() filter: OrderFilterDto, + @Query() expand?: ExpandQuery, + ) { + return this.service.findMany(accountId, user, filter, { expand: expand.fields }); + } + + @ApiOkResponse({ description: 'Get order', type: OrderDto }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: items, shippedAt.', + }) + @Get('orders/:orderId') + public async findOne( + @CurrentAuth() { accountId, user }: AuthData, + @Param('orderId', ParseIntPipe) orderId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.findOne(accountId, user, { orderId }, { expand: expand.fields }); + } + + @ApiCreatedResponse({ description: 'Update order', type: OrderDto }) + @Put('sections/:sectionId/orders/:orderId') + public async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('orderId', ParseIntPipe) orderId: number, + @Query('returnStocks') returnStocks: string, + @Body() dto: UpdateOrderDto, + ) { + return this.service.update(accountId, user, sectionId, orderId, dto, returnStocks === 'true'); + } + + @ApiOkResponse({ description: 'Delete order' }) + @Delete('sections/:sectionId/orders/:orderId') + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('orderId', ParseIntPipe) orderId: number, + @Query('returnStocks') returnStocks: string, + ) { + return this.service.delete(accountId, { sectionId, orderId }, { returnStocks: returnStocks === 'true' }); + } +} diff --git a/backend/src/modules/inventory/order/order.module.ts b/backend/src/modules/inventory/order/order.module.ts new file mode 100644 index 0000000..e630b0e --- /dev/null +++ b/backend/src/modules/inventory/order/order.module.ts @@ -0,0 +1,33 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { OrderStatusModule } from '../order-status/order-status.module'; +import { ProductModule } from '../product/product.module'; +import { ReservationModule } from '../reservation/reservation.module'; +import { ShipmentModule } from '../shipment/shipment.module'; +import { WarehouseModule } from '../warehouse/warehouse.module'; + +import { OrderItem } from './entities/order-item.entity'; +import { Order } from './entities/order.entity'; +import { OrderItemService } from './services/order-item.service'; +import { OrderService } from './services/order.service'; +import { OrderHandler } from './services/order.handler'; +import { OrderController } from './order.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Order, OrderItem]), + IAMModule, + OrderStatusModule, + forwardRef(() => ReservationModule), + forwardRef(() => ProductModule), + ShipmentModule, + forwardRef(() => WarehouseModule), + ], + controllers: [OrderController], + providers: [OrderItemService, OrderService, OrderHandler], + exports: [OrderService], +}) +export class OrderModule {} diff --git a/backend/src/modules/inventory/order/services/order-item.service.ts b/backend/src/modules/inventory/order/services/order-item.service.ts new file mode 100644 index 0000000..051bb21 --- /dev/null +++ b/backend/src/modules/inventory/order/services/order-item.service.ts @@ -0,0 +1,80 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { ProductService } from '../../product/product.service'; +import { ReservationService } from '../../reservation/reservation.service'; + +import { OrderItemDto } from '../dto/order-item.dto'; +import { OrderItem } from '../entities/order-item.entity'; + +@Injectable() +export class OrderItemService { + constructor( + @InjectRepository(OrderItem) + private readonly repository: Repository, + private readonly reservationService: ReservationService, + @Inject(forwardRef(() => ProductService)) + private readonly productService: ProductService, + ) {} + + public async createMany(accountId: number, orderId: number, dtos: OrderItemDto[]): Promise { + return await Promise.all( + dtos.map(async (dto) => { + const item = await this.repository.save(OrderItem.fromDto(accountId, orderId, dto)); + + item.reservations = await this.reservationService.create( + accountId, + orderId, + item.id, + item.productId, + dto.reservations ?? [], + ); + + return item; + }), + ); + } + + public async getForOrder(accountId: number, sectionId: number, orderId: number): Promise { + const items = await this.repository.findBy({ orderId }); + for (const item of items) { + item.product = await this.productService.findById(accountId, sectionId, item.productId); + item.reservations = await this.reservationService.findMany(accountId, { + orderId, + orderItemId: item.id, + }); + } + return items; + } + + public async updateForOrder(accountId: number, orderId: number, dtos: OrderItemDto[]): Promise { + const currentItems = await this.repository.findBy({ accountId, orderId }); + const currentItemIds = currentItems.map((item) => item.id); + + const createdDtos = dtos.filter((dto) => !currentItemIds.includes(dto.id)); + const updatedDtos = dtos.filter((dto) => currentItemIds.includes(dto.id)); + const deletedItems = currentItems.filter((item) => !dtos.some((dto) => dto.id === item.id)); + + await this.createMany(accountId, orderId, createdDtos); + await this.updateItems(accountId, orderId, currentItems, updatedDtos); + await this.repository.delete({ accountId, id: In(deletedItems.map((item) => item.id)) }); + } + + private async updateItems( + accountId: number, + orderId: number, + orderItems: OrderItem[], + dtos: OrderItemDto[], + ): Promise { + await Promise.all( + dtos.map(async (dto) => { + const orderItem = orderItems.find((item) => item.id === dto.id); + if (orderItem) { + await this.repository.save(orderItem.update(dto)); + await this.reservationService.create(accountId, orderId, orderItem.id, orderItem.productId, dto.reservations); + } + }), + ); + } +} diff --git a/backend/src/modules/inventory/order/services/order.handler.ts b/backend/src/modules/inventory/order/services/order.handler.ts new file mode 100644 index 0000000..3217c5f --- /dev/null +++ b/backend/src/modules/inventory/order/services/order.handler.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +import { ProductsEventType, ShipmentStatusChangedEvent } from '../../common'; +import { OrderService } from './order.service'; + +@Injectable() +export class OrderHandler { + constructor(private readonly service: OrderService) {} + + @Cron(CronExpression.EVERY_HOUR) + public async checkCancelOrders() { + if (process.env.SCHEDULE_PRODUCTS_ORDER_CHECK_CANCEL_DISABLE === 'true') return; + this.service.checkCancelOrders(); + } + + @OnEvent(ProductsEventType.ShipmentStatusChanged, { async: true }) + public async onShipmentStatusChanged(event: ShipmentStatusChangedEvent): Promise { + this.service.processShipmentStatusChanged({ + accountId: event.accountId, + sectionId: event.sectionId, + orderId: event.orderId, + statusId: event.statusId, + }); + } +} diff --git a/backend/src/modules/inventory/order/services/order.service.ts b/backend/src/modules/inventory/order/services/order.service.ts new file mode 100644 index 0000000..29ee7e3 --- /dev/null +++ b/backend/src/modules/inventory/order/services/order.service.ts @@ -0,0 +1,376 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { CrmEventType, EntityPriceUpdateEvent } from '@/CRM/common'; + +import { ProductOrderCreatedEvent, ProductOrderEvent, ProductsEventType } from '../../common'; + +import { OrderStatusCode } from '../../order-status/enums/order-status-code.enum'; +import { OrderStatusService } from '../../order-status/order-status.service'; +import { ReservationService } from '../../reservation/reservation.service'; +import { ShipmentService } from '../../shipment/shipment.service'; +import { WarehouseService } from '../../warehouse/warehouse.service'; + +import { ExpandableField } from '../types/expandable-field'; +import { CreateOrderDto } from '../dto/create-order.dto'; +import { UpdateOrderDto } from '../dto/update-order.dto'; +import { Order } from '../entities/order.entity'; +import { OrderHelper } from '../helper/order.helper'; +import { OrderItemService } from './order-item.service'; + +interface FindFilter { + sectionId?: number; + warehouseId?: number | number[]; + withoutWarehouse?: boolean; + orderId?: number | number[]; + entityId?: number; + statusId?: { include?: number[]; exclude?: number[] }; +} +interface FindOptions { + expand?: ExpandableField[]; +} +interface CancelOptions { + returnStocks?: boolean; +} +type ProcessOptions = { processShipments?: boolean } & CancelOptions; +type DeleteOptions = { newWarehouseId?: number } & CancelOptions; + +@Injectable() +export class OrderService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Order) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly orderItemService: OrderItemService, + private readonly reservationService: ReservationService, + private readonly orderStatusService: OrderStatusService, + @Inject(forwardRef(() => ShipmentService)) + private readonly shipmentService: ShipmentService, + @Inject(forwardRef(() => WarehouseService)) + private readonly warehouseService: WarehouseService, + ) {} + + public async create(accountId: number, user: User, sectionId: number, dto: CreateOrderDto): Promise { + await this.authService.check({ + action: 'create', + user, + authorizable: Order.getAuthorizable(sectionId), + throwError: true, + }); + + const totalAmount = OrderHelper.calcTotalAmount(dto.items, dto.taxIncluded); + const orderNumber = await this.getOrderNumber(sectionId, dto.entityId); + const order = await this.repository.save( + Order.fromDto(accountId, sectionId, orderNumber, user.id, totalAmount, dto), + ); + this.eventEmitter.emit( + ProductsEventType.ProductOrderCreated, + new ProductOrderCreatedEvent({ + accountId, + entityId: order.entityId, + orderId: order.id, + createdAt: order.createdAt.toISOString(), + }), + ); + + if (dto.items) { + await this.orderItemService.createMany(accountId, order.id, dto.items); + order.items = await this.orderItemService.getForOrder(accountId, sectionId, order.id); + } + + if (order.statusId) { + await this.processOrderStatus(accountId, sectionId, order.statusId, order, { processShipments: true }); + } + order.shipments = await this.shipmentService.findMany({ + accountId, + user, + filter: { sectionId, orderId: order.id }, + }); + + this.updateEntityValue(accountId, order.entityId); + + return order; + } + + public async findOne( + accountId: number, + user: User | null, + filter: FindFilter, + options?: FindOptions, + ): Promise { + if (user) { + const warehouses = await this.warehouseService.findMany({ + user, + filter: { accountId, sectionId: filter.sectionId, warehouseId: filter.warehouseId, onlyAvailable: true }, + }); + filter.withoutWarehouse = !filter.warehouseId; + filter.warehouseId = warehouses?.length ? warehouses.map((w) => w.id) : undefined; + } + const order = await this.createQb(accountId, filter).getOne(); + if (user) { + await this.authService.check({ action: 'view', user, authorizable: order, throwError: true }); + } + return order && options?.expand ? await this.expandOne(accountId, user, order, options.expand) : order; + } + public async findMany( + accountId: number, + user: User | null, + filter: FindFilter, + options?: FindOptions, + ): Promise { + if (user) { + const warehouses = await this.warehouseService.findMany({ + user, + filter: { accountId, sectionId: filter.sectionId, warehouseId: filter.warehouseId, onlyAvailable: true }, + }); + filter.withoutWarehouse = !filter.warehouseId; + filter.warehouseId = warehouses?.length ? warehouses.map((w) => w.id) : undefined; + } + + const orders = await this.createQb(accountId, filter, true).getMany(); + const checkedOrders: Order[] = []; + if (user) { + for (const order of orders) { + if (await this.authService.check({ action: 'view', user, authorizable: order })) { + checkedOrders.push(order); + } + } + } else { + checkedOrders.push(...orders); + } + return checkedOrders && options?.expand + ? await this.expandMany(accountId, user, checkedOrders, options.expand) + : checkedOrders; + } + + public async update( + accountId: number, + user: User, + sectionId: number, + orderId: number, + dto: UpdateOrderDto, + returnStocks?: boolean, + ): Promise { + const order = await this.findOne(accountId, null, { sectionId, orderId }); + if (!order) { + throw NotFoundError.withId(Order, orderId); + } + + await this.authService.check({ action: 'edit', user, authorizable: order, throwError: true }); + + const prevStatusId = order.statusId; + const totalAmount = OrderHelper.calcTotalAmount(dto.items, dto.taxIncluded); + + await this.repository.save(order.updateFromDto(totalAmount, dto)); + + if (dto.items) { + await this.orderItemService.updateForOrder(accountId, orderId, dto.items); + order.items = await this.orderItemService.getForOrder(accountId, sectionId, order.id); + } + + if (order.statusId && order.statusId !== prevStatusId) { + await this.processOrderStatus(accountId, sectionId, order.statusId, order, { + processShipments: true, + returnStocks, + }); + } + order.shipments = await this.shipmentService.findMany({ + accountId, + user, + filter: { sectionId, orderId: order.id }, + }); + + this.updateEntityValue(accountId, order.entityId); + + return order; + } + + public async processShipmentStatusChanged({ + accountId, + sectionId, + orderId, + statusId, + }: { + accountId: number; + sectionId: number; + orderId: number; + statusId: number; + }): Promise { + const order = await this.findOne(accountId, null, { sectionId, orderId }); + if (order && statusId !== order.statusId) { + order.statusId = statusId; + await this.repository.save(order); + + order.items = await this.orderItemService.getForOrder(accountId, sectionId, order.id); + await this.processOrderStatus(accountId, sectionId, statusId, order); + this.updateEntityValue(accountId, order.entityId); + } + } + + public async checkCancelOrders() { + const orders = await this.repository + .createQueryBuilder('o') + .leftJoin('order_status', 'os', 'os.id = o.status_id') + .where('o.cancel_after IS NOT NULL') + .andWhere('os.code = :code', { code: OrderStatusCode.Reserved }) + .andWhere(`o.updated_at + (o.cancel_after * INTERVAL '1 hour') < now()`) + .getMany(); + + orders.forEach(async (order) => { + const status = await this.orderStatusService.findOne(order.accountId, { code: OrderStatusCode.Cancelled }); + if (status) { + order.statusId = status.id; + await this.repository.save(order); + + await this.processOrderStatus(order.accountId, order.sectionId, order.statusId, order, { + processShipments: true, + returnStocks: true, + }); + } + }); + } + + public async delete(accountId: number, filter: FindFilter, options?: DeleteOptions) { + await this.reservationService.delete(accountId, filter, options); + await this.shipmentService.delete(accountId, filter, options); + + const qb = this.createQb(accountId, filter); + if (options?.newWarehouseId) { + await qb.update({ warehouseId: options.newWarehouseId }).execute(); + } else { + const orders = await qb.clone().getMany(); + await qb.clone().delete().execute(); + for (const order of orders) { + this.updateEntityValue(accountId, order.entityId); + this.eventEmitter.emit( + ProductsEventType.ProductOrderDeleted, + new ProductOrderEvent({ accountId: order.accountId, entityId: order.entityId, orderId: order.id }), + ); + } + } + } + + private createQb(accountId: number, filter: FindFilter, ordered = false) { + const qb = this.repository.createQueryBuilder('orders').where('orders.account_id = :accountId', { accountId }); + if (filter.sectionId) { + qb.andWhere('orders.section_id = :sectionId', { sectionId: filter.sectionId }); + } + if (filter.warehouseId) { + qb.andWhere( + new Brackets((qbW) => { + if (Array.isArray(filter.warehouseId)) { + qbW.andWhere('orders.warehouse_id IN (:...warehouseIds)', { warehouseIds: filter.warehouseId }); + } else { + qbW.andWhere('orders.warehouse_id = :warehouseId', { warehouseId: filter.warehouseId }); + } + if (filter.withoutWarehouse) { + qbW.orWhere('orders.warehouse_id IS NULL'); + } + }), + ); + } + if (filter.orderId) { + if (Array.isArray(filter.orderId)) { + qb.andWhere('orders.id IN (:...orderIds)', { orderIds: filter.orderId }); + } else { + qb.andWhere('orders.id = :orderId', { orderId: filter.orderId }); + } + } + if (filter.entityId) { + qb.andWhere('orders.entity_id = :entityId', { entityId: filter.entityId }); + } + if (filter.statusId) { + if (filter.statusId.include) { + qb.andWhere('orders.status_id IN (:...statusIds)', { statusIds: filter.statusId.include }); + } + if (filter.statusId.exclude) { + qb.andWhere( + new Brackets((qb) => { + qb.where('orders.status_id NOT IN (:...statusIds)', { statusIds: filter.statusId.exclude }).orWhere( + 'orders.status_id IS NULL', + ); + }), + ); + } + } + if (ordered) { + qb.orderBy('orders.created_at', 'DESC'); + } + return qb; + } + + private async expandOne( + accountId: number, + user: User | null, + order: Order, + expand: ExpandableField[], + ): Promise { + if (expand.includes('items')) { + order.items = await this.orderItemService.getForOrder(accountId, order.sectionId, order.id); + } + if (expand.includes('shipments') || expand.includes('shippedAt')) { + order.shipments = await this.shipmentService.findMany({ + accountId, + user, + filter: { sectionId: order.sectionId, orderId: order.id }, + }); + } + return order; + } + private async expandMany( + accountId: number, + user: User | null, + orders: Order[], + expand: ExpandableField[], + ): Promise { + return await Promise.all(orders.map((order) => this.expandOne(accountId, user, order, expand))); + } + + private async processOrderStatus( + accountId: number, + sectionId: number, + statusId: number, + order: Order, + options?: ProcessOptions, + ): Promise { + const status = await this.orderStatusService.findOne(accountId, { statusId }); + if ([OrderStatusCode.Shipped, OrderStatusCode.Cancelled, OrderStatusCode.Returned].includes(status.code)) { + await this.reservationService.delete(accountId, { orderId: order.id }); + } + if (options?.processShipments) { + await this.shipmentService.processOrder(accountId, sectionId, order, status, options); + } + } + + private async getOrderNumber(sectionId: number, entityId: number): Promise { + const result = await this.repository + .createQueryBuilder('o') + .select('MAX(o.order_number)', 'order_number') + .where('o.entity_id = :entityId', { entityId }) + .andWhere('o.section_id = :sectionId', { sectionId }) + .getRawOne(); + + return Number(result?.order_number ?? 0) + 1; + } + + private async updateEntityValue(accountId: number, entityId: number) { + const cancelledStatus = await this.orderStatusService.findOne(accountId, { code: OrderStatusCode.Cancelled }); + const orders = await this.findMany(accountId, null, { + entityId, + statusId: { exclude: cancelledStatus ? [cancelledStatus.id] : undefined }, + }); + const totalAmount = orders.reduce((sum, order) => sum + order.totalAmount, 0); + + this.eventEmitter.emit( + CrmEventType.EntityPriceUpdate, + new EntityPriceUpdateEvent({ accountId, entityId, price: totalAmount }), + ); + } +} diff --git a/backend/src/modules/inventory/order/types/expandable-field.ts b/backend/src/modules/inventory/order/types/expandable-field.ts new file mode 100644 index 0000000..bc53c1c --- /dev/null +++ b/backend/src/modules/inventory/order/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'items' | 'shipments' | 'shippedAt'; diff --git a/backend/src/modules/inventory/order/types/index.ts b/backend/src/modules/inventory/order/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/inventory/order/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/inventory/product-category/dto/create-product-category.dto.ts b/backend/src/modules/inventory/product-category/dto/create-product-category.dto.ts new file mode 100644 index 0000000..e7cb8b9 --- /dev/null +++ b/backend/src/modules/inventory/product-category/dto/create-product-category.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { ProductCategoryDto } from './product-category.dto'; + +export class CreateProductCategoryDto extends PickType(ProductCategoryDto, ['name', 'parentId'] as const) {} diff --git a/backend/src/modules/inventory/product-category/dto/index.ts b/backend/src/modules/inventory/product-category/dto/index.ts new file mode 100644 index 0000000..9fb0e1a --- /dev/null +++ b/backend/src/modules/inventory/product-category/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-product-category.dto'; +export * from './product-category.dto'; +export * from './update-product-category.dto'; diff --git a/backend/src/modules/inventory/product-category/dto/product-category.dto.ts b/backend/src/modules/inventory/product-category/dto/product-category.dto.ts new file mode 100644 index 0000000..95c33d2 --- /dev/null +++ b/backend/src/modules/inventory/product-category/dto/product-category.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class ProductCategoryDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + sectionId: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + parentId: number | null; + + @ApiProperty({ type: [ProductCategoryDto] }) + @IsArray() + children: ProductCategoryDto[]; + + constructor(id: number, sectionId: number, name: string, parentId: number | null, children: ProductCategoryDto[]) { + this.id = id; + this.sectionId = sectionId; + this.name = name; + this.parentId = parentId; + this.children = children; + } +} diff --git a/backend/src/modules/inventory/product-category/dto/update-product-category.dto.ts b/backend/src/modules/inventory/product-category/dto/update-product-category.dto.ts new file mode 100644 index 0000000..47b1979 --- /dev/null +++ b/backend/src/modules/inventory/product-category/dto/update-product-category.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { ProductCategoryDto } from './product-category.dto'; + +export class UpdateProductCategoryDto extends PickType(ProductCategoryDto, ['name'] as const) {} diff --git a/backend/src/modules/inventory/product-category/entities/index.ts b/backend/src/modules/inventory/product-category/entities/index.ts new file mode 100644 index 0000000..d8c1648 --- /dev/null +++ b/backend/src/modules/inventory/product-category/entities/index.ts @@ -0,0 +1 @@ +export * from './product-category.entity'; diff --git a/backend/src/modules/inventory/product-category/entities/product-category.entity.ts b/backend/src/modules/inventory/product-category/entities/product-category.entity.ts new file mode 100644 index 0000000..36202eb --- /dev/null +++ b/backend/src/modules/inventory/product-category/entities/product-category.entity.ts @@ -0,0 +1,83 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; + +import { CreateProductCategoryDto } from '../dto/create-product-category.dto'; +import { ProductCategoryDto } from '../dto/product-category.dto'; + +@Entity() +export class ProductCategory implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column() + name: string; + + @Column() + parentId: number | null; + + @Column() + createdBy: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + private _children: ProductCategory[] | null; + + constructor( + accountId: number, + sectionId: number, + name: string, + parentId: number | null, + createdBy: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.sectionId = sectionId; + this.name = name; + this.parentId = parentId; + this.createdBy = createdBy; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public set children(children: ProductCategory[] | null) { + this._children = children; + } + public get children(): ProductCategory[] | null { + return this._children; + } + + static getAuthorizable(sectionId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Products, id: sectionId }); + } + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.Products, + id: this.sectionId, + createdBy: this.createdBy, + }; + } + + public static fromDto(accountId: number, sectionId: number, createdBy: number, dto: CreateProductCategoryDto) { + return new ProductCategory(accountId, sectionId, dto.name, dto.parentId, createdBy); + } + + public toDto(): ProductCategoryDto { + return new ProductCategoryDto( + this.id, + this.sectionId, + this.name, + this.parentId, + this._children?.map((child) => child.toDto()) ?? [], + ); + } +} diff --git a/backend/src/modules/inventory/product-category/product-category.controller.ts b/backend/src/modules/inventory/product-category/product-category.controller.ts new file mode 100644 index 0000000..2d843f2 --- /dev/null +++ b/backend/src/modules/inventory/product-category/product-category.controller.ts @@ -0,0 +1,60 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ProductCategoryDto, CreateProductCategoryDto, UpdateProductCategoryDto } from './dto'; +import { ProductCategoryService } from './product-category.service'; + +@ApiTags('inventory/products/category') +@Controller('products/sections/:sectionId/categories') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ProductCategoryController { + constructor(private readonly service: ProductCategoryService) {} + + @ApiCreatedResponse({ description: 'Create product category', type: ProductCategoryDto }) + @Post() + public async createCategory( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: CreateProductCategoryDto, + ) { + return await this.service.create(accountId, user, sectionId, dto); + } + + @ApiCreatedResponse({ description: 'Get product categories', type: [ProductCategoryDto] }) + @Get() + public async getCategories( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + ) { + return await this.service.getHierarchy(accountId, user, sectionId); + } + + @ApiCreatedResponse({ description: 'Update product category', type: ProductCategoryDto }) + @Put('/:categoryId') + public async updateCategory( + @CurrentAuth() { accountId, user }: AuthData, + @Body() dto: UpdateProductCategoryDto, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('categoryId', ParseIntPipe) categoryId: number, + ) { + return await this.service.update(accountId, user, sectionId, categoryId, dto); + } + + @ApiCreatedResponse({ description: 'Delete product category' }) + @Delete('/:categoryId') + public async deleteCategory( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('categoryId', ParseIntPipe) categoryId: number, + @Query('newCategoryId') newCategoryId?: number, + ): Promise { + await this.service.delete(accountId, user, sectionId, categoryId, isNaN(newCategoryId) ? null : newCategoryId); + } +} diff --git a/backend/src/modules/inventory/product-category/product-category.module.ts b/backend/src/modules/inventory/product-category/product-category.module.ts new file mode 100644 index 0000000..cf60be3 --- /dev/null +++ b/backend/src/modules/inventory/product-category/product-category.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { ProductCategory } from './entities/product-category.entity'; +import { ProductCategoryService } from './product-category.service'; +import { ProductCategoryController } from './product-category.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([ProductCategory]), IAMModule], + controllers: [ProductCategoryController], + providers: [ProductCategoryService], + exports: [ProductCategoryService], +}) +export class ProductCategoryModule {} diff --git a/backend/src/modules/inventory/product-category/product-category.service.ts b/backend/src/modules/inventory/product-category/product-category.service.ts new file mode 100644 index 0000000..7e1dc54 --- /dev/null +++ b/backend/src/modules/inventory/product-category/product-category.service.ts @@ -0,0 +1,142 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { IsNull, Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { CreateProductCategoryDto, UpdateProductCategoryDto } from './dto'; +import { ProductCategory } from './entities'; + +@Injectable() +export class ProductCategoryService { + constructor( + @InjectRepository(ProductCategory) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + ) {} + + public async create( + accountId: number, + user: User, + sectionId: number, + dto: CreateProductCategoryDto, + ): Promise { + await this.authService.check({ + action: 'create', + user, + authorizable: ProductCategory.getAuthorizable(sectionId), + throwError: true, + }); + + return await this.repository.save(ProductCategory.fromDto(accountId, sectionId, user.id, dto)); + } + + private async getOne(accountId: number, sectionId: number, categoryId: number): Promise { + const category = await this.repository.findOneBy({ accountId, id: categoryId, sectionId }); + if (!category) { + throw NotFoundError.withId(ProductCategory, categoryId); + } + + return category; + } + + public async getHierarchy( + accountId: number, + user: User, + sectionId: number, + categoryId: number | null = null, + ): Promise { + await this.authService.check({ + action: 'view', + user, + authorizable: ProductCategory.getAuthorizable(sectionId), + throwError: true, + }); + + return await this.getCategories(accountId, sectionId, categoryId); + } + + public async getCategories( + accountId: number, + sectionId: number, + categoryId: number | null, + ): Promise { + const categories = await this.repository.find({ + where: { accountId, sectionId, parentId: categoryId ?? IsNull() }, + order: { id: 'ASC' }, + }); + + for (const category of categories) { + category.children = await this.getCategories(accountId, sectionId, category.id); + } + + return categories; + } + + public async getCategoriesFlat( + accountId: number, + sectionId: number, + categoryId: number | null, + ): Promise { + const category = categoryId ? await this.getOne(accountId, sectionId, categoryId) : null; + const flat: ProductCategory[] = category ? [category] : []; + + const categories = await this.getCategories(accountId, sectionId, categoryId); + for (const category of categories) { + flat.push(...this.flatCategory(category)); + } + + return flat; + } + + private flatCategory(category: ProductCategory): ProductCategory[] { + const flat: ProductCategory[] = [category]; + for (const child of category.children) { + flat.push(...this.flatCategory(child)); + } + return flat; + } + + public async update( + accountId: number, + user: User, + sectionId: number, + categoryId: number, + dto: UpdateProductCategoryDto, + ): Promise { + const category = await this.getOne(accountId, sectionId, categoryId); + await this.authService.check({ action: 'edit', user, authorizable: category, throwError: true }); + + category.name = dto.name; + await this.repository.save(category); + + category.children = await this.getHierarchy(accountId, user, sectionId, category.id); + + return category; + } + + public async delete( + accountId: number, + user: User, + sectionId: number, + categoryId: number, + newCategoryId: number | null, + ): Promise { + await this.authService.check({ + action: 'delete', + user, + authorizable: ProductCategory.getAuthorizable(sectionId), + throwError: true, + }); + + const children = await this.repository.findBy({ accountId, sectionId, parentId: categoryId }); + for (const child of children) { + await this.delete(accountId, user, sectionId, child.id, newCategoryId); + } + + await this.repository.delete(categoryId); + } +} diff --git a/backend/src/modules/inventory/product-price/dto/create-product-price.dto.ts b/backend/src/modules/inventory/product-price/dto/create-product-price.dto.ts new file mode 100644 index 0000000..2fec9a5 --- /dev/null +++ b/backend/src/modules/inventory/product-price/dto/create-product-price.dto.ts @@ -0,0 +1,4 @@ +import { OmitType } from '@nestjs/swagger'; +import { ProductPriceDto } from './product-price.dto'; + +export class CreateProductPriceDto extends OmitType(ProductPriceDto, ['id'] as const) {} diff --git a/backend/src/modules/inventory/product-price/dto/index.ts b/backend/src/modules/inventory/product-price/dto/index.ts new file mode 100644 index 0000000..7e5ef4f --- /dev/null +++ b/backend/src/modules/inventory/product-price/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-product-price.dto'; +export * from './product-price.dto'; +export * from './update-product-price.dto'; diff --git a/backend/src/modules/inventory/product-price/dto/product-price.dto.ts b/backend/src/modules/inventory/product-price/dto/product-price.dto.ts new file mode 100644 index 0000000..c389db4 --- /dev/null +++ b/backend/src/modules/inventory/product-price/dto/product-price.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { Currency } from '@/common'; + +export class ProductPriceDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + name: string | null; + + @ApiProperty() + @IsNumber() + unitPrice: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + maxDiscount: number | null; + + @ApiProperty({ enum: Currency }) + @IsEnum(Currency) + currency: Currency; + + constructor(id: number, name: string | null, unitPrice: number, currency: Currency, maxDiscount: number | null) { + this.id = id; + this.name = name; + this.unitPrice = unitPrice; + this.currency = currency; + this.maxDiscount = maxDiscount; + } +} diff --git a/backend/src/modules/inventory/product-price/dto/update-product-price.dto.ts b/backend/src/modules/inventory/product-price/dto/update-product-price.dto.ts new file mode 100644 index 0000000..bdb05bf --- /dev/null +++ b/backend/src/modules/inventory/product-price/dto/update-product-price.dto.ts @@ -0,0 +1,4 @@ +import { OmitType } from '@nestjs/swagger'; +import { ProductPriceDto } from './product-price.dto'; + +export class UpdateProductPriceDto extends OmitType(ProductPriceDto, ['id'] as const) {} diff --git a/backend/src/modules/inventory/product-price/entities/index.ts b/backend/src/modules/inventory/product-price/entities/index.ts new file mode 100644 index 0000000..dd584a4 --- /dev/null +++ b/backend/src/modules/inventory/product-price/entities/index.ts @@ -0,0 +1 @@ +export * from './product-price.entity'; diff --git a/backend/src/modules/inventory/product-price/entities/product-price.entity.ts b/backend/src/modules/inventory/product-price/entities/product-price.entity.ts new file mode 100644 index 0000000..b27ded5 --- /dev/null +++ b/backend/src/modules/inventory/product-price/entities/product-price.entity.ts @@ -0,0 +1,70 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { Currency } from '@/common'; + +import { CreateProductPriceDto, UpdateProductPriceDto, ProductPriceDto } from '../dto'; + +@Entity() +export class ProductPrice { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string | null; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + unitPrice: number; + + @Column() + currency: Currency; + + @Column({ nullable: true }) + maxDiscount: number | null; + + @Column() + productId: number; + + @Column() + accountId: number; + + constructor( + accountId: number, + name: string | null, + unitPrice: number, + currency: Currency, + maxDiscount: number | null, + productId: number, + ) { + this.accountId = accountId; + this.name = name; + this.unitPrice = unitPrice; + this.currency = currency; + this.maxDiscount = maxDiscount; + this.productId = productId; + } + + public static fromDto(accountId: number, productId: number, dto: CreateProductPriceDto): ProductPrice { + dto.unitPrice = parseFloat(dto.unitPrice.toFixed(2)); + + return new ProductPrice(accountId, dto.name, dto.unitPrice, dto.currency, dto.maxDiscount, productId); + } + + public update(dto: UpdateProductPriceDto): ProductPrice { + this.name = dto.name; + this.currency = dto.currency; + this.maxDiscount = dto.maxDiscount; + this.unitPrice = parseFloat(dto.unitPrice.toFixed(2)); + + return this; + } + + public toDto(): ProductPriceDto { + return new ProductPriceDto(this.id, this.name, this.unitPrice, this.currency, this.maxDiscount); + } +} diff --git a/backend/src/modules/inventory/product-price/errors/index.ts b/backend/src/modules/inventory/product-price/errors/index.ts new file mode 100644 index 0000000..22a55a0 --- /dev/null +++ b/backend/src/modules/inventory/product-price/errors/index.ts @@ -0,0 +1 @@ +export * from './invalid-discount.error'; diff --git a/backend/src/modules/inventory/product-price/errors/invalid-discount.error.ts b/backend/src/modules/inventory/product-price/errors/invalid-discount.error.ts new file mode 100644 index 0000000..e6f4e92 --- /dev/null +++ b/backend/src/modules/inventory/product-price/errors/invalid-discount.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class InvalidDiscountError extends ServiceError { + constructor(message = 'Discount cannot be more than 100% and less than 0%') { + super({ errorCode: 'products.invalid_discount', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/modules/inventory/product-price/product-price.controller.ts b/backend/src/modules/inventory/product-price/product-price.controller.ts new file mode 100644 index 0000000..ef1fcb4 --- /dev/null +++ b/backend/src/modules/inventory/product-price/product-price.controller.ts @@ -0,0 +1,56 @@ +import { Body, Controller, Delete, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ProductPriceService } from './product-price.service'; +import { ProductPrice } from './entities/product-price.entity'; +import { ProductPriceDto } from './dto/product-price.dto'; +import { CreateProductPriceDto } from './dto/create-product-price.dto'; +import { UpdateProductPriceDto } from './dto/update-product-price.dto'; + +@ApiTags('inventory/products/prices') +@Controller('products/sections/:sectionId/products/:productId/prices') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ProductPriceController { + constructor(private readonly service: ProductPriceService) {} + + @ApiCreatedResponse({ description: 'Create product price', type: ProductPriceDto }) + @Post() + public async create( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + @Body() dto: CreateProductPriceDto, + ): Promise { + return await this.service.create(accountId, user, sectionId, productId, dto); + } + + @ApiCreatedResponse({ description: 'Update product price', type: ProductPriceDto }) + @Put('/:priceId') + public async update( + @CurrentAuth() { user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + @Param('priceId', ParseIntPipe) priceId: number, + @Body() dto: UpdateProductPriceDto, + ): Promise { + return await this.service.update(user, sectionId, productId, priceId, dto); + } + + @ApiCreatedResponse({ description: 'Delete product price' }) + @Delete('/:priceId') + public async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + @Param('priceId', ParseIntPipe) priceId: number, + ): Promise { + await this.service.delete(accountId, user, sectionId, productId, priceId); + } +} diff --git a/backend/src/modules/inventory/product-price/product-price.module.ts b/backend/src/modules/inventory/product-price/product-price.module.ts new file mode 100644 index 0000000..ca6cb9c --- /dev/null +++ b/backend/src/modules/inventory/product-price/product-price.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { ProductPrice } from './entities/product-price.entity'; +import { ProductPriceService } from './product-price.service'; +import { ProductPriceController } from './product-price.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([ProductPrice]), IAMModule], + controllers: [ProductPriceController], + providers: [ProductPriceService], + exports: [ProductPriceService], +}) +export class ProductPriceModule {} diff --git a/backend/src/modules/inventory/product-price/product-price.service.ts b/backend/src/modules/inventory/product-price/product-price.service.ts new file mode 100644 index 0000000..67dd9e3 --- /dev/null +++ b/backend/src/modules/inventory/product-price/product-price.service.ts @@ -0,0 +1,127 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { Product } from '../product/entities/product.entity'; +import { CreateProductPriceDto } from './dto/create-product-price.dto'; +import { UpdateProductPriceDto } from './dto/update-product-price.dto'; +import { ProductPrice } from './entities/product-price.entity'; +import { InvalidDiscountError } from './errors/invalid-discount.error'; + +const MAX_DISCOUNT_PERCENT = 100; +const MIN_DISCOUNT_PERCENT = 0; + +@Injectable() +export class ProductPriceService { + constructor( + @InjectRepository(ProductPrice) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + ) {} + + public async create( + accountId: number, + user: User, + sectionId: number, + productId: number, + dto: CreateProductPriceDto, + ): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + return await this.createInternal(accountId, productId, dto); + } + + private async createInternal( + accountId: number, + productId: number, + dto: CreateProductPriceDto, + ): Promise { + this.checkMaxDiscountValue(dto.maxDiscount); + return await this.repository.save(ProductPrice.fromDto(accountId, productId, dto)); + } + + public async createPricesForProduct( + accountId: number, + productId: number, + dtos: CreateProductPriceDto[], + ): Promise { + return await Promise.all(dtos.map(async (dto) => await this.createInternal(accountId, productId, dto))); + } + + public async getPricesForProduct(accountId: number, productId: number): Promise { + return this.repository.find({ + where: { accountId, productId }, + order: { id: 'asc' }, + }); + } + + private async getById(productId: number, priceId: number): Promise { + const price = await this.repository.findOneBy({ productId, id: priceId }); + + if (!price) { + throw NotFoundError.withId(ProductPrice, priceId); + } + + return price; + } + + public async update( + user: User, + sectionId: number, + productId: number, + priceId: number, + dto: UpdateProductPriceDto, + ): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + this.checkMaxDiscountValue(dto.maxDiscount); + const price = await this.getById(productId, priceId); + await this.repository.save(price.update(dto)); + + return price; + } + + public async delete( + accountId: number, + user: User, + sectionId: number, + productId: number, + priceId: number, + ): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + const result = await this.repository.delete({ accountId, productId, id: priceId }); + if (result.affected === 0) { + throw NotFoundError.withId(ProductPrice, priceId); + } + } + + private checkMaxDiscountValue(discount: number | null | undefined) { + if ( + (discount !== null || discount !== undefined) && + discount > MAX_DISCOUNT_PERCENT && + discount < MIN_DISCOUNT_PERCENT + ) { + throw new InvalidDiscountError(); + } + } +} diff --git a/backend/src/modules/inventory/product-stock/dto/create-product-stock.dto.ts b/backend/src/modules/inventory/product-stock/dto/create-product-stock.dto.ts new file mode 100644 index 0000000..f525a88 --- /dev/null +++ b/backend/src/modules/inventory/product-stock/dto/create-product-stock.dto.ts @@ -0,0 +1,4 @@ +import { OmitType } from '@nestjs/swagger'; +import { ProductStockDto } from './product-stock.dto'; + +export class CreateProductStockDto extends OmitType(ProductStockDto, ['reserved', 'available'] as const) {} diff --git a/backend/src/modules/inventory/product-stock/dto/index.ts b/backend/src/modules/inventory/product-stock/dto/index.ts new file mode 100644 index 0000000..5cf581a --- /dev/null +++ b/backend/src/modules/inventory/product-stock/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-product-stock.dto'; +export * from './product-stock.dto'; +export * from './update-product-stock.dto'; +export * from './update-product-stocks.dto'; diff --git a/backend/src/modules/inventory/product-stock/dto/product-stock.dto.ts b/backend/src/modules/inventory/product-stock/dto/product-stock.dto.ts new file mode 100644 index 0000000..b99cbd2 --- /dev/null +++ b/backend/src/modules/inventory/product-stock/dto/product-stock.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class ProductStockDto { + @ApiProperty() + @IsNumber() + warehouseId: number; + + @ApiProperty() + @IsNumber() + stockQuantity: number; + + @ApiProperty() + @IsNumber() + reserved: number; + + @ApiProperty() + @IsNumber() + available: number; +} diff --git a/backend/src/modules/inventory/product-stock/dto/update-product-stock.dto.ts b/backend/src/modules/inventory/product-stock/dto/update-product-stock.dto.ts new file mode 100644 index 0000000..8ce54bf --- /dev/null +++ b/backend/src/modules/inventory/product-stock/dto/update-product-stock.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty, OmitType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { ProductStockDto } from './product-stock.dto'; + +export class UpdateProductStockDto extends OmitType(ProductStockDto, [ + 'reserved', + 'available', + 'stockQuantity', +] as const) { + @ApiProperty({ nullable: true, description: 'Stock quantity' }) + @IsOptional() + @IsNumber() + stockQuantity: number | null; +} diff --git a/backend/src/modules/inventory/product-stock/dto/update-product-stocks.dto.ts b/backend/src/modules/inventory/product-stock/dto/update-product-stocks.dto.ts new file mode 100644 index 0000000..a20a03d --- /dev/null +++ b/backend/src/modules/inventory/product-stock/dto/update-product-stocks.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { UpdateProductStockDto } from './update-product-stock.dto'; + +export class UpdateProductStocksDto { + @ApiProperty({ type: [UpdateProductStockDto], description: 'Product stocks' }) + @IsArray() + stocks: UpdateProductStockDto[]; +} diff --git a/backend/src/modules/inventory/product-stock/entities/index.ts b/backend/src/modules/inventory/product-stock/entities/index.ts new file mode 100644 index 0000000..491f1aa --- /dev/null +++ b/backend/src/modules/inventory/product-stock/entities/index.ts @@ -0,0 +1 @@ +export * from './product-stock.entity'; diff --git a/backend/src/modules/inventory/product-stock/entities/product-stock.entity.ts b/backend/src/modules/inventory/product-stock/entities/product-stock.entity.ts new file mode 100644 index 0000000..2b6915a --- /dev/null +++ b/backend/src/modules/inventory/product-stock/entities/product-stock.entity.ts @@ -0,0 +1,48 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ProductStockDto } from '../dto'; + +@Entity() +export class ProductStock { + @PrimaryColumn() + productId: number; + + @PrimaryColumn() + warehouseId: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + stockQuantity: number; + + @Column() + accountId: number; + + constructor(accountId: number, productId: number, warehouseId: number, stockQuantity: number) { + this.accountId = accountId; + this.productId = productId; + this.warehouseId = warehouseId; + this.stockQuantity = stockQuantity; + } + + private _reserved: number | null; + public get reserved(): number { + return this._reserved ?? 0; + } + public set reserved(value: number) { + this._reserved = value; + } + + public toDto(): ProductStockDto { + return { + warehouseId: this.warehouseId, + stockQuantity: this.stockQuantity, + reserved: this.reserved, + available: this.stockQuantity - this.reserved, + }; + } +} diff --git a/backend/src/modules/inventory/product-stock/product-stock.controller.ts b/backend/src/modules/inventory/product-stock/product-stock.controller.ts new file mode 100644 index 0000000..dc1e162 --- /dev/null +++ b/backend/src/modules/inventory/product-stock/product-stock.controller.ts @@ -0,0 +1,30 @@ +import { Body, Controller, Param, ParseIntPipe, Put } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ProductStockDto, UpdateProductStocksDto } from './dto'; +import { ProductStockService } from './product-stock.service'; + +@ApiTags('inventory/products/stocks') +@Controller('products/sections/:sectionId/products/:productId/stocks') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class StockController { + constructor(private service: ProductStockService) {} + + @ApiOkResponse({ description: 'Update product stocks', type: [ProductStockDto] }) + @Put() + public async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + @Body() { stocks }: UpdateProductStocksDto, + ) { + return this.service.update({ accountId, user, sectionId, productId, dtos: stocks }); + } +} diff --git a/backend/src/modules/inventory/product-stock/product-stock.module.ts b/backend/src/modules/inventory/product-stock/product-stock.module.ts new file mode 100644 index 0000000..cef463d --- /dev/null +++ b/backend/src/modules/inventory/product-stock/product-stock.module.ts @@ -0,0 +1,19 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { ReservationModule } from '../reservation/reservation.module'; +import { WarehouseModule } from '../warehouse/warehouse.module'; + +import { ProductStock } from './entities/product-stock.entity'; +import { ProductStockService } from './product-stock.service'; +import { StockController } from './product-stock.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([ProductStock]), IAMModule, ReservationModule, forwardRef(() => WarehouseModule)], + controllers: [StockController], + providers: [ProductStockService], + exports: [ProductStockService], +}) +export class ProductStockModule {} diff --git a/backend/src/modules/inventory/product-stock/product-stock.service.ts b/backend/src/modules/inventory/product-stock/product-stock.service.ts new file mode 100644 index 0000000..4bfabc8 --- /dev/null +++ b/backend/src/modules/inventory/product-stock/product-stock.service.ts @@ -0,0 +1,188 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { Product } from '../product/entities/product.entity'; +import { ReservationService } from '../reservation/reservation.service'; +import { WarehouseService } from '../warehouse/warehouse.service'; + +import { CreateProductStockDto, UpdateProductStockDto } from './dto'; +import { ProductStock } from './entities'; + +interface FindFilter { + accountId: number; + productId: number; + warehouseId?: number | number[]; +} + +@Injectable() +export class ProductStockService { + constructor( + @InjectRepository(ProductStock) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly reservationService: ReservationService, + @Inject(forwardRef(() => WarehouseService)) + private readonly warehouseService: WarehouseService, + ) {} + + public async create({ + accountId, + productId, + dtos, + }: { + accountId: number; + productId: number; + dtos: CreateProductStockDto[]; + }): Promise { + return await this.repository.save( + dtos.map((dto) => new ProductStock(accountId, productId, dto.warehouseId, dto.stockQuantity)), + ); + } + + public async findMany({ user, filter }: { user: User | null; filter: FindFilter }): Promise { + if (user) { + const warehouses = await this.warehouseService.findMany({ + user, + filter: { accountId: filter.accountId, warehouseId: filter.warehouseId, onlyAvailable: true }, + }); + filter.warehouseId = warehouses?.length ? warehouses.map((w) => w.id) : undefined; + } + const [stocks, reserved] = await Promise.all([ + this.createFindQb(filter).getMany(), + this.reservationService.getReservedQuantities(filter.accountId, { + productId: filter.productId, + warehouseId: filter.warehouseId, + }), + ]); + stocks.forEach( + (stock) => (stock.reserved = reserved.find((r) => r.warehouseId === stock.warehouseId)?.quantity ?? 0), + ); + + return stocks; + } + + public async update({ + accountId, + user, + sectionId, + productId, + dtos, + }: { + accountId: number; + user: User; + sectionId: number; + productId: number; + dtos: UpdateProductStockDto[]; + }): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + const deleteStocks = dtos.filter((dto) => dto.stockQuantity === null); + if (deleteStocks.length > 0) { + await this.repository.delete({ productId, warehouseId: In(deleteStocks.map((ds) => ds.warehouseId)) }); + } + + const updateStocks = dtos.filter((dto) => dto.stockQuantity !== null); + if (updateStocks.length > 0) { + await this.repository.save( + updateStocks.map((dto) => new ProductStock(accountId, productId, dto.warehouseId, dto.stockQuantity)), + ); + } + + return await this.findMany({ user, filter: { accountId, productId } }); + } + + public async delete({ + accountId, + warehouseId, + newWarehouseId, + }: { + accountId: number; + warehouseId: number; + newWarehouseId?: number; + }): Promise { + if (newWarehouseId) { + const stocks = await this.repository.findBy({ accountId, warehouseId }); + for (const stock of stocks) { + const otherStock = await this.repository.findOneBy({ + accountId, + warehouseId: newWarehouseId, + productId: stock.productId, + }); + + if (otherStock) { + otherStock.stockQuantity += stock.stockQuantity; + await this.repository.save(otherStock); + } else { + await this.repository.update( + { productId: stock.productId, warehouseId: stock.warehouseId }, + { warehouseId: newWarehouseId }, + ); + } + } + } + + await this.repository.delete({ accountId, warehouseId }); + } + + public async reduce({ + accountId, + warehouseId, + productId, + quantity, + }: { + accountId: number; + warehouseId: number; + productId: number; + quantity: number; + }): Promise { + const stock = await this.repository.findOneBy({ accountId, warehouseId, productId }); + if (stock) { + stock.stockQuantity -= quantity; + await this.repository.save(stock); + } + return stock; + } + + public async increase({ + accountId, + warehouseId, + productId, + quantity, + }: { + accountId: number; + warehouseId: number; + productId: number; + quantity: number; + }): Promise { + const stock = await this.repository.findOneBy({ accountId, warehouseId, productId }); + if (stock) { + stock.stockQuantity += quantity; + await this.repository.save(stock); + } + return stock; + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('stock') + .where('stock.account_id = :accountId', { accountId: filter.accountId }) + .andWhere('stock.product_id = :productId', { productId: filter.productId }); + if (filter.warehouseId) { + if (Array.isArray(filter.warehouseId)) { + qb.andWhere('stock.warehouse_id IN (:...warehouseIds)', { warehouseIds: filter.warehouseId }); + } else { + qb.andWhere('stock.warehouse_id = :warehouseId', { warehouseId: filter.warehouseId }); + } + } + return qb; + } +} diff --git a/backend/src/modules/inventory/product/dto/create-product.dto.ts b/backend/src/modules/inventory/product/dto/create-product.dto.ts new file mode 100644 index 0000000..1adec56 --- /dev/null +++ b/backend/src/modules/inventory/product/dto/create-product.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { ProductDto } from './product.dto'; +import { CreateProductPriceDto } from '../../product-price/dto/create-product-price.dto'; +import { CreateProductStockDto } from '../../product-stock/dto/create-product-stock.dto'; + +export class CreateProductDto extends PickType(ProductDto, [ + 'name', + 'type', + 'description', + 'sku', + 'unit', + 'tax', + 'categoryId', +] as const) { + @ApiProperty({ type: [CreateProductPriceDto] }) + @IsArray() + prices: CreateProductPriceDto[]; + + @ApiProperty({ type: [String] }) + @IsArray() + photoFileIds: string[]; + + @ApiProperty({ type: [CreateProductStockDto] }) + @IsArray() + stocks: CreateProductStockDto[]; +} diff --git a/backend/src/modules/inventory/product/dto/file-upload-request.ts b/backend/src/modules/inventory/product/dto/file-upload-request.ts new file mode 100644 index 0000000..dc5f2f2 --- /dev/null +++ b/backend/src/modules/inventory/product/dto/file-upload-request.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class FilesUploadRequest { + @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } }) + files: any[]; +} diff --git a/backend/src/modules/inventory/product/dto/get-products.meta.ts b/backend/src/modules/inventory/product/dto/get-products.meta.ts new file mode 100644 index 0000000..3d1e548 --- /dev/null +++ b/backend/src/modules/inventory/product/dto/get-products.meta.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class GetProductsMeta { + @ApiProperty() + @IsNumber() + totalCount: number; + + constructor(totalCount: number) { + this.totalCount = totalCount; + } +} diff --git a/backend/src/modules/inventory/product/dto/get-products.result.ts b/backend/src/modules/inventory/product/dto/get-products.result.ts new file mode 100644 index 0000000..5f84572 --- /dev/null +++ b/backend/src/modules/inventory/product/dto/get-products.result.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty } from 'class-validator'; + +import { ProductDto } from './product.dto'; +import { GetProductsMeta } from './get-products.meta'; + +export class GetProductsResult { + @ApiProperty({ type: [ProductDto] }) + @IsArray() + products: ProductDto[]; + + @ApiProperty() + @IsNotEmpty() + meta: GetProductsMeta; + + constructor(products: ProductDto[], meta: GetProductsMeta) { + this.products = products; + this.meta = meta; + } +} diff --git a/backend/src/modules/inventory/product/dto/index.ts b/backend/src/modules/inventory/product/dto/index.ts new file mode 100644 index 0000000..1ab867e --- /dev/null +++ b/backend/src/modules/inventory/product/dto/index.ts @@ -0,0 +1,8 @@ +export * from './create-product.dto'; +export * from './file-upload-request'; +export * from './get-products.meta'; +export * from './get-products.result'; +export * from './product-info.dto'; +export * from './product.dto'; +export * from './products-filter'; +export * from './update-product.dto'; diff --git a/backend/src/modules/inventory/product/dto/product-info.dto.ts b/backend/src/modules/inventory/product/dto/product-info.dto.ts new file mode 100644 index 0000000..e5cc53b --- /dev/null +++ b/backend/src/modules/inventory/product/dto/product-info.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class ProductInfoDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + constructor(id: number, name: string) { + this.id = id; + this.name = name; + } +} diff --git a/backend/src/modules/inventory/product/dto/product.dto.ts b/backend/src/modules/inventory/product/dto/product.dto.ts new file mode 100644 index 0000000..972f6e6 --- /dev/null +++ b/backend/src/modules/inventory/product/dto/product.dto.ts @@ -0,0 +1,113 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { FileLinkDto } from '@/CRM/Service/FileLink/FileLinkDto'; + +import { ProductType } from '../enums/product-type.enum'; +import { ProductPriceDto } from '../../product-price/dto/product-price.dto'; +import { ProductStockDto } from '../../product-stock/dto/product-stock.dto'; +import { RentalScheduleStatus } from '../../rental-schedule/enums'; +import { RentalEventDto } from '../../rental-schedule/dto/rental-event.dto'; + +export class ProductDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + sectionId: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty({ enum: ProductType }) + @IsEnum(ProductType) + type: ProductType; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + description: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + sku: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + unit: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + tax: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + categoryId: number | null; + + @ApiProperty() + @IsString() + updatedAt: string; + + @ApiProperty({ type: [ProductPriceDto] }) + @IsArray() + prices: ProductPriceDto[]; + + @ApiProperty({ type: [FileLinkDto] }) + @IsArray() + photoFileLinks: FileLinkDto[]; + + @ApiProperty({ type: [ProductStockDto] }) + @IsArray() + stocks: ProductStockDto[]; + + @ApiPropertyOptional({ nullable: true, enum: RentalScheduleStatus }) + @IsOptional() + @IsEnum(RentalScheduleStatus) + rentalStatus: RentalScheduleStatus | null; + + @ApiPropertyOptional({ nullable: true, type: [RentalEventDto] }) + @IsOptional() + @IsArray() + rentalEvents: RentalEventDto[] | null; + + constructor( + id: number, + sectionId: number, + name: string, + type: ProductType, + description: string | null, + sku: string | null, + unit: string | null, + tax: number | null, + categoryId: number | null, + updatedAt: string, + prices: ProductPriceDto[], + photoFileLinks: FileLinkDto[], + stocks: ProductStockDto[], + rentalStatus: RentalScheduleStatus | null, + rentalEvents: RentalEventDto[] | null, + ) { + this.id = id; + this.sectionId = sectionId; + this.name = name; + this.type = type; + this.description = description; + this.sku = sku; + this.unit = unit; + this.tax = tax; + this.categoryId = categoryId; + this.updatedAt = updatedAt; + this.prices = prices; + this.photoFileLinks = photoFileLinks; + this.stocks = stocks; + this.rentalStatus = rentalStatus; + this.rentalEvents = rentalEvents; + } +} diff --git a/backend/src/modules/inventory/product/dto/products-filter.ts b/backend/src/modules/inventory/product/dto/products-filter.ts new file mode 100644 index 0000000..1e56438 --- /dev/null +++ b/backend/src/modules/inventory/product/dto/products-filter.ts @@ -0,0 +1,29 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class ProductsFilter { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + warehouseId?: number; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + categoryId?: number; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + search?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + sku?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + ids?: string; +} diff --git a/backend/src/modules/inventory/product/dto/update-product.dto.ts b/backend/src/modules/inventory/product/dto/update-product.dto.ts new file mode 100644 index 0000000..9d5e857 --- /dev/null +++ b/backend/src/modules/inventory/product/dto/update-product.dto.ts @@ -0,0 +1,11 @@ +import { PickType } from '@nestjs/swagger'; +import { ProductDto } from './product.dto'; + +export class UpdateProductDto extends PickType(ProductDto, [ + 'name', + 'description', + 'sku', + 'unit', + 'tax', + 'categoryId', +] as const) {} diff --git a/backend/src/modules/inventory/product/entities/index.ts b/backend/src/modules/inventory/product/entities/index.ts new file mode 100644 index 0000000..9120a7a --- /dev/null +++ b/backend/src/modules/inventory/product/entities/index.ts @@ -0,0 +1 @@ +export * from './product.entity'; diff --git a/backend/src/modules/inventory/product/entities/product.entity.ts b/backend/src/modules/inventory/product/entities/product.entity.ts new file mode 100644 index 0000000..06aa3e2 --- /dev/null +++ b/backend/src/modules/inventory/product/entities/product.entity.ts @@ -0,0 +1,148 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; +import { FileLinkDto } from '@/CRM/Service/FileLink/FileLinkDto'; + +import { PermissionObjectType } from '../../common'; + +import { ProductPrice } from '../../product-price/entities/product-price.entity'; +import { ProductStock } from '../../product-stock/entities'; +import { RentalScheduleStatus } from '../../rental-schedule/enums'; +import { RentalEvent } from '../../rental-schedule/entities/rental-event.entity'; +import { ProductType } from '../enums/product-type.enum'; +import { ProductDto } from '../dto/product.dto'; +import { CreateProductDto } from '../dto/create-product.dto'; +import { ProductInfoDto } from '../dto/product-info.dto'; + +@Entity() +export class Product implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column() + name: string; + + @Column() + type: ProductType; + + @Column() + description: string | null; + + @Column() + sku: string | null; + + @Column() + unit: string | null; + + @Column() + tax: number | null; + + @Column() + isDeleted: boolean; + + @Column() + categoryId: number | null; + + @Column() + createdBy: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + @Column() + updatedAt: Date; + + constructor( + accountId: number, + sectionId: number, + name: string, + type: ProductType, + description: string | null, + sku: string | null, + unit: string | null, + tax: number | null, + isDeleted: boolean, + categoryId: number | null, + createdBy: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.sectionId = sectionId; + this.name = name; + this.type = type; + this.description = description; + this.sku = sku; + this.unit = unit; + this.tax = tax; + this.isDeleted = isDeleted; + this.categoryId = categoryId; + this.createdBy = createdBy; + this.createdAt = createdAt ?? DateUtil.now(); + this.updatedAt = this.createdAt; + } + + static getAuthorizable(sectionId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Products, id: sectionId }); + } + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.Products, + id: this.sectionId, + createdBy: this.createdBy, + }; + } + + public static fromDto(accountId: number, sectionId: number, createdBy: number, dto: CreateProductDto): Product { + return new Product( + accountId, + sectionId, + dto.name, + dto.type, + dto.description, + dto.sku, + dto.unit, + dto.tax, + false, + dto.categoryId, + createdBy, + ); + } + + public toDto( + prices: ProductPrice[] | null, + photoFileLinks: FileLinkDto[], + stocks: ProductStock[] | null, + rentalStatus: RentalScheduleStatus | null = null, + rentalEvents: RentalEvent[] | null = null, + ): ProductDto { + return new ProductDto( + this.id, + this.sectionId, + this.name, + this.type, + this.description, + this.sku, + this.unit, + this.tax, + this.categoryId, + this.updatedAt.toISOString(), + prices ? prices.map((price) => price.toDto()) : [], + photoFileLinks, + stocks ? stocks.map((stock) => stock.toDto()) : [], + rentalStatus, + rentalEvents ? rentalEvents.map((event) => event.toDto()) : [], + ); + } + + public toInfo(): ProductInfoDto { + return new ProductInfoDto(this.id, this.name); + } +} diff --git a/backend/src/modules/inventory/product/enums/index.ts b/backend/src/modules/inventory/product/enums/index.ts new file mode 100644 index 0000000..f5a64a2 --- /dev/null +++ b/backend/src/modules/inventory/product/enums/index.ts @@ -0,0 +1 @@ +export * from './product-type.enum'; diff --git a/backend/src/modules/inventory/product/enums/product-type.enum.ts b/backend/src/modules/inventory/product/enums/product-type.enum.ts new file mode 100644 index 0000000..0ca24ac --- /dev/null +++ b/backend/src/modules/inventory/product/enums/product-type.enum.ts @@ -0,0 +1,5 @@ +export enum ProductType { + PRODUCT = 'product', + SERVICE = 'service', + KIT = 'kit', +} diff --git a/backend/src/modules/inventory/product/product.controller.ts b/backend/src/modules/inventory/product/product.controller.ts new file mode 100644 index 0000000..a59b588 --- /dev/null +++ b/backend/src/modules/inventory/product/product.controller.ts @@ -0,0 +1,122 @@ +import { + Body, + Controller, + Delete, + FileTypeValidator, + Get, + MaxFileSizeValidator, + Param, + ParseFilePipe, + ParseIntPipe, + Post, + Put, + Query, + UploadedFiles, + UseInterceptors, +} from '@nestjs/common'; +import { AnyFilesInterceptor } from '@nestjs/platform-express'; +import { ApiBody, ApiConsumes, ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; +import { memoryStorage } from 'multer'; + +import { DatePeriodDto, PagingQuery } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { StorageFile } from '@/modules/storage/types/storage-file'; +import { FileLinkDto } from '@/CRM/Service/FileLink/FileLinkDto'; + +import { ProductService } from './product.service'; +import { CreateProductDto } from './dto/create-product.dto'; +import { FilesUploadRequest } from './dto/file-upload-request'; +import { GetProductsResult } from './dto/get-products.result'; +import { ProductDto } from './dto/product.dto'; +import { ProductsFilter } from './dto/products-filter'; +import { UpdateProductDto } from './dto/update-product.dto'; + +const ProductImageFile = { + MaxSize: 5242880, + Type: 'image/*', +}; + +@ApiTags('inventory/products') +@Controller('products/sections/:sectionId/products') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +export class ProductController { + constructor(private readonly service: ProductService) {} + + @ApiCreatedResponse({ description: 'Create product', type: ProductDto }) + @Post() + public async create( + @CurrentAuth() { account, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: CreateProductDto, + ): Promise { + return await this.service.create(account, user, sectionId, dto); + } + + @ApiCreatedResponse({ description: 'Get products', type: GetProductsResult }) + @Get() + public async getMany( + @CurrentAuth() { account, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Query() filter: ProductsFilter, + @Query() paging: PagingQuery, + @Query() period: DatePeriodDto, + ): Promise { + return await this.service.getProducts(account, user, sectionId, filter, period, paging); + } + + @ApiCreatedResponse({ description: 'Product', type: ProductDto }) + @Get('/:productId') + public async getOne( + @CurrentAuth() { account, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + ): Promise { + return this.service.getDtoById(account, user, sectionId, productId); + } + + @ApiCreatedResponse({ description: 'Product', type: ProductDto }) + @Put('/:productId') + public async update( + @CurrentAuth() { account, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + @Body() dto: UpdateProductDto, + ): Promise { + return this.service.update(account, user, sectionId, productId, dto); + } + + @ApiConsumes('multipart/form-data') + @ApiBody({ description: 'Photos to upload', type: FilesUploadRequest }) + @ApiCreatedResponse({ description: 'Uploaded product photos' }) + @UseInterceptors(AnyFilesInterceptor({ storage: memoryStorage() })) + @Post('/:productId/photos') + async upload( + @UploadedFiles( + new ParseFilePipe({ + validators: [ + new MaxFileSizeValidator({ maxSize: ProductImageFile.MaxSize }), + new FileTypeValidator({ fileType: ProductImageFile.Type }), + ], + }), + ) + files: Express.Multer.File[], + @CurrentAuth() { account, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + ): Promise { + return await this.service.uploadPhotos(account, user, sectionId, productId, StorageFile.fromMulterFiles(files)); + } + + @ApiCreatedResponse({ description: 'Delete product' }) + @Delete('/:productId') + public async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + ): Promise { + await this.service.markDeleted(accountId, user, sectionId, productId); + } +} diff --git a/backend/src/modules/inventory/product/product.module.ts b/backend/src/modules/inventory/product/product.module.ts new file mode 100644 index 0000000..fcef1b1 --- /dev/null +++ b/backend/src/modules/inventory/product/product.module.ts @@ -0,0 +1,32 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { ProductPriceModule } from '../product-price/product-price.module'; +import { ProductStockModule } from '../product-stock/product-stock.module'; +import { ProductCategoryModule } from '../product-category/product-category.module'; +import { RentalScheduleModule } from '../rental-schedule/rental-schedule.module'; + +import { Product } from './entities/product.entity'; +import { ProductService } from './product.service'; +import { ProductController } from './product.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Product]), + IAMModule, + forwardRef(() => CrmModule), + StorageModule, + ProductPriceModule, + ProductStockModule, + ProductCategoryModule, + forwardRef(() => RentalScheduleModule), + ], + controllers: [ProductController], + providers: [ProductService], + exports: [ProductService], +}) +export class ProductModule {} diff --git a/backend/src/modules/inventory/product/product.service.ts b/backend/src/modules/inventory/product/product.service.ts new file mode 100644 index 0000000..24240e8 --- /dev/null +++ b/backend/src/modules/inventory/product/product.service.ts @@ -0,0 +1,298 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, In, Not, Repository } from 'typeorm'; + +import { + BadRequestError, + DatePeriod, + DatePeriodDto, + DateUtil, + FileLinkSource, + NotFoundError, + PagingQuery, +} from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; +import { FileLinkService } from '@/CRM/Service/FileLink/FileLinkService'; +import { FileLinkDto } from '@/CRM/Service/FileLink/FileLinkDto'; + +import { ProductPriceService } from '../product-price/product-price.service'; +import { ProductStockService } from '../product-stock/product-stock.service'; +import { ProductCategoryService } from '../product-category/product-category.service'; +import { RentalScheduleService } from '../rental-schedule/rental-schedule.service'; + +import { ProductType } from './enums/product-type.enum'; +import { CreateProductDto } from './dto/create-product.dto'; +import { GetProductsMeta } from './dto/get-products.meta'; +import { GetProductsResult } from './dto/get-products.result'; +import { ProductDto } from './dto/product.dto'; +import { ProductsFilter } from './dto/products-filter'; +import { UpdateProductDto } from './dto/update-product.dto'; +import { Product } from './entities/product.entity'; + +@Injectable() +export class ProductService { + constructor( + @InjectRepository(Product) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly productPriceService: ProductPriceService, + private readonly fileLinkService: FileLinkService, + private readonly storageService: StorageService, + @Inject(forwardRef(() => ProductStockService)) + private readonly stockService: ProductStockService, + private readonly categoryService: ProductCategoryService, + @Inject(forwardRef(() => RentalScheduleService)) + private readonly scheduleService: RentalScheduleService, + ) {} + + public async create(account: Account, user: User, sectionId: number, dto: CreateProductDto): Promise { + await this.authService.check({ + action: 'create', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + if (dto.sku && (await this.exists(account.id, sectionId, dto.sku))) { + throw new BadRequestError(`Product with sku ${dto.sku} already exists.`); + } + + const product = await this.repository.save(Product.fromDto(account.id, sectionId, user.id, dto)); + + if (dto.prices?.length > 0) { + await this.productPriceService.createPricesForProduct(account.id, product.id, dto.prices); + } + if (dto.photoFileIds?.length > 0) { + await this.fileLinkService.processFiles(account.id, FileLinkSource.PRODUCT_PHOTO, product.id, dto.photoFileIds); + } + if (dto.stocks?.length > 0) { + await this.stockService.create({ accountId: account.id, productId: product.id, dtos: dto.stocks }); + } + + return await this.createDto(account, user, product); + } + + public async findById(accountId: number, sectionId: number, productId: number): Promise { + return await this.repository.findOneBy({ accountId, id: productId, sectionId }); + } + public async getById(accountId: number, user: User, sectionId: number, productId: number): Promise { + const product = await this.findById(accountId, sectionId, productId); + if (!product) { + throw NotFoundError.withId(Product, productId); + } + await this.authService.check({ action: 'view', user, authorizable: product, throwError: true }); + + return product; + } + + private async exists(accountId: number, sectionId: number, sku: string, excludeProductId?: number): Promise { + return this.repository.existsBy({ + accountId, + sectionId, + sku, + isDeleted: false, + id: excludeProductId ? Not(excludeProductId) : undefined, + }); + } + + public async getDtoById(account: Account, user: User, sectionId: number, productId: number): Promise { + const product = await this.getById(account.id, user, sectionId, productId); + + return await this.createDto(account, user, product); + } + + public async getProducts( + account: Account, + user: User, + sectionId: number, + filter: ProductsFilter, + periodDto: DatePeriodDto, + paging: PagingQuery, + ): Promise { + await this.authService.check({ + action: 'view', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + const qb = await this.createQb(account.id, sectionId, filter); + + const products = await qb.orderBy('created_at', 'DESC').offset(paging.skip).limit(paging.take).getMany(); + const period = DatePeriod.fromDto(periodDto); + const dtos = await Promise.all(products.map((product) => this.createDto(account, user, product, period))); + const totalCount = await qb.getCount(); + + return new GetProductsResult(dtos, new GetProductsMeta(totalCount)); + } + + public async getProductsSimple( + accountId: number, + sectionId: number, + filter: ProductsFilter, + paging: PagingQuery, + ): Promise { + const qb = await this.createQb(accountId, sectionId, filter); + + return await qb.orderBy('created_at', 'DESC').offset(paging.skip).limit(paging.take).getMany(); + } + + //TODO: Change ProductDto to Product with prices and stocks + public async findManyFull( + account: Account, + user: User, + sectionId: number, + filter?: ProductsFilter, + ): Promise { + const qb = await this.createQb(account.id, sectionId, filter); + const products = await qb.getMany(); + return await Promise.all(products.map((product) => this.createDto(account, user, product))); + } + + public async update( + account: Account, + user: User, + sectionId: number, + productId: number, + dto: UpdateProductDto, + ): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + if (dto.sku && (await this.exists(account.id, sectionId, dto.sku, productId))) { + throw new BadRequestError(`Product with sku ${dto.sku} already exists.`); + } + + await this.repository.update( + { id: productId, accountId: account.id, sectionId }, + { + name: dto.name, + description: dto.description, + sku: dto.sku, + unit: dto.unit, + tax: dto.tax, + categoryId: dto.categoryId, + updatedAt: DateUtil.now(), + }, + ); + + return await this.getDtoById(account, user, sectionId, productId); + } + + public async markDeleted(accountId: number, user: User, sectionId: number, productId: number) { + await this.authService.check({ + action: 'delete', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + await this.repository.update({ accountId, id: productId, sectionId }, { isDeleted: true }); + } + + public async delete(accountId: number, ids: number[]) { + for (const id of ids) { + this.fileLinkService.processFiles(accountId, FileLinkSource.PRODUCT_PHOTO, id, []); + } + await this.repository.delete({ accountId, id: In(ids) }); + } + + public async uploadPhotos( + account: Account, + user: User, + sectionId: number, + productId: number, + files: StorageFile[], + ): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: Product.getAuthorizable(sectionId), + throwError: true, + }); + + const fileInfos = await this.storageService.storeProductFiles({ + accountId: account.id, + userId: user.id, + productId, + files, + section: 'photos', + }); + return await this.fileLinkService.addFiles( + account, + FileLinkSource.PRODUCT_PHOTO, + productId, + fileInfos.map((fileInfo) => fileInfo.id), + ); + } + + private async createDto(account: Account, user: User, product: Product, period?: DatePeriod): Promise { + const prices = await this.productPriceService.getPricesForProduct(account.id, product.id); + const fileLinks = await this.fileLinkService.getFileLinkDtos(account, FileLinkSource.PRODUCT_PHOTO, product.id); + const stocks = await this.stockService.findMany({ user, filter: { accountId: account.id, productId: product.id } }); + + if (period?.from && period?.to) { + const status = await this.scheduleService.checkProductStatus(account.id, product.sectionId, product.id, [period]); + + return product.toDto(prices, fileLinks, stocks, status.status, status.events); + } + + return product.toDto(prices, fileLinks, stocks); + } + + private async createQb(accountId: number, sectionId: number, filter?: ProductsFilter) { + const qb = this.repository + .createQueryBuilder('p') + .where('p.account_id = :accountId', { accountId }) + .andWhere('p.section_id = :sectionId', { sectionId }) + .andWhere({ isDeleted: false }); + + if (filter?.ids) { + qb.andWhere('id IN (:...ids)', { ids: filter.ids.split(',').map((id) => parseInt(id)) }); + return qb; + } + + if (filter?.categoryId) { + const categories = await this.categoryService.getCategoriesFlat(accountId, sectionId, filter.categoryId); + qb.andWhere('p.category_id IN (:...categoryIds)', { categoryIds: categories.map((c) => c.id) }); + } + + if (filter?.warehouseId) { + qb.andWhere( + new Brackets((qb) => { + qb.where('p.type IN (:...types)', { types: [ProductType.SERVICE, ProductType.KIT] }).orWhere( + '0 < (select sum(s.stock_quantity) from product_stock s where s.product_id = p.id and warehouse_id = :wId)', + { + wId: filter.warehouseId, + }, + ); + }), + ); + } + + if (filter?.search) { + qb.andWhere( + new Brackets((qb1) => + qb1 + .where('p.name ILIKE :searchName', { searchName: `%${filter.search}%` }) + .orWhere('p.sku ILIKE :searchSku', { searchSku: `%${filter.search}%` }), + ), + ); + } + + if (filter?.sku) { + qb.andWhere('p.sku = :sku', { sku: filter.sku }); + } + + return qb; + } +} diff --git a/backend/src/modules/inventory/products-section/dto/create-products-section.dto.ts b/backend/src/modules/inventory/products-section/dto/create-products-section.dto.ts new file mode 100644 index 0000000..a4862fc --- /dev/null +++ b/backend/src/modules/inventory/products-section/dto/create-products-section.dto.ts @@ -0,0 +1,8 @@ +import { OmitType } from '@nestjs/swagger'; +import { ProductsSectionDto } from './products-section.dto'; + +export class CreateProductsSectionDto extends OmitType(ProductsSectionDto, [ + 'id', + 'entityTypeIds', + 'schedulerIds', +] as const) {} diff --git a/backend/src/modules/inventory/products-section/dto/index.ts b/backend/src/modules/inventory/products-section/dto/index.ts new file mode 100644 index 0000000..ed4624e --- /dev/null +++ b/backend/src/modules/inventory/products-section/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-products-section.dto'; +export * from './link-modules.dto'; +export * from './products-section-entity-type.dto'; +export * from './products-section.dto'; +export * from './update-products-section.dto'; diff --git a/backend/src/modules/inventory/products-section/dto/link-modules.dto.ts b/backend/src/modules/inventory/products-section/dto/link-modules.dto.ts new file mode 100644 index 0000000..6bd5ae0 --- /dev/null +++ b/backend/src/modules/inventory/products-section/dto/link-modules.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +export class LinkModulesDto { + @ApiProperty({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + entityTypeIds: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + schedulerIds?: number[] | null; +} diff --git a/backend/src/modules/inventory/products-section/dto/products-section-entity-type.dto.ts b/backend/src/modules/inventory/products-section/dto/products-section-entity-type.dto.ts new file mode 100644 index 0000000..87f552f --- /dev/null +++ b/backend/src/modules/inventory/products-section/dto/products-section-entity-type.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class ProductsSectionEntityTypeDto { + @ApiProperty() + @IsNumber() + sectionId: number; + + @ApiProperty() + @IsNumber() + entityTypeId: number; + + constructor(sectionId: number, entityTypeId: number) { + this.sectionId = sectionId; + this.entityTypeId = entityTypeId; + } +} diff --git a/backend/src/modules/inventory/products-section/dto/products-section.dto.ts b/backend/src/modules/inventory/products-section/dto/products-section.dto.ts new file mode 100644 index 0000000..8e8f331 --- /dev/null +++ b/backend/src/modules/inventory/products-section/dto/products-section.dto.ts @@ -0,0 +1,69 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ProductsSectionType } from '../enums/products-section-type.enum'; + +export class ProductsSectionDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsString() + icon: string; + + @ApiPropertyOptional({ nullable: true, enum: ProductsSectionType }) + @IsOptional() + @IsEnum(ProductsSectionType) + type: ProductsSectionType | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsBoolean() + enableWarehouse?: boolean | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsBoolean() + enableBarcode?: boolean | null; + + @ApiPropertyOptional({ nullable: true, description: 'in hours' }) + @IsOptional() + @IsNumber() + cancelAfter?: number | null; + + @ApiProperty({ type: [Number] }) + @IsArray() + entityTypeIds: number[]; + + @ApiProperty({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + schedulerIds: number[] | null; + + constructor({ + id, + name, + icon, + type, + enableWarehouse, + enableBarcode, + cancelAfter, + entityTypeIds, + schedulerIds, + }: ProductsSectionDto) { + this.id = id; + this.name = name; + this.icon = icon; + this.type = type; + this.enableWarehouse = enableWarehouse; + this.enableBarcode = enableBarcode; + this.cancelAfter = cancelAfter; + this.entityTypeIds = entityTypeIds; + this.schedulerIds = schedulerIds; + } +} diff --git a/backend/src/modules/inventory/products-section/dto/update-products-section.dto.ts b/backend/src/modules/inventory/products-section/dto/update-products-section.dto.ts new file mode 100644 index 0000000..8ea75ad --- /dev/null +++ b/backend/src/modules/inventory/products-section/dto/update-products-section.dto.ts @@ -0,0 +1,9 @@ +import { OmitType } from '@nestjs/swagger'; +import { ProductsSectionDto } from './products-section.dto'; + +export class UpdateProductsSectionDto extends OmitType(ProductsSectionDto, [ + 'id', + 'type', + 'entityTypeIds', + 'schedulerIds', +] as const) {} diff --git a/backend/src/modules/inventory/products-section/entities/index.ts b/backend/src/modules/inventory/products-section/entities/index.ts new file mode 100644 index 0000000..cc6d5ec --- /dev/null +++ b/backend/src/modules/inventory/products-section/entities/index.ts @@ -0,0 +1,2 @@ +export * from './products-section-entity-type.entity'; +export * from './products-section.entity'; diff --git a/backend/src/modules/inventory/products-section/entities/products-section-entity-type.entity.ts b/backend/src/modules/inventory/products-section/entities/products-section-entity-type.entity.ts new file mode 100644 index 0000000..f0a8b03 --- /dev/null +++ b/backend/src/modules/inventory/products-section/entities/products-section-entity-type.entity.ts @@ -0,0 +1,29 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ProductsSectionEntityTypeDto } from '../dto'; + +@Entity() +export class ProductsSectionEntityType { + @PrimaryColumn() + sectionId: number; + + @PrimaryColumn() + entityTypeId: number; + + @Column() + accountId: number; + + constructor(accountId: number, sectionId: number, entityTypeId: number) { + this.accountId = accountId; + this.sectionId = sectionId; + this.entityTypeId = entityTypeId; + } + + public static fromDto(accountId: number, dto: ProductsSectionEntityTypeDto): ProductsSectionEntityType { + return new ProductsSectionEntityType(accountId, dto.sectionId, dto.entityTypeId); + } + + public toDto(): ProductsSectionEntityTypeDto { + return new ProductsSectionEntityTypeDto(this.sectionId, this.entityTypeId); + } +} diff --git a/backend/src/modules/inventory/products-section/entities/products-section.entity.ts b/backend/src/modules/inventory/products-section/entities/products-section.entity.ts new file mode 100644 index 0000000..081d742 --- /dev/null +++ b/backend/src/modules/inventory/products-section/entities/products-section.entity.ts @@ -0,0 +1,122 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; +import { CreateProductsSectionDto, ProductsSectionDto, UpdateProductsSectionDto } from '../dto'; +import { ProductsSectionType } from '../enums'; +import { ProductsSectionEntityType } from './products-section-entity-type.entity'; + +@Entity() +export class ProductsSection implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column() + icon: string; + + @Column({ default: 'sale' }) + type: ProductsSectionType; + + @Column() + enableWarehouse: boolean; + + @Column({ default: true }) + enableBarcode: boolean; + + @Column({ nullable: true }) + cancelAfter: number | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + private _links: ProductsSectionEntityType[] = []; + private _schedulerIds: number[] | null; + + constructor( + accountId: number, + name: string, + icon: string, + type: ProductsSectionType, + enableWarehouse: boolean, + enableBarcode: boolean, + cancelAfter: number | null, + createdAt?: Date, + ) { + this.name = name; + this.icon = icon; + this.type = type; + this.enableWarehouse = enableWarehouse; + this.enableBarcode = enableBarcode; + this.accountId = accountId; + this.cancelAfter = cancelAfter; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public get links(): ProductsSectionEntityType[] { + return this._links; + } + public set links(value: ProductsSectionEntityType[]) { + this._links = value; + } + + public get schedulerIds(): number[] | null { + return this._schedulerIds; + } + public set schedulerIds(value: number[] | null) { + this._schedulerIds = value; + } + + static getAuthorizable(sectionId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Products, id: sectionId }); + } + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.Products, + id: this.id, + }; + } + + public static fromDto(accountId: number, dto: CreateProductsSectionDto): ProductsSection { + return new ProductsSection( + accountId, + dto.name, + dto.icon, + dto.type ?? ProductsSectionType.Sale, + dto.enableWarehouse ?? true, + dto.enableBarcode ?? true, + dto.cancelAfter, + ); + } + + public toDto(): ProductsSectionDto { + return new ProductsSectionDto({ + id: this.id, + name: this.name, + icon: this.icon, + type: this.type, + enableWarehouse: this.enableWarehouse, + enableBarcode: this.enableBarcode, + cancelAfter: this.cancelAfter, + entityTypeIds: this._links.map((l) => l.entityTypeId), + schedulerIds: this.schedulerIds ?? null, + }); + } + + public update(dto: UpdateProductsSectionDto): ProductsSection { + this.name = dto.name; + this.icon = dto.icon; + this.enableWarehouse = dto.enableWarehouse ?? true; + this.enableBarcode = dto.enableBarcode ?? true; + this.cancelAfter = dto.cancelAfter !== undefined ? dto.cancelAfter : this.cancelAfter; + + return this; + } +} diff --git a/backend/src/modules/inventory/products-section/enums/index.ts b/backend/src/modules/inventory/products-section/enums/index.ts new file mode 100644 index 0000000..d24604c --- /dev/null +++ b/backend/src/modules/inventory/products-section/enums/index.ts @@ -0,0 +1 @@ +export * from './products-section-type.enum'; diff --git a/backend/src/modules/inventory/products-section/enums/products-section-type.enum.ts b/backend/src/modules/inventory/products-section/enums/products-section-type.enum.ts new file mode 100644 index 0000000..74f67c5 --- /dev/null +++ b/backend/src/modules/inventory/products-section/enums/products-section-type.enum.ts @@ -0,0 +1,4 @@ +export enum ProductsSectionType { + Sale = 'sale', + Rental = 'rental', +} diff --git a/backend/src/modules/inventory/products-section/products-section.controller.ts b/backend/src/modules/inventory/products-section/products-section.controller.ts new file mode 100644 index 0000000..ef952ba --- /dev/null +++ b/backend/src/modules/inventory/products-section/products-section.controller.ts @@ -0,0 +1,85 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { UserAccess } from '@/modules/iam/common/decorators/user-access.decorator'; + +import { ProductsSectionDto, CreateProductsSectionDto, UpdateProductsSectionDto, LinkModulesDto } from './dto'; +import { ProductsSectionService } from './services/products-section.service'; + +@ApiTags('inventory/section') +@Controller('products/sections') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ProductsSectionController { + constructor(private readonly service: ProductsSectionService) {} + + @ApiCreatedResponse({ description: 'Create product section', type: ProductsSectionDto }) + @Post() + @UserAccess({ adminOnly: true }) + public async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateProductsSectionDto) { + return await this.service.create(accountId, dto); + } + + @ApiCreatedResponse({ description: 'Get all product sections', type: [ProductsSectionDto] }) + @Get() + public async getMany(@CurrentAuth() { accountId, user }: AuthData) { + return await this.service.getAllFull(accountId, user); + } + + @ApiCreatedResponse({ description: 'Get product section', type: ProductsSectionDto }) + @Get('/:sectionId') + public async getOne( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + ) { + return await this.service.getOneFull(accountId, user, sectionId); + } + + @ApiCreatedResponse({ description: 'Update product section', type: ProductsSectionDto }) + @Put('/:sectionId') + @UserAccess({ adminOnly: true }) + public async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: UpdateProductsSectionDto, + ) { + return await this.service.update(accountId, user, sectionId, dto); + } + + @ApiCreatedResponse({ description: 'Delete product section', type: Boolean }) + @Delete('/:sectionId') + @UserAccess({ adminOnly: true }) + public async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + ): Promise { + return await this.service.delete(accountId, user, sectionId); + } + + @ApiCreatedResponse({ description: 'Link products to entity types', type: Boolean }) + @Post('/:sectionId/link') + @UserAccess({ adminOnly: true }) + public async linkEntityTypes( + @CurrentAuth() { accountId }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body('entityTypeIds') entityTypeIds: number[], + ): Promise { + return await this.service.linkEntityTypes(accountId, sectionId, entityTypeIds); + } + + @ApiCreatedResponse({ description: 'Link products to entity types', type: Boolean }) + @Post('/:sectionId/links') + @UserAccess({ adminOnly: true }) + public async link( + @CurrentAuth() { accountId }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: LinkModulesDto, + ): Promise { + return await this.service.link(accountId, sectionId, dto); + } +} diff --git a/backend/src/modules/inventory/products-section/products-section.module.ts b/backend/src/modules/inventory/products-section/products-section.module.ts new file mode 100644 index 0000000..9695cc3 --- /dev/null +++ b/backend/src/modules/inventory/products-section/products-section.module.ts @@ -0,0 +1,28 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { SchedulerModule } from '@/modules/scheduler/scheduler.module'; + +import { OrderModule } from '../order/order.module'; +import { RentalOrderModule } from '../rental-order/rental-order.module'; + +import { ProductsSectionEntityType } from './entities/products-section-entity-type.entity'; +import { ProductsSection } from './entities/products-section.entity'; +import { ProductsSectionLinkerService } from './services/products-section-linker.service'; +import { ProductsSectionService } from './services/products-section.service'; +import { ProductsSectionController } from './products-section.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ProductsSection, ProductsSectionEntityType]), + IAMModule, + forwardRef(() => SchedulerModule), + OrderModule, + RentalOrderModule, + ], + controllers: [ProductsSectionController], + providers: [ProductsSectionService, ProductsSectionLinkerService], + exports: [ProductsSectionService], +}) +export class ProductsSectionModule {} diff --git a/backend/src/modules/inventory/products-section/services/products-section-linker.service.ts b/backend/src/modules/inventory/products-section/services/products-section-linker.service.ts new file mode 100644 index 0000000..ba98357 --- /dev/null +++ b/backend/src/modules/inventory/products-section/services/products-section-linker.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ProductsSectionEntityType } from '../entities'; + +@Injectable() +export class ProductsSectionLinkerService { + constructor( + @InjectRepository(ProductsSectionEntityType) + private readonly repository: Repository, + ) {} + + public async getLinkedSectionIds(accountId: number, entityTypeId: number): Promise { + const pset = await this.repository.findBy({ accountId, entityTypeId }); + + return pset.map((p) => p.sectionId); + } + + public async linkSectionWithEntityTypes( + accountId: number, + sectionId: number, + entityTypeIds: number[], + ): Promise { + await this.repository.delete({ accountId, sectionId }); + + return await this.repository.save( + entityTypeIds.map((etId) => new ProductsSectionEntityType(accountId, sectionId, etId)), + ); + } + + public async linkEntityTypeWithSections( + accountId: number, + entityTypeId: number, + sectionIds: number[], + ): Promise { + await this.repository.delete({ accountId, entityTypeId }); + + return await this.repository.save( + sectionIds.map((sid) => new ProductsSectionEntityType(accountId, sid, entityTypeId)), + ); + } + + public async ensureLinked(accountId: number, sectionId: number, entityTypeId: number): Promise { + await this.repository.save({ accountId, sectionId, entityTypeId }); + } +} diff --git a/backend/src/modules/inventory/products-section/services/products-section.service.ts b/backend/src/modules/inventory/products-section/services/products-section.service.ts new file mode 100644 index 0000000..c9dd4e0 --- /dev/null +++ b/backend/src/modules/inventory/products-section/services/products-section.service.ts @@ -0,0 +1,168 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { ScheduleService } from '@/modules/scheduler/schedule/services/schedule.service'; + +import { ProductsEventType, ProductsSectionEvent } from '../../common'; +import { OrderService } from '../../order/services/order.service'; +import { RentalOrderService } from '../../rental-order/services/rental-order.service'; + +import { CreateProductsSectionDto, UpdateProductsSectionDto, LinkModulesDto } from '../dto'; +import { ProductsSection, ProductsSectionEntityType } from '../entities'; +import { ProductsSectionLinkerService } from './products-section-linker.service'; + +@Injectable() +export class ProductsSectionService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(ProductsSection) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly linkerService: ProductsSectionLinkerService, + private readonly orderService: OrderService, + @Inject(forwardRef(() => RentalOrderService)) + private readonly rentalOrderService: RentalOrderService, + @Inject(forwardRef(() => ScheduleService)) + private readonly scheduleService: ScheduleService, + ) {} + + public async create(accountId: number, dto: CreateProductsSectionDto): Promise { + const section = await this.repository.save(ProductsSection.fromDto(accountId, dto)); + + this.eventEmitter.emit( + ProductsEventType.ProductsSectionCreated, + new ProductsSectionEvent({ accountId, sectionId: section.id }), + ); + + return section; + } + + public async getAllFull(accountId: number, user: User): Promise { + const sections = await this.repository + .createQueryBuilder('ps') + .where('ps.account_id = :accountId', { accountId }) + .leftJoinAndMapMany('ps.links', ProductsSectionEntityType, 'link', 'link.section_id = ps.id') + .orderBy('ps.created_at', 'ASC') + .getMany(); + + const allowedSections: ProductsSection[] = []; + for (const section of sections) { + if ( + await this.authService.check({ + action: 'view', + user, + authorizable: ProductsSection.getAuthorizable(section.id), + }) + ) { + section.schedulerIds = await this.scheduleService.getLinkedSchedulerIds(accountId, { + productsSectionId: section.id, + }); + allowedSections.push(section); + } + } + + return allowedSections; + } + + public async getOneFull(accountId: number, user: User, sectionId: number): Promise { + await this.authService.check({ + action: 'view', + user, + authorizable: ProductsSection.getAuthorizable(sectionId), + throwError: true, + }); + + const section = await this.repository + .createQueryBuilder('ps') + .where('ps.account_id = :accountId', { accountId }) + .andWhere('ps.id = :id', { id: sectionId }) + .leftJoinAndMapMany('ps.links', ProductsSectionEntityType, 'link', 'link.section_id = ps.id') + .getOneOrFail(); + + section.schedulerIds = await this.scheduleService.getLinkedSchedulerIds(accountId, { + productsSectionId: sectionId, + }); + + return section; + } + + private async getOne(accountId: number, sectionId: number): Promise { + return await this.repository + .createQueryBuilder('ps') + .where('ps.account_id = :accountId', { accountId }) + .andWhere('ps.id = :id', { id: sectionId }) + .getOneOrFail(); + } + + public async update( + accountId: number, + user: User, + sectionId: number, + dto: UpdateProductsSectionDto, + ): Promise { + const section = await this.getOne(accountId, sectionId); + await this.repository.save(section.update(dto)); + + return this.getOneFull(accountId, user, sectionId); + } + + public async delete(accountId: number, user: User, sectionId: number): Promise { + await this.orderService.delete(accountId, { sectionId }); + await this.rentalOrderService.delete(accountId, user, { sectionId }); + + const result = await this.repository.delete({ accountId, id: sectionId }); + + this.eventEmitter.emit( + ProductsEventType.ProductsSectionDeleted, + new ProductsSectionEvent({ accountId, sectionId }), + ); + + return result.affected > 0; + } + + public async link(accountId: number, sectionId: number, dto: LinkModulesDto): Promise { + if (dto.entityTypeIds) { + await this.linkerService.linkSectionWithEntityTypes(accountId, sectionId, dto.entityTypeIds); + } + + if (dto.schedulerIds) { + await this.scheduleService.linkProductsSection(accountId, dto.schedulerIds, sectionId); + } + + return true; + } + + public async linkEntityTypes(accountId: number, sectionId: number, entityTypeIds: number[]): Promise { + await this.linkerService.linkSectionWithEntityTypes(accountId, sectionId, entityTypeIds); + + return true; + } + + public async linkSections(accountId: number, entityTypeId: number, sectionIds: number[]): Promise { + await this.linkerService.linkEntityTypeWithSections(accountId, entityTypeId, sectionIds); + + return true; + } + + public async getLinkedSectionIds(accountId: number, user: User, entityTypeId: number): Promise { + const sectionIds = await this.linkerService.getLinkedSectionIds(accountId, entityTypeId); + + const availableSectionIds: number[] = []; + for (const sectionId of sectionIds) { + if ( + await this.authService.check({ action: 'view', user, authorizable: ProductsSection.getAuthorizable(sectionId) }) + ) { + availableSectionIds.push(sectionId); + } + } + return availableSectionIds; + } + + public async ensureLinked(accountId: number, sectionId: number, entityTypeId: number): Promise { + await this.linkerService.ensureLinked(accountId, sectionId, entityTypeId); + } +} diff --git a/backend/src/modules/inventory/rental-interval/dto/rental-interval.dto.ts b/backend/src/modules/inventory/rental-interval/dto/rental-interval.dto.ts new file mode 100644 index 0000000..eb7c052 --- /dev/null +++ b/backend/src/modules/inventory/rental-interval/dto/rental-interval.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; + +import { RentalIntervalType } from '../entities/rental-interval-type.enum'; + +export class RentalIntervalDto { + @ApiProperty({ enum: RentalIntervalType }) + @IsEnum(RentalIntervalType) + type: RentalIntervalType; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + startTime: string | null; + + constructor(type: RentalIntervalType, startTime: string | null) { + this.type = type; + this.startTime = startTime; + } +} diff --git a/backend/src/modules/inventory/rental-interval/entities/rental-interval-type.enum.ts b/backend/src/modules/inventory/rental-interval/entities/rental-interval-type.enum.ts new file mode 100644 index 0000000..5a194af --- /dev/null +++ b/backend/src/modules/inventory/rental-interval/entities/rental-interval-type.enum.ts @@ -0,0 +1,3 @@ +export enum RentalIntervalType { + DAY = 'day', +} diff --git a/backend/src/modules/inventory/rental-interval/entities/rental-interval.entity.ts b/backend/src/modules/inventory/rental-interval/entities/rental-interval.entity.ts new file mode 100644 index 0000000..896b489 --- /dev/null +++ b/backend/src/modules/inventory/rental-interval/entities/rental-interval.entity.ts @@ -0,0 +1,44 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { RentalIntervalType } from './rental-interval-type.enum'; +import { RentalIntervalDto } from '../dto/rental-interval.dto'; + +@Entity() +export class RentalInterval { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column() + type: RentalIntervalType; + + @Column({ type: 'time', nullable: true }) + startTime: string | null; + + @Column() + accountId: number; + + constructor(accountId: number, sectionId: number, type: RentalIntervalType, startTime: string | null) { + this.accountId = accountId; + this.sectionId = sectionId; + this.type = type; + this.startTime = startTime; + } + + public static create(accountId: number, sectionId: number, dto: RentalIntervalDto): RentalInterval { + return new RentalInterval(accountId, sectionId, dto.type, dto.startTime); + } + + public update(dto: RentalIntervalDto): RentalInterval { + this.type = dto.type ?? this.type; + this.startTime = dto.startTime ?? this.startTime; + + return this; + } + + public toDto(): RentalIntervalDto { + return new RentalIntervalDto(this.type, this.startTime?.substring(0, 5) ?? null); + } +} diff --git a/backend/src/modules/inventory/rental-interval/rental-interval.controller.ts b/backend/src/modules/inventory/rental-interval/rental-interval.controller.ts new file mode 100644 index 0000000..7f5dc13 --- /dev/null +++ b/backend/src/modules/inventory/rental-interval/rental-interval.controller.ts @@ -0,0 +1,39 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { RentalIntervalService } from './rental-interval.service'; +import { RentalInterval } from './entities/rental-interval.entity'; +import { RentalIntervalDto } from './dto/rental-interval.dto'; + +@ApiTags('inventory/rental/interval') +@Controller('rental/sections/:sectionId/interval') +@JwtAuthorized() +@TransformToDto() +export class RentalIntervalController { + constructor(private readonly service: RentalIntervalService) {} + + @ApiCreatedResponse({ description: 'Set product rental interval', type: RentalIntervalDto }) + @Post() + public async setRentalInterval( + @CurrentAuth() { accountId }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: RentalIntervalDto, + ): Promise { + return await this.service.setRentalInterval(accountId, sectionId, dto); + } + + @ApiCreatedResponse({ description: 'Get product section rental interval', type: RentalIntervalDto }) + @Get() + public async findRentalInterval( + @CurrentAuth() { accountId }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + ): Promise { + return await this.service.findRentalInterval(accountId, sectionId); + } +} diff --git a/backend/src/modules/inventory/rental-interval/rental-interval.module.ts b/backend/src/modules/inventory/rental-interval/rental-interval.module.ts new file mode 100644 index 0000000..b92dd69 --- /dev/null +++ b/backend/src/modules/inventory/rental-interval/rental-interval.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { RentalIntervalService } from './rental-interval.service'; +import { RentalIntervalController } from './rental-interval.controller'; +import { RentalInterval } from './entities/rental-interval.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([RentalInterval]), IAMModule], + controllers: [RentalIntervalController], + providers: [RentalIntervalService], + exports: [RentalIntervalService], +}) +export class RentalIntervalModule {} diff --git a/backend/src/modules/inventory/rental-interval/rental-interval.service.ts b/backend/src/modules/inventory/rental-interval/rental-interval.service.ts new file mode 100644 index 0000000..4ea67bc --- /dev/null +++ b/backend/src/modules/inventory/rental-interval/rental-interval.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { RentalInterval } from './entities/rental-interval.entity'; +import { RentalIntervalDto } from './dto/rental-interval.dto'; + +@Injectable() +export class RentalIntervalService { + constructor( + @InjectRepository(RentalInterval) + private readonly repository: Repository, + ) {} + + public async findRentalInterval(accountId: number, sectionId: number): Promise { + return await this.repository.findOneBy({ accountId, sectionId }); + } + + public async setRentalInterval( + accountId: number, + sectionId: number, + dto: RentalIntervalDto, + ): Promise { + const interval = await this.findRentalInterval(accountId, sectionId); + + return interval + ? await this.repository.save(interval.update(dto)) + : await this.repository.save(RentalInterval.create(accountId, sectionId, dto)); + } +} diff --git a/backend/src/modules/inventory/rental-order/dto/create-rental-order-item.dto.ts b/backend/src/modules/inventory/rental-order/dto/create-rental-order-item.dto.ts new file mode 100644 index 0000000..e4da836 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/create-rental-order-item.dto.ts @@ -0,0 +1,5 @@ +import { OmitType } from '@nestjs/swagger'; + +import { RentalOrderItemDto } from './rental-order-item.dto'; + +export class CreateRentalOrderItemDto extends OmitType(RentalOrderItemDto, ['id'] as const) {} diff --git a/backend/src/modules/inventory/rental-order/dto/create-rental-order.dto.ts b/backend/src/modules/inventory/rental-order/dto/create-rental-order.dto.ts new file mode 100644 index 0000000..102e334 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/create-rental-order.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty, OmitType } from '@nestjs/swagger'; +import { IsArray, IsNumber } from 'class-validator'; + +import { RentalOrderDto } from './rental-order.dto'; +import { CreateRentalOrderItemDto } from './create-rental-order-item.dto'; + +export class CreateRentalOrderDto extends OmitType(RentalOrderDto, [ + 'id', + 'sectionId', + 'orderNumber', + 'createdBy', + 'createdAt', + 'items', + 'entityInfo', +] as const) { + @ApiProperty() + @IsNumber() + entityId: number; + + @ApiProperty({ type: [CreateRentalOrderItemDto] }) + @IsArray() + items: CreateRentalOrderItemDto[]; +} diff --git a/backend/src/modules/inventory/rental-order/dto/index.ts b/backend/src/modules/inventory/rental-order/dto/index.ts new file mode 100644 index 0000000..68a0d3e --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/index.ts @@ -0,0 +1,7 @@ +export * from './create-rental-order-item.dto'; +export * from './create-rental-order.dto'; +export * from './rental-order-filter'; +export * from './rental-order-item.dto'; +export * from './rental-order.dto'; +export * from './update-rental-order-item.dto'; +export * from './update-rental-order.dto'; diff --git a/backend/src/modules/inventory/rental-order/dto/rental-order-filter.ts b/backend/src/modules/inventory/rental-order/dto/rental-order-filter.ts new file mode 100644 index 0000000..975ce42 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/rental-order-filter.ts @@ -0,0 +1,16 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional } from 'class-validator'; + +import { RentalOrderStatus } from '../enums'; + +export class RentalOrderFilter { + @ApiPropertyOptional({ type: Number, nullable: true }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiPropertyOptional({ enum: RentalOrderStatus, isArray: true, nullable: true }) + @IsOptional() + @IsArray() + statuses?: RentalOrderStatus[] | null; +} diff --git a/backend/src/modules/inventory/rental-order/dto/rental-order-item.dto.ts b/backend/src/modules/inventory/rental-order/dto/rental-order-item.dto.ts new file mode 100644 index 0000000..59237c6 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/rental-order-item.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class RentalOrderItemDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + productId: number; + + @ApiProperty() + @IsNumber() + unitPrice: number; + + @ApiProperty() + @IsNumber() + tax: number; + + @ApiProperty() + @IsNumber() + discount: number; + + @ApiProperty() + @IsNumber() + sortOrder: number; + + constructor(id: number, productId: number, unitPrice: number, tax: number, discount: number, sortOrder: number) { + this.id = id; + this.productId = productId; + this.unitPrice = unitPrice; + this.tax = tax; + this.discount = discount; + this.sortOrder = sortOrder; + } +} diff --git a/backend/src/modules/inventory/rental-order/dto/rental-order.dto.ts b/backend/src/modules/inventory/rental-order/dto/rental-order.dto.ts new file mode 100644 index 0000000..b3e6566 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/rental-order.dto.ts @@ -0,0 +1,88 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { Currency, DatePeriodDto } from '@/common'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { RentalOrderStatus } from '../enums'; +import { RentalOrderItemDto } from './rental-order-item.dto'; + +export class RentalOrderDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + sectionId: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + warehouseId: number | null; + + @ApiProperty() + @IsNumber() + orderNumber: number; + + @ApiProperty() + @IsNumber() + createdBy: number; + + @ApiProperty({ enum: Currency }) + @IsEnum(Currency) + currency: Currency; + + @ApiProperty() + @IsBoolean() + taxIncluded: boolean; + + @ApiProperty({ enum: RentalOrderStatus }) + @IsEnum(RentalOrderStatus) + status: RentalOrderStatus; + + @ApiProperty() + @IsString() + createdAt: string; + + @ApiProperty({ type: [DatePeriodDto] }) + @IsArray() + periods: DatePeriodDto[]; + + @ApiProperty({ type: [RentalOrderItemDto] }) + @IsArray() + items: RentalOrderItemDto[]; + + @ApiProperty({ type: EntityInfoDto }) + @IsNotEmpty() + entityInfo: EntityInfoDto; + + constructor( + id: number, + sectionId: number, + warehouseId: number | null, + orderNumber: number, + createdBy: number, + currency: Currency, + taxIncluded: boolean, + status: RentalOrderStatus, + createdAt: string, + periods: DatePeriodDto[], + items: RentalOrderItemDto[], + entityInfo: EntityInfoDto, + ) { + this.id = id; + this.sectionId = sectionId; + this.warehouseId = warehouseId; + this.orderNumber = orderNumber; + this.createdBy = createdBy; + this.currency = currency; + this.taxIncluded = taxIncluded; + this.status = status; + this.createdAt = createdAt; + this.periods = periods; + this.items = items; + this.entityInfo = entityInfo; + } +} diff --git a/backend/src/modules/inventory/rental-order/dto/update-rental-order-item.dto.ts b/backend/src/modules/inventory/rental-order/dto/update-rental-order-item.dto.ts new file mode 100644 index 0000000..01fe511 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/update-rental-order-item.dto.ts @@ -0,0 +1,11 @@ +import { ApiPropertyOptional, OmitType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { RentalOrderItemDto } from './rental-order-item.dto'; + +export class UpdateRentalOrderItemDto extends OmitType(RentalOrderItemDto, ['id'] as const) { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + id?: number; +} diff --git a/backend/src/modules/inventory/rental-order/dto/update-rental-order.dto.ts b/backend/src/modules/inventory/rental-order/dto/update-rental-order.dto.ts new file mode 100644 index 0000000..8640901 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/dto/update-rental-order.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty, OmitType } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { RentalOrderDto } from './rental-order.dto'; +import { UpdateRentalOrderItemDto } from './update-rental-order-item.dto'; + +export class UpdateRentalOrderDto extends OmitType(RentalOrderDto, [ + 'id', + 'sectionId', + 'orderNumber', + 'createdBy', + 'createdAt', + 'items', + 'entityInfo', +] as const) { + @ApiProperty({ type: [UpdateRentalOrderItemDto] }) + @IsArray() + items: UpdateRentalOrderItemDto[]; +} diff --git a/backend/src/modules/inventory/rental-order/entities/index.ts b/backend/src/modules/inventory/rental-order/entities/index.ts new file mode 100644 index 0000000..8b34ff1 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/entities/index.ts @@ -0,0 +1,3 @@ +export * from './rental-order-item.entity'; +export * from './rental-order-period.entity'; +export * from './rental-order.entity'; diff --git a/backend/src/modules/inventory/rental-order/entities/rental-order-item.entity.ts b/backend/src/modules/inventory/rental-order/entities/rental-order-item.entity.ts new file mode 100644 index 0000000..0313d51 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/entities/rental-order-item.entity.ts @@ -0,0 +1,90 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { CreateRentalOrderItemDto, RentalOrderItemDto } from '../dto'; + +@Entity() +export class RentalOrderItem { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + orderId: number; + + @Column() + productId: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + unitPrice: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + tax: number; + + @Column({ + type: 'numeric', + transformer: { + to: (value: number) => value, + from: (value: unknown) => Number(value), + }, + }) + discount: number; + + @Column() + sortOrder: number; + + @Column() + accountId: number; + + constructor( + accountId: number, + orderId: number, + productId: number, + unitPrice: number, + tax: number, + discount: number, + sortOrder: number, + id?: number, + ) { + this.id = id; + this.accountId = accountId; + this.orderId = orderId; + this.productId = productId; + this.unitPrice = unitPrice; + this.tax = tax; + this.discount = discount; + this.sortOrder = sortOrder; + } + + public static fromDto( + accountId: number, + orderId: number, + dto: CreateRentalOrderItemDto, + id?: number, + ): RentalOrderItem { + return new RentalOrderItem( + accountId, + orderId, + dto.productId, + dto.unitPrice, + dto.tax, + dto.discount, + dto.sortOrder, + id, + ); + } + + public toDto(): RentalOrderItemDto { + return new RentalOrderItemDto(this.id, this.productId, this.unitPrice, this.tax, this.discount, this.sortOrder); + } +} diff --git a/backend/src/modules/inventory/rental-order/entities/rental-order-period.entity.ts b/backend/src/modules/inventory/rental-order/entities/rental-order-period.entity.ts new file mode 100644 index 0000000..4e4d943 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/entities/rental-order-period.entity.ts @@ -0,0 +1,36 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DatePeriodDto } from '@/common'; + +@Entity() +export class RentalOrderPeriod { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + orderId: number; + + @Column() + startDate: Date; + + @Column() + endDate: Date; + + @Column() + accountId: number; + + constructor(accountId: number, orderId: number, startDate: Date, endDate: Date) { + this.accountId = accountId; + this.orderId = orderId; + this.startDate = startDate; + this.endDate = endDate; + } + + public static fromDto(accountId: number, orderId: number, dto: DatePeriodDto): RentalOrderPeriod { + return new RentalOrderPeriod(accountId, orderId, new Date(dto.startDate), new Date(dto.endDate)); + } + + public toDto(): DatePeriodDto { + return { startDate: this.startDate.toISOString(), endDate: this.endDate.toISOString() }; + } +} diff --git a/backend/src/modules/inventory/rental-order/entities/rental-order.entity.ts b/backend/src/modules/inventory/rental-order/entities/rental-order.entity.ts new file mode 100644 index 0000000..dd27452 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/entities/rental-order.entity.ts @@ -0,0 +1,155 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { Currency, DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { PermissionObjectType } from '../../common'; + +import { CreateRentalOrderDto, RentalOrderDto, UpdateRentalOrderDto } from '../dto'; +import { RentalOrderStatus } from '../enums'; +import { RentalOrderItem } from './rental-order-item.entity'; +import { RentalOrderPeriod } from './rental-order-period.entity'; + +@Entity() +export class RentalOrder implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column({ nullable: true }) + warehouseId: number | null; + + @Column() + entityId: number; + + @Column() + createdBy: number; + + @Column() + currency: Currency; + + @Column() + taxIncluded: boolean; + + @Column() + status: RentalOrderStatus; + + @Column() + orderNumber: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + private _periods: RentalOrderPeriod[] = []; + private _items: RentalOrderItem[] = []; + private _entityInfo: EntityInfoDto = null; + + constructor( + accountId: number, + sectionId: number, + warehouseId: number | null, + createdBy: number, + currency: Currency, + taxIncluded: boolean, + entityId: number, + status: RentalOrderStatus, + orderNumber: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.sectionId = sectionId; + this.warehouseId = warehouseId; + this.entityId = entityId; + this.createdBy = createdBy; + this.currency = currency; + this.taxIncluded = taxIncluded; + this.status = status; + this.orderNumber = orderNumber; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public get periods(): RentalOrderPeriod[] { + return this._periods; + } + public set periods(periods: RentalOrderPeriod[]) { + this._periods = periods; + } + + public get items(): RentalOrderItem[] { + return this._items; + } + public set items(items: RentalOrderItem[]) { + this._items = items; + } + + public get entityInfo(): EntityInfoDto { + return this._entityInfo; + } + public set entityInfo(value: EntityInfoDto) { + this._entityInfo = value; + } + + static getAuthorizable(sectionId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.ProductsOrder, id: sectionId }); + } + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.ProductsOrder, + id: this.sectionId, + createdBy: this.createdBy, + }; + } + + public static fromDto( + accountId: number, + sectionId: number, + orderNumber: number, + createdBy: number, + dto: CreateRentalOrderDto, + ): RentalOrder { + return new RentalOrder( + accountId, + sectionId, + dto.warehouseId, + createdBy, + dto.currency, + dto.taxIncluded, + dto.entityId, + dto.status, + orderNumber, + ); + } + + public toDto(): RentalOrderDto { + return new RentalOrderDto( + this.id, + this.sectionId, + this.warehouseId, + this.orderNumber, + this.createdBy, + this.currency, + this.taxIncluded, + this.status, + this.createdAt.toISOString(), + this._periods?.map((period) => period.toDto()) ?? [], + this._items?.map((item) => item.toDto()) ?? [], + this._entityInfo, + ); + } + + public update(dto: UpdateRentalOrderDto): RentalOrder { + this.warehouseId = dto.warehouseId; + this.status = dto.status; + this.currency = dto.currency; + this.taxIncluded = dto.taxIncluded; + + return this; + } +} diff --git a/backend/src/modules/inventory/rental-order/enums/index.ts b/backend/src/modules/inventory/rental-order/enums/index.ts new file mode 100644 index 0000000..e172053 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/enums/index.ts @@ -0,0 +1 @@ +export * from './rental-order-status.enum'; diff --git a/backend/src/modules/inventory/rental-order/enums/rental-order-status.enum.ts b/backend/src/modules/inventory/rental-order/enums/rental-order-status.enum.ts new file mode 100644 index 0000000..2bcdcaf --- /dev/null +++ b/backend/src/modules/inventory/rental-order/enums/rental-order-status.enum.ts @@ -0,0 +1,10 @@ +export enum RentalOrderStatus { + Formed = 'formed', + Reserved = 'reserved', + SentToWarehouse = 'sent_to_warehouse', + Shipped = 'shipped', + Delivered = 'delivered', + Returned = 'returned', + AcceptedToWarehouse = 'accepted_to_warehouse', + Cancelled = 'cancelled', +} diff --git a/backend/src/modules/inventory/rental-order/rental-order.controller.ts b/backend/src/modules/inventory/rental-order/rental-order.controller.ts new file mode 100644 index 0000000..e3b45ee --- /dev/null +++ b/backend/src/modules/inventory/rental-order/rental-order.controller.ts @@ -0,0 +1,93 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { RentalOrderDto, CreateRentalOrderDto, RentalOrderFilter, UpdateRentalOrderDto } from './dto'; +import { RentalOrder } from './entities'; +import { RentalOrderStatus } from './enums'; +import { RentalOrderService } from './services'; + +@ApiTags('inventory/rental/orders') +@Controller('rental/sections/:sectionId/orders') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class RentalOrderController { + constructor(private readonly service: RentalOrderService) {} + + @ApiCreatedResponse({ description: 'Create rental order', type: RentalOrderDto }) + @Post() + public async create( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: CreateRentalOrderDto, + ): Promise { + return await this.service.create(accountId, user, sectionId, dto); + } + + @ApiCreatedResponse({ description: 'Get rental order', type: RentalOrderDto }) + @Get('/:orderId') + public async getOne( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('orderId', ParseIntPipe) orderId: number, + ): Promise { + return this.service.getOne(accountId, user, sectionId, orderId); + } + + @ApiCreatedResponse({ description: 'Get entity rental orders', type: [RentalOrderDto] }) + @Post('/search') + public async getMany( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() filter?: RentalOrderFilter, + ): Promise { + return this.service.findMany(accountId, user, { ...filter, sectionId }); + } + + @ApiCreatedResponse({ description: 'Get entity rental orders', type: [RentalOrderDto] }) + @Get('/entity/:entityId') + public async getForEntity( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('entityId', ParseIntPipe) entityId: number, + ): Promise { + return this.service.findMany(accountId, user, { sectionId, entityId }); + } + + @ApiCreatedResponse({ description: 'Update rental order', type: RentalOrderDto }) + @Put('/:orderId') + public async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('orderId', ParseIntPipe) orderId: number, + @Body() dto: UpdateRentalOrderDto, + ): Promise { + return this.service.update(accountId, user, sectionId, orderId, dto); + } + + @ApiOkResponse({ description: 'Delete rental order' }) + @Delete('/:orderId') + public async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('orderId', ParseIntPipe) orderId: number, + ): Promise { + return this.service.delete(accountId, user, { sectionId, orderId }, { checkPermission: true }); + } + + @ApiCreatedResponse({ description: 'Change rental order status', type: RentalOrderDto }) + @Put('/:orderId/status/:status') + public async changeStatus( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('orderId', ParseIntPipe) orderId: number, + @Param('status') status: RentalOrderStatus, + ): Promise { + return this.service.changeStatus(accountId, user, sectionId, orderId, status); + } +} diff --git a/backend/src/modules/inventory/rental-order/rental-order.module.ts b/backend/src/modules/inventory/rental-order/rental-order.module.ts new file mode 100644 index 0000000..86cd6df --- /dev/null +++ b/backend/src/modules/inventory/rental-order/rental-order.module.ts @@ -0,0 +1,29 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; + +import { RentalScheduleModule } from '../rental-schedule/rental-schedule.module'; +import { WarehouseModule } from '../warehouse/warehouse.module'; + +import { RentalOrder } from './entities/rental-order.entity'; +import { RentalOrderItem } from './entities/rental-order-item.entity'; +import { RentalOrderPeriod } from './entities/rental-order-period.entity'; +import { RentalOrderController } from './rental-order.controller'; +import { RentalOrderService } from './services/rental-order.service'; +import { RentalOrderItemService } from './services/rental-order-item.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([RentalOrder, RentalOrderItem, RentalOrderPeriod]), + IAMModule, + EntityInfoModule, + forwardRef(() => WarehouseModule), + forwardRef(() => RentalScheduleModule), + ], + controllers: [RentalOrderController], + providers: [RentalOrderItemService, RentalOrderService], + exports: [RentalOrderService], +}) +export class RentalOrderModule {} diff --git a/backend/src/modules/inventory/rental-order/services/index.ts b/backend/src/modules/inventory/rental-order/services/index.ts new file mode 100644 index 0000000..d213c50 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/services/index.ts @@ -0,0 +1,2 @@ +export * from './rental-order-item.service'; +export * from './rental-order.service'; diff --git a/backend/src/modules/inventory/rental-order/services/rental-order-item.service.ts b/backend/src/modules/inventory/rental-order/services/rental-order-item.service.ts new file mode 100644 index 0000000..98833e5 --- /dev/null +++ b/backend/src/modules/inventory/rental-order/services/rental-order-item.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { CreateRentalOrderItemDto, UpdateRentalOrderItemDto } from '../dto'; +import { RentalOrderItem } from '../entities'; + +@Injectable() +export class RentalOrderItemService { + constructor( + @InjectRepository(RentalOrderItem) + private readonly repository: Repository, + ) {} + + public async create({ + accountId, + orderId, + dto, + }: { + accountId: number; + orderId: number; + dto: CreateRentalOrderItemDto; + }): Promise { + return this.repository.save(RentalOrderItem.fromDto(accountId, orderId, dto)); + } + public async createMany({ + accountId, + orderId, + dtos, + }: { + accountId: number; + orderId: number; + dtos: CreateRentalOrderItemDto[]; + }): Promise { + return Promise.all(dtos.map((dto) => this.create({ accountId, orderId, dto }))); + } + + public async update({ + accountId, + orderId, + dto, + }: { + accountId: number; + orderId: number; + dto: UpdateRentalOrderItemDto; + }): Promise { + return this.repository.save(RentalOrderItem.fromDto(accountId, orderId, dto, dto.id)); + } + + public async updateMany({ + accountId, + orderId, + dtos, + }: { + accountId: number; + orderId: number; + dtos: UpdateRentalOrderItemDto[]; + }): Promise { + return Promise.all(dtos.map((dto) => this.update({ accountId, orderId, dto }))); + } + + public async processBatch({ + accountId, + orderId, + items, + dtos, + }: { + accountId: number; + orderId: number; + items: RentalOrderItem[]; + dtos: UpdateRentalOrderItemDto[]; + }): Promise { + const added = dtos.filter((dto) => !items.some((item) => item.id === dto.id)); + const updated = dtos.filter((dto) => items.some((item) => item.id === dto.id)); + const removed = items.filter((item) => !dtos.some((dto) => dto.id === item.id)); + + await this.deleteMany({ accountId, orderId, ids: removed.map((i) => i.id) }); + + const result: RentalOrderItem[] = []; + result.push(...(await this.createMany({ accountId, orderId, dtos: added }))); + result.push(...(await this.updateMany({ accountId, orderId, dtos: updated }))); + return result; + } + + public async deleteMany({ + accountId, + orderId, + ids, + }: { + accountId: number; + orderId: number; + ids: number[]; + }): Promise { + await this.repository.delete({ accountId, orderId, id: In(ids) }); + } +} diff --git a/backend/src/modules/inventory/rental-order/services/rental-order.service.ts b/backend/src/modules/inventory/rental-order/services/rental-order.service.ts new file mode 100644 index 0000000..8afb0ff --- /dev/null +++ b/backend/src/modules/inventory/rental-order/services/rental-order.service.ts @@ -0,0 +1,342 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Brackets, Repository } from 'typeorm'; +import Decimal from 'decimal.js'; + +import { DatePeriodDto, DateUtil, NotFoundError } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; +import { CrmEventType, EntityPriceUpdateEvent } from '@/CRM/common'; + +import { ProductsEventType, RentalOrderCreatedEvent, RentalOrderEvent } from '../../common'; +import { RentalScheduleService } from '../../rental-schedule/rental-schedule.service'; +import { WarehouseService } from '../../warehouse/warehouse.service'; + +import { CreateRentalOrderDto, UpdateRentalOrderDto } from '../dto'; +import { RentalOrder, RentalOrderItem, RentalOrderPeriod } from '../entities'; +import { RentalOrderStatus } from '../enums'; +import { RentalOrderItemService } from './rental-order-item.service'; + +interface FindFilter { + sectionId?: number; + warehouseId?: number | number[]; + withoutWarehouse?: boolean; + orderId?: number | number[]; + entityId?: number | null; + statuses?: RentalOrderStatus[] | null; +} +interface DeleteOptions { + newWarehouseId?: number; + checkPermission?: boolean; +} + +@Injectable() +export class RentalOrderService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(RentalOrder) + private readonly repository: Repository, + @InjectRepository(RentalOrderPeriod) + private readonly repositoryPeriod: Repository, + private readonly authService: AuthorizationService, + private readonly itemsService: RentalOrderItemService, + @Inject(forwardRef(() => RentalScheduleService)) + private readonly scheduleService: RentalScheduleService, + private readonly entityInfoService: EntityInfoService, + @Inject(forwardRef(() => WarehouseService)) + private readonly warehouseService: WarehouseService, + ) {} + + public async create( + accountId: number, + user: User, + sectionId: number, + dto: CreateRentalOrderDto, + ): Promise { + await this.authService.check({ + action: 'create', + user, + authorizable: RentalOrder.getAuthorizable(sectionId), + throwError: true, + }); + + const orderNumber = await this.getOrderNumber(sectionId, dto.entityId); + const order = await this.repository.save(RentalOrder.fromDto(accountId, sectionId, orderNumber, user.id, dto)); + this.eventEmitter.emit( + ProductsEventType.RentalOrderCreated, + new RentalOrderCreatedEvent({ + accountId, + entityId: order.entityId, + rentalOrderId: order.id, + createdAt: order.createdAt.toISOString(), + }), + ); + + if (dto.periods) { + order.periods = await this.setRentalPeriods({ accountId, orderId: order.id, dtos: dto.periods }); + } + if (dto.items) { + order.items = await this.itemsService.createMany({ accountId, orderId: order.id, dtos: dto.items }); + } + + await this.scheduleService.processOrder(accountId, order); + order.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: order.entityId }); + + this.updateEntityPrice({ accountId, entityId: order.entityId }); + + return order; + } + + public async getOne(accountId: number, user: User, sectionId: number | null, orderId: number): Promise { + const order = await this.findOne(accountId, user, { sectionId, orderId }); + if (!order) { + throw NotFoundError.withId(RentalOrder, orderId); + } + await this.authService.check({ action: 'view', user, authorizable: order, throwError: true }); + + return order; + } + + public async findOne(accountId: number, user: User | null, filter?: FindFilter): Promise { + if (user) { + const warehouses = await this.warehouseService.findMany({ + user, + filter: { accountId, sectionId: filter.sectionId, warehouseId: filter.warehouseId, onlyAvailable: true }, + }); + filter.withoutWarehouse = !filter.warehouseId; + filter.warehouseId = warehouses?.length ? warehouses.map((w) => w.id) : undefined; + } + const order = await this.createQb(accountId, filter).getOne(); + if (user) { + order.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: order.entityId }); + } + + return order; + } + + public async findMany(accountId: number, user: User | null, filter: FindFilter): Promise { + if (user && filter.sectionId) { + await this.authService.check({ + action: 'view', + user, + authorizable: RentalOrder.getAuthorizable(filter.sectionId), + throwError: true, + }); + } + + if (user) { + const warehouses = await this.warehouseService.findMany({ + user, + filter: { accountId, sectionId: filter.sectionId, warehouseId: filter.warehouseId, onlyAvailable: true }, + }); + filter.withoutWarehouse = !filter.warehouseId; + filter.warehouseId = warehouses?.length ? warehouses.map((w) => w.id) : undefined; + } + + const orders = await this.createQb(accountId, filter).orderBy('rental_order.created_at', 'DESC').getMany(); + if (user) { + for (const order of orders) { + order.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: order.entityId }); + } + } + + return orders; + } + + public async update( + accountId: number, + user: User, + sectionId: number, + orderId: number, + dto: UpdateRentalOrderDto, + ): Promise { + const order = await this.findOne(accountId, user, { sectionId, orderId }); + if (!order) { + throw NotFoundError.withId(RentalOrder, orderId); + } + await this.authService.check({ action: 'edit', user, authorizable: order, throwError: true }); + + await this.repository.save(order.update(dto)); + + if (dto.periods) { + order.periods = await this.setRentalPeriods({ accountId, orderId: order.id, dtos: dto.periods }); + } + if (dto.items) { + order.items = await this.itemsService.processBatch({ + accountId, + orderId: order.id, + items: order.items, + dtos: dto.items, + }); + } + + await this.scheduleService.processOrder(accountId, order); + order.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: order.entityId }); + + this.updateEntityPrice({ accountId, entityId: order.entityId }); + + return order; + } + + public async changeStatus( + accountId: number, + user: User, + sectionId: number, + orderId: number, + status: RentalOrderStatus, + ): Promise { + const order = await this.findOne(accountId, user, { sectionId, orderId }); + if (!order) { + throw NotFoundError.withId(RentalOrder, orderId); + } + + await this.authService.check({ action: 'edit', user, authorizable: order, throwError: true }); + + if (status && order.status !== status) { + order.status = status; + await this.repository.save(order); + await this.scheduleService.processOrder(accountId, order); + } + + this.updateEntityPrice({ accountId, entityId: order.entityId }); + + return order; + } + + public async delete(accountId: number, user: User, filter: FindFilter, options?: DeleteOptions) { + if (options?.checkPermission && filter.sectionId) { + await this.authService.check({ + action: 'delete', + user, + authorizable: RentalOrder.getAuthorizable(filter.sectionId), + throwError: true, + }); + } + const qb = this.createQb(accountId, filter); + if (options?.newWarehouseId) { + await qb.update({ warehouseId: options.newWarehouseId }).execute(); + } else { + const orders = await qb.clone().getMany(); + await qb.clone().delete().execute(); + for (const order of orders) { + this.updateEntityPrice({ accountId, entityId: order.entityId }); + this.eventEmitter.emit( + ProductsEventType.RentalOrderDeleted, + new RentalOrderEvent({ accountId: order.accountId, entityId: order.entityId, rentalOrderId: order.id }), + ); + } + } + } + + public async getEntityIdsByOrderItemIds( + orderItemIds: number[], + ): Promise<{ orderItemId: number; entityId: number }[]> { + const result = await this.repository + .createQueryBuilder('rental_order') + .select('item.id', 'itemId') + .addSelect('rental_order.entity_id', 'entityId') + .leftJoin(RentalOrderItem, 'item', 'item.order_id = rental_order.id') + .where('item.id IN (:...orderItemIds)', { orderItemIds }) + .getRawMany(); + + return result.map((r) => ({ orderItemId: Number(r.itemId), entityId: Number(r.entityId) })); + } + + private createQb(accountId: number, filter?: FindFilter) { + const qb = this.repository + .createQueryBuilder('rental_order') + .where('rental_order.account_id = :accountId', { accountId: accountId }) + .leftJoinAndMapMany('rental_order.items', RentalOrderItem, 'item', 'rental_order.id = item.order_id') + .leftJoinAndMapMany('rental_order.periods', RentalOrderPeriod, 'period', 'rental_order.id = period.order_id'); + + if (filter?.sectionId) { + qb.andWhere('rental_order.section_id = :sectionId', { sectionId: filter.sectionId }); + } + if (filter.warehouseId) { + qb.andWhere( + new Brackets((qbW) => { + if (Array.isArray(filter.warehouseId)) { + qbW.andWhere('rental_order.warehouse_id IN (:...warehouseIds)', { warehouseIds: filter.warehouseId }); + } else { + qbW.andWhere('rental_order.warehouse_id = :warehouseId', { warehouseId: filter.warehouseId }); + } + if (filter.withoutWarehouse) { + qbW.orWhere('rental_order.warehouse_id IS NULL'); + } + }), + ); + } + if (filter?.orderId) { + qb.andWhere('rental_order.id IN (:...orderIds)', { + orderIds: Array.isArray(filter.orderId) ? filter.orderId : [filter.orderId], + }); + } + if (filter?.entityId) { + qb.andWhere('rental_order.entity_id = :entityId', { entityId: filter.entityId }); + } + if (filter?.statuses && filter.statuses.length > 0) { + qb.andWhere('rental_order.status IN (:...statuses)', { statuses: filter.statuses }); + } + + return qb; + } + + private async getOrderNumber(sectionId: number, entityId: number): Promise { + const result = await this.repository + .createQueryBuilder('ro') + .select('MAX(ro.order_number)', 'order_number') + .where('ro.entity_id = :entityId', { entityId }) + .andWhere('ro.section_id = :sectionId', { sectionId }) + .getRawOne(); + + return Number(result?.order_number ?? 0) + 1; + } + + private async updateEntityPrice({ accountId, entityId }: { accountId: number; entityId: number }) { + const orders = await this.findMany(accountId, null, { entityId }); + + const price = orders + .filter((order) => order.status !== RentalOrderStatus.Cancelled) + .reduce((sum, order) => sum + this.calculateTotalAmount(order), 0); + + this.eventEmitter.emit(CrmEventType.EntityPriceUpdate, new EntityPriceUpdateEvent({ accountId, entityId, price })); + } + private calculateTotalAmount({ items, periods, taxIncluded }: RentalOrder): number { + const amount = items.reduce((total, item) => { + return total.plus(this.calculateItemAmount({ item, taxIncluded })); + }, new Decimal(0)); + + return amount.mul(this.calculateRentalLength(periods)).toNumber(); + } + private calculateItemAmount({ item, taxIncluded }: { item: RentalOrderItem; taxIncluded: boolean }): number { + let amount = new Decimal(item.unitPrice); + if (item.discount) { + amount = amount.sub(amount.mul(new Decimal(item.discount).div(100))); + } + + return taxIncluded ? amount.toNumber() : amount.add(amount.mul(new Decimal(item.tax).div(100))).toNumber(); + } + private calculateRentalLength(periods: RentalOrderPeriod[]): number { + return periods.reduce( + (length, { startDate, endDate }) => length + DateUtil.diff({ startDate, endDate, unit: 'day' }), + 1, + ); + } + + private async setRentalPeriods({ + accountId, + orderId, + dtos, + }: { + accountId: number; + orderId: number; + dtos: DatePeriodDto[]; + }): Promise { + await this.repositoryPeriod.delete({ accountId, orderId }); + + return await this.repositoryPeriod.save(dtos.map((dto) => RentalOrderPeriod.fromDto(accountId, orderId, dto))); + } +} diff --git a/backend/src/modules/inventory/rental-schedule/dto/check-rental-status.dto.ts b/backend/src/modules/inventory/rental-schedule/dto/check-rental-status.dto.ts new file mode 100644 index 0000000..f2b08de --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/dto/check-rental-status.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { DatePeriodDto } from '@/common'; + +export class CheckRentalStatusDto { + @ApiProperty({ type: [Number] }) + @IsArray() + productIds: number[]; + + @ApiProperty({ type: [DatePeriodDto] }) + @IsArray() + periods: DatePeriodDto[]; + + constructor(productIds: number[], periods: DatePeriodDto[]) { + this.productIds = productIds; + this.periods = periods; + } +} diff --git a/backend/src/modules/inventory/rental-schedule/dto/index.ts b/backend/src/modules/inventory/rental-schedule/dto/index.ts new file mode 100644 index 0000000..473d6c3 --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/dto/index.ts @@ -0,0 +1,4 @@ +export * from './check-rental-status.dto'; +export * from './product-rental-status.dto'; +export * from './rental-event.dto'; +export * from './rental-schedule.dto'; diff --git a/backend/src/modules/inventory/rental-schedule/dto/product-rental-status.dto.ts b/backend/src/modules/inventory/rental-schedule/dto/product-rental-status.dto.ts new file mode 100644 index 0000000..f57e9eb --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/dto/product-rental-status.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber } from 'class-validator'; + +import { RentalScheduleStatus } from '../enums'; +import { RentalEventDto } from './rental-event.dto'; + +export class ProductRentalStatusDto { + @ApiProperty() + @IsNumber() + productId: number; + + @ApiProperty({ nullable: true, enum: RentalScheduleStatus }) + @IsEnum(RentalScheduleStatus) + rentalStatus: RentalScheduleStatus; + + @ApiProperty({ type: [RentalEventDto] }) + @IsArray() + rentalEvents: RentalEventDto[]; + + constructor(productId: number, rentalStatus: RentalScheduleStatus, rentalEvents: RentalEventDto[]) { + this.productId = productId; + this.rentalStatus = rentalStatus; + this.rentalEvents = rentalEvents; + } +} diff --git a/backend/src/modules/inventory/rental-schedule/dto/rental-event.dto.ts b/backend/src/modules/inventory/rental-schedule/dto/rental-event.dto.ts new file mode 100644 index 0000000..5aab54d --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/dto/rental-event.dto.ts @@ -0,0 +1,54 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty, IsNumber, IsString } from 'class-validator'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { RentalScheduleStatus } from '../enums'; + +export class RentalEventDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + productId: number; + + @ApiProperty() + @IsNumber() + orderItemId: number; + + @ApiProperty() + @IsString() + startDate: string; + + @ApiProperty() + @IsString() + endDate: string; + + @ApiProperty({ enum: RentalScheduleStatus }) + @IsEnum(RentalScheduleStatus) + status: RentalScheduleStatus; + + @ApiProperty({ type: EntityInfoDto }) + @IsNotEmpty() + entityInfo: EntityInfoDto; + + constructor( + id: number, + productId: number, + orderItemId: number, + startDate: string, + endDate: string, + status: RentalScheduleStatus, + entityInfo: EntityInfoDto, + ) { + this.id = id; + this.productId = productId; + this.orderItemId = orderItemId; + this.startDate = startDate; + this.endDate = endDate; + this.status = status; + this.entityInfo = entityInfo; + } +} diff --git a/backend/src/modules/inventory/rental-schedule/dto/rental-schedule.dto.ts b/backend/src/modules/inventory/rental-schedule/dto/rental-schedule.dto.ts new file mode 100644 index 0000000..639817d --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/dto/rental-schedule.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { ProductInfoDto } from '../../product/dto/product-info.dto'; +import { RentalEventDto } from './rental-event.dto'; + +export class RentalScheduleDto { + @ApiProperty({ type: [ProductInfoDto] }) + @IsArray() + products: ProductInfoDto[]; + + @ApiProperty({ type: [RentalEventDto] }) + @IsArray() + events: RentalEventDto[]; + + constructor(products: ProductInfoDto[], events: RentalEventDto[]) { + this.products = products; + this.events = events; + } +} diff --git a/backend/src/modules/inventory/rental-schedule/entities/index.ts b/backend/src/modules/inventory/rental-schedule/entities/index.ts new file mode 100644 index 0000000..f028b02 --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/entities/index.ts @@ -0,0 +1 @@ +export * from './rental-event.entity'; diff --git a/backend/src/modules/inventory/rental-schedule/entities/rental-event.entity.ts b/backend/src/modules/inventory/rental-schedule/entities/rental-event.entity.ts new file mode 100644 index 0000000..d32ae3f --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/entities/rental-event.entity.ts @@ -0,0 +1,83 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { RentalScheduleStatus } from '../enums'; +import { RentalEventDto } from '../dto'; + +@Entity() +export class RentalEvent { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column() + productId: number; + + @Column() + orderItemId: number; + + @Column() + startDate: Date; + + @Column() + endDate: Date; + + @Column() + status: RentalScheduleStatus; + + @Column() + accountId: number; + + private _entityInfo: EntityInfoDto; + + constructor( + accountId: number, + sectionId: number, + productId: number, + orderItemId: number, + startDate: Date, + endDate: Date, + status: RentalScheduleStatus, + ) { + this.accountId = accountId; + this.sectionId = sectionId; + this.productId = productId; + this.orderItemId = orderItemId; + this.startDate = startDate; + this.endDate = endDate; + this.status = status; + } + + public get entityInfo(): EntityInfoDto { + return this._entityInfo; + } + public set entityInfo(entityInfo: EntityInfoDto) { + this._entityInfo = entityInfo; + } + + public toDto(): RentalEventDto { + return new RentalEventDto( + this.id, + this.productId, + this.orderItemId, + this.startDate.toISOString(), + this.endDate.toISOString(), + this.status, + this._entityInfo, + ); + } + + public ensureDates(notEarlier: Date, notLater: Date): RentalEvent { + if (this.startDate < notEarlier) { + this.startDate = notEarlier; + } + if (this.endDate > notLater) { + this.endDate = notLater; + } + + return this; + } +} diff --git a/backend/src/modules/inventory/rental-schedule/enums/index.ts b/backend/src/modules/inventory/rental-schedule/enums/index.ts new file mode 100644 index 0000000..380423d --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/enums/index.ts @@ -0,0 +1 @@ +export * from './rental-schedule-status.enum'; diff --git a/backend/src/modules/inventory/rental-schedule/enums/rental-schedule-status.enum.ts b/backend/src/modules/inventory/rental-schedule/enums/rental-schedule-status.enum.ts new file mode 100644 index 0000000..eb2ba05 --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/enums/rental-schedule-status.enum.ts @@ -0,0 +1,5 @@ +export enum RentalScheduleStatus { + Available = 'available', + Reserved = 'reserved', + Rented = 'rented', +} diff --git a/backend/src/modules/inventory/rental-schedule/rental-schedule.controller.ts b/backend/src/modules/inventory/rental-schedule/rental-schedule.controller.ts new file mode 100644 index 0000000..99cbcfa --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/rental-schedule.controller.ts @@ -0,0 +1,65 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto, DatePeriodDto, PagingQuery } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ProductsFilter } from '../product/dto/products-filter'; + +import { RentalScheduleDto, CheckRentalStatusDto, RentalEventDto, ProductRentalStatusDto } from './dto'; +import { RentalScheduleService } from './rental-schedule.service'; + +@ApiTags('inventory/rental/schedule') +@Controller('rental/sections/:sectionId/schedule') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class RentalScheduleController { + constructor(private readonly service: RentalScheduleService) {} + + @ApiCreatedResponse({ description: 'Get rental schedule', type: RentalScheduleDto }) + @Get() + public async getSchedule( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Query() period: DatePeriodDto, + @Query() filter: ProductsFilter, + @Query() paging: PagingQuery, + ) { + return this.service.getSchedule(accountId, user, sectionId, period, filter, paging); + } + + @ApiCreatedResponse({ description: 'Get rental schedule', type: [ProductRentalStatusDto] }) + @Post('check') + public async checkProductsStatus( + @CurrentAuth() { accountId }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: CheckRentalStatusDto, + ) { + return this.service.checkProductsStatus(accountId, sectionId, dto); + } + + @ApiCreatedResponse({ description: 'Get rental events schedule for product', type: [RentalEventDto] }) + @Get('products/:productId') + public async getProductSchedule( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + @Query() period: DatePeriodDto, + ) { + return this.service.getProductSchedule(accountId, user, sectionId, productId, period); + } + + @ApiOkResponse({ description: 'Release block for product in date interval' }) + @Put('products/:productId/release') + public async releaseProduct( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('productId', ParseIntPipe) productId: number, + @Body() period: DatePeriodDto, + ) { + return this.service.releaseProductByDates(accountId, user, sectionId, productId, period); + } +} diff --git a/backend/src/modules/inventory/rental-schedule/rental-schedule.module.ts b/backend/src/modules/inventory/rental-schedule/rental-schedule.module.ts new file mode 100644 index 0000000..97639b0 --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/rental-schedule.module.ts @@ -0,0 +1,25 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; + +import { ProductModule } from '../product/product.module'; +import { RentalOrderModule } from '../rental-order/rental-order.module'; +import { RentalEvent } from './entities/rental-event.entity'; +import { RentalScheduleController } from './rental-schedule.controller'; +import { RentalScheduleService } from './rental-schedule.service'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([RentalEvent]), + IAMModule, + EntityInfoModule, + forwardRef(() => ProductModule), + forwardRef(() => RentalOrderModule), + ], + controllers: [RentalScheduleController], + providers: [RentalScheduleService], + exports: [RentalScheduleService], +}) +export class RentalScheduleModule {} diff --git a/backend/src/modules/inventory/rental-schedule/rental-schedule.service.ts b/backend/src/modules/inventory/rental-schedule/rental-schedule.service.ts new file mode 100644 index 0000000..aa8f08e --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/rental-schedule.service.ts @@ -0,0 +1,358 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, LessThanOrEqual, MoreThanOrEqual, Repository, SelectQueryBuilder } from 'typeorm'; + +import { DatePeriod, DatePeriodDto, DateUtil, isUnique, PagingQuery } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; + +import { ProductsFilter } from '../product/dto/products-filter'; +import { ProductService } from '../product/product.service'; +import { RentalOrderStatus } from '../rental-order/enums'; +import { RentalOrderService } from '../rental-order/services/rental-order.service'; +import { RentalOrder } from '../rental-order/entities/rental-order.entity'; + +import { CheckRentalStatusDto } from './dto'; +import { RentalEvent } from './entities'; +import { RentalScheduleStatus } from './enums'; +import { RentalSchedule, ProductRentalStatus } from './types'; + +@Injectable() +export class RentalScheduleService { + constructor( + @InjectRepository(RentalEvent) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + @Inject(forwardRef(() => ProductService)) + private readonly productService: ProductService, + @Inject(forwardRef(() => RentalOrderService)) + private readonly rentalOrderService: RentalOrderService, + private readonly entityInfoService: EntityInfoService, + ) {} + + public async getSchedule( + accountId: number, + user: User, + sectionId: number, + periodDto: DatePeriodDto, + filter: ProductsFilter, + paging: PagingQuery, + ): Promise { + await this.authService.check({ + action: 'view', + user, + authorizable: RentalOrder.getAuthorizable(sectionId), + throwError: true, + }); + + const products = await this.productService.getProductsSimple(accountId, sectionId, filter, paging); + if (products.length === 0) { + return new RentalSchedule([], []); + } + + const events = await this.buildSchedule(accountId, sectionId, DatePeriod.fromDto(periodDto), { + productId: products.map((p) => p.id), + }); + + if (events.length === 0) { + return new RentalSchedule(products, []); + } + + const itemsWithEntity = await this.rentalOrderService.getEntityIdsByOrderItemIds( + events.map((e) => e.orderItemId).filter(isUnique), + ); + const entityIds = itemsWithEntity.map((i) => i.entityId).filter(isUnique); + const entityInfos = + entityIds.length > 0 ? await this.entityInfoService.findMany({ accountId, user, entityIds }) : []; + + for (const event of events) { + const itemWithEntity = itemsWithEntity.find((i) => i.orderItemId === event.orderItemId); + if (itemWithEntity) { + event.entityInfo = entityInfos.find((i) => i.id === itemWithEntity.entityId); + } + } + + return new RentalSchedule(products, events); + } + + public async getProductSchedule( + accountId: number, + user: User, + sectionId: number, + productId: number, + periodDto: DatePeriodDto, + ): Promise { + await this.authService.check({ + action: 'view', + user, + authorizable: RentalOrder.getAuthorizable(sectionId), + throwError: true, + }); + + return await this.buildSchedule(accountId, sectionId, DatePeriod.fromDto(periodDto), { productId }); + } + + public async processOrder(accountId: number, order: RentalOrder): Promise { + switch (order.status) { + case RentalOrderStatus.Formed: + case RentalOrderStatus.Cancelled: + await this.releaseProducts(accountId, order); + break; + case RentalOrderStatus.Reserved: + case RentalOrderStatus.SentToWarehouse: + await this.releaseProducts(accountId, order); + await this.blockProducts(accountId, order, RentalScheduleStatus.Reserved); + break; + case RentalOrderStatus.Shipped: + case RentalOrderStatus.Delivered: + await this.releaseProducts(accountId, order); + await this.blockProducts(accountId, order, RentalScheduleStatus.Rented); + break; + case RentalOrderStatus.Returned: + case RentalOrderStatus.AcceptedToWarehouse: + await this.releaseProductsFromDate(accountId, order, DateUtil.now()); + break; + } + } + + public async releaseProductByDates( + accountId: number, + user: User, + sectionId: number, + productId: number, + period: DatePeriodDto, + ): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: RentalOrder.getAuthorizable(sectionId), + throwError: true, + }); + + const startDate = DateUtil.fromISOString(period.startDate); + const endDate = DateUtil.fromISOString(period.endDate); + + await this.releaseInnerEvents(accountId, sectionId, productId, startDate, endDate); + await this.splitOuterEvent(accountId, sectionId, productId, startDate, endDate); + await this.releaseIntersectEvents(accountId, sectionId, productId, startDate, endDate); + } + + public async checkProductStatus( + accountId: number, + sectionId: number, + productId: number, + periods: DatePeriod[], + ): Promise { + const events = ( + await Promise.all(periods.map((period) => this.buildSchedule(accountId, sectionId, period, { productId }))) + ).flat(); + + const status = events.some((event) => event.status === RentalScheduleStatus.Rented) + ? RentalScheduleStatus.Rented + : events.some((event) => event.status === RentalScheduleStatus.Reserved) + ? RentalScheduleStatus.Reserved + : RentalScheduleStatus.Available; + + return new ProductRentalStatus(productId, status, events); + } + + public async checkProductsStatus( + accountId: number, + sectionId: number, + dto: CheckRentalStatusDto, + ): Promise { + const periods = dto.periods.map((p) => DatePeriod.fromDto(p)); + + return Promise.all( + dto.productIds.map((productId) => this.checkProductStatus(accountId, sectionId, productId, periods)), + ); + } + + private async buildSchedule( + accountId: number, + sectionId: number, + period: DatePeriod, + filter?: { productId?: number | number[] }, + ): Promise { + const qb = this.createQueryBuilder(accountId, sectionId, filter); + + const inners = await this.getInnerEvents(qb, period.from, period.to); + const outers = await this.getOuterEvents(qb, period.from, period.to); + const [lefts, rights] = await this.getIntersectEvents(qb, period.from, period.to); + + return [...outers, ...lefts, ...inners, ...rights] + .filter((r) => !!r) + .sort((a, b) => DateUtil.sort(a.startDate, b.startDate)); + } + + private async releaseProducts(accountId: number, order: RentalOrder): Promise { + await this.repository.delete({ + accountId, + sectionId: order.sectionId, + orderItemId: In(order.items.map((item) => item.id)), + }); + } + private async releaseProductsFromDate(accountId: number, order: RentalOrder, date: Date): Promise { + const itemIds = order.items.map((i) => i.id); + + // release all future events + await this.repository + .createQueryBuilder() + .delete() + .where('account_id = :accountId', { accountId }) + .andWhere('section_id = :sectionId', { sectionId: order.sectionId }) + .andWhere('order_item_id IN (:...itemIds)', { itemIds }) + .andWhere('start_date > :date', { date }) + .execute(); + + // release intersect events + const events = await this.repository + .createQueryBuilder() + .where('account_id = :accountId', { accountId }) + .andWhere('section_id = :sectionId', { sectionId: order.sectionId }) + .andWhere('order_item_id IN (:...itemIds)', { itemIds }) + .andWhere('start_date < :start_date', { start_date: date }) + .andWhere('end_date > :end_date', { end_date: date }) + .getMany(); + for (const event of events) { + event.endDate = date; + await this.repository.save(event); + } + } + private async blockProducts(accountId: number, order: RentalOrder, status: RentalScheduleStatus): Promise { + for (const item of order.items) { + await this.repository.save( + order.periods.map( + (period) => + new RentalEvent( + accountId, + order.sectionId, + item.productId, + item.id, + period.startDate, + period.endDate, + status, + ), + ), + ); + } + } + + private async releaseInnerEvents( + accountId: number, + sectionId: number, + productId: number, + startDate: Date, + endDate: Date, + ) { + await this.repository.delete({ + accountId, + sectionId, + productId, + startDate: MoreThanOrEqual(startDate), + endDate: LessThanOrEqual(endDate), + }); + } + private async splitOuterEvent( + accountId: number, + sectionId: number, + productId: number, + startDate: Date, + endDate: Date, + ) { + const qb = this.createQueryBuilder(accountId, sectionId, { productId }); + + const outers = await this.getOuterEvents(qb, startDate, endDate); + for (const outer of outers) { + await this.repository.save( + new RentalEvent(accountId, sectionId, productId, outer.orderItemId, outer.startDate, startDate, outer.status), + ); + await this.repository.save( + new RentalEvent(accountId, sectionId, productId, outer.orderItemId, endDate, outer.endDate, outer.status), + ); + await this.repository.delete(outer.id); + } + } + private async releaseIntersectEvents( + accountId: number, + sectionId: number, + productId: number, + startDate: Date, + endDate: Date, + ) { + const qb = this.createQueryBuilder(accountId, sectionId, { productId }); + + const [lefts, rights] = await this.getIntersectEvents(qb, startDate, endDate); + if (lefts?.length > 0) { + lefts.forEach((left) => left.ensureDates(left.startDate, startDate)); + await this.repository.save(lefts); + } + if (rights?.length > 0) { + rights.forEach((right) => right.ensureDates(endDate, right.endDate)); + await this.repository.save(rights); + } + } + + private createQueryBuilder( + accountId: number, + sectionId: number, + filter?: { productId?: number | number[] }, + ): SelectQueryBuilder { + const qb = this.repository + .createQueryBuilder() + .where('account_id = :accountId', { accountId }) + .andWhere('section_id = :sectionId', { sectionId }); + + if (filter?.productId) { + if (Array.isArray(filter.productId)) { + qb.andWhere('product_id IN (:...productIds)', { productIds: filter.productId }); + } else { + qb.andWhere('product_id = :productId', { productId: filter.productId }); + } + } + + return qb; + } + + private async getInnerEvents( + qb: SelectQueryBuilder, + startDate: Date, + endDate: Date, + ): Promise { + return await qb + .clone() + .andWhere('start_date >= :startDate', { startDate: startDate }) + .andWhere('end_date <= :endDate', { endDate: endDate }) + .getMany(); + } + private async getOuterEvents( + qb: SelectQueryBuilder, + startDate: Date, + endDate: Date, + ): Promise { + return await qb + .clone() + .andWhere('start_date < :startDate', { startDate: startDate }) + .andWhere('end_date > :endDate', { endDate: endDate }) + .getMany(); + } + private async getIntersectEvents(qb: SelectQueryBuilder, startDate: Date, endDate: Date) { + const lefts = await qb + .clone() + .andWhere('start_date < :startDate1', { startDate1: startDate }) + .andWhere('end_date >= :startDate2', { startDate2: startDate }) + .andWhere('end_date <= :endDate', { endDate: endDate }) + .getMany(); + + const rights = await qb + .clone() + .andWhere('start_date >= :startDate', { startDate: startDate }) + .andWhere('start_date <= :endDate1', { endDate1: endDate }) + .andWhere('end_date > :endDate2', { endDate2: endDate }) + .getMany(); + + return [lefts, rights]; + } +} diff --git a/backend/src/modules/inventory/rental-schedule/types/index.ts b/backend/src/modules/inventory/rental-schedule/types/index.ts new file mode 100644 index 0000000..b8badc7 --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/types/index.ts @@ -0,0 +1,2 @@ +export * from './product-rental-status'; +export * from './rental-schedule'; diff --git a/backend/src/modules/inventory/rental-schedule/types/product-rental-status.ts b/backend/src/modules/inventory/rental-schedule/types/product-rental-status.ts new file mode 100644 index 0000000..7c1cd32 --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/types/product-rental-status.ts @@ -0,0 +1,23 @@ +import { ProductRentalStatusDto } from '../dto'; +import { type RentalEvent } from '../entities'; +import { type RentalScheduleStatus } from '../enums'; + +export class ProductRentalStatus { + productId: number; + status: RentalScheduleStatus; + events: RentalEvent[]; + + constructor(productId: number, status: RentalScheduleStatus, events: RentalEvent[]) { + this.productId = productId; + this.status = status; + this.events = events; + } + + public toDto(): ProductRentalStatusDto { + return new ProductRentalStatusDto( + this.productId, + this.status, + this.events.map((e) => e.toDto()), + ); + } +} diff --git a/backend/src/modules/inventory/rental-schedule/types/rental-schedule.ts b/backend/src/modules/inventory/rental-schedule/types/rental-schedule.ts new file mode 100644 index 0000000..b073157 --- /dev/null +++ b/backend/src/modules/inventory/rental-schedule/types/rental-schedule.ts @@ -0,0 +1,22 @@ +import { type Product } from '../../product/entities/product.entity'; + +import { RentalScheduleDto } from '../dto'; +import { type RentalEvent } from '../entities'; + +export class RentalSchedule { + products: Product[]; + + events: RentalEvent[]; + + constructor(products: Product[], events: RentalEvent[]) { + this.products = products; + this.events = events; + } + + public toDto(): RentalScheduleDto { + return new RentalScheduleDto( + this.products.map((p) => p.toInfo()), + this.events.map((e) => e.toDto()), + ); + } +} diff --git a/backend/src/modules/inventory/reservation/dto/index.ts b/backend/src/modules/inventory/reservation/dto/index.ts new file mode 100644 index 0000000..e32c17f --- /dev/null +++ b/backend/src/modules/inventory/reservation/dto/index.ts @@ -0,0 +1 @@ +export * from './reservation.dto'; diff --git a/backend/src/modules/inventory/reservation/dto/reservation.dto.ts b/backend/src/modules/inventory/reservation/dto/reservation.dto.ts new file mode 100644 index 0000000..fce96e8 --- /dev/null +++ b/backend/src/modules/inventory/reservation/dto/reservation.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class ReservationDto { + @ApiProperty() + @IsNumber() + warehouseId: number; + + @ApiProperty() + @IsNumber() + quantity: number; + + constructor(warehouseId: number, quantity: number) { + this.warehouseId = warehouseId; + this.quantity = quantity; + } +} diff --git a/backend/src/modules/inventory/reservation/entities/index.ts b/backend/src/modules/inventory/reservation/entities/index.ts new file mode 100644 index 0000000..800ca3e --- /dev/null +++ b/backend/src/modules/inventory/reservation/entities/index.ts @@ -0,0 +1 @@ +export * from './reservation.entity'; diff --git a/backend/src/modules/inventory/reservation/entities/reservation.entity.ts b/backend/src/modules/inventory/reservation/entities/reservation.entity.ts new file mode 100644 index 0000000..8571f26 --- /dev/null +++ b/backend/src/modules/inventory/reservation/entities/reservation.entity.ts @@ -0,0 +1,64 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { ReservationDto } from '../dto'; + +@Entity() +export class Reservation { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + orderId: number; + + @Column() + orderItemId: number; + + @Column() + productId: number; + + @Column() + warehouseId: number; + + @Column() + quantity: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + orderId: number, + orderItemId: number, + productId: number, + warehouseId: number, + quantity: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.orderId = orderId; + this.orderItemId = orderItemId; + this.productId = productId; + this.warehouseId = warehouseId; + this.quantity = quantity; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public static fromDto( + accountId: number, + orderId: number, + orderItemId: number, + productId: number, + dto: ReservationDto, + ): Reservation { + return new Reservation(accountId, orderId, orderItemId, productId, dto.warehouseId, dto.quantity); + } + + public toDto(): ReservationDto { + return new ReservationDto(this.warehouseId, this.quantity); + } +} diff --git a/backend/src/modules/inventory/reservation/reservation.module.ts b/backend/src/modules/inventory/reservation/reservation.module.ts new file mode 100644 index 0000000..2011a93 --- /dev/null +++ b/backend/src/modules/inventory/reservation/reservation.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { Reservation } from './entities/reservation.entity'; +import { ReservationService } from './reservation.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Reservation]), IAMModule], + providers: [ReservationService], + exports: [ReservationService], +}) +export class ReservationModule {} diff --git a/backend/src/modules/inventory/reservation/reservation.service.ts b/backend/src/modules/inventory/reservation/reservation.service.ts new file mode 100644 index 0000000..c8e11f9 --- /dev/null +++ b/backend/src/modules/inventory/reservation/reservation.service.ts @@ -0,0 +1,93 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ReservationDto } from './dto'; +import { Reservation } from './entities'; + +interface FindFilter { + warehouseId?: number | number[]; + orderId?: number | number[]; + orderItemId?: number; + productId?: number; +} +interface DeleteOptions { + newWarehouseId?: number; +} +interface ReservationByWarehouse { + quantity: number; + warehouseId: number; +} + +@Injectable() +export class ReservationService { + constructor( + @InjectRepository(Reservation) + private readonly repository: Repository, + ) {} + + public async create( + accountId: number, + orderId: number, + orderItemId: number, + productId: number, + dtos: ReservationDto[], + ): Promise { + await this.createQb(accountId, { orderId, orderItemId, productId }).delete().execute(); + + const reservations: Reservation[] = []; + for (const dto of dtos) { + reservations.push( + await this.repository.save(Reservation.fromDto(accountId, orderId, orderItemId, productId, dto)), + ); + } + + return reservations; + } + + public async findMany(accountId: number, filter: FindFilter): Promise { + return this.createQb(accountId, filter).getMany(); + } + + public async getReservedQuantities(accountId: number, filter: FindFilter): Promise { + return this.createQb(accountId, filter) + .select('reservation.warehouse_id', 'warehouseId') + .addSelect('sum(reservation.quantity)', 'quantity') + .groupBy('reservation.warehouse_id') + .getRawMany(); + } + + public async delete(accountId: number, filter: FindFilter, options?: DeleteOptions) { + const qb = this.createQb(accountId, filter); + if (options?.newWarehouseId) { + await qb.update({ warehouseId: options.newWarehouseId }).execute(); + } else { + await qb.delete().execute(); + } + } + + private createQb(accountId: number, filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('reservation') + .where('reservation.account_id = :accountId', { accountId }); + if (filter.warehouseId) { + if (Array.isArray(filter.warehouseId)) { + qb.andWhere('reservation.warehouse_id IN (:...warehouseIds)', { warehouseIds: filter.warehouseId }); + } else { + qb.andWhere('reservation.warehouse_id = :warehouseId', { warehouseId: filter.warehouseId }); + } + } + if (filter.orderId) { + qb.andWhere('reservation.order_id IN (:...orderIds)', { + orderIds: Array.isArray(filter.orderId) ? filter.orderId : [filter.orderId], + }); + } + if (filter.orderItemId) { + qb.andWhere('reservation.order_item_id = :orderItemId', { orderItemId: filter.orderItemId }); + } + if (filter.productId) { + qb.andWhere('reservation.product_id = :productId', { productId: filter.productId }); + } + return qb; + } +} diff --git a/backend/src/modules/inventory/shipment/dto/change-status-query.ts b/backend/src/modules/inventory/shipment/dto/change-status-query.ts new file mode 100644 index 0000000..0cff97f --- /dev/null +++ b/backend/src/modules/inventory/shipment/dto/change-status-query.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +export class ChangeStatusQuery { + @ApiPropertyOptional({ description: 'Return stocks to warehouse' }) + @IsOptional() + @IsBoolean() + returnStocks?: boolean; +} diff --git a/backend/src/modules/inventory/shipment/dto/index.ts b/backend/src/modules/inventory/shipment/dto/index.ts new file mode 100644 index 0000000..32eda3b --- /dev/null +++ b/backend/src/modules/inventory/shipment/dto/index.ts @@ -0,0 +1,5 @@ +export * from './change-status-query'; +export * from './shipment-filter.dto'; +export * from './shipment-item.dto'; +export * from './shipment-result.dto'; +export * from './shipment.dto'; diff --git a/backend/src/modules/inventory/shipment/dto/shipment-filter.dto.ts b/backend/src/modules/inventory/shipment/dto/shipment-filter.dto.ts new file mode 100644 index 0000000..560759a --- /dev/null +++ b/backend/src/modules/inventory/shipment/dto/shipment-filter.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { PagingQuery } from '@/common'; + +export class ShipmentFilterDto extends PagingQuery { + @ApiProperty({ description: 'Order ID' }) + @IsOptional() + @IsNumber() + orderId?: number; +} diff --git a/backend/src/modules/inventory/shipment/dto/shipment-item.dto.ts b/backend/src/modules/inventory/shipment/dto/shipment-item.dto.ts new file mode 100644 index 0000000..bba5aca --- /dev/null +++ b/backend/src/modules/inventory/shipment/dto/shipment-item.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class ShipmentItemDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + productId: number; + + @ApiProperty() + @IsNumber() + quantity: number; +} diff --git a/backend/src/modules/inventory/shipment/dto/shipment-result.dto.ts b/backend/src/modules/inventory/shipment/dto/shipment-result.dto.ts new file mode 100644 index 0000000..0d0c795 --- /dev/null +++ b/backend/src/modules/inventory/shipment/dto/shipment-result.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray } from 'class-validator'; + +import { PagingMeta } from '@/common'; +import { ShipmentDto } from './shipment.dto'; + +export class ShipmentResultDto { + @ApiProperty({ type: [ShipmentDto], description: 'List of shipments' }) + @IsArray() + shipments: ShipmentDto[]; + + @ApiProperty({ type: PagingMeta, description: 'Paging metadata' }) + meta: PagingMeta; +} diff --git a/backend/src/modules/inventory/shipment/dto/shipment.dto.ts b/backend/src/modules/inventory/shipment/dto/shipment.dto.ts new file mode 100644 index 0000000..3b86e0b --- /dev/null +++ b/backend/src/modules/inventory/shipment/dto/shipment.dto.ts @@ -0,0 +1,54 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto'; + +import { ShipmentItemDto } from './shipment-item.dto'; + +export class ShipmentDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + sectionId: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + name: string | null; + + @ApiProperty() + @IsNumber() + warehouseId: number; + + @ApiProperty() + @IsNumber() + orderId: number; + + @ApiProperty() + @IsNumber() + orderNumber: number; + + @ApiProperty() + @IsNumber() + statusId: number; + + @ApiProperty({ type: EntityInfoDto }) + @IsNotEmpty() + entityInfo: EntityInfoDto; + + @ApiProperty() + @IsString() + createdAt: string; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + shippedAt: string | null; + + @ApiProperty({ type: [ShipmentItemDto] }) + @IsArray() + items: ShipmentItemDto[]; +} diff --git a/backend/src/modules/inventory/shipment/entities/index.ts b/backend/src/modules/inventory/shipment/entities/index.ts new file mode 100644 index 0000000..8fbda92 --- /dev/null +++ b/backend/src/modules/inventory/shipment/entities/index.ts @@ -0,0 +1,2 @@ +export * from './shipment-item.entity'; +export * from './shipment.entity'; diff --git a/backend/src/modules/inventory/shipment/entities/shipment-item.entity.ts b/backend/src/modules/inventory/shipment/entities/shipment-item.entity.ts new file mode 100644 index 0000000..0ec8274 --- /dev/null +++ b/backend/src/modules/inventory/shipment/entities/shipment-item.entity.ts @@ -0,0 +1,32 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { ShipmentItemDto } from '../dto'; + +@Entity() +export class ShipmentItem { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + shipmentId: number; + + @Column() + productId: number; + + @Column() + quantity: number; + + constructor(accountId: number, shipmentId: number, productId: number, quantity: number) { + this.accountId = accountId; + this.shipmentId = shipmentId; + this.productId = productId; + this.quantity = quantity; + } + + public toDto(): ShipmentItemDto { + return { id: this.id, productId: this.productId, quantity: this.quantity }; + } +} diff --git a/backend/src/modules/inventory/shipment/entities/shipment.entity.ts b/backend/src/modules/inventory/shipment/entities/shipment.entity.ts new file mode 100644 index 0000000..112e287 --- /dev/null +++ b/backend/src/modules/inventory/shipment/entities/shipment.entity.ts @@ -0,0 +1,112 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { EntityInfoDto } from '@/modules/entity/entity-info/dto'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; + +import { ShipmentDto } from '../dto'; +import { ShipmentItem } from './shipment-item.entity'; + +@Entity() +export class Shipment implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column() + name: string; + + @Column() + warehouseId: number; + + @Column() + entityId: number; + + @Column() + orderId: number; + + @Column() + orderNumber: number; + + @Column() + statusId: number; + + @Column() + shippedAt: Date | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + sectionId: number, + name: string, + warehouseId: number, + entityId: number, + orderId: number, + orderNumber: number, + statusId: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.sectionId = sectionId; + this.name = name; + this.warehouseId = warehouseId; + this.entityId = entityId; + this.orderId = orderId; + this.orderNumber = orderNumber; + this.statusId = statusId; + this.shippedAt = null; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _items: ShipmentItem[] | null; + public get items(): ShipmentItem[] | null { + return this._items; + } + public set items(value: ShipmentItem[] | null) { + this._items = value; + } + + private _entityInfo: EntityInfoDto | null; + public get entityInfo(): EntityInfoDto | null { + return this._entityInfo; + } + public set entityInfo(value: EntityInfoDto | null) { + this._entityInfo = value; + } + + static getAuthorizable(sectionId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.ProductsShipment, id: sectionId }); + } + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.ProductsShipment, + id: this.sectionId, + }; + } + + public toDto(): ShipmentDto { + return { + id: this.id, + sectionId: this.sectionId, + name: this.name, + warehouseId: this.warehouseId, + orderId: this.orderId, + orderNumber: this.orderNumber, + statusId: this.statusId, + createdAt: this.createdAt.toISOString(), + shippedAt: this.shippedAt ? this.shippedAt.toISOString() : null, + items: this.items ? this.items.map((item) => item.toDto()) : [], + entityInfo: this.entityInfo, + }; + } +} diff --git a/backend/src/modules/inventory/shipment/shipment.controller.ts b/backend/src/modules/inventory/shipment/shipment.controller.ts new file mode 100644 index 0000000..410f647 --- /dev/null +++ b/backend/src/modules/inventory/shipment/shipment.controller.ts @@ -0,0 +1,52 @@ +import { Controller, Get, Param, ParseIntPipe, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ChangeStatusQuery, ShipmentDto, ShipmentFilterDto, ShipmentResultDto } from './dto'; +import { ShipmentService } from './shipment.service'; + +@ApiTags('inventory/shipments') +@Controller('products/sections/:sectionId/shipments') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ShipmentController { + constructor(private readonly service: ShipmentService) {} + + @ApiCreatedResponse({ description: 'Get shipment by id', type: ShipmentDto }) + @Get('/:shipmentId') + public async getShipment( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('shipmentId', ParseIntPipe) shipmentId: number, + ) { + return this.service.findOne({ accountId, user, filter: { sectionId, shipmentId } }); + } + + @ApiCreatedResponse({ description: 'Get shipments', type: ShipmentResultDto }) + @Get() + public async getShipments( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Query() filter: ShipmentFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.getShipments({ accountId, user, sectionId, orderId: filter.orderId, paging }); + } + + @ApiCreatedResponse({ description: 'Change shipment status', type: ShipmentDto }) + @Put('/:shipmentId/status/:statusId') + public async changeStatus( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('shipmentId', ParseIntPipe) shipmentId: number, + @Param('statusId', ParseIntPipe) statusId: number, + @Query() query: ChangeStatusQuery, + ) { + return this.service.changeStatus(accountId, user, sectionId, shipmentId, statusId, query); + } +} diff --git a/backend/src/modules/inventory/shipment/shipment.module.ts b/backend/src/modules/inventory/shipment/shipment.module.ts new file mode 100644 index 0000000..57740ec --- /dev/null +++ b/backend/src/modules/inventory/shipment/shipment.module.ts @@ -0,0 +1,30 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; + +import { OrderStatusModule } from '../order-status/order-status.module'; +import { ReservationModule } from '../reservation/reservation.module'; +import { ProductStockModule } from '../product-stock/product-stock.module'; +import { WarehouseModule } from '../warehouse/warehouse.module'; + +import { Shipment, ShipmentItem } from './entities'; +import { ShipmentService } from './shipment.service'; +import { ShipmentController } from './shipment.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ShipmentItem, Shipment]), + IAMModule, + EntityInfoModule, + OrderStatusModule, + forwardRef(() => ReservationModule), + forwardRef(() => ProductStockModule), + forwardRef(() => WarehouseModule), + ], + controllers: [ShipmentController], + providers: [ShipmentService], + exports: [ShipmentService], +}) +export class ShipmentModule {} diff --git a/backend/src/modules/inventory/shipment/shipment.service.ts b/backend/src/modules/inventory/shipment/shipment.service.ts new file mode 100644 index 0000000..7247b63 --- /dev/null +++ b/backend/src/modules/inventory/shipment/shipment.service.ts @@ -0,0 +1,362 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Repository } from 'typeorm'; + +import { DateUtil, NotFoundError, PagingQuery } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; + +import { ProductsEventType, ShipmentDeletedEvent, ShipmentCreatedEvent, ShipmentStatusChangedEvent } from '../common'; + +import { Order, OrderItem } from '../order/entities'; +import { OrderStatusCode } from '../order-status/enums'; +import { OrderStatus } from '../order-status/entities'; +import { OrderStatusService } from '../order-status/order-status.service'; +import { ProductStockService } from '../product-stock/product-stock.service'; +import { WarehouseService } from '../warehouse/warehouse.service'; + +import { Shipment, ShipmentItem } from './entities'; +import { ShipmentResult } from './types'; + +interface FindFilter { + sectionId?: number; + warehouseId?: number | number[]; + orderId?: number | number[]; + entityId?: number; + shipmentId?: number | number[]; +} +interface CancelOptions { + returnStocks?: boolean; +} +type DeleteOptions = { newWarehouseId?: number } & CancelOptions; + +@Injectable() +export class ShipmentService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Shipment) + private readonly repository: Repository, + @InjectRepository(ShipmentItem) + private readonly itemRepository: Repository, + private readonly authService: AuthorizationService, + private readonly orderStatusService: OrderStatusService, + private readonly entityInfoService: EntityInfoService, + @Inject(forwardRef(() => ProductStockService)) + private readonly stockService: ProductStockService, + @Inject(forwardRef(() => WarehouseService)) + private readonly warehouseService: WarehouseService, + ) {} + + public async findOne({ accountId, user, filter }: { accountId: number; user: User | null; filter: FindFilter }) { + const shipments = await this.getAuthorizedShipments(accountId, user, filter); + const shipment = shipments[0]; + if (!shipment) return null; + shipment.entityInfo = await this.entityInfoService.findOne({ + accountId: shipment.accountId, + user, + entityId: shipment.entityId, + }); + return shipment; + } + + public async findMany({ accountId, user, filter }: { accountId: number; user: User | null; filter: FindFilter }) { + const shipments = await this.getAuthorizedShipments(accountId, user, filter); + await Promise.all( + shipments.map(async (shipment) => { + shipment.entityInfo = await this.entityInfoService.findOne({ + accountId: shipment.accountId, + user, + entityId: shipment.entityId, + }); + }), + ); + return shipments; + } + + private async getAuthorizedShipments(accountId: number, user: User | null, filter: FindFilter) { + if (user) { + const warehouses = await this.warehouseService.findMany({ + user, + filter: { accountId, sectionId: filter.sectionId, warehouseId: filter.warehouseId, onlyAvailable: true }, + }); + filter.warehouseId = warehouses?.length ? warehouses.map((w) => w.id) : undefined; + } + const shipments = await this.createQb(accountId, filter, true).getMany(); + return user + ? shipments.filter( + async (shipment) => await this.authService.check({ action: 'view', user, authorizable: shipment }), + ) + : shipments; + } + + public async getShipments({ + accountId, + user, + sectionId, + orderId, + paging, + }: { + accountId: number; + user: User; + sectionId: number; + orderId?: number; + paging: PagingQuery; + }): Promise { + await this.authService.check({ + action: 'view', + user, + authorizable: Shipment.getAuthorizable(sectionId), + throwError: true, + }); + + const filter: FindFilter = { sectionId, orderId }; + if (user) { + const warehouses = await this.warehouseService.findMany({ + user, + filter: { accountId, sectionId, onlyAvailable: true }, + }); + filter.warehouseId = warehouses?.length ? warehouses.map((w) => w.id) : undefined; + } + + const qb = this.createQb(accountId, filter, true) + .orderBy('shipment.created_at', 'DESC') + .addOrderBy('shipment.id', 'DESC') + .limit(paging.take) + .offset(paging.skip); + + const [shipments, total] = await qb.getManyAndCount(); + + await Promise.all( + shipments.map(async (shipment) => { + shipment.entityInfo = await this.entityInfoService.findOne({ + accountId: shipment.accountId, + user, + entityId: shipment.entityId, + }); + }), + ); + + return new ShipmentResult(shipments, paging.skip, total); + } + + public async changeStatus( + accountId: number, + user: User, + sectionId: number, + shipmentId: number, + statusId: number, + options?: CancelOptions, + ): Promise { + const shipment = await this.findOne({ accountId, user, filter: { sectionId, shipmentId } }); + if (!shipment) { + throw NotFoundError.withId(Shipment, shipmentId); + } + + await this.authService.check({ action: 'edit', user, authorizable: shipment, throwError: true }); + + if (shipment.statusId !== statusId) { + const status = await this.orderStatusService.findOne(accountId, { statusId }); + await this.processShipment(accountId, sectionId, shipment, status, options); + } + + return shipment; + } + + public async delete(accountId: number, filter: FindFilter, options?: DeleteOptions) { + if (options?.newWarehouseId) { + await this.createQb(accountId, filter).update({ warehouseId: options.newWarehouseId }).execute(); + } else { + const shipments = await this.createQb(accountId, filter, true).getMany(); + if (options?.returnStocks) { + await Promise.all( + shipments.map(async (shipment) => { + const status = await this.orderStatusService.findOne(accountId, { statusId: shipment.statusId }); + if (status.code === OrderStatusCode.Shipped) { + await this.increaseStocks({ accountId, shipment }); + } + }), + ); + } + await this.createQb(accountId, filter).delete().execute(); + shipments.forEach((shipment) => + this.eventEmitter.emit( + ProductsEventType.ShipmentDeleted, + new ShipmentDeletedEvent({ + accountId, + sectionId: shipment.sectionId, + orderId: shipment.orderId, + shipmentId: shipment.id, + entityId: shipment.entityId, + }), + ), + ); + } + } + + public async processOrder( + accountId: number, + sectionId: number, + order: Order, + status: OrderStatus, + options?: CancelOptions, + ) { + if (status.code === OrderStatusCode.SentForShipment) { + await this.createForOrder(accountId, sectionId, order); + } else { + await this.processOrderShipments(accountId, sectionId, order.id, status, options); + } + } + + private createQb(accountId: number, filter: FindFilter, includeItems = false) { + const qb = this.repository.createQueryBuilder('shipment').where('shipment.account_id = :accountId', { accountId }); + if (includeItems) { + qb.leftJoinAndMapMany('shipment.items', ShipmentItem, 'shipment_item', 'shipment_item.shipment_id = shipment.id'); + } + if (filter.sectionId) { + qb.andWhere('shipment.section_id = :sectionId', { sectionId: filter.sectionId }); + } + if (filter.warehouseId) { + if (Array.isArray(filter.warehouseId)) { + qb.andWhere('shipment.warehouse_id IN (:...warehouseIds)', { warehouseIds: filter.warehouseId }); + } else { + qb.andWhere('shipment.warehouse_id = :warehouseId', { warehouseId: filter.warehouseId }); + } + } + if (filter.orderId) { + qb.andWhere('shipment.order_id IN (:...orderIds)', { + orderIds: Array.isArray(filter.orderId) ? filter.orderId : [filter.orderId], + }); + } + if (filter.entityId) { + qb.andWhere('shipment.entity_id = :entityId', { entityId: filter.entityId }); + } + if (filter.shipmentId) { + if (Array.isArray(filter.shipmentId)) { + qb.andWhere('shipment.id IN (:...shipmentIds)', { shipmentIds: filter.shipmentId }); + } else { + qb.andWhere('shipment.id = :shipmentId', { shipmentId: filter.shipmentId }); + } + } + return qb; + } + + private async createForOrder(accountId: number, sectionId: number, order: Order): Promise { + await this.createQb(accountId, { sectionId, orderId: order.id }).delete().execute(); + const shipments = new Map(); + for (const item of order.items) { + await this.createForOrderItem(accountId, sectionId, order, item, shipments); + } + return Array.from(shipments.values()); + } + + private async createForOrderItem( + accountId: number, + sectionId: number, + order: Order, + orderItem: OrderItem, + shipments: Map, + ) { + for (const reservation of orderItem.reservations) { + if (!shipments.has(reservation.warehouseId)) { + const shipment = await this.repository.save( + new Shipment( + accountId, + sectionId, + `Shipment for order #${order.id}`, + reservation.warehouseId, + order.entityId, + order.id, + order.orderNumber, + order.statusId, + ), + ); + shipments.set(reservation.warehouseId, shipment); + this.eventEmitter.emit( + ProductsEventType.ShipmentCreated, + new ShipmentCreatedEvent({ + accountId, + sectionId, + orderId: order.id, + shipmentId: shipment.id, + entityId: order.entityId, + createdAt: shipment.createdAt.toISOString(), + }), + ); + } + + await this.itemRepository.save( + new ShipmentItem( + accountId, + shipments.get(reservation.warehouseId).id, + orderItem.productId, + reservation.quantity, + ), + ); + } + } + + private async processOrderShipments( + accountId: number, + sectionId: number, + orderId: number, + status: OrderStatus, + options?: CancelOptions, + ) { + const shipments = await this.findMany({ accountId, user: null, filter: { sectionId, orderId } }); + await Promise.all( + shipments + .filter((shipment) => shipment.statusId !== status.id) + .map((shipment) => this.processShipment(accountId, sectionId, shipment, status, options)), + ); + } + + private async processShipment( + accountId: number, + sectionId: number, + shipment: Shipment, + status: OrderStatus, + options?: CancelOptions, + ) { + if (status.code === OrderStatusCode.Shipped) { + shipment.shippedAt = DateUtil.now(); + await this.reduceStocks({ accountId, shipment }); + } else if ([OrderStatusCode.Returned, OrderStatusCode.Cancelled].includes(status.code)) { + shipment.shippedAt = null; + if (options?.returnStocks) { + await this.increaseStocks({ accountId, shipment }); + } + } + shipment.statusId = status.id; + await this.repository.save(shipment); + + this.eventEmitter.emit( + ProductsEventType.ShipmentStatusChanged, + new ShipmentStatusChangedEvent({ + accountId, + sectionId, + orderId: shipment.orderId, + shipmentId: shipment.id, + statusId: shipment.statusId, + }), + ); + } + + private async reduceStocks({ accountId, shipment }: { accountId: number; shipment: Shipment }) { + await Promise.all( + shipment.items.map(({ productId, quantity }) => + this.stockService.reduce({ accountId, warehouseId: shipment.warehouseId, productId, quantity }), + ), + ); + } + + private async increaseStocks({ accountId, shipment }: { accountId: number; shipment: Shipment }) { + await Promise.all( + shipment.items.map(({ productId, quantity }) => + this.stockService.increase({ accountId, warehouseId: shipment.warehouseId, productId, quantity }), + ), + ); + } +} diff --git a/backend/src/modules/inventory/shipment/types/index.ts b/backend/src/modules/inventory/shipment/types/index.ts new file mode 100644 index 0000000..151d9cc --- /dev/null +++ b/backend/src/modules/inventory/shipment/types/index.ts @@ -0,0 +1 @@ +export * from './shipment-result'; diff --git a/backend/src/modules/inventory/shipment/types/shipment-result.ts b/backend/src/modules/inventory/shipment/types/shipment-result.ts new file mode 100644 index 0000000..87d34ec --- /dev/null +++ b/backend/src/modules/inventory/shipment/types/shipment-result.ts @@ -0,0 +1,23 @@ +import { PagingMeta } from '@/common'; + +import { ShipmentResultDto } from '../dto'; +import { Shipment } from '../entities'; + +export class ShipmentResult { + shipments: Shipment[]; + offset: number; + total: number; + + constructor(shipments: Shipment[], offset: number, total: number) { + this.shipments = shipments; + this.offset = offset; + this.total = total; + } + + public toDto(): ShipmentResultDto { + return { + shipments: this.shipments.map((shipment) => shipment.toDto()), + meta: new PagingMeta(this.offset, this.total), + }; + } +} diff --git a/backend/src/modules/inventory/warehouse/dto/create-warehouse.dto.ts b/backend/src/modules/inventory/warehouse/dto/create-warehouse.dto.ts new file mode 100644 index 0000000..ae9dd13 --- /dev/null +++ b/backend/src/modules/inventory/warehouse/dto/create-warehouse.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { WarehouseDto } from './warehouse.dto'; + +export class CreateWarehouseDto extends PickType(WarehouseDto, ['name'] as const) {} diff --git a/backend/src/modules/inventory/warehouse/dto/index.ts b/backend/src/modules/inventory/warehouse/dto/index.ts new file mode 100644 index 0000000..192c60c --- /dev/null +++ b/backend/src/modules/inventory/warehouse/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-warehouse.dto'; +export * from './update-warehouse.dto'; +export * from './warehouse.dto'; diff --git a/backend/src/modules/inventory/warehouse/dto/update-warehouse.dto.ts b/backend/src/modules/inventory/warehouse/dto/update-warehouse.dto.ts new file mode 100644 index 0000000..0482c06 --- /dev/null +++ b/backend/src/modules/inventory/warehouse/dto/update-warehouse.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { WarehouseDto } from './warehouse.dto'; + +export class UpdateWarehouseDto extends PickType(WarehouseDto, ['name'] as const) {} diff --git a/backend/src/modules/inventory/warehouse/dto/warehouse.dto.ts b/backend/src/modules/inventory/warehouse/dto/warehouse.dto.ts new file mode 100644 index 0000000..67afc52 --- /dev/null +++ b/backend/src/modules/inventory/warehouse/dto/warehouse.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common'; + +export class WarehouseDto { + @ApiProperty({ description: 'Warehouse ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Section ID' }) + @IsNumber() + sectionId: number; + + @ApiProperty({ description: 'Warehouse name' }) + @IsString() + name: string; + + @ApiProperty({ type: UserRights, description: 'User rights' }) + userRights: UserRights; +} diff --git a/backend/src/modules/inventory/warehouse/entities/index.ts b/backend/src/modules/inventory/warehouse/entities/index.ts new file mode 100644 index 0000000..015b6f0 --- /dev/null +++ b/backend/src/modules/inventory/warehouse/entities/index.ts @@ -0,0 +1 @@ +export * from './warehouse.entity'; diff --git a/backend/src/modules/inventory/warehouse/entities/warehouse.entity.ts b/backend/src/modules/inventory/warehouse/entities/warehouse.entity.ts new file mode 100644 index 0000000..8e6a82c --- /dev/null +++ b/backend/src/modules/inventory/warehouse/entities/warehouse.entity.ts @@ -0,0 +1,84 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; +import { Authorizable, AuthorizableObject, SimpleAuthorizable, UserRights } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; +import { CreateWarehouseDto, UpdateWarehouseDto, WarehouseDto } from '../dto'; + +@Entity() +export class Warehouse implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sectionId: number; + + @Column() + name: string; + + @Column() + createdBy: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, sectionId: number, name: string, createdBy: number, createdAt?: Date) { + this.accountId = accountId; + this.sectionId = sectionId; + this.name = name; + this.createdBy = createdBy; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _userRights: UserRights | null; + public get userRights(): UserRights { + return this._userRights ?? UserRights.full(); + } + public set userRights(value: UserRights | null) { + this._userRights = value; + } + + static getAuthorizable(warehouseId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Warehouse, id: warehouseId }); + } + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.Warehouse, + id: this.id, + createdBy: this.createdBy, + }; + } + + public static fromDto({ + accountId, + sectionId, + createdBy, + dto, + }: { + accountId: number; + sectionId: number; + createdBy: number; + dto: CreateWarehouseDto; + }): Warehouse { + return new Warehouse(accountId, sectionId, dto.name, createdBy); + } + + public update(dto: UpdateWarehouseDto): Warehouse { + this.name = dto.name !== undefined ? dto.name : this.name; + + return this; + } + + public toDto(): WarehouseDto { + return { + id: this.id, + sectionId: this.sectionId, + name: this.name, + userRights: this.userRights, + }; + } +} diff --git a/backend/src/modules/inventory/warehouse/warehouse.controller.ts b/backend/src/modules/inventory/warehouse/warehouse.controller.ts new file mode 100644 index 0000000..6e24c30 --- /dev/null +++ b/backend/src/modules/inventory/warehouse/warehouse.controller.ts @@ -0,0 +1,70 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { WarehouseDto, CreateWarehouseDto, UpdateWarehouseDto } from './dto'; +import { WarehouseService } from './warehouse.service'; + +@ApiTags('inventory/warehouses') +@Controller('products/sections/:sectionId/warehouses') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class WarehouseController { + constructor(private readonly service: WarehouseService) {} + + @ApiOperation({ summary: 'Create warehouse', description: 'Create warehouse in inventory section' }) + @ApiParam({ name: 'sectionId', type: Number, required: true, description: 'Section ID' }) + @ApiBody({ type: CreateWarehouseDto, required: true, description: 'Warehouse data' }) + @ApiCreatedResponse({ description: 'Warehouse', type: WarehouseDto }) + @Post() + public async create( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Body() dto: CreateWarehouseDto, + ) { + return this.service.create({ accountId, user, sectionId, dto }); + } + + @ApiOperation({ summary: 'Get warehouses', description: 'Get available warehouses in inventory section' }) + @ApiParam({ name: 'sectionId', type: Number, required: true, description: 'Section ID' }) + @ApiOkResponse({ description: 'Warehouses', type: [WarehouseDto] }) + @Get() + public async findMany( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + ) { + return this.service.findMany({ user, filter: { accountId, sectionId } }); + } + + @ApiOperation({ summary: 'Update warehouse', description: 'Update warehouse in inventory section' }) + @ApiParam({ name: 'sectionId', type: Number, required: true, description: 'Section ID' }) + @ApiParam({ name: 'warehouseId', type: Number, required: true, description: 'Warehouse ID' }) + @ApiBody({ type: UpdateWarehouseDto, required: true, description: 'Warehouse data' }) + @ApiOkResponse({ description: 'Warehouse', type: WarehouseDto }) + @Put(':warehouseId') + public async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('warehouseId', ParseIntPipe) warehouseId: number, + @Body() dto: UpdateWarehouseDto, + ) { + return this.service.update({ accountId, user, sectionId, warehouseId, dto }); + } + + @ApiOperation({ summary: 'Delete warehouse', description: 'Delete warehouse in inventory section' }) + @ApiParam({ name: 'sectionId', type: Number, required: true, description: 'Section ID' }) + @ApiParam({ name: 'warehouseId', type: Number, required: true, description: 'Warehouse ID' }) + @ApiQuery({ name: 'newWarehouseId', type: Number, required: false, description: 'New warehouse ID' }) + @ApiOkResponse() + @Delete(':warehouseId') + public async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('sectionId', ParseIntPipe) sectionId: number, + @Param('warehouseId', ParseIntPipe) warehouseId: number, + @Query('newWarehouseId') newWarehouseId?: number, + ) { + return this.service.delete({ accountId, user, sectionId, warehouseId, newWarehouseId }); + } +} diff --git a/backend/src/modules/inventory/warehouse/warehouse.module.ts b/backend/src/modules/inventory/warehouse/warehouse.module.ts new file mode 100644 index 0000000..3057452 --- /dev/null +++ b/backend/src/modules/inventory/warehouse/warehouse.module.ts @@ -0,0 +1,30 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { OrderModule } from '../order/order.module'; +import { ProductStockModule } from '../product-stock/product-stock.module'; +import { ReservationModule } from '../reservation/reservation.module'; +import { ShipmentModule } from '../shipment/shipment.module'; +import { RentalOrderModule } from '../rental-order/rental-order.module'; + +import { Warehouse } from './entities/warehouse.entity'; +import { WarehouseService } from './warehouse.service'; +import { WarehouseController } from './warehouse.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Warehouse]), + IAMModule, + forwardRef(() => OrderModule), + forwardRef(() => ProductStockModule), + ReservationModule, + ShipmentModule, + forwardRef(() => RentalOrderModule), + ], + controllers: [WarehouseController], + providers: [WarehouseService], + exports: [WarehouseService], +}) +export class WarehouseModule {} diff --git a/backend/src/modules/inventory/warehouse/warehouse.service.ts b/backend/src/modules/inventory/warehouse/warehouse.service.ts new file mode 100644 index 0000000..65676bd --- /dev/null +++ b/backend/src/modules/inventory/warehouse/warehouse.service.ts @@ -0,0 +1,170 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { ForbiddenError } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { OrderService } from '../order/services/order.service'; +import { ProductsSection } from '../products-section/entities'; +import { ProductStockService } from '../product-stock/product-stock.service'; +import { RentalOrderService } from '../rental-order/services/rental-order.service'; + +import { CreateWarehouseDto, UpdateWarehouseDto } from './dto'; +import { Warehouse } from './entities'; + +interface FindFilter { + accountId: number; + sectionId?: number; + warehouseId?: number | number[]; + onlyAvailable?: boolean; +} + +@Injectable() +export class WarehouseService { + constructor( + @InjectRepository(Warehouse) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + @Inject(forwardRef(() => OrderService)) + private readonly orderService: OrderService, + @Inject(forwardRef(() => ProductStockService)) + private readonly stockService: ProductStockService, + @Inject(forwardRef(() => RentalOrderService)) + private readonly rentalOrderService: RentalOrderService, + ) {} + + public async create({ + accountId, + user, + sectionId, + dto, + }: { + accountId: number; + user: User; + sectionId: number; + dto: CreateWarehouseDto; + }): Promise { + await this.authService.check({ + action: 'create', + user, + authorizable: ProductsSection.getAuthorizable(sectionId), + throwError: true, + }); + + return this.repository.save(Warehouse.fromDto({ accountId, sectionId, createdBy: user.id, dto })); + } + + public async findOne({ user, filter }: { user: User; filter: FindFilter }): Promise { + if (filter.sectionId) { + await this.authService.check({ + action: 'view', + user, + authorizable: ProductsSection.getAuthorizable(filter.sectionId), + throwError: true, + }); + } + + const warehouse = await this.createFindQb(filter).getOne(); + if (warehouse) warehouse.userRights = await this.authService.getUserRights({ user, authorizable: warehouse }); + + return filter.onlyAvailable && !warehouse.userRights.canView ? null : warehouse; + } + public async findMany({ user, filter }: { user: User; filter: FindFilter }): Promise { + if (filter.sectionId) { + await this.authService.check({ + action: 'view', + user, + authorizable: ProductsSection.getAuthorizable(filter.sectionId), + throwError: true, + }); + } + + const warehouses = await this.createFindQb(filter).orderBy({ name: 'ASC' }).getMany(); + await Promise.all( + warehouses.map( + async (warehouse) => + (warehouse.userRights = await this.authService.getUserRights({ user, authorizable: warehouse })), + ), + ); + + return filter.onlyAvailable ? warehouses.filter((warehouse) => warehouse.userRights.canView) : warehouses; + } + + public async update({ + accountId, + user, + sectionId, + warehouseId, + dto, + }: { + accountId: number; + user: User; + sectionId: number; + warehouseId: number; + dto: UpdateWarehouseDto; + }): Promise { + await this.authService.check({ + action: 'edit', + user, + authorizable: ProductsSection.getAuthorizable(sectionId), + throwError: true, + }); + + const warehouse = await this.findOne({ user, filter: { accountId, sectionId, warehouseId } }); + if (!warehouse.userRights.canEdit) { + throw new ForbiddenError(); + } + + await this.repository.save(warehouse.update(dto)); + + return warehouse; + } + + public async delete({ + accountId, + user, + sectionId, + warehouseId, + newWarehouseId, + }: { + accountId: number; + user: User; + sectionId: number; + warehouseId: number; + newWarehouseId?: number; + }): Promise { + await this.authService.check({ + action: 'delete', + user, + authorizable: ProductsSection.getAuthorizable(sectionId), + throwError: true, + }); + + const warehouse = await this.findOne({ user, filter: { accountId, sectionId, warehouseId } }); + if (!warehouse.userRights.canDelete) { + throw new ForbiddenError(); + } + + await this.orderService.delete(accountId, { sectionId, warehouseId }, { newWarehouseId }); + await this.stockService.delete({ accountId, warehouseId, newWarehouseId }); + await this.rentalOrderService.delete(accountId, user, { sectionId, warehouseId }, { newWarehouseId }); + + await this.repository.delete({ accountId, sectionId, id: warehouseId }); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder().where({ accountId: filter.accountId }); + if (filter.sectionId) qb.andWhere({ sectionId: filter.sectionId }); + if (filter.warehouseId) { + if (Array.isArray(filter.warehouseId)) { + qb.andWhere({ id: In(filter.warehouseId) }); + } else { + qb.andWhere({ id: filter.warehouseId }); + } + } + return qb; + } +} diff --git a/backend/src/modules/mail/mail-message-scheduled/dto/create-mail-message-scheduled.dto.ts b/backend/src/modules/mail/mail-message-scheduled/dto/create-mail-message-scheduled.dto.ts new file mode 100644 index 0000000..cce42a0 --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/dto/create-mail-message-scheduled.dto.ts @@ -0,0 +1,12 @@ +import { PickType } from '@nestjs/swagger'; + +import { MailMessageScheduledDto } from './mail-message-scheduled.dto'; + +export class CreateMailMessageScheduledDto extends PickType(MailMessageScheduledDto, [ + 'mailboxId', + 'sendFrom', + 'subject', + 'content', + 'sendTo', + 'entityId', +] as const) {} diff --git a/backend/src/modules/mail/mail-message-scheduled/dto/index.ts b/backend/src/modules/mail/mail-message-scheduled/dto/index.ts new file mode 100644 index 0000000..c8ebe9b --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-mail-message-scheduled.dto'; +export * from './mail-message-scheduled-filter.dto'; +export * from './mail-message-scheduled.dto'; diff --git a/backend/src/modules/mail/mail-message-scheduled/dto/mail-message-scheduled-filter.dto.ts b/backend/src/modules/mail/mail-message-scheduled/dto/mail-message-scheduled-filter.dto.ts new file mode 100644 index 0000000..6ac9e95 --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/dto/mail-message-scheduled-filter.dto.ts @@ -0,0 +1,49 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +export class MailMessageScheduledFilterDto { + @ApiPropertyOptional({ description: 'Mailbox IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + mailboxId?: number[]; + + @ApiPropertyOptional({ description: 'User IDs who sent the message' }) + @IsOptional() + @IsNumber({}, { each: true }) + sendFrom?: number[]; + + @ApiPropertyOptional({ description: 'Entity IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + entityId?: number[]; + + @ApiPropertyOptional({ description: 'Is the message sent' }) + @IsOptional() + @IsBoolean() + isSent?: boolean; + + @ApiPropertyOptional({ description: 'Date and time when the message was sent' }) + @IsOptional() + sentAt?: DatePeriodFilter; + + @ApiPropertyOptional({ description: 'Date and time when the message was created' }) + @IsOptional() + createdAt?: DatePeriodFilter; + + @ApiPropertyOptional({ description: 'Message subject' }) + @IsOptional() + @IsString() + subject?: string; + + @ApiPropertyOptional({ description: 'Message content' }) + @IsOptional() + @IsString() + content?: string; + + @ApiPropertyOptional({ description: 'Recipient email address' }) + @IsOptional() + @IsString() + sentTo?: string; +} diff --git a/backend/src/modules/mail/mail-message-scheduled/dto/mail-message-scheduled.dto.ts b/backend/src/modules/mail/mail-message-scheduled/dto/mail-message-scheduled.dto.ts new file mode 100644 index 0000000..1df8b09 --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/dto/mail-message-scheduled.dto.ts @@ -0,0 +1,41 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class MailMessageScheduledDto { + @ApiProperty({ description: 'Scheduled message ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'User ID who sent the message' }) + @IsNumber() + sendFrom: number; + + @ApiProperty({ description: 'Date and time when the message was created' }) + @IsString() + createdAt: string; + + @ApiProperty({ description: 'Mailbox ID' }) + @IsNumber() + mailboxId: number; + + @ApiProperty({ description: 'Message subject' }) + @IsString() + subject: string; + + @ApiProperty({ description: 'Message content' }) + @IsString() + content: string; + + @ApiProperty({ description: 'Array of recipient email addresses' }) + @IsString({ each: true }) + sendTo: string[]; + + @ApiPropertyOptional({ description: 'Entity ID associated with the message' }) + @IsNumber() + entityId: number; + + @ApiPropertyOptional({ description: 'Date and time when the message was sent', nullable: true }) + @IsOptional() + @IsNumber() + sentAt?: string | null; +} diff --git a/backend/src/modules/mail/mail-message-scheduled/entities/index.ts b/backend/src/modules/mail/mail-message-scheduled/entities/index.ts new file mode 100644 index 0000000..fa6155a --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/entities/index.ts @@ -0,0 +1 @@ +export * from './mail-message-scheduled.entity'; diff --git a/backend/src/modules/mail/mail-message-scheduled/entities/mail-message-scheduled.entity.ts b/backend/src/modules/mail/mail-message-scheduled/entities/mail-message-scheduled.entity.ts new file mode 100644 index 0000000..be2e90d --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/entities/mail-message-scheduled.entity.ts @@ -0,0 +1,84 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { CreateMailMessageScheduledDto, MailMessageScheduledDto } from '../dto'; + +@Entity() +export class MailMessageScheduled { + @PrimaryGeneratedColumn('identity') + id: number | undefined; + + @Column() + accountId: number; + + @Column() + sendFrom: number; + + @Column() + createdAt: Date; + + @Column() + mailboxId: number; + + @Column() + subject: string; + + @Column() + content: string; + + @Column({ type: 'simple-array' }) + sendTo: string[]; + + @Column() + entityId: number; + + @Column({ nullable: true }) + sentAt: Date | null; + + constructor( + accountId: number, + sendFrom: number, + mailboxId: number, + subject: string, + content: string, + sendTo: string[], + entityId: number, + createdAt: Date = new Date(), + sentAt: Date | null = null, + ) { + this.accountId = accountId; + this.sendFrom = sendFrom; + this.createdAt = createdAt; + this.mailboxId = mailboxId; + this.subject = subject; + this.content = content; + this.sendTo = sendTo; + this.entityId = entityId; + this.sentAt = sentAt; + } + + public static fromDto(accountId: number, dto: CreateMailMessageScheduledDto): MailMessageScheduled { + return new MailMessageScheduled( + accountId, + dto.sendFrom, + dto.mailboxId, + dto.subject, + dto.content, + dto.sendTo, + dto.entityId, + ); + } + + public toDto(): MailMessageScheduledDto { + return { + id: this.id, + sendFrom: this.sendFrom, + createdAt: this.createdAt.toISOString(), + mailboxId: this.mailboxId, + subject: this.subject, + content: this.content, + sendTo: this.sendTo, + entityId: this.entityId, + sentAt: this.sentAt?.toISOString(), + }; + } +} diff --git a/backend/src/modules/mail/mail-message-scheduled/index.ts b/backend/src/modules/mail/mail-message-scheduled/index.ts new file mode 100644 index 0000000..5a00c3c --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/index.ts @@ -0,0 +1 @@ +export * from './mail-message-scheduled.module'; diff --git a/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.controller.ts b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.controller.ts new file mode 100644 index 0000000..01c8c99 --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.controller.ts @@ -0,0 +1,67 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CreateMailMessageScheduledDto, MailMessageScheduledDto, MailMessageScheduledFilterDto } from './dto'; +import { MailMessageScheduledService } from './mail-message-scheduled.service'; + +@ApiTags('mail/scheduled') +@Controller('mail/scheduled') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class MailMessageScheduledController { + constructor(private readonly service: MailMessageScheduledService) {} + + @ApiOperation({ summary: 'Create scheduled message', description: 'Create scheduled message' }) + @ApiBody({ description: 'Data for creating scheduled message', type: CreateMailMessageScheduledDto }) + @ApiCreatedResponse({ description: 'Scheduled message', type: MailMessageScheduledDto }) + @Post() + async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateMailMessageScheduledDto) { + return this.service.create({ accountId, dto }); + } + + @ApiOperation({ summary: 'Get scheduled messages', description: 'Get scheduled messages' }) + @ApiOkResponse({ description: 'Scheduled messages', type: [MailMessageScheduledDto] }) + @Get() + async findMany(@CurrentAuth() { accountId }: AuthData, @Query() paging: PagingQuery) { + return this.service.findMany({ filter: { accountId }, paging }); + } + + @ApiOperation({ summary: 'Get scheduled message', description: 'Get scheduled message' }) + @ApiParam({ name: 'messageId', description: 'Scheduled message ID' }) + @ApiOkResponse({ description: 'Scheduled message', type: MailMessageScheduledDto }) + @Get(':messageId') + async findOne(@CurrentAuth() { accountId }: AuthData, @Param('messageId', ParseIntPipe) messageId: number) { + return this.service.findOne({ accountId, messageId }); + } + + @ApiOperation({ summary: 'Search scheduled messages', description: 'Search scheduled messages' }) + @ApiBody({ description: 'Data for searching scheduled messages', type: MailMessageScheduledFilterDto }) + @ApiOkResponse({ description: 'Scheduled messages', type: [MailMessageScheduledDto] }) + @Post('search') + async search( + @CurrentAuth() { accountId }: AuthData, + @Body() filter: MailMessageScheduledFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.findMany({ filter: { accountId, ...filter }, paging }); + } + + @ApiOperation({ summary: 'Delete scheduled message', description: 'Delete scheduled message' }) + @ApiParam({ name: 'messageId', description: 'Scheduled message ID' }) + @ApiOkResponse() + @Delete(':messageId') + async delete(@CurrentAuth() { accountId }: AuthData, @Param('messageId', ParseIntPipe) messageId: number) { + return this.service.delete({ accountId, messageId }); + } + + @ApiOperation({ summary: 'Delete scheduled messages', description: 'Delete scheduled messages' }) + @ApiBody({ description: 'Data for deleting scheduled messages', type: MailMessageScheduledFilterDto }) + @ApiOkResponse() + @Post('delete') + async deleteMany(@CurrentAuth() { accountId }: AuthData, @Body() filter: MailMessageScheduledFilterDto) { + return this.service.delete({ accountId, ...filter }); + } +} diff --git a/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.handler.ts b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.handler.ts new file mode 100644 index 0000000..44a6716 --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.handler.ts @@ -0,0 +1,69 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +import { ActionEmailSendSettings, AutomationJob, EntityTypeActionType, OnAutomationJob } from '@/modules/automation'; +import { MailboxEvent, MailEventType } from '@/Mailing/common'; + +import { MailMessageScheduledService } from './mail-message-scheduled.service'; + +interface EntityVariables { + id?: number | null; + stageId?: number | null; +} + +interface SendEmailVariables { + accountId?: number | null; + entity?: EntityVariables | null; + emailSettings?: ActionEmailSendSettings | null; +} + +@Injectable() +export class MailMessageScheduledHandler { + private readonly logger = new Logger(MailMessageScheduledHandler.name); + constructor(private readonly service: MailMessageScheduledService) {} + + @Cron(CronExpression.EVERY_MINUTE) + async sendScheduledMessages() { + if (process.env.SCHEDULE_MAIL_SCHEDULED_DISABLE === 'true') return; + this.logger.log('Before: Sending scheduled messages'); + const processed = await this.service.processMessages(); + this.logger.log(`After: Sending scheduled messages. Processed: ${processed}`); + } + + @OnEvent(MailEventType.MailboxDeleted, { async: true }) + async onMailboxDeleted(event: MailboxEvent) { + this.service.delete({ accountId: event.accountId, mailboxId: event.mailboxId, isSent: false }); + } + + /** + * @deprecated use new @see handleSendEmailJob instead + */ + @OnAutomationJob(EntityTypeActionType.SendEmail) + async handleSendEmailJobOld(job: AutomationJob): Promise<{ variables?: unknown }> { + return this.handleSendEmailJob(job); + } + @OnAutomationJob(EntityTypeActionType.EmailSend) + async handleSendEmailJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.emailSettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, emailSettings } = job.variables; + + const messages = await this.service.processAutomation({ + accountId, + entityId: entity.id, + entityStageId: entity.stageId, + settings: emailSettings, + }); + + return { variables: { ...job.variables, messages: messages?.map((message) => message.toDto()) } }; + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + return { variables: job.variables }; + } + } +} diff --git a/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.module.ts b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.module.ts new file mode 100644 index 0000000..58f9853 --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { DocumentsModule } from '@/modules/documents/documents.module'; +import { CrmModule } from '@/CRM/crm.module'; +import { MailingModule } from '@/Mailing/MailingModule'; + +import { MailMessageScheduled } from './entities'; +import { MailMessageScheduledService } from './mail-message-scheduled.service'; +import { MailMessageScheduledController } from './mail-message-scheduled.controller'; +import { MailMessageScheduledHandler } from './mail-message-scheduled.handler'; + +@Module({ + imports: [TypeOrmModule.forFeature([MailMessageScheduled]), IAMModule, MailingModule, CrmModule, DocumentsModule], + providers: [MailMessageScheduledService, MailMessageScheduledHandler], + controllers: [MailMessageScheduledController], +}) +export class MailMessageScheduledModule {} diff --git a/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.service.ts b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.service.ts new file mode 100644 index 0000000..5fd2c6f --- /dev/null +++ b/backend/src/modules/mail/mail-message-scheduled/mail-message-scheduled.service.ts @@ -0,0 +1,369 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import Handlebars from 'handlebars'; + +import { DatePeriod, DatePeriodFilter, DateUtil, isUnique, PagingQuery } from '@/common'; +import { ActionEmailSendSettings } from '@/modules/automation'; + +import { EntityCategory } from '@/CRM/common'; +import { EntityTypeService } from '@/CRM/entity-type/entity-type.service'; +import { Entity } from '@/CRM/Model/Entity/Entity'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { Mailbox } from '@/Mailing/mailbox/entities'; +import { MailboxService as MailboxSettingsService } from '@/Mailing/mailbox/services'; +import { MailboxService } from '@/Mailing/Service/Mailbox/MailboxService'; +import { FieldType } from '@/modules/entity/entity-field/common'; +import { FieldService } from '@/modules/entity/entity-field/field'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value'; +import { DocumentGenerationService } from '@/modules/documents/document-generation/document-generation.service'; + +import { MailMessageScheduled } from './entities'; +import { CreateMailMessageScheduledDto } from './dto'; + +interface FindFilter { + accountId: number; + messageId?: number | number[]; + mailboxId?: number | number[]; + sendFrom?: number | number[]; + entityId?: number | number[]; + isSent?: boolean; + sentAt?: DatePeriodFilter; + createdAt?: DatePeriodFilter; + subject?: string; + content?: string; + sentTo?: string; +} + +interface MailboxMessage { + accountId: number; + mailboxId: number; + messageId: number; +} + +const DefaultLimit = 1000; + +@Injectable() +export class MailMessageScheduledService { + constructor( + @InjectRepository(MailMessageScheduled) + private readonly repository: Repository, + private readonly mailboxSettingsService: MailboxSettingsService, + private readonly mailboxService: MailboxService, + private readonly entityTypeService: EntityTypeService, + private readonly entityService: EntityService, + private readonly fieldService: FieldService, + private readonly fieldValueService: FieldValueService, + private readonly documentGenerationService: DocumentGenerationService, + ) {} + + async create({ + accountId, + dto, + }: { + accountId: number; + dto: CreateMailMessageScheduledDto; + }): Promise { + return this.repository.save(MailMessageScheduled.fromDto(accountId, dto)); + } + + async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + + async findMany({ filter, paging }: { filter: FindFilter; paging?: PagingQuery }): Promise { + return this.createFindQb(filter).orderBy('created_at', 'DESC').limit(paging.take).offset(paging.skip).getMany(); + } + + async delete(filter: FindFilter): Promise { + await this.createFindQb(filter).delete().execute(); + } + + async processMessages(): Promise { + const queue = await this.repository + .createQueryBuilder() + .select('account_id', 'accountId') + .addSelect('mailbox_id', 'mailboxId') + .addSelect('min(id)', 'messageId') + .where('sent_at is null') + .groupBy('account_id') + .addGroupBy('mailbox_id') + .getRawMany(); + + let processed = 0; + for (const item of queue) { + if (item.messageId) { + if (await this.processMessage(item)) { + processed++; + } + } + } + return processed; + } + + private async processMessage({ accountId, mailboxId, messageId }: MailboxMessage): Promise { + const maxSendAt = await this.repository + .createQueryBuilder() + .select('max(sent_at)', 'max') + .where('account_id = :accountId', { accountId }) + .andWhere(`mailbox_id = :mailboxId`, { mailboxId }) + .andWhere('sent_at is not null') + .getRawOne<{ sentAt: Date }>(); + + const mailbox = await this.mailboxSettingsService.findOne({ accountId, mailboxId }); + if (!maxSendAt?.sentAt || this.canSend({ lastSentAt: maxSendAt.sentAt, limit: mailbox.emailsPerDay })) { + this.sendMessage({ accountId, mailbox, messageId }); + return true; + } + return false; + } + + private canSend({ lastSentAt, limit }: { lastSentAt: Date; limit?: number | null }): boolean { + const secondsPerEmail = 86400 / (limit ?? DefaultLimit); + const diff = DateUtil.diff({ startDate: lastSentAt, endDate: DateUtil.now(), unit: 'second' }); + + return diff > secondsPerEmail; + } + + private async sendMessage({ + accountId, + mailbox, + messageId, + }: { + accountId: number; + mailbox: Mailbox; + messageId: number; + }) { + const message = await this.findOne({ accountId, messageId }); + + await this.mailboxService.sendMessageForMailbox( + accountId, + mailbox, + { + sentTo: message.sendTo, + subject: message.subject, + contentText: null, + contentHtml: message.content, + entityId: message.entityId, + cc: null, + bcc: null, + replyTo: null, + replyToMessageId: null, + fileIds: null, + }, + null, + message.sendFrom, + ); + + await this.repository.update({ accountId, id: messageId }, { sentAt: DateUtil.now() }); + } + + async processAutomation({ + accountId, + entityId, + entityStageId, + settings, + }: { + accountId: number; + entityId: number; + entityStageId: number | null | undefined; + settings: ActionEmailSendSettings; + }): Promise { + const messages: MailMessageScheduled[] = []; + + const entity = await this.entityService.findOne(accountId, { entityId }); + if (entity && (!entity.stageId || settings.allowAnyStage || entity.stageId === entityStageId)) { + const hasOptions = Boolean(settings.options); + if (!hasOptions || settings.options?.main) { + const entity = await this.entityService.findOne(accountId, { entityId }); + if (entity) { + const message = await this.createAutomationMessage({ + accountId, + entity, + settings, + onlyFirst: Boolean(settings.options?.main?.onlyFirstValue), + }); + if (message) { + messages.push(message); + } + } + } + + let primaryCompany = Boolean(settings.options?.company?.onlyFirstEntity); + let primaryContact = Boolean(settings.options?.contact?.onlyFirstEntity); + const entities = await this.entityService.findLinkedEntities({ accountId, entityId }); + for (const entity of entities) { + const entityType = await this.entityTypeService.findOne(accountId, { id: entity.entityTypeId }); + let send = [EntityCategory.CONTACT, EntityCategory.COMPANY].includes(entityType.entityCategory); + let onlyFirst = false; + if (send && hasOptions) { + if (entityType.entityCategory === EntityCategory.COMPANY) { + send = settings.options.company && (!settings.options.company?.onlyFirstEntity || primaryCompany); + primaryCompany = false; + onlyFirst = Boolean(settings.options.company?.onlyFirstValue); + } else if (entityType.entityCategory === EntityCategory.CONTACT) { + send = settings.options.contact && (!settings.options.contact?.onlyFirstEntity || primaryContact); + primaryContact = false; + onlyFirst = Boolean(settings.options.contact?.onlyFirstValue); + } + } + + if (send) { + const message = await this.createAutomationMessage({ accountId, entity, settings, onlyFirst }); + if (message) { + messages.push(message); + } + } + } + } + + return messages; + } + + private async createAutomationMessage({ + accountId, + entity, + settings, + onlyFirst, + }: { + accountId: number; + entity: Entity; + settings: ActionEmailSendSettings; + onlyFirst: boolean; + }): Promise { + const fieldIds = await this.fieldService.findManyIds({ + accountId, + entityTypeId: entity.entityTypeId, + type: FieldType.Email, + }); + if (fieldIds.length) { + let sendTo: string[] = []; + if (onlyFirst) { + for (const fieldId of fieldIds) { + const fieldValue = await this.fieldValueService.findOne({ accountId, entityId: entity.id, fieldId }); + if (fieldValue) { + const value = fieldValue + .getValue() + .filter((email) => email !== '') + .filter(isUnique); + if (value.length) { + sendTo = [value[0]]; + break; + } + } + } + } else { + const fieldValues = await this.fieldValueService.findMany({ + accountId, + entityId: entity.id, + fieldId: fieldIds, + }); + sendTo = fieldValues + .map((fv) => fv.getValue()) + .flat() + .filter((email) => email !== '') + .filter(isUnique); + } + + if (sendTo.length) { + const data = await this.documentGenerationService.getDataForGeneration({ accountId, entityId: entity.id }); + data['contact_id'] = entity.id; + data['contact_name'] = entity.name; + const subject = Handlebars.compile(settings.subject)(data); + const content = Handlebars.compile(settings.content + (settings.signature ?? ''))(data); + + return this.create({ + accountId, + dto: { + mailboxId: settings.mailboxId, + sendFrom: settings.userId ?? entity.responsibleUserId, + subject: subject, + content: content, + sendTo: sendTo, + entityId: entity.id, + }, + }); + } + } + + return null; + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId: filter.accountId }); + + if (filter?.messageId) { + if (Array.isArray(filter.messageId)) { + qb.andWhere('id IN (:...ids)', { ids: filter.messageId }); + } else { + qb.andWhere('id = :id', { id: filter.messageId }); + } + } + + if (filter?.mailboxId) { + if (Array.isArray(filter.mailboxId)) { + qb.andWhere('mailbox_id IN (:...mailboxIds)', { mailboxIds: filter.mailboxId }); + } else { + qb.andWhere('mailbox_id = :mailboxId', { mailboxId: filter.mailboxId }); + } + } + + if (filter?.sendFrom) { + if (Array.isArray(filter.sendFrom)) { + qb.andWhere('send_from IN (:...sendFrom)', { sendFrom: filter.sendFrom }); + } else { + qb.andWhere('send_from = :sendFrom', { sendFrom: filter.sendFrom }); + } + } + + if (filter?.entityId) { + if (Array.isArray(filter.entityId)) { + qb.andWhere('entity_id IN (:...entityIds)', { entityIds: filter.entityId }); + } else { + qb.andWhere('entity_id = :entityId', { entityId: filter.entityId }); + } + } + + if (filter?.isSent !== undefined) { + if (filter.isSent) { + qb.andWhere('sent_at IS NOT NULL'); + } else { + qb.andWhere('sent_at IS NULL'); + } + } + + if (filter.sentAt) { + const dates = DatePeriod.fromFilter(filter.sentAt); + if (dates.from) { + qb.andWhere('sent_at >= :sentAtFrom', { sentAtFrom: dates.from }); + } + if (dates.to) { + qb.andWhere('sent_at <= :sentAtTo', { sentAtTo: dates.to }); + } + } + + if (filter.createdAt) { + const dates = DatePeriod.fromFilter(filter.createdAt); + if (dates.from) { + qb.andWhere('created_at >= :createdAtFrom', { createdAtFrom: dates.from }); + } + if (dates.to) { + qb.andWhere('created_at <= :createdAtTo', { createdAtTo: dates.to }); + } + } + + if (filter.subject) { + qb.andWhere('subject ILIKE :subject', { subject: `%${filter.subject}%` }); + } + + if (filter.content) { + qb.andWhere('content ILIKE :content', { content: `%${filter.content}%` }); + } + + if (filter.sentTo) { + qb.andWhere('send_to ILIKE :sentTo', { sentTo: `%${filter.sentTo}%` }); + } + + return qb; + } +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/config/imapflow.config.ts b/backend/src/modules/mail/mail-providers/imapflow/config/imapflow.config.ts new file mode 100644 index 0000000..167bcbb --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/config/imapflow.config.ts @@ -0,0 +1,16 @@ +import { registerAs } from '@nestjs/config'; + +export interface ImapflowConfig { + searchTimeout: number; + searchBatchSize: number; + partLoadTimeout: number; +} + +export default registerAs( + 'imapflow', + (): ImapflowConfig => ({ + searchTimeout: parseInt(process.env.MAIL_IMAPFLOW_SEARCH_TIMEOUT, 10) || 30000, + searchBatchSize: parseInt(process.env.MAIL_IMAPFLOW_SEARCH_BATCH_SIZE, 10) || 100, + partLoadTimeout: parseInt(process.env.MAIL_IMAPFLOW_PART_LOAD_TIMEOUT, 10) || 15000, + }), +); diff --git a/backend/src/modules/mail/mail-providers/imapflow/config/index.ts b/backend/src/modules/mail/mail-providers/imapflow/config/index.ts new file mode 100644 index 0000000..0f0ee3e --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/config/index.ts @@ -0,0 +1 @@ +export * from './imapflow.config'; diff --git a/backend/src/modules/mail/mail-providers/imapflow/dto/create-mailbox-imapflow.dto.ts b/backend/src/modules/mail/mail-providers/imapflow/dto/create-mailbox-imapflow.dto.ts new file mode 100644 index 0000000..aaea845 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/dto/create-mailbox-imapflow.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty, OmitType } from '@nestjs/swagger'; +import { IsDefined, ValidateNested } from 'class-validator'; + +import { CreateMailboxDto } from '@/Mailing/mailbox/dto'; +import { CreateMailboxSettingsImapflowDto } from './create-mailbox-settings-imapflow.dto'; + +export class CreateMailboxImapflowDto extends OmitType(CreateMailboxDto, ['provider'] as const) { + @ApiProperty({ type: CreateMailboxSettingsImapflowDto, description: 'Imapflow Settings' }) + @IsDefined() + @ValidateNested() + settings: CreateMailboxSettingsImapflowDto; +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/dto/create-mailbox-settings-imapflow.dto.ts b/backend/src/modules/mail/mail-providers/imapflow/dto/create-mailbox-settings-imapflow.dto.ts new file mode 100644 index 0000000..7e5b322 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/dto/create-mailbox-settings-imapflow.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { MailboxSettingsImapflowDto } from './mailbox-settings-imapflow.dto'; + +export class CreateMailboxSettingsImapflowDto extends PickType(MailboxSettingsImapflowDto, [ + 'imapServer', + 'imapPort', + 'imapSecure', + 'smtpServer', + 'smtpPort', + 'smtpSecure', +] as const) { + @ApiProperty({ description: 'Mail password' }) + @IsString() + password: string; +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/dto/index.ts b/backend/src/modules/mail/mail-providers/imapflow/dto/index.ts new file mode 100644 index 0000000..4a0d857 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/dto/index.ts @@ -0,0 +1,6 @@ +export * from './create-mailbox-imapflow.dto'; +export * from './create-mailbox-settings-imapflow.dto'; +export * from './mailbox-imapflow.dto'; +export * from './mailbox-settings-imapflow.dto'; +export * from './update-mailbox-imapflow.dto'; +export * from './update-mailbox-settings-imapflow.dto'; diff --git a/backend/src/modules/mail/mail-providers/imapflow/dto/mailbox-imapflow.dto.ts b/backend/src/modules/mail/mail-providers/imapflow/dto/mailbox-imapflow.dto.ts new file mode 100644 index 0000000..5175423 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/dto/mailbox-imapflow.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { MailboxDto } from '@/Mailing/mailbox/dto/mailbox.dto'; +import { MailboxSettingsImapflowDto } from './mailbox-settings-imapflow.dto'; + +export class MailboxImapflowDto extends MailboxDto { + @ApiProperty({ type: MailboxSettingsImapflowDto, description: 'Imapflow Settings' }) + settings: MailboxSettingsImapflowDto; +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/dto/mailbox-settings-imapflow.dto.ts b/backend/src/modules/mail/mail-providers/imapflow/dto/mailbox-settings-imapflow.dto.ts new file mode 100644 index 0000000..b9e5b0a --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/dto/mailbox-settings-imapflow.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsString } from 'class-validator'; + +export class MailboxSettingsImapflowDto { + @ApiProperty({ description: 'IMAP server address' }) + @IsString() + imapServer: string; + + @ApiProperty({ description: 'IMAP server port' }) + @IsNumber() + imapPort: number; + + @ApiProperty({ description: 'IMAP server secure' }) + @IsBoolean() + imapSecure: boolean; + + @ApiProperty({ description: 'SMTP server address' }) + @IsString() + smtpServer: string; + + @ApiProperty({ description: 'SMTP server port' }) + @IsNumber() + smtpPort: number; + + @ApiProperty({ description: 'SMTP server secure' }) + @IsBoolean() + smtpSecure: boolean; +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/dto/update-mailbox-imapflow.dto.ts b/backend/src/modules/mail/mail-providers/imapflow/dto/update-mailbox-imapflow.dto.ts new file mode 100644 index 0000000..b821ad0 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/dto/update-mailbox-imapflow.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; + +import { UpdateMailboxDto } from '@/Mailing/mailbox/dto'; +import { UpdateMailboxSettingsImapflowDto } from './update-mailbox-settings-imapflow.dto'; + +export class UpdateMailboxImapflowDto extends UpdateMailboxDto { + @ApiPropertyOptional({ type: UpdateMailboxSettingsImapflowDto, description: 'Imapflow Settings' }) + settings?: UpdateMailboxSettingsImapflowDto; +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/dto/update-mailbox-settings-imapflow.dto.ts b/backend/src/modules/mail/mail-providers/imapflow/dto/update-mailbox-settings-imapflow.dto.ts new file mode 100644 index 0000000..0027a54 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/dto/update-mailbox-settings-imapflow.dto.ts @@ -0,0 +1,20 @@ +import { ApiPropertyOptional, PartialType, PickType } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +import { MailboxSettingsImapflowDto } from './mailbox-settings-imapflow.dto'; + +export class UpdateMailboxSettingsImapflowDto extends PartialType( + PickType(MailboxSettingsImapflowDto, [ + 'imapServer', + 'imapPort', + 'imapSecure', + 'smtpServer', + 'smtpPort', + 'smtpSecure', + ] as const), +) { + @ApiPropertyOptional({ description: 'Mail password' }) + @IsOptional() + @IsString() + password?: string; +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/entities/index.ts b/backend/src/modules/mail/mail-providers/imapflow/entities/index.ts new file mode 100644 index 0000000..fb28fb1 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/entities/index.ts @@ -0,0 +1 @@ +export * from './mailbox-settings-imapflow.entity'; diff --git a/backend/src/modules/mail/mail-providers/imapflow/entities/mailbox-settings-imapflow.entity.ts b/backend/src/modules/mail/mail-providers/imapflow/entities/mailbox-settings-imapflow.entity.ts new file mode 100644 index 0000000..5d469c2 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/entities/mailbox-settings-imapflow.entity.ts @@ -0,0 +1,107 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { CreateMailboxSettingsImapflowDto, MailboxSettingsImapflowDto, UpdateMailboxSettingsImapflowDto } from '../dto'; +import { ImapflowSyncInfo } from '../types'; + +@Entity() +export class MailboxSettingsImapflow { + @PrimaryColumn() + mailboxId: number; + + @Column() + password: string; + + @Column() + imapServer: string; + + @Column() + imapPort: number; + + @Column() + imapSecure: boolean; + + @Column() + smtpServer: string; + + @Column() + smtpPort: number; + + @Column() + smtpSecure: boolean; + + @Column({ type: 'jsonb', nullable: true }) + syncInfo: ImapflowSyncInfo[] | null; + + @Column() + accountId: number; + + constructor( + accountId: number, + mailboxId: number, + password: string, + imapServer: string, + imapPort: number, + imapSecure: boolean, + smtpServer: string, + smtpPort: number, + smtpSecure: boolean, + syncInfo: ImapflowSyncInfo[] | null = null, + ) { + this.accountId = accountId; + this.mailboxId = mailboxId; + this.password = password; + this.imapServer = imapServer; + this.imapPort = imapPort; + this.imapSecure = imapSecure; + this.smtpServer = smtpServer; + this.smtpPort = smtpPort; + this.smtpSecure = smtpSecure; + this.syncInfo = syncInfo; + } + + static fromDto({ + accountId, + mailboxId, + dto, + }: { + accountId: number; + mailboxId: number; + dto: CreateMailboxSettingsImapflowDto; + }): MailboxSettingsImapflow { + return new MailboxSettingsImapflow( + accountId, + mailboxId, + dto.password, + dto.imapServer, + dto.imapPort, + dto.imapSecure, + dto.smtpServer, + dto.smtpPort, + dto.smtpSecure, + ); + } + + update(dto: UpdateMailboxSettingsImapflowDto & { syncInfo?: ImapflowSyncInfo[] | null }): MailboxSettingsImapflow { + this.password = dto.password !== undefined ? dto.password : this.password; + this.imapServer = dto.imapServer !== undefined ? dto.imapServer : this.imapServer; + this.imapPort = dto.imapPort !== undefined ? dto.imapPort : this.imapPort; + this.imapSecure = dto.imapSecure !== undefined ? dto.imapSecure : this.imapSecure; + this.smtpServer = dto.smtpServer !== undefined ? dto.smtpServer : this.smtpServer; + this.smtpPort = dto.smtpPort !== undefined ? dto.smtpPort : this.smtpPort; + this.smtpSecure = dto.smtpSecure !== undefined ? dto.smtpSecure : this.smtpSecure; + this.syncInfo = dto.syncInfo !== undefined ? dto.syncInfo : this.syncInfo; + + return this; + } + + toDto(): MailboxSettingsImapflowDto { + return { + imapServer: this.imapServer, + imapPort: this.imapPort, + imapSecure: this.imapSecure, + smtpServer: this.smtpServer, + smtpPort: this.smtpPort, + smtpSecure: this.smtpSecure, + }; + } +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/errors/index.ts b/backend/src/modules/mail/mail-providers/imapflow/errors/index.ts new file mode 100644 index 0000000..ca1bf11 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/errors/index.ts @@ -0,0 +1 @@ +export * from './mail-connection.error'; diff --git a/backend/src/modules/mail/mail-providers/imapflow/errors/mail-connection.error.ts b/backend/src/modules/mail/mail-providers/imapflow/errors/mail-connection.error.ts new file mode 100644 index 0000000..c81043d --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/errors/mail-connection.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class MailConnectionError extends ServiceError { + constructor(message = 'Connection error') { + super({ errorCode: 'mail.connection_error', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/imapflow.controller.ts b/backend/src/modules/mail/mail-providers/imapflow/imapflow.controller.ts new file mode 100644 index 0000000..00d9796 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/imapflow.controller.ts @@ -0,0 +1,55 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { DeleteMailboxQuery } from '@/Mailing/common'; +import { CreateMailboxImapflowDto, MailboxImapflowDto, UpdateMailboxImapflowDto } from './dto'; +import { ImapflowService } from './imapflow.service'; + +@ApiTags('mail/settings/imapflow') +@Controller('mail/settings/imapflow/mailboxes') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ImapflowController { + constructor(private readonly service: ImapflowService) {} + + @ApiOperation({ summary: 'Create mailbox', description: 'Create mailbox for imapflow provider' }) + @ApiBody({ type: CreateMailboxImapflowDto, required: true, description: 'Mailbox settings' }) + @ApiCreatedResponse({ description: 'Mailbox', type: MailboxImapflowDto }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateMailboxImapflowDto) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Get mailbox', description: 'Get mailbox for imapflow provider' }) + @ApiParam({ name: 'mailboxId', type: Number, required: true, description: 'Mailbox ID' }) + @ApiOkResponse({ description: 'Mailbox', type: MailboxImapflowDto }) + @AuthDataPrefetch({ user: true }) + @Get(':mailboxId') + async findMailbox(@CurrentAuth() { accountId, user }: AuthData, @Param('mailboxId', ParseIntPipe) mailboxId: number) { + return this.service.findMailbox({ accountId, mailboxId, ownerId: user.isAdmin ? undefined : user.id }); + } + + @ApiOperation({ summary: 'Update mailbox', description: 'Update mailbox for imapflow provider' }) + @ApiParam({ name: 'mailboxId', type: Number, required: true, description: 'Mailbox ID' }) + @ApiOkResponse({ description: 'Mailbox', type: MailboxImapflowDto }) + @Patch(':mailboxId') + async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Body() dto: UpdateMailboxImapflowDto, + ) { + return this.service.update({ accountId, user, mailboxId, dto }); + } + + @Delete(':mailboxId') + async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('mailboxId', ParseIntPipe) mailboxId: number, + @Query() query: DeleteMailboxQuery, + ) { + await this.service.delete({ accountId, user, mailboxId, softDelete: query?.save }); + } +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/imapflow.module.ts b/backend/src/modules/mail/mail-providers/imapflow/imapflow.module.ts new file mode 100644 index 0000000..51533ea --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/imapflow.module.ts @@ -0,0 +1,23 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { MailingModule } from '@/Mailing/MailingModule'; + +import imapflowConfig from './config/imapflow.config'; +import { MailboxSettingsImapflow } from './entities'; +import { ImapflowService } from './imapflow.service'; +import { ImapflowController } from './imapflow.controller'; + +@Module({ + imports: [ + ConfigModule.forFeature(imapflowConfig), + TypeOrmModule.forFeature([MailboxSettingsImapflow]), + IAMModule, + MailingModule, + ], + providers: [ImapflowService], + controllers: [ImapflowController], +}) +export class ImapflowModule {} diff --git a/backend/src/modules/mail/mail-providers/imapflow/imapflow.service.ts b/backend/src/modules/mail/mail-providers/imapflow/imapflow.service.ts new file mode 100644 index 0000000..663f4df --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/imapflow.service.ts @@ -0,0 +1,1076 @@ +import { Injectable, Logger, NotImplementedException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { + FetchMessageObject, + ImapFlow, + Logger as ImapFlowLogger, + ListTreeResponse, + MessageAddressObject, + MessageStructureObject, + SearchObject, +} from 'imapflow'; +import { AddressObject, ParsedMail, simpleParser, Source } from 'mailparser'; +import * as iconv from 'iconv-lite'; +import * as quotedPrintable from 'quoted-printable'; +import { convert } from 'html-to-text'; +import nodemailer from 'nodemailer'; +import Mail from 'nodemailer/lib/mailer'; +import { v4 as uuidv4 } from 'uuid'; + +import { DateUtil, NotFoundError, StringUtil, withTimeout } from '@/common'; + +import { User } from '@/modules/iam/user/entities'; +import { StorageFile } from '@/modules/storage/types'; +import { + detectMailboxFolderType, + EmailAddress, + EmailAddressValue, + FolderMessages, + MailboxFolderExternal, + MailboxFolderType, + MailboxSyncMessages, + MailboxSyncResult, + MailMessageAttachment, + MailMessageExternal, + MailMessagePayloadExternal, + SendMailMessageDto, +} from '@/Mailing/common'; +import { Mailbox, MailboxService } from '@/Mailing/mailbox'; +import { MailIntegration, MailProvider, MailProviderCapability } from '@/Mailing/mail-provider'; +import { MailMessagePayload } from '@/Mailing/mail-message-payload'; +import { MailboxFolderService } from '@/Mailing/mailbox-folder'; +import { MailMessageBuilderService, HEADER_ENTITY_ID } from '@/Mailing/mail-message-builder'; +import { MailMessage } from '@/Mailing/Model/MailMessage/MailMessage'; + +import { ImapflowConfig } from './config'; +import { CreateMailboxImapflowDto, UpdateMailboxImapflowDto } from './dto'; +import { MailboxSettingsImapflow } from './entities'; +import { MailConnectionError } from './errors'; +import { ImapflowSyncInfo, MailboxImapflow } from './types'; + +const ProviderName = 'imapflow'; + +interface FindFilter { + accountId: number; + mailboxId?: number; +} + +interface ImapflowConnectionParams { + email: string; + password: string; + imapServer: string; + imapPort: number; + imapSecure: boolean; + verifyOnly?: boolean; +} + +interface WithConnectionActionParams { + mailboxId?: number | null; + client: ImapFlow; + settings: MailboxSettingsImapflow; +} +interface WithConnectionErrorParams { + mailboxId?: number | null; + error: unknown; +} +interface WithConnectionParam { + email: string; + verifyOnly?: boolean; + action: (params: WithConnectionActionParams) => Promise; + error?: (params: WithConnectionErrorParams) => Promise; +} +interface WithConnectionParamMailbox extends WithConnectionParam { + accountId: number; + mailboxId: number; +} + +interface WithConnectionParamSettings extends WithConnectionParam { + settings: MailboxSettingsImapflow; +} + +interface WithBoxActionParams { + mailboxId?: number | null; + client: ImapFlow; + path: string; +} +interface WithBoxErrorParams { + mailboxId?: number | null; + error: Error; +} +interface WithBoxParams { + mailboxId: number; + client: ImapFlow; + path: string; + action: (params: WithBoxActionParams) => Promise; + error?: (params: WithBoxErrorParams) => R | null; +} + +interface ProcessMessagesResult { + result: boolean; + message?: string | null; + messages?: MailboxSyncMessages; + syncInfo?: ImapflowSyncInfo[]; +} +interface ProcessMessagesInFolderParams extends WithBoxActionParams { + folderUidNext: number; + syncUidNext: number; + syncDate: Date; +} +interface ProcessMessagesInFolderResult { + result: boolean; + message?: string | null; + syncInfo: ImapflowSyncInfo; + messages?: MailboxSyncMessages | null; +} + +interface SendMessageResult { + message: string; + uid?: number | null; +} + +// eslint-disable-next-line no-control-regex +const cleanString = (str: string) => str?.replace(/[\u0000\f]/g, '')?.trim(); +const concatAddress = (addresses: MessageAddressObject[]) => + addresses + .map((a) => `${a.name ? `${a.name} ` : `${a.address}`}${a.name && a.address ? `<${a.address}>` : ''}`) + .join(', '); + +@Injectable() +@MailIntegration(ProviderName) +export class ImapflowService implements MailProvider { + private readonly logger = new Logger(ImapflowService.name); + private readonly _config: ImapflowConfig; + + constructor( + private readonly configService: ConfigService, + @InjectRepository(MailboxSettingsImapflow) + private readonly repository: Repository, + private readonly mailboxService: MailboxService, + private readonly mailboxFolderService: MailboxFolderService, + private readonly mailMessageBuilder: MailMessageBuilderService, + ) { + this._config = this.configService.get('imapflow'); + } + + isCapable(capability: MailProviderCapability): boolean { + if (capability === 'thread') { + return false; + } + + return false; + } + + async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateMailboxImapflowDto; + }): Promise { + const mailbox = await this.mailboxService.create({ accountId, userId, dto: { ...dto, provider: ProviderName } }); + let settings = MailboxSettingsImapflow.fromDto({ accountId, mailboxId: mailbox.id, dto: dto.settings }); + const { result, message } = await this.verify({ email: mailbox.email, settings }); + if (result) { + settings = await this.repository.save(settings); + return new MailboxImapflow({ mailbox, settings }); + } + throw new MailConnectionError(message); + } + + async findOne(filter: FindFilter): Promise { + return this.createQb(filter).getOne(); + } + + async findMailbox(filter: FindFilter & { ownerId?: number }): Promise { + const mailbox = await this.mailboxService.findOne({ ...filter, provider: ProviderName }); + if (mailbox) { + const settings = await this.findOne(filter); + return new MailboxImapflow({ mailbox, settings }); + } + return null; + } + + async update({ + accountId, + user, + mailboxId, + dto, + }: { + accountId: number; + user: User; + mailboxId: number; + dto: UpdateMailboxImapflowDto & { syncInfo?: ImapflowSyncInfo[] }; + }): Promise { + let mailbox = await this.mailboxService.findOne({ accountId, mailboxId }); + let settings = await this.findOne({ accountId, mailboxId }); + if (!mailbox || !settings) { + throw NotFoundError.withId(Mailbox, mailboxId); + } + + if (dto.email || dto.settings) { + if (dto.settings) settings = settings.update(dto.settings); + const { result, message } = await this.verify({ email: dto.email ?? mailbox.email, settings }); + if (!result) { + throw new MailConnectionError(message); + } + } + + if (dto.settings) await this.repository.save(settings); + + mailbox = await this.mailboxService.update({ accountId, user, mailboxId, dto }); + + return new MailboxImapflow({ mailbox, settings }); + } + + private async updateSyncInfo({ + accountId, + mailboxId, + syncInfo, + }: { + accountId: number; + mailboxId: number; + syncInfo: ImapflowSyncInfo[]; + }) { + await this.repository.update({ accountId, mailboxId }, { syncInfo }); + } + + async delete({ + accountId, + user, + mailboxId, + softDelete, + }: { + accountId: number; + user: User; + mailboxId: number; + softDelete?: boolean; + }): Promise { + await this.mailboxService.delete({ accountId, user, mailboxId, softDelete }); + } + + async verify({ email, settings }: { email: string; settings: MailboxSettingsImapflow }): Promise { + return this.withConnection({ + email, + settings, + verifyOnly: true, + action: async () => ({ result: true }) as MailboxSyncResult, + error: async (params) => this.handleError(params), + }); + } + + async sync({ + mailbox, + syncFull, + syncDate, + }: { + mailbox: Mailbox; + syncFull?: boolean; + syncDate?: Date; + }): Promise { + return this.withConnection({ + email: mailbox.email, + accountId: mailbox.accountId, + mailboxId: mailbox.id, + action: async (params) => this.processMailbox({ ...params, syncFull, syncDate }), + error: async (params) => this.handleError(params), + }); + } + + async getAttachment({ + mailbox, + message, + payload, + }: { + mailbox: Mailbox; + message: MailMessage; + payload: MailMessagePayload; + }): Promise { + const folder = await this.mailboxFolderService.findOne({ + accountId: message.accountId, + messageId: message.id, + }); + if (folder) { + const content = await this.withConnection({ + email: mailbox.email, + accountId: mailbox.accountId, + mailboxId: mailbox.id, + action: async (params) => { + return await this.withMailbox({ + mailboxId: mailbox.id, + client: params.client, + path: folder.externalId, + action: async ({ client }) => { + const [, uid] = this.parseExternalId(message.externalId); + const partData = await withTimeout( + client.download(String(uid), payload.attachment || payload.externalId, { uid: true }), + this._config.partLoadTimeout, + null, + ); + if (partData?.content) { + const { content } = partData; + const chunks = []; + for await (const chunk of content) { + chunks.push(chunk); + } + return Buffer.concat(chunks); + } + + return null; + }, + error: () => null as Buffer, + }); + }, + }); + + if (content) return { mimeType: payload.mimeType, filename: payload.filename, content }; + } + + return null; + } + + async send({ + accountId, + mailbox, + userName, + dto, + replyToMessage, + attachments, + }: { + accountId: number; + mailbox: Mailbox; + userName: string; + dto: SendMailMessageDto; + replyToMessage?: MailMessage | null; + attachments: StorageFile[]; + }): Promise { + try { + const settings = await this.findOne({ accountId, mailboxId: mailbox.id }); + const transporter = nodemailer.createTransport({ + host: settings.smtpServer, + port: settings.smtpPort, + secure: settings.smtpSecure, + auth: { + user: mailbox.email, + pass: settings.password, + }, + }); + + const mail = await this.mailMessageBuilder.createNodemailerMessage( + mailbox.email, + userName, + dto, + replyToMessage, + attachments, + ); + const { messageId } = await transporter.sendMail(mail); + if (messageId) { + mail.messageId = messageId; + const sentFolder = await this.mailboxFolderService.findOne({ + accountId, + mailboxId: mailbox.id, + type: MailboxFolderType.Sent, + }); + if (sentFolder) { + const sentMessage = await this.sendMessage({ + email: mailbox.email, + settings, + mail, + path: sentFolder.externalId, + }); + if (sentMessage) { + return this.createExternalMessage({ + id: sentMessage.uid ?? uuidv4(), + raw: sentMessage.message, + folderName: sentFolder.externalId, + }); + } + } + } + } catch (e) { + const error = e as Error; + this.logger.error(`SMTP send message error for mailbox ${mailbox.id}: ${error?.message}`, error?.stack); + } + + return null; + } + + private createQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('msi') + .where('msi.accountId = :accountId', { accountId: filter.accountId }); + if (filter.mailboxId) { + qb.andWhere('msi.mailboxId = :mailboxId', { mailboxId: filter.mailboxId }); + } + return qb; + } + + private async withConnection(params: WithConnectionParamMailbox | WithConnectionParamSettings): Promise { + const { email, verifyOnly, action, error } = params; + + let localSettings: MailboxSettingsImapflow; + if ('settings' in params) { + localSettings = params.settings; + } else if ('accountId' in params && 'mailboxId' in params) { + localSettings = await this.findOne({ accountId: params.accountId, mailboxId: params.mailboxId }); + } else { + throw new Error('No mailboxId or settings provided'); + } + + const client = this.getClient({ ...localSettings, email, verifyOnly }); + try { + await client.connect(); + return await action({ mailboxId: localSettings.mailboxId, client, settings: localSettings }); + } catch (e) { + this.logger.warn(`Connection error for mailbox ${localSettings.mailboxId}. ${e.toString()}`); + return error ? await error({ mailboxId: localSettings.mailboxId, error: e as Error }) : null; + } finally { + if (!verifyOnly) { + client.logout(); + } + } + } + + private getClient({ + email, + password, + imapServer, + imapPort, + imapSecure, + verifyOnly = false, + }: ImapflowConnectionParams): ImapFlow { + return new ImapFlow({ + host: imapServer, + port: imapPort, + secure: imapSecure, + auth: { user: email, pass: password }, + verifyOnly: verifyOnly, + logger: this.logger as unknown as ImapFlowLogger, + }); + } + + private async withMailbox({ mailboxId, client, path, action, error }: WithBoxParams): Promise { + const lock = await client.getMailboxLock(path); + try { + return await action({ mailboxId, client, path }); + } catch (e) { + this.logger.warn(`Box <${path}> error for mailbox ${mailboxId}. ${e.toString()}`); + return error ? await error({ mailboxId, error: e as Error }) : null; + } finally { + lock.release(); + } + } + + private async processMailbox({ + mailboxId, + client, + settings, + syncDate, + syncFull, + }: WithConnectionActionParams & { syncFull?: boolean | null; syncDate?: Date | null }): Promise { + const root = await client.listTree(); + const folders = await this.processFolders({ client, folders: root.root ? root.folders : [root] }); + + const { result, message, messages, syncInfo } = await this.processMessages({ + client, + mailboxId, + folders, + syncInfo: !syncFull && settings.syncInfo ? settings.syncInfo : [], + syncDate: syncDate, + }); + + if (result) { + await this.updateSyncInfo({ accountId: settings.accountId, mailboxId, syncInfo }); + } + + return { result, message, folders, messages }; + } + + private async processFolders({ + client, + folders, + }: { + client: ImapFlow; + folders: ListTreeResponse[]; + }): Promise { + return Promise.all( + folders.map(async (folder) => { + const status = await client.status(folder.path, { uidValidity: true, uidNext: true }); + return { + id: folder.path, + uidValidity: Number(status?.uidValidity || 0), + uidNext: Number(status?.uidNext || 0), + name: folder.name, + type: detectMailboxFolderType({ specialUse: folder.specialUse }), + folders: folder.folders?.length ? await this.processFolders({ client, folders: folder.folders }) : undefined, + }; + }), + ); + } + + private async processMessages({ + client, + mailboxId, + folders, + syncInfo, + syncDate, + }: { + client: ImapFlow; + mailboxId: number; + folders: MailboxFolderExternal[]; + syncInfo: ImapflowSyncInfo[]; + syncDate?: Date; + }): Promise { + const messages: MailboxSyncMessages = { added: [], updated: [], deleted: [] }; + const newSyncInfo: ImapflowSyncInfo[] = []; + for (const folder of folders) { + const currentSncInfo = syncInfo.find((si) => si.boxName === folder.id); + const { + result: folderResult, + message: folderMessage, + messages: folderMsgs, + syncInfo: folderSyncInfo, + } = await this.withMailbox({ + mailboxId, + client, + path: folder.id, + action: (params) => + this.processMessagesInFolder({ + ...params, + folderUidNext: folder.uidNext, + syncUidNext: currentSncInfo?.uidnext, + syncDate, + }), + error: (e) => ({ result: false, message: e.error.message }) as ProcessMessagesInFolderResult, + }); + if (!folderResult) return { result: false, message: folderMessage }; + if (folderMsgs?.added?.length) messages.added.push(...folderMsgs.added); + if (folderMsgs?.updated?.length) messages.updated.push(...folderMsgs.updated); + if (folderMsgs?.deleted?.length) messages.deleted.push(...folderMsgs.deleted); + if (folderSyncInfo) newSyncInfo.push(folderSyncInfo); + + if (folder.folders?.length) { + const { + result: subFolderResult, + message: subFolderMessage, + messages: subFolderMsgs, + syncInfo: subFolderSyncInfo, + } = await this.processMessages({ + client, + mailboxId, + folders: folder.folders, + syncInfo, + syncDate, + }); + if (!subFolderResult) return { result: false, message: subFolderMessage }; + if (subFolderMsgs?.added?.length) messages.added.push(...subFolderMsgs.added); + if (subFolderMsgs?.updated?.length) messages.updated.push(...subFolderMsgs.updated); + if (subFolderMsgs?.deleted?.length) messages.deleted.push(...subFolderMsgs.deleted); + if (subFolderSyncInfo?.length) newSyncInfo.push(...subFolderSyncInfo); + } + } + + return { result: true, messages, syncInfo: newSyncInfo }; + } + + private async processMessagesInFolder({ + client, + path, + folderUidNext, + syncUidNext, + syncDate, + }: ProcessMessagesInFolderParams): Promise { + const added: MailMessageExternal[] = []; + const search: SearchObject = {}; + let uidTo: number | undefined = undefined; + if (syncUidNext) { + if (syncUidNext >= folderUidNext) { + // No new mail in box + return { result: true, syncInfo: { boxName: path, uidnext: folderUidNext } }; + } + uidTo = Math.min(syncUidNext + this._config.searchBatchSize, folderUidNext); + search.uid = `${syncUidNext}:${uidTo}`; + } else { + search.since = syncDate ?? DateUtil.now(); + } + + const messages = await withTimeout( + client.fetchAll( + search, + { uid: true, flags: true, bodyStructure: true, envelope: true, size: true, threadId: true, headers: true }, + { uid: true }, + ), + this._config.searchTimeout, + [], + ); + + for (const msg of messages) { + const message = await this.processMessage({ folderName: path, msg, client }); + if (message) added.push(message); + } + + return { result: true, messages: { added }, syncInfo: { boxName: path, uidnext: uidTo ?? folderUidNext } }; + } + + private async processMessage({ + msg, + folderName, + client, + }: { + folderName: string; + msg: FetchMessageObject; + client: ImapFlow; + }): Promise { + const { uid, threadId, envelope, flags, headers, bodyStructure } = msg; + const parsed = await simpleParser(headers.toString('utf-8')); + + const references = this.getHeaderValue(parsed, 'references') + ?.join(',') + ?.split(',') + ?.map((i) => i.trim()); + const entityIdStr = this.getHeaderValue(parsed, HEADER_ENTITY_ID); + const entityId = entityIdStr ? Number(entityIdStr) : null; + const { hasAttachment, payloads } = await this.getMessagePayloads({ client, uid, bodyStructure }); + const snippet = this.getMessageSnippet(payloads); + + return { + id: this.createExternalId({ folderName, id: uid }), + threadId: threadId, + messageId: envelope.messageId, + sentFrom: envelope.from ? { text: concatAddress(envelope.from), values: envelope.from } : null, + sentTo: envelope.to ? { text: concatAddress(envelope.to), values: envelope.to } : null, + replyTo: envelope.replyTo ? { text: concatAddress(envelope.replyTo), values: envelope.replyTo } : null, + cc: envelope.cc ? { text: concatAddress(envelope.cc), values: envelope.cc } : null, + subject: envelope.subject, + date: envelope.date, + inReplyTo: envelope.inReplyTo, + references: references, + isSeen: flags.has(`\\Seen`), + entityId: entityId, + folders: [folderName], + snippet: snippet, + hasAttachment: hasAttachment, + payloads: payloads, + }; + } + + private async getMessagePayloads({ + client, + uid, + bodyStructure, + }: { + client: ImapFlow; + uid: number; + bodyStructure: MessageStructureObject; + }): Promise<{ + hasAttachment: boolean; + payloads: MailMessagePayloadExternal[]; + }> { + const flatStructure = this.flattenBodyStructure(bodyStructure); + const payloads: MailMessagePayloadExternal[] = []; + let hasAttachment = false; + for (const part of flatStructure) { + if (part.type.startsWith('text/')) { + const content = await this.getTextPartContent({ client, uid, part }); + if (content) { + payloads.push({ + id: part.id || part.part, + mimeType: part.type, + filename: null, + attachmentId: part.part, + content: cleanString(content), + size: part.size, + }); + } + } else { + const dispositionParameters = part.dispositionParameters as unknown as Record | null; + const parameters = part.parameters as unknown as Record | null; + let filename: string | null = null; + if (dispositionParameters?.['filename']) { + filename = StringUtil.decodeRFC5987(dispositionParameters['filename']); + } + if (!filename && parameters?.['name']) { + filename = StringUtil.decodeMimeWord(parameters['name']); + } + if (filename) { + hasAttachment = true; + payloads.push({ + id: part.id || part.part, + mimeType: part.type, + filename: filename, + attachmentId: part.part, + content: null, + size: part.size, + }); + } + } + } + return { hasAttachment, payloads }; + } + + private flattenBodyStructure(bodyStructure: MessageStructureObject): MessageStructureObject[] { + return bodyStructure.childNodes + ? [bodyStructure, ...bodyStructure.childNodes.flatMap((child) => this.flattenBodyStructure(child))] + : [bodyStructure]; + } + + private async getTextPartContent({ + client, + uid, + part, + }: { + client: ImapFlow; + uid: number; + part: MessageStructureObject; + }): Promise { + const partData = await withTimeout( + client.download(`${uid}`, part.part || '1', { uid: true }), + this._config.partLoadTimeout, + null, + ); + if (partData) { + const { content, meta } = partData; + const chunks = []; + for await (const chunk of content) { + chunks.push(chunk); + } + const buffer = Buffer.concat(chunks); + const decodedBuffer = this.decodeBuffer({ buffer, encoding: part.encoding }); + return iconv.decode(decodedBuffer, meta.charset || 'utf-8'); + } + + return null; + } + + private decodeBuffer({ encoding, buffer }: { encoding: string; buffer: Buffer }): Buffer { + switch (encoding.toUpperCase()) { + case 'BASE64': + return buffer; + case 'QUOTED-PRINTABLE': + return Buffer.from(quotedPrintable.decode(buffer.toString())); + case '7BIT': + case '8BIT': + case 'BINARY': + default: + return buffer; + } + } + + private getMessageSnippet(payloads: MailMessagePayloadExternal[]): string { + const plain = payloads.find((p) => p.mimeType === 'text/plain'); + if (plain && plain.content) { + return plain.content.trim().substring(0, 150).trim(); + } + + const html = payloads.find((p) => p.mimeType === 'text/html'); + if (html && html.content) { + return convert(html.content)?.trim()?.substring(0, 150)?.trim(); + } + + return ''; + } + + private async handleError({ error }: WithConnectionErrorParams): Promise { + return { result: false, message: error['responseText'] }; + } + + private getHeaderValue(parsed: ParsedMail, key: string): T | null { + return parsed.headers.has(key) ? (parsed.headers.get(key) as T) : null; + } + + private createExternalId({ folderName, id }: { folderName: string; id: number | string }): string { + return `${folderName}-${id}`; + } + + private parseExternalId(externalId: string): [string, number] { + const lastDashIndex = externalId.lastIndexOf('-'); + if (lastDashIndex === -1) { + throw new Error(`Invalid externalId format: ${externalId}`); + } + const folderName = externalId.substring(0, lastDashIndex); + const idStr = externalId.substring(lastDashIndex + 1); + const id = Number(idStr); + if (isNaN(id)) { + throw new Error(`Invalid message ID in externalId: ${externalId}`); + } + return [folderName, id]; + } + + private async sendMessage({ + email, + settings, + mail, + path, + }: { + email: string; + settings: MailboxSettingsImapflow; + mail: Mail.Options; + path: string; + }): Promise { + return this.withConnection({ + email: email, + settings, + action: async ({ client }) => { + const message = await this.mailMessageBuilder.createRawMessage(mail, 'utf-8'); + const result = await client.append(path, message, ['\\Seen']); + return { message, uid: result?.uid }; + }, + error: async () => null as SendMessageResult, + }); + } + + private async createExternalMessage({ + id, + raw, + folderName, + isSeen = true, + }: { + id: number | string; + raw: Source; + folderName: string; + isSeen?: boolean; + }): Promise { + const parsed = await simpleParser(raw); + if (!parsed) { + return null; + } + + const { from, to, replyTo, cc, subject, date, messageId, inReplyTo, references, text, html, attachments, headers } = + parsed; + const messageDate = date ?? DateUtil.now(); + const snippet = text ? text.substring(0, 150).trim() : ''; + const entityId: number | null = headers.has(HEADER_ENTITY_ID) + ? parseInt(headers.get(HEADER_ENTITY_ID) as string) + : null; + const payloads = this.formatAsExternalPayload(text, html, attachments); + return { + id: this.createExternalId({ folderName, id }), + threadId: null, + snippet, + sentFrom: this.convertAddress(from), + sentTo: this.convertAddress(to), + replyTo: this.convertAddress(replyTo), + cc: this.convertAddress(cc), + subject, + date: messageDate, + hasAttachment: attachments && attachments.length > 0, + messageId, + inReplyTo: inReplyTo, + references: typeof references === 'string' ? [references] : references, + isSeen, + entityId, + folders: [folderName], + payloads, + }; + } + + private convertAddress(addresses: AddressObject | AddressObject[] | null | undefined): EmailAddress | null { + if (!addresses) { + return null; + } + + if (Array.isArray(addresses)) { + const texts: string[] = []; + const values: EmailAddressValue[] = []; + for (const address of addresses) { + if (address) { + texts.push(address.text); + values.push(...address.value.filter((v) => v?.address).map((v) => ({ address: v.address, name: v.name }))); + } + } + return { text: texts.join(', '), values }; + } + + return { + text: addresses.text, + values: addresses.value.filter((v) => v?.address).map((v) => ({ address: v.address, name: v.name })), + }; + } + + private formatAsExternalPayload( + text: string, + html: string | boolean, + attachments: object[], + ): MailMessagePayloadExternal[] { + const payloads: MailMessagePayloadExternal[] = []; + if (text && text.trim()) { + payloads.push({ + id: null, + mimeType: 'text/plain', + filename: null, + attachmentId: null, + content: text, + size: text.length, + }); + } + if (html && typeof html === 'string') { + payloads.push({ + id: null, + mimeType: 'text/html', + filename: null, + attachmentId: null, + content: html.trim(), + size: html.trim().length, + }); + } + if (attachments && attachments.length > 0) { + attachments.forEach((a) => + payloads.push({ + id: a['partId'], + mimeType: a['contentType'], + filename: a['filename'], + attachmentId: a['id'], + content: null, + size: a['size'], + }), + ); + } + return payloads; + } + + async setSeen({ + mailbox, + seen, + messages, + }: { + accountId: number; + mailbox: Mailbox; + seen: boolean; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.changeFlags(mailbox, messages, ['\\Seen'], seen ? 'add' : 'del'); + } else { + throw new NotImplementedException(); + } + } + private async changeFlags( + mailbox: Mailbox, + groupedMessages: FolderMessages[], + flags: string[], + action: 'add' | 'del', + ): Promise { + return this.withConnection({ + accountId: mailbox.accountId, + email: mailbox.email, + mailboxId: mailbox.id, + action: async ({ client }) => { + for (const { folderId, messageIds } of groupedMessages) { + const parsedIds = messageIds.map((m) => this.parseExternalId(m)[1]); + await this.withMailbox({ + mailboxId: mailbox.id, + client, + path: folderId, + action: async ({ client }) => { + switch (action) { + case 'add': + await client.messageFlagsAdd(parsedIds, flags, { uid: true }); + break; + case 'del': + await client.messageFlagsRemove(parsedIds, flags, { uid: true }); + break; + } + }, + }); + } + return true; + }, + error: async () => { + return false; + }, + }); + } + + async trash({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox, messages, MailboxFolderType.Trash); + } else { + throw new NotImplementedException(); + } + } + async untrash({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox, messages, MailboxFolderType.Inbox); + } else { + throw new NotImplementedException(); + } + } + + async spam({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox, messages, MailboxFolderType.Junk); + } else { + throw new NotImplementedException(); + } + } + async unspam({ + mailbox, + messages, + }: { + mailbox: Mailbox; + messages: { threadId: string } | FolderMessages[]; + }): Promise { + if (Array.isArray(messages)) { + return this.moveMessages(mailbox, messages, MailboxFolderType.Inbox); + } else { + throw new NotImplementedException(); + } + } + + private async moveMessages( + mailbox: Mailbox, + groupedMessages: FolderMessages[], + type: MailboxFolderType, + ): Promise { + return this.withConnection({ + accountId: mailbox.accountId, + email: mailbox.email, + mailboxId: mailbox.id, + action: async ({ client }) => { + const toFolder = await this.mailboxFolderService.findOne({ + accountId: mailbox.accountId, + mailboxId: mailbox.id, + type, + }); + if (toFolder) { + for (const { folderId, messageIds } of groupedMessages) { + const parsedIds = messageIds.map((m) => this.parseExternalId(m)[1]); + await this.withMailbox({ + mailboxId: mailbox.id, + client, + path: folderId, + action: async ({ client }) => { + await client.messageMove(parsedIds, toFolder.externalId, { uid: true }); + }, + }); + } + } + return true; + }, + error: async () => { + return false; + }, + }); + } +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/index.ts b/backend/src/modules/mail/mail-providers/imapflow/index.ts new file mode 100644 index 0000000..0f01405 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/index.ts @@ -0,0 +1,7 @@ +export * from './dto'; +export * from './entities'; +export * from './errors'; +export * from './imapflow.controller'; +export * from './imapflow.module'; +export * from './imapflow.service'; +export * from './types'; diff --git a/backend/src/modules/mail/mail-providers/imapflow/types/imapflow-sync-info.ts b/backend/src/modules/mail/mail-providers/imapflow/types/imapflow-sync-info.ts new file mode 100644 index 0000000..a18960e --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/types/imapflow-sync-info.ts @@ -0,0 +1,4 @@ +export class ImapflowSyncInfo { + boxName: string; + uidnext: number; +} diff --git a/backend/src/modules/mail/mail-providers/imapflow/types/index.ts b/backend/src/modules/mail/mail-providers/imapflow/types/index.ts new file mode 100644 index 0000000..ae78418 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/types/index.ts @@ -0,0 +1,2 @@ +export * from './imapflow-sync-info'; +export * from './mailbox-imapflow'; diff --git a/backend/src/modules/mail/mail-providers/imapflow/types/mailbox-imapflow.ts b/backend/src/modules/mail/mail-providers/imapflow/types/mailbox-imapflow.ts new file mode 100644 index 0000000..1866020 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/imapflow/types/mailbox-imapflow.ts @@ -0,0 +1,21 @@ +import { Mailbox } from '@/Mailing/mailbox/entities'; + +import { MailboxImapflowDto } from '../dto'; +import { MailboxSettingsImapflow } from '../entities'; + +export class MailboxImapflow { + private readonly mailbox: Mailbox; + private readonly settings: MailboxSettingsImapflow; + + constructor({ mailbox, settings }: { mailbox: Mailbox; settings: MailboxSettingsImapflow }) { + this.mailbox = mailbox; + this.settings = settings; + } + + toDto(): MailboxImapflowDto { + return { + ...this.mailbox.toDto(), + settings: this.settings.toDto(), + }; + } +} diff --git a/backend/src/modules/mail/mail-providers/index.ts b/backend/src/modules/mail/mail-providers/index.ts new file mode 100644 index 0000000..cf00a59 --- /dev/null +++ b/backend/src/modules/mail/mail-providers/index.ts @@ -0,0 +1,2 @@ +export * from './imapflow'; +export * from './mail-providers.module'; diff --git a/backend/src/modules/mail/mail-providers/mail-providers.module.ts b/backend/src/modules/mail/mail-providers/mail-providers.module.ts new file mode 100644 index 0000000..4248adb --- /dev/null +++ b/backend/src/modules/mail/mail-providers/mail-providers.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; + +import { ImapflowModule } from './imapflow'; + +@Module({ + imports: [ImapflowModule], +}) +export class MailProvidersModule {} diff --git a/backend/src/modules/mail/mail.module.ts b/backend/src/modules/mail/mail.module.ts new file mode 100644 index 0000000..84ac532 --- /dev/null +++ b/backend/src/modules/mail/mail.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { MailMessageScheduledModule } from './mail-message-scheduled'; +import { MailProvidersModule } from './mail-providers'; + +@Module({ + imports: [MailMessageScheduledModule, MailProvidersModule], +}) +export class MailModule {} diff --git a/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.controller.ts b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.controller.ts new file mode 100644 index 0000000..865eb70 --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.controller.ts @@ -0,0 +1,67 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CreateChatMessageScheduledDto, ChatMessageScheduledDto, ChatMessageScheduledFilterDto } from './dto'; +import { ChatMessageScheduledService } from './chat-message-scheduled.service'; + +@ApiTags('multichat/messages/scheduled') +@Controller('/chat/messages/scheduled') +@JwtAuthorized({ access: { adminOnly: true } }) +@TransformToDto() +export class ChatMessageScheduledController { + constructor(private readonly service: ChatMessageScheduledService) {} + + @ApiOperation({ summary: 'Create scheduled message', description: 'Create scheduled message' }) + @ApiBody({ description: 'Data for creating scheduled message', type: CreateChatMessageScheduledDto }) + @ApiCreatedResponse({ description: 'Scheduled message', type: ChatMessageScheduledDto }) + @Post() + public async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateChatMessageScheduledDto) { + return this.service.create({ accountId, dto }); + } + + @ApiOperation({ summary: 'Get scheduled messages', description: 'Get scheduled messages' }) + @ApiOkResponse({ description: 'Scheduled messages', type: [ChatMessageScheduledDto] }) + @Get() + public async findMany(@CurrentAuth() { accountId }: AuthData, @Query() paging: PagingQuery) { + return this.service.findMany({ filter: { accountId }, paging }); + } + + @ApiOperation({ summary: 'Get scheduled message', description: 'Get scheduled message' }) + @ApiParam({ name: 'messageId', description: 'Scheduled message ID' }) + @ApiOkResponse({ description: 'Scheduled message', type: ChatMessageScheduledDto }) + @Get(':messageId') + public async findOne(@CurrentAuth() { accountId }: AuthData, @Param('messageId', ParseIntPipe) messageId: number) { + return this.service.findOne({ accountId, messageId }); + } + + @ApiOperation({ summary: 'Search scheduled messages', description: 'Search scheduled messages' }) + @ApiBody({ description: 'Data for searching scheduled messages', type: ChatMessageScheduledFilterDto }) + @ApiOkResponse({ description: 'Scheduled messages', type: [ChatMessageScheduledDto] }) + @Post('search') + public async search( + @CurrentAuth() { accountId }: AuthData, + @Body() filter: ChatMessageScheduledFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.findMany({ filter: { accountId, ...filter }, paging }); + } + + @ApiOperation({ summary: 'Delete scheduled message', description: 'Delete scheduled message' }) + @ApiParam({ name: 'messageId', description: 'Scheduled message ID' }) + @ApiOkResponse() + @Delete(':messageId') + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('messageId', ParseIntPipe) messageId: number) { + return this.service.delete({ accountId, messageId }); + } + + @ApiOperation({ summary: 'Delete scheduled messages', description: 'Delete scheduled messages' }) + @ApiBody({ description: 'Data for deleting scheduled messages', type: ChatMessageScheduledFilterDto }) + @ApiOkResponse() + @Post('delete') + public async deleteMany(@CurrentAuth() { accountId }: AuthData, @Body() filter: ChatMessageScheduledFilterDto) { + return this.service.delete({ accountId, ...filter }); + } +} diff --git a/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.handler.ts b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.handler.ts new file mode 100644 index 0000000..4f339c7 --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.handler.ts @@ -0,0 +1,55 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +import { ActionChatSendSettings, AutomationJob, EntityTypeActionType, OnAutomationJob } from '@/modules/automation'; + +import { ChatMessageScheduledService } from './chat-message-scheduled.service'; + +interface EntityVariables { + id?: number | null; + stageId?: number | null; +} + +interface SendChatVariables { + accountId: number; + entity?: EntityVariables | null; + chatSettings?: ActionChatSendSettings | null; +} + +@Injectable() +export class ChatMessageScheduledHandler { + private readonly logger = new Logger(ChatMessageScheduledHandler.name); + constructor(private readonly service: ChatMessageScheduledService) {} + + @Cron(CronExpression.EVERY_MINUTE) + public async sendScheduledMessages() { + if (process.env.SCHEDULE_CHAT_SCHEDULED_DISABLE === 'true') return; + this.logger.log('Before: Sending scheduled messages'); + const processed = await this.service.processMessages(); + this.logger.log(`After: Sending scheduled messages. Processed: ${processed}`); + } + + @OnAutomationJob(EntityTypeActionType.ChatSendExternal) + async handleSendMessageJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.chatSettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, chatSettings } = job.variables; + + const messages = await this.service.processAutomation({ + accountId, + entityId: entity.id, + entityStageId: entity.stageId, + settings: chatSettings, + }); + + return { variables: { ...job.variables, messages: messages?.map((message) => message.toDto()) } }; + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + return { variables: job.variables }; + } + } +} diff --git a/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.module.ts b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.module.ts new file mode 100644 index 0000000..74059ed --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.module.ts @@ -0,0 +1,34 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { ChatModule } from '../chat/chat.module'; +import { ChatMessageModule } from '../chat-message/chat-message.module'; +import { ChatProviderModule } from '../chat-provider/chat-provider.module'; +import { ChatUserModule } from '../chat-user/chat-user.module'; +import { ProvidersModule } from '../providers/providers.module'; +import { DocumentsModule } from '@/modules/documents/documents.module'; + +import { ChatMessageScheduled } from './entities'; +import { ChatMessageScheduledService } from './chat-message-scheduled.service'; +import { ChatMessageScheduledController } from './chat-message-scheduled.controller'; +import { ChatMessageScheduledHandler } from './chat-message-scheduled.handler'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ChatMessageScheduled]), + IAMModule, + forwardRef(() => CrmModule), + ChatModule, + ChatMessageModule, + ChatProviderModule, + ChatUserModule, + ProvidersModule, + DocumentsModule, + ], + providers: [ChatMessageScheduledService, ChatMessageScheduledHandler], + controllers: [ChatMessageScheduledController], +}) +export class ChatMessageScheduledModule {} diff --git a/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.service.ts b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.service.ts new file mode 100644 index 0000000..890d62e --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/chat-message-scheduled.service.ts @@ -0,0 +1,436 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import Handlebars from 'handlebars'; + +import { DatePeriod, DatePeriodFilter, DateUtil, isUnique, PagingQuery } from '@/common'; +import { AccountService } from '@/modules/iam/account/account.service'; +import { UserService } from '@/modules/iam/user/user.service'; +import { ActionChatSendSettings } from '@/modules/automation'; +import { EntityCategory } from '@/CRM/common'; +import { EntityTypeService } from '@/CRM/entity-type/entity-type.service'; +import { Entity } from '@/CRM/Model/Entity/Entity'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { FieldType } from '@/modules/entity/entity-field/common'; +import { FieldService } from '@/modules/entity/entity-field/field'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value'; +import { DocumentGenerationService } from '@/modules/documents/document-generation/document-generation.service'; + +import { ChatService } from '../chat/services'; +import { ChatMessageService } from '../chat-message/services'; +import { ChatProvider, ChatProviderService } from '../chat-provider'; +import { ChatUserService } from '../chat-user'; +import { ChatProviderProxyService } from '../providers/chat-provider-proxy.service'; + +import { ChatMessageScheduled } from './entities'; +import { CreateChatMessageScheduledDto } from './dto'; + +interface FindFilter { + accountId: number; + messageId?: number | number[]; + providerId?: number | number[]; + sendFrom?: number | number[]; + entityId?: number | number[]; + isSent?: boolean; + sentAt?: DatePeriodFilter; + createdAt?: DatePeriodFilter; + message?: string; + sentTo?: string; +} + +interface ProviderMessage { + accountId: number; + providerId: number; + messageId: number; +} + +const DefaultLimit = 10; + +@Injectable() +export class ChatMessageScheduledService { + private readonly logger = new Logger(ChatMessageScheduledService.name); + + constructor( + @InjectRepository(ChatMessageScheduled) + private readonly repository: Repository, + private readonly accountService: AccountService, + private readonly userService: UserService, + private readonly entityTypeService: EntityTypeService, + private readonly entityService: EntityService, + private readonly fieldService: FieldService, + private readonly fieldValueService: FieldValueService, + private readonly chatProviderProxyService: ChatProviderProxyService, + private readonly chatProviderService: ChatProviderService, + private readonly chatUserService: ChatUserService, + private readonly chatService: ChatService, + private readonly chatMessageService: ChatMessageService, + private readonly documentGenerationService: DocumentGenerationService, + ) {} + + async create({ + accountId, + dto, + }: { + accountId: number; + dto: CreateChatMessageScheduledDto; + }): Promise { + return this.repository.save(ChatMessageScheduled.fromDto(accountId, dto)); + } + + async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + + async findMany({ filter, paging }: { filter: FindFilter; paging?: PagingQuery }): Promise { + return this.createFindQb(filter).orderBy('created_at', 'DESC').limit(paging.take).offset(paging.skip).getMany(); + } + + async delete(filter: FindFilter): Promise { + await this.createFindQb(filter).delete().execute(); + } + + async processMessages(): Promise { + const queue = await this.repository + .createQueryBuilder() + .select('account_id', 'accountId') + .addSelect('provider_id', 'providerId') + .addSelect('min(id)', 'messageId') + .where('sent_at is null') + .groupBy('account_id') + .addGroupBy('provider_id') + .getRawMany(); + + return (await Promise.all(queue.map(async (item) => item.messageId && (await this.processMessage(item))))).filter( + Boolean, + ).length; + } + + async processAutomation({ + accountId, + entityId, + entityStageId, + settings, + }: { + accountId: number; + entityId: number; + entityStageId: number | null | undefined; + settings: ActionChatSendSettings; + }): Promise { + const messages: ChatMessageScheduled[] = []; + + const entity = await this.entityService.findOne(accountId, { entityId }); + if (entity && (!entity.stageId || settings.allowAnyStage || entity.stageId === entityStageId)) { + const hasOptions = Boolean(settings.options); + if (!hasOptions || settings.options?.main) { + const mainMessage = await this.createAutomationMessage({ + accountId, + entity, + settings, + onlyFirst: Boolean(settings.options?.main?.onlyFirstValue), + }); + if (mainMessage) messages.push(mainMessage); + } + + let primaryCompany = Boolean(settings.options?.company?.onlyFirstEntity); + let primaryContact = Boolean(settings.options?.contact?.onlyFirstEntity); + const entities = await this.entityService.findLinkedEntities({ accountId, entityId }); + + const messagePromises = entities.map(async (entity) => { + const entityType = await this.entityTypeService.findOne(accountId, { id: entity.entityTypeId }); + let send = [EntityCategory.CONTACT, EntityCategory.COMPANY].includes(entityType.entityCategory); + let onlyFirst = false; + + if (send && hasOptions) { + if (entityType.entityCategory === EntityCategory.COMPANY) { + send = settings.options.company && (!settings.options.company?.onlyFirstEntity || primaryCompany); + primaryCompany = false; + onlyFirst = Boolean(settings.options.company?.onlyFirstValue); + } else if (entityType.entityCategory === EntityCategory.CONTACT) { + send = settings.options.contact && (!settings.options.contact?.onlyFirstEntity || primaryContact); + primaryContact = false; + onlyFirst = Boolean(settings.options.contact?.onlyFirstValue); + } + } + + if (send) { + return this.createAutomationMessage({ accountId, entity, settings, onlyFirst }); + } + return null; + }); + + messages.push(...(await Promise.all(messagePromises)).filter(Boolean)); + + if (settings.phoneNumbers?.length) { + const messagePromises = settings.phoneNumbers + .filter(isUnique) + .map((phoneNumber) => this.createAutomationMessage({ accountId, entity, settings, phoneNumber })); + messages.push(...(await Promise.all(messagePromises)).filter(Boolean)); + } + } + return messages; + } + + private async processMessage({ accountId, providerId, messageId }: ProviderMessage): Promise { + const maxSendAt = await this.repository + .createQueryBuilder() + .select('max(sent_at)', 'max') + .where('account_id = :accountId', { accountId }) + .andWhere(`provider_id = :providerId`, { providerId }) + .andWhere('sent_at is not null') + .getRawOne<{ sentAt: Date }>(); + + const provider = await this.chatProviderService.findOne(accountId, null, { providerId }); + if (!maxSendAt?.sentAt || this.canSend({ lastSentAt: maxSendAt.sentAt, limit: provider.messagePerDay })) { + this.sendMessage({ accountId, provider, messageId }); + return true; + } + return false; + } + + private canSend({ lastSentAt, limit }: { lastSentAt: Date; limit?: number | null }): boolean { + const secondsPerEmail = 86400 / (limit ?? DefaultLimit); + const diff = DateUtil.diff({ startDate: lastSentAt, endDate: DateUtil.now(), unit: 'second' }); + + return diff > secondsPerEmail; + } + + private async sendMessage({ + accountId, + provider, + messageId, + }: { + accountId: number; + provider: ChatProvider; + messageId: number; + }) { + const message = await this.findOne({ accountId, messageId }); + + if (message?.entityId) { + const chatIds = await this.getChatId({ accountId, userId: message.sendFrom, provider, message }); + if (chatIds.length) { + const account = await this.accountService.findOne({ accountId }); + const user = await this.userService.findOne({ accountId, id: message.sendFrom }); + + await Promise.all( + chatIds.map(async (chatId) => { + try { + await this.chatMessageService.create(account, user, chatId, { text: message.message }); + } catch (e) { + this.logger.warn(`Send scheduled message with id ${messageId} error: ${e.toString()}`); + } + }), + ); + } + } else if (message.phoneNumber) { + await this.chatProviderProxyService.sendDirectMessage({ + accountId, + provider, + phone: message.phoneNumber, + message: message.message, + }); + } + + await this.repository.update({ accountId, id: messageId }, { sentAt: DateUtil.now() }); + } + + private async getChatId({ + accountId, + userId, + provider, + message, + }: { + accountId: number; + userId: number; + provider: ChatProvider; + message: ChatMessageScheduled; + }): Promise { + const chats = await this.chatService.findMany({ + accountId, + filter: { providerId: message.providerId, entityId: message.entityId }, + }); + if (chats.length) { + return !message.onlyFirst ? chats.map((c) => c.id) : [chats[0].id]; + } else if (provider.canSendByPhone()) { + const entity = await this.entityService.findOne(accountId, { entityId: message.entityId }); + const phones = await this.getPhones({ accountId, entity, onlyFirst: message.onlyFirst }); + const chatIds: number[] = []; + for (const phone of phones) { + const externalChatUser = await this.chatUserService.findOne(accountId, { + providerId: provider.id, + externalId: phone, + }); + if (externalChatUser) { + await this.chatUserService.addUsers({ accountId, chatId: externalChatUser.chatId, userIds: [userId] }); + + chatIds.push(externalChatUser.chatId); + } else { + const chat = await this.chatService.createExternalChat(accountId, userId, { + providerId: provider.id, + title: entity.name, + entityId: entity.id, + externalUser: { + firstName: entity.name, + externalId: phone, + phone: phone, + }, + }); + + chatIds.push(chat.id); + } + } + + return chatIds; + } + + return []; + } + + private async getPhones({ + accountId, + entity, + onlyFirst, + }: { + accountId: number; + entity: Entity; + onlyFirst: boolean; + }): Promise { + const fieldIds = await this.fieldService.findManyIds({ + accountId, + entityTypeId: entity.entityTypeId, + type: FieldType.Phone, + }); + if (fieldIds.length) { + if (onlyFirst) { + for (const fieldId of fieldIds) { + const fieldValue = await this.fieldValueService.findOne({ accountId, entityId: entity.id, fieldId }); + if (fieldValue) { + const value = fieldValue + .getValue() + .filter((fv) => fv !== '') + .filter(isUnique); + if (value.length) { + return [value[0]]; + } + } + } + } else { + const fieldValues = await this.fieldValueService.findMany({ + accountId, + entityId: entity.id, + fieldId: fieldIds, + }); + return fieldValues + .map((fv) => fv.getValue()) + .flat() + .filter((fv) => fv !== '') + .filter(isUnique); + } + } + + return []; + } + + private async createAutomationMessage({ + accountId, + entity, + settings, + onlyFirst = false, + phoneNumber, + }: { + accountId: number; + entity: Entity; + settings: ActionChatSendSettings; + onlyFirst?: boolean; + phoneNumber?: string; + }): Promise { + const data = await this.documentGenerationService.getDataForGeneration({ accountId, entityId: entity.id }); + data['contact_name'] = entity.name; + const message = Handlebars.compile(settings.message)(data); + + return this.create({ + accountId, + dto: { + providerId: settings.providerId, + sendFrom: settings.userId ?? entity.responsibleUserId, + message, + entityId: !phoneNumber ? entity.id : undefined, + phoneNumber, + onlyFirst, + }, + }); + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId: filter.accountId }); + + if (filter?.messageId) { + if (Array.isArray(filter.messageId)) { + qb.andWhere('id IN (:...ids)', { ids: filter.messageId }); + } else { + qb.andWhere('id = :id', { id: filter.messageId }); + } + } + + if (filter?.providerId) { + if (Array.isArray(filter.providerId)) { + qb.andWhere('provider_id IN (:...providerIds)', { providerIds: filter.providerId }); + } else { + qb.andWhere('provider_id = :providerId', { providerId: filter.providerId }); + } + } + + if (filter?.sendFrom) { + if (Array.isArray(filter.sendFrom)) { + qb.andWhere('send_from IN (:...sendFrom)', { sendFrom: filter.sendFrom }); + } else { + qb.andWhere('send_from = :sendFrom', { sendFrom: filter.sendFrom }); + } + } + + if (filter?.entityId) { + if (Array.isArray(filter.entityId)) { + qb.andWhere('entity_id IN (:...entityIds)', { entityIds: filter.entityId }); + } else { + qb.andWhere('entity_id = :entityId', { entityId: filter.entityId }); + } + } + + if (filter?.isSent !== undefined) { + if (filter.isSent) { + qb.andWhere('sent_at IS NOT NULL'); + } else { + qb.andWhere('sent_at IS NULL'); + } + } + + if (filter.sentAt) { + const dates = DatePeriod.fromFilter(filter.sentAt); + if (dates.from) { + qb.andWhere('sent_at >= :sentAtFrom', { sentAtFrom: dates.from }); + } + if (dates.to) { + qb.andWhere('sent_at <= :sentAtTo', { sentAtTo: dates.to }); + } + } + + if (filter.createdAt) { + const dates = DatePeriod.fromFilter(filter.createdAt); + if (dates.from) { + qb.andWhere('created_at >= :createdAtFrom', { createdAtFrom: dates.from }); + } + if (dates.to) { + qb.andWhere('created_at <= :createdAtTo', { createdAtTo: dates.to }); + } + } + + if (filter.message) { + qb.andWhere('message ILIKE :content', { content: `%${filter.message}%` }); + } + + if (filter.sentTo) { + qb.andWhere('send_to ILIKE :sentTo', { sentTo: `%${filter.sentTo}%` }); + } + + return qb; + } +} diff --git a/backend/src/modules/multichat/chat-message-scheduled/dto/chat-message-scheduled-filter.dto.ts b/backend/src/modules/multichat/chat-message-scheduled/dto/chat-message-scheduled-filter.dto.ts new file mode 100644 index 0000000..a6bfef8 --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/dto/chat-message-scheduled-filter.dto.ts @@ -0,0 +1,44 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; + +export class ChatMessageScheduledFilterDto { + @ApiPropertyOptional({ description: 'Provider IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + providerId?: number[]; + + @ApiPropertyOptional({ description: 'User IDs who sent the message' }) + @IsOptional() + @IsNumber({}, { each: true }) + sendFrom?: number[]; + + @ApiPropertyOptional({ description: 'Entity IDs' }) + @IsOptional() + @IsNumber({}, { each: true }) + entityId?: number[]; + + @ApiPropertyOptional({ description: 'Is the message sent' }) + @IsOptional() + @IsBoolean() + isSent?: boolean; + + @ApiPropertyOptional({ description: 'Date and time when the message was sent' }) + @IsOptional() + sentAt?: DatePeriodFilter; + + @ApiPropertyOptional({ description: 'Date and time when the message was created' }) + @IsOptional() + createdAt?: DatePeriodFilter; + + @ApiPropertyOptional({ description: 'Message text' }) + @IsOptional() + @IsString() + message?: string; + + @ApiPropertyOptional({ description: 'Recipient (phone numbers)' }) + @IsOptional() + @IsString() + sentTo?: string; +} diff --git a/backend/src/modules/multichat/chat-message-scheduled/dto/chat-message-scheduled.dto.ts b/backend/src/modules/multichat/chat-message-scheduled/dto/chat-message-scheduled.dto.ts new file mode 100644 index 0000000..939872b --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/dto/chat-message-scheduled.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class ChatMessageScheduledDto { + @ApiProperty({ description: 'Scheduled message ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'User ID who sent the message' }) + @IsNumber() + sendFrom: number; + + @ApiProperty({ description: 'Date and time when the message was created' }) + @IsString() + createdAt: string; + + @ApiProperty({ description: 'Chat Provider ID' }) + @IsNumber() + providerId: number; + + @ApiProperty({ description: 'Message text' }) + @IsString() + message: string; + + @ApiPropertyOptional({ nullable: true, description: 'Entity ID associated with the message' }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'Phone number associated with the message' }) + @IsOptional() + @IsString() + phoneNumber?: string | null; + + @ApiProperty({ description: 'Send only to first chat' }) + @IsBoolean() + onlyFirst: boolean; + + @ApiPropertyOptional({ description: 'Date and time when the message was sent', nullable: true }) + @IsOptional() + @IsNumber() + sentAt?: string | null; +} diff --git a/backend/src/modules/multichat/chat-message-scheduled/dto/create-chat-message-scheduled.dto.ts b/backend/src/modules/multichat/chat-message-scheduled/dto/create-chat-message-scheduled.dto.ts new file mode 100644 index 0000000..ed3ce3d --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/dto/create-chat-message-scheduled.dto.ts @@ -0,0 +1,17 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsBoolean, IsOptional } from 'class-validator'; + +import { ChatMessageScheduledDto } from './chat-message-scheduled.dto'; + +export class CreateChatMessageScheduledDto extends PickType(ChatMessageScheduledDto, [ + 'providerId', + 'sendFrom', + 'message', + 'entityId', + 'phoneNumber', +] as const) { + @ApiPropertyOptional({ description: 'Send only to first chat' }) + @IsOptional() + @IsBoolean() + onlyFirst?: boolean; +} diff --git a/backend/src/modules/multichat/chat-message-scheduled/dto/index.ts b/backend/src/modules/multichat/chat-message-scheduled/dto/index.ts new file mode 100644 index 0000000..cefbf65 --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/dto/index.ts @@ -0,0 +1,3 @@ +export * from './chat-message-scheduled-filter.dto'; +export * from './chat-message-scheduled.dto'; +export * from './create-chat-message-scheduled.dto'; diff --git a/backend/src/modules/multichat/chat-message-scheduled/entities/chat-message-scheduled.entity.ts b/backend/src/modules/multichat/chat-message-scheduled/entities/chat-message-scheduled.entity.ts new file mode 100644 index 0000000..87b5610 --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/entities/chat-message-scheduled.entity.ts @@ -0,0 +1,84 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { ChatMessageScheduledDto, CreateChatMessageScheduledDto } from '../dto'; + +@Entity() +export class ChatMessageScheduled { + @PrimaryGeneratedColumn('identity') + id: number | undefined; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + @Column() + sendFrom: number; + + @Column() + providerId: number; + + @Column() + message: string; + + @Column({ nullable: true }) + entityId: number | null; + + @Column({ nullable: true }) + phoneNumber: string | null; + + @Column({ default: false }) + onlyFirst: boolean; + + @Column({ nullable: true }) + sentAt: Date | null; + + constructor( + accountId: number, + sendFrom: number, + providerId: number, + message: string, + entityId: number | null, + phoneNumber: string | null, + onlyFirst: boolean, + createdAt: Date = new Date(), + sentAt: Date | null = null, + ) { + this.accountId = accountId; + this.sendFrom = sendFrom; + this.createdAt = createdAt; + this.providerId = providerId; + this.message = message; + this.entityId = entityId; + this.phoneNumber = phoneNumber; + this.onlyFirst = onlyFirst; + this.sentAt = sentAt; + } + + public static fromDto(accountId: number, dto: CreateChatMessageScheduledDto): ChatMessageScheduled { + return new ChatMessageScheduled( + accountId, + dto.sendFrom, + dto.providerId, + dto.message, + dto.entityId, + dto.phoneNumber, + dto.onlyFirst ?? false, + ); + } + + public toDto(): ChatMessageScheduledDto { + return { + id: this.id, + sendFrom: this.sendFrom, + createdAt: this.createdAt.toISOString(), + providerId: this.providerId, + message: this.message, + onlyFirst: this.onlyFirst, + entityId: this.entityId, + phoneNumber: this.phoneNumber, + sentAt: this.sentAt?.toISOString(), + }; + } +} diff --git a/backend/src/modules/multichat/chat-message-scheduled/entities/index.ts b/backend/src/modules/multichat/chat-message-scheduled/entities/index.ts new file mode 100644 index 0000000..93e04e9 --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/entities/index.ts @@ -0,0 +1 @@ +export * from './chat-message-scheduled.entity'; diff --git a/backend/src/modules/multichat/chat-message-scheduled/index.ts b/backend/src/modules/multichat/chat-message-scheduled/index.ts new file mode 100644 index 0000000..311f657 --- /dev/null +++ b/backend/src/modules/multichat/chat-message-scheduled/index.ts @@ -0,0 +1,6 @@ +export * from './chat-message-scheduled.controller'; +export * from './chat-message-scheduled.handler'; +export * from './chat-message-scheduled.module'; +export * from './chat-message-scheduled.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/modules/multichat/chat-message/chat-message.controller.ts b/backend/src/modules/multichat/chat-message/chat-message.controller.ts new file mode 100644 index 0000000..2ddba9b --- /dev/null +++ b/backend/src/modules/multichat/chat-message/chat-message.controller.ts @@ -0,0 +1,119 @@ +import { Body, Controller, Delete, Get, Param, ParseEnumPipe, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto, PagingQuery } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ChatMessageStatus } from '../common'; +import { ChatMessageDto } from './dto/chat-message.dto'; +import { ChatMessagesFilterDto } from './dto/chat-messages-filter.dto'; +import { ChatMessagesResultDto } from './dto/chat-messages-result.dto'; +import { SendChatMessageDto } from './dto/send-chat-message.dto'; +import { ChatMessageService } from './services/chat-message.service'; + +@ApiTags('multichat/messages') +@Controller('/chat/chats/:chatId/messages') +@JwtAuthorized({ prefetch: { account: true, user: true } }) +@TransformToDto() +export class ChatMessageController { + constructor(private readonly service: ChatMessageService) {} + + @ApiCreatedResponse({ description: 'Create chat message', type: ChatMessageDto }) + @Post() + public async create( + @CurrentAuth() { account, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Body() dto: SendChatMessageDto, + ) { + return await this.service.create(account, user, chatId, dto); + } + + @ApiCreatedResponse({ description: 'Get chat messages', type: ChatMessagesResultDto }) + @Get() + public async getMany( + @CurrentAuth() { account, userId }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Query() filter: ChatMessagesFilterDto, + @Query() paging: PagingQuery, + ) { + return await this.service.getMessagesForUI(account, userId, chatId, filter, paging); + } + + @ApiCreatedResponse({ description: 'Get chat message', type: ChatMessageDto }) + @Get(':messageId') + public async getOne( + @CurrentAuth() { account, userId }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return await this.service.getMessageDto(account, userId, chatId, messageId); + } + + @ApiCreatedResponse({ description: 'Update chat message', type: ChatMessageDto }) + @Put(':messageId') + public async update( + @CurrentAuth() { account, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + @Body() dto: SendChatMessageDto, + ) { + return await this.service.update(account, user, chatId, messageId, dto); + } + + @ApiOkResponse({ description: 'Chat message deleted', type: Boolean }) + @Delete(':messageId') + public async delete( + @CurrentAuth() { account, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ): Promise { + return await this.service.delete(account, user, chatId, messageId); + } + + @ApiCreatedResponse({ description: 'Update chat messages status', type: [ChatMessageDto] }) + @Post('status/:status') + public async updateMessagesStatus( + @CurrentAuth() { account, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('status', new ParseEnumPipe(ChatMessageStatus)) status: ChatMessageStatus, + @Body('messageIds') messageIds: number[], + ) { + return await this.service.updateStatusBatch(account, user, chatId, messageIds, status); + } + + @ApiCreatedResponse({ description: 'Update chat message status', type: ChatMessageDto }) + @Put(':messageId/status/:status') + public async updateMessageStatus( + @CurrentAuth() { account, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + @Param('status', new ParseEnumPipe(ChatMessageStatus)) status: ChatMessageStatus, + ) { + return await this.service.updateStatus(account, user, chatId, messageId, status); + } + + @ApiCreatedResponse({ description: 'React to chat message', type: ChatMessageDto }) + @Put(':messageId/react/:reaction') + public async reactMessage( + @CurrentAuth() { account, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + @Param('reaction') reaction: string, + ) { + return await this.service.react(account, user, chatId, messageId, reaction); + } + + @ApiCreatedResponse({ description: 'Clear reaction to chat message', type: ChatMessageDto }) + @Put(':messageId/unreact/:reactionId') + public async unreactMessage( + @CurrentAuth() { account, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + @Param('reactionId', ParseIntPipe) reactionId: number, + ) { + return await this.service.unreact(account, user, chatId, messageId, reactionId); + } +} diff --git a/backend/src/modules/multichat/chat-message/chat-message.module.ts b/backend/src/modules/multichat/chat-message/chat-message.module.ts new file mode 100644 index 0000000..5942e1e --- /dev/null +++ b/backend/src/modules/multichat/chat-message/chat-message.module.ts @@ -0,0 +1,43 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; + +import { ChatModule } from '../chat/chat.module'; +import { ChatProviderModule } from '../chat-provider/chat-provider.module'; +import { ProvidersModule } from '../providers/providers.module'; +import { ChatUserModule } from '../chat-user'; + +import { ChatMessageFile } from './entities/chat-message-file.entity'; +import { ChatMessageReaction } from './entities/chat-message-reaction.entity'; +import { ChatMessageUserStatus } from './entities/chat-message-user-status.entity'; +import { ChatMessage } from './entities/chat-message.entity'; +import { ChatMessageFileService } from './services/chat-message-file.service'; +import { ChatMessageReactionService } from './services/chat-message-reaction.service'; +import { ChatMessageUserStatusService } from './services/chat-message-user-status.service'; +import { ChatMessageService } from './services/chat-message.service'; +import { ChatNotificationService } from './services/chat-notification.service'; +import { ChatMessageController } from './chat-message.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ChatMessage, ChatMessageFile, ChatMessageUserStatus, ChatMessageReaction]), + IAMModule, + StorageModule, + forwardRef(() => ChatModule), + ChatProviderModule, + ProvidersModule, + ChatUserModule, + ], + providers: [ + ChatMessageService, + ChatMessageUserStatusService, + ChatMessageFileService, + ChatMessageReactionService, + ChatNotificationService, + ], + controllers: [ChatMessageController], + exports: [ChatMessageService], +}) +export class ChatMessageModule {} diff --git a/backend/src/modules/multichat/chat-message/dto/chat-message-file.dto.ts b/backend/src/modules/multichat/chat-message/dto/chat-message-file.dto.ts new file mode 100644 index 0000000..7244a1b --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/chat-message-file.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class ChatMessageFileDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + fileId: string | null; + + @ApiProperty() + @IsString() + fileName: string; + + @ApiProperty() + @IsNumber() + fileSize: number; + + @ApiProperty() + @IsString() + fileType: string; + + @ApiProperty() + @IsString() + downloadUrl: string; + + @ApiProperty() + @IsString() + createdAt: string; + + constructor( + id: number, + fileId: string | null, + fileName: string, + fileSize: number, + fileType: string, + createdAt: string, + downloadUrl: string, + ) { + this.id = id; + this.fileId = fileId; + this.fileName = fileName; + this.fileSize = fileSize; + this.fileType = fileType; + this.createdAt = createdAt; + this.downloadUrl = downloadUrl; + } +} diff --git a/backend/src/modules/multichat/chat-message/dto/chat-message-reaction.dto.ts b/backend/src/modules/multichat/chat-message/dto/chat-message-reaction.dto.ts new file mode 100644 index 0000000..989d1b5 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/chat-message-reaction.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class ChatMessageReactionDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + chatUserId: number; + + @ApiProperty() + @IsString() + reaction: string; + + constructor(id: number, chatUserId: number, reaction: string) { + this.id = id; + this.chatUserId = chatUserId; + this.reaction = reaction; + } +} diff --git a/backend/src/modules/multichat/chat-message/dto/chat-message-user-status.dto.ts b/backend/src/modules/multichat/chat-message/dto/chat-message-user-status.dto.ts new file mode 100644 index 0000000..509882f --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/chat-message-user-status.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { ChatMessageStatus } from '../../common/enums/chat-message-status.enum'; + +export class ChatMessageUserStatusDto { + @ApiProperty() + chatUserId: number; + + @ApiProperty() + status: ChatMessageStatus; + + @ApiProperty() + createdAt: string; + + constructor(chatUserId: number, status: ChatMessageStatus, createdAt: string) { + this.chatUserId = chatUserId; + this.status = status; + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/multichat/chat-message/dto/chat-message.dto.ts b/backend/src/modules/multichat/chat-message/dto/chat-message.dto.ts new file mode 100644 index 0000000..81e8162 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/chat-message.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; + +import { ChatMessageFileDto } from './chat-message-file.dto'; +import { ChatMessageReactionDto } from './chat-message-reaction.dto'; +import { ChatMessageUserStatusDto } from './chat-message-user-status.dto'; + +export class ChatMessageDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + chatId: number; + + @ApiProperty() + @IsNumber() + chatUserId: number; + + @ApiProperty() + @IsString() + text: string; + + @ApiProperty({ type: [ChatMessageUserStatusDto] }) + @IsArray() + statuses: ChatMessageUserStatusDto[]; + + @ApiProperty({ type: [ChatMessageFileDto] }) + @IsArray() + files: ChatMessageFileDto[]; + + @ApiProperty({ type: ChatMessageDto, nullable: true }) + @IsOptional() + @IsObject() + replyTo: ChatMessageDto | null; + + @ApiProperty({ type: [ChatMessageReactionDto] }) + @IsArray() + reactions: ChatMessageReactionDto[]; + + @ApiProperty() + @IsString() + createdAt: string; + + constructor( + id: number, + chatId: number, + chatUserId: number, + text: string, + statuses: ChatMessageUserStatusDto[], + files: ChatMessageFileDto[], + replyTo: ChatMessageDto | null, + reactions: ChatMessageReactionDto[], + createdAt: string, + ) { + this.id = id; + this.chatId = chatId; + this.chatUserId = chatUserId; + this.text = text; + this.statuses = statuses; + this.files = files; + this.replyTo = replyTo; + this.reactions = reactions; + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/multichat/chat-message/dto/chat-messages-filter.dto.ts b/backend/src/modules/multichat/chat-message/dto/chat-messages-filter.dto.ts new file mode 100644 index 0000000..23a93f9 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/chat-messages-filter.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class ChatMessagesFilterDto { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + focusedMessageId?: number | null; +} diff --git a/backend/src/modules/multichat/chat-message/dto/chat-messages-result.dto.ts b/backend/src/modules/multichat/chat-message/dto/chat-messages-result.dto.ts new file mode 100644 index 0000000..6158d78 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/chat-messages-result.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsObject } from 'class-validator'; + +import { PagingMeta } from '@/common'; +import { ChatMessageDto } from './chat-message.dto'; + +export class ChatMessagesResultDto { + @ApiProperty({ type: [ChatMessageDto] }) + @IsArray() + messages: ChatMessageDto[]; + + @ApiProperty({ type: PagingMeta }) + @IsObject() + meta: PagingMeta; + + constructor(messages: ChatMessageDto[], meta: PagingMeta) { + this.messages = messages; + this.meta = meta; + } +} diff --git a/backend/src/modules/multichat/chat-message/dto/index.ts b/backend/src/modules/multichat/chat-message/dto/index.ts new file mode 100644 index 0000000..d90da75 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/index.ts @@ -0,0 +1,7 @@ +export * from './chat-message-file.dto'; +export * from './chat-message-reaction.dto'; +export * from './chat-message-user-status.dto'; +export * from './chat-message.dto'; +export * from './chat-messages-filter.dto'; +export * from './chat-messages-result.dto'; +export * from './send-chat-message.dto'; diff --git a/backend/src/modules/multichat/chat-message/dto/send-chat-message.dto.ts b/backend/src/modules/multichat/chat-message/dto/send-chat-message.dto.ts new file mode 100644 index 0000000..53c9533 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/dto/send-chat-message.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class SendChatMessageDto { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + replyToId?: number | number; + + @ApiProperty() + @IsString() + text: string; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsArray() + fileIds?: string[] | null; +} diff --git a/backend/src/modules/multichat/chat-message/entities/chat-message-file.entity.ts b/backend/src/modules/multichat/chat-message/entities/chat-message-file.entity.ts new file mode 100644 index 0000000..2694e0a --- /dev/null +++ b/backend/src/modules/multichat/chat-message/entities/chat-message-file.entity.ts @@ -0,0 +1,76 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { ChatMessageFileDto } from '../dto/chat-message-file.dto'; + +@Entity() +export class ChatMessageFile { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + messageId: number; + + @Column({ nullable: true }) + externalId: string | null; + + @Column({ nullable: true }) + fileId: string | null; + + @Column() + name: string; + + @Column() + mimeType: string; + + @Column() + size: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + private _downloadUrl: string | null = null; + + constructor( + accountId: number, + messageId: number, + externalId: string | null, + fileId: string | null, + name: string, + mimeType: string, + size: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.messageId = messageId; + this.externalId = externalId; + this.fileId = fileId; + this.name = name; + this.mimeType = mimeType; + this.size = size; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public set downloadUrl(downloadUrl: string | null) { + this._downloadUrl = downloadUrl; + } + public get downloadUrl(): string | null { + return this._downloadUrl; + } + + public toDto(): ChatMessageFileDto { + return new ChatMessageFileDto( + this.id, + this.fileId, + this.name, + this.size, + this.mimeType, + this.createdAt.toISOString(), + this._downloadUrl, + ); + } +} diff --git a/backend/src/modules/multichat/chat-message/entities/chat-message-reaction.entity.ts b/backend/src/modules/multichat/chat-message/entities/chat-message-reaction.entity.ts new file mode 100644 index 0000000..2b914db --- /dev/null +++ b/backend/src/modules/multichat/chat-message/entities/chat-message-reaction.entity.ts @@ -0,0 +1,38 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { ChatMessageReactionDto } from '../dto/chat-message-reaction.dto'; + +@Entity() +export class ChatMessageReaction { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + messageId: number; + + @Column() + chatUserId: number; + + @Column() + reaction: string; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, messageId: number, chatUserId: number, reaction: string, createdAt?: Date) { + this.accountId = accountId; + this.messageId = messageId; + this.chatUserId = chatUserId; + this.reaction = reaction; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public toDto(): ChatMessageReactionDto { + return new ChatMessageReactionDto(this.id, this.chatUserId, this.reaction); + } +} diff --git a/backend/src/modules/multichat/chat-message/entities/chat-message-user-status.entity.ts b/backend/src/modules/multichat/chat-message/entities/chat-message-user-status.entity.ts new file mode 100644 index 0000000..b37b6c3 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/entities/chat-message-user-status.entity.ts @@ -0,0 +1,47 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { ChatMessageStatus } from '../../common/enums/chat-message-status.enum'; +import { ChatMessageUserStatusDto } from '../dto/chat-message-user-status.dto'; + +@Entity() +export class ChatMessageUserStatus { + @PrimaryColumn() + chatId: number; + + @PrimaryColumn() + messageId: number; + + @PrimaryColumn() + chatUserId: number; + + @Column() + status: ChatMessageStatus; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + chatId: number, + messageId: number, + chatUserId: number, + status: ChatMessageStatus, + createdAt?: Date, + ) { + this.accountId = accountId; + this.chatId = chatId; + this.messageId = messageId; + this.chatUserId = chatUserId; + this.status = status; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public toDto(): ChatMessageUserStatusDto { + return new ChatMessageUserStatusDto(this.chatUserId, this.status, this.createdAt.toISOString()); + } +} diff --git a/backend/src/modules/multichat/chat-message/entities/chat-message.entity.ts b/backend/src/modules/multichat/chat-message/entities/chat-message.entity.ts new file mode 100644 index 0000000..bc9757b --- /dev/null +++ b/backend/src/modules/multichat/chat-message/entities/chat-message.entity.ts @@ -0,0 +1,119 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { ChatUser } from '../../chat-user'; +import { ChatMessageFile } from './chat-message-file.entity'; +import { ChatMessageReaction } from './chat-message-reaction.entity'; +import { ChatMessageUserStatus } from './chat-message-user-status.entity'; + +import { ChatMessageDto } from '../dto/chat-message.dto'; + +@Entity() +export class ChatMessage { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + chatId: number; + + @Column() + chatUserId: number; + + @Column({ nullable: true }) + externalId: string | null; + + @Column({ nullable: true }) + replyToId: number | null; + + @Column() + text: string; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + chatId: number, + chatUserId: number, + externalId: string | null, + replyToId: number | null, + text: string, + createdAt?: Date, + ) { + this.accountId = accountId; + this.chatId = chatId; + this.chatUserId = chatUserId; + this.externalId = externalId; + this.replyToId = replyToId; + this.text = text; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _statuses: ChatMessageUserStatus[] | null; + public get statuses(): ChatMessageUserStatus[] | null { + return this._statuses; + } + public set statuses(value: ChatMessageUserStatus[] | null) { + this._statuses = value; + } + + private _files: ChatMessageFile[] | null; + public get files(): ChatMessageFile[] | null { + return this._files; + } + public set files(value: ChatMessageFile[] | null) { + this._files = value; + } + + private _replyTo: ChatMessage | null; + public get replyTo(): ChatMessage | null { + return this._replyTo; + } + public set replyTo(value: ChatMessage | null) { + this._replyTo = value; + } + + private _reactions: ChatMessageReaction[] | null; + public get reactions(): ChatMessageReaction[] | null { + return this._reactions; + } + public set reactions(value: ChatMessageReaction[] | null) { + this._reactions = value; + } + + private _chatUser: ChatUser | null; + public get chatUser(): ChatUser | null { + return this._chatUser; + } + public set chatUser(value: ChatUser | null) { + this._chatUser = value; + } + + public update(replyToId: number | null, text: string): ChatMessage { + this.replyToId = replyToId; + this.text = text; + return this; + } + + public toDto(): ChatMessageDto { + const statuses = this._statuses ? this._statuses.map((status) => status.toDto()) : []; + const files = this._files ? this._files.map((file) => file.toDto()) : []; + const replyTo = this._replyTo ? this._replyTo.toDto() : null; + const reactions = this._reactions ? this._reactions.map((reaction) => reaction.toDto()) : []; + return new ChatMessageDto( + this.id, + this.chatId, + this.chatUserId, + this.text, + statuses, + files, + replyTo, + reactions, + this.createdAt.toISOString(), + ); + } +} diff --git a/backend/src/modules/multichat/chat-message/entities/index.ts b/backend/src/modules/multichat/chat-message/entities/index.ts new file mode 100644 index 0000000..7fb3e25 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/entities/index.ts @@ -0,0 +1,4 @@ +export * from './chat-message-file.entity'; +export * from './chat-message-reaction.entity'; +export * from './chat-message-user-status.entity'; +export * from './chat-message.entity'; diff --git a/backend/src/modules/multichat/chat-message/services/chat-message-file.service.ts b/backend/src/modules/multichat/chat-message/services/chat-message-file.service.ts new file mode 100644 index 0000000..c6eb279 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/services/chat-message-file.service.ts @@ -0,0 +1,76 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { StorageUrlService } from '@/modules/storage/storage-url.service'; +import { StorageService } from '@/modules/storage/storage.service'; + +import { ChatMessageFile } from '../entities/chat-message-file.entity'; + +@Injectable() +export class ChatMessageFileService { + constructor( + @InjectRepository(ChatMessageFile) + private readonly repository: Repository, + private readonly storageService: StorageService, + private readonly storageUrlService: StorageUrlService, + ) {} + + public async addMessageFiles(account: Account, messageId: number, fileIds: string[]): Promise { + const files: ChatMessageFile[] = []; + for (const fileId of fileIds) { + const fileInfo = await this.storageService.markUsed({ accountId: account.id, id: fileId }); + if (fileInfo) { + const file = new ChatMessageFile( + account.id, + messageId, + null, + fileInfo.id, + fileInfo.originalName, + fileInfo.mimeType, + fileInfo.size, + fileInfo.createdAt, + ); + + file.downloadUrl = this.storageUrlService.getDownloadUrl(account.subdomain, file.fileId); + + files.push(await this.repository.save(file)); + } + } + return files; + } + + public async updateMessageFiles(account: Account, messageId: number, fileIds: string[]): Promise { + const currentFiles = await this.repository.findBy({ accountId: account.id, messageId }); + const currentFileIds = currentFiles.map((file) => file.fileId); + + const addedFileIds = fileIds.filter((fileId) => !currentFileIds.includes(fileId)); + const addedFiles = await this.addMessageFiles(account, messageId, addedFileIds); + + const deletedFiles = currentFiles.filter((file) => !fileIds.includes(file.fileId)); + await this.deleteFiles(account.id, messageId, deletedFiles); + + return currentFiles.filter((file) => !deletedFiles.includes(file)).concat(addedFiles); + } + + public async deleteMessageFiles(accountId: number, messageId: number) { + const files = await this.repository.findBy({ accountId, messageId }); + await this.deleteFiles(accountId, messageId, files); + } + + public setFileDownloadUrl(account: Account, file: ChatMessageFile): ChatMessageFile { + file.downloadUrl = this.storageUrlService.getDownloadUrl(account.subdomain, file.fileId); + + return file; + } + + private async deleteFiles(accountId: number, messageId: number, files: ChatMessageFile[]) { + for (const file of files) { + const deleted = await this.storageService.delete({ accountId, id: file.fileId }); + if (deleted) { + await this.repository.delete({ accountId, messageId, id: file.id }); + } + } + } +} diff --git a/backend/src/modules/multichat/chat-message/services/chat-message-reaction.service.ts b/backend/src/modules/multichat/chat-message/services/chat-message-reaction.service.ts new file mode 100644 index 0000000..c9bf230 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/services/chat-message-reaction.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ChatUser } from '../../chat-user'; +import { ChatMessageReaction } from '../entities/chat-message-reaction.entity'; + +@Injectable() +export class ChatMessageReactionService { + constructor( + @InjectRepository(ChatMessageReaction) + private readonly repository: Repository, + ) {} + + public async add( + accountId: number, + chatUser: ChatUser, + messageId: number, + reaction: string, + ): Promise { + return await this.repository.save(new ChatMessageReaction(accountId, messageId, chatUser.id, reaction)); + } + + public async remove(accountId: number, chatUser: ChatUser, messageId: number, reactionId: number): Promise { + await this.repository.delete({ accountId, chatUserId: chatUser.id, messageId, id: reactionId }); + } +} diff --git a/backend/src/modules/multichat/chat-message/services/chat-message-user-status.service.ts b/backend/src/modules/multichat/chat-message/services/chat-message-user-status.service.ts new file mode 100644 index 0000000..1f55f59 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/services/chat-message-user-status.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ChatMessageStatus } from '../../common/enums/chat-message-status.enum'; +import { ChatMessageUserStatus } from '../entities/chat-message-user-status.entity'; + +@Injectable() +export class ChatMessageUserStatusService { + constructor( + @InjectRepository(ChatMessageUserStatus) + private readonly repository: Repository, + ) {} + + async setStatus( + accountId: number, + chatId: number, + chatUserId: number, + messageId: number, + status: ChatMessageStatus, + ): Promise { + const cmus = new ChatMessageUserStatus(accountId, chatId, messageId, chatUserId, status); + await this.repository.upsert(cmus, ['chatId', 'messageId', 'chatUserId']); + return cmus; + } + + async updateStatusDirect({ + accountId, + userId, + chatId, + status, + }: { + accountId: number; + userId: number; + chatId?: number; + status: ChatMessageStatus; + }) { + const sql = ` +INSERT INTO chat_message_user_status (chat_id, message_id, chat_user_id, status, account_id, created_at) +SELECT cm.chat_id, cm.id, cu.id, '${status}', cm.account_id, NOW() +FROM chat_user cu +JOIN chat_message cm ON cm.chat_id = cu.chat_id +WHERE cu.account_id = ${accountId} + AND cu.user_id = ${userId} + AND ${chatId ? `cu.chat_id = ${chatId}` : 'TRUE'} +ON CONFLICT (chat_id, message_id, chat_user_id) + DO UPDATE SET status = EXCLUDED.status, created_at = NOW() + WHERE chat_message_user_status.status IS DISTINCT FROM EXCLUDED.status; + `; + + await this.repository.query(sql); + } +} diff --git a/backend/src/modules/multichat/chat-message/services/chat-message.service.ts b/backend/src/modules/multichat/chat-message/services/chat-message.service.ts new file mode 100644 index 0000000..3572eef --- /dev/null +++ b/backend/src/modules/multichat/chat-message/services/chat-message.service.ts @@ -0,0 +1,522 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, SelectQueryBuilder } from 'typeorm'; + +import { NotFoundError, PagingMeta, PagingQuery } from '@/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { ChatMessageStatus, MultichatEventType } from '../../common'; + +import { ChatUser, ChatUserService } from '../../chat-user'; + +import { SendChatMessageDto, ChatMessagesFilterDto, ChatMessagesResultDto } from '../dto'; +import { ChatMessage, ChatMessageUserStatus, ChatMessageFile, ChatMessageReaction } from '../entities'; +import { ExpandableField } from '../types'; +import { ChatMessageFileService } from './chat-message-file.service'; +import { ChatMessageReactionService } from './chat-message-reaction.service'; +import { ChatMessageUserStatusService } from './chat-message-user-status.service'; +import { ChatNotificationService } from './chat-notification.service'; + +interface FindFilter { + chatId: number; +} +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class ChatMessageService { + constructor( + @InjectRepository(ChatMessage) + private readonly repository: Repository, + @Inject(forwardRef(() => ChatUserService)) + private readonly chatUserService: ChatUserService, + private readonly chatMessageUserStatusService: ChatMessageUserStatusService, + private readonly chatMessageFileService: ChatMessageFileService, + private readonly chatMessageReactionService: ChatMessageReactionService, + private readonly chatNotificationService: ChatNotificationService, + ) {} + + async create( + account: Account, + user: User, + chatId: number, + dto: SendChatMessageDto, + notifyUsers = true, + ): Promise { + const chatUser = await this.chatUserService.getOne(account.id, { chatId, userId: user.id }); + + const message = await this.repository.save( + new ChatMessage(account.id, chatId, chatUser.id, null, dto.replyToId, dto.text), + ); + + if (dto.fileIds) { + message.files = await this.chatMessageFileService.addMessageFiles(account, message.id, dto.fileIds); + } + + if (dto.replyToId) { + message.replyTo = await this.getMessageSimple(account.id, chatId, dto.replyToId); + } + + message.statuses = [ + await this.chatMessageUserStatusService.setStatus( + account.id, + chatId, + chatUser.id, + message.id, + ChatMessageStatus.SEEN, + ), + ]; + + if (notifyUsers) { + this.chatNotificationService.notifyUsers( + account, + chatId, + chatUser, + message, + MultichatEventType.ChatMessageCreated, + user.fullName, + ); + } + + return message; + } + + async createExternal( + account: Account, + chatUser: ChatUser, + text: string, + externalId: string | null, + fileIds?: string[] | null, + notifyUsers = true, + ): Promise { + const message = await this.repository.save( + new ChatMessage(account.id, chatUser.chatId, chatUser.id, externalId, null, text), + ); + + if (fileIds) { + message.files = await this.chatMessageFileService.addMessageFiles(account, message.id, fileIds); + } + + message.statuses = [ + await this.chatMessageUserStatusService.setStatus( + account.id, + chatUser.chatId, + chatUser.id, + message.id, + ChatMessageStatus.SEEN, + ), + ]; + + if (notifyUsers) { + this.chatNotificationService.notifyUsers( + account, + chatUser.chatId, + chatUser, + message, + MultichatEventType.ChatMessageCreated, + chatUser.externalUser?.fullName(), + ); + } + + return message; + } + + async getMessageDto(account: Account, userId: number, chatId: number, messageId: number): Promise { + await this.chatUserService.getOne(account.id, { chatId, userId }); + + return await this.getMessageFull(account, chatId, messageId); + } + + async findOne(accountId: number, filter: FindFilter, options?: FindOptions): Promise { + const message = await this.createFindQb(accountId, filter).getOne(); + return message && options?.expand ? await this.expandOne(message, options.expand) : message; + } + async findMany(accountId: number, filter: FindFilter, options?: FindOptions): Promise { + const messages = await this.createFindQb(accountId, filter).orderBy('cm.created_at', 'DESC').getMany(); + return messages && options?.expand ? await this.expandMany(messages, options.expand) : messages; + } + + private createFindQb(accountId: number, filter: FindFilter) { + const qb = this.repository.createQueryBuilder('cm').where('cm.account_id = :accountId', { accountId }); + + if (filter.chatId) { + qb.andWhere('cm.chat_id = :chatId', { chatId: filter.chatId }); + } + + return qb; + } + + private async expandOne(message: ChatMessage, expand: ExpandableField[]): Promise { + if (expand.includes('chatUser')) { + message.chatUser = await this.chatUserService.findOne(message.accountId, { id: message.chatUserId }); + } + return message; + } + private async expandMany(messages: ChatMessage[], expand: ExpandableField[]): Promise { + return await Promise.all(messages.map((message) => this.expandOne(message, expand))); + } + + async update( + account: Account, + user: User, + chatId: number, + messageId: number, + dto: SendChatMessageDto, + ): Promise { + const chatUser = await this.chatUserService.getOne(account.id, { chatId, userId: user.id }); + + const message = await this.getMessageSimple(account.id, chatId, messageId, chatUser.id); + + await this.repository.save(message.update(dto.replyToId, dto.text)); + + if (dto.fileIds) { + message.files = await this.chatMessageFileService.updateMessageFiles(account, message.id, dto.fileIds); + } + + if (dto.replyToId) { + message.replyTo = await this.getMessageSimple(account.id, chatId, dto.replyToId); + } + + this.chatNotificationService.notifyUsers( + account, + chatId, + chatUser, + message, + MultichatEventType.ChatMessageUpdated, + user.fullName, + ); + + return message; + } + + async updateStatus( + account: Account, + user: User, + chatId: number, + messageId: number, + status: ChatMessageStatus, + ): Promise { + const chatUser = await this.chatUserService.getOne(account.id, { chatId, userId: user.id }); + const message = await this.getMessageFull(account, chatId, messageId); + + const userStatus = await this.chatMessageUserStatusService.setStatus( + account.id, + chatId, + chatUser.id, + message.id, + status, + ); + message.statuses = [...message.statuses.filter((s) => userStatus.chatUserId !== s.chatUserId), userStatus]; + + this.chatNotificationService.notifyUsers( + account, + chatId, + chatUser, + message, + MultichatEventType.ChatMessageUpdated, + user.fullName, + ); + + return message; + } + + async updateStatusBatch( + account: Account, + user: User, + chatId: number, + messageIds: number[], + status: ChatMessageStatus, + ): Promise { + const chatUser = await this.chatUserService.getOne(account.id, { chatId, userId: user.id }); + const messages = await this.getMessagesFull(account.id, chatId, messageIds); + + for (const message of messages) { + const userStatus = await this.chatMessageUserStatusService.setStatus( + account.id, + chatId, + chatUser.id, + message.id, + status, + ); + message.statuses = [...message.statuses.filter((s) => userStatus.chatUserId !== s.chatUserId), userStatus]; + + this.chatNotificationService.notifyUsers( + account, + chatId, + chatUser, + message, + MultichatEventType.ChatMessageUpdated, + user.fullName, + ); + } + + return messages; + } + + async updateStatusDirect({ + accountId, + user, + chatId, + status, + }: { + accountId: number; + user: User; + chatId?: number; + status: ChatMessageStatus; + }) { + await this.chatMessageUserStatusService.updateStatusDirect({ accountId, userId: user.id, chatId, status }); + } + + async react(account: Account, user: User, chatId: number, messageId: number, reaction: string): Promise { + const chatUser = await this.chatUserService.getOne(account.id, { chatId, userId: user.id }); + const message = await this.getMessageFull(account, chatId, messageId); + + const messageReaction = await this.chatMessageReactionService.add(account.id, chatUser, message.id, reaction); + message.reactions = [...message.reactions, messageReaction]; + + this.chatNotificationService.notifyUsers( + account, + chatId, + chatUser, + message, + MultichatEventType.ChatMessageUpdated, + user.fullName, + ); + + return message; + } + + async unreact( + account: Account, + user: User, + chatId: number, + messageId: number, + reactionId: number, + ): Promise { + const chatUser = await this.chatUserService.getOne(account.id, { chatId, userId: user.id }); + const message = await this.getMessageFull(account, chatId, messageId); + + await this.chatMessageReactionService.remove(account.id, chatUser, message.id, reactionId); + message.reactions = message.reactions.filter((reaction) => reaction.id !== reactionId); + + this.chatNotificationService.notifyUsers( + account, + chatId, + chatUser, + message, + MultichatEventType.ChatMessageUpdated, + user.fullName, + ); + + return message; + } + + async delete(account: Account, user: User, chatId: number, messageId: number): Promise { + const chatUser = await this.chatUserService.getOne(account.id, { chatId, userId: user.id }); + + const message = await this.getMessageSimple(account.id, chatId, messageId, chatUser.id); + + await this.chatMessageFileService.deleteMessageFiles(account.id, message.id); + + await this.repository.delete(message.id); + + this.chatNotificationService.notifyUsers( + account, + chatId, + chatUser, + message, + MultichatEventType.ChatMessageDeleted, + user.fullName, + ); + + return true; + } + + async getMessagesForUI( + account: Account, + userId: number, + chatId: number, + filter: ChatMessagesFilterDto, + paging: PagingQuery, + ): Promise { + await this.chatUserService.getOne(account.id, { chatId, userId }); + + const qb = this.getChatMessagesFullQuery(account.id, chatId); + const total = await qb.clone().getCount(); + + const [messages, offset] = filter.focusedMessageId + ? await this.getMessagesFullToMessage(account, chatId, filter.focusedMessageId, qb, paging) + : await this.getMessagesFullWithPaging(qb, paging, total); + + this.setAllFileDownloadUrls(account, messages); + + return new ChatMessagesResultDto( + messages.map((message) => message.toDto()), + new PagingMeta(offset, total), + ); + } + + async getMessageSimple( + accountId: number, + chatId: number, + messageId: number, + chatUserId?: number, + ): Promise { + const message = await this.repository.findOneBy({ + accountId, + chatId, + id: messageId, + chatUserId, + }); + + if (!message) { + throw new NotFoundError(`Message ${messageId} not found in chat ${chatId}`); + } + + return message; + } + + private async getMessagesFullWithPaging( + qb: SelectQueryBuilder, + paging: PagingQuery, + total: number, + ): Promise<[ChatMessage[], number]> { + const messages = await qb + .clone() + .skip(paging.skip) + .take(paging.take) + //FIX: https://github.com/typeorm/typeorm/issues/8213 + .orderBy('msg.createdAt', 'DESC') + .addOrderBy('msg.id', 'DESC') + .getMany(); + + const offset = Math.min(paging.take + paging.skip, total); + return [messages, offset]; + } + + private async getMessagesFullToMessage( + account: Account, + chatId: number, + focusedMessageId: number, + qb: SelectQueryBuilder, + paging: PagingQuery, + ): Promise<[ChatMessage[], number]> { + const message = await this.getMessageFull(account, chatId, focusedMessageId, false); + const newerQb = qb.clone().andWhere('msg.created_at > :createdAt', { createdAt: message.createdAt }); + const newerCount = await newerQb.clone().getCount(); + const skip = Math.min(newerCount, paging.skip); + const newerMessages = await newerQb + .clone() + .skip(skip) + //FIX: https://github.com/typeorm/typeorm/issues/8213 + .orderBy('msg.createdAt', 'DESC') + .addOrderBy('msg.id', 'DESC') + .getMany(); + + const olderMessages = await qb + .clone() + .andWhere('msg.created_at < :createdAt', { createdAt: message.createdAt }) + .take(paging.take) + //FIX: https://github.com/typeorm/typeorm/issues/8213 + .orderBy('msg.createdAt', 'DESC') + .addOrderBy('msg.id', 'DESC') + .getMany(); + + const messages = [...newerMessages, message, ...olderMessages]; + const offset = messages.length + skip; + + return [messages, offset]; + } + + private async getMessageFull( + account: Account, + chatId: number, + messageId: number, + setFileDownloadUrl = true, + ): Promise { + const message = await this.getChatMessagesFullQuery(account.id, chatId) + .andWhere('msg.id = :messageId', { messageId }) + .getOne(); + + if (!message) { + throw new NotFoundError(`Message ${messageId} not found in chat ${chatId}`); + } + + if (setFileDownloadUrl) { + this.setFileDownloadUrls(account, message); + } + + return message; + } + + private async getMessagesFull(accountId: number, chatId: number, messageIds: number[]): Promise { + if (messageIds.length === 0) { + return []; + } + + return await this.getChatMessagesFullQuery(accountId, chatId) + .andWhere('msg.id IN (:...messageIds)', { messageIds }) + .getMany(); + } + + async getLastMessageId(accountId: number, chatId: number): Promise { + const data = await this.repository + .createQueryBuilder('msg') + .select('msg.id', 'id') + .where('msg.account_id = :accountId', { accountId }) + .andWhere('msg.chat_id = :chatId', { chatId }) + .orderBy('msg.created_at', 'DESC') + .getRawOne(); + + return data.id; + } + + async getLastMessageCreatedAt(accountId: number, chatId: number): Promise { + const data = await this.repository + .createQueryBuilder('msg') + .select('msg.created_at', 'created_at') + .where('msg.account_id = :accountId', { accountId }) + .andWhere('msg.chat_id = :chatId', { chatId }) + .orderBy('msg.created_at', 'DESC') + .getRawOne(); + + return data?.created_at ? new Date(data.created_at) : null; + } + + async getLastMessageInfo(accountId: number, chatId: number, messageId: number): Promise { + return await this.repository + .createQueryBuilder('msg') + .leftJoin(ChatUser, 'user', 'msg.chat_id = user.chat_id') + .leftJoinAndMapMany('msg.statuses', ChatMessageUserStatus, 'status', 'msg.id = status.message_id') + .leftJoinAndMapMany('msg.files', ChatMessageFile, 'file', 'msg.id = file.message_id') + .where('msg.account_id = :accountId', { accountId }) + .andWhere('msg.chat_id = :chatId', { chatId }) + .andWhere('msg.id = :messageId', { messageId }) + .orderBy('msg.created_at', 'DESC') + .addOrderBy('msg.id', 'DESC') + .getOne(); + } + + private getChatMessagesFullQuery(accountId: number, chatId: number): SelectQueryBuilder { + return this.repository + .createQueryBuilder('msg') + .leftJoinAndMapMany('msg.statuses', ChatMessageUserStatus, 'status', 'msg.id = status.message_id') + .leftJoinAndMapMany('msg.files', ChatMessageFile, 'file', 'msg.id = file.message_id') + .leftJoinAndMapOne('msg.replyTo', ChatMessage, 'replyTo', 'msg.reply_to_id = replyTo.id') + .leftJoinAndMapMany('msg.reactions', ChatMessageReaction, 'reaction', 'msg.id = reaction.message_id') + .where('msg.account_id = :accountId', { accountId }) + .andWhere('msg.chat_id = :chatId', { chatId }); + } + + private setAllFileDownloadUrls(account: Account, messages: ChatMessage[]): void { + messages.forEach((message) => this.setFileDownloadUrls(account, message)); + } + private setFileDownloadUrls(account: Account, message: ChatMessage): void { + message.files.forEach((file) => { + this.chatMessageFileService.setFileDownloadUrl(account, file); + }); + } +} diff --git a/backend/src/modules/multichat/chat-message/services/chat-notification.service.ts b/backend/src/modules/multichat/chat-message/services/chat-notification.service.ts new file mode 100644 index 0000000..b1dab0d --- /dev/null +++ b/backend/src/modules/multichat/chat-message/services/chat-notification.service.ts @@ -0,0 +1,133 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; + +import { + ChatMessageCreatedEvent, + ChatMessageEvent, + ChatMessageUpdatedEvent, + ChatUserRole, + MultichatEventType, +} from '../../common'; +import { ChatUserService, ChatUser } from '../../chat-user'; +import { ChatProviderService } from '../../chat-provider/services/chat-provider.service'; +import { ChatProviderProxyService } from '../../providers/chat-provider-proxy.service'; +import { Chat } from '../../chat/entities/chat.entity'; +import { ChatService } from '../../chat/services/chat.service'; + +import { ChatMessage } from '../entities/chat-message.entity'; + +@Injectable() +export class ChatNotificationService { + constructor( + private eventEmitter: EventEmitter2, + @Inject(forwardRef(() => ChatService)) + private readonly chatService: ChatService, + @Inject(forwardRef(() => ChatUserService)) + private readonly chatUserService: ChatUserService, + @Inject(forwardRef(() => ChatProviderService)) + private readonly chatProviderService: ChatProviderService, + private readonly providerProxyService: ChatProviderProxyService, + ) {} + + public async notifyUsers( + account: Account, + chatId: number, + sendBy: ChatUser, + message: ChatMessage, + type: MultichatEventType, + fromUserName: string, + ): Promise { + const chat = await this.chatService.findOne({ accountId: account.id, filter: { chatId } }); + const chatUsers = await this.chatUserService.findMany(account.id, { chatId }); + + const internalUsers = chatUsers.filter((chatUser) => chatUser.userId && chatUser.id !== sendBy.id); + if (internalUsers.length > 0) { + this.notifyInternalUsers(account.id, chat, message, type, internalUsers, fromUserName); + } + + const externalUsers = chatUsers.filter( + (chatUser) => chatUser.role === ChatUserRole.EXTERNAL && chatUser.id !== sendBy.id, + ); + if (externalUsers.length > 0) { + const provider = await this.chatProviderService.findOne(account.id, null, { providerId: chat.providerId }); + if (provider) { + this.providerProxyService.notifyChatUsers(account, provider, chat, type, message, externalUsers); + } + } + } + + private async notifyInternalUsers( + accountId: number, + chat: Chat, + message: ChatMessage, + type: MultichatEventType, + chatUsers: ChatUser[], + fromUserName: string, + ): Promise { + const lastMessageId = await this.chatService.getLastMessageId(accountId, chat.id); + chatUsers.forEach(async (chatUser) => { + const event = this.createMessageEvent(accountId, chat, message, chatUser, type, fromUserName, lastMessageId); + this.eventEmitter.emit(type, event); + }); + } + + private createMessageEvent( + accountId: number, + chat: Chat, + message: ChatMessage, + chatUser: ChatUser, + type: MultichatEventType, + fromUserName: string, + lastMessageId?: number | null, + ): ChatMessageEvent { + switch (type) { + case MultichatEventType.ChatMessageCreated: + return this.createMessageCreatedEvent(accountId, chat, message, chatUser, fromUserName); + case MultichatEventType.ChatMessageUpdated: + return this.createMessageChangedEvent(accountId, chat, message, chatUser, lastMessageId); + case MultichatEventType.ChatMessageDeleted: + return this.createMessageChangedEvent(accountId, chat, message, chatUser, lastMessageId); + default: + throw new Error(`Unknown event type: ${type}`); + } + } + private createMessageCreatedEvent( + accountId: number, + chat: Chat, + message: ChatMessage, + chatUser: ChatUser, + fromUserName: string, + ) { + const text = message.text || message.files?.[0]?.name; + return new ChatMessageCreatedEvent({ + accountId, + userId: chatUser.userId, + providerId: chat.providerId, + chatId: chat.id, + fromUser: fromUserName, + messageId: message.id, + text, + createdAt: message.createdAt.toISOString(), + entityId: chat.entityId, + }); + } + + private createMessageChangedEvent( + accountId: number, + chat: Chat, + message: ChatMessage, + chatUser: ChatUser, + lastMessageId: number | null, + ) { + return new ChatMessageUpdatedEvent({ + accountId, + userId: chatUser.userId, + providerId: chat.providerId, + chatId: chat.id, + messageId: message.id, + isLastMessage: message.id === lastMessageId, + }); + } +} diff --git a/backend/src/modules/multichat/chat-message/services/index.ts b/backend/src/modules/multichat/chat-message/services/index.ts new file mode 100644 index 0000000..f11e80a --- /dev/null +++ b/backend/src/modules/multichat/chat-message/services/index.ts @@ -0,0 +1,5 @@ +export * from './chat-message-file.service'; +export * from './chat-message-reaction.service'; +export * from './chat-message-user-status.service'; +export * from './chat-message.service'; +export * from './chat-notification.service'; diff --git a/backend/src/modules/multichat/chat-message/types/expandable-field.ts b/backend/src/modules/multichat/chat-message/types/expandable-field.ts new file mode 100644 index 0000000..62e2f5e --- /dev/null +++ b/backend/src/modules/multichat/chat-message/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'chatUser'; diff --git a/backend/src/modules/multichat/chat-message/types/index.ts b/backend/src/modules/multichat/chat-message/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/multichat/chat-message/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/multichat/chat-provider-user/chat-provider-user.module.ts b/backend/src/modules/multichat/chat-provider-user/chat-provider-user.module.ts new file mode 100644 index 0000000..b014d67 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider-user/chat-provider-user.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { ChatProviderUser } from './entities'; +import { ChatProviderUserService } from './chat-provider-user.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([ChatProviderUser])], + providers: [ChatProviderUserService], + exports: [ChatProviderUserService], +}) +export class ChatProviderUserModule {} diff --git a/backend/src/modules/multichat/chat-provider-user/chat-provider-user.service.ts b/backend/src/modules/multichat/chat-provider-user/chat-provider-user.service.ts new file mode 100644 index 0000000..90f4bb1 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider-user/chat-provider-user.service.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ChatProviderUserType } from './enums'; +import { ChatProviderUser } from './entities'; + +interface FindFilter { + providerId?: number; + type?: ChatProviderUserType; +} + +@Injectable() +export class ChatProviderUserService { + constructor( + @InjectRepository(ChatProviderUser) + private repository: Repository, + ) {} + + async create( + accountId: number, + providerId: number, + userIds: number[], + type: ChatProviderUserType, + ): Promise { + return this.repository.save(userIds.map((userId) => new ChatProviderUser(providerId, userId, type, accountId))); + } + + async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getMany(); + } + + async update( + accountId: number, + providerId: number, + currentUsers: ChatProviderUser[], + userIds: number[], + type: ChatProviderUserType, + ): Promise { + const addUsers = userIds.filter((id) => !currentUsers.some((user) => user.userId === id)); + const removeUsers = currentUsers.filter((user) => !userIds.some((id) => id === user.userId)); + + currentUsers.push( + ...(await this.repository.save( + addUsers.map((userId) => new ChatProviderUser(providerId, userId, type, accountId)), + )), + ); + + if (removeUsers.length > 0) { + await this.repository.remove(removeUsers); + } + + return currentUsers.filter((user) => !removeUsers.some((u) => u.userId === user.userId)); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('cpu').where('cpu.account_id = :accountId', { accountId }); + + if (filter?.providerId) { + qb.andWhere('cpu.provider_id = :providerId', { providerId: filter.providerId }); + } + + if (filter?.type) { + qb.andWhere('cpu.type = :type', { type: filter.type }); + } + + return qb; + } +} diff --git a/backend/src/modules/multichat/chat-provider-user/entities/chat-provider-user.entity.ts b/backend/src/modules/multichat/chat-provider-user/entities/chat-provider-user.entity.ts new file mode 100644 index 0000000..ca2cf26 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider-user/entities/chat-provider-user.entity.ts @@ -0,0 +1,24 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { ChatProviderUserType } from '../enums'; + +@Entity() +export class ChatProviderUser { + @PrimaryColumn() + providerId: number; + + @PrimaryColumn() + userId: number; + + @PrimaryColumn({ type: 'enum', enum: ChatProviderUserType, default: ChatProviderUserType.Accessible }) + type: ChatProviderUserType; + + @Column() + accountId: number; + + constructor(providerId: number, userId: number, type: ChatProviderUserType, accountId: number) { + this.providerId = providerId; + this.userId = userId; + this.type = type; + this.accountId = accountId; + } +} diff --git a/backend/src/modules/multichat/chat-provider-user/entities/index.ts b/backend/src/modules/multichat/chat-provider-user/entities/index.ts new file mode 100644 index 0000000..aa1dead --- /dev/null +++ b/backend/src/modules/multichat/chat-provider-user/entities/index.ts @@ -0,0 +1 @@ +export * from './chat-provider-user.entity'; diff --git a/backend/src/modules/multichat/chat-provider-user/enums/chat-provider-user-type.enum.ts b/backend/src/modules/multichat/chat-provider-user/enums/chat-provider-user-type.enum.ts new file mode 100644 index 0000000..30a53f5 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider-user/enums/chat-provider-user-type.enum.ts @@ -0,0 +1,5 @@ +export enum ChatProviderUserType { + Accessible = 'accessible', + Responsible = 'responsible', + Supervisor = 'supervisor', +} diff --git a/backend/src/modules/multichat/chat-provider-user/enums/index.ts b/backend/src/modules/multichat/chat-provider-user/enums/index.ts new file mode 100644 index 0000000..166405a --- /dev/null +++ b/backend/src/modules/multichat/chat-provider-user/enums/index.ts @@ -0,0 +1 @@ +export * from './chat-provider-user-type.enum'; diff --git a/backend/src/modules/multichat/chat-provider-user/index.ts b/backend/src/modules/multichat/chat-provider-user/index.ts new file mode 100644 index 0000000..329f250 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider-user/index.ts @@ -0,0 +1,4 @@ +export * from './chat-provider-user.module'; +export * from './chat-provider-user.service'; +export * from './entities'; +export * from './enums'; diff --git a/backend/src/modules/multichat/chat-provider/chat-provider.controller.ts b/backend/src/modules/multichat/chat-provider/chat-provider.controller.ts new file mode 100644 index 0000000..cf0250a --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/chat-provider.controller.ts @@ -0,0 +1,26 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ChatProviderDto } from './dto'; +import { ChatProviderService } from './services'; + +@ApiTags('multichat/providers') +@Controller('chat/providers') +@JwtAuthorized() +@TransformToDto() +export class ChatProviderController { + constructor(private readonly service: ChatProviderService) {} + + @ApiOperation({ summary: 'Get available providers', description: 'Get available providers for current user' }) + @ApiOkResponse({ description: 'Chat providers', type: [ChatProviderDto] }) + @Get() + async findMany(@CurrentAuth() { accountId, userId }: AuthData): Promise { + return this.service.findMany(accountId, userId, {}, { expand: ['unseenCount'] }); + } +} diff --git a/backend/src/modules/multichat/chat-provider/chat-provider.module.ts b/backend/src/modules/multichat/chat-provider/chat-provider.module.ts new file mode 100644 index 0000000..dd3c80c --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/chat-provider.module.ts @@ -0,0 +1,25 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { ChatModule } from '../chat/chat.module'; +import { ChatProviderUserModule } from '../chat-provider-user/chat-provider-user.module'; +import { ChatUserModule } from '../chat-user/chat-user.module'; + +import { ChatProvider, ChatProviderEntitySettings } from './entities'; +import { ChatProviderEntitySettingsService, ChatProviderHandler, ChatProviderService } from './services'; +import { ChatProviderController } from './chat-provider.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ChatProvider, ChatProviderEntitySettings]), + IAMModule, + ChatProviderUserModule, + forwardRef(() => ChatModule), + ChatUserModule, + ], + providers: [ChatProviderService, ChatProviderHandler, ChatProviderEntitySettingsService], + controllers: [ChatProviderController], + exports: [ChatProviderService], +}) +export class ChatProviderModule {} diff --git a/backend/src/modules/multichat/chat-provider/const/chat-provider-defaults.ts b/backend/src/modules/multichat/chat-provider/const/chat-provider-defaults.ts new file mode 100644 index 0000000..9c8d76c --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/const/chat-provider-defaults.ts @@ -0,0 +1,8 @@ +import { ChatProviderStatus, ChatProviderTransport, ChatProviderType } from '../../common'; + +export const ChatProviderDefaults = { + type: ChatProviderType.Amwork, + transport: ChatProviderTransport.Amwork, + status: ChatProviderStatus.Active, + messagePerDay: 100, +}; diff --git a/backend/src/modules/multichat/chat-provider/const/index.ts b/backend/src/modules/multichat/chat-provider/const/index.ts new file mode 100644 index 0000000..8903c78 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/const/index.ts @@ -0,0 +1 @@ +export * from './chat-provider-defaults'; diff --git a/backend/src/modules/multichat/chat-provider/dto/chat-provider-entity-settings.dto.ts b/backend/src/modules/multichat/chat-provider/dto/chat-provider-entity-settings.dto.ts new file mode 100644 index 0000000..3c326fb --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/dto/chat-provider-entity-settings.dto.ts @@ -0,0 +1,44 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class ChatProviderEntitySettingsDto { + @ApiPropertyOptional({ description: 'Contact entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + contactEntityTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + leadEntityTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead board ID', nullable: true }) + @IsOptional() + @IsNumber() + leadBoardId?: number | null; + + @ApiPropertyOptional({ description: 'Lead stage ID', nullable: true }) + @IsOptional() + @IsNumber() + leadStageId?: number | null; + + @ApiPropertyOptional({ description: 'Lead name', nullable: true }) + @IsOptional() + @IsString() + leadName: string | null; + + @ApiPropertyOptional({ description: 'Lead and Contact responsible user ID', nullable: true }) + @IsOptional() + @IsNumber() + ownerId?: number | null; + + @ApiPropertyOptional({ description: 'Do not create lead if active lead exists', nullable: true }) + @IsOptional() + @IsBoolean() + checkActiveLead?: boolean; + + @ApiPropertyOptional({ description: 'Do not create duplicate contact', nullable: true }) + @IsOptional() + @IsBoolean() + checkDuplicate?: boolean; +} diff --git a/backend/src/modules/multichat/chat-provider/dto/chat-provider.dto.ts b/backend/src/modules/multichat/chat-provider/dto/chat-provider.dto.ts new file mode 100644 index 0000000..8927a70 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/dto/chat-provider.dto.ts @@ -0,0 +1,56 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ChatProviderType, ChatProviderStatus, ChatProviderTransport } from '../../common'; +import { ChatProviderEntitySettingsDto } from './chat-provider-entity-settings.dto'; + +export class ChatProviderDto { + @ApiProperty({ description: 'Chat provider ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Chat provider type', enum: ChatProviderType }) + @IsEnum(ChatProviderType) + type: ChatProviderType; + + @ApiProperty({ description: 'Chat provider transport', enum: ChatProviderTransport }) + @IsEnum(ChatProviderTransport) + transport: ChatProviderTransport; + + @ApiProperty({ description: 'Chat provider title', nullable: true }) + @IsOptional() + @IsString() + title: string | null; + + @ApiProperty({ description: 'Chat provider status', enum: ChatProviderStatus }) + @IsEnum(ChatProviderStatus) + status: ChatProviderStatus; + + @ApiProperty({ description: 'Messages per day for automated sending' }) + @IsNumber() + messagePerDay: number; + + @ApiPropertyOptional({ description: 'Accessible user IDs', type: [Number], nullable: true }) + @IsOptional() + @IsArray() + accessibleUserIds?: number[] | null; + + @ApiPropertyOptional({ description: 'Responsible user IDs', type: [Number], nullable: true }) + @IsOptional() + @IsArray() + responsibleUserIds?: number[] | null; + + @ApiPropertyOptional({ description: 'Supervisor user IDs', type: [Number], nullable: true }) + @IsOptional() + @IsArray() + supervisorUserIds?: number[] | null; + + @ApiPropertyOptional({ description: 'Unseen message count', nullable: true }) + @IsOptional() + @IsNumber() + unseenCount?: number | null; + + @ApiPropertyOptional({ description: 'Entity settings', nullable: true }) + @IsOptional() + entitySettings?: ChatProviderEntitySettingsDto | null; +} diff --git a/backend/src/modules/multichat/chat-provider/dto/create-chat-provider.dto.ts b/backend/src/modules/multichat/chat-provider/dto/create-chat-provider.dto.ts new file mode 100644 index 0000000..73205ef --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/dto/create-chat-provider.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +import { ChatProviderDto } from './chat-provider.dto'; + +export class CreateChatProviderDto extends PickType(ChatProviderDto, [ + 'type', + 'transport', + 'title', + 'status', + 'accessibleUserIds', + 'responsibleUserIds', + 'supervisorUserIds', + 'entitySettings', +] as const) { + @ApiProperty({ description: 'Messages per day for automated sending' }) + @IsOptional() + @IsNumber() + messagePerDay?: number; +} diff --git a/backend/src/modules/multichat/chat-provider/dto/index.ts b/backend/src/modules/multichat/chat-provider/dto/index.ts new file mode 100644 index 0000000..6237ae0 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/dto/index.ts @@ -0,0 +1,4 @@ +export * from './chat-provider-entity-settings.dto'; +export * from './chat-provider.dto'; +export * from './create-chat-provider.dto'; +export * from './update-chat-provider.dto'; diff --git a/backend/src/modules/multichat/chat-provider/dto/update-chat-provider.dto.ts b/backend/src/modules/multichat/chat-provider/dto/update-chat-provider.dto.ts new file mode 100644 index 0000000..7c9fae7 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/dto/update-chat-provider.dto.ts @@ -0,0 +1,15 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { ChatProviderDto } from './chat-provider.dto'; + +export class UpdateChatProviderDto extends PartialType( + PickType(ChatProviderDto, [ + 'title', + 'status', + 'messagePerDay', + 'accessibleUserIds', + 'responsibleUserIds', + 'supervisorUserIds', + 'entitySettings', + ] as const), +) {} diff --git a/backend/src/modules/multichat/chat-provider/entities/chat-provider-entity-settings.entity.ts b/backend/src/modules/multichat/chat-provider/entities/chat-provider-entity-settings.entity.ts new file mode 100644 index 0000000..7f2a64f --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/entities/chat-provider-entity-settings.entity.ts @@ -0,0 +1,110 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ChatProviderEntitySettingsDto } from '../dto'; + +@Entity() +export class ChatProviderEntitySettings { + @Column() + accountId: number; + + @PrimaryColumn() + providerId: number; + + @Column({ nullable: true }) + contactEntityTypeId: number | null; + + @Column({ nullable: true }) + leadEntityTypeId: number | null; + + @Column({ nullable: true }) + leadBoardId: number | null; + + @Column({ nullable: true }) + leadStageId: number | null; + + @Column({ nullable: true }) + leadName: string | null; + + @Column({ nullable: true }) + ownerId: number | null; + + @Column({ default: false }) + checkActiveLead: boolean; + + @Column({ default: false }) + checkDuplicate: boolean; + + constructor( + accountId: number, + providerId: number, + contactEntityTypeId: number | null, + leadEntityTypeId: number | null, + leadBoardId: number | null, + leadStageId: number | null, + leadName: string | null, + ownerId: number | null, + checkActiveLead: boolean, + checkDuplicate: boolean, + ) { + this.accountId = accountId; + this.providerId = providerId; + this.contactEntityTypeId = contactEntityTypeId; + this.leadEntityTypeId = leadEntityTypeId; + this.leadBoardId = leadBoardId; + this.leadStageId = leadStageId; + this.leadName = leadName; + this.ownerId = ownerId; + this.checkActiveLead = checkActiveLead; + this.checkDuplicate = checkDuplicate; + } + + static fromDto({ + accountId, + providerId, + dto, + }: { + accountId: number; + providerId: number; + dto: ChatProviderEntitySettingsDto; + }): ChatProviderEntitySettings { + return new ChatProviderEntitySettings( + accountId, + providerId, + dto.contactEntityTypeId, + dto.leadEntityTypeId, + dto.leadBoardId, + dto.leadStageId, + dto.leadName, + dto.ownerId, + dto.checkActiveLead ?? false, + dto.checkDuplicate ?? false, + ); + } + + update(dto: ChatProviderEntitySettingsDto): ChatProviderEntitySettings { + this.contactEntityTypeId = + dto.contactEntityTypeId !== undefined ? dto.contactEntityTypeId : this.contactEntityTypeId; + this.leadEntityTypeId = dto.leadEntityTypeId !== undefined ? dto.leadEntityTypeId : this.leadEntityTypeId; + this.leadBoardId = dto.leadBoardId !== undefined ? dto.leadBoardId : this.leadBoardId; + this.leadStageId = dto.leadStageId !== undefined ? dto.leadStageId : this.leadStageId; + this.leadName = dto.leadName !== undefined ? dto.leadName : this.leadName; + this.ownerId = dto.ownerId !== undefined ? dto.ownerId : this.ownerId; + this.checkActiveLead = dto.checkActiveLead !== undefined ? dto.checkActiveLead : this.checkActiveLead; + this.checkDuplicate = dto.checkDuplicate !== undefined ? dto.checkDuplicate : this.checkDuplicate; + + return this; + } + + toDto(): ChatProviderEntitySettingsDto { + return { + contactEntityTypeId: this.contactEntityTypeId, + leadEntityTypeId: this.leadEntityTypeId, + leadBoardId: this.leadBoardId, + leadStageId: this.leadStageId, + leadName: this.leadName, + ownerId: this.ownerId, + checkActiveLead: this.checkActiveLead, + checkDuplicate: this.checkDuplicate, + }; + } +} diff --git a/backend/src/modules/multichat/chat-provider/entities/chat-provider.entity.ts b/backend/src/modules/multichat/chat-provider/entities/chat-provider.entity.ts new file mode 100644 index 0000000..c0ad4d7 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/entities/chat-provider.entity.ts @@ -0,0 +1,130 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { ChatProviderStatus, ChatProviderTransport, ChatProviderType } from '../../common'; +import { ChatProviderUser } from '../../chat-provider-user'; +import { CreateChatProviderDto, ChatProviderDto, UpdateChatProviderDto } from '../dto'; +import { ChatProviderEntitySettings } from './chat-provider-entity-settings.entity'; + +@Entity() +export class ChatProvider { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + createdBy: number; + + @Column() + type: ChatProviderType; + + @Column() + transport: ChatProviderTransport; + + @Column({ nullable: true }) + title: string | null; + + @Column() + status: ChatProviderStatus; + + @Column() + messagePerDay: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + createdBy: number, + type: ChatProviderType, + transport: ChatProviderTransport, + title: string | null, + status: ChatProviderStatus, + messagePerDay: number, + createdAt?: Date, + ) { + this.accountId = accountId; + this.createdBy = createdBy; + this.type = type; + this.transport = transport; + this.title = title; + this.status = status; + this.messagePerDay = messagePerDay; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _accessibleUsers: ChatProviderUser[]; + get accessibleUsers(): ChatProviderUser[] { + return this._accessibleUsers; + } + set accessibleUsers(value: ChatProviderUser[]) { + this._accessibleUsers = value; + } + + private _responsibleUsers: ChatProviderUser[]; + get responsibleUsers(): ChatProviderUser[] { + return this._responsibleUsers; + } + set responsibleUsers(value: ChatProviderUser[]) { + this._responsibleUsers = value; + } + + private _unseenCount: number | null; + get unseenCount(): number | null { + return this._unseenCount; + } + set unseenCount(value: number | null) { + this._unseenCount = value; + } + + private _supervisorUsers: ChatProviderUser[]; + get supervisorUsers(): ChatProviderUser[] { + return this._supervisorUsers; + } + set supervisorUsers(value: ChatProviderUser[]) { + this._supervisorUsers = value; + } + + private _entitySettings: ChatProviderEntitySettings | null; + get entitySettings(): ChatProviderEntitySettings | null { + return this._entitySettings; + } + set entitySettings(value: ChatProviderEntitySettings | null) { + this._entitySettings = value; + } + + canSendByPhone(): boolean { + return [ChatProviderType.Twilio, ChatProviderType.Wazzup].includes(this.type); + } + + static fromDto(accountId: number, createdBy: number, dto: CreateChatProviderDto): ChatProvider { + return new ChatProvider(accountId, createdBy, dto.type, dto.transport, dto.title, dto.status, dto.messagePerDay); + } + + toDto(): ChatProviderDto { + return { + id: this.id, + type: this.type, + transport: this.transport, + title: this.title, + status: this.status, + messagePerDay: this.messagePerDay, + accessibleUserIds: this.accessibleUsers?.map((user) => user.userId), + responsibleUserIds: this.responsibleUsers?.map((user) => user.userId), + supervisorUserIds: this.supervisorUsers?.map((user) => user.userId), + unseenCount: this.unseenCount, + entitySettings: this.entitySettings?.toDto(), + }; + } + + update(dto: UpdateChatProviderDto): ChatProvider { + this.title = dto.title !== undefined ? dto.title : this.title; + this.status = dto.status !== undefined ? dto.status : this.status; + this.messagePerDay = dto.messagePerDay !== undefined ? dto.messagePerDay : this.messagePerDay; + + return this; + } +} diff --git a/backend/src/modules/multichat/chat-provider/entities/index.ts b/backend/src/modules/multichat/chat-provider/entities/index.ts new file mode 100644 index 0000000..82da57c --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/entities/index.ts @@ -0,0 +1,2 @@ +export * from './chat-provider-entity-settings.entity'; +export * from './chat-provider.entity'; diff --git a/backend/src/modules/multichat/chat-provider/index.ts b/backend/src/modules/multichat/chat-provider/index.ts new file mode 100644 index 0000000..30a9009 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/index.ts @@ -0,0 +1,6 @@ +export * from './chat-provider.controller'; +export * from './chat-provider.module'; +export * from './dto'; +export * from './entities'; +export * from './services'; +export * from './types'; diff --git a/backend/src/modules/multichat/chat-provider/services/chat-provider-entity-settings.service.ts b/backend/src/modules/multichat/chat-provider/services/chat-provider-entity-settings.service.ts new file mode 100644 index 0000000..a0c50d4 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/services/chat-provider-entity-settings.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ChatProviderEntitySettingsDto } from '../dto'; +import { ChatProviderEntitySettings } from '../entities'; + +@Injectable() +export class ChatProviderEntitySettingsService { + constructor( + @InjectRepository(ChatProviderEntitySettings) + private readonly repository: Repository, + ) {} + + async create({ + accountId, + providerId, + dto, + }: { + accountId: number; + providerId: number; + dto: ChatProviderEntitySettingsDto; + }): Promise { + return this.repository.save(ChatProviderEntitySettings.fromDto({ accountId, providerId, dto })); + } + + async findOne({ + accountId, + providerId, + }: { + accountId: number; + providerId: number; + }): Promise { + return this.repository.findOneBy({ accountId, providerId }); + } + + async update({ + accountId, + providerId, + dto, + }: { + accountId: number; + providerId: number; + dto: ChatProviderEntitySettingsDto; + }): Promise { + const settings = await this.findOne({ accountId, providerId }); + if (settings) { + await this.repository.save(settings.update(dto)); + return settings; + } else { + return this.create({ accountId, providerId, dto }); + } + } + + async updateUser({ + accountId, + userId, + newUserId, + }: { + accountId: number; + userId: number; + newUserId?: number | null; + }): Promise { + await this.repository.update({ accountId, ownerId: userId }, { ownerId: newUserId ?? null }); + } + + async delete({ accountId, providerId }: { accountId: number; providerId: number }): Promise { + await this.repository.delete({ accountId, providerId }); + } +} diff --git a/backend/src/modules/multichat/chat-provider/services/chat-provider.handler.ts b/backend/src/modules/multichat/chat-provider/services/chat-provider.handler.ts new file mode 100644 index 0000000..6ed90e5 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/services/chat-provider.handler.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { ApplicationConfig } from '@/config'; + +import { AccountCreatedEvent, IamEventType, UserDeletedEvent } from '@/modules/iam/common'; + +import { ChatProviderDefaults } from '../const'; +import { ChatProviderService } from './chat-provider.service'; +import { ChatProviderEntitySettingsService } from './chat-provider-entity-settings.service'; + +@Injectable() +export class ChatProviderHandler { + private _appName: string; + constructor( + private readonly configService: ConfigService, + private readonly chatProviderService: ChatProviderService, + private readonly chatProviderEntitySettingsService: ChatProviderEntitySettingsService, + ) { + this._appName = this.configService.get('application').name; + } + + @OnEvent(IamEventType.AccountCreated, { async: true }) + async handleAccountCreatedEvent(event: AccountCreatedEvent) { + await this.chatProviderService.create(event.accountId, event.ownerId, { + type: ChatProviderDefaults.type, + transport: ChatProviderDefaults.transport, + title: this._appName, + status: ChatProviderDefaults.status, + messagePerDay: ChatProviderDefaults.messagePerDay, + }); + } + + @OnEvent(IamEventType.UserDeleted, { async: true }) + async onUserDeleted(event: UserDeletedEvent) { + await this.chatProviderEntitySettingsService.updateUser({ + accountId: event.accountId, + userId: event.userId, + newUserId: event.newUserId, + }); + } +} diff --git a/backend/src/modules/multichat/chat-provider/services/chat-provider.service.ts b/backend/src/modules/multichat/chat-provider/services/chat-provider.service.ts new file mode 100644 index 0000000..fc7c593 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/services/chat-provider.service.ts @@ -0,0 +1,329 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { + ChatProviderEvent, + ChatProviderTransport, + ChatProviderType, + ChatUserRole, + MultichatEventType, +} from '../../common'; +import { ChatService } from '../../chat/services/chat.service'; +import { ChatProviderUserService, ChatProviderUserType } from '../../chat-provider-user'; +import { ChatUser, ChatUserExternalDto, ChatUserService } from '../../chat-user'; + +import { ChatProviderDefaults } from '../const'; +import { CreateChatProviderDto, UpdateChatProviderDto } from '../dto'; +import { ChatProvider } from '../entities'; +import { ExpandableField } from '../types'; +import { ChatProviderEntitySettingsService } from './chat-provider-entity-settings.service'; + +interface FindFilter { + providerId?: number | number[]; + type?: ChatProviderType; + transport?: ChatProviderTransport; +} +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class ChatProviderService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(ChatProvider) + private readonly repository: Repository, + private readonly cpUserService: ChatProviderUserService, + private readonly cpEntitySettingsService: ChatProviderEntitySettingsService, + @Inject(forwardRef(() => ChatService)) + private readonly chatService: ChatService, + private readonly chatUserService: ChatUserService, + ) {} + + async create(accountId: number, userId: number, dto: CreateChatProviderDto): Promise { + dto.messagePerDay ??= ChatProviderDefaults.messagePerDay; + const provider = await this.repository.save(ChatProvider.fromDto(accountId, userId, dto)); + + if (dto.accessibleUserIds?.length > 0) { + provider.accessibleUsers = await this.cpUserService.create( + accountId, + provider.id, + dto.accessibleUserIds, + ChatProviderUserType.Accessible, + ); + } + if (dto.responsibleUserIds?.length > 0) { + provider.responsibleUsers = await this.cpUserService.create( + accountId, + provider.id, + dto.responsibleUserIds, + ChatProviderUserType.Responsible, + ); + } + if (dto.supervisorUserIds?.length > 0) { + provider.supervisorUsers = await this.cpUserService.create( + accountId, + provider.id, + dto.supervisorUserIds, + ChatProviderUserType.Supervisor, + ); + } + if (dto.entitySettings) { + provider.entitySettings = await this.cpEntitySettingsService.create({ + accountId, + providerId: provider.id, + dto: dto.entitySettings, + }); + } + + this.eventEmitter.emit( + MultichatEventType.ChatProviderCreated, + new ChatProviderEvent({ accountId, userId, providerId: provider.id, status: provider.status }), + ); + + return provider; + } + + async findOne( + accountId: number, + userId: number | null, + filter?: FindFilter, + options?: FindOptions, + ): Promise { + const provider = await this.createFindQb(accountId, filter).getOne(); + + const expandedProvider = + provider && options?.expand ? await this.expandOne(accountId, userId, provider, options.expand) : provider; + + if (userId) { + const accessibleUsers = + expandedProvider.accessibleUsers || + (await this.cpUserService.findMany(accountId, { + providerId: provider.id, + type: ChatProviderUserType.Accessible, + })); + + return !accessibleUsers || accessibleUsers.length === 0 || accessibleUsers.some((u) => u.userId === userId) + ? expandedProvider + : null; + } else { + return expandedProvider; + } + } + + async findMany( + accountId: number, + userId: number | null, + filter?: FindFilter, + options?: FindOptions, + ): Promise { + const providers = await this.createFindQb(accountId, filter).orderBy('cp.created_at').getMany(); + const expandedProviders = + providers && options?.expand ? await this.expandMany(accountId, userId, providers, options.expand) : providers; + + if (userId) { + const filteredProviders: ChatProvider[] = []; + for (const provider of expandedProviders) { + const accessibleUsers = + provider.accessibleUsers || + (await this.cpUserService.findMany(accountId, { + providerId: provider.id, + type: ChatProviderUserType.Accessible, + })); + if (!accessibleUsers || accessibleUsers.length === 0 || accessibleUsers.some((u) => u.userId === userId)) { + filteredProviders.push(provider); + } + } + return filteredProviders; + } else { + return expandedProviders; + } + } + + async update( + accountId: number, + userId: number | null, + providerId: number, + dto: UpdateChatProviderDto, + ): Promise { + const provider: ChatProvider = await this.findOne( + accountId, + userId, + { providerId }, + { expand: ['accessibleUsers', 'responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + + await this.repository.save(provider.update(dto)); + + if (dto.accessibleUserIds) { + provider.accessibleUsers = await this.cpUserService.update( + accountId, + provider.id, + provider.accessibleUsers, + dto.accessibleUserIds, + ChatProviderUserType.Accessible, + ); + } + if (dto.responsibleUserIds) { + provider.responsibleUsers = await this.cpUserService.update( + accountId, + provider.id, + provider.responsibleUsers, + dto.responsibleUserIds, + ChatProviderUserType.Responsible, + ); + } + if (dto.supervisorUserIds) { + provider.supervisorUsers = await this.cpUserService.update( + accountId, + provider.id, + provider.supervisorUsers, + dto.supervisorUserIds, + ChatProviderUserType.Supervisor, + ); + } + + if (dto.entitySettings === null) { + await this.cpEntitySettingsService.delete({ accountId, providerId: provider.id }); + provider.entitySettings = null; + } else if (dto.entitySettings) { + provider.entitySettings = await this.cpEntitySettingsService.update({ + accountId, + providerId: provider.id, + dto: dto.entitySettings, + }); + } + + this.eventEmitter.emit( + MultichatEventType.ChatProviderUpdated, + new ChatProviderEvent({ accountId, userId, providerId: provider.id, status: provider.status }), + ); + + return provider; + } + + async delete({ accountId, userId, providerId }: { accountId: number; userId: number; providerId: number }) { + await this.repository.delete({ accountId, id: providerId }); + + this.eventEmitter.emit( + MultichatEventType.ChatProviderDeleted, + new ChatProviderEvent({ accountId, userId, providerId }), + ); + } + + async getChatUserExternal({ + accountId, + providerId, + chatExternalId, + externalUserDto, + }: { + accountId: number; + providerId: number; + chatExternalId: string; + externalUserDto: ChatUserExternalDto; + }): Promise { + const chat = await this.chatService.findOne({ + accountId, + filter: { providerId, externalId: chatExternalId }, + }); + if (chat) { + const chatUser = await this.chatUserService.findOne(accountId, { + providerId, + chatId: chat.id, + role: ChatUserRole.EXTERNAL, + }); + if (chatUser) { + return chatUser; + } else { + const [newUser] = await this.chatUserService.addUsers({ + accountId, + chatId: chat.id, + externalUsers: [externalUserDto], + }); + return newUser; + } + } else { + const newChat = await this.chatService.createExternalChat(accountId, null, { + providerId: providerId, + externalId: chatExternalId, + title: `${externalUserDto.firstName} ${externalUserDto.lastName}`.trim(), + externalUser: externalUserDto, + }); + + return newChat.users.find((u) => u.externalUser?.externalId === externalUserDto.externalId); + } + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('cp').where('cp.account_id = :accountId', { accountId }); + + if (filter?.providerId) { + if (Array.isArray(filter.providerId)) { + qb.andWhere('cp.id IN (:...ids)', { ids: filter.providerId }); + } else { + qb.andWhere('cp.id = :id', { id: filter.providerId }); + } + } + + if (filter?.type) { + qb.andWhere('cp.type = :type', { type: filter.type }); + } + + if (filter?.transport) { + qb.andWhere('cp.transport = :transport', { transport: filter.transport }); + } + + return qb; + } + + private async expandOne( + accountId: number, + userId: number | null, + provider: ChatProvider, + expand: ExpandableField[], + ): Promise { + if (userId && expand.includes('unseenCount')) { + provider.unseenCount = await this.chatService.getUnseenForUser(accountId, userId, provider.id); + } + + if (expand.includes('accessibleUsers')) { + provider.accessibleUsers = await this.cpUserService.findMany(accountId, { + providerId: provider.id, + type: ChatProviderUserType.Accessible, + }); + } + + if (expand.includes('responsibleUsers')) { + provider.responsibleUsers = await this.cpUserService.findMany(accountId, { + providerId: provider.id, + type: ChatProviderUserType.Responsible, + }); + } + + if (expand.includes('supervisorUsers')) { + provider.supervisorUsers = await this.cpUserService.findMany(accountId, { + providerId: provider.id, + type: ChatProviderUserType.Supervisor, + }); + } + + if (expand.includes('entitySettings')) { + provider.entitySettings = await this.cpEntitySettingsService.findOne({ + accountId, + providerId: provider.id, + }); + } + + return provider; + } + private async expandMany( + accountId: number, + userId: number | null, + providers: ChatProvider[], + expand: ExpandableField[], + ): Promise { + return Promise.all(providers.map((provider) => this.expandOne(accountId, userId, provider, expand))); + } +} diff --git a/backend/src/modules/multichat/chat-provider/services/index.ts b/backend/src/modules/multichat/chat-provider/services/index.ts new file mode 100644 index 0000000..b09e3e3 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/services/index.ts @@ -0,0 +1,3 @@ +export * from './chat-provider-entity-settings.service'; +export * from './chat-provider.handler'; +export * from './chat-provider.service'; diff --git a/backend/src/modules/multichat/chat-provider/types/expandable-field.ts b/backend/src/modules/multichat/chat-provider/types/expandable-field.ts new file mode 100644 index 0000000..46ba67a --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/types/expandable-field.ts @@ -0,0 +1,6 @@ +export type ExpandableField = + | 'accessibleUsers' + | 'responsibleUsers' + | 'unseenCount' + | 'supervisorUsers' + | 'entitySettings'; diff --git a/backend/src/modules/multichat/chat-provider/types/index.ts b/backend/src/modules/multichat/chat-provider/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/multichat/chat-provider/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/multichat/chat-user/chat-user.module.ts b/backend/src/modules/multichat/chat-user/chat-user.module.ts new file mode 100644 index 0000000..158b9fb --- /dev/null +++ b/backend/src/modules/multichat/chat-user/chat-user.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { ChatUser, ChatUserExternal } from './entities'; +import { ChatUserService } from './chat-user.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([ChatUser, ChatUserExternal])], + providers: [ChatUserService], + exports: [ChatUserService], +}) +export class ChatUserModule {} diff --git a/backend/src/modules/multichat/chat-user/chat-user.service.ts b/backend/src/modules/multichat/chat-user/chat-user.service.ts new file mode 100644 index 0000000..6635af4 --- /dev/null +++ b/backend/src/modules/multichat/chat-user/chat-user.service.ts @@ -0,0 +1,195 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { NotFoundError } from '@/common'; + +import { ChatUserRole } from '../common'; +import { ChatUserExternalDto } from './dto'; +import { ChatUser, ChatUserExternal } from './entities'; + +interface FindFilter { + id?: number; + chatId?: number; + userId?: number; + providerId?: number; + externalId?: string; + role?: ChatUserRole; +} + +@Injectable() +export class ChatUserService { + constructor( + @InjectRepository(ChatUser) + private readonly repository: Repository, + @InjectRepository(ChatUserExternal) + private readonly extRepository: Repository, + ) {} + + async createForChat( + accountId: number, + chatId: number, + { + ownerIds, + userIds, + supervisorIds, + externalUsers, + }: { + ownerIds: number[]; + userIds?: number[]; + supervisorIds?: number[]; + externalUsers?: ChatUserExternalDto[]; + }, + ): Promise { + const chatUsers: ChatUser[] = []; + + for (const ownerId of ownerIds) { + chatUsers.push(await this.repository.save(new ChatUser(accountId, chatId, ChatUserRole.OWNER, ownerId))); + } + + if (userIds?.length > 0) { + for (const userId of userIds.filter((id) => !chatUsers.some((u) => u.userId === id))) { + chatUsers.push(await this.repository.save(new ChatUser(accountId, chatId, ChatUserRole.USER, userId))); + } + } + + if (supervisorIds?.length > 0) { + for (const supervisorId of supervisorIds.filter((id) => !chatUsers.some((u) => u.userId === id))) { + chatUsers.push( + await this.repository.save(new ChatUser(accountId, chatId, ChatUserRole.SUPERVISOR, supervisorId)), + ); + } + } + + if (externalUsers?.length > 0) { + for (const extUserDto of externalUsers) { + const user = await this.repository.save(new ChatUser(accountId, chatId, ChatUserRole.EXTERNAL, null)); + user.externalUser = await this.extRepository.save(ChatUserExternal.fromDto(accountId, user.id, extUserDto)); + chatUsers.push(user); + } + } + + return chatUsers; + } + + async findOne(accountId: number, filter: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + async getOne(accountId: number, filter: FindFilter): Promise { + const chatUser = await this.findOne(accountId, filter); + + if (!chatUser) { + throw new NotFoundError(`User ${filter?.userId} not found in chat ${filter?.chatId}`); + } + + return chatUser; + } + async findMany(accountId: number, filter: FindFilter): Promise { + return this.createFindQb(accountId, filter).getMany(); + } + async count(accountId: number, filter: FindFilter): Promise { + return this.createFindQb(accountId, filter).getCount(); + } + + async updateForGroupChat( + accountId: number, + chatId: number, + ownerId: number, + currentUsers: ChatUser[], + participantIds: number[], + ): Promise { + const removeUsers = currentUsers + .filter((user) => user.role !== ChatUserRole.EXTERNAL) + .filter((user) => !participantIds.some((id) => id === user.userId)) + .filter((user) => user.userId !== ownerId); + + const addedUsers = await this.addUsers({ accountId, chatId, userIds: participantIds, currentUsers }); + if (addedUsers.length) { + currentUsers.push(...addedUsers); + } + + if (removeUsers.length > 0) { + await this.repository.remove(removeUsers); + } + + return currentUsers.filter((user) => !removeUsers.some((u) => u.userId === user.userId)); + } + + async addUsers({ + accountId, + chatId, + userIds, + currentUsers, + externalUsers, + }: { + accountId: number; + chatId: number; + userIds?: number[] | null; + currentUsers?: ChatUser[]; + externalUsers?: ChatUserExternalDto[] | null; + }): Promise { + const chatUsers: ChatUser[] = []; + + if (userIds) { + const users = currentUsers ?? (await this.findMany(accountId, { chatId })); + const addUsers = userIds.filter((id) => !users.some((user) => user.userId === id)); + for (const userId of addUsers) { + chatUsers.push(await this.repository.save(new ChatUser(accountId, chatId, ChatUserRole.USER, userId))); + } + } + + if (externalUsers) { + for (const extUserDto of externalUsers) { + const user = await this.repository.save(new ChatUser(accountId, chatId, ChatUserRole.EXTERNAL, null)); + user.externalUser = await this.extRepository.save(ChatUserExternal.fromDto(accountId, user.id, extUserDto)); + chatUsers.push(user); + } + } + + return chatUsers; + } + + async updateExternalUser(accountId: number, chatUser: ChatUser, dto: ChatUserExternalDto): Promise { + const extUser = await this.extRepository.findOneBy({ accountId, chatUserId: chatUser.id }); + + await this.extRepository.save(extUser.update(dto)); + + chatUser.externalUser = extUser; + return chatUser; + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository + .createQueryBuilder('user') + .leftJoinAndMapOne('user.externalUser', ChatUserExternal, 'ext_user', `ext_user.chat_user_id = user.id`) + .where('user.account_id = :accountId', { accountId }); + + if (filter?.id) { + qb.andWhere('user.id = :id', { id: filter.id }); + } + + if (filter?.chatId) { + qb.andWhere('user.chat_id = :chatId', { chatId: filter.chatId }); + } + + if (filter?.userId) { + qb.andWhere('user.user_id = :userId', { userId: filter.userId }); + } + + if (filter?.externalId) { + qb.andWhere('ext_user.external_id = :externalId', { externalId: filter.externalId }); + } + + if (filter?.providerId) { + qb.leftJoin('chat', 'chat', 'user.chat_id = chat.id').andWhere('chat.provider_id = :providerId', { + providerId: filter.providerId, + }); + } + + if (filter?.role) { + qb.andWhere('user.role = :role', { role: filter.role }); + } + + return qb; + } +} diff --git a/backend/src/modules/multichat/chat-user/dto/chat-user-external.dto.ts b/backend/src/modules/multichat/chat-user/dto/chat-user-external.dto.ts new file mode 100644 index 0000000..2c9b5db --- /dev/null +++ b/backend/src/modules/multichat/chat-user/dto/chat-user-external.dto.ts @@ -0,0 +1,56 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class ChatUserExternalDto { + @ApiProperty() + @IsString() + externalId: string; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + firstName?: string; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + lastName?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + avatarUrl?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + phone?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + email?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + link?: string | null; + + constructor( + externalId: string, + firstName?: string, + lastName?: string | null, + avatarUrl?: string | null, + phone?: string | null, + email?: string | null, + link?: string | null, + ) { + this.externalId = externalId; + this.firstName = firstName; + this.lastName = lastName; + this.avatarUrl = avatarUrl; + this.phone = phone; + this.email = email; + this.link = link; + } +} diff --git a/backend/src/modules/multichat/chat-user/dto/chat-user.dto.ts b/backend/src/modules/multichat/chat-user/dto/chat-user.dto.ts new file mode 100644 index 0000000..8bc1a07 --- /dev/null +++ b/backend/src/modules/multichat/chat-user/dto/chat-user.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { ChatUserRole } from '../../common'; +import { ChatUserExternalDto } from './chat-user-external.dto'; + +export class ChatUserDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + userId: number | null; + + @ApiProperty({ enum: ChatUserRole }) + @IsEnum(ChatUserRole) + role: ChatUserRole; + + @ApiProperty({ type: ChatUserExternalDto, nullable: true }) + @IsOptional() + externalUser: ChatUserExternalDto | null; + + constructor({ id, userId, role, externalUser }: ChatUserDto) { + this.id = id; + this.userId = userId; + this.role = role; + this.externalUser = externalUser; + } +} diff --git a/backend/src/modules/multichat/chat-user/dto/index.ts b/backend/src/modules/multichat/chat-user/dto/index.ts new file mode 100644 index 0000000..8d35780 --- /dev/null +++ b/backend/src/modules/multichat/chat-user/dto/index.ts @@ -0,0 +1,2 @@ +export * from './chat-user-external.dto'; +export * from './chat-user.dto'; diff --git a/backend/src/modules/multichat/chat-user/entities/chat-user-external.entity.ts b/backend/src/modules/multichat/chat-user/entities/chat-user-external.entity.ts new file mode 100644 index 0000000..01906cb --- /dev/null +++ b/backend/src/modules/multichat/chat-user/entities/chat-user-external.entity.ts @@ -0,0 +1,96 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { ChatUserExternalDto } from '../dto'; + +@Entity() +export class ChatUserExternal { + @PrimaryColumn() + chatUserId: number; + + @Column() + externalId: string; + + @Column({ nullable: true }) + firstName: string | null; + + @Column({ nullable: true }) + lastName: string | null; + + @Column({ nullable: true }) + avatarUrl: string | null; + + @Column({ nullable: true }) + phone: string | null; + + @Column({ nullable: true }) + email: string | null; + + @Column({ nullable: true }) + link: string | null; + + @Column() + accountId: number; + + constructor( + accountId: number, + chatUserId: number, + externalId: string, + firstName: string | null, + lastName: string | null, + avatarUrl: string | null, + phone: string | null, + email: string | null, + link: string | null, + ) { + this.accountId = accountId; + this.chatUserId = chatUserId; + this.externalId = externalId; + this.firstName = firstName; + this.lastName = lastName; + this.avatarUrl = avatarUrl; + this.phone = phone; + this.email = email; + this.link = link; + } + + public static fromDto(accountId: number, chatUserId: number, dto: ChatUserExternalDto): ChatUserExternal { + return new ChatUserExternal( + accountId, + chatUserId, + dto.externalId, + dto.firstName, + dto.lastName, + dto.avatarUrl, + dto.phone, + dto.email, + dto.link, + ); + } + + public update(dto: ChatUserExternalDto): ChatUserExternal { + this.externalId = dto.externalId ? dto.externalId : this.externalId; + this.firstName = dto.firstName ? dto.firstName : this.firstName; + this.lastName = dto.lastName ? dto.lastName : this.lastName; + this.avatarUrl = dto.avatarUrl ? dto.avatarUrl : this.avatarUrl; + this.phone = dto.phone ? dto.phone : this.phone; + this.email = dto.email ? dto.email : this.email; + this.link = dto.link ? dto.link : this.link; + + return this; + } + + public toDto(): ChatUserExternalDto { + return { + externalId: this.externalId, + firstName: this.firstName, + lastName: this.lastName, + avatarUrl: this.avatarUrl, + phone: this.phone, + email: this.email, + link: this.link, + }; + } + + public fullName() { + return `${this.firstName} ${this.lastName ?? ''}`.trim(); + } +} diff --git a/backend/src/modules/multichat/chat-user/entities/chat-user.entity.ts b/backend/src/modules/multichat/chat-user/entities/chat-user.entity.ts new file mode 100644 index 0000000..44b186f --- /dev/null +++ b/backend/src/modules/multichat/chat-user/entities/chat-user.entity.ts @@ -0,0 +1,47 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { ChatUserRole } from '../../common'; +import { ChatUserDto } from '../dto'; +import { ChatUserExternal } from './chat-user-external.entity'; + +@Entity() +export class ChatUser { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + chatId: number; + + @Column({ nullable: true }) + userId: number | null; + + @Column() + role: ChatUserRole; + + @Column() + accountId: number; + + constructor(accountId: number, chatId: number, role: ChatUserRole, userId: number | null) { + this.accountId = accountId; + this.chatId = chatId; + this.role = role; + this.userId = userId; + } + + private _externalUser: ChatUserExternal | null; + public get externalUser(): ChatUserExternal | null { + return this._externalUser; + } + public set externalUser(value: ChatUserExternal | null) { + this._externalUser = value; + } + + public toDto(): ChatUserDto { + return new ChatUserDto({ + id: this.id, + userId: this.userId, + role: this.role, + externalUser: this.externalUser?.toDto() ?? null, + }); + } +} diff --git a/backend/src/modules/multichat/chat-user/entities/index.ts b/backend/src/modules/multichat/chat-user/entities/index.ts new file mode 100644 index 0000000..dc4bcd1 --- /dev/null +++ b/backend/src/modules/multichat/chat-user/entities/index.ts @@ -0,0 +1,2 @@ +export * from './chat-user-external.entity'; +export * from './chat-user.entity'; diff --git a/backend/src/modules/multichat/chat-user/index.ts b/backend/src/modules/multichat/chat-user/index.ts new file mode 100644 index 0000000..0e6900f --- /dev/null +++ b/backend/src/modules/multichat/chat-user/index.ts @@ -0,0 +1,4 @@ +export * from './chat-user.module'; +export * from './chat-user.service'; +export * from './dto'; +export * from './entities'; diff --git a/backend/src/modules/multichat/chat/chat.controller.ts b/backend/src/modules/multichat/chat/chat.controller.ts new file mode 100644 index 0000000..39a8181 --- /dev/null +++ b/backend/src/modules/multichat/chat/chat.controller.ts @@ -0,0 +1,231 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + ParseEnumPipe, + ParseIntPipe, + Patch, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; +import { ChatPagingQuery } from '@/common/dto/paging/chat-paging-query.dto'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { EntityInfoDto } from '@/modules/entity/entity-info'; + +import { ChatMessageStatus } from '../common'; +import { + ChatDto, + CreatePersonalChatDto, + CreateGroupChatDto, + CreateExternalChatDto, + FindChatsFullResultDto, + ChatFindFilterDto, + ChatFindPersonalFilterDto, + ChatFindByMessageContentFilterDto, + UpdateGroupChatDto, + CreateContactLeadDto, +} from './dto'; +import { ChatService } from './services'; + +@ApiTags('multichat/chats') +@Controller('chat/chats') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ChatController { + constructor(private readonly service: ChatService) {} + + @ApiOperation({ summary: 'Create personal chat', description: 'Create personal chat' }) + @ApiBody({ description: 'Data for creating personal chat', type: CreatePersonalChatDto }) + @ApiCreatedResponse({ description: 'Chat', type: ChatDto }) + @Post('personal') + async createPersonal(@CurrentAuth() { accountId, user }: AuthData, @Body() dto: CreatePersonalChatDto) { + return this.service.createPersonalChat(accountId, user, dto); + } + + @ApiOperation({ summary: 'Create group chat', description: 'Create group chat' }) + @ApiBody({ description: 'Data for creating group chat', type: CreateGroupChatDto }) + @ApiCreatedResponse({ description: 'Chat', type: ChatDto }) + @Post('group') + async createGroup(@CurrentAuth() { accountId, user }: AuthData, @Body() dto: CreateGroupChatDto) { + return this.service.createGroupChat(accountId, user, dto); + } + + @ApiOperation({ summary: 'Create external chat', description: 'Create external chat' }) + @ApiBody({ description: 'Data for creating external chat', type: CreateExternalChatDto }) + @ApiCreatedResponse({ description: 'Chat', type: ChatDto }) + @Post('external') + async createExternal(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateExternalChatDto) { + return this.service.createExternalChat(accountId, userId, dto); + } + + @ApiOperation({ summary: 'Check exists', description: 'Check group chat exists for entity' }) + @ApiParam({ name: 'entityId', description: 'Checked Entity ID' }) + @ApiOkResponse({ description: 'Chat exists', type: Boolean }) + @Get('group/exists/:entityId') + async checkByEntityId(@CurrentAuth() { accountId }: AuthData, @Param('entityId', ParseIntPipe) entityId: number) { + return (await this.service.count(accountId, { entityId })) > 0; + } + + @ApiOperation({ + summary: 'Get current user chats', + description: 'Get current user chat with pagination and provider filter', + }) + @ApiQuery({ name: 'providerId', description: 'Provider ID', required: false }) + @ApiQuery({ name: 'limit', description: 'Limit for pagination', required: false }) + @ApiQuery({ name: 'offset', description: 'Offset for pagination', required: false }) + @ApiQuery({ name: 'cursor', description: 'Cursor position for pagination', required: false }) + @ApiOkResponse({ description: 'Chat list', type: [ChatDto] }) + @Get() + async getChats( + @CurrentAuth() { accountId, user }: AuthData, + @Query() paging: ChatPagingQuery, + @Query('providerId') providerId: number | null, + ) { + return this.service.getChats(accountId, user, providerId, paging); + } + + @ApiOperation({ + summary: 'Search chats (simple)', + description: 'Search chats with filter. Chats returned without additional data.', + }) + @ApiOkResponse({ description: 'Search chat result', type: FindChatsFullResultDto }) + @Get('find') + async findMany( + @CurrentAuth() { accountId, userId }: AuthData, + @Query() filter: ChatFindFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.findMany({ accountId, filter, paging, accessUserId: userId }); + } + + @ApiOperation({ + summary: 'Search chats by user full name', + description: 'Search chats by user full name. Chats returned with users, last message, entity info, etc.', + }) + @ApiOkResponse({ description: 'Search chat result', type: FindChatsFullResultDto }) + @Get('find/full/personal') + async findManyFullPersonal( + @CurrentAuth() { accountId, user }: AuthData, + @Query() filter: ChatFindPersonalFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.findManyFullPersonal(accountId, user, filter, paging); + } + + @ApiOperation({ + summary: 'Search chats by message content', + description: 'Search chats by message content. Chats returned with users, last message, entity info, etc.', + }) + @ApiOkResponse({ description: 'Search chat result', type: FindChatsFullResultDto }) + @Get('find/full/by-message-content') + async findManyFullByMessageContent( + @CurrentAuth() { accountId, user }: AuthData, + @Query() paging: PagingQuery, + @Query() filter: ChatFindByMessageContentFilterDto, + ) { + return this.service.findManyFullByMessageContent(accountId, user, filter, paging); + } + + @ApiOperation({ + summary: 'Search chats (full)', + description: 'Search chats with filter. Chats returned with users, last message, entity info, etc.', + }) + @ApiOkResponse({ description: 'Search chat result', type: FindChatsFullResultDto }) + @Get('find/full') + async findManyFull( + @CurrentAuth() { accountId, user }: AuthData, + @Query() filter: ChatFindFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.findManyFull(accountId, user, filter, paging); + } + + @ApiOperation({ summary: 'Get chat', description: 'Get chat by ID' }) + @ApiParam({ name: 'chatId', description: 'Chat ID' }) + @ApiOkResponse({ description: 'Chat', type: ChatDto }) + @Get(':chatId') + async getChatFull(@CurrentAuth() { accountId, user }: AuthData, @Param('chatId', ParseIntPipe) chatId: number) { + return this.service.getChatFull(accountId, user, chatId); + } + + @ApiOperation({ summary: 'Update group chat', description: 'Update group chat' }) + @ApiParam({ name: 'chatId', description: 'Chat ID' }) + @ApiBody({ description: 'Data for updating group chat', type: UpdateGroupChatDto }) + @ApiOkResponse({ description: 'Chat', type: ChatDto }) + @Patch('group/:chatId') + async updateGroup( + @CurrentAuth() { accountId, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Body() dto: UpdateGroupChatDto, + ) { + return this.service.updateGroupChat(accountId, user, chatId, dto); + } + + @ApiOperation({ summary: 'Delete chat', description: 'Delete chat by ID' }) + @ApiParam({ name: 'chatId', description: 'Chat ID' }) + @ApiOkResponse({ description: 'Deleted chat ID', type: Number }) + @Delete(':chatId') + async delete(@CurrentAuth() { accountId, userId }: AuthData, @Param('chatId', ParseIntPipe) chatId: number) { + return this.service.delete(accountId, userId, chatId); + } + + @ApiOperation({ summary: 'Pin chat message', description: 'Pin chat message' }) + @ApiParam({ name: 'chatId', description: 'Chat ID' }) + @ApiParam({ name: 'messageId', description: 'Message ID' }) + @ApiOkResponse({ description: 'Chat', type: ChatDto }) + @Put(':chatId/pin/:messageId') + async pinChatMessage( + @CurrentAuth() { accountId, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return this.service.pinMessage(accountId, user, chatId, messageId); + } + + @ApiOperation({ summary: 'Unpin chat message', description: 'Unpin chat message' }) + @ApiParam({ name: 'chatId', description: 'Chat ID' }) + @ApiParam({ name: 'messageId', description: 'Message ID' }) + @ApiOkResponse({ description: 'Chat', type: ChatDto }) + @Put(':chatId/unpin/:messageId') + async unpinChatMessage( + @CurrentAuth() { accountId, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('messageId', ParseIntPipe) messageId: number, + ) { + return this.service.unpinMessage(accountId, user, chatId, messageId); + } + + @ApiOperation({ summary: 'Update chat messages status', description: 'Update chat messages status' }) + @ApiParam({ name: 'chatId', description: 'Chat ID' }) + @ApiParam({ name: 'status', description: 'Message status' }) + @ApiOkResponse() + @Put(':chatId/status/:status') + async updateMessagesStatus( + @CurrentAuth() { accountId, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Param('status', new ParseEnumPipe(ChatMessageStatus)) status: ChatMessageStatus, + ) { + await this.service.updateMessagesStatus({ accountId, user, chatId, status }); + } + + @ApiOperation({ summary: 'Create contact and lead', description: 'Create contact and lead for external chat' }) + @ApiParam({ name: 'chatId', description: 'Chat ID' }) + @ApiBody({ description: 'Data for creating contact and lead', type: CreateContactLeadDto }) + @ApiCreatedResponse({ description: 'Created entity info', type: EntityInfoDto }) + @Post(':chatId/contact') + async createLinkedEntities( + @CurrentAuth() { accountId, user }: AuthData, + @Param('chatId', ParseIntPipe) chatId: number, + @Body() dto: CreateContactLeadDto, + ) { + return this.service.createLinkedEntities({ accountId, user, chatId, dto }); + } +} diff --git a/backend/src/modules/multichat/chat/chat.module.ts b/backend/src/modules/multichat/chat/chat.module.ts new file mode 100644 index 0000000..40e8509 --- /dev/null +++ b/backend/src/modules/multichat/chat/chat.module.ts @@ -0,0 +1,34 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { CrmModule } from '@/CRM/crm.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { DocumentsModule } from '@/modules/documents/documents.module'; + +import { ChatUserModule } from '../chat-user'; +import { ChatMessageModule } from '../chat-message/chat-message.module'; +import { ChatProviderModule } from '../chat-provider/chat-provider.module'; +import { ProvidersModule } from '../providers/providers.module'; + +import { Chat, ChatPinnedMessage } from './entities'; +import { ChatService, ChatPinnedMessageService, ChatHandler } from './services'; +import { ChatController } from './chat.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Chat, ChatPinnedMessage]), + IAMModule, + forwardRef(() => CrmModule), + EntityInfoModule, + forwardRef(() => ChatMessageModule), + ChatProviderModule, + ProvidersModule, + ChatUserModule, + DocumentsModule, + ], + providers: [ChatService, ChatPinnedMessageService, ChatHandler], + controllers: [ChatController], + exports: [ChatService], +}) +export class ChatModule {} diff --git a/backend/src/modules/multichat/chat/dto/chat-find-by-message-content-filter.dto.ts b/backend/src/modules/multichat/chat/dto/chat-find-by-message-content-filter.dto.ts new file mode 100644 index 0000000..c5551fa --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/chat-find-by-message-content-filter.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class ChatFindByMessageContentFilterDto { + @ApiProperty({ description: 'Content to find messages with matching text', required: true }) + @IsString() + messageContent: string; + + @ApiPropertyOptional({ description: 'Provider ID to filter messages', nullable: true, required: false }) + @IsOptional() + @IsNumber() + providerId?: number | null; +} diff --git a/backend/src/modules/multichat/chat/dto/chat-find-filter.dto.ts b/backend/src/modules/multichat/chat/dto/chat-find-filter.dto.ts new file mode 100644 index 0000000..7ad9144 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/chat-find-filter.dto.ts @@ -0,0 +1,30 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; +import { ChatProviderTransport } from '../../common'; + +export class ChatFindFilterDto { + @ApiPropertyOptional({ description: 'Chat ID', nullable: true }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiPropertyOptional({ description: 'Phone number if external user in chat', nullable: true }) + @IsOptional() + @IsString() + phoneNumber?: string | null; + + @ApiPropertyOptional({ description: 'Chat provider transport', enum: ChatProviderTransport, nullable: true }) + @IsOptional() + @IsEnum(ChatProviderTransport) + transport?: ChatProviderTransport | null; + + @ApiPropertyOptional({ description: 'Chat title', nullable: true }) + @IsOptional() + @IsString() + title?: string | null; + + @ApiPropertyOptional({ description: 'Provider ID', nullable: true, required: false }) + @IsOptional() + @IsNumber() + providerId?: number; +} diff --git a/backend/src/modules/multichat/chat/dto/chat-find-personal-filter.dto.ts b/backend/src/modules/multichat/chat/dto/chat-find-personal-filter.dto.ts new file mode 100644 index 0000000..b594615 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/chat-find-personal-filter.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class ChatFindPersonalFilterDto { + @ApiProperty({ description: 'Full name of user with whom personal chat was created', required: false }) + @IsOptional() + @IsString() + fullName?: string; + + @ApiPropertyOptional({ description: 'Provider ID', nullable: true, required: false }) + @IsOptional() + @IsNumber() + providerId?: number | null; +} diff --git a/backend/src/modules/multichat/chat/dto/chat.dto.ts b/backend/src/modules/multichat/chat/dto/chat.dto.ts new file mode 100644 index 0000000..40ca2cb --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/chat.dto.ts @@ -0,0 +1,73 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { ChatType } from '../../common'; +import { ChatMessageDto } from '../../chat-message/dto/chat-message.dto'; +import { ChatUserDto } from '../../chat-user'; + +export class ChatDto { + @ApiProperty({ description: 'Chat ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Chat provider ID' }) + @IsNumber() + providerId: number; + + @ApiProperty({ description: 'User ID who created the chat' }) + @IsNumber() + createdBy: number; + + @ApiProperty({ description: 'External ID of the chat', nullable: true }) + @IsOptional() + @IsString() + externalId: string | null; + + @ApiProperty({ description: 'Type of the chat', enum: ChatType }) + @IsEnum(ChatType) + type: ChatType; + + @ApiProperty({ description: 'Title of the chat', nullable: true }) + @IsOptional() + @IsString() + title: string | null; + + @ApiProperty({ description: 'Entity ID associated with the chat', nullable: true }) + @IsOptional() + @IsNumber() + entityId: number | null; + + @ApiProperty({ description: 'Date and time when the chat was created' }) + @IsString() + createdAt: string; + + @ApiProperty({ description: 'Users in the chat', type: [ChatUserDto] }) + @IsArray() + users: ChatUserDto[]; + + @ApiProperty({ description: 'Pinned messages in the chat', type: [ChatMessageDto] }) + @IsArray() + pinnedMessages: ChatMessageDto[]; + + @ApiProperty({ description: 'Last message in the chat', type: [ChatMessageDto], nullable: true }) + lastMessage: ChatMessageDto | null; + + @ApiProperty({ description: 'Number of unseen messages in the chat for requested user' }) + @IsNumber() + unseenCount: number; + + @ApiProperty({ description: 'Date and time when the chat was last updated' }) + @IsString() + updatedAt: string; + + @ApiProperty({ description: 'Entity information associated with the chat', type: EntityInfoDto, nullable: true }) + @IsOptional() + entityInfo: EntityInfoDto | null; + + @ApiPropertyOptional({ description: 'Whether the user has access to the chat', nullable: true }) + @IsOptional() + @IsBoolean() + hasAccess?: boolean | null; +} diff --git a/backend/src/modules/multichat/chat/dto/create-contact-lead.dto.ts b/backend/src/modules/multichat/chat/dto/create-contact-lead.dto.ts new file mode 100644 index 0000000..a691cd1 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/create-contact-lead.dto.ts @@ -0,0 +1,44 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class CreateContactLeadDto { + @ApiPropertyOptional({ description: 'Contact entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + contactTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead entity type ID', nullable: true }) + @IsOptional() + @IsNumber() + leadTypeId?: number | null; + + @ApiPropertyOptional({ description: 'Lead board ID', nullable: true }) + @IsOptional() + @IsNumber() + leadBoardId?: number | null; + + @ApiPropertyOptional({ description: 'Lead stage ID', nullable: true }) + @IsOptional() + @IsNumber() + leadStageId?: number | null; + + @ApiPropertyOptional({ description: 'Lead name', nullable: true }) + @IsOptional() + @IsString() + leadName?: string | null; + + @ApiPropertyOptional({ description: 'Lead and Contact responsible user ID', nullable: true }) + @IsOptional() + @IsNumber() + ownerId?: number | null; + + @ApiPropertyOptional({ description: 'Do not create lead if active lead exists', nullable: true }) + @IsOptional() + @IsBoolean() + checkActiveLead?: boolean; + + @ApiPropertyOptional({ description: 'Do not create duplicate contact', nullable: true }) + @IsOptional() + @IsBoolean() + checkDuplicate?: boolean; +} diff --git a/backend/src/modules/multichat/chat/dto/create-external-chat.dto.ts b/backend/src/modules/multichat/chat/dto/create-external-chat.dto.ts new file mode 100644 index 0000000..df4a136 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/create-external-chat.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ChatUserExternalDto } from '../../chat-user'; + +export class CreateExternalChatDto { + @ApiProperty({ description: 'Provider ID' }) + @IsNumber() + providerId: number; + + @ApiProperty({ description: 'Chat title' }) + @IsString() + title: string; + + @ApiPropertyOptional({ description: 'User IDs of chat participants', type: [Number], nullable: true }) + @IsOptional() + @IsArray() + participantIds?: number[] | null; + + @ApiPropertyOptional({ description: 'Entity ID associated with the chat', nullable: true }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiPropertyOptional({ description: 'External ID of the chat', nullable: true }) + @IsOptional() + @IsString() + externalId?: string | null; + + @ApiPropertyOptional({ description: 'External user data', type: ChatUserExternalDto }) + @IsOptional() + externalUser?: ChatUserExternalDto; +} diff --git a/backend/src/modules/multichat/chat/dto/create-group-chat.dto.ts b/backend/src/modules/multichat/chat/dto/create-group-chat.dto.ts new file mode 100644 index 0000000..ecc8be2 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/create-group-chat.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class CreateGroupChatDto { + @ApiProperty({ description: 'Provider ID' }) + @IsNumber() + providerId: number; + + @ApiProperty({ description: 'Chat title' }) + @IsString() + title: string; + + @ApiProperty({ description: 'User IDs of chat participants', type: [Number] }) + @IsArray() + participantIds: number[]; + + @ApiPropertyOptional({ description: 'Entity ID associated with the chat', nullable: true }) + @IsOptional() + @IsNumber() + entityId?: number | null; +} diff --git a/backend/src/modules/multichat/chat/dto/create-personal-chat.dto.ts b/backend/src/modules/multichat/chat/dto/create-personal-chat.dto.ts new file mode 100644 index 0000000..60dcf0a --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/create-personal-chat.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; + +export class CreatePersonalChatDto { + @ApiProperty({ description: 'Provider ID' }) + @IsNumber() + providerId: number; + + @ApiProperty({ description: 'User ID of chat companion' }) + @IsNumber() + companionId: number; +} diff --git a/backend/src/modules/multichat/chat/dto/find-chats-full-result.dto.ts b/backend/src/modules/multichat/chat/dto/find-chats-full-result.dto.ts new file mode 100644 index 0000000..4df69d7 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/find-chats-full-result.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsObject } from 'class-validator'; + +import { PagingMeta } from '@/common'; +import { ChatDto } from './chat.dto'; + +export class FindChatsFullResultDto { + @ApiProperty({ description: 'List of chats', type: [ChatDto] }) + @IsArray() + chats: ChatDto[]; + + @ApiProperty({ description: 'Chat metadata', type: PagingMeta }) + @IsObject() + meta: PagingMeta; + + constructor(chats: ChatDto[], meta: PagingMeta) { + this.chats = chats; + this.meta = meta; + } +} diff --git a/backend/src/modules/multichat/chat/dto/index.ts b/backend/src/modules/multichat/chat/dto/index.ts new file mode 100644 index 0000000..1d0e5b9 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/index.ts @@ -0,0 +1,10 @@ +export * from './chat-find-by-message-content-filter.dto'; +export * from './chat-find-filter.dto'; +export * from './chat-find-personal-filter.dto'; +export * from './chat.dto'; +export * from './create-contact-lead.dto'; +export * from './create-external-chat.dto'; +export * from './create-group-chat.dto'; +export * from './create-personal-chat.dto'; +export * from './find-chats-full-result.dto'; +export * from './update-group-chat.dto'; diff --git a/backend/src/modules/multichat/chat/dto/update-group-chat.dto.ts b/backend/src/modules/multichat/chat/dto/update-group-chat.dto.ts new file mode 100644 index 0000000..d727333 --- /dev/null +++ b/backend/src/modules/multichat/chat/dto/update-group-chat.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class UpdateGroupChatDto { + @ApiPropertyOptional({ description: 'Chat title', nullable: true }) + @IsOptional() + @IsString() + title?: string | null; + + @ApiPropertyOptional({ description: 'Entity ID associated with the chat', nullable: true }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiProperty({ description: 'User IDs of chat participants', type: [Number] }) + @IsOptional() + @IsArray() + participantIds?: number[]; +} diff --git a/backend/src/modules/multichat/chat/entities/chat-pinned-message.entity.ts b/backend/src/modules/multichat/chat/entities/chat-pinned-message.entity.ts new file mode 100644 index 0000000..35574d7 --- /dev/null +++ b/backend/src/modules/multichat/chat/entities/chat-pinned-message.entity.ts @@ -0,0 +1,25 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +@Entity() +export class ChatPinnedMessage { + @PrimaryColumn() + chatId: number; + + @PrimaryColumn() + messageId: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor(chatId: number, messageId: number, accountId: number, createdAt?: Date) { + this.chatId = chatId; + this.messageId = messageId; + this.accountId = accountId; + this.createdAt = createdAt ?? DateUtil.now(); + } +} diff --git a/backend/src/modules/multichat/chat/entities/chat.entity.ts b/backend/src/modules/multichat/chat/entities/chat.entity.ts new file mode 100644 index 0000000..0be7224 --- /dev/null +++ b/backend/src/modules/multichat/chat/entities/chat.entity.ts @@ -0,0 +1,164 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { ChatType } from '../../common'; +import { ChatMessage } from '../../chat-message/entities/chat-message.entity'; +import { ChatUser } from '../../chat-user'; +import { CreatePersonalChatDto, CreateGroupChatDto, CreateExternalChatDto, ChatDto } from '../dto'; + +@Entity() +export class Chat { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + providerId: number; + + @Column({ nullable: true }) + createdBy: number | null; + + @Column({ nullable: true }) + externalId: string | null; + + @Column() + type: ChatType; + + @Column({ nullable: true }) + title: string | null; + + @Column({ nullable: true }) + entityId: number | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + @Column({ select: false, insert: false, update: false }) + totalMessageCount: number | null; + + @Column({ select: false, insert: false, update: false }) + seenByUserCount: number | null; + + @Column({ select: false, insert: false, update: false }) + updatedAt: Date; + + constructor( + accountId: number, + providerId: number, + createdBy: number | null, + externalId: string | null, + type: ChatType, + title: string | null, + entityId: number | null, + createdAt?: Date, + ) { + this.accountId = accountId; + this.providerId = providerId; + this.createdBy = createdBy; + this.externalId = externalId; + this.type = type; + this.title = title; + this.entityId = entityId; + this.createdAt = createdAt ?? DateUtil.now(); + this.updatedAt = this.createdAt; + } + + private _users: ChatUser[]; + public set users(value: ChatUser[]) { + this._users = value; + } + public get users(): ChatUser[] { + return this._users; + } + + private _pinnedMessages: ChatMessage[]; + public set pinnedMessages(value: ChatMessage[]) { + this._pinnedMessages = value; + } + public get pinnedMessages(): ChatMessage[] { + return this._pinnedMessages; + } + + private _lastMessage: ChatMessage | null; + public set lastMessage(value: ChatMessage | null) { + this._lastMessage = value; + } + public get lastMessage(): ChatMessage | null { + return this._lastMessage; + } + + private _entityInfo: EntityInfoDto | null; + public set entityInfo(value: EntityInfoDto | null) { + this._entityInfo = value; + } + public get entityInfo(): EntityInfoDto | null { + return this._entityInfo; + } + + private _hasAccess: boolean | null; + public get hasAccess(): boolean | null { + return this._hasAccess; + } + public set hasAccess(value: boolean | null) { + this._hasAccess = value; + } + + public static personalFromDto( + accountId: number, + createdBy: number, + dto: CreatePersonalChatDto, + externalId: string | null = null, + ): Chat { + return new Chat(accountId, dto.providerId, createdBy, externalId, ChatType.PERSONAL, null, null); + } + + public static groupFromDto( + accountId: number, + createdBy: number, + dto: CreateGroupChatDto, + externalId: string | null = null, + ): Chat { + return new Chat(accountId, dto.providerId, createdBy, externalId, ChatType.GROUP, dto.title, dto.entityId); + } + + public static externalFromDto(accountId: number, createdBy: number | null, dto: CreateExternalChatDto): Chat { + return new Chat( + accountId, + dto.providerId, + createdBy, + dto.externalId ?? null, + ChatType.GROUP, + dto.title, + dto.entityId, + ); + } + + public toDto(): ChatDto { + const users = this._users ? this._users.map((user) => user.toDto()) : []; + const pinnedMessages = this._pinnedMessages ? this._pinnedMessages.map((message) => message.toDto()) : []; + const lastMessage = this._lastMessage ? this._lastMessage.toDto() : null; + const unseenCount = this.totalMessageCount ? this.totalMessageCount - (this.seenByUserCount ?? 0) : 0; + return { + id: this.id, + providerId: this.providerId, + createdBy: this.createdBy, + externalId: this.externalId, + type: this.type, + title: this.title, + entityId: this.entityId, + createdAt: this.createdAt.toISOString(), + users, + pinnedMessages, + lastMessage, + unseenCount, + updatedAt: this.updatedAt?.toISOString(), + entityInfo: this.entityInfo, + hasAccess: this.hasAccess, + }; + } +} diff --git a/backend/src/modules/multichat/chat/entities/index.ts b/backend/src/modules/multichat/chat/entities/index.ts new file mode 100644 index 0000000..931fa57 --- /dev/null +++ b/backend/src/modules/multichat/chat/entities/index.ts @@ -0,0 +1,2 @@ +export * from './chat-pinned-message.entity'; +export * from './chat.entity'; diff --git a/backend/src/modules/multichat/chat/services/chat-pinned-message.service.ts b/backend/src/modules/multichat/chat/services/chat-pinned-message.service.ts new file mode 100644 index 0000000..13f4c52 --- /dev/null +++ b/backend/src/modules/multichat/chat/services/chat-pinned-message.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ChatPinnedMessage } from '../entities'; + +@Injectable() +export class ChatPinnedMessageService { + constructor( + @InjectRepository(ChatPinnedMessage) + private repository: Repository, + ) {} + + public async pinMessage(accountId: number, chatId: number, messageId: number): Promise { + return await this.repository.save(new ChatPinnedMessage(chatId, messageId, accountId)); + } + + public async unpinMessage(accountId: number, chatId: number, messageId: number): Promise { + await this.repository.delete({ chatId, messageId, accountId }); + } +} diff --git a/backend/src/modules/multichat/chat/services/chat.handler.ts b/backend/src/modules/multichat/chat/services/chat.handler.ts new file mode 100644 index 0000000..51d94f4 --- /dev/null +++ b/backend/src/modules/multichat/chat/services/chat.handler.ts @@ -0,0 +1,51 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { + ActionChatSendAmworkSettings, + AutomationJob, + EntityTypeActionType, + OnAutomationJob, +} from '@/modules/automation'; + +import { ChatService } from './chat.service'; + +interface EntityVariables { + id?: number | null; + stageId?: number | null; +} + +interface SendChatVariables { + accountId: number; + entity?: EntityVariables | null; + chatSettings?: ActionChatSendAmworkSettings | null; +} + +@Injectable() +export class ChatHandler { + private readonly logger = new Logger(ChatHandler.name); + constructor(private readonly service: ChatService) {} + + @OnAutomationJob(EntityTypeActionType.ChatSendAmwork) + async handleSendMessageJob(job: AutomationJob): Promise<{ variables?: unknown }> { + if (!job.variables?.accountId || !job.variables?.entity || !job.variables?.chatSettings) { + this.logger.warn(`Automation job variables are not valid`, job.variables); + return { variables: job.variables }; + } + + try { + const { accountId, entity, chatSettings } = job.variables; + + const chat = await this.service.processAutomation({ + accountId, + entityId: entity.id, + entityStageId: entity.stageId, + settings: chatSettings, + }); + + return { variables: { ...job.variables, chat: chat?.toDto() } }; + } catch (e) { + this.logger.error(`Automation job error`, (e as Error)?.stack); + return { variables: job.variables }; + } + } +} diff --git a/backend/src/modules/multichat/chat/services/chat.service.ts b/backend/src/modules/multichat/chat/services/chat.service.ts new file mode 100644 index 0000000..5a37c56 --- /dev/null +++ b/backend/src/modules/multichat/chat/services/chat.service.ts @@ -0,0 +1,925 @@ +import Handlebars from 'handlebars'; +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, type SelectQueryBuilder } from 'typeorm'; + +import { + BadRequestError, + ForbiddenError, + NotFoundError, + PagingMeta, + type PagingQuery, +} from '@/common'; +import { ChatPagingQuery } from '@/common/dto/paging/chat-paging-query.dto'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AccountService } from '@/modules/iam/account/account.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { UserService } from '@/modules/iam/user/user.service'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; +import { ActionChatSendAmworkSettings } from '@/modules/automation'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { DocumentGenerationService } from '@/modules/documents/document-generation/document-generation.service'; + +import { + ChatEvent, + ChatMessageStatus, + ChatProviderTransport, + ChatType, + ChatUserRole, + MultichatEventType, +} from '../../common'; +import { ChatUser, ChatUserExternalDto, ChatUserService } from '../../chat-user'; +import { ChatMessageUserStatus } from '../../chat-message/entities/chat-message-user-status.entity'; +import { ChatMessage } from '../../chat-message/entities/chat-message.entity'; +import { ChatMessageService } from '../../chat-message/services/chat-message.service'; +import { ChatProviderService } from '../../chat-provider/services/chat-provider.service'; +import { ChatProviderProxyService } from '../../providers/chat-provider-proxy.service'; + +import { + CreatePersonalChatDto, + CreateGroupChatDto, + CreateExternalChatDto, + UpdateGroupChatDto, + FindChatsFullResultDto, + ChatFindPersonalFilterDto, + ChatFindByMessageContentFilterDto, + CreateContactLeadDto, +} from '../dto'; +import { Chat, ChatPinnedMessage } from '../entities'; +import { ChatPinnedMessageService } from './chat-pinned-message.service'; + +const LAST_MESSAGE = 'select cm.id from chat_message cm where cm.chat_id = chat.id order by cm.created_at desc limit 1'; + +interface FindFilter { + chatId?: number; + type?: ChatType; + entityId?: number; + phoneNumber?: string; + externalId?: string; + providerId?: number | number[]; + transport?: ChatProviderTransport; + title?: string; +} + +interface ChatUpdateData { + externalId?: string; +} + +@Injectable() +export class ChatService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Chat) + private readonly repository: Repository, + private readonly accountService: AccountService, + private readonly userService: UserService, + @Inject(forwardRef(() => EntityService)) + private readonly entityService: EntityService, + private readonly entityInfoService: EntityInfoService, + private readonly chatUserService: ChatUserService, + private readonly chatPinnedMessageService: ChatPinnedMessageService, + @Inject(forwardRef(() => ChatMessageService)) + private readonly chatMessageService: ChatMessageService, + @Inject(forwardRef(() => ChatProviderService)) + private readonly chatProviderService: ChatProviderService, + private readonly providerProxyService: ChatProviderProxyService, + @Inject(forwardRef(() => DocumentGenerationService)) + private readonly documentGenerationService: DocumentGenerationService, + ) {} + + async createPersonalChat(accountId: number, user: User, dto: CreatePersonalChatDto): Promise { + const currentChat = await this.createFindQb(accountId, null, { + type: ChatType.PERSONAL, + providerId: dto.providerId, + }) + .innerJoin(ChatUser, 'cu1', 'cu1.chat_id = chat.id and cu1.user_id = :user1Id', { user1Id: user.id }) + .innerJoin(ChatUser, 'cu2', 'cu2.chat_id = chat.id and cu2.user_id = :user2Id', { user2Id: dto.companionId }) + .getOne(); + + if (currentChat) { + return this.getChatFull(accountId, user, currentChat.id); + } + + const chat = await this.repository.save(Chat.personalFromDto(accountId, user.id, dto)); + chat.users = await this.chatUserService.createForChat(accountId, chat.id, { + ownerIds: [chat.createdBy, dto.companionId], + }); + chat.hasAccess = true; + + this.notifyChatUsers(accountId, chat, MultichatEventType.ChatCreated, user.id); + + return chat; + } + + async createGroupChat(accountId: number, user: User | null, dto: CreateGroupChatDto): Promise { + if (dto.entityId) { + const currentChat = await this.findOne({ + accountId, + filter: { type: ChatType.GROUP, providerId: dto.providerId, entityId: dto.entityId }, + }); + + if (currentChat) { + const chat = await this.getChatFull(accountId, user, currentChat.id, false); + const addedUsers = await this.chatUserService.addUsers({ accountId, chatId: chat.id, userIds: [user.id] }); + if (addedUsers.length) { + if (chat.users) { + chat.users.push(...addedUsers); + } else { + chat.users = addedUsers; + } + } + chat.hasAccess = true; + return chat; + } + } + + const chat = await this.repository.save(Chat.groupFromDto(accountId, user.id, dto)); + + chat.users = await this.chatUserService.createForChat(accountId, chat.id, { + ownerIds: [user.id], + userIds: dto.participantIds, + }); + chat.hasAccess = true; + + this.notifyChatUsers(accountId, chat, MultichatEventType.ChatCreated, user.id); + + return chat; + } + + async createExternalChat(accountId: number, userId: number | null, dto: CreateExternalChatDto): Promise { + if (!dto.externalId) { + dto.externalId = await this.providerProxyService.createChatExternalId( + accountId, + dto.providerId, + dto.externalUser.externalId, + ); + } + + const currentChat = await this.findOne({ + accountId, + filter: { type: ChatType.GROUP, providerId: dto.providerId, externalId: dto.externalId }, + }); + + if (currentChat) { + const chat = await this.getChatFull(accountId, null, currentChat.id, false); + if (userId) { + const addedUsers = await this.chatUserService.addUsers({ accountId, chatId: chat.id, userIds: [userId] }); + if (addedUsers.length) { + if (chat.users) { + chat.users.push(...addedUsers); + } else { + chat.users = addedUsers; + } + } + } + chat.hasAccess = true; + return chat; + } else { + const chat = await this.repository.save(Chat.externalFromDto(accountId, userId, dto)); + const provider = await this.chatProviderService.findOne( + accountId, + null, + { providerId: dto.providerId }, + { expand: ['responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + const participantIds = + dto.participantIds?.length > 0 ? dto.participantIds : provider.responsibleUsers.map((u) => u.userId); + const supervisorIds = provider.supervisorUsers.map((u) => u.userId); + dto.externalUser.phone = dto.externalUser.phone?.startsWith('+') + ? dto.externalUser.phone.slice(1) + : dto.externalUser.phone; + chat.users = await this.chatUserService.createForChat(accountId, chat.id, { + ownerIds: userId ? [userId] : participantIds, + userIds: participantIds, + supervisorIds: supervisorIds, + externalUsers: [dto.externalUser], + }); + chat.hasAccess = true; + + if (!dto.entityId && provider.entitySettings) { + const user = await this.userService.findOne({ + accountId, + id: provider.entitySettings.ownerId ?? userId ?? provider.createdBy, + }); + const entity = await this.createLinkedEntities({ + accountId, + user, + chatId: chat.id, + dto: { + contactTypeId: provider.entitySettings.contactEntityTypeId, + leadTypeId: provider.entitySettings.leadEntityTypeId, + leadBoardId: provider.entitySettings.leadBoardId, + leadStageId: provider.entitySettings.leadStageId, + leadName: provider.entitySettings.leadName, + ownerId: provider.entitySettings.ownerId, + checkActiveLead: provider.entitySettings.checkActiveLead, + checkDuplicate: provider.entitySettings.checkDuplicate, + }, + }); + if (entity) { + chat.entityId = entity.id; + } + } + + this.notifyChatUsers(accountId, chat, MultichatEventType.ChatCreated, userId); + + return chat; + } + } + + async updateExternalId(accountId: number, chatId: number, { externalId }: ChatUpdateData) { + const chat = await this.findOne({ accountId, filter: { chatId } }); + if (chat) { + if (externalId !== undefined) { + chat.externalId = externalId; + } + + await this.repository.save(chat); + } + } + + async mergeChat(account: Account, fromChatId: number, toChatId: number) { + const fromChat = await this.findOne({ accountId: account.id, filter: { chatId: fromChatId } }); + fromChat.users = await this.chatUserService.findMany(account.id, { chatId: fromChatId }); + const toChat = await this.findOne({ accountId: account.id, filter: { chatId: toChatId } }); + toChat.users = await this.chatUserService.findMany(account.id, { chatId: toChatId }); + + const fromChatInternalUsers = fromChat.users.filter((u) => u.userId); + const toChatInternalUsers = toChat.users.filter((u) => u.userId); + const addInternalUsers = fromChatInternalUsers.filter( + (fu) => !toChatInternalUsers.some((tu) => tu.userId === fu.userId), + ); + + const fromChatExternalUsers = fromChat.users.filter((u) => u.role === ChatUserRole.EXTERNAL); + const toChatExternalUsers = toChat.users.filter((u) => u.role === ChatUserRole.EXTERNAL); + const addExternalUsers = fromChatExternalUsers.filter( + (fu) => + !( + toChatExternalUsers.some((tu) => tu.externalUser.externalId === fu.externalUser.externalId) || + toChatExternalUsers.some((tu) => tu.externalUser.phone === fu.externalUser.phone) + ), + ); + + if (addInternalUsers.length > 0 || addExternalUsers.length > 0) { + const addUsers = await this.chatUserService.addUsers({ + accountId: account.id, + chatId: toChatId, + userIds: addInternalUsers.map((iu) => iu.userId), + externalUsers: addExternalUsers.map( + (eu) => + new ChatUserExternalDto( + eu.externalUser.externalId, + eu.externalUser.firstName, + eu.externalUser.lastName, + eu.externalUser.avatarUrl, + eu.externalUser.phone, + eu.externalUser.email, + eu.externalUser.link, + ), + ), + }); + toChat.users.push(...addUsers); + } + + //TODO; merge messages + const messages = await this.chatMessageService.findMany( + account.id, + { chatId: fromChatId }, + { expand: ['chatUser'] }, + ); + for (const message of messages) { + const toChatUser = message.chatUser.userId + ? toChat.users.find((u) => u.userId === message.chatUser.userId) + : toChat.users.find( + (u) => + u.externalUser?.externalId === message.chatUser.externalUser.externalId || + u.externalUser?.phone === message.chatUser.externalUser.phone, + ); + if (toChatUser.userId) { + const user = await this.userService.findOne({ accountId: account.id, id: toChatUser.userId }); + await this.chatMessageService.create(account, user, toChat.id, { text: message.text }, false); + } else if (toChatUser.externalUser) { + await this.chatMessageService.createExternal( + account, + toChatUser, + message.text, + message.externalId, + null, + false, + ); + } + } + await this.delete(account.id, null, fromChatId); + } + + async updateGroupChat(accountId: number, user: User, chatId: number, dto: UpdateGroupChatDto): Promise { + const chat = await this.getChatFull(accountId, user, chatId); + + if (chat.type !== ChatType.GROUP) { + throw NotFoundError.withId(Chat, chatId); + } + + if (dto.participantIds) { + chat.users = await this.chatUserService.updateForGroupChat( + accountId, + chat.id, + chat.createdBy, + chat.users, + dto.participantIds, + ); + } + + if (dto.entityId) { + chat.entityId = dto.entityId; + } + if (dto.title) { + chat.title = dto.title; + } + await this.repository.save(chat); + + this.notifyChatUsers(accountId, chat, MultichatEventType.ChatUpdated, user.id); + + return chat; + } + + async pinMessage(accountId: number, user: User, chatId: number, messageId: number): Promise { + const chat = await this.getChatFull(accountId, user, chatId); + + const message = await this.chatMessageService.getMessageSimple(accountId, chatId, messageId); + await this.chatPinnedMessageService.pinMessage(accountId, chat.id, message.id); + chat.pinnedMessages = [message, ...chat.pinnedMessages.filter((msg) => msg.id !== message.id)]; + + this.notifyChatUsers(accountId, chat, MultichatEventType.ChatUpdated, user.id); + + return chat; + } + + async unpinMessage(accountId: number, user: User, chatId: number, messageId: number): Promise { + const chat = await this.getChatFull(accountId, user, chatId); + + const message = await this.chatMessageService.getMessageSimple(accountId, chatId, messageId); + await this.chatPinnedMessageService.unpinMessage(accountId, chat.id, message.id); + chat.pinnedMessages = chat.pinnedMessages.filter((msg) => msg.id !== message.id); + + this.notifyChatUsers(accountId, chat, MultichatEventType.ChatUpdated, user.id); + + return chat; + } + + async updateMessagesStatus({ + accountId, + user, + chatId, + status, + }: { + accountId: number; + user: User; + chatId?: number; + status: ChatMessageStatus; + }) { + await this.chatMessageService.updateStatusDirect({ accountId, user, chatId, status }); + } + + async delete(accountId: number, userId: number | null, chatId: number): Promise { + const chat = await this.findOne({ accountId, filter: { chatId } }); + + if (chat) { + const chatUser = userId ? await this.chatUserService.findOne(accountId, { chatId, userId }) : null; + + if (userId && (!chatUser || chatUser.role === ChatUserRole.USER)) { + throw new ForbiddenError(`User ${userId} can not delete chat ${chatId}`); + } + + await this.notifyChatUsers(accountId, chat, MultichatEventType.ChatDeleted, userId); + await this.repository.delete(chatId); + + return chatId; + } + + return null; + } + + async findOne({ + accountId, + filter, + accessUserId, + }: { + accountId: number; + filter: FindFilter; + accessUserId?: number; + }): Promise { + const chat = await this.createFindQb(accountId, null, filter).getOne(); + if (accessUserId) { + chat.hasAccess = (await this.chatUserService.count(accountId, { chatId: chat.id, userId: accessUserId })) > 0; + } + + return chat; + } + + async findMany({ + accountId, + filter, + paging, + accessUserId, + }: { + accountId: number; + filter: FindFilter; + paging?: PagingQuery; + accessUserId?: number; + }): Promise { + const chats = await this.createFindQb(accountId, null, filter).offset(paging?.skip).limit(paging?.take).getMany(); + if (accessUserId) { + for (const chat of chats) { + chat.hasAccess = (await this.chatUserService.count(accountId, { chatId: chat.id, userId: accessUserId })) > 0; + } + } + return chats; + } + + async findManyFull( + accountId: number, + user: User, + filter: FindFilter, + paging?: PagingQuery, + ): Promise { + const qb = this.createFindQb(accountId, user.id, filter, true); + + const total = await qb.clone().getCount(); + const offset = Math.min(paging.take + paging.skip, total); + + const chats = await qb.offset(paging?.skip).limit(paging?.take).getMany(); + + for (const chat of chats) { + chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id }); + chat.hasAccess = chat.users.some((u) => u.userId === user.id); + + if (chat.lastMessage) { + chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id); + } + + if (chat.entityId) { + chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId }); + } + } + + return new FindChatsFullResultDto( + chats.map((chat) => chat.toDto()), + new PagingMeta(offset, total), + ); + } + + async findManyFullPersonal( + accountId: number, + user: User, + filter: ChatFindPersonalFilterDto, + paging?: PagingQuery, + ): Promise { + const qb = this.createFindQb(accountId, user.id, { ...filter, type: ChatType.PERSONAL }, true); + + qb.innerJoin('chat_user', 'cu', 'cu.chat_id = chat.id') + .innerJoin('users', 'u', 'u.id = cu.user_id') + .andWhere(`LOWER(u.first_name || ' ' || u.last_name) ilike :fullName`, { + fullName: `%${filter.fullName.trim()}%`, + }) + .andWhere('chat.created_by != u.id'); + + const total = await qb.clone().getCount(); + const offset = Math.min(paging.take + paging.skip, total); + + const chats = await qb.offset(paging?.skip).limit(paging?.take).getMany(); + + if (filter.fullName) { + const personalChats: Chat[] = []; + + for (const chat of chats) { + chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id }); + chat.hasAccess = chat.users.some((u) => u.userId === user.id); + + if (chat.lastMessage) { + chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id); + } + + if (chat.entityId) { + chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId }); + } + + personalChats.push(chat); + } + + return new FindChatsFullResultDto( + personalChats.map((chat) => chat.toDto()), + new PagingMeta(offset, total), + ); + } else { + return new FindChatsFullResultDto([], new PagingMeta(0, 0)); + } + } + + async findManyFullByMessageContent( + accountId: number, + user: User, + filter: ChatFindByMessageContentFilterDto, + paging?: PagingQuery, + ): Promise { + const qb = this.createFindByMessageContentQb(accountId, user.id, filter.messageContent, filter.providerId); + + const total = await qb.getCount(); + const offset = Math.min(paging.take + paging.skip, total); + + const chats = await qb.offset(paging?.skip).limit(paging?.take).getMany(); + + for (const chat of chats) { + chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id }); + chat.hasAccess = chat.users.some((u) => u.userId === user.id); + + if (chat.entityId) { + chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId }); + } + } + + return new FindChatsFullResultDto( + chats.map((chat) => chat.toDto()), + new PagingMeta(offset, total), + ); + } + + async count(accountId: number, filter: FindFilter): Promise { + return this.createFindQb(accountId, null, filter).getCount(); + } + + async getChatFull(accountId: number, user: User | null, chatId: number, hasUser = true): Promise { + const qb = this.createFindQb(accountId, hasUser ? (user?.id ?? null) : null, { chatId }, true); + const chat = await qb + .leftJoin(ChatPinnedMessage, 'cpm', 'cpm.chat_id = chat.id') + .leftJoinAndMapMany('chat.pinnedMessages', ChatMessage, 'pinned_msg', 'pinned_msg.id = cpm.message_id') + .getOne(); + + if (!chat) { + throw NotFoundError.withId(Chat, chatId); + } + + chat.users = await this.chatUserService.findMany(accountId, { chatId }); + chat.hasAccess = user && hasUser ? chat.users.some((u) => u.userId === user.id) : null; + + if (chat.lastMessage) { + chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id); + } + + if (user && chat.entityId) { + chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId }); + } + + return chat; + } + + async getChats( + accountId: number, + user: User, + providerId: number | null | undefined, + paging: ChatPagingQuery, + ): Promise { + const providers = await this.chatProviderService.findMany(accountId, user.id, { + providerId: providerId ?? undefined, + }); + if (providers.length === 0) { + return []; + } + + const qb = this.createFindQb(accountId, user.id, { providerId: providers.map((p) => p.id) }, true); + + // Check if cursor-based pagination is requested + if (paging.cursor) { + // Cursor-based pagination logic + const cursorChat = await this.findOne({ accountId, filter: { chatId: paging.cursor } }); + const lastMessageCreatedAt = cursorChat + ? await this.chatMessageService.getLastMessageCreatedAt(accountId, cursorChat.id) + : null; + const from = lastMessageCreatedAt ?? cursorChat?.createdAt; + + if (from) { + qb.andWhere('COALESCE(last_msg.created_at, chat.created_at) < :from', { from }); + } + + const chats = await qb.orderBy('chat_updated_at', 'DESC').addOrderBy('chat.id', 'DESC').take(paging.take).getMany(); + + for (const chat of chats) { + chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id }); + if (chat.lastMessage) { + chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id); + } + if (user && chat.entityId) { + chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId }); + } + chat.hasAccess = chat.users.some((u) => u.userId === user.id); + } + + return chats; + } else { + // Offset-based pagination logic (default) + const chats = await qb + .orderBy('chat_updated_at', 'DESC') + .addOrderBy('chat.id', 'DESC') + .offset(paging.skip) + .limit(paging.take) + .getMany(); + + for (const chat of chats) { + chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id }); + if (chat.lastMessage) { + chat.lastMessage = await this.chatMessageService.getLastMessageInfo(accountId, chat.id, chat.lastMessage.id); + } + if (user && chat.entityId) { + chat.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: chat.entityId }); + } + chat.hasAccess = chat.users.some((u) => u.userId === user.id); + } + + return chats; + } + } + + async getUnseenForUser(accountId: number, userId: number, providerId?: number): Promise { + const cacheKey = ({ + accountId, + userId, + providerId, + status, + }: { + accountId: number; + userId: number; + providerId?: number; + status?: ChatMessageStatus; + }) => `ChatMessage.count:${accountId}:${userId}${providerId ? `:${providerId}` : ''}${status ? `:${status}` : ''}`; + const messagesQb = this.repository + .createQueryBuilder('chat') + .select('count(msg.id)', 'messagesCount') + .leftJoin(ChatUser, 'user', 'user.chat_id = chat.id') + .leftJoin(ChatMessage, 'msg', 'msg.chat_id = chat.id') + .where('chat.account_id = :accountId', { accountId }) + .andWhere('user.user_id = :userId', { userId }); + if (providerId) { + messagesQb.andWhere('chat.provider_id = :providerId', { providerId }); + } + const { messagesCount } = await messagesQb.cache(cacheKey({ accountId, userId, providerId }), 15000).getRawOne(); + + const seenQb = this.repository + .createQueryBuilder('chat') + .select('count(msg.id)', 'seenCount') + .leftJoin(ChatMessage, 'msg', 'msg.chat_id = chat.id') + .leftJoin(ChatMessageUserStatus, 'cmus', 'cmus.message_id = msg.id') + .leftJoin(ChatUser, 'user', 'user.id = cmus.chat_user_id and user.chat_id = chat.id') + .where('chat.account_id = :accountId', { accountId }) + .andWhere('user.user_id = :userId', { userId }) + .andWhere('cmus.status = :status', { status: ChatMessageStatus.SEEN }); + if (providerId) { + seenQb.andWhere('chat.provider_id = :providerId', { providerId }); + } + const { seenCount } = await seenQb + .cache(cacheKey({ accountId, userId, providerId, status: ChatMessageStatus.SEEN }), 15000) + .getRawOne(); + + return messagesCount - seenCount; + } + + async getLastMessageId(accountId: number, chatId: number): Promise { + return this.chatMessageService.getLastMessageId(accountId, chatId); + } + + async createLinkedEntities({ + accountId, + user, + chatId, + dto, + }: { + accountId: number; + user: User; + chatId: number; + dto: CreateContactLeadDto; + }) { + if (!dto.leadTypeId && !dto.contactTypeId) { + throw new BadRequestError('No contact or lead type provided'); + } + + const chatUser = await this.chatUserService.findOne(accountId, { chatId, role: ChatUserRole.EXTERNAL }); + if (!chatUser?.externalUser) { + throw new BadRequestError(`No external user in chat ${chatId}`); + } + + const fieldValues = []; + if (chatUser.externalUser.phone) { + fieldValues.push({ fieldType: FieldType.Phone, value: chatUser.externalUser.phone }); + } + if (chatUser.externalUser.email) { + fieldValues.push({ fieldType: FieldType.Email, value: chatUser.externalUser.email }); + } + if (chatUser.externalUser.link) { + fieldValues.push({ fieldType: FieldType.Link, value: chatUser.externalUser.link }); + } + const lead = dto.leadTypeId + ? { + ownerId: dto.ownerId, + entityTypeId: dto.leadTypeId, + boardId: dto.leadBoardId, + stageId: dto.leadStageId, + name: dto.leadName ?? (!dto.contactTypeId ? chatUser.externalUser.fullName() : undefined), + fieldValues, + } + : null; + const contact = dto.contactTypeId + ? { + ownerId: dto.ownerId, + entityTypeId: dto.contactTypeId, + name: chatUser.externalUser.fullName(), + fieldValues, + linkedEntities: lead ? [lead] : undefined, + } + : null; + + if (contact || lead) { + const [entity] = await this.entityService.createSimple({ + accountId, + user, + dto: contact ?? lead, + options: { checkActiveLead: dto.checkActiveLead, checkDuplicate: dto.checkDuplicate }, + }); + this.updateGroupChat(accountId, user, chatId, { entityId: entity.id }); + + return this.entityInfoService.getEntityInfo({ user, entity, access: true }); + } + + return null; + } + + //TODO: move to ChatNotificationService + async notifyChatUsers(accountId: number, chat: Chat, event: MultichatEventType, userId?: number): Promise { + if (!chat.users) { + chat.users = await this.chatUserService.findMany(accountId, { chatId: chat.id }); + } + const usersToNotify = userId + ? chat.users.filter((chatUser) => chatUser.userId && chatUser.userId !== userId) + : chat.users.filter((chatUser) => chatUser.userId); + usersToNotify.forEach(async (chatUser) => { + this.eventEmitter.emit( + event, + new ChatEvent({ accountId, userId: chatUser.userId, providerId: chat.providerId, chatId: chat.id }), + ); + }); + } + + async processAutomation({ + accountId, + entityId, + entityStageId, + settings, + }: { + accountId: number; + entityId: number; + entityStageId: number | null | undefined; + settings: ActionChatSendAmworkSettings; + }): Promise { + const entity = await this.entityInfoService.findOne({ accountId, entityId }); + if (entity && (!entity.stageId || settings.allowAnyStage || entity.stageId === entityStageId)) { + let chat = await this.findOne({ accountId, filter: { entityId, transport: ChatProviderTransport.Amwork } }); + const ownerUser = await this.userService.findOne({ accountId, id: settings.userId ?? entity.ownerId }); + const participantIds = settings.sendTo ?? [ + (await this.userService.findOne({ accountId, id: entity.ownerId })).id, + ]; + if (chat) { + await this.chatUserService.addUsers({ accountId, chatId: chat.id, userIds: [ownerUser.id, ...participantIds] }); + } else { + const provider = await this.chatProviderService.findOne(accountId, ownerUser.id, { + transport: ChatProviderTransport.Amwork, + }); + chat = await this.createGroupChat(accountId, ownerUser, { + providerId: provider.id, + title: entity.name, + participantIds, + entityId: entity.id, + }); + } + + if (chat) { + const account = await this.accountService.findOne({ accountId }); + const data = await this.documentGenerationService.getDataForGeneration({ accountId, entityId: entity.id }); + data['contact_name'] = entity.name; + const text = Handlebars.compile(settings.message)(data); + + await this.chatMessageService.create(account, ownerUser, chat.id, { text }); + } + + return chat; + } + + return null; + } + + private createFindQb( + accountId: number, + userId: number | null, + filter: FindFilter, + full = false, + ): SelectQueryBuilder { + const qb = full + ? this.createFullQb(accountId, userId) + : this.repository.createQueryBuilder('chat').where('chat.account_id = :accountId', { accountId }); + + if (filter.chatId) { + qb.andWhere('chat.id = :chatId', { chatId: filter.chatId }); + } + + if (filter.type) { + qb.andWhere('chat.type = :type', { type: filter.type }); + } + + if (filter.entityId) { + qb.andWhere('chat.entity_id = :entityId', { entityId: filter.entityId }); + } + + if (filter.title) { + qb.andWhere('chat.title ilike :title', { title: `%${filter.title.trim()}%` }); + } + + if (filter.phoneNumber) { + qb.innerJoin('chat_user', 'cu', 'cu.chat_id = chat.id') + .innerJoin('chat_user_external', 'cue', 'cue.chat_user_id = cu.id') + .andWhere(`cue.phone ilike :phone`, { phone: `%${filter.phoneNumber.replace(/^\+/, '')}%` }); + } + + if (filter.externalId) { + qb.andWhere('chat.external_id = :externalId', { externalId: filter.externalId }); + } + + if (filter.providerId) { + if (Array.isArray(filter.providerId)) { + qb.andWhere('chat.provider_id IN (:...providerIds)', { providerIds: filter.providerId }); + } else { + qb.andWhere('chat.provider_id = :providerId', { providerId: filter.providerId }); + } + } + + if (filter.transport) { + qb.innerJoin('chat_provider', 'cp', 'cp.id = chat.provider_id').andWhere('cp.transport = :transport', { + transport: filter.transport, + }); + } + + return qb; + } + + private createFindByMessageContentQb( + accountId: number, + userId: number, + messageContent: string, + providerId?: number, + ): SelectQueryBuilder { + const qb = this.createFindQb(accountId, userId, { providerId }, true); + + // Remove existing join with alias 'last_msg' if it exists + qb.expressionMap.joinAttributes = qb.expressionMap.joinAttributes.filter((join) => join.alias.name !== 'last_msg'); + + // First message with text matching the content will be used as the lastMessage + qb.leftJoinAndMapOne( + 'chat.lastMessage', + ChatMessage, + 'last_msg', + `last_msg.chat_id = chat.id AND last_msg.text ilike :messageContent`, + { messageContent: `%${messageContent.trim()}%` }, + ).andWhere('last_msg.id is not null'); + + return qb; + } + + private createFullQb(accountId: number, userId: number | null) { + const qb = this.repository + .createQueryBuilder('chat') + .leftJoin(ChatUser, 'user', 'user.chat_id = chat.id') + .where('chat.account_id = :accountId', { accountId }) + .addSelect((subQuery) => { + return subQuery + .select('COUNT(status.*)', 'seen_count') + .from(ChatMessageUserStatus, 'status') + .where('status.chat_id = chat.id') + .andWhere('status.chat_user_id = user.id') + .andWhere('status.status = :status', { status: ChatMessageStatus.SEEN }); + }, 'chat_seen_by_user_count') + .addSelect((subQuery) => { + return subQuery.select('COUNT(msg.*)', 'message_count').from(ChatMessage, 'msg').where('msg.chat_id = chat.id'); + }, 'chat_total_message_count') + .leftJoinAndMapOne( + 'chat.lastMessage', + ChatMessage, + 'last_msg', + `last_msg.chat_id = chat.id AND last_msg.id = (${LAST_MESSAGE})`, + ) + .addSelect('COALESCE(last_msg.created_at, chat.created_at)', 'chat_updated_at'); + + if (userId) { + qb.andWhere('user.user_id = :userId', { userId }); + } + + return qb; + } +} diff --git a/backend/src/modules/multichat/chat/services/index.ts b/backend/src/modules/multichat/chat/services/index.ts new file mode 100644 index 0000000..16dd6a7 --- /dev/null +++ b/backend/src/modules/multichat/chat/services/index.ts @@ -0,0 +1,3 @@ +export * from './chat-pinned-message.service'; +export * from './chat.handler'; +export * from './chat.service'; diff --git a/backend/src/modules/multichat/common/enums/chat-message-status.enum.ts b/backend/src/modules/multichat/common/enums/chat-message-status.enum.ts new file mode 100644 index 0000000..9f66e07 --- /dev/null +++ b/backend/src/modules/multichat/common/enums/chat-message-status.enum.ts @@ -0,0 +1,4 @@ +export enum ChatMessageStatus { + RECEIVED = 'received', + SEEN = 'seen', +} diff --git a/backend/src/modules/multichat/common/enums/chat-provider-status.enum.ts b/backend/src/modules/multichat/common/enums/chat-provider-status.enum.ts new file mode 100644 index 0000000..0d904ae --- /dev/null +++ b/backend/src/modules/multichat/common/enums/chat-provider-status.enum.ts @@ -0,0 +1,6 @@ +export enum ChatProviderStatus { + Draft = 'draft', + Active = 'active', + Inactive = 'inactive', + Deleted = 'deleted', +} diff --git a/backend/src/modules/multichat/common/enums/chat-provider-transport.enum.ts b/backend/src/modules/multichat/common/enums/chat-provider-transport.enum.ts new file mode 100644 index 0000000..2b03f57 --- /dev/null +++ b/backend/src/modules/multichat/common/enums/chat-provider-transport.enum.ts @@ -0,0 +1,9 @@ +export enum ChatProviderTransport { + Amwork = 'amwork', + Whatsapp = 'whatsapp', + Messenger = 'messenger', + Telegram = 'telegram', + Instagram = 'instagram', + Vk = 'vk', + Avito = 'avito', +} diff --git a/backend/src/modules/multichat/common/enums/chat-provider-type.enum.ts b/backend/src/modules/multichat/common/enums/chat-provider-type.enum.ts new file mode 100644 index 0000000..04accf8 --- /dev/null +++ b/backend/src/modules/multichat/common/enums/chat-provider-type.enum.ts @@ -0,0 +1,6 @@ +export enum ChatProviderType { + Amwork = 'amwork', + Twilio = 'twilio', + Facebook = 'facebook', + Wazzup = 'wazzup', +} diff --git a/backend/src/modules/multichat/common/enums/chat-type.enum.ts b/backend/src/modules/multichat/common/enums/chat-type.enum.ts new file mode 100644 index 0000000..3002a22 --- /dev/null +++ b/backend/src/modules/multichat/common/enums/chat-type.enum.ts @@ -0,0 +1,4 @@ +export enum ChatType { + PERSONAL = 'personal', + GROUP = 'group', +} diff --git a/backend/src/modules/multichat/common/enums/chat-user-role.enum.ts b/backend/src/modules/multichat/common/enums/chat-user-role.enum.ts new file mode 100644 index 0000000..93afb7f --- /dev/null +++ b/backend/src/modules/multichat/common/enums/chat-user-role.enum.ts @@ -0,0 +1,7 @@ +export enum ChatUserRole { + OWNER = 'owner', + ADMIN = 'admin', + SUPERVISOR = 'supervisor', + USER = 'user', + EXTERNAL = 'external', +} diff --git a/backend/src/modules/multichat/common/enums/index.ts b/backend/src/modules/multichat/common/enums/index.ts new file mode 100644 index 0000000..133e579 --- /dev/null +++ b/backend/src/modules/multichat/common/enums/index.ts @@ -0,0 +1,6 @@ +export * from './chat-message-status.enum'; +export * from './chat-provider-status.enum'; +export * from './chat-provider-transport.enum'; +export * from './chat-provider-type.enum'; +export * from './chat-type.enum'; +export * from './chat-user-role.enum'; diff --git a/backend/src/modules/multichat/common/events/chat-message/chat-message-created.event.ts b/backend/src/modules/multichat/common/events/chat-message/chat-message-created.event.ts new file mode 100644 index 0000000..005c478 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat-message/chat-message-created.event.ts @@ -0,0 +1,27 @@ +import { ChatMessageEvent } from './chat-message.event'; + +export class ChatMessageCreatedEvent extends ChatMessageEvent { + fromUser: string; + text: string; + createdAt: string; + entityId: number | null; + + constructor({ + accountId, + userId, + providerId, + chatId, + fromUser, + messageId, + text, + createdAt, + entityId, + }: ChatMessageCreatedEvent) { + super({ accountId, userId, providerId, chatId, messageId }); + + this.fromUser = fromUser; + this.text = text; + this.createdAt = createdAt; + this.entityId = entityId; + } +} diff --git a/backend/src/modules/multichat/common/events/chat-message/chat-message-updated.event.ts b/backend/src/modules/multichat/common/events/chat-message/chat-message-updated.event.ts new file mode 100644 index 0000000..aab0798 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat-message/chat-message-updated.event.ts @@ -0,0 +1,11 @@ +import { ChatMessageEvent } from './chat-message.event'; + +export class ChatMessageUpdatedEvent extends ChatMessageEvent { + isLastMessage: boolean; + + constructor({ accountId, userId, providerId, chatId, messageId, isLastMessage }: ChatMessageUpdatedEvent) { + super({ accountId, userId, providerId, chatId, messageId }); + + this.isLastMessage = isLastMessage; + } +} diff --git a/backend/src/modules/multichat/common/events/chat-message/chat-message.event.ts b/backend/src/modules/multichat/common/events/chat-message/chat-message.event.ts new file mode 100644 index 0000000..8a92344 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat-message/chat-message.event.ts @@ -0,0 +1,11 @@ +import { ChatEvent } from '../chat/chat.event'; + +export class ChatMessageEvent extends ChatEvent { + messageId: number; + + constructor({ accountId, userId, providerId, chatId, messageId }: ChatMessageEvent) { + super({ accountId, userId, providerId, chatId }); + + this.messageId = messageId; + } +} diff --git a/backend/src/modules/multichat/common/events/chat-message/index.ts b/backend/src/modules/multichat/common/events/chat-message/index.ts new file mode 100644 index 0000000..4a225c5 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat-message/index.ts @@ -0,0 +1,3 @@ +export * from './chat-message-created.event'; +export * from './chat-message-updated.event'; +export * from './chat-message.event'; diff --git a/backend/src/modules/multichat/common/events/chat-provider/chat-provider.event.ts b/backend/src/modules/multichat/common/events/chat-provider/chat-provider.event.ts new file mode 100644 index 0000000..5babb09 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat-provider/chat-provider.event.ts @@ -0,0 +1,15 @@ +import { ChatProviderStatus } from '../../enums'; + +export class ChatProviderEvent { + accountId: number; + userId: number; + providerId: number; + status?: ChatProviderStatus; + + constructor({ accountId, userId, providerId, status }: ChatProviderEvent) { + this.accountId = accountId; + this.userId = userId; + this.providerId = providerId; + this.status = status; + } +} diff --git a/backend/src/modules/multichat/common/events/chat-provider/index.ts b/backend/src/modules/multichat/common/events/chat-provider/index.ts new file mode 100644 index 0000000..48711a4 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat-provider/index.ts @@ -0,0 +1 @@ +export * from './chat-provider.event'; diff --git a/backend/src/modules/multichat/common/events/chat/chat.event.ts b/backend/src/modules/multichat/common/events/chat/chat.event.ts new file mode 100644 index 0000000..7eda179 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat/chat.event.ts @@ -0,0 +1,13 @@ +export class ChatEvent { + accountId: number; + userId: number; + providerId: number; + chatId: number; + + constructor({ accountId, userId, providerId, chatId }: ChatEvent) { + this.accountId = accountId; + this.userId = userId; + this.providerId = providerId; + this.chatId = chatId; + } +} diff --git a/backend/src/modules/multichat/common/events/chat/index.ts b/backend/src/modules/multichat/common/events/chat/index.ts new file mode 100644 index 0000000..03f1a40 --- /dev/null +++ b/backend/src/modules/multichat/common/events/chat/index.ts @@ -0,0 +1 @@ +export * from './chat.event'; diff --git a/backend/src/modules/multichat/common/events/index.ts b/backend/src/modules/multichat/common/events/index.ts new file mode 100644 index 0000000..6e3f340 --- /dev/null +++ b/backend/src/modules/multichat/common/events/index.ts @@ -0,0 +1,4 @@ +export * from './chat'; +export * from './chat-message'; +export * from './chat-provider'; +export * from './multichat-event-type.enum'; diff --git a/backend/src/modules/multichat/common/events/multichat-event-type.enum.ts b/backend/src/modules/multichat/common/events/multichat-event-type.enum.ts new file mode 100644 index 0000000..bbaa1b0 --- /dev/null +++ b/backend/src/modules/multichat/common/events/multichat-event-type.enum.ts @@ -0,0 +1,11 @@ +export enum MultichatEventType { + ChatCreated = 'chat:created', + ChatUpdated = 'chat:updated', + ChatDeleted = 'chat:deleted', + ChatMessageCreated = 'chat:message:created', + ChatMessageUpdated = 'chat:message:updated', + ChatMessageDeleted = 'chat:message:deleted', + ChatProviderCreated = 'chat:provider:created', + ChatProviderUpdated = 'chat:provider:updated', + ChatProviderDeleted = 'chat:provider:deleted', +} diff --git a/backend/src/modules/multichat/common/index.ts b/backend/src/modules/multichat/common/index.ts new file mode 100644 index 0000000..df1eda9 --- /dev/null +++ b/backend/src/modules/multichat/common/index.ts @@ -0,0 +1,2 @@ +export * from './enums'; +export * from './events'; diff --git a/backend/src/modules/multichat/multichat.controller.ts b/backend/src/modules/multichat/multichat.controller.ts new file mode 100644 index 0000000..503283f --- /dev/null +++ b/backend/src/modules/multichat/multichat.controller.ts @@ -0,0 +1,33 @@ +import { Controller, Get, Param, ParseEnumPipe, Put } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { ChatMessageStatus } from './common'; +import { ChatService } from './chat/services/chat.service'; + +@ApiTags('multichat') +@Controller('chat') +@JwtAuthorized() +export class MultichatController { + constructor(private readonly service: ChatService) {} + + @ApiOperation({ summary: 'Get unseen messages count', description: 'Get unseen messages count for current user' }) + @ApiOkResponse({ description: `Unseen messages count`, type: Number }) + @Get('unseen-count') + async getUnseenCount(@CurrentAuth() { accountId, userId }: AuthData): Promise { + return await this.service.getUnseenForUser(accountId, userId); + } + + @ApiOperation({ summary: 'Update all chats messages status', description: 'Update all chats messages status' }) + @ApiParam({ name: 'status', description: 'Message status' }) + @ApiOkResponse() + @Put('status/:status') + @AuthDataPrefetch({ user: true }) + async updateMessagesStatus( + @CurrentAuth() { accountId, user }: AuthData, + @Param('status', new ParseEnumPipe(ChatMessageStatus)) status: ChatMessageStatus, + ) { + await this.service.updateMessagesStatus({ accountId, user, status }); + } +} diff --git a/backend/src/modules/multichat/multichat.module.ts b/backend/src/modules/multichat/multichat.module.ts new file mode 100644 index 0000000..25d3ac2 --- /dev/null +++ b/backend/src/modules/multichat/multichat.module.ts @@ -0,0 +1,27 @@ +import { Module } from '@nestjs/common'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { ChatModule } from './chat/chat.module'; +import { ChatMessageModule } from './chat-message/chat-message.module'; +import { ChatProviderModule } from './chat-provider/chat-provider.module'; +import { ProvidersModule } from './providers/providers.module'; +import { ChatUserModule } from './chat-user/chat-user.module'; +import { ChatMessageScheduledModule } from './chat-message-scheduled/chat-message-scheduled.module'; + +import { MultichatController } from './multichat.controller'; + +@Module({ + imports: [ + IAMModule, + ChatModule, + ChatUserModule, + ChatMessageModule, + ChatProviderModule, + ProvidersModule, + ChatMessageScheduledModule, + ], + controllers: [MultichatController], + exports: [ChatModule], +}) +export class MultichatModule {} diff --git a/backend/src/modules/multichat/providers/chat-provider-proxy.service.ts b/backend/src/modules/multichat/providers/chat-provider-proxy.service.ts new file mode 100644 index 0000000..0d5dfa1 --- /dev/null +++ b/backend/src/modules/multichat/providers/chat-provider-proxy.service.ts @@ -0,0 +1,78 @@ +import { Injectable } from '@nestjs/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; + +import { ChatProviderType, MultichatEventType } from '../common'; +import { ChatUser } from '../chat-user'; +import { ChatProvider } from '../chat-provider/entities'; +import { ChatProviderService } from '../chat-provider/services/chat-provider.service'; +import { Chat } from '../chat/entities/chat.entity'; +import { ChatMessage } from '../chat-message/entities/chat-message.entity'; + +import { MessengerProviderService } from './facebook/messenger-provider.service'; +import { TwilioProviderService } from './twilio/twilio-provider.service'; +import { WazzupProviderService } from './wazzup/wazzup-provider.service'; + +@Injectable() +export class ChatProviderProxyService { + constructor( + private readonly chatProviderService: ChatProviderService, + private readonly twilioService: TwilioProviderService, + private readonly messengerService: MessengerProviderService, + private readonly wazzupService: WazzupProviderService, + ) {} + + async createChatExternalId(accountId: number, providerId: number, userExternalId: string): Promise { + const provider = await this.chatProviderService.findOne(accountId, null, { providerId }); + switch (provider?.type) { + case ChatProviderType.Amwork: + return null; + case ChatProviderType.Facebook: + return this.messengerService.createChatExternalId(userExternalId); + case ChatProviderType.Twilio: + return this.twilioService.createChatExternalId(userExternalId); + case ChatProviderType.Wazzup: + return this.wazzupService.createChatExternalId(accountId, providerId, userExternalId); + } + } + + async notifyChatUsers( + account: Account, + provider: ChatProvider, + chat: Chat, + type: MultichatEventType, + message: ChatMessage, + users: ChatUser[], + ): Promise { + switch (provider.type) { + case ChatProviderType.Facebook: + await this.messengerService.notifyChatUsers(account, message, type, provider, users); + break; + case ChatProviderType.Twilio: + await this.twilioService.notifyChatUsers(account, message, type, provider, users); + break; + case ChatProviderType.Wazzup: + await this.wazzupService.notifyChatUsers(account, provider, chat, type, message, users); + break; + } + } + + async sendDirectMessage({ + accountId, + provider, + phone, + message, + }: { + accountId: number; + provider: ChatProvider; + phone: string; + message: string; + }): Promise { + switch (provider?.type) { + case ChatProviderType.Twilio: + return this.twilioService.sendDirectMessage({ accountId, providerId: provider.id, phone, message }); + case ChatProviderType.Wazzup: + return this.wazzupService.sendDirectMessage({ accountId, providerId: provider.id, phone, message }); + } + } +} diff --git a/backend/src/modules/multichat/providers/facebook/config/facebook.config.ts b/backend/src/modules/multichat/providers/facebook/config/facebook.config.ts new file mode 100644 index 0000000..47b2de6 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/config/facebook.config.ts @@ -0,0 +1,20 @@ +import { registerAs } from '@nestjs/config'; + +export interface FacebookConfig { + appId: string; + appSecret: string; + appAccessToken: string; + messengerAuthConfigId: string; + messengerValidationToken: string; +} + +export default registerAs( + 'facebook', + (): FacebookConfig => ({ + appId: process.env.FB_APP_ID, + appSecret: process.env.FB_APP_SECRET, + appAccessToken: process.env.FB_APP_ACCESS_TOKEN, + messengerAuthConfigId: process.env.FB_MESSENGER_AUTH_CONFIG_ID, + messengerValidationToken: process.env.FB_MESSENGER_VALIDATION_TOKEN, + }), +); diff --git a/backend/src/modules/multichat/providers/facebook/controllers/index.ts b/backend/src/modules/multichat/providers/facebook/controllers/index.ts new file mode 100644 index 0000000..564fda8 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/controllers/index.ts @@ -0,0 +1,2 @@ +export * from './messenger-provider.controller'; +export * from './public-messenger-provider.controller'; diff --git a/backend/src/modules/multichat/providers/facebook/controllers/messenger-provider.controller.ts b/backend/src/modules/multichat/providers/facebook/controllers/messenger-provider.controller.ts new file mode 100644 index 0000000..c2d632f --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/controllers/messenger-provider.controller.ts @@ -0,0 +1,95 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { AuthDataPrefetch } from '@/modules/iam/common/decorators/auth-data-prefetch.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { MessengerProviderDto, CreateMessengerProviderDto, UpdateMessengerProviderDto } from '../dto'; +import { MessengerProviderService } from '../messenger-provider.service'; + +@ApiTags('multichat/settings/messenger') +@Controller('chat/settings/providers/messenger') +@JwtAuthorized() +@TransformToDto() +export class MessengerProviderController { + constructor(private readonly service: MessengerProviderService) {} + + @ApiOperation({ summary: 'Get Facebook Auth redirect url', description: 'Get Facebook Auth redirect url' }) + @ApiQuery({ name: 'display', type: String, required: false, description: 'Facebook OAuth screen display type' }) + @ApiOkResponse({ description: 'Facebook Auth redirect url', type: String }) + @Get('auth/connect') + async connect( + @CurrentAuth() { accountId, userId }: AuthData, + @Query('display') display: string | undefined, + ): Promise { + return this.service.getConnectUrl(accountId, userId, display); + } + + @ApiOperation({ + summary: 'Create Facebook Messenger chat provider', + description: 'Create Facebook Messenger chat provider', + }) + @ApiBody({ description: 'Data for creating Facebook Messenger chat provider', type: CreateMessengerProviderDto }) + @ApiCreatedResponse({ description: 'Facebook Messenger chat provider', type: MessengerProviderDto }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateMessengerProviderDto) { + return this.service.create(accountId, userId, dto); + } + + @ApiOperation({ + summary: 'Get Facebook Messenger chat providers with settings', + description: 'Get Facebook Messenger chat providers with settings', + }) + @ApiOkResponse({ description: 'Facebook Messenger chat providers', type: [MessengerProviderDto] }) + @AuthDataPrefetch({ user: true }) + @Get() + async getProviders(@CurrentAuth() { accountId, user }: AuthData) { + return this.service.getProvidersWithSettings(accountId, user); + } + + @ApiOperation({ + summary: 'Get Facebook Messenger chat provider with settings', + description: 'Get Facebook Messenger chat provider with settings', + }) + @ApiParam({ name: 'providerId', type: Number, required: true, description: 'Facebook Messenger chat provider ID' }) + @ApiOkResponse({ description: 'Facebook Messenger chat provider', type: MessengerProviderDto }) + @Get(':providerId') + async getProvider(@CurrentAuth() { accountId }: AuthData, @Param('providerId', ParseIntPipe) providerId: number) { + return this.service.getProviderWithSettings(accountId, providerId); + } + + @ApiOperation({ + summary: 'Update Facebook Messenger chat provider', + description: 'Update Facebook Messenger chat provider', + }) + @ApiParam({ name: 'providerId', type: Number, required: true, description: 'Facebook Messenger chat provider ID' }) + @ApiBody({ description: 'Data for updating Facebook Messenger chat provider', type: UpdateMessengerProviderDto }) + @ApiOkResponse({ description: 'Facebook Messenger chat provider', type: MessengerProviderDto }) + @Put(':providerId') + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('providerId', ParseIntPipe) providerId: number, + @Body() dto: UpdateMessengerProviderDto, + ) { + return this.service.update(accountId, providerId, dto); + } + + @ApiOperation({ + summary: 'Delete Facebook Messenger chat provider', + description: 'Delete Facebook Messenger chat provider', + }) + @ApiParam({ name: 'providerId', type: Number, required: true, description: 'Facebook Messenger chat provider ID' }) + @ApiOkResponse({ description: 'Success', type: Boolean }) + @Delete(':providerId') + async delete( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('providerId', ParseIntPipe) providerId: number, + ): Promise { + await this.service.delete({ accountId, userId, providerId }); + + return true; + } +} diff --git a/backend/src/modules/multichat/providers/facebook/controllers/public-messenger-provider.controller.ts b/backend/src/modules/multichat/providers/facebook/controllers/public-messenger-provider.controller.ts new file mode 100644 index 0000000..ec02d08 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/controllers/public-messenger-provider.controller.ts @@ -0,0 +1,45 @@ +import { Body, Controller, Get, Post, Query, Redirect } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { BadRequestError } from '@/common'; +import { MessengerProviderService } from '../messenger-provider.service'; + +@ApiExcludeController(true) +@Controller('chat/messenger') +export class PublicMessengerProviderController { + constructor(private readonly service: MessengerProviderService) {} + + @Redirect() + @Get('callback') + async callback(@Query('code') code: string, @Query('state') state: string, @Query('error') error?: string) { + const redirectUrl = await this.service.authCallback(code, state, error); + + return { url: redirectUrl, statusCode: 302 }; + } + + @Get('webhook') + async verifyWebhook( + @Query('hub.verify_token') verifyToken: string, + @Query('hub.challenge') challenge: string, + ): Promise { + return this.service.verifyWebhook(verifyToken, challenge); + } + + @Post('webhook') + async handleMessage(@Body() body: unknown): Promise { + await this.service.handleWebhook(body); + } + + @Post('deauthorise') + async deauthoriseUser(@Body('signed_request') signedRequest: string): Promise { + const result = await this.service.handleDeauthoriseRequest(signedRequest); + if (!result) { + throw new BadRequestError('Invalid signed request'); + } + } + + @Post('delete') + async deleteUser(@Body('signed_request') signedRequest: string) { + return this.service.handleDeleteAuthRequest(signedRequest); + } +} diff --git a/backend/src/modules/multichat/providers/facebook/dto/create-messenger-provider.dto.ts b/backend/src/modules/multichat/providers/facebook/dto/create-messenger-provider.dto.ts new file mode 100644 index 0000000..d1d8121 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/dto/create-messenger-provider.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { CreateChatProviderDto } from '../../../chat-provider/dto'; + +export class CreateMessengerProviderDto extends CreateChatProviderDto { + @ApiProperty({ description: 'User ID' }) + @IsString() + userId: string; + + @ApiProperty({ description: 'User access token' }) + @IsString() + userAccessToken: string; + + @ApiProperty({ description: 'Page ID' }) + @IsString() + pageId: string; + + @ApiProperty({ description: 'Page access token' }) + @IsString() + pageAccessToken: string; +} diff --git a/backend/src/modules/multichat/providers/facebook/dto/index.ts b/backend/src/modules/multichat/providers/facebook/dto/index.ts new file mode 100644 index 0000000..87344b4 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-messenger-provider.dto'; +export * from './messenger-provider.dto'; +export * from './update-messenger-provider.dto'; diff --git a/backend/src/modules/multichat/providers/facebook/dto/messenger-provider.dto.ts b/backend/src/modules/multichat/providers/facebook/dto/messenger-provider.dto.ts new file mode 100644 index 0000000..412c377 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/dto/messenger-provider.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { ChatProviderDto } from '../../../chat-provider/dto'; + +export class MessengerProviderDto extends ChatProviderDto { + @ApiProperty({ description: 'Page id' }) + @IsString() + pageId: string; + + @ApiProperty({ description: 'Page access token' }) + @IsString() + pageAccessToken: string; +} diff --git a/backend/src/modules/multichat/providers/facebook/dto/update-messenger-provider.dto.ts b/backend/src/modules/multichat/providers/facebook/dto/update-messenger-provider.dto.ts new file mode 100644 index 0000000..7db7180 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/dto/update-messenger-provider.dto.ts @@ -0,0 +1,3 @@ +import { UpdateChatProviderDto } from '../../../chat-provider/dto'; + +export class UpdateMessengerProviderDto extends UpdateChatProviderDto {} diff --git a/backend/src/modules/multichat/providers/facebook/entities/chat-provider-messenger.entity.ts b/backend/src/modules/multichat/providers/facebook/entities/chat-provider-messenger.entity.ts new file mode 100644 index 0000000..2e617a9 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/entities/chat-provider-messenger.entity.ts @@ -0,0 +1,65 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ChatProvider } from '../../../chat-provider/entities'; +import { MessengerProviderDto, CreateMessengerProviderDto } from '../dto'; + +@Entity() +export class ChatProviderMessenger { + @PrimaryColumn() + providerId: number; + + @Column() + userId: string; + + @Column() + userAccessToken: string; + + @Column() + pageId: string; + + @Column() + pageAccessToken: string; + + @Column() + accountId: number; + + _provider: ChatProvider; + + constructor( + accountId: number, + providerId: number, + userId: string, + userAccessToken: string, + pageId: string, + pageAccessToken: string, + ) { + this.accountId = accountId; + this.providerId = providerId; + this.userId = userId; + this.userAccessToken = userAccessToken; + this.pageId = pageId; + this.pageAccessToken = pageAccessToken; + } + + public get provider(): ChatProvider { + return this._provider; + } + public set provider(provider: ChatProvider) { + this._provider = provider; + } + + public toDto(): MessengerProviderDto { + return { ...this.provider.toDto(), pageId: this.pageId, pageAccessToken: this.pageAccessToken }; + } + + public static fromDto(accountId: number, providerId: number, dto: CreateMessengerProviderDto): ChatProviderMessenger { + return new ChatProviderMessenger( + accountId, + providerId, + dto.userId, + dto.userAccessToken, + dto.pageId, + dto.pageAccessToken, + ); + } +} diff --git a/backend/src/modules/multichat/providers/facebook/entities/index.ts b/backend/src/modules/multichat/providers/facebook/entities/index.ts new file mode 100644 index 0000000..9b0ecb0 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/entities/index.ts @@ -0,0 +1 @@ +export * from './chat-provider-messenger.entity'; diff --git a/backend/src/modules/multichat/providers/facebook/facebook-provider.module.ts b/backend/src/modules/multichat/providers/facebook/facebook-provider.module.ts new file mode 100644 index 0000000..3a4ef58 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/facebook-provider.module.ts @@ -0,0 +1,29 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; + +import { ChatMessageModule } from '../../chat-message/chat-message.module'; +import { ChatProviderModule } from '../../chat-provider/chat-provider.module'; + +import facebookConfig from './config/facebook.config'; +import { ChatProviderMessenger } from './entities'; +import { MessengerProviderService } from './messenger-provider.service'; +import { MessengerProviderController, PublicMessengerProviderController } from './controllers'; + +@Module({ + imports: [ + ConfigModule.forFeature(facebookConfig), + TypeOrmModule.forFeature([ChatProviderMessenger]), + IAMModule, + StorageModule, + forwardRef(() => ChatMessageModule), + ChatProviderModule, + ], + providers: [MessengerProviderService], + controllers: [MessengerProviderController, PublicMessengerProviderController], + exports: [MessengerProviderService], +}) +export class FacebookProviderModule {} diff --git a/backend/src/modules/multichat/providers/facebook/messenger-provider.service.ts b/backend/src/modules/multichat/providers/facebook/messenger-provider.service.ts new file mode 100644 index 0000000..4c02648 --- /dev/null +++ b/backend/src/modules/multichat/providers/facebook/messenger-provider.service.ts @@ -0,0 +1,573 @@ +import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { HttpService } from '@nestjs/axios'; +import { Repository } from 'typeorm'; +import { catchError } from 'rxjs/operators'; +import { lastValueFrom } from 'rxjs'; +import * as crypto from 'crypto'; + +import { + ForbiddenError, + formatState, + formatUrlQuery, + FrontendRoute, + NotFoundError, + parseState, + StringUtil, + UrlGeneratorService, +} from '@/common'; + +import { AccountService } from '@/modules/iam/account/account.service'; +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { StorageUrlService } from '@/modules/storage/storage-url.service'; +import { StorageService } from '@/modules/storage/storage.service'; + +import { ChatProviderStatus, ChatProviderTransport, ChatProviderType, MultichatEventType } from '../../common'; +import { ChatUser } from '../../chat-user'; +import { ChatMessage } from '../../chat-message/entities/chat-message.entity'; +import { ChatMessageService } from '../../chat-message/services/chat-message.service'; +import { ChatProvider } from '../../chat-provider/entities'; +import { ChatProviderService } from '../../chat-provider/services/chat-provider.service'; + +import { FacebookConfig } from './config/facebook.config'; +import { CreateMessengerProviderDto, UpdateMessengerProviderDto } from './dto'; +import { ChatProviderMessenger } from './entities'; + +const FacebookUrls = { + facebook: 'https://www.facebook.com', + oauthDialog: () => `${FacebookUrls.facebook}/dialog/oauth`, + graph: 'https://graph.facebook.com/v19.0', + oauthAccessToken: () => `${FacebookUrls.graph}/oauth/access_token`, + profile: (profileId: string) => `${FacebookUrls.graph}/${profileId}`, + messages: (profileId: string) => `${FacebookUrls.profile(profileId)}/messages`, + subscriptions: (appId: string) => `${FacebookUrls.profile(appId)}/subscriptions`, + subscribedApps: (profileId: string) => `${FacebookUrls.profile(profileId)}/subscribed_apps`, + accounts: (profileId: string) => `${FacebookUrls.profile(profileId)}/accounts`, + permissions: (profileId: string) => `${FacebookUrls.profile(profileId)}/permissions`, +} as const; + +const CALLBACK_PATH = '/api/chat/messenger/callback'; +//const WEBHOOK_PATH = '/api/chat/messenger/webhook'; + +@Injectable() +export class MessengerProviderService { + private readonly logger = new Logger(MessengerProviderService.name); + private _config: FacebookConfig; + + constructor( + private readonly configService: ConfigService, + private readonly httpService: HttpService, + @InjectRepository(ChatProviderMessenger) + private readonly repository: Repository, + private readonly accountService: AccountService, + private readonly storageService: StorageService, + private readonly storageUrlService: StorageUrlService, + private readonly urlGenerator: UrlGeneratorService, + private readonly chatProviderService: ChatProviderService, + @Inject(forwardRef(() => ChatMessageService)) + private readonly chatMessageService: ChatMessageService, + ) { + this._config = this.configService.get('facebook'); + } + + getConnectUrl(accountId: number, userId: number, display?: string): string { + return formatUrlQuery(FacebookUrls.oauthDialog(), { + response_type: 'code', + override_default_response_type: 'true', + display: display, + client_id: this._config.appId, + redirect_uri: this.getAuthRedirectUrl(), + config_id: this._config.messengerAuthConfigId, + state: formatState(accountId, userId), + auth_type: 'rerequest', + }); + } + + private getAuthRedirectUrl(): string { + return this.urlGenerator.createUrl({ route: CALLBACK_PATH }); + } + + /* + private getMessageWebhookUrl(): string { + return this.urlGenerator.createUrl(WEBHOOK_PATH); + } + */ + + async authCallback(code: string, state: string, error?: string): Promise { + const [accountId, userId] = parseState(state, Number); + const account = await this.accountService.findOne({ accountId }); + + const redirectUrl = this.urlGenerator.createUrl({ + route: FrontendRoute.settings.facebook.messenger(), + subdomain: account.subdomain, + }); + + if (error) { + return formatUrlQuery(redirectUrl, { error }); + } + + const accessToken = await this.getAccessToken(code); + + const profile = await this.getPageProfile('me', accessToken); + + if (!profile?.accounts?.data) { + return formatUrlQuery(redirectUrl, { error: 'true' }); + } + + const providerIds: number[] = []; + for (const { id, name, access_token } of profile.accounts.data) { + if (await this.subscribePage(id, access_token)) { + const fbmProviders = await this.findProvidersByPageId(id, account.id); + let fbmProvider = fbmProviders.length > 0 ? fbmProviders[0] : null; + if (fbmProvider) { + fbmProvider = await this.updateAccessToken(fbmProvider, accessToken); + } else { + fbmProvider = await this.create(accountId, userId, { + type: ChatProviderType.Facebook, + transport: ChatProviderTransport.Messenger, + title: name, + status: ChatProviderStatus.Active, + accessibleUserIds: [], + responsibleUserIds: [], + userId: profile.id, + userAccessToken: accessToken, + pageId: id, + pageAccessToken: access_token, + }); + } + providerIds.push(fbmProvider.providerId); + } + } + + return formatUrlQuery(redirectUrl, { providerId: String(providerIds[0]) }); + } + + private async getAccessToken(code: string): Promise { + try { + const response$ = this.httpService + .get(FacebookUrls.oauthAccessToken(), { + params: { + client_id: this._config.appId, + client_secret: this._config.appSecret, + redirect_uri: this.getAuthRedirectUrl(), + code: code, + }, + }) + .pipe( + catchError((error) => { + this.logger.error(`Get access token error`, (error as Error)?.stack); + throw error; + }), + ); + const response = await lastValueFrom(response$); + return response.data.access_token; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return null; + } + } + + /** + * Configured in FB App Messenger + */ + /* + private async subscribeApp(): Promise { + try { + const response$ = this.httpService + .post(FacebookUrls.subscriptions(this._config.appId), null, { + params: { + object: 'page', + callback_url: this.getMessageWebhookUrl(), + fields: 'messages', + include_values: true, + verify_token: this._config.messengerValidationToken, + access_token: this._config.appAccessToken, + }, + }) + .pipe( + catchError((error) => { + this.logger.error(`Subscribe app error: ${JSON.stringify(error)}`); + throw error; + }), + ); + const response = await lastValueFrom(response$); + return Boolean(response.data.success); + } catch (e) { + return false; + } + } + */ + + private async subscribePage(pageId: string, accessToken: string): Promise { + try { + const response$ = this.httpService + .post(FacebookUrls.subscribedApps(pageId), null, { + params: { + subscribed_fields: 'messages', + access_token: accessToken, + }, + }) + .pipe( + catchError((error) => { + this.logger.error(`Subscribe app error`, (error as Error)?.stack); + throw error; + }), + ); + const response = await lastValueFrom(response$); + return Boolean(response.data.success); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return false; + } + } + + async handleDeauthoriseRequest(signedRequest: string): Promise { + const userId = await this.deleteAuthData(signedRequest); + + return !!userId; + } + + async handleDeleteAuthRequest(signedRequest: string) { + const verifyUrl = this.urlGenerator.createUrl({ route: FrontendRoute.settings.facebook.deleteVerify() }); + const userId = await this.deleteAuthData(signedRequest); + + return { url: verifyUrl, confirmation_code: userId ?? '' }; + } + + private async deleteAuthData(signedRequest: string): Promise { + try { + const [encodedSig, payload] = signedRequest.split('.'); + + const sig = StringUtil.decode(encodedSig, 'base64', 'hex'); + const expectedSig = crypto.createHmac('sha256', this._config.appSecret).update(payload).digest('hex'); + + if (sig !== expectedSig) { + return null; + } + + const decodedPayload = StringUtil.decode(payload, 'base64', 'utf-8'); + const data = JSON.parse(decodedPayload) as { user_id: string }; + + const fbmProviders = await this.findProvidersByPageId(data.user_id); + for (const fbmProvider of fbmProviders) { + await this.chatProviderService.update(fbmProvider.accountId, null, fbmProvider.providerId, { + status: ChatProviderStatus.Deleted, + }); + } + + return data.user_id; + } catch (error) { + this.logger.error(`Handle deauthorise user error`, (error as Error)?.stack); + return null; + } + } + + async getProvidersWithSettings(accountId: number, user: User): Promise { + const allProviders = await this.repository + .createQueryBuilder('tp') + .where('tp.account_id = :accountId', { accountId }) + .getMany(); + + for (const provider of allProviders) { + provider.provider = await this.chatProviderService.findOne( + accountId, + null, + { providerId: provider.providerId }, + { expand: ['accessibleUsers', 'responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + } + + return user.isAdmin + ? allProviders + : allProviders.filter( + (p) => + !p.provider.accessibleUsers || + p.provider.accessibleUsers.length === 0 || + p.provider.accessibleUsers.some((u) => u.userId === user.id), + ); + } + + async getProviderWithSettings(accountId: number, providerId: number): Promise { + const provider = await this.chatProviderService.findOne( + accountId, + null, + { providerId }, + { expand: ['accessibleUsers', 'responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + + const fbmProvider = await this.getProvider(accountId, providerId); + fbmProvider.provider = provider; + + return fbmProvider; + } + + private async getProvider(accountId: number, providerId: number): Promise { + const provider = await this.repository.findOneBy({ accountId, providerId }); + + if (!provider) { + throw NotFoundError.withId(ChatProviderMessenger, providerId); + } + + return provider; + } + + private async findProvidersByPageId(pageId: string, accountId?: number): Promise { + return this.repository.findBy({ pageId, accountId }); + } + + async create(accountId: number, userId: number, dto: CreateMessengerProviderDto): Promise { + const provider = await this.chatProviderService.create(accountId, userId, dto); + + const fbmProvider = await this.repository.save(ChatProviderMessenger.fromDto(accountId, provider.id, dto)); + fbmProvider.provider = provider; + + return fbmProvider; + } + + async update(accountId: number, providerId: number, dto: UpdateMessengerProviderDto): Promise { + const provider = await this.chatProviderService.update(accountId, null, providerId, dto); + + const fbmProvider = await this.getProvider(accountId, providerId); + + fbmProvider.provider = provider; + return fbmProvider; + } + + private async updateAccessToken( + fbmProvider: ChatProviderMessenger, + accessToken: string, + ): Promise { + fbmProvider.pageAccessToken = accessToken; + await this.repository.save(fbmProvider); + + return fbmProvider; + } + + async delete({ accountId, userId, providerId }: { accountId: number; userId: number; providerId: number }) { + const fbmProvider = await this.getProvider(accountId, providerId); + + if (fbmProvider?.pageAccessToken) { + await this.revokeAccessToken(fbmProvider.userId, fbmProvider.userAccessToken); + } + + await this.repository.delete({ accountId, providerId }); + await this.chatProviderService.delete({ accountId, userId, providerId }); + } + + private async revokeAccessToken(userId: string, accessToken: string): Promise { + try { + await lastValueFrom( + this.httpService.delete(FacebookUrls.permissions(userId), { params: { access_token: accessToken } }).pipe( + catchError((error) => { + this.logger.error(`Error revoking access token`, (error as Error)?.stack); + throw error; + }), + ), + ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return; + } + } + + async notifyChatUsers( + account: Account, + message: ChatMessage, + type: MultichatEventType, + provider: ChatProvider, + users: ChatUser[], + ): Promise { + const fbmProvider = await this.getProvider(account.id, provider.id); + fbmProvider.provider = provider; + + switch (type) { + case MultichatEventType.ChatMessageCreated: + this.sendMessage(account, fbmProvider, message, users); + break; + } + } + + private async sendMessage( + account: Account, + provider: ChatProviderMessenger, + message: ChatMessage, + users: ChatUser[], + ): Promise { + const files = + message.files && message.files.length > 0 + ? message.files.map((file) => ({ + url: this.storageUrlService.getTemporaryUrl(file.fileId, account.subdomain), + isImage: file.mimeType.startsWith('image/'), + })) + : []; + + for (const user of users) { + if (user.externalUser) { + if (message.text) { + const data = { + recipient: { id: user.externalUser.externalId }, + message: { text: message.text }, + }; + + this.sendOneMessage(data, provider.pageAccessToken); + } + + for (const file of files) { + const data = { + recipient: { id: user.externalUser.externalId }, + message: { + attachment: { type: 'file', payload: { url: file.url, is_reusable: true } }, + }, + }; + + this.sendOneMessage(data, provider.pageAccessToken); + } + } + } + } + + private async sendOneMessage(data: unknown, pageAccessToken: string): Promise { + try { + const response$ = this.httpService + .post(FacebookUrls.messages('me'), data, { + params: { access_token: pageAccessToken }, + }) + .pipe( + catchError((error) => { + this.logger.error(`Send message error`, (error as Error)?.stack); + throw error; + }), + ); + await lastValueFrom(response$); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return null; + } + } + + async createChatExternalId(userExternalId: string): Promise { + return this.formatChatExternalId(userExternalId); + } + + private formatChatExternalId(userExternalId: string): string { + return userExternalId; + } + + async verifyWebhook(token: string, challenge: string): Promise { + if (token === this._config.messengerValidationToken) { + return challenge; + } + throw new ForbiddenError(); + } + + async handleWebhook(body: any): Promise { + this.logger.debug(`Handle webhook body: ${JSON.stringify(body)}`); + if (body.object !== 'page') { + return; + } + + for (const entry of body.entry) { + const fbmProviders = await this.findProvidersByPageId(entry.id); + for (const fbmProvider of fbmProviders) { + const account = await this.accountService.findOne({ accountId: fbmProvider.accountId }); + for (const event of entry.messaging) { + if (event.message) { + await this.handleMessage(account, fbmProvider, event); + } + } + } + } + } + + private async handleMessage(account: Account, fbmProvider: ChatProviderMessenger, event: any) { + this.logger.debug(`Handle message: ${JSON.stringify(event)}`); + try { + const chatUser = await this.getChatUser({ + accountId: account.id, + providerId: fbmProvider.providerId, + pageAccessToken: fbmProvider.pageAccessToken, + profileId: event.sender.id, + }); + if (chatUser) { + const { mid, text, attachments } = event.message; + const fileIds = attachments ? await this.getMessageFileIds(account.id, attachments) : null; + await this.chatMessageService.createExternal(account, chatUser, text ?? '', mid, fileIds); + } + } catch (error) { + this.logger.error(`Handle message error`, (error as Error)?.stack); + } + } + + private async getChatUser({ + accountId, + providerId, + pageAccessToken, + profileId, + }: { + accountId: number; + providerId: number; + pageAccessToken: string; + profileId: string; + }): Promise { + const profile = await this.getUserProfile(profileId, pageAccessToken); + if (!profile.id) { + return null; + } + + return this.chatProviderService.getChatUserExternal({ + accountId, + providerId, + chatExternalId: this.formatChatExternalId(profile.id), + externalUserDto: { + externalId: profile.id, + firstName: profile.first_name ?? 'Unknown', + lastName: profile.last_name ?? 'User', + avatarUrl: profile.picture?.data?.url ?? null, + }, + }); + } + + private async getPageProfile(profileId: string, pageAccessToken: string): Promise { + return this.getProfile(profileId, pageAccessToken, 'id,accounts{id,name,access_token}'); + } + private async getUserProfile(profileId: string, pageAccessToken: string): Promise { + return this.getProfile(profileId, pageAccessToken, 'id,first_name,last_name,picture'); + } + private async getProfile(profileId: string, pageAccessToken: string, fields: string): Promise { + try { + const response$ = this.httpService + .get(FacebookUrls.profile(profileId), { + params: { access_token: pageAccessToken, fields: fields }, + }) + .pipe( + catchError((error) => { + this.logger.error(`Get user profile error`, (error as Error)?.stack); + throw error; + }), + ); + const response = await lastValueFrom(response$); + return response.data; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return { id: profileId }; + } + } + + private async getMessageFileIds(accountId: number, attachments: any[]): Promise { + const fileIds: string[] = []; + for (const attachment of attachments) { + const fileUrl = attachment?.payload?.url; + + if (fileUrl) { + const fileInfo = await this.storageService.storeExternalFile(accountId, null, fileUrl); + if (fileInfo) { + fileIds.push(fileInfo.id); + } + } + } + + return fileIds; + } +} diff --git a/backend/src/modules/multichat/providers/providers.module.ts b/backend/src/modules/multichat/providers/providers.module.ts new file mode 100644 index 0000000..aec7bf8 --- /dev/null +++ b/backend/src/modules/multichat/providers/providers.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; + +import { ChatProviderModule } from '../chat-provider/chat-provider.module'; +import { TwilioProviderModule } from './twilio/twilio-provider.module'; +import { FacebookProviderModule } from './facebook/facebook-provider.module'; +import { WazzupProviderModule } from './wazzup/wazzup-provider.module'; +import { ChatProviderProxyService } from './chat-provider-proxy.service'; + +@Module({ + imports: [ChatProviderModule, FacebookProviderModule, TwilioProviderModule, WazzupProviderModule], + providers: [ChatProviderProxyService], + exports: [ChatProviderProxyService], +}) +export class ProvidersModule {} diff --git a/backend/src/modules/multichat/providers/twilio/controllers/index.ts b/backend/src/modules/multichat/providers/twilio/controllers/index.ts new file mode 100644 index 0000000..8b6a66a --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/controllers/index.ts @@ -0,0 +1,2 @@ +export * from './public-twilio-provider.controller'; +export * from './twilio-provider.controller'; diff --git a/backend/src/modules/multichat/providers/twilio/controllers/public-twilio-provider.controller.ts b/backend/src/modules/multichat/providers/twilio/controllers/public-twilio-provider.controller.ts new file mode 100644 index 0000000..b94e449 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/controllers/public-twilio-provider.controller.ts @@ -0,0 +1,18 @@ +import { Body, Controller, Post, Res } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; +import { Response } from 'express'; + +import { TwilioProviderService } from '../twilio-provider.service'; + +@ApiExcludeController(true) +@Controller('chat/twilio') +export class PublicTwilioProviderController { + constructor(private readonly service: TwilioProviderService) {} + + @Post('webhook') + async handleWebhook(@Body() body: unknown, @Res() res: Response) { + const response = await this.service.handleWebhook(body); + + res.type('text/xml').send(response); + } +} diff --git a/backend/src/modules/multichat/providers/twilio/controllers/twilio-provider.controller.ts b/backend/src/modules/multichat/providers/twilio/controllers/twilio-provider.controller.ts new file mode 100644 index 0000000..5ae28b8 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/controllers/twilio-provider.controller.ts @@ -0,0 +1,78 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { AuthDataPrefetch } from '@/modules/iam/common/decorators/auth-data-prefetch.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { TwilioProviderDto, CreateTwilioProviderDto, UpdateTwilioProviderDto } from '../dto'; +import { TwilioProviderService } from '../twilio-provider.service'; + +@ApiTags('multichat/settings/twilio') +@Controller('chat/settings/providers/twilio') +@JwtAuthorized() +@TransformToDto() +export class TwilioProviderController { + constructor(private readonly service: TwilioProviderService) {} + + @ApiOperation({ summary: 'Create twilio chat provider', description: 'Create twilio chat provider' }) + @ApiBody({ description: 'Data for creating Twilio chat provider', type: CreateTwilioProviderDto }) + @ApiCreatedResponse({ description: 'Twilio chat provider', type: TwilioProviderDto }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateTwilioProviderDto) { + return this.service.create(accountId, userId, dto); + } + + @ApiOperation({ + summary: 'Get twilio chat providers with settings', + description: 'Get twilio chat providers with settings', + }) + @ApiOkResponse({ description: 'Twilio chat providers', type: [TwilioProviderDto] }) + @AuthDataPrefetch({ user: true }) + @Get() + async getProvidersWithSettings(@CurrentAuth() { accountId, user }: AuthData) { + return this.service.getProvidersWithSettings(accountId, user); + } + + @ApiOperation({ + summary: 'Get twilio chat provider with settings', + description: 'Get twilio chat provider with settings', + }) + @ApiParam({ name: 'providerId', description: 'Twilio chat provider ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Twilio chat provider', type: TwilioProviderDto }) + @Get(':providerId') + async getProviderWithSettings( + @CurrentAuth() { accountId }: AuthData, + @Param('providerId', ParseIntPipe) providerId: number, + ) { + return this.service.getProviderWithSettings(accountId, providerId); + } + + @ApiOperation({ summary: 'Update twilio chat provider', description: 'Update twilio chat provider' }) + @ApiParam({ name: 'providerId', type: Number, required: true, description: 'Twilio chat provider ID' }) + @ApiBody({ description: 'Data for updating Twilio chat provider', type: UpdateTwilioProviderDto }) + @ApiOkResponse({ description: 'Twilio chat provider', type: TwilioProviderDto }) + @Put(':providerId') + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('providerId', ParseIntPipe) providerId: number, + @Body() dto: UpdateTwilioProviderDto, + ) { + return this.service.update(accountId, providerId, dto); + } + + @ApiOperation({ summary: 'Delete twilio chat provider', description: 'Delete twilio chat provider' }) + @ApiParam({ name: 'providerId', type: Number, required: true, description: 'Twilio chat provider ID' }) + @ApiOkResponse({ type: Boolean }) + @Delete(':providerId') + async delete( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('providerId', ParseIntPipe) providerId: number, + ): Promise { + await this.service.delete({ accountId, userId, providerId }); + + return true; + } +} diff --git a/backend/src/modules/multichat/providers/twilio/dto/create-twilio-provider.dto.ts b/backend/src/modules/multichat/providers/twilio/dto/create-twilio-provider.dto.ts new file mode 100644 index 0000000..b373b9d --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/dto/create-twilio-provider.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { CreateChatProviderDto } from '../../../chat-provider/dto'; + +export class CreateTwilioProviderDto extends CreateChatProviderDto { + @ApiProperty({ description: 'Twilio account SID' }) + @IsString() + accountSid: string; + + @ApiProperty({ description: 'Twilio auth token' }) + @IsString() + authToken: string; + + @ApiProperty({ description: 'Twilio phone number' }) + @IsString() + phoneNumber: string; +} diff --git a/backend/src/modules/multichat/providers/twilio/dto/index.ts b/backend/src/modules/multichat/providers/twilio/dto/index.ts new file mode 100644 index 0000000..77ecb78 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-twilio-provider.dto'; +export * from './twilio-provider.dto'; +export * from './update-twilio-provider.dto'; diff --git a/backend/src/modules/multichat/providers/twilio/dto/twilio-provider.dto.ts b/backend/src/modules/multichat/providers/twilio/dto/twilio-provider.dto.ts new file mode 100644 index 0000000..e8fd183 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/dto/twilio-provider.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { ChatProviderDto } from '../../../chat-provider/dto'; + +export class TwilioProviderDto extends ChatProviderDto { + @ApiProperty({ description: 'Twilio account SID' }) + @IsString() + accountSid: string; + + @ApiProperty({ description: 'Twilio phone number' }) + @IsString() + phoneNumber: string; +} diff --git a/backend/src/modules/multichat/providers/twilio/dto/update-twilio-provider.dto.ts b/backend/src/modules/multichat/providers/twilio/dto/update-twilio-provider.dto.ts new file mode 100644 index 0000000..8ada9f4 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/dto/update-twilio-provider.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +import { UpdateChatProviderDto } from '../../../chat-provider/dto'; + +export class UpdateTwilioProviderDto extends UpdateChatProviderDto { + @ApiProperty({ description: 'Twilio account SID' }) + @IsString() + accountSid: string; + + @ApiPropertyOptional({ description: 'Twilio auth token' }) + @IsOptional() + @IsString() + authToken?: string; + + @ApiProperty({ description: 'Twilio phone number' }) + @IsString() + phoneNumber: string; +} diff --git a/backend/src/modules/multichat/providers/twilio/entities/chat-provider-twilio.entity.ts b/backend/src/modules/multichat/providers/twilio/entities/chat-provider-twilio.entity.ts new file mode 100644 index 0000000..401e9e3 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/entities/chat-provider-twilio.entity.ts @@ -0,0 +1,54 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ChatProvider } from '../../../chat-provider/entities'; +import { TwilioProviderDto, CreateTwilioProviderDto, UpdateTwilioProviderDto } from '../dto'; + +@Entity() +export class ChatProviderTwilio { + @PrimaryColumn() + providerId: number; + + @Column() + accountSid: string; + + @Column() + authToken: string; + + @Column() + phoneNumber: string; + + @Column() + accountId: number; + + _provider: ChatProvider; + + constructor(accountId: number, providerId: number, accountSid: string, authToken: string, phoneNumber: string) { + this.accountId = accountId; + this.providerId = providerId; + this.accountSid = accountSid; + this.authToken = authToken; + this.phoneNumber = phoneNumber; + } + + public get provider(): ChatProvider { + return this._provider; + } + public set provider(provider: ChatProvider) { + this._provider = provider; + } + + public toDto(): TwilioProviderDto { + return { ...this.provider.toDto(), accountSid: this.accountSid, phoneNumber: this.phoneNumber }; + } + + public static fromDto(accountId: number, providerId: number, dto: CreateTwilioProviderDto): ChatProviderTwilio { + return new ChatProviderTwilio(accountId, providerId, dto.accountSid, dto.authToken, dto.phoneNumber); + } + + public update(dto: UpdateTwilioProviderDto): ChatProviderTwilio { + this.accountSid = dto.accountSid; + this.phoneNumber = dto.phoneNumber; + this.authToken = dto.authToken ? dto.authToken : this.authToken; + return this; + } +} diff --git a/backend/src/modules/multichat/providers/twilio/entities/index.ts b/backend/src/modules/multichat/providers/twilio/entities/index.ts new file mode 100644 index 0000000..b011673 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/entities/index.ts @@ -0,0 +1 @@ +export * from './chat-provider-twilio.entity'; diff --git a/backend/src/modules/multichat/providers/twilio/twilio-provider.module.ts b/backend/src/modules/multichat/providers/twilio/twilio-provider.module.ts new file mode 100644 index 0000000..2d5c815 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/twilio-provider.module.ts @@ -0,0 +1,26 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; + +import { ChatMessageModule } from '../../chat-message/chat-message.module'; +import { ChatProviderModule } from '../../chat-provider/chat-provider.module'; + +import { ChatProviderTwilio } from './entities'; +import { TwilioProviderService } from './twilio-provider.service'; +import { PublicTwilioProviderController, TwilioProviderController } from './controllers'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ChatProviderTwilio]), + IAMModule, + StorageModule, + forwardRef(() => ChatMessageModule), + ChatProviderModule, + ], + providers: [TwilioProviderService], + controllers: [TwilioProviderController, PublicTwilioProviderController], + exports: [TwilioProviderService], +}) +export class TwilioProviderModule {} diff --git a/backend/src/modules/multichat/providers/twilio/twilio-provider.service.ts b/backend/src/modules/multichat/providers/twilio/twilio-provider.service.ts new file mode 100644 index 0000000..2608661 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/twilio-provider.service.ts @@ -0,0 +1,311 @@ +import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import TwilioSDK from 'twilio'; + +import { NotFoundError, splitByFirstSpace } from '@/common'; + +import { AccountService } from '@/modules/iam/account/account.service'; +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { StorageUrlService } from '@/modules/storage/storage-url.service'; +import { StorageService } from '@/modules/storage/storage.service'; + +import { ChatProviderType, MultichatEventType } from '../../common'; +import { ChatUser } from '../../chat-user'; +import { ChatMessage } from '../../chat-message/entities/chat-message.entity'; +import { ChatMessageService } from '../../chat-message/services/chat-message.service'; +import { ChatProvider } from '../../chat-provider/entities'; +import { ChatProviderService } from '../../chat-provider/services/chat-provider.service'; + +import { CreateTwilioProviderDto, UpdateTwilioProviderDto } from './dto'; +import { ChatProviderTwilio } from './entities'; +import { TwilioRequestBody } from './types'; + +@Injectable() +export class TwilioProviderService { + private readonly logger = new Logger(TwilioProviderService.name); + + constructor( + @InjectRepository(ChatProviderTwilio) + private readonly repository: Repository, + private readonly accountService: AccountService, + private readonly storageService: StorageService, + private readonly storageUrlService: StorageUrlService, + private readonly chatProviderService: ChatProviderService, + @Inject(forwardRef(() => ChatMessageService)) + private readonly chatMessageService: ChatMessageService, + ) {} + + async getProvidersWithSettings(accountId: number, user: User): Promise { + const allProviders = await this.repository + .createQueryBuilder('tp') + .where('tp.account_id = :accountId', { accountId }) + .getMany(); + + for (const provider of allProviders) { + provider.provider = await this.chatProviderService.findOne( + accountId, + null, + { providerId: provider.providerId }, + { expand: ['accessibleUsers', 'responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + } + + return user.isAdmin + ? allProviders + : allProviders.filter( + (p) => + !p.provider.accessibleUsers || + p.provider.accessibleUsers.length === 0 || + p.provider.accessibleUsers.some((u) => u.userId === user.id), + ); + } + + async getProviderWithSettings(accountId: number, providerId: number): Promise { + const provider = await this.chatProviderService.findOne( + accountId, + null, + { providerId }, + { expand: ['accessibleUsers', 'responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + + const twilioProvider = await this.getProvider(accountId, providerId); + twilioProvider.provider = provider; + + return twilioProvider; + } + + private async getProvider(accountId: number, providerId: number): Promise { + const provider = await this.repository.findOneBy({ accountId, providerId }); + + if (!provider) { + throw NotFoundError.withId(ChatProviderTwilio, providerId); + } + + return provider; + } + + private async getProviderByPhoneNumber(phoneNumber: string): Promise { + const twilioProvider = await this.repository.findOneBy({ phoneNumber }); + + if (!twilioProvider) { + throw NotFoundError.withId(ChatProviderTwilio, phoneNumber); + } + + return twilioProvider; + } + + async create(accountId: number, userId: number, dto: CreateTwilioProviderDto): Promise { + const provider = await this.chatProviderService.create(accountId, userId, dto); + + const twilioProvider = await this.repository.save(ChatProviderTwilio.fromDto(accountId, provider.id, dto)); + twilioProvider.provider = provider; + + return twilioProvider; + } + + async update(accountId: number, providerId: number, dto: UpdateTwilioProviderDto): Promise { + const provider = await this.chatProviderService.update(accountId, null, providerId, dto); + + const twilioProvider = await this.getProvider(accountId, providerId); + await this.repository.save(twilioProvider.update(dto)); + + twilioProvider.provider = provider; + + return twilioProvider; + } + + async delete({ accountId, userId, providerId }: { accountId: number; userId: number; providerId: number }) { + await this.repository.delete({ accountId, providerId }); + await this.chatProviderService.delete({ accountId, userId, providerId }); + } + + async notifyChatUsers( + account: Account, + message: ChatMessage, + type: MultichatEventType, + provider: ChatProvider, + users: ChatUser[], + ): Promise { + const twilioProvider = await this.getProvider(account.id, provider.id); + const twilio = TwilioSDK(twilioProvider.accountSid, twilioProvider.authToken); + const from = this.formatPhoneNumber(twilioProvider.phoneNumber); + + switch (type) { + case MultichatEventType.ChatMessageCreated: + await this.sendMessage(account, twilio, from, message, users); + break; + } + } + + private async sendMessage( + account: Account, + twilio: TwilioSDK.Twilio, + from: string, + message: ChatMessage, + users: ChatUser[], + ): Promise { + const fileUrls = + message.files && message.files.length > 0 + ? message.files.map((file) => this.storageUrlService.getTemporaryUrl(file.fileId, account.subdomain)) + : undefined; + + await Promise.all( + users + .filter((u) => u.externalUser) + .map(async (user) => { + try { + await twilio.messages.create({ + from, + to: this.formatPhoneNumber(user.externalUser.phone), + body: message.text, + mediaUrl: fileUrls, + }); + } catch (e) { + this.logger.error(`Send message error`, (e as Error)?.stack); + } + }), + ); + } + + async sendDirectMessage({ + accountId, + providerId, + phone, + message, + }: { + accountId: number; + providerId: number; + phone: string; + message: string; + }): Promise { + try { + const twilioProvider = await this.getProvider(accountId, providerId); + const twilio = TwilioSDK(twilioProvider.accountSid, twilioProvider.authToken); + + await twilio.messages.create({ + from: this.formatPhoneNumber(twilioProvider.phoneNumber), + to: this.formatPhoneNumber(phone), + body: message, + }); + } catch (e) { + this.logger.error(`Send message error`, (e as Error)?.stack); + } + } + + async createChatExternalId(userExternalId: string): Promise { + return this.formatChatExternalId(userExternalId); + } + + private formatChatExternalId(userExternalId: string): string { + return userExternalId; + } + + async handleWebhook(body: unknown): Promise { + const message = body as TwilioRequestBody; + if (message.To) { + const [, providerPhoneNumber] = this.parsePhoneNumber(message.To); + const twilioProvider = await this.getProviderByPhoneNumber(providerPhoneNumber); + if (twilioProvider) { + const account = await this.accountService.findOne({ accountId: twilioProvider.accountId }); + await this.handleMessage({ account, twilioProvider, message }); + } + } + + return new TwilioSDK.twiml.MessagingResponse().toString(); + } + + private async handleMessage({ + account, + twilioProvider, + message, + }: { + account: Account; + twilioProvider: ChatProviderTwilio; + message: TwilioRequestBody; + }) { + this.logger.debug(`Handle message: ${JSON.stringify(message)}`); + try { + const chatUser = await this.getChatUser({ + accountId: account.id, + providerId: twilioProvider.providerId, + message, + }); + if (chatUser) { + const fileIds = await this.getMessageFileIds(account.id, twilioProvider, message); + await this.chatMessageService.createExternal(account, chatUser, message.Body, message.MessageSid, fileIds); + } + } catch (error) { + this.logger.error(`Handle message error`, (error as Error)?.stack); + } + } + + private async getChatUser({ + accountId, + providerId, + message, + }: { + accountId: number; + providerId: number; + message: TwilioRequestBody; + }): Promise { + const [, userPhoneNumber] = this.parsePhoneNumber(message.From); + const [firstName, lastName] = splitByFirstSpace(message.ProfileName || message.WaId || userPhoneNumber); + + return this.chatProviderService.getChatUserExternal({ + accountId, + providerId, + chatExternalId: this.formatChatExternalId(userPhoneNumber), + externalUserDto: { + externalId: userPhoneNumber, + firstName, + lastName, + phone: userPhoneNumber, + }, + }); + } + + private async getMessageFileIds( + accountId: number, + twilioProvider: ChatProviderTwilio, + data: TwilioRequestBody, + ): Promise { + if (!data.NumMedia) { + return null; + } + + const auth = `Basic ${Buffer.from(`${twilioProvider.accountSid}:${twilioProvider.authToken}`).toString('base64')}`; + const numMedia = parseInt(data.NumMedia, 10); + const fileIds: string[] = []; + for (let i = 0; i < numMedia; i++) { + const fileUrl = data[`MediaUrl${i}`]; + + if (fileUrl) { + const fileInfo = await this.storageService.storeExternalFile(accountId, null, fileUrl, { + authorization: auth, + }); + if (fileInfo) { + fileIds.push(fileInfo.id); + } + } + } + + return fileIds; + } + + private formatPhoneNumber(phoneNumber: string): string { + const formattedPhoneNumber = phoneNumber.startsWith('+') ? phoneNumber : `+${phoneNumber}`; + + return `whatsapp:${formattedPhoneNumber}`; + } + + private parsePhoneNumber(phoneNumber: string): [ChatProviderType, string] { + const match = phoneNumber.match(/^whatsapp:(.+)$/); + if (!match) { + throw new Error('Unsupported phone number format'); + } + + return [ChatProviderType.Twilio, match[1]]; + } +} diff --git a/backend/src/modules/multichat/providers/twilio/types/index.ts b/backend/src/modules/multichat/providers/twilio/types/index.ts new file mode 100644 index 0000000..f43c3a9 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/types/index.ts @@ -0,0 +1 @@ +export * from './twilio-request-body'; diff --git a/backend/src/modules/multichat/providers/twilio/types/twilio-request-body.ts b/backend/src/modules/multichat/providers/twilio/types/twilio-request-body.ts new file mode 100644 index 0000000..6ad2ec5 --- /dev/null +++ b/backend/src/modules/multichat/providers/twilio/types/twilio-request-body.ts @@ -0,0 +1,16 @@ +export class TwilioRequestBody { + SmsMessageSid?: string; + NumMedia?: string; + ProfileName?: string; + SmsSid?: string; + WaId?: string; + SmsStatus?: string; + Body?: string; + To?: string; + NumSegments?: string; + ReferralNumMedia?: string; + MessageSid?: string; + AccountSid?: string; + From?: string; + ApiVersion?: string; +} diff --git a/backend/src/modules/multichat/providers/wazzup/config/index.ts b/backend/src/modules/multichat/providers/wazzup/config/index.ts new file mode 100644 index 0000000..4d5fc2e --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/config/index.ts @@ -0,0 +1 @@ +export * from './wazzup.config'; diff --git a/backend/src/modules/multichat/providers/wazzup/config/wazzup.config.ts b/backend/src/modules/multichat/providers/wazzup/config/wazzup.config.ts new file mode 100644 index 0000000..e5148a3 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/config/wazzup.config.ts @@ -0,0 +1,11 @@ +import { registerAs } from '@nestjs/config'; + +export interface WazzupConfig { + secret: string; + enqueue: boolean; +} + +export default registerAs( + 'wazzup', + (): WazzupConfig => ({ secret: process.env.WAZZUP_SECRET, enqueue: process.env.WAZZUP_PROCESS_ENQUEUE === 'true' }), +); diff --git a/backend/src/modules/multichat/providers/wazzup/controllers/index.ts b/backend/src/modules/multichat/providers/wazzup/controllers/index.ts new file mode 100644 index 0000000..f7f1eca --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/controllers/index.ts @@ -0,0 +1,2 @@ +export * from './public-wazzup-provider.controller'; +export * from './wazzup-provider.controller'; diff --git a/backend/src/modules/multichat/providers/wazzup/controllers/public-wazzup-provider.controller.ts b/backend/src/modules/multichat/providers/wazzup/controllers/public-wazzup-provider.controller.ts new file mode 100644 index 0000000..5e3f7bd --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/controllers/public-wazzup-provider.controller.ts @@ -0,0 +1,18 @@ +import { Body, Controller, Post, Res } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; +import { Response } from 'express'; + +import { WazzupProviderService } from '../wazzup-provider.service'; + +@ApiExcludeController(true) +@Controller('chat/wazzup') +export class PublicWazzupProviderController { + constructor(private readonly service: WazzupProviderService) {} + + @Post('webhook') + async handleWebhook(@Body() body: unknown, @Res() res: Response) { + const response = await this.service.handleWebhook(body); + + res.type('text/json').status(200).send(response); + } +} diff --git a/backend/src/modules/multichat/providers/wazzup/controllers/wazzup-provider.controller.ts b/backend/src/modules/multichat/providers/wazzup/controllers/wazzup-provider.controller.ts new file mode 100644 index 0000000..9ad1b70 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/controllers/wazzup-provider.controller.ts @@ -0,0 +1,87 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { AuthDataPrefetch } from '@/modules/iam/common/decorators/auth-data-prefetch.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { WazzupProviderDto, CreateWazzupProviderDto, UpdateWazzupProviderDto, WazzupChannelDto } from '../dto'; +import { WazzupProviderService } from '../wazzup-provider.service'; + +@ApiTags('multichat/settings/wazzup') +@Controller('chat/settings/providers/wazzup') +@JwtAuthorized() +@TransformToDto() +export class WazzupProviderController { + constructor(private readonly service: WazzupProviderService) {} + + @ApiOperation({ summary: 'Create Wazzup chat provider', description: 'Create Wazzup chat provider' }) + @ApiBody({ description: 'Data for creating Wazzup chat provider', type: CreateWazzupProviderDto }) + @ApiCreatedResponse({ description: 'Wazzup chat provider', type: WazzupProviderDto }) + @AuthDataPrefetch({ account: true }) + @Post() + async create(@CurrentAuth() { account, userId }: AuthData, @Body() dto: CreateWazzupProviderDto) { + return this.service.create(account, userId, dto); + } + + @ApiOperation({ summary: 'Get Wazzup API key', description: 'Get Wazzup API key' }) + @ApiQuery({ name: 'state', type: String, required: true, description: 'State' }) + @ApiOkResponse({ description: 'Wazzup API key', type: String }) + @AuthDataPrefetch({ account: true }) + @Get('api-key') + async getApiKey(@CurrentAuth() { account }: AuthData, @Query('state') state: string) { + return this.service.getApiKey(account, state); + } + + @ApiOperation({ summary: 'Get Wazzup channels', description: 'Get Wazzup channels' }) + @ApiQuery({ name: 'apiKey', type: String, required: true, description: 'API key' }) + @ApiOkResponse({ description: 'Wazzup channels', type: [WazzupChannelDto] }) + @Get('channels') + async findChannels(@CurrentAuth() { accountId }: AuthData, @Query('apiKey') apiKey: string) { + return this.service.findChannels(accountId, apiKey); + } + + @ApiOperation({ summary: 'Get Wazzup chat providers', description: 'Get Wazzup chat providers' }) + @ApiOkResponse({ description: 'Wazzup chat providers', type: [WazzupProviderDto] }) + @AuthDataPrefetch({ user: true }) + @Get() + async findMany(@CurrentAuth() { accountId, user }: AuthData) { + return this.service.findMany(accountId, user); + } + + @ApiOperation({ summary: 'Get Wazzup chat provider', description: 'Get Wazzup chat provider' }) + @ApiParam({ name: 'providerId', description: 'Wazzup chat provider ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Wazzup chat provider', type: WazzupProviderDto }) + @Get(':providerId') + async findOne(@CurrentAuth() { accountId }: AuthData, @Param('providerId', ParseIntPipe) providerId: number) { + return this.service.findOne(accountId, providerId); + } + + @ApiOperation({ summary: 'Update Wazzup chat provider', description: 'Update Wazzup chat provider' }) + @ApiParam({ name: 'providerId', description: 'Wazzup chat provider ID', type: Number, required: true }) + @ApiBody({ description: 'Data for updating Wazzup chat provider', type: UpdateWazzupProviderDto }) + @ApiOkResponse({ description: 'Wazzup chat provider', type: WazzupProviderDto }) + @Patch(':providerId') + async update( + @CurrentAuth() { accountId }: AuthData, + @Param('providerId', ParseIntPipe) providerId: number, + @Body() dto: UpdateWazzupProviderDto, + ) { + return this.service.update(accountId, providerId, dto); + } + + @ApiOperation({ summary: 'Delete Wazzup chat provider', description: 'Delete Wazzup chat provider' }) + @ApiParam({ name: 'providerId', description: 'Wazzup chat provider ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Success', type: Boolean }) + @Delete(':providerId') + async delete( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('providerId', ParseIntPipe) providerId: number, + ): Promise { + await this.service.delete({ accountId, userId, providerId }); + + return true; + } +} diff --git a/backend/src/modules/multichat/providers/wazzup/dto/create-wazzup-provider.dto.ts b/backend/src/modules/multichat/providers/wazzup/dto/create-wazzup-provider.dto.ts new file mode 100644 index 0000000..e3ee7c3 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/dto/create-wazzup-provider.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsString } from 'class-validator'; + +import { CreateChatProviderDto } from '../../../chat-provider/dto'; +import { WazzupTransport } from '../enums'; + +export class CreateWazzupProviderDto extends CreateChatProviderDto { + @ApiProperty() + @IsString() + apiKey: string; + + @ApiProperty() + @IsString() + channelId: string; + + @ApiProperty() + @IsString() + plainId: string; + + @ApiProperty({ enum: WazzupTransport }) + @IsEnum(WazzupTransport) + channelTransport: WazzupTransport; +} diff --git a/backend/src/modules/multichat/providers/wazzup/dto/index.ts b/backend/src/modules/multichat/providers/wazzup/dto/index.ts new file mode 100644 index 0000000..e708f27 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-wazzup-provider.dto'; +export * from './update-wazzup-provider.dto'; +export * from './wazzup-channel.dto'; +export * from './wazzup-provider.dto'; diff --git a/backend/src/modules/multichat/providers/wazzup/dto/update-wazzup-provider.dto.ts b/backend/src/modules/multichat/providers/wazzup/dto/update-wazzup-provider.dto.ts new file mode 100644 index 0000000..c864d80 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/dto/update-wazzup-provider.dto.ts @@ -0,0 +1,3 @@ +import { UpdateChatProviderDto } from '../../../chat-provider/dto'; + +export class UpdateWazzupProviderDto extends UpdateChatProviderDto {} diff --git a/backend/src/modules/multichat/providers/wazzup/dto/wazzup-channel.dto.ts b/backend/src/modules/multichat/providers/wazzup/dto/wazzup-channel.dto.ts new file mode 100644 index 0000000..b31ea29 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/dto/wazzup-channel.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsString } from 'class-validator'; + +import { WazzupChannelState, WazzupTransport } from '../enums'; + +export class WazzupChannelDto { + @ApiProperty() + @IsString() + channelId: string; + + @ApiProperty({ enum: WazzupTransport }) + @IsEnum(WazzupTransport) + transport: WazzupTransport; + + @ApiProperty({ enum: WazzupChannelState }) + @IsEnum(WazzupChannelState) + state: WazzupChannelState; + + @ApiProperty() + @IsString() + plainId: string; + + @ApiProperty() + @IsString() + name: string; +} diff --git a/backend/src/modules/multichat/providers/wazzup/dto/wazzup-provider.dto.ts b/backend/src/modules/multichat/providers/wazzup/dto/wazzup-provider.dto.ts new file mode 100644 index 0000000..8cf5cd5 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/dto/wazzup-provider.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +import { ChatProviderDto } from '../../../chat-provider/dto'; + +export class WazzupProviderDto extends ChatProviderDto { + @ApiProperty({ description: 'Channel ID' }) + @IsString() + channelId: string; + + @ApiProperty({ description: 'Plain ID' }) + @IsString() + plainId: string; +} diff --git a/backend/src/modules/multichat/providers/wazzup/entities/chat-provider-wazzup.entity.ts b/backend/src/modules/multichat/providers/wazzup/entities/chat-provider-wazzup.entity.ts new file mode 100644 index 0000000..5ebb08a --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/entities/chat-provider-wazzup.entity.ts @@ -0,0 +1,60 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { ChatProvider } from '../../../chat-provider/entities'; + +import { WazzupTransport } from '../enums'; +import { WazzupProviderDto, CreateWazzupProviderDto } from '../dto'; + +@Entity() +export class ChatProviderWazzup { + @PrimaryColumn() + providerId: number; + + @Column() + apiKey: string; + + @Column() + channelId: string; + + @Column() + transport: WazzupTransport; + + @Column() + plainId: string; + + @Column() + accountId: number; + + _provider: ChatProvider; + + constructor( + accountId: number, + providerId: number, + apiKey: string, + channelId: string, + transport: WazzupTransport, + plainId: string, + ) { + this.accountId = accountId; + this.providerId = providerId; + this.apiKey = apiKey; + this.channelId = channelId; + this.transport = transport; + this.plainId = plainId; + } + + public get provider(): ChatProvider { + return this._provider; + } + public set provider(provider: ChatProvider) { + this._provider = provider; + } + + public toDto(): WazzupProviderDto { + return { ...this.provider.toDto(), channelId: this.channelId, plainId: this.plainId }; + } + + public static fromDto(accountId: number, providerId: number, dto: CreateWazzupProviderDto): ChatProviderWazzup { + return new ChatProviderWazzup(accountId, providerId, dto.apiKey, dto.channelId, dto.channelTransport, dto.plainId); + } +} diff --git a/backend/src/modules/multichat/providers/wazzup/entities/index.ts b/backend/src/modules/multichat/providers/wazzup/entities/index.ts new file mode 100644 index 0000000..6c30944 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/entities/index.ts @@ -0,0 +1 @@ +export * from './chat-provider-wazzup.entity'; diff --git a/backend/src/modules/multichat/providers/wazzup/enums/index.ts b/backend/src/modules/multichat/providers/wazzup/enums/index.ts new file mode 100644 index 0000000..92f24a1 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/enums/index.ts @@ -0,0 +1,5 @@ +export * from './wazzup-channel-state.enum'; +export * from './wazzup-chat-type.enum'; +export * from './wazzup-message-status.enum'; +export * from './wazzup-message-type.enum'; +export * from './wazzup-transport.enum'; diff --git a/backend/src/modules/multichat/providers/wazzup/enums/wazzup-channel-state.enum.ts b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-channel-state.enum.ts new file mode 100644 index 0000000..2e10614 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-channel-state.enum.ts @@ -0,0 +1,32 @@ +/** + * Enum for defining the states of a channel in the Wazzup system. + * Each state describes the current operational status or issues affecting the channel. + */ +export enum WazzupChannelState { + /** channel is active */ + active = 'active', + /** channel is starting */ + init = 'init', + /** the channel is turned off: it was removed from subscription or deleted with messages saved */ + disabled = 'disabled', + /** no connection to the phone */ + phoneUnavailable = 'phoneUnavailable', + /** QR code must be scanned */ + qridle = 'qridle', + /** the channel is authorized in another Wazzup account */ + openelsewhere = 'openelsewhere', + /** the channel is not pai */ + notEnoughMoney = 'notEnoughMoney', + /** channel QR was scanned by another phone number */ + foreignphone = 'foreignphone', + /** not authorized */ + unauthorized = 'unauthorized', + /** channel is waiting for a password for two-factor authentication */ + waitForPassword = 'waitForPassword', + /** the channel is blocked */ + blocked = 'blocked', + /** the WABA channel is in moderation */ + onModeration = 'onModeration', + /** the WABA channel is rejected */ + rejected = 'rejected', +} diff --git a/backend/src/modules/multichat/providers/wazzup/enums/wazzup-chat-type.enum.ts b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-chat-type.enum.ts new file mode 100644 index 0000000..0c696bb --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-chat-type.enum.ts @@ -0,0 +1,20 @@ +/** + * Enum representing different types of chat channels supported in the Wazzup system. + * Each member specifies a unique platform and type of chat interaction. + */ +export enum WazzupChatType { + /** WhatsApp individual chat. */ + Whatsapp = 'whatsapp', + /** WhatsApp group chat. */ + Whatsgroup = 'whatsgroup', + /** Instagram direct messages. */ + Instagram = 'instagram', + /** Telegram individual chat. */ + Telegram = 'telegram', + /** Telegram group chat. */ + Telegroup = 'telegroup', + /** Vkontakte (VK) messaging. */ + Vk = 'vk', + /** Avito messaging system. */ + Avito = 'avito', +} diff --git a/backend/src/modules/multichat/providers/wazzup/enums/wazzup-message-status.enum.ts b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-message-status.enum.ts new file mode 100644 index 0000000..ec06c65 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-message-status.enum.ts @@ -0,0 +1,20 @@ +/** + * Enum representing the status of messages as reported by webhooks in the Wazzup system. + * Each status correlates to a specific state in the message delivery process. + */ +export enum WazzupMessageStatus { + /** Message has been sent (indicated by one grey check mark). */ + Sent = 'sent', + + /** Message has been delivered to the recipient's device (indicated by two grey check marks). */ + Delivered = 'delivered', + + /** Message has been read by the recipient (indicated by two blue check marks). */ + Read = 'read', + + /** There was an error in sending the message. */ + Error = 'error', + + /** Incoming message from another user. */ + Inbound = 'inbound', +} diff --git a/backend/src/modules/multichat/providers/wazzup/enums/wazzup-message-type.enum.ts b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-message-type.enum.ts new file mode 100644 index 0000000..946711f --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-message-type.enum.ts @@ -0,0 +1,38 @@ +/** + * Enum representing the different types of messages that can be handled in the Wazzup system. + * Each member specifies the format or nature of the message content. + */ +export enum WazzupMessageType { + /** Text message. */ + Text = 'text', + + /** Image file. */ + Image = 'image', + + /** Audio file. */ + Audio = 'audio', + + /** Video file. */ + Video = 'video', + + /** Document file. */ + Document = 'document', + + /** Contact card (vCard format). */ + Vcard = 'vcard', + + /** Geolocation data. */ + Geo = 'geo', + + /** WhatsApp Business API template message. */ + WapiTemplate = 'wapi_template', + + /** Unsupported message type. */ + Unsupported = 'unsupported', + + /** Notification for a missed call. */ + MissingCall = 'missing_call', + + /** Message type that is not recognized or is erroneous. */ + Unknown = 'unknown', +} diff --git a/backend/src/modules/multichat/providers/wazzup/enums/wazzup-transport.enum.ts b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-transport.enum.ts new file mode 100644 index 0000000..c603e6a --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/enums/wazzup-transport.enum.ts @@ -0,0 +1,21 @@ +/** + * Enum for specifying the transport type for messages in the Wazzup system. + * Each member of the enum represents a different platform through which + * messages can be sent or received. + */ +export enum WazzupTransport { + /** WhatsApp channel */ + Whatsapp = 'whatsapp', + /** Instagram channel */ + Instagram = 'instagram', + /** Telegram channel */ + Tgapi = 'tgapi', + /** WABA channel */ + Wapi = 'wapi', + /** Telegram Bot channel */ + Telegram = 'telegram', + /** VK channel */ + Vk = 'vk', + /** Avito channel */ + Avito = 'avito', +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/index.ts b/backend/src/modules/multichat/providers/wazzup/types/index.ts new file mode 100644 index 0000000..bda3542 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/index.ts @@ -0,0 +1,8 @@ +export * from './wazzup-channel'; +export * from './wazzup-connect-request'; +export * from './wazzup-message-contact'; +export * from './wazzup-message-error'; +export * from './wazzup-message'; +export * from './wazzup-send-message-response'; +export * from './wazzup-send-message'; +export * from './wazzup-webhook-request'; diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-channel.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-channel.ts new file mode 100644 index 0000000..ec0351c --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-channel.ts @@ -0,0 +1,20 @@ +import { type WazzupChannelDto } from '../dto'; +import { type WazzupChannelState, type WazzupTransport } from '../enums'; + +export class WazzupChannel { + channelId: string; + transport: WazzupTransport; + state: WazzupChannelState; + plainId: string; + name: string; + + public toDto(): WazzupChannelDto { + return { + channelId: this.channelId, + transport: this.transport, + state: this.state, + plainId: this.plainId, + name: this.name, + }; + } +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-connect-request.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-connect-request.ts new file mode 100644 index 0000000..e50e6b3 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-connect-request.ts @@ -0,0 +1,13 @@ +export class WazzupConnectRequest { + state: string; + secret: string; + crmKey: string; + name: string; + + constructor({ state, secret, crmKey, name }: WazzupConnectRequest) { + this.state = state; + this.secret = secret; + this.crmKey = crmKey; + this.name = name; + } +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-message-contact.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-message-contact.ts new file mode 100644 index 0000000..9855102 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-message-contact.ts @@ -0,0 +1,6 @@ +export interface WazzupMessageContact { + name?: string; + avatarUri?: string; + username?: string; + phone?: string; +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-message-error.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-message-error.ts new file mode 100644 index 0000000..657f760 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-message-error.ts @@ -0,0 +1,4 @@ +export interface WazzupMessageError { + error?: string; + description?: string; +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-message.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-message.ts new file mode 100644 index 0000000..ac69420 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-message.ts @@ -0,0 +1,20 @@ +import { type WazzupChatType, type WazzupMessageStatus, type WazzupMessageType } from '../enums'; +import { type WazzupMessageContact } from './wazzup-message-contact'; +import { type WazzupMessageError } from './wazzup-message-error'; + +export class WazzupMessage { + messageId: string; + channelId: string; + chatType: WazzupChatType; + chatId: string; + dateTime: string; + type: WazzupMessageType; + status: WazzupMessageStatus; + error: WazzupMessageError; + text: string; + contentUri: string; + authorName: string; + isEcho: boolean; + contact: WazzupMessageContact; + avitoProfileId?: string; +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-send-message-response.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-send-message-response.ts new file mode 100644 index 0000000..c179f03 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-send-message-response.ts @@ -0,0 +1,4 @@ +export class WazzupSendMessageResponse { + messageId: string; + chatId: string; +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-send-message.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-send-message.ts new file mode 100644 index 0000000..f3da0d7 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-send-message.ts @@ -0,0 +1,23 @@ +export class WazzupSendMessage { + channelId: string; + chatType: string; + chatId?: string; + text?: string; + contentUri?: string; + refMessageId?: string; + /** Only for Telegram, for direct message, without @ */ + username?: string; + /** Only for Telegram for direct message, only numbers */ + phone?: string; + + constructor({ channelId, chatType, chatId, text, contentUri, refMessageId, username, phone }: WazzupSendMessage) { + this.channelId = channelId; + this.chatType = chatType; + this.chatId = chatId; + this.text = text; + this.contentUri = contentUri; + this.refMessageId = refMessageId; + this.username = username; + this.phone = phone; + } +} diff --git a/backend/src/modules/multichat/providers/wazzup/types/wazzup-webhook-request.ts b/backend/src/modules/multichat/providers/wazzup/types/wazzup-webhook-request.ts new file mode 100644 index 0000000..e1270c6 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/types/wazzup-webhook-request.ts @@ -0,0 +1,6 @@ +import { type WazzupMessage } from './wazzup-message'; + +export class WazzupWebhookRequest { + test?: boolean; + messages?: WazzupMessage[]; +} diff --git a/backend/src/modules/multichat/providers/wazzup/wazzup-provider.module.ts b/backend/src/modules/multichat/providers/wazzup/wazzup-provider.module.ts new file mode 100644 index 0000000..83b0e86 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/wazzup-provider.module.ts @@ -0,0 +1,31 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; + +import { ChatModule } from '../../chat/chat.module'; +import { ChatMessageModule } from '../../chat-message/chat-message.module'; +import { ChatProviderModule } from '../../chat-provider/chat-provider.module'; + +import wazzupConfig from './config/wazzup.config'; +import { ChatProviderWazzup } from './entities'; +import { WazzupProviderService } from './wazzup-provider.service'; +import { PublicWazzupProviderController, WazzupProviderController } from './controllers'; + +@Module({ + imports: [ + ConfigModule.forFeature(wazzupConfig), + TypeOrmModule.forFeature([ChatProviderWazzup]), + IAMModule, + StorageModule, + forwardRef(() => ChatModule), + forwardRef(() => ChatMessageModule), + ChatProviderModule, + ], + providers: [WazzupProviderService], + controllers: [WazzupProviderController, PublicWazzupProviderController], + exports: [WazzupProviderService], +}) +export class WazzupProviderModule {} diff --git a/backend/src/modules/multichat/providers/wazzup/wazzup-provider.service.ts b/backend/src/modules/multichat/providers/wazzup/wazzup-provider.service.ts new file mode 100644 index 0000000..b155e82 --- /dev/null +++ b/backend/src/modules/multichat/providers/wazzup/wazzup-provider.service.ts @@ -0,0 +1,444 @@ +import { HttpService } from '@nestjs/axios'; +import { Inject, Injectable, Logger, forwardRef } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { catchError, lastValueFrom } from 'rxjs'; +import { Repository } from 'typeorm'; + +import { splitByFirstSpace, TaskQueue, UrlGeneratorService } from '@/common'; +import { ApplicationConfig } from '@/config'; +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AccountService } from '@/modules/iam/account/account.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageUrlService } from '@/modules/storage/storage-url.service'; + +import { MultichatEventType } from '../../common'; +import { ChatUser } from '../../chat-user'; +import { ChatProvider } from '../../chat-provider/entities'; +import { ChatProviderService } from '../../chat-provider/services/chat-provider.service'; +import { Chat } from '../../chat/entities/chat.entity'; +import { ChatService } from '../../chat/services/chat.service'; +import { ChatMessage } from '../../chat-message/entities/chat-message.entity'; +import { ChatMessageService } from '../../chat-message/services/chat-message.service'; + +import { WazzupConfig } from './config'; +import { CreateWazzupProviderDto, UpdateWazzupProviderDto } from './dto'; +import { ChatProviderWazzup } from './entities'; +import { + WazzupChannel, + WazzupConnectRequest, + WazzupMessage, + WazzupSendMessage, + WazzupSendMessageResponse, + WazzupWebhookRequest, +} from './types'; +import { WazzupChatType, WazzupMessageStatus, WazzupTransport } from './enums'; + +const WazzupUrls = { + wazzup: 'https://api.wazzup24.com/v3', + webhooks: () => `${WazzupUrls.wazzup}/webhooks`, + channels: () => `${WazzupUrls.wazzup}/channels`, + message: () => `${WazzupUrls.wazzup}/message`, + connect: () => `${WazzupUrls.wazzup}/connect`, +} as const; + +const WEBHOOK_PATH = '/api/chat/wazzup/webhook'; + +const chatTypeMap: Record = { + [WazzupTransport.Avito]: WazzupChatType.Avito, + [WazzupTransport.Instagram]: WazzupChatType.Instagram, + [WazzupTransport.Telegram]: WazzupChatType.Telegram, + [WazzupTransport.Tgapi]: WazzupChatType.Telegram, + [WazzupTransport.Vk]: WazzupChatType.Vk, + [WazzupTransport.Wapi]: WazzupChatType.Whatsapp, + [WazzupTransport.Whatsapp]: WazzupChatType.Whatsapp, +}; + +const tgapiDirectPrefix = 'tgd_'; + +@Injectable() +export class WazzupProviderService { + private readonly logger = new Logger(WazzupProviderService.name); + private config: WazzupConfig; + private _appName: string; + private readonly queue = new TaskQueue(); + + constructor( + private readonly configService: ConfigService, + private readonly httpService: HttpService, + @InjectRepository(ChatProviderWazzup) + private readonly repository: Repository, + private readonly urlGenerator: UrlGeneratorService, + private readonly accountService: AccountService, + private readonly storageService: StorageService, + private readonly storageUrlService: StorageUrlService, + private readonly chatProviderService: ChatProviderService, + @Inject(forwardRef(() => ChatService)) + private readonly chatService: ChatService, + @Inject(forwardRef(() => ChatMessageService)) + private readonly chatMessageService: ChatMessageService, + ) { + this.config = this.configService.get('wazzup'); + this._appName = this.configService.get('application').name; + } + + async getApiKey(account: Account, state: string): Promise { + try { + const response$ = this.httpService + .post( + WazzupUrls.connect(), + new WazzupConnectRequest({ + state, + secret: this.config.secret, + crmKey: account.subdomain, + name: `${this._appName}-${account.subdomain}`, + }), + ) + .pipe( + catchError((error) => { + this.logger.error(`WAuth connect error`, (error as Error)?.stack); + throw error; + }), + ); + const response = await lastValueFrom(response$); + return response.data.data as string; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return null; + } + } + + async findChannels(accountId: number, apiKey: string): Promise { + try { + const response$ = this.httpService + .get(WazzupUrls.channels(), { headers: { Authorization: `Bearer ${apiKey}` } }) + .pipe( + catchError((error) => { + this.logger.error(`Get channels error for accountId=${accountId}`, (error as Error)?.stack); + throw error; + }), + ); + const response = await lastValueFrom(response$); + return response.data as WazzupChannel[]; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return []; + } + } + + async create(account: Account, userId: number, dto: CreateWazzupProviderDto): Promise { + if (!(await this.setWebhook(account, dto.apiKey))) { + throw new Error('Webhook not connected'); + } + + const provider = await this.chatProviderService.create(account.id, userId, dto); + + const wazzupProvider = await this.repository.save(ChatProviderWazzup.fromDto(account.id, provider.id, dto)); + wazzupProvider.provider = provider; + + return wazzupProvider; + } + + async findMany(accountId: number, user: User): Promise { + const allProviders = await this.repository + .createQueryBuilder('wp') + .where('wp.account_id = :accountId', { accountId }) + .getMany(); + + for (const provider of allProviders) { + provider.provider = await this.chatProviderService.findOne( + accountId, + null, + { providerId: provider.providerId }, + { expand: ['accessibleUsers', 'responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + } + + return user.isAdmin + ? allProviders + : allProviders.filter( + (p) => + !p.provider.accessibleUsers || + p.provider.accessibleUsers.length === 0 || + p.provider.accessibleUsers.some((u) => u.userId === user.id), + ); + } + + async findOne(accountId: number, providerId: number): Promise { + const provider = await this.chatProviderService.findOne( + accountId, + null, + { providerId }, + { expand: ['accessibleUsers', 'responsibleUsers', 'supervisorUsers', 'entitySettings'] }, + ); + + const wazzupProvider = await this.repository.findOneBy({ accountId, providerId }); + wazzupProvider.provider = provider; + + return wazzupProvider; + } + + async update(accountId: number, providerId: number, dto: UpdateWazzupProviderDto): Promise { + const provider = await this.chatProviderService.update(accountId, null, providerId, dto); + + const wazzupProvider = await this.repository.findOneBy({ accountId, providerId }); + + wazzupProvider.provider = provider; + + return wazzupProvider; + } + + async delete({ accountId, userId, providerId }: { accountId: number; userId: number; providerId: number }) { + await this.repository.delete({ accountId, providerId }); + await this.chatProviderService.delete({ accountId, userId, providerId }); + } + + async handleWebhook(body: unknown): Promise { + const request = body as WazzupWebhookRequest; + if (request.test) { + //webhook test + } + + if (request.messages) { + for (const message of request.messages) { + if (message.status === WazzupMessageStatus.Inbound) { + const wazzupProvider = await this.repository.findOneBy({ channelId: message.channelId }); + if (wazzupProvider) { + const account = await this.accountService.findOne({ accountId: wazzupProvider.accountId }); + if (this.config.enqueue) { + this.queue.enqueue(() => this.handleMessage({ account, providerId: wazzupProvider.providerId, message })); + } else { + await this.handleMessage({ account, providerId: wazzupProvider.providerId, message }); + } + } + } + } + } + + return 'ok'; + } + + private async handleMessage({ + account, + providerId, + message, + }: { + account: Account; + providerId: number; + message: WazzupMessage; + }) { + this.logger.debug(`Handle message: ${JSON.stringify(message)}`); + try { + const chatUser = await this.getChatUser({ accountId: account.id, providerId, message }); + if (chatUser) { + const fileIds = await this.getMessageFileIds(account.id, message.contentUri); + await this.chatMessageService.createExternal(account, chatUser, message.text ?? '', message.messageId, fileIds); + } + } catch (error) { + this.logger.error(`Handle message error`, (error as Error)?.stack); + } + } + + private async getChatUser({ + accountId, + providerId, + message, + }: { + accountId: number; + providerId: number; + message: WazzupMessage; + }): Promise { + const userExternalId = ( + message.contact?.username || + message.contact?.phone || + message.avitoProfileId || + message.authorName || + '' + ).trim(); + const userName = (message.contact.name || message.contact.username || message.authorName || '').trim(); + const [firstName, lastName] = splitByFirstSpace(userName); + + return this.chatProviderService.getChatUserExternal({ + accountId, + providerId, + chatExternalId: this.formatChatExternalId(message), + externalUserDto: { + externalId: userExternalId, + firstName, + lastName, + avatarUrl: message.contact.avatarUri, + phone: message.contact.phone, + }, + }); + } + + async notifyChatUsers( + account: Account, + provider: ChatProvider, + chat: Chat, + type: MultichatEventType, + message: ChatMessage, + users: ChatUser[], + ): Promise { + const wazzupProvider = await this.repository.findOneBy({ accountId: account.id, providerId: provider.id }); + wazzupProvider.provider = provider; + + switch (type) { + case MultichatEventType.ChatMessageCreated: + this.sendMessage(account, wazzupProvider, chat, message, users); + break; + } + } + + async createChatExternalId(accountId: number, providerId: number, userExternalId: string): Promise { + const provider = await this.repository.findOneBy({ accountId, providerId }); + const chatId = this.formatChatId({ provider, userExternalId }); + return this.formatChatExternalId({ chatType: chatTypeMap[provider.transport], chatId }); + } + + private async setWebhook(account: Account, apiKey: string): Promise { + const webhooksUri = this.urlGenerator.createUrl({ route: WEBHOOK_PATH, subdomain: account.subdomain }); + try { + const response$ = this.httpService.patch( + WazzupUrls.webhooks(), + { webhooksUri, subscriptions: { messagesAndStatuses: true } }, + { headers: { Authorization: `Bearer ${apiKey}` } }, + ); + const response = await lastValueFrom(response$); + return !!response.data; + } catch (e) { + this.logger.error(`Connect webhook error`, (e as Error)?.stack); + return false; + } + } + + private async sendMessage( + account: Account, + provider: ChatProviderWazzup, + chat: Chat, + message: ChatMessage, + users: ChatUser[], + ): Promise { + const fileUrls = + message.files && message.files.length > 0 + ? message.files.map((file) => this.storageUrlService.getTemporaryUrl(file.fileId, account.subdomain)) + : []; + + for (const user of users) { + if (user.externalUser) { + const { chatType, chatId, tgPhone } = this.parseChatExternalId(chat.externalId); + + if (message.text) { + try { + const response$ = this.httpService.post( + WazzupUrls.message(), + new WazzupSendMessage({ + channelId: provider.channelId, + chatType, + chatId, + phone: tgPhone, + text: message.text, + }), + { headers: { Authorization: `Bearer ${provider.apiKey}` } }, + ); + const response = await lastValueFrom(response$); + const data = response.data as WazzupSendMessageResponse; + if (tgPhone && data?.chatId) { + const externalId = this.formatChatExternalId({ chatType, chatId: data.chatId }); + const existingChat = await this.chatService.findOne({ + accountId: account.id, + filter: { providerId: provider.providerId, externalId }, + }); + if (existingChat) { + await this.chatService.mergeChat(account, chat.id, existingChat.id); + } else { + await this.chatService.updateExternalId(account.id, chat.id, { externalId }); + } + } + } catch (e) { + this.logger.error(`Send message error`, (e as Error)?.stack); + } + } + + for (const fileUrl of fileUrls) { + try { + const response$ = this.httpService.post( + WazzupUrls.message(), + new WazzupSendMessage({ channelId: provider.channelId, chatType, chatId, contentUri: fileUrl }), + { headers: { Authorization: `Bearer ${provider.apiKey}` } }, + ); + await lastValueFrom(response$); + } catch (e) { + this.logger.error(`Send file error`, (e as Error)?.stack); + } + } + } + } + } + + async sendDirectMessage({ + accountId, + providerId, + phone, + message, + }: { + accountId: number; + providerId: number; + phone: string; + message: string; + }): Promise { + this.logger.debug(`sendDirectMessage: ${JSON.stringify({ accountId, providerId, phone, message })}`); + const provider = await this.repository.findOneBy({ accountId, providerId }); + const phoneNumber = phone.startsWith('+') ? phone.slice(1) : phone; + const wazzupMessage = new WazzupSendMessage({ + channelId: provider.channelId, + chatType: chatTypeMap[provider.transport], + phone: phoneNumber, + text: message, + }); + this.logger.debug(`sendDirectMessage wazzupMessage: ${JSON.stringify(wazzupMessage)}`); + try { + const { data } = await lastValueFrom( + this.httpService.post(WazzupUrls.message(), wazzupMessage, { + headers: { Authorization: `Bearer ${provider.apiKey}` }, + }), + ); + this.logger.debug(`sendDirectMessage response data: ${JSON.stringify(data)}`); + } catch (e) { + this.logger.error(`Send message error`, (e as Error)?.stack); + } + } + + private async getMessageFileIds(accountId: number, contentUri: string | null): Promise { + if (!contentUri) { + return null; + } + + const fileInfo = await this.storageService.storeExternalFile(accountId, null, contentUri); + return fileInfo ? [fileInfo.id] : []; + } + + private formatChatId({ provider, userExternalId }: { provider: ChatProviderWazzup; userExternalId: string }) { + const externalId = userExternalId.startsWith('+') ? userExternalId.slice(1) : userExternalId; + return provider.transport === WazzupTransport.Tgapi ? `${tgapiDirectPrefix}${externalId}` : externalId; + } + + private formatChatExternalId({ chatType, chatId }: { chatType: WazzupChatType; chatId: string }): string { + return `${chatType}|${chatId}`; + } + private parseChatExternalId(externalId: string): { + chatType: WazzupChatType; + chatId: string | undefined; + tgPhone: string | undefined; + } { + const index = externalId.indexOf('|'); + const chatType = externalId.substring(0, index); + const rest = externalId.substring(index + 1); + + const tgPhone = rest.startsWith(tgapiDirectPrefix) ? rest.slice(tgapiDirectPrefix.length) : undefined; + const chatId = rest.startsWith(tgapiDirectPrefix) ? undefined : rest; + + return { chatType: chatType as WazzupChatType, chatId, tgPhone }; + } +} diff --git a/backend/src/modules/notification/common/events/index.ts b/backend/src/modules/notification/common/events/index.ts new file mode 100644 index 0000000..2693fe9 --- /dev/null +++ b/backend/src/modules/notification/common/events/index.ts @@ -0,0 +1,2 @@ +export * from './notification'; +export * from './notification-event-type.enum'; diff --git a/backend/src/modules/notification/common/events/notification-event-type.enum.ts b/backend/src/modules/notification/common/events/notification-event-type.enum.ts new file mode 100644 index 0000000..08ebed2 --- /dev/null +++ b/backend/src/modules/notification/common/events/notification-event-type.enum.ts @@ -0,0 +1,4 @@ +export enum NotificationEventType { + NOTIFICATION_CREATED = 'notification:new', + NOTIFICATION_UNSEEN = 'notification:unseen', +} diff --git a/backend/src/modules/notification/common/events/notification/index.ts b/backend/src/modules/notification/common/events/notification/index.ts new file mode 100644 index 0000000..257cafb --- /dev/null +++ b/backend/src/modules/notification/common/events/notification/index.ts @@ -0,0 +1 @@ +export * from './notification-unseen.event'; diff --git a/backend/src/modules/notification/common/events/notification/notification-unseen.event.ts b/backend/src/modules/notification/common/events/notification/notification-unseen.event.ts new file mode 100644 index 0000000..ab9cb44 --- /dev/null +++ b/backend/src/modules/notification/common/events/notification/notification-unseen.event.ts @@ -0,0 +1,11 @@ +export class NotificationUnseenEvent { + accountId: number; + userId: number; + unseenCount: number; + + constructor({ accountId, userId, unseenCount }: NotificationUnseenEvent) { + this.accountId = accountId; + this.userId = userId; + this.unseenCount = unseenCount; + } +} diff --git a/backend/src/modules/notification/common/index.ts b/backend/src/modules/notification/common/index.ts new file mode 100644 index 0000000..7981d6b --- /dev/null +++ b/backend/src/modules/notification/common/index.ts @@ -0,0 +1 @@ +export * from './events'; diff --git a/backend/src/modules/notification/notification-settings/dto/notification-settings.dto.ts b/backend/src/modules/notification/notification-settings/dto/notification-settings.dto.ts new file mode 100644 index 0000000..d1191c7 --- /dev/null +++ b/backend/src/modules/notification/notification-settings/dto/notification-settings.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { NotificationTypeSettingsDto } from './notification-type-settings.dto'; + +export class NotificationSettingsDto { + @ApiProperty() + enablePopup: boolean; + + @ApiProperty() + types: NotificationTypeSettingsDto[]; + + constructor(enablePopup: boolean, types: NotificationTypeSettingsDto[]) { + this.enablePopup = enablePopup; + this.types = types; + } +} diff --git a/backend/src/modules/notification/notification-settings/dto/notification-type-settings.dto.ts b/backend/src/modules/notification/notification-settings/dto/notification-type-settings.dto.ts new file mode 100644 index 0000000..75458c7 --- /dev/null +++ b/backend/src/modules/notification/notification-settings/dto/notification-type-settings.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { NotificationType } from '../../notification/enums'; + +export class NotificationTypeSettingsDto { + @ApiProperty() + type: NotificationType; + + @ApiProperty() + isEnabled: boolean; + + @ApiProperty({ nullable: true }) + objectId: number | null; + + @ApiProperty({ nullable: true }) + before: number | null; + + @ApiProperty({ nullable: true }) + followUserIds: number[] | null; + + constructor( + type: NotificationType, + isEnabled: boolean, + objectId: number | null, + before: number | null, + followUserIds: number[] | null, + ) { + this.type = type; + this.isEnabled = isEnabled; + this.objectId = objectId; + this.before = before; + this.followUserIds = followUserIds; + } +} diff --git a/backend/src/modules/notification/notification-settings/entities/notification-settings.entity.ts b/backend/src/modules/notification/notification-settings/entities/notification-settings.entity.ts new file mode 100644 index 0000000..7e7bf68 --- /dev/null +++ b/backend/src/modules/notification/notification-settings/entities/notification-settings.entity.ts @@ -0,0 +1,42 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { NotificationSettingsDto } from '../dto/notification-settings.dto'; +import { NotificationTypeSettingsDto } from '../dto/notification-type-settings.dto'; + +@Entity() +export class NotificationSettings { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + userId: number; + + @Column() + enablePopup: boolean; + + @Column() + accountId: number; + + constructor(accountId: number, userId: number, enablePopup: boolean) { + this.accountId = accountId; + this.userId = userId; + this.enablePopup = enablePopup; + } + + public static create(accountId: number, userId: number, dto: NotificationSettingsDto): NotificationSettings { + return new NotificationSettings(accountId, userId, dto.enablePopup); + } + + public update(dto: NotificationSettingsDto): NotificationSettings { + this.enablePopup = dto.enablePopup; + return this; + } + + public static getDefaultEnablePopup(): boolean { + return true; + } + + public toDto(types: NotificationTypeSettingsDto[]): NotificationSettingsDto { + return new NotificationSettingsDto(this.enablePopup, types); + } +} diff --git a/backend/src/modules/notification/notification-settings/entities/notification-type-follow-user.entity.ts b/backend/src/modules/notification/notification-settings/entities/notification-type-follow-user.entity.ts new file mode 100644 index 0000000..8a3c38e --- /dev/null +++ b/backend/src/modules/notification/notification-settings/entities/notification-type-follow-user.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class NotificationTypeFollowUser { + @PrimaryColumn() + typeId: number; + + @PrimaryColumn() + userId: number; + + @Column() + accountId: number; + + constructor(typeId: number, userId: number, accountId: number) { + this.typeId = typeId; + this.userId = userId; + this.accountId = accountId; + } +} diff --git a/backend/src/modules/notification/notification-settings/entities/notification-type-settings.entity.ts b/backend/src/modules/notification/notification-settings/entities/notification-type-settings.entity.ts new file mode 100644 index 0000000..d1c11f3 --- /dev/null +++ b/backend/src/modules/notification/notification-settings/entities/notification-type-settings.entity.ts @@ -0,0 +1,79 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { NotificationType } from '../../notification/enums'; +import { NotificationTypeSettingsDto } from '../dto/notification-type-settings.dto'; + +@Entity() +export class NotificationTypeSettings { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + settingsId: number; + + @Column() + type: NotificationType; + + @Column() + isEnabled: boolean; + + @Column({ nullable: true }) + objectId: number | null; + + @Column({ nullable: true }) + before: number | null; // in seconds + + @Column() + accountId: number; + + constructor( + accountId: number, + settingsId: number, + type: NotificationType, + isEnabled: boolean, + objectId: number | null, + before: number | null, + ) { + this.accountId = accountId; + this.settingsId = settingsId; + this.type = type; + this.isEnabled = isEnabled; + this.objectId = objectId; + this.before = before; + } + + public static fromDto( + accountId: number, + settingsId: number, + dto: NotificationTypeSettingsDto, + ): NotificationTypeSettings { + return new NotificationTypeSettings(accountId, settingsId, dto.type, dto.isEnabled, dto.objectId, dto.before); + } + + public static createDefault( + accountId: number, + settingsId: number, + type: NotificationType, + objectId: number | null = null, + ): NotificationTypeSettings { + return new NotificationTypeSettings( + accountId, + settingsId, + type, + NotificationTypeSettings.getDefaultEnabled(type), + objectId, + NotificationTypeSettings.getDefaultBefore(type), + ); + } + + public static getDefaultEnabled(type: NotificationType): boolean { + return !(type === NotificationType.ACTIVITY_OVERDUE_EMPLOYEE || type === NotificationType.TASK_OVERDUE_EMPLOYEE); + } + public static getDefaultBefore(type: NotificationType): number | null { + return type === NotificationType.ACTIVITY_BEFORE_START || type === NotificationType.TASK_BEFORE_START ? 3600 : null; + } + + public toDto(followUserIds: number[] | null): NotificationTypeSettingsDto { + return new NotificationTypeSettingsDto(this.type, this.isEnabled, this.objectId, this.before, followUserIds); + } +} diff --git a/backend/src/modules/notification/notification-settings/notification-settings.controller.ts b/backend/src/modules/notification/notification-settings/notification-settings.controller.ts new file mode 100644 index 0000000..bae09fb --- /dev/null +++ b/backend/src/modules/notification/notification-settings/notification-settings.controller.ts @@ -0,0 +1,31 @@ +import { Body, Controller, Get, Put } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { NotificationSettingsService } from './notification-settings.service'; +import { NotificationSettingsDto } from './dto/notification-settings.dto'; + +@ApiTags('notification') +@Controller('/notifications/settings') +@JwtAuthorized({ prefetch: { user: true } }) +export class NotificationSettingsController { + constructor(private readonly service: NotificationSettingsService) {} + + @ApiCreatedResponse({ description: 'Notification settings for user', type: NotificationSettingsDto }) + @Get() + public async getSettings(@CurrentAuth() { accountId, user }: AuthData): Promise { + return this.service.getSettings(accountId, user); + } + + @ApiCreatedResponse({ description: 'Notification settings for user', type: NotificationSettingsDto }) + @Put() + public async updateSettings( + @CurrentAuth() { accountId, user }: AuthData, + @Body() dto: NotificationSettingsDto, + ): Promise { + return this.service.updateSettings(accountId, user, dto); + } +} diff --git a/backend/src/modules/notification/notification-settings/notification-settings.service.ts b/backend/src/modules/notification/notification-settings/notification-settings.service.ts new file mode 100644 index 0000000..c80361d --- /dev/null +++ b/backend/src/modules/notification/notification-settings/notification-settings.service.ts @@ -0,0 +1,200 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { IamEventType, UserCreatedEvent } from '@/modules/iam/common'; +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { EntityTypeService } from '@/CRM/entity-type/entity-type.service'; + +import { NotificationType } from '../notification/enums'; + +import { NotificationSettings } from './entities/notification-settings.entity'; +import { NotificationTypeSettings } from './entities/notification-type-settings.entity'; +import { NotificationTypeFollowUser } from './entities/notification-type-follow-user.entity'; + +import { NotificationSettingsDto } from './dto/notification-settings.dto'; +import { NotificationTypeSettingsDto } from './dto/notification-type-settings.dto'; + +@Injectable() +export class NotificationSettingsService { + constructor( + @InjectRepository(NotificationSettings) + private readonly repositorySettings: Repository, + @InjectRepository(NotificationTypeSettings) + private readonly repositoryTypeSettings: Repository, + @InjectRepository(NotificationTypeFollowUser) + private readonly repositoryFollowUser: Repository, + private readonly userService: UserService, + @Inject(forwardRef(() => EntityTypeService)) + private readonly entityTypeService: EntityTypeService, + ) {} + + @OnEvent(IamEventType.UserCreated, { async: true }) + public async handleUserCreatedEvent(event: UserCreatedEvent) { + const user = await this.userService.findOne({ accountId: event.accountId, id: event.userId }); + await this.createDefaultSettings(event.accountId, user); + } + + public async createDefaultSettings(accountId: number, user: User) { + const settings = await this.repositorySettings.save(new NotificationSettings(accountId, user.id, true)); + const typeSettings: NotificationTypeSettings[] = []; + await this.createMissedTypes(accountId, user, settings.id, typeSettings); + return { settings, typeSettings }; + } + private async createMissedTypes( + accountId: number, + user: User, + settingsId: number, + typeSettings: NotificationTypeSettings[] = [], + ) { + for (const typeString in NotificationType) { + const type = NotificationType[typeString] as NotificationType; + if (type !== NotificationType.ENTITY_NEW && !typeSettings.find((t) => t.type === type)) { + typeSettings.push(await this.createDefaultTypeSettings(accountId, settingsId, type)); + } + } + + const entityTypes = await this.entityTypeService.getAccessibleForUser(accountId, user); + const deletedEntityTypes: number[] = []; + for (const typeSetting of typeSettings.filter((ts) => ts.type === NotificationType.ENTITY_NEW)) { + if (!entityTypes.some((et) => et.id === typeSetting.objectId)) { + deletedEntityTypes.push(typeSetting.id); + } + } + for (const id of deletedEntityTypes) { + const index = typeSettings.findIndex((ts) => ts.id === id); + if (index !== -1) { + typeSettings.splice(index, 1); + } + } + + for (const et of entityTypes) { + if (!typeSettings.find((t) => t.type === NotificationType.ENTITY_NEW && t.objectId === et.id)) { + typeSettings.push( + await this.createDefaultTypeSettings(accountId, settingsId, NotificationType.ENTITY_NEW, et.id), + ); + } + } + } + private async createDefaultTypeSettings( + accountId: number, + settingsId: number, + type: NotificationType, + objectId: number | null = null, + ): Promise { + return await this.repositoryTypeSettings.save( + NotificationTypeSettings.createDefault(accountId, settingsId, type, objectId), + ); + } + + public async getSettings(accountId: number, user: User): Promise { + const current = await this.repositorySettings.findOneBy({ accountId, userId: user.id }); + if (current) { + const typeSettings = await this.repositoryTypeSettings.find({ + where: { settingsId: current.id }, + order: { id: 'ASC' }, + }); + await this.createMissedTypes(accountId, user, current.id, typeSettings); + return current.toDto(await this.convertToTypeSettingsDto(typeSettings)); + } else { + const { settings, typeSettings } = await this.createDefaultSettings(accountId, user); + return settings.toDto(await this.convertToTypeSettingsDto(typeSettings)); + } + } + private async convertToTypeSettingsDto(typeSettings: NotificationTypeSettings[]) { + const typeSettingsDtos: NotificationTypeSettingsDto[] = []; + for (const ts of typeSettings) { + const followUsers = await this.repositoryFollowUser.findBy({ typeId: ts.id }); + typeSettingsDtos.push(ts.toDto(followUsers.map((fu) => fu.userId))); + } + return typeSettingsDtos; + } + + public async updateSettings( + accountId: number, + user: User, + dto: NotificationSettingsDto, + ): Promise { + let settings = await this.repositorySettings.findOneBy({ accountId, userId: user.id }); + if (settings) { + await this.repositorySettings.save(settings.update(dto)); + } else { + settings = await this.repositorySettings.save(NotificationSettings.create(accountId, user.id, dto)); + } + + await this.repositoryTypeSettings.delete({ settingsId: settings.id }); + for (const type of dto.types) { + const typeSettings = await this.repositoryTypeSettings.save( + NotificationTypeSettings.fromDto(accountId, settings.id, type), + ); + if (type.followUserIds && type.followUserIds.length > 0) { + await this.repositoryFollowUser.insert( + type.followUserIds.map((fu) => new NotificationTypeFollowUser(typeSettings.id, fu, accountId)), + ); + } + } + + return await this.getSettings(accountId, user); + } + + public async checkEnabled( + accountId: number, + userId: number, + type: NotificationType, + objectId: number | null = null, + ): Promise<{ isEnabled: boolean; enablePopup: boolean }> { + const settings = await this.repositorySettings.findOneBy({ accountId, userId }); + if (settings) { + const typeSettings = await this.repositoryTypeSettings.findOneBy({ + settingsId: settings.id, + type, + objectId: objectId ?? undefined, + }); + if (typeSettings) { + return { isEnabled: typeSettings.isEnabled, enablePopup: settings.enablePopup }; + } + } + return { + isEnabled: NotificationTypeSettings.getDefaultEnabled(type), + enablePopup: NotificationSettings.getDefaultEnablePopup(), + }; + } + + public async getNotificationSettingsWithBefore(type: NotificationType, offset: number, limit: number) { + const result: { userId: number; before: number }[] = await this.repositoryTypeSettings + .createQueryBuilder('nts') + .select('ns.user_id', 'userId') + .addSelect('nts.before', 'before') + .leftJoin('notification_settings', 'ns', 'nts.settings_id = ns.id') + .where('nts.type = :type', { type }) + .andWhere('nts.is_enabled = true') + .offset(offset) + .limit(limit) + .getRawMany(); + return result; + } + + public async getNotificationSettingsWithFollow(type: NotificationType, offset: number, limit: number) { + const result: { userId: number; typeId: number }[] = await this.repositoryTypeSettings + .createQueryBuilder('nts') + .select('ns.user_id', 'userId') + .addSelect('nts.id', 'typeId') + .leftJoin('notification_settings', 'ns', 'nts.settings_id = ns.id') + .where('nts.type = :type', { type }) + .andWhere('nts.is_enabled = true') + .offset(offset) + .limit(limit) + .getRawMany(); + return await Promise.all( + result.map(async (r) => { + return { + ...r, + followUserIds: (await this.repositoryFollowUser.findBy({ typeId: r.typeId })).map((f) => f.userId), + }; + }), + ); + } +} diff --git a/backend/src/modules/notification/notification.module.ts b/backend/src/modules/notification/notification.module.ts new file mode 100644 index 0000000..7c2d847 --- /dev/null +++ b/backend/src/modules/notification/notification.module.ts @@ -0,0 +1,35 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { Notification } from './notification/entities/notification.entity'; +import { NotificationService } from './notification/notification.service'; +import { NotificationEventHandler } from './notification/notification-event.handler'; +import { NotificationScheduler } from './notification/notification-scheduler'; +import { NotificationController } from './notification/notification.controller'; + +import { NotificationSettings } from './notification-settings/entities/notification-settings.entity'; +import { NotificationTypeSettings } from './notification-settings/entities/notification-type-settings.entity'; +import { NotificationTypeFollowUser } from './notification-settings/entities/notification-type-follow-user.entity'; +import { NotificationSettingsService } from './notification-settings/notification-settings.service'; +import { NotificationSettingsController } from './notification-settings/notification-settings.controller'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Notification, + NotificationSettings, + NotificationTypeSettings, + NotificationTypeFollowUser, + ]), + IAMModule, + CrmModule, + EntityInfoModule, + ], + providers: [NotificationService, NotificationScheduler, NotificationEventHandler, NotificationSettingsService], + controllers: [NotificationController, NotificationSettingsController], +}) +export class NotificationModule {} diff --git a/backend/src/modules/notification/notification/dto/create-notification.dto.ts b/backend/src/modules/notification/notification/dto/create-notification.dto.ts new file mode 100644 index 0000000..fce1ecc --- /dev/null +++ b/backend/src/modules/notification/notification/dto/create-notification.dto.ts @@ -0,0 +1,47 @@ +import { NotificationType } from '../enums'; + +export class CreateNotificationDto { + accountId: number; + + userId: number; + + type: NotificationType; + + objectId: number; + + entityId: number | null; + + fromUser: number | null; + + title: string | null; + + description: string | null; + + startsIn: number | null; + + constructor( + accountId: number, + userId: number, + type: NotificationType, + objectId: number, + entityId: number | null, + fromUser: number | null, + title: string | null, + description: string | null, + startsIn: number | null = null, + ) { + this.accountId = accountId; + this.userId = userId; + this.type = type; + this.objectId = objectId; + this.entityId = entityId; + this.fromUser = fromUser; + this.title = title; + this.description = description; + this.startsIn = startsIn; + } + + setStartsIn(startsIn: number | null) { + this.startsIn = startsIn; + } +} diff --git a/backend/src/modules/notification/notification/dto/index.ts b/backend/src/modules/notification/notification/dto/index.ts new file mode 100644 index 0000000..424aed5 --- /dev/null +++ b/backend/src/modules/notification/notification/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-notification.dto'; +export * from './notification.dto'; +export * from './notifications-result.dto'; diff --git a/backend/src/modules/notification/notification/dto/notification.dto.ts b/backend/src/modules/notification/notification/dto/notification.dto.ts new file mode 100644 index 0000000..73f647c --- /dev/null +++ b/backend/src/modules/notification/notification/dto/notification.dto.ts @@ -0,0 +1,66 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { NotificationType } from '../enums'; + +export class NotificationDto { + @ApiProperty() + id: number; + + @ApiProperty() + userId: number; + + @ApiProperty() + type: NotificationType; + + @ApiProperty() + objectId: number; + + @ApiProperty() + entityInfo: EntityInfoDto | null; + + @ApiProperty() + fromUser: number | null; + + @ApiProperty() + title: string | null; + + @ApiProperty() + description: string | null; + + @ApiProperty() + isSeen: boolean; + + @ApiProperty() + startsIn: number | null; + + @ApiProperty() + createdAt: string; + + constructor( + id: number, + userId: number, + type: NotificationType, + objectId: number, + entityInfo: EntityInfoDto | null, + fromUser: number | null, + title: string | null, + description: string, + isSeen: boolean, + startsIn: number | null, + createdAt: string, + ) { + this.id = id; + this.userId = userId; + this.type = type; + this.objectId = objectId; + this.entityInfo = entityInfo; + this.fromUser = fromUser; + this.title = title; + this.description = description; + this.isSeen = isSeen; + this.startsIn = startsIn; + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/notification/notification/dto/notifications-result.dto.ts b/backend/src/modules/notification/notification/dto/notifications-result.dto.ts new file mode 100644 index 0000000..35ccf60 --- /dev/null +++ b/backend/src/modules/notification/notification/dto/notifications-result.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { NotificationDto } from './notification.dto'; + +export class NotificationsResult { + @ApiProperty() + meta: PagingMeta; + + @ApiProperty() + notifications: NotificationDto[]; + + constructor(notifications: NotificationDto[], meta: PagingMeta) { + this.notifications = notifications; + this.meta = meta; + } +} diff --git a/backend/src/modules/notification/notification/entities/index.ts b/backend/src/modules/notification/notification/entities/index.ts new file mode 100644 index 0000000..b95a361 --- /dev/null +++ b/backend/src/modules/notification/notification/entities/index.ts @@ -0,0 +1 @@ +export * from './notification.entity'; diff --git a/backend/src/modules/notification/notification/entities/notification.entity.ts b/backend/src/modules/notification/notification/entities/notification.entity.ts new file mode 100644 index 0000000..d9d2210 --- /dev/null +++ b/backend/src/modules/notification/notification/entities/notification.entity.ts @@ -0,0 +1,104 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { CreateNotificationDto, NotificationDto } from '../dto'; +import { NotificationType } from '../enums'; + +@Entity() +export class Notification { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + userId: number; + + @Column() + type: NotificationType; + + @Column() + objectId: number; + + @Column({ nullable: true }) + entityId: number | null; + + @Column({ nullable: true }) + fromUser: number | null; + + @Column({ nullable: true }) + title: string | null; + + @Column({ nullable: true }) + description: string | null; + + @Column() + isSeen: boolean; + + @Column({ nullable: true }) + startsIn: number | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + userId: number, + type: NotificationType, + objectId: number, + entityId: number | null, + fromUser: number | null, + title: string | null, + description: string | null, + isSeen: boolean, + startsIn: number | null, + createdAt?: Date, + ) { + this.accountId = accountId; + this.userId = userId; + this.type = type; + this.objectId = objectId; + this.entityId = entityId; + this.fromUser = fromUser; + this.title = title; + this.description = description; + this.isSeen = isSeen; + this.startsIn = startsIn; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public static fromDto(dto: CreateNotificationDto) { + return new Notification( + dto.accountId, + dto.userId, + dto.type, + dto.objectId, + dto.entityId, + dto.fromUser, + dto.title, + dto.description, + false, + dto.startsIn, + ); + } + + public toDto(entityInfo: EntityInfoDto | null): NotificationDto { + return new NotificationDto( + this.id, + this.userId, + this.type, + this.objectId, + entityInfo, + this.fromUser, + this.title, + this.description, + this.isSeen, + this.startsIn, + this.createdAt.toISOString(), + ); + } +} diff --git a/backend/src/modules/notification/notification/enums/index.ts b/backend/src/modules/notification/notification/enums/index.ts new file mode 100644 index 0000000..34c7f9c --- /dev/null +++ b/backend/src/modules/notification/notification/enums/index.ts @@ -0,0 +1 @@ +export * from './notification-type.enum'; diff --git a/backend/src/modules/notification/notification/enums/notification-type.enum.ts b/backend/src/modules/notification/notification/enums/notification-type.enum.ts new file mode 100644 index 0000000..bca9fb0 --- /dev/null +++ b/backend/src/modules/notification/notification/enums/notification-type.enum.ts @@ -0,0 +1,17 @@ +export enum NotificationType { + TASK_NEW = 'task_new', + TASK_OVERDUE = 'task_overdue', + TASK_BEFORE_START = 'task_before_start', + TASK_OVERDUE_EMPLOYEE = 'task_overdue_employee', + ACTIVITY_NEW = 'activity_new', + ACTIVITY_OVERDUE = 'activity_overdue', + ACTIVITY_BEFORE_START = 'activity_before_start', + ACTIVITY_OVERDUE_EMPLOYEE = 'activity_overdue_employee', + TASK_COMMENT_NEW = 'task_comment_new', + CHAT_MESSAGE_NEW = 'chat_message_new', + MAIL_MESSAGE_NEW = 'mail_new', + ENTITY_NOTE_NEW = 'entity_note_new', + ENTITY_NEW = 'entity_new', + ENTITY_RESPONSIBLE_CHANGE = 'entity_responsible_change', + ENTITY_IMPORT_COMPLETED = 'entity_import_completed', +} diff --git a/backend/src/modules/notification/notification/notification-event.handler.ts b/backend/src/modules/notification/notification/notification-event.handler.ts new file mode 100644 index 0000000..ef32ce1 --- /dev/null +++ b/backend/src/modules/notification/notification/notification-event.handler.ts @@ -0,0 +1,170 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { convert } from 'html-to-text'; + +import { UserNotification } from '@/common'; +import { + ActivityCreatedEvent, + CrmEventType, + EntityCreatedEvent, + EntityImportEvent, + EntityOwnerChangedEvent, + NoteCreatedEvent, + TaskCommentCreatedEvent, + TaskCreatedEvent, +} from '@/CRM/common'; +import { MailEventType, MailMessageReceivedEvent } from '@/Mailing/common'; + +import { CreateNotificationDto } from './dto'; +import { NotificationType } from './enums'; +import { NotificationService } from './notification.service'; + +@Injectable() +export class NotificationEventHandler { + constructor(private readonly notificationService: NotificationService) {} + + @OnEvent(CrmEventType.ActivityCreated, { async: true }) + public async onActivityCreated(event: ActivityCreatedEvent) { + if (event.createdBy !== event.ownerId) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.ownerId, + NotificationType.ACTIVITY_NEW, + event.activityId, + event.entityId, + event.createdBy, + null, + convert(event.activityText), + ), + ); + } + } + + @OnEvent(CrmEventType.TaskCreated, { async: true }) + public async onTaskCreated(event: TaskCreatedEvent) { + if (event.createdBy !== event.ownerId) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.ownerId, + NotificationType.TASK_NEW, + event.taskId, + event.entityId, + event.createdBy, + event.taskTitle, + convert(event.taskText), + ), + ); + } + } + + @OnEvent(CrmEventType.TaskCommentCreated, { async: true }) + public async onTaskCommentCreated(event: TaskCommentCreatedEvent) { + if (event.createdBy !== event.ownerId) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.ownerId, + NotificationType.TASK_COMMENT_NEW, + event.taskId, + event.entityId, + event.createdBy, + event.taskTitle, + convert(event.taskComment), + ), + ); + } + } + + @OnEvent(CrmEventType.EntityCreated, { async: true }) + public async onEntityCreated(event: EntityCreatedEvent) { + if (event.userNotification === UserNotification.Suppressed) { + return; + } + if (event.userNotification === UserNotification.Forced || event.createdBy !== event.ownerId) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.ownerId, + NotificationType.ENTITY_NEW, + event.entityTypeId, + event.entityId, + event.createdBy, + event.entityName, + null, + ), + ); + } + } + + @OnEvent(CrmEventType.EntityOwnerChanged, { async: true }) + public async onEntityOwnerChanged(event: EntityOwnerChangedEvent) { + if (event.changedBy !== event.ownerId) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.ownerId, + NotificationType.ENTITY_RESPONSIBLE_CHANGE, + event.entityTypeId, + event.entityId, + event.changedBy, + event.entityName, + null, + ), + ); + } + } + + @OnEvent(CrmEventType.EntityImportCompleted, { async: true }) + public async onEntityImportCompleted(event: EntityImportEvent) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.userId, + NotificationType.ENTITY_IMPORT_COMPLETED, + event.entityTypeId, + null, + event.userId, + null, + `Import of '${event.fileName}' is completed. Created ${event.totalCount} ${event.entityTypeName}.`, + ), + ); + } + + @OnEvent(CrmEventType.NoteCreated, { async: true }) + public async onEntityNoteCreated(event: NoteCreatedEvent) { + if (event.createdBy !== event.ownerId) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.ownerId, + NotificationType.ENTITY_NOTE_NEW, + event.noteId, + event.entityId, + event.createdBy, + event.entityName, + convert(event.noteText), + ), + ); + } + } + + @OnEvent(MailEventType.MailMessageReceived, { async: true }) + public async onMailMessageReceived(event: MailMessageReceivedEvent) { + if (event.isInbox) { + await this.notificationService.create( + new CreateNotificationDto( + event.accountId, + event.ownerId, + NotificationType.MAIL_MESSAGE_NEW, + event.messageId, + event.entityId, + null, + event.messageSubject, + event.messageSnippet, + ), + ); + } + } +} diff --git a/backend/src/modules/notification/notification/notification-scheduler.ts b/backend/src/modules/notification/notification/notification-scheduler.ts new file mode 100644 index 0000000..401cd64 --- /dev/null +++ b/backend/src/modules/notification/notification/notification-scheduler.ts @@ -0,0 +1,172 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron, CronExpression } from '@nestjs/schedule'; + +import { DateUtil } from '@/common'; + +import { ActivityService } from '@/CRM/activity/activity.service'; +import { TaskService } from '@/CRM/task/task.service'; + +import { NotificationSettingsService } from '../notification-settings/notification-settings.service'; + +import { NotificationType } from './enums'; +import { NotificationService } from './notification.service'; + +const PROCESS_LIMIT = 100; + +@Injectable() +export class NotificationScheduler { + private readonly logger = new Logger(NotificationScheduler.name); + constructor( + private notificationService: NotificationService, + private taskService: TaskService, + private activityService: ActivityService, + private notificationSettingsService: NotificationSettingsService, + ) {} + + @Cron(CronExpression.EVERY_MINUTE) + public async notifyTaskOverdue() { + if (process.env.SCHEDULE_NOTIFICATION_TASK_OVERDUE_DISABLE === 'true') return; + this.logger.log('Before: Running task overdue notifications'); + const to = DateUtil.now(); + const from = DateUtil.sub(to, { minutes: 1 }); + + const notifications = await this.taskService.getOverdueNotifications(from, to); + if (notifications && notifications.length > 0) { + notifications.forEach((notification) => this.notificationService.create(notification)); + } + this.logger.log(`After: Running task overdue notifications. Processed: ${notifications.length}`); + } + + @Cron(CronExpression.EVERY_MINUTE) + public async notifyTaskBeforeStart() { + if (process.env.SCHEDULE_NOTIFICATION_TASK_BEFORE_START_DISABLE === 'true') return; + this.logger.log('Before: Running task before start notifications'); + const to = DateUtil.now(); + const from = DateUtil.sub(to, { minutes: 1 }); + let offset = 0; + let processed = false; + do { + const result = await this.notificationSettingsService.getNotificationSettingsWithBefore( + NotificationType.TASK_BEFORE_START, + offset, + PROCESS_LIMIT, + ); + result.forEach(async ({ userId, before }) => { + const currentFrom = DateUtil.add(from, { seconds: before }); + const currentTo = DateUtil.add(to, { seconds: before }); + const notifications = await this.taskService.getBeforeStartNotifications(userId, currentFrom, currentTo); + if (notifications && notifications.length > 0) { + notifications.forEach((notification) => { + notification.setStartsIn(before); + this.notificationService.create(notification); + }); + } + }); + offset += result.length; + processed = result.length >= PROCESS_LIMIT; + } while (processed); + this.logger.log(`After: Running task before start notifications. Processed: ${offset}`); + } + + @Cron(CronExpression.EVERY_MINUTE) + public async notifyTaskOverdueFollow() { + if (process.env.SCHEDULE_NOTIFICATION_TASK_OVERDUE_FOLLOW_DISABLE === 'true') return; + this.logger.log('Before: Running task overdue follow notifications'); + const to = DateUtil.now(); + const from = DateUtil.sub(to, { minutes: 1 }); + let offset = 0; + let processed = false; + do { + const result = await this.notificationSettingsService.getNotificationSettingsWithFollow( + NotificationType.TASK_OVERDUE_EMPLOYEE, + offset, + PROCESS_LIMIT, + ); + result.forEach(async ({ userId, followUserIds }) => { + const notifications = await this.taskService.getOverdueForFollowNotifications(userId, from, to, followUserIds); + if (notifications && notifications.length > 0) { + notifications.forEach((notification) => this.notificationService.create(notification)); + } + }); + offset += result.length; + processed = result.length >= PROCESS_LIMIT; + } while (processed); + this.logger.log(`After: Running task overdue follow notifications. Processed: ${offset}`); + } + + @Cron(CronExpression.EVERY_MINUTE) + public async notifyActivityOverdue() { + if (process.env.SCHEDULE_NOTIFICATION_ACTIVITY_OVERDUE_DISABLE === 'true') return; + this.logger.log('Before: Running activity overdue notifications'); + const to = DateUtil.now(); + const from = DateUtil.sub(to, { minutes: 1 }); + + const notifications = await this.activityService.getOverdueNotifications(from, to); + if (notifications && notifications.length > 0) { + notifications.forEach((notification) => this.notificationService.create(notification)); + } + this.logger.log(`After: Running activity overdue notifications. Processed: ${notifications.length}`); + } + + @Cron(CronExpression.EVERY_MINUTE) + public async notifyActivityBeforeStart() { + if (process.env.SCHEDULE_NOTIFICATION_ACTIVITY_BEFORE_START_DISABLE === 'true') return; + this.logger.log('Before: Running activity before start notifications'); + const to = DateUtil.now(); + const from = DateUtil.sub(to, { minutes: 1 }); + let offset = 0; + let processed = false; + do { + const result = await this.notificationSettingsService.getNotificationSettingsWithBefore( + NotificationType.ACTIVITY_BEFORE_START, + offset, + PROCESS_LIMIT, + ); + result.forEach(async ({ userId, before }) => { + const currentFrom = DateUtil.add(from, { seconds: before }); + const currentTo = DateUtil.add(to, { seconds: before }); + const notifications = await this.activityService.getBeforeStartNotifications(userId, currentFrom, currentTo); + if (notifications && notifications.length > 0) { + notifications.forEach((notification) => { + notification.setStartsIn(before); + this.notificationService.create(notification); + }); + } + }); + offset += result.length; + processed = result.length >= PROCESS_LIMIT; + } while (processed); + this.logger.log(`After: Running activity before start notifications. Processed: ${offset}`); + } + + @Cron(CronExpression.EVERY_MINUTE) + public async notifyActivityOverdueFollow() { + if (process.env.SCHEDULE_NOTIFICATION_ACTIVITY_OVERDUE_FOLLOW_DISABLE === 'true') return; + this.logger.log('Before: Running activity overdue follow notifications'); + const to = DateUtil.now(); + const from = DateUtil.sub(to, { minutes: 1 }); + let offset = 0; + let processed = false; + do { + const result = await this.notificationSettingsService.getNotificationSettingsWithFollow( + NotificationType.ACTIVITY_OVERDUE_EMPLOYEE, + offset, + PROCESS_LIMIT, + ); + result.forEach(async ({ userId, followUserIds }) => { + const notifications = await this.activityService.getOverdueForFollowNotifications( + userId, + from, + to, + followUserIds, + ); + if (notifications && notifications.length > 0) { + notifications.forEach((notification) => this.notificationService.create(notification)); + } + }); + offset += result.length; + processed = result.length >= PROCESS_LIMIT; + } while (processed); + this.logger.log(`After: Running activity overdue follow notifications. Processed: ${offset}`); + } +} diff --git a/backend/src/modules/notification/notification/notification.controller.ts b/backend/src/modules/notification/notification/notification.controller.ts new file mode 100644 index 0000000..4de14a1 --- /dev/null +++ b/backend/src/modules/notification/notification/notification.controller.ts @@ -0,0 +1,45 @@ +import { Controller, Get, Param, Put, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { AuthDataPrefetch } from '@/modules/iam/common/decorators/auth-data-prefetch.decorator'; + +import { NotificationsResult } from './dto'; +import { NotificationService } from './notification.service'; + +@ApiTags('notification') +@Controller('/notifications') +@JwtAuthorized() +export class NotificationController { + constructor(private readonly service: NotificationService) {} + + @AuthDataPrefetch({ user: true }) + @ApiCreatedResponse({ description: 'Notifications for user', type: NotificationsResult }) + @Get() + public async getNotifications( + @CurrentAuth() { accountId, user }: AuthData, + @Query() paging: PagingQuery, + ): Promise { + return this.service.getNotifications(accountId, user, paging); + } + + @ApiCreatedResponse({ description: 'Unread notifications count', type: Number }) + @Get('/unseen-count') + public async getUnseenCount(@CurrentAuth() { accountId, userId }: AuthData): Promise { + return this.service.getUnseenCount(accountId, userId); + } + + @Put('/:id/seen') + public async markSeenNotification(@CurrentAuth() { accountId, userId }: AuthData, @Param('id') id: number) { + return await this.service.markSeenNotification(accountId, userId, id); + } + + @Put('/seen') + public async markSeenAllNotifications(@CurrentAuth() { accountId, userId }: AuthData) { + return await this.service.markSeenAllNotifications(accountId, userId); + } +} diff --git a/backend/src/modules/notification/notification/notification.service.ts b/backend/src/modules/notification/notification/notification.service.ts new file mode 100644 index 0000000..d84088a --- /dev/null +++ b/backend/src/modules/notification/notification/notification.service.ts @@ -0,0 +1,94 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { PagingQuery, PagingMeta } from '@/common'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; + +import { NotificationEventType, NotificationUnseenEvent } from '../common'; + +import { NotificationSettingsService } from '../notification-settings/notification-settings.service'; + +import { CreateNotificationDto, NotificationsResult, NotificationDto } from './dto'; +import { Notification } from './entities'; +import { NotificationType } from './enums'; + +@Injectable() +export class NotificationService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Notification) + private readonly repository: Repository, + private readonly notificationSettingsService: NotificationSettingsService, + private readonly entityInfoService: EntityInfoService, + private readonly userService: UserService, + ) {} + + public async create(dto: CreateNotificationDto) { + const { isEnabled, enablePopup } = await this.notificationSettingsService.checkEnabled( + dto.accountId, + dto.userId, + dto.type, + dto.type === NotificationType.ENTITY_NEW ? dto.objectId : null, + ); + + if (isEnabled) { + const notification = await this.repository.save(Notification.fromDto(dto)); + if (enablePopup) { + const user = await this.userService.findOne({ accountId: dto.accountId, id: dto.userId }); + const event = await this.convertToDto(notification, user); + this.eventEmitter.emit(NotificationEventType.NOTIFICATION_CREATED, event); + } + } + + const unseenCount = await this.getUnseenCount(dto.accountId, dto.userId); + this.eventEmitter.emit( + NotificationEventType.NOTIFICATION_UNSEEN, + new NotificationUnseenEvent({ accountId: dto.accountId, userId: dto.userId, unseenCount }), + ); + } + + public async getNotifications(accountId: number, user: User, paging: PagingQuery): Promise { + const [notifications, total] = await this.repository.findAndCount({ + where: { accountId, userId: user.id }, + take: paging.take, + skip: paging.skip, + order: { createdAt: 'DESC', id: 'DESC' }, + }); + + const notificationDtos: NotificationDto[] = []; + for (const notification of notifications) { + notificationDtos.push(await this.convertToDto(notification, user)); + } + + return new NotificationsResult(notificationDtos, new PagingMeta(paging.skip + paging.take, total)); + } + + private async convertToDto(notification: Notification, user: User): Promise { + const entityInfo = notification.entityId + ? await this.entityInfoService.findOne({ + accountId: notification.accountId, + user, + entityId: notification.entityId, + }) + : null; + + return notification.toDto(entityInfo); + } + + public async getUnseenCount(accountId: number, userId: number): Promise { + return await this.repository.countBy({ accountId, userId, isSeen: false }); + } + + public async markSeenNotification(accountId: number, userId: number, id: number) { + await this.repository.update({ accountId, userId, id }, { isSeen: true }); + } + + public async markSeenAllNotifications(accountId: number, userId: number) { + await this.repository.update({ accountId, userId }, { isSeen: true }); + } +} diff --git a/backend/src/modules/partner/dto/index.ts b/backend/src/modules/partner/dto/index.ts new file mode 100644 index 0000000..15f1104 --- /dev/null +++ b/backend/src/modules/partner/dto/index.ts @@ -0,0 +1,3 @@ +export * from './partner-lead.dto'; +export * from './partner-login.dto'; +export * from './partner-summary.dto'; diff --git a/backend/src/modules/partner/dto/partner-lead.dto.ts b/backend/src/modules/partner/dto/partner-lead.dto.ts new file mode 100644 index 0000000..6f2eb12 --- /dev/null +++ b/backend/src/modules/partner/dto/partner-lead.dto.ts @@ -0,0 +1,51 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +export class PartnerLeadDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsString() + registrationDate: string; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + paymentDate: string | null; + + @ApiProperty() + @IsNumber() + paymentAmount: number; + + @ApiProperty() + @IsNumber() + partnerBonus: number; + + @ApiProperty() + @IsNumber() + isPaidToPartner: boolean; + + constructor({ + id, + name, + registrationDate, + paymentDate, + paymentAmount, + partnerBonus, + isPaidToPartner, + }: PartnerLeadDto) { + this.id = id; + this.name = name; + this.registrationDate = registrationDate; + this.paymentDate = paymentDate; + this.paymentAmount = paymentAmount; + this.partnerBonus = partnerBonus; + this.isPaidToPartner = isPaidToPartner; + } +} diff --git a/backend/src/modules/partner/dto/partner-login.dto.ts b/backend/src/modules/partner/dto/partner-login.dto.ts new file mode 100644 index 0000000..6582a4a --- /dev/null +++ b/backend/src/modules/partner/dto/partner-login.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class PartnerLoginDto { + @ApiProperty() + @IsString() + email: string; + + @ApiProperty() + @IsNotEmpty() + password: string; +} diff --git a/backend/src/modules/partner/dto/partner-summary.dto.ts b/backend/src/modules/partner/dto/partner-summary.dto.ts new file mode 100644 index 0000000..e609fc0 --- /dev/null +++ b/backend/src/modules/partner/dto/partner-summary.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class PartnerSummaryDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsNumber() + registrationsCount: number; + + @ApiProperty() + @IsNumber() + payingLeadsCount: number; + + @ApiProperty() + @IsNumber() + totalPayments: number; + + @ApiProperty() + @IsNumber() + totalPartnerBonus: number; + + constructor({ id, name, registrationsCount, payingLeadsCount, totalPayments, totalPartnerBonus }: PartnerSummaryDto) { + this.id = id; + this.name = name; + this.registrationsCount = registrationsCount; + this.payingLeadsCount = payingLeadsCount; + this.totalPayments = totalPayments; + this.totalPartnerBonus = totalPartnerBonus; + } +} diff --git a/backend/src/modules/partner/partner.controller.ts b/backend/src/modules/partner/partner.controller.ts new file mode 100644 index 0000000..70a4569 --- /dev/null +++ b/backend/src/modules/partner/partner.controller.ts @@ -0,0 +1,47 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Post } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { Subdomain, TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { UserDto } from '@/modules/iam/user/dto/user.dto'; +import { JwtToken } from '@/modules/iam/authentication/dto/jwt-token'; + +import { PartnerLeadDto, PartnerLoginDto, PartnerSummaryDto } from './dto'; +import { PartnerService } from './partner.service'; + +@ApiTags('partners') +@Controller('partners') +@TransformToDto() +export class PartnerController { + constructor(private readonly service: PartnerService) {} + + @ApiOkResponse({ description: 'Jwt token to auth', type: JwtToken }) + @Post('login') + public async login(@Subdomain() subdomain: string | null, @Body() dto: PartnerLoginDto): Promise { + return this.service.login(subdomain, dto.email, dto.password); + } + + @ApiOkResponse({ description: 'Partner leads', type: [UserDto] }) + @JwtAuthorized() + @Get(':partnerId/user') + public async getUser(@CurrentAuth() { accountId }: AuthData, @Param('partnerId', ParseIntPipe) partnerId: number) { + return this.service.getUser(accountId, partnerId); + } + + @ApiOkResponse({ description: 'Partner leads', type: [PartnerLeadDto] }) + @JwtAuthorized() + @Get(':partnerId/leads') + public async getLeads(@CurrentAuth() { accountId }: AuthData, @Param('partnerId', ParseIntPipe) partnerId: number) { + return this.service.getLeads(accountId, partnerId); + } + + @ApiOkResponse({ description: 'Partner summary', type: PartnerSummaryDto }) + @JwtAuthorized() + @Get(':partnerId/summary') + public async getSummary(@CurrentAuth() { accountId }: AuthData, @Param('partnerId', ParseIntPipe) partnerId: number) { + return this.service.getSummary(accountId, partnerId); + } +} diff --git a/backend/src/modules/partner/partner.module.ts b/backend/src/modules/partner/partner.module.ts new file mode 100644 index 0000000..813aa2f --- /dev/null +++ b/backend/src/modules/partner/partner.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityFieldModule } from '@/modules/entity/entity-field/entity-field.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { PartnerService } from './partner.service'; +import { PartnerController } from './partner.controller'; + +@Module({ + imports: [IAMModule, EntityFieldModule, CrmModule], + providers: [PartnerService], + controllers: [PartnerController], + exports: [PartnerService], +}) +export class PartnerModule {} diff --git a/backend/src/modules/partner/partner.service.ts b/backend/src/modules/partner/partner.service.ts new file mode 100644 index 0000000..3d50307 --- /dev/null +++ b/backend/src/modules/partner/partner.service.ts @@ -0,0 +1,228 @@ +import { Injectable } from '@nestjs/common'; + +import { DateUtil } from '@/common'; + +import { AccountService } from '@/modules/iam/account/account.service'; +import { AuthenticationService } from '@/modules/iam/authentication/authentication.service'; +import { UserDto } from '@/modules/iam/user/dto/user.dto'; +import { UserRole } from '@/modules/iam/common/enums/user-role.enum'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { Field } from '@/modules/entity/entity-field/field/entities/field.entity'; +import { FieldService } from '@/modules/entity/entity-field/field/field.service'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value/field-value.service'; +import { EntityCategory } from '@/CRM/common'; +import { Entity } from '@/CRM/Model/Entity/Entity'; +import { EntityTypeService } from '@/CRM/entity-type/entity-type.service'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { EntityLinkService } from '@/CRM/entity-link/entity-link.service'; +import { EntityTypeLinkService } from '@/CRM/entity-type-link/entity-type-link.service'; + +import { Partner, PartnerLead, PartnerSummary } from './types'; + +const PartnerEntityFields = { + Password: 'Password', + Ref: 'ref', + Commission: 'Commission %', +}; + +const PartnerDealFields = { + DateOfPayment: 'Date of payment', + PaidToPartner: 'Paid to partner', +}; + +@Injectable() +export class PartnerService { + constructor( + private readonly accountService: AccountService, + private readonly authService: AuthenticationService, + private readonly entityTypeService: EntityTypeService, + private readonly entityTypeLinkService: EntityTypeLinkService, + private readonly entityService: EntityService, + private readonly entityLinkService: EntityLinkService, + private readonly fieldService: FieldService, + private readonly fieldValueService: FieldValueService, + ) {} + + public async login(subdomain: string, email: string, password: string) { + const account = await this.accountService.findOne({ subdomain }); + + if (account) { + const partner = await this.findByFieldValue(account.id, { type: FieldType.Email }, email); + + if (partner && partner.password === password) { + return this.authService.createJwtToken({ + accountId: account.id, + subdomain: account.subdomain, + userId: partner.id, + isPartner: true, + }); + } + } + + return null; + } + + public async getUser(accountId: number, partnerId: number): Promise { + const partner = await this.createPartner(accountId, partnerId); + + return partner + ? { + id: partner.id, + firstName: partner.name, + lastName: null, + email: partner.email, + phone: partner.phone, + role: UserRole.PARTNER, + isActive: true, + isPlatformAdmin: false, + } + : null; + } + + public async linkLeadWithPartner(accountId: number, entityId: number, ref: string): Promise { + const partner = await this.findByFieldValue( + accountId, + { type: FieldType.Text, name: PartnerEntityFields.Ref }, + ref, + ); + if (partner) { + await this.entityLinkService.create({ accountId, sourceId: partner.id, targetId: entityId }); + } + } + + public async getLeads(accountId: number, partnerId: number): Promise { + const leads: PartnerLead[] = []; + + const entity = await this.entityService.findOne(accountId, { entityId: partnerId }); + const partner = await this.createPartner(accountId, entity); + if (partner) { + const entityTypeLinks = await this.entityTypeLinkService.findMany({ accountId, sourceId: entity.entityTypeId }); + for (const entityTypeLink of entityTypeLinks) { + const entityType = await this.entityTypeService.findOne(accountId, { id: entityTypeLink.targetId }); + if (entityType?.entityCategory === EntityCategory.DEAL) { + const dealFields = await this.fieldService.findMany({ accountId, entityTypeId: entityType.id }); + const entityLinks = await this.entityLinkService.findMany({ accountId, sourceId: partner.id }); + for (const entityLink of entityLinks) { + const entity = await this.entityService.findOne(accountId, { entityId: entityLink.targetId }); + if (entity.entityTypeId === entityType.id) { + leads.push(await this.createLead(accountId, entity, dealFields, partner.commission)); + } + } + } + } + } + + return leads.sort((l1, l2) => DateUtil.sort(l1.registrationDate, l2.registrationDate)); + } + + public async getSummary(accountId: number, partnerId: number): Promise { + const partner = await this.createPartner(accountId, partnerId); + if (!partner) { + return null; + } + + const leads = await this.getLeads(accountId, partnerId); + + const payingLeads = leads.filter((lead) => lead.paymentDate !== null); + const totalPayments = payingLeads + .map((lead) => lead.paymentAmount) + .reduce((total: number, currentValue: number) => total + currentValue, 0); + const totalPartnerBonus = payingLeads + .map((lead) => lead.partnerBonus) + .reduce((total: number, currentValue: number) => total + currentValue, 0); + + return new PartnerSummary( + partnerId, + partner.name, + leads.length, + payingLeads.length, + totalPayments, + totalPartnerBonus, + ); + } + + private async findByFieldValue( + accountId: number, + { type, name }: { type: FieldType; name?: string }, + value: string, + ): Promise { + const entityTypes = await this.entityTypeService.findMany(accountId, { category: EntityCategory.PARTNER }); + for (const entityType of entityTypes) { + const refField = await this.fieldService.findOne({ accountId, entityTypeId: entityType.id, type, name }); + if (refField) { + const refFieldValue = await this.fieldValueService.findOne({ accountId, fieldId: refField.id, value }); + if (refFieldValue) { + return this.createPartner(accountId, refFieldValue.entityId); + } + } + } + return null; + } + + private async createPartner(accountId: number, entityOrId: Entity | number): Promise { + const entity = + entityOrId instanceof Entity ? entityOrId : await this.entityService.findOne(accountId, { entityId: entityOrId }); + if (!entity) { + return null; + } + + const fields = await this.fieldService.findMany({ accountId, entityTypeId: entity.entityTypeId }); + const fieldValues = await this.fieldValueService.findMany({ accountId, entityId: entity.id }); + + const emailField = fields.find((f) => f.type === FieldType.Email); + const phoneField = fields.find((f) => f.type === FieldType.Phone); + const passwordField = fields.find((f) => f.name.toLowerCase() === PartnerEntityFields.Password.toLowerCase()); + const refField = fields.find((f) => f.name.toLowerCase() === PartnerEntityFields.Ref.toLowerCase()); + const commissionField = fields.find((f) => f.name.toLowerCase() === PartnerEntityFields.Commission.toLowerCase()); + + const emailFieldValue = emailField ? fieldValues.find((fv) => fv.fieldId === emailField.id) : null; + const phoneFieldValue = phoneField ? fieldValues.find((fv) => fv.fieldId === phoneField.id) : null; + const passwordFieldValue = passwordField ? fieldValues.find((fv) => fv.fieldId === passwordField.id) : null; + const refFieldValue = refField ? fieldValues.find((fv) => fv.fieldId === refField.id) : null; + const commissionFieldValue = commissionField ? fieldValues.find((fv) => fv.fieldId === commissionField.id) : null; + + const emails = emailFieldValue?.getValue(); + const phones = phoneFieldValue?.getValue(); + const password = passwordFieldValue?.getValue(); + const ref = refFieldValue?.getValue(); + const commission = commissionFieldValue?.getValue() ?? 0; + + return emails && password + ? new Partner(entity.id, entity.name, emails?.[0], phones?.[0], password, ref, commission) + : null; + } + + private async createLead( + accountId: number, + entity: Entity, + fields: Field[], + commission: number, + ): Promise { + const fieldValues = await this.fieldValueService.findMany({ accountId, entityId: entity.id }); + + const paymentAmountField = fields.find((f) => f.type === FieldType.Value); + const paymentDateField = fields.find((f) => f.name.toLowerCase() === PartnerDealFields.DateOfPayment.toLowerCase()); + const paidToPartnerField = fields.find( + (f) => f.name.toLowerCase() === PartnerDealFields.PaidToPartner.toLowerCase(), + ); + + const paymentAmountFV = paymentAmountField ? fieldValues.find((fv) => fv.fieldId === paymentAmountField.id) : null; + const paymentDateFV = paymentDateField ? fieldValues.find((fv) => fv.fieldId === paymentDateField.id) : null; + const paidToPartnerFV = paidToPartnerField ? fieldValues.find((fv) => fv.fieldId === paidToPartnerField.id) : null; + + const paymentAmount = paymentAmountFV ? paymentAmountFV.getValue() : 0; + const paymentDate = paymentDateFV ? DateUtil.fromISOString(paymentDateFV.getValue()) : null; + const paidToPartner = paidToPartnerFV ? paidToPartnerFV.getValue() : false; + const partnerBonus = (paymentAmount / 100) * commission; + + return new PartnerLead( + entity.id, + entity.name, + entity.createdAt, + paymentDate, + paymentAmount, + partnerBonus, + paidToPartner, + ); + } +} diff --git a/backend/src/modules/partner/types/index.ts b/backend/src/modules/partner/types/index.ts new file mode 100644 index 0000000..c4252f7 --- /dev/null +++ b/backend/src/modules/partner/types/index.ts @@ -0,0 +1,3 @@ +export * from './partner-lead'; +export * from './partner-summary'; +export * from './partner'; diff --git a/backend/src/modules/partner/types/partner-lead.ts b/backend/src/modules/partner/types/partner-lead.ts new file mode 100644 index 0000000..f1aeccf --- /dev/null +++ b/backend/src/modules/partner/types/partner-lead.ts @@ -0,0 +1,37 @@ +import { PartnerLeadDto } from '../dto'; + +export class PartnerLead { + id: number; + name: string; + registrationDate: Date; + paymentDate: Date | null; + paymentAmount: number | null; + partnerBonus: number | null; + isPaidToPartner: boolean | null; + + constructor( + id: number, + name: string, + registrationDate: Date, + paymentDate: Date | null, + paymentAmount: number | null, + partnerBonus: number | null, + isPaidToPartner: boolean | null, + ) { + this.id = id; + this.name = name; + this.registrationDate = registrationDate; + this.paymentDate = paymentDate; + this.paymentAmount = paymentAmount; + this.partnerBonus = partnerBonus; + this.isPaidToPartner = isPaidToPartner; + } + + public toDto(): PartnerLeadDto { + return new PartnerLeadDto({ + ...this, + paymentDate: this.paymentDate?.toISOString(), + registrationDate: this.registrationDate?.toISOString(), + }); + } +} diff --git a/backend/src/modules/partner/types/partner-summary.ts b/backend/src/modules/partner/types/partner-summary.ts new file mode 100644 index 0000000..96cf8f3 --- /dev/null +++ b/backend/src/modules/partner/types/partner-summary.ts @@ -0,0 +1,30 @@ +import { PartnerSummaryDto } from '../dto'; + +export class PartnerSummary { + id: number; + name: string; + registrationsCount: number; + payingLeadsCount: number; + totalPayments: number; + totalPartnerBonus: number; + + constructor( + id: number, + name: string, + registrationsCount: number, + payingLeadsCount: number, + totalPayments: number, + totalPartnerBonus: number, + ) { + this.id = id; + this.name = name; + this.registrationsCount = registrationsCount; + this.payingLeadsCount = payingLeadsCount; + this.totalPayments = totalPayments; + this.totalPartnerBonus = totalPartnerBonus; + } + + public toDto(): PartnerSummaryDto { + return new PartnerSummaryDto(this); + } +} diff --git a/backend/src/modules/partner/types/partner.ts b/backend/src/modules/partner/types/partner.ts new file mode 100644 index 0000000..5bb74d3 --- /dev/null +++ b/backend/src/modules/partner/types/partner.ts @@ -0,0 +1,27 @@ +export class Partner { + id: number; + name: string; + email: string; + phone: string | null; + password: string; + ref: string | null; + commission: number; + + constructor( + id: number, + name: string, + email: string, + phone: string | null, + password: string, + ref: string, + commission: number, + ) { + this.id = id; + this.name = name; + this.email = email; + this.phone = phone; + this.password = password; + this.ref = ref; + this.commission = commission; + } +} diff --git a/backend/src/modules/scheduler/common/enums/index.ts b/backend/src/modules/scheduler/common/enums/index.ts new file mode 100644 index 0000000..74dba74 --- /dev/null +++ b/backend/src/modules/scheduler/common/enums/index.ts @@ -0,0 +1,2 @@ +export * from './permission-object-type.enum'; +export * from './schedule-appointment-status.enum'; diff --git a/backend/src/modules/scheduler/common/enums/permission-object-type.enum.ts b/backend/src/modules/scheduler/common/enums/permission-object-type.enum.ts new file mode 100644 index 0000000..bef09d3 --- /dev/null +++ b/backend/src/modules/scheduler/common/enums/permission-object-type.enum.ts @@ -0,0 +1,3 @@ +export enum PermissionObjectType { + Schedule = 'schedule', +} diff --git a/backend/src/modules/scheduler/common/enums/schedule-appointment-status.enum.ts b/backend/src/modules/scheduler/common/enums/schedule-appointment-status.enum.ts new file mode 100644 index 0000000..84823a0 --- /dev/null +++ b/backend/src/modules/scheduler/common/enums/schedule-appointment-status.enum.ts @@ -0,0 +1,6 @@ +export enum ScheduleAppointmentStatus { + NotConfirmed = 'not_confirmed', + Confirmed = 'confirmed', + Completed = 'completed', + Canceled = 'canceled', +} diff --git a/backend/src/modules/scheduler/common/events/index.ts b/backend/src/modules/scheduler/common/events/index.ts new file mode 100644 index 0000000..1e9e0f8 --- /dev/null +++ b/backend/src/modules/scheduler/common/events/index.ts @@ -0,0 +1,4 @@ +export * from './schedule'; +export * from './schedule-appointment'; +export * from './schedule-performer'; +export * from './scheduler-event-type.enum'; diff --git a/backend/src/modules/scheduler/common/events/schedule-appointment/index.ts b/backend/src/modules/scheduler/common/events/schedule-appointment/index.ts new file mode 100644 index 0000000..27d77d9 --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-appointment/index.ts @@ -0,0 +1,5 @@ +export * from './schedule-appointment-created.event'; +export * from './schedule-appointment-ext-upsert.event'; +export * from './schedule-appointment-ext.event'; +export * from './schedule-appointment-updated.event'; +export * from './schedule-appointment.event'; diff --git a/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-created.event.ts b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-created.event.ts new file mode 100644 index 0000000..6ffb85d --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-created.event.ts @@ -0,0 +1,20 @@ +import { ScheduleAppointmentStatus } from '../../enums'; +import { SchedulerAppointmentEvent } from './schedule-appointment.event'; + +export class SchedulerAppointmentCreatedEvent extends SchedulerAppointmentEvent { + title: string; + comment: string; + startDate: Date; + endDate: Date; + status: ScheduleAppointmentStatus; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.title = data.title; + this.comment = data.comment; + this.startDate = data.startDate; + this.endDate = data.endDate; + this.status = data.status; + } +} diff --git a/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-ext-upsert.event.ts b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-ext-upsert.event.ts new file mode 100644 index 0000000..53fa16e --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-ext-upsert.event.ts @@ -0,0 +1,24 @@ +import { ScheduleAppointmentStatus } from '../../enums'; +import { SchedulerAppointmentExtEvent } from './schedule-appointment-ext.event'; + +export class SchedulerAppointmentExtUpsertEvent extends SchedulerAppointmentExtEvent { + ownerId: number; + performerId: number; + title: string; + comment?: string | null; + startDate: Date; + endDate: Date; + status?: ScheduleAppointmentStatus | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.ownerId = data.ownerId; + this.performerId = data.performerId; + this.title = data.title; + this.comment = data.comment; + this.startDate = data.startDate; + this.endDate = data.endDate; + this.status = data.status; + } +} diff --git a/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-ext.event.ts b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-ext.event.ts new file mode 100644 index 0000000..a0912d1 --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-ext.event.ts @@ -0,0 +1,17 @@ +import { ServiceEvent } from '@/common'; + +export class SchedulerAppointmentExtEvent extends ServiceEvent { + externalId?: string | null; + accountId: number; + scheduleId: number; + appointmentId?: number | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.externalId = data.externalId; + this.accountId = data.accountId; + this.scheduleId = data.scheduleId; + this.appointmentId = data.appointmentId; + } +} diff --git a/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-updated.event.ts b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-updated.event.ts new file mode 100644 index 0000000..354082b --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment-updated.event.ts @@ -0,0 +1,3 @@ +import { SchedulerAppointmentCreatedEvent } from './schedule-appointment-created.event'; + +export class SchedulerAppointmentUpdatedEvent extends SchedulerAppointmentCreatedEvent {} diff --git a/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment.event.ts b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment.event.ts new file mode 100644 index 0000000..d9c131e --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-appointment/schedule-appointment.event.ts @@ -0,0 +1,23 @@ +import { ServiceEvent } from '@/common'; + +export class SchedulerAppointmentEvent extends ServiceEvent { + accountId: number; + ownerId: number; + scheduleId: number; + performerId: number; + appointmentId: number; + entityId: number | null; + externalId?: string | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.accountId = data.accountId; + this.ownerId = data.ownerId; + this.scheduleId = data.scheduleId; + this.performerId = data.performerId; + this.appointmentId = data.appointmentId; + this.entityId = data.entityId; + this.externalId = data.externalId; + } +} diff --git a/backend/src/modules/scheduler/common/events/schedule-performer/index.ts b/backend/src/modules/scheduler/common/events/schedule-performer/index.ts new file mode 100644 index 0000000..ab289d2 --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-performer/index.ts @@ -0,0 +1,2 @@ +export * from './schedule-performer-deleted.event'; +export * from './schedule-performer.event'; diff --git a/backend/src/modules/scheduler/common/events/schedule-performer/schedule-performer-deleted.event.ts b/backend/src/modules/scheduler/common/events/schedule-performer/schedule-performer-deleted.event.ts new file mode 100644 index 0000000..1dfd9bd --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-performer/schedule-performer-deleted.event.ts @@ -0,0 +1,11 @@ +import { SchedulePerformerEvent } from './schedule-performer.event'; + +export class SchedulePerformerDeletedEvent extends SchedulePerformerEvent { + newPerformerId?: number | null; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.newPerformerId = data.newPerformerId; + } +} diff --git a/backend/src/modules/scheduler/common/events/schedule-performer/schedule-performer.event.ts b/backend/src/modules/scheduler/common/events/schedule-performer/schedule-performer.event.ts new file mode 100644 index 0000000..2af2e3b --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule-performer/schedule-performer.event.ts @@ -0,0 +1,15 @@ +import { ServiceEvent } from '@/common'; + +export class SchedulePerformerEvent extends ServiceEvent { + accountId: number; + scheduleId: number; + performerId: number; + + constructor(data: Omit & { key?: string }) { + super(data); + + this.accountId = data.accountId; + this.scheduleId = data.scheduleId; + this.performerId = data.performerId; + } +} diff --git a/backend/src/modules/scheduler/common/events/schedule/index.ts b/backend/src/modules/scheduler/common/events/schedule/index.ts new file mode 100644 index 0000000..b4a7b8a --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule/index.ts @@ -0,0 +1,2 @@ +export * from './schedule-updated.event'; +export * from './schedule.event'; diff --git a/backend/src/modules/scheduler/common/events/schedule/schedule-updated.event.ts b/backend/src/modules/scheduler/common/events/schedule/schedule-updated.event.ts new file mode 100644 index 0000000..1fa6372 --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule/schedule-updated.event.ts @@ -0,0 +1,12 @@ +import { ScheduleEvent } from './schedule.event'; + +export class ScheduleUpdatedEvent extends ScheduleEvent { + typeChanged: boolean; + timePeriodChanged: boolean; + + constructor({ accountId, userId, scheduleId, typeChanged, timePeriodChanged }: ScheduleUpdatedEvent) { + super({ accountId, userId, scheduleId }); + this.typeChanged = typeChanged; + this.timePeriodChanged = timePeriodChanged; + } +} diff --git a/backend/src/modules/scheduler/common/events/schedule/schedule.event.ts b/backend/src/modules/scheduler/common/events/schedule/schedule.event.ts new file mode 100644 index 0000000..e74f8d7 --- /dev/null +++ b/backend/src/modules/scheduler/common/events/schedule/schedule.event.ts @@ -0,0 +1,11 @@ +export class ScheduleEvent { + accountId: number; + userId: number; + scheduleId: number; + + constructor({ accountId, userId, scheduleId }: ScheduleEvent) { + this.accountId = accountId; + this.userId = userId; + this.scheduleId = scheduleId; + } +} diff --git a/backend/src/modules/scheduler/common/events/scheduler-event-type.enum.ts b/backend/src/modules/scheduler/common/events/scheduler-event-type.enum.ts new file mode 100644 index 0000000..9cafab6 --- /dev/null +++ b/backend/src/modules/scheduler/common/events/scheduler-event-type.enum.ts @@ -0,0 +1,12 @@ +export enum SchedulerEventType { + ScheduleAppointmentCreated = 'schedule:appointment:created', + ScheduleAppointmentDeleted = 'schedule:appointment:deleted', + ScheduleAppointmentUpdated = 'schedule:appointment:updated', + ScheduleAppointmentUpsertExt = 'schedule:appointment:upsert-ext', + ScheduleCreated = 'schedule:created', + ScheduleDeleted = 'schedule:deleted', + ScheduleUpdated = 'schedule:updated', + SchedulePerformerCreated = 'schedule:performer:created', + SchedulePerformerDeleted = 'schedule:performer:deleted', + SchedulePerformerUpdated = 'schedule:performer:updated', +} diff --git a/backend/src/modules/scheduler/common/index.ts b/backend/src/modules/scheduler/common/index.ts new file mode 100644 index 0000000..df1eda9 --- /dev/null +++ b/backend/src/modules/scheduler/common/index.ts @@ -0,0 +1,2 @@ +export * from './enums'; +export * from './events'; diff --git a/backend/src/modules/scheduler/schedule-appointment/dto/create-schedule-appointment.dto.ts b/backend/src/modules/scheduler/schedule-appointment/dto/create-schedule-appointment.dto.ts new file mode 100644 index 0000000..1d7a488 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/dto/create-schedule-appointment.dto.ts @@ -0,0 +1,31 @@ +import { ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsBoolean, IsDateString, IsNumber, IsOptional } from 'class-validator'; + +import { ScheduleAppointmentDto } from './schedule-appointment.dto'; + +export class CreateScheduleAppointmentDto extends PickType(ScheduleAppointmentDto, [ + 'scheduleId', + 'startDate', + 'status', + 'title', + 'comment', + 'entityId', + 'performerId', + 'orderId', + 'externalId', +] as const) { + @ApiPropertyOptional({ description: 'Appointment end date' }) + @IsOptional() + @IsDateString() + endDate?: string; + + @ApiPropertyOptional({ description: 'Check intersection with other appointments' }) + @IsOptional() + @IsBoolean() + checkIntersection?: boolean; + + @ApiPropertyOptional({ description: 'Owner id' }) + @IsOptional() + @IsNumber() + ownerId?: number; +} diff --git a/backend/src/modules/scheduler/schedule-appointment/dto/index.ts b/backend/src/modules/scheduler/schedule-appointment/dto/index.ts new file mode 100644 index 0000000..66febdf --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/dto/index.ts @@ -0,0 +1,6 @@ +export * from './create-schedule-appointment.dto'; +export * from './schedule-appointment-filter.dto'; +export * from './schedule-appointment-result.dto'; +export * from './schedule-appointment-statistic.dto'; +export * from './schedule-appointment.dto'; +export * from './update-schedule-appointment.dto'; diff --git a/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-filter.dto.ts b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-filter.dto.ts new file mode 100644 index 0000000..3ebf062 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-filter.dto.ts @@ -0,0 +1,52 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { DatePeriodDto } from '@/common'; +import { ScheduleAppointmentStatus } from '../../common'; + +export class ScheduleAppointmentFilterDto extends DatePeriodDto { + @ApiPropertyOptional({ description: 'Schedule ID' }) + @IsOptional() + @IsNumber() + scheduleId?: number; + + @ApiPropertyOptional({ description: 'Linked entity ID' }) + @IsOptional() + @IsNumber() + entityId?: number; + + @ApiPropertyOptional({ description: 'Performer ID' }) + @IsOptional() + @IsNumber() + performerId?: number; + + @ApiPropertyOptional({ description: 'Show canceled appointments' }) + @IsOptional() + @IsBoolean() + showCanceled?: boolean; + + @ApiPropertyOptional({ description: 'Appointment title' }) + @IsOptional() + @IsString() + title?: string; + + @ApiPropertyOptional({ enum: ScheduleAppointmentStatus, isArray: true, description: 'Appointment status' }) + @IsOptional() + @IsEnum(ScheduleAppointmentStatus, { each: true }) + status?: ScheduleAppointmentStatus | ScheduleAppointmentStatus[]; + + @ApiPropertyOptional({ description: 'Show only appointments which is first for linked entity' }) + @IsOptional() + @IsBoolean() + isNewbie?: boolean; + + @ApiPropertyOptional({ description: 'Show only appointments without next appointment for same linked entity' }) + @IsOptional() + @IsBoolean() + isNotScheduled?: boolean; + + @ApiPropertyOptional({ description: 'Show only appointments which is not took place' }) + @IsOptional() + @IsBoolean() + isNotTookPlace?: boolean; +} diff --git a/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-result.dto.ts b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-result.dto.ts new file mode 100644 index 0000000..5e1b01f --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-result.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; + +import { ScheduleAppointmentDto } from './schedule-appointment.dto'; + +export class ScheduleAppointmentResultDto { + @ApiProperty({ type: [ScheduleAppointmentDto], description: 'List of schedule appointments' }) + appointments: ScheduleAppointmentDto[]; + + @ApiProperty({ type: PagingMeta, description: 'Paging metadata' }) + meta: PagingMeta; + + constructor(appointments: ScheduleAppointmentDto[], meta: PagingMeta) { + this.appointments = appointments; + this.meta = meta; + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-statistic.dto.ts b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-statistic.dto.ts new file mode 100644 index 0000000..2dfb3f3 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment-statistic.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsObject } from 'class-validator'; + +import { ScheduleAppointmentStatus } from '../../common'; + +export class ScheduleAppointmentStatisticDto { + @ApiProperty({ description: 'Total number of appointments' }) + @IsNumber() + total: number; + + @ApiProperty({ description: 'Number of appointments grouped by status' }) + @IsObject() + statuses: Record; + + @ApiProperty({ description: 'Number of appointments which is first for linked entity' }) + @IsNumber() + newbies: number; + + @ApiProperty({ description: 'Number of appointments without next appointment for same linked entity' }) + @IsNumber() + notScheduled: number; + + @ApiProperty({ description: 'Number of appointments which is not took place' }) + @IsNumber() + notTookPlace: number; +} diff --git a/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment.dto.ts b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment.dto.ts new file mode 100644 index 0000000..ab17dfd --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/dto/schedule-appointment.dto.ts @@ -0,0 +1,84 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; + +import { UserRights } from '@/modules/iam/common/types/user-rights'; +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; +import { OrderDto } from '@/modules/inventory/order/dto/order.dto'; + +import { ScheduleAppointmentStatus } from '../../common'; + +export class ScheduleAppointmentDto { + @ApiProperty({ description: 'Appointment ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Schedule ID' }) + @IsNumber() + scheduleId: number; + + @ApiProperty({ description: 'Appointment start date' }) + @IsDateString() + startDate: string; + + @ApiProperty({ description: 'Appointment end date' }) + @IsDateString() + endDate: string; + + @ApiProperty({ enum: ScheduleAppointmentStatus, description: 'Appointment status' }) + @IsEnum(ScheduleAppointmentStatus) + status: ScheduleAppointmentStatus; + + @ApiPropertyOptional({ nullable: true, description: 'Appointment title' }) + @IsOptional() + @IsString() + title?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Appointment comment' }) + @IsOptional() + @IsString() + comment?: string | null; + + @ApiProperty({ description: 'User ID for appointment owner' }) + @IsNumber() + ownerId: number; + + @ApiPropertyOptional({ nullable: true, description: 'Linked entity ID' }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiPropertyOptional({ type: EntityInfoDto, nullable: true, description: 'Linked entity' }) + @IsOptional() + entityInfo?: EntityInfoDto | null; + + @ApiProperty({ description: 'Performer ID' }) + @IsNumber() + performerId: number; + + @ApiPropertyOptional({ nullable: true, description: 'Linked order ID' }) + @IsOptional() + @IsNumber() + orderId?: number | null; + + @ApiPropertyOptional({ type: OrderDto, nullable: true, description: 'Linked order' }) + @IsOptional() + order?: OrderDto | null; + + @ApiPropertyOptional({ nullable: true, description: 'Previous appointment count' }) + @IsOptional() + @IsNumber() + prevAppointmentCount?: number | null; + + @ApiProperty({ description: 'Appointment creation date' }) + @IsDateString() + createdAt: string; + + @ApiProperty({ type: () => UserRights, description: 'User rights for current user' }) + @IsObject() + userRights: UserRights; + + @ApiPropertyOptional({ nullable: true, description: 'External ID' }) + @IsOptional() + @IsString() + externalId?: string | null; +} diff --git a/backend/src/modules/scheduler/schedule-appointment/dto/update-schedule-appointment.dto.ts b/backend/src/modules/scheduler/schedule-appointment/dto/update-schedule-appointment.dto.ts new file mode 100644 index 0000000..8349117 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/dto/update-schedule-appointment.dto.ts @@ -0,0 +1,18 @@ +import { ApiPropertyOptional, OmitType, PartialType } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional } from 'class-validator'; + +import { ScheduleAppointmentDto } from './schedule-appointment.dto'; + +export class UpdateScheduleAppointmentDto extends PartialType( + OmitType(ScheduleAppointmentDto, ['id', 'ownerId', 'createdAt', 'userRights', 'order', 'entityInfo'] as const), +) { + @ApiPropertyOptional({ description: 'Check intersection with other appointments' }) + @IsOptional() + @IsBoolean() + checkIntersection?: boolean; + + @ApiPropertyOptional({ description: 'Owner id' }) + @IsOptional() + @IsNumber() + ownerId?: number; +} diff --git a/backend/src/modules/scheduler/schedule-appointment/entities/index.ts b/backend/src/modules/scheduler/schedule-appointment/entities/index.ts new file mode 100644 index 0000000..ee3a0de --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/entities/index.ts @@ -0,0 +1 @@ +export * from './schedule-appointment.entity'; diff --git a/backend/src/modules/scheduler/schedule-appointment/entities/schedule-appointment.entity.ts b/backend/src/modules/scheduler/schedule-appointment/entities/schedule-appointment.entity.ts new file mode 100644 index 0000000..28872ec --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/entities/schedule-appointment.entity.ts @@ -0,0 +1,219 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { Authorizable, AuthorizableObject, SimpleAuthorizable, UserRights } from '@/modules/iam/common'; +import { Order } from '@/modules/inventory/order/entities/order.entity'; +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { PermissionObjectType, ScheduleAppointmentStatus } from '../../common'; +import { SchedulePerformer } from '../../schedule-performer'; + +import { CreateScheduleAppointmentDto, UpdateScheduleAppointmentDto, ScheduleAppointmentDto } from '../dto'; + +@Entity() +export class ScheduleAppointment implements Authorizable { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + @Column() + scheduleId: number; + + @Column() + startDate: Date; + + @Column() + endDate: Date; + + @Column() + status: ScheduleAppointmentStatus; + + @Column({ nullable: true }) + title: string | null; + + @Column({ nullable: true }) + comment: string | null; + + @Column() + ownerId: number; + + @Column({ nullable: true }) + entityId: number | null; + + @Column() + performerId: number; + + @Column({ nullable: true }) + orderId: number | null; + + @Column() + externalId: string | null; + + constructor( + accountId: number, + scheduleId: number, + startDate: Date, + endDate: Date, + status: ScheduleAppointmentStatus, + title: string | null, + comment: string | null, + ownerId: number, + entityId: number | null, + performerId: number, + orderId: number | null, + externalId: string | null, + createdAt?: Date, + ) { + this.accountId = accountId; + this.scheduleId = scheduleId; + this.startDate = startDate; + this.endDate = endDate; + this.status = status; + this.title = title; + this.comment = comment; + this.ownerId = ownerId; + this.entityId = entityId; + this.performerId = performerId; + this.orderId = orderId; + this.externalId = externalId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _performer: SchedulePerformer | null; + public get performer(): SchedulePerformer | null { + return this._performer; + } + public set performer(value: SchedulePerformer | null) { + this._performer = value; + } + + private _prevAppointmentCount: number | null; + public get prevAppointmentCount(): number | null { + return this._prevAppointmentCount; + } + public set prevAppointmentCount(value: number | null) { + this._prevAppointmentCount = value; + } + + private _userRights: UserRights | null; + public get userRights(): UserRights | null { + return this._userRights; + } + public set userRights(value: UserRights | null) { + this._userRights = value; + } + + private _order: Order | null; + public get order(): Order | null { + return this._order; + } + public set order(value: Order | null) { + this._order = value; + } + + private _entityInfo: EntityInfoDto | null; + public get entityInfo(): EntityInfoDto | null { + return this._entityInfo; + } + public set entityInfo(value: EntityInfoDto | null) { + this._entityInfo = value; + } + + public static fromDto( + accountId: number, + createdBy: number, + dto: CreateScheduleAppointmentDto, + timePeriod: number | null, + ): ScheduleAppointment { + const start = DateUtil.fromISOString(dto.startDate); + const end = dto.endDate ? DateUtil.fromISOString(dto.endDate) : DateUtil.add(start, { seconds: timePeriod }); + return new ScheduleAppointment( + accountId, + dto.scheduleId, + start, + end, + dto.status, + dto.title, + dto.comment, + dto.ownerId ?? createdBy, + dto.entityId, + dto.performerId, + dto.orderId ?? null, + dto.externalId ?? null, + ); + } + + public update(dto: UpdateScheduleAppointmentDto): ScheduleAppointment { + this.scheduleId = dto.scheduleId !== undefined ? dto.scheduleId : this.scheduleId; + this.startDate = dto.startDate !== undefined ? DateUtil.fromISOString(dto.startDate) : this.startDate; + this.endDate = dto.endDate !== undefined ? DateUtil.fromISOString(dto.endDate) : this.endDate; + this.status = dto.status !== undefined ? dto.status : this.status; + this.title = dto.title !== undefined ? dto.title : this.title; + this.comment = dto.comment !== undefined ? dto.comment : this.comment; + this.entityId = dto.entityId !== undefined ? dto.entityId : this.entityId; + this.performerId = dto.performerId !== undefined ? dto.performerId : this.performerId; + this.orderId = dto.orderId !== undefined ? dto.orderId : this.orderId; + this.ownerId = dto.ownerId !== undefined ? dto.ownerId : this.ownerId; + this.externalId = dto.externalId !== undefined ? dto.externalId : this.externalId; + + return this; + } + + public hasChanges(dto: UpdateScheduleAppointmentDto): boolean { + return ( + (dto.scheduleId !== undefined && dto.scheduleId !== this.scheduleId) || + (dto.startDate !== undefined && DateUtil.fromISOString(dto.startDate) !== this.startDate) || + (dto.endDate !== undefined && DateUtil.fromISOString(dto.endDate) !== this.endDate) || + (dto.status !== undefined && dto.status !== this.status) || + (dto.title !== undefined && dto.title !== this.title) || + (dto.comment !== undefined && dto.comment !== this.comment) || + (dto.entityId !== undefined && dto.entityId !== this.entityId) || + (dto.performerId !== undefined && dto.performerId !== this.performerId) || + (dto.orderId !== undefined && dto.orderId !== this.orderId) || + (dto.ownerId !== undefined && dto.ownerId !== this.ownerId) || + (dto.externalId !== undefined && dto.externalId !== this.externalId) + ); + } + + public toDto(): ScheduleAppointmentDto { + return { + id: this.id, + scheduleId: this.scheduleId, + startDate: this.startDate.toISOString(), + endDate: this.endDate.toISOString(), + status: this.status, + title: this.title, + comment: this.comment, + ownerId: this.ownerId, + entityId: this.entityId, + performerId: this.performerId, + orderId: this.orderId, + prevAppointmentCount: this.prevAppointmentCount, + createdAt: this.createdAt.toISOString(), + userRights: this.userRights ?? UserRights.full(), + order: this.order?.toDto(), + entityInfo: this.entityInfo, + externalId: this.externalId, + }; + } + + static getAuthorizable(scheduleId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Schedule, id: scheduleId }); + } + + getAuthorizableObject(): AuthorizableObject { + return { + type: PermissionObjectType.Schedule, + id: this.scheduleId, + createdBy: this.ownerId, + ownerId: this.performer?.userId, + departmentId: this.performer?.departmentId, + }; + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/errors/appointment-dates-conflict.error.ts b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-dates-conflict.error.ts new file mode 100644 index 0000000..419aebd --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-dates-conflict.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class AppointmentDatesConflictError extends ServiceError { + constructor(message = 'Start date is more or equal than end date') { + super({ errorCode: 'scheduler.appointment.dates_conflict', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/errors/appointment-day-limit.error.ts b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-day-limit.error.ts new file mode 100644 index 0000000..3f66161 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-day-limit.error.ts @@ -0,0 +1,14 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class AppointmentDayLimitError extends ServiceError { + constructor({ appointmentId, message = 'Appointment day limit error' }: { appointmentId: number; message?: string }) { + super({ + errorCode: 'scheduler.appointment.day_limit', + status: HttpStatus.BAD_REQUEST, + message, + details: { appointmentId }, + }); + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/errors/appointment-intersection.error.ts b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-intersection.error.ts new file mode 100644 index 0000000..81a5c8e --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-intersection.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class AppointmentIntersectionError extends ServiceError { + constructor(message = 'Intersect appointments') { + super({ errorCode: 'scheduler.appointment.intersection', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/errors/appointment-out-of-work-time.error.ts b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-out-of-work-time.error.ts new file mode 100644 index 0000000..ca7f92d --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/errors/appointment-out-of-work-time.error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class AppointmentOutOfWorkTimeError extends ServiceError { + constructor(message = 'Appointment is out of work time') { + super({ errorCode: 'scheduler.appointment.out_of_work_time', status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/errors/index.ts b/backend/src/modules/scheduler/schedule-appointment/errors/index.ts new file mode 100644 index 0000000..079b896 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/errors/index.ts @@ -0,0 +1,4 @@ +export * from './appointment-dates-conflict.error'; +export * from './appointment-day-limit.error'; +export * from './appointment-intersection.error'; +export * from './appointment-out-of-work-time.error'; diff --git a/backend/src/modules/scheduler/schedule-appointment/index.ts b/backend/src/modules/scheduler/schedule-appointment/index.ts new file mode 100644 index 0000000..b645e4e --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/index.ts @@ -0,0 +1,7 @@ +export * from './dto'; +export * from './entities'; +export * from './errors'; +export * from './schedule-appointment.controller'; +export * from './schedule-appointment.handler'; +export * from './schedule-appointment.service'; +export * from './types'; diff --git a/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.controller.ts b/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.controller.ts new file mode 100644 index 0000000..5890032 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.controller.ts @@ -0,0 +1,174 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto, PagingQuery, ExpandQuery } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { + ScheduleAppointmentDto, + CreateScheduleAppointmentDto, + ScheduleAppointmentResultDto, + ScheduleAppointmentFilterDto, + UpdateScheduleAppointmentDto, + ScheduleAppointmentStatisticDto, +} from './dto'; +import { EntityListItem, EntityListMeta } from '@/CRM/Service/Entity/Dto/List'; +import { ExpandableField, Spot } from './types'; +import { ScheduleAppointmentService } from './schedule-appointment.service'; + +@ApiTags('scheduler/appointment') +@Controller('appointments') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ScheduleAppointmentController { + constructor(private readonly service: ScheduleAppointmentService) {} + + @ApiOperation({ summary: 'Create schedule appointment', description: 'Create schedule appointment' }) + @ApiBody({ description: 'Data for creating schedule appointment', type: CreateScheduleAppointmentDto }) + @ApiCreatedResponse({ description: 'Schedule appointment', type: ScheduleAppointmentDto }) + @Post() + async create(@CurrentAuth() { accountId, user }: AuthData, @Body() dto: CreateScheduleAppointmentDto) { + return this.service.create({ accountId, user, dto }); + } + + @ApiOperation({ summary: 'Get schedule appointments', description: 'Get schedule appointments according filter' }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: order,prevAppointmentCount,entityInfo.', + }) + @ApiOkResponse({ description: 'Schedule appointments', type: ScheduleAppointmentResultDto }) + @Get() + async getSchedule( + @CurrentAuth() { accountId, user }: AuthData, + @Query() filter: ScheduleAppointmentFilterDto, + @Query() paging: PagingQuery, + @Query() expand?: ExpandQuery, + ) { + return this.service.getSchedule({ accountId, user, filter, paging, options: { expand: expand.fields } }); + } + + @ApiOperation({ + summary: 'Get schedule appointment statistic', + description: 'Get schedule appointment statistic according filter', + }) + @ApiOkResponse({ description: 'Schedule appointment statistic', type: ScheduleAppointmentStatisticDto }) + @Get('statistic') + async getStatistic(@CurrentAuth() { accountId }: AuthData, @Query() filter: ScheduleAppointmentFilterDto) { + return this.service.getStatistic({ accountId, filter }); + } + + @ApiOperation({ summary: 'Get appointments count', description: 'Get appointments count according filter' }) + @ApiOkResponse({ description: 'Appointments count', type: Number }) + @Get('count') + async getCount(@CurrentAuth() { accountId }: AuthData, @Query() filter: ScheduleAppointmentFilterDto) { + return this.service.getCount({ accountId, filter, isSchedule: true }); + } + + @ApiOperation({ + summary: 'Get last schedule appointment', + description: 'Get last schedule appointment according filter', + }) + @ApiQuery({ name: 'filter', type: ScheduleAppointmentFilterDto, required: false, description: 'Filter' }) + @ApiOkResponse({ description: 'Schedule appointments', type: ScheduleAppointmentDto }) + @Get('last') + async getLast(@CurrentAuth() { accountId, user }: AuthData, @Query() filter: ScheduleAppointmentFilterDto) { + return this.service.getLast({ accountId, user, filter }); + } + + @ApiOperation({ + summary: 'Get schedule appointment linked cards', + description: 'Get schedule appointment linked cards', + }) + @ApiOkResponse({ description: 'Entity list linked with appointments', type: [EntityListItem] }) + @Get('cards/list') + async getEntityList( + @CurrentAuth() { accountId, user }: AuthData, + @Query() filter: ScheduleAppointmentFilterDto, + @Query() paging: PagingQuery, + ): Promise { + return this.service.getEntityList({ accountId, user, filter, paging }); + } + + @ApiOperation({ + summary: 'Get schedule appointment linked cards meta', + description: 'Get schedule appointment linked cards meta', + }) + @ApiOkResponse({ description: 'Meta for entity list', type: EntityListMeta }) + @Get('cards/list/meta') + async getEntityListMeta( + @CurrentAuth() { accountId }: AuthData, + @Query() filter: ScheduleAppointmentFilterDto, + ): Promise { + return this.service.getEntityListMeta({ accountId, filter }); + } + + @ApiOperation({ summary: 'Get available spots', description: 'Get available spots' }) + @ApiOkResponse({ description: 'Available spots', type: [Spot] }) + @Get('spots') + async getAvailableSpots( + @CurrentAuth() { accountId }: AuthData, + @Query('scheduleId', ParseIntPipe) scheduleId: number, + @Query('performerId', ParseIntPipe) performerId: number, + @Query('minDate') minDate: string, + @Query('maxDate') maxDate: string, + @Query('daysLimit', ParseIntPipe) daysLimit: number, + ) { + return this.service.getAvailableSpots({ + accountId, + scheduleId, + performerId, + minDate: new Date(minDate), + maxDate: new Date(maxDate), + daysLimit, + }); + } + + @ApiOperation({ summary: 'Get schedule appointment', description: 'Get schedule appointment by ID' }) + @ApiParam({ name: 'appointmentId', description: 'Schedule appointment ID' }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: order,prevAppointmentCount,entityInfo.', + }) + @ApiOkResponse({ description: 'Schedule appointments', type: ScheduleAppointmentDto }) + @Get(':appointmentId') + async getOne( + @CurrentAuth() { accountId, user }: AuthData, + @Param('appointmentId', ParseIntPipe) appointmentId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.getOne({ accountId, user, appointmentId, options: { expand: expand.fields } }); + } + + @ApiOperation({ summary: 'Update schedule appointment', description: 'Update schedule appointment' }) + @ApiParam({ name: 'appointmentId', description: 'Schedule appointment ID' }) + @ApiBody({ description: 'Data for updating schedule appointment', type: UpdateScheduleAppointmentDto }) + @ApiOkResponse({ description: 'Schedule appointment', type: ScheduleAppointmentDto }) + @Patch(':appointmentId') + async update( + @CurrentAuth() { accountId, user }: AuthData, + @Param('appointmentId', ParseIntPipe) appointmentId: number, + @Body() dto: UpdateScheduleAppointmentDto, + ) { + return this.service.update({ accountId, user, appointmentId, dto }); + } + + @ApiOperation({ summary: 'Delete schedule appointment', description: 'Delete schedule appointment' }) + @ApiParam({ name: 'appointmentId', description: 'Schedule appointment ID' }) + @ApiOkResponse() + @Delete(':appointmentId') + async delete( + @CurrentAuth() { accountId, user }: AuthData, + @Param('appointmentId', ParseIntPipe) appointmentId: number, + ) { + return this.service.delete({ accountId, user, filter: { appointmentId } }); + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.handler.ts b/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.handler.ts new file mode 100644 index 0000000..65d2972 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.handler.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { SchedulerAppointmentExtUpsertEvent, SchedulerEventType, ScheduleUpdatedEvent } from '../common'; +import { ScheduleAppointmentService } from './schedule-appointment.service'; + +@Injectable() +export class ScheduleAppointmentHandler { + constructor(private readonly service: ScheduleAppointmentService) {} + + @OnEvent(SchedulerEventType.ScheduleUpdated, { async: true }) + public async onUserDeleted(event: ScheduleUpdatedEvent) { + if (event.typeChanged || event.timePeriodChanged) { + await this.service.delete({ accountId: event.accountId, filter: { scheduleId: event.scheduleId } }); + } + } + + @OnEvent(SchedulerEventType.ScheduleAppointmentUpsertExt, { async: true }) + public async onTaskUpsertExt(event: SchedulerAppointmentExtUpsertEvent) { + await this.service.handleUpsertExt(event); + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.service.ts b/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.service.ts new file mode 100644 index 0000000..b575564 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/schedule-appointment.service.ts @@ -0,0 +1,962 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository, SelectQueryBuilder } from 'typeorm'; +import { fromZonedTime, toZonedTime } from 'date-fns-tz'; + +import { DatePeriod, DateUtil, ForbiddenError, NotFoundError, PagingQuery, ServiceEvent } from '@/common'; + +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { UserService } from '@/modules/iam/user/user.service'; +import { UserCalendarService } from '@/modules/iam/user-calendar/services/user-calendar.service'; +import { WorkingTimeService } from '@/modules/iam/working-time/working-time.service'; +import { OrderService } from '@/modules/inventory/order/services/order.service'; +import { EntityListItem, EntityListMeta } from '@/CRM/Service/Entity/Dto/List'; +import { EntityBoardService } from '@/CRM/Service/Entity/EntityBoardService'; + +import { + ScheduleAppointmentStatus, + SchedulerAppointmentCreatedEvent, + SchedulerAppointmentEvent, + SchedulerAppointmentExtUpsertEvent, + SchedulerAppointmentUpdatedEvent, + SchedulerEventType, +} from '../common'; +import { ScheduleService } from '../schedule/services/schedule.service'; +import { Schedule } from '../schedule/entities'; +import { SchedulePerformer } from '../schedule-performer/entities'; + +import { CreateScheduleAppointmentDto, ScheduleAppointmentFilterDto, UpdateScheduleAppointmentDto } from './dto'; +import { ScheduleAppointment } from './entities'; +import { AppointmentDatesConflictError, AppointmentDayLimitError, AppointmentIntersectionError } from './errors'; +import { ExpandableField, ScheduleAppointmentResult, ScheduleAppointmentStatistic, Spot } from './types'; + +interface FindFilter { + accountId: number; + appointmentId?: number | number[]; + scheduleId?: number; + entityId?: number; + performerId?: number; + showCanceled?: boolean; + title?: string; + status?: ScheduleAppointmentStatus | ScheduleAppointmentStatus[]; + from?: Date; + to?: Date; + isNewbie?: boolean; + isNotScheduled?: boolean; + isNotTookPlace?: boolean; + externalId?: string; +} + +interface DeleteFilter { + appointmentId?: number | number[]; + scheduleId?: number; +} + +interface FindOptions { + joinPerformer?: boolean; + isSchedule?: boolean; + expand?: ExpandableField[]; +} + +interface StatusCount { + status: ScheduleAppointmentStatus; + count: number; +} +interface Count { + count: number; +} + +interface WorkingTime { + dayOfWeek: string; + timeFrom: string; + timeTo: string; +} +interface SpotScheduleSettings { + appointmentLimit?: number; + timeBufferBefore?: number; + timeBufferAfter?: number; + timezone: string; + intervals: WorkingTime[]; +} + +@Injectable() +export class ScheduleAppointmentService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(ScheduleAppointment) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly userService: UserService, + private readonly orderService: OrderService, + private readonly scheduleService: ScheduleService, + private readonly entityInfoService: EntityInfoService, + private readonly entityBoardService: EntityBoardService, + private readonly userCalendarService: UserCalendarService, + private readonly workingTimeService: WorkingTimeService, + ) {} + + async create({ + accountId, + user, + dto, + skipPermissionCheck, + event, + }: { + accountId: number; + user: User; + dto: CreateScheduleAppointmentDto; + skipPermissionCheck?: boolean; + event?: ServiceEvent; + }): Promise { + if (!skipPermissionCheck) { + await this.authService.check({ + action: 'create', + user, + authorizable: ScheduleAppointment.getAuthorizable(dto.scheduleId), + throwError: true, + }); + } + + const schedule = await this.scheduleService.findOne({ filter: { accountId, scheduleId: dto.scheduleId } }); + if (!schedule?.timePeriod && !dto.endDate) { + throw new AppointmentDatesConflictError('Schedule time period is not defined'); + } + + let appointment = ScheduleAppointment.fromDto(accountId, user.id, dto, schedule.timePeriod); + if (appointment.endDate <= appointment.startDate) { + throw new AppointmentDatesConflictError(); + } + + if (dto.checkIntersection && (await this.hasIntersect(appointment))) { + throw new AppointmentIntersectionError(); + } + + if (appointment.entityId) { + await this.checkEntityDayLimit(appointment); + } + + appointment = await this.repository.save(appointment); + + this.eventEmitter.emit( + SchedulerEventType.ScheduleAppointmentCreated, + new SchedulerAppointmentCreatedEvent({ + source: ScheduleAppointment.name, + accountId, + ownerId: appointment.ownerId, + scheduleId: appointment.scheduleId, + performerId: appointment.performerId, + appointmentId: appointment.id, + externalId: appointment.externalId, + title: appointment.title, + comment: appointment.comment, + startDate: appointment.startDate, + endDate: appointment.endDate, + status: appointment.status, + entityId: appointment.entityId, + prevEvent: event, + }), + ); + + appointment.userRights = await this.authService.getUserRights({ user, authorizable: appointment }); + + return this.expandOne({ accountId, user, appointment, expand: ['prevAppointmentCount', 'entityInfo', 'order'] }); + } + + async getOne({ + accountId, + user, + appointmentId, + options, + }: { + accountId: number; + user: User; + appointmentId: number; + options?: FindOptions; + }): Promise { + const appointment = await this.createFindQb({ + filter: { accountId, appointmentId }, + options: { joinPerformer: true }, + }).getOne(); + + if (!appointment) { + throw NotFoundError.withId(ScheduleAppointment, appointmentId); + } + + appointment.userRights = await this.authService.getUserRights({ user, authorizable: appointment }); + if (!appointment.userRights.canView) { + throw new ForbiddenError(); + } + + return options?.expand ? this.expandOne({ accountId, user, appointment, expand: options.expand }) : appointment; + } + + async getCount({ + accountId, + filter, + isSchedule, + }: { + accountId: number; + filter: ScheduleAppointmentFilterDto; + isSchedule?: boolean; + }): Promise { + const period = DatePeriod.fromDto(filter); + return this.createFindQb({ + filter: { accountId, ...filter, from: period.from, to: period.to }, + options: { isSchedule }, + }).getCount(); + } + + async getLast({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: ScheduleAppointmentFilterDto; + }): Promise { + const period = DatePeriod.fromDto(filter); + const appointment = await this.createFindQb({ + filter: { accountId, ...filter, from: period.from, to: DateUtil.now() }, + options: { joinPerformer: true }, + }) + .orderBy('appointment.end_date', 'DESC') + .getOne(); + + if (appointment) { + appointment.userRights = await this.authService.getUserRights({ user, authorizable: appointment }); + } + + return appointment; + } + + async findOne({ + filter, + joinPerformer, + }: { + filter: FindFilter; + joinPerformer?: boolean; + }): Promise { + return this.createFindQb({ filter, options: { joinPerformer } }).getOne(); + } + async findMany({ + filter, + joinPerformer, + }: { + filter: FindFilter; + joinPerformer?: boolean; + }): Promise { + return this.createFindQb({ filter, options: { joinPerformer } }).getMany(); + } + + async getSchedule({ + accountId, + user, + filter, + paging, + options, + }: { + accountId: number; + user: User; + filter: ScheduleAppointmentFilterDto; + paging: PagingQuery; + options?: FindOptions; + }): Promise { + const period = DatePeriod.fromDto(filter); + const [appointments, total] = await this.createFindQb({ + filter: { accountId, ...filter, from: period.from, to: period.to }, + options: { isSchedule: true, joinPerformer: true }, + }) + .offset(paging.offset) + .limit(paging.limit) + .orderBy('appointment.start_date', 'DESC') + .getManyAndCount(); + + const expanded: ScheduleAppointment[] = await Promise.all( + appointments.map(async (appointment) => { + appointment.userRights = await this.authService.getUserRights({ user, authorizable: appointment }); + return appointment.userRights.canView && options?.expand + ? this.expandOne({ accountId, user, appointment, expand: options.expand }) + : appointment; + }), + ); + + const result = new ScheduleAppointmentResult( + expanded.filter((a) => a.userRights.canView), + paging.skip + paging.take, + total, + ); + + return result; + } + + async getStatistic({ + accountId, + filter, + }: { + accountId: number; + filter: ScheduleAppointmentFilterDto; + }): Promise { + const period = DatePeriod.fromDto(filter); + const qb = this.createFindQb({ + filter: { accountId, ...filter, from: period.from, to: period.to }, + options: { isSchedule: true }, + }); + + const [rawStatuses, newbies, notScheduled, notTookPlace] = await Promise.all([ + qb + .clone() + .select('appointment.status', 'status') + .addSelect('COUNT(*)::int', 'count') + .groupBy('appointment.status') + .getRawMany(), + qb + .clone() + .select('COUNT(DISTINCT appointment.entityId)::int', 'count') + .andWhere('appointment.entityId IS NOT NULL') + .andWhere( + `NOT EXISTS (SELECT 1 FROM schedule_appointment ia WHERE ia.schedule_id = appointment.schedule_id AND ` + + `ia.entity_id IS NOT NULL AND ia.entity_id = appointment.entity_id ` + + `AND ia.status != '${ScheduleAppointmentStatus.Canceled}' AND ia.end_date < appointment.start_date)`, + ) + .getRawOne(), + qb + .clone() + .select('COUNT(DISTINCT appointment.entity_id)::int', 'count') + .andWhere( + `NOT EXISTS (SELECT 1 FROM schedule_appointment fa ` + + `WHERE fa.schedule_id = appointment.schedule_id AND fa.entity_id IS NOT NULL ` + + `AND fa.entity_id = appointment.entity_id ` + + `AND fa.status != '${ScheduleAppointmentStatus.Canceled}' AND fa.start_date > appointment.start_date)`, + ) + .getRawOne(), + qb + .clone() + .select('COUNT(*)::int', 'count') + .andWhere('appointment.status IN (:...notTookPlaceStatuses)', { + notTookPlaceStatuses: [ScheduleAppointmentStatus.NotConfirmed, ScheduleAppointmentStatus.Confirmed], + }) + .andWhere('appointment.end_date < now()') + .getRawOne(), + ]); + + let total = 0; + const statuses: Record = { + not_confirmed: 0, + confirmed: 0, + completed: 0, + canceled: 0, + }; + for (const rawStatus of rawStatuses) { + total += rawStatus.count; + statuses[rawStatus.status] += rawStatus.count; + } + + return new ScheduleAppointmentStatistic({ + total, + newbies: newbies?.count ?? 0, + notScheduled: notScheduled?.count ?? 0, + notTookPlace: notTookPlace?.count ?? 0, + statuses, + }); + } + + async getEntityList({ + accountId, + user, + filter, + paging, + }: { + accountId: number; + user: User; + filter: ScheduleAppointmentFilterDto; + paging: PagingQuery; + }): Promise { + const period = DatePeriod.fromDto(filter); + const entities = await this.createFindQb({ + filter: { accountId, ...filter, from: period.from, to: period.to }, + options: { isSchedule: true }, + }) + .select('DISTINCT(appointment.entity_id)', 'entityId') + .andWhere('appointment.entity_id IS NOT NULL') + .orderBy('appointment.entity_id') + .offset(paging.skip) + .limit(paging.take) + .getRawMany<{ entityId: number }>(); + + return entities.length + ? this.entityBoardService.createListItems({ accountId, user, entityIds: entities.map((e) => e.entityId) }) + : []; + } + + async getEntityListMeta({ + accountId, + filter, + }: { + accountId: number; + filter: ScheduleAppointmentFilterDto; + }): Promise { + const period = DatePeriod.fromDto(filter); + const { cnt } = await this.createFindQb({ + filter: { accountId, ...filter, from: period.from, to: period.to }, + options: { isSchedule: true }, + }) + .select('COUNT(DISTINCT(appointment.entity_id))', 'cnt') + .andWhere('appointment.entity_id IS NOT NULL') + .getRawOne<{ cnt: number }>(); + + return { totalCount: cnt, hasPrice: false, totalPrice: 0 }; + } + + async getAvailableDates({ + accountId, + scheduleId, + performerId, + minDate, + maxDate, + daysLimit, + timezone, + }: { + accountId: number; + scheduleId: number; + performerId: number; + minDate: Date; + maxDate: Date; + daysLimit: number; + timezone: string; + }): Promise { + const spots = await this.getAvailableSpots({ accountId, scheduleId, performerId, minDate, maxDate, daysLimit }); + + const uniqueDates = new Set(); + + spots.forEach((spot) => { + uniqueDates.add(DateUtil.format(toZonedTime(spot.from, timezone), 'yyyy-MM-dd')); + uniqueDates.add(DateUtil.format(toZonedTime(spot.to, timezone), 'yyyy-MM-dd')); + }); + + return Array.from(uniqueDates); + } + + async getAvailableSpots({ + accountId, + scheduleId, + performerId, + minDate, + maxDate, + daysLimit, + }: { + accountId: number; + scheduleId: number; + performerId: number; + minDate: Date; + maxDate: Date; + daysLimit: number; + }): Promise { + const schedule = await this.scheduleService.findOne({ filter: { accountId, scheduleId } }); + if (!schedule?.timePeriod) { + return []; + } + const performer = schedule?.performers.find((p) => p.id === performerId); + if (!performer) { + return []; + } + + const settings = await this.getSpotScheduleSettings({ accountId, schedule, performer }); + + const now = DateUtil.now(); + const limitDate = DateUtil.add(now, { days: daysLimit }); + const from = now > minDate ? now : minDate; + const to = limitDate < maxDate ? limitDate : maxDate; + if (from >= to) { + return []; + } + + const spots = this.generateSpots({ from, to, seconds: schedule.timePeriod, settings }); + if (!spots.length) { + return []; + } + + const appointments = await this.createFindQb({ + filter: { accountId, scheduleId: schedule.id, performerId: performer.id, from, to }, + options: { isSchedule: true }, + }).getMany(); + + const occupiedSpots: Spot[] = appointments.map((a) => ({ + from: settings.timeBufferBefore ? DateUtil.sub(a.startDate, { seconds: settings.timeBufferBefore }) : a.startDate, + to: settings.timeBufferAfter ? DateUtil.add(a.endDate, { seconds: settings.timeBufferAfter }) : a.endDate, + })); + + return spots.filter((spot) => !occupiedSpots.some((occ) => occ.from < spot.to && occ.to > spot.from)); + } + private async getSpotScheduleSettings({ + accountId, + schedule, + performer, + }: { + accountId: number; + schedule: Schedule; + performer: SchedulePerformer; + }): Promise { + const workingTime = performer.userId + ? await this.workingTimeService.getForUser({ accountId, userId: performer.userId }) + : await this.workingTimeService.getForDepartment({ + accountId, + departmentId: performer.departmentId, + }); + + const settings: SpotScheduleSettings = { + timezone: workingTime.timeZone, + appointmentLimit: schedule.appointmentLimit, + timeBufferBefore: schedule.timeBufferBefore, + timeBufferAfter: schedule.timeBufferAfter, + intervals: undefined, + }; + + if (schedule.intervals?.length) { + settings.intervals = schedule.intervals.map((i) => ({ + dayOfWeek: i.dayOfWeek, + timeFrom: i.timeFrom, + timeTo: i.timeTo, + })); + } else if (performer.userId) { + const userCalendar = await this.userCalendarService.findOne({ accountId, userId: performer.userId }); + if (userCalendar) { + settings.appointmentLimit = userCalendar.appointmentLimit; + settings.timeBufferBefore = userCalendar.timeBufferBefore; + settings.timeBufferAfter = userCalendar.timeBufferAfter; + if (userCalendar.intervals?.length) { + settings.intervals = userCalendar.intervals.map((i) => ({ + dayOfWeek: i.dayOfWeek, + timeFrom: i.timeFrom, + timeTo: i.timeTo, + })); + } + } + } + + if (!settings.intervals?.length) { + settings.intervals = workingTime.workingDays?.map((d) => ({ + dayOfWeek: d, + timeFrom: workingTime.workingTimeFrom, + timeTo: workingTime.workingTimeTo, + })); + } + + return settings; + } + private generateSpots({ + from, + to, + seconds, + settings, + }: { + from: Date; + to: Date; + seconds: number; + settings: SpotScheduleSettings; + }): Spot[] { + const dates = this.getSpotDates({ from, to, timezone: settings.timezone }); + const spots: Spot[] = []; + for (const date of dates) { + for (const interval of settings.intervals) { + if (interval.dayOfWeek.toLowerCase() === date.dayOfWeek.toLowerCase()) { + const spotFrom = fromZonedTime(`${date.date} ${interval.timeFrom}`, settings.timezone); + const spotTo = fromZonedTime(`${date.date} ${interval.timeTo}`, settings.timezone); + const generated = this.generateTimeSpots({ from: spotFrom, to: spotTo, seconds }); + spots.push(...generated.filter((spot) => spot.from >= from && spot.to <= to)); + } + } + } + return spots.sort((a, b) => a.from.getTime() - b.from.getTime()); + } + private getSpotDates({ + from, + to, + timezone, + }: { + from: Date; + to: Date; + timezone: string; + }): { date: string; dayOfWeek: string }[] { + const dates: { date: string; dayOfWeek: string }[] = []; + let currentDate = from; + while (currentDate < to) { + const tzCurrentDate = toZonedTime(currentDate, timezone); + dates.push({ + date: DateUtil.format(tzCurrentDate, 'yyyy-MM-dd'), + dayOfWeek: DateUtil.format(tzCurrentDate, 'EEEE'), + }); + currentDate = DateUtil.add(currentDate, { days: 1 }); + } + const tzDateTo = toZonedTime(to, timezone); + dates.push({ date: DateUtil.format(tzDateTo, 'yyyy-MM-dd'), dayOfWeek: DateUtil.format(tzDateTo, 'EEEE') }); + return Array.from(new Map(dates.map((d) => [d.date, d])).values()); + } + private generateTimeSpots({ from, to, seconds }: { from: Date; to: Date; seconds: number }): Spot[] { + const spots: Spot[] = []; + let spotFrom = from; + let spotTo = DateUtil.add(spotFrom, { seconds }); + + while (spotTo <= to) { + spots.push({ from: spotFrom, to: spotTo }); + spotFrom = spotTo; + spotTo = DateUtil.add(spotTo, { seconds }); + } + return spots; + } + + async update({ + accountId, + user, + appointmentId, + dto, + skipPermissionCheck, + event, + }: { + accountId: number; + user: User; + appointmentId: number; + dto: UpdateScheduleAppointmentDto; + skipPermissionCheck?: boolean; + event?: ServiceEvent; + }): Promise { + const appointment = await this.getOne({ accountId, user, appointmentId }); + + return this.updateAppointment({ accountId, user, appointment, dto, skipPermissionCheck, event }); + } + + async updateAppointment({ + accountId, + user, + appointment, + dto, + skipPermissionCheck, + event, + }: { + accountId: number; + user: User; + appointment: ScheduleAppointment; + dto: UpdateScheduleAppointmentDto; + skipPermissionCheck?: boolean; + event?: ServiceEvent; + }): Promise { + if (!skipPermissionCheck && !appointment.userRights.canEdit) { + throw new ForbiddenError(); + } + + if (!appointment.hasChanges(dto)) { + return appointment; + } + + appointment = appointment.update(dto); + + if (appointment.endDate <= appointment.startDate) { + throw new AppointmentDatesConflictError(); + } + + if (dto.checkIntersection && (await this.hasIntersect(appointment))) { + throw new AppointmentIntersectionError(); + } + + if (appointment.entityId) { + await this.checkEntityDayLimit(appointment); + } + + await this.repository.save(appointment); + + this.eventEmitter.emit( + SchedulerEventType.ScheduleAppointmentUpdated, + new SchedulerAppointmentUpdatedEvent({ + source: ScheduleAppointment.name, + accountId, + ownerId: appointment.ownerId, + scheduleId: appointment.scheduleId, + performerId: appointment.performerId, + appointmentId: appointment.id, + externalId: appointment.externalId, + title: appointment.title, + comment: appointment.comment, + startDate: appointment.startDate, + endDate: appointment.endDate, + status: appointment.status, + entityId: appointment.entityId, + prevEvent: event, + }), + ); + + return this.expandOne({ accountId, user, appointment, expand: ['prevAppointmentCount', 'entityInfo', 'order'] }); + } + + async changePerformer(accountId: number, oldPerformerId: number, newPerformerId: number): Promise { + await this.repository.update({ accountId, performerId: oldPerformerId }, { performerId: newPerformerId }); + } + + async delete({ + accountId, + user, + filter, + event, + }: { + accountId: number; + user?: User | null; + filter: DeleteFilter; + event?: ServiceEvent; + }): Promise { + const appointments = await this.createFindQb({ filter: { accountId, ...filter } }).getMany(); + for (const appointment of appointments) { + if (!user || (await this.authService.check({ action: 'delete', user, authorizable: appointment }))) { + await this.repository.delete({ accountId, id: appointment.id }); + + this.eventEmitter.emit( + SchedulerEventType.ScheduleAppointmentDeleted, + new SchedulerAppointmentEvent({ + source: ScheduleAppointment.name, + accountId, + ownerId: appointment.ownerId, + scheduleId: appointment.scheduleId, + performerId: appointment.performerId, + appointmentId: appointment.id, + entityId: appointment.entityId, + externalId: appointment.externalId, + prevEvent: event, + }), + ); + } + } + } + + async handleUpsertExt(event: SchedulerAppointmentExtUpsertEvent): Promise { + const schedule = await this.scheduleService.findOne({ + filter: { accountId: event.accountId, scheduleId: event.scheduleId }, + }); + const user = await this.userService.findOne({ accountId: event.accountId, id: event.ownerId }); + if (schedule && user) { + const { accountId, appointmentId, externalId } = event; + let appointment = externalId + ? await this.findOne({ filter: { accountId, scheduleId: schedule.id, externalId, showCanceled: true } }) + : undefined; + if (!appointment && appointmentId) { + appointment = await this.findOne({ + filter: { accountId, scheduleId: schedule.id, appointmentId, showCanceled: true }, + }); + } + + if (appointment) { + return this.updateAppointment({ + accountId, + user, + appointment, + dto: { + title: event.title, + comment: event.comment, + startDate: event.startDate.toISOString(), + endDate: event.endDate.toISOString(), + status: event.status, + externalId, + }, + skipPermissionCheck: true, + event, + }); + } else { + const performer = schedule.performers.find((p) => p.id === event.performerId); + if (performer) { + return this.create({ + accountId, + user, + dto: { + scheduleId: schedule.id, + startDate: event.startDate.toISOString(), + endDate: event.endDate.toISOString(), + status: event.status, + title: event.title, + comment: event.comment, + performerId: performer.id, + checkIntersection: false, + ownerId: event.ownerId, + externalId, + }, + skipPermissionCheck: true, + event, + }); + } + } + } + return null; + } + + private createFindQb({ + filter, + options, + }: { + filter: FindFilter; + options?: FindOptions; + }): SelectQueryBuilder { + const qb = this.repository + .createQueryBuilder('appointment') + .where('appointment.account_id = :accountId', { accountId: filter.accountId }); + + if (options?.joinPerformer) { + qb.leftJoinAndMapOne( + 'appointment.performer', + SchedulePerformer, + 'performer', + 'appointment.performer_id = performer.id', + ); + } + + if (filter.appointmentId) { + if (Array.isArray(filter.appointmentId)) { + qb.andWhere('appointment.id IN (:...appointmentIds)', { appointmentIds: filter.appointmentId }); + } else { + qb.andWhere('appointment.id = :appointmentId', { appointmentId: filter.appointmentId }); + } + } + + if (filter.externalId) { + qb.andWhere('appointment.external_id = :externalId', { externalId: filter.externalId }); + } + + if (filter.scheduleId) { + qb.andWhere('appointment.schedule_id = :scheduleId', { scheduleId: filter.scheduleId }); + } + if (filter.entityId) { + qb.andWhere('appointment.entity_id = :entityId', { entityId: filter.entityId }); + } + if (filter.performerId) { + qb.andWhere('appointment.performer_id = :performerId', { performerId: filter.performerId }); + } + if (filter.status) { + if (Array.isArray(filter.status)) { + qb.andWhere('appointment.status IN (:...statuses)', { statuses: filter.status }); + } else { + qb.andWhere('appointment.status = :status', { status: filter.status }); + } + } else if (!filter.showCanceled) { + qb.andWhere('appointment.status != :status', { status: ScheduleAppointmentStatus.Canceled }); + } + if (filter.title) { + qb.andWhere('appointment.title ILIKE :title', { title: `%${filter.title}%` }); + } + + if (options?.isSchedule) { + if (filter.from && filter.to) { + // eslint-disable-next-line max-len, prettier/prettier + qb.andWhere('appointment.start_date < :to', { to: filter.to }).andWhere('appointment.end_date > :from', { from: filter.from }); + } else if (filter.from) { + qb.andWhere('appointment.end_date >= :from', { from: filter.from }); + } else if (filter.to) { + qb.andWhere('appointment.start_date <= :to', { to: filter.to }); + } + } else { + if (filter.from) { + qb.andWhere('appointment.start_date >= :from', { from: filter.from }); + } + if (filter.to) { + qb.andWhere('appointment.end_date <= :to', { to: filter.to }); + } + } + if (filter.isNewbie && filter.from) { + qb.andWhere('appointment.entityId IS NOT NULL').andWhere( + `NOT EXISTS (SELECT 1 FROM schedule_appointment ia WHERE ia.schedule_id = appointment.schedule_id AND ` + + `ia.entity_id IS NOT NULL AND ia.entity_id = appointment.entity_id ` + + `AND ia.status != '${ScheduleAppointmentStatus.Canceled}' AND ia.end_date < appointment.start_date)`, + ); + } + if (filter.isNotScheduled) { + qb.andWhere('appointment.entityId IS NOT NULL').andWhere( + `NOT EXISTS (SELECT 1 FROM schedule_appointment fa WHERE fa.schedule_id = appointment.schedule_id AND ` + + `fa.entity_id IS NOT NULL AND fa.entity_id = appointment.entity_id ` + + `AND fa.status != '${ScheduleAppointmentStatus.Canceled}' AND fa.start_date > appointment.start_date)`, + ); + } + if (filter.isNotTookPlace) { + qb.andWhere('appointment.status IN (:...filterNotTookPlaceStatuses)', { + filterNotTookPlaceStatuses: [ScheduleAppointmentStatus.NotConfirmed, ScheduleAppointmentStatus.Confirmed], + }).andWhere('appointment.end_date < now()'); + } + + return qb; + } + + private async countPrevAppointment(accountId: number, appointment: ScheduleAppointment): Promise { + return appointment.entityId + ? this.repository + .createQueryBuilder('appointment') + .where('appointment.account_id = :accountId', { accountId }) + .andWhere('appointment.entity_id = :entityId', { entityId: appointment.entityId }) + .andWhere('appointment.schedule_id = :scheduleId', { scheduleId: appointment.scheduleId }) + .andWhere('appointment.status != :canceled', { canceled: ScheduleAppointmentStatus.Canceled }) + .andWhere('appointment.end_date <= :endDate', { endDate: appointment.endDate }) + .getCount() + : 1; + } + + private async hasIntersect({ + id, + accountId, + scheduleId, + performerId, + startDate, + endDate, + }: ScheduleAppointment): Promise { + const appointments = await this.createFindQb({ + filter: { accountId, scheduleId, performerId, from: startDate, to: endDate }, + options: { isSchedule: true }, + }).getMany(); + + return appointments.filter((a) => a.id !== id).length > 0; + } + + private async checkEntityDayLimit({ id, accountId, scheduleId, entityId, startDate, endDate }: ScheduleAppointment) { + const schedule = await this.scheduleService.findOne({ filter: { accountId, scheduleId } }); + if (schedule.oneEntityPerDay) { + const other = await this.createFindQb({ + filter: { + accountId, + scheduleId, + entityId, + from: DateUtil.startOf(startDate, 'day'), + to: DateUtil.endOf(endDate, 'day'), + }, + }).getOne(); + if (other && other.id !== id) { + throw new AppointmentDayLimitError({ appointmentId: other.id }); + } + } + } + + private async expandOne({ + accountId, + user, + appointment, + expand, + }: { + accountId: number; + user: User; + appointment: ScheduleAppointment; + expand: ExpandableField[]; + }): Promise { + if (expand.includes('prevAppointmentCount')) { + appointment.prevAppointmentCount = await this.countPrevAppointment(accountId, appointment); + } + if (expand.includes('order') && appointment.orderId) { + appointment.order = await this.orderService.findOne( + accountId, + user, + { orderId: appointment.orderId }, + { expand: ['items', 'shipments'] }, + ); + } + if (expand.includes('entityInfo') && appointment.entityId) { + appointment.entityInfo = await this.entityInfoService.findOne({ + accountId, + user, + entityId: appointment.entityId, + }); + } + return appointment; + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/types/expandable-field.ts b/backend/src/modules/scheduler/schedule-appointment/types/expandable-field.ts new file mode 100644 index 0000000..fc91247 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'prevAppointmentCount' | 'order' | 'entityInfo'; diff --git a/backend/src/modules/scheduler/schedule-appointment/types/index.ts b/backend/src/modules/scheduler/schedule-appointment/types/index.ts new file mode 100644 index 0000000..360c635 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/types/index.ts @@ -0,0 +1,4 @@ +export * from './expandable-field'; +export * from './schedule-appointment-result'; +export * from './schedule-appointment-statistic'; +export * from './spot'; diff --git a/backend/src/modules/scheduler/schedule-appointment/types/schedule-appointment-result.ts b/backend/src/modules/scheduler/schedule-appointment/types/schedule-appointment-result.ts new file mode 100644 index 0000000..b63e2ef --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/types/schedule-appointment-result.ts @@ -0,0 +1,23 @@ +import { PagingMeta } from '@/common'; + +import { ScheduleAppointmentResultDto } from '../dto/schedule-appointment-result.dto'; +import { type ScheduleAppointment } from '../entities/schedule-appointment.entity'; + +export class ScheduleAppointmentResult { + appointments: ScheduleAppointment[]; + offset: number; + total: number; + + constructor(appointments: ScheduleAppointment[], offset: number, total: number) { + this.appointments = appointments; + this.offset = offset; + this.total = total; + } + + public toDto(): ScheduleAppointmentResultDto { + return new ScheduleAppointmentResultDto( + this.appointments.map((appointment) => appointment.toDto()), + new PagingMeta(this.offset, this.total), + ); + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/types/schedule-appointment-statistic.ts b/backend/src/modules/scheduler/schedule-appointment/types/schedule-appointment-statistic.ts new file mode 100644 index 0000000..add5e1d --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/types/schedule-appointment-statistic.ts @@ -0,0 +1,40 @@ +import { ScheduleAppointmentStatus } from '../../common'; +import { ScheduleAppointmentStatisticDto } from '../dto'; + +export class ScheduleAppointmentStatistic { + total: number; + statuses: Record; + newbies: number; + notScheduled: number; + notTookPlace: number; + + constructor({ + total, + statuses, + newbies, + notScheduled, + notTookPlace, + }: { + total: number; + statuses: Record; + newbies: number; + notScheduled: number; + notTookPlace: number; + }) { + this.total = total; + this.statuses = statuses; + this.newbies = newbies; + this.notScheduled = notScheduled; + this.notTookPlace = notTookPlace; + } + + public toDto(): ScheduleAppointmentStatisticDto { + return { + total: this.total, + statuses: this.statuses, + newbies: this.newbies, + notScheduled: this.notScheduled, + notTookPlace: this.notTookPlace, + }; + } +} diff --git a/backend/src/modules/scheduler/schedule-appointment/types/spot.ts b/backend/src/modules/scheduler/schedule-appointment/types/spot.ts new file mode 100644 index 0000000..d72a7ae --- /dev/null +++ b/backend/src/modules/scheduler/schedule-appointment/types/spot.ts @@ -0,0 +1,4 @@ +export class Spot { + from: Date; + to: Date; +} diff --git a/backend/src/modules/scheduler/schedule-performer/dtos/create-schedule-performer.dto.ts b/backend/src/modules/scheduler/schedule-performer/dtos/create-schedule-performer.dto.ts new file mode 100644 index 0000000..1c5cbe9 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/dtos/create-schedule-performer.dto.ts @@ -0,0 +1,4 @@ +import { OmitType } from '@nestjs/swagger'; +import { SchedulePerformerDto } from './schedule-performer.dto'; + +export class CreateSchedulePerformerDto extends OmitType(SchedulePerformerDto, ['id'] as const) {} diff --git a/backend/src/modules/scheduler/schedule-performer/dtos/index.ts b/backend/src/modules/scheduler/schedule-performer/dtos/index.ts new file mode 100644 index 0000000..e5c9134 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './create-schedule-performer.dto'; +export * from './schedule-performer.dto'; +export * from './update-schedule-performer.dto'; diff --git a/backend/src/modules/scheduler/schedule-performer/dtos/schedule-performer.dto.ts b/backend/src/modules/scheduler/schedule-performer/dtos/schedule-performer.dto.ts new file mode 100644 index 0000000..a463149 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/dtos/schedule-performer.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { SchedulePerformerType } from '../enums'; + +export class SchedulePerformerDto { + @ApiProperty({ description: 'Schedule performer ID' }) + @IsNumber() + id: number; + + @ApiProperty({ enum: SchedulePerformerType, description: 'Schedule performer type' }) + @IsEnum(SchedulePerformerType) + type: SchedulePerformerType; + + @ApiProperty({ nullable: true, description: 'User ID if performer is User' }) + @IsOptional() + @IsNumber() + userId?: number | null; + + @ApiProperty({ nullable: true, description: 'Department ID if performer is Department' }) + @IsOptional() + @IsNumber() + departmentId?: number | null; +} diff --git a/backend/src/modules/scheduler/schedule-performer/dtos/update-schedule-performer.dto.ts b/backend/src/modules/scheduler/schedule-performer/dtos/update-schedule-performer.dto.ts new file mode 100644 index 0000000..85a4c09 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/dtos/update-schedule-performer.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from '@nestjs/swagger'; + +import { CreateSchedulePerformerDto } from './create-schedule-performer.dto'; + +export class UpdateSchedulePerformerDto extends PartialType(CreateSchedulePerformerDto) {} diff --git a/backend/src/modules/scheduler/schedule-performer/entities/index.ts b/backend/src/modules/scheduler/schedule-performer/entities/index.ts new file mode 100644 index 0000000..f11b72b --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/entities/index.ts @@ -0,0 +1 @@ +export * from './schedule-performer.entity'; diff --git a/backend/src/modules/scheduler/schedule-performer/entities/schedule-performer.entity.ts b/backend/src/modules/scheduler/schedule-performer/entities/schedule-performer.entity.ts new file mode 100644 index 0000000..d264425 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/entities/schedule-performer.entity.ts @@ -0,0 +1,64 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { CreateSchedulePerformerDto, UpdateSchedulePerformerDto, SchedulePerformerDto } from '../dtos'; +import { SchedulePerformerType } from '../enums'; + +@Entity() +export class SchedulePerformer { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + scheduleId: number; + + @Column() + type: SchedulePerformerType; + + @Column({ nullable: true }) + userId: number | null; + + @Column({ nullable: true }) + departmentId: number | null; + + constructor( + accountId: number, + scheduleId: number, + type: SchedulePerformerType, + userId: number | null, + departmentId: number | null, + ) { + this.accountId = accountId; + this.scheduleId = scheduleId; + this.type = type; + this.userId = userId; + this.departmentId = departmentId; + } + + public static fromDto(accountId: number, scheduleId: number, dto: CreateSchedulePerformerDto): SchedulePerformer { + return new SchedulePerformer(accountId, scheduleId, dto.type, dto.userId, dto.departmentId); + } + + public update(dto: UpdateSchedulePerformerDto): SchedulePerformer { + this.type = dto.type !== undefined ? dto.type : this.type; + this.userId = dto.userId !== undefined ? dto.userId : this.userId; + this.departmentId = dto.departmentId !== undefined ? dto.departmentId : this.departmentId; + + return this; + } + + public toDto(): SchedulePerformerDto { + return { id: this.id, type: this.type, userId: this.userId, departmentId: this.departmentId }; + } + + public equals(other: { type: SchedulePerformerType; userId?: number | null; departmentId?: number | null }): boolean { + switch (other.type) { + case SchedulePerformerType.Department: + return this.departmentId === other.departmentId; + case SchedulePerformerType.User: + return this.userId === other.userId; + } + } +} diff --git a/backend/src/modules/scheduler/schedule-performer/enums/index.ts b/backend/src/modules/scheduler/schedule-performer/enums/index.ts new file mode 100644 index 0000000..e825a7c --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/enums/index.ts @@ -0,0 +1 @@ +export * from './schedule-performer-type.enum'; diff --git a/backend/src/modules/scheduler/schedule-performer/enums/schedule-performer-type.enum.ts b/backend/src/modules/scheduler/schedule-performer/enums/schedule-performer-type.enum.ts new file mode 100644 index 0000000..b797684 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/enums/schedule-performer-type.enum.ts @@ -0,0 +1,4 @@ +export enum SchedulePerformerType { + User = 'user', + Department = 'department', +} diff --git a/backend/src/modules/scheduler/schedule-performer/index.ts b/backend/src/modules/scheduler/schedule-performer/index.ts new file mode 100644 index 0000000..f5ba5b5 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/index.ts @@ -0,0 +1,5 @@ +export * from './dtos'; +export * from './entities'; +export * from './enums'; +export * from './schedule-performer.handler'; +export * from './schedule-performer.service'; diff --git a/backend/src/modules/scheduler/schedule-performer/schedule-performer.handler.ts b/backend/src/modules/scheduler/schedule-performer/schedule-performer.handler.ts new file mode 100644 index 0000000..c5beaa3 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/schedule-performer.handler.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { DepartmentDeletedEvent, IamEventType, UserDeletedEvent } from '@/modules/iam/common'; + +import { SchedulePerformerService } from './schedule-performer.service'; + +@Injectable() +export class SchedulePerformerHandler { + constructor(private readonly service: SchedulePerformerService) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + await this.service.deletePerformer({ + accountId: event.accountId, + userId: event.userId, + newId: event.newUserId, + }); + } + + @OnEvent(IamEventType.DepartmentDeleted, { async: true }) + public async onDepartmentDeleted(event: DepartmentDeletedEvent) { + await this.service.deletePerformer({ + accountId: event.accountId, + departmentId: event.departmentId, + newId: event.newDepartmentId, + }); + } +} diff --git a/backend/src/modules/scheduler/schedule-performer/schedule-performer.service.ts b/backend/src/modules/scheduler/schedule-performer/schedule-performer.service.ts new file mode 100644 index 0000000..9eb427d --- /dev/null +++ b/backend/src/modules/scheduler/schedule-performer/schedule-performer.service.ts @@ -0,0 +1,214 @@ +import { forwardRef, Inject, Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { BadRequestError, NotFoundError } from '@/common'; + +import { SchedulePerformerDeletedEvent, SchedulePerformerEvent, SchedulerEventType } from '../common'; +import { ScheduleAppointmentService } from '../schedule-appointment/schedule-appointment.service'; + +import { CreateSchedulePerformerDto, UpdateSchedulePerformerDto } from './dtos'; +import { SchedulePerformer } from './entities'; + +interface FindFilter { + accountId: number; + performerId?: number; + userId?: number; + departmentId?: number; + scheduleId?: number; +} + +@Injectable() +export class SchedulePerformerService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(SchedulePerformer) + private readonly repository: Repository, + @Inject(forwardRef(() => ScheduleAppointmentService)) + private readonly appointmentService: ScheduleAppointmentService, + ) {} + + public async create({ + accountId, + scheduleId, + dto, + }: { + accountId: number; + scheduleId: number; + dto: CreateSchedulePerformerDto; + }): Promise { + const performer = await this.repository.save(SchedulePerformer.fromDto(accountId, scheduleId, dto)); + + this.eventEmitter.emit( + SchedulerEventType.SchedulePerformerCreated, + new SchedulePerformerEvent({ source: SchedulePerformer.name, accountId, scheduleId, performerId: performer.id }), + ); + + return performer; + } + public async createMany({ + accountId, + scheduleId, + dtos, + }: { + accountId: number; + scheduleId: number; + dtos: CreateSchedulePerformerDto[]; + }): Promise { + return Promise.all(dtos.map((dto) => this.create({ accountId, scheduleId, dto }))); + } + + public async findOne(filter: FindFilter): Promise { + return this.createFindQb(filter).getOne(); + } + public async findMany(filter: FindFilter): Promise { + return await this.createFindQb(filter).orderBy('sp.id').getMany(); + } + + public async processMany({ + accountId, + scheduleId, + current, + dtos, + }: { + accountId: number; + scheduleId: number; + current: SchedulePerformer[]; + dtos: CreateSchedulePerformerDto[]; + }): Promise { + const created = dtos.filter((dto) => !current.some((p) => p.equals(dto))); + const deleted = current.filter((p) => !dtos.some((dto) => p.equals(dto))); + + if (created.length) { + current.push(...(await this.createMany({ accountId, scheduleId, dtos: created }))); + } + + if (deleted.length) { + await Promise.all(deleted.map((p) => this.delete({ accountId, scheduleId, performerId: p.id }))); + } + + return current.filter((p) => !deleted.some((r) => p.id === r.id)); + } + + public async update({ + accountId, + scheduleId, + performerId, + dto, + }: { + accountId: number; + scheduleId: number; + performerId: number; + dto: UpdateSchedulePerformerDto; + }): Promise { + const performer = await this.findOne({ accountId, scheduleId, performerId }); + if (!performer) { + throw NotFoundError.withId(SchedulePerformer, performerId); + } + + await this.repository.save(performer.update(dto)); + + this.eventEmitter.emit( + SchedulerEventType.SchedulePerformerUpdated, + new SchedulePerformerEvent({ source: SchedulePerformer.name, accountId, scheduleId, performerId }), + ); + + return performer; + } + + public async delete({ + accountId, + scheduleId, + performerId, + newPerformerId, + }: { + accountId: number; + scheduleId: number; + performerId: number; + newPerformerId?: number; + }) { + if (newPerformerId) { + await this.appointmentService.changePerformer(accountId, performerId, newPerformerId); + } + await this.repository.delete({ accountId, scheduleId, id: performerId }); + + this.eventEmitter.emit( + SchedulerEventType.SchedulePerformerDeleted, + new SchedulePerformerDeletedEvent({ + source: SchedulePerformer.name, + accountId, + scheduleId, + performerId, + newPerformerId, + }), + ); + } + + public async deletePerformer({ + accountId, + userId, + departmentId, + newId, + }: { + accountId: number; + userId?: number | null; + departmentId?: number | null; + newId?: number | null; + }) { + if ((!userId && !departmentId) || (userId && departmentId)) { + throw new BadRequestError('userId or departmentId must be specified'); + } + const performers = await this.findMany({ accountId, userId, departmentId }); + if (performers.length) { + if (newId) { + await Promise.all( + performers.map(async (performer) => { + const coPerformers = await this.findMany({ accountId, scheduleId: performer.scheduleId }); + const newPerformer = coPerformers.find( + (p) => (userId && p.userId === newId) || (departmentId && p.departmentId === newId), + ); + if (newPerformer) { + await this.delete({ + accountId, + scheduleId: performer.scheduleId, + performerId: performer.id, + newPerformerId: newPerformer.id, + }); + } else { + await this.update({ + accountId, + scheduleId: performer.scheduleId, + performerId: performer.id, + dto: { userId: userId ? newId : undefined, departmentId: departmentId ? newId : undefined }, + }); + } + }), + ); + } else { + await Promise.all( + performers.map((p) => this.delete({ accountId, scheduleId: p.scheduleId, performerId: p.id })), + ); + } + } + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('sp') + .where('sp.account_id = :accountId', { accountId: filter.accountId }); + if (filter?.performerId) { + qb.andWhere('sp.id = :id', { id: filter.performerId }); + } + if (filter?.userId) { + qb.andWhere('sp.user_id = :userId', { userId: filter.userId }); + } + if (filter?.departmentId) { + qb.andWhere('sp.department_id = :departmentId', { departmentId: filter.departmentId }); + } + if (filter?.scheduleId) { + qb.andWhere('sp.schedule_id = :scheduleId', { scheduleId: filter.scheduleId }); + } + return qb; + } +} diff --git a/backend/src/modules/scheduler/schedule-reporting/dto/index.ts b/backend/src/modules/scheduler/schedule-reporting/dto/index.ts new file mode 100644 index 0000000..a8335fa --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/dto/index.ts @@ -0,0 +1,3 @@ +export * from './schedule-report-filter.dto'; +export * from './schedule-report-row.dto'; +export * from './schedule-report.dto'; diff --git a/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report-filter.dto.ts b/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report-filter.dto.ts new file mode 100644 index 0000000..ff88d73 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report-filter.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter } from '@/common'; +import { ScheduleReportType } from '../enums'; + +export class ScheduleReportFilterDto { + @ApiProperty({ enum: ScheduleReportType, description: 'Report type' }) + @IsEnum(ScheduleReportType) + type: ScheduleReportType; + + @ApiProperty({ description: 'Schedule ID' }) + @IsNumber() + scheduleId: number; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Board IDs filter' }) + @IsOptional() + @IsNumber({}, { each: true }) + boardIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs filter' }) + @IsOptional() + @IsNumber({}, { each: true }) + userIds?: number[] | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period filter' }) + @IsOptional() + period?: DatePeriodFilter | null; +} diff --git a/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report-row.dto.ts b/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report-row.dto.ts new file mode 100644 index 0000000..2f77109 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report-row.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; + +import { QuantityAmountDto } from '@/common'; + +export class ScheduleReportRowDto { + @ApiProperty({ nullable: true, description: 'Owner ID' }) + @IsOptional() + @IsNumber() + ownerId: number | null; + + @ApiProperty({ nullable: true, description: 'Owner name' }) + @IsOptional() + @IsString() + ownerName: string | null; + + @ApiProperty({ type: QuantityAmountDto, description: 'Sold' }) + sold: QuantityAmountDto; + + @ApiProperty({ description: 'All' }) + @IsNumber() + all: number; + + @ApiProperty({ description: 'Scheduled' }) + @IsNumber() + scheduled: number; + + @ApiProperty({ description: 'Confirmed' }) + @IsNumber() + confirmed: number; + + @ApiProperty({ description: 'Completed' }) + @IsNumber() + completed: number; + + @ApiProperty({ description: 'Canceled' }) + @IsNumber() + canceled: number; +} diff --git a/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report.dto.ts b/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report.dto.ts new file mode 100644 index 0000000..53cb331 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/dto/schedule-report.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ScheduleReportRowDto } from './schedule-report-row.dto'; + +export class ScheduleReportDto { + @ApiProperty({ type: [ScheduleReportRowDto], description: 'Rows' }) + rows: ScheduleReportRowDto[]; + + @ApiProperty({ type: ScheduleReportRowDto, description: 'Total' }) + total: ScheduleReportRowDto; +} diff --git a/backend/src/modules/scheduler/schedule-reporting/enums/index.ts b/backend/src/modules/scheduler/schedule-reporting/enums/index.ts new file mode 100644 index 0000000..7803868 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/enums/index.ts @@ -0,0 +1 @@ +export * from './schedule-report-type.enum'; diff --git a/backend/src/modules/scheduler/schedule-reporting/enums/schedule-report-type.enum.ts b/backend/src/modules/scheduler/schedule-reporting/enums/schedule-report-type.enum.ts new file mode 100644 index 0000000..1617c60 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/enums/schedule-report-type.enum.ts @@ -0,0 +1,6 @@ +export enum ScheduleReportType { + Client = 'client', + Department = 'department', + Owner = 'owner', + Performer = 'performer', +} diff --git a/backend/src/modules/scheduler/schedule-reporting/index.ts b/backend/src/modules/scheduler/schedule-reporting/index.ts new file mode 100644 index 0000000..d19252e --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './enums'; +export * from './schedule-reporting.controller'; +export * from './schedule-reporting.service'; +export * from './types'; diff --git a/backend/src/modules/scheduler/schedule-reporting/schedule-reporting.controller.ts b/backend/src/modules/scheduler/schedule-reporting/schedule-reporting.controller.ts new file mode 100644 index 0000000..27e3ef6 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/schedule-reporting.controller.ts @@ -0,0 +1,27 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { ScheduleReportDto, ScheduleReportFilterDto } from './dto'; +import { ScheduleReportingService } from './schedule-reporting.service'; + +@ApiTags('scheduler/reporting') +@Controller('reporting') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class ScheduleReportingController { + constructor(private readonly service: ScheduleReportingService) {} + + @ApiOperation({ summary: 'Get schedule report', description: 'Get schedule report' }) + @ApiBody({ type: ScheduleReportFilterDto, description: 'Schedule report filter' }) + @ApiOkResponse({ description: 'Schedule report', type: ScheduleReportDto }) + @Post('schedule') + public async getReport(@CurrentAuth() { accountId, user }: AuthData, @Body() filter: ScheduleReportFilterDto) { + return this.service.getReport({ accountId, user, filter }); + } +} diff --git a/backend/src/modules/scheduler/schedule-reporting/schedule-reporting.service.ts b/backend/src/modules/scheduler/schedule-reporting/schedule-reporting.service.ts new file mode 100644 index 0000000..5e909c5 --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/schedule-reporting.service.ts @@ -0,0 +1,249 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { DatePeriod, ForbiddenError, intersection, propagateData, QuantityAmount } from '@/common'; +import { AuthorizationService } from '@/modules/iam/authorization'; +import { DepartmentService } from '@/modules/iam/department'; +import { User } from '@/modules/iam/user/entities'; +import { FieldType } from '@/modules/entity/entity-field/common'; +import { BoardStageService, GroupedStages } from '@/CRM/board-stage'; + +import { ScheduleAppointmentStatus } from '../common'; +import { Schedule, ScheduleService } from '../schedule'; +import { ScheduleAppointment } from '../schedule-appointment'; +import { SchedulePerformer, SchedulePerformerType } from '../schedule-performer'; + +import { ScheduleReportFilterDto } from './dto'; +import { ScheduleReportType } from './enums'; +import { ScheduleReport, ScheduleReportRow } from './types'; + +interface ScheduleReportFilter { + stages: GroupedStages; + boardIds?: number[]; + period?: DatePeriod; + userIds?: number[]; + departmentIds?: number[]; +} + +interface RawReportRow { + owner_id: number; + owner_name: string; + sold_quantity: number; + sold_amount: number; + all: number; + scheduled: number; + confirmed: number; + completed: number; + canceled: number; +} + +@Injectable() +export class ScheduleReportingService { + constructor( + @InjectRepository(ScheduleAppointment) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly departmentService: DepartmentService, + private readonly stageService: BoardStageService, + private readonly scheduleService: ScheduleService, + ) {} + + public async getReport({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: ScheduleReportFilterDto; + }) { + const { allow, userIds, departmentIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: Schedule.getAuthorizable(filter.scheduleId), + }); + if (!allow) { + throw new ForbiddenError(); + } + + const { entityTypeId } = await this.scheduleService.findOne({ + filter: { accountId, scheduleId: filter.scheduleId }, + }); + + const rowFilter = { + stages: await this.stageService.getGroupedByType({ + accountId, + entityTypeId, + boardId: filter.boardIds?.length ? filter.boardIds : undefined, + }), + boardIds: filter.boardIds, + period: filter.period ? DatePeriod.fromFilter(filter.period) : undefined, + userIds: filter.userIds?.length ? intersection(filter.userIds, userIds) : userIds, + departmentIds, + }; + const rows = await this.getRows({ + accountId, + scheduleId: filter.scheduleId, + type: filter.type, + filter: rowFilter, + isTotal: false, + }); + const total = await this.getRows({ + accountId, + scheduleId: filter.scheduleId, + type: filter.type, + filter: rowFilter, + isTotal: true, + }); + + const report = new ScheduleReport(rows, total.values().next().value); + + if (filter.type === ScheduleReportType.Department && report.rows.size) { + const hierarchy = await this.departmentService.getHierarchy({ accountId }); + + if (hierarchy.length) { + propagateData(hierarchy, report.rows, ScheduleReportRow.empty); + } + } + + return report; + } + + private async getRows({ + accountId, + scheduleId, + type, + filter, + isTotal, + }: { + accountId: number; + scheduleId: number; + type: ScheduleReportType; + filter: ScheduleReportFilter; + isTotal: boolean; + }): Promise> { + const rowMap = new Map(); + const rows = await this.createQb({ accountId, scheduleId, type, filter, isTotal }).getRawMany(); + for (const row of rows) { + rowMap.set( + row.owner_id, + new ScheduleReportRow( + row.owner_id, + row.owner_name, + new QuantityAmount(row.sold_quantity, row.sold_amount ? Number(row.sold_amount) : 0), + row.all, + row.scheduled, + row.confirmed, + row.completed, + row.canceled, + ), + ); + } + + return rowMap; + } + + private createQb({ + accountId, + scheduleId, + type, + filter, + isTotal, + }: { + accountId: number; + scheduleId: number; + type: ScheduleReportType; + filter: ScheduleReportFilter; + isTotal: boolean; + }) { + const qb = this.repository + .createQueryBuilder('sa') + .innerJoin(SchedulePerformer, 'sp', 'sa.performer_id = sp.id') + .leftJoin('users', 'pu', 'sp.user_id = pu.id') + .leftJoin('users', 'ou', 'sa.owner_id = ou.id') + .leftJoin('entity', 'e', 'sa.entity_id = e.id') + .leftJoin('users', 'eu', 'e.responsible_user_id = eu.id') + .select(`count(*)::int`, 'all') + .addSelect(`count(*) filter (where sa.status = '${ScheduleAppointmentStatus.NotConfirmed}')::int`, 'scheduled') + .addSelect(`count(*) filter (where sa.status = '${ScheduleAppointmentStatus.Confirmed}')::int`, 'confirmed') + .addSelect(`count(*) filter (where sa.status = '${ScheduleAppointmentStatus.Completed}')::int`, 'completed') + .addSelect(`count(*) filter (where sa.status = '${ScheduleAppointmentStatus.Canceled}')::int`, 'canceled') + .where('sa.account_id = :accountId', { accountId }) + .andWhere('sa.schedule_id = :scheduleId', { scheduleId }); + + if (isTotal) { + qb.addSelect('0::int', 'owner_id'); + } else { + switch (type) { + case ScheduleReportType.Performer: + qb.addSelect('sp.user_id', 'owner_id').groupBy('sp.user_id'); + break; + case ScheduleReportType.Owner: + qb.addSelect('sa.owner_id', 'owner_id').groupBy('sa.owner_id'); + break; + case ScheduleReportType.Department: + qb.addSelect( + `case when sp.type = '${SchedulePerformerType.Department}' then sp.department_id else pu.department_id end`, + 'owner_id', + ).groupBy( + `case when sp.type = '${SchedulePerformerType.Department}' then sp.department_id else pu.department_id end`, + ); + break; + case ScheduleReportType.Client: + qb.addSelect('e.id', 'owner_id').addSelect('e.name', 'owner_name').groupBy('e.id').addGroupBy('e.name'); + break; + } + } + + if (filter.stages.won?.length) { + qb.addSelect( + `count(*) filter (where e.stage_id = any(array[${filter.stages.won.join(',')}]))::int`, + 'sold_quantity', + ) + .addSelect(`sum(cast(fv.payload::json->>'value' as decimal))::decimal`, 'sold_amount') + .leftJoin('field_value', 'fv', `fv.entity_id = e.id and fv.field_type = '${FieldType.Value}'`); + } + + if (filter.userIds?.length) { + if (type === ScheduleReportType.Performer) { + qb.andWhere('sp.user_id IN (:...userIds)', { userIds: filter.userIds }); + } else if (type === ScheduleReportType.Owner) { + qb.andWhere('sa.owner_id IN (:...userIds)', { userIds: filter.userIds }); + } else if (type === ScheduleReportType.Client) { + qb.andWhere('e.responsible_user_id IN (:...userIds)', { userIds: filter.userIds }); + } + } + + if (filter.departmentIds?.length) { + if (type === ScheduleReportType.Performer) { + qb.andWhere('pu.department_id IN (:...departmentIds)', { departmentIds: filter.departmentIds }); + } else if (type === ScheduleReportType.Owner) { + qb.andWhere('ou.department_id IN (:...departmentIds)', { departmentIds: filter.departmentIds }); + } else if (type === ScheduleReportType.Client) { + qb.andWhere('eu.department_id IN (:...departmentIds)', { departmentIds: filter.departmentIds }); + } else if (type === ScheduleReportType.Department) { + qb.andWhere( + // eslint-disable-next-line max-len + `case when sp.type = '${SchedulePerformerType.Department}' then sp.department_id else pu.department_id end IN (:...departmentIds)`, + { departmentIds: filter.departmentIds }, + ); + } + } + + if (filter.period) { + if (filter.period.from) { + qb.andWhere('sa.start_date >= :from', { from: filter.period.from }); + } + if (filter.period.to) { + qb.andWhere('sa.end_date <= :to', { to: filter.period.to }); + } + } + + if (filter.boardIds?.length) { + qb.andWhere('e.board_id IN (:...boardIds)', { boardIds: filter.boardIds }); + } + + return qb; + } +} diff --git a/backend/src/modules/scheduler/schedule-reporting/types/index.ts b/backend/src/modules/scheduler/schedule-reporting/types/index.ts new file mode 100644 index 0000000..6c72a3a --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/types/index.ts @@ -0,0 +1,2 @@ +export * from './schedule-report-row'; +export * from './schedule-report'; diff --git a/backend/src/modules/scheduler/schedule-reporting/types/schedule-report-row.ts b/backend/src/modules/scheduler/schedule-reporting/types/schedule-report-row.ts new file mode 100644 index 0000000..91baa9e --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/types/schedule-report-row.ts @@ -0,0 +1,62 @@ +import { QuantityAmount } from '@/common'; + +import { ScheduleReportRowDto } from '../dto'; + +export class ScheduleReportRow { + ownerId: number; + ownerName: string | null; + sold: QuantityAmount; + all: number; + scheduled: number; + confirmed: number; + completed: number; + canceled: number; + + constructor( + ownerId: number, + ownerName: string | null, + sold: QuantityAmount, + all: number, + scheduled: number, + confirmed: number, + completed: number, + canceled: number, + ) { + this.ownerId = ownerId; + this.ownerName = ownerName; + this.sold = sold; + this.all = all; + this.scheduled = scheduled; + this.confirmed = confirmed; + this.completed = completed; + this.canceled = canceled; + } + + public static empty(ownerId: number): ScheduleReportRow { + return new ScheduleReportRow(ownerId, null, QuantityAmount.empty(), 0, 0, 0, 0, 0); + } + + public toDto(): ScheduleReportRowDto { + return { + ownerId: this.ownerId, + ownerName: this.ownerName, + sold: this.sold.toDto(), + all: this.all, + scheduled: this.scheduled, + confirmed: this.confirmed, + completed: this.completed, + canceled: this.canceled, + }; + } + + public add(row: ScheduleReportRow): ScheduleReportRow { + this.sold.add(row.sold); + this.all += row.all; + this.scheduled += row.scheduled; + this.confirmed += row.confirmed; + this.completed += row.completed; + this.canceled += row.canceled; + + return this; + } +} diff --git a/backend/src/modules/scheduler/schedule-reporting/types/schedule-report.ts b/backend/src/modules/scheduler/schedule-reporting/types/schedule-report.ts new file mode 100644 index 0000000..81b5cab --- /dev/null +++ b/backend/src/modules/scheduler/schedule-reporting/types/schedule-report.ts @@ -0,0 +1,23 @@ +import { ScheduleReportDto } from '../dto'; +import { ScheduleReportRow } from './schedule-report-row'; + +export class ScheduleReport { + rows: Map; + total: ScheduleReportRow; + + constructor(rows: Map, total: ScheduleReportRow) { + this.rows = rows; + this.total = total; + } + + public static createEmptyRow(ownerId: number): ScheduleReportRow { + return ScheduleReportRow.empty(ownerId); + } + + public toDto(): ScheduleReportDto { + return { + rows: Array.from(this.rows.values()).map((row) => row.toDto()), + total: this.total.toDto(), + }; + } +} diff --git a/backend/src/modules/scheduler/schedule/dto/create-schedule.dto.ts b/backend/src/modules/scheduler/schedule/dto/create-schedule.dto.ts new file mode 100644 index 0000000..47ba647 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/dto/create-schedule.dto.ts @@ -0,0 +1,16 @@ +import { ApiPropertyOptional, OmitType } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { ScheduleDto } from './schedule.dto'; +import { CreateSchedulePerformerDto } from '../../schedule-performer'; + +export class CreateScheduleDto extends OmitType(ScheduleDto, ['id', 'createdAt', 'performers'] as const) { + @ApiPropertyOptional({ + type: [CreateSchedulePerformerDto], + nullable: true, + description: 'Available performers for schedule', + }) + @IsOptional() + @IsArray() + performers: CreateSchedulePerformerDto[] | null; +} diff --git a/backend/src/modules/scheduler/schedule/dto/index.ts b/backend/src/modules/scheduler/schedule/dto/index.ts new file mode 100644 index 0000000..1e82fdb --- /dev/null +++ b/backend/src/modules/scheduler/schedule/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-schedule.dto'; +export * from './schedule-filter.dto'; +export * from './schedule-time-interval.dto'; +export * from './schedule.dto'; +export * from './update-schedule.dto'; diff --git a/backend/src/modules/scheduler/schedule/dto/schedule-filter.dto.ts b/backend/src/modules/scheduler/schedule/dto/schedule-filter.dto.ts new file mode 100644 index 0000000..ced0d7c --- /dev/null +++ b/backend/src/modules/scheduler/schedule/dto/schedule-filter.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class ScheduleFilterDto { + @ApiPropertyOptional({ description: 'EntityType ID' }) + @IsOptional() + @IsNumber() + entityTypeId?: number; +} diff --git a/backend/src/modules/scheduler/schedule/dto/schedule-time-interval.dto.ts b/backend/src/modules/scheduler/schedule/dto/schedule-time-interval.dto.ts new file mode 100644 index 0000000..72c3015 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/dto/schedule-time-interval.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ScheduleTimeIntervalDto { + @ApiProperty({ description: 'Day of week', examples: ['Monday', 'Sunday'] }) + @IsString() + dayOfWeek: string; + + @ApiProperty({ description: 'Interval time from', examples: ['09:00', '21:00'] }) + @IsString() + timeFrom: string; + + @ApiProperty({ description: 'Interval time to', examples: ['09:00', '21:00'] }) + @IsString() + timeTo: string; +} diff --git a/backend/src/modules/scheduler/schedule/dto/schedule.dto.ts b/backend/src/modules/scheduler/schedule/dto/schedule.dto.ts new file mode 100644 index 0000000..44c051e --- /dev/null +++ b/backend/src/modules/scheduler/schedule/dto/schedule.dto.ts @@ -0,0 +1,70 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { SchedulePerformerDto } from '../../schedule-performer'; +import { ScheduleType } from '../enums'; +import { ScheduleTimeIntervalDto } from './schedule-time-interval.dto'; + +export class ScheduleDto { + @ApiProperty({ description: 'Schedule ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Schedule name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Schedule icon name' }) + @IsString() + icon: string; + + @ApiProperty({ enum: ScheduleType, description: 'Schedule type' }) + @IsEnum(ScheduleType) + type: ScheduleType; + + @ApiProperty({ description: 'Time slot size in seconds for Board schedule' }) + @IsNumber() + timePeriod: number; + + @ApiPropertyOptional({ nullable: true, description: 'Appointments limit for one time slot for Board schedule' }) + @IsOptional() + @IsNumber() + appointmentLimit?: number | null; + + @ApiPropertyOptional({ description: 'Time buffer before appointment in seconds' }) + @IsOptional() + @IsNumber() + timeBufferBefore?: number | null; + + @ApiPropertyOptional({ description: 'Time buffer after appointment in seconds' }) + @IsOptional() + @IsNumber() + timeBufferAfter?: number | null; + + @ApiPropertyOptional({ description: 'Allow only one entity per day' }) + @IsOptional() + @IsBoolean() + oneEntityPerDay?: boolean; + + @ApiPropertyOptional({ nullable: true, description: 'Linked EntityType ID' }) + @IsOptional() + @IsNumber() + entityTypeId?: number | null; + + @ApiPropertyOptional({ nullable: true, description: 'Linked ProductsSection ID' }) + @IsOptional() + @IsNumber() + productsSectionId?: number | null; + + @ApiProperty({ description: 'Date and time when the schedule was created in ISO format' }) + @IsString() + createdAt: string; + + @ApiProperty({ type: [SchedulePerformerDto], description: 'Schedule performers' }) + @IsArray() + performers: SchedulePerformerDto[]; + + @ApiPropertyOptional({ type: [ScheduleTimeIntervalDto], description: 'Schedule time intervals' }) + @IsOptional() + intervals?: ScheduleTimeIntervalDto[] | null; +} diff --git a/backend/src/modules/scheduler/schedule/dto/update-schedule.dto.ts b/backend/src/modules/scheduler/schedule/dto/update-schedule.dto.ts new file mode 100644 index 0000000..530fa75 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/dto/update-schedule.dto.ts @@ -0,0 +1,16 @@ +import { ApiPropertyOptional, OmitType, PartialType } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { ScheduleDto } from './schedule.dto'; +import { CreateSchedulePerformerDto } from '../../schedule-performer'; + +export class UpdateScheduleDto extends PartialType(OmitType(ScheduleDto, ['id', 'createdAt', 'performers'] as const)) { + @ApiPropertyOptional({ + type: [CreateSchedulePerformerDto], + nullable: true, + description: 'Available performers for schedule', + }) + @IsOptional() + @IsArray() + performers?: CreateSchedulePerformerDto[] | null; +} diff --git a/backend/src/modules/scheduler/schedule/entities/index.ts b/backend/src/modules/scheduler/schedule/entities/index.ts new file mode 100644 index 0000000..601a275 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/entities/index.ts @@ -0,0 +1,2 @@ +export * from './schedule-time-interval.entity'; +export * from './schedule.entity'; diff --git a/backend/src/modules/scheduler/schedule/entities/schedule-time-interval.entity.ts b/backend/src/modules/scheduler/schedule/entities/schedule-time-interval.entity.ts new file mode 100644 index 0000000..8f061f3 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/entities/schedule-time-interval.entity.ts @@ -0,0 +1,51 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { ScheduleTimeIntervalDto } from '../dto'; + +@Entity() +export class ScheduleTimeInterval { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + scheduleId: number; + + @Column() + dayOfWeek: string; + + @Column({ type: 'time' }) + timeFrom: string; + + @Column({ type: 'time' }) + timeTo: string; + + constructor(accountId: number, scheduleId: number, dayOfWeek: string, timeFrom: string, timeTo: string) { + this.accountId = accountId; + this.scheduleId = scheduleId; + this.dayOfWeek = dayOfWeek; + this.timeFrom = timeFrom; + this.timeTo = timeTo; + } + + static fromDto({ + accountId, + scheduleId, + dto, + }: { + accountId: number; + scheduleId: number; + dto: ScheduleTimeIntervalDto; + }): ScheduleTimeInterval { + return new ScheduleTimeInterval(accountId, scheduleId, dto.dayOfWeek, dto.timeFrom, dto.timeTo); + } + + toDto(): ScheduleTimeIntervalDto { + return { + dayOfWeek: this.dayOfWeek, + timeFrom: this.timeFrom.substring(0, 5), + timeTo: this.timeTo.substring(0, 5), + }; + } +} diff --git a/backend/src/modules/scheduler/schedule/entities/schedule.entity.ts b/backend/src/modules/scheduler/schedule/entities/schedule.entity.ts new file mode 100644 index 0000000..fc8870b --- /dev/null +++ b/backend/src/modules/scheduler/schedule/entities/schedule.entity.ts @@ -0,0 +1,152 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { Authorizable, SimpleAuthorizable } from '@/modules/iam/common'; + +import { PermissionObjectType } from '../../common'; +import { SchedulePerformer } from '../../schedule-performer'; + +import { CreateScheduleDto, ScheduleDto, UpdateScheduleDto } from '../dto'; +import { ScheduleType } from '../enums'; +import { ScheduleTimeInterval } from './schedule-time-interval.entity'; + +@Entity() +export class Schedule { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + name: string; + + @Column() + icon: string; + + @Column() + type: ScheduleType; + + @Column({ default: 1800 }) + timePeriod: number; + + @Column({ nullable: true }) + appointmentLimit: number | null; + + @Column({ nullable: true }) + timeBufferBefore: number | null; + + @Column({ nullable: true }) + timeBufferAfter: number | null; + + @Column({ default: false }) + oneEntityPerDay: boolean; + + @Column({ nullable: true }) + entityTypeId: number | null; + + @Column({ nullable: true }) + productsSectionId: number | null; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + accountId: number, + name: string, + icon: string, + type: ScheduleType, + timePeriod: number, + appointmentLimit: number | null, + timeBufferBefore: number | null, + timeBufferAfter: number | null, + oneEntityPerDay: boolean, + entityTypeId: number | null, + productsSectionId: number | null, + createdAt?: Date, + ) { + this.accountId = accountId; + this.name = name; + this.icon = icon; + this.type = type; + this.timePeriod = timePeriod; + this.appointmentLimit = appointmentLimit; + this.timeBufferBefore = timeBufferBefore; + this.timeBufferAfter = timeBufferAfter; + this.oneEntityPerDay = oneEntityPerDay; + this.entityTypeId = entityTypeId; + this.productsSectionId = productsSectionId; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _performers: SchedulePerformer[]; + get performers(): SchedulePerformer[] { + return this._performers; + } + set performers(value: SchedulePerformer[]) { + this._performers = value; + } + + private _intervals: ScheduleTimeInterval[] | null; + get intervals(): ScheduleTimeInterval[] | null { + return this._intervals; + } + set intervals(value: ScheduleTimeInterval[] | null) { + this._intervals = value; + } + + static fromDto({ accountId, dto }: { accountId: number; dto: CreateScheduleDto }): Schedule { + return new Schedule( + accountId, + dto.name, + dto.icon, + dto.type, + dto.timePeriod ?? 1800, + dto.appointmentLimit ?? null, + dto.timeBufferBefore ?? null, + dto.timeBufferAfter ?? null, + dto.oneEntityPerDay ?? false, + dto.entityTypeId, + dto.productsSectionId, + ); + } + + toDto(): ScheduleDto { + return { + id: this.id, + name: this.name, + icon: this.icon, + type: this.type, + timePeriod: this.timePeriod, + appointmentLimit: this.appointmentLimit, + timeBufferBefore: this.timeBufferBefore, + timeBufferAfter: this.timeBufferAfter, + oneEntityPerDay: this.oneEntityPerDay, + entityTypeId: this.entityTypeId, + productsSectionId: this.productsSectionId, + createdAt: this.createdAt.toISOString(), + performers: this._performers?.map((p) => p.toDto()), + intervals: this._intervals?.map((interval) => interval.toDto()), + }; + } + + update(dto: UpdateScheduleDto): Schedule { + this.name = dto.name !== undefined ? dto.name : this.name; + this.icon = dto.icon !== undefined ? dto.icon : this.icon; + this.type = dto.type !== undefined ? dto.type : this.type; + this.timePeriod = dto.timePeriod !== undefined ? dto.timePeriod : this.timePeriod; + this.appointmentLimit = dto.appointmentLimit !== undefined ? dto.appointmentLimit : this.appointmentLimit; + this.timeBufferBefore = dto.timeBufferBefore !== undefined ? dto.timeBufferBefore : this.timeBufferBefore; + this.timeBufferAfter = dto.timeBufferAfter !== undefined ? dto.timeBufferAfter : this.timeBufferAfter; + this.oneEntityPerDay = dto.oneEntityPerDay !== undefined ? dto.oneEntityPerDay : this.oneEntityPerDay; + this.entityTypeId = dto.entityTypeId !== undefined ? dto.entityTypeId : this.entityTypeId; + this.productsSectionId = dto.productsSectionId !== undefined ? dto.productsSectionId : this.productsSectionId; + + return this; + } + + static getAuthorizable(scheduleId: number): Authorizable { + return new SimpleAuthorizable({ type: PermissionObjectType.Schedule, id: scheduleId }); + } +} diff --git a/backend/src/modules/scheduler/schedule/enums/index.ts b/backend/src/modules/scheduler/schedule/enums/index.ts new file mode 100644 index 0000000..3ded87e --- /dev/null +++ b/backend/src/modules/scheduler/schedule/enums/index.ts @@ -0,0 +1 @@ +export * from './schedule-type.enum'; diff --git a/backend/src/modules/scheduler/schedule/enums/schedule-type.enum.ts b/backend/src/modules/scheduler/schedule/enums/schedule-type.enum.ts new file mode 100644 index 0000000..03931c6 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/enums/schedule-type.enum.ts @@ -0,0 +1,4 @@ +export enum ScheduleType { + Schedule = 'schedule', + Board = 'board', +} diff --git a/backend/src/modules/scheduler/schedule/index.ts b/backend/src/modules/scheduler/schedule/index.ts new file mode 100644 index 0000000..1f73968 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './schedule.controller'; +export * from './services'; diff --git a/backend/src/modules/scheduler/schedule/schedule.controller.ts b/backend/src/modules/scheduler/schedule/schedule.controller.ts new file mode 100644 index 0000000..cfa34ce --- /dev/null +++ b/backend/src/modules/scheduler/schedule/schedule.controller.ts @@ -0,0 +1,70 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized, UserAccess } from '@/modules/iam/common'; + +import { ScheduleDto, CreateScheduleDto, ScheduleFilterDto, UpdateScheduleDto } from './dto'; +import { ScheduleService } from './services'; + +@ApiTags('scheduler/schedule') +@Controller('schedules') +@JwtAuthorized() +@TransformToDto() +export class ScheduleController { + constructor(private readonly service: ScheduleService) {} + + @ApiOperation({ summary: 'Create schedule', description: 'Create schedule with performers' }) + @ApiBody({ description: 'Data for creating schedule', type: CreateScheduleDto }) + @ApiCreatedResponse({ description: 'Schedule', type: ScheduleDto }) + @UserAccess({ adminOnly: true }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateScheduleDto) { + return this.service.create({ accountId, userId, dto }); + } + + @ApiOperation({ summary: 'Get list of schedules', description: 'Get list of schedules with performers' }) + @ApiOkResponse({ description: 'List of schedules', type: [ScheduleDto] }) + @AuthDataPrefetch({ user: true }) + @Get() + async getMany(@CurrentAuth() { accountId, user }: AuthData, @Query() filter: ScheduleFilterDto) { + return this.service.findMany({ + user, + filter: { accountId, entityTypeId: filter.entityTypeId }, + checkPerformers: true, + }); + } + + @ApiOperation({ summary: 'Get schedule', description: 'Get schedule with performers' }) + @ApiParam({ name: 'scheduleId', description: 'Schedule ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Schedule', type: ScheduleDto }) + @AuthDataPrefetch({ user: true }) + @Get('/:scheduleId') + async getOne(@CurrentAuth() { accountId, user }: AuthData, @Param('scheduleId', ParseIntPipe) scheduleId: number) { + return this.service.findOne({ user, filter: { accountId, scheduleId }, checkPerformers: true }); + } + + @ApiOperation({ summary: 'Update schedule', description: 'Update schedule with performers' }) + @ApiBody({ description: 'Data for updating schedule', type: UpdateScheduleDto }) + @ApiParam({ name: 'scheduleId', description: 'Schedule ID', type: Number, required: true }) + @ApiOkResponse({ description: 'Schedules', type: ScheduleDto }) + @UserAccess({ adminOnly: true }) + @Put('/:scheduleId') + async update( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('scheduleId', ParseIntPipe) scheduleId: number, + @Body() dto: UpdateScheduleDto, + ) { + return this.service.update({ accountId, userId, scheduleId, dto }); + } + + @ApiOperation({ summary: 'Delete schedule', description: 'Delete schedule with performers and appointments' }) + @ApiParam({ name: 'scheduleId', description: 'Schedule ID', type: Number, required: true }) + @ApiOkResponse() + @UserAccess({ adminOnly: true }) + @Delete('/:scheduleId') + async delete(@CurrentAuth() { accountId, userId }: AuthData, @Param('scheduleId', ParseIntPipe) scheduleId: number) { + return this.service.delete({ accountId, userId, scheduleId }); + } +} diff --git a/backend/src/modules/scheduler/schedule/services/index.ts b/backend/src/modules/scheduler/schedule/services/index.ts new file mode 100644 index 0000000..8371b80 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/services/index.ts @@ -0,0 +1,2 @@ +export * from './schedule-time-interval.service'; +export * from './schedule.service'; diff --git a/backend/src/modules/scheduler/schedule/services/schedule-time-interval.service.ts b/backend/src/modules/scheduler/schedule/services/schedule-time-interval.service.ts new file mode 100644 index 0000000..a129b4c --- /dev/null +++ b/backend/src/modules/scheduler/schedule/services/schedule-time-interval.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ScheduleTimeIntervalDto } from '../dto'; +import { ScheduleTimeInterval } from '../entities'; + +interface FindFilter { + accountId: number; + scheduleId: number; +} + +@Injectable() +export class ScheduleTimeIntervalService { + constructor( + @InjectRepository(ScheduleTimeInterval) + private readonly repository: Repository, + ) {} + + async create({ + accountId, + scheduleId, + dto, + }: { + accountId: number; + scheduleId: number; + dto: ScheduleTimeIntervalDto; + }) { + return this.repository.save(ScheduleTimeInterval.fromDto({ accountId, scheduleId, dto })); + } + async createMany({ + accountId, + scheduleId, + dtos, + }: { + accountId: number; + scheduleId: number; + dtos: ScheduleTimeIntervalDto[]; + }) { + return Promise.all(dtos.map((dto) => this.create({ accountId, scheduleId, dto }))); + } + + async findMany(filter: FindFilter): Promise { + return this.createQb(filter).getMany(); + } + + async updateMany({ + accountId, + scheduleId, + dtos, + }: { + accountId: number; + scheduleId: number; + dtos: ScheduleTimeIntervalDto[]; + }) { + await this.deleteMany({ accountId, scheduleId }); + return this.createMany({ accountId, scheduleId, dtos }); + } + + async deleteMany({ accountId, scheduleId }: FindFilter) { + await this.repository.delete({ accountId, scheduleId }); + } + + private createQb({ accountId, scheduleId }: FindFilter) { + return this.repository + .createQueryBuilder('interval') + .where('interval.account_id = :accountId', { accountId }) + .andWhere('interval.schedule_id = :scheduleId', { scheduleId }); + } +} diff --git a/backend/src/modules/scheduler/schedule/services/schedule.service.ts b/backend/src/modules/scheduler/schedule/services/schedule.service.ts new file mode 100644 index 0000000..203d5e9 --- /dev/null +++ b/backend/src/modules/scheduler/schedule/services/schedule.service.ts @@ -0,0 +1,263 @@ +import { Inject, Injectable, forwardRef } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +import { ForbiddenError } from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { ProductsSectionService } from '@/modules/inventory/products-section/services/products-section.service'; + +import { ScheduleEvent, SchedulerEventType, ScheduleUpdatedEvent } from '../../common'; + +import { SchedulePerformerService, SchedulePerformer, SchedulePerformerType } from '../../schedule-performer'; + +import { CreateScheduleDto, UpdateScheduleDto } from '../dto'; +import { Schedule } from '../entities'; +import { ScheduleTimeIntervalService } from './schedule-time-interval.service'; + +interface FindFilter { + accountId: number; + scheduleId?: number; + entityTypeId?: number; +} + +@Injectable() +export class ScheduleService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(Schedule) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + @Inject(forwardRef(() => ProductsSectionService)) + private readonly productsSectionService: ProductsSectionService, + @Inject(forwardRef(() => SchedulePerformerService)) + private readonly performerService: SchedulePerformerService, + private readonly intervalService: ScheduleTimeIntervalService, + ) {} + + async create({ + accountId, + userId, + dto, + }: { + accountId: number; + userId: number; + dto: CreateScheduleDto; + }): Promise { + const schedule = await this.repository.save(Schedule.fromDto({ accountId, dto })); + + if (dto.performers) { + schedule.performers = await this.performerService.createMany({ + accountId, + scheduleId: schedule.id, + dtos: dto.performers, + }); + } + if (dto.intervals) { + schedule.intervals = await this.intervalService.createMany({ + accountId, + scheduleId: schedule.id, + dtos: dto.intervals, + }); + } + + if (dto.productsSectionId && dto.entityTypeId) { + await this.productsSectionService.ensureLinked(accountId, dto.productsSectionId, dto.entityTypeId); + } + + this.eventEmitter.emit( + SchedulerEventType.ScheduleCreated, + new ScheduleEvent({ accountId, userId, scheduleId: schedule.id }), + ); + + return schedule; + } + + async findOne({ + user, + filter, + checkPerformers, + }: { + user?: User | null; + filter: FindFilter; + checkPerformers?: boolean; + }): Promise { + const schedule = await this.createFindQb(filter).getOne(); + if (!schedule) return null; + + schedule.intervals = await this.intervalService.findMany({ accountId: filter.accountId, scheduleId: schedule.id }); + return user && checkPerformers ? this.filterPerformers({ user, schedule, throwError: true }) : schedule; + } + + async findMany({ + user, + filter, + checkPerformers, + }: { + user?: User | null; + filter: FindFilter; + checkPerformers?: boolean; + }): Promise { + const schedules = await this.createFindQb(filter).orderBy('schedule.created_at', 'DESC').getMany(); + if (!schedules.length) return []; + + await Promise.all( + schedules.map(async (schedule) => { + schedule.intervals = await this.intervalService.findMany({ + accountId: filter.accountId, + scheduleId: schedule.id, + }); + }), + ); + + return user && checkPerformers + ? (await Promise.all(schedules.map((schedule) => this.filterPerformers({ user, schedule })))).filter(Boolean) + : schedules; + } + + async update({ + accountId, + userId, + scheduleId, + dto, + }: { + accountId: number; + userId: number; + scheduleId: number; + dto: UpdateScheduleDto; + }): Promise { + const schedule = await this.findOne({ filter: { accountId, scheduleId } }); + + const typeChanged = dto.type && dto.type !== schedule.type; + const timePeriodChanged = dto.timePeriod && dto.timePeriod !== schedule.timePeriod; + + await this.repository.save(schedule.update(dto)); + + if (dto.performers) { + schedule.performers = await this.performerService.processMany({ + accountId, + scheduleId: schedule.id, + current: schedule.performers, + dtos: dto.performers, + }); + } + + if (dto.intervals) { + schedule.intervals = await this.intervalService.updateMany({ + accountId, + scheduleId: schedule.id, + dtos: dto.intervals, + }); + } + + if (dto.productsSectionId && dto.entityTypeId) { + await this.productsSectionService.ensureLinked(accountId, dto.productsSectionId, dto.entityTypeId); + } + + this.eventEmitter.emit( + SchedulerEventType.ScheduleUpdated, + new ScheduleUpdatedEvent({ accountId, userId, scheduleId: schedule.id, typeChanged, timePeriodChanged }), + ); + + return schedule; + } + + async delete({ + accountId, + userId, + scheduleId, + }: { + accountId: number; + userId: number; + scheduleId: number; + }): Promise { + await this.repository.delete({ accountId, id: scheduleId }); + + this.eventEmitter.emit(SchedulerEventType.ScheduleDeleted, new ScheduleEvent({ accountId, userId, scheduleId })); + } + + async getLinkedSchedulerIds( + accountId: number, + filter: { productsSectionId?: number; entityTypeId?: number }, + ): Promise { + const qb = this.repository + .createQueryBuilder('schedule') + .select('schedule.id', 'id') + .where('schedule.accountId = :accountId', { accountId }); + + if (filter.productsSectionId) { + qb.andWhere('schedule.products_section_id = :productsSectionId', { productsSectionId: filter.productsSectionId }); + } + if (filter.entityTypeId) { + qb.andWhere('schedule.entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + } + + return (await qb.getRawMany()).map((s) => s.id); + } + + async linkProductsSection(accountId: number, scheduleIds: number[] | null, productsSectionId: number): Promise { + await this.repository.update({ accountId, productsSectionId }, { productsSectionId: null }); + if (scheduleIds?.length > 0) { + await this.repository.update({ accountId, id: In(scheduleIds) }, { productsSectionId }); + } + } + + async linkEntityType(accountId: number, scheduleIds: number[] | null, entityTypeId: number): Promise { + await this.repository.update({ accountId, entityTypeId }, { entityTypeId: null }); + if (scheduleIds?.length > 0) { + await this.repository.update({ accountId, id: In(scheduleIds) }, { entityTypeId }); + } + } + + private createFindQb(filter: FindFilter) { + const qb = this.repository + .createQueryBuilder('schedule') + .where('schedule.accountId = :accountId', { accountId: filter.accountId }) + .leftJoinAndMapMany('schedule.performers', SchedulePerformer, 'performer', 'schedule.id = performer.schedule_id'); + + if (filter.scheduleId) { + qb.andWhere('schedule.id = :id', { id: filter.scheduleId }); + } + if (filter.entityTypeId) { + qb.andWhere('schedule.entity_type_id = :entityTypeId', { entityTypeId: filter.entityTypeId }); + } + + return qb; + } + + private async filterPerformers({ + user, + schedule, + throwError = false, + }: { + user: User; + schedule: Schedule; + throwError?: boolean; + }): Promise { + const { allow, userIds, departmentIds } = await this.authService.getPermissions({ + action: 'view', + user, + authorizable: Schedule.getAuthorizable(schedule.id), + }); + + if (!allow) { + if (throwError) { + throw new ForbiddenError(); + } else { + return null; + } + } + + if (userIds) { + schedule.performers = schedule.performers.filter( + (p) => + (p.type === SchedulePerformerType.User && userIds.includes(p.userId)) || + (p.type === SchedulePerformerType.Department && departmentIds.includes(p.departmentId)), + ); + } + + return schedule; + } +} diff --git a/backend/src/modules/scheduler/scheduler.module.ts b/backend/src/modules/scheduler/scheduler.module.ts new file mode 100644 index 0000000..018ce34 --- /dev/null +++ b/backend/src/modules/scheduler/scheduler.module.ts @@ -0,0 +1,45 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { InventoryModule } from '@/modules/inventory/inventory.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { + Schedule, + ScheduleController, + ScheduleService, + ScheduleTimeInterval, + ScheduleTimeIntervalService, +} from './schedule'; +import { + ScheduleAppointment, + ScheduleAppointmentController, + ScheduleAppointmentHandler, + ScheduleAppointmentService, +} from './schedule-appointment'; +import { SchedulePerformer, SchedulePerformerHandler, SchedulePerformerService } from './schedule-performer'; +import { ScheduleReportingController, ScheduleReportingService } from './schedule-reporting'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([Schedule, ScheduleTimeInterval, SchedulePerformer, ScheduleAppointment]), + IAMModule, + forwardRef(() => CrmModule), + forwardRef(() => InventoryModule), + EntityInfoModule, + ], + controllers: [ScheduleController, ScheduleAppointmentController, ScheduleReportingController], + providers: [ + ScheduleService, + ScheduleTimeIntervalService, + SchedulePerformerService, + SchedulePerformerHandler, + ScheduleAppointmentService, + ScheduleAppointmentHandler, + ScheduleReportingService, + ], + exports: [ScheduleService, ScheduleAppointmentService], +}) +export class SchedulerModule {} diff --git a/backend/src/modules/setup/account-setup/account-setup.module.ts b/backend/src/modules/setup/account-setup/account-setup.module.ts new file mode 100644 index 0000000..cc3a11c --- /dev/null +++ b/backend/src/modules/setup/account-setup/account-setup.module.ts @@ -0,0 +1,67 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { StorageModule } from '@/modules/storage/storage.module'; +import { InventoryModule } from '@/modules/inventory/inventory.module'; +import { SchedulerModule } from '@/modules/scheduler/scheduler.module'; +import { EntityFieldModule } from '@/modules/entity/entity-field/entity-field.module'; +import { IntegrationModule } from '@/modules/integration/integration.module'; +import { PartnerModule } from '@/modules/partner/partner.module'; + +import { CrmModule } from '@/CRM/crm.module'; +import { TaskSettingsModule } from '@/CRM/task-settings/task-settings.module'; + +import accountSetupConfig from './config/account-setup.config'; + +import { RmsModule } from '../rms/rms.module'; +import { DemoDataModule } from '../demo-data/demo-data.module'; + +import { + RmsActivityService, + RmsBoardService, + RmsEntityService, + RmsEntityTypeService, + RmsFieldService, + RmsNoteService, + RmsTaskService, + SetupCrmService, + SetupIAMService, + SetupProductsService, + SetupSchedulerService, +} from './services'; +import { AccountSetupService } from './account-setup.service'; +import { PublicAccountSetupController } from './public-account-setup.controller'; + +@Module({ + imports: [ + ConfigModule.forFeature(accountSetupConfig), + IAMModule, + StorageModule, + CrmModule, + IntegrationModule, + InventoryModule, + SchedulerModule, + TaskSettingsModule, + EntityFieldModule, + RmsModule, + DemoDataModule, + PartnerModule, + ], + providers: [ + RmsActivityService, + RmsBoardService, + RmsEntityService, + RmsEntityTypeService, + RmsFieldService, + RmsNoteService, + RmsTaskService, + SetupCrmService, + SetupIAMService, + SetupProductsService, + SetupSchedulerService, + AccountSetupService, + ], + controllers: [PublicAccountSetupController], +}) +export class AccountSetupModule {} diff --git a/backend/src/modules/setup/account-setup/account-setup.service.ts b/backend/src/modules/setup/account-setup/account-setup.service.ts new file mode 100644 index 0000000..b05edb5 --- /dev/null +++ b/backend/src/modules/setup/account-setup/account-setup.service.ts @@ -0,0 +1,349 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { capitalizeFirst, DateUtil } from '@/common'; +import { ApplicationConfig } from '@/config'; + +import { AccountService } from '@/modules/iam/account/account.service'; +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { AuthenticationService } from '@/modules/iam/authentication/authentication.service'; +import { UserRole } from '@/modules/iam/common/enums/user-role.enum'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { UserService } from '@/modules/iam/user/user.service'; +import { LoginLinkDto } from '@/modules/iam/authentication/dto/login-link.dto'; +import { SubscriptionDiscountService } from '@/modules/iam/subscription-discount/subscription-discount.service'; +import { FieldType } from '@/modules/entity/entity-field/common'; +import { SimpleFieldValueDto } from '@/modules/entity/entity-field/field-value'; +import { AppsumoService } from '@/modules/integration/appsumo'; +import { PartnerService } from '@/modules/partner/partner.service'; +import { CreateSimpleEntityDto } from '@/CRM/Service/Entity/Dto'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { NoteService } from '@/CRM/note/note.service'; + +import { AccountSetupConfig } from './config'; +import { RmsModules, RmsModulePrefix, DemoDataType } from '../common'; +import { DemoDataService } from '../demo-data/demo-data.service'; +import { RmsService } from '../rms/services/rms.service'; + +import { SetupAccountDto } from './dto'; +import { SetupCrmService, SetupIAMService, SetupProductsService, SetupSchedulerService } from './services'; + +interface ContactInfo { + name: string; + phone: string; + email: string; +} +interface DealInfo { + name: string; + firstVisit?: Date | null; +} + +const RMSDemoCode = 'demo'; + +const RestrictedFieldNames = ['password']; +const ExtraFieldNames = { + promoCode: 'promoCode', + rmsCode: 'rmsCode', + rmsModules: 'rmsModules', + ref: 'ref', + appName: 'appName', +}; + +@Injectable() +export class AccountSetupService { + private readonly logger = new Logger(AccountSetupService.name); + private _config: AccountSetupConfig; + private _appName: string; + + constructor( + private readonly configService: ConfigService, + private readonly accountService: AccountService, + private readonly authService: AuthenticationService, + private readonly userService: UserService, + private readonly rmsService: RmsService, + private readonly demoDataService: DemoDataService, + private readonly setupCrmService: SetupCrmService, + private readonly setupIAMService: SetupIAMService, + private readonly setupProductsService: SetupProductsService, + private readonly setupSchedulerService: SetupSchedulerService, + private readonly appsumoService: AppsumoService, + private readonly entityService: EntityService, + private readonly noteService: NoteService, + private readonly partnerService: PartnerService, + private readonly discountService: SubscriptionDiscountService, + ) { + this._config = this.configService.get('accountSetup'); + this._appName = this.configService.get('application').name; + } + + async create(dto: SetupAccountDto, gaClientId?: string | null): Promise { + const subscription = dto.appsumo ? await this.appsumoService.findSubscription(dto.appsumo) : undefined; + + const { account, owner } = await this.accountService.create(dto, { + gaClientId, + subscription, + firstVisit: dto.firstVisit, + }); + + if (!dto.firstVisit) { + dto.firstVisit = account.createdAt.toISOString(); + } + + await this.setupAccount(account, owner, dto.rmsCode, dto.rmsModules); + + await this.handleRegistrationEvent({ account, owner, dto }); + + if (dto.appsumo) { + await this.appsumoService.update(dto.appsumo, { accountId: account.id }); + } + + return this.authService.createLoginLink({ accountId: account.id, subdomain: account.subdomain, userId: owner.id }); + } + + private async setupAccount(account: Account, owner: User, rmsCode: string | null, modulesCode: string | null) { + const modules = modulesCode ? this.parseModules(modulesCode) : undefined; + + const rms = await this.rmsService.findOne({ code: rmsCode || RMSDemoCode }); + if (rms && rms.accountId) { + await this.setupRMS(account, owner, rms.accountId, modules); + } else { + await this.setupDefault(account.id, owner); + } + } + + private parseModules(modulesCode: string): RmsModules { + const modules = new RmsModules(); + const moduleCodes = modulesCode.replace(/\s+/g, '').split(','); + for (const moduleCode of moduleCodes) { + const [prefix, code] = moduleCode.split('_'); + if (prefix && code) { + const id = Number(code); + if (prefix === RmsModulePrefix.EntityType) { + if (!modules.entityTypeIds.includes(id)) { + modules.entityTypeIds.push(id); + } + } else if (prefix === RmsModulePrefix.ProductSection) { + if (!modules.productSectionIds.includes(id)) { + modules.productSectionIds.push(id); + } + } else if (prefix === RmsModulePrefix.Scheduler) { + if (!modules.schedulerIds.includes(id)) { + modules.schedulerIds.push(id); + } + } + } + } + return modules; + } + + private async setupRMS(account: Account, owner: User, rmsAccountId: number, modules?: RmsModules) { + const rmsAccount = await this.accountService.findOne({ accountId: rmsAccountId }); + + const { rmsOwner, usersMap, departmentsMap } = await this.setupIAMService.copyAll(rmsAccount.id, account, owner); + const demoUserIds = Array.from(usersMap.values()) + .filter((u) => u.id !== owner.id) + .map((u) => u.id); + if (demoUserIds.length > 0) { + await this.demoDataService.create(account.id, DemoDataType.User, demoUserIds); + } + + const { entityTypesMap, entitiesMap, tasksMap } = await this.setupCrmService.copyAll( + rmsAccount.id, + account.id, + usersMap, + modules?.entityTypeIds, + ); + const demoEntityIds = Array.from(entitiesMap.values()); + if (demoEntityIds.length > 0) { + await this.demoDataService.create(account.id, DemoDataType.Entity, demoEntityIds); + } + const demoTaskIds = Array.from(tasksMap.values()); + if (demoTaskIds.length > 0) { + await this.demoDataService.create(account.id, DemoDataType.Task, demoTaskIds); + } + + const { sectionsMap, productsMap, salesOrdersMap, rentalOrdersMap } = await this.setupProductsService.copyAll( + rmsAccount, + rmsOwner, + account, + owner, + entityTypesMap, + entitiesMap, + modules?.productSectionIds, + ); + const demoProductIds = Array.from(productsMap.values()); + if (demoProductIds.length > 0) { + await this.demoDataService.create(account.id, DemoDataType.Product, demoProductIds); + } + const demoSaleOrderIds = Array.from(salesOrdersMap.values()); + if (demoSaleOrderIds.length > 0) { + await this.demoDataService.create(account.id, DemoDataType.SalesOrder, demoSaleOrderIds); + } + const demoRentalOrderIds = Array.from(rentalOrdersMap.values()); + if (demoRentalOrderIds.length > 0) { + await this.demoDataService.create(account.id, DemoDataType.RentalOrder, demoRentalOrderIds); + } + + const { appointmentsMap } = await this.setupSchedulerService.copyAll( + rmsAccount.id, + account.id, + owner, + usersMap, + departmentsMap, + entityTypesMap, + entitiesMap, + sectionsMap, + salesOrdersMap, + modules?.schedulerIds, + ); + const demoAppointmentIds = Array.from(appointmentsMap.values()); + if (demoAppointmentIds.length > 0) { + await this.demoDataService.create(account.id, DemoDataType.ScheduleAppointment, demoAppointmentIds); + } + } + + private async setupDefault(accountId: number, owner: User) { + await this.setupCrmService.setupDefault(accountId, owner); + } + + private async handleRegistrationEvent({ + account, + owner, + dto, + }: { + account: Account; + owner: User; + dto: SetupAccountDto; + }) { + if (!this._config.accountId) return; + + try { + const responsible = this._config.responsibleId + ? await this.userService.findOne({ accountId: this._config.accountId, id: this._config.responsibleId }) + : await this.userService.findOne({ accountId: this._config.accountId, role: UserRole.OWNER }); + + const extraParams = dto.extraUserInfo ? this.parseExtraParams(dto.extraUserInfo) : {}; + if (dto.promoCode) { + extraParams[ExtraFieldNames.promoCode] = dto.promoCode; + } + if (dto.rmsCode) { + extraParams[ExtraFieldNames.rmsCode] = dto.rmsCode; + } + if (dto.rmsModules) { + extraParams[ExtraFieldNames.rmsModules] = dto.rmsModules; + } + if (dto.ref) { + extraParams[ExtraFieldNames.ref] = dto.ref; + } + extraParams[ExtraFieldNames.appName] = this._appName; + + const deal = await this.createDealDto( + { + name: `Registration: ${account.companyName}`, + firstVisit: dto.firstVisit ? DateUtil.fromISOString(dto.firstVisit) : undefined, + }, + extraParams, + ); + const contact = this.createContactDto( + { + name: `${owner.firstName} ${owner.lastName}`, + phone: owner.phone, + email: owner.email, + }, + [deal], + ); + + const entities = await this.entityService.createSimple({ + accountId: this._config.accountId, + user: responsible, + dto: contact, + options: { createdAt: account.createdAt, checkDuplicate: true }, + }); + + await this.noteService.create({ + accountId: this._config.accountId, + userId: responsible.id, + entityId: entities[1].id, + dto: { text: this.getCommentHtml({ account: account, registration: dto }) }, + options: { createdAt: account.createdAt }, + }); + + if (dto.ref) { + await this.partnerService.linkLeadWithPartner(this._config.accountId, entities[1].id, dto.ref); + } + } catch (e) { + this.logger.error(`Error during lead creation in ${this._config.accountId} account`, (e as Error)?.stack); + } + } + + private parseExtraParams(extra: { analytics?: object }): Record { + if (!extra.analytics) return {}; + + const extraParams: Record = {}; + for (const [key, value] of Object.entries(extra.analytics)) { + if (value) { + extraParams[key] = value; + } + } + + return extraParams; + } + + private createContactDto( + { name, phone, email }: ContactInfo, + linkedEntities: CreateSimpleEntityDto[], + ): CreateSimpleEntityDto { + return { + entityTypeId: this._config.contactId, + name, + fieldValues: [ + { fieldType: FieldType.Phone, value: phone }, + { fieldType: FieldType.Email, value: email }, + ], + linkedEntities, + }; + } + private async createDealDto( + { name, firstVisit }: DealInfo, + extraParams?: Record, + ): Promise { + const fieldValues: SimpleFieldValueDto[] = []; + if (firstVisit) { + const discounts = await this.discountService.findMany(firstVisit); + discounts.forEach((discount) => { + fieldValues.push({ fieldName: discount.code, value: discount.endAt }); + }); + } + if (extraParams) { + for (const key of Object.keys(extraParams)) { + fieldValues.push({ fieldName: key, value: extraParams[key] }); + } + } + + return { + entityTypeId: this._config.dealId, + boardId: this._config.dealBoardId, + name, + fieldValues, + }; + } + + private getCommentHtml(obj: object): string { + const comment: string[] = []; + comment.push('
    '); + for (const key of Object.keys(obj)) { + if (!RestrictedFieldNames.includes(key)) { + const value = obj[key]; + if (value instanceof Object) { + comment.push(`
  • ${capitalizeFirst(key)}:`); + comment.push(this.getCommentHtml(value)); + comment.push('
  • '); + } else if (value !== null && value !== undefined) { + comment.push(`
  • ${capitalizeFirst(key)}: ${value}
  • `); + } + } + } + comment.push('
'); + return comment.join(''); + } +} diff --git a/backend/src/modules/setup/account-setup/config/account-setup.config.ts b/backend/src/modules/setup/account-setup/config/account-setup.config.ts new file mode 100644 index 0000000..6a2a6c6 --- /dev/null +++ b/backend/src/modules/setup/account-setup/config/account-setup.config.ts @@ -0,0 +1,24 @@ +import { registerAs } from '@nestjs/config'; + +export interface AccountSetupConfig { + accountId: number; + contactId: number; + dealId: number; + dealBoardId: number | undefined; + responsibleId: number; +} + +export default registerAs( + 'accountSetup', + (): AccountSetupConfig => ({ + accountId: process.env.ACCOUNT_SETUP_ACCOUNT_ID ? parseInt(process.env.ACCOUNT_SETUP_ACCOUNT_ID, 10) : undefined, + contactId: process.env.ACCOUNT_SETUP_CONTACT_ID ? parseInt(process.env.ACCOUNT_SETUP_CONTACT_ID, 10) : undefined, + dealId: process.env.ACCOUNT_SETUP_DEAL_ID ? parseInt(process.env.ACCOUNT_SETUP_DEAL_ID, 10) : undefined, + dealBoardId: process.env.ACCOUNT_SETUP_DEAL_BOARD_ID + ? parseInt(process.env.ACCOUNT_SETUP_DEAL_BOARD_ID, 10) + : undefined, + responsibleId: process.env.ACCOUNT_SETUP_RESPONSIBLE_ID + ? parseInt(process.env.ACCOUNT_SETUP_RESPONSIBLE_ID, 10) + : undefined, + }), +); diff --git a/backend/src/modules/setup/account-setup/config/index.ts b/backend/src/modules/setup/account-setup/config/index.ts new file mode 100644 index 0000000..8deaa62 --- /dev/null +++ b/backend/src/modules/setup/account-setup/config/index.ts @@ -0,0 +1 @@ +export * from './account-setup.config'; diff --git a/backend/src/modules/setup/account-setup/dto/index.ts b/backend/src/modules/setup/account-setup/dto/index.ts new file mode 100644 index 0000000..c0f134a --- /dev/null +++ b/backend/src/modules/setup/account-setup/dto/index.ts @@ -0,0 +1 @@ +export * from './setup-account.dto'; diff --git a/backend/src/modules/setup/account-setup/dto/setup-account.dto.ts b/backend/src/modules/setup/account-setup/dto/setup-account.dto.ts new file mode 100644 index 0000000..ec5fc30 --- /dev/null +++ b/backend/src/modules/setup/account-setup/dto/setup-account.dto.ts @@ -0,0 +1,40 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +import { CreateAccountDto } from '@/modules/iam/account/dto/create-account.dto'; + +export class SetupAccountDto extends CreateAccountDto { + @ApiPropertyOptional({ nullable: true, description: 'Referral code' }) + @IsOptional() + @IsString() + ref?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'Promo code' }) + @IsOptional() + @IsString() + promoCode?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'RMS code' }) + @IsOptional() + @IsString() + rmsCode?: string | null; + + @ApiPropertyOptional({ nullable: true, description: 'RMS modules' }) + @IsOptional() + @IsString() + rmsModules?: string | null; + + @ApiPropertyOptional({ description: 'Appsumo code', nullable: true }) + @IsOptional() + @IsString() + appsumo?: string | null; + + @ApiPropertyOptional({ description: 'Extra user info', nullable: true }) + @IsOptional() + extraUserInfo?: object | null; + + @ApiPropertyOptional({ description: 'First visit date', nullable: true }) + @IsOptional() + @IsString() + firstVisit?: string | null; +} diff --git a/backend/src/modules/setup/account-setup/public-account-setup.controller.ts b/backend/src/modules/setup/account-setup/public-account-setup.controller.ts new file mode 100644 index 0000000..bb332ea --- /dev/null +++ b/backend/src/modules/setup/account-setup/public-account-setup.controller.ts @@ -0,0 +1,22 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { ApiAccessRequired } from '@/modules/iam/common/decorators/api-access-required.decorator'; +import { GAClientId } from '@/modules/iam/common/decorators/ga-client-id.decorator'; +import { LoginLinkDto } from '@/modules/iam/authentication/dto/login-link.dto'; + +import { SetupAccountDto } from './dto'; +import { AccountSetupService } from './account-setup.service'; + +@ApiTags('setup/account') +@Controller('setup/account') +@ApiAccessRequired() +export class PublicAccountSetupController { + constructor(private readonly service: AccountSetupService) {} + + @ApiCreatedResponse({ type: LoginLinkDto }) + @Post() + async create(@GAClientId() gaClientId: string, @Body() dto: SetupAccountDto): Promise { + return this.service.create(dto, gaClientId); + } +} diff --git a/backend/src/modules/setup/account-setup/services/crm/index.ts b/backend/src/modules/setup/account-setup/services/crm/index.ts new file mode 100644 index 0000000..27067ab --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/index.ts @@ -0,0 +1,7 @@ +export * from './rms-activity.service'; +export * from './rms-board.service'; +export * from './rms-entity-type.service'; +export * from './rms-entity.service'; +export * from './rms-field.service'; +export * from './rms-note.service'; +export * from './rms-task.service'; diff --git a/backend/src/modules/setup/account-setup/services/crm/rms-activity.service.ts b/backend/src/modules/setup/account-setup/services/crm/rms-activity.service.ts new file mode 100644 index 0000000..f6230f4 --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/rms-activity.service.ts @@ -0,0 +1,77 @@ +import { Injectable } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { ActivityService } from '@/CRM/activity/activity.service'; +import { ActivityTypeService } from '@/CRM/activity-type/activity-type.service'; + +@Injectable() +export class RmsActivityService { + constructor( + private readonly activityService: ActivityService, + private readonly activityTypeService: ActivityTypeService, + ) {} + + public async setupDefault(accountId: number) { + await Promise.all([ + this.activityTypeService.create({ accountId, dto: { name: 'Call' } }), + this.activityTypeService.create({ accountId, dto: { name: 'Email' } }), + this.activityTypeService.create({ accountId, dto: { name: 'Meeting' } }), + ]); + } + + public async copyAll( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entitiesMap: Map, + ): Promise> { + const activityTypesMap = await this.copyActivityTypes(rmsAccountId, accountId); + + return await this.copyActivities(rmsAccountId, accountId, usersMap, entitiesMap, activityTypesMap); + } + + public async copyActivityTypes(rmsAccountId: number, accountId: number) { + const activityTypesMap = new Map(); + + const activityTypes = await this.activityTypeService.findMany({ accountId: rmsAccountId }); + for (const activityType of activityTypes) { + const newActivityType = await this.activityTypeService.create({ accountId, dto: { name: activityType.name } }); + activityTypesMap.set(activityType.id, newActivityType.id); + } + + return activityTypesMap; + } + + public async copyActivities( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entitiesMap: Map, + activityTypesMap: Map, + ): Promise> { + const activitiesMap = new Map(); + + const rmsActivities = await this.activityService.findMany({ accountId: rmsAccountId }); + for (const rmsActivity of rmsActivities) { + if (entitiesMap.has(rmsActivity.entityId)) { + const activity = await this.activityService.create(accountId, usersMap.get(rmsActivity.createdBy), { + responsibleUserId: usersMap.get(rmsActivity.responsibleUserId).id, + startDate: rmsActivity.startDate ? rmsActivity.startDate.toISOString() : null, + endDate: rmsActivity.endDate ? rmsActivity.endDate.toISOString() : null, + text: rmsActivity.text, + entityId: entitiesMap.get(rmsActivity.entityId), + activityTypeId: activityTypesMap.get(rmsActivity.activityTypeId), + isResolved: rmsActivity.isResolved, + resolvedDate: rmsActivity.resolvedDate ? rmsActivity.resolvedDate.toISOString() : null, + result: rmsActivity.result, + weight: rmsActivity.weight, + }); + + activitiesMap.set(rmsActivity.id, activity.id); + } + } + + return activitiesMap; + } +} diff --git a/backend/src/modules/setup/account-setup/services/crm/rms-board.service.ts b/backend/src/modules/setup/account-setup/services/crm/rms-board.service.ts new file mode 100644 index 0000000..ddf6bcb --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/rms-board.service.ts @@ -0,0 +1,113 @@ +import { Injectable } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { BoardService } from '@/CRM/board/board.service'; +import { BoardType } from '@/CRM/board/enums'; +import { BoardStageService } from '@/CRM/board-stage'; + +@Injectable() +export class RmsBoardService { + constructor( + private readonly boardService: BoardService, + private readonly stageService: BoardStageService, + ) {} + + public async setupDefault(accountId: number, owner: User) { + await this.boardService.create({ + accountId, + user: owner, + dto: { name: 'Tasks board', type: BoardType.Task, recordId: null, sortOrder: 0 }, + options: { isSystem: true }, + }); + } + + public async copyBoardsAndStages( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entityTypesMap: Map, + ): Promise<{ boardsMap: Map; stagesMap: Map }> { + const boardsMap = await this.copyBoards(rmsAccountId, accountId, usersMap, entityTypesMap); + const stagesMap = await this.copyStages(rmsAccountId, accountId, boardsMap); + + return { boardsMap, stagesMap }; + } + + private async copyBoards( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entityTypesMap: Map, + ): Promise> { + const boardsMap = new Map(); + + const rmsBoards = await this.boardService.findMany({ + filter: { accountId: rmsAccountId, isSystem: true }, + }); + for (const rmsEntityTypeId of entityTypesMap.keys()) { + const rmsEtBoards = await this.boardService.findMany({ + filter: { accountId: rmsAccountId, recordId: rmsEntityTypeId }, + }); + for (const rmsEtBoard of rmsEtBoards) { + if (rmsEtBoard.taskBoardId) { + const rmsEtTaskBoard = await this.boardService.findOne({ + filter: { accountId: rmsAccountId, boardId: rmsEtBoard.taskBoardId }, + }); + rmsBoards.push(rmsEtTaskBoard); + } + rmsBoards.push(rmsEtBoard); + } + } + for (const rmsBoard of rmsBoards) { + const board = await this.boardService.create({ + accountId, + user: rmsBoard.ownerId ? usersMap.get(rmsBoard.ownerId) : null, + dto: { + name: rmsBoard.name, + type: rmsBoard.type, + recordId: rmsBoard.recordId ? entityTypesMap.get(rmsBoard.recordId) : null, + sortOrder: rmsBoard.sortOrder, + participantIds: rmsBoard.participantIds ? rmsBoard.participantIds.map((id) => usersMap.get(id).id) : null, + }, + options: { + ownerId: rmsBoard.ownerId ? usersMap.get(rmsBoard.ownerId).id : null, + isSystem: rmsBoard.isSystem, + taskBoardId: rmsBoard.taskBoardId ? boardsMap.get(rmsBoard.taskBoardId) : null, + createDefaultStages: false, + }, + }); + boardsMap.set(rmsBoard.id, board.id); + } + + return boardsMap; + } + + private async copyStages( + rmsAccountId: number, + accountId: number, + boardsMap: Map, + ): Promise> { + const stagesMap = new Map(); + + for (const [rmsBoardId, boardId] of boardsMap) { + const rmsStages = await this.stageService.findMany({ accountId: rmsAccountId, boardId: rmsBoardId }); + for (const rmsStage of rmsStages) { + const stage = await this.stageService.create({ + accountId, + boardId, + dto: { + name: rmsStage.name, + color: rmsStage.color, + code: rmsStage.code, + isSystem: rmsStage.isSystem, + sortOrder: rmsStage.sortOrder, + }, + }); + stagesMap.set(rmsStage.id, stage.id); + } + } + + return stagesMap; + } +} diff --git a/backend/src/modules/setup/account-setup/services/crm/rms-entity-type.service.ts b/backend/src/modules/setup/account-setup/services/crm/rms-entity-type.service.ts new file mode 100644 index 0000000..dca1ba8 --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/rms-entity-type.service.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; + +import { EntityType } from '@/CRM/entity-type/entities/entity-type.entity'; +import { EntityTypeService } from '@/CRM/entity-type/entity-type.service'; +import { EntityTypeLinkService } from '@/CRM/entity-type-link/entity-type-link.service'; +import { EntityTypeFeatureService } from '@/CRM/feature/entity-type-feature.service'; + +@Injectable() +export class RmsEntityTypeService { + constructor( + private readonly entityTypeService: EntityTypeService, + private readonly entityTypeLinkService: EntityTypeLinkService, + private readonly entityTypeFeatureService: EntityTypeFeatureService, + ) {} + + public async copyEntityTypes( + rmsAccountId: number, + accountId: number, + entityTypeIds?: number[], + ): Promise> { + const typeMap = new Map(); + const allTypes = await this.entityTypeService.findMany(rmsAccountId); + const types = entityTypeIds ? allTypes.filter((t) => entityTypeIds.includes(t.id)) : allTypes; + for (const type of types) { + const et = await this.entityTypeService.save(EntityType.copy(accountId, type)); + typeMap.set(type.id, et.id); + } + + for (const type of types) { + const newEntityTypeId = typeMap.get(type.id); + + const allLinks = await this.entityTypeLinkService.findMany({ accountId: rmsAccountId, sourceId: type.id }); + const links = allLinks.filter((l) => typeMap.has(l.targetId)); + for (const link of links) { + await this.entityTypeLinkService.create({ + accountId, + sourceId: newEntityTypeId, + dto: { targetId: typeMap.get(link.targetId), sortOrder: link.sortOrder }, + createBackLink: false, + }); + } + + const features = await this.entityTypeFeatureService.findByEntityTypeId(rmsAccountId, type.id); + const featureIds = features.map((f) => f.featureId); + await this.entityTypeFeatureService.setEntityTypeFeatures(accountId, newEntityTypeId, featureIds); + } + + return typeMap; + } +} diff --git a/backend/src/modules/setup/account-setup/services/crm/rms-entity.service.ts b/backend/src/modules/setup/account-setup/services/crm/rms-entity.service.ts new file mode 100644 index 0000000..86931c4 --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/rms-entity.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { EntityLinkService } from '@/CRM/entity-link/entity-link.service'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { Entity } from '@/CRM/Model/Entity/Entity'; + +@Injectable() +export class RmsEntityService { + constructor( + private readonly entityService: EntityService, + private readonly entityLinkService: EntityLinkService, + ) {} + + public async copyEntities( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entityTypesMap: Map, + boardsMap: Map, + stagesMap: Map, + ): Promise> { + const entitiesMap = new Map(); + + for (const [rmsEntityTypeId, entityTypeId] of entityTypesMap) { + const rmsEntities = await this.entityService.findMany(rmsAccountId, { entityTypeId: rmsEntityTypeId }); + for (const rmsEntity of rmsEntities) { + const entity = await this.entityService.save( + new Entity( + accountId, + rmsEntity.name, + entityTypeId, + usersMap.get(rmsEntity.responsibleUserId).id, + rmsEntity.boardId ? boardsMap.get(rmsEntity.boardId) : null, + rmsEntity.stageId ? stagesMap.get(rmsEntity.stageId) : null, + usersMap.get(rmsEntity.createdBy).id, + rmsEntity.weight, + rmsEntity.focused, + rmsEntity.closedAt, + rmsEntity.updatedAt, + rmsEntity.createdAt, + rmsEntity.participantIds ? rmsEntity.participantIds.map((id) => usersMap.get(id).id) : null, + null, + null, + ), + ); + entitiesMap.set(rmsEntity.id, entity.id); + } + } + + const linked: number[] = []; + for (const [rmsEntityId, entityId] of entitiesMap) { + const rmsAllLinks = await this.entityLinkService.findMany({ accountId: rmsAccountId, sourceId: rmsEntityId }); + const rmsLinks = rmsAllLinks.filter((l) => entitiesMap.has(l.targetId)); + for (const rmsLink of rmsLinks) { + if (!linked.includes(rmsLink.targetId)) { + await this.entityLinkService.create({ + accountId, + sourceId: entityId, + targetId: entitiesMap.get(rmsLink.targetId), + sortOrder: rmsLink.sortOrder, + }); + } + } + linked.push(rmsEntityId); + } + + return entitiesMap; + } +} diff --git a/backend/src/modules/setup/account-setup/services/crm/rms-field.service.ts b/backend/src/modules/setup/account-setup/services/crm/rms-field.service.ts new file mode 100644 index 0000000..dfddffd --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/rms-field.service.ts @@ -0,0 +1,266 @@ +import { Injectable } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; +import { FieldType, FieldTypes, FormulaUtil } from '@/modules/entity/entity-field/common'; +import { FieldGroupService } from '@/modules/entity/entity-field/field-group/field-group.service'; +import { Field, FieldService } from '@/modules/entity/entity-field/field'; +import { FieldOptionService } from '@/modules/entity/entity-field/field-option/field-option.service'; +import { FieldValueService } from '@/modules/entity/entity-field/field-value/field-value.service'; +import { + FieldPayloadOption, + FieldPayloadOptions, + FieldPayloadParticipants, + FieldPayloadValue, +} from '@/modules/entity/entity-field/field-value/types'; +import { FieldSettingsService } from '@/modules/entity/entity-field/field-settings/field-settings.service'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +@Injectable() +export class RmsFieldService { + constructor( + private readonly storageService: StorageService, + private readonly fieldGroupService: FieldGroupService, + private readonly fieldService: FieldService, + private readonly fieldOptionService: FieldOptionService, + private readonly fieldValueService: FieldValueService, + private readonly fieldSettingsService: FieldSettingsService, + ) {} + + public async copyAll( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entityTypesMap: Map, + entitiesMap: Map, + stagesMap: Map, + ) { + const fieldGroupsMap = await this.copyFieldGroups(rmsAccountId, accountId, entityTypesMap); + const fieldsMap = await this.copyFields(rmsAccountId, accountId, entityTypesMap, fieldGroupsMap); + const fieldOptionsMap = await this.copyFieldOptions(rmsAccountId, accountId, fieldsMap); + await this.copyFieldValues(rmsAccountId, accountId, usersMap, entitiesMap, fieldsMap, fieldOptionsMap); + await this.copyFieldSettings(rmsAccountId, accountId, usersMap, fieldsMap, stagesMap); + + return fieldsMap; + } + + private async copyFieldGroups( + rmsAccountId: number, + accountId: number, + entityTypesMap: Map, + ): Promise> { + const fieldGroupsMap = new Map(); + + for (const [rmsEntityTypeId, entityTypeId] of entityTypesMap) { + const rmsFieldGroups = await this.fieldGroupService.findMany({ + accountId: rmsAccountId, + entityTypeId: rmsEntityTypeId, + }); + for (const rmsFieldGroup of rmsFieldGroups) { + const fieldGroup = await this.fieldGroupService.create({ + accountId, + entityTypeId, + dto: { name: rmsFieldGroup.name, sortOrder: rmsFieldGroup.sortOrder }, + }); + fieldGroupsMap.set(rmsFieldGroup.id, fieldGroup.id); + } + } + + return fieldGroupsMap; + } + + private async copyFields( + rmsAccountId: number, + accountId: number, + entityTypesMap: Map, + fieldGroupsMap: Map, + ): Promise> { + const fieldsMap = new Map(); + + for (const [rmsEntityTypeId, entityTypeId] of entityTypesMap) { + const rmsFields = await this.fieldService.findMany({ accountId: rmsAccountId, entityTypeId: rmsEntityTypeId }); + for (const rmsField of rmsFields) { + const field = await this.fieldService.create({ + accountId, + entityTypeId, + dto: { + name: rmsField.name, + type: rmsField.type, + code: rmsField.code, + active: rmsField.active, + sortOrder: rmsField.sortOrder, + entityTypeId: entityTypeId, + fieldGroupId: fieldGroupsMap.get(rmsField.fieldGroupId), + value: null, + }, + options: { skipProcessing: true }, + }); + fieldsMap.set(rmsField.id, field.id); + } + } + + for (const [rmsEntityTypeId, entityTypeId] of entityTypesMap) { + const rmsFields = await this.fieldService.findMany({ + accountId: rmsAccountId, + entityTypeId: rmsEntityTypeId, + type: FieldTypes.formula, + }); + for (const rmsField of rmsFields.filter((f) => f.value)) { + const fieldId = fieldsMap.get(rmsField.id); + if (fieldId) { + await this.fieldService.update({ + accountId, + entityTypeId, + fieldId, + dto: { id: fieldId, value: this.copyFieldValue(rmsField, entityTypesMap, fieldsMap) }, + options: { skipProcessing: true }, + }); + } + } + } + + return fieldsMap; + } + + private copyFieldValue(field: Field, entityTypesMap: Map, fieldsMap: Map): string { + let formula = field.value; + const formulaKeys = FormulaUtil.extractVariables(field.value); + for (const formulaKey of formulaKeys) { + const { entityTypeId: rmsEntityTypeId, fieldId: rmsFieldId } = FormulaUtil.parseFieldKey(formulaKey); + const entityTypeId = entityTypesMap.get(rmsEntityTypeId); + const fieldId = fieldsMap.get(rmsFieldId); + if (entityTypeId && fieldId) { + formula = formula.replace(formulaKey, FormulaUtil.createFieldKey({ entityTypeId, fieldId })); + } else { + formula = formula.replace(formulaKey, '0'); + } + } + + return formula; + } + + private async copyFieldOptions( + rmsAccountId: number, + accountId: number, + fieldsMap: Map, + ): Promise> { + const fieldOptionsMap = new Map(); + + for (const [rmsFieldId, fieldId] of fieldsMap) { + const rmsFieldOptions = await this.fieldOptionService.findMany({ accountId: rmsAccountId, fieldId: rmsFieldId }); + for (const rmsFieldOption of rmsFieldOptions) { + const option = await this.fieldOptionService.create({ + accountId, + fieldId, + dto: { + label: rmsFieldOption.label, + color: rmsFieldOption.color, + sortOrder: rmsFieldOption.sortOrder, + }, + }); + fieldOptionsMap.set(rmsFieldOption.id, option.id); + } + } + + return fieldOptionsMap; + } + + private async copyFieldValues( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entitiesMap: Map, + fieldsMap: Map, + fieldOptionsMap: Map, + ): Promise { + for (const [rmsFieldId, fieldId] of fieldsMap) { + const rmsFieldValues = await this.fieldValueService.findMany({ accountId: rmsAccountId, fieldId: rmsFieldId }); + for (const rmsFieldValue of rmsFieldValues) { + const payload = rmsFieldValue.payload; + if (FieldTypes.select.includes(rmsFieldValue.fieldType)) { + (payload as FieldPayloadOption).optionId = fieldOptionsMap.get(rmsFieldValue.getValue()); + } else if (FieldTypes.multiSelect.includes(rmsFieldValue.fieldType)) { + (payload as FieldPayloadOptions).optionIds = rmsFieldValue + .getValue() + .map((id) => fieldOptionsMap.get(id)); + } else if (rmsFieldValue.fieldType === FieldType.Participant) { + (payload as FieldPayloadValue).value = usersMap.get(rmsFieldValue.getValue()).id; + } else if (rmsFieldValue.fieldType === FieldType.Participants) { + (payload as FieldPayloadParticipants).userIds = rmsFieldValue + .getValue() + .filter((id: number) => usersMap.has(id)) + .map((id: number) => usersMap.get(id).id); + } else if (rmsFieldValue.fieldType === FieldType.File) { + const fileIds: string[] = []; + const rmsFileIds = rmsFieldValue.getValue(); + if (rmsFileIds?.length) { + for (const rmsFileId of rmsFieldValue.getValue()) { + const { file, content } = await this.storageService.getFile({ + fileId: rmsFileId, + accountId: rmsAccountId, + }); + const fileInfo = await this.storageService.storeCommonFile({ + accountId, + file: StorageFile.fromFileInfo(file, Buffer.from(content)), + }); + fileIds.push(fileInfo.id); + } + } + (payload as FieldPayloadValue).value = fileIds.length ? fileIds : null; + } + + await this.fieldValueService.setValue({ + accountId, + entityId: entitiesMap.get(rmsFieldValue.entityId), + fieldId, + dto: { + fieldId, + fieldType: rmsFieldValue.fieldType, + payload, + }, + }); + } + } + } + + private async copyFieldSettings( + rmsAccountId: number, + accountId: number, + usersMap: Map, + fieldsMap: Map, + stagesMap: Map, + ): Promise { + for (const [rmsFieldId, fieldId] of fieldsMap) { + const rmsFieldSettings = await this.fieldSettingsService.findOne({ + accountId: rmsAccountId, + fieldId: rmsFieldId, + }); + if (rmsFieldSettings) { + await this.fieldSettingsService.update({ + accountId, + fieldId, + dto: { + excludeUserIds: rmsFieldSettings.excludeUserIds + ? rmsFieldSettings.excludeUserIds.map((id) => usersMap.get(id).id) + : null, + readonlyUserIds: rmsFieldSettings.readonlyUserIds + ? rmsFieldSettings.readonlyUserIds.map((id) => usersMap.get(id).id) + : null, + hideUserIds: rmsFieldSettings.hideUserIds + ? rmsFieldSettings.hideUserIds.map((id) => usersMap.get(id).id) + : null, + importantStageIds: rmsFieldSettings.importantStageIds + ? rmsFieldSettings.importantStageIds.map((id) => stagesMap.get(id)) + : null, + requiredStageIds: rmsFieldSettings.requiredStageIds + ? rmsFieldSettings.requiredStageIds.map((id) => stagesMap.get(id)) + : null, + hideStageIds: rmsFieldSettings.hideStageIds + ? rmsFieldSettings.hideStageIds.map((id) => stagesMap.get(id)) + : null, + }, + }); + } + } + } +} diff --git a/backend/src/modules/setup/account-setup/services/crm/rms-note.service.ts b/backend/src/modules/setup/account-setup/services/crm/rms-note.service.ts new file mode 100644 index 0000000..d874b72 --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/rms-note.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { NoteService } from '@/CRM/note/note.service'; + +@Injectable() +export class RmsNoteService { + constructor(private readonly noteService: NoteService) {} + + public async copyNotes( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entitiesMap: Map, + ): Promise> { + const notesMap = new Map(); + + const rmsNotes = await this.noteService.findMany({ accountId: rmsAccountId }); + for (const rmsNote of rmsNotes) { + if (entitiesMap.has(rmsNote.entityId)) { + const note = await this.noteService.create({ + accountId, + userId: usersMap.get(rmsNote.createdBy).id, + entityId: entitiesMap.get(rmsNote.entityId), + dto: { text: rmsNote.text }, + }); + notesMap.set(rmsNote.id, note.id); + } + } + + return notesMap; + } +} diff --git a/backend/src/modules/setup/account-setup/services/crm/rms-task.service.ts b/backend/src/modules/setup/account-setup/services/crm/rms-task.service.ts new file mode 100644 index 0000000..d7e5707 --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/crm/rms-task.service.ts @@ -0,0 +1,137 @@ +import { Injectable } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { TaskService } from '@/CRM/task/task.service'; +import { TaskSettingsService } from '@/CRM/task-settings/task-settings.service'; +import { TaskSettingsType } from '@/CRM/task-settings/enums/task-settings-type.enum'; +import { TaskSubtaskService } from '@/CRM/task-subtask/task-subtask.service'; + +@Injectable() +export class RmsTaskService { + constructor( + private readonly taskService: TaskService, + private readonly taskSettingsService: TaskSettingsService, + private readonly subtaskService: TaskSubtaskService, + ) {} + + public async copyAll( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entityTypesMap: Map, + entitiesMap: Map, + boardsMap: Map, + stagesMap: Map, + ): Promise> { + const taskSettingsMap = await this.copyTaskSettings(rmsAccountId, accountId, entityTypesMap, boardsMap); + + const tasksMap = await this.copyTasks( + rmsAccountId, + accountId, + usersMap, + boardsMap, + stagesMap, + entitiesMap, + taskSettingsMap, + ); + + await this.copySubtasks(rmsAccountId, accountId, tasksMap); + + return tasksMap; + } + + private async copyTaskSettings( + rmsAccountId: number, + accountId: number, + entityTypesMap: Map, + boardsMap: Map, + ): Promise> { + const taskSettingsMap = new Map(); + + const rmsTaskSettings = await this.taskSettingsService.findMany(rmsAccountId); + for (const rmsTaskSetting of rmsTaskSettings) { + const recordId = this.getRecordId(rmsTaskSetting.type, rmsTaskSetting.recordId, entityTypesMap, boardsMap); + if (recordId) { + const taskSetting = await this.taskSettingsService.create(accountId, { + type: rmsTaskSetting.type, + recordId: recordId, + activeFields: rmsTaskSetting.activeFields, + }); + taskSettingsMap.set(rmsTaskSetting.id, taskSetting.id); + } + } + + return taskSettingsMap; + } + + private getRecordId( + type: TaskSettingsType, + recordId: number, + entityTypesMap: Map, + boardsMap: Map, + ): number | null { + if (type === TaskSettingsType.EntityType) { + return entityTypesMap.get(recordId); + } + if (type === TaskSettingsType.TaskBoard) { + return boardsMap.get(recordId); + } + return null; + } + + private async copyTasks( + rmsAccountId: number, + accountId: number, + usersMap: Map, + boardsMap: Map, + stagesMap: Map, + entitiesMap: Map, + taskSettingsMap: Map, + ): Promise> { + const tasksMap = new Map(); + + const rmsTasks = await this.taskService.findMany({ accountId: rmsAccountId }); + for (const rmsTask of rmsTasks) { + const stageId = rmsTask.stageId ? stagesMap.get(rmsTask.stageId) : null; + if (rmsTask.stageId && !stageId) continue; + + const task = await this.taskService.create({ + accountId, + user: usersMap.get(rmsTask.createdBy), + dto: { + responsibleUserId: usersMap.get(rmsTask.responsibleUserId).id, + startDate: rmsTask.startDate ? rmsTask.startDate.toISOString() : null, + endDate: rmsTask.endDate ? rmsTask.endDate.toISOString() : null, + text: rmsTask.text, + isResolved: rmsTask.isResolved, + resolvedDate: rmsTask.resolvedDate ? rmsTask.resolvedDate.toISOString() : null, + weight: rmsTask.weight, + entityId: rmsTask.entityId ? entitiesMap.get(rmsTask.entityId) : null, + title: rmsTask.title, + plannedTime: rmsTask.plannedTime, + boardId: rmsTask.boardId ? boardsMap.get(rmsTask.boardId) : null, + stageId: stageId, + settingsId: rmsTask.settingsId ? taskSettingsMap.get(rmsTask.settingsId) : null, + }, + }); + + tasksMap.set(rmsTask.id, task.id); + } + + return tasksMap; + } + + private async copySubtasks(rmsAccountId: number, accountId: number, tasksMap: Map) { + const rmsSubtasks = await this.subtaskService.findMany(rmsAccountId); + + for (const rmsSubtask of rmsSubtasks) { + if (tasksMap.has(rmsSubtask.taskId)) { + await this.subtaskService.create(accountId, tasksMap.get(rmsSubtask.taskId), { + text: rmsSubtask.text, + resolved: rmsSubtask.resolved, + }); + } + } + } +} diff --git a/backend/src/modules/setup/account-setup/services/index.ts b/backend/src/modules/setup/account-setup/services/index.ts new file mode 100644 index 0000000..5bde882 --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/index.ts @@ -0,0 +1,5 @@ +export * from './crm'; +export * from './setup-crm.service'; +export * from './setup-iam.service'; +export * from './setup-products.service'; +export * from './setup-scheduler.service'; diff --git a/backend/src/modules/setup/account-setup/services/setup-crm.service.ts b/backend/src/modules/setup/account-setup/services/setup-crm.service.ts new file mode 100644 index 0000000..4169e20 --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/setup-crm.service.ts @@ -0,0 +1,81 @@ +import { Injectable } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; +import { + RmsActivityService, + RmsBoardService, + RmsEntityTypeService, + RmsEntityService, + RmsFieldService, + RmsNoteService, + RmsTaskService, +} from './crm'; + +interface CrmMaps { + entityTypesMap: Map; + entitiesMap: Map; + activitiesMap: Map; + notesMap: Map; + tasksMap: Map; +} + +@Injectable() +export class SetupCrmService { + constructor( + private readonly rmsActivityService: RmsActivityService, + private readonly rmsBoardService: RmsBoardService, + private readonly rmsEntityTypeService: RmsEntityTypeService, + private readonly rmsEntityService: RmsEntityService, + private readonly rmsFieldService: RmsFieldService, + private readonly rmsNoteService: RmsNoteService, + private readonly rmsTaskService: RmsTaskService, + ) {} + + public async setupDefault(accountId: number, owner: User) { + await this.rmsBoardService.setupDefault(accountId, owner); + await this.rmsActivityService.setupDefault(accountId); + } + + public async copyAll( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entityTypeIds?: number[], + ): Promise { + const entityTypesMap = await this.rmsEntityTypeService.copyEntityTypes(rmsAccountId, accountId, entityTypeIds); + + const { boardsMap, stagesMap } = await this.rmsBoardService.copyBoardsAndStages( + rmsAccountId, + accountId, + usersMap, + entityTypesMap, + ); + + const entitiesMap = await this.rmsEntityService.copyEntities( + rmsAccountId, + accountId, + usersMap, + entityTypesMap, + boardsMap, + stagesMap, + ); + + await this.rmsFieldService.copyAll(rmsAccountId, accountId, usersMap, entityTypesMap, entitiesMap, stagesMap); + + const tasksMap = await this.rmsTaskService.copyAll( + rmsAccountId, + accountId, + usersMap, + entityTypesMap, + entitiesMap, + boardsMap, + stagesMap, + ); + + const activitiesMap = await this.rmsActivityService.copyAll(rmsAccountId, accountId, usersMap, entitiesMap); + + const notesMap = await this.rmsNoteService.copyNotes(rmsAccountId, accountId, usersMap, entitiesMap); + + return { entityTypesMap, entitiesMap, activitiesMap, notesMap, tasksMap }; + } +} diff --git a/backend/src/modules/setup/account-setup/services/setup-iam.service.ts b/backend/src/modules/setup/account-setup/services/setup-iam.service.ts new file mode 100644 index 0000000..8b5089d --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/setup-iam.service.ts @@ -0,0 +1,106 @@ +import { Injectable } from '@nestjs/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { UserRole } from '@/modules/iam/common/enums/user-role.enum'; +import { DepartmentService } from '@/modules/iam/department/department.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { UserService } from '@/modules/iam/user/user.service'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +interface IAMMaps { + usersMap: Map; + rmsOwner: User; + departmentsMap: Map; +} + +@Injectable() +export class SetupIAMService { + constructor( + private readonly userService: UserService, + private readonly departmentService: DepartmentService, + private readonly storageService: StorageService, + ) {} + + public async copyAll(rmsAccountId: number, account: Account, owner: User): Promise { + const departmentsMap = await this.copyDepartments(rmsAccountId, account.id); + const [rmsOwner, usersMap] = await this.copyUsers(rmsAccountId, account, owner, departmentsMap); + + return { usersMap, rmsOwner, departmentsMap }; + } + + private async copyDepartments( + rmsAccountId: number, + accountId: number, + departmentMap: Map = new Map(), + parentId: number = null, + ): Promise> { + const rmsDepartments = await this.departmentService.getHierarchy({ + accountId: rmsAccountId, + departmentId: parentId, + expand: ['settings'], + }); + for (const rmsDepartment of rmsDepartments) { + const department = await this.departmentService.create({ + accountId, + dto: { + name: rmsDepartment.name, + parentId: rmsDepartment.parentId ? departmentMap.get(rmsDepartment.parentId) : null, + settings: rmsDepartment.settings ? rmsDepartment.settings.toDto() : undefined, + }, + }); + departmentMap.set(rmsDepartment.id, department.id); + await this.copyDepartments(rmsAccountId, accountId, departmentMap, rmsDepartment.id); + } + + return departmentMap; + } + + private async copyUsers( + rmsAccountId: number, + account: Account, + owner: User, + departmentsMap: Map, + ): Promise<[User, Map]> { + const usersMap = new Map(); + const rmsUsers = await this.userService.findMany({ accountId: rmsAccountId }); + let rmsOwner: User = null; + for (const rmsUser of rmsUsers) { + if (rmsUser.role === UserRole.OWNER) { + usersMap.set(rmsUser.id, owner); + rmsOwner = rmsUser; + } else { + const user = await this.userService.create({ + account, + dto: { + firstName: rmsUser.firstName, + lastName: rmsUser.lastName, + email: `${account.id}${rmsUser.email}`, + password: `${account.id}${rmsUser.email}`, + phone: rmsUser.phone, + role: rmsUser.role, + isActive: rmsUser.isActive, + departmentId: rmsUser.departmentId ? departmentsMap.get(rmsUser.departmentId) : null, + position: rmsUser.position, + }, + }); + + if (rmsUser.avatarId) { + const { file, content } = await this.storageService.getFile({ + fileId: rmsUser.avatarId, + accountId: rmsAccountId, + }); + await this.userService.setAvatar({ + account, + userId: user.id, + file: StorageFile.fromFileInfo(file, Buffer.from(content)), + }); + } + + usersMap.set(rmsUser.id, user); + } + } + + return [rmsOwner, usersMap]; + } +} diff --git a/backend/src/modules/setup/account-setup/services/setup-products.service.ts b/backend/src/modules/setup/account-setup/services/setup-products.service.ts new file mode 100644 index 0000000..e79bace --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/setup-products.service.ts @@ -0,0 +1,338 @@ +import { Injectable } from '@nestjs/common'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { StorageService } from '@/modules/storage/storage.service'; +import { StorageFile } from '@/modules/storage/types/storage-file'; + +import { ProductsSectionService } from '@/modules/inventory/products-section/services/products-section.service'; +import { RentalIntervalService } from '@/modules/inventory/rental-interval/rental-interval.service'; +import { WarehouseService } from '@/modules/inventory/warehouse/warehouse.service'; +import { ProductCategoryService } from '@/modules/inventory/product-category/product-category.service'; +import { ProductService } from '@/modules/inventory/product/product.service'; +import { OrderStatusService } from '@/modules/inventory/order-status/order-status.service'; +import { OrderService } from '@/modules/inventory/order/services/order.service'; +import { RentalOrderService } from '@/modules/inventory/rental-order/services/rental-order.service'; + +interface ProductsMaps { + sectionsMap: Map; + productsMap: Map; + salesOrdersMap: Map; + rentalOrdersMap: Map; +} + +@Injectable() +export class SetupProductsService { + constructor( + private readonly storageService: StorageService, + private readonly productsSectionService: ProductsSectionService, + private readonly rentalIntervalService: RentalIntervalService, + private readonly warehouseService: WarehouseService, + private readonly productCategoryService: ProductCategoryService, + private readonly productService: ProductService, + private readonly orderStatusService: OrderStatusService, + private readonly orderService: OrderService, + private readonly rentalOrderService: RentalOrderService, + ) {} + + public async copyAll( + rmsAccount: Account, + rmsOwner: User, + account: Account, + owner: User, + entityTypesMap: Map, + entitiesMap: Map, + sectionIds?: number[], + ): Promise { + const sectionsMap = await this.copyProductsSections( + rmsAccount.id, + rmsOwner, + account.id, + entityTypesMap, + sectionIds, + ); + const warehousesMap = await this.copyWarehouses(rmsAccount.id, rmsOwner, account.id, owner, sectionsMap); + const categoriesMap = await this.copyProductCategories(rmsAccount.id, account.id, owner, sectionsMap); + const productsMap = await this.copyProducts( + rmsAccount, + rmsOwner, + account, + owner, + sectionsMap, + categoriesMap, + warehousesMap, + ); + const orderStatusesMap = await this.copyOrderStatuses(rmsAccount.id, account.id); + const salesOrdersMap = await this.copySalesOrders( + rmsAccount.id, + rmsOwner, + account.id, + owner, + entitiesMap, + sectionsMap, + warehousesMap, + productsMap, + orderStatusesMap, + ); + const rentalOrdersMap = await this.copyRentalOrders( + rmsAccount.id, + rmsOwner, + account.id, + owner, + entitiesMap, + sectionsMap, + warehousesMap, + productsMap, + ); + + return { sectionsMap, productsMap, salesOrdersMap, rentalOrdersMap }; + } + + private async copyProductsSections( + rmsAccountId: number, + rmsOwner: User, + accountId: number, + entityTypesMap: Map, + sectionIds?: number[], + ): Promise> { + const allSections = await this.productsSectionService.getAllFull(rmsAccountId, rmsOwner); + const rmsSections = sectionIds ? allSections.filter((s) => sectionIds.includes(s.id)) : allSections; + + const sectionsMap = new Map(); + for (const rmsSection of rmsSections) { + const section = await this.productsSectionService.create(accountId, rmsSection); + sectionsMap.set(rmsSection.id, section.id); + + if (rmsSection.links) { + const entityTypeIds: number[] = []; + for (const link of rmsSection.links.filter((l) => entityTypesMap.has(l.entityTypeId))) { + entityTypeIds.push(entityTypesMap.get(link.entityTypeId)); + } + if (entityTypeIds.length > 0) { + await this.productsSectionService.linkEntityTypes(accountId, section.id, entityTypeIds); + } + } + + const rmsRentalInterval = await this.rentalIntervalService.findRentalInterval(rmsAccountId, rmsSection.id); + if (rmsRentalInterval) { + await this.rentalIntervalService.setRentalInterval(accountId, section.id, { + type: rmsRentalInterval.type, + startTime: rmsRentalInterval.startTime, + }); + } + } + return sectionsMap; + } + + private async copyWarehouses( + rmsAccountId: number, + rmsOwner: User, + accountId: number, + owner: User, + sectionsMap: Map, + ): Promise> { + const warehousesMap = new Map(); + for (const [rmsSectionId, sectionId] of sectionsMap) { + const rmsWarehouses = await this.warehouseService.findMany({ + user: rmsOwner, + filter: { accountId: rmsAccountId, sectionId: rmsSectionId }, + }); + for (const rmsWarehouse of rmsWarehouses) { + const warehouse = await this.warehouseService.create({ + accountId, + user: owner, + sectionId, + dto: { name: rmsWarehouse.name }, + }); + warehousesMap.set(rmsWarehouse.id, warehouse.id); + } + } + return warehousesMap; + } + + private async copyProductCategories( + rmsAccountId: number, + accountId: number, + owner: User, + sectionMap: Map, + ): Promise> { + const categoriesMap: Map = new Map(); + for (const [rmsSectionId, sectionId] of sectionMap) { + const rmsCategories = await this.productCategoryService.getCategoriesFlat(rmsAccountId, rmsSectionId, null); + for (const rmsCategory of rmsCategories) { + const category = await this.productCategoryService.create(accountId, owner, sectionId, { + name: rmsCategory.name, + parentId: rmsCategory.parentId ? categoriesMap.get(rmsCategory.parentId) : null, + }); + categoriesMap.set(rmsCategory.id, category.id); + } + } + return categoriesMap; + } + + private async copyProducts( + rmsAccount: Account, + rmsOwner: User, + account: Account, + owner: User, + sectionsMap: Map, + categoriesMap: Map, + warehousesMap: Map, + ): Promise> { + const productsMap = new Map(); + + for (const [rmsSectionId, sectionId] of sectionsMap) { + const rmsProducts = await this.productService.findManyFull(rmsAccount, rmsOwner, rmsSectionId); + for (const rmsProduct of rmsProducts) { + const prices = rmsProduct.prices.map((p) => { + return { name: p.name, unitPrice: p.unitPrice, currency: p.currency, maxDiscount: p.maxDiscount }; + }); + const stocks = rmsProduct.stocks.map((s) => { + return { warehouseId: warehousesMap.get(s.warehouseId), stockQuantity: s.stockQuantity }; + }); + const photos: string[] = []; + const product = await this.productService.create(account, owner, sectionId, { + name: rmsProduct.name, + type: rmsProduct.type, + description: rmsProduct.description, + sku: rmsProduct.sku, + unit: rmsProduct.unit, + tax: rmsProduct.tax, + categoryId: rmsProduct.categoryId ? categoriesMap.get(rmsProduct.categoryId) : null, + prices, + stocks, + photoFileIds: photos, + }); + + for (const photo of rmsProduct.photoFileLinks) { + const { file, content } = await this.storageService.getFile({ + fileId: photo.fileId, + accountId: rmsAccount.id, + }); + await this.productService.uploadPhotos(account, owner, sectionId, product.id, [ + StorageFile.fromFileInfo(file, Buffer.from(content)), + ]); + } + + productsMap.set(rmsProduct.id, product.id); + } + } + + return productsMap; + } + + private async copyOrderStatuses(rmsAccountId: number, accountId: number) { + const orderStatusesMap = new Map(); + + const rmsOrderStatuses = await this.orderStatusService.findMany(rmsAccountId); + for (const rmsOrderStatus of rmsOrderStatuses) { + const orderStatus = await this.orderStatusService.create(accountId, rmsOrderStatus); + orderStatusesMap.set(rmsOrderStatus.id, orderStatus.id); + } + + return orderStatusesMap; + } + + private async copySalesOrders( + rmsAccountId: number, + rmsOwner: User, + accountId: number, + owner: User, + entitiesMap: Map, + sectionsMap: Map, + warehousesMap: Map, + productsMap: Map, + orderStatusesMap: Map, + ): Promise> { + const ordersMap = new Map(); + for (const [rmsSectionId, sectionId] of sectionsMap) { + const allRmsOrders = await this.orderService.findMany( + rmsAccountId, + rmsOwner, + { sectionId: rmsSectionId }, + { expand: ['items'] }, + ); + const rmsOrders = allRmsOrders.filter((o) => entitiesMap.has(o.entityId)); + for (const rmsOrder of rmsOrders) { + const items = rmsOrder.items + .map((i) => { + const reservations = i.reservations.map((r) => { + return { warehouseId: warehousesMap.get(r.warehouseId), quantity: r.quantity }; + }); + return { + id: undefined, + unitPrice: i.unitPrice, + quantity: i.quantity, + tax: i.tax, + discount: i.discount, + productId: productsMap.get(i.productId), + sortOrder: i.sortOrder, + productInfo: undefined, + reservations, + }; + }) + .filter((i) => i.productId); + if (items.length) { + const order = await this.orderService.create(accountId, owner, sectionId, { + entityId: entitiesMap.get(rmsOrder.entityId), + currency: rmsOrder.currency, + taxIncluded: rmsOrder.taxIncluded, + statusId: rmsOrder.statusId ? orderStatusesMap.get(rmsOrder.statusId) : null, + warehouseId: rmsOrder.warehouseId ? warehousesMap.get(rmsOrder.warehouseId) : null, + items, + }); + ordersMap.set(rmsOrder.id, order.id); + } + } + } + return ordersMap; + } + + private async copyRentalOrders( + rmsAccountId: number, + rmsOwner: User, + accountId: number, + owner: User, + entitiesMap: Map, + sectionsMap: Map, + warehousesMap: Map, + productsMap: Map, + ): Promise> { + const ordersMap = new Map(); + for (const [rmsSectionId, sectionId] of sectionsMap) { + const allRmsOrders = await this.rentalOrderService.findMany(rmsAccountId, rmsOwner, { + sectionId: rmsSectionId, + }); + const rmsOrders = allRmsOrders.filter((o) => entitiesMap.has(o.entityId)); + for (const rmsOrder of rmsOrders) { + const periods = rmsOrder.periods.map((p) => { + return { startDate: p.startDate.toISOString(), endDate: p.endDate.toISOString() }; + }); + const items = rmsOrder.items + .map((i) => { + return { + productId: productsMap.get(i.productId), + unitPrice: i.unitPrice, + tax: i.tax, + discount: i.discount, + sortOrder: i.sortOrder, + }; + }) + .filter((i) => i.productId); + if (items.length) { + const order = await this.rentalOrderService.create(accountId, owner, sectionId, { + warehouseId: rmsOrder.warehouseId ? warehousesMap.get(rmsOrder.warehouseId) : null, + currency: rmsOrder.currency, + taxIncluded: rmsOrder.taxIncluded, + status: rmsOrder.status, + entityId: entitiesMap.get(rmsOrder.entityId), + periods, + items, + }); + ordersMap.set(rmsOrder.id, order.id); + } + } + } + return ordersMap; + } +} diff --git a/backend/src/modules/setup/account-setup/services/setup-scheduler.service.ts b/backend/src/modules/setup/account-setup/services/setup-scheduler.service.ts new file mode 100644 index 0000000..e586bca --- /dev/null +++ b/backend/src/modules/setup/account-setup/services/setup-scheduler.service.ts @@ -0,0 +1,149 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; + +import { ScheduleService } from '@/modules/scheduler/schedule/services/schedule.service'; +import { ScheduleAppointmentService } from '@/modules/scheduler/schedule-appointment/schedule-appointment.service'; + +interface SchedulerMaps { + schedulesMap: Map; + appointmentsMap: Map; +} + +@Injectable() +export class SetupSchedulerService { + private readonly logger = new Logger(SetupSchedulerService.name); + + constructor( + private readonly scheduleService: ScheduleService, + private readonly appointmentService: ScheduleAppointmentService, + ) {} + + public async copyAll( + rmsAccountId: number, + accountId: number, + owner: User, + usersMap: Map, + departmentsMap: Map, + entityTypesMap: Map, + entitiesMap: Map, + sectionsMap: Map, + ordersMap: Map, + schedulerIds?: number[], + ): Promise { + const { schedulesMap, performersMap } = await this.copySchedules( + rmsAccountId, + accountId, + owner, + usersMap, + departmentsMap, + entityTypesMap, + sectionsMap, + schedulerIds, + ); + + const appointmentsMap = await this.copyAppointments( + rmsAccountId, + accountId, + usersMap, + entitiesMap, + ordersMap, + schedulesMap, + performersMap, + ); + + return { schedulesMap, appointmentsMap }; + } + + private async copySchedules( + rmsAccountId: number, + accountId: number, + owner: User, + usersMap: Map, + departmentsMap: Map, + entityTypesMap: Map, + sectionsMap: Map, + schedulerIds?: number[], + ): Promise<{ schedulesMap: Map; performersMap: Map }> { + const allSchedules = await this.scheduleService.findMany({ filter: { accountId: rmsAccountId } }); + const rmsSchedules = schedulerIds ? allSchedules.filter((s) => schedulerIds.includes(s.id)) : allSchedules; + + const schedulesMap = new Map(); + const performersMap = new Map(); + for (const rmsSchedule of rmsSchedules) { + try { + const schedule = await this.scheduleService.create({ + accountId, + userId: owner.id, + dto: { + name: rmsSchedule.name, + icon: rmsSchedule.icon, + type: rmsSchedule.type, + timePeriod: rmsSchedule.timePeriod, + appointmentLimit: rmsSchedule.appointmentLimit, + entityTypeId: rmsSchedule.entityTypeId ? entityTypesMap.get(rmsSchedule.entityTypeId) : null, + productsSectionId: rmsSchedule.productsSectionId ? sectionsMap.get(rmsSchedule.productsSectionId) : null, + performers: rmsSchedule.performers.map((p) => ({ + type: p.type, + userId: p.userId ? usersMap.get(p.userId).id : null, + departmentId: p.departmentId ? departmentsMap.get(p.departmentId) : null, + })), + }, + }); + schedulesMap.set(rmsSchedule.id, schedule.id); + rmsSchedule.performers.forEach((rmsPerformer, idx) => + performersMap.set(rmsPerformer.id, schedule.performers[idx].id), + ); + } catch (e) { + this.logger.error(`Error during schedule creation for account ${accountId}`, (e as Error)?.stack); + continue; + } + } + + return { schedulesMap, performersMap }; + } + + private async copyAppointments( + rmsAccountId: number, + accountId: number, + usersMap: Map, + entitiesMap: Map, + ordersMap: Map, + schedulesMap: Map, + performersMap: Map, + ): Promise> { + const appointmentsMap = new Map(); + + for (const [rmsScheduleId, scheduleId] of schedulesMap) { + const rmsAppointments = await this.appointmentService.findMany({ + filter: { accountId: rmsAccountId, scheduleId: rmsScheduleId }, + joinPerformer: true, + }); + for (const rmsAppointment of rmsAppointments) { + try { + const appointment = await this.appointmentService.create({ + accountId, + user: usersMap.get(rmsAppointment.ownerId), + dto: { + scheduleId, + startDate: rmsAppointment.startDate.toISOString(), + endDate: rmsAppointment.endDate.toISOString(), + status: rmsAppointment.status, + title: rmsAppointment.title, + comment: rmsAppointment.comment, + entityId: rmsAppointment.entityId ? entitiesMap.get(rmsAppointment.entityId) : null, + performerId: performersMap.get(rmsAppointment.performerId), + orderId: rmsAppointment.orderId ? ordersMap.get(rmsAppointment.orderId) : null, + }, + }); + appointmentsMap.set(rmsAppointment.id, appointment.id); + } catch (e) { + this.logger.error(`Error during appointment creation for schedule ${scheduleId}`, (e as Error)?.stack); + continue; + } + } + } + + return appointmentsMap; + } +} diff --git a/backend/src/modules/setup/common/enums/demo-data-type.enum.ts b/backend/src/modules/setup/common/enums/demo-data-type.enum.ts new file mode 100644 index 0000000..e82e460 --- /dev/null +++ b/backend/src/modules/setup/common/enums/demo-data-type.enum.ts @@ -0,0 +1,9 @@ +export enum DemoDataType { + User = 'user', + Entity = 'entity', + Task = 'task', + Product = 'product', + SalesOrder = 'sales_order', + RentalOrder = 'rental_order', + ScheduleAppointment = 'schedule_appointment', +} diff --git a/backend/src/modules/setup/common/enums/index.ts b/backend/src/modules/setup/common/enums/index.ts new file mode 100644 index 0000000..4e19f1b --- /dev/null +++ b/backend/src/modules/setup/common/enums/index.ts @@ -0,0 +1,3 @@ +export * from './demo-data-type.enum'; +export * from './industry-code.enum'; +export * from './rmx-module-prefix.enum'; diff --git a/backend/src/modules/setup/common/enums/industry-code.enum.ts b/backend/src/modules/setup/common/enums/industry-code.enum.ts new file mode 100644 index 0000000..e188605 --- /dev/null +++ b/backend/src/modules/setup/common/enums/industry-code.enum.ts @@ -0,0 +1,8 @@ +export enum IndustryCode { + IT_AND_DEVELOPMENT = 'it_and_development', + CONSTRUCTION_AND_ENGINEERING = 'construction_and_engineering', + ADVERTISING_AND_MARKETING = 'advertising_and_marketing', + CONSULTING_AND_OUTSOURCING = 'consulting_and_outsourcing', + MANUFACTURING = 'manufacturing', + EDUCATION = 'education', +} diff --git a/backend/src/modules/setup/common/enums/rmx-module-prefix.enum.ts b/backend/src/modules/setup/common/enums/rmx-module-prefix.enum.ts new file mode 100644 index 0000000..77ed845 --- /dev/null +++ b/backend/src/modules/setup/common/enums/rmx-module-prefix.enum.ts @@ -0,0 +1,5 @@ +export enum RmsModulePrefix { + EntityType = 'et', + ProductSection = 'ps', + Scheduler = 'sc', +} diff --git a/backend/src/modules/setup/common/index.ts b/backend/src/modules/setup/common/index.ts new file mode 100644 index 0000000..968a286 --- /dev/null +++ b/backend/src/modules/setup/common/index.ts @@ -0,0 +1,2 @@ +export * from './enums'; +export * from './types'; diff --git a/backend/src/modules/setup/common/types/index.ts b/backend/src/modules/setup/common/types/index.ts new file mode 100644 index 0000000..ed0f570 --- /dev/null +++ b/backend/src/modules/setup/common/types/index.ts @@ -0,0 +1 @@ +export * from './rms-modules'; diff --git a/backend/src/modules/setup/common/types/rms-modules.ts b/backend/src/modules/setup/common/types/rms-modules.ts new file mode 100644 index 0000000..7e84d4b --- /dev/null +++ b/backend/src/modules/setup/common/types/rms-modules.ts @@ -0,0 +1,5 @@ +export class RmsModules { + entityTypeIds: number[] = []; + productSectionIds: number[] = []; + schedulerIds: number[] = []; +} diff --git a/backend/src/modules/setup/demo-data/demo-data.controller.ts b/backend/src/modules/setup/demo-data/demo-data.controller.ts new file mode 100644 index 0000000..fba6fee --- /dev/null +++ b/backend/src/modules/setup/demo-data/demo-data.controller.ts @@ -0,0 +1,28 @@ +import { Controller, Get, Delete } from '@nestjs/common'; +import { ApiOkResponse, ApiDefaultResponse, ApiTags } from '@nestjs/swagger'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { AuthDataPrefetch } from '@/modules/iam/common/decorators/auth-data-prefetch.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { DemoDataService } from './demo-data.service'; + +@ApiTags('setup/demo-data') +@Controller('/setup/demo-data') +@JwtAuthorized() +export class DemoDataController { + constructor(private service: DemoDataService) {} + + @ApiDefaultResponse({ description: 'Check demo data exists in account', type: Boolean }) + @Get('exists') + public async exists(@CurrentAuth() { accountId }: AuthData): Promise { + return this.service.exists(accountId); + } + + @ApiOkResponse({ description: 'Delete all demo data in account' }) + @AuthDataPrefetch({ user: true }) + @Delete() + public async delete(@CurrentAuth() { accountId, user }: AuthData): Promise { + await this.service.delete(accountId, user); + } +} diff --git a/backend/src/modules/setup/demo-data/demo-data.module.ts b/backend/src/modules/setup/demo-data/demo-data.module.ts new file mode 100644 index 0000000..46f0224 --- /dev/null +++ b/backend/src/modules/setup/demo-data/demo-data.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { CrmModule } from '@/CRM/crm.module'; +import { InventoryModule } from '@/modules/inventory/inventory.module'; +import { SchedulerModule } from '@/modules/scheduler/scheduler.module'; + +import { DemoData } from './entities/demo-data.entity'; +import { DemoDataController } from './demo-data.controller'; +import { DemoDataService } from './demo-data.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([DemoData]), IAMModule, CrmModule, InventoryModule, SchedulerModule], + controllers: [DemoDataController], + providers: [DemoDataService], + exports: [DemoDataService], +}) +export class DemoDataModule {} diff --git a/backend/src/modules/setup/demo-data/demo-data.service.ts b/backend/src/modules/setup/demo-data/demo-data.service.ts new file mode 100644 index 0000000..97541a7 --- /dev/null +++ b/backend/src/modules/setup/demo-data/demo-data.service.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { ProductService } from '@/modules/inventory/product/product.service'; +import { OrderService } from '@/modules/inventory/order/services/order.service'; +import { RentalOrderService } from '@/modules/inventory/rental-order/services/rental-order.service'; +import { ScheduleAppointmentService } from '@/modules/scheduler/schedule-appointment/schedule-appointment.service'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { TaskService } from '@/CRM/task/task.service'; + +import { DemoDataType } from '../common/enums/demo-data-type.enum'; +import { DemoData } from './entities/demo-data.entity'; + +@Injectable() +export class DemoDataService { + constructor( + @InjectRepository(DemoData) + private readonly repository: Repository, + private readonly userService: UserService, + private readonly entityService: EntityService, + private readonly taskService: TaskService, + private readonly productService: ProductService, + private readonly orderService: OrderService, + private readonly rentalService: RentalOrderService, + private readonly appointmentService: ScheduleAppointmentService, + ) {} + + public async create(accountId: number, type: DemoDataType, ids: number[]) { + await this.repository.insert(new DemoData(accountId, type, ids)); + } + + public async exists(accountId: number): Promise { + return (await this.repository.countBy({ accountId })) > 0; + } + + public async delete(accountId: number, user: User) { + const demoData = await this.repository.findBy({ accountId }); + + const appointments = demoData.filter((dd) => dd.type === DemoDataType.ScheduleAppointment); + if (appointments.length) { + await Promise.all( + appointments.map((a) => this.appointmentService.delete({ accountId, user, filter: { appointmentId: a.ids } })), + ); + await this.repository.delete(appointments.map((dd) => dd.id)); + } + + const rentalOrders = demoData.filter((dd) => dd.type === DemoDataType.RentalOrder); + if (rentalOrders.length) { + await Promise.all(rentalOrders.map((ro) => this.rentalService.delete(accountId, user, { orderId: ro.ids }))); + await this.repository.delete(rentalOrders.map((dd) => dd.id)); + } + + const salesOrders = demoData.filter((dd) => dd.type === DemoDataType.SalesOrder); + if (salesOrders.length) { + await Promise.all(salesOrders.map((order) => this.orderService.delete(accountId, { orderId: order.ids }))); + await this.repository.delete(salesOrders.map((dd) => dd.id)); + } + + const products = demoData.filter((dd) => dd.type === DemoDataType.Product); + if (products.length) { + await Promise.all(products.map((product) => this.productService.delete(accountId, product.ids))); + await this.repository.delete(products.map((dd) => dd.id)); + } + + const tasks = demoData.filter((dd) => dd.type === DemoDataType.Task); + if (tasks.length) { + await Promise.all( + tasks.map((task) => this.taskService.delete({ user, filter: { accountId, taskId: task.ids } })), + ); + await this.repository.delete(tasks.map((dd) => dd.id)); + } + + const entities = demoData.filter((dd) => dd.type === DemoDataType.Entity); + if (entities.length) { + await Promise.all(entities.map((entity) => this.entityService.deleteMany(accountId, user, entity.ids))); + await this.repository.delete(entities.map((dd) => dd.id)); + } + + const users = demoData.filter((dd) => dd.type === DemoDataType.User); + if (users.length) { + await Promise.all(users.map((user) => this.userService.delete({ accountId, userId: user.ids }))); + await this.repository.delete(users.map((dd) => dd.id)); + } + } +} diff --git a/backend/src/modules/setup/demo-data/entities/demo-data.entity.ts b/backend/src/modules/setup/demo-data/entities/demo-data.entity.ts new file mode 100644 index 0000000..81d011d --- /dev/null +++ b/backend/src/modules/setup/demo-data/entities/demo-data.entity.ts @@ -0,0 +1,30 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { DemoDataType } from '../../common/enums/demo-data-type.enum'; + +@Entity() +export class DemoData { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + type: DemoDataType; + + @Column('simple-array', { name: 'ids' }) + private _ids: string[]; + + constructor(accountId: number, type: DemoDataType, ids: number[]) { + this.accountId = accountId; + this.type = type; + this.ids = ids; + } + + public get ids(): number[] { + return this._ids?.map((id) => Number(id)) || []; + } + public set ids(value: number[]) { + this._ids = value?.map((id) => id.toString()) || []; + } +} diff --git a/backend/src/modules/setup/rms/dto/industry.dto.ts b/backend/src/modules/setup/rms/dto/industry.dto.ts new file mode 100644 index 0000000..c05ed63 --- /dev/null +++ b/backend/src/modules/setup/rms/dto/industry.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsString } from 'class-validator'; + +import { IndustryCode } from '../../common/enums/industry-code.enum'; +import { Industry } from '../entities/industry.entity'; +import { ReadyMadeSolutionDto } from './ready-made-solution.dto'; + +export class IndustryDto { + @ApiProperty({ enum: IndustryCode }) + @IsEnum(IndustryCode) + code: IndustryCode; + + @ApiProperty() + @IsString() + name: string; + + @ApiProperty() + @IsString() + color: string; + + @ApiProperty({ type: [ReadyMadeSolutionDto] }) + @IsArray() + readyMadeSolutions: ReadyMadeSolutionDto[]; + + constructor(code: IndustryCode, name: string, color: string, readyMadeSolutions: ReadyMadeSolutionDto[]) { + this.code = code; + this.name = name; + this.color = color; + this.readyMadeSolutions = readyMadeSolutions; + } + + public static fromModel(model: Industry, readyMadeSolutions: ReadyMadeSolutionDto[]) { + return new IndustryDto(model.code, model.name, model.color, readyMadeSolutions); + } +} diff --git a/backend/src/modules/setup/rms/dto/ready-made-solution.dto.ts b/backend/src/modules/setup/rms/dto/ready-made-solution.dto.ts new file mode 100644 index 0000000..54ee895 --- /dev/null +++ b/backend/src/modules/setup/rms/dto/ready-made-solution.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class ReadyMadeSolutionDto { + @ApiProperty() + @IsString() + code: string; + + @ApiProperty() + @IsString() + name: string; + + constructor(code: string, name: string) { + this.code = code; + this.name = name; + } +} diff --git a/backend/src/modules/setup/rms/entities/industry.entity.ts b/backend/src/modules/setup/rms/entities/industry.entity.ts new file mode 100644 index 0000000..60cb886 --- /dev/null +++ b/backend/src/modules/setup/rms/entities/industry.entity.ts @@ -0,0 +1,21 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { IndustryCode } from '../../common/enums/industry-code.enum'; + +@Entity() +export class Industry { + @PrimaryColumn() + code: IndustryCode; + + @Column() + name: string; + + @Column() + color: string; + + @Column() + sortOrder: number; + + @Column() + active: boolean; +} diff --git a/backend/src/modules/setup/rms/entities/ready-made-solution.entity.ts b/backend/src/modules/setup/rms/entities/ready-made-solution.entity.ts new file mode 100644 index 0000000..7278298 --- /dev/null +++ b/backend/src/modules/setup/rms/entities/ready-made-solution.entity.ts @@ -0,0 +1,29 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { IndustryCode } from '../../common/enums/industry-code.enum'; +import { ReadyMadeSolutionDto } from '../dto/ready-made-solution.dto'; + +@Entity() +export class ReadyMadeSolution { + @PrimaryColumn() + code: string; + + @Column() + name: string; + + @Column({ nullable: true }) + accountId: number | null; + + @Column() + sortOrder: number; + + @Column() + active: boolean; + + @Column() + industryCode: IndustryCode | null; + + public toDto(): ReadyMadeSolutionDto { + return new ReadyMadeSolutionDto(this.code, this.name); + } +} diff --git a/backend/src/modules/setup/rms/rms.controller.ts b/backend/src/modules/setup/rms/rms.controller.ts new file mode 100644 index 0000000..bf00aec --- /dev/null +++ b/backend/src/modules/setup/rms/rms.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { ApiAccessRequired } from '@/modules/iam/common/decorators/api-access-required.decorator'; + +import { IndustryService } from './services/industry.service'; +import { IndustryDto } from './dto/industry.dto'; + +@ApiTags('setup/rms') +@Controller('/setup/rms') +@ApiAccessRequired() +export class RmsController { + constructor(private readonly service: IndustryService) {} + + @ApiCreatedResponse({ description: 'Industries', type: [IndustryDto] }) + @Get('industries') + public async getIndustries(): Promise { + return await this.service.getIndustryDtos(); + } +} diff --git a/backend/src/modules/setup/rms/rms.module.ts b/backend/src/modules/setup/rms/rms.module.ts new file mode 100644 index 0000000..7d2bd4d --- /dev/null +++ b/backend/src/modules/setup/rms/rms.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import { Industry } from './entities/industry.entity'; +import { ReadyMadeSolution } from './entities/ready-made-solution.entity'; +import { IndustryService } from './services/industry.service'; +import { RmsService } from './services/rms.service'; +import { RmsController } from './rms.controller'; + +@Module({ + imports: [TypeOrmModule.forFeature([Industry, ReadyMadeSolution]), IAMModule], + controllers: [RmsController], + providers: [RmsService, IndustryService], + exports: [RmsService], +}) +export class RmsModule {} diff --git a/backend/src/modules/setup/rms/services/industry.service.ts b/backend/src/modules/setup/rms/services/industry.service.ts new file mode 100644 index 0000000..a991c12 --- /dev/null +++ b/backend/src/modules/setup/rms/services/industry.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { IndustryDto } from '../dto/industry.dto'; +import { Industry } from '../entities/industry.entity'; +import { RmsService } from './rms.service'; + +@Injectable() +export class IndustryService { + constructor( + @InjectRepository(Industry) + private readonly repository: Repository, + private readonly rmsService: RmsService, + ) {} + + public async getIndustryDtos(): Promise { + const industries = await this.repository + .createQueryBuilder() + .where({ active: true }) + .orderBy('sort_order', 'ASC') + .getMany(); + const solutions = await this.rmsService.findMany({ isActive: true }); + + const industryDtos: IndustryDto[] = []; + for (const industry of industries) { + const solutionDtos = solutions.filter((s) => s.industryCode === industry.code).map((s) => s.toDto()); + industryDtos.push(IndustryDto.fromModel(industry, solutionDtos)); + } + + return industryDtos; + } +} diff --git a/backend/src/modules/setup/rms/services/rms.service.ts b/backend/src/modules/setup/rms/services/rms.service.ts new file mode 100644 index 0000000..1ea2df6 --- /dev/null +++ b/backend/src/modules/setup/rms/services/rms.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { ReadyMadeSolution } from '../entities/ready-made-solution.entity'; + +interface FindFilter { + code?: string; + isActive?: boolean; +} + +@Injectable() +export class RmsService { + constructor( + @InjectRepository(ReadyMadeSolution) + private repository: Repository, + ) {} + + public async findOne(filter?: FindFilter): Promise { + return this.createQb(filter).getOne(); + } + + public async findMany(filter?: FindFilter): Promise { + return this.createQb(filter).getMany(); + } + + private createQb(filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('rms').orderBy('sort_order', 'ASC'); + + if (filter?.code) { + qb.where({ code: filter.code }); + } + if (filter?.isActive !== undefined) { + qb.where({ active: filter.isActive }); + } + + return qb; + } +} diff --git a/backend/src/modules/setup/setup.module.ts b/backend/src/modules/setup/setup.module.ts new file mode 100644 index 0000000..75c6041 --- /dev/null +++ b/backend/src/modules/setup/setup.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { AccountSetupModule } from './account-setup/account-setup.module'; +import { DemoDataModule } from './demo-data/demo-data.module'; +import { RmsModule } from './rms/rms.module'; +//import { TestDataModule } from './test-data/test-data.module'; + +@Module({ + imports: [RmsModule, DemoDataModule, AccountSetupModule /*, TestDataModule*/], +}) +export class SetupModule {} diff --git a/backend/src/modules/setup/test-data/dto/create-test-accounts-query.ts b/backend/src/modules/setup/test-data/dto/create-test-accounts-query.ts new file mode 100644 index 0000000..e1045e3 --- /dev/null +++ b/backend/src/modules/setup/test-data/dto/create-test-accounts-query.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsDateString, IsNumber, IsString } from 'class-validator'; + +export class CreateTestAccountsQuery { + @ApiProperty() + @IsString() + token: string; + + @ApiProperty() + @IsNumber() + count: number; + + @ApiProperty() + @IsDateString() + fromDate: string; + + @ApiProperty() + @IsDateString() + toDate: string; +} diff --git a/backend/src/modules/setup/test-data/entities/test-account.entity.ts b/backend/src/modules/setup/test-data/entities/test-account.entity.ts new file mode 100644 index 0000000..b2740d0 --- /dev/null +++ b/backend/src/modules/setup/test-data/entities/test-account.entity.ts @@ -0,0 +1,7 @@ +import { Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class TestAccount { + @PrimaryColumn() + accountId: number; +} diff --git a/backend/src/modules/setup/test-data/public-test-data.controller.ts b/backend/src/modules/setup/test-data/public-test-data.controller.ts new file mode 100644 index 0000000..ef55e84 --- /dev/null +++ b/backend/src/modules/setup/test-data/public-test-data.controller.ts @@ -0,0 +1,38 @@ +import { Controller, Get, Query, Redirect } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { UrlGeneratorService } from '@/common'; +import { ApiAccessRequired } from '@/modules/iam/common/decorators/api-access-required.decorator'; + +import { TestDataService } from './test-data.service'; +import { CreateTestAccountsQuery } from './dto/create-test-accounts-query'; + +@ApiExcludeController(true) +@Controller('/setup/test-data') +export class PublicTestDataController { + constructor( + private readonly service: TestDataService, + private readonly urlGeneratorService: UrlGeneratorService, + ) {} + + @Get('account/create') + @ApiAccessRequired() + public async createTestAccounts(@Query() query: CreateTestAccountsQuery) { + await this.service.createTestAccounts(query); + } + + @Redirect() + @Get('account/login') + public async loginTestUser() { + const link = await this.service.loginTestUser(); + const url = link + ? this.urlGeneratorService.createUrl({ + route: 'login-link', + subdomain: link.subdomain, + query: { loginLink: link.loginLink }, + }) + : this.urlGeneratorService.createUrl(); + + return { url, statusCode: 302 }; + } +} diff --git a/backend/src/modules/setup/test-data/test-data.module.ts b/backend/src/modules/setup/test-data/test-data.module.ts new file mode 100644 index 0000000..c3c54d1 --- /dev/null +++ b/backend/src/modules/setup/test-data/test-data.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import { TestAccount } from './entities/test-account.entity'; +import { PublicTestDataController } from './public-test-data.controller'; +import { TestDataService } from './test-data.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([TestAccount]), IAMModule, CrmModule], + controllers: [PublicTestDataController], + providers: [TestDataService], +}) +export class TestDataModule {} diff --git a/backend/src/modules/setup/test-data/test-data.service.ts b/backend/src/modules/setup/test-data/test-data.service.ts new file mode 100644 index 0000000..50ea05c --- /dev/null +++ b/backend/src/modules/setup/test-data/test-data.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { faker } from '@faker-js/faker'; + +import { ForbiddenError, DateUtil } from '@/common'; +import { ApplicationConfig } from '@/config'; + +import { AccountService } from '@/modules/iam/account/account.service'; +import { AuthenticationService } from '@/modules/iam/authentication/authentication.service'; +import { LoginLinkDto } from '@/modules/iam/authentication/dto/login-link.dto'; +import { UserService } from '@/modules/iam/user/user.service'; + +import { TestAccount } from './entities/test-account.entity'; +import { CreateTestAccountsQuery } from './dto/create-test-accounts-query'; + +@Injectable() +export class TestDataService { + constructor( + private readonly configService: ConfigService, + @InjectRepository(TestAccount) + private readonly repository: Repository, + private readonly authService: AuthenticationService, + private readonly accountService: AccountService, + private readonly userService: UserService, + ) {} + + public async loginTestUser(): Promise { + const testAccountCount = await this.repository.count(); + const testAccountNumber = Math.floor(Math.random() * testAccountCount); + const testAccount = await this.repository.find({ skip: testAccountNumber, take: 1 }); + + if (testAccount.length) { + const account = await this.accountService.findOne({ accountId: testAccount[0].accountId }); + const users = await this.userService.findMany({ accountId: account.id, isActive: true }); + const testUserNumber = Math.floor(Math.random() * users.length); + const user = users[testUserNumber]; + return this.authService.createLoginLink({ accountId: account.id, subdomain: account.subdomain, userId: user.id }); + } + + return null; + } + + public async createTestAccounts(query: CreateTestAccountsQuery) { + const verificationToken = this.configService.get('application').verificationToken; + if (!verificationToken || !query.token || verificationToken !== query.token) { + throw new ForbiddenError('Invalid verification token'); + } + + const dates = this.generateRandomDates(query.fromDate, query.toDate, query.count); + for (let i = 0; i < query.count; i++) { + await this.createTestAccount(dates[i]); + } + } + + private async createTestAccount(createdAt: Date): Promise { + try { + const sexType = faker.person.sexType(); + const firstName = faker.person.firstName(sexType); + const lastName = faker.person.lastName(sexType); + const email = faker.internet.email({ firstName, lastName }); + const { account } = await this.accountService.create( + { + firstName, + lastName, + email, + phone: faker.phone.number(), + password: email, + companyName: faker.company.name(), + }, + { + skipPhoneCheck: true, + createdAt: createdAt, + subscription: { isTrial: false, termInDays: 3650 }, + }, + ); + await this.repository.save({ accountId: account.id }); + return true; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return false; + } + } + + private generateRandomDates(fromDate: string, toDate: string, count: number): Date[] { + const start = DateUtil.fromISOString(fromDate).getTime(); + const end = DateUtil.fromISOString(toDate).getTime(); + + return Array.from({ length: count }, () => { + const skew = Math.random() ** 1.618; + const skewedTimestamp = start + (1 - skew) * (end - start); + return new Date(skewedTimestamp); + }).sort((a, b) => a.getTime() - b.getTime()); + } +} diff --git a/backend/src/modules/storage/config/aws.config.ts b/backend/src/modules/storage/config/aws.config.ts new file mode 100644 index 0000000..58b215c --- /dev/null +++ b/backend/src/modules/storage/config/aws.config.ts @@ -0,0 +1,20 @@ +import { registerAs } from '@nestjs/config'; + +export interface AwsConfig { + endpointUrl: string; + region: string; + bucket: string; + accessKeyId: string; + secretAccessKey: string; +} + +export default registerAs( + 'aws', + (): AwsConfig => ({ + endpointUrl: process.env.AWS_ENDPOINT_URL, + region: process.env.AWS_REGION, + bucket: process.env.AWS_BUCKET, + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + }), +); diff --git a/backend/src/modules/storage/dto/file-info-result.dto.ts b/backend/src/modules/storage/dto/file-info-result.dto.ts new file mode 100644 index 0000000..fb91360 --- /dev/null +++ b/backend/src/modules/storage/dto/file-info-result.dto.ts @@ -0,0 +1,34 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsDateString, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class FileInfoResultDto { + @ApiProperty({ description: 'File ID' }) + @IsString() + id: string; + + @ApiProperty({ description: 'File name' }) + @IsString() + fileName: string; + + @ApiProperty({ description: 'File size' }) + @IsNumber() + fileSize: number; + + @ApiProperty({ description: 'Mime type' }) + @IsString() + mimeType: string; + + @ApiPropertyOptional({ description: 'Download URL', nullable: true }) + @IsOptional() + @IsString() + downloadUrl?: string | null; + + @ApiPropertyOptional({ description: 'Preview URL', nullable: true }) + @IsOptional() + @IsString() + previewUrl?: string | null; + + @ApiProperty({ description: 'Created at' }) + @IsDateString() + createdAt: string; +} diff --git a/backend/src/modules/storage/dto/file-upload-request.ts b/backend/src/modules/storage/dto/file-upload-request.ts new file mode 100644 index 0000000..dc5f2f2 --- /dev/null +++ b/backend/src/modules/storage/dto/file-upload-request.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class FilesUploadRequest { + @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } }) + files: any[]; +} diff --git a/backend/src/modules/storage/dto/file-upload-result.ts b/backend/src/modules/storage/dto/file-upload-result.ts new file mode 100644 index 0000000..b7de1a0 --- /dev/null +++ b/backend/src/modules/storage/dto/file-upload-result.ts @@ -0,0 +1,39 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsString, IsNumber, IsOptional, IsDateString } from 'class-validator'; + +export class FileUploadResult { + @ApiPropertyOptional({ description: 'Upload key', nullable: true }) + @IsOptional() + @IsString() + key?: string | null; + + @ApiProperty({ description: 'File ID' }) + @IsString() + id: string; + + @ApiProperty({ description: 'File name' }) + @IsString() + fileName: string; + + @ApiProperty({ description: 'File size' }) + @IsNumber() + fileSize: number; + + @ApiProperty({ description: 'Mime type' }) + @IsString() + mimeType: string; + + @ApiPropertyOptional({ description: 'Download URL', nullable: true }) + @IsOptional() + @IsString() + downloadUrl?: string | null; + + @ApiPropertyOptional({ description: 'Preview URL', nullable: true }) + @IsOptional() + @IsString() + previewUrl?: string | null; + + @ApiProperty({ description: 'Created at' }) + @IsDateString() + createdAt: string; +} diff --git a/backend/src/modules/storage/dto/image-options.dto.ts b/backend/src/modules/storage/dto/image-options.dto.ts new file mode 100644 index 0000000..fa82f40 --- /dev/null +++ b/backend/src/modules/storage/dto/image-options.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class ImageOptionsDto { + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + width?: number; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + height?: number; +} diff --git a/backend/src/modules/storage/dto/index.ts b/backend/src/modules/storage/dto/index.ts new file mode 100644 index 0000000..50ed03a --- /dev/null +++ b/backend/src/modules/storage/dto/index.ts @@ -0,0 +1,4 @@ +export * from './file-info-result.dto'; +export * from './file-upload-request'; +export * from './file-upload-result'; +export * from './image-options.dto'; diff --git a/backend/src/modules/storage/entities/file-info.entity.ts b/backend/src/modules/storage/entities/file-info.entity.ts new file mode 100644 index 0000000..c88ac64 --- /dev/null +++ b/backend/src/modules/storage/entities/file-info.entity.ts @@ -0,0 +1,66 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { imageMimeTypes, MimeType } from '../enums/mime-type.enum'; + +@Entity() +export class FileInfo { + @PrimaryColumn() + id: string; + + @Column({ nullable: true }) + createdBy: number | null; + + @Column() + originalName: string; + + @Column() + mimeType: string; + + @Column() + size: number; + + @Column({ nullable: true }) + hashSha256: string | null; + + @Column() + storePath: string; + + @Column() + isUsed: boolean; + + @Column() + accountId: number; + + @Column() + createdAt: Date; + + constructor( + id: string, + accountId: number, + createdBy: number | null, + originalName: string, + mimeType: string, + size: number, + hashSha256: string, + storePath: string, + isUsed: boolean, + createdAt?: Date, + ) { + this.id = id; + this.accountId = accountId; + this.createdBy = createdBy; + this.originalName = originalName; + this.mimeType = mimeType; + this.size = size; + this.hashSha256 = hashSha256; + this.storePath = storePath; + this.isUsed = isUsed; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public isImage(): boolean { + return imageMimeTypes.includes(this.mimeType as MimeType); + } +} diff --git a/backend/src/modules/storage/entities/index.ts b/backend/src/modules/storage/entities/index.ts new file mode 100644 index 0000000..d30d1c3 --- /dev/null +++ b/backend/src/modules/storage/entities/index.ts @@ -0,0 +1 @@ +export * from './file-info.entity'; diff --git a/backend/src/modules/storage/enums/index.ts b/backend/src/modules/storage/enums/index.ts new file mode 100644 index 0000000..c2eded4 --- /dev/null +++ b/backend/src/modules/storage/enums/index.ts @@ -0,0 +1 @@ +export * from './mime-type.enum'; diff --git a/backend/src/modules/storage/enums/mime-type.enum.ts b/backend/src/modules/storage/enums/mime-type.enum.ts new file mode 100644 index 0000000..852318b --- /dev/null +++ b/backend/src/modules/storage/enums/mime-type.enum.ts @@ -0,0 +1,10 @@ +export enum MimeType { + PNG = 'image/png', + JPEG = 'image/jpeg', + GIF = 'image/gif', + WEBP = 'image/webp', + PDF = 'application/pdf', + DOCX = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', +} + +export const imageMimeTypes = [MimeType.PNG, MimeType.JPEG, MimeType.GIF, MimeType.WEBP]; diff --git a/backend/src/modules/storage/providers/aws-s3.provider.ts b/backend/src/modules/storage/providers/aws-s3.provider.ts new file mode 100644 index 0000000..9435531 --- /dev/null +++ b/backend/src/modules/storage/providers/aws-s3.provider.ts @@ -0,0 +1,60 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; + +import { AwsConfig } from '../config/aws.config'; + +@Injectable() +export class AwsS3Provider { + private readonly logger = new Logger(AwsS3Provider.name); + private s3Client: S3Client; + private _bucketName: string; + + constructor(private readonly configService: ConfigService) { + this._bucketName = this.configService.get('aws').bucket; + + this.s3Client = new S3Client({}); + } + + public async storeBuffer( + key: string, + buffer: Buffer, + sha256Hash: string, + mimeType: string, + originalName: string, + ): Promise { + try { + const param = { + Bucket: this._bucketName, + Key: key, + Body: buffer, + ContentType: mimeType, + ChecksumSHA256: sha256Hash, + ContentDisposition: originalName ? `attachment; filename="${encodeURI(originalName)}"` : originalName, + }; + await this.s3Client.send(new PutObjectCommand(param)); + return true; + } catch (e) { + this.logger.error(`Error in AwsS3Provider`, (e as Error)?.stack); + return false; + } + } + + public async deleteFile(key: string): Promise { + try { + await this.s3Client.send(new DeleteObjectCommand({ Bucket: this._bucketName, Key: key })); + return true; + } catch (e) { + return false; + } + } + + public async getFile(key: string): Promise { + try { + const data = await this.s3Client.send(new GetObjectCommand({ Bucket: this._bucketName, Key: key })); + return await data?.Body?.transformToByteArray(); + } catch (e) { + return null; + } + } +} diff --git a/backend/src/modules/storage/providers/index.ts b/backend/src/modules/storage/providers/index.ts new file mode 100644 index 0000000..0af015e --- /dev/null +++ b/backend/src/modules/storage/providers/index.ts @@ -0,0 +1 @@ +export * from './aws-s3.provider'; diff --git a/backend/src/modules/storage/storage-public.controller.ts b/backend/src/modules/storage/storage-public.controller.ts new file mode 100644 index 0000000..4ccf408 --- /dev/null +++ b/backend/src/modules/storage/storage-public.controller.ts @@ -0,0 +1,56 @@ +import { Controller, Get, Param, Query, Res, StreamableFile } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; +import { Response } from 'express'; + +import { ImageOptionsDto } from './dto'; +import { StorageService } from './storage.service'; + +@ApiTags('storage') +@Controller('storage') +export class StoragePublicController { + constructor(private readonly service: StorageService) {} + + @ApiOperation({ summary: 'Get image from storage', description: 'Get image from storage by account id' }) + @ApiParam({ name: 'accountId', type: Number, required: true, description: 'Account id' }) + @ApiParam({ name: 'id', type: String, required: true, description: 'Image id' }) + @ApiOkResponse({ description: 'Image file', type: StreamableFile }) + @Get('image/:accountId/:id') + async getImage( + @Param('accountId') accountId: number, + @Param('id') id: string, + @Res({ passthrough: true }) res: Response, + @Query() options?: ImageOptionsDto, + ): Promise { + const result = await this.service.getImage({ accountId, id, options }); + if (result) { + res.set({ + 'Content-Type': result.mimeType, + 'Cache-Control': 'public, max-age=31536000, immutable', + }); + return new StreamableFile(result.content); + } + return null; + } + + @ApiOperation({ summary: 'Get file from storage', description: 'Get file from storage by temporary token' }) + @ApiParam({ name: 'token', type: String, required: true, description: 'Temporary token' }) + @ApiOkResponse({ description: 'File', type: StreamableFile }) + @Get('tmp/:token') + async getTemporaryFile( + @Param('token') token: string, + @Res({ passthrough: true }) res: Response, + ): Promise { + const result = await this.service.getFileByTmpToken(token); + if (result) { + res.set({ + 'Content-Type': result.file.mimeType, + 'Content-Disposition': `attachment; filename="${encodeURI(result.file.originalName)}"`, + }); + if (result.file.hashSha256) { + res.set('Digest', 'sha-256=' + result.file.hashSha256); + } + return new StreamableFile(result.content); + } + return null; + } +} diff --git a/backend/src/modules/storage/storage-url.service.ts b/backend/src/modules/storage/storage-url.service.ts new file mode 100644 index 0000000..1b1bcf8 --- /dev/null +++ b/backend/src/modules/storage/storage-url.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@nestjs/common'; + +import { TokenService, UrlGeneratorService } from '@/common'; + +const Paths = { + downloadPath: '/api/storage/file/:fileId', + imagePath: '/api/storage/image/:accountId/:fileId', + temporaryPath: '/api/storage/tmp/:token', +} as const; + +@Injectable() +export class StorageUrlService { + constructor( + private readonly tokenService: TokenService, + private readonly urlGenerator: UrlGeneratorService, + ) {} + + public getDownloadUrl(subdomain: string, fileId: string): string { + return this.urlGenerator.createUrl({ route: Paths.downloadPath, subdomain, path: { fileId } }); + } + + public getImageUrl(accountId: number, subdomain: string, fileId: string): string { + return this.urlGenerator.createUrl({ + route: Paths.imagePath, + subdomain, + path: { accountId: accountId.toString(), fileId }, + }); + } + + public getTemporaryUrl(fileId: string, subdomain?: string): string { + const token = this.tokenService.create({ fileId }, { expiresIn: '15m' }); + return this.urlGenerator.createUrl({ route: Paths.temporaryPath, subdomain, path: { token } }); + } +} diff --git a/backend/src/modules/storage/storage.controller.ts b/backend/src/modules/storage/storage.controller.ts new file mode 100644 index 0000000..014456e --- /dev/null +++ b/backend/src/modules/storage/storage.controller.ts @@ -0,0 +1,88 @@ +import { + Controller, + Delete, + Get, + Param, + Post, + Res, + StreamableFile, + UploadedFiles, + UseInterceptors, +} from '@nestjs/common'; +import { ApiBody, ApiConsumes, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; +import { AnyFilesInterceptor } from '@nestjs/platform-express'; +import { Response } from 'express'; +import { memoryStorage } from 'multer'; + +import { AuthData } from '@/modules/iam/common/types/auth-data'; +import { AuthDataPrefetch } from '@/modules/iam/common/decorators/auth-data-prefetch.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; + +import { StorageService } from './storage.service'; +import { FilesUploadRequest } from './dto/file-upload-request'; +import { FileUploadResult } from './dto/file-upload-result'; +import { FileInfoResultDto } from './dto'; + +@ApiTags('storage') +@Controller('storage') +@JwtAuthorized() +export class StorageController { + constructor(private readonly service: StorageService) {} + + @ApiOperation({ summary: 'Upload files', description: 'Upload files' }) + @ApiConsumes('multipart/form-data') + @ApiBody({ description: 'Files to upload', type: FilesUploadRequest }) + @ApiOkResponse({ description: 'Uploaded files info', type: [FileUploadResult] }) + @AuthDataPrefetch({ account: true }) + @Post('upload') + @UseInterceptors(AnyFilesInterceptor({ storage: memoryStorage() })) + async upload( + @UploadedFiles() + files: Express.Multer.File[], + @CurrentAuth() { account, userId }: AuthData, + ): Promise { + return this.service.uploadCommonFiles({ account, userId, files }); + } + + @ApiOperation({ summary: 'Get file info', description: 'Get file info' }) + @ApiParam({ name: 'fileId', description: 'File id', type: String, required: true }) + @ApiOkResponse({ description: 'File info', type: FileInfoResultDto }) + @AuthDataPrefetch({ account: true }) + @Get('info/:fileId') + async info(@CurrentAuth() { account }: AuthData, @Param('fileId') fileId: string) { + return this.service.getFileInfo({ account, fileId }); + } + + @ApiOperation({ summary: 'Get file from storage', description: 'Get file from storage' }) + @ApiParam({ name: 'fileId', description: 'File id', type: String, required: true }) + @ApiOkResponse({ description: 'File', type: StreamableFile }) + @Get('file/:fileId') + async get( + @CurrentAuth() { accountId }: AuthData, + @Param('fileId') fileId: string, + @Res({ passthrough: true }) res: Response, + ): Promise { + const result = await this.service.getFile({ fileId, accountId }); + if (result) { + res.set({ + 'Content-Type': result.file.mimeType, + 'Content-Disposition': `attachment; filename="${encodeURI(result.file.originalName)}"`, + }); + if (result.file.hashSha256) { + res.set('Digest', 'sha-256=' + result.file.hashSha256); + } + return new StreamableFile(result.content); + } + + return null; + } + + @ApiOperation({ summary: 'Delete file from storage', description: 'Delete file from storage' }) + @ApiParam({ name: 'id', description: 'File id', type: String, required: true }) + @ApiOkResponse({ description: 'Result', type: Boolean }) + @Delete('file/:id') + async delete(@Param('id') id: string, @CurrentAuth() { accountId }: AuthData): Promise { + return this.service.delete({ accountId, id }); + } +} diff --git a/backend/src/modules/storage/storage.module.ts b/backend/src/modules/storage/storage.module.ts new file mode 100644 index 0000000..87962e7 --- /dev/null +++ b/backend/src/modules/storage/storage.module.ts @@ -0,0 +1,27 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { MulterModule } from '@nestjs/platform-express'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; + +import awsConfig from './config/aws.config'; +import { FileInfo } from './entities/file-info.entity'; +import { AwsS3Provider } from './providers/aws-s3.provider'; +import { StorageService } from './storage.service'; +import { StorageUrlService } from './storage-url.service'; +import { StorageController } from './storage.controller'; +import { StoragePublicController } from './storage-public.controller'; + +@Module({ + imports: [ + MulterModule.register(), + ConfigModule.forFeature(awsConfig), + TypeOrmModule.forFeature([FileInfo]), + forwardRef(() => IAMModule), + ], + providers: [StorageService, StorageUrlService, AwsS3Provider], + controllers: [StorageController, StoragePublicController], + exports: [StorageService, StorageUrlService], +}) +export class StorageModule {} diff --git a/backend/src/modules/storage/storage.service.ts b/backend/src/modules/storage/storage.service.ts new file mode 100644 index 0000000..b778c63 --- /dev/null +++ b/backend/src/modules/storage/storage.service.ts @@ -0,0 +1,332 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { HttpService } from '@nestjs/axios'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { v4 as uuidv4 } from 'uuid'; +import sharp, { ResizeOptions } from 'sharp'; +import { Readable } from 'stream'; +import crypto from 'crypto'; +import { lastValueFrom } from 'rxjs'; +import * as mime from 'mime-types'; + +import { DateUtil, TokenService } from '@/common'; +import { Account } from '@/modules/iam/account/entities/account.entity'; + +import { FileUploadResult, ImageOptionsDto } from './dto'; +import { FileInfo } from './entities'; +import { FileInfoResult, StorageFile, TemporaryFile } from './types'; +import { AwsS3Provider } from './providers'; +import { StorageUrlService } from './storage-url.service'; + +type Section = 'avatar' | 'logo' | 'photos' | string; +const getResizeOptions = (section: Section): ResizeOptions => { + return section === 'avatar' ? { width: 440, height: 440 } : undefined; +}; + +@Injectable() +export class StorageService { + private readonly logger = new Logger(StorageService.name); + + constructor( + private readonly httpService: HttpService, + @InjectRepository(FileInfo) + private readonly repository: Repository, + private readonly tokenService: TokenService, + private readonly awsS3Provider: AwsS3Provider, + private readonly urlService: StorageUrlService, + ) {} + + public async uploadCommonFiles({ + account, + userId, + files, + }: { + account: Account; + userId?: number | null; + files: Express.Multer.File[]; + }): Promise { + return Promise.all(files.map((file) => this.uploadCommonFile({ account, userId, file }))); + } + + public async uploadCommonFile({ + account, + userId, + file, + }: { + account: Account; + userId?: number | null; + file: Express.Multer.File; + }): Promise { + const fileInfo = await this.storeCommonFile({ accountId: account.id, userId, file: StorageFile.fromMulter(file) }); + if (fileInfo) { + const downloadUrl = this.urlService.getDownloadUrl(account.subdomain, fileInfo.id); + const previewUrl = fileInfo.isImage() + ? this.urlService.getImageUrl(account.id, account.subdomain, fileInfo.id) + : undefined; + return { + key: file.fieldname, + id: fileInfo.id, + fileName: fileInfo.originalName, + fileSize: fileInfo.size, + mimeType: fileInfo.mimeType, + createdAt: fileInfo.createdAt.toISOString(), + downloadUrl, + previewUrl, + }; + } + + return null; + } + + public async storeCommonFile({ + accountId, + userId, + file, + }: { + accountId: number; + userId?: number | null; + file: StorageFile; + }): Promise { + const now = DateUtil.now(); + const path = `${accountId}/${now.getFullYear()}/${now.getMonth()}/${now.getDate()}`; + + return this.storeFile({ accountId, userId, path, file }); + } + + public async storeAccountFile({ + accountId, + userId, + file, + section, + }: { + accountId: number; + userId: number; + file: StorageFile; + section?: Section; + }): Promise { + const path = `${accountId}/account${section ? `/${section}` : ''}`; + + return this.storeFile({ accountId, userId, path, file }); + } + + public async storeUserFile({ + accountId, + userId, + file, + section, + }: { + accountId: number; + userId: number; + file: StorageFile; + section?: Section; + }): Promise { + const path = `${accountId}/users/${userId}${section ? `/${section}` : ''}`; + + return this.storeFile({ accountId, userId, path, file, resizeOptions: getResizeOptions(section) }); + } + + public async storeProductFiles({ + accountId, + userId, + productId, + files, + section, + }: { + accountId: number; + userId: number; + productId: number; + files: StorageFile[]; + section?: Section; + }): Promise { + const path = `${accountId}/products/${productId}${section ? `/${section}` : ''}`; + + return (await Promise.all(files.map((file) => this.storeFile({ accountId, userId, path, file })))).filter(Boolean); + } + + public async storeExternalFile( + accountId: number, + userId: number | null, + fileUrl: string, + options?: { authorization?: string }, + ): Promise { + let response; + try { + const response$ = this.httpService.get(fileUrl, { + headers: { Authorization: options?.authorization }, + responseType: 'arraybuffer', + }); + response = await lastValueFrom(response$); + } catch (e) { + this.logger.error(`Error while storing external file`, (e as Error)?.stack); + return null; + } + + const { fileName, contentType } = this.extractFileInfo(response); + const buffer = Buffer.from(response.data); + + return this.storeCommonFile({ + accountId, + userId, + file: new StorageFile(fileName, contentType, buffer.length, buffer), + }); + } + + public async getFileInfo({ account, fileId }: { account: Account; fileId: string }): Promise { + const fileInfo = await this.repository.findOneBy({ id: fileId }); + if (fileInfo) { + const downloadUrl = this.urlService.getDownloadUrl(account.subdomain, fileInfo.id); + const previewUrl = fileInfo.isImage() + ? this.urlService.getImageUrl(account.id, account.subdomain, fileInfo.id) + : undefined; + return new FileInfoResult({ + id: fileInfo.id, + fileName: fileInfo.originalName, + fileSize: fileInfo.size, + mimeType: fileInfo.mimeType, + createdAt: fileInfo.createdAt, + downloadUrl, + previewUrl, + }); + } + return null; + } + + public async getFile({ + fileId, + accountId, + }: { + fileId: string; + accountId?: number; + }): Promise<{ file: FileInfo; content: Uint8Array }> { + const file = await this.repository.findOneBy({ id: fileId, accountId }); + if (file) { + const content = await this.awsS3Provider.getFile(file.storePath); + return { file, content }; + } + return null; + } + + public async getFileByTmpToken(token: string): Promise<{ file: FileInfo; content: Uint8Array }> { + const { fileId } = this.tokenService.verify(decodeURIComponent(token)); + + return fileId ? this.getFile({ fileId }) : null; + } + + public async getImage({ + accountId, + id, + options, + }: { + accountId: number; + id: string; + options?: ImageOptionsDto; + }): Promise<{ content: Readable; mimeType: string }> { + const file = await this.repository.findOneBy({ id, accountId }); + if (file) { + const content = await this.awsS3Provider.getFile(file.storePath); + if (options) { + return { + content: sharp(content).resize(options.width, options.height), + mimeType: file.mimeType, + }; + } + return { content: Readable.from(content), mimeType: file.mimeType }; + } + return null; + } + + public async delete({ accountId, id }: { accountId: number; id: string | string[] }): Promise { + const deleteOne = async ({ accountId, id }: { accountId: number; id: string }): Promise => { + const file = await this.repository.findOneBy({ id, accountId }); + if (file) { + const deleted = await this.awsS3Provider.deleteFile(file.storePath); + if (deleted) { + await this.repository.delete(id); + } + return deleted; + } + return true; + }; + + return Array.isArray(id) + ? Promise.all(id.map((fileId) => deleteOne({ accountId, id: fileId }))).then((result) => result.every(Boolean)) + : deleteOne({ accountId, id }); + } + + public async markUsed({ accountId, id }: { accountId: number; id: string }): Promise { + await this.repository.update({ accountId, id }, { isUsed: true }); + return this.repository.findOneBy({ accountId, id }); + } + public async markUsedMany({ accountId, ids }: { accountId: number; ids: string[] }): Promise { + return Promise.all(ids.map((id) => this.markUsed({ accountId, id }))); + } + + private async storeFile({ + accountId, + userId, + path, + file, + resizeOptions, + }: { + accountId: number; + userId?: number | null; + path: string; + file: StorageFile; + resizeOptions?: ResizeOptions; + }): Promise { + const uploadFile = resizeOptions ? await this.resizeImage(file, resizeOptions) : file; + const id = uuidv4(); + const key = `${path}/${id}`; + const sha256Hash = crypto.createHash('sha256').update(uploadFile.buffer).digest('base64'); + const result = await this.awsS3Provider.storeBuffer( + key, + uploadFile.buffer, + sha256Hash, + uploadFile.mimeType, + uploadFile.originalName, + ); + if (result) { + const fileInfo = new FileInfo( + id, + accountId, + userId ?? null, + decodeURI(file.originalName), + uploadFile.mimeType, + uploadFile.size, + sha256Hash, + key, + false, + ); + await this.repository.insert(fileInfo); + + return fileInfo; + } + + return null; + } + + private extractFileInfo(response: any): { fileName: string; contentType: string } { + const contentType = response.headers['content-type'] as string; + const contentDisposition = response.headers['content-disposition']; + let fileName = ''; + if (contentDisposition) { + //TODO: use StringUtil decoding + const filenameRegex = /filename\*?=(?:[^\']*'')?([^;\n"']*)['"]?/; + const matches = filenameRegex.exec(contentDisposition); + if (matches != null && matches[1]) { + fileName = matches[1].replace(/['"]/g, ''); + } + } + if (!fileName) { + const extension = mime.extension(contentType) || 'bin'; + fileName = `${uuidv4()}.${extension}`; + } + + return { fileName, contentType }; + } + + private async resizeImage(file: StorageFile, resizeOptions: ResizeOptions): Promise { + const resized = await sharp(file.buffer).resize(resizeOptions).toBuffer(); + return new StorageFile(file.originalName, file.mimeType, resized.length, resized); + } +} diff --git a/backend/src/modules/storage/types/file-info-result.ts b/backend/src/modules/storage/types/file-info-result.ts new file mode 100644 index 0000000..50f2168 --- /dev/null +++ b/backend/src/modules/storage/types/file-info-result.ts @@ -0,0 +1,33 @@ +import { FileInfoResultDto } from '../dto'; + +export class FileInfoResult { + id: string; + fileName: string; + fileSize: number; + mimeType: string; + downloadUrl: string; + previewUrl?: string | null; + createdAt: Date; + + constructor(date: Omit) { + this.id = date.id; + this.fileName = date.fileName; + this.fileSize = date.fileSize; + this.mimeType = date.mimeType; + this.downloadUrl = date.downloadUrl; + this.previewUrl = date.previewUrl; + this.createdAt = date.createdAt; + } + + public toDto(): FileInfoResultDto { + return { + id: this.id, + fileName: this.fileName, + fileSize: this.fileSize, + mimeType: this.mimeType, + downloadUrl: this.downloadUrl, + previewUrl: this.previewUrl, + createdAt: this.createdAt.toISOString(), + }; + } +} diff --git a/backend/src/modules/storage/types/index.ts b/backend/src/modules/storage/types/index.ts new file mode 100644 index 0000000..f096e7f --- /dev/null +++ b/backend/src/modules/storage/types/index.ts @@ -0,0 +1,3 @@ +export * from './file-info-result'; +export * from './storage-file'; +export * from './temporary-file'; diff --git a/backend/src/modules/storage/types/storage-file.ts b/backend/src/modules/storage/types/storage-file.ts new file mode 100644 index 0000000..db42f48 --- /dev/null +++ b/backend/src/modules/storage/types/storage-file.ts @@ -0,0 +1,29 @@ +import { type FileInfo } from '../entities/file-info.entity'; + +export class StorageFile { + originalName: string; + mimeType: string; + size: number; + buffer: Buffer; + encoding?: string | undefined; + + constructor(originalName: string, mimeType: string, size: number, buffer: Buffer, encoding?: string | undefined) { + this.originalName = originalName; + this.mimeType = mimeType; + this.size = size; + this.buffer = buffer; + this.encoding = encoding; + } + + public static fromFileInfo(fileInfo: FileInfo, buffer: Buffer) { + return new StorageFile(fileInfo.originalName, fileInfo.mimeType, fileInfo.size, buffer); + } + + public static fromMulter(file: Express.Multer.File) { + return new StorageFile(decodeURIComponent(file.originalname), file.mimetype, file.size, file.buffer, file.encoding); + } + + public static fromMulterFiles(files: Express.Multer.File[]): StorageFile[] { + return files.map((file) => StorageFile.fromMulter(file)); + } +} diff --git a/backend/src/modules/storage/types/temporary-file.ts b/backend/src/modules/storage/types/temporary-file.ts new file mode 100644 index 0000000..eeb3e18 --- /dev/null +++ b/backend/src/modules/storage/types/temporary-file.ts @@ -0,0 +1,3 @@ +export class TemporaryFile { + fileId: string; +} diff --git a/backend/src/modules/telephony/common/events/index.ts b/backend/src/modules/telephony/common/events/index.ts new file mode 100644 index 0000000..658e4e0 --- /dev/null +++ b/backend/src/modules/telephony/common/events/index.ts @@ -0,0 +1,2 @@ +export * from './telephony-call'; +export * from './telephony-event-type.enum'; diff --git a/backend/src/modules/telephony/common/events/telephony-call/index.ts b/backend/src/modules/telephony/common/events/telephony-call/index.ts new file mode 100644 index 0000000..158e3ee --- /dev/null +++ b/backend/src/modules/telephony/common/events/telephony-call/index.ts @@ -0,0 +1,3 @@ +export * from './telephony-call-created.event'; +export * from './telephony-call-updated.event'; +export * from './telephony-call.event'; diff --git a/backend/src/modules/telephony/common/events/telephony-call/telephony-call-created.event.ts b/backend/src/modules/telephony/common/events/telephony-call/telephony-call-created.event.ts new file mode 100644 index 0000000..45b340a --- /dev/null +++ b/backend/src/modules/telephony/common/events/telephony-call/telephony-call-created.event.ts @@ -0,0 +1,11 @@ +import { TelephonyCallEvent } from './telephony-call.event'; + +export class TelephonyCallCreatedEvent extends TelephonyCallEvent { + createdAt: string; + + constructor({ accountId, entityId, callId, createdAt }: TelephonyCallCreatedEvent) { + super({ accountId, entityId, callId }); + + this.createdAt = createdAt; + } +} diff --git a/backend/src/modules/telephony/common/events/telephony-call/telephony-call-updated.event.ts b/backend/src/modules/telephony/common/events/telephony-call/telephony-call-updated.event.ts new file mode 100644 index 0000000..5012c65 --- /dev/null +++ b/backend/src/modules/telephony/common/events/telephony-call/telephony-call-updated.event.ts @@ -0,0 +1,13 @@ +import { TelephonyCallEvent } from './telephony-call.event'; + +export class TelephonyCallUpdatedEvent extends TelephonyCallEvent { + createdAt: string; + oldEntityId: number | null; + + constructor({ accountId, entityId, callId, createdAt, oldEntityId }: TelephonyCallUpdatedEvent) { + super({ accountId, entityId, callId }); + + this.createdAt = createdAt; + this.oldEntityId = oldEntityId; + } +} diff --git a/backend/src/modules/telephony/common/events/telephony-call/telephony-call.event.ts b/backend/src/modules/telephony/common/events/telephony-call/telephony-call.event.ts new file mode 100644 index 0000000..075be80 --- /dev/null +++ b/backend/src/modules/telephony/common/events/telephony-call/telephony-call.event.ts @@ -0,0 +1,11 @@ +export class TelephonyCallEvent { + accountId: number; + entityId: number | null; + callId: number; + + constructor({ accountId, entityId, callId }: TelephonyCallEvent) { + this.accountId = accountId; + this.entityId = entityId; + this.callId = callId; + } +} diff --git a/backend/src/modules/telephony/common/events/telephony-event-type.enum.ts b/backend/src/modules/telephony/common/events/telephony-event-type.enum.ts new file mode 100644 index 0000000..f71e312 --- /dev/null +++ b/backend/src/modules/telephony/common/events/telephony-event-type.enum.ts @@ -0,0 +1,4 @@ +export enum TelephonyEventType { + TelephonyCallCreated = 'telephony:call:created', + TelephonyCallUpdated = 'telephony:call:updated', +} diff --git a/backend/src/modules/telephony/common/index.ts b/backend/src/modules/telephony/common/index.ts new file mode 100644 index 0000000..7981d6b --- /dev/null +++ b/backend/src/modules/telephony/common/index.ts @@ -0,0 +1 @@ +export * from './events'; diff --git a/backend/src/modules/telephony/telephony.module.ts b/backend/src/modules/telephony/telephony.module.ts new file mode 100644 index 0000000..3d2f62f --- /dev/null +++ b/backend/src/modules/telephony/telephony.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { RouterModule } from '@nestjs/core'; + +import { VoximplantModule } from './voximplant/voximplant.module'; + +@Module({ + imports: [VoximplantModule, RouterModule.register([{ path: 'telephony/voximplant', module: VoximplantModule }])], + exports: [VoximplantModule], +}) +export class TelephonyModule {} diff --git a/backend/src/modules/telephony/voximplant/common/enums/call-direction.enum.ts b/backend/src/modules/telephony/voximplant/common/enums/call-direction.enum.ts new file mode 100644 index 0000000..243c2f7 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/enums/call-direction.enum.ts @@ -0,0 +1,4 @@ +export enum CallDirection { + INCOMING = 'incoming', + OUTGOING = 'outgoing', +} diff --git a/backend/src/modules/telephony/voximplant/common/enums/call-status.enum.ts b/backend/src/modules/telephony/voximplant/common/enums/call-status.enum.ts new file mode 100644 index 0000000..495870d --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/enums/call-status.enum.ts @@ -0,0 +1,8 @@ +export enum CallStatus { + STARTED = 'started', + ACCEPTED = 'accepted', + SUCCESS = 'success', + CANCELED = 'canceled', + FAILED = 'failed', + MISSED = 'missed', +} diff --git a/backend/src/modules/telephony/voximplant/common/enums/index.ts b/backend/src/modules/telephony/voximplant/common/enums/index.ts new file mode 100644 index 0000000..51b58df --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/enums/index.ts @@ -0,0 +1,2 @@ +export * from './call-direction.enum'; +export * from './call-status.enum'; diff --git a/backend/src/modules/telephony/voximplant/common/errors/index.ts b/backend/src/modules/telephony/voximplant/common/errors/index.ts new file mode 100644 index 0000000..0284dae --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/errors/index.ts @@ -0,0 +1 @@ +export * from './voximplant-error'; diff --git a/backend/src/modules/telephony/voximplant/common/errors/voximplant-error.ts b/backend/src/modules/telephony/voximplant/common/errors/voximplant-error.ts new file mode 100644 index 0000000..fa125d8 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/errors/voximplant-error.ts @@ -0,0 +1,9 @@ +import { HttpStatus } from '@nestjs/common'; + +import { ServiceError } from '@/common'; + +export class VoximplantError extends ServiceError { + constructor({ message = 'Voximplant error', errorCode = 'voximplant' }) { + super({ errorCode: errorCode, status: HttpStatus.BAD_REQUEST, message }); + } +} diff --git a/backend/src/modules/telephony/voximplant/common/index.ts b/backend/src/modules/telephony/voximplant/common/index.ts new file mode 100644 index 0000000..5d840f7 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/index.ts @@ -0,0 +1,3 @@ +export * from './enums'; +export * from './errors'; +export * from './types'; diff --git a/backend/src/modules/telephony/voximplant/common/types/index.ts b/backend/src/modules/telephony/voximplant/common/types/index.ts new file mode 100644 index 0000000..c1722d2 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/types/index.ts @@ -0,0 +1 @@ +export * from './voximplant-application-param.interface'; diff --git a/backend/src/modules/telephony/voximplant/common/types/voximplant-application-param.interface.ts b/backend/src/modules/telephony/voximplant/common/types/voximplant-application-param.interface.ts new file mode 100644 index 0000000..6614c9e --- /dev/null +++ b/backend/src/modules/telephony/voximplant/common/types/voximplant-application-param.interface.ts @@ -0,0 +1,4 @@ +export interface VoximplantApplicationParam { + applicationId: number; + applicationName: string; +} diff --git a/backend/src/modules/telephony/voximplant/config/voximplant.config.ts b/backend/src/modules/telephony/voximplant/config/voximplant.config.ts new file mode 100644 index 0000000..7de4ca6 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/config/voximplant.config.ts @@ -0,0 +1,16 @@ +import { registerAs } from '@nestjs/config'; + +export interface VoximplantConfig { + accountId: number; + accountApiKey: string; + credentialsFile: string; +} + +export default registerAs( + 'voximplant', + (): VoximplantConfig => ({ + accountId: parseInt(process.env.VOXIMPLANT_PARENT_ACCOUNT_ID, 10), + accountApiKey: process.env.VOXIMPLANT_PARENT_ACCOUNT_API_KEY, + credentialsFile: process.env.VOXIMPLANT_CREDENTIALS_FILE, + }), +); diff --git a/backend/src/modules/telephony/voximplant/voximplant-account/dto/index.ts b/backend/src/modules/telephony/voximplant/voximplant-account/dto/index.ts new file mode 100644 index 0000000..7d74ab8 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-account/dto/index.ts @@ -0,0 +1 @@ +export * from './voximplant-account.dto'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-account/dto/voximplant-account.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-account/dto/voximplant-account.dto.ts new file mode 100644 index 0000000..e834664 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-account/dto/voximplant-account.dto.ts @@ -0,0 +1,50 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsString } from 'class-validator'; + +export class VoximplantAccountDto { + @ApiProperty() + @IsNumber() + accountId: number; + + @ApiProperty() + @IsString() + accountName: string; + + @ApiProperty() + @IsString() + apiKey: string; + + @ApiProperty() + @IsNumber() + billingAccountId: number; + + @ApiProperty() + @IsNumber() + applicationId: number; + + @ApiProperty() + @IsString() + applicationName: string; + + @ApiProperty() + @IsBoolean() + isActive: boolean; + + constructor( + accountId: number, + accountName: string, + apiKey: string, + billingAccountId: number, + applicationId: number, + applicationName: string, + isActive: boolean, + ) { + this.accountId = accountId; + this.accountName = accountName; + this.apiKey = apiKey; + this.billingAccountId = billingAccountId; + this.applicationId = applicationId; + this.applicationName = applicationName; + this.isActive = isActive; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-account/entities/index.ts b/backend/src/modules/telephony/voximplant/voximplant-account/entities/index.ts new file mode 100644 index 0000000..6d2419d --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-account/entities/index.ts @@ -0,0 +1 @@ +export * from './voximplant-account.entity'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-account/entities/voximplant-account.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-account/entities/voximplant-account.entity.ts new file mode 100644 index 0000000..09c95b3 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-account/entities/voximplant-account.entity.ts @@ -0,0 +1,81 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; +import { VoximplantAccountDto } from '../dto'; + +@Entity() +export class VoximplantAccount { + @PrimaryColumn() + accountId: number; + + @Column() + externalId: number; + + @Column() + accountName: string; + + @Column() + accountEmail: string; + + @Column() + apiKey: string; + + @Column() + password: string; + + @Column() + billingAccountId: number; + + @Column() + isActive: boolean; + + @Column() + keyId: string; + + @Column() + privateKey: string; + + @Column() + applicationId: number; + + @Column() + applicationName: string; + + constructor( + accountId: number, + externalId: number, + accountName: string, + accountEmail: string, + apiKey: string, + password: string, + billingAccountId: number, + isActive: boolean, + keyId: string, + privateKey: string, + applicationId: number, + applicationName: string, + ) { + this.accountId = accountId; + this.externalId = externalId; + this.accountName = accountName; + this.accountEmail = accountEmail; + this.apiKey = apiKey; + this.password = password; + this.billingAccountId = billingAccountId; + this.isActive = isActive; + this.keyId = keyId; + this.privateKey = privateKey; + this.applicationId = applicationId; + this.applicationName = applicationName; + } + + public toDto(): VoximplantAccountDto { + return new VoximplantAccountDto( + this.externalId, + this.accountName, + this.apiKey, + this.billingAccountId, + this.applicationId, + this.applicationName, + this.isActive, + ); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-account/index.ts b/backend/src/modules/telephony/voximplant/voximplant-account/index.ts new file mode 100644 index 0000000..e6ff647 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-account/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './voximplant-account.controller'; +export * from './voximplant-account.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-account/voximplant-account.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-account/voximplant-account.controller.ts new file mode 100644 index 0000000..34b901b --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-account/voximplant-account.controller.ts @@ -0,0 +1,35 @@ +import { Controller, Delete, Get, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { VoximplantAccountDto } from './dto'; +import { VoximplantAccountService } from './voximplant-account.service'; + +@ApiTags('telephony/voximplant/account') +@Controller('account') +@JwtAuthorized() +@TransformToDto() +export class VoximplantAccountController { + constructor(private service: VoximplantAccountService) {} + + @AuthDataPrefetch({ account: true }) + @ApiCreatedResponse({ description: 'Create voximplant account', type: VoximplantAccountDto }) + @Post() + public async create(@CurrentAuth() { account }: AuthData) { + return this.service.create(account); + } + + @ApiCreatedResponse({ description: 'Get linked voximplant account', type: VoximplantAccountDto }) + @Get() + public async findOne(@CurrentAuth() { accountId }: AuthData) { + return this.service.findOne(accountId); + } + + @ApiCreatedResponse({ description: 'Delete voximplant account' }) + @Delete() + public async delete(@CurrentAuth() { accountId }: AuthData) { + return this.service.markActive(accountId, false); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-account/voximplant-account.service.ts b/backend/src/modules/telephony/voximplant/voximplant-account/voximplant-account.service.ts new file mode 100644 index 0000000..977b2ae --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-account/voximplant-account.service.ts @@ -0,0 +1,135 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import VoximplantApiClient from '@amwork/voximplant-apiclient-nodejs'; + +import { NotFoundError } from '@/common'; +import { ApplicationConfig } from '@/config'; + +import { Account } from '@/modules/iam/account/entities/account.entity'; + +import { VoximplantCoreService } from '../voximplant-core'; + +import { VoximplantAccount } from './entities'; +import { VoximplantApplicationParam } from '../common'; + +@Injectable() +export class VoximplantAccountService { + private _appName: string; + + constructor( + private readonly configService: ConfigService, + @InjectRepository(VoximplantAccount) + private readonly repository: Repository, + private readonly viCoreService: VoximplantCoreService, + ) { + this._appName = this.configService.get('application').name; + } + + public async getClient( + account: number | VoximplantAccount, + ): Promise<{ client: VoximplantApiClient; appParam: VoximplantApplicationParam }> { + const viAccount = account instanceof VoximplantAccount ? account : await this.getOne(account); + + const client = new VoximplantApiClient({ + account_email: viAccount.accountEmail, + account_id: viAccount.externalId, + key_id: viAccount.keyId, + private_key: viAccount.privateKey, + }); + const appParam = { + applicationId: viAccount.applicationId, + applicationName: viAccount.applicationName, + }; + + return { client, appParam }; + } + + public async create(account: Account): Promise { + const viAccount = await this.repository.findOneBy({ accountId: account.id }); + if (viAccount) { + return viAccount.isActive ? viAccount : await this.setActive(viAccount, true); + } + + const extAccount = await this.viCoreService.createChildAccount(account); + if (extAccount) { + const newAccount = new VoximplantAccount( + account.id, + extAccount.accountId, + extAccount.accountName, + extAccount.accountEmail, + extAccount.apiKey, + extAccount.password, + extAccount.billingAccountId, + extAccount.active, + extAccount.key.keyId, + extAccount.key.privateKey, + 0, + '', + ); + + const { client } = await this.getClient(newAccount); + const { result, applicationId, applicationName } = await client.Applications.addApplication({ + applicationName: this._appName, + }); + if (result) { + return await this.repository.save( + new VoximplantAccount( + account.id, + extAccount.accountId, + extAccount.accountName, + extAccount.accountEmail, + extAccount.apiKey, + extAccount.password, + extAccount.billingAccountId, + extAccount.active, + extAccount.key.keyId, + extAccount.key.privateKey, + applicationId, + applicationName, + ), + ); + } + } + return null; + } + + public async findOne(accountId: number): Promise { + return await this.repository.findOneBy({ accountId }); + } + + public async findOneExt(filter: { applicationId: number }): Promise { + return await this.repository.findOneBy({ applicationId: filter.applicationId }); + } + + public async getOne(accountId: number): Promise { + const viAccount = await this.repository.findOneBy({ accountId }); + if (!viAccount) { + throw NotFoundError.withId(VoximplantAccount, accountId); + } + + return viAccount; + } + + public async markActive(accountId: number, isActive: boolean): Promise { + const viAccount = await this.getOne(accountId); + return await this.setActive(viAccount, isActive); + } + + private async setActive(viAccount: VoximplantAccount, isActive: boolean): Promise { + const result = await this.viCoreService.setActiveChildAccount( + viAccount.externalId, + viAccount.accountName, + viAccount.accountEmail, + isActive, + ); + + if (result) { + viAccount.isActive = isActive; + return await this.repository.save(viAccount); + } + + return viAccount; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/dto/create-voximplant-call-ext.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-call/dto/create-voximplant-call-ext.dto.ts new file mode 100644 index 0000000..2228fae --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/dto/create-voximplant-call-ext.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +import { CreateVoximplantCallDto } from './create-voximplant-call.dto'; + +export class CreateVoximplantCallExtDto extends CreateVoximplantCallDto { + @ApiProperty() + @IsString() + userName: string; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + viPhoneNumber?: string | null; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/dto/create-voximplant-call.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-call/dto/create-voximplant-call.dto.ts new file mode 100644 index 0000000..f84417e --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/dto/create-voximplant-call.dto.ts @@ -0,0 +1,16 @@ +import { PickType } from '@nestjs/swagger'; + +import { VoximplantCallDto } from './voximplant-call.dto'; + +export class CreateVoximplantCallDto extends PickType(VoximplantCallDto, [ + 'sessionId', + 'callId', + 'numberId', + 'entityId', + 'direction', + 'phoneNumber', + 'duration', + 'status', + 'failureReason', + 'recordUrl', +] as const) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/dto/index.ts b/backend/src/modules/telephony/voximplant/voximplant-call/dto/index.ts new file mode 100644 index 0000000..77eac88 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/dto/index.ts @@ -0,0 +1,6 @@ +export * from './create-voximplant-call-ext.dto'; +export * from './create-voximplant-call.dto'; +export * from './update-voximplant-call-ext.dto'; +export * from './update-voximplant-call.dto'; +export * from './voximplant-call-list.dto'; +export * from './voximplant-call.dto'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/dto/update-voximplant-call-ext.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-call/dto/update-voximplant-call-ext.dto.ts new file mode 100644 index 0000000..017500b --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/dto/update-voximplant-call-ext.dto.ts @@ -0,0 +1,7 @@ +import { OmitType, PartialType } from '@nestjs/swagger'; + +import { CreateVoximplantCallExtDto } from './create-voximplant-call-ext.dto'; + +export class UpdateVoximplantCallExtDto extends PartialType( + OmitType(CreateVoximplantCallExtDto, ['sessionId', 'callId', 'viPhoneNumber', 'numberId'] as const), +) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/dto/update-voximplant-call.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-call/dto/update-voximplant-call.dto.ts new file mode 100644 index 0000000..be7f3c1 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/dto/update-voximplant-call.dto.ts @@ -0,0 +1,15 @@ +import { PartialType, PickType } from '@nestjs/swagger'; +import { VoximplantCallDto } from './voximplant-call.dto'; + +export class UpdateVoximplantCallDto extends PartialType( + PickType(VoximplantCallDto, [ + 'entityId', + 'direction', + 'phoneNumber', + 'duration', + 'status', + 'failureReason', + 'recordUrl', + 'comment', + ] as const), +) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/dto/voximplant-call-list.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-call/dto/voximplant-call-list.dto.ts new file mode 100644 index 0000000..5d28aae --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/dto/voximplant-call-list.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsObject } from 'class-validator'; + +import { PagingMeta } from '@/common'; +import { VoximplantCallDto } from './voximplant-call.dto'; + +export class VoximplantCallListDto { + @ApiProperty({ type: [VoximplantCallDto] }) + @IsArray() + calls: VoximplantCallDto[]; + + @ApiProperty({ type: PagingMeta }) + @IsObject() + meta: PagingMeta; + + constructor(calls: VoximplantCallDto[], meta: PagingMeta) { + this.calls = calls; + this.meta = meta; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/dto/voximplant-call.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-call/dto/voximplant-call.dto.ts new file mode 100644 index 0000000..f0a69eb --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/dto/voximplant-call.dto.ts @@ -0,0 +1,108 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; +import { CallDirection, CallStatus } from '../../common'; + +export class VoximplantCallDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + sessionId: string; + + @ApiProperty() + @IsString() + callId: string; + + @ApiProperty() + @IsNumber() + userId: number; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + numberId: number | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiProperty({ enum: CallDirection }) + @IsEnum(CallDirection) + direction: CallDirection; + + @ApiProperty() + @IsString() + phoneNumber: string; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsNumber() + duration?: number | null; + + @ApiPropertyOptional({ nullable: true, enum: CallStatus }) + @IsOptional() + @IsEnum(CallStatus) + status?: CallStatus | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + failureReason?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + recordUrl?: string | null; + + @ApiPropertyOptional({ nullable: true }) + @IsOptional() + @IsString() + comment: string | null; + + @ApiProperty() + @IsString() + createdAt: string; + + @ApiPropertyOptional({ type: EntityInfoDto, nullable: true }) + @IsObject() + entityInfo: EntityInfoDto | null; + + constructor( + id: number, + sessionId: string, + callId: string, + userId: number, + numberId: number | null, + entityId: number | null, + direction: CallDirection, + phoneNumber: string, + duration: number | null, + status: CallStatus | null, + failureReason: string | null, + recordUrl: string | null, + comment: string | null, + createdAt: string, + entityInfo?: EntityInfoDto | null, + ) { + this.id = id; + this.sessionId = sessionId; + this.callId = callId; + this.userId = userId; + this.numberId = numberId; + this.entityId = entityId; + this.direction = direction; + this.phoneNumber = phoneNumber; + this.duration = duration; + this.status = status; + this.failureReason = failureReason; + this.recordUrl = recordUrl; + this.comment = comment; + this.createdAt = createdAt; + this.entityInfo = entityInfo; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/entities/index.ts b/backend/src/modules/telephony/voximplant/voximplant-call/entities/index.ts new file mode 100644 index 0000000..44c902b --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/entities/index.ts @@ -0,0 +1 @@ +export * from './voximplant-call.entity'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/entities/voximplant-call.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-call/entities/voximplant-call.entity.ts new file mode 100644 index 0000000..20b200c --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/entities/voximplant-call.entity.ts @@ -0,0 +1,155 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { EntityInfoDto } from '@/modules/entity/entity-info/dto/entity-info.dto'; + +import { CallDirection, CallStatus } from '../../common'; +import { CreateVoximplantCallDto, UpdateVoximplantCallDto, VoximplantCallDto } from '../dto'; + +@Entity() +export class VoximplantCall { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + sessionId: string; + + @Column() + callId: string; + + @Column() + userId: number; + + @Column({ nullable: true }) + numberId: number | null; + + @Column({ nullable: true }) + entityId: number | null; + + @Column() + direction: CallDirection; + + @Column() + phoneNumber: string; + + @Column({ nullable: true }) + duration: number | null; + + @Column({ nullable: true }) + status: CallStatus | null; + + @Column({ nullable: true }) + failureReason: string | null; + + @Column({ nullable: true }) + recordUrl: string | null; + + @Column() + accountId: number; + + @Column({ nullable: true }) + comment: string | null; + + @Column() + createdAt: Date; + + private _entityInfo: EntityInfoDto = null; + + constructor( + accountId: number, + sessionId: string, + callId: string, + userId: number, + numberId: number | null, + entityId: number | null, + direction: CallDirection, + phoneNumber: string, + duration: number | null, + status: CallStatus | null, + failureReason: string | null, + recordUrl: string | null, + comment: string, + createdAt?: Date, + ) { + this.accountId = accountId; + this.sessionId = sessionId; + this.callId = callId; + this.userId = userId; + this.numberId = numberId; + this.entityId = entityId; + this.direction = direction; + this.phoneNumber = phoneNumber; + this.duration = duration; + this.status = status; + this.failureReason = failureReason; + this.recordUrl = recordUrl; + this.comment = comment ?? null; + this.createdAt = createdAt ?? DateUtil.now(); + } + + public get entityInfo(): EntityInfoDto { + return this._entityInfo; + } + public set entityInfo(value: EntityInfoDto) { + this._entityInfo = value; + } + + public static fromDto( + accountId: number, + userId: number, + dto: CreateVoximplantCallDto, + createdAt?: Date, + ): VoximplantCall { + return new VoximplantCall( + accountId, + dto.sessionId, + dto.callId, + userId, + dto.numberId, + dto.entityId, + dto.direction, + dto.phoneNumber, + dto.duration, + dto.status, + dto.failureReason, + dto.recordUrl, + null, + createdAt, + ); + } + + public update(userId: number | null | undefined, dto: UpdateVoximplantCallDto): VoximplantCall { + this.userId = userId; + this.entityId = dto.entityId; + this.direction = dto.direction; + this.phoneNumber = dto.phoneNumber; + this.duration = dto.duration; + this.status = dto.status; + this.failureReason = dto.failureReason; + this.recordUrl = dto.recordUrl; + this.comment = dto.comment; + + return this; + } + + public toDto(): VoximplantCallDto { + return new VoximplantCallDto( + this.id, + this.sessionId, + this.callId, + this.userId, + this.numberId, + this.entityId, + this.direction, + this.phoneNumber, + this.duration, + this.status, + this.failureReason, + this.recordUrl, + this.comment, + this.createdAt.toISOString(), + this._entityInfo, + ); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/index.ts b/backend/src/modules/telephony/voximplant/voximplant-call/index.ts new file mode 100644 index 0000000..184c81f --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/index.ts @@ -0,0 +1,6 @@ +export * from './dto'; +export * from './entities'; +export * from './types'; +export * from './voximplant-call-public.controller'; +export * from './voximplant-call.controller'; +export * from './voximplant-call.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/types/index.ts b/backend/src/modules/telephony/voximplant/voximplant-call/types/index.ts new file mode 100644 index 0000000..f469aa9 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/types/index.ts @@ -0,0 +1 @@ +export * from './voximplant-call-list'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/types/voximplant-call-list.ts b/backend/src/modules/telephony/voximplant/voximplant-call/types/voximplant-call-list.ts new file mode 100644 index 0000000..2c58390 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/types/voximplant-call-list.ts @@ -0,0 +1,23 @@ +import { PagingMeta } from '@/common'; + +import { VoximplantCallListDto } from '../dto'; +import { VoximplantCall } from '../entities'; + +export class VoximplantCallList { + calls: VoximplantCall[]; + offset: number; + total: number; + + constructor(calls: VoximplantCall[], offset: number, total: number) { + this.calls = calls; + this.offset = offset; + this.total = total; + } + + public toDto(): VoximplantCallListDto { + return new VoximplantCallListDto( + this.calls ? this.calls.map((c) => c.toDto()) : [], + new PagingMeta(this.offset, this.total), + ); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call-public.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call-public.controller.ts new file mode 100644 index 0000000..7cd95d9 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call-public.controller.ts @@ -0,0 +1,33 @@ +import { Body, Controller, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; + +import { VoximplantCallDto, CreateVoximplantCallExtDto, UpdateVoximplantCallExtDto } from './dto'; +import { VoximplantCallService } from './voximplant-call.service'; + +@ApiTags('telephony/voximplant/integration') +@Controller('integration/:applicationId/calls') +@TransformToDto() +export class VoximplantCallPublicController { + constructor(private service: VoximplantCallService) {} + + @ApiCreatedResponse({ description: 'Create voximplant call', type: VoximplantCallDto }) + @Post() + async createExt( + @Param('applicationId', ParseIntPipe) applicationId: number, + @Body() dto: CreateVoximplantCallExtDto, + ) { + return this.service.createExt(applicationId, dto); + } + + @ApiCreatedResponse({ description: 'Update voximplant call', type: VoximplantCallDto }) + @Patch(':externalId') + async updateExt( + @Param('applicationId', ParseIntPipe) applicationId: number, + @Param('externalId') externalId: string, + @Body() dto: UpdateVoximplantCallExtDto, + ) { + return this.service.updateExt(applicationId, externalId, dto); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call.controller.ts new file mode 100644 index 0000000..1e0cbdc --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call.controller.ts @@ -0,0 +1,44 @@ +import { Body, Controller, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto, PagingQuery } from '@/common'; +import { AuthData, AuthDataPrefetch, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { VoximplantCallDto, CreateVoximplantCallDto, VoximplantCallListDto, UpdateVoximplantCallDto } from './dto'; +import { VoximplantCallService } from './voximplant-call.service'; + +@ApiTags('telephony/voximplant/calls') +@Controller('calls') +@JwtAuthorized() +@TransformToDto() +export class VoximplantCallController { + constructor(private readonly service: VoximplantCallService) {} + + @ApiCreatedResponse({ description: 'Create voximplant call', type: VoximplantCallDto }) + @Post() + async create(@CurrentAuth() { accountId, userId }: AuthData, @Body() dto: CreateVoximplantCallDto) { + return this.service.create(accountId, userId, dto); + } + + @AuthDataPrefetch({ user: true }) + @ApiCreatedResponse({ description: 'Get voximplant calls', type: VoximplantCallListDto }) + @Get() + async getList(@CurrentAuth() { accountId, user }: AuthData, @Query() paging: PagingQuery) { + return this.service.getList(accountId, user, paging); + } + @ApiCreatedResponse({ description: 'Get voximplant calls', type: VoximplantCallDto }) + @Get(':callId') + async getOne(@CurrentAuth() { accountId }: AuthData, @Param('callId', ParseIntPipe) callId: number) { + return this.service.findOne(accountId, { id: callId }); + } + + @ApiCreatedResponse({ description: 'Update voximplant call', type: VoximplantCallDto }) + @Patch(':externalId') + async updateByExternalId( + @CurrentAuth() { accountId, userId }: AuthData, + @Param('externalId') externalId: string, + @Body() dto: UpdateVoximplantCallDto, + ) { + return this.service.updateByExternalId(accountId, userId, externalId, dto); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call.service.ts b/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call.service.ts new file mode 100644 index 0000000..c88dfa5 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-call/voximplant-call.service.ts @@ -0,0 +1,180 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { PagingQuery } from '@/common'; + +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; + +import { TelephonyCallCreatedEvent, TelephonyCallUpdatedEvent, TelephonyEventType } from '../../common'; + +import { VoximplantAccountService } from '../voximplant-account'; +import { VoximplantScenarioService } from '../voximplant-scenario'; +import { VoximplantUserService } from '../voximplant-user'; +import { VoximplantNumberService } from '../voximplant-number'; + +import { + CreateVoximplantCallDto, + CreateVoximplantCallExtDto, + UpdateVoximplantCallDto, + UpdateVoximplantCallExtDto, +} from './dto'; +import { VoximplantCall } from './entities'; +import { VoximplantCallList } from './types'; + +interface FindFilter { + id?: number; + externalId?: string; +} + +@Injectable() +export class VoximplantCallService { + constructor( + private readonly eventEmitter: EventEmitter2, + @InjectRepository(VoximplantCall) + private readonly repository: Repository, + private readonly entityInfoService: EntityInfoService, + private readonly viAccountService: VoximplantAccountService, + private readonly viUserService: VoximplantUserService, + private readonly viScenarioService: VoximplantScenarioService, + private readonly viNumberService: VoximplantNumberService, + ) {} + + async create(accountId: number, userId: number, dto: CreateVoximplantCallDto): Promise { + const call = await this.repository.save(VoximplantCall.fromDto(accountId, userId, dto)); + + this.eventEmitter.emit( + TelephonyEventType.TelephonyCallCreated, + new TelephonyCallCreatedEvent({ + accountId, + entityId: call.entityId, + callId: call.id, + createdAt: call.createdAt.toISOString(), + }), + ); + + return call; + } + + async createExt(applicationId: number, dto: CreateVoximplantCallExtDto): Promise { + const viAccount = await this.viAccountService.findOneExt({ applicationId }); + if (viAccount) { + const viUser = await this.viUserService.findOne(viAccount.accountId, { userName: dto.userName }); + if (viUser) { + const viNumber = dto.viPhoneNumber + ? await this.viNumberService.findOne(viAccount.accountId, { phoneNumber: dto.viPhoneNumber }) + : null; + dto.numberId = viNumber?.id ?? dto.numberId ?? null; + return this.create(viAccount.accountId, viUser.userId, dto); + } + } + return null; + } + + async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + + async findOneFull(accountId: number, user: User, filter?: FindFilter): Promise { + const call = await this.findOne(accountId, filter); + if (call?.entityId) { + call.entityInfo = await this.entityInfoService.findOne({ accountId, user, entityId: call.entityId }); + } + return call; + } + + async getList(accountId: number, user: User, paging?: PagingQuery): Promise { + const [calls, total] = await this.createFindQb(accountId) + .orderBy('created_at', 'DESC') + .limit(paging?.take) + .offset(paging?.skip) + .getManyAndCount(); + for (const call of calls) { + call.entityInfo = call.entityId + ? await this.entityInfoService.findOne({ accountId, user, entityId: call.entityId }) + : null; + } + return new VoximplantCallList(calls, paging?.take + paging?.skip, total); + } + + async updateByExternalId( + accountId: number, + userId: number, + externalId: string, + dto: UpdateVoximplantCallDto, + ): Promise { + const viCall = await this.findOne(accountId, { externalId }); + return viCall ? this.updateCall(accountId, viCall, dto, userId) : null; + } + + async updateExt( + applicationId: number, + externalId: string, + dto: UpdateVoximplantCallExtDto, + ): Promise { + const viAccount = await this.viAccountService.findOneExt({ applicationId }); + + if (viAccount) { + const viCall = await this.findOne(viAccount.accountId, { externalId }); + if (viCall) { + const userId = dto.userName + ? (await this.viUserService.findOne(viAccount.accountId, { userName: dto.userName }))?.userId + : undefined; + return this.updateCall(viAccount.accountId, viCall, dto, userId); + } + } + return null; + } + + private async updateCall( + accountId: number, + viCall: VoximplantCall, + dto: UpdateVoximplantCallDto, + userId: number | null | undefined, + ): Promise { + const { status, entityId } = viCall; + await this.repository.save(viCall.update(userId, dto)); + + const call = await this.findOne(viCall.accountId, { id: viCall.id }); + + if (status !== call.status) { + const result = await this.viScenarioService.processCall(accountId, call); + if (result?.entities?.length > 0) { + call.entityId = result.entities[0].id; + await this.repository.save(call); + } + } + + this.eventEmitter.emit( + TelephonyEventType.TelephonyCallUpdated, + new TelephonyCallUpdatedEvent({ + accountId, + entityId: call.entityId, + callId: call.id, + createdAt: call.createdAt.toISOString(), + oldEntityId: entityId, + }), + ); + + return call; + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder().where('account_id = :accountId', { accountId }); + if (filter?.id) { + qb.andWhere('id = :id', { id: filter.id }); + } + if (filter?.externalId) { + qb.andWhere( + new Brackets((qb) => + qb + .where('call_id = :callId', { callId: filter.externalId }) + .orWhere('session_id = :sessionId', { sessionId: filter.externalId }), + ), + ); + } + return qb; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-core/index.ts b/backend/src/modules/telephony/voximplant/voximplant-core/index.ts new file mode 100644 index 0000000..b8058b3 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-core/index.ts @@ -0,0 +1,2 @@ +export * from './voximplant-core.controller'; +export * from './voximplant-core.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-core/voximplant-core.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-core/voximplant-core.controller.ts new file mode 100644 index 0000000..d2197c4 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-core/voximplant-core.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common'; + +import { VoximplantCoreService } from './voximplant-core.service'; + +@ApiTags('telephony/voximplant/core') +@Controller('core') +@JwtAuthorized() +@TransformToDto() +export class VoximplantCoreController { + constructor(private service: VoximplantCoreService) {} + + @Get('children') + public async getChildrenAccounts() { + return this.service.getChildrenAccounts(); + } + + @Get('keys') + public async getKeys() { + return this.service.getKeys(); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-core/voximplant-core.service.ts b/backend/src/modules/telephony/voximplant/voximplant-core/voximplant-core.service.ts new file mode 100644 index 0000000..0f9e5ba --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-core/voximplant-core.service.ts @@ -0,0 +1,172 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { lastValueFrom } from 'rxjs'; +import VoximplantApiClient from '@voximplant/apiclient-nodejs'; + +import { PasswordUtil } from '@/common'; +import { ApplicationConfig } from '@/config'; + +import { AccountSettingsService } from '@/modules/iam/account-settings/account-settings.service'; +import { UserService } from '@/modules/iam/user/user.service'; +import { Account } from '@/modules/iam/account/entities/account.entity'; +import { UserRole } from '@/modules/iam/common/enums/user-role.enum'; + +import { VoximplantConfig } from '../config/voximplant.config'; + +const VoximplantUrls = { + base: 'https://api.voximplant.com/platform_api', + addAccount: () => `${VoximplantUrls.base}/AddAccount`, + createKey: () => `${VoximplantUrls.base}/CreateKey`, +} as const; + +const VOXIMPLANT_ACCOUNT_NAME_MAX = 20; + +interface Key { + keyId: string; + privateKey: string; +} +interface ChildAccount { + accountId: number; + accountName: string; + accountEmail: string; + apiKey: string; + password: string; + billingAccountId: number; + active: boolean; + key: Key; +} + +@Injectable() +export class VoximplantCoreService { + private readonly logger = new Logger(VoximplantCoreService.name); + private _appName: string; + private _viConfig: VoximplantConfig; + + private client: VoximplantApiClient; + + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + private readonly accountSettingsService: AccountSettingsService, + private readonly userService: UserService, + ) { + this._appName = this.configService.get('application').name; + this._viConfig = this.configService.get('voximplant'); + + const credentialsPath = `${process.cwd()}${this._viConfig.credentialsFile}`; + try { + this.client = new VoximplantApiClient({ pathToCredentials: credentialsPath }); + } catch (e) { + this.logger.warn(`Failed to initialize Voximplant client: ${(e as Error).message}`); + this.client = null; + } + } + + public async createChildAccount(account: Account): Promise { + if (!this.client) { + this.logger.warn('Voximplant client not initialized'); + return null; + } + const accountSettings = await this.accountSettingsService.getOne(account.id); + const owner = await this.userService.findOne({ accountId: account.id, role: UserRole.OWNER }); + const accountName = `${this._appName}-${account.subdomain}`.substring(0, VOXIMPLANT_ACCOUNT_NAME_MAX).toLowerCase(); + try { + const params = { + parent_account_id: this._viConfig.accountId, + parent_account_api_key: this._viConfig.accountApiKey, + account_name: accountName, + account_email: owner.email, + account_password: PasswordUtil.generateSecure(), + active: true, + language_code: accountSettings.language, + location: accountSettings.timeZone, + account_notifications: true, + tariff_changing_notifications: true, + min_balance_to_notify: 100, + Authorization: this.client.generateAuthHeader(), + }; + const { data } = await lastValueFrom(this.httpService.post(VoximplantUrls.addAccount(), {}, { params })); + if (data.result === 1) { + const key = await this.createKey(data.account_id, data.api_key); + if (key) { + return { + accountId: data.account_id, + accountName: params.account_name, + accountEmail: params.account_email, + apiKey: data.api_key, + password: params.account_password, + billingAccountId: data.billing_account_id, + active: data.active, + key, + }; + } + } else { + this.logger.error(`Create child account error: ${JSON.stringify(data)}`); + } + } catch (e) { + this.logger.error(`Create child account error`, (e as Error)?.stack); + } + return null; + } + + public async getChildrenAccounts() { + if (!this.client) { + this.logger.warn('Voximplant client not initialized'); + return []; + } + return await this.client.Accounts.getChildrenAccounts({}); + } + + public async setActiveChildAccount( + childAccountId: number, + childAccountName: string, + childAccountEmail: string, + isActive: boolean, + ): Promise { + if (!this.client) { + this.logger.warn('Voximplant client not initialized'); + return false; + } + const { result } = await this.client.Accounts.setChildAccountInfo({ + childAccountId, + childAccountName, + childAccountEmail, + active: isActive, + }); + + return result === 1; + } + + private async createKey(accountId: number, apiKey: string): Promise { + try { + const params = { + account_id: accountId, + api_key: apiKey, + description: `${this._appName} Integration`, + role_name: 'Owner', + }; + const { data } = await lastValueFrom(this.httpService.post(VoximplantUrls.createKey(), {}, { params })); + if (data.result) { + return { + keyId: data.result.key_id, + privateKey: data.result.private_key, + }; + } + } catch (e) { + this.logger.error(`Create key error`, (e as Error)?.stack); + } + return null; + } + + public async getKeys() { + if (!this.client) { + this.logger.warn('Voximplant client not initialized'); + return { roles: [], keys: [] }; + } + const { result: keys } = await this.client.RoleSystem.getKeys({}); + const { result: roles } = await this.client.RoleSystem.getRoles({}); + + return { roles, keys }; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/dto/create-voximplant-number.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-number/dto/create-voximplant-number.dto.ts new file mode 100644 index 0000000..d92167c --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/dto/create-voximplant-number.dto.ts @@ -0,0 +1,9 @@ +import { PickType } from '@nestjs/swagger'; + +import { VoximplantNumberDto } from './voximplant-number.dto'; + +export class CreateVoximplantNumberDto extends PickType(VoximplantNumberDto, [ + 'phoneNumber', + 'externalId', + 'userIds', +] as const) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/dto/index.ts b/backend/src/modules/telephony/voximplant/voximplant-number/dto/index.ts new file mode 100644 index 0000000..9d9eb69 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-voximplant-number.dto'; +export * from './phone-number.dto'; +export * from './update-voximplant-number.dto'; +export * from './voximplant-number-filter.dto'; +export * from './voximplant-number.dto'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/dto/phone-number.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-number/dto/phone-number.dto.ts new file mode 100644 index 0000000..2636ae1 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/dto/phone-number.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class PhoneNumberDto { + @ApiProperty() + @IsString() + externalId: string; + + @ApiProperty() + @IsString() + phoneNumber: string; + + @ApiProperty() + @IsString() + countryCode: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + regionName?: string; + + constructor({ externalId, phoneNumber, countryCode, regionName }: PhoneNumberDto) { + this.externalId = externalId; + this.phoneNumber = phoneNumber; + this.countryCode = countryCode; + this.regionName = regionName; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/dto/update-voximplant-number.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-number/dto/update-voximplant-number.dto.ts new file mode 100644 index 0000000..4bddc02 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/dto/update-voximplant-number.dto.ts @@ -0,0 +1,7 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { VoximplantNumberDto } from './voximplant-number.dto'; + +export class UpdateVoximplantNumberDto extends PartialType( + PickType(VoximplantNumberDto, ['phoneNumber', 'externalId', 'userIds'] as const), +) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/dto/voximplant-number-filter.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-number/dto/voximplant-number-filter.dto.ts new file mode 100644 index 0000000..2c0533e --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/dto/voximplant-number-filter.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class VoximplantNumberFilterDto { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + accessibleUserId?: number; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/dto/voximplant-number.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-number/dto/voximplant-number.dto.ts new file mode 100644 index 0000000..548832f --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/dto/voximplant-number.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class VoximplantNumberDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsString() + phoneNumber: string; + + @ApiProperty({ nullable: true }) + @IsString() + externalId: string | null; + + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + constructor({ id, phoneNumber, externalId, userIds }: VoximplantNumberDto) { + this.id = id; + this.phoneNumber = phoneNumber; + this.externalId = externalId; + this.userIds = userIds; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/entities/index.ts b/backend/src/modules/telephony/voximplant/voximplant-number/entities/index.ts new file mode 100644 index 0000000..4091ec1 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/entities/index.ts @@ -0,0 +1,2 @@ +export * from './voximplant-number-user.entity'; +export * from './voximplant-number.entity'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/entities/voximplant-number-user.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-number/entities/voximplant-number-user.entity.ts new file mode 100644 index 0000000..bbb25eb --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/entities/voximplant-number-user.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class VoximplantNumberUser { + @PrimaryColumn() + numberId: number; + + @PrimaryColumn() + userId: number; + + @Column() + accountId: number; + + constructor(accountId: number, numberId: number, userId: number) { + this.accountId = accountId; + this.numberId = numberId; + this.userId = userId; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/entities/voximplant-number.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-number/entities/voximplant-number.entity.ts new file mode 100644 index 0000000..df4a295 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/entities/voximplant-number.entity.ts @@ -0,0 +1,48 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { CreateVoximplantNumberDto, UpdateVoximplantNumberDto, VoximplantNumberDto } from '../dto'; +import { VoximplantNumberUser } from './voximplant-number-user.entity'; + +@Entity() +export class VoximplantNumber { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + phoneNumber: string; + + @Column({ nullable: true }) + externalId: string | null; + + constructor(accountId: number, phoneNumber: string, externalId: string | null) { + this.accountId = accountId; + this.phoneNumber = phoneNumber; + this.externalId = externalId; + } + + private _users: VoximplantNumberUser[] | null | undefined; + public get users(): VoximplantNumberUser[] | null | undefined { + return this._users; + } + public set users(value: VoximplantNumberUser[] | null | undefined) { + this._users = value; + } + + public static fromDto(accountId: number, dto: CreateVoximplantNumberDto): VoximplantNumber { + return new VoximplantNumber(accountId, dto.phoneNumber, dto.externalId); + } + + public update(dto: UpdateVoximplantNumberDto): VoximplantNumber { + this.phoneNumber = dto.phoneNumber !== undefined ? dto.phoneNumber : this.phoneNumber; + this.externalId = dto.externalId !== undefined ? dto.externalId : this.externalId; + + return this; + } + + public toDto(): VoximplantNumberDto { + return new VoximplantNumberDto({ ...this, userIds: this._users?.map((u) => u.userId) }); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/index.ts b/backend/src/modules/telephony/voximplant/voximplant-number/index.ts new file mode 100644 index 0000000..e66c244 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './services'; +export * from './types'; +export * from './voximplant-number.controller'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/services/index.ts b/backend/src/modules/telephony/voximplant/voximplant-number/services/index.ts new file mode 100644 index 0000000..357cca9 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/services/index.ts @@ -0,0 +1,2 @@ +export * from './voximplant-number-user.service'; +export * from './voximplant-number.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/services/voximplant-number-user.service.ts b/backend/src/modules/telephony/voximplant/voximplant-number/services/voximplant-number-user.service.ts new file mode 100644 index 0000000..2f7bd36 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/services/voximplant-number-user.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { VoximplantNumberUser } from '../entities'; + +interface FindFilter { + numberId?: number; +} + +@Injectable() +export class VoximplantNumberUserService { + constructor( + @InjectRepository(VoximplantNumberUser) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, numberId: number, userIds: number[]): Promise { + return await this.repository.save(userIds.map((userId) => new VoximplantNumberUser(accountId, numberId, userId))); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return await this.createFindQb(accountId, filter).getOne(); + } + public async findMany(accountId: number, filter?: FindFilter): Promise { + return await this.createFindQb(accountId, filter).getMany(); + } + + public async update( + accountId: number, + numberId: number, + currentUsers: VoximplantNumberUser[], + userIds: number[], + ): Promise { + const addUsers = userIds.filter((id) => !currentUsers.some((user) => user.userId === id)); + const removeUsers = currentUsers.filter((user) => !userIds.some((id) => id === user.userId)); + + currentUsers.push(...(await this.create(accountId, numberId, addUsers))); + + if (removeUsers.length) { + await this.repository.remove(removeUsers); + } + + return currentUsers.filter((user) => !removeUsers.some((u) => u.userId === user.userId)); + } + + public async removeUser(accountId: number, userId: number) { + await this.repository.delete({ accountId, userId }); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('vinu').where('vinu.account_id = :accountId', { accountId }); + + if (filter?.numberId) { + qb.andWhere('vinu.number_id = :numberId', { numberId: filter.numberId }); + } + + return qb; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/services/voximplant-number.service.ts b/backend/src/modules/telephony/voximplant/voximplant-number/services/voximplant-number.service.ts new file mode 100644 index 0000000..f5ec9e6 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/services/voximplant-number.service.ts @@ -0,0 +1,135 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { VoximplantAccountService } from '../../voximplant-account'; + +import { CreateVoximplantNumberDto, UpdateVoximplantNumberDto } from '../dto'; +import { VoximplantNumber } from '../entities'; +import { ExpandableField, PhoneNumber } from '../types'; +import { VoximplantNumberUserService } from './voximplant-number-user.service'; + +interface FindFilter { + id?: number; + phoneNumber?: string; + accessibleUserId?: number; +} +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class VoximplantNumberService { + constructor( + @InjectRepository(VoximplantNumber) + private readonly repository: Repository, + private readonly viAccountService: VoximplantAccountService, + private readonly viNumberUserService: VoximplantNumberUserService, + ) {} + + public async getAvailableNumbers(accountId: number): Promise { + const { client, appParam } = await this.viAccountService.getClient(accountId); + + const response = await client.PhoneNumbers.getPhoneNumbers(appParam); + if (response.result) { + const numbers: PhoneNumber[] = []; + for (const number of response.result) { + numbers.push( + new PhoneNumber({ + externalId: number.phoneId.toString(), + phoneNumber: number.phoneNumber, + countryCode: number.phoneCountryCode, + regionName: number.phoneRegionName, + }), + ); + } + return numbers; + } + + return []; + } + + public async create(accountId: number, dto: CreateVoximplantNumberDto): Promise { + const viNumber = await this.repository.save(VoximplantNumber.fromDto(accountId, dto)); + + if (dto.userIds?.length) { + viNumber.users = await this.viNumberUserService.create(accountId, viNumber.id, dto.userIds); + } + + return viNumber; + } + + public async findOne( + accountId: number, + filter?: FindFilter, + options?: FindOptions, + ): Promise { + const viNumber = await this.createFindQb(accountId, filter).getOne(); + return viNumber && options?.expand ? await this.expandOne(viNumber, options.expand) : viNumber; + } + + public async findMany(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const viNumbers = await this.createFindQb(accountId, filter).orderBy('vin.id').getMany(); + return viNumbers && options?.expand ? await this.expandMany(viNumbers, options.expand) : viNumbers; + } + + public async getCount(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getCount(); + } + + public async checkAvailable(accountId: number, userId: number, phoneNumber: string): Promise { + return (await this.getCount(accountId, { accessibleUserId: userId, phoneNumber })) > 0; + } + + public async update(accountId: number, numberId: number, dto: UpdateVoximplantNumberDto): Promise { + const viNumber = await this.findOne(accountId, { id: numberId }, { expand: ['users'] }); + + await this.repository.save(viNumber.update(dto)); + + if (dto.userIds) { + viNumber.users = await this.viNumberUserService.update(accountId, viNumber.id, viNumber.users, dto.userIds); + } + + return viNumber; + } + + public async delete(accountId: number, numberId: number) { + await this.repository.delete({ accountId, id: numberId }); + } + + public async removeUser(accountId: number, userId: number) { + await this.viNumberUserService.removeUser(accountId, userId); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('vin').where('vin.account_id = :accountId', { accountId }); + + if (filter?.id) { + qb.andWhere('vin.id = :id', { id: filter.id }); + } + if (filter?.phoneNumber) { + qb.andWhere('vin.phone_number ilike :phoneNumber', { phoneNumber: `%${filter.phoneNumber}%` }); + } + if (filter?.accessibleUserId) { + qb.leftJoin('voximplant_number_user', 'vinu', 'vinu.number_id = vin.id').andWhere( + new Brackets((qb1) => + qb1 + .where('vinu.user_id = :accessibleUserId', { accessibleUserId: filter.accessibleUserId }) + .orWhere('vinu.user_id is NULL'), + ), + ); + } + + return qb; + } + + private async expandOne(viNumber: VoximplantNumber, expand: ExpandableField[]): Promise { + if (expand.includes('users')) { + viNumber.users = await this.viNumberUserService.findMany(viNumber.accountId, { numberId: viNumber.id }); + } + return viNumber; + } + private async expandMany(viNumbers: VoximplantNumber[], expand: ExpandableField[]): Promise { + return await Promise.all(viNumbers.map((viNumber) => this.expandOne(viNumber, expand))); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/types/expandable-field.ts b/backend/src/modules/telephony/voximplant/voximplant-number/types/expandable-field.ts new file mode 100644 index 0000000..13ac0ef --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'users'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/types/index.ts b/backend/src/modules/telephony/voximplant/voximplant-number/types/index.ts new file mode 100644 index 0000000..a2c92c0 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/types/index.ts @@ -0,0 +1,2 @@ +export * from './expandable-field'; +export * from './phone-number'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/types/phone-number.ts b/backend/src/modules/telephony/voximplant/voximplant-number/types/phone-number.ts new file mode 100644 index 0000000..6ec9969 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/types/phone-number.ts @@ -0,0 +1,29 @@ +import { PhoneNumberDto } from '../dto'; + +export class PhoneNumber { + externalId: string; + phoneNumber: string; + countryCode: string; + regionName?: string; + + constructor({ + externalId, + phoneNumber, + countryCode, + regionName, + }: { + externalId: string; + phoneNumber: string; + countryCode: string; + regionName?: string; + }) { + this.externalId = externalId; + this.phoneNumber = phoneNumber; + this.countryCode = countryCode; + this.regionName = regionName; + } + + public toDto(): PhoneNumberDto { + return new PhoneNumberDto(this); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-number/voximplant-number.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-number/voximplant-number.controller.ts new file mode 100644 index 0000000..83640bc --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-number/voximplant-number.controller.ts @@ -0,0 +1,88 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { ExpandQuery, TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized, UserAccess } from '@/modules/iam/common'; + +import { + CreateVoximplantNumberDto, + PhoneNumberDto, + UpdateVoximplantNumberDto, + VoximplantNumberDto, + VoximplantNumberFilterDto, +} from './dto'; +import { ExpandableField } from './types'; +import { VoximplantNumberService } from './services'; + +@ApiTags('telephony/voximplant/numbers') +@Controller('numbers') +@JwtAuthorized() +@TransformToDto() +export class VoximplantNumberController { + constructor(private readonly service: VoximplantNumberService) {} + + @ApiOkResponse({ description: 'Get available voximplant numbers', type: [PhoneNumberDto] }) + @Get('available') + public async getAvailableNumbers(@CurrentAuth() { accountId }: AuthData) { + return this.service.getAvailableNumbers(accountId); + } + + @ApiCreatedResponse({ description: 'Create voximplant number', type: VoximplantNumberDto }) + @Post() + @UserAccess({ adminOnly: true }) + public async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateVoximplantNumberDto) { + return this.service.create(accountId, dto); + } + + @ApiOkResponse({ description: 'Get voximplant numbers', type: [VoximplantNumberDto] }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: users.', + }) + @Get() + public async getMany( + @CurrentAuth() { accountId }: AuthData, + @Query() filter: VoximplantNumberFilterDto, + @Query() expand?: ExpandQuery, + ) { + return this.service.findMany(accountId, filter, { expand: expand.fields }); + } + + @ApiOkResponse({ description: 'Get voximplant number', type: VoximplantNumberDto }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: users.', + }) + @Get(':numberId') + public async getOne( + @CurrentAuth() { accountId }: AuthData, + @Param('numberId', ParseIntPipe) numberId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.findOne(accountId, { id: numberId }, { expand: expand.fields }); + } + + @ApiCreatedResponse({ description: 'Update voximplant number', type: VoximplantNumberDto }) + @Patch(':numberId') + @UserAccess({ adminOnly: true }) + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('numberId', ParseIntPipe) numberId: number, + @Body() dto: UpdateVoximplantNumberDto, + ) { + return this.service.update(accountId, numberId, dto); + } + + @ApiOkResponse({ description: 'Delete voximplant number' }) + @Delete(':numberId') + @UserAccess({ adminOnly: true }) + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('numberId', ParseIntPipe) numberId: number) { + return this.service.delete(accountId, numberId); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-history-report-filter.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-history-report-filter.dto.ts new file mode 100644 index 0000000..12bdb3e --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-history-report-filter.dto.ts @@ -0,0 +1,35 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter, NumberFilter } from '@/common'; +import { CallDirection, CallStatus } from '../../common'; + +export class CallHistoryReportFilterDto { + @ApiPropertyOptional({ type: Number, nullable: true, description: 'Entity type ID' }) + @IsOptional() + @IsNumber() + entityTypeId?: number | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs' }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + @ApiPropertyOptional({ enum: CallDirection, nullable: true, description: 'Call direction' }) + @IsOptional() + @IsEnum(CallDirection) + direction?: CallDirection | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; + + @ApiPropertyOptional({ type: NumberFilter, nullable: true, description: 'Duration' }) + @IsOptional() + duration?: NumberFilter | null; + + @ApiPropertyOptional({ enum: CallStatus, nullable: true, description: 'Call status' }) + @IsOptional() + @IsEnum(CallStatus) + status?: CallStatus | null; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-history-report.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-history-report.dto.ts new file mode 100644 index 0000000..f1a903c --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-history-report.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { PagingMeta } from '@/common'; +import { VoximplantCallDto } from '../../voximplant-call'; + +export class CallHistoryReportDto { + @ApiProperty({ type: [VoximplantCallDto], description: 'Calls' }) + calls: VoximplantCallDto[]; + + @ApiProperty({ type: PagingMeta, description: 'Paging meta' }) + meta: PagingMeta; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-block.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-block.dto.ts new file mode 100644 index 0000000..3d92409 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-block.dto.ts @@ -0,0 +1,26 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { QuantityAmountDto } from '@/common'; + +export class CallReportBlockDto { + @ApiProperty({ type: QuantityAmountDto, description: 'All calls' }) + all: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Incoming calls' }) + incoming: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Outgoing calls' }) + outgoing: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Missed calls' }) + missed: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Average for all calls' }) + avgAll: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Average for incoming calls' }) + avgIncoming: QuantityAmountDto; + + @ApiProperty({ type: QuantityAmountDto, description: 'Average for outgoing calls' }) + avgOutgoing: QuantityAmountDto; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-filter.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-filter.dto.ts new file mode 100644 index 0000000..6a269e5 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-filter.dto.ts @@ -0,0 +1,46 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { DatePeriodFilter, NumberFilter } from '@/common'; +import { BoardStageType } from '@/CRM/board-stage'; + +import { TelephonyReportType } from '../enums'; + +export class CallReportFilterDto { + @ApiProperty({ enum: TelephonyReportType, description: 'Report type' }) + @IsEnum(TelephonyReportType) + type: TelephonyReportType; + + @ApiPropertyOptional({ type: Number, nullable: true, description: 'Entity type ID' }) + @IsOptional() + @IsNumber() + entityTypeId?: number | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Board IDs' }) + @IsOptional() + @IsArray() + boardIds?: number[] | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs' }) + @IsOptional() + @IsArray() + userIds?: number[] | null; + + @ApiPropertyOptional({ enum: BoardStageType, nullable: true, description: 'Stage type' }) + @IsOptional() + @IsEnum(BoardStageType) + stageType?: BoardStageType | null; + + @ApiPropertyOptional({ type: DatePeriodFilter, nullable: true, description: 'Period' }) + @IsOptional() + period?: DatePeriodFilter | null; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'Number IDs' }) + @IsOptional() + @IsArray() + numberIds?: number[] | null; + + @ApiPropertyOptional({ type: NumberFilter, nullable: true, description: 'Duration' }) + @IsOptional() + duration?: NumberFilter | null; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-row.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-row.dto.ts new file mode 100644 index 0000000..60539db --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report-row.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber } from 'class-validator'; + +import { CallReportBlockDto } from './call-report-block.dto'; + +export class CallReportRowDto { + @ApiProperty({ description: 'User ID or Department ID depends from report type or 0 for total row.' }) + @IsNumber() + ownerId: number; + + @ApiProperty({ type: CallReportBlockDto, description: 'Call report block' }) + @IsArray() + call: CallReportBlockDto; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report.dto.ts new file mode 100644 index 0000000..74429e0 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/call-report.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +import { CallReportRowDto } from './call-report-row.dto'; + +export class CallReportDto { + @ApiProperty({ type: [CallReportRowDto], description: 'Call report rows for users' }) + users: CallReportRowDto[]; + + @ApiProperty({ type: [CallReportRowDto], description: 'Call report rows for departments' }) + departments: CallReportRowDto[]; + + @ApiProperty({ type: CallReportRowDto, description: 'Total call report row' }) + total: CallReportRowDto; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/index.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/index.ts new file mode 100644 index 0000000..f64f8b5 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/dto/index.ts @@ -0,0 +1,6 @@ +export * from './call-history-report-filter.dto'; +export * from './call-history-report.dto'; +export * from './call-report-block.dto'; +export * from './call-report-filter.dto'; +export * from './call-report-row.dto'; +export * from './call-report.dto'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/enums/index.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/enums/index.ts new file mode 100644 index 0000000..e468c83 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/enums/index.ts @@ -0,0 +1 @@ +export * from './telephony-report-type.enum'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/enums/telephony-report-type.enum.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/enums/telephony-report-type.enum.ts new file mode 100644 index 0000000..feefe4a --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/enums/telephony-report-type.enum.ts @@ -0,0 +1,5 @@ +export enum TelephonyReportType { + User = 'user', + Rating = 'rating', + Department = 'department', +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/index.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/index.ts new file mode 100644 index 0000000..061bcf6 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './types'; +export * from './voximplant-reporting.controller'; +export * from './voximplant-reporting.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-history-report.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-history-report.ts new file mode 100644 index 0000000..a553f20 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-history-report.ts @@ -0,0 +1,17 @@ +import { PagingMeta } from '@/common'; +import { VoximplantCall } from '../../voximplant-call'; +import { CallHistoryReportDto } from '../dto'; + +export class CallHistoryReport { + calls: VoximplantCall[]; + meta: PagingMeta; + + constructor({ calls, offset, total }: { calls: VoximplantCall[]; offset: number; total: number }) { + this.calls = calls; + this.meta = new PagingMeta(offset, total); + } + + public toDto(): CallHistoryReportDto { + return { calls: this.calls.map((call) => call.toDto()), meta: this.meta }; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report-block.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report-block.ts new file mode 100644 index 0000000..887b7b9 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report-block.ts @@ -0,0 +1,63 @@ +import { QuantityAmount } from '@/common'; +import { CallReportBlockDto } from '../dto'; + +export class CallReportBlock { + all: QuantityAmount; + incoming: QuantityAmount; + outgoing: QuantityAmount; + missed: QuantityAmount; + avgAll: QuantityAmount; + avgIncoming: QuantityAmount; + avgOutgoing: QuantityAmount; + + constructor( + all: QuantityAmount, + incoming: QuantityAmount, + outgoing: QuantityAmount, + missed: QuantityAmount, + avgAll: QuantityAmount, + avgIncoming: QuantityAmount, + avgOutgoing: QuantityAmount, + ) { + this.all = all; + this.incoming = incoming; + this.outgoing = outgoing; + this.missed = missed; + this.avgAll = avgAll; + this.avgIncoming = avgIncoming; + this.avgOutgoing = avgOutgoing; + } + + public static empty(): CallReportBlock { + return new CallReportBlock( + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + QuantityAmount.empty(), + ); + } + + public toDto(): CallReportBlockDto { + return { + all: this.all.toDto(), + incoming: this.incoming.toDto(), + outgoing: this.outgoing.toDto(), + missed: this.missed.toDto(), + avgAll: this.avgAll.toDto(), + avgIncoming: this.avgIncoming.toDto(), + avgOutgoing: this.avgOutgoing.toDto(), + }; + } + + public add(cell: CallReportBlock): CallReportBlock { + this.all.add(cell.all); + this.incoming.add(cell.incoming); + this.outgoing.add(cell.outgoing); + this.missed.add(cell.missed); + + return this; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report-row.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report-row.ts new file mode 100644 index 0000000..1db01d5 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report-row.ts @@ -0,0 +1,26 @@ +import { CallReportRowDto } from '../dto'; +import { CallReportBlock } from './call-report-block'; + +export class CallReportRow { + ownerId: number; + call: CallReportBlock; + + constructor(ownerId: number, call: CallReportBlock) { + this.ownerId = ownerId; + this.call = call; + } + + public static empty(ownerId: number): CallReportRow { + return new CallReportRow(ownerId, CallReportBlock.empty()); + } + + public toDto(): CallReportRowDto { + return { ownerId: this.ownerId, call: this.call.toDto() }; + } + + public add(row: CallReportRow): CallReportRow { + this.call.add(row.call); + + return this; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report.ts new file mode 100644 index 0000000..fb8b317 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/call-report.ts @@ -0,0 +1,30 @@ +import { CallReportDto } from '../dto'; +import { CallReportRow } from './call-report-row'; + +export class CallReport { + users: Map; + departments: Map; + total: CallReportRow; + + constructor({ + users, + departments, + total, + }: { + users: Map; + departments: Map; + total: CallReportRow; + }) { + this.users = users; + this.departments = departments; + this.total = total; + } + + public toDto(): CallReportDto { + return { + users: Array.from(this.users.values()).map((u) => u.toDto()), + departments: Array.from(this.departments.values()).map((u) => u.toDto()), + total: this.total.toDto(), + }; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/types/index.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/index.ts new file mode 100644 index 0000000..286ab3a --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/types/index.ts @@ -0,0 +1,4 @@ +export * from './call-history-report'; +export * from './call-report-block'; +export * from './call-report-row'; +export * from './call-report'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/voximplant-reporting.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/voximplant-reporting.controller.ts new file mode 100644 index 0000000..8882fb9 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/voximplant-reporting.controller.ts @@ -0,0 +1,36 @@ +import { Body, Controller, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { PagingQuery, TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { CallHistoryReportDto, CallHistoryReportFilterDto, CallReportDto, CallReportFilterDto } from './dto'; +import { VoximplantReportingService } from './voximplant-reporting.service'; + +@ApiTags('telephony/voximplant/reporting') +@Controller('reporting') +@JwtAuthorized({ prefetch: { user: true } }) +@TransformToDto() +export class VoximplantReportingController { + constructor(private readonly service: VoximplantReportingService) {} + + @ApiOperation({ summary: 'Get call report', description: 'Get call report' }) + @ApiBody({ type: CallReportFilterDto, required: true, description: 'Call report filter' }) + @ApiOkResponse({ description: 'General call report', type: CallReportDto }) + @Post('call') + public async getCallReport(@CurrentAuth() { accountId, user }: AuthData, @Body() filter: CallReportFilterDto) { + return this.service.getCallReport({ accountId, user, filter }); + } + + @ApiOperation({ summary: 'Get call history report', description: 'Get call history report' }) + @ApiBody({ type: CallHistoryReportFilterDto, required: true, description: 'Call history report filter' }) + @ApiOkResponse({ description: 'Call history report', type: CallHistoryReportDto }) + @Post('call/history') + public async getCallHistoryReport( + @CurrentAuth() { accountId, user }: AuthData, + @Body() filter: CallHistoryReportFilterDto, + @Query() paging: PagingQuery, + ) { + return this.service.getCallHistoryReport({ accountId, user, filter, paging }); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-reporting/voximplant-reporting.service.ts b/backend/src/modules/telephony/voximplant/voximplant-reporting/voximplant-reporting.service.ts new file mode 100644 index 0000000..3387718 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-reporting/voximplant-reporting.service.ts @@ -0,0 +1,334 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { + NumberFilter, + PagingQuery, + DateUtil, + DatePeriod, + ForbiddenError, + propagateData, + isUnique, + intersection, +} from '@/common'; + +import { AuthorizationService } from '@/modules/iam/authorization/authorization.service'; +import { AccountSettingsService } from '@/modules/iam/account-settings/account-settings.service'; +import { DepartmentService } from '@/modules/iam/department'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { EntityInfoService } from '@/modules/entity/entity-info/entity-info.service'; +import { EntityType } from '@/CRM/entity-type/entities'; + +import { CallStatus, CallDirection } from '../common'; +import { VoximplantCall } from '../voximplant-call/entities/voximplant-call.entity'; + +import { CallHistoryReportFilterDto, CallReportFilterDto } from './dto'; +import { TelephonyReportType } from './enums'; +import { CallHistoryReport, CallReport, CallReportRow } from './types'; + +interface FindFilter { + accountId: number; + status?: CallStatus; + entityTypeId?: number; + stageIds?: number[]; + userIds?: number[]; + direction?: CallDirection; + period?: DatePeriod; + duration?: NumberFilter; + numberIds?: number[] | null; +} + +enum CallGroupBy { + None = 'none', + User = 'user', + Department = 'department', +} +interface RawReportRow { + direction: CallDirection; + owner_id: number; + quantity: number; + duration: number; +} + +@Injectable() +export class VoximplantReportingService { + constructor( + @InjectRepository(VoximplantCall) + private readonly repository: Repository, + private readonly authService: AuthorizationService, + private readonly accountSettingsService: AccountSettingsService, + private readonly departmentService: DepartmentService, + private readonly entityInfoService: EntityInfoService, + ) {} + + public async getCallHistoryReport({ + accountId, + user, + filter, + paging, + }: { + accountId: number; + user: User; + filter: CallHistoryReportFilterDto; + paging: PagingQuery; + }): Promise { + let allowedUserIds: number[] | undefined = undefined; + if (filter.entityTypeId) { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: EntityType.getAuthorizable(filter.entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + allowedUserIds = userIds; + } + + const qb = this.createFindQb({ + accountId, + userIds: filter.userIds?.length ? intersection(filter.userIds, allowedUserIds) : allowedUserIds, + direction: filter.direction, + period: filter.period ? DatePeriod.fromFilter(filter.period) : undefined, + duration: filter.duration, + status: filter.status, + }); + + const calls = await qb.clone().orderBy('call.created_at', 'DESC').offset(paging.skip).limit(paging.take).getMany(); + + const entityIds = calls + .filter((c) => !!c.entityId) + .map((c) => c.entityId) + .filter(isUnique); + const entityInfos = entityIds.length ? await this.entityInfoService.findMany({ accountId, user, entityIds }) : []; + if (entityInfos.length) { + calls.forEach((call) => { + call.entityInfo = entityInfos.find((e) => e.id === call.entityId) ?? null; + }); + } + + const total = await qb.getCount(); + + return new CallHistoryReport({ calls, offset: paging.take + paging.skip, total }); + } + + public async getCallReport({ + accountId, + user, + filter, + }: { + accountId: number; + user: User; + filter: CallReportFilterDto; + }): Promise { + let allowedUserIds: number[] | undefined = undefined; + if (filter.entityTypeId) { + const { allow, userIds } = await this.authService.getPermissions({ + action: 'report', + user, + authorizable: EntityType.getAuthorizable(filter.entityTypeId), + }); + if (!allow) { + throw new ForbiddenError(); + } + allowedUserIds = userIds; + } + + const reportFilter: FindFilter = { + accountId, + //TODO: add EntityTypes and Stages filtering logic + // entityTypeId: filter.entityTypeId, + // stageIds: [], + userIds: filter.userIds?.length ? intersection(filter.userIds, allowedUserIds) : allowedUserIds, + period: filter.period ? DatePeriod.fromFilter(filter.period) : undefined, + numberIds: filter.numberIds, + duration: filter.duration, + }; + + const period = await this.getReportPeriod(reportFilter); + + const users = + filter.type !== TelephonyReportType.Department + ? await this.getReportGroupBy({ filter: reportFilter, groupBy: CallGroupBy.User }) + : new Map(); + const departments = + filter.type !== TelephonyReportType.Rating + ? await this.getReportGroupBy({ filter: reportFilter, groupBy: CallGroupBy.Department }) + : new Map(); + + const totalRow = await this.getReportGroupBy({ filter: reportFilter, groupBy: CallGroupBy.None }); + + if (departments.size) { + const hierarchy = await this.departmentService.getHierarchy({ accountId }); + + if (hierarchy.length) { + propagateData(hierarchy, departments, CallReportRow.empty); + } + } + + await this.setAverage({ rows: users, period }); + await this.setAverage({ rows: departments, period }); + await this.setAverage({ rows: totalRow, period }); + + const total = totalRow.values().next().value ?? CallReportRow.empty(0); + const report = new CallReport({ users, departments, total }); + + return report; + } + + private async getReportGroupBy({ + filter, + groupBy, + }: { + filter: FindFilter; + groupBy: CallGroupBy; + }): Promise> { + const rowMap = new Map(); + + const success = await this.getReportRaw({ filter: { ...filter, status: CallStatus.SUCCESS }, groupBy }); + for (const item of success) { + const row = rowMap.get(item.owner_id) ?? CallReportRow.empty(item.owner_id); + switch (item.direction) { + case CallDirection.INCOMING: + row.call.incoming.quantity = item.quantity; + row.call.incoming.amount = item.duration; + break; + case CallDirection.OUTGOING: + row.call.outgoing.quantity = item.quantity; + row.call.outgoing.amount = item.duration; + break; + } + row.call.all.quantity += item.quantity; + row.call.all.amount += item.duration; + rowMap.set(item.owner_id, row); + } + + const missed = await this.getReportRaw({ filter: { ...filter, status: CallStatus.MISSED }, groupBy }); + for (const item of missed) { + const row = rowMap.get(item.owner_id) ?? CallReportRow.empty(item.owner_id); + row.call.missed.quantity += item.quantity; + row.call.missed.amount += item.duration; + row.call.all.quantity += item.quantity; + row.call.all.amount += item.duration; + rowMap.set(item.owner_id, row); + } + + return rowMap; + } + + private async getReportRaw({ + filter, + groupBy, + }: { + filter: FindFilter; + groupBy: CallGroupBy; + }): Promise { + const qb = this.createFindQb(filter) + .select('call.direction', 'direction') + .addSelect('count(call.id)::int', 'quantity') + .addSelect('sum(call.duration)::int', 'duration') + .groupBy('call.direction'); + + switch (groupBy) { + case CallGroupBy.User: + qb.addGroupBy('call.user_id').addSelect('call.user_id', 'owner_id'); + break; + case CallGroupBy.Department: + qb.leftJoin('users', 'u', 'u.id = call.user_id') + .addGroupBy('u.department_id') + .addSelect('COALESCE(u.department_id, 0)', 'owner_id'); + break; + case CallGroupBy.None: + qb.addSelect('0', 'owner_id'); + break; + } + + return qb.getRawMany(); + } + + private async getReportPeriod(filter: FindFilter): Promise { + const qb = this.createFindQb(filter); + const periodDates = await qb + .select('min(call.created_at)', 'min_date') + .addSelect('max(call.created_at)', 'max_date') + .getRawOne<{ min_date: string; max_date: string }>(); + + if (periodDates.min_date && periodDates.max_date) { + const startDate = new Date(periodDates.min_date); + const endDate = new Date(periodDates.max_date); + + const { workingDays } = await this.accountSettingsService.getOne(filter.accountId); + return DateUtil.workingDaysBetween(startDate, endDate, workingDays); + } + + return 1; + } + + private async setAverage({ rows, period }: { rows: Map; period: number }) { + rows.forEach((value) => { + value.call.avgAll.quantity = Math.round(value.call.all.quantity / period); + value.call.avgAll.amount = Math.round(value.call.all.amount / period); + value.call.avgIncoming.quantity = Math.round(value.call.incoming.quantity / period); + value.call.avgIncoming.amount = Math.round(value.call.incoming.amount / period); + value.call.avgOutgoing.quantity = Math.round(value.call.outgoing.quantity / period); + value.call.avgOutgoing.amount = Math.round(value.call.outgoing.amount / period); + }); + } + + private createFindQb({ + accountId, + status, + entityTypeId, + stageIds, + userIds, + direction, + period, + duration, + numberIds, + }: FindFilter) { + const qb = this.repository.createQueryBuilder('call').andWhere('call.account_id = :accountId', { accountId }); + + if (status) { + qb.andWhere('call.status = :status', { status }); + } + if (entityTypeId || stageIds) { + qb.leftJoin('entity', 'e', 'e.id = call.entity_id'); + + if (entityTypeId) { + qb.andWhere('e.entity_type_id = :entityTypeId', { entityTypeId }); + } + + if (stageIds?.length) { + qb.andWhere('call.stage_id IN (:...stageIds)', { stageIds }); + } + } + if (userIds?.length) { + qb.andWhere('call.user_id IN (:...userIds)', { userIds }); + } + if (direction) { + qb.andWhere('call.direction = :direction', { direction }); + } + if (period) { + if (period.from) { + qb.andWhere('call.created_at >= :from', { from: period.from }); + } + if (period.to) { + qb.andWhere('call.created_at <= :to', { to: period.to }); + } + } + if (duration) { + if (duration.min) { + qb.andWhere('call.duration >= :min', { min: duration.min }); + } + if (duration.max) { + qb.andWhere('call.duration <= :max', { max: duration.max }); + } + } + if (numberIds?.length) { + qb.andWhere('call.number_id IN (:...numberIds)', { numberIds }); + } + + return qb; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/index.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/index.ts new file mode 100644 index 0000000..1aac3d8 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/index.ts @@ -0,0 +1,4 @@ +export * from './voximplant-scenario-entity.dto'; +export * from './voximplant-scenario-note.dto'; +export * from './voximplant-scenario-task.dto'; +export * from './voximplant-scenarios.dto'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-entity.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-entity.dto.ts new file mode 100644 index 0000000..60ffc57 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-entity.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { ScenarioType } from '../enums'; + +export class VoximplantScenarioEntityDto { + @ApiProperty({ enum: ScenarioType }) + @IsEnum(ScenarioType) + scenarioType: ScenarioType; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + contactId: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + dealId: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + boardId: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + ownerId: number | null; + + constructor( + scenarioType: ScenarioType, + contactId: number | null, + dealId: number | null, + boardId: number | null, + ownerId: number | null, + ) { + this.scenarioType = scenarioType; + this.contactId = contactId; + this.dealId = dealId; + this.boardId = boardId; + this.ownerId = ownerId; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-note.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-note.dto.ts new file mode 100644 index 0000000..15ff4ae --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-note.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsString } from 'class-validator'; + +import { ScenarioType } from '../enums'; + +export class VoximplantScenarioNoteDto { + @ApiProperty({ enum: ScenarioType }) + @IsEnum(ScenarioType) + scenarioType: ScenarioType; + + @ApiProperty() + @IsString() + noteText: string; + + constructor(scenarioType: ScenarioType, noteText: string) { + this.scenarioType = scenarioType; + this.noteText = noteText; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-task.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-task.dto.ts new file mode 100644 index 0000000..3c068d5 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenario-task.dto.ts @@ -0,0 +1,86 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { ScenarioType } from '../enums'; + +export class VoximplantScenarioTaskDto { + @ApiProperty({ enum: ScenarioType }) + @IsEnum(ScenarioType) + scenarioType: ScenarioType; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsBoolean() + createActivity: boolean | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + activityTypeId: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + activityText: string | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + activityDuration: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + activityOwnerId: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsBoolean() + createTask: boolean | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + taskTitle: string | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsString() + taskText: string | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + taskDuration: number | null; + + @ApiProperty({ nullable: true }) + @IsOptional() + @IsNumber() + taskOwnerId: number | null; + + constructor( + scenarioType: ScenarioType, + createActivity: boolean | null, + activityTypeId: number | null, + activityText: string | null, + activityDuration: number | null, + activityOwnerId: number | null, + createTask: boolean | null, + taskTitle: string | null, + taskText: string | null, + taskDuration: number | null, + taskOwnerId: number | null, + ) { + this.scenarioType = scenarioType; + this.createActivity = createActivity; + this.activityTypeId = activityTypeId; + this.activityText = activityText; + this.activityDuration = activityDuration; + this.activityOwnerId = activityOwnerId; + this.createTask = createTask; + this.taskTitle = taskTitle; + this.taskText = taskText; + this.taskDuration = taskDuration; + this.taskOwnerId = taskOwnerId; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenarios.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenarios.dto.ts new file mode 100644 index 0000000..7aba284 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/dto/voximplant-scenarios.dto.ts @@ -0,0 +1,33 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsOptional } from 'class-validator'; + +import { VoximplantScenarioEntityDto } from './voximplant-scenario-entity.dto'; +import { VoximplantScenarioNoteDto } from './voximplant-scenario-note.dto'; +import { VoximplantScenarioTaskDto } from './voximplant-scenario-task.dto'; + +export class VoximplantScenariosDto { + @ApiProperty({ type: [VoximplantScenarioEntityDto], nullable: true }) + @IsOptional() + @IsArray() + entities: VoximplantScenarioEntityDto[] | null; + + @ApiProperty({ type: [VoximplantScenarioNoteDto], nullable: true }) + @IsOptional() + @IsArray() + notes: VoximplantScenarioNoteDto[] | null; + + @ApiProperty({ type: [VoximplantScenarioTaskDto], nullable: true }) + @IsOptional() + @IsArray() + tasks: VoximplantScenarioTaskDto[] | null; + + constructor( + entities: VoximplantScenarioEntityDto[] | null, + notes: VoximplantScenarioNoteDto[] | null, + tasks: VoximplantScenarioTaskDto[] | null, + ) { + this.entities = entities; + this.notes = notes; + this.tasks = tasks; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/index.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/index.ts new file mode 100644 index 0000000..00e8a95 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/index.ts @@ -0,0 +1,3 @@ +export * from './voximplant-scenario-entity.entity'; +export * from './voximplant-scenario-note.entity'; +export * from './voximplant-scenario-task.entity'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-entity.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-entity.entity.ts new file mode 100644 index 0000000..78b299b --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-entity.entity.ts @@ -0,0 +1,59 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { ScenarioType } from '../enums'; +import { VoximplantScenarioEntityDto } from '../dto'; + +@Entity() +export class VoximplantScenarioEntity { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + scenarioType: ScenarioType; + + @Column({ nullable: true }) + contactId: number | null; + + @Column({ nullable: true }) + dealId: number | null; + + @Column({ nullable: true }) + boardId: number | null; + + @Column({ nullable: true }) + ownerId: number | null; + + constructor( + accountId: number, + scenarioType: ScenarioType, + contactId: number | null, + dealId: number | null, + boardId: number | null, + ownerId: number | null, + ) { + this.accountId = accountId; + this.scenarioType = scenarioType; + this.contactId = contactId; + this.dealId = dealId; + this.boardId = boardId; + this.ownerId = ownerId; + } + + public static fromDto(accountId: number, dto: VoximplantScenarioEntityDto): VoximplantScenarioEntity { + return new VoximplantScenarioEntity( + accountId, + dto.scenarioType, + dto.contactId, + dto.dealId, + dto.boardId, + dto.ownerId, + ); + } + + public toDto(): VoximplantScenarioEntityDto { + return new VoximplantScenarioEntityDto(this.scenarioType, this.contactId, this.dealId, this.boardId, this.ownerId); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-note.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-note.entity.ts new file mode 100644 index 0000000..c7460df --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-note.entity.ts @@ -0,0 +1,33 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { ScenarioType } from '../enums'; +import { VoximplantScenarioNoteDto } from '../dto'; + +@Entity() +export class VoximplantScenarioNote { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + scenarioType: ScenarioType; + + @Column() + noteText: string; + + constructor(accountId: number, scenarioType: ScenarioType, noteText: string) { + this.accountId = accountId; + this.scenarioType = scenarioType; + this.noteText = noteText; + } + + public static fromDto(accountId: number, dto: VoximplantScenarioNoteDto): VoximplantScenarioNote { + return new VoximplantScenarioNote(accountId, dto.scenarioType, dto.noteText); + } + + public toDto(): VoximplantScenarioNoteDto { + return new VoximplantScenarioNoteDto(this.scenarioType, this.noteText); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-task.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-task.entity.ts new file mode 100644 index 0000000..df37fe0 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/entities/voximplant-scenario-task.entity.ts @@ -0,0 +1,107 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { ScenarioType } from '../enums'; +import { VoximplantScenarioTaskDto } from '../dto'; + +@Entity() +export class VoximplantScenarioTask { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + scenarioType: ScenarioType; + + @Column({ nullable: true, default: false }) + createActivity: boolean | null; + + @Column({ nullable: true }) + activityTypeId: number | null; + + @Column({ nullable: true }) + activityText: string | null; + + @Column({ nullable: true }) + activityDuration: number | null; + + @Column({ nullable: true }) + activityOwnerId: number | null; + + @Column({ nullable: true, default: false }) + createTask: boolean | null; + + @Column({ nullable: true }) + taskTitle: string | null; + + @Column({ nullable: true }) + taskText: string | null; + + @Column({ nullable: true }) + taskDuration: number | null; + + @Column({ nullable: true }) + taskOwnerId: number | null; + + constructor( + accountId: number, + scenarioType: ScenarioType, + createActivity: boolean | null, + activityTypeId: number | null, + activityText: string | null, + activityDuration: number | null, + activityOwnerId: number | null, + createTask: boolean | null, + taskTitle: string | null, + taskText: string | null, + taskDuration: number | null, + taskOwnerId: number | null, + ) { + this.accountId = accountId; + this.scenarioType = scenarioType; + this.createActivity = createActivity; + this.activityTypeId = activityTypeId; + this.activityText = activityText; + this.activityDuration = activityDuration; + this.activityOwnerId = activityOwnerId; + this.createTask = createTask; + this.taskTitle = taskTitle; + this.taskText = taskText; + this.taskDuration = taskDuration; + this.taskOwnerId = taskOwnerId; + } + + public static fromDto(accountId: number, dto: VoximplantScenarioTaskDto): VoximplantScenarioTask { + return new VoximplantScenarioTask( + accountId, + dto.scenarioType, + dto.createActivity, + dto.activityTypeId, + dto.activityText, + dto.activityDuration, + dto.activityOwnerId, + dto.createTask, + dto.taskTitle, + dto.taskText, + dto.taskDuration, + dto.taskOwnerId, + ); + } + + public toDto(): VoximplantScenarioTaskDto { + return new VoximplantScenarioTaskDto( + this.scenarioType, + this.createActivity, + this.activityTypeId, + this.activityText, + this.activityDuration, + this.activityOwnerId, + this.createTask, + this.taskTitle, + this.taskText, + this.taskDuration, + this.taskOwnerId, + ); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/enums/index.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/enums/index.ts new file mode 100644 index 0000000..40a9bbd --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/enums/index.ts @@ -0,0 +1 @@ +export * from './scenario-type.enum'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/enums/scenario-type.enum.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/enums/scenario-type.enum.ts new file mode 100644 index 0000000..e6f70b8 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/enums/scenario-type.enum.ts @@ -0,0 +1,7 @@ +export enum ScenarioType { + INCOMING_UNKNOWN = 'incoming_unknown', + INCOMING_UNKNOWN_MISSING = 'incoming_unknown_missing', + INCOMING_KNOWN_MISSING = 'incoming_known_missing', + OUTGOING_UNKNOWN = 'outgoing_unknown', + OUTGOING_UNANSWERED = 'outgoing_unanswered', +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/index.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/index.ts new file mode 100644 index 0000000..184b3a1 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/index.ts @@ -0,0 +1,6 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './types'; +export * from './voximplant-scenario.controller'; +export * from './voximplant-scenario.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/types/index.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/types/index.ts new file mode 100644 index 0000000..e4ade5a --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/types/index.ts @@ -0,0 +1 @@ +export * from './voximplant-scenarios'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/types/voximplant-scenarios.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/types/voximplant-scenarios.ts new file mode 100644 index 0000000..f24a52a --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/types/voximplant-scenarios.ts @@ -0,0 +1,34 @@ +import { VoximplantScenariosDto } from '../dto'; +import { VoximplantScenarioEntity, VoximplantScenarioNote, VoximplantScenarioTask } from '../entities'; + +export class VoximplantScenarios { + entities: VoximplantScenarioEntity[] | null; + notes: VoximplantScenarioNote[] | null; + tasks: VoximplantScenarioTask[] | null; + + constructor( + entities: VoximplantScenarioEntity[] | null, + notes: VoximplantScenarioNote[] | null, + tasks: VoximplantScenarioTask[] | null, + ) { + this.entities = entities; + this.notes = notes; + this.tasks = tasks; + } + + public static fromDto(accountId: number, dto: VoximplantScenariosDto): VoximplantScenarios { + return new VoximplantScenarios( + dto.entities?.map((entity) => VoximplantScenarioEntity.fromDto(accountId, entity)) ?? null, + dto.notes?.map((note) => VoximplantScenarioNote.fromDto(accountId, note)) ?? null, + dto.tasks?.map((task) => VoximplantScenarioTask.fromDto(accountId, task)) ?? null, + ); + } + + public toDto(): VoximplantScenariosDto { + return new VoximplantScenariosDto( + this.entities?.map((entity) => entity.toDto()) ?? [], + this.notes?.map((note) => note.toDto()) ?? [], + this.tasks?.map((task) => task.toDto()) ?? [], + ); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/voximplant-scenario.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/voximplant-scenario.controller.ts new file mode 100644 index 0000000..74cf9ec --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/voximplant-scenario.controller.ts @@ -0,0 +1,49 @@ +import { Body, Controller, Get, Post, Put, Param, ParseEnumPipe } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized, UserAccess } from '@/modules/iam/common'; + +import { CallDirection } from '../common'; +import { VoximplantScenariosDto } from './dto'; +import { VoximplantScenarioService } from './voximplant-scenario.service'; + +@ApiTags('telephony/voximplant/scenarios') +@Controller('scenarios') +@JwtAuthorized() +@TransformToDto() +export class VoximplantScenarioController { + constructor(private service: VoximplantScenarioService) {} + + @ApiCreatedResponse({ description: 'Create voximplant scenarios', type: VoximplantScenariosDto }) + @Post() + @UserAccess({ adminOnly: true }) + public async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: VoximplantScenariosDto) { + return this.service.upsert(accountId, dto); + } + + @ApiCreatedResponse({ description: 'Get voximplant scenarios', type: VoximplantScenariosDto }) + @Get() + public async findOne(@CurrentAuth() { accountId }: AuthData) { + return this.service.findOne(accountId); + } + + @ApiCreatedResponse({ + description: 'Check contact creation voximplant scenario is manual', + type: Boolean, + }) + @Get('check-manual/:callDirection') + public async checkContactsCreationScenarioIsManual( + @CurrentAuth() { accountId }: AuthData, + @Param('callDirection', new ParseEnumPipe(CallDirection)) callDirection: CallDirection, + ): Promise { + return this.service.checkContactsCreationScenarioIsManual(accountId, callDirection); + } + + @ApiCreatedResponse({ description: 'Update voximplant account' }) + @Put() + @UserAccess({ adminOnly: true }) + public async update(@CurrentAuth() { accountId }: AuthData, @Body() dto: VoximplantScenariosDto) { + return this.service.upsert(accountId, dto); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-scenario/voximplant-scenario.service.ts b/backend/src/modules/telephony/voximplant/voximplant-scenario/voximplant-scenario.service.ts new file mode 100644 index 0000000..6f46393 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-scenario/voximplant-scenario.service.ts @@ -0,0 +1,243 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; + +import { Entity } from '@/CRM/Model/Entity/Entity'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; +import { NoteService } from '@/CRM/note/note.service'; +import { ActivityService } from '@/CRM/activity/activity.service'; +import { TaskService } from '@/CRM/task/task.service'; + +import { CallStatus, CallDirection } from '../common'; +import { VoximplantCall } from '../voximplant-call'; + +import { VoximplantScenariosDto } from './dto'; +import { VoximplantScenarioEntity, VoximplantScenarioNote, VoximplantScenarioTask } from './entities'; +import { ScenarioType } from './enums'; +import { VoximplantScenarios } from './types'; + +interface RunScenarioValues { + phone?: string; + entityId?: number; + userId?: number; +} + +@Injectable() +export class VoximplantScenarioService { + constructor( + @InjectRepository(VoximplantScenarioEntity) + private readonly repositoryEntity: Repository, + @InjectRepository(VoximplantScenarioNote) + private readonly repositoryNote: Repository, + @InjectRepository(VoximplantScenarioTask) + private readonly repositoryTask: Repository, + private readonly userService: UserService, + private readonly entityService: EntityService, + private readonly activityService: ActivityService, + private readonly taskService: TaskService, + private readonly noteService: NoteService, + ) {} + + async upsert(accountId: number, dto: VoximplantScenariosDto): Promise { + await this.repositoryEntity.delete({ accountId }); + await this.repositoryNote.delete({ accountId }); + await this.repositoryTask.delete({ accountId }); + + const scenarios = VoximplantScenarios.fromDto(accountId, dto); + const entities = await this.repositoryEntity.save(scenarios.entities); + const notes = await this.repositoryNote.save(scenarios.notes); + const tasks = await this.repositoryTask.save(scenarios.tasks); + + return new VoximplantScenarios(entities, notes, tasks); + } + + async findOne(accountId: number): Promise { + const entities = await this.repositoryEntity.findBy({ accountId }); + const notes = await this.repositoryNote.findBy({ accountId }); + const tasks = await this.repositoryTask.findBy({ accountId }); + + return new VoximplantScenarios(entities, notes, tasks); + } + + async processCall(accountId: number, viCall: VoximplantCall): Promise<{ entities: Entity[] } | null> { + if (viCall.status === CallStatus.ACCEPTED && !viCall.entityId) { + return this.runScenario( + accountId, + viCall.direction === CallDirection.INCOMING ? ScenarioType.INCOMING_UNKNOWN : ScenarioType.OUTGOING_UNKNOWN, + { phone: viCall.phoneNumber, userId: viCall.userId }, + ); + } + + if (viCall.status === CallStatus.MISSED) { + if (viCall.entityId) { + return this.runScenario( + accountId, + viCall.direction === CallDirection.INCOMING + ? ScenarioType.INCOMING_KNOWN_MISSING + : ScenarioType.OUTGOING_UNANSWERED, + { phone: viCall.phoneNumber, userId: viCall.userId, entityId: viCall.entityId }, + ); + } else if (viCall.direction === CallDirection.INCOMING) { + return this.runScenario(accountId, ScenarioType.INCOMING_UNKNOWN_MISSING, { + phone: viCall.phoneNumber, + }); + } + } + + return null; + } + + async checkContactsCreationScenarioIsManual(accountId: number, callDirection: CallDirection): Promise { + const scenarioType = + callDirection === CallDirection.INCOMING ? ScenarioType.INCOMING_UNKNOWN : ScenarioType.OUTGOING_UNKNOWN; + return !(await this.repositoryEntity.findOneBy({ accountId, scenarioType })); + } + + private async runScenario( + accountId: number, + type: ScenarioType, + values: RunScenarioValues, + ): Promise<{ entities: Entity[] }> { + const scenarios = await this.findOne(accountId); + const entities: Entity[] = []; + if (scenarios?.entities) { + const result = await this.runEntitiesScenarios( + accountId, + scenarios.entities.filter((s) => s.scenarioType === type), + values, + ); + if (result.entities?.length > 0) { + entities.push(...result.entities); + } + } + if (!values.entityId && entities.length > 0) { + const [lastEntity] = entities.slice(-1); // assuming to link task/activity with the last created entity + values.entityId = lastEntity.id; + } + if (scenarios?.tasks) { + await this.runTasksScenarios( + accountId, + scenarios.tasks.filter((s) => s.scenarioType === type), + values, + ); + } + if (scenarios?.notes) { + await this.runNotesScenarios( + accountId, + scenarios.notes.filter((s) => s.scenarioType === type), + values, + ); + } + + return { entities }; + } + + private async runEntitiesScenarios( + accountId: number, + scenarios: VoximplantScenarioEntity[], + values: RunScenarioValues, + ): Promise<{ entities: Entity[] }> { + if (!values.phone) return { entities: [] }; + + const entities: Entity[] = []; + for (const scenario of scenarios) { + const ownerId = values.userId ?? scenario.ownerId; + if (ownerId) { + const owner = await this.userService.findOne({ accountId, id: ownerId }); + const lead = scenario.dealId + ? { + entityTypeId: scenario.dealId, + boardId: scenario.boardId, + fieldValues: this.getFieldValues(values), + } + : null; + const contact = scenario.contactId + ? { + entityTypeId: scenario.contactId, + fieldValues: this.getFieldValues(values), + linkedEntities: lead ? [lead] : undefined, + } + : null; + if (contact || lead) { + const created = await this.entityService.createSimple({ + accountId, + user: owner, + dto: contact ?? lead, + options: { checkDuplicate: true }, + }); + entities.push(...created); + } + } + } + return { entities }; + } + + private getFieldValues(values: RunScenarioValues) { + return values.phone + ? [ + { + fieldType: FieldType.Phone, + value: values.phone.startsWith('+') ? values.phone : '+' + values.phone, + }, + ] + : undefined; + } + + private async runTasksScenarios(accountId: number, scenarios: VoximplantScenarioTask[], values: RunScenarioValues) { + if (!values.entityId) return; + + const now = DateUtil.now(); + for (const scenario of scenarios) { + if (scenario.createActivity && scenario.activityOwnerId) { + const owner = await this.userService.findOne({ accountId, id: scenario.activityOwnerId }); + const endDate = DateUtil.add(now, { seconds: scenario.activityDuration }); + await this.activityService.create(accountId, owner, { + responsibleUserId: owner.id, + startDate: now.toISOString(), + endDate: endDate.toISOString(), + text: scenario.activityText, + activityTypeId: scenario.activityTypeId, + entityId: values.entityId, + }); + } + if (scenario.createTask) { + const owner = await this.userService.findOne({ accountId, id: scenario.taskOwnerId }); + const endDate = DateUtil.add(now, { seconds: scenario.taskDuration }); + await this.taskService.create({ + accountId, + user: owner, + dto: { + responsibleUserId: owner.id, + startDate: now.toISOString(), + endDate: endDate.toISOString(), + text: scenario.taskText, + title: scenario.taskTitle, + plannedTime: scenario.taskDuration, + entityId: values.entityId, + settingsId: null, + boardId: null, + stageId: null, + }, + }); + } + } + } + + private async runNotesScenarios(accountId: number, scenarios: VoximplantScenarioNote[], values: RunScenarioValues) { + if (!values.entityId || !values.userId) return; + + for (const scenario of scenarios) { + await this.noteService.create({ + accountId, + userId: values.userId, + entityId: values.entityId, + dto: { text: scenario.noteText }, + }); + } + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/dto/create-voximplant-sip.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/create-voximplant-sip.dto.ts new file mode 100644 index 0000000..fdf7512 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/create-voximplant-sip.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty, ApiPropertyOptional, PickType } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +import { VoximplantSIPDto } from './voximplant-sip.dto'; + +export class CreateVoximplantSIPDto extends PickType(VoximplantSIPDto, ['type', 'name', 'userIds'] as const) { + @ApiProperty() + @IsString() + proxy: string; + + @ApiProperty() + @IsString() + sipUsername: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + authUser?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + password?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + outboundProxy?: string; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/dto/index.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/index.ts new file mode 100644 index 0000000..126950d --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/index.ts @@ -0,0 +1,5 @@ +export * from './create-voximplant-sip.dto'; +export * from './update-voximplant-sip.dto'; +export * from './voximplant-sip-filter.dto'; +export * from './voximplant-sip-registration.dto'; +export * from './voximplant-sip.dto'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/dto/update-voximplant-sip.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/update-voximplant-sip.dto.ts new file mode 100644 index 0000000..cb97b30 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/update-voximplant-sip.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from '@nestjs/swagger'; + +import { CreateVoximplantSIPDto } from './create-voximplant-sip.dto'; + +export class UpdateVoximplantSIPDto extends PartialType(CreateVoximplantSIPDto) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip-filter.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip-filter.dto.ts new file mode 100644 index 0000000..8476894 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip-filter.dto.ts @@ -0,0 +1,9 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNumber, IsOptional } from 'class-validator'; + +export class VoximplantSipFilterDto { + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + accessibleUserId?: number; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip-registration.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip-registration.dto.ts new file mode 100644 index 0000000..def8d93 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip-registration.dto.ts @@ -0,0 +1,85 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class VoximplantSIPRegistrationDto { + @ApiProperty({ description: 'The SIP registration ID' }) + @IsNumber() + sipRegistrationId: number; + + @ApiProperty({ description: 'The SIP username' }) + @IsString() + sipUsername: string; + + @ApiProperty({ description: 'The SIP proxy' }) + @IsString() + proxy: string; + + @ApiProperty({ description: 'The last time updated' }) + @IsNumber() + lastUpdated: number; + + @ApiPropertyOptional({ description: 'The SIP authentications user' }) + @IsOptional() + @IsString() + authUser?: string; + + @ApiPropertyOptional({ description: 'The SIP outbound proxy' }) + @IsOptional() + @IsString() + outboundProxy?: string; + + @ApiPropertyOptional({ description: 'The successful SIP registration' }) + @IsOptional() + @IsBoolean() + successful?: boolean; + + @ApiPropertyOptional({ description: 'The status code from a SIP registration' }) + @IsOptional() + @IsNumber() + statusCode?: number; + + @ApiPropertyOptional({ description: 'The error message from a SIP registration' }) + @IsOptional() + @IsString() + errorMessage?: string; + + @ApiProperty({ description: 'The subscription deactivation flag' }) + @IsBoolean() + deactivated: boolean; + + @ApiProperty({ description: 'The next subscription renewal date' }) + @IsString() + nextSubscriptionRenewal: Date; + + @ApiProperty({ description: 'The purchase date in 24-h format: YYYY-MM-DD HH:mm:ss' }) + @IsString() + purchaseDate: Date; + + @ApiProperty({ description: 'The subscription monthly charge' }) + @IsString() + subscriptionPrice: string; + + @ApiProperty({ description: 'SIP registration is persistent' }) + @IsBoolean() + isPersistent: boolean; + + @ApiPropertyOptional({ description: 'The id of the bound user' }) + @IsOptional() + @IsNumber() + userId?: number; + + @ApiPropertyOptional({ description: 'The name of the bound user' }) + @IsOptional() + @IsString() + userName?: string; + + @ApiPropertyOptional({ description: 'The id of the bound rule' }) + @IsOptional() + @IsNumber() + ruleId?: number; + + @ApiPropertyOptional({ description: 'The name of the bound rule' }) + @IsOptional() + @IsString() + ruleName?: string; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip.dto.ts new file mode 100644 index 0000000..4d97f97 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/dto/voximplant-sip.dto.ts @@ -0,0 +1,40 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { PbxProviderType } from '../enums'; +import { VoximplantSIPRegistrationDto } from './voximplant-sip-registration.dto'; + +export class VoximplantSIPDto { + @ApiProperty() + @IsNumber() + id: number; + + @ApiProperty() + @IsNumber() + externalId: number; + + @ApiProperty({ enum: PbxProviderType }) + @IsEnum(PbxProviderType) + type: PbxProviderType; + + @ApiProperty() + @IsString() + name: string; + + @ApiPropertyOptional({ type: [Number], nullable: true }) + @IsOptional() + @IsNumber({}, { each: true }) + userIds?: number[] | null; + + @ApiPropertyOptional({ type: VoximplantSIPRegistrationDto }) + registration?: VoximplantSIPRegistrationDto; + + constructor({ id, externalId, type, name, userIds, registration }: VoximplantSIPDto) { + this.id = id; + this.externalId = externalId; + this.type = type; + this.name = name; + this.userIds = userIds; + this.registration = registration; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/entities/index.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/entities/index.ts new file mode 100644 index 0000000..506d5d7 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/entities/index.ts @@ -0,0 +1,2 @@ +export * from './voximplant-sip-user.entity'; +export * from './voximplant-sip.entity'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/entities/voximplant-sip-user.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/entities/voximplant-sip-user.entity.ts new file mode 100644 index 0000000..d4dabfb --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/entities/voximplant-sip-user.entity.ts @@ -0,0 +1,19 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class VoximplantSipUser { + @PrimaryColumn() + sipId: number; + + @PrimaryColumn() + userId: number; + + @Column() + accountId: number; + + constructor(accountId: number, sipId: number, userId: number) { + this.accountId = accountId; + this.sipId = sipId; + this.userId = userId; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/entities/voximplant-sip.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/entities/voximplant-sip.entity.ts new file mode 100644 index 0000000..575d8f6 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/entities/voximplant-sip.entity.ts @@ -0,0 +1,58 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { PbxProviderType } from '../enums'; +import { VoximplantSIPDto } from '../dto'; +import { VoximplantSIPRegistration } from '../types'; +import { VoximplantSipUser } from './voximplant-sip-user.entity'; + +@Entity() +export class VoximplantSip { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + externalId: number; + + @Column() + type: PbxProviderType; + + @Column() + name: string; + + constructor(accountId: number, externalId: number, type: PbxProviderType, name: string) { + this.accountId = accountId; + this.externalId = externalId; + this.type = type; + this.name = name; + } + + private _users: VoximplantSipUser[] | null | undefined; + public get users(): VoximplantSipUser[] | null | undefined { + return this._users; + } + public set users(value: VoximplantSipUser[] | null | undefined) { + this._users = value; + } + + private _registration: VoximplantSIPRegistration | null; + public get registration(): VoximplantSIPRegistration | null { + return this._registration; + } + public set registration(value: VoximplantSIPRegistration | null) { + this._registration = value; + } + + public toDto(): VoximplantSIPDto { + return new VoximplantSIPDto({ + id: this.id, + externalId: this.externalId, + type: this.type, + name: this.name, + userIds: this._users?.map((u) => u.userId), + registration: this.registration, + }); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/enums/index.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/enums/index.ts new file mode 100644 index 0000000..74c9c14 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/enums/index.ts @@ -0,0 +1 @@ +export * from './pbx-provider-type.enum'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/enums/pbx-provider-type.enum.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/enums/pbx-provider-type.enum.ts new file mode 100644 index 0000000..c111896 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/enums/pbx-provider-type.enum.ts @@ -0,0 +1,12 @@ +export enum PbxProviderType { + Uis = 'uis', + Zadarma = 'zadarma', + MangoOffice = 'mango_office', + Beeline = 'beeline', + Mts = 'mts', + Mgts = 'mgts', + Tele2 = 'tele2', + Megafon = 'megafon', + Rostelecom = 'rostelecom', + Unknown = 'unknown', +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/index.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/index.ts new file mode 100644 index 0000000..a0988ba --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/index.ts @@ -0,0 +1,6 @@ +export * from './dto'; +export * from './entities'; +export * from './enums'; +export * from './services'; +export * from './types'; +export * from './voximplant-sip.controller'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/services/index.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/services/index.ts new file mode 100644 index 0000000..0094935 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/services/index.ts @@ -0,0 +1,2 @@ +export * from './voximplant-sip-user.service'; +export * from './voximplant-sip.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/services/voximplant-sip-user.service.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/services/voximplant-sip-user.service.ts new file mode 100644 index 0000000..8981370 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/services/voximplant-sip-user.service.ts @@ -0,0 +1,60 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +import { VoximplantSipUser } from '../entities'; + +interface FindFilter { + sipId?: number; +} + +@Injectable() +export class VoximplantSipUserService { + constructor( + @InjectRepository(VoximplantSipUser) + private readonly repository: Repository, + ) {} + + public async create(accountId: number, sipId: number, userIds: number[]): Promise { + return await this.repository.save(userIds.map((userId) => new VoximplantSipUser(accountId, sipId, userId))); + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return await this.createFindQb(accountId, filter).getOne(); + } + public async findMany(accountId: number, filter?: FindFilter): Promise { + return await this.createFindQb(accountId, filter).getMany(); + } + + public async update( + accountId: number, + sipId: number, + currentUsers: VoximplantSipUser[], + userIds: number[], + ): Promise { + const addUsers = userIds.filter((id) => !currentUsers.some((user) => user.userId === id)); + const removeUsers = currentUsers.filter((user) => !userIds.some((id) => id === user.userId)); + + currentUsers.push(...(await this.create(accountId, sipId, addUsers))); + + if (removeUsers.length) { + await this.repository.remove(removeUsers); + } + + return currentUsers.filter((user) => !removeUsers.some((u) => u.userId === user.userId)); + } + + public async removeUser(accountId: number, userId: number) { + await this.repository.delete({ accountId, userId }); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('visu').where('visu.account_id = :accountId', { accountId }); + + if (filter?.sipId) { + qb.andWhere('visu.sip_id = :sipId', { sipId: filter.sipId }); + } + + return qb; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/services/voximplant-sip.service.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/services/voximplant-sip.service.ts new file mode 100644 index 0000000..18c32b1 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/services/voximplant-sip.service.ts @@ -0,0 +1,192 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { NotFoundError, ObjectUtil } from '@/common'; + +import { VoximplantError } from '../../common'; +import { VoximplantAccountService } from '../../voximplant-account/voximplant-account.service'; + +import { CreateVoximplantSIPDto, UpdateVoximplantSIPDto } from '../dto'; +import { VoximplantSip } from '../entities'; +import { ExpandableField, VoximplantSIPRegistration } from '../types'; +import { VoximplantSipUserService } from './voximplant-sip-user.service'; + +interface FindFilter { + sipId?: number; + externalId?: number; + accessibleUserId?: number; +} + +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class VoximplantSipService { + private readonly logger = new Logger(VoximplantSipService.name); + constructor( + @InjectRepository(VoximplantSip) + private readonly repository: Repository, + private readonly viAccountService: VoximplantAccountService, + private readonly viSipUserService: VoximplantSipUserService, + ) {} + public async create(accountId: number, dto: CreateVoximplantSIPDto): Promise { + const viSIPRegistration = await this.createSIPRegistration(accountId, dto); + + if (viSIPRegistration) { + const viSip = await this.repository.save(new VoximplantSip(accountId, viSIPRegistration, dto.type, dto.name)); + + if (dto.userIds?.length) { + await this.viSipUserService.create(accountId, viSip.id, dto.userIds); + } + + return this.findOne(accountId, { sipId: viSip.id }, { expand: ['users', 'registration'] }); + } + + throw new VoximplantError({ message: 'Create SIP registration error' }); + } + + public async findOne(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const viSip = await this.createFindQb(accountId, filter).getOne(); + + return viSip && options?.expand ? await this.expandOne(viSip, options.expand) : viSip; + } + public async findMany(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const viSips = await this.createFindQb(accountId, filter).orderBy('vis.id', 'ASC').getMany(); + + return viSips && options?.expand ? await this.expandMany(viSips, options.expand) : viSips; + } + + public async update(accountId: number, sipId: number, dto: UpdateVoximplantSIPDto): Promise { + const viSip = await this.findOne(accountId, { sipId }, { expand: ['users'] }); + if (!viSip) { + throw NotFoundError.withId(VoximplantSip, sipId); + } + + viSip.type = dto.type ?? viSip.type; + viSip.name = dto.name ?? viSip.name; + this.repository.save(viSip); + + if (dto.userIds) { + await this.viSipUserService.update(accountId, viSip.id, viSip.users, dto.userIds); + } + + await this.updateSIPRegistration(accountId, viSip.externalId, dto); + + return this.findOne(accountId, { sipId }, { expand: ['users', 'registration'] }); + } + + public async delete(accountId: number, sipId: number): Promise { + const viSip = await this.repository.findOne({ where: { accountId, id: sipId } }); + if (!viSip) { + throw NotFoundError.withId(VoximplantSip, sipId); + } + + if (await this.deleteSIPRegistration(accountId, viSip.externalId)) { + await this.repository.delete({ accountId, id: sipId }); + } + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('vis').where('vis.account_id = :accountId', { accountId }); + + if (filter?.sipId) { + qb.andWhere('vis.id = :id', { id: filter.sipId }); + } + if (filter?.externalId) { + qb.andWhere('vis.external_id = :externalId', { externalId: filter.externalId }); + } + if (filter?.accessibleUserId) { + qb.leftJoin('voximplant_sip_user', 'visu', 'visu.sip_id = vis.id').andWhere( + new Brackets((qb1) => + qb1 + .where('visu.user_id = :accessibleUserId', { accessibleUserId: filter.accessibleUserId }) + .orWhere('visu.user_id is NULL'), + ), + ); + } + + return qb; + } + + private async expandOne(viSip: VoximplantSip, expand: ExpandableField[]): Promise { + if (expand.includes('users')) { + viSip.users = await this.viSipUserService.findMany(viSip.accountId, { sipId: viSip.id }); + } + if (expand.includes('registration')) { + viSip.registration = await this.getSIPRegistration(viSip.accountId, viSip.externalId); + } + return viSip; + } + private async expandMany(viSips: VoximplantSip[], expand: ExpandableField[]): Promise { + return await Promise.all(viSips.map((viSip) => this.expandOne(viSip, expand))); + } + + private async createSIPRegistration(accountId: number, dto: CreateVoximplantSIPDto): Promise { + const { client, appParam } = await this.viAccountService.getClient(accountId); + + const viResponse = await client.SIPRegistration.createSipRegistration( + ObjectUtil.assign(appParam, { ...dto, userIds: undefined }), + ); + + if (!viResponse.result) { + this.logger.error(`Create SIP registration error: ${JSON.stringify(viResponse)}`); + return null; + } + + return viResponse.sipRegistrationId; + } + + private async getSIPRegistration( + accountId: number, + sipRegistrationId: number, + ): Promise { + const { client, appParam } = await this.viAccountService.getClient(accountId); + + const viResponse = await client.SIPRegistration.getSipRegistrations({ + ...appParam, + sipRegistrationId, + ruleId: [], + ruleName: '', + userId: [], + userName: '', + }); + if (!viResponse.result) { + this.logger.error(`Get SIP registration error: ${JSON.stringify(viResponse)}`); + return null; + } + + return viResponse.result[0]; + } + + private async updateSIPRegistration( + accountId: number, + sipRegistrationId: number, + dto: UpdateVoximplantSIPDto, + ): Promise { + const { client, appParam } = await this.viAccountService.getClient(accountId); + + const viResponse = await client.SIPRegistration.updateSipRegistration( + ObjectUtil.assign({ ...appParam, sipRegistrationId }, { ...dto, userIds: undefined }), + ); + if (!viResponse.result) { + this.logger.error(`Create SIP registration error: ${JSON.stringify(viResponse)}`); + return null; + } + + return sipRegistrationId; + } + + private async deleteSIPRegistration(accountId: number, sipRegistrationId: number): Promise { + const { client, appParam } = await this.viAccountService.getClient(accountId); + + const viResponse = await client.SIPRegistration.deleteSipRegistration({ ...appParam, sipRegistrationId }); + if (!viResponse.result) { + this.logger.error(`Create SIP registration error: ${JSON.stringify(viResponse)}`); + return null; + } + + return sipRegistrationId; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/types/expandable-field.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/types/expandable-field.ts new file mode 100644 index 0000000..fc21342 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/types/expandable-field.ts @@ -0,0 +1 @@ +export type ExpandableField = 'users' | 'registration'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/types/index.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/types/index.ts new file mode 100644 index 0000000..7708040 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/types/index.ts @@ -0,0 +1,2 @@ +export * from './expandable-field'; +export * from './voximplant-sip-registration'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/types/voximplant-sip-registration.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/types/voximplant-sip-registration.ts new file mode 100644 index 0000000..524aaae --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/types/voximplant-sip-registration.ts @@ -0,0 +1,82 @@ +export interface VoximplantSIPRegistration { + /** + * The SIP registration ID + */ + sipRegistrationId: number; + /** + * The user name from sip proxy + */ + sipUsername: string; + /** + * The sip proxy + */ + proxy: string; + /** + * The last time updated + */ + lastUpdated: number; + /** + * The SIP authentications user + */ + authUser?: string; + /** + * The outbound proxy + */ + outboundProxy?: string; + /** + * The successful SIP registration + */ + successful?: boolean; + /** + * The status code from a SIP registration + */ + statusCode?: number; + /** + * The error message from a SIP registration + */ + errorMessage?: string; + /** + * The subscription deactivation flag. The SIP registration is frozen if true + */ + deactivated: boolean; + /** + * The next subscription renewal date in format: YYYY-MM-DD + */ + nextSubscriptionRenewal: Date; + /** + * The purchase date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + purchaseDate: Date; + /** + * The subscription monthly charge + */ + subscriptionPrice: string; + /** + * SIP registration is persistent. Set false to activate it only on the user login + */ + isPersistent: boolean; + /** + * The id of the bound user + */ + userId?: number; + /** + * The name of the bound user + */ + userName?: string; + /** + * The id of the bound application + */ + applicationId?: number; + /** + * The name of the bound application + */ + applicationName?: string; + /** + * The id of the bound rule + */ + ruleId?: number; + /** + * The name of the bound rule + */ + ruleName?: string; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-sip/voximplant-sip.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-sip/voximplant-sip.controller.ts new file mode 100644 index 0000000..e72e862 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-sip/voximplant-sip.controller.ts @@ -0,0 +1,93 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { ExpandQuery, TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized, UserAccess } from '@/modules/iam/common'; + +import { CreateVoximplantSIPDto, UpdateVoximplantSIPDto, VoximplantSIPDto, VoximplantSipFilterDto } from './dto'; +import { ExpandableField } from './types'; +import { VoximplantSipService } from './services'; + +@ApiTags('telephony/voximplant/sip') +@Controller('sip') +@JwtAuthorized() +@TransformToDto() +export class VoximplantSipController { + constructor(private readonly service: VoximplantSipService) {} + + @ApiCreatedResponse({ description: 'Create voximplant SIP registration', type: VoximplantSIPDto }) + @Post() + @UserAccess({ adminOnly: true }) + public async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateVoximplantSIPDto) { + return this.service.create(accountId, dto); + } + + @ApiOkResponse({ description: 'Get voximplant SIP registrations', type: [VoximplantSIPDto] }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: users,registration.', + }) + @Get() + public async getMany( + @CurrentAuth() { accountId }: AuthData, + @Query() filter: VoximplantSipFilterDto, + @Query() expand?: ExpandQuery, + ) { + return this.service.findMany(accountId, filter, { expand: expand.fields }); + } + + @ApiOkResponse({ description: 'Get voximplant SIP registration by external Id', type: VoximplantSIPDto }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: users,registration.', + }) + @Get('external/:externalId') + public async getOneByExternalId( + @CurrentAuth() { accountId }: AuthData, + @Param('externalId', ParseIntPipe) externalId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.findOne(accountId, { externalId }, { expand: expand.fields }); + } + + @ApiOkResponse({ description: 'Get voximplant SIP registration', type: VoximplantSIPDto }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields. Values: users,registration.', + }) + @Get(':sipId') + public async getOne( + @CurrentAuth() { accountId }: AuthData, + @Param('sipId', ParseIntPipe) sipId: number, + @Query() expand?: ExpandQuery, + ) { + return this.service.findOne(accountId, { sipId }, { expand: expand.fields }); + } + + @ApiOkResponse({ description: 'Update voximplant SIP registration', type: VoximplantSIPDto }) + @Patch(':sipId') + @UserAccess({ adminOnly: true }) + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('sipId', ParseIntPipe) sipId: number, + @Body() dto: UpdateVoximplantSIPDto, + ) { + return this.service.update(accountId, sipId, dto); + } + + @ApiOkResponse({ description: 'Delete voximplant SIP registration' }) + @Delete(':sipId') + @UserAccess({ adminOnly: true }) + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('sipId', ParseIntPipe) sipId: number) { + return this.service.delete(accountId, sipId); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/dto/create-voximplant-user.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-user/dto/create-voximplant-user.dto.ts new file mode 100644 index 0000000..d811e8c --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/dto/create-voximplant-user.dto.ts @@ -0,0 +1,4 @@ +import { PickType } from '@nestjs/swagger'; +import { VoximplantUserDto } from './voximplant-user.dto'; + +export class CreateVoximplantUserDto extends PickType(VoximplantUserDto, ['isActive'] as const) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/dto/create-voximplant-users-batch.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-user/dto/create-voximplant-users-batch.dto.ts new file mode 100644 index 0000000..90905e2 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/dto/create-voximplant-users-batch.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty, PickType } from '@nestjs/swagger'; +import { IsNumber } from 'class-validator'; +import { VoximplantUserDto } from './voximplant-user.dto'; + +export class CreateVoximplantUsersBatchDto extends PickType(VoximplantUserDto, ['isActive'] as const) { + @ApiProperty({ type: Number, isArray: true }) + @IsNumber({}, { each: true }) + userIds: number[]; +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/dto/index.ts b/backend/src/modules/telephony/voximplant/voximplant-user/dto/index.ts new file mode 100644 index 0000000..4abb6ee --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/dto/index.ts @@ -0,0 +1,6 @@ +export * from './create-voximplant-user.dto'; +export * from './create-voximplant-users-batch.dto'; +export * from './update-voximplant-user.dto'; +export * from './users-queue.dto'; +export * from './voximplant-user-sip-data.dto'; +export * from './voximplant-user.dto'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/dto/update-voximplant-user.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-user/dto/update-voximplant-user.dto.ts new file mode 100644 index 0000000..9030f04 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/dto/update-voximplant-user.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateVoximplantUserDto } from './create-voximplant-user.dto'; + +export class UpdateVoximplantUserDto extends PartialType(CreateVoximplantUserDto) {} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/dto/users-queue.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-user/dto/users-queue.dto.ts new file mode 100644 index 0000000..de350fb --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/dto/users-queue.dto.ts @@ -0,0 +1,31 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class UsersQueueDto { + @ApiPropertyOptional({ nullable: true, type: [String] }) + @IsOptional() + @IsArray() + users?: string[] | null; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + entityName?: string | null; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + entityId?: number | null; + + @ApiPropertyOptional() + @IsOptional() + @IsNumber() + entityTypeId?: number | null; + + constructor({ users, entityName, entityId, entityTypeId }: UsersQueueDto) { + this.users = users; + this.entityName = entityName; + this.entityId = entityId; + this.entityTypeId = entityTypeId; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/dto/voximplant-user-sip-data.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-user/dto/voximplant-user-sip-data.dto.ts new file mode 100644 index 0000000..160c19d --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/dto/voximplant-user-sip-data.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class VoximplantUserSIPDataDto { + @ApiProperty() + @IsString() + userName: string; + + @ApiProperty() + @IsString() + domain: string; + + @ApiProperty() + @IsString() + password: string; + + constructor({ userName, domain, password }: VoximplantUserSIPDataDto) { + this.userName = userName; + this.domain = domain; + this.password = password; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/dto/voximplant-user.dto.ts b/backend/src/modules/telephony/voximplant/voximplant-user/dto/voximplant-user.dto.ts new file mode 100644 index 0000000..43f6045 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/dto/voximplant-user.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNumber, IsString } from 'class-validator'; + +export class VoximplantUserDto { + @ApiProperty() + @IsNumber() + userId: number; + + @ApiProperty() + @IsString() + userName: string; + + @ApiProperty() + @IsBoolean() + isActive: boolean; + + constructor({ userId, userName, isActive }: VoximplantUserDto) { + this.userId = userId; + this.userName = userName; + this.isActive = isActive; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/entities/index.ts b/backend/src/modules/telephony/voximplant/voximplant-user/entities/index.ts new file mode 100644 index 0000000..ede7f0a --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/entities/index.ts @@ -0,0 +1 @@ +export * from './voximplant-user.entity'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/entities/voximplant-user.entity.ts b/backend/src/modules/telephony/voximplant/voximplant-user/entities/voximplant-user.entity.ts new file mode 100644 index 0000000..5ff2317 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/entities/voximplant-user.entity.ts @@ -0,0 +1,52 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +import { UpdateVoximplantUserDto, VoximplantUserDto } from '../dto'; + +@Entity() +export class VoximplantUser { + @PrimaryColumn() + userId: number; + + @Column() + externalId: number; + + @Column() + userName: string; + + @Column() + password: string; + + @Column({ default: true }) + isActive: boolean; + + @Column() + accountId: number; + + constructor( + accountId: number, + userId: number, + externalId: number, + userName: string, + password: string, + isActive = true, + ) { + this.accountId = accountId; + this.userId = userId; + this.externalId = externalId; + this.userName = userName; + this.password = password; + this.isActive = isActive; + } + + public update(dto: UpdateVoximplantUserDto): VoximplantUser { + if (dto.isActive !== undefined) { + this.isActive = dto.isActive; + } + + return this; + } + + public toDto(): VoximplantUserDto { + return new VoximplantUserDto(this); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/index.ts b/backend/src/modules/telephony/voximplant/voximplant-user/index.ts new file mode 100644 index 0000000..0bd944d --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './voximplant-user-public.controller'; +export * from './voximplant-user.controller'; +export * from './voximplant-user.service'; diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user-public.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user-public.controller.ts new file mode 100644 index 0000000..3727c9e --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user-public.controller.ts @@ -0,0 +1,21 @@ +import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; + +import { UsersQueueDto } from './dto'; +import { VoximplantUserService } from './voximplant-user.service'; + +@ApiTags('telephony/voximplant/integration') +@Controller('integration/:applicationId/users') +export class VoximplantUserPublicController { + constructor(private service: VoximplantUserService) {} + + @ApiCreatedResponse({ description: 'Get users queue and contact info', type: UsersQueueDto }) + @Get() + public async getUsersQueue( + @Param('applicationId', ParseIntPipe) applicationId: number, + @Query('phone') phone: string, + @Query('viPhoneNumber') viPhoneNumber?: string | null, + ) { + return this.service.getUsersQueue(applicationId, { phone, viPhoneNumber }); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user.controller.ts b/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user.controller.ts new file mode 100644 index 0000000..51a1fc7 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user.controller.ts @@ -0,0 +1,87 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger'; + +import { TransformToDto } from '@/common'; +import { AuthData, CurrentAuth, JwtAuthorized } from '@/modules/iam/common'; + +import { + VoximplantUserDto, + CreateVoximplantUserDto, + CreateVoximplantUsersBatchDto, + VoximplantUserSIPDataDto, + UpdateVoximplantUserDto, +} from './dto'; +import { VoximplantUserService } from './voximplant-user.service'; + +@ApiTags('telephony/voximplant/users') +@Controller('users') +@JwtAuthorized() +@TransformToDto() +export class VoximplantUserController { + constructor(private readonly service: VoximplantUserService) {} + + @ApiCreatedResponse({ description: 'Create voximplant user', type: VoximplantUserDto }) + @Post(':userId') + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Body() dto: CreateVoximplantUserDto, + ) { + return this.service.create(accountId, userId, dto); + } + + @ApiCreatedResponse({ description: 'Batch create voximplant users', type: VoximplantUserDto, isArray: true }) + @Post('batch/create') + public async createBatch(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateVoximplantUsersBatchDto) { + return this.service.createBatch(accountId, dto); + } + + @ApiOkResponse({ description: 'Get linked voximplant users', type: [VoximplantUserDto] }) + @Get() + public async getMany( + @CurrentAuth() { accountId }: AuthData, + @Query('accessiblePhoneNumber') accessiblePhoneNumber?: string, + ) { + return this.service.findMany(accountId, { accessiblePhoneNumber }); + } + + @ApiOkResponse({ description: 'Get voximplant user', type: VoximplantUserDto }) + @Get(':userId') + public async getOne(@CurrentAuth() { accountId }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return this.service.findOne(accountId, { userId }); + } + + @ApiOkResponse({ description: 'Voximplant user name', type: String }) + @Get('my/username') + public async getUserName(@CurrentAuth() { accountId, userId }: AuthData) { + return this.service.getUserName(accountId, userId); + } + + @ApiOkResponse({ description: 'Voximplant user login token', type: String }) + @Get('my/login-token') + public async getLoginToken(@CurrentAuth() { accountId, userId }: AuthData, @Query('key') key: string) { + return this.service.getLoginToken(accountId, userId, key); + } + + @ApiOkResponse({ description: 'Voximplant user SIP data', type: VoximplantUserSIPDataDto }) + @Get(':userId/sip') + public async getSIPData(@CurrentAuth() { accountId }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return this.service.getSIPData(accountId, userId); + } + + @ApiCreatedResponse({ description: 'Update voximplant user', type: VoximplantUserDto }) + @Patch(':userId') + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('userId', ParseIntPipe) userId: number, + @Body() dto: UpdateVoximplantUserDto, + ) { + return this.service.update(accountId, userId, dto); + } + + @ApiOkResponse({ description: 'Delete voximplant user' }) + @Delete(':userId') + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('userId', ParseIntPipe) userId: number) { + return this.service.delete(accountId, userId); + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user.service.ts b/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user.service.ts new file mode 100644 index 0000000..bc45143 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant-user/voximplant-user.service.ts @@ -0,0 +1,253 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import * as crypto from 'crypto'; +import VoximplantApiClient from '@amwork/voximplant-apiclient-nodejs'; + +import { NotFoundError, PasswordUtil } from '@/common'; + +import { UserService } from '@/modules/iam/user/user.service'; +import { User } from '@/modules/iam/user/entities/user.entity'; +import { FieldType } from '@/modules/entity/entity-field/common/enums/field-type.enum'; +import { EntityService } from '@/CRM/Service/Entity/EntityService'; + +import { VoximplantApplicationParam } from '../common'; +import { VoximplantAccountService } from '../voximplant-account'; +import { VoximplantNumberService } from '../voximplant-number'; + +import { + CreateVoximplantUserDto, + CreateVoximplantUsersBatchDto, + UpdateVoximplantUserDto, + UsersQueueDto, + VoximplantUserSIPDataDto, +} from './dto'; +import { VoximplantUser } from './entities'; + +interface FindFilter { + userId?: number; + externalId?: number; + userName?: string; + isActive?: boolean; + accessiblePhoneNumber?: string; +} + +@Injectable() +export class VoximplantUserService { + private readonly logger = new Logger(VoximplantUserService.name); + constructor( + @InjectRepository(VoximplantUser) + private readonly repository: Repository, + private readonly entityService: EntityService, + private readonly userService: UserService, + private readonly viAccountService: VoximplantAccountService, + private readonly viNumberService: VoximplantNumberService, + ) {} + + public async create(accountId: number, userId: number, dto: CreateVoximplantUserDto): Promise { + const { client, appParam } = await this.viAccountService.getClient(accountId); + const linkedUser = await this.userService.findOne({ accountId, id: userId }); + return this.createViUser(accountId, linkedUser, client, appParam, dto.isActive); + } + + public async createBatch(accountId: number, dto: CreateVoximplantUsersBatchDto): Promise { + const { client, appParam } = await this.viAccountService.getClient(accountId); + const users = await this.userService.findMany({ accountId, id: dto.userIds }); + const viUsers: VoximplantUser[] = []; + for (const user of users) { + const viUser = await this.createViUser(accountId, user, client, appParam, dto.isActive); + if (viUser) { + viUsers.push(viUser); + } + } + return viUsers; + } + + private async createViUser( + accountId: number, + linkedUser: User, + client: VoximplantApiClient, + appParam: VoximplantApplicationParam, + isActive: boolean, + ): Promise { + const viUser = { + ...appParam, + userName: `user-${linkedUser.id}`, + userDisplayName: linkedUser.fullName, + userPassword: PasswordUtil.generateSecure(), + userActive: isActive, + }; + const viResponse = await client.Users.addUser(viUser); + if (!viResponse.result) { + this.logger.error(`Create user error: ${JSON.stringify(viResponse)}`); + } + return viResponse.result + ? await this.repository.save( + new VoximplantUser( + accountId, + linkedUser.id, + viResponse.userId, + viUser.userName, + viUser.userPassword, + isActive, + ), + ) + : null; + } + + public async findMany(accountId: number, filter?: FindFilter): Promise { + const viUsers = await this.createFindQb(accountId, filter).orderBy('viu.user_id', 'ASC').getMany(); + + if (filter?.accessiblePhoneNumber) { + const availableViUsers: VoximplantUser[] = []; + for (const viUser of viUsers) { + if (await this.viNumberService.checkAvailable(accountId, viUser.userId, filter.accessiblePhoneNumber)) { + availableViUsers.push(viUser); + } + } + + return availableViUsers; + } + + return viUsers; + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).orderBy('viu.user_id', 'ASC').getOne(); + } + + public async getOne(accountId: number, userId: number): Promise { + const viUser = await this.findOne(accountId, { userId }); + if (!viUser) { + throw NotFoundError.withId(VoximplantUser, userId); + } + return viUser; + } + + public async update(accountId: number, userId: number, dto: UpdateVoximplantUserDto): Promise { + const viUser = await this.getOne(accountId, userId); + await this.repository.save(viUser.update(dto)); + + const { client, appParam } = await this.viAccountService.getClient(accountId); + const request = { + ...appParam, + userId: viUser.externalId, + userName: viUser.userName, + userActive: viUser.isActive, + }; + await client.Users.setUserInfo(request); + + if (!dto.isActive) { + await this.viNumberService.removeUser(accountId, userId); + } + + return viUser; + } + + public async getUserName(accountId: number, userId: number): Promise { + const viUser = await this.findOne(accountId, { userId: userId }); + if (!viUser) { + return null; + } + const viAccount = await this.viAccountService.getOne(accountId); + return `${viUser.userName}@${viAccount.applicationName}`; + } + + public async getLoginToken(accountId: number, userId: number, key: string): Promise { + const viUser = await this.getOne(accountId, userId); + return this.calculateMD5(viUser.userName, viUser.password, key); + } + + public async getSIPData(accountId: number, userId: number): Promise { + const viUser = await this.findOne(accountId, { userId: userId }); + if (!viUser) { + return null; + } + const viAccount = await this.viAccountService.getOne(accountId); + return new VoximplantUserSIPDataDto({ + userName: viUser.userName, + domain: viAccount.applicationName, + password: viUser.password, + }); + } + + public async delete(accountId: number, userId: number) { + const viUser = await this.getOne(accountId, userId); + + const { client, appParam } = await this.viAccountService.getClient(accountId); + const { result } = await client.Users.delUser({ + ...appParam, + userId: viUser.externalId, + userName: viUser.userName, + }); + + if (result) { + await this.repository.delete({ accountId: accountId, userId }); + } + } + + public async getUsersQueue( + applicationId: number, + { phone, viPhoneNumber }: { phone: string; viPhoneNumber?: string | null }, + ): Promise { + const viAccount = await this.viAccountService.findOneExt({ applicationId }); + if (!viAccount) { + return null; + } + + const entity = phone + ? await this.entityService.findOne(viAccount.accountId, { + fieldValue: { type: FieldType.Phone, value: phone.startsWith('+') ? phone.slice(1) : phone }, + }) + : null; + + const operators = await this.findOperators(viAccount.accountId, { + userId: entity?.responsibleUserId, + viPhoneNumber, + }); + + return new UsersQueueDto({ + users: operators.map((o) => o.userName), + entityName: entity?.name ?? null, + entityId: entity?.id ?? null, + entityTypeId: entity?.entityTypeId ?? null, + }); + } + + private calculateMD5(user: string, password: string, key: string): string { + const userHash = crypto.createHash('md5').update(`${user}:voximplant.com:${password}`).digest('hex'); + return crypto.createHash('md5').update(`${key}|${userHash}`).digest('hex'); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('viu').where('viu.account_id = :accountId', { accountId }); + + if (filter?.userId) { + qb.andWhere('viu.user_id = :userId', { userId: filter.userId }); + } + + if (filter?.externalId) { + qb.andWhere('viu.external_id = :externalId', { externalId: filter.externalId }); + } + + if (filter?.userName) { + qb.andWhere('viu.user_name = :userName', { userName: filter.userName }); + } + + if (filter?.isActive !== undefined) { + qb.andWhere('viu.is_active = :isActive', { isActive: filter.isActive }); + } + + return qb; + } + + private async findOperators( + accountId: number, + { userId, viPhoneNumber }: { userId?: number; viPhoneNumber?: string | null }, + ): Promise { + const queue = await this.findMany(accountId, { isActive: true, accessiblePhoneNumber: viPhoneNumber ?? undefined }); + const responsible = userId ? await this.findOne(accountId, { userId, isActive: true }) : null; + + return responsible ? [responsible, ...queue.filter((o) => o.userId !== responsible.userId)] : queue; + } +} diff --git a/backend/src/modules/telephony/voximplant/voximplant.module.ts b/backend/src/modules/telephony/voximplant/voximplant.module.ts new file mode 100644 index 0000000..51733d5 --- /dev/null +++ b/backend/src/modules/telephony/voximplant/voximplant.module.ts @@ -0,0 +1,92 @@ +import { Module, forwardRef } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '@/modules/iam/iam.module'; +import { EntityInfoModule } from '@/modules/entity/entity-info/entity-info.module'; +import { CrmModule } from '@/CRM/crm.module'; + +import voximplantConfig from './config/voximplant.config'; +import { VoximplantAccount, VoximplantAccountController, VoximplantAccountService } from './voximplant-account'; +import { + VoximplantCall, + VoximplantCallController, + VoximplantCallPublicController, + VoximplantCallService, +} from './voximplant-call'; +import { VoximplantCoreController, VoximplantCoreService } from './voximplant-core'; +import { + VoximplantNumber, + VoximplantNumberController, + VoximplantNumberService, + VoximplantNumberUser, + VoximplantNumberUserService, +} from './voximplant-number'; +import { VoximplantReportingService, VoximplantReportingController } from './voximplant-reporting'; +import { + VoximplantScenarioEntity, + VoximplantScenarioNote, + VoximplantScenarioTask, + VoximplantScenarioService, + VoximplantScenarioController, +} from './voximplant-scenario'; +import { + VoximplantSip, + VoximplantSipController, + VoximplantSipService, + VoximplantSipUser, + VoximplantSipUserService, +} from './voximplant-sip'; +import { + VoximplantUser, + VoximplantUserService, + VoximplantUserController, + VoximplantUserPublicController, +} from './voximplant-user'; + +@Module({ + imports: [ + ConfigModule.forFeature(voximplantConfig), + TypeOrmModule.forFeature([ + VoximplantAccount, + VoximplantUser, + VoximplantNumber, + VoximplantNumberUser, + VoximplantCall, + VoximplantScenarioEntity, + VoximplantScenarioNote, + VoximplantScenarioTask, + VoximplantSip, + VoximplantSipUser, + ]), + IAMModule, + EntityInfoModule, + forwardRef(() => CrmModule), + ], + providers: [ + VoximplantCoreService, + VoximplantAccountService, + VoximplantUserService, + VoximplantNumberService, + VoximplantNumberUserService, + VoximplantCallService, + VoximplantScenarioService, + VoximplantReportingService, + VoximplantSipService, + VoximplantSipUserService, + ], + controllers: [ + VoximplantCoreController, + VoximplantAccountController, + VoximplantUserController, + VoximplantUserPublicController, + VoximplantNumberController, + VoximplantCallController, + VoximplantCallPublicController, + VoximplantScenarioController, + VoximplantSipController, + VoximplantReportingController, + ], + exports: [VoximplantCallService, VoximplantReportingService], +}) +export class VoximplantModule {} diff --git a/backend/src/modules/tutorial/common/dto/index.ts b/backend/src/modules/tutorial/common/dto/index.ts new file mode 100644 index 0000000..f5a2f5a --- /dev/null +++ b/backend/src/modules/tutorial/common/dto/index.ts @@ -0,0 +1 @@ +export * from './tutorial-filter.dto'; diff --git a/backend/src/modules/tutorial/common/dto/tutorial-filter.dto.ts b/backend/src/modules/tutorial/common/dto/tutorial-filter.dto.ts new file mode 100644 index 0000000..655d732 --- /dev/null +++ b/backend/src/modules/tutorial/common/dto/tutorial-filter.dto.ts @@ -0,0 +1,21 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { TutorialProductType } from '../enums'; + +export class TutorialFilterDto { + @ApiPropertyOptional({ description: 'User ID' }) + @IsOptional() + @IsNumber() + userId?: number; + + @ApiPropertyOptional({ enum: TutorialProductType, description: 'Product type' }) + @IsOptional() + @IsEnum(TutorialProductType) + productType?: TutorialProductType; + + @ApiPropertyOptional({ description: 'Related object ID' }) + @IsOptional() + @IsNumber() + objectId?: number; +} diff --git a/backend/src/modules/tutorial/common/enums/index.ts b/backend/src/modules/tutorial/common/enums/index.ts new file mode 100644 index 0000000..c854c28 --- /dev/null +++ b/backend/src/modules/tutorial/common/enums/index.ts @@ -0,0 +1 @@ +export * from './tutorial-product-type.enum'; diff --git a/backend/src/modules/tutorial/common/enums/tutorial-product-type.enum.ts b/backend/src/modules/tutorial/common/enums/tutorial-product-type.enum.ts new file mode 100644 index 0000000..a15ebf6 --- /dev/null +++ b/backend/src/modules/tutorial/common/enums/tutorial-product-type.enum.ts @@ -0,0 +1,10 @@ +export enum TutorialProductType { + BUILDER = 'builder', + TASK = 'task', + ENTITY_TYPE = 'entity_type', + PRODUCTS_SECTION = 'products_section', + SCHEDULER = 'scheduler', + MAIL = 'mail', + MULTI_MESSENGER = 'multi_messenger', + SETTINGS = 'settings', +} diff --git a/backend/src/modules/tutorial/common/index.ts b/backend/src/modules/tutorial/common/index.ts new file mode 100644 index 0000000..7cec66d --- /dev/null +++ b/backend/src/modules/tutorial/common/index.ts @@ -0,0 +1,2 @@ +export * from './dto'; +export * from './enums'; diff --git a/backend/src/modules/tutorial/config/tutorial.config.ts b/backend/src/modules/tutorial/config/tutorial.config.ts new file mode 100644 index 0000000..e76b573 --- /dev/null +++ b/backend/src/modules/tutorial/config/tutorial.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +export interface TutorialConfig { + language: string; +} + +export default registerAs( + 'tutorial', + (): TutorialConfig => ({ + language: process.env.TUTORIAL_LANGUAGE, + }), +); diff --git a/backend/src/modules/tutorial/tutorial-core/index.ts b/backend/src/modules/tutorial/tutorial-core/index.ts new file mode 100644 index 0000000..80be155 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-core/index.ts @@ -0,0 +1,2 @@ +export * from './tutorial-core.controller'; +export * from './tutorial-core.service'; diff --git a/backend/src/modules/tutorial/tutorial-core/tutorial-core.controller.ts b/backend/src/modules/tutorial/tutorial-core/tutorial-core.controller.ts new file mode 100644 index 0000000..4c6df08 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-core/tutorial-core.controller.ts @@ -0,0 +1,33 @@ +import { Controller, Get, Query } from '@nestjs/common'; +import { ApiOkResponse, ApiOperation, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { DateUtil, TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { TutorialFilterDto } from '../common'; +import { TutorialCoreService } from './tutorial-core.service'; + +@ApiTags('tutorial') +@Controller('') +@JwtAuthorized() +@TransformToDto() +export class TutorialCoreController { + constructor(private readonly service: TutorialCoreService) {} + + @ApiOperation({ + summary: 'Get tutorial items count', + description: 'Get tutorial items count with filter and from date.', + }) + @ApiQuery({ name: 'from', required: false, type: Date, description: 'From date in ISO format' }) + @ApiOkResponse({ description: 'Tutorial items count', type: Number }) + @Get('count') + public async count( + @CurrentAuth() { accountId }: AuthData, + @Query() filter: TutorialFilterDto, + @Query('from') from?: string, + ) { + return this.service.count(accountId, { ...filter, createdFrom: from ? DateUtil.fromISOString(from) : undefined }); + } +} diff --git a/backend/src/modules/tutorial/tutorial-core/tutorial-core.service.ts b/backend/src/modules/tutorial/tutorial-core/tutorial-core.service.ts new file mode 100644 index 0000000..e2d3af0 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-core/tutorial-core.service.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { AccountCreatedEvent, IamEventType } from '@/modules/iam/common'; + +import { TutorialConfig } from '../config/tutorial.config'; +import { TutorialProductType } from '../common'; +import { TutorialGroupService } from '../tutorial-group'; +import { TutorialItemService } from '../tutorial-item'; + +import { DefaultTutorial } from './tutorial-defaults'; + +interface FindFilter { + userId?: number; + productType?: TutorialProductType; + objectId?: number; + createdFrom?: Date; +} + +@Injectable() +export class TutorialCoreService { + private _language: string | undefined; + + constructor( + private readonly configService: ConfigService, + private readonly groupService: TutorialGroupService, + private readonly itemService: TutorialItemService, + ) { + this._language = this.configService.get('tutorial')?.language; + } + + @OnEvent(IamEventType.AccountCreated, { async: true }) + public async handleRegistrationEvent(event: AccountCreatedEvent) { + await this.createDefault(event.accountId); + } + + public async count(accountId: number, filter?: FindFilter): Promise { + return this.itemService.count(accountId, filter); + } + + private async createDefault(accountId: number): Promise { + if (this._language) { + const groups = DefaultTutorial[this._language]; + if (groups) { + for (const group of groups) { + const createdGroup = await this.groupService.create(accountId, group); + for (const item of group.items) { + await this.itemService.create(accountId, createdGroup.id, item); + } + } + } + } + } +} diff --git a/backend/src/modules/tutorial/tutorial-core/tutorial-defaults.ts b/backend/src/modules/tutorial/tutorial-core/tutorial-defaults.ts new file mode 100644 index 0000000..943359e --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-core/tutorial-defaults.ts @@ -0,0 +1,157 @@ +interface DefaultItem { + name: string; + link: string; + sortOrder: number; +} +interface DefaultGroup { + name: string; + sortOrder: number; + items: DefaultItem[]; +} + +export const DefaultTutorial: Record = { + ru: [ + { + name: 'Видеоуроки по модулям и функциям', + sortOrder: 0, + items: [ + { + name: 'Обзор функционала воронки продаж', + link: 'https://rutube.ru/video/7866956dff4e26e1c898c1a31450b3ff', + sortOrder: 0, + }, + { + name: 'Как работать в сделке', + link: 'https://rutube.ru/video/1fc6c8cf6306c32f7775dc91b557fe6b', + sortOrder: 1, + }, + { + name: 'Как добавить товар/услугу в сделку', + link: 'https://rutube.ru/video/55cfc3a2a8fe8d891cab17135d6d758a', + sortOrder: 2, + }, + { + name: 'Как работать в разделе задачах', + link: 'https://rutube.ru/video/76111a083b5579cb8c7e3e3fb6b225f8', + sortOrder: 3, + }, + { + name: 'Как пользоваться Дашбордом', + link: 'https://rutube.ru/video/d0b6ff03e0005f31236d8fa1d31a942d', + sortOrder: 4, + }, + { + name: 'Как пользоваться отчетами', + link: 'https://rutube.ru/video/c5a7cfbeda45bd110f0cba20bcc399ac', + sortOrder: 5, + }, + { + name: 'Как работать с товарами и услугами', + link: 'https://rutube.ru/video/286a3a8bd132332648bb7a37e313943c', + sortOrder: 6, + }, + { + name: 'Как работать с проектами и задачами', + link: 'https://rutube.ru/video/2384f907194a481a9d88486d85772496', + sortOrder: 7, + }, + { + name: 'Как настроить уведомления', + link: 'https://rutube.ru/video/861b2a529cf2fb93109df88e55458be2', + sortOrder: 8, + }, + { + name: 'Как настроить свой профиль', + link: 'https://rutube.ru/video/e2b52a8139e7da53fd280c1e37694e04', + sortOrder: 9, + }, + ], + }, + { + name: 'Видеоуроки по настройке и кастомизации', + sortOrder: 1, + items: [ + { + name: 'Как настроить план продаж в CRM', + link: 'https://rutube.ru/video/39a6b69a7042b6318a22eeedc08cdf51', + sortOrder: 0, + }, + { + name: 'Как настроить воронку продаж', + link: 'https://rutube.ru/video/d6c71e34f6ff41463616f3884d1e0b7a', + sortOrder: 1, + }, + { + name: 'Как настроить базу знаний', + link: 'https://rutube.ru/video/58c01491d9b6391eb5e59f19d58e47c5', + sortOrder: 2, + }, + { + name: 'Как добавить и настроить права пользователю', + link: 'https://rutube.ru/video/d0a83e46fa070e0c763d916ec4d7f9fd', + sortOrder: 3, + }, + { + name: 'Продвинутые настройки полей в карточке', + link: 'https://rutube.ru/video/203a6cea52d5935c4503f2632373b87b', + sortOrder: 4, + }, + { + name: `Как настроить раздел "Товары, остатки и услуги"`, + link: 'https://rutube.ru/video/dedae77a65ded0aa531f8c5c90075f45', + sortOrder: 5, + }, + ], + }, + ], + en: [ + { + name: 'Video Tutorials on Modules and Functionality', + sortOrder: 0, + items: [ + { name: 'Project and Task Management in Amwork', link: 'https://youtu.be/23QxzqJEvKo', sortOrder: 0 }, + { name: 'Task Management and Board Types in Amwork', link: 'https://youtu.be/vCDBNbvmDnQ', sortOrder: 1 }, + { name: 'Sales Pipeline and List View in Amwork', link: 'https://youtu.be/Ptc2hGY5swo', sortOrder: 2 }, + { + name: 'Reviewing the CRM Section and Sales Pipeline in Amwork', + link: 'https://youtu.be/kN-yTmBc-7c', + sortOrder: 3, + }, + { name: 'Reviewing Deal Cards in Amwork', link: 'https://youtu.be/24HgmAyuTjk', sortOrder: 4 }, + { + name: 'Dashboard and Sales Plan Configuration in Amwork', + link: 'https://youtu.be/2S7PhICbzzU', + sortOrder: 5, + }, + { + name: 'Harnessing the Power of Reports in CRM Section in Amwork', + link: 'https://youtu.be/i6puY7ZZ9Ms', + sortOrder: 6, + }, + { + name: 'Overview of the "Warehouse & Product Management" Section in Amwork', + link: 'https://youtu.be/-OiDVZ3nFTI', + sortOrder: 7, + }, + { name: 'Creating Orders in Deal Cards in Amwork', link: 'https://youtu.be/86mGKesNytM', sortOrder: 8 }, + ], + }, + { + name: 'Video Tutorials on Setup and Customization', + sortOrder: 1, + items: [ + { name: 'Configuring a Sales Pipeline in Amwork', link: 'https://youtu.be/JXHUx7zSA-A', sortOrder: 0 }, + { + name: 'Customizing Fields for Deal and Project Cards in Amwork', + link: 'https://youtu.be/wb3PuZfMFP0', + sortOrder: 1, + }, + { + name: 'Setting Up Automations in Boards and Sales Pipelines in Amwork', + link: 'https://youtu.be/DDJlekyDJBY', + sortOrder: 2, + }, + ], + }, + ], +}; diff --git a/backend/src/modules/tutorial/tutorial-group/dto/create-tutorial-group.dto.ts b/backend/src/modules/tutorial/tutorial-group/dto/create-tutorial-group.dto.ts new file mode 100644 index 0000000..7a81639 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/dto/create-tutorial-group.dto.ts @@ -0,0 +1,5 @@ +import { PickType } from '@nestjs/swagger'; + +import { TutorialGroupDto } from './tutorial-group.dto'; + +export class CreateTutorialGroupDto extends PickType(TutorialGroupDto, ['name', 'sortOrder'] as const) {} diff --git a/backend/src/modules/tutorial/tutorial-group/dto/index.ts b/backend/src/modules/tutorial/tutorial-group/dto/index.ts new file mode 100644 index 0000000..8b4b734 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-tutorial-group.dto'; +export * from './tutorial-group.dto'; +export * from './update-tutorial-group.dto'; diff --git a/backend/src/modules/tutorial/tutorial-group/dto/tutorial-group.dto.ts b/backend/src/modules/tutorial/tutorial-group/dto/tutorial-group.dto.ts new file mode 100644 index 0000000..da6a5af --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/dto/tutorial-group.dto.ts @@ -0,0 +1,35 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { TutorialItemDto } from '../../tutorial-item'; + +export class TutorialGroupDto { + @ApiProperty({ description: 'Tutorial group ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Tutorial group name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Tutorial group sort order' }) + @IsNumber() + sortOrder: number; + + @ApiProperty({ description: 'Tutorial group creation date in ISO format' }) + @IsString() + createdAt: string; + + @ApiProperty({ type: [TutorialItemDto], nullable: true, description: 'Tutorial group items' }) + @IsOptional() + @IsArray() + items: TutorialItemDto[] | null; + + constructor({ id, name, sortOrder, createdAt, items }: TutorialGroupDto) { + this.id = id; + this.name = name; + this.sortOrder = sortOrder; + this.createdAt = createdAt; + this.items = items; + } +} diff --git a/backend/src/modules/tutorial/tutorial-group/dto/update-tutorial-group.dto.ts b/backend/src/modules/tutorial/tutorial-group/dto/update-tutorial-group.dto.ts new file mode 100644 index 0000000..dde1edc --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/dto/update-tutorial-group.dto.ts @@ -0,0 +1,5 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { TutorialGroupDto } from './tutorial-group.dto'; + +export class UpdateTutorialGroupDto extends PartialType(PickType(TutorialGroupDto, ['name', 'sortOrder'] as const)) {} diff --git a/backend/src/modules/tutorial/tutorial-group/entities/index.ts b/backend/src/modules/tutorial/tutorial-group/entities/index.ts new file mode 100644 index 0000000..15b8248 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/entities/index.ts @@ -0,0 +1 @@ +export * from './tutorial-group.entity'; diff --git a/backend/src/modules/tutorial/tutorial-group/entities/tutorial-group.entity.ts b/backend/src/modules/tutorial/tutorial-group/entities/tutorial-group.entity.ts new file mode 100644 index 0000000..746f94d --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/entities/tutorial-group.entity.ts @@ -0,0 +1,60 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { TutorialItem } from '../../tutorial-item'; +import { CreateTutorialGroupDto, UpdateTutorialGroupDto, TutorialGroupDto } from '../dto'; + +@Entity() +export class TutorialGroup { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + name: string; + + @Column() + sortOrder: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, name: string, sortOrder: number, createdAt?: Date) { + this.accountId = accountId; + this.name = name; + this.sortOrder = sortOrder; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _items: TutorialItem[] | null = null; + public get items(): TutorialItem[] | null { + return this._items; + } + public set items(value: TutorialItem[] | null) { + this._items = value; + } + + public static fromDto(accountId: number, dto: CreateTutorialGroupDto): TutorialGroup { + return new TutorialGroup(accountId, dto.name, dto.sortOrder); + } + + public update(dto: UpdateTutorialGroupDto): TutorialGroup { + this.name = dto.name ?? this.name; + this.sortOrder = dto.sortOrder ?? this.sortOrder; + + return this; + } + + public toDto(): TutorialGroupDto { + return new TutorialGroupDto({ + id: this.id, + name: this.name, + sortOrder: this.sortOrder, + createdAt: this.createdAt.toISOString(), + items: this.items?.map((item) => item.toDto()) ?? null, + }); + } +} diff --git a/backend/src/modules/tutorial/tutorial-group/index.ts b/backend/src/modules/tutorial/tutorial-group/index.ts new file mode 100644 index 0000000..79ba756 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/index.ts @@ -0,0 +1,4 @@ +export * from './dto'; +export * from './entities'; +export * from './tutorial-group.controller'; +export * from './tutorial-group.service'; diff --git a/backend/src/modules/tutorial/tutorial-group/tutorial-group.controller.ts b/backend/src/modules/tutorial/tutorial-group/tutorial-group.controller.ts new file mode 100644 index 0000000..9ffcbda --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/tutorial-group.controller.ts @@ -0,0 +1,101 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; + +import { ExpandQuery, SortOrderListDto, TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { UserAccess } from '@/modules/iam/common/decorators/user-access.decorator'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { TutorialFilterDto } from '../common'; +import { ExpandableField } from './types'; +import { TutorialGroupDto, CreateTutorialGroupDto, UpdateTutorialGroupDto } from './dto'; +import { TutorialGroupService } from './tutorial-group.service'; + +@ApiTags('tutorial/groups') +@Controller('groups') +@JwtAuthorized() +@TransformToDto() +export class TutorialGroupController { + constructor(private readonly service: TutorialGroupService) {} + + @ApiOperation({ summary: 'Create tutorial group', description: 'Create tutorial group' }) + @ApiBody({ type: CreateTutorialGroupDto, required: true, description: 'Data for creating tutorial group' }) + @ApiCreatedResponse({ description: 'Created tutorial group', type: TutorialGroupDto }) + @Post() + @UserAccess({ adminOnly: true }) + public async create(@CurrentAuth() { accountId }: AuthData, @Body() dto: CreateTutorialGroupDto) { + return this.service.create(accountId, dto); + } + + @ApiOperation({ summary: 'Get tutorial groups', description: 'Get tutorial groups' }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields', + enum: ExpandableField, + }) + @ApiOkResponse({ description: 'Tutorial groups', type: [TutorialGroupDto] }) + @Get() + public async findMany( + @CurrentAuth() { accountId }: AuthData, + @Query() filter: TutorialFilterDto, + @Query() expand: ExpandQuery, + ) { + return this.service.findMany(accountId, filter, { expand: expand.fields }); + } + + @ApiOperation({ summary: 'Get tutorial group', description: 'Get tutorial group' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id', example: 1 }) + @ApiQuery({ + name: 'expand', + type: String, + required: false, + isArray: true, + description: 'Expand fields', + enum: ExpandableField, + }) + @ApiOkResponse({ description: 'Tutorial group', type: TutorialGroupDto }) + @Get(':groupId') + public async findOne( + @CurrentAuth() { accountId }: AuthData, + @Param('groupId', ParseIntPipe) groupId: number, + @Query() expand: ExpandQuery, + ) { + return this.service.findOne(accountId, { groupId }, { expand: expand.fields }); + } + + @ApiOperation({ summary: 'Sort tutorial groups', description: 'Sort tutorial groups' }) + @ApiBody({ type: SortOrderListDto, required: true, description: 'Data for sorting tutorial groups' }) + @ApiOkResponse() + @Patch('sort') + @UserAccess({ adminOnly: true }) + public async sort(@CurrentAuth() { accountId }: AuthData, @Body() dto: SortOrderListDto) { + return this.service.sort(accountId, dto); + } + + @ApiOperation({ summary: 'Update tutorial group', description: 'Update tutorial group' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id', example: 1 }) + @ApiBody({ type: UpdateTutorialGroupDto, required: true, description: 'Data for updating tutorial group' }) + @ApiOkResponse({ description: 'Updated tutorial group', type: TutorialGroupDto }) + @Patch(':groupId') + @UserAccess({ adminOnly: true }) + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('groupId', ParseIntPipe) groupId: number, + @Body() dto: UpdateTutorialGroupDto, + ) { + return this.service.update(accountId, groupId, dto); + } + + @ApiOperation({ summary: 'Delete tutorial group', description: 'Delete tutorial group' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id', example: 1 }) + @ApiOkResponse() + @Delete(':groupId') + @UserAccess({ adminOnly: true }) + public async delete(@CurrentAuth() { accountId }: AuthData, @Param('groupId', ParseIntPipe) groupId: number) { + return this.service.delete(accountId, groupId); + } +} diff --git a/backend/src/modules/tutorial/tutorial-group/tutorial-group.service.ts b/backend/src/modules/tutorial/tutorial-group/tutorial-group.service.ts new file mode 100644 index 0000000..032088b --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/tutorial-group.service.ts @@ -0,0 +1,119 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { NotFoundError, SortOrderListDto } from '@/common'; + +import { TutorialProductType } from '../common'; +import { TutorialItemService } from '../tutorial-item'; +import { ExpandableField } from './types'; +import { CreateTutorialGroupDto, UpdateTutorialGroupDto } from './dto'; +import { TutorialGroup } from './entities'; + +interface FindFilter { + groupId?: number; + userId?: number; + productType?: TutorialProductType; + objectId?: number; +} +interface FindOptions { + expand?: ExpandableField[]; +} + +@Injectable() +export class TutorialGroupService { + constructor( + @InjectRepository(TutorialGroup) + private readonly repository: Repository, + private readonly itemService: TutorialItemService, + ) {} + + public async create(accountId: number, dto: CreateTutorialGroupDto): Promise { + return this.repository.save(TutorialGroup.fromDto(accountId, dto)); + } + + public async findOne(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const group = await this.createFindQb(accountId, filter).getOne(); + + return group && options?.expand ? await this.expandOne(group, options.expand, filter) : group; + } + + public async findMany(accountId: number, filter?: FindFilter, options?: FindOptions): Promise { + const groups = await this.createFindQb(accountId, filter).orderBy('tg.sort_order').getMany(); + + return groups && options?.expand ? await this.expandMany(groups, options.expand, filter) : groups; + } + + public async update(accountId: number, groupId: number, dto: UpdateTutorialGroupDto): Promise { + const group = await this.findOne(accountId, { groupId }); + if (!group) { + throw NotFoundError.withId(TutorialGroup, groupId); + } + + return this.repository.save(group.update(dto)); + } + + public async sort(accountId: number, dto: SortOrderListDto) { + for (const item of dto.items) { + await this.repository.update({ id: item.id, accountId }, { sortOrder: item.sortOrder }); + } + } + + public async delete(accountId: number, groupId: number) { + await this.repository.delete({ id: groupId, accountId }); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.repository.createQueryBuilder('tg').where('tg.account_id = :accountId', { accountId }); + if (filter?.groupId) { + qb.andWhere('tg.id = :id', { id: filter.groupId }); + } + if (filter?.userId || filter?.productType || filter?.objectId) { + qb.leftJoin('tutorial_item', 'ti', 'tg.id = ti.group_id'); + if (filter?.userId) { + qb.leftJoin('tutorial_item_user', 'tiu', 'ti.id = tiu.item_id'); + qb.andWhere( + new Brackets((qb1) => + qb1.where('tiu.user_id = :userId', { userId: filter.userId }).orWhere('tiu.user_id IS NULL'), + ), + ); + } + if (filter?.productType || filter?.objectId) { + qb.leftJoin('tutorial_item_product', 'tip', 'ti.id = tip.item_id'); + if (filter?.productType) { + qb.andWhere( + new Brackets((qb2) => + qb2.where('tip.type = :type', { type: filter.productType }).orWhere('tip.type IS NULL'), + ), + ); + } + if (filter?.objectId) { + qb.andWhere( + new Brackets((qb3) => + qb3.where('tip.object_id = :objectId', { objectId: filter.objectId }).orWhere('tip.object_id IS NULL'), + ), + ); + } + } + } + return qb; + } + + private async expandOne( + group: TutorialGroup, + expand: ExpandableField[], + filter?: FindFilter, + ): Promise { + if (expand.includes(ExpandableField.items)) { + group.items = await this.itemService.findMany(group.accountId, { ...filter, groupId: group.id }); + } + return group; + } + private async expandMany( + groups: TutorialGroup[], + expand: ExpandableField[], + filter?: FindFilter, + ): Promise { + return await Promise.all(groups.map((group) => this.expandOne(group, expand, filter))); + } +} diff --git a/backend/src/modules/tutorial/tutorial-group/types/expandable-field.ts b/backend/src/modules/tutorial/tutorial-group/types/expandable-field.ts new file mode 100644 index 0000000..7a36b44 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/types/expandable-field.ts @@ -0,0 +1,3 @@ +export enum ExpandableField { + items = 'items', +} diff --git a/backend/src/modules/tutorial/tutorial-group/types/index.ts b/backend/src/modules/tutorial/tutorial-group/types/index.ts new file mode 100644 index 0000000..36e5d96 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-group/types/index.ts @@ -0,0 +1 @@ +export * from './expandable-field'; diff --git a/backend/src/modules/tutorial/tutorial-item/dto/create-tutorial-item.dto.ts b/backend/src/modules/tutorial/tutorial-item/dto/create-tutorial-item.dto.ts new file mode 100644 index 0000000..634f3d2 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/dto/create-tutorial-item.dto.ts @@ -0,0 +1,11 @@ +import { PickType } from '@nestjs/swagger'; + +import { TutorialItemDto } from './tutorial-item.dto'; + +export class CreateTutorialItemDto extends PickType(TutorialItemDto, [ + 'name', + 'link', + 'sortOrder', + 'userIds', + 'products', +] as const) {} diff --git a/backend/src/modules/tutorial/tutorial-item/dto/index.ts b/backend/src/modules/tutorial/tutorial-item/dto/index.ts new file mode 100644 index 0000000..594de2a --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/dto/index.ts @@ -0,0 +1,4 @@ +export * from './create-tutorial-item.dto'; +export * from './tutorial-item-product.dto'; +export * from './tutorial-item.dto'; +export * from './update-tutorial-item.dto'; diff --git a/backend/src/modules/tutorial/tutorial-item/dto/tutorial-item-product.dto.ts b/backend/src/modules/tutorial/tutorial-item/dto/tutorial-item-product.dto.ts new file mode 100644 index 0000000..656f088 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/dto/tutorial-item-product.dto.ts @@ -0,0 +1,20 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsNumber, IsOptional } from 'class-validator'; + +import { TutorialProductType } from '../../common'; + +export class TutorialItemProductDto { + @ApiProperty({ enum: TutorialProductType, description: 'Related product type' }) + @IsEnum(TutorialProductType) + type: TutorialProductType; + + @ApiPropertyOptional({ nullable: true, description: 'Related Object ID' }) + @IsOptional() + @IsNumber() + objectId?: number | null | undefined; + + constructor({ type, objectId }: TutorialItemProductDto) { + this.type = type; + this.objectId = objectId; + } +} diff --git a/backend/src/modules/tutorial/tutorial-item/dto/tutorial-item.dto.ts b/backend/src/modules/tutorial/tutorial-item/dto/tutorial-item.dto.ts new file mode 100644 index 0000000..ec7a8fc --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/dto/tutorial-item.dto.ts @@ -0,0 +1,55 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; + +import { TutorialItemProductDto } from './tutorial-item-product.dto'; + +export class TutorialItemDto { + @ApiProperty({ description: 'Tutorial item ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Tutorial group ID' }) + @IsNumber() + groupId: number; + + @ApiProperty({ description: 'Tutorial item name' }) + @IsString() + name: string; + + @ApiProperty({ description: 'Tutorial item link' }) + @IsString() + link: string; + + @ApiProperty({ description: 'Tutorial item sort order' }) + @IsNumber() + sortOrder: number; + + @ApiProperty({ description: 'Tutorial item creation date in ISO format' }) + @IsString() + createdAt: string; + + @ApiPropertyOptional({ type: [Number], nullable: true, description: 'User IDs associated with the tutorial item' }) + @IsOptional() + @IsNumber({}, { each: true }) + userIds?: number[] | null; + + @ApiPropertyOptional({ + type: [TutorialItemProductDto], + nullable: true, + description: 'Products associated with the tutorial item', + }) + @IsOptional() + @IsArray() + products?: TutorialItemProductDto[] | null; + + constructor({ id, groupId, name, link, sortOrder, createdAt, userIds, products }: TutorialItemDto) { + this.id = id; + this.groupId = groupId; + this.name = name; + this.link = link; + this.sortOrder = sortOrder; + this.createdAt = createdAt; + this.userIds = userIds; + this.products = products; + } +} diff --git a/backend/src/modules/tutorial/tutorial-item/dto/update-tutorial-item.dto.ts b/backend/src/modules/tutorial/tutorial-item/dto/update-tutorial-item.dto.ts new file mode 100644 index 0000000..9bd447c --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/dto/update-tutorial-item.dto.ts @@ -0,0 +1,7 @@ +import { PartialType, PickType } from '@nestjs/swagger'; + +import { TutorialItemDto } from './tutorial-item.dto'; + +export class UpdateTutorialItemDto extends PartialType( + PickType(TutorialItemDto, ['name', 'link', 'sortOrder', 'userIds', 'products'] as const), +) {} diff --git a/backend/src/modules/tutorial/tutorial-item/entities/index.ts b/backend/src/modules/tutorial/tutorial-item/entities/index.ts new file mode 100644 index 0000000..735860e --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/entities/index.ts @@ -0,0 +1,3 @@ +export * from './tutorial-item-product.entity'; +export * from './tutorial-item-user.entity'; +export * from './tutorial-item.entity'; diff --git a/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item-product.entity.ts b/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item-product.entity.ts new file mode 100644 index 0000000..db46c89 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item-product.entity.ts @@ -0,0 +1,37 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { TutorialProductType } from '../../common'; +import { TutorialItemProductDto } from '../dto'; + +@Entity() +export class TutorialItemProduct { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + itemId: number; + + @Column() + type: TutorialProductType; + + @Column({ nullable: true }) + objectId: number | null; + + constructor(accountId: number, itemId: number, type: TutorialProductType, objectId: number | null) { + this.accountId = accountId; + this.itemId = itemId; + this.type = type; + this.objectId = objectId; + } + + public static fromDto(accountId: number, itemId: number, dto: TutorialItemProductDto): TutorialItemProduct { + return new TutorialItemProduct(accountId, itemId, dto.type, dto.objectId ?? null); + } + + public toDto(): TutorialItemProductDto { + return new TutorialItemProductDto({ type: this.type, objectId: this.objectId }); + } +} diff --git a/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item-user.entity.ts b/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item-user.entity.ts new file mode 100644 index 0000000..ac94145 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item-user.entity.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryColumn } from 'typeorm'; + +@Entity() +export class TutorialItemUser { + @PrimaryColumn() + itemId: number; + + @PrimaryColumn() + userId: number; + + constructor(itemId: number, userId: number) { + this.itemId = itemId; + this.userId = userId; + } +} diff --git a/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item.entity.ts b/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item.entity.ts new file mode 100644 index 0000000..1e951a5 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/entities/tutorial-item.entity.ts @@ -0,0 +1,81 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +import { DateUtil } from '@/common'; + +import { CreateTutorialItemDto, UpdateTutorialItemDto, TutorialItemDto } from '../dto'; +import { TutorialItemUser } from './tutorial-item-user.entity'; +import { TutorialItemProduct } from './tutorial-item-product.entity'; + +@Entity() +export class TutorialItem { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column() + accountId: number; + + @Column() + groupId: number; + + @Column() + name: string; + + @Column() + link: string; + + @Column() + sortOrder: number; + + @Column() + createdAt: Date; + + constructor(accountId: number, groupId: number, name: string, link: string, sortOrder: number, createdAt?: Date) { + this.accountId = accountId; + this.groupId = groupId; + this.name = name; + this.link = link; + this.sortOrder = sortOrder; + this.createdAt = createdAt ?? DateUtil.now(); + } + + private _users: TutorialItemUser[] | null = null; + public get users(): TutorialItemUser[] | null { + return this._users; + } + public set users(value: TutorialItemUser[] | null) { + this._users = value; + } + + private _products: TutorialItemProduct[] | null = null; + public get products(): TutorialItemProduct[] | null { + return this._products; + } + public set products(value: TutorialItemProduct[] | null) { + this._products = value; + } + + public static fromDto(accountId: number, groupId: number, dto: CreateTutorialItemDto): TutorialItem { + return new TutorialItem(accountId, groupId, dto.name, dto.link, dto.sortOrder); + } + + public update(dto: UpdateTutorialItemDto): TutorialItem { + this.name = dto.name ?? this.name; + this.link = dto.link ?? this.link; + this.sortOrder = dto.sortOrder ?? this.sortOrder; + + return this; + } + + public toDto(): TutorialItemDto { + return new TutorialItemDto({ + id: this.id, + groupId: this.groupId, + name: this.name, + link: this.link, + sortOrder: this.sortOrder, + createdAt: this.createdAt.toISOString(), + userIds: this.users?.length ? this.users.map((user) => user.userId) : null, + products: this.products?.length ? this.products.map((product) => product.toDto()) : null, + }); + } +} diff --git a/backend/src/modules/tutorial/tutorial-item/index.ts b/backend/src/modules/tutorial/tutorial-item/index.ts new file mode 100644 index 0000000..6ca8652 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/index.ts @@ -0,0 +1,5 @@ +export * from './dto'; +export * from './entities'; +export * from './tutorial-item.controller'; +export * from './tutorial-item.handler'; +export * from './tutorial-item.service'; diff --git a/backend/src/modules/tutorial/tutorial-item/tutorial-item.controller.ts b/backend/src/modules/tutorial/tutorial-item/tutorial-item.controller.ts new file mode 100644 index 0000000..a57c859 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/tutorial-item.controller.ts @@ -0,0 +1,94 @@ +import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from '@nestjs/common'; +import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; + +import { SortOrderListDto, TransformToDto } from '@/common'; +import { JwtAuthorized } from '@/modules/iam/common/decorators/jwt-authorized.decorator'; +import { CurrentAuth } from '@/modules/iam/common/decorators/current-auth.decorator'; +import { UserAccess } from '@/modules/iam/common/decorators/user-access.decorator'; +import { AuthData } from '@/modules/iam/common/types/auth-data'; + +import { TutorialItemDto, CreateTutorialItemDto, UpdateTutorialItemDto } from './dto'; +import { TutorialItemService } from './tutorial-item.service'; + +@ApiTags('tutorial/items') +@Controller('groups/:groupId/items') +@JwtAuthorized() +@TransformToDto() +export class TutorialItemController { + constructor(private readonly service: TutorialItemService) {} + + @ApiOperation({ summary: 'Create tutorial item', description: 'Create tutorial item' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id' }) + @ApiBody({ type: CreateTutorialItemDto, required: true, description: 'Data for creating tutorial item' }) + @ApiCreatedResponse({ description: 'Created tutorial item', type: TutorialItemDto }) + @Post() + @UserAccess({ adminOnly: true }) + public async create( + @CurrentAuth() { accountId }: AuthData, + @Param('groupId', ParseIntPipe) groupId: number, + @Body() dto: CreateTutorialItemDto, + ) { + return this.service.create(accountId, groupId, dto); + } + + @ApiOperation({ summary: 'Get tutorial items', description: 'Get tutorial items' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id' }) + @ApiOkResponse({ description: 'Tutorial items', type: [TutorialItemDto] }) + @Get() + public async findMany(@CurrentAuth() { accountId }: AuthData, @Param('groupId', ParseIntPipe) groupId: number) { + return this.service.findMany(accountId, { groupId }); + } + + @ApiOperation({ summary: 'Get tutorial item', description: 'Get tutorial item' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id' }) + @ApiParam({ name: 'itemId', type: Number, required: true, description: 'Tutorial item id' }) + @ApiOkResponse({ description: 'Tutorial item', type: TutorialItemDto }) + @Get(':itemId') + public async findOne( + @CurrentAuth() { accountId }: AuthData, + @Param('groupId', ParseIntPipe) groupId: number, + @Param('itemId', ParseIntPipe) itemId: number, + ) { + return this.service.findOne(accountId, { groupId, itemId }); + } + + @ApiOperation({ summary: 'Sort tutorial items', description: 'Sort tutorial items' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id' }) + @ApiBody({ type: SortOrderListDto, required: true, description: 'Data for sorting tutorial items' }) + @ApiOkResponse() + @Patch('sort') + @UserAccess({ adminOnly: true }) + public async sort(@CurrentAuth() { accountId }: AuthData, @Body() dto: SortOrderListDto) { + return this.service.sort(accountId, dto); + } + + @ApiOperation({ summary: 'Update tutorial item', description: 'Update tutorial item' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id' }) + @ApiParam({ name: 'itemId', type: Number, required: true, description: 'Tutorial item id' }) + @ApiBody({ type: UpdateTutorialItemDto, required: true, description: 'Data for updating tutorial item' }) + @ApiOkResponse({ description: 'Updated tutorial item', type: TutorialItemDto }) + @Patch(':itemId') + @UserAccess({ adminOnly: true }) + public async update( + @CurrentAuth() { accountId }: AuthData, + @Param('groupId', ParseIntPipe) groupId: number, + @Param('itemId', ParseIntPipe) itemId: number, + @Body() dto: UpdateTutorialItemDto, + ) { + return this.service.update(accountId, groupId, itemId, dto); + } + + @ApiOperation({ summary: 'Delete tutorial item', description: 'Delete tutorial item' }) + @ApiParam({ name: 'groupId', type: Number, required: true, description: 'Tutorial group id' }) + @ApiParam({ name: 'itemId', type: Number, required: true, description: 'Tutorial item id' }) + @ApiOkResponse() + @Delete(':itemId') + @UserAccess({ adminOnly: true }) + public async delete( + @CurrentAuth() { accountId }: AuthData, + @Param('groupId', ParseIntPipe) groupId: number, + @Param('itemId', ParseIntPipe) itemId: number, + ) { + return this.service.delete(accountId, groupId, itemId); + } +} diff --git a/backend/src/modules/tutorial/tutorial-item/tutorial-item.handler.ts b/backend/src/modules/tutorial/tutorial-item/tutorial-item.handler.ts new file mode 100644 index 0000000..8e75568 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/tutorial-item.handler.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { IamEventType, UserDeletedEvent } from '@/modules/iam/common'; +import { ProductsEventType, ProductsSectionEvent } from '@/modules/inventory/common'; +import { ScheduleEvent, SchedulerEventType } from '@/modules/scheduler/common'; +import { CrmEventType, EntityTypeEvent } from '@/CRM/common'; + +import { TutorialProductType } from '../common'; +import { TutorialItemService } from './tutorial-item.service'; + +@Injectable() +export class TutorialItemHandler { + constructor(private readonly service: TutorialItemService) {} + + @OnEvent(IamEventType.UserDeleted, { async: true }) + public async onUserDeleted(event: UserDeletedEvent) { + await this.service.deleteUser(event.accountId, event.userId); + } + + @OnEvent(CrmEventType.EntityTypeDeleted, { async: true }) + public async onEntityTypeDeleted(event: EntityTypeEvent) { + await this.service.deleteProduct(event.accountId, TutorialProductType.ENTITY_TYPE, event.entityTypeId); + } + + @OnEvent(ProductsEventType.ProductsSectionDeleted, { async: true }) + public async onProductsSectionDeleted(event: ProductsSectionEvent) { + await this.service.deleteProduct(event.accountId, TutorialProductType.PRODUCTS_SECTION, event.sectionId); + } + + @OnEvent(SchedulerEventType.ScheduleDeleted, { async: true }) + public async onScheduleDeleted(event: ScheduleEvent) { + await this.service.deleteProduct(event.accountId, TutorialProductType.SCHEDULER, event.scheduleId); + } +} diff --git a/backend/src/modules/tutorial/tutorial-item/tutorial-item.service.ts b/backend/src/modules/tutorial/tutorial-item/tutorial-item.service.ts new file mode 100644 index 0000000..73f9ee0 --- /dev/null +++ b/backend/src/modules/tutorial/tutorial-item/tutorial-item.service.ts @@ -0,0 +1,145 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Brackets, Repository } from 'typeorm'; + +import { NotFoundError, SortOrderListDto } from '@/common'; + +import { TutorialProductType } from '../common'; +import { CreateTutorialItemDto, UpdateTutorialItemDto } from './dto'; +import { TutorialItem, TutorialItemProduct, TutorialItemUser } from './entities'; + +interface FindFilter { + itemId?: number; + groupId?: number; + userId?: number; + productType?: TutorialProductType; + objectId?: number; + createdFrom?: Date; +} + +@Injectable() +export class TutorialItemService { + constructor( + @InjectRepository(TutorialItem) + private readonly itemRepository: Repository, + @InjectRepository(TutorialItemUser) + private readonly userRepository: Repository, + @InjectRepository(TutorialItemProduct) + private readonly productRepository: Repository, + ) {} + + public async create(accountId: number, groupId: number, dto: CreateTutorialItemDto): Promise { + const item = await this.itemRepository.save(TutorialItem.fromDto(accountId, groupId, dto)); + + if (dto.userIds?.length) { + item.users = await this.userRepository.save(dto.userIds.map((userId) => new TutorialItemUser(item.id, userId))); + } + + if (dto.products?.length) { + item.products = await this.productRepository.save( + dto.products.map((product) => TutorialItemProduct.fromDto(accountId, item.id, product)), + ); + } + + return item; + } + + public async findOne(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getOne(); + } + public async findMany(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).orderBy('ti.sort_order').getMany(); + } + public async count(accountId: number, filter?: FindFilter): Promise { + return this.createFindQb(accountId, filter).getCount(); + } + + public async update( + accountId: number, + groupId: number, + itemId: number, + dto: UpdateTutorialItemDto, + ): Promise { + const item = await this.findOne(accountId, { groupId, itemId }); + if (!item) { + throw NotFoundError.withId(TutorialItem, itemId); + } + + if (dto.userIds !== undefined) { + await this.userRepository.delete({ itemId: item.id }); + if (dto.userIds?.length) { + item.users = await this.userRepository.save(dto.userIds.map((userId) => new TutorialItemUser(item.id, userId))); + } else { + item.users = []; + } + } + + if (dto.products !== undefined) { + await this.productRepository.delete({ accountId, itemId: item.id }); + if (dto.products?.length) { + item.products = await this.productRepository.save( + dto.products.map((product) => TutorialItemProduct.fromDto(accountId, item.id, product)), + ); + } else { + item.products = []; + } + } + + return this.itemRepository.save(item.update(dto)); + } + + public async sort(accountId: number, dto: SortOrderListDto) { + for (const item of dto.items) { + await this.itemRepository.update({ id: item.id, accountId }, { sortOrder: item.sortOrder }); + } + } + + public async delete(accountId: number, groupId: number, itemId: number) { + await this.itemRepository.delete({ id: itemId, groupId, accountId }); + } + + public async deleteUser(_accountId: number, userId: number) { + await this.userRepository.delete({ userId }); + } + + public async deleteProduct(accountId: number, type: TutorialProductType, objectId: number) { + await this.productRepository.delete({ accountId, type, objectId }); + } + + private createFindQb(accountId: number, filter?: FindFilter) { + const qb = this.itemRepository + .createQueryBuilder('ti') + .leftJoinAndMapMany('ti.users', TutorialItemUser, 'tiu', 'ti.id = tiu.item_id') + .leftJoinAndMapMany('ti.products', TutorialItemProduct, 'tip', 'ti.id = tip.item_id') + .where('ti.account_id = :accountId', { accountId }); + if (filter?.itemId) { + qb.andWhere('ti.id = :id', { id: filter.itemId }); + } + if (filter?.groupId) { + qb.andWhere('ti.group_id = :groupId', { groupId: filter.groupId }); + } + if (filter?.userId) { + qb.andWhere( + new Brackets((qb1) => + qb1.where('tiu.user_id = :userId', { userId: filter.userId }).orWhere('tiu.user_id IS NULL'), + ), + ); + } + if (filter?.productType) { + qb.andWhere( + new Brackets((qb2) => qb2.where('tip.type = :type', { type: filter.productType }).orWhere('tip.type IS NULL')), + ); + } + if (filter?.objectId) { + qb.andWhere( + new Brackets((qb3) => + qb3.where('tip.object_id = :objectId', { objectId: filter.objectId }).orWhere('tip.object_id IS NULL'), + ), + ); + } + if (filter?.createdFrom) { + qb.andWhere('ti.created_at >= :createdFrom', { createdFrom: filter.createdFrom }); + } + return qb; + } +} diff --git a/backend/src/modules/tutorial/tutorial.module.ts b/backend/src/modules/tutorial/tutorial.module.ts new file mode 100644 index 0000000..97b41de --- /dev/null +++ b/backend/src/modules/tutorial/tutorial.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { IAMModule } from '../iam/iam.module'; + +import tutorialConfig from './config/tutorial.config'; +import { + TutorialItem, + TutorialItemUser, + TutorialItemService, + TutorialItemController, + TutorialItemProduct, + TutorialItemHandler, +} from './tutorial-item'; +import { TutorialGroup, TutorialGroupService, TutorialGroupController } from './tutorial-group'; +import { TutorialCoreController, TutorialCoreService } from './tutorial-core'; + +@Module({ + imports: [ + ConfigModule.forFeature(tutorialConfig), + TypeOrmModule.forFeature([TutorialGroup, TutorialItem, TutorialItemUser, TutorialItemProduct]), + IAMModule, + ], + providers: [TutorialGroupService, TutorialItemService, TutorialItemHandler, TutorialCoreService], + controllers: [TutorialGroupController, TutorialItemController, TutorialCoreController], +}) +export class TutorialModule {} diff --git a/backend/src/support/config/index.ts b/backend/src/support/config/index.ts new file mode 100644 index 0000000..5b665e1 --- /dev/null +++ b/backend/src/support/config/index.ts @@ -0,0 +1 @@ +export * from './support.config'; diff --git a/backend/src/support/config/support.config.ts b/backend/src/support/config/support.config.ts new file mode 100644 index 0000000..1a816fe --- /dev/null +++ b/backend/src/support/config/support.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; + +export interface SupportConfig { + accessCode: string; +} + +export default registerAs( + 'support', + (): SupportConfig => ({ + accessCode: process.env.SUPPORT_ACCESS_CODE, + }), +); diff --git a/backend/src/support/health/health.controller.ts b/backend/src/support/health/health.controller.ts new file mode 100644 index 0000000..8d9ebdd --- /dev/null +++ b/backend/src/support/health/health.controller.ts @@ -0,0 +1,23 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; +import { HealthCheck, HealthCheckService, MemoryHealthIndicator, TypeOrmHealthIndicator } from '@nestjs/terminus'; + +@ApiExcludeController(true) +@Controller('support/health') +export class HealthController { + constructor( + private readonly health: HealthCheckService, + private readonly db: TypeOrmHealthIndicator, + private readonly memory: MemoryHealthIndicator, + ) {} + + @Get() + @HealthCheck() + check() { + return this.health.check([ + () => this.db.pingCheck('database'), + () => this.memory.checkHeap('memory.heap', 2 * 1024 * 1024 * 1024), + () => this.memory.checkRSS('memory.rss', 2 * 1024 * 1024 * 1024), + ]); + } +} diff --git a/backend/src/support/health/health.module.ts b/backend/src/support/health/health.module.ts new file mode 100644 index 0000000..9ed184e --- /dev/null +++ b/backend/src/support/health/health.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TerminusModule } from '@nestjs/terminus'; + +import { HealthController } from './health.controller'; + +@Module({ + imports: [TerminusModule], + controllers: [HealthController], +}) +export class HealthModule {} diff --git a/backend/src/support/health/index.ts b/backend/src/support/health/index.ts new file mode 100644 index 0000000..4ac749f --- /dev/null +++ b/backend/src/support/health/index.ts @@ -0,0 +1,2 @@ +export * from './health.controller'; +export * from './health.module'; diff --git a/backend/src/support/heapdump/heapdump.controller.ts b/backend/src/support/heapdump/heapdump.controller.ts new file mode 100644 index 0000000..98cdc3e --- /dev/null +++ b/backend/src/support/heapdump/heapdump.controller.ts @@ -0,0 +1,15 @@ +import { Controller, Post, Query } from '@nestjs/common'; +import { ApiExcludeController } from '@nestjs/swagger'; + +import { HeapdumpService } from './heapdump.service'; + +@ApiExcludeController(true) +@Controller('support/heapdump') +export class HeapdumpController { + constructor(private readonly service: HeapdumpService) {} + + @Post('create') + public async writeSnapshot(@Query('code') code: string) { + return this.service.writeSnapshot(code); + } +} diff --git a/backend/src/support/heapdump/heapdump.module.ts b/backend/src/support/heapdump/heapdump.module.ts new file mode 100644 index 0000000..07ae1ea --- /dev/null +++ b/backend/src/support/heapdump/heapdump.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { HeapdumpService } from './heapdump.service'; +import { HeapdumpController } from './heapdump.controller'; + +@Module({ + providers: [HeapdumpService], + controllers: [HeapdumpController], +}) +export class HeapdumpModule {} diff --git a/backend/src/support/heapdump/heapdump.service.ts b/backend/src/support/heapdump/heapdump.service.ts new file mode 100644 index 0000000..60fb66f --- /dev/null +++ b/backend/src/support/heapdump/heapdump.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +import { DateUtil } from '@/common'; + +import { SupportConfig } from '../config'; + +@Injectable() +export class HeapdumpService { + private readonly _config: SupportConfig | undefined; + + constructor(private readonly configService: ConfigService) { + this._config = this.configService.get('support'); + } + + public async writeSnapshot(code: string) { + if (this._config?.accessCode && this._config.accessCode === code) { + try { + const { writeSnapshot } = await import('heapdump'); + writeSnapshot(`heapdump-${DateUtil.now().toISOString()}.heapsnapshot`); + } catch (error) { + console.error('Heapdump not available:', error); + } + } + } +} diff --git a/backend/src/support/heapdump/index.ts b/backend/src/support/heapdump/index.ts new file mode 100644 index 0000000..5b64774 --- /dev/null +++ b/backend/src/support/heapdump/index.ts @@ -0,0 +1,3 @@ +export * from './heapdump.controller'; +export * from './heapdump.module'; +export * from './heapdump.service'; diff --git a/backend/src/support/support.module.ts b/backend/src/support/support.module.ts new file mode 100644 index 0000000..410e613 --- /dev/null +++ b/backend/src/support/support.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common'; +import { ConditionalModule, ConfigModule } from '@nestjs/config'; + +import supportConfig from './config/support.config'; +import { HeapdumpModule } from './heapdump'; +import { HealthModule } from './health'; +import { VersionModule } from './version'; + +@Module({ + imports: [ + ConfigModule.forFeature(supportConfig), + ConditionalModule.registerWhen(HeapdumpModule, 'SUPPORT_HEAPDUMP_ENABLED'), + ConditionalModule.registerWhen(HealthModule, 'SUPPORT_HEALTH_ENABLED'), + VersionModule, + ], +}) +export class SupportModule {} diff --git a/backend/src/support/version/dto/create-version.dto.ts b/backend/src/support/version/dto/create-version.dto.ts new file mode 100644 index 0000000..ca6ded6 --- /dev/null +++ b/backend/src/support/version/dto/create-version.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class CreateVersionDto { + @ApiProperty({ description: 'Version number, should be in the format of x.x.x, where x is a number' }) + @IsString() + @IsNotEmpty() + version: string; + + @ApiPropertyOptional() + @IsOptional() + code: string; +} diff --git a/backend/src/support/version/dto/current-version.dto.ts b/backend/src/support/version/dto/current-version.dto.ts new file mode 100644 index 0000000..4bec118 --- /dev/null +++ b/backend/src/support/version/dto/current-version.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; + +export class CurrentVersionDto { + @ApiProperty({ description: 'Current version of an app in x.x.x format, where x is a number' }) + @IsString() + currentVersion: string; +} diff --git a/backend/src/support/version/dto/index.ts b/backend/src/support/version/dto/index.ts new file mode 100644 index 0000000..10d7e68 --- /dev/null +++ b/backend/src/support/version/dto/index.ts @@ -0,0 +1,3 @@ +export * from './create-version.dto'; +export * from './current-version.dto'; +export * from './version.dto'; diff --git a/backend/src/support/version/dto/version.dto.ts b/backend/src/support/version/dto/version.dto.ts new file mode 100644 index 0000000..62ff4c9 --- /dev/null +++ b/backend/src/support/version/dto/version.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString } from 'class-validator'; + +export class VersionDto { + @ApiProperty({ description: 'Version ID' }) + @IsNumber() + id: number; + + @ApiProperty({ description: 'Version number in x.x.x format, where x is a number' }) + @IsString() + version: string; + + @ApiProperty({ description: 'Version creation date in ISO format' }) + @IsString() + date: string; +} diff --git a/backend/src/support/version/entities/index.ts b/backend/src/support/version/entities/index.ts new file mode 100644 index 0000000..4c0d419 --- /dev/null +++ b/backend/src/support/version/entities/index.ts @@ -0,0 +1 @@ +export * from './version.entity'; diff --git a/backend/src/support/version/entities/version.entity.ts b/backend/src/support/version/entities/version.entity.ts new file mode 100644 index 0000000..b923378 --- /dev/null +++ b/backend/src/support/version/entities/version.entity.ts @@ -0,0 +1,33 @@ +import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; +import { VersionDto } from '../dto/version.dto'; +import { DateUtil } from '@/common'; +import type { CreateVersionDto } from '../dto/create-version.dto'; + +@Entity() +export class Version { + @PrimaryGeneratedColumn('identity') + id: number; + + @Column({ type: 'varchar', length: 16, unique: true }) + version: string; + + @Column({ type: 'timestamp without time zone' }) + date: Date; + + constructor(version: string) { + this.version = version; + this.date = DateUtil.now(); + } + + static fromDto(dto: CreateVersionDto): Version { + return new Version(dto.version); + } + + public toDto(): VersionDto { + return { + id: this.id, + version: this.version, + date: this.date.toISOString(), + }; + } +} diff --git a/backend/src/support/version/index.ts b/backend/src/support/version/index.ts new file mode 100644 index 0000000..f7432d9 --- /dev/null +++ b/backend/src/support/version/index.ts @@ -0,0 +1,3 @@ +export * from './version.controller'; +export * from './version.module'; +export * from './version.service'; diff --git a/backend/src/support/version/version.controller.ts b/backend/src/support/version/version.controller.ts new file mode 100644 index 0000000..d94257e --- /dev/null +++ b/backend/src/support/version/version.controller.ts @@ -0,0 +1,51 @@ +import { TransformToDto } from '@/common'; +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { + ApiBody, + ApiCreatedResponse, + ApiExcludeEndpoint, + ApiOkResponse, + ApiOperation, + ApiParam, + ApiTags, +} from '@nestjs/swagger'; + +import { CreateVersionDto, VersionDto, CurrentVersionDto } from './dto'; +import { VersionService } from './version.service'; + +@ApiTags('support/version') +@Controller('support/version') +@TransformToDto() +export class VersionController { + constructor(private readonly service: VersionService) {} + + @ApiExcludeEndpoint(true) + @ApiOperation({ + summary: 'Create frontend version', + }) + @ApiBody({ type: CreateVersionDto, required: true, description: 'Data for creating frontend version' }) + @ApiCreatedResponse({ description: 'Created version item', type: VersionDto }) + @Post('frontend') + async create(@Body() dto: CreateVersionDto) { + return this.service.create(dto); + } + + @ApiOperation({ + summary: 'Get latest frontend version', + description: 'Latest frontend version will be return if it is greater than the current version, otherwise null', + }) + @ApiParam({ + name: 'currentVersion', + type: String, + required: true, + description: 'Current version of the frontend app, usually retrieved from the "version" property of package.json', + }) + @ApiOkResponse({ + type: VersionDto, + description: 'Latest frontend version or null if provided current version is greater than the latest version', + }) + @Get('frontend/latest') + async getLatest(@Query() dto: CurrentVersionDto) { + return this.service.getLatest(dto); + } +} diff --git a/backend/src/support/version/version.module.ts b/backend/src/support/version/version.module.ts new file mode 100644 index 0000000..8b0c32b --- /dev/null +++ b/backend/src/support/version/version.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { VersionService } from './version.service'; +import { VersionController } from './version.controller'; +import { Version } from './entities/version.entity'; +import { IAMModule } from '@/modules/iam/iam.module'; + +@Module({ + imports: [TypeOrmModule.forFeature([Version]), IAMModule], + providers: [VersionService], + controllers: [VersionController], +}) +export class VersionModule {} diff --git a/backend/src/support/version/version.service.ts b/backend/src/support/version/version.service.ts new file mode 100644 index 0000000..0d4dfcb --- /dev/null +++ b/backend/src/support/version/version.service.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSource, Repository } from 'typeorm'; + +import { ForbiddenError } from '@/common'; + +import { SupportConfig } from '../config'; +import { CreateVersionDto, CurrentVersionDto } from './dto'; +import { Version } from './entities'; + +const cacheKey = 'Version:latest'; + +@Injectable() +export class VersionService { + private readonly _config: SupportConfig | undefined; + + constructor( + private readonly configService: ConfigService, + @InjectRepository(Version) + private readonly repository: Repository, + private readonly dataSource: DataSource, + ) { + this._config = this.configService.get('support'); + } + + async create(dto: CreateVersionDto): Promise { + if (this._config?.accessCode && this._config.accessCode === dto.code) { + await this.dataSource.queryResultCache?.remove([cacheKey]); + + return this.repository.save(Version.fromDto(dto)); + } else { + throw new ForbiddenError(); + } + } + + async getLatest({ currentVersion }: CurrentVersionDto): Promise { + const isLatestGreater = ({ + currentVersion, + latestVersion, + }: { + currentVersion: string; + latestVersion: string; + }): boolean => { + const currentVersionParts = currentVersion.split('.').map((p) => parseInt(p, 10)); + const latestVersionParts = latestVersion.split('.').map((p) => parseInt(p, 10)); + + // Ensure both versions have the same length by padding with zeroes + const maxLength = Math.max(currentVersionParts.length, latestVersionParts.length); + + for (let i = 0; i < maxLength; i++) { + const currentVersionPart = currentVersionParts[i] || 0; + const latestVersionPart = latestVersionParts[i] || 0; + + if (currentVersionPart < latestVersionPart) return true; + if (currentVersionPart > latestVersionPart) return false; + } + + return false; // Versions are equal + }; + + const latestVersion = await this.repository + .createQueryBuilder('version') + .orderBy('version.date', 'DESC') + .limit(1) + .cache(cacheKey, 1209600000) + .getOne(); + + return latestVersion && isLatestGreater({ currentVersion, latestVersion: latestVersion.version }) + ? latestVersion + : null; + } +} diff --git a/backend/src/types/environment.d.ts b/backend/src/types/environment.d.ts new file mode 100644 index 0000000..b8b3dc6 --- /dev/null +++ b/backend/src/types/environment.d.ts @@ -0,0 +1,99 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + readonly npm_package_version: string; + readonly APPLICATION_BASE_URL: string; + readonly APPLICATION_BASE_URL_TEMPLATE: string; + readonly APPLICATION_PORT: string; + readonly APPLICATION_NAME: string; + readonly APPLICATION_FEEDBACK_EMAIL: string; + readonly APPLICATION_SUPPORT_EMAIL: string; + readonly APPLICATION_VERIFICATION_TOKEN: string; + readonly APPLICATION_API_KEY_REQUIRED: string; + readonly APPLICATION_SKELETON_KEY: string; + readonly APPLICATION_SUBDOMAIN_PREFIX: string; + readonly API_DOC_ENABLED: string; + readonly SCHEDULE_ENABLED: string; + readonly SCHEDULE_CHAT_SCHEDULED_DISABLE: string; + readonly SCHEDULE_INTEGRATION_GOOGLE_CALENDAR_DISABLE: string; + readonly SCHEDULE_MAIL_ACTIVE_DISABLE: string; + readonly SCHEDULE_MAIL_INACTIVE_DISABLE: string; + readonly SCHEDULE_MAIL_SCHEDULED_DISABLE: string; + readonly SCHEDULE_NOTIFICATION_TASK_OVERDUE_DISABLE: string; + readonly SCHEDULE_NOTIFICATION_TASK_BEFORE_START_DISABLE: string; + readonly SCHEDULE_NOTIFICATION_TASK_OVERDUE_FOLLOW_DISABLE: string; + readonly SCHEDULE_NOTIFICATION_ACTIVITY_OVERDUE_DISABLE: string; + readonly SCHEDULE_NOTIFICATION_ACTIVITY_BEFORE_START_DISABLE: string; + readonly SCHEDULE_NOTIFICATION_ACTIVITY_OVERDUE_FOLLOW_DISABLE: string; + readonly SCHEDULE_PRODUCTS_ORDER_CHECK_CANCEL_DISABLE: string; + readonly WINSTON_ENABLED: string; + readonly NEW_RELIC_ENABLED: string; + readonly ACCOUNT_SETUP_ACCOUNT_ID: string; + readonly ACCOUNT_SETUP_CONTACT_ID: string; + readonly ACCOUNT_SETUP_DEAL_ID: string; + readonly ACCOUNT_SETUP_DEAL_BOARD_ID: string; + readonly ACCOUNT_SETUP_RESPONSIBLE_ID: string; + readonly STORAGE_DOWNLOAD_PATH: string; + readonly STORAGE_IMAGE_PATH: string; + readonly STORAGE_TEMPORARY_PATH: string; + readonly DOCUMENTS_HOST: string; + readonly AWS_ENDPOINT_URL: string; + readonly AWS_REGION: string; + readonly AWS_BUCKET: string; + readonly AWS_ACCESS_KEY_ID: string; + readonly AWS_SECRET_ACCESS_KEY: string; + readonly AUTOMATION_JOB_DISCOVERY_ENABLED: string; + readonly VOXIMPLANT_PARENT_ACCOUNT_ID: string; + readonly VOXIMPLANT_PARENT_ACCOUNT_API_KEY: string; + readonly VOXIMPLANT_CREDENTIALS_FILE: string; + readonly FB_APP_ID: string; + readonly FB_APP_SECRET: string; + readonly FB_APP_ACCESS_TOKEN: string; + readonly FB_MESSENGER_AUTH_CONFIG_ID: string; + readonly FB_MESSENGER_VALIDATION_TOKEN: string; + readonly GA_MEASUREMENT_ID: string; + readonly GA_API_SECRET: string; + readonly WAZZUP_SECRET: string; + readonly WAZZUP_PROCESS_ENQUEUE: string; + readonly POSTGRES_HOST: string; + readonly POSTGRES_PORT: string; + readonly POSTGRES_USER: string; + readonly POSTGRES_PASSWORD: string; + readonly POSTGRES_DB: string; + readonly POSTGRES_QUERY_LOGGING: string; + readonly TYPEORM_CACHE_TYPE: string; + readonly TYPEORM_CACHE_DURATION: string; + readonly CACHE_TYPE: string; + readonly CACHE_IOREDIS_HOST: string; + readonly CACHE_IOREDIS_PORT: string; + readonly STRIPE_SECRET: string; + readonly STRIPE_WEBHOOK_SECRET: string; + readonly SUPPORT_ACCESS_CODE: string; + readonly MAILING_HOST: string; + readonly MAILING_PORT: string; + readonly MAILING_SECURE: string; + readonly MAILING_USER: string; + readonly MAILING_PASSWORD: string; + readonly MAILING_FROM: string; + readonly MAILING_REPLY_TO: string; + readonly TUTORIAL_LANGUAGE: string; + readonly GOOGLE_AUTH_CLIENT_ID: string; + readonly GOOGLE_AUTH_CLIENT_SECRET: string; + readonly GMAIL_API_CLIENT_ID: string; + readonly GMAIL_API_CLIENT_SECRET: string; + readonly MAIL_MANUAL_SEARCH_TIMEOUT: string; + readonly MAIL_MANUAL_SEARCH_BATCH_SIZE: string; + readonly MAIL_MANUAL_PART_LOAD_TIMEOUT: string; + readonly MAIL_IMAPFLOW_SEARCH_TIMEOUT: string; + readonly MAIL_IMAPFLOW_SEARCH_BATCH_SIZE: string; + readonly MAIL_IMAPFLOW_PART_LOAD_TIMEOUT: string; + readonly DATA_ENRICHMENT_GEOHELPER_API_KEY: string; + readonly DATA_ENRICHMENT_DADATA_API_KEY: string; + readonly APPSUMO_CLIENT_ID: string; + readonly APPSUMO_CLIENT_SECRET: string; + readonly APPSUMO_PRIVATE_KEY: string; + } + } +} + +export {}; diff --git a/backend/src/types/express.d.ts b/backend/src/types/express.d.ts new file mode 100644 index 0000000..c289870 --- /dev/null +++ b/backend/src/types/express.d.ts @@ -0,0 +1,15 @@ +// types/express.d.ts +import 'express'; + +declare module 'express' { + export interface Request { + id?: string; + subdomain?: string; + callerAccountId?: number; + accountId?: number; + userId?: number; + token?: unknown; + account?: unknown; + user?: unknown; + } +} diff --git a/backend/src/types/imapflow/index.d.ts b/backend/src/types/imapflow/index.d.ts new file mode 100644 index 0000000..87122e4 --- /dev/null +++ b/backend/src/types/imapflow/index.d.ts @@ -0,0 +1,394 @@ +declare module 'imapflow' { + import { EventEmitter } from 'stream'; + export type Readable = import('stream').Readable; + + export class ImapFlow extends EventEmitter { + constructor(options: ImapFlowOptions); + authenticated: string | boolean; + capabilities: Map; + emitLogs: boolean; + enabled: Set; + id: string; + idling: boolean; + mailbox: MailboxObject | boolean; + secureConnection: boolean; + serverInfo: IdInfoObject; + usable: boolean; + + append( + path: string, + content: string | Buffer, + flags?: string[], + idate?: Date | string, + ): Promise; + + connect(): Promise; + logout(): Promise; + close(): void; + download( + range: SequenceString, + part?: string, + options?: { uid?: boolean; maxBytes?: number; chunkSize?: number }, + ): Promise; + + getMailboxLock(path: string, options?: null | { readonly?: boolean }): Promise; + + getQuota(path: string): Promise; + + idle(): Promise; + + /** + * @see {@link https://imapflow.com/module-imapflow-ImapFlow.html#list} + */ + list(options?: ListOptions): Promise; + + listTree(options?: ListOptions): Promise; + + mailboxClose(): Promise; + + mailboxCreate(path: string | string[]): Promise; + + mailboxDelete(path: string | string[]): Promise; + + mailboxOpen(path: string | string[], options?: { readOnly?: boolean }): Promise; + + mailboxRename(path: string | string[], newPath: string | string[]): Promise; + + mailboxSubscribe(path: string | string[]): Promise; + + mailboxUnsubscribe(path: string | string[]): Promise; + + messageCopy( + range: SequenceString | number[] | SearchObject, + destination: string, + options?: { uid?: boolean }, + ): Promise; + + messageDelete(range: SequenceString | number[] | SearchObject, options?: { uid?: boolean }): Promise; + + messageFlagsAdd( + range: SequenceString | number[] | SearchObject, + Array: string[], + options?: { uid?: boolean; unchangedSince?: bigint; useLabels?: boolean }, + ): Promise; + + messageFlagsRemove( + range: SequenceString | number[] | SearchObject, + Array: string[], + options?: { uid?: boolean; unchangedSince?: bigint; useLabels?: boolean }, + ): Promise; + + messageFlagsSet( + range: SequenceString | number[] | SearchObject, + Array: string[], + options?: { uid?: boolean; unchangedSince?: bigint; useLabels?: boolean }, + ): Promise; + + messageMove( + range: SequenceString | number[] | SearchObject, + destination: string, + options?: { uid?: boolean }, + ): Promise; + + fetchOne( + seq: SequenceString, + query: FetchQueryObject, + options?: { + uid?: boolean; + }, + ): Promise; + + noop(): Promise; + + search(query: SearchObject, options?: { uid?: boolean }): Promise; + + status( + path: string, + query: { + messages?: boolean; + recent?: boolean; + uidNext?: boolean; + uidValidity?: boolean; + unseen?: boolean; + highestModseq?: boolean; + }, + ): Promise; + + fetch( + range: SequenceString | number[] | SearchObject, + query: FetchQueryObject, + options?: { uid?: boolean; changedSince?: bigint; binary?: boolean }, + ): AsyncGenerator; + + fetchAll( + range: SequenceString | number[] | SearchObject, + query: FetchQueryObject, + options?: { uid?: boolean; changedSince?: bigint; binary?: boolean }, + ): Promise; + } + + export interface ImapFlowOptions { + host: string; + port: number; + auth: { + user: string; + pass?: string; + accessToken?: string; + }; + secure?: boolean; + servername?: string; + disableCompression?: boolean; + clientInfo?: IdInfoObject; + disableAutoIdle?: boolean; + tls?: object; + logger?: Logger | false; + emitLogs?: boolean; + verifyOnly?: boolean; + logRaw?: boolean; + proxy?: string; + qresync?: boolean; + maxIdleTime?: number; + missingIdleCommand?: string; + disableBinary?: boolean; + disableAutoEnable?: boolean; + connectionTimeout?: number; + greetingTimeout?: number; + socketTimeout?: number; + } + + export interface AppendResponseObject { + path: string; + uidValidity?: bigint; + uid?: number; + seq?: number; + } + + export interface CopyResponseObject { + path: string; + destination: string; + uidValidity?: bigint; + uidMap?: Map; + } + + export interface DownloadObject { + content: Readable; + meta: { + expectedSize: number; + contentType: string; + charset?: string; + disposition?: string; + filename?: string; + }; + } + + export interface MailboxObject { + path: string; + delimiter: string; + flags: Set; + specialUse: string; + listed: boolean; + subscribed: boolean; + permanentFlags: Set; + mailboxId: string; + highestModseq: bigint; + uidValidity: bigint; + uidNext: number; + exists: number; + } + + export interface MailboxLockObject { + path: string; + release: () => void; + } + + export interface FetchMessageObject { + seq: number; + uid: number; + source: Buffer; + modseq: bigint; + emailId: string; + threadId?: string; + labels: Set; + size: number; + flags: Set; + envelope: MessageEnvelopeObject; + bodyStructure: MessageStructureObject; + internalDate: Date; + bodyParts: Map; + headers: Buffer; + } + + export interface FetchQueryObject { + uid?: boolean; + flags?: boolean; + bodyStructure?: boolean; + envelope?: boolean; + internalDate?: boolean; + size?: boolean; + source?: boolean | object; + threadId?: boolean; + labels?: boolean; + headers?: boolean | string[]; + bodyParts?: string[]; + } + + export interface MailboxRenameResponse { + path: string; + newPath: string; + } + + export interface MessageAddressObject { + name?: string; + address?: string; + } + + export interface MessageEnvelopeObject { + date: Date; + subject: string; + messageId: string; + inReplyTo: string; + from: MessageAddressObject[]; + sender: MessageAddressObject[]; + replyTo: MessageAddressObject[]; + to: MessageAddressObject[]; + cc: MessageAddressObject[]; + bcc: MessageAddressObject[]; + } + + export interface QuotaResponse { + path: string; + storage?: object; + messages?: object; + } + + export type SequenceString = string; + + export interface SearchObject { + seq?: SequenceString; + answered?: boolean; + deleted?: boolean; + draft?: boolean; + flagged?: boolean; + seen?: boolean; + all?: boolean; + new?: boolean; + old?: boolean; + recent?: boolean; + from?: string; + to?: string; + cc?: string; + bcc?: string; + body?: string; + subject?: string; + larger?: number; + smaller?: number; + uid?: SequenceString; + modseq?: bigint; + emailId?: string; + threadId?: string; + before?: Date | string; + on?: Date | string; + since?: Date | string; + sentBefore?: Date | string; + sentOn?: Date | string; + sentSince?: Date | string; + keyword?: string; + unKeyword?: string; + header?: Record; + or?: SearchObject[]; + } + + export interface StatusObject { + path: string; + messages?: number; + recent?: number; + uidNext?: number; + uidValidity?: bigint; + unseen?: number; + highestModseq?: bigint; + } + + export interface IdInfoObject { + name?: string; + version?: string; + os?: string; + vendor?: string; + 'support-url'?: string; + date?: Date; + } + + export interface ListResponse { + path: string; + name: string; + delimiter: string; + flags: Set; + specialUse: string; + listed: boolean; + subscribed: boolean; + status?: StatusObject; + } + + export interface ListTreeResponse { + root: boolean; + path: string; + name: string; + delimiter: string; + flags: []; + specialUse: string; + listed: boolean; + subscribed: boolean; + disabled: boolean; + folders: ListTreeResponse[]; + } + + export interface MailboxCreateResponse { + path: string; + mailboxId?: string; + created: boolean; + } + + export interface MailboxDeleteResponse { + path: string; + } + + export interface MessageStructureObject { + part: string; + type: string; + parameters: string; + id: string; + encoding: string; + size: number; + envelope: MessageEnvelopeObject; + disposition: string; + dispositionParameters: string; + childNodes: MessageStructureObject[]; + } + + export interface Logger { + debug: (obj: object) => void; + info: (obj: object) => void; + warn: (obj: object) => void; + error: (obj: object) => void; + } + + export interface ListOptions { + statusQuery?: StatusQuery; + specialUseHints?: SpecialUseHints; + } + + export interface StatusQuery { + messages?: boolean; + recent?: boolean; + uidNext?: boolean; + uidValidity?: boolean; + unseen?: boolean; + highestModseq?: boolean; + } + + export interface SpecialUseHints { + sent: string; + trash: string; + junk: string; + drafts: string; + } +} diff --git a/backend/src/types/reset.d.ts b/backend/src/types/reset.d.ts new file mode 100644 index 0000000..12bd3ed --- /dev/null +++ b/backend/src/types/reset.d.ts @@ -0,0 +1 @@ +import '@total-typescript/ts-reset'; diff --git a/backend/tsconfig.build.json b/backend/tsconfig.build.json new file mode 100644 index 0000000..8b195b3 --- /dev/null +++ b/backend/tsconfig.build.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "incremental": true, + "tsBuildInfoFile": "./dist/.tsbuildinfo", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false + }, + "exclude": [ + "node_modules", + "test", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.e2e-spec.ts" + ] +} diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..796fc52 --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,56 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "declaration": true, + "removeComments": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "esModuleInterop": true, + "resolveJsonModule": true, + + "paths": { + "@/*": ["src/*"], + }, + "typeRoots": ["./types", "./node_modules/@types"], + + // Strict Checks + "strict": true, + "noImplicitAny": false, + "strictNullChecks": false, + "strictPropertyInitialization": false, + "exactOptionalPropertyTypes": false, + "useUnknownInCatchVariables": true, + "strictFunctionTypes": true, + "noImplicitThis": true, + "strictBindCallApply": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + // Linter Checks + "noImplicitReturns": true, + "noImplicitOverride": true, + "forceConsistentCasingInFileNames": true, + // https://eslint.org/docs/rules/consistent-return ? + "noFallthroughCasesInSwitch": true, + // https://eslint.org/docs/rules/no-fallthrough + "noUnusedLocals": true, + // https://eslint.org/docs/rules/no-unused-vars + "noUnusedParameters": true, + // https://eslint.org/docs/rules/no-unused-vars#args + "allowUnreachableCode": false, + // https://eslint.org/docs/rules/no-unreachable ? + "allowUnusedLabels": false, + // https://eslint.org/docs/rules/no-unused-labels + // Base Strict Checks + "noImplicitUseStrict": false, + "suppressExcessPropertyErrors": false, + "suppressImplicitAnyIndexErrors": false, + "noStrictGenericChecks": false + } +} diff --git a/backend/var/jwt/private.pem b/backend/var/jwt/private.pem new file mode 100644 index 0000000..90a65a7 --- /dev/null +++ b/backend/var/jwt/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCj4+xjbxSELVpp +1jKE140Q+xk8ckA6+DlC/n57HYOp1mNPKm3i0i+SN5cD28GQzjW72pcpYZ8YleYE +oG9epoCyAodc0P8Bf6zqQhQoLP26idJbv+m6RhbEAoYTUcWz5IrIZ4aqE51HM3oJ +kOo2tDrug/zSX9zq+zS++YyBZOLkdc7NJ/0YgKJcvEsHbJuuxAzn6VszX8ClWQnm +uwUQ4voQaV2rTr7BvE2Y0J0PZMgDAt5fSD1x0wvIfUXO3/8MGe/uDLDQL4e3ztcE +VZgb2r28P1k85c9XcpCkdVN0moKyzrPKq+bcDBIbBV9Q5f3r7d4EYEnDalpHpgEY +f67yXI4DAgMBAAECggEASTJEo2w7B4WR+e72hSoYENt0u/BzC2NNf8RWDPpzkWj0 +1ainh0REhtNZGRoO63ONwCaymILHIZ3hK3PUCbvngplqh2O4YJz7R2zXv9HISIXB +c8TUyKMBC+3sn7hHyj5qVXMXS+KSvfgZqygT0vbP0zMTuYmjCzfCqQCfZjL+uvXD +oBz+TcCLK68dJSB5CoAh5EAs7FGMDIFAxYvBv96zQGrEuvfpucFc7v2XznChjGgb +sE9D8gg927z1PUbQsWv8SOvDHwHBqix9f8ph1phasmFNXN5ZAS7lYoIGX0Hro6T2 +RseW5h38toC3Lc4U9wmLIFexiegNsern4xRkdFnX4QKBgQDUQ79+RbjkkFX5zaCA +JOUPKBY+M1eGJIp0a4BS1/SNDTUOnLd6yQHoujKy61tJdSOS+jwSJeF0fhGF4r1u +PjarekW0i1hbUVK//KM1Q8OsHX5Tk/tsPYVLw3HFQpoiTvZOeGYItguMoMmQnmTK +iN8ZR4MJYHbpl5nOxjIOVvYA6wKBgQDFqJdw7Y22GY5HrEC6M+vKynWbSlFgz5S5 +xVtpuFLtRhy8lCOZq1me2oBl6oS+y1xYoft/KTPM4ygZLwH10f4V7qkOQwN+OuAC +SB8+eBK4LO54KbtDkRXYpEkmUJNSVHtWXiJGEqXVgCIvF8YvBYWXpMCwBqXHHhGs +Ygs7CkghSQKBgB1lVHu0RCrDImT56SRV97Llpk7u5Uwae2IsERVn+uId1h8z7OUA +OVd1kdfdaEMACfEs3mzU+igb3WlhQUKnMwMEZ+rc8VuUI5Wa8y9JNyv62afRcpxG +2NLpOjRLSPU/YjTzz42dSHQtQDza8rJpyhvCH4+I4G7xI8fTAtOhj2gJAoGACS4/ +entOLbsaJLIXf46R0SV+OOxGw1xg6BAGou5wy5yKEShATw7qZrp3ZER0TfhcHbHI +YKulQEr8vc61JJnQV2xyZbsvGlnZtcFr0hb5p5xOpz4o+IZwoVNgImtzrEtIP0a4 +CNEs6rG85LsR9XUoM1bvrD1izdDTuVIEe4WKvCECgYBEX5pIxbr3+ydEmkn/Wv/p +FfwBn9BcqYJ8ACNN2FwWv2uUYI+AyZnt+FpqfrIlG7yDNp4GJzTkxbZToj7IlT7u +45KgJwmd1GDfpdjPbs0lXFKoWacr/sOLhCkbtZaXAJWX96IiJCDoY3HpeWUg9Usn +kNr7g4gv0qhxwwImd5ubRQ== +-----END PRIVATE KEY----- diff --git a/backend/var/jwt/public.pem b/backend/var/jwt/public.pem new file mode 100644 index 0000000..89f5ef6 --- /dev/null +++ b/backend/var/jwt/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo+PsY28UhC1aadYyhNeN +EPsZPHJAOvg5Qv5+ex2DqdZjTypt4tIvkjeXA9vBkM41u9qXKWGfGJXmBKBvXqaA +sgKHXND/AX+s6kIUKCz9uonSW7/pukYWxAKGE1HFs+SKyGeGqhOdRzN6CZDqNrQ6 +7oP80l/c6vs0vvmMgWTi5HXOzSf9GICiXLxLB2ybrsQM5+lbM1/ApVkJ5rsFEOL6 +EGldq06+wbxNmNCdD2TIAwLeX0g9cdMLyH1Fzt//DBnv7gyw0C+Ht87XBFWYG9q9 +vD9ZPOXPV3KQpHVTdJqCss6zyqvm3AwSGwVfUOX96+3eBGBJw2paR6YBGH+u8lyO +AwIDAQAB +-----END PUBLIC KEY----- diff --git a/backend/var/voximplant/credentials.json b/backend/var/voximplant/credentials.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/backend/var/voximplant/credentials.json @@ -0,0 +1 @@ +{} diff --git a/backend/yarn.lock b/backend/yarn.lock new file mode 100644 index 0000000..24243c7 --- /dev/null +++ b/backend/yarn.lock @@ -0,0 +1,14269 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10 + +"@amwork/voximplant-apiclient-nodejs@npm:^2.3.0-f": + version: 2.3.0-f + resolution: "@amwork/voximplant-apiclient-nodejs@npm:2.3.0-f" + dependencies: + axios: "npm:0.21.4" + form-data: "npm:2.5.1" + jsonwebtoken: "npm:8.5.1" + checksum: 10/351b00f1eacc10ff42693df1d7a3b8ebc592cc98120b439123079c02b192814bacb6bb0921201b16d7ab67d3af453b719ddffa4f03a2ec63d8e21ee4f051f018 + languageName: node + linkType: hard + +"@angular-devkit/core@npm:19.2.17": + version: 19.2.17 + resolution: "@angular-devkit/core@npm:19.2.17" + dependencies: + ajv: "npm:8.17.1" + ajv-formats: "npm:3.0.1" + jsonc-parser: "npm:3.3.1" + picomatch: "npm:4.0.2" + rxjs: "npm:7.8.1" + source-map: "npm:0.7.4" + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + checksum: 10/59cc560e46ac2a9b6e7cb36eaaf025ecf936040130fe355611201f4ab733a8b7c4a3c3cb0db5443174157b5e2319cd563967d18143113393ac7dabd62ddb4256 + languageName: node + linkType: hard + +"@angular-devkit/core@npm:19.2.19": + version: 19.2.19 + resolution: "@angular-devkit/core@npm:19.2.19" + dependencies: + ajv: "npm:8.17.1" + ajv-formats: "npm:3.0.1" + jsonc-parser: "npm:3.3.1" + picomatch: "npm:4.0.2" + rxjs: "npm:7.8.1" + source-map: "npm:0.7.4" + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + checksum: 10/d56ecde9ca344a96bbc0a132cf18df0a5f952e8d6e40211f107f7ba5f7b54832f874770ed0c8852b1bc3cd409a51a97cf8a8d21642440f50542aab9bf88d2d16 + languageName: node + linkType: hard + +"@angular-devkit/schematics-cli@npm:19.2.19": + version: 19.2.19 + resolution: "@angular-devkit/schematics-cli@npm:19.2.19" + dependencies: + "@angular-devkit/core": "npm:19.2.19" + "@angular-devkit/schematics": "npm:19.2.19" + "@inquirer/prompts": "npm:7.3.2" + ansi-colors: "npm:4.1.3" + symbol-observable: "npm:4.0.0" + yargs-parser: "npm:21.1.1" + bin: + schematics: bin/schematics.js + checksum: 10/fac274082d5fba5265a2d5dd414749ada125404f646d8f4367f43973ece87776baa036b8bd7c0ed9cec3e64d01ad88ba2450d8333f3fbac72cedfa9a5f269f41 + languageName: node + linkType: hard + +"@angular-devkit/schematics@npm:19.2.17": + version: 19.2.17 + resolution: "@angular-devkit/schematics@npm:19.2.17" + dependencies: + "@angular-devkit/core": "npm:19.2.17" + jsonc-parser: "npm:3.3.1" + magic-string: "npm:0.30.17" + ora: "npm:5.4.1" + rxjs: "npm:7.8.1" + checksum: 10/1f90652841c1bd974c1b8cd514ef1b2c2679380bf9c6f1714adaee78f1b4c24752958549bd5a92114b7b2fefcedb1ff0ab0e418277e08c4905a168aabb24d63a + languageName: node + linkType: hard + +"@angular-devkit/schematics@npm:19.2.19": + version: 19.2.19 + resolution: "@angular-devkit/schematics@npm:19.2.19" + dependencies: + "@angular-devkit/core": "npm:19.2.19" + jsonc-parser: "npm:3.3.1" + magic-string: "npm:0.30.17" + ora: "npm:5.4.1" + rxjs: "npm:7.8.1" + checksum: 10/1ef5813fc39ba0b00e628658b4b3f385719699c8e6f9c34cfaf2b6a316f1aefb7bbd2ed7633c5fd1dc99e2c769ea38546ee736bbb122bff02ccbe27bea1e1c37 + languageName: node + linkType: hard + +"@aws-crypto/crc32@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/crc32@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10/1b0a56ad4cb44c9512d8b1668dcf9306ab541d3a73829f435ca97abaec8d56f3db953db03ad0d0698754fea16fcd803d11fa42e0889bc7b803c6a030b04c63de + languageName: node + linkType: hard + +"@aws-crypto/crc32c@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/crc32c@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10/08bd1db17d7c772fa6e34b38a360ce77ad041164743113eefa8343c2af917a419697daf090c5854129ef19f3a9673ed1fd8446e03eb32c8ed52d2cc409b0dee7 + languageName: node + linkType: hard + +"@aws-crypto/sha1-browser@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha1-browser@npm:5.2.0" + dependencies: + "@aws-crypto/supports-web-crypto": "npm:^5.2.0" + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10/239f4c59cce9abd33c01117b10553fbef868a063e74faf17edb798c250d759a2578841efa2837e5e51854f52ef57dbc40780b073cae20f89ebed6a8cc7fa06f1 + languageName: node + linkType: hard + +"@aws-crypto/sha256-browser@npm:5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha256-browser@npm:5.2.0" + dependencies: + "@aws-crypto/sha256-js": "npm:^5.2.0" + "@aws-crypto/supports-web-crypto": "npm:^5.2.0" + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + "@aws-sdk/util-locate-window": "npm:^3.0.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10/2b1b701ca6caa876333b4eb2b96e5187d71ebb51ebf8e2d632690dbcdedeff038202d23adcc97e023437ed42bb1963b7b463e343687edf0635fd4b98b2edad1a + languageName: node + linkType: hard + +"@aws-crypto/sha256-js@npm:5.2.0, @aws-crypto/sha256-js@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/sha256-js@npm:5.2.0" + dependencies: + "@aws-crypto/util": "npm:^5.2.0" + "@aws-sdk/types": "npm:^3.222.0" + tslib: "npm:^2.6.2" + checksum: 10/f46aace7b873c615be4e787ab0efd0148ef7de48f9f12c7d043e05c52e52b75bb0bf6dbcb9b2852d940d7724fab7b6d5ff1469160a3dd024efe7a68b5f70df8c + languageName: node + linkType: hard + +"@aws-crypto/supports-web-crypto@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/supports-web-crypto@npm:5.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/6ed0c7e17f4f6663d057630805c45edb35d5693380c24ab52d4c453ece303c6c8a6ade9ee93c97dda77d9f6cae376ffbb44467057161c513dffa3422250edaf5 + languageName: node + linkType: hard + +"@aws-crypto/util@npm:5.2.0, @aws-crypto/util@npm:^5.2.0": + version: 5.2.0 + resolution: "@aws-crypto/util@npm:5.2.0" + dependencies: + "@aws-sdk/types": "npm:^3.222.0" + "@smithy/util-utf8": "npm:^2.0.0" + tslib: "npm:^2.6.2" + checksum: 10/f80a174c404e1ad4364741c942f440e75f834c08278fa754349fe23a6edc679d480ea9ced5820774aee58091ed270067022d8059ecf1a7ef452d58134ac7e9e1 + languageName: node + linkType: hard + +"@aws-sdk/client-s3@npm:^3.817.0": + version: 3.967.0 + resolution: "@aws-sdk/client-s3@npm:3.967.0" + dependencies: + "@aws-crypto/sha1-browser": "npm:5.2.0" + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/credential-provider-node": "npm:3.967.0" + "@aws-sdk/middleware-bucket-endpoint": "npm:3.966.0" + "@aws-sdk/middleware-expect-continue": "npm:3.965.0" + "@aws-sdk/middleware-flexible-checksums": "npm:3.967.0" + "@aws-sdk/middleware-host-header": "npm:3.965.0" + "@aws-sdk/middleware-location-constraint": "npm:3.965.0" + "@aws-sdk/middleware-logger": "npm:3.965.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.965.0" + "@aws-sdk/middleware-sdk-s3": "npm:3.967.0" + "@aws-sdk/middleware-ssec": "npm:3.965.0" + "@aws-sdk/middleware-user-agent": "npm:3.967.0" + "@aws-sdk/region-config-resolver": "npm:3.965.0" + "@aws-sdk/signature-v4-multi-region": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/util-endpoints": "npm:3.965.0" + "@aws-sdk/util-user-agent-browser": "npm:3.965.0" + "@aws-sdk/util-user-agent-node": "npm:3.967.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.2" + "@smithy/eventstream-serde-browser": "npm:^4.2.7" + "@smithy/eventstream-serde-config-resolver": "npm:^4.3.7" + "@smithy/eventstream-serde-node": "npm:^4.2.7" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-blob-browser": "npm:^4.2.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/hash-stream-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/md5-js": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.3" + "@smithy/middleware-retry": "npm:^4.4.19" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.4" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.18" + "@smithy/util-defaults-mode-node": "npm:^4.2.21" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/util-waiter": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10/53dc42207b838918207bdda601fd5222dced8f9cf09a57922dc754063f1ff9eb0bb7fca54d563a0f6b7e52c4424c4f174e2d5cb23a4295a593725ff9d4ad1831 + languageName: node + linkType: hard + +"@aws-sdk/client-ses@npm:^3.731.1": + version: 3.967.0 + resolution: "@aws-sdk/client-ses@npm:3.967.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/credential-provider-node": "npm:3.967.0" + "@aws-sdk/middleware-host-header": "npm:3.965.0" + "@aws-sdk/middleware-logger": "npm:3.965.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.965.0" + "@aws-sdk/middleware-user-agent": "npm:3.967.0" + "@aws-sdk/region-config-resolver": "npm:3.965.0" + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/util-endpoints": "npm:3.965.0" + "@aws-sdk/util-user-agent-browser": "npm:3.965.0" + "@aws-sdk/util-user-agent-node": "npm:3.967.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.2" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.3" + "@smithy/middleware-retry": "npm:^4.4.19" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.4" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.18" + "@smithy/util-defaults-mode-node": "npm:^4.2.21" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/util-waiter": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10/c8a4d62ed65feac8145174d42d18953646bb6c10d27cb5a725c69756d58d146ca0ea991d9587628dee549ae388e49b8cf315103d5d67286c274a512e427b49dc + languageName: node + linkType: hard + +"@aws-sdk/client-sso@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/client-sso@npm:3.967.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/middleware-host-header": "npm:3.965.0" + "@aws-sdk/middleware-logger": "npm:3.965.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.965.0" + "@aws-sdk/middleware-user-agent": "npm:3.967.0" + "@aws-sdk/region-config-resolver": "npm:3.965.0" + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/util-endpoints": "npm:3.965.0" + "@aws-sdk/util-user-agent-browser": "npm:3.965.0" + "@aws-sdk/util-user-agent-node": "npm:3.967.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.2" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.3" + "@smithy/middleware-retry": "npm:^4.4.19" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.4" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.18" + "@smithy/util-defaults-mode-node": "npm:^4.2.21" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/202df5a094385e5c31b5a598f376d2c80b9e6eb28f65258f0970c640626b2bb3660154e6139643b8dc763fb67856011c99f714f04f742ea5c4ffddd728822a86 + languageName: node + linkType: hard + +"@aws-sdk/core@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/core@npm:3.967.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/xml-builder": "npm:3.965.0" + "@smithy/core": "npm:^3.20.2" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/signature-v4": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.4" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/bf963572be0af3047c8d0dcb9b9b6e03b4939b0d33b4ddf819b20fb0f9df10c6918cc09efff0bb0e7e83e587d4ff39c9ed294bc579566c2a1c0598d77f15d5fa + languageName: node + linkType: hard + +"@aws-sdk/crc64-nvme@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/crc64-nvme@npm:3.965.0" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/c6a9ad1cf744c9e0f45908ade14da0d5941ce48116271ac4f58e97b27a55e0165fd7a021d3a865b9e37e3a14b3b8f074a815e21dc631800f7cdf090419918c87 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-env@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-env@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/ce2c1ad5ae4e14bedcf381f109d618cb756674193a71679ed92b51d6ba7e6dc753b1c805d13c94ed2811cee444a257e055e35b35b9c675d784ee7c2274db1755 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-http@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-http@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.4" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-stream": "npm:^4.5.8" + tslib: "npm:^2.6.2" + checksum: 10/080a61872dcb7f4032a3484536876cc8016e0aa6d314afe0cfb12d3de2f3a2075b0613d7f176b03d9c0790e7139d13dd194d11e1df9f84d3e8c1a9de438e55d6 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-ini@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/credential-provider-env": "npm:3.967.0" + "@aws-sdk/credential-provider-http": "npm:3.967.0" + "@aws-sdk/credential-provider-login": "npm:3.967.0" + "@aws-sdk/credential-provider-process": "npm:3.967.0" + "@aws-sdk/credential-provider-sso": "npm:3.967.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.967.0" + "@aws-sdk/nested-clients": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/credential-provider-imds": "npm:^4.2.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/3d110133574c6430bab8c928efec1f5d9c4b5fc17304ec6295ad6dca865287616b79b967bc5632093b3e21f10d2a39ecff648a5b822913deef0c420ca8310053 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-login@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-login@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/nested-clients": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/78c2a937e30d43c3dfddb8531ab8a5aa44fde359de1df185c2ab1138039789b83fc8d431967bf534cb06814577620397cc6594b0fe639596d87e3a3eb4d28ae2 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-node@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.967.0" + dependencies: + "@aws-sdk/credential-provider-env": "npm:3.967.0" + "@aws-sdk/credential-provider-http": "npm:3.967.0" + "@aws-sdk/credential-provider-ini": "npm:3.967.0" + "@aws-sdk/credential-provider-process": "npm:3.967.0" + "@aws-sdk/credential-provider-sso": "npm:3.967.0" + "@aws-sdk/credential-provider-web-identity": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/credential-provider-imds": "npm:^4.2.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/7fc672af134d058fae3497fb776573164c4f973e6145bc49c24f0162063351de2878e5d1f4909335888c70ebacf62ae0ac26819b4d9388b7d56b117c261cd860 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-process@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-process@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/e6dbf90b17311050c70329c803a974c0bad0be343385793b5fc3d4101682c7c162dbdea5a941965c03edde9f5714ffaf86e32c72c3d3b0261277d99d7d439650 + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-sso@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.967.0" + dependencies: + "@aws-sdk/client-sso": "npm:3.967.0" + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/token-providers": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/45a3aad27f304fce6ad46caf32b617710a39211c8859874332de02aa0e721e0f9fa028ba13e8c47387d54ee44bcc770d311523e895058569e2987d2b0d8a628e + languageName: node + linkType: hard + +"@aws-sdk/credential-provider-web-identity@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/nested-clients": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/ba6a5be560aedf3dfff52b51f3b2b26cdbe0e721818e51fc4509ea636ac0408470c6bde4368b8dd9428a87f618f454672657048a8604d17d902879009a5b2f9e + languageName: node + linkType: hard + +"@aws-sdk/middleware-bucket-endpoint@npm:3.966.0": + version: 3.966.0 + resolution: "@aws-sdk/middleware-bucket-endpoint@npm:3.966.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/util-arn-parser": "npm:3.966.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-config-provider": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/538a39c2d3b95e9e3399dd4c45677f526c56c93dd9aeabc6263e7ce524a513cbcc67cccc9e4b739453cd001928922d5c96813360ac0989d548c27ea769409701 + languageName: node + linkType: hard + +"@aws-sdk/middleware-expect-continue@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/middleware-expect-continue@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/5f0f47c43f1c97398092eeabe6d71e31c76614bef2e4540923c57a3c769a21e5acf890aaa4ebd70708bd78c7da1ff9e43b789b12d4269496052c266e910e63be + languageName: node + linkType: hard + +"@aws-sdk/middleware-flexible-checksums@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.967.0" + dependencies: + "@aws-crypto/crc32": "npm:5.2.0" + "@aws-crypto/crc32c": "npm:5.2.0" + "@aws-crypto/util": "npm:5.2.0" + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/crc64-nvme": "npm:3.965.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/is-array-buffer": "npm:^4.2.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/2297b2841935ec72f3bdbfd69e4adc125a27513c486e6ec48067c31f4687c9b27add9f781e039ee2d9bea70b0644da47a0896ef3a532a0d7352364af32fb919a + languageName: node + linkType: hard + +"@aws-sdk/middleware-host-header@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/middleware-host-header@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/b97bea5587c09c7abd99c0d6ddaca5e4b4c234f6a8f997a4b2fb7ce41e638ddbc214e8766cbb9f179f5bdd6645943305469f62e492bc223e62e018a2ae4b5574 + languageName: node + linkType: hard + +"@aws-sdk/middleware-location-constraint@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/middleware-location-constraint@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/632fcd5c587171d563748577023f81fa63aadc2241559aff61abf60256e216d9c09d7af23b885980caaad29a734ff97251aed11b23891ab6e4347d4730bb0dba + languageName: node + linkType: hard + +"@aws-sdk/middleware-logger@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/middleware-logger@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/b0f4363b698e1254f9a4ddced3aabf0bc2aa9d1e570b76b70f42967ba879a72cc10aa99f53a0e881916de414b5a891b768a22bdc93d3facc15ece1cc0db818b2 + languageName: node + linkType: hard + +"@aws-sdk/middleware-recursion-detection@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/middleware-recursion-detection@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@aws/lambda-invoke-store": "npm:^0.2.2" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/10c231d847fdea3b5affae8a32c51b91ca8d1beacfdbd6f6ee19b590faa7a3596eea656976fb2588cec6871cbea53b8fc824877b613e2c0336fa7bdb2b0eaa5f + languageName: node + linkType: hard + +"@aws-sdk/middleware-sdk-s3@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/util-arn-parser": "npm:3.966.0" + "@smithy/core": "npm:^3.20.2" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/signature-v4": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.4" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-config-provider": "npm:^4.2.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/ce738c27ea8c5d11359d76c79718b1ce0eb07ae96e2d8b6c2437cc6369a00c9e7a85b7076d3685a5c2a49a861304acbecf5eb159d10cd7f3d93026c1d265fb57 + languageName: node + linkType: hard + +"@aws-sdk/middleware-ssec@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/middleware-ssec@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/0ef6ca70ca23c29759df106a6537a8fd9d2509f6b814f13e31b9e623fa9a3d3d3db0a2aee316c60dd1b64f2715f12ce49bf49968fc657ebd336eeb443072ec25 + languageName: node + linkType: hard + +"@aws-sdk/middleware-user-agent@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/middleware-user-agent@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/util-endpoints": "npm:3.965.0" + "@smithy/core": "npm:^3.20.2" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/da8b8389ecd2bb5d53cd571a2b440f0aefd2fe0cfdf942d765e0ff8806bb56d171ecc2e4032becd16e5f6de01322e5234bac305b75e7d63367738e3dceaf81a8 + languageName: node + linkType: hard + +"@aws-sdk/nested-clients@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/nested-clients@npm:3.967.0" + dependencies: + "@aws-crypto/sha256-browser": "npm:5.2.0" + "@aws-crypto/sha256-js": "npm:5.2.0" + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/middleware-host-header": "npm:3.965.0" + "@aws-sdk/middleware-logger": "npm:3.965.0" + "@aws-sdk/middleware-recursion-detection": "npm:3.965.0" + "@aws-sdk/middleware-user-agent": "npm:3.967.0" + "@aws-sdk/region-config-resolver": "npm:3.965.0" + "@aws-sdk/types": "npm:3.965.0" + "@aws-sdk/util-endpoints": "npm:3.965.0" + "@aws-sdk/util-user-agent-browser": "npm:3.965.0" + "@aws-sdk/util-user-agent-node": "npm:3.967.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/core": "npm:^3.20.2" + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/hash-node": "npm:^4.2.7" + "@smithy/invalid-dependency": "npm:^4.2.7" + "@smithy/middleware-content-length": "npm:^4.2.7" + "@smithy/middleware-endpoint": "npm:^4.4.3" + "@smithy/middleware-retry": "npm:^4.4.19" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/smithy-client": "npm:^4.10.4" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-body-length-node": "npm:^4.2.1" + "@smithy/util-defaults-mode-browser": "npm:^4.3.18" + "@smithy/util-defaults-mode-node": "npm:^4.2.21" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/95f461e0a48b8fd40ba16e181e9021acd5c554d39f85d556cc06d771034fb1936c63e58bfc7069bc1e8b64037745e8cd7570f8c040d7b49c5033585d0a9ea045 + languageName: node + linkType: hard + +"@aws-sdk/region-config-resolver@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/region-config-resolver@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/7e75ea9ddb14b588abbc015c920b6e4b6888c4afd19b7be456668597b16f2b0c8edd6808839e1d17c4407edbec967d3f5dab970ba6041dfd0966685ecefd2a07 + languageName: node + linkType: hard + +"@aws-sdk/signature-v4-multi-region@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.967.0" + dependencies: + "@aws-sdk/middleware-sdk-s3": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/signature-v4": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/56d4ab1db6255d89749e193033fd91de3e1fd771bfe184934615fca94a80d8e3e7cbae91f807125fbd5df112ef446e59c7faf9b196a7b57ce0c83f5bdddb75b3 + languageName: node + linkType: hard + +"@aws-sdk/token-providers@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/token-providers@npm:3.967.0" + dependencies: + "@aws-sdk/core": "npm:3.967.0" + "@aws-sdk/nested-clients": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/a0573434431a2a412209a0d010a45bc3d1687ee4a7702db956a727941f03a9255f04994f1f8faca2cd47337bcdd4338734df433d7b89f454156688739cf0514c + languageName: node + linkType: hard + +"@aws-sdk/types@npm:3.965.0, @aws-sdk/types@npm:^3.222.0": + version: 3.965.0 + resolution: "@aws-sdk/types@npm:3.965.0" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/7382007ab2be375cb9fc0bdb8fd546dd56ac1b6301bec0bc8d24f7a123dce68f41a15f7017a7c1b0ae89fb08c831c3ce2476b24914783a307691b847c47a1028 + languageName: node + linkType: hard + +"@aws-sdk/util-arn-parser@npm:3.966.0": + version: 3.966.0 + resolution: "@aws-sdk/util-arn-parser@npm:3.966.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/fbbbdf9150bb78be0253a83fb18c1dbe1e12436824805e2a2dd6625115485be7beec581e4d35b448b2637ab4a17800fc40931c933f19182770d1ecfb99d5b3bd + languageName: node + linkType: hard + +"@aws-sdk/util-endpoints@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/util-endpoints@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-endpoints": "npm:^3.2.7" + tslib: "npm:^2.6.2" + checksum: 10/d4f372fe6159a5df94269a46376ec7138defe333483416648e6e0befe254381426c80fbc2480eaac880cf45f97e4ba514bfcec2e9f4e685191ccd38fec2cb465 + languageName: node + linkType: hard + +"@aws-sdk/util-locate-window@npm:^3.0.0": + version: 3.965.0 + resolution: "@aws-sdk/util-locate-window@npm:3.965.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/c6b956699e9092df26cda39af2008eb56f3d63b5e8b4b9135e37488a11ea83a3de269a561919459db7f45bcb9232073f9bc670d3452f0f40c52b05372b078022 + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-browser@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/util-user-agent-browser@npm:3.965.0" + dependencies: + "@aws-sdk/types": "npm:3.965.0" + "@smithy/types": "npm:^4.11.0" + bowser: "npm:^2.11.0" + tslib: "npm:^2.6.2" + checksum: 10/5784b639240493f2d0741424a1b393d5058c1c4982cf8044029a1b241159e0ed49e6f45513b0cba0e534c373964ad10c6177ea8dfeda81ce46b323371ff5941f + languageName: node + linkType: hard + +"@aws-sdk/util-user-agent-node@npm:3.967.0": + version: 3.967.0 + resolution: "@aws-sdk/util-user-agent-node@npm:3.967.0" + dependencies: + "@aws-sdk/middleware-user-agent": "npm:3.967.0" + "@aws-sdk/types": "npm:3.965.0" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + peerDependencies: + aws-crt: ">=1.0.0" + peerDependenciesMeta: + aws-crt: + optional: true + checksum: 10/621f98862c3b7b85fd8ab664e516485e37025acbbe6b106363e924b157a5b9e3804b93021f93d859291cb89f7724f9b82980bdff483158fef7688bbc804a43c3 + languageName: node + linkType: hard + +"@aws-sdk/xml-builder@npm:3.965.0": + version: 3.965.0 + resolution: "@aws-sdk/xml-builder@npm:3.965.0" + dependencies: + "@smithy/types": "npm:^4.11.0" + fast-xml-parser: "npm:5.2.5" + tslib: "npm:^2.6.2" + checksum: 10/7a6c4346d0fc3bad2e87b1782ccf777935369e29d5d2fe89534739d40f76478c319675cbb18d63dfd709a5c299796723466f024a0a7305f8c9122af9bd2c3f78 + languageName: node + linkType: hard + +"@aws/lambda-invoke-store@npm:^0.2.2": + version: 0.2.3 + resolution: "@aws/lambda-invoke-store@npm:0.2.3" + checksum: 10/d0efa8ca73b2d8dc0bf634525eefa1b72cda85f5d47366264849343a6f2860cfa5c52b7f766a16b78da8406bbd3ee975da3abb1dbe38183f8af95413eafeb256 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.16.7": + version: 7.28.6 + resolution: "@babel/code-frame@npm:7.28.6" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.28.5" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10/93e7ed9e039e3cb661bdb97c26feebafacc6ec13d745881dae5c7e2708f579475daebe7a3b5d23b183bb940b30744f52f4a5bcb65b4df03b79d82fcb38495784 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10/0ae29cc2005084abdae2966afdb86ed14d41c9c37db02c3693d5022fba9f5d59b011d039380b8e537c34daf117c549f52b452398f576e908fb9db3c7abbb3a00 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10/8e5d9b0133702cfacc7f368bf792f0f8ac0483794877c6dca5fcb73810ee138e27527701826fb58a40a004f3a5ec0a2f3c3dd5e326d262530b119918f3132ba7 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.6.0, @babel/parser@npm:^7.9.6": + version: 7.28.6 + resolution: "@babel/parser@npm:7.28.6" + dependencies: + "@babel/types": "npm:^7.28.6" + bin: + parser: ./bin/babel-parser.js + checksum: 10/483a6fb5f9876ec9cbbb98816f2c94f39ae4d1158d35f87e1c4bf19a1f56027c96a1a3962ff0c8c46e8322a6d9e1c80d26b7f9668410df13d5b5769d9447b010 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.26.10, @babel/runtime@npm:^7.28.4": + version: 7.28.6 + resolution: "@babel/runtime@npm:7.28.6" + checksum: 10/fbcd439cb74d4a681958eb064c509829e3f46d8a4bfaaf441baa81bb6733d1e680bccc676c813883d7741bcaada1d0d04b15aa320ef280b5734e2192b50decf9 + languageName: node + linkType: hard + +"@babel/types@npm:^7.28.6, @babel/types@npm:^7.6.1, @babel/types@npm:^7.9.6": + version: 7.28.6 + resolution: "@babel/types@npm:7.28.6" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10/f9c6e52b451065aae5654686ecfc7de2d27dd0fbbc204ee2bd912a71daa359521a32f378981b1cf333ace6c8f86928814452cb9f388a7da59ad468038deb6b5f + languageName: node + linkType: hard + +"@borewit/text-codec@npm:^0.2.1": + version: 0.2.1 + resolution: "@borewit/text-codec@npm:0.2.1" + checksum: 10/3d7e824ac4d3ea16e6e910a7f2bac79f262602c3dbc2f525fd9b86786269c5d7bbd673090a0277d7f92652e534f263e292d5ace080bc9bdf57dc6921c1973f70 + languageName: node + linkType: hard + +"@camunda8/orchestration-cluster-api@npm:^1.2.2": + version: 1.2.3 + resolution: "@camunda8/orchestration-cluster-api@npm:1.2.3" + dependencies: + p-retry: "npm:^6.0.1" + typed-env: "npm:^2.0.0" + zod: "npm:^4" + checksum: 10/708463de5d80e805e01548b69cc89510d1cc6e22fb70a446ae104ce4e967462f130125dd03d64bf1a82e14b4fe79f40ae1c82ed8bcb9854da875227a28d36a08 + languageName: node + linkType: hard + +"@camunda8/sdk@npm:^8.7.9": + version: 8.8.4 + resolution: "@camunda8/sdk@npm:8.8.4" + dependencies: + "@camunda8/orchestration-cluster-api": "npm:^1.2.2" + "@grpc/grpc-js": "npm:1.12.5" + "@grpc/proto-loader": "npm:0.7.13" + "@types/form-data": "npm:^2.2.1" + "@types/node": "npm:^22.18.6" + chalk: "npm:^2.4.2" + console-stamp: "npm:^3.0.2" + dayjs: "npm:^1.8.15" + debug: "npm:^4.3.4" + fast-xml-parser: "npm:^4.1.3" + form-data: "npm:^4.0.2" + got: "npm:^11.8.6" + jwt-decode: "npm:^4.0.0" + lodash.mergewith: "npm:^4.6.2" + long: "npm:^4.0.0" + lossless-json: "npm:^4.0.1" + p-cancelable: "npm:^2.1.1" + promise-retry: "npm:^1.1.1" + reflect-metadata: "npm:^0.2.1" + stack-trace: "npm:0.0.10" + typed-duration: "npm:^1.0.12" + typed-emitter: "npm:^2.1.0" + typed-env: "npm:^2.0.0" + win-ca: "npm:3.5.1" + winston: "npm:^3.14.2" + dependenciesMeta: + win-ca: + optional: true + checksum: 10/0e1b29844a965c44485cb4068b710ed31e617ad1d7e95845eff5990122d4db7f2eba7a68361e574a5b623f7ed4eb88fb4d42be186c185ff5c645790299dc7c36 + languageName: node + linkType: hard + +"@colors/colors@npm:1.5.0": + version: 1.5.0 + resolution: "@colors/colors@npm:1.5.0" + checksum: 10/9d226461c1e91e95f067be2bdc5e6f99cfe55a721f45afb44122e23e4b8602eeac4ff7325af6b5a369f36396ee1514d3809af3f57769066d80d83790d8e53339 + languageName: node + linkType: hard + +"@colors/colors@npm:1.6.0, @colors/colors@npm:^1.6.0": + version: 1.6.0 + resolution: "@colors/colors@npm:1.6.0" + checksum: 10/66d00284a3a9a21e5e853b256942e17edbb295f4bd7b9aa7ef06bbb603568d5173eb41b0f64c1e51748bc29d382a23a67d99956e57e7431c64e47e74324182d9 + languageName: node + linkType: hard + +"@cspotcode/source-map-support@npm:^0.8.0": + version: 0.8.1 + resolution: "@cspotcode/source-map-support@npm:0.8.1" + dependencies: + "@jridgewell/trace-mapping": "npm:0.3.9" + checksum: 10/b6e38a1712fab242c86a241c229cf562195aad985d0564bd352ac404be583029e89e93028ffd2c251d2c407ecac5fb0cbdca94a2d5c10f29ac806ede0508b3ff + languageName: node + linkType: hard + +"@css-inline/css-inline-android-arm-eabi@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-android-arm-eabi@npm:0.14.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@css-inline/css-inline-android-arm64@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-android-arm64@npm:0.14.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@css-inline/css-inline-darwin-arm64@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-darwin-arm64@npm:0.14.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@css-inline/css-inline-darwin-x64@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-darwin-x64@npm:0.14.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@css-inline/css-inline-linux-arm-gnueabihf@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-linux-arm-gnueabihf@npm:0.14.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@css-inline/css-inline-linux-arm64-gnu@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-linux-arm64-gnu@npm:0.14.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@css-inline/css-inline-linux-arm64-musl@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-linux-arm64-musl@npm:0.14.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@css-inline/css-inline-linux-x64-gnu@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-linux-x64-gnu@npm:0.14.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@css-inline/css-inline-linux-x64-musl@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-linux-x64-musl@npm:0.14.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@css-inline/css-inline-win32-x64-msvc@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline-win32-x64-msvc@npm:0.14.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@css-inline/css-inline@npm:0.14.1": + version: 0.14.1 + resolution: "@css-inline/css-inline@npm:0.14.1" + dependencies: + "@css-inline/css-inline-android-arm-eabi": "npm:0.14.1" + "@css-inline/css-inline-android-arm64": "npm:0.14.1" + "@css-inline/css-inline-darwin-arm64": "npm:0.14.1" + "@css-inline/css-inline-darwin-x64": "npm:0.14.1" + "@css-inline/css-inline-linux-arm-gnueabihf": "npm:0.14.1" + "@css-inline/css-inline-linux-arm64-gnu": "npm:0.14.1" + "@css-inline/css-inline-linux-arm64-musl": "npm:0.14.1" + "@css-inline/css-inline-linux-x64-gnu": "npm:0.14.1" + "@css-inline/css-inline-linux-x64-musl": "npm:0.14.1" + "@css-inline/css-inline-win32-x64-msvc": "npm:0.14.1" + dependenciesMeta: + "@css-inline/css-inline-android-arm-eabi": + optional: true + "@css-inline/css-inline-android-arm64": + optional: true + "@css-inline/css-inline-darwin-arm64": + optional: true + "@css-inline/css-inline-darwin-x64": + optional: true + "@css-inline/css-inline-linux-arm-gnueabihf": + optional: true + "@css-inline/css-inline-linux-arm64-gnu": + optional: true + "@css-inline/css-inline-linux-arm64-musl": + optional: true + "@css-inline/css-inline-linux-x64-gnu": + optional: true + "@css-inline/css-inline-linux-x64-musl": + optional: true + "@css-inline/css-inline-win32-x64-msvc": + optional: true + checksum: 10/60523f5cb449ae41c671281304aa4ea76d6123b3265382a3f1710f4d2d50c37edf2d3e0f89bf7a486772b8893e1653a179e80dad2205facdd39e8c34afb1c03b + languageName: node + linkType: hard + +"@dabh/diagnostics@npm:^2.0.8": + version: 2.0.8 + resolution: "@dabh/diagnostics@npm:2.0.8" + dependencies: + "@so-ric/colorspace": "npm:^1.1.6" + enabled: "npm:2.0.x" + kuler: "npm:^2.0.0" + checksum: 10/ac2267a4ee1874f608493f21d386ea29f0acac6716124e26e3e48e01ce5706b095585a14adce1bee14b6567d3b8fdd0c5a0bbb7ab0e15c9a743d55eb02f093ce + languageName: node + linkType: hard + +"@date-fns/tz@npm:^1.2.0": + version: 1.4.1 + resolution: "@date-fns/tz@npm:1.4.1" + checksum: 10/062097590005cce3da4c7d9880f9c77d386cff5b4dd58fa3dde3c346a8b2e4f4a8025a613306351a7cad8eb71178a0f67b4840d5884f73aa4c759085fac92063 + languageName: node + linkType: hard + +"@date-fns/utc@npm:^2.1.0": + version: 2.1.1 + resolution: "@date-fns/utc@npm:2.1.1" + checksum: 10/80db3b4afbe261ed441f4c0fe688a5979ab7377fafa8100920b46bfd5f3dcf2f0ded9e63869fdbbedad0f5fc05e411e88bab1e3374e5173a822052666f01550a + languageName: node + linkType: hard + +"@emnapi/core@npm:^1.7.1": + version: 1.8.1 + resolution: "@emnapi/core@npm:1.8.1" + dependencies: + "@emnapi/wasi-threads": "npm:1.1.0" + tslib: "npm:^2.4.0" + checksum: 10/904ea60c91fc7d8aeb4a8f2c433b8cfb47c50618f2b6f37429fc5093c857c6381c60628a5cfbc3a7b0d75b0a288f21d4ed2d4533e82f92c043801ef255fd6a5c + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.7.0, @emnapi/runtime@npm:^1.7.1": + version: 1.8.1 + resolution: "@emnapi/runtime@npm:1.8.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/26725e202d4baefdc4a6ba770f703dfc80825a27c27a08c22bac1e1ce6f8f75c47b4fe9424d9b63239463c33ef20b650f08d710da18dfa1164a95e5acb865dba + languageName: node + linkType: hard + +"@emnapi/wasi-threads@npm:1.1.0": + version: 1.1.0 + resolution: "@emnapi/wasi-threads@npm:1.1.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/0d557e75262d2f4c95cb2a456ba0785ef61f919ce488c1d76e5e3acfd26e00c753ef928cd80068363e0c166ba8cc0141305daf0f81aad5afcd421f38f11e0f4e + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10/863b5467868551c9ae34d03eefe634633d08f623fc7b19d860f8f26eb6f303c1a5934253124163bee96181e45ed22bf27473dccc295937c3078493a4a8c9eddd + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.12.2": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10/049b280fddf71dd325514e0a520024969431dc3a8b02fa77476e6820e9122f28ab4c9168c11821f91a27982d2453bcd7a66193356ea84e84fb7c8d793be1ba0c + languageName: node + linkType: hard + +"@eslint/config-array@npm:^0.21.1": + version: 0.21.1 + resolution: "@eslint/config-array@npm:0.21.1" + dependencies: + "@eslint/object-schema": "npm:^2.1.7" + debug: "npm:^4.3.1" + minimatch: "npm:^3.1.2" + checksum: 10/6eaa0435972f735ce52d581f355a0b616e50a9b8a73304a7015398096e252798b9b3b968a67b524eefb0fdeacc57c4d960f0ec6432abe1c1e24be815b88c5d18 + languageName: node + linkType: hard + +"@eslint/config-helpers@npm:^0.4.2": + version: 0.4.2 + resolution: "@eslint/config-helpers@npm:0.4.2" + dependencies: + "@eslint/core": "npm:^0.17.0" + checksum: 10/3f2b4712d8e391c36ec98bc200f7dea423dfe518e42956569666831b89ede83b33120c761dfd3ab6347d8e8894a6d4af47254a18d464a71c6046fd88065f6daf + languageName: node + linkType: hard + +"@eslint/core@npm:^0.17.0": + version: 0.17.0 + resolution: "@eslint/core@npm:0.17.0" + dependencies: + "@types/json-schema": "npm:^7.0.15" + checksum: 10/f9a428cc651ec15fb60d7d60c2a7bacad4666e12508320eafa98258e976fafaa77d7be7be91519e75f801f15f830105420b14a458d4aab121a2b0a59bc43517b + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^3.3.1": + version: 3.3.3 + resolution: "@eslint/eslintrc@npm:3.3.3" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^10.0.1" + globals: "npm:^14.0.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.1" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10/b586a364ff15ce1b68993aefc051ca330b1fece15fb5baf4a708d00113f9a14895cffd84a5f24c5a97bd4b4321130ab2314f90aa462a250f6b859c2da2cba1f3 + languageName: node + linkType: hard + +"@eslint/js@npm:9.39.2, @eslint/js@npm:^9.27.0": + version: 9.39.2 + resolution: "@eslint/js@npm:9.39.2" + checksum: 10/6b7f676746f3111b5d1b23715319212ab9297868a0fa9980d483c3da8965d5841673aada2d5653e85a3f7156edee0893a7ae7035211b4efdcb2848154bb947f2 + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^2.1.7": + version: 2.1.7 + resolution: "@eslint/object-schema@npm:2.1.7" + checksum: 10/946ef5d6235b4d1c0907c6c6e6429c8895f535380c562b7705c131f63f2e961b06e8785043c86a293da48e0a60c6286d98ba395b8b32ea55561fe6e4417cb7e4 + languageName: node + linkType: hard + +"@eslint/plugin-kit@npm:^0.4.1": + version: 0.4.1 + resolution: "@eslint/plugin-kit@npm:0.4.1" + dependencies: + "@eslint/core": "npm:^0.17.0" + levn: "npm:^0.4.1" + checksum: 10/c5947d0ffeddca77d996ac1b886a66060c1a15ed1d5e425d0c7e7d7044a4bd3813fc968892d03950a7831c9b89368a2f7b281e45dd3c74a048962b74bf3a1cb4 + languageName: node + linkType: hard + +"@esm2cjs/cacheable-lookup@npm:^7.0.0": + version: 7.0.0 + resolution: "@esm2cjs/cacheable-lookup@npm:7.0.0" + checksum: 10/35ca669df48ca9de7d0c0cb18e775637d79e0624600993175a9fa353ee2f2e61ddd4a0dff65d60d630fecf87fcf39e0973923226fb81d999563ffd81f7388d1f + languageName: node + linkType: hard + +"@faker-js/faker@npm:^9.8.0": + version: 9.9.0 + resolution: "@faker-js/faker@npm:9.9.0" + checksum: 10/b24b1be0fb3090d54abaaa3a814f37d1a7551f1207dcd330a1af2c70f6312a8b95ebb82653577757dd62f4cbbb56caa3c83513053253654dc3e286a05040ecbe + languageName: node + linkType: hard + +"@fast-csv/format@npm:4.3.5": + version: 4.3.5 + resolution: "@fast-csv/format@npm:4.3.5" + dependencies: + "@types/node": "npm:^14.0.1" + lodash.escaperegexp: "npm:^4.1.2" + lodash.isboolean: "npm:^3.0.3" + lodash.isequal: "npm:^4.5.0" + lodash.isfunction: "npm:^3.0.9" + lodash.isnil: "npm:^4.0.0" + checksum: 10/94fcc061422ad82c7973926acba96c7f0e539d39f8c9c986f4d369ba0bbda535407a5243ddafa0a41a310261205824577b66e74bd0ed81aaaff0d9c33db9e426 + languageName: node + linkType: hard + +"@fast-csv/parse@npm:4.3.6": + version: 4.3.6 + resolution: "@fast-csv/parse@npm:4.3.6" + dependencies: + "@types/node": "npm:^14.0.1" + lodash.escaperegexp: "npm:^4.1.2" + lodash.groupby: "npm:^4.6.0" + lodash.isfunction: "npm:^3.0.9" + lodash.isnil: "npm:^4.0.0" + lodash.isundefined: "npm:^3.0.1" + lodash.uniq: "npm:^4.5.0" + checksum: 10/12b338134de8801c895f50f8bb5315b67a6181d5b39d99445be80898633541b06be77a2b14a8395fc51c3f028138e9fb8a2b5bc5258f50c08bef22fd9dd07ee0 + languageName: node + linkType: hard + +"@grpc/grpc-js@npm:1.12.5": + version: 1.12.5 + resolution: "@grpc/grpc-js@npm:1.12.5" + dependencies: + "@grpc/proto-loader": "npm:^0.7.13" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10/4f8ead236dcab4d94e15e62d65ad2d93732d37f5cc52ffafe67ae00f69eae4a4c97d6d34a1b9eac9f30206468f2d15302ea6649afcba1d38929afa9d1e7c12d5 + languageName: node + linkType: hard + +"@grpc/grpc-js@npm:^1.13.2": + version: 1.14.3 + resolution: "@grpc/grpc-js@npm:1.14.3" + dependencies: + "@grpc/proto-loader": "npm:^0.8.0" + "@js-sdsl/ordered-map": "npm:^4.4.2" + checksum: 10/bb9bfe2f749179ae5ac7774d30486dfa2e0b004518c28de158b248e0f6f65f40138f01635c48266fa540670220f850216726e3724e1eb29d078817581c96e4db + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:0.7.13": + version: 0.7.13 + resolution: "@grpc/proto-loader@npm:0.7.13" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.2.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/7e2d842c2061cbaf6450c71da0077263be3bab165454d5c8a3e1ae4d3c6d2915f02fd27da63ff01f05e127b1221acd40705273f5d29303901e60514e852992f4 + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.7.13, @grpc/proto-loader@npm:^0.7.5": + version: 0.7.15 + resolution: "@grpc/proto-loader@npm:0.7.15" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.2.5" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/2e2b33ace8bc34211522751a9e654faf9ac997577a9e9291b1619b4c05d7878a74d2101c3bc43b2b2b92bca7509001678fb191d4eb100684cc2910d66f36c373 + languageName: node + linkType: hard + +"@grpc/proto-loader@npm:^0.8.0": + version: 0.8.0 + resolution: "@grpc/proto-loader@npm:0.8.0" + dependencies: + lodash.camelcase: "npm:^4.3.0" + long: "npm:^5.0.0" + protobufjs: "npm:^7.5.3" + yargs: "npm:^17.7.2" + bin: + proto-loader-gen-types: build/bin/proto-loader-gen-types.js + checksum: 10/216813bdca52cd3a84ac355ad93c2c3f54252be47327692fe666fd85baa5b1d50aa681ebc5626ab08926564fb2deae3b2ea435aa5bd883197650bbe56f2ae108 + languageName: node + linkType: hard + +"@humanfs/core@npm:^0.19.1": + version: 0.19.1 + resolution: "@humanfs/core@npm:0.19.1" + checksum: 10/270d936be483ab5921702623bc74ce394bf12abbf57d9145a69e8a0d1c87eb1c768bd2d93af16c5705041e257e6d9cc7529311f63a1349f3678abc776fc28523 + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.6": + version: 0.16.7 + resolution: "@humanfs/node@npm:0.16.7" + dependencies: + "@humanfs/core": "npm:^0.19.1" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10/b3633d3dce898592cac515ba5e6693c78e6be92863541d3eaf2c009b10f52b2fa62ff6e6e06f240f2447ddbe7b5f1890bc34e9308470675c876eee207553a08d + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10/e993950e346331e5a32eefb27948ecdee2a2c4ab3f072b8f566cd213ef485dd50a3ca497050608db91006f5479e43f91a439aef68d2a313bd3ded06909c7c5b3 + languageName: node + linkType: hard + +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10/0b32cfd362bea7a30fbf80bb38dcaf77fee9c2cae477ee80b460871d03590110ac9c77d654f04ec5beaf71b6f6a89851bdf6c1e34ccdf2f686bd86fcd97d9e61 + languageName: node + linkType: hard + +"@img/colour@npm:^1.0.0": + version: 1.0.0 + resolution: "@img/colour@npm:1.0.0" + checksum: 10/bd248d7c4b8ba99a72b22a005a63f1d3309ee8343a74b6d0d1314bae300a3096919991a09e9a9243cf6ca50e393b4c5a7e065488ed616c3b58d052473240b812 + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-darwin-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.2.4" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.2.4" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-arm@npm:1.2.4" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.2.4" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.2.4" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.2.4" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linux-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.2.4" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.2.4": + version: 1.2.4 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.2.4" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-arm@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-ppc64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-riscv64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-s390x@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linux-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-linuxmusl-x64@npm:0.34.5" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-wasm32@npm:0.34.5" + dependencies: + "@emnapi/runtime": "npm:^1.7.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-arm64@npm:0.34.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-ia32@npm:0.34.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.34.5": + version: 0.34.5 + resolution: "@img/sharp-win32-x64@npm:0.34.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@inquirer/ansi@npm:^1.0.2": + version: 1.0.2 + resolution: "@inquirer/ansi@npm:1.0.2" + checksum: 10/d1496e573a63ee6752bcf3fc93375cdabc55b0d60f0588fe7902282c710b223252ad318ff600ee904e48555634663b53fda517f5b29ce9fbda90bfae18592fbc + languageName: node + linkType: hard + +"@inquirer/checkbox@npm:^4.1.2, @inquirer/checkbox@npm:^4.3.2": + version: 4.3.2 + resolution: "@inquirer/checkbox@npm:4.3.2" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/4ac5dd2679981e23f066c51c605cb1c63ccda9ea6e1ad895e675eb26702aaf6cf961bf5ca3acd832efba5edcf9883b6742002c801673d2b35c123a7fa7db7b23 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^5.1.21, @inquirer/confirm@npm:^5.1.6": + version: 5.1.21 + resolution: "@inquirer/confirm@npm:5.1.21" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/a107aa0073965ea510affb9e5b55baf40333503d600970c458c07770cd4e0eee01efc4caba66f0409b0fadc9550d127329622efb543cffcabff3ad0e7f865372 + languageName: node + linkType: hard + +"@inquirer/core@npm:^10.3.2": + version: 10.3.2 + resolution: "@inquirer/core@npm:10.3.2" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + cli-width: "npm:^4.1.0" + mute-stream: "npm:^2.0.0" + signal-exit: "npm:^4.1.0" + wrap-ansi: "npm:^6.2.0" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/eb434bdf0ae7d904367003c772bcd80cbf679f79c087c99a4949fd7288e9a2f713ec3ea63381b9a001f52389ab56a77fcd88d64d81a03b1195193410ce8971c2 + languageName: node + linkType: hard + +"@inquirer/editor@npm:^4.2.23, @inquirer/editor@npm:^4.2.7": + version: 4.2.23 + resolution: "@inquirer/editor@npm:4.2.23" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/external-editor": "npm:^1.0.3" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/f91b9aadba6ea28a0f4ea5f075af421e076262aebbd737e1b9779f086fa9d559d064e9942a581544645d1dcf56d6b685e8063fe46677880fbca73f6de4e4e7c5 + languageName: node + linkType: hard + +"@inquirer/expand@npm:^4.0.23, @inquirer/expand@npm:^4.0.9": + version: 4.0.23 + resolution: "@inquirer/expand@npm:4.0.23" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/73ad1d6376e5efe2a452c33494d6d16ee2670c638ae470a795fdff4acb59a8e032e38e141f87b603b6e96320977519b375dac6471d86d5e3087a9c1db40e3111 + languageName: node + linkType: hard + +"@inquirer/external-editor@npm:^1.0.3": + version: 1.0.3 + resolution: "@inquirer/external-editor@npm:1.0.3" + dependencies: + chardet: "npm:^2.1.1" + iconv-lite: "npm:^0.7.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/c95d7237a885b32031715089f92820525731d4d3c2bd7afdb826307dc296cc2b39e7a644b0bb265441963348cca42e7785feb29c3aaf18fd2b63131769bf6587 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^1.0.15": + version: 1.0.15 + resolution: "@inquirer/figures@npm:1.0.15" + checksum: 10/3f858807f361ca29f41ec1076bbece4098cc140d86a06159d42c6e3f6e4d9bec9e10871ccfcbbaa367d6a8462b01dff89f2b1b157d9de6e8726bec85533f525c + languageName: node + linkType: hard + +"@inquirer/input@npm:^4.1.6, @inquirer/input@npm:^4.3.1": + version: 4.3.1 + resolution: "@inquirer/input@npm:4.3.1" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/713aaa4c94263299fbd7adfd65378f788cac1b5047f2b7e1ea349ca669db6c7c91b69ab6e2f6660cdbc28c7f7888c5c77ab4433bd149931597e43976d1ba5f34 + languageName: node + linkType: hard + +"@inquirer/number@npm:^3.0.23, @inquirer/number@npm:^3.0.9": + version: 3.0.23 + resolution: "@inquirer/number@npm:3.0.23" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/50694807b71746e15ed69d100aae3c8014d83c90aa660e8a179fe0db1046f26d727947542f64e24cc8b969a61659cb89fe36208cc2b59c1816382b598e686dd2 + languageName: node + linkType: hard + +"@inquirer/password@npm:^4.0.23, @inquirer/password@npm:^4.0.9": + version: 4.0.23 + resolution: "@inquirer/password@npm:4.0.23" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/97364970b01c85946a4a50ad876c53ef0c1857a9144e24fad65e5dfa4b4e5dd42564fbcdfa2b49bb049a25d127efbe0882cb18afcdd47b166ebd01c6c4b5e825 + languageName: node + linkType: hard + +"@inquirer/prompts@npm:7.10.1": + version: 7.10.1 + resolution: "@inquirer/prompts@npm:7.10.1" + dependencies: + "@inquirer/checkbox": "npm:^4.3.2" + "@inquirer/confirm": "npm:^5.1.21" + "@inquirer/editor": "npm:^4.2.23" + "@inquirer/expand": "npm:^4.0.23" + "@inquirer/input": "npm:^4.3.1" + "@inquirer/number": "npm:^3.0.23" + "@inquirer/password": "npm:^4.0.23" + "@inquirer/rawlist": "npm:^4.1.11" + "@inquirer/search": "npm:^3.2.2" + "@inquirer/select": "npm:^4.4.2" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/b3e3386edd255e4e91c7908050674f8a2e69b043883c00feec2f87d697be37bc6e8cd4a360e7e3233a9825ae7ea044a2ac63d5700926d27f9959013d8566f890 + languageName: node + linkType: hard + +"@inquirer/prompts@npm:7.3.2": + version: 7.3.2 + resolution: "@inquirer/prompts@npm:7.3.2" + dependencies: + "@inquirer/checkbox": "npm:^4.1.2" + "@inquirer/confirm": "npm:^5.1.6" + "@inquirer/editor": "npm:^4.2.7" + "@inquirer/expand": "npm:^4.0.9" + "@inquirer/input": "npm:^4.1.6" + "@inquirer/number": "npm:^3.0.9" + "@inquirer/password": "npm:^4.0.9" + "@inquirer/rawlist": "npm:^4.0.9" + "@inquirer/search": "npm:^3.0.9" + "@inquirer/select": "npm:^4.0.9" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/476ea162f6820628dbbb4ffff78a9ddf0cc9d99237bfbd976b944034eb4362eec748cabe2c886fa69eab551866172952fc26f2c12f4429008321105048743b41 + languageName: node + linkType: hard + +"@inquirer/rawlist@npm:^4.0.9, @inquirer/rawlist@npm:^4.1.11": + version: 4.1.11 + resolution: "@inquirer/rawlist@npm:4.1.11" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/0d8f6484cfc20749190e95eecfb2d034bafb3644ec4907b84b1673646f5dd71730e38e35565ea98dfd240d8851e3cff653edafcc4e0af617054b127b407e3229 + languageName: node + linkType: hard + +"@inquirer/search@npm:^3.0.9, @inquirer/search@npm:^3.2.2": + version: 3.2.2 + resolution: "@inquirer/search@npm:3.2.2" + dependencies: + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/abaed2df7763633ff4414b58d1c87233b69ed3cd2ac77629f0d54b72b8b585dc4806c7a2a8261daba58af5b0a2147e586d079fdc82060b6bcf56b75d3d03f3a7 + languageName: node + linkType: hard + +"@inquirer/select@npm:^4.0.9, @inquirer/select@npm:^4.4.2": + version: 4.4.2 + resolution: "@inquirer/select@npm:4.4.2" + dependencies: + "@inquirer/ansi": "npm:^1.0.2" + "@inquirer/core": "npm:^10.3.2" + "@inquirer/figures": "npm:^1.0.15" + "@inquirer/type": "npm:^3.0.10" + yoctocolors-cjs: "npm:^2.1.3" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/795ec0ac77d575f20bd6a12fb1c040093e62217ac0c80194829a8d3c3d1e09f70ad738e9a9dd6095cc8358fff4e13882209c09bdf8eb0864a86dcabef5b0a6a6 + languageName: node + linkType: hard + +"@inquirer/type@npm:^3.0.10": + version: 3.0.10 + resolution: "@inquirer/type@npm:3.0.10" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10/57d113a9db7abc73326491e29bedc88ef362e53779f9f58a1b61225e0be068ce0c54e33cd65f4a13ca46131676fb72c3ef488463c4c9af0aa89680684c55d74c + languageName: node + linkType: hard + +"@ioredis/commands@npm:1.5.0": + version: 1.5.0 + resolution: "@ioredis/commands@npm:1.5.0" + checksum: 10/b5a842fde4785c20318b4b26c7ae98bb1f9cbdc1ade3e55a2de07d5db8fb4e9281e590fb08559e5a9027909f0f9f1214688af33e6fb469ce4ff3decc7cfde679 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10/102fbc6d2c0d5edf8f6dbf2b3feb21695a21bc850f11bc47c4f06aa83bd8884fde3fe9d6d797d619901d96865fdcb4569ac2a54c937992c48885c5e3d9967fe8 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10/cf3b7f206aff12128214a1df764ac8cdbc517c110db85249b945282407e3dfc5c6e66286383a7c9391a059fc8e6e6a8ca82262fc9d2590bd615376141fbebd2d + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: 10/e9ed5fd27c3aec1095e3a16e0c0cf148d1fee55a38665c35f7b3f86a9b5d00d042ddaabc98e8a1cb7463b9378c15f22a94eb35e99469c201453eb8375191f243 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10/4412e9e6713c89c1e66d80bb0bb5a2a93192f10477623a27d08f228ba0316bb880affabc5bfe7f838f58a34d26c2c190da726e576cdfc18c49a72e89adabdcf5 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10/902f8261dcf450b4af7b93f9656918e02eec80a2169e155000cb2059f90113dd98f3ccf6efc6072cee1dd84cac48cade51da236972d942babc40e4c23da4d62a + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.0.3, @jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10/97106439d750a409c22c8bff822d648f6a71f3aa9bc8e5129efdc36343cd3096ddc4eeb1c62d2fe48e9bdd4db37b05d4646a17114ecebd3bbcacfa2de51c3c1d + languageName: node + linkType: hard + +"@jridgewell/source-map@npm:^0.3.3": + version: 0.3.11 + resolution: "@jridgewell/source-map@npm:0.3.11" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.25" + checksum: 10/847f1177d3d133a0966ef61ca29abea0d79788a0652f90ee1893b3da968c190b7e31c3534cc53701179dd6b14601eef3d78644e727e05b1a08c68d281aedc4ba + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10/5d9d207b462c11e322d71911e55e21a4e2772f71ffe8d6f1221b8eb5ae6774458c1d242f897fb0814e8714ca9a6b498abfa74dfe4f434493342902b1a48b33a5 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:0.3.9": + version: 0.3.9 + resolution: "@jridgewell/trace-mapping@npm:0.3.9" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.0.3" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + checksum: 10/83deafb8e7a5ca98993c2c6eeaa93c270f6f647a4c0dc00deb38c9cf9b2d3b7bf15e8839540155247ef034a052c0ec4466f980bf0c9e2ab63b97d16c0cedd3ff + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10/da0283270e691bdb5543806077548532791608e52386cfbbf3b9e8fb00457859d1bd01d512851161c886eb3a2f3ce6fd9bcf25db8edf3bddedd275bd4a88d606 + languageName: node + linkType: hard + +"@js-sdsl/ordered-map@npm:^4.4.2": + version: 4.4.2 + resolution: "@js-sdsl/ordered-map@npm:4.4.2" + checksum: 10/ac64e3f0615ecc015461c9f527f124d2edaa9e68de153c1e270c627e01e83d046522d7e872692fd57a8c514578b539afceff75831c0d8b2a9a7a347fbed35af4 + languageName: node + linkType: hard + +"@lukeed/csprng@npm:^1.0.0": + version: 1.1.0 + resolution: "@lukeed/csprng@npm:1.1.0" + checksum: 10/926f5f7fc629470ca9a8af355bfcd0271d34535f7be3890f69902432bddc3262029bb5dbe9025542cf6c9883d878692eef2815fc2f3ba5b92e9da1f9eba2e51b + languageName: node + linkType: hard + +"@microsoft/tsdoc@npm:0.16.0": + version: 0.16.0 + resolution: "@microsoft/tsdoc@npm:0.16.0" + checksum: 10/1eaad3605234dc7e44898c15d1ba3c97fb968af1117025400cba572ce268da05afc36634d1fb9e779457af3ff7f13330aee07a962510a4d9c6612c13f71ee41e + languageName: node + linkType: hard + +"@napi-rs/nice-android-arm-eabi@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-android-arm-eabi@npm:1.1.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/nice-android-arm64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-android-arm64@npm:1.1.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-darwin-arm64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-darwin-arm64@npm:1.1.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-darwin-x64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-darwin-x64@npm:1.1.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/nice-freebsd-x64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-freebsd-x64@npm:1.1.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/nice-linux-arm-gnueabihf@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-arm-gnueabihf@npm:1.1.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@napi-rs/nice-linux-arm64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-arm64-gnu@npm:1.1.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-arm64-musl@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-arm64-musl@npm:1.1.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/nice-linux-ppc64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-ppc64-gnu@npm:1.1.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-riscv64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-riscv64-gnu@npm:1.1.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-s390x-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-s390x-gnu@npm:1.1.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-x64-gnu@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-x64-gnu@npm:1.1.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@napi-rs/nice-linux-x64-musl@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-linux-x64-musl@npm:1.1.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@napi-rs/nice-openharmony-arm64@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-openharmony-arm64@npm:1.1.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-win32-arm64-msvc@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-win32-arm64-msvc@npm:1.1.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@napi-rs/nice-win32-ia32-msvc@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-win32-ia32-msvc@npm:1.1.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@napi-rs/nice-win32-x64-msvc@npm:1.1.1": + version: 1.1.1 + resolution: "@napi-rs/nice-win32-x64-msvc@npm:1.1.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@napi-rs/nice@npm:^1.0.1": + version: 1.1.1 + resolution: "@napi-rs/nice@npm:1.1.1" + dependencies: + "@napi-rs/nice-android-arm-eabi": "npm:1.1.1" + "@napi-rs/nice-android-arm64": "npm:1.1.1" + "@napi-rs/nice-darwin-arm64": "npm:1.1.1" + "@napi-rs/nice-darwin-x64": "npm:1.1.1" + "@napi-rs/nice-freebsd-x64": "npm:1.1.1" + "@napi-rs/nice-linux-arm-gnueabihf": "npm:1.1.1" + "@napi-rs/nice-linux-arm64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-arm64-musl": "npm:1.1.1" + "@napi-rs/nice-linux-ppc64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-riscv64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-s390x-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-x64-gnu": "npm:1.1.1" + "@napi-rs/nice-linux-x64-musl": "npm:1.1.1" + "@napi-rs/nice-openharmony-arm64": "npm:1.1.1" + "@napi-rs/nice-win32-arm64-msvc": "npm:1.1.1" + "@napi-rs/nice-win32-ia32-msvc": "npm:1.1.1" + "@napi-rs/nice-win32-x64-msvc": "npm:1.1.1" + dependenciesMeta: + "@napi-rs/nice-android-arm-eabi": + optional: true + "@napi-rs/nice-android-arm64": + optional: true + "@napi-rs/nice-darwin-arm64": + optional: true + "@napi-rs/nice-darwin-x64": + optional: true + "@napi-rs/nice-freebsd-x64": + optional: true + "@napi-rs/nice-linux-arm-gnueabihf": + optional: true + "@napi-rs/nice-linux-arm64-gnu": + optional: true + "@napi-rs/nice-linux-arm64-musl": + optional: true + "@napi-rs/nice-linux-ppc64-gnu": + optional: true + "@napi-rs/nice-linux-riscv64-gnu": + optional: true + "@napi-rs/nice-linux-s390x-gnu": + optional: true + "@napi-rs/nice-linux-x64-gnu": + optional: true + "@napi-rs/nice-linux-x64-musl": + optional: true + "@napi-rs/nice-openharmony-arm64": + optional: true + "@napi-rs/nice-win32-arm64-msvc": + optional: true + "@napi-rs/nice-win32-ia32-msvc": + optional: true + "@napi-rs/nice-win32-x64-msvc": + optional: true + checksum: 10/3f197c9536d0294f732a2acbe05a6d2fddc2794873b5b73edd395f56e3aed90b46c053001af80ea006d4d276cbb4e4196f8dbee0c214163b8e4b787e570a37e1 + languageName: node + linkType: hard + +"@napi-rs/wasm-runtime@npm:^1.1.0": + version: 1.1.1 + resolution: "@napi-rs/wasm-runtime@npm:1.1.1" + dependencies: + "@emnapi/core": "npm:^1.7.1" + "@emnapi/runtime": "npm:^1.7.1" + "@tybys/wasm-util": "npm:^0.10.1" + checksum: 10/080e7f2aefb84e09884d21c650a2cbafdf25bfd2634693791b27e36eec0ddaa3c1656a943f8c913ac75879a0b04e68f8a827897ee655ab54a93169accf05b194 + languageName: node + linkType: hard + +"@nestjs-modules/mailer@npm:2.0.2": + version: 2.0.2 + resolution: "@nestjs-modules/mailer@npm:2.0.2" + dependencies: + "@css-inline/css-inline": "npm:0.14.1" + "@types/ejs": "npm:^3.1.5" + "@types/mjml": "npm:^4.7.4" + "@types/pug": "npm:^2.0.10" + ejs: "npm:^3.1.10" + glob: "npm:10.3.12" + handlebars: "npm:^4.7.8" + liquidjs: "npm:^10.11.1" + mjml: "npm:^4.15.3" + preview-email: "npm:^3.0.19" + pug: "npm:^3.0.2" + peerDependencies: + "@nestjs/common": ">=7.0.9" + "@nestjs/core": ">=7.0.9" + "@types/ejs": ">=3.0.3" + "@types/mjml": ">=4.7.4" + "@types/pug": ">=2.0.6" + ejs: ">=3.1.2" + handlebars: ">=4.7.6" + liquidjs: ">=10.8.2" + mjml: ">=4.15.3" + nodemailer: ">=6.4.6" + preview-email: ">=3.0.19" + pug: ">=3.0.1" + dependenciesMeta: + "@types/ejs": + optional: true + "@types/mjml": + optional: true + "@types/pug": + optional: true + ejs: + optional: true + handlebars: + optional: true + liquidjs: + optional: true + mjml: + optional: true + preview-email: + optional: true + pug: + optional: true + checksum: 10/ca81095b78e36492d85fa76b87e98833261f3cb644130042a64b9433c038d83ff78754d5be04f713d399370196aa1e0d99fd25cc2077b3efce6da78cc8e2c07b + languageName: node + linkType: hard + +"@nestjs/axios@npm:^4.0.0": + version: 4.0.1 + resolution: "@nestjs/axios@npm:4.0.1" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + axios: ^1.3.1 + rxjs: ^7.0.0 + checksum: 10/0cc741e4fbfc39920afbb6c58050e0dbfecc8e2f7b249d879802a5b03b65df3714e828970e2bf12283d3d27e1f1ab7ca5ec62b5698dc50f105680e100a0a33f6 + languageName: node + linkType: hard + +"@nestjs/cli@npm:^11.0.7": + version: 11.0.14 + resolution: "@nestjs/cli@npm:11.0.14" + dependencies: + "@angular-devkit/core": "npm:19.2.19" + "@angular-devkit/schematics": "npm:19.2.19" + "@angular-devkit/schematics-cli": "npm:19.2.19" + "@inquirer/prompts": "npm:7.10.1" + "@nestjs/schematics": "npm:^11.0.1" + ansis: "npm:4.2.0" + chokidar: "npm:4.0.3" + cli-table3: "npm:0.6.5" + commander: "npm:4.1.1" + fork-ts-checker-webpack-plugin: "npm:9.1.0" + glob: "npm:13.0.0" + node-emoji: "npm:1.11.0" + ora: "npm:5.4.1" + tsconfig-paths: "npm:4.2.0" + tsconfig-paths-webpack-plugin: "npm:4.2.0" + typescript: "npm:5.9.3" + webpack: "npm:5.103.0" + webpack-node-externals: "npm:3.0.0" + peerDependencies: + "@swc/cli": ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0 + "@swc/core": ^1.3.62 + peerDependenciesMeta: + "@swc/cli": + optional: true + "@swc/core": + optional: true + bin: + nest: bin/nest.js + checksum: 10/f6174353b2b1781cb2c370958d94456cd58574ae959c157bf9ef2135381778b0c7a8689b2767fe6b7f890a9312e76828a4a74a59820285a8fbcccf332665b894 + languageName: node + linkType: hard + +"@nestjs/common@npm:^11.1.2": + version: 11.1.11 + resolution: "@nestjs/common@npm:11.1.11" + dependencies: + file-type: "npm:21.2.0" + iterare: "npm:1.2.1" + load-esm: "npm:1.0.3" + tslib: "npm:2.8.1" + uid: "npm:2.0.2" + peerDependencies: + class-transformer: ">=0.4.1" + class-validator: ">=0.13.2" + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + checksum: 10/a0027d306e95549350395627f311689a1c75bc66cd458ccf81916ee6f574b4e726c0397b0af62664a6338b8523a99a5b4f4ef59c55a2d385ad645bfb22c11aaa + languageName: node + linkType: hard + +"@nestjs/config@npm:^4.0.2": + version: 4.0.2 + resolution: "@nestjs/config@npm:4.0.2" + dependencies: + dotenv: "npm:16.4.7" + dotenv-expand: "npm:12.0.1" + lodash: "npm:4.17.21" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + rxjs: ^7.1.0 + checksum: 10/ed0234807a0677c301894cbeafe293226ad8d8279659a542e2f23cd3e40adeb38aecabcb5ace46bcee83437943400d03e0c374a3ca63e2d7a77e80f71088740d + languageName: node + linkType: hard + +"@nestjs/core@npm:^11.1.2": + version: 11.1.11 + resolution: "@nestjs/core@npm:11.1.11" + dependencies: + "@nuxt/opencollective": "npm:0.4.1" + fast-safe-stringify: "npm:2.1.1" + iterare: "npm:1.2.1" + path-to-regexp: "npm:8.3.0" + tslib: "npm:2.8.1" + uid: "npm:2.0.2" + peerDependencies: + "@nestjs/common": ^11.0.0 + "@nestjs/microservices": ^11.0.0 + "@nestjs/platform-express": ^11.0.0 + "@nestjs/websockets": ^11.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + "@nestjs/microservices": + optional: true + "@nestjs/platform-express": + optional: true + "@nestjs/websockets": + optional: true + checksum: 10/f94b8049741862d698b7079cdb70cd9eb91875f22c52a04707cc1e81bbd2db030c3992a3c454518bf507f29cd8f12ec18189b6d475f7e8c5f5fab827537f0cd3 + languageName: node + linkType: hard + +"@nestjs/event-emitter@npm:^3.0.1": + version: 3.0.1 + resolution: "@nestjs/event-emitter@npm:3.0.1" + dependencies: + eventemitter2: "npm:6.4.9" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/core": ^10.0.0 || ^11.0.0 + checksum: 10/6050c615aefada991da136f854dd3a2344aa69286886a53093897d4c2e2158142ad9e1b7b1a55b0a0cbc6a7b43a4916cb1988ff01d766c97bfc7e1dca862c93a + languageName: node + linkType: hard + +"@nestjs/jwt@npm:^11.0.0": + version: 11.0.2 + resolution: "@nestjs/jwt@npm:11.0.2" + dependencies: + "@types/jsonwebtoken": "npm:9.0.10" + jsonwebtoken: "npm:9.0.3" + peerDependencies: + "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + checksum: 10/87ebe45dbbe9acfd31042b7cb18eeb945b9b9574bd807a076204b9fd502fc81e99e7f30c788bdeac69b603454526e603fd97111c358c5f01346cda35e6fb3fc5 + languageName: node + linkType: hard + +"@nestjs/mapped-types@npm:2.1.0": + version: 2.1.0 + resolution: "@nestjs/mapped-types@npm:2.1.0" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + checksum: 10/4d75143e233f743338ba0db0b92301ab297221bb8842d2b999954f8d90f8ccee68350e2c01381f97a7f30af230b8b783e066939f8afe2d7c8abb46596776858b + languageName: node + linkType: hard + +"@nestjs/platform-express@npm:^11.1.2": + version: 11.1.11 + resolution: "@nestjs/platform-express@npm:11.1.11" + dependencies: + cors: "npm:2.8.5" + express: "npm:5.2.1" + multer: "npm:2.0.2" + path-to-regexp: "npm:8.3.0" + tslib: "npm:2.8.1" + peerDependencies: + "@nestjs/common": ^11.0.0 + "@nestjs/core": ^11.0.0 + checksum: 10/877658da61e9d64c260f85590c8a15ae81a3466e5a0c51e94f2016b699cac7af59d994a18c124aaa4920920e9f24e0c1abf081d717f138d3d63b7d20cd86e2ef + languageName: node + linkType: hard + +"@nestjs/platform-socket.io@npm:^11.1.2": + version: 11.1.11 + resolution: "@nestjs/platform-socket.io@npm:11.1.11" + dependencies: + socket.io: "npm:4.8.3" + tslib: "npm:2.8.1" + peerDependencies: + "@nestjs/common": ^11.0.0 + "@nestjs/websockets": ^11.0.0 + rxjs: ^7.1.0 + checksum: 10/7e9188356cac31e92cdd3b095e2481e630f73f563622caff56cd4ca1fea3b63ec8053ede8fc2993bf6c329e4067c0df5cae0da0fd0e9496ebb8b234d79c4c377 + languageName: node + linkType: hard + +"@nestjs/schedule@npm:^6.0.0": + version: 6.1.0 + resolution: "@nestjs/schedule@npm:6.1.0" + dependencies: + cron: "npm:4.3.5" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/core": ^10.0.0 || ^11.0.0 + checksum: 10/640cf14e3701d08d0432c623668789c279790f88c59e1c22ca28a81713d8584cdf36bbe76ecc5afab7e13cd4648b75e47ab798ce14875501ed2cbb9e7cab6771 + languageName: node + linkType: hard + +"@nestjs/schematics@npm:^11.0.1, @nestjs/schematics@npm:^11.0.5": + version: 11.0.9 + resolution: "@nestjs/schematics@npm:11.0.9" + dependencies: + "@angular-devkit/core": "npm:19.2.17" + "@angular-devkit/schematics": "npm:19.2.17" + comment-json: "npm:4.4.1" + jsonc-parser: "npm:3.3.1" + pluralize: "npm:8.0.0" + peerDependencies: + typescript: ">=4.8.2" + checksum: 10/d916f454858e9cb28d61842d46e04e07a5f48cd85f7c0ae6ff14835a2d6b70353e2da16e2d4809fc11b5ac62282af6ebe9e8f365610374647713d90e65692e00 + languageName: node + linkType: hard + +"@nestjs/swagger@npm:^11.2.0": + version: 11.2.4 + resolution: "@nestjs/swagger@npm:11.2.4" + dependencies: + "@microsoft/tsdoc": "npm:0.16.0" + "@nestjs/mapped-types": "npm:2.1.0" + js-yaml: "npm:4.1.1" + lodash: "npm:4.17.21" + path-to-regexp: "npm:8.3.0" + swagger-ui-dist: "npm:5.31.0" + peerDependencies: + "@fastify/static": ^8.0.0 || ^9.0.0 + "@nestjs/common": ^11.0.1 + "@nestjs/core": ^11.0.1 + class-transformer: "*" + class-validator: "*" + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + "@fastify/static": + optional: true + class-transformer: + optional: true + class-validator: + optional: true + checksum: 10/da4c797df5d6d4da969e0036d09e02d9b437ea1640a55dab5a79db9c0fae36d357b3e1affb2b1a65bcef499aadbc9ca6010ca4513cbd545c6cf47e797fb176a1 + languageName: node + linkType: hard + +"@nestjs/terminus@npm:^11.0.0": + version: 11.0.0 + resolution: "@nestjs/terminus@npm:11.0.0" + dependencies: + boxen: "npm:5.1.2" + check-disk-space: "npm:3.4.0" + peerDependencies: + "@grpc/grpc-js": "*" + "@grpc/proto-loader": "*" + "@mikro-orm/core": "*" + "@mikro-orm/nestjs": "*" + "@nestjs/axios": ^2.0.0 || ^3.0.0 || ^4.0.0 + "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/core": ^10.0.0 || ^11.0.0 + "@nestjs/microservices": ^10.0.0 || ^11.0.0 + "@nestjs/mongoose": ^11.0.0 + "@nestjs/sequelize": ^10.0.0 || ^11.0.0 + "@nestjs/typeorm": ^10.0.0 || ^11.0.0 + "@prisma/client": "*" + mongoose: "*" + reflect-metadata: 0.1.x || 0.2.x + rxjs: 7.x + sequelize: "*" + typeorm: "*" + peerDependenciesMeta: + "@grpc/grpc-js": + optional: true + "@grpc/proto-loader": + optional: true + "@mikro-orm/core": + optional: true + "@mikro-orm/nestjs": + optional: true + "@nestjs/axios": + optional: true + "@nestjs/microservices": + optional: true + "@nestjs/mongoose": + optional: true + "@nestjs/sequelize": + optional: true + "@nestjs/typeorm": + optional: true + "@prisma/client": + optional: true + mongoose: + optional: true + sequelize: + optional: true + typeorm: + optional: true + checksum: 10/586862409aed749b9a7030aa72e7b018c06896ee889c0b1016c6c690b25d370a75a80251201c1955deb4c780cd05866c108ae608a9e2c0e0adc8bb0868d2f9aa + languageName: node + linkType: hard + +"@nestjs/typeorm@npm:^11.0.0": + version: 11.0.0 + resolution: "@nestjs/typeorm@npm:11.0.0" + peerDependencies: + "@nestjs/common": ^10.0.0 || ^11.0.0 + "@nestjs/core": ^10.0.0 || ^11.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.2.0 + typeorm: ^0.3.0 + checksum: 10/2bfd490565ba2a0b007efecb4eedd5a24c4df126cdbbb0dd99b082653596674ac78f911f55422330256e5f6a8d49f615510c9ec1447c486749f8becf9d3679a5 + languageName: node + linkType: hard + +"@nestjs/websockets@npm:^11.1.2": + version: 11.1.11 + resolution: "@nestjs/websockets@npm:11.1.11" + dependencies: + iterare: "npm:1.2.1" + object-hash: "npm:3.0.0" + tslib: "npm:2.8.1" + peerDependencies: + "@nestjs/common": ^11.0.0 + "@nestjs/core": ^11.0.0 + "@nestjs/platform-socket.io": ^11.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + "@nestjs/platform-socket.io": + optional: true + checksum: 10/fd2d42f2b4692cdb62b40a51c1ba3d4f41920fc303e2963060108e2f99a06dfe6ccad72178db9a196e35d8121a9e9731f65f4f97819dc834a1767bfdcd4c6a1b + languageName: node + linkType: hard + +"@newrelic/fn-inspect@npm:^4.4.0": + version: 4.4.0 + resolution: "@newrelic/fn-inspect@npm:4.4.0" + dependencies: + nan: "npm:^2.22.2" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.8.1" + prebuildify: "npm:^6.0.1" + checksum: 10/a2bfcac79b2b03e8d8d2ce47ed36bdcea0148e911610c278bf1601a61ac9d53a867e6ade3bbd4042401dff2e9c829fc8e9b3cef17a06a781af2c41a5b1fcf33c + languageName: node + linkType: hard + +"@newrelic/native-metrics@npm:^11.1.0": + version: 11.1.0 + resolution: "@newrelic/native-metrics@npm:11.1.0" + dependencies: + nan: "npm:^2.22.2" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.8.1" + prebuildify: "npm:^6.0.1" + checksum: 10/ced07f45186c98b589b7079a5773cd7e53b9c024f97022889d51fe2c70f99f4894744030d641e350bd1ab2744ae05483b57afaf08a1ae3bdf2b655e02852bc28 + languageName: node + linkType: hard + +"@newrelic/security-agent@npm:^2.4.2": + version: 2.4.4 + resolution: "@newrelic/security-agent@npm:2.4.4" + dependencies: + axios: "npm:^1.12.0" + check-disk-space: "npm:^3.4.0" + content-type: "npm:^1.0.5" + cron: "npm:^3.1.7" + fast-safe-stringify: "npm:^2.1.1" + find-package-json: "npm:^1.2.0" + hash.js: "npm:^1.1.7" + html-entities: "npm:^2.3.6" + https-proxy-agent: "npm:^7.0.4" + is-invalid-path: "npm:^1.0.2" + js-yaml: "npm:^4.1.0" + jsonschema: "npm:^1.4.1" + lodash: "npm:^4.17.21" + log4js: "npm:^6.9.1" + pretty-bytes: "npm:^5.6.0" + request-ip: "npm:^3.3.0" + ringbufferjs: "npm:^2.0.0" + semver: "npm:^7.5.4" + unescape: "npm:^1.0.1" + unescape-js: "npm:^1.1.4" + uuid: "npm:^9.0.1" + ws: "npm:^8.17.1" + checksum: 10/d851ae9a5db9561bd496a22095e56c78059f240f6895569c443190a2108b3bd667a7168ac5c4647ff47512bf17f8f84b35495fe1dc1446b3666103a18e45cbbe + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": "npm:2.0.5" + run-parallel: "npm:^1.1.9" + checksum: 10/6ab2a9b8a1d67b067922c36f259e3b3dfd6b97b219c540877a4944549a4d49ea5ceba5663905ab5289682f1f3c15ff441d02f0447f620a42e1cb5e1937174d4b + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 10/012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": "npm:2.1.5" + fastq: "npm:^1.6.0" + checksum: 10/40033e33e96e97d77fba5a238e4bba4487b8284678906a9f616b5579ddaf868a18874c0054a75402c9fbaaa033a25ceae093af58c9c30278e35c23c9479e79b0 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10/1a81573becc60515031accc696e6405e9b894e65c12b98ef4aeee03b5617c41948633159dbf6caf5dde5b47367eeb749bdc7b7dfb21960930a9060a935c6f636 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/4935c7719d17830d0f9fa46c50be17b2a3c945cec61760f6d0909bce47677c42e1810ca673305890f9e84f008ec4d8e841182f371e42100a8159d15f22249208 + languageName: node + linkType: hard + +"@nuxt/opencollective@npm:0.4.1": + version: 0.4.1 + resolution: "@nuxt/opencollective@npm:0.4.1" + dependencies: + consola: "npm:^3.2.3" + bin: + opencollective: bin/opencollective.js + checksum: 10/37739657e87196c7f1019a76bc33dc6e33b028eeeec43ffbf29c821e89bf5c170514e9e224456e1da85d95859ba63a3a36bd7ce1b82f2d366f7be3d6299e7631 + languageName: node + linkType: hard + +"@one-ini/wasm@npm:0.1.1": + version: 0.1.1 + resolution: "@one-ini/wasm@npm:0.1.1" + checksum: 10/673c11518dba2e582e42415cbefe928513616f3af25e12f6e4e6b1b98b52b3e6c14bc251a361654af63cd64f208f22a1f7556fa49da2bf7efcf28cb14f16f807 + languageName: node + linkType: hard + +"@opentelemetry/api-logs@npm:0.201.1": + version: 0.201.1 + resolution: "@opentelemetry/api-logs@npm:0.201.1" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/baa14906caf848b7ff32fdd2b8cbad5c96b6e5b4bb4e52cb4118b323b77b2e99630b4d58d92f110343d475a21fd5bdcaaa37c29a4a386136ed4ee01528a2b2ed + languageName: node + linkType: hard + +"@opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.9.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/core@npm:2.0.1" + dependencies: + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/dd891afd427067a9e6c610c36ab5638b0b9e5303ccca7c75ad744f5db53c6162a4b5d9cd2f5a77cdc3e4bda2eae850a4e29983ea244c929b7b872b7e086fc61c + languageName: node + linkType: hard + +"@opentelemetry/core@npm:2.3.0, @opentelemetry/core@npm:^2.0.0": + version: 2.3.0 + resolution: "@opentelemetry/core@npm:2.3.0" + dependencies: + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/a4deceb8088e3d8d5ba0460778d758a1b74ab6768dd97ec645ae6d4cbb64746da1a29d103c98eeb1a713448da556fd7fcf350c6c5aff6388079365aa6ae32465 + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-http@npm:0.201.1": + version: 0.201.1 + resolution: "@opentelemetry/exporter-metrics-otlp-http@npm:0.201.1" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-exporter-base": "npm:0.201.1" + "@opentelemetry/otlp-transformer": "npm:0.201.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/764860e8744bcfc08ad48c97e7980412500c9f215193b15a33ca53e7d0161349f6d60e47360eef3487515cc9832ceec00e1cb5e817e232ac2cb725cd453d9281 + languageName: node + linkType: hard + +"@opentelemetry/exporter-metrics-otlp-proto@npm:^0.201.1": + version: 0.201.1 + resolution: "@opentelemetry/exporter-metrics-otlp-proto@npm:0.201.1" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/exporter-metrics-otlp-http": "npm:0.201.1" + "@opentelemetry/otlp-exporter-base": "npm:0.201.1" + "@opentelemetry/otlp-transformer": "npm:0.201.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/3ac7cc346dbb3b3881057bf32c4e1fb09fbb030d3a7b4b43522340beeeb2f33e886bb7bea2a8a9f3c644f2bfc846bc8476d720fbfe4851e91f19867013005afc + languageName: node + linkType: hard + +"@opentelemetry/otlp-exporter-base@npm:0.201.1": + version: 0.201.1 + resolution: "@opentelemetry/otlp-exporter-base@npm:0.201.1" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/otlp-transformer": "npm:0.201.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d9c64ebf531e5a7e3d42537d2058e331165e4764b4a54d453668c1f8bbfa14008255771110bb106aa44c094ad76933a46f37f67d4b362908d511e57e72b7cd09 + languageName: node + linkType: hard + +"@opentelemetry/otlp-transformer@npm:0.201.1": + version: 0.201.1 + resolution: "@opentelemetry/otlp-transformer@npm:0.201.1" + dependencies: + "@opentelemetry/api-logs": "npm:0.201.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/sdk-logs": "npm:0.201.1" + "@opentelemetry/sdk-metrics": "npm:2.0.1" + "@opentelemetry/sdk-trace-base": "npm:2.0.1" + protobufjs: "npm:^7.3.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/bed6f7d12aba212cfc9dd0c482de6d983f31a994faa4cb13f651f1cbe98ae8935ed25a4a25887cdcdc9a53af1ee8cd3406e869d900499c0cbadf87f3218dcdb4 + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/resources@npm:2.0.1" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/282f3831de2755d0fda2d8b6e37f9587ea248066d50c7d2f14c803ac9d5262a0f1db98a4185bcdc5acaeeece0b61f4fce43bc3896a79f1da79045ae4928618bf + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:2.3.0, @opentelemetry/resources@npm:^2.0.1": + version: 2.3.0 + resolution: "@opentelemetry/resources@npm:2.3.0" + dependencies: + "@opentelemetry/core": "npm:2.3.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/8deee5b81a9fe730569402da71190def11ed8b9a8f87b8340dafc773a1e174e52ca28c8117d26ff75aaea975adf6949fa0433305f3c5c0ce718e312e2826765a + languageName: node + linkType: hard + +"@opentelemetry/sdk-logs@npm:0.201.1": + version: 0.201.1 + resolution: "@opentelemetry/sdk-logs@npm:0.201.1" + dependencies: + "@opentelemetry/api-logs": "npm:0.201.1" + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ">=1.4.0 <1.10.0" + checksum: 10/c2d8aad418268c5ab4ad18f8eea5bb11fff1659b9bbbcd30546a622c2a6e04e3361de7809e702bff7c151cf7c21408ab8fd798b43ffbc8f549bfb91d0c40d4bb + languageName: node + linkType: hard + +"@opentelemetry/sdk-metrics@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/sdk-metrics@npm:2.0.1" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + peerDependencies: + "@opentelemetry/api": ">=1.9.0 <1.10.0" + checksum: 10/eb23d0657ce7ef0784f6c89af650de83530099782758fce574316a8e82ff2bca0eb3adffa88c5fdd04eaced6150deb53ea0ea05aae06d2783795691734e85473 + languageName: node + linkType: hard + +"@opentelemetry/sdk-metrics@npm:^2.0.1": + version: 2.3.0 + resolution: "@opentelemetry/sdk-metrics@npm:2.3.0" + dependencies: + "@opentelemetry/core": "npm:2.3.0" + "@opentelemetry/resources": "npm:2.3.0" + peerDependencies: + "@opentelemetry/api": ">=1.9.0 <1.10.0" + checksum: 10/c657f19f9ce7f887ac9d092260b9848411ba5a7c77457b864b440cba9073f7b913cfcb2f582c8b9239635fcfd755613510524964d306f1c16ec659a85e7fcdcb + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:2.0.1": + version: 2.0.1 + resolution: "@opentelemetry/sdk-trace-base@npm:2.0.1" + dependencies: + "@opentelemetry/core": "npm:2.0.1" + "@opentelemetry/resources": "npm:2.0.1" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/9de1e36bbce9bd7c0563e6395765fffc0f8c78806cb33cc95267e98dffd82de33857a51288073a104c10418b934e51560bcb5dcaf4e63e5c9e096f65cadd42cd + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:^2.0.0": + version: 2.3.0 + resolution: "@opentelemetry/sdk-trace-base@npm:2.3.0" + dependencies: + "@opentelemetry/core": "npm:2.3.0" + "@opentelemetry/resources": "npm:2.3.0" + "@opentelemetry/semantic-conventions": "npm:^1.29.0" + peerDependencies: + "@opentelemetry/api": ">=1.3.0 <1.10.0" + checksum: 10/235b3117704b69ead030819eaca5aa161ca6da2930603a26cb6aa32f07e6a4b1268a97471911a1939d308f8e9fda2c9b486febd208f5edcf506b79eeeed31aed + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:^1.29.0": + version: 1.38.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.38.0" + checksum: 10/9d549f4896e900f644d5e70dd7142505daff88ed83c1cb7bcd976ac55e9496d4ddd686bb2815dd68655c739950514394c3b73ff51e53b2e4ff2d54a7f6d22521 + languageName: node + linkType: hard + +"@oxc-resolver/binding-android-arm-eabi@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-android-arm-eabi@npm:11.16.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-android-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-android-arm64@npm:11.16.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-darwin-arm64@npm:11.16.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-darwin-x64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-darwin-x64@npm:11.16.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-freebsd-x64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-freebsd-x64@npm:11.16.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm-gnueabihf@npm:11.16.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm-musleabihf@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm-musleabihf@npm:11.16.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm64-gnu@npm:11.16.2" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-arm64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-arm64-musl@npm:11.16.2" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-ppc64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-ppc64-gnu@npm:11.16.2" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-riscv64-gnu@npm:11.16.2" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-riscv64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-riscv64-musl@npm:11.16.2" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-s390x-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-s390x-gnu@npm:11.16.2" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-gnu@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-x64-gnu@npm:11.16.2" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-resolver/binding-linux-x64-musl@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-linux-x64-musl@npm:11.16.2" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@oxc-resolver/binding-openharmony-arm64@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-openharmony-arm64@npm:11.16.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-wasm32-wasi@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-wasm32-wasi@npm:11.16.2" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.0" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-arm64-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-arm64-msvc@npm:11.16.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-ia32-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-ia32-msvc@npm:11.16.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@oxc-resolver/binding-win32-x64-msvc@npm:11.16.2": + version: 11.16.2 + resolution: "@oxc-resolver/binding-win32-x64-msvc@npm:11.16.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@pinojs/redact@npm:^0.4.0": + version: 0.4.0 + resolution: "@pinojs/redact@npm:0.4.0" + checksum: 10/2210ffb6b38357853d47239fd0532cc9edb406325270a81c440a35cece22090127c30c2ead3eefa3e608f2244087485308e515c431f4f69b6bd2e16cbd32812b + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 10/115e8ceeec6bc69dff2048b35c0ab4f8bbee12d8bb6c1f4af758604586d802b6e669dcb02dda61d078de42c2b4ddce41b3d9e726d7daa6b4b850f4adbf7333ff + languageName: node + linkType: hard + +"@pkgr/core@npm:^0.2.9": + version: 0.2.9 + resolution: "@pkgr/core@npm:0.2.9" + checksum: 10/bb2fb86977d63f836f8f5b09015d74e6af6488f7a411dcd2bfdca79d76b5a681a9112f41c45bdf88a9069f049718efc6f3900d7f1de66a2ec966068308ae517f + languageName: node + linkType: hard + +"@prisma/prisma-fmt-wasm@npm:^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085": + version: 4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085 + resolution: "@prisma/prisma-fmt-wasm@npm:4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085" + checksum: 10/6b413ee7d51b3cd4eb20869a5772c42200a93c9a763c64c5b64249d5c87603244a546deb8e1a9cb21923d891bcc90099633ee2dbb76c4dafc975bc8e4f9c459c + languageName: node + linkType: hard + +"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/aspromise@npm:1.1.2" + checksum: 10/8a938d84fe4889411296db66b29287bd61ea3c14c2d23e7a8325f46a2b8ce899857c5f038d65d7641805e6c1d06b495525c7faf00c44f85a7ee6476649034969 + languageName: node + linkType: hard + +"@protobufjs/base64@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/base64@npm:1.1.2" + checksum: 10/c71b100daeb3c9bdccab5cbc29495b906ba0ae22ceedc200e1ba49717d9c4ab15a6256839cebb6f9c6acae4ed7c25c67e0a95e734f612b258261d1a3098fe342 + languageName: node + linkType: hard + +"@protobufjs/codegen@npm:^2.0.4": + version: 2.0.4 + resolution: "@protobufjs/codegen@npm:2.0.4" + checksum: 10/c6ee5fa172a8464f5253174d3c2353ea520c2573ad7b6476983d9b1346f4d8f2b44aa29feb17a949b83c1816bc35286a5ea265ed9d8fdd2865acfa09668c0447 + languageName: node + linkType: hard + +"@protobufjs/eventemitter@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/eventemitter@npm:1.1.0" + checksum: 10/03af3e99f17ad421283d054c88a06a30a615922a817741b43ca1b13e7c6b37820a37f6eba9980fb5150c54dba6e26cb6f7b64a6f7d8afa83596fafb3afa218c3 + languageName: node + linkType: hard + +"@protobufjs/fetch@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/fetch@npm:1.1.0" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.1" + "@protobufjs/inquire": "npm:^1.1.0" + checksum: 10/67ae40572ad536e4ef94269199f252c024b66e3059850906bdaee161ca1d75c73d04d35cd56f147a8a5a079f5808e342b99e61942c1dae15604ff0600b09a958 + languageName: node + linkType: hard + +"@protobufjs/float@npm:^1.0.2": + version: 1.0.2 + resolution: "@protobufjs/float@npm:1.0.2" + checksum: 10/634c2c989da0ef2f4f19373d64187e2a79f598c5fb7991afb689d29a2ea17c14b796b29725945fa34b9493c17fb799e08ac0a7ccaae460ee1757d3083ed35187 + languageName: node + linkType: hard + +"@protobufjs/inquire@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/inquire@npm:1.1.0" + checksum: 10/c09efa34a5465cb120775e1a482136f2340a58b4abce7e93d72b8b5a9324a0e879275016ef9fcd73d72a4731639c54f2bb755bb82f916e4a78892d1d840bb3d2 + languageName: node + linkType: hard + +"@protobufjs/path@npm:^1.1.2": + version: 1.1.2 + resolution: "@protobufjs/path@npm:1.1.2" + checksum: 10/bb709567935fd385a86ad1f575aea98131bbd719c743fb9b6edd6b47ede429ff71a801cecbd64fc72deebf4e08b8f1bd8062793178cdaed3713b8d15771f9b83 + languageName: node + linkType: hard + +"@protobufjs/pool@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/pool@npm:1.1.0" + checksum: 10/b9c7047647f6af28e92aac54f6f7c1f7ff31b201b4bfcc7a415b2861528854fce3ec666d7e7e10fd744da905f7d4aef2205bbcc8944ca0ca7a82e18134d00c46 + languageName: node + linkType: hard + +"@protobufjs/utf8@npm:^1.1.0": + version: 1.1.0 + resolution: "@protobufjs/utf8@npm:1.1.0" + checksum: 10/131e289c57534c1d73a0e55782d6751dd821db1583cb2f7f7e017c9d6747addaebe79f28120b2e0185395d990aad347fb14ffa73ef4096fa38508d61a0e64602 + languageName: node + linkType: hard + +"@scarf/scarf@npm:=1.4.0": + version: 1.4.0 + resolution: "@scarf/scarf@npm:1.4.0" + checksum: 10/1b39a18fa29e91cfbc134c588e20c5f01a1b21ec4473614123801155b48378e9c3bf72adaca8c67e433ae951ab653268e9502cc5733230d8927532f74a6b89c9 + languageName: node + linkType: hard + +"@selderee/plugin-htmlparser2@npm:^0.11.0": + version: 0.11.0 + resolution: "@selderee/plugin-htmlparser2@npm:0.11.0" + dependencies: + domhandler: "npm:^5.0.3" + selderee: "npm:^0.11.0" + checksum: 10/7550108d270e6ea2be4850d55cbf4d58d5a90c109a15b874c3c7c622a1399bd8015359ef3672983a86118432ca8325a6aca1fe79d961b01278fdaeaea8895c5f + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^4.0.0": + version: 4.6.0 + resolution: "@sindresorhus/is@npm:4.6.0" + checksum: 10/e7f36ed72abfcd5e0355f7423a72918b9748bb1ef370a59f3e5ad8d40b728b85d63b272f65f63eec1faf417cda89dcb0aeebe94015647b6054659c1442fe5ce0 + languageName: node + linkType: hard + +"@sindresorhus/is@npm:^5.2.0": + version: 5.6.0 + resolution: "@sindresorhus/is@npm:5.6.0" + checksum: 10/b077c325acec98e30f7d86df158aaba2e7af2acb9bb6a00fda4b91578539fbff4ecebe9b934e24fec0e6950de3089d89d79ec02d9062476b20ce185be0e01bd6 + languageName: node + linkType: hard + +"@smithy/abort-controller@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/abort-controller@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/1da13fa31900a8ee7ad2f561a27510a3d1fbda417aea8ec9a5bf681ebecd92f8264d4ee1ba2cd96ebfe71927b3e2f5eb5959e8ed80d86d04951059fb70a0a46d + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader-native@npm:^4.2.1": + version: 4.2.1 + resolution: "@smithy/chunked-blob-reader-native@npm:4.2.1" + dependencies: + "@smithy/util-base64": "npm:^4.3.0" + tslib: "npm:^2.6.2" + checksum: 10/491cd1fbf74c53cc8c63abef1d9c0e93d1c0773db2c4458d4d3bd08217ea58872e413191b56259fd8081653ee07628e3ffcf7ff594d124378401fc3637794474 + languageName: node + linkType: hard + +"@smithy/chunked-blob-reader@npm:^5.2.0": + version: 5.2.0 + resolution: "@smithy/chunked-blob-reader@npm:5.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/c2f3b93343daba9a71e2f00fb93ae527a03c0adb6c6c6e194834bf4a67111e87f0694e2d9dd9b70bca87e9eb9da1d905d4450147e54e4cd27c6703dd98d58e0c + languageName: node + linkType: hard + +"@smithy/config-resolver@npm:^4.4.5": + version: 4.4.5 + resolution: "@smithy/config-resolver@npm:4.4.5" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-config-provider": "npm:^4.2.0" + "@smithy/util-endpoints": "npm:^3.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10/b86f3299f86cd93c84a15ccc7e223b032d3ce8c97d13d3a777515ed9874bb1ec116988204caace744cac014fdfda315682e43644142f44c7ada0bf426b7bfa8b + languageName: node + linkType: hard + +"@smithy/core@npm:^3.20.2, @smithy/core@npm:^3.20.3": + version: 3.20.3 + resolution: "@smithy/core@npm:3.20.3" + dependencies: + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-body-length-browser": "npm:^4.2.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-stream": "npm:^4.5.8" + "@smithy/util-utf8": "npm:^4.2.0" + "@smithy/uuid": "npm:^1.1.0" + tslib: "npm:^2.6.2" + checksum: 10/44f08b510f3de910a1c51f7ec603b48c50949917473e75cea4d7ed35992e9c052b8cda5311cd406e69a69213ed131d6c992b6dd1b382475eea5c5d575872fd54 + languageName: node + linkType: hard + +"@smithy/credential-provider-imds@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/credential-provider-imds@npm:4.2.7" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10/d017372f20b8cfc7b972a1f5d277712a8ec340cdb7da4ee2c14ec63972147f651196f5f1c570a82f534645600471480da11257d5d43ec47f601640a01c30baf9 + languageName: node + linkType: hard + +"@smithy/eventstream-codec@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-codec@npm:4.2.7" + dependencies: + "@aws-crypto/crc32": "npm:5.2.0" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-hex-encoding": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/8b772db92fc0b694f271e72ebcd34becadd020c03faa25170892463b43c79510af5f259c2d21ed7728fe8fe4014d17a6607412d62d36e73b28197d14f3de22ae + languageName: node + linkType: hard + +"@smithy/eventstream-serde-browser@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-serde-browser@npm:4.2.7" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/c1ab7b131d73e795dd8f4c86e7d833f9503c9c63f3375c833857ded49eae223aee846b1b4c1d140290e467d00d8262dcee8ab928aba8a75d4a1a6f3493965222 + languageName: node + linkType: hard + +"@smithy/eventstream-serde-config-resolver@npm:^4.3.7": + version: 4.3.7 + resolution: "@smithy/eventstream-serde-config-resolver@npm:4.3.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/072a9b376de2d6143bd0ab87507d3441341b3e2eee7d1ed083af662170daea0ebb49cf6737a6b09b0a7d6fa44df80f875651867c778fff5a4a365d01826d0b05 + languageName: node + linkType: hard + +"@smithy/eventstream-serde-node@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-serde-node@npm:4.2.7" + dependencies: + "@smithy/eventstream-serde-universal": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/b578ba12d3853d76c3965ad9201ac6d9205f8e8de14e2b1b7aa3f2419017c25d8307563eb29b59cc0cbfa70a638d3cc7126448ff8f3603803648ca8cb5876e88 + languageName: node + linkType: hard + +"@smithy/eventstream-serde-universal@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/eventstream-serde-universal@npm:4.2.7" + dependencies: + "@smithy/eventstream-codec": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/6d8bcc898b315e961f1e3e41ac291f9cfb10ca199d2e70b489e878b5544ce88c2123df186ed38ec4f496d99a47890e95c6cd0acee173e0ee4cba79a095e5be5b + languageName: node + linkType: hard + +"@smithy/fetch-http-handler@npm:^5.3.8": + version: 5.3.8 + resolution: "@smithy/fetch-http-handler@npm:5.3.8" + dependencies: + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/querystring-builder": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + tslib: "npm:^2.6.2" + checksum: 10/4e7c20c10d32c117c4fc48b478c23d211d7487df34b216402403f94a0943a3c4a433fc48b0eb07e91fc36ccc827dd58f9784d4e828315ae05b7acf25f6fee342 + languageName: node + linkType: hard + +"@smithy/hash-blob-browser@npm:^4.2.8": + version: 4.2.8 + resolution: "@smithy/hash-blob-browser@npm:4.2.8" + dependencies: + "@smithy/chunked-blob-reader": "npm:^5.2.0" + "@smithy/chunked-blob-reader-native": "npm:^4.2.1" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/6e5507ebea6244f9a2d4084853ff607591ad81f732211cf05ef2add12b322b3277730b149bd26bdc5f432232394b0bbab8517f6763d7d7b69cb654d8395a2cb9 + languageName: node + linkType: hard + +"@smithy/hash-node@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/hash-node@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-buffer-from": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/e7fb084d56db52fad35830b9be6f316347738831850c73e8ed9f4dfe2d2fd2b7ef74655e620ebec8d87a2d66eb73ad017830d4b3a3540eeac89ca23a3170236a + languageName: node + linkType: hard + +"@smithy/hash-stream-node@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/hash-stream-node@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/88f20898060d7f282b2a4a7d6ac2d09dcdd3311e30e78f241732551a49f674b1d81ff0a837080e19254308d94690ad1cd62d5afc862757cd0856dc2007daaf04 + languageName: node + linkType: hard + +"@smithy/invalid-dependency@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/invalid-dependency@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/f9d563b7d8476c089487f82a4716fe29ef21b38110f5efcb1c0f9b4b9c2654fa0da120fca7d8b819ac3fc081b83d7359eed61e83fbe8c047aeacc5f0b5a10398 + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^2.2.0": + version: 2.2.0 + resolution: "@smithy/is-array-buffer@npm:2.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/d366743ecc7a9fc3bad21dbb3950d213c12bdd4aeb62b1265bf6cbe38309df547664ef3e51ab732e704485194f15e89d361943b0bfbe3fe1a4b3178b942913cc + languageName: node + linkType: hard + +"@smithy/is-array-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/is-array-buffer@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/fdc097ce6a8b241565e2d56460ec289730bcd734dcde17c23d1eaaa0996337f897217166276a3fd82491fe9fd17447aadf62e8d9056b3d2b9daf192b4b668af9 + languageName: node + linkType: hard + +"@smithy/md5-js@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/md5-js@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/6d9cb3f775aff31365a6d0ccd0d7ba97c3625f8ea0939bd06d7c50bd93f3f2fb5502c8381a47266db6ee8325baaeb30978901f40ad804f7dba733b50af4224f5 + languageName: node + linkType: hard + +"@smithy/middleware-content-length@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/middleware-content-length@npm:4.2.7" + dependencies: + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/91ec3b159bf888926ca64e6e4daa5240339e6ab0404c60bf35e91a06842a9c914e23fa4a87fb5f6a7faadbf90ba57ee5f7ad5ddfaa4430a04d58ade089af5c6e + languageName: node + linkType: hard + +"@smithy/middleware-endpoint@npm:^4.4.3, @smithy/middleware-endpoint@npm:^4.4.4": + version: 4.4.4 + resolution: "@smithy/middleware-endpoint@npm:4.4.4" + dependencies: + "@smithy/core": "npm:^3.20.3" + "@smithy/middleware-serde": "npm:^4.2.8" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + "@smithy/url-parser": "npm:^4.2.7" + "@smithy/util-middleware": "npm:^4.2.7" + tslib: "npm:^2.6.2" + checksum: 10/faab14b250586f72f261eece7d5cd515b7f6e7a3a99f4cb21fed723c732fd42be65995a17fc4a7d05e4c83d8ee1f9023f13314e8a10e1d99b033a30044d37ecd + languageName: node + linkType: hard + +"@smithy/middleware-retry@npm:^4.4.19": + version: 4.4.20 + resolution: "@smithy/middleware-retry@npm:4.4.20" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/service-error-classification": "npm:^4.2.7" + "@smithy/smithy-client": "npm:^4.10.5" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-retry": "npm:^4.2.7" + "@smithy/uuid": "npm:^1.1.0" + tslib: "npm:^2.6.2" + checksum: 10/a145eacf859e9a1040362873028f37e0a941cd8a625b071a05a1c9248b1ad174c972c087097b3fb873c3697c9833793822909dc15e477b47e56396c0a64a6cab + languageName: node + linkType: hard + +"@smithy/middleware-serde@npm:^4.2.8": + version: 4.2.8 + resolution: "@smithy/middleware-serde@npm:4.2.8" + dependencies: + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/775e773a779e488f47d46024202f9faa87d8cabc44d64daae2fc272dfe55f5b2a929660517a6cdb201a5ee40d1b37740029e68d335997beb1a5718fac52e5fbf + languageName: node + linkType: hard + +"@smithy/middleware-stack@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/middleware-stack@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/8e5342192271eb58b79cb4175fde62a969d481c6f16a438b594d3a87cd3386188d03412d7d59c6e9181d15efd2cb65f5feb7d6d2df0c65f5a02e886054e90ab9 + languageName: node + linkType: hard + +"@smithy/node-config-provider@npm:^4.3.7": + version: 4.3.7 + resolution: "@smithy/node-config-provider@npm:4.3.7" + dependencies: + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/shared-ini-file-loader": "npm:^4.4.2" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/eecd04b69623fba976f12129bf2c4f8a4d4b6462b7e852894a1863754a1e900249564215d5cde664ed54d11b061ebcaa86357c43bd2286fe2780208f287cf9c6 + languageName: node + linkType: hard + +"@smithy/node-http-handler@npm:^4.4.7": + version: 4.4.7 + resolution: "@smithy/node-http-handler@npm:4.4.7" + dependencies: + "@smithy/abort-controller": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/querystring-builder": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/14b32cac0b9959787952b8cb404e6246cb7c0e7780a18f60e22a7d4184517630f79fd112045b6cd64178ecb2ae1dc3cd75217cdcf964a8ca10d77f651194d7f1 + languageName: node + linkType: hard + +"@smithy/property-provider@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/property-provider@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/8193bffe7ef04e280a5239020f7414f1f842b7487a5608961a0d230de10a4a7c090a12fff4269aac614d3ff179d3d97e6fe9a0d4476063e45c01dc6aeeb81a47 + languageName: node + linkType: hard + +"@smithy/protocol-http@npm:^5.3.7": + version: 5.3.7 + resolution: "@smithy/protocol-http@npm:5.3.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/b9948867bab60f3083acb17c2d1afc1f4e69744cbae93a3f924b2a620cf0e866b44e1cf89260b876bec6ddbc0457a0728d2160a8e4f8913f1523952640f5220b + languageName: node + linkType: hard + +"@smithy/querystring-builder@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/querystring-builder@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + "@smithy/util-uri-escape": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/0dc850cf224756be70db475f6b8df6e1b6680a89d91099dd6ad0271dff9ab30ebbbba8c7ec1c221e3f00ae79c2304031840438cf0b4d017ebb2aa72131279155 + languageName: node + linkType: hard + +"@smithy/querystring-parser@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/querystring-parser@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/81c3ca8e6fc98371db50545b8a9d2420cd883e0369591f49bef5be16dffb9126ad51a49560ca2c94ccc37d7e9e262f43f327ec58fa3dc3328f7a9842402092f4 + languageName: node + linkType: hard + +"@smithy/service-error-classification@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/service-error-classification@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + checksum: 10/c4ab617aee6b9811e7b54f7e4887c5d5311fd88882570188fa7f08fed4b76e7e5dd76efeaee3e4cade2d161525eca47d499ac55d4142eb06df4923a443a26df6 + languageName: node + linkType: hard + +"@smithy/shared-ini-file-loader@npm:^4.4.2": + version: 4.4.2 + resolution: "@smithy/shared-ini-file-loader@npm:4.4.2" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/fd0a4c967fb9bf2f2c96bf41c54579eed80cbd75eb81351669ba7759d6c5f2139a31e535133fb985e499b7dd2c1a007e39a5fde057a1930101d3423b8252e33c + languageName: node + linkType: hard + +"@smithy/signature-v4@npm:^5.3.7": + version: 5.3.7 + resolution: "@smithy/signature-v4@npm:5.3.7" + dependencies: + "@smithy/is-array-buffer": "npm:^4.2.0" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-hex-encoding": "npm:^4.2.0" + "@smithy/util-middleware": "npm:^4.2.7" + "@smithy/util-uri-escape": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/1fa5c14433c8d852501a70f4624e36e5aa554fcb673ecb857b329ae41bc445b626d7cc57f499a4172435db5df86dc10ea927e9a76fed98a3de995258aecc79ff + languageName: node + linkType: hard + +"@smithy/smithy-client@npm:^4.10.4, @smithy/smithy-client@npm:^4.10.5": + version: 4.10.5 + resolution: "@smithy/smithy-client@npm:4.10.5" + dependencies: + "@smithy/core": "npm:^3.20.3" + "@smithy/middleware-endpoint": "npm:^4.4.4" + "@smithy/middleware-stack": "npm:^4.2.7" + "@smithy/protocol-http": "npm:^5.3.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-stream": "npm:^4.5.8" + tslib: "npm:^2.6.2" + checksum: 10/a2a95aba11660f84670b1c9618a660f0b1eacb57a34532faf9d9a37bf1c1d78f1408d23cfa36e273c3527c89cb05e8720a231d024245b3c65b30b8d9553f1581 + languageName: node + linkType: hard + +"@smithy/types@npm:^4.11.0": + version: 4.11.0 + resolution: "@smithy/types@npm:4.11.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/253484df3d0625137c745774af854c3175b0f7d56e826a03348fcb94aa2b60dd164380515920ed539b7e0b070f994d3ab20a0a95ad9fe385233921f5a48193de + languageName: node + linkType: hard + +"@smithy/url-parser@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/url-parser@npm:4.2.7" + dependencies: + "@smithy/querystring-parser": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/e545eddbcc0d5b0cc7934eb0e5a9e316a3af7946a76ebdac8661c06bb38f71060acabf989d7c332ce6f120cb08cbd1b23e1974c952a5a0fc884056bbfeca2d8d + languageName: node + linkType: hard + +"@smithy/util-base64@npm:^4.3.0": + version: 4.3.0 + resolution: "@smithy/util-base64@npm:4.3.0" + dependencies: + "@smithy/util-buffer-from": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/87065ca13e3745858e0bb0ab6374433b258c378ee2a5ef865b74f6a4208c56db7db2b9ee5f888e021de0107fae49e9957662c4c6847fe10529e2f6cc882426b4 + languageName: node + linkType: hard + +"@smithy/util-body-length-browser@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-body-length-browser@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/deeb689b52652651c11530a324e07725805533899215ad1f93c5e9a14931443e22b313491a3c2a6d7f61d6dd1e84f9154d0d32de62bf61e0bd8e6ab7bf5f81ed + languageName: node + linkType: hard + +"@smithy/util-body-length-node@npm:^4.2.1": + version: 4.2.1 + resolution: "@smithy/util-body-length-node@npm:4.2.1" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/efb1333d35120124ec0c751b7b7d5657eb9ad6d0bf6171ff61fde2504639883d36e9562613c70eca623b726193b22601c8ff60e40a8156102d4c5b12fae222f8 + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^2.2.0": + version: 2.2.0 + resolution: "@smithy/util-buffer-from@npm:2.2.0" + dependencies: + "@smithy/is-array-buffer": "npm:^2.2.0" + tslib: "npm:^2.6.2" + checksum: 10/53253e4e351df3c4b7907dca48a0a6ceae783e98a8e73526820b122b3047a53fd127c19f4d8301f68d852011d821da519da783de57e0b22eed57c4df5b90d089 + languageName: node + linkType: hard + +"@smithy/util-buffer-from@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-buffer-from@npm:4.2.0" + dependencies: + "@smithy/is-array-buffer": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/6a81e658554d7123fe089426a840b5e691aee4aa4f0d72b79af19dcf57ccb212dca518acb447714792d48c2dc99bda5e0e823dab05e450ee2393146706d476f9 + languageName: node + linkType: hard + +"@smithy/util-config-provider@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-config-provider@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/d65f36401c7a085660cf201a1b317d271e390258b619179fff88248c2db64fc35e6c62fe055f1e55be8935b06eb600379824dabf634fb26d528f54fe60c9d77b + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-browser@npm:^4.3.18": + version: 4.3.19 + resolution: "@smithy/util-defaults-mode-browser@npm:4.3.19" + dependencies: + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/smithy-client": "npm:^4.10.5" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/d13350bbec06de2facc626b7314a7b5a6e23220a2d3ef4f1b2ee0c84fb470cf4075be7a5302a1abcebbbcffd4f777d2e0e8decd2a9468e364220ca88c5ee0673 + languageName: node + linkType: hard + +"@smithy/util-defaults-mode-node@npm:^4.2.21": + version: 4.2.22 + resolution: "@smithy/util-defaults-mode-node@npm:4.2.22" + dependencies: + "@smithy/config-resolver": "npm:^4.4.5" + "@smithy/credential-provider-imds": "npm:^4.2.7" + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/property-provider": "npm:^4.2.7" + "@smithy/smithy-client": "npm:^4.10.5" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/57784cba201ad66908e37c5225833025b1bdc7f72708d44df1b4e990cc58c4f45de4e2e2c6d638742a3f9bf9b51c6afd93fce3822e91b5b9d3544530179ddc6b + languageName: node + linkType: hard + +"@smithy/util-endpoints@npm:^3.2.7": + version: 3.2.7 + resolution: "@smithy/util-endpoints@npm:3.2.7" + dependencies: + "@smithy/node-config-provider": "npm:^4.3.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/a5954b8091ca60e60e8b5665e937fec5caf1c90f1f380957424906a3adb92fc7f7fd4c565e19cc138a55b95a1a6fb9f82b78eb4499687c168704f24db739c2e0 + languageName: node + linkType: hard + +"@smithy/util-hex-encoding@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-hex-encoding@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/478773d73690e39167b67481116c4fd47cecfc97c3a935d88db9271fb0718627bec1cbc143efbf0cd49d1ac417bde7e76aa74139ea07e365b51e66797f63a45d + languageName: node + linkType: hard + +"@smithy/util-middleware@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/util-middleware@npm:4.2.7" + dependencies: + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/ffe3c2def97376a85e59cb7c1c516060e243bcf3e971538ced046f5b5e9771ef146637d430e9c87ae38ee1ac11f02a218f70fafd6d5099d7ff9595738e27c3c1 + languageName: node + linkType: hard + +"@smithy/util-retry@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/util-retry@npm:4.2.7" + dependencies: + "@smithy/service-error-classification": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/176b3cdf460579a92446a4a671d72308846102481475afdb81fae1d23f8596ad3b91b6f65af0596c33a20b56ccbede351bc8c6e732fdc58eca6a90088406c631 + languageName: node + linkType: hard + +"@smithy/util-stream@npm:^4.5.8": + version: 4.5.8 + resolution: "@smithy/util-stream@npm:4.5.8" + dependencies: + "@smithy/fetch-http-handler": "npm:^5.3.8" + "@smithy/node-http-handler": "npm:^4.4.7" + "@smithy/types": "npm:^4.11.0" + "@smithy/util-base64": "npm:^4.3.0" + "@smithy/util-buffer-from": "npm:^4.2.0" + "@smithy/util-hex-encoding": "npm:^4.2.0" + "@smithy/util-utf8": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/455941f6b94c8984e91855a8e3b43b397b8a90b46d47f54f4ee9b0a7a4ba49b593087361d24315f6787f0f591d5f2c2d2a0e68bfdad8e6621a67bee99e94672c + languageName: node + linkType: hard + +"@smithy/util-uri-escape@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-uri-escape@npm:4.2.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/a838a3afe557d7087d4500735c79d5da72e0cd5a08f95d1a1c450ba29d9cd85c950228eedbd9b2494156f4eb8658afb0a9a5bd2df3fc4f297faed886c396242b + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^2.0.0": + version: 2.3.0 + resolution: "@smithy/util-utf8@npm:2.3.0" + dependencies: + "@smithy/util-buffer-from": "npm:^2.2.0" + tslib: "npm:^2.6.2" + checksum: 10/c766ead8dac6bc6169f4cac1cc47ef7bd86928d06255148f9528228002f669c8cc49f78dc2b9ba5d7e214d40315024a9e32c5c9130b33e20f0fe4532acd0dff5 + languageName: node + linkType: hard + +"@smithy/util-utf8@npm:^4.2.0": + version: 4.2.0 + resolution: "@smithy/util-utf8@npm:4.2.0" + dependencies: + "@smithy/util-buffer-from": "npm:^4.2.0" + tslib: "npm:^2.6.2" + checksum: 10/d49f58fc6681255eecc3dee39c657b80ef8a4c5617e361bdaf6aaa22f02e378622376153cafc9f0655fb80162e88fc98bbf459f8dd5ba6d7c4b9a59e6eaa05f8 + languageName: node + linkType: hard + +"@smithy/util-waiter@npm:^4.2.7": + version: 4.2.7 + resolution: "@smithy/util-waiter@npm:4.2.7" + dependencies: + "@smithy/abort-controller": "npm:^4.2.7" + "@smithy/types": "npm:^4.11.0" + tslib: "npm:^2.6.2" + checksum: 10/16c48cf2b3a89122c8e65981769bb2ef4fbc4260465dc1b547babbc37e4a49d1e15a7e6c8a51865585a6311d844fad561c9663cd5bc772fd311c64d8549b2850 + languageName: node + linkType: hard + +"@smithy/uuid@npm:^1.1.0": + version: 1.1.0 + resolution: "@smithy/uuid@npm:1.1.0" + dependencies: + tslib: "npm:^2.6.2" + checksum: 10/fe77b1cebbbf2d541ee2f07eec6d4573af16e08dd3228758f59dcbe85a504112cefe81b971818cf39e2e3fa0ed1fcc61d392cddc50fca13d9dc9bd835e366db0 + languageName: node + linkType: hard + +"@so-ric/colorspace@npm:^1.1.6": + version: 1.1.6 + resolution: "@so-ric/colorspace@npm:1.1.6" + dependencies: + color: "npm:^5.0.2" + text-hex: "npm:1.0.x" + checksum: 10/fc3285e5cb9a458d255aa678d9453174ca40689a4c692f1617907996ab8eb78839542439604ced484c4f674a5297f7ba8b0e63fcfe901174f43c3d9c3c881b52 + languageName: node + linkType: hard + +"@socket.io/component-emitter@npm:~3.1.0": + version: 3.1.2 + resolution: "@socket.io/component-emitter@npm:3.1.2" + checksum: 10/89888f00699eb34e3070624eb7b8161fa29f064aeb1389a48f02195d55dd7c52a504e52160016859f6d6dffddd54324623cdd47fd34b3d46f9ed96c18c456edc + languageName: node + linkType: hard + +"@sqltools/formatter@npm:^1.2.5": + version: 1.2.5 + resolution: "@sqltools/formatter@npm:1.2.5" + checksum: 10/ce9335025cd033f8f1ac997d290af22d5a5cdbd5f04cbf0fa18d5388871e980a4fc67875037821799b356032f851732dee1017b2ee7de84f5c2a2b8bfd5604f5 + languageName: node + linkType: hard + +"@swc/cli@npm:^0.7.7": + version: 0.7.9 + resolution: "@swc/cli@npm:0.7.9" + dependencies: + "@swc/counter": "npm:^0.1.3" + "@xhmikosr/bin-wrapper": "npm:^13.0.5" + commander: "npm:^8.3.0" + minimatch: "npm:^9.0.3" + piscina: "npm:^4.3.1" + semver: "npm:^7.3.8" + slash: "npm:3.0.0" + source-map: "npm:^0.7.3" + tinyglobby: "npm:^0.2.13" + peerDependencies: + "@swc/core": ^1.2.66 + chokidar: ^4.0.1 + peerDependenciesMeta: + chokidar: + optional: true + bin: + spack: bin/spack.js + swc: bin/swc.js + swcx: bin/swcx.js + checksum: 10/b138d2a7c85320b1075891f2442ed261d1c3afe3518549829c1211ac1063b29237b5c51db65511b69a6d5fc828cc4425bc4107d9e6a8be0dff8a05da06fc8218 + languageName: node + linkType: hard + +"@swc/core-darwin-arm64@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-darwin-arm64@npm:1.15.8" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-darwin-x64@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-darwin-x64@npm:1.15.8" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/core-linux-arm-gnueabihf@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.8" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@swc/core-linux-arm64-gnu@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.8" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-arm64-musl@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.8" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-linux-x64-gnu@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.8" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-x64-musl@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-linux-x64-musl@npm:1.15.8" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-win32-arm64-msvc@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.8" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-win32-ia32-msvc@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.8" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/core-win32-x64-msvc@npm:1.15.8": + version: 1.15.8 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.8" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/core@npm:^1.11.29": + version: 1.15.8 + resolution: "@swc/core@npm:1.15.8" + dependencies: + "@swc/core-darwin-arm64": "npm:1.15.8" + "@swc/core-darwin-x64": "npm:1.15.8" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.8" + "@swc/core-linux-arm64-gnu": "npm:1.15.8" + "@swc/core-linux-arm64-musl": "npm:1.15.8" + "@swc/core-linux-x64-gnu": "npm:1.15.8" + "@swc/core-linux-x64-musl": "npm:1.15.8" + "@swc/core-win32-arm64-msvc": "npm:1.15.8" + "@swc/core-win32-ia32-msvc": "npm:1.15.8" + "@swc/core-win32-x64-msvc": "npm:1.15.8" + "@swc/counter": "npm:^0.1.3" + "@swc/types": "npm:^0.1.25" + peerDependencies: + "@swc/helpers": ">=0.5.17" + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10/893156733eff53632148969d4ed570b023a2469479d0eb113319181f8517bcdddb533954eccf689265f230111893a568e8f24e1412eba71217cd390135246ee4 + languageName: node + linkType: hard + +"@swc/counter@npm:^0.1.3": + version: 0.1.3 + resolution: "@swc/counter@npm:0.1.3" + checksum: 10/df8f9cfba9904d3d60f511664c70d23bb323b3a0803ec9890f60133954173047ba9bdeabce28cd70ba89ccd3fd6c71c7b0bd58be85f611e1ffbe5d5c18616598 + languageName: node + linkType: hard + +"@swc/types@npm:^0.1.25": + version: 0.1.25 + resolution: "@swc/types@npm:0.1.25" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10/f6741450224892d12df43e5ca7f3cc0287df644dcd672626eb0cc2a3a8e3e875f4b29eb11336f37c7240cf6e010ba59eb3a79f4fb8bee5cbd168dfc1326ff369 + languageName: node + linkType: hard + +"@szmarczak/http-timer@npm:^4.0.5": + version: 4.0.6 + resolution: "@szmarczak/http-timer@npm:4.0.6" + dependencies: + defer-to-connect: "npm:^2.0.0" + checksum: 10/c29df3bcec6fc3bdec2b17981d89d9c9fc9bd7d0c9bcfe92821dc533f4440bc890ccde79971838b4ceed1921d456973c4180d7175ee1d0023ad0562240a58d95 + languageName: node + linkType: hard + +"@szmarczak/http-timer@npm:^5.0.1": + version: 5.0.1 + resolution: "@szmarczak/http-timer@npm:5.0.1" + dependencies: + defer-to-connect: "npm:^2.0.1" + checksum: 10/fc9cb993e808806692e4a3337c90ece0ec00c89f4b67e3652a356b89730da98bc824273a6d67ca84d5f33cd85f317dcd5ce39d8cc0a2f060145a608a7cb8ce92 + languageName: node + linkType: hard + +"@tokenizer/inflate@npm:^0.2.6": + version: 0.2.7 + resolution: "@tokenizer/inflate@npm:0.2.7" + dependencies: + debug: "npm:^4.4.0" + fflate: "npm:^0.8.2" + token-types: "npm:^6.0.0" + checksum: 10/6cee1857e47ca0fc053d6cd87773b7c21857ab84cb847c7d9437a76d923e265c88f8e99a4ac9643c2f989f4b9791259ca17128f0480191449e2b412821a1b9a7 + languageName: node + linkType: hard + +"@tokenizer/inflate@npm:^0.4.1": + version: 0.4.1 + resolution: "@tokenizer/inflate@npm:0.4.1" + dependencies: + debug: "npm:^4.4.3" + token-types: "npm:^6.1.1" + checksum: 10/27d58757e1a6c004e86f8a5f1a40fe47cb48aa6891864d03de6eab27d42fafc1456f396bc8bc300e16913b0a85f42034d011db0213d17e544ed201a7fc24244e + languageName: node + linkType: hard + +"@tokenizer/token@npm:^0.3.0": + version: 0.3.0 + resolution: "@tokenizer/token@npm:0.3.0" + checksum: 10/889c1f1e63ac7c92c0ea22d4a2861142f1b43c3d92eb70ec42aa9e9851fab2e9952211d50f541b287781280df2f979bf5600a9c1f91fbc61b7fcf9994e9376a5 + languageName: node + linkType: hard + +"@total-typescript/ts-reset@npm:^0.6.1": + version: 0.6.1 + resolution: "@total-typescript/ts-reset@npm:0.6.1" + checksum: 10/3e18063433c4667561b2a5ea6b698d95f8ca2cbecc7e3717b60f9266e2fd7070b2006cab0df255e60eaad5658d1cec4afc89ea4ca5645c8f36a312093bba3645 + languageName: node + linkType: hard + +"@tsconfig/node10@npm:^1.0.7": + version: 1.0.12 + resolution: "@tsconfig/node10@npm:1.0.12" + checksum: 10/27e2f989dbb20f773aa121b609a5361a473b7047ff286fce7c851e61f5eec0c74f0bdb38d5bd69c8a06f17e60e9530188f2219b1cbeabeac91f0a5fd348eac2a + languageName: node + linkType: hard + +"@tsconfig/node12@npm:^1.0.7": + version: 1.0.11 + resolution: "@tsconfig/node12@npm:1.0.11" + checksum: 10/5ce29a41b13e7897a58b8e2df11269c5395999e588b9a467386f99d1d26f6c77d1af2719e407621412520ea30517d718d5192a32403b8dfcc163bf33e40a338a + languageName: node + linkType: hard + +"@tsconfig/node14@npm:^1.0.0": + version: 1.0.3 + resolution: "@tsconfig/node14@npm:1.0.3" + checksum: 10/19275fe80c4c8d0ad0abed6a96dbf00642e88b220b090418609c4376e1cef81bf16237bf170ad1b341452feddb8115d8dd2e5acdfdea1b27422071163dc9ba9d + languageName: node + linkType: hard + +"@tsconfig/node16@npm:^1.0.2": + version: 1.0.4 + resolution: "@tsconfig/node16@npm:1.0.4" + checksum: 10/202319785901f942a6e1e476b872d421baec20cf09f4b266a1854060efbf78cde16a4d256e8bc949d31e6cd9a90f1e8ef8fb06af96a65e98338a2b6b0de0a0ff + languageName: node + linkType: hard + +"@tybys/wasm-util@npm:^0.10.1": + version: 0.10.1 + resolution: "@tybys/wasm-util@npm:0.10.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/7fe0d239397aebb002ac4855d30c197c06a05ea8df8511350a3a5b1abeefe26167c60eda8a5508337571161e4c4b53d7c1342296123f9607af8705369de9fa7f + languageName: node + linkType: hard + +"@types/bcrypt@npm:^5.0.2": + version: 5.0.2 + resolution: "@types/bcrypt@npm:5.0.2" + dependencies: + "@types/node": "npm:*" + checksum: 10/b1f97532ffe6079cb57a464f28b5b37a30bc9620f43469e1f27ab9c979c8a114be5b667e7b115a5556fd5be463b65968da9bb32573c6faf74fecf6e565d8974b + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.6 + resolution: "@types/body-parser@npm:1.19.6" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: 10/33041e88eae00af2cfa0827e951e5f1751eafab2a8b6fce06cd89ef368a988907996436b1325180edaeddd1c0c7d0d0d4c20a6c9ff294a91e0039a9db9e9b658 + languageName: node + linkType: hard + +"@types/cacheable-request@npm:^6.0.1": + version: 6.0.3 + resolution: "@types/cacheable-request@npm:6.0.3" + dependencies: + "@types/http-cache-semantics": "npm:*" + "@types/keyv": "npm:^3.1.4" + "@types/node": "npm:*" + "@types/responselike": "npm:^1.0.0" + checksum: 10/159f9fdb2a1b7175eef453ae2ced5ea04c0d2b9610cc9ccd9f9abb066d36dacb1f37acd879ace10ad7cbb649490723feb396fb7307004c9670be29636304b988 + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 10/7eb1bc5342a9604facd57598a6c62621e244822442976c443efb84ff745246b10d06e8b309b6e80130026a396f19bf6793b7cecd7380169f369dac3bfc46fb99 + languageName: node + linkType: hard + +"@types/cookie-parser@npm:^1": + version: 1.4.10 + resolution: "@types/cookie-parser@npm:1.4.10" + peerDependencies: + "@types/express": "*" + checksum: 10/1f37b5a4115dbfd4b7bbea2d874fbf9495eca8c3e8c87fa7e38c50f9fff66222377c911cfdc7a1ea08855e822919c5534ebbcc4bf25b596bc6f7270e403483d9 + languageName: node + linkType: hard + +"@types/cors@npm:^2.8.12": + version: 2.8.19 + resolution: "@types/cors@npm:2.8.19" + dependencies: + "@types/node": "npm:*" + checksum: 10/9545cc532c9218754443f48a0c98c1a9ba4af1fe54a3425c95de75ff3158147bb39e666cb7c6bf98cc56a9c6dc7b4ce5b2cbdae6b55d5942e50c81b76ed6b825 + languageName: node + linkType: hard + +"@types/ejs@npm:^3.1.5": + version: 3.1.5 + resolution: "@types/ejs@npm:3.1.5" + checksum: 10/918898fd279108087722c1713e2ddb0c152ab839397946d164db8a18b5bbd732af9746373882a9bcf4843d35c6b191a8f569a7a4e51e90726d24501b39f40367 + languageName: node + linkType: hard + +"@types/eslint-scope@npm:^3.7.7": + version: 3.7.7 + resolution: "@types/eslint-scope@npm:3.7.7" + dependencies: + "@types/eslint": "npm:*" + "@types/estree": "npm:*" + checksum: 10/e2889a124aaab0b89af1bab5959847c5bec09809209255de0e63b9f54c629a94781daa04adb66bffcdd742f5e25a17614fb933965093c0eea64aacda4309380e + languageName: node + linkType: hard + +"@types/eslint@npm:*": + version: 9.6.1 + resolution: "@types/eslint@npm:9.6.1" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10/719fcd255760168a43d0e306ef87548e1e15bffe361d5f4022b0f266575637acc0ecb85604ac97879ee8ae83c6a6d0613b0ed31d0209ddf22a0fe6d608fc56fe + languageName: node + linkType: hard + +"@types/estree@npm:*, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10/25a4c16a6752538ffde2826c2cc0c6491d90e69cd6187bef4a006dd2c3c45469f049e643d7e516c515f21484dc3d48fd5c870be158a5beb72f5baf3dc43e4099 + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^5.0.0": + version: 5.1.1 + resolution: "@types/express-serve-static-core@npm:5.1.1" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: 10/7f3d8cf7e68764c9f3e8f6a12825b69ccf5287347fc1c20b29803d4f08a4abc1153ae11d7258852c61aad50f62ef72d4c1b9c97092b0a90462c3dddec2f6026c + languageName: node + linkType: hard + +"@types/express@npm:*, @types/express@npm:^5.0.2": + version: 5.0.6 + resolution: "@types/express@npm:5.0.6" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/serve-static": "npm:^2" + checksum: 10/da2cc3de1b1a4d7f20ed3fb6f0a8ee08e99feb3c2eb5a8d643db77017d8d0e70fee9e95da38a73f51bcdf5eda3bb6435073c0271dc04fb16fda92e55daf911fa + languageName: node + linkType: hard + +"@types/form-data@npm:^2.2.1": + version: 2.2.1 + resolution: "@types/form-data@npm:2.2.1" + dependencies: + "@types/node": "npm:*" + checksum: 10/7a322e03928b67a0cc6fbc721ff39214eefa7e9dbb027066137a8473ba62f81c51153747d6a3aadbb206fa189f60d19ee95e0c7998ef36ab0d52fdc00edcc930 + languageName: node + linkType: hard + +"@types/heapdump@npm:^0": + version: 0.3.4 + resolution: "@types/heapdump@npm:0.3.4" + checksum: 10/a9c62274e4b0cf841811410275fb58c1d82af128e5ee61d6df94297819cc45b0c2d900b155377a734788f84f3d5b76f9de4e36682870d24b38dc414b787fe507 + languageName: node + linkType: hard + +"@types/html-to-text@npm:^9.0.4": + version: 9.0.4 + resolution: "@types/html-to-text@npm:9.0.4" + checksum: 10/dd06963e7e11cf49cfab65fdadef62db43e098963999f307cfb77c6917cc26bc2634034203f5c1268eac47af6671d50af0ce341fd085726dfec0f6228a9cd016 + languageName: node + linkType: hard + +"@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.2": + version: 4.0.4 + resolution: "@types/http-cache-semantics@npm:4.0.4" + checksum: 10/a59566cff646025a5de396d6b3f44a39ab6a74f2ed8150692e0f31cc52f3661a68b04afe3166ebe0d566bd3259cb18522f46e949576d5204781cd6452b7fe0c5 + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.5 + resolution: "@types/http-errors@npm:2.0.5" + checksum: 10/a88da669366bc483e8f3b3eb3d34ada5f8d13eeeef851b1204d77e2ba6fc42aba4566d877cca5c095204a3f4349b87fe397e3e21288837bdd945dd514120755b + languageName: node + linkType: hard + +"@types/imap-simple@npm:^4.2.10": + version: 4.2.10 + resolution: "@types/imap-simple@npm:4.2.10" + dependencies: + "@types/imap": "npm:*" + "@types/node": "npm:*" + checksum: 10/625cdb58bd89123efc01ac0616cac3e0e467cd83ff1db879ca0fbb1406ebc918f7f3bd577ec091506b7315d2d19c62e9cf1fa3cb538d82eb5d009cfcf695b886 + languageName: node + linkType: hard + +"@types/imap@npm:*": + version: 0.8.43 + resolution: "@types/imap@npm:0.8.43" + dependencies: + "@types/node": "npm:*" + checksum: 10/c4aeacfd9b7f9fe4239bc0e09087260109e3373a3e2b331bd3d525569a38e46f472eff40c03e4417a50c3a7ad5b40309bba38b0ba1de75faf22e531f05d5ebbc + languageName: node + linkType: hard + +"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10/1a3c3e06236e4c4aab89499c428d585527ce50c24fe8259e8b3926d3df4cfbbbcf306cfc73ddfb66cbafc973116efd15967020b0f738f63e09e64c7d260519e7 + languageName: node + linkType: hard + +"@types/jsonwebtoken@npm:9.0.10": + version: 9.0.10 + resolution: "@types/jsonwebtoken@npm:9.0.10" + dependencies: + "@types/ms": "npm:*" + "@types/node": "npm:*" + checksum: 10/d7960d995ad815511c7f4e7f09d91522dfe16e7e800cdd6226c8b1b624c534e0f3b8f8f3beb60e3189865269f028002f1a490189beca5afd02bc96ef1d68f21f + languageName: node + linkType: hard + +"@types/keyv@npm:^3.1.4": + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" + dependencies: + "@types/node": "npm:*" + checksum: 10/e009a2bfb50e90ca9b7c6e8f648f8464067271fd99116f881073fa6fa76dc8d0133181dd65e6614d5fb1220d671d67b0124aef7d97dc02d7e342ab143a47779d + languageName: node + linkType: hard + +"@types/luxon@npm:~3.4.0": + version: 3.4.2 + resolution: "@types/luxon@npm:3.4.2" + checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d + languageName: node + linkType: hard + +"@types/luxon@npm:~3.7.0": + version: 3.7.1 + resolution: "@types/luxon@npm:3.7.1" + checksum: 10/c7bc164c278393ea0be938f986c74b4cddfab9013b1aff4495b016f771ded1d5b7b7b4825b2c7f0b8799edce19c5f531c28ff434ab3dedf994ac2d99a20fd4c4 + languageName: node + linkType: hard + +"@types/mailparser@npm:^3.4.6": + version: 3.4.6 + resolution: "@types/mailparser@npm:3.4.6" + dependencies: + "@types/node": "npm:*" + iconv-lite: "npm:^0.6.3" + checksum: 10/9a003d472acb51bcc012a3de560c82394414ffb495223213eb580dc1ecb8bae9443b54aa917ad6475d5a1a22a7cd7290cb81baf26238fc22446cde436cd82404 + languageName: node + linkType: hard + +"@types/mime-types@npm:^2": + version: 2.1.4 + resolution: "@types/mime-types@npm:2.1.4" + checksum: 10/f8c521c54ee0c0b9f90a65356a80b1413ed27ccdc94f5c7ebb3de5d63cedb559cd2610ea55b4100805c7349606a920d96e54f2d16b2f0afa6b7cd5253967ccc9 + languageName: node + linkType: hard + +"@types/mjml-core@npm:*": + version: 4.15.2 + resolution: "@types/mjml-core@npm:4.15.2" + checksum: 10/7c29f5409b4abf14153c27d0249d4169d23fa97b9b2b50fef3527a17b547e11c692cc451398a2400c505f11670c9204cff3cf5a158ee55690ad092d03b782bbb + languageName: node + linkType: hard + +"@types/mjml@npm:^4.7.4": + version: 4.7.4 + resolution: "@types/mjml@npm:4.7.4" + dependencies: + "@types/mjml-core": "npm:*" + checksum: 10/097b46f6d79c02c6d17681d5722bd476f7df7418666673d187824c30994847cd2909bdb0351a878b0690ff5e7bf096a9216e12910c82cba9b88a4ce1b45d803b + languageName: node + linkType: hard + +"@types/ms@npm:*": + version: 2.1.0 + resolution: "@types/ms@npm:2.1.0" + checksum: 10/532d2ebb91937ccc4a89389715e5b47d4c66e708d15942fe6cc25add6dc37b2be058230a327dd50f43f89b8b6d5d52b74685a9e8f70516edfc9bdd6be910eff4 + languageName: node + linkType: hard + +"@types/multer@npm:^1.4.12": + version: 1.4.13 + resolution: "@types/multer@npm:1.4.13" + dependencies: + "@types/express": "npm:*" + checksum: 10/f650a9f431007f05a5c51d6359140bea5f77d1299668d45a0791ddbf7789f1c220a3e038039c5e0de6608e273d5ee8001e9649c606d7e0e847e8774ccc5d1d68 + languageName: node + linkType: hard + +"@types/node@npm:*, @types/node@npm:>=10.0.0, @types/node@npm:>=13.7.0, @types/node@npm:>=8.1.0": + version: 25.0.7 + resolution: "@types/node@npm:25.0.7" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10/1bf778bd087160903bca1d1d0ed9e0c25abd12ac9322a4d58a6918d110c8a3eaa2f5dc1437e0009b57bfa2791c2b409ebdb4bb4dc0c2e8f096db07e285e63578 + languageName: node + linkType: hard + +"@types/node@npm:^14.0.1": + version: 14.18.63 + resolution: "@types/node@npm:14.18.63" + checksum: 10/82a7775898c2ea6db0b610a463512206fb2c7adc1af482c7eb44b99d94375fff51c74f67ae75a63c5532971159f30c866a4d308000624ef02fd9a7175e277019 + languageName: node + linkType: hard + +"@types/node@npm:^22.15.24, @types/node@npm:^22.18.6": + version: 22.19.5 + resolution: "@types/node@npm:22.19.5" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10/59662e5e1cf5039ab82677a050ed8f7428510695aef160b10809a29396d98fea671e6e203f7fca98fc59d8d3f36c32efea34d34e7c18346d4ed2129d56e9293c + languageName: node + linkType: hard + +"@types/nodemailer@npm:^6.4.17": + version: 6.4.21 + resolution: "@types/nodemailer@npm:6.4.21" + dependencies: + "@aws-sdk/client-ses": "npm:^3.731.1" + "@types/node": "npm:*" + checksum: 10/3388d11defbef0b1b471720f88ff2fd206ab5879bc7089d90d3467198f869299dc374f01cf07054025c7e87b186a7297b1040d318d65e9ec19a130732aa1448d + languageName: node + linkType: hard + +"@types/pug@npm:^2.0.10": + version: 2.0.10 + resolution: "@types/pug@npm:2.0.10" + checksum: 10/b7331b87ceffe6a55b200d0fbfd3f4defdc6703ce21d25a8f3f01d9022a3b36ab0e2ee7d6eeb16b82b6375c0cadf35e1633219775237cb0c62e785fbd35df57c + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.14.0 + resolution: "@types/qs@npm:6.14.0" + checksum: 10/1909205514d22b3cbc7c2314e2bd8056d5f05dfb21cf4377f0730ee5e338ea19957c41735d5e4806c746176563f50005bbab602d8358432e25d900bdf4970826 + languageName: node + linkType: hard + +"@types/quoted-printable@npm:^1": + version: 1.0.2 + resolution: "@types/quoted-printable@npm:1.0.2" + checksum: 10/4ea61f57cc15cfa47a5229f1a8125ad6d35671b0ff23712d67efa5390e34d89043923062780febe9d510673e5b79ffba5ec4433c8adc5e7003c4b669eb7ebf3a + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 10/95640233b689dfbd85b8c6ee268812a732cf36d5affead89e806fe30da9a430767af8ef2cd661024fd97e19d61f3dec75af2df5e80ec3bea000019ab7028629a + languageName: node + linkType: hard + +"@types/responselike@npm:^1.0.0": + version: 1.0.3 + resolution: "@types/responselike@npm:1.0.3" + dependencies: + "@types/node": "npm:*" + checksum: 10/6ac4b35723429b11b117e813c7acc42c3af8b5554caaf1fc750404c1ae59f9b7376bc69b9e9e194a5a97357a597c2228b7173d317320f0360d617b6425212f58 + languageName: node + linkType: hard + +"@types/retry@npm:0.12.2": + version: 0.12.2 + resolution: "@types/retry@npm:0.12.2" + checksum: 10/e5675035717b39ce4f42f339657cae9637cf0c0051cf54314a6a2c44d38d91f6544be9ddc0280587789b6afd056be5d99dbe3e9f4df68c286c36321579b1bf4a + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 1.2.1 + resolution: "@types/send@npm:1.2.1" + dependencies: + "@types/node": "npm:*" + checksum: 10/81ef5790037ba1d2d458392e4241501f0f8b4838cc8797e169e179e099410e12069ec68e8dbd39211cb097c4a9b1ff1682dbcea897ab4ce21dad93438b862d27 + languageName: node + linkType: hard + +"@types/serve-static@npm:^2": + version: 2.2.0 + resolution: "@types/serve-static@npm:2.2.0" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + checksum: 10/f2bad1304c7d0d3b7221faff3e490c40129d3803f4fb1b2fb84f31f561071c5e6a4b876c41bbbe82d5645034eea936e946bcaaf993dac1093ce68b56effad6e0 + languageName: node + linkType: hard + +"@types/triple-beam@npm:^1.3.2": + version: 1.3.5 + resolution: "@types/triple-beam@npm:1.3.5" + checksum: 10/519b6a1b30d4571965c9706ad5400a200b94e4050feca3e7856e3ea7ac00ec9903e32e9a10e2762d0f7e472d5d03e5f4b29c16c0bd8c1f77c8876c683b2231f1 + languageName: node + linkType: hard + +"@types/uuid@npm:^10.0.0": + version: 10.0.0 + resolution: "@types/uuid@npm:10.0.0" + checksum: 10/e3958f8b0fe551c86c14431f5940c3470127293280830684154b91dc7eb3514aeb79fe3216968833cf79d4d1c67f580f054b5be2cd562bebf4f728913e73e944 + languageName: node + linkType: hard + +"@types/validator@npm:^13.15.3": + version: 13.15.10 + resolution: "@types/validator@npm:13.15.10" + checksum: 10/63117a776ced4d066d7fb63130d90ba487d38209dd45c25641ca1a6f5040e8394cc9a855750b919b72a923c5ffb51f8474f213b10b5aaa27d9db108bef07ad10 + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/eslint-plugin@npm:8.53.0" + dependencies: + "@eslint-community/regexpp": "npm:^4.12.2" + "@typescript-eslint/scope-manager": "npm:8.53.0" + "@typescript-eslint/type-utils": "npm:8.53.0" + "@typescript-eslint/utils": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + ignore: "npm:^7.0.5" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.4.0" + peerDependencies: + "@typescript-eslint/parser": ^8.53.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/2cbfa92d21018d53b33db102500f121cedd67405939a11c20d04a0fdc535412f1e554479a9994a244127a151609fe16ae8bce810749261f243eac13360df1ab1 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/parser@npm:8.53.0" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.53.0" + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + debug: "npm:^4.4.3" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/5337f472aeb3d04041a3c9c9e9d9884e685ba7e4f722ab2963f1054087a62a42946dd0d39993e60506efef0d2a4cc1b0619b34e49261913d6f4d8cdbf3490d56 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/project-service@npm:8.53.0" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.53.0" + "@typescript-eslint/types": "npm:^8.53.0" + debug: "npm:^4.4.3" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10/2f232f241f57c0f42194a8bcb8c207e4ed4345d7cc097434d394c2904338e64f386903931395ef97cd2cf3ae33d98645f0d6164660d794e33259e2c3978052ff + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/scope-manager@npm:8.53.0" + dependencies: + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + checksum: 10/40a651cfc16f9464f92b5a58492207c1f89a1ff98cfedd2d33d1dbe8234ce50c3a543267f1b489f903b001e0abcaf1568e7c9b70c009871c34af6ef3602ac0bf + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.53.0, @typescript-eslint/tsconfig-utils@npm:^8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.53.0" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10/91f1f02ec8a3daf7d3dc9e43a847ef834444a6e073e3a4a07a311d898b225124d9c4abb4b48266d821f0ea4225614266084e5157182e7ba7aaecafefbae00c7e + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/type-utils@npm:8.53.0" + dependencies: + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" + "@typescript-eslint/utils": "npm:8.53.0" + debug: "npm:^4.4.3" + ts-api-utils: "npm:^2.4.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/5be4036b475bbc4bb9a834beefe8114286bbe2dee54c96c65c02d6ceabac3422605802dcbefdbf20ae9ede3c85bf2f650eda2acc7ed1a3bf75f02ed478e7cdd1 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:8.53.0, @typescript-eslint/types@npm:^8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/types@npm:8.53.0" + checksum: 10/36ee696a92ed575385b5c1ccc46e3fec9c5d9aa6f3640f8ad0234ed5a763c9ab78c7d3419fd3d462a966f6b95472390b8040055e4e73c75c52671478e90749ff + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/typescript-estree@npm:8.53.0" + dependencies: + "@typescript-eslint/project-service": "npm:8.53.0" + "@typescript-eslint/tsconfig-utils": "npm:8.53.0" + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/visitor-keys": "npm:8.53.0" + debug: "npm:^4.4.3" + minimatch: "npm:^9.0.5" + semver: "npm:^7.7.3" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.4.0" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10/bdacb2f3ffde535c3955bbfbd062d2010943f7693034cde4019ccde699e826e7ef91d7e1d2f3652c30584c013924410dae5056417909e8169f1e3d7272636bd9 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/utils@npm:8.53.0" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.9.1" + "@typescript-eslint/scope-manager": "npm:8.53.0" + "@typescript-eslint/types": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/ef123c8531de793d8d4f5fa51076402bfe809481feaee605086986c370c94361c525ec550b2c4c6703cf60e026e87862428c044c763ead3ea9bf9bce8ad79310 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.53.0": + version: 8.53.0 + resolution: "@typescript-eslint/visitor-keys@npm:8.53.0" + dependencies: + "@typescript-eslint/types": "npm:8.53.0" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10/879e1dfbd002059c0eb59f9660c26eb71a1643622906e4af444dbe5297e95ad210d763b53308b6372b55d85159a161982a8848352706a7d361fd3e17d6ba96d0 + languageName: node + linkType: hard + +"@tyriar/fibonacci-heap@npm:^2.0.7": + version: 2.0.9 + resolution: "@tyriar/fibonacci-heap@npm:2.0.9" + checksum: 10/5c9ae30a8be47290610217cbb77ff9d0edefd386de899070f64fa4a9baf01a46147b0034a14ea80de601ab0d1521114d0c952a588608743d7aaffafdd7a0b109 + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 10/80d6910946f2b1552a2406650051c91bbd1f24a6bf854354203d84fe2714b3e8ce4618f49cc3410494173a1c1e8e9777372fe68dce74bd45faf0a7a1a6ccf448 + languageName: node + linkType: hard + +"@voximplant/apiclient-nodejs@npm:^4.2.0": + version: 4.5.0 + resolution: "@voximplant/apiclient-nodejs@npm:4.5.0" + dependencies: + axios: "npm:0.21.4" + form-data: "npm:2.5.1" + jsonwebtoken: "npm:9.0.2" + checksum: 10/470a803b40578e6b4fa84333dbbe5ee589257a138388655394846a6fc4079c44d3f83667e0c8fc71c0d8ee593dc9fb9bbf7f369ad923025568557d3c39c0c28b + languageName: node + linkType: hard + +"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/ast@npm:1.14.1" + dependencies: + "@webassemblyjs/helper-numbers": "npm:1.13.2" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + checksum: 10/f83e6abe38057f5d87c1fb356513a371a8b43c9b87657f2790741a66b1ef8ecf958d1391bc42f27c5fb33f58ab8286a38ea849fdd21f433cd4df1307424bab45 + languageName: node + linkType: hard + +"@webassemblyjs/floating-point-hex-parser@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.13.2" + checksum: 10/e866ec8433f4a70baa511df5e8f2ebcd6c24f4e2cc6274c7c5aabe2bcce3459ea4680e0f35d450e1f3602acf3913b6b8e4f15069c8cfd34ae8609fb9a7d01795 + languageName: node + linkType: hard + +"@webassemblyjs/helper-api-error@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-api-error@npm:1.13.2" + checksum: 10/48b5df7fd3095bb252f59a139fe2cbd999a62ac9b488123e9a0da3906ad8a2f2da7b2eb21d328c01a90da987380928706395c2897d1f3ed9e2125b6d75a920d0 + languageName: node + linkType: hard + +"@webassemblyjs/helper-buffer@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.14.1" + checksum: 10/9690afeafa5e765a34620aa6216e9d40f9126d4e37e9726a2594bf60cab6b211ef20ab6670fd3c4449dd4a3497e69e49b2b725c8da0fb213208c7f45f15f5d5b + languageName: node + linkType: hard + +"@webassemblyjs/helper-numbers@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-numbers@npm:1.13.2" + dependencies: + "@webassemblyjs/floating-point-hex-parser": "npm:1.13.2" + "@webassemblyjs/helper-api-error": "npm:1.13.2" + "@xtuc/long": "npm:4.2.2" + checksum: 10/e4c7d0b09811e1cda8eec644a022b560b28f4e974f50195375ccd007df5ee48a922a6dcff5ac40b6a8ec850d56d0ea6419318eee49fec7819ede14e90417a6a4 + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-bytecode@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.13.2" + checksum: 10/3edd191fff7296df1ef3b023bdbe6cb5ea668f6386fd197ccfce46015c6f2a8cc9763cfb86503a0b94973ad27996645afff2252ee39a236513833259a47af6ed + languageName: node + linkType: hard + +"@webassemblyjs/helper-wasm-section@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + checksum: 10/6b73874f906532512371181d7088460f767966f26309e836060c5a8e4e4bfe6d523fb5f4c034b34aa22ebb1192815f95f0e264298769485c1f0980fdd63ae0ce + languageName: node + linkType: hard + +"@webassemblyjs/ieee754@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/ieee754@npm:1.13.2" + dependencies: + "@xtuc/ieee754": "npm:^1.2.0" + checksum: 10/d7e3520baa37a7309fa7db4d73d69fb869878853b1ebd4b168821bd03fcc4c0e1669c06231315b0039035d9a7a462e53de3ad982da4a426a4b0743b5888e8673 + languageName: node + linkType: hard + +"@webassemblyjs/leb128@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/leb128@npm:1.13.2" + dependencies: + "@xtuc/long": "npm:4.2.2" + checksum: 10/3a10542c86807061ec3230bac8ee732289c852b6bceb4b88ebd521a12fbcecec7c432848284b298154f28619e2746efbed19d6904aef06c49ef20a0b85f650cf + languageName: node + linkType: hard + +"@webassemblyjs/utf8@npm:1.13.2": + version: 1.13.2 + resolution: "@webassemblyjs/utf8@npm:1.13.2" + checksum: 10/27885e5d19f339501feb210867d69613f281eda695ac508f04d69fa3398133d05b6870969c0242b054dc05420ed1cc49a64dea4fe0588c18d211cddb0117cc54 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-edit@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/helper-wasm-section": "npm:1.14.1" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + "@webassemblyjs/wasm-opt": "npm:1.14.1" + "@webassemblyjs/wasm-parser": "npm:1.14.1" + "@webassemblyjs/wast-printer": "npm:1.14.1" + checksum: 10/c62c50eadcf80876713f8c9f24106b18cf208160ab842fcb92060fd78c37bf37e7fcf0b7cbf1afc05d230277c2ce0f3f728432082c472dd1293e184a95f9dbdd + languageName: node + linkType: hard + +"@webassemblyjs/wasm-gen@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/ieee754": "npm:1.13.2" + "@webassemblyjs/leb128": "npm:1.13.2" + "@webassemblyjs/utf8": "npm:1.13.2" + checksum: 10/6085166b0987d3031355fe17a4f9ef0f412e08098d95454059aced2bd72a4c3df2bc099fa4d32d640551fc3eca1ac1a997b44432e46dc9d84642688e42c17ed4 + languageName: node + linkType: hard + +"@webassemblyjs/wasm-opt@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-buffer": "npm:1.14.1" + "@webassemblyjs/wasm-gen": "npm:1.14.1" + "@webassemblyjs/wasm-parser": "npm:1.14.1" + checksum: 10/fa5d1ef8d2156e7390927f938f513b7fb4440dd6804b3d6c8622b7b1cf25a3abf1a5809f615896d4918e04b27b52bc3cbcf18faf2d563cb563ae0a9204a492db + languageName: node + linkType: hard + +"@webassemblyjs/wasm-parser@npm:1.14.1, @webassemblyjs/wasm-parser@npm:^1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@webassemblyjs/helper-api-error": "npm:1.13.2" + "@webassemblyjs/helper-wasm-bytecode": "npm:1.13.2" + "@webassemblyjs/ieee754": "npm:1.13.2" + "@webassemblyjs/leb128": "npm:1.13.2" + "@webassemblyjs/utf8": "npm:1.13.2" + checksum: 10/07d9805fda88a893c984ed93d5a772d20d671e9731358ab61c6c1af8e0e58d1c42fc230c18974dfddebc9d2dd7775d514ba4d445e70080b16478b4b16c39c7d9 + languageName: node + linkType: hard + +"@webassemblyjs/wast-printer@npm:1.14.1": + version: 1.14.1 + resolution: "@webassemblyjs/wast-printer@npm:1.14.1" + dependencies: + "@webassemblyjs/ast": "npm:1.14.1" + "@xtuc/long": "npm:4.2.2" + checksum: 10/cef09aad2fcd291bfcf9efdae2ea1e961a1ba0f925d1d9dcdd8c746d32fbaf431b6d26a0241699c0e39f82139018aa720b4ceb84ac6f4c78f13072747480db69 + languageName: node + linkType: hard + +"@xhmikosr/archive-type@npm:^7.1.0": + version: 7.1.0 + resolution: "@xhmikosr/archive-type@npm:7.1.0" + dependencies: + file-type: "npm:^20.5.0" + checksum: 10/ce363bea7798572b74c0821248d9abbb28847290b298b02d142ee1f95f72561f1947e95121450980cf3ea03242ded914ba896681b7ca817f1a247653624d8d7e + languageName: node + linkType: hard + +"@xhmikosr/bin-check@npm:^7.1.0": + version: 7.1.0 + resolution: "@xhmikosr/bin-check@npm:7.1.0" + dependencies: + execa: "npm:^5.1.1" + isexe: "npm:^2.0.0" + checksum: 10/e087422baf51077af8937fe3b922a9e3b38d4389e99fe60545dc6ce3ee2545fa05152d544e5b7f30d7f1ad60c215bb46ca46984d80f1e4ac75579baf5b93581e + languageName: node + linkType: hard + +"@xhmikosr/bin-wrapper@npm:^13.0.5": + version: 13.2.0 + resolution: "@xhmikosr/bin-wrapper@npm:13.2.0" + dependencies: + "@xhmikosr/bin-check": "npm:^7.1.0" + "@xhmikosr/downloader": "npm:^15.2.0" + "@xhmikosr/os-filter-obj": "npm:^3.0.0" + bin-version-check: "npm:^5.1.0" + checksum: 10/5aa70b9e30a19924c244f046099af808fa6dc8b9799de72628d0b5836c6ddb68fee4c31ed5e6c4733db5ceb878b6a6f0218a42ebc67ece39aadfd81cefe3ba32 + languageName: node + linkType: hard + +"@xhmikosr/decompress-tar@npm:^8.0.1, @xhmikosr/decompress-tar@npm:^8.1.0": + version: 8.1.0 + resolution: "@xhmikosr/decompress-tar@npm:8.1.0" + dependencies: + file-type: "npm:^20.5.0" + is-stream: "npm:^2.0.1" + tar-stream: "npm:^3.1.7" + checksum: 10/c827aac52b6e894e8fb23815cfcfbdda656759c7fcb897e35a26369215ba61a7a9086fc90c82866e17a9bec3355a106b1f4e7c89a9de01181c976a10ef454fa4 + languageName: node + linkType: hard + +"@xhmikosr/decompress-tarbz2@npm:^8.1.0": + version: 8.1.0 + resolution: "@xhmikosr/decompress-tarbz2@npm:8.1.0" + dependencies: + "@xhmikosr/decompress-tar": "npm:^8.0.1" + file-type: "npm:^20.5.0" + is-stream: "npm:^2.0.1" + seek-bzip: "npm:^2.0.0" + unbzip2-stream: "npm:^1.4.3" + checksum: 10/17ead7b0fe1719ce6f2918b9b4bd8fe53c5ebd99400f47528fb6eb3d9f217ff91dd79c7619267190a037187f2fe6482e80d261ab8ae97c9dac29dab7de8f4913 + languageName: node + linkType: hard + +"@xhmikosr/decompress-targz@npm:^8.1.0": + version: 8.1.0 + resolution: "@xhmikosr/decompress-targz@npm:8.1.0" + dependencies: + "@xhmikosr/decompress-tar": "npm:^8.0.1" + file-type: "npm:^20.5.0" + is-stream: "npm:^2.0.1" + checksum: 10/d2eca44f03f2935e1b687cf61402862bbf0c71c7d1b10f6b6b94c0ea16a6839d820355ec434cf1bbc42a36e3891e52925a38ea6c3f5022746f720643ac9ce039 + languageName: node + linkType: hard + +"@xhmikosr/decompress-unzip@npm:^7.1.0": + version: 7.1.0 + resolution: "@xhmikosr/decompress-unzip@npm:7.1.0" + dependencies: + file-type: "npm:^20.5.0" + get-stream: "npm:^6.0.1" + yauzl: "npm:^3.1.2" + checksum: 10/7b085f50472cfeadfa690110a8f39d4177c90a30db08d6dd988abb0a0504eec650c0df7f0f3b538aa909d0653dea6652798f3851a546e742badb161ef75b94f4 + languageName: node + linkType: hard + +"@xhmikosr/decompress@npm:^10.2.0": + version: 10.2.0 + resolution: "@xhmikosr/decompress@npm:10.2.0" + dependencies: + "@xhmikosr/decompress-tar": "npm:^8.1.0" + "@xhmikosr/decompress-tarbz2": "npm:^8.1.0" + "@xhmikosr/decompress-targz": "npm:^8.1.0" + "@xhmikosr/decompress-unzip": "npm:^7.1.0" + graceful-fs: "npm:^4.2.11" + strip-dirs: "npm:^3.0.0" + checksum: 10/083f59bda6440b698322802e61c2b68e47d9a7f3c90d432d91a260b1630bda3c19088fb0cc8a42c67754a9de86e546f68c8d314408ff72180f0bba2b6ef65f81 + languageName: node + linkType: hard + +"@xhmikosr/downloader@npm:^15.2.0": + version: 15.2.0 + resolution: "@xhmikosr/downloader@npm:15.2.0" + dependencies: + "@xhmikosr/archive-type": "npm:^7.1.0" + "@xhmikosr/decompress": "npm:^10.2.0" + content-disposition: "npm:^0.5.4" + defaults: "npm:^2.0.2" + ext-name: "npm:^5.0.0" + file-type: "npm:^20.5.0" + filenamify: "npm:^6.0.0" + get-stream: "npm:^6.0.1" + got: "npm:^13.0.0" + checksum: 10/16b1a80aeca8d4a82643c477df2b5616d464da94c0279eeb53f03d4ccacc0c96e107cf8c109b8f9d26d30c4f01af5713e396b986afc8218573c7bdf318eaf399 + languageName: node + linkType: hard + +"@xhmikosr/os-filter-obj@npm:^3.0.0": + version: 3.0.0 + resolution: "@xhmikosr/os-filter-obj@npm:3.0.0" + dependencies: + arch: "npm:^3.0.0" + checksum: 10/8ec5e94e0a9f612b22997fb6cd2e82b121e03106ed0cf3404163b54c11812278e1f350d67af42d7c7093451c99d9755a6d6a284d8dae27b2b262790237e74193 + languageName: node + linkType: hard + +"@xmldom/xmldom@npm:^0.9.8": + version: 0.9.8 + resolution: "@xmldom/xmldom@npm:0.9.8" + checksum: 10/5f88d27a50bb624b0b46b8359853e1a3501217e743b132c5cbe76115646bb4b7a2128c1f0c32f045c7b452689f5206e9bba7d7292c6c13a81eed5e43c2e5ec7c + languageName: node + linkType: hard + +"@xtuc/ieee754@npm:^1.2.0": + version: 1.2.0 + resolution: "@xtuc/ieee754@npm:1.2.0" + checksum: 10/ab033b032927d77e2f9fa67accdf31b1ca7440974c21c9cfabc8349e10ca2817646171c4f23be98d0e31896d6c2c3462a074fe37752e523abc3e45c79254259c + languageName: node + linkType: hard + +"@xtuc/long@npm:4.2.2": + version: 4.2.2 + resolution: "@xtuc/long@npm:4.2.2" + checksum: 10/7217bae9fe240e0d804969e7b2af11cb04ec608837c78b56ca88831991b287e232a0b7fce8d548beaff42aaf0197ffa471d81be6ac4c4e53b0148025a2c076ec + languageName: node + linkType: hard + +"@zone-eu/mailsplit@npm:5.4.8": + version: 5.4.8 + resolution: "@zone-eu/mailsplit@npm:5.4.8" + dependencies: + libbase64: "npm:1.3.0" + libmime: "npm:5.3.7" + libqp: "npm:2.1.1" + checksum: 10/31f78be7aef62df7ca62edf9de2ef426ac979d5a516ae8def9c76eea911bda92d8cd8d36a0e8c203cc975d9e5d25faf4b8056f1883a1d615d550684bb6fd38ae + languageName: node + linkType: hard + +"abbrev@npm:^2.0.0": + version: 2.0.0 + resolution: "abbrev@npm:2.0.0" + checksum: 10/ca0a54e35bea4ece0ecb68a47b312e1a9a6f772408d5bcb9051230aaa94b0460671c5b5c9cb3240eb5b7bc94c52476550eb221f65a0bbd0145bdc9f3113a6707 + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10/e2f0c6a6708ad738b3e8f50233f4800de31ad41a6cdc50e0cbe51b76fed69fd0213516d92c15ce1a9985fca71a14606a9be22bf00f8475a58987b9bfb671c582 + languageName: node + linkType: hard + +"accepts@npm:^2.0.0": + version: 2.0.0 + resolution: "accepts@npm:2.0.0" + dependencies: + mime-types: "npm:^3.0.0" + negotiator: "npm:^1.0.0" + checksum: 10/ea1343992b40b2bfb3a3113fa9c3c2f918ba0f9197ae565c48d3f84d44b174f6b1d5cd9989decd7655963eb03a272abc36968cc439c2907f999bd5ef8653d5a7 + languageName: node + linkType: hard + +"accepts@npm:~1.3.4": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 10/67eaaa90e2917c58418e7a9b89392002d2b1ccd69bcca4799135d0c632f3b082f23f4ae4ddeedbced5aa59bcc7bdf4699c69ebed4593696c922462b7bc5744d6 + languageName: node + linkType: hard + +"acorn-import-attributes@npm:^1.9.5": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" + peerDependencies: + acorn: ^8 + checksum: 10/8bfbfbb6e2467b9b47abb4d095df717ab64fce2525da65eabee073e85e7975fb3a176b6c8bba17c99a7d8ede283a10a590272304eb54a93c4aa1af9790d47a8b + languageName: node + linkType: hard + +"acorn-import-phases@npm:^1.0.3": + version: 1.0.4 + resolution: "acorn-import-phases@npm:1.0.4" + peerDependencies: + acorn: ^8.14.0 + checksum: 10/471050ac7d9b61909c837b426de9eeef2958997f6277ad7dea88d5894fd9b3245d8ed4a225c2ca44f814dbb20688009db7a80e525e8196fc9e98c5285b66161d + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10/d4371eaef7995530b5b5ca4183ff6f062ca17901a6d3f673c9ac011b01ede37e7a1f7f61f8f5cfe709e88054757bb8f3277dc4061087cdf4f2a1f90ccbcdb977 + languageName: node + linkType: hard + +"acorn-walk@npm:^8.1.1": + version: 8.3.4 + resolution: "acorn-walk@npm:8.3.4" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10/871386764e1451c637bb8ab9f76f4995d408057e9909be6fb5ad68537ae3375d85e6a6f170b98989f44ab3ff6c74ad120bc2779a3d577606e7a0cd2b4efcaf77 + languageName: node + linkType: hard + +"acorn@npm:^7.1.1": + version: 7.4.1 + resolution: "acorn@npm:7.4.1" + bin: + acorn: bin/acorn + checksum: 10/8be2a40714756d713dfb62544128adce3b7102c6eb94bc312af196c2cc4af76e5b93079bd66b05e9ca31b35a9b0ce12171d16bc55f366cafdb794fdab9d753ec + languageName: node + linkType: hard + +"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.15.0, acorn@npm:^8.4.1": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10/77f2de5051a631cf1729c090e5759148459cdb76b5f5c70f890503d629cf5052357b0ce783c0f976dd8a93c5150f59f6d18df1def3f502396a20f81282482fa4 + languageName: node + linkType: hard + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10/21fb903e0917e5cb16591b4d0ef6a028a54b83ac30cd1fca58dece3d4e0990512a8723f9f83130d88a41e2af8b1f7be1386fda3ea2d181bb1a62155e75e95e23 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10/79bef167247789f955aaba113bae74bf64aa1e1acca4b1d6bb444bdf91d82c3e07e9451ef6a6e2e35e8f71a6f97ce33e3d855a5328eb9fad1bc3cc4cfd031ed8 + languageName: node + linkType: hard + +"ajv-formats@npm:3.0.1": + version: 3.0.1 + resolution: "ajv-formats@npm:3.0.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10/5679b9f9ced9d0213a202a37f3aa91efcffe59a6de1a6e3da5c873344d3c161820a1f11cc29899661fee36271fd2895dd3851b6461c902a752ad661d1c1e8722 + languageName: node + linkType: hard + +"ajv-formats@npm:^2.1.1": + version: 2.1.1 + resolution: "ajv-formats@npm:2.1.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10/70c263ded219bf277ffd9127f793b625f10a46113b2e901e150da41931fcfd7f5592da6d66862f4449bb157ffe65867c3294a7df1d661cc232c4163d5a1718ed + languageName: node + linkType: hard + +"ajv-keywords@npm:^3.5.2": + version: 3.5.2 + resolution: "ajv-keywords@npm:3.5.2" + peerDependencies: + ajv: ^6.9.1 + checksum: 10/d57c9d5bf8849bddcbd801b79bc3d2ddc736c2adb6b93a6a365429589dd7993ddbd5d37c6025ed6a7f89c27506b80131d5345c5b1fa6a97e40cd10a96bcd228c + languageName: node + linkType: hard + +"ajv-keywords@npm:^5.1.0": + version: 5.1.0 + resolution: "ajv-keywords@npm:5.1.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + peerDependencies: + ajv: ^8.8.2 + checksum: 10/5021f96ab7ddd03a4005326bd06f45f448ebfbb0fe7018b1b70b6c28142fa68372bda2057359814b83fd0b2d4c8726c297f0a7557b15377be7b56ce5344533d8 + languageName: node + linkType: hard + +"ajv@npm:8.17.1, ajv@npm:^8.0.0, ajv@npm:^8.9.0": + version: 8.17.1 + resolution: "ajv@npm:8.17.1" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10/ee3c62162c953e91986c838f004132b6a253d700f1e51253b99791e2dbfdb39161bc950ebdc2f156f8568035bb5ed8be7bd78289cd9ecbf3381fe8f5b82e3f33 + languageName: node + linkType: hard + +"ajv@npm:^6.12.4, ajv@npm:^6.12.5": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10/48d6ad21138d12eb4d16d878d630079a2bda25a04e745c07846a4ad768319533031e28872a9b3c5790fa1ec41aabdf2abed30a56e5a03ebc2cf92184b8ee306c + languageName: node + linkType: hard + +"alce@npm:1.2.0": + version: 1.2.0 + resolution: "alce@npm:1.2.0" + dependencies: + esprima: "npm:^1.2.0" + estraverse: "npm:^1.5.0" + checksum: 10/71cae3f84c712188ea55a7411133bda2e7702a55b2dccbc7a3c3da2c63d9abda5571b6bda574a393ef5432929d28e392b25c03d41e102905aeb215694573ad2d + languageName: node + linkType: hard + +"amwork-backend@workspace:.": + version: 0.0.0-use.local + resolution: "amwork-backend@workspace:." + dependencies: + "@amwork/voximplant-apiclient-nodejs": "npm:^2.3.0-f" + "@aws-sdk/client-s3": "npm:^3.817.0" + "@camunda8/sdk": "npm:^8.7.9" + "@date-fns/tz": "npm:^1.2.0" + "@date-fns/utc": "npm:^2.1.0" + "@eslint/js": "npm:^9.27.0" + "@esm2cjs/cacheable-lookup": "npm:^7.0.0" + "@faker-js/faker": "npm:^9.8.0" + "@nestjs-modules/mailer": "npm:2.0.2" + "@nestjs/axios": "npm:^4.0.0" + "@nestjs/cli": "npm:^11.0.7" + "@nestjs/common": "npm:^11.1.2" + "@nestjs/config": "npm:^4.0.2" + "@nestjs/core": "npm:^11.1.2" + "@nestjs/event-emitter": "npm:^3.0.1" + "@nestjs/jwt": "npm:^11.0.0" + "@nestjs/platform-express": "npm:^11.1.2" + "@nestjs/platform-socket.io": "npm:^11.1.2" + "@nestjs/schedule": "npm:^6.0.0" + "@nestjs/schematics": "npm:^11.0.5" + "@nestjs/swagger": "npm:^11.2.0" + "@nestjs/terminus": "npm:^11.0.0" + "@nestjs/typeorm": "npm:^11.0.0" + "@nestjs/websockets": "npm:^11.1.2" + "@newrelic/native-metrics": "npm:^11.1.0" + "@swc/cli": "npm:^0.7.7" + "@swc/core": "npm:^1.11.29" + "@total-typescript/ts-reset": "npm:^0.6.1" + "@types/bcrypt": "npm:^5.0.2" + "@types/cookie-parser": "npm:^1" + "@types/express": "npm:^5.0.2" + "@types/heapdump": "npm:^0" + "@types/html-to-text": "npm:^9.0.4" + "@types/imap-simple": "npm:^4.2.10" + "@types/mailparser": "npm:^3.4.6" + "@types/mime-types": "npm:^2" + "@types/multer": "npm:^1.4.12" + "@types/node": "npm:^22.15.24" + "@types/nodemailer": "npm:^6.4.17" + "@types/quoted-printable": "npm:^1" + "@types/uuid": "npm:^10.0.0" + "@voximplant/apiclient-nodejs": "npm:^4.2.0" + angular-expressions: "npm:^1.4.3" + axios: "npm:^1.9.0" + bcrypt: "npm:^6.0.0" + class-transformer: "npm:^0.5.1" + class-validator: "npm:^0.14.2" + cookie-parser: "npm:^1.4.7" + date-fns: "npm:^4.1.0" + date-fns-tz: "npm:^3.2.0" + decimal.js: "npm:^10.5.0" + docxtemplater: "npm:^3.63.2" + dotenv: "npm:^16.6.1" + eslint: "npm:^9.27.0" + eslint-config-prettier: "npm:^10.1.5" + eslint-plugin-prettier: "npm:^5.4.0" + exceljs: "npm:^4.4.0" + express: "npm:^5.1.0" + express-rate-limit: "npm:^8.2.1" + form-data: "npm:^4.0.2" + generate-password: "npm:^1.7.1" + googleapis: "npm:^149.0.0" + handlebars: "npm:^4.7.8" + heapdump: "npm:^0.3.15" + helmet: "npm:^8.1.0" + html-to-text: "npm:^9.0.5" + iconv-lite: "npm:^0.6.3" + imap-simple: "npm:^5.1.0" + imapflow: "npm:^1.0.187" + ioredis: "npm:^5.6.1" + jsonwebtoken: "npm:^9.0.2" + knip: "npm:^5.59.1" + libphonenumber-js: "npm:^1.12.8" + lvovich: "npm:^2.0.2" + mailparser: "npm:^3.7.3" + mathjs: "npm:^14.5.1" + mime-types: "npm:^3.0.1" + multer: "npm:^2.0.0" + nest-winston: "npm:^1.10.2" + newrelic: "npm:^12.20.0" + nodemailer: "npm:^7.0.3" + number-to-words-ru: "npm:^2.4.1" + path-to-regexp: "npm:^8.2.0" + pg: "npm:^8.16.0" + pizzip: "npm:^3.2.0" + prettier: "npm:^3.5.3" + qs: "npm:^6.14.0" + quoted-printable: "npm:^1.0.1" + reflect-metadata: "npm:^0.2.2" + rimraf: "npm:^6.0.1" + rxjs: "npm:^7.8.2" + sharp: "npm:^0.34.2" + slugify: "npm:^1.6.6" + socket.io: "npm:^4.8.1" + stripe: "npm:^17.7.0" + ts-node: "npm:^10.9.2" + tsconfig-paths: "npm:4.2.0" + twilio: "npm:^5.7.0" + typeorm: "npm:^0.3.24" + typeorm-naming-strategies: "npm:^4.1.0" + typescript: "npm:^5.8.3" + typescript-eslint: "npm:^8.33.0" + uuid: "npm:^11.1.0" + winston: "npm:^3.17.0" + written-number: "npm:^0.11.1" + languageName: unknown + linkType: soft + +"angular-expressions@npm:^1.4.3": + version: 1.5.1 + resolution: "angular-expressions@npm:1.5.1" + checksum: 10/68b579848eaaf745de510eff918d983d403d70c54ecdf2714a3390935dac8d180d45bb8490d69378308a03eeb73ba70f4419193cfdb9db211c8186b22001052b + languageName: node + linkType: hard + +"ansi-align@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: "npm:^4.1.0" + checksum: 10/4c7e8b6a10eaf18874ecee964b5db62ac86d0b9266ad4987b3a1efcb5d11a9e12c881ee40d14951833135a8966f10a3efe43f9c78286a6e632f53d85ad28b9c0 + languageName: node + linkType: hard + +"ansi-colors@npm:4.1.3, ansi-colors@npm:^4.1.1": + version: 4.1.3 + resolution: "ansi-colors@npm:4.1.3" + checksum: 10/43d6e2fc7b1c6e4dc373de708ee76311ec2e0433e7e8bd3194e7ff123ea6a747428fc61afdcf5969da5be3a5f0fd054602bec56fc0ebe249ce2fcde6e649e3c2 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 10/2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.2.2 + resolution: "ansi-regex@npm:6.2.2" + checksum: 10/9b17ce2c6daecc75bcd5966b9ad672c23b184dc3ed9bf3c98a0702f0d2f736c15c10d461913568f2cf527a5e64291c7473358885dd493305c84a1cfed66ba94f + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: "npm:^1.9.0" + checksum: 10/d85ade01c10e5dd77b6c89f34ed7531da5830d2cb5882c645f330079975b716438cd7ebb81d0d6e6b4f9c577f19ae41ab55f07f19786b02f9dfd9e0377395665 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10/b4494dfbfc7e4591b4711a396bd27e540f8153914123dccb4cdbbcb514015ada63a3809f362b9d8d4f6b17a706f1d7bea3c6f974b15fa5ae76b5b502070889ff + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10/c49dad7639f3e48859bd51824c93b9eb0db628afc243c51c3dd2410c4a15ede1a83881c6c7341aa2b159c4f90c11befb38f2ba848c07c66c9f9de4bcd7cb9f30 + languageName: node + linkType: hard + +"ansis@npm:4.2.0, ansis@npm:^4.2.0": + version: 4.2.0 + resolution: "ansis@npm:4.2.0" + checksum: 10/493e15fad267bd6e3e275d6886c3b3c96a075784d9eae3e16d16383d488e94cc3deb1b357e1246f572599767360548ef9e5b7eab9b72e4ee3f7bad9ce6bc8797 + languageName: node + linkType: hard + +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: "npm:^3.0.0" + picomatch: "npm:^2.0.4" + checksum: 10/3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 + languageName: node + linkType: hard + +"app-root-path@npm:^3.1.0": + version: 3.1.0 + resolution: "app-root-path@npm:3.1.0" + checksum: 10/b4cdab5f7e51ec43fa04c97eca2adedf8e18d6c3dd21cd775b70457c5e71f0441c692a49dcceb426f192640b7393dcd41d85c36ef98ecb7c785a53159c912def + languageName: node + linkType: hard + +"append-field@npm:^1.0.0": + version: 1.0.0 + resolution: "append-field@npm:1.0.0" + checksum: 10/afb50f5ff668af1cb66bc5cfebb55ed9a1d99e24901782ee83d00aed1a499835f9375a149cf27b17f79595ecfcc3d1de0cd5b020b210a5359c43eaf607c217de + languageName: node + linkType: hard + +"arch@npm:^3.0.0": + version: 3.0.0 + resolution: "arch@npm:3.0.0" + checksum: 10/9af0c58900980c300737945881859df6dd2a4e4d07f697c77704a7ba85a701aa60aa7c3a3ce1eb57ef76fda726ebccf1e2a9ddd763c89fe82c961d55b4b9c374 + languageName: node + linkType: hard + +"archiver-utils@npm:^2.1.0": + version: 2.1.0 + resolution: "archiver-utils@npm:2.1.0" + dependencies: + glob: "npm:^7.1.4" + graceful-fs: "npm:^4.2.0" + lazystream: "npm:^1.0.0" + lodash.defaults: "npm:^4.2.0" + lodash.difference: "npm:^4.5.0" + lodash.flatten: "npm:^4.4.0" + lodash.isplainobject: "npm:^4.0.6" + lodash.union: "npm:^4.6.0" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^2.0.0" + checksum: 10/4df493c0e6a3a544119b08b350308923500e2c6efee6a283cba4c3202293ce3acb70897e54e24f735e3a38ff43e5a65f66e2e5225fdfc955bf2335491377be2e + languageName: node + linkType: hard + +"archiver-utils@npm:^3.0.4": + version: 3.0.4 + resolution: "archiver-utils@npm:3.0.4" + dependencies: + glob: "npm:^7.2.3" + graceful-fs: "npm:^4.2.0" + lazystream: "npm:^1.0.0" + lodash.defaults: "npm:^4.2.0" + lodash.difference: "npm:^4.5.0" + lodash.flatten: "npm:^4.4.0" + lodash.isplainobject: "npm:^4.0.6" + lodash.union: "npm:^4.6.0" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10/a838c325a1e1d6798c07e6a3af08f480fdce57cba2964bff8761126715aa1b71e9a119442eac19b7ec6313f5298e54a180dc6612ae548825fbc9be6836e50487 + languageName: node + linkType: hard + +"archiver@npm:^5.0.0": + version: 5.3.2 + resolution: "archiver@npm:5.3.2" + dependencies: + archiver-utils: "npm:^2.1.0" + async: "npm:^3.2.4" + buffer-crc32: "npm:^0.2.1" + readable-stream: "npm:^3.6.0" + readdir-glob: "npm:^1.1.2" + tar-stream: "npm:^2.2.0" + zip-stream: "npm:^4.1.0" + checksum: 10/9384b3b20d330f95140c2b7a9b51140d14e9bc7b133be6cf573067ed8fc67a6e9618cfbfe60b1ba78b8034857001fd02c8900f2fba4864514670a2274d36dc9e + languageName: node + linkType: hard + +"arg@npm:^4.1.0": + version: 4.1.3 + resolution: "arg@npm:4.1.3" + checksum: 10/969b491082f20cad166649fa4d2073ea9e974a4e5ac36247ca23d2e5a8b3cb12d60e9ff70a8acfe26d76566c71fd351ee5e6a9a6595157eb36f92b1fd64e1599 + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10/18640244e641a417ec75a9bd38b0b2b6b95af5199aa241b131d4b2fb206f334d7ecc600bd194861610a5579084978bfcbb02baa399dbe442d56d0ae5e60dbaef + languageName: node + linkType: hard + +"array-timsort@npm:^1.0.3": + version: 1.0.3 + resolution: "array-timsort@npm:1.0.3" + checksum: 10/f417f073b3733baec3a80decdf5d45bf763f04676ef3610b0e71f9b1d88c6e4c38154c05b28b31529d308bfd0e043d08059fcd9df966245a1276af15b5584936 + languageName: node + linkType: hard + +"asap@npm:~2.0.3": + version: 2.0.6 + resolution: "asap@npm:2.0.6" + checksum: 10/b244c0458c571945e4b3be0b14eb001bea5596f9868cc50cc711dc03d58a7e953517d3f0dad81ccde3ff37d1f074701fa76a6f07d41aaa992d7204a37b915dda + languageName: node + linkType: hard + +"assert-never@npm:^1.2.1": + version: 1.4.0 + resolution: "assert-never@npm:1.4.0" + checksum: 10/e51e1fc4587405b929d07f1b033f7e92b9f59f9a9ced20f2bb2e5218b06a079fdf8c5b0e993366acc2ca25050a437a2df1f4e624959151e60f4893cad4e50d89 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10/3d49e7acbeee9e84537f4cb0e0f91893df8eba976759875ae8ee9e3d3c82f6ecdebdb347c2fad9926b92596d93cdfc78ecc988bcdf407e40433e8e8e6fe5d78e + languageName: node + linkType: hard + +"async@npm:^3.2.3, async@npm:^3.2.4, async@npm:^3.2.6": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 10/cb6e0561a3c01c4b56a799cc8bab6ea5fef45f069ab32500b6e19508db270ef2dffa55e5aed5865c5526e9907b1f8be61b27530823b411ffafb5e1538c86c368 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10/3ce727cbc78f69d6a4722517a58ee926c8c21083633b1d3fdf66fd688f6c127a53a592141bd4866f9b63240a86e9d8e974b13919450bd17fa33c2d22c4558ad8 + languageName: node + linkType: hard + +"atomic-sleep@npm:^1.0.0": + version: 1.0.0 + resolution: "atomic-sleep@npm:1.0.0" + checksum: 10/3ab6d2cf46b31394b4607e935ec5c1c3c4f60f3e30f0913d35ea74b51b3585e84f590d09e58067f11762eec71c87d25314ce859030983dc0e4397eed21daa12e + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab + languageName: node + linkType: hard + +"axios@npm:0.21.4": + version: 0.21.4 + resolution: "axios@npm:0.21.4" + dependencies: + follow-redirects: "npm:^1.14.0" + checksum: 10/da644592cb6f8f9f8c64fdabd7e1396d6769d7a4c1ea5f8ae8beb5c2eb90a823e3a574352b0b934ac62edc762c0f52647753dc54f7d07279127a7e5c4cd20272 + languageName: node + linkType: hard + +"axios@npm:^1.12.0, axios@npm:^1.9.0": + version: 1.13.2 + resolution: "axios@npm:1.13.2" + dependencies: + follow-redirects: "npm:^1.15.6" + form-data: "npm:^4.0.4" + proxy-from-env: "npm:^1.1.0" + checksum: 10/ae4e06dcd18289f2fd18179256d550d27f9a53ecb2f9c59f2ccc4efd1d7151839ba8c3e0fb533dac793e4a59a576ca8689a19244dce5c396680837674a47a867 + languageName: node + linkType: hard + +"b4a@npm:^1.6.4": + version: 1.7.3 + resolution: "b4a@npm:1.7.3" + peerDependencies: + react-native-b4a: "*" + peerDependenciesMeta: + react-native-b4a: + optional: true + checksum: 10/048ddd0eeec6a75e6f8dee07d52354e759032f0ef678b556e05bf5a137d7a4102002cadb953b3fb37a635995a1013875d715d115dbafaf12bcad6528d2166054 + languageName: node + linkType: hard + +"babel-walk@npm:3.0.0-canary-5": + version: 3.0.0-canary-5 + resolution: "babel-walk@npm:3.0.0-canary-5" + dependencies: + "@babel/types": "npm:^7.9.6" + checksum: 10/f4cea17303b33266fa97be471df9917d386bb5cd2756ae4c4725b3f105b9d630789818be202de06afa546e94810add61d614aa5eeb16e2a3027636cbafac2c1a + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10/9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 + languageName: node + linkType: hard + +"bare-events@npm:^2.7.0": + version: 2.8.2 + resolution: "bare-events@npm:2.8.2" + peerDependencies: + bare-abort-controller: "*" + peerDependenciesMeta: + bare-abort-controller: + optional: true + checksum: 10/f31848ea2f5627c3a50aadfc17e518a602629f7a6671da1352975cc6c8a520441fcc9d93c0a21f8f95de65b1a5133fcd5f766d312f3d5a326dde4fe7d2fc575f + languageName: node + linkType: hard + +"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10/669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 + languageName: node + linkType: hard + +"base64id@npm:2.0.0, base64id@npm:~2.0.0": + version: 2.0.0 + resolution: "base64id@npm:2.0.0" + checksum: 10/e3312328429e512b0713469c5312f80b447e71592cae0a5bddf3f1adc9c89d1b2ed94156ad7bb9f529398f310df7ff6f3dbe9550735c6a759f247c088ea67364 + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.9.0": + version: 2.9.14 + resolution: "baseline-browser-mapping@npm:2.9.14" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10/a329881e5f673c0834843640e9c954c478f643fb983449c99850392e48cf52dfb1dc3de8d81c6a6a2802c86310833accc5e3deb6bef5fb6e329989e28ca5489b + languageName: node + linkType: hard + +"bcrypt@npm:^6.0.0": + version: 6.0.0 + resolution: "bcrypt@npm:6.0.0" + dependencies: + node-addon-api: "npm:^8.3.0" + node-gyp: "npm:latest" + node-gyp-build: "npm:^4.8.4" + checksum: 10/24dc552828435f2346fe0a27eb2b23e4fdcc4f139d069db0dbee6e3b37fcf8e88ffbd6473a138e1d594a4b9df91e9b71994d15cf9fc6f5c3ff68f3d851fd973a + languageName: node + linkType: hard + +"big-integer@npm:^1.6.17": + version: 1.6.52 + resolution: "big-integer@npm:1.6.52" + checksum: 10/4bc6ae152a96edc9f95020f5fc66b13d26a9ad9a021225a9f0213f7e3dc44269f423aa8c42e19d6ac4a63bb2b22140b95d10be8f9ca7a6d9aa1b22b330d1f514 + languageName: node + linkType: hard + +"bignumber.js@npm:^9.0.0": + version: 9.3.1 + resolution: "bignumber.js@npm:9.3.1" + checksum: 10/1be0372bf0d6d29d0a49b9e6a9cefbd54dad9918232ad21fcd4ec39030260773abf0c76af960c6b3b98d3115a3a71e61c6a111812d1395040a039cfa178e0245 + languageName: node + linkType: hard + +"bin-version-check@npm:^5.1.0": + version: 5.1.0 + resolution: "bin-version-check@npm:5.1.0" + dependencies: + bin-version: "npm:^6.0.0" + semver: "npm:^7.5.3" + semver-truncate: "npm:^3.0.0" + checksum: 10/d99679cfe0964703045fe0145a98f117888942b621dfe2c2377305ee9a9d735374d8e3ecb3b476507b284af2567699f24f7ecb2feb1f27ad6086ad60b3198893 + languageName: node + linkType: hard + +"bin-version@npm:^6.0.0": + version: 6.0.0 + resolution: "bin-version@npm:6.0.0" + dependencies: + execa: "npm:^5.0.0" + find-versions: "npm:^5.0.0" + checksum: 10/78c29422ea9597eb4c8d4f0eff96df60d09aa82b53a87925bc403efbe5c55251b1a07baac538381d9096377f92d27e3c03963efa86db5bc0d6431b9563946229 + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.3.0 + resolution: "binary-extensions@npm:2.3.0" + checksum: 10/bcad01494e8a9283abf18c1b967af65ee79b0c6a9e6fcfafebfe91dbe6e0fc7272bafb73389e198b310516ae04f7ad17d79aacf6cb4c0d5d5202a7e2e52c7d98 + languageName: node + linkType: hard + +"binary@npm:~0.3.0": + version: 0.3.0 + resolution: "binary@npm:0.3.0" + dependencies: + buffers: "npm:~0.1.1" + chainsaw: "npm:~0.1.0" + checksum: 10/127591ebb7bfca242ec11be9ef874bcde17c520f249d764810045971b6617b659e8af4452f8a1586db7fd47e1b481a75d22c8f207fc1466c0f099b9435e51679 + languageName: node + linkType: hard + +"bl@npm:^4.0.3, bl@npm:^4.1.0": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: "npm:^5.5.0" + inherits: "npm:^2.0.4" + readable-stream: "npm:^3.4.0" + checksum: 10/b7904e66ed0bdfc813c06ea6c3e35eafecb104369dbf5356d0f416af90c1546de3b74e5b63506f0629acf5e16a6f87c3798f16233dcff086e9129383aa02ab55 + languageName: node + linkType: hard + +"bluebird@npm:~3.4.1": + version: 3.4.7 + resolution: "bluebird@npm:3.4.7" + checksum: 10/340e4d11d4b6a26d90371180effb4e500197c2943e5426472d6b6bffca0032a534226ad10255fc0e39c025bea197341c6b2a4258f8c0f18217c7b3a254c76c14 + languageName: node + linkType: hard + +"body-parser@npm:^2.2.1": + version: 2.2.2 + resolution: "body-parser@npm:2.2.2" + dependencies: + bytes: "npm:^3.1.2" + content-type: "npm:^1.0.5" + debug: "npm:^4.4.3" + http-errors: "npm:^2.0.0" + iconv-lite: "npm:^0.7.0" + on-finished: "npm:^2.4.1" + qs: "npm:^6.14.1" + raw-body: "npm:^3.0.1" + type-is: "npm:^2.0.1" + checksum: 10/69671f67d4d5ae5974593901a92d639757231da1725ed6de4d35e86cde9ce7650afdf1cd28df9b6f7892ea7f9eb03ccb30c70fe27d679275ae4cb4aae5ce1b21 + languageName: node + linkType: hard + +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 10/3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0 + languageName: node + linkType: hard + +"bowser@npm:^2.11.0": + version: 2.13.1 + resolution: "bowser@npm:2.13.1" + checksum: 10/b93c4f92b0ee2225c7bcfd8cd8a657e4abe4dadfae51588e7567b39846d7e47d98dfb4b178a23989eb753a36dc6451a18c5adce7a38bc41f5df7b2de19e4a759 + languageName: node + linkType: hard + +"boxen@npm:5.1.2": + version: 5.1.2 + resolution: "boxen@npm:5.1.2" + dependencies: + ansi-align: "npm:^3.0.0" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.1.0" + cli-boxes: "npm:^2.2.1" + string-width: "npm:^4.2.2" + type-fest: "npm:^0.20.2" + widest-line: "npm:^3.1.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10/bc3d3d88d77dc8cabb0811844acdbd4805e8ca8011222345330817737042bf6f86d93eb74a3f7e0cab634e64ef69db03cf52b480761ed90a965de0c8ff1bea8c + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10/12cb6d6310629e3048cadb003e1aca4d8c9bb5c67c3c321bafdd7e7a50155de081f78ea3e0ed92ecc75a9015e784f301efc8132383132f4f7904ad1ac529c562 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10/01dff195e3646bc4b0d27b63d9bab84d2ebc06121ff5013ad6e5356daa5a9d6b60fa26cf73c74797f2dc3fbec112af13578d51f75228c1112b26c790a87b0488 + languageName: node + linkType: hard + +"braces@npm:^3.0.3, braces@npm:~3.0.2": + version: 3.0.3 + resolution: "braces@npm:3.0.3" + dependencies: + fill-range: "npm:^7.1.1" + checksum: 10/fad11a0d4697a27162840b02b1fad249c1683cbc510cd5bf1a471f2f8085c046d41094308c577a50a03a579dd99d5a6b3724c4b5e8b14df2c4443844cfcda2c6 + languageName: node + linkType: hard + +"browserslist@npm:^4.26.3": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" + dependencies: + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" + bin: + browserslist: cli.js + checksum: 10/64f2a97de4bce8473c0e5ae0af8d76d1ead07a5b05fc6bc87b848678bb9c3a91ae787b27aa98cdd33fc00779607e6c156000bed58fefb9cf8e4c5a183b994cdb + languageName: node + linkType: hard + +"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3": + version: 0.2.13 + resolution: "buffer-crc32@npm:0.2.13" + checksum: 10/06252347ae6daca3453b94e4b2f1d3754a3b146a111d81c68924c22d91889a40623264e95e67955b1cb4a68cbedf317abeabb5140a9766ed248973096db5ce1c + languageName: node + linkType: hard + +"buffer-equal-constant-time@npm:^1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10/80bb945f5d782a56f374b292770901065bad21420e34936ecbe949e57724b4a13874f735850dd1cc61f078773c4fb5493a41391e7bda40d1fa388d6bd80daaab + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 10/0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb + languageName: node + linkType: hard + +"buffer-indexof-polyfill@npm:~1.0.0": + version: 1.0.2 + resolution: "buffer-indexof-polyfill@npm:1.0.2" + checksum: 10/808c58a3f06cc6ee2231060959eaa31c490248465f2847e8cfebd3e62563521e67346391caad03ce7616fd765374eb53e941bdd22edb2336431171f46fddcd89 + languageName: node + linkType: hard + +"buffer@npm:^5.2.1, buffer@npm:^5.5.0": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10/997434d3c6e3b39e0be479a80288875f71cd1c07d75a3855e6f08ef848a3c966023f79534e22e415ff3a5112708ce06127277ab20e527146d55c84566405c7c6 + languageName: node + linkType: hard + +"buffer@npm:^6.0.3": + version: 6.0.3 + resolution: "buffer@npm:6.0.3" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.2.1" + checksum: 10/b6bc68237ebf29bdacae48ce60e5e28fc53ae886301f2ad9496618efac49427ed79096750033e7eab1897a4f26ae374ace49106a5758f38fb70c78c9fda2c3b1 + languageName: node + linkType: hard + +"buffers@npm:~0.1.1": + version: 0.1.1 + resolution: "buffers@npm:0.1.1" + checksum: 10/9f0b64bbb8ac4783b1740219ab3532b03ef978fa38e70a0ba8c0695a2f6bc7e2af0ce42f0756b0c1a127070493055adbaf490fb68d95bebd7ccc310c6a483860 + languageName: node + linkType: hard + +"busboy@npm:^1.6.0": + version: 1.6.0 + resolution: "busboy@npm:1.6.0" + dependencies: + streamsearch: "npm:^1.1.0" + checksum: 10/bee10fa10ea58e7e3e7489ffe4bda6eacd540a17de9f9cd21cc37e297b2dd9fe52b2715a5841afaec82900750d810d01d7edb4b2d456427f449b92b417579763 + languageName: node + linkType: hard + +"bytes@npm:^3.1.2, bytes@npm:~3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10/388a0169970df9d051da30437f93f81b7e91efb570ad0ff2b8fde33279fbe726c1bc8e8e2b9c05053ffb4f563854c73db395e8712e3b62347a1bc4f7fb8899ff + languageName: node + linkType: hard + +"cacheable-lookup@npm:^5.0.3": + version: 5.0.4 + resolution: "cacheable-lookup@npm:5.0.4" + checksum: 10/618a8b3eea314060e74cb3285a6154e8343c244a34235acf91cfe626ee0705c24e3cd11e4b1a7b3900bd749ee203ae65afe13adf610c8ab173e99d4a208faf75 + languageName: node + linkType: hard + +"cacheable-lookup@npm:^7.0.0": + version: 7.0.0 + resolution: "cacheable-lookup@npm:7.0.0" + checksum: 10/69ea78cd9f16ad38120372e71ba98b64acecd95bbcbcdad811f857dc192bad81ace021f8def012ce19178583db8d46afd1a00b3e8c88527e978e049edbc23252 + languageName: node + linkType: hard + +"cacheable-request@npm:^10.2.8": + version: 10.2.14 + resolution: "cacheable-request@npm:10.2.14" + dependencies: + "@types/http-cache-semantics": "npm:^4.0.2" + get-stream: "npm:^6.0.1" + http-cache-semantics: "npm:^4.1.1" + keyv: "npm:^4.5.3" + mimic-response: "npm:^4.0.0" + normalize-url: "npm:^8.0.0" + responselike: "npm:^3.0.0" + checksum: 10/102f454ac68eb66f99a709c5cf65e90ed89f1b9269752578d5a08590b3986c3ea47a5d9dff208fe7b65855a29da129a2f23321b88490106898e0ba70b807c912 + languageName: node + linkType: hard + +"cacheable-request@npm:^7.0.2": + version: 7.0.4 + resolution: "cacheable-request@npm:7.0.4" + dependencies: + clone-response: "npm:^1.0.2" + get-stream: "npm:^5.1.0" + http-cache-semantics: "npm:^4.0.0" + keyv: "npm:^4.0.0" + lowercase-keys: "npm:^2.0.0" + normalize-url: "npm:^6.0.1" + responselike: "npm:^2.0.0" + checksum: 10/0f4f2001260ecca78b9f64fc8245e6b5a5dcde24ea53006daab71f5e0e1338095aa1512ec099c4f9895a9e5acfac9da423cb7c079e131485891e9214aca46c41 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10/00482c1f6aa7cfb30fb1dbeb13873edf81cfac7c29ed67a5957d60635a56b2a4a480f1016ddbdb3395cc37900d46037fb965043a51c5c789ffeab4fc535d18b5 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10/659b03c79bbfccf0cde3a79e7d52570724d7290209823e1ca5088f94b52192dc1836b82a324d0144612f816abb2f1734447438e38d9dafe0b3f82c2a1b9e3bce + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10/ef2b96e126ec0e58a7ff694db43f4d0d44f80e641370c21549ed911fecbdbc2df3ebc9bddad918d6bbdefeafb60bb3337902006d5176d72bcd2da74820991af7 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10/072d17b6abb459c2ba96598918b55868af677154bec7e73d222ef95a8fdb9bbf7dae96a8421085cdad8cd190d86653b5b6dc55a4484f2e5b2e27d5e0c3fc15b3 + languageName: node + linkType: hard + +"camel-case@npm:^3.0.0": + version: 3.0.0 + resolution: "camel-case@npm:3.0.0" + dependencies: + no-case: "npm:^2.2.0" + upper-case: "npm:^1.1.1" + checksum: 10/4190ed6ab8acf4f3f6e1a78ad4d0f3f15ce717b6bfa1b5686d58e4bcd29960f6e312dd746b5fa259c6d452f1413caef25aee2e10c9b9a580ac83e516533a961a + languageName: node + linkType: hard + +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 10/8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001759": + version: 1.0.30001764 + resolution: "caniuse-lite@npm:1.0.30001764" + checksum: 10/24c6f402902181faa997a6da1cb63410f9376e9e8a33d733121862e7665d200a54d70e551c5626748f78078401c0744496a58d0451fceb8f7fa12498ae12ff20 + languageName: node + linkType: hard + +"chainsaw@npm:~0.1.0": + version: 0.1.0 + resolution: "chainsaw@npm:0.1.0" + dependencies: + traverse: "npm:>=0.3.0 <0.4" + checksum: 10/d85627cd3440eb908b9cd72a1ddce4a36bb1ebc9d431a4a2f44b4435cbefdd83625c05114d870381ba765849c34ad05f236c3f590b1581ea03c22897fe6883d0 + languageName: node + linkType: hard + +"chalk@npm:^2.4.2": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: "npm:^3.2.1" + escape-string-regexp: "npm:^1.0.5" + supports-color: "npm:^5.3.0" + checksum: 10/3d1d103433166f6bfe82ac75724951b33769675252d8417317363ef9d54699b7c3b2d46671b772b893a8e50c3ece70c4b933c73c01e81bc60ea4df9b55afa303 + languageName: node + linkType: hard + +"chalk@npm:^3.0.0": + version: 3.0.0 + resolution: "chalk@npm:3.0.0" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10/37f90b31fd655fb49c2bd8e2a68aebefddd64522655d001ef417e6f955def0ed9110a867ffc878a533f2dafea5f2032433a37c8a7614969baa7f8a1cd424ddfc + languageName: node + linkType: hard + +"chalk@npm:^4.0.0, chalk@npm:^4.1.0, chalk@npm:^4.1.2": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10/cb3f3e594913d63b1814d7ca7c9bafbf895f75fbf93b92991980610dfd7b48500af4e3a5d4e3a8f337990a96b168d7eb84ee55efdce965e2ee8efc20f8c8f139 + languageName: node + linkType: hard + +"character-parser@npm:^2.2.0": + version: 2.2.0 + resolution: "character-parser@npm:2.2.0" + dependencies: + is-regex: "npm:^1.0.3" + checksum: 10/5980ddc776a133ba7a264aaec77ab9dd883aa9ff45bb335bfa93ae26b8e7360e60b2c97119f85cc4d2203829c9977df7c6ba6612354b1dfc9ef41d84fde03002 + languageName: node + linkType: hard + +"chardet@npm:^2.1.1": + version: 2.1.1 + resolution: "chardet@npm:2.1.1" + checksum: 10/d56913b65e45c5c86f331988e2ef6264c131bfeadaae098ee719bf6610546c77740e37221ffec802dde56b5e4466613a4c754786f4da6b5f6c5477243454d324 + languageName: node + linkType: hard + +"check-disk-space@npm:3.4.0, check-disk-space@npm:^3.4.0": + version: 3.4.0 + resolution: "check-disk-space@npm:3.4.0" + checksum: 10/73130c32e26aaa1eda359706d9cbcde21785285dd829ec2b850c59e88ce99d8ebf830d6849315b2d4bc7eed9082aaea7c5f065f1ace591f49d3851f0c21157f9 + languageName: node + linkType: hard + +"cheerio-select@npm:^2.1.0": + version: 2.1.0 + resolution: "cheerio-select@npm:2.1.0" + dependencies: + boolbase: "npm:^1.0.0" + css-select: "npm:^5.1.0" + css-what: "npm:^6.1.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.0.1" + checksum: 10/b5d89208c23468c3a32d1e04f88b9e8c6e332e3649650c5cd29255e2cebc215071ae18563f58c3dc3f6ef4c234488fc486035490fceb78755572288245e2931a + languageName: node + linkType: hard + +"cheerio@npm:1.0.0-rc.12": + version: 1.0.0-rc.12 + resolution: "cheerio@npm:1.0.0-rc.12" + dependencies: + cheerio-select: "npm:^2.1.0" + dom-serializer: "npm:^2.0.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.0.1" + htmlparser2: "npm:^8.0.1" + parse5: "npm:^7.0.0" + parse5-htmlparser2-tree-adapter: "npm:^7.0.0" + checksum: 10/812fed61aa4b669bbbdd057d0d7f73ba4649cabfd4fc3a8f1d5c7499e4613b430636102716369cbd6bbed8f1bdcb06387ae8342289fb908b2743184775f94f18 + languageName: node + linkType: hard + +"chokidar@npm:4.0.3, chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10/bf2a575ea5596000e88f5db95461a9d59ad2047e939d5a4aac59dd472d126be8f1c1ff3c7654b477cf532d18f42a97279ef80ee847972fd2a25410bf00b80b59 + languageName: node + linkType: hard + +"chokidar@npm:^3.0.0": + version: 3.6.0 + resolution: "chokidar@npm:3.6.0" + dependencies: + anymatch: "npm:~3.1.2" + braces: "npm:~3.0.2" + fsevents: "npm:~2.3.2" + glob-parent: "npm:~5.1.2" + is-binary-path: "npm:~2.1.0" + is-glob: "npm:~4.0.1" + normalize-path: "npm:~3.0.0" + readdirp: "npm:~3.6.0" + dependenciesMeta: + fsevents: + optional: true + checksum: 10/c327fb07704443f8d15f7b4a7ce93b2f0bc0e6cea07ec28a7570aa22cd51fcf0379df589403976ea956c369f25aa82d84561947e227cd925902e1751371658df + languageName: node + linkType: hard + +"chownr@npm:^1.1.1": + version: 1.1.4 + resolution: "chownr@npm:1.1.4" + checksum: 10/115648f8eb38bac5e41c3857f3e663f9c39ed6480d1349977c4d96c95a47266fcacc5a5aabf3cb6c481e22d72f41992827db47301851766c4fd77ac21a4f081d + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10/b63cb1f73d171d140a2ed8154ee6566c8ab775d3196b0e03a2a94b5f6a0ce7777ee5685ca56849403c8d17bd457a6540672f9a60696a6137c7a409097495b82c + languageName: node + linkType: hard + +"chrome-trace-event@npm:^1.0.2": + version: 1.0.4 + resolution: "chrome-trace-event@npm:1.0.4" + checksum: 10/1762bed739774903bf5915fe3045c3120fc3c7f7d929d88e566447ea38944937a6370ccb687278318c43c24f837ad22dac780bed67c066336815557b8cf558c6 + languageName: node + linkType: hard + +"ci-info@npm:^3.8.0": + version: 3.9.0 + resolution: "ci-info@npm:3.9.0" + checksum: 10/75bc67902b4d1c7b435497adeb91598f6d52a3389398e44294f6601b20cfef32cf2176f7be0eb961d9e085bb333a8a5cae121cb22f81cf238ae7f58eb80e9397 + languageName: node + linkType: hard + +"cjs-module-lexer@npm:^1.2.2": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10/d2b92f919a2dedbfd61d016964fce8da0035f827182ed6839c97cac56e8a8077cfa6a59388adfe2bc588a19cef9bbe830d683a76a6e93c51f65852062cfe2591 + languageName: node + linkType: hard + +"class-transformer@npm:^0.5.1": + version: 0.5.1 + resolution: "class-transformer@npm:0.5.1" + checksum: 10/750327e3e9a5cf233c5234252f4caf6b06c437bf68a24acbdcfb06c8e0bfff7aa97c30428184813e38e08111b42871f20c5cf669ea4490f8ae837c09f08b31e7 + languageName: node + linkType: hard + +"class-validator@npm:^0.14.2": + version: 0.14.3 + resolution: "class-validator@npm:0.14.3" + dependencies: + "@types/validator": "npm:^13.15.3" + libphonenumber-js: "npm:^1.11.1" + validator: "npm:^13.15.20" + checksum: 10/492a3d3bf6db896a10bb473298b9e51cf3384a2810e32c549f1f82050786835aacc8c887b6d2f4a90aa82c4359f88252ef707f75295971db58532a73c40fffdf + languageName: node + linkType: hard + +"clean-css@npm:^4.2.1": + version: 4.2.4 + resolution: "clean-css@npm:4.2.4" + dependencies: + source-map: "npm:~0.6.0" + checksum: 10/4f64dbebfa29feb79be25d6f91239239179adc805c6d7442e2c728970ca23a75b5f238118477b4b78553b89e50f14a64fe35145ecc86b6badf971883c4ad2ffe + languageName: node + linkType: hard + +"cli-boxes@npm:^2.2.1": + version: 2.2.1 + resolution: "cli-boxes@npm:2.2.1" + checksum: 10/be79f8ec23a558b49e01311b39a1ea01243ecee30539c880cf14bf518a12e223ef40c57ead0cb44f509bffdffc5c129c746cd50d863ab879385370112af4f585 + languageName: node + linkType: hard + +"cli-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "cli-cursor@npm:3.1.0" + dependencies: + restore-cursor: "npm:^3.1.0" + checksum: 10/2692784c6cd2fd85cfdbd11f53aea73a463a6d64a77c3e098b2b4697a20443f430c220629e1ca3b195ea5ac4a97a74c2ee411f3807abf6df2b66211fec0c0a29 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.5.0": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 10/a0a863f442df35ed7294424f5491fa1756bd8d2e4ff0c8736531d886cec0ece4d85e8663b77a5afaf1d296e3cbbebff92e2e99f52bbea89b667cbe789b994794 + languageName: node + linkType: hard + +"cli-table3@npm:0.6.5": + version: 0.6.5 + resolution: "cli-table3@npm:0.6.5" + dependencies: + "@colors/colors": "npm:1.5.0" + string-width: "npm:^4.2.0" + dependenciesMeta: + "@colors/colors": + optional: true + checksum: 10/8dca71256f6f1367bab84c33add3f957367c7c43750a9828a4212ebd31b8df76bd7419d386e3391ac7419698a8540c25f1a474584028f35b170841cde2e055c5 + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10/b58876fbf0310a8a35c79b72ecfcf579b354e18ad04e6b20588724ea2b522799a758507a37dfe132fafaf93a9922cafd9514d9e1598e6b2cd46694853aed099f + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.1" + wrap-ansi: "npm:^7.0.0" + checksum: 10/eaa5561aeb3135c2cddf7a3b3f562fc4238ff3b3fc666869ef2adf264be0f372136702f16add9299087fb1907c2e4ec5dbfe83bd24bce815c70a80c6c1a2e950 + languageName: node + linkType: hard + +"clone-response@npm:^1.0.2": + version: 1.0.3 + resolution: "clone-response@npm:1.0.3" + dependencies: + mimic-response: "npm:^1.0.0" + checksum: 10/4e671cac39b11c60aa8ba0a450657194a5d6504df51bca3fac5b3bd0145c4f8e8464898f87c8406b83232e3bc5cca555f51c1f9c8ac023969ebfbf7f6bdabb2e + languageName: node + linkType: hard + +"clone@npm:^1.0.2": + version: 1.0.4 + resolution: "clone@npm:1.0.4" + checksum: 10/d06418b7335897209e77bdd430d04f882189582e67bd1f75a04565f3f07f5b3f119a9d670c943b6697d0afb100f03b866b3b8a1f91d4d02d72c4ecf2bb64b5dd + languageName: node + linkType: hard + +"cluster-key-slot@npm:^1.1.0": + version: 1.1.2 + resolution: "cluster-key-slot@npm:1.1.2" + checksum: 10/516ed8b5e1a14d9c3a9c96c72ef6de2d70dfcdbaa0ec3a90bc7b9216c5457e39c09a5775750c272369070308542e671146120153062ab5f2f481bed5de2c925f + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: "npm:1.1.3" + checksum: 10/ffa319025045f2973919d155f25e7c00d08836b6b33ea2d205418c59bd63a665d713c52d9737a9e0fe467fb194b40fbef1d849bae80d674568ee220a31ef3d10 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10/fa00c91b4332b294de06b443923246bccebe9fab1b253f7fe1772d37b06a2269b4039a85e309abe1fe11b267b11c08d1d0473fda3badd6167f57313af2887a64 + languageName: node + linkType: hard + +"color-convert@npm:^3.1.3": + version: 3.1.3 + resolution: "color-convert@npm:3.1.3" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10/36b9b99c138f90eb11a28d1ad911054a9facd6cffde4f00dc49a34ebde7cae28454b2285ede64f273b6a8df9c3228b80e4352f4471978fa8b5005fe91341a67b + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 10/09c5d3e33d2105850153b14466501f2bfb30324a2f76568a408763a3b7433b0e50e5b4ab1947868e65cb101bb7cb75029553f2c333b6d4b8138a73fcc133d69d + languageName: node + linkType: hard + +"color-name@npm:^2.0.0": + version: 2.1.0 + resolution: "color-name@npm:2.1.0" + checksum: 10/eb014f71d87408e318e95d3f554f188370d354ba8e0ffa4341d0fd19de391bfe2bc96e563d4f6614644d676bc24f475560dffee3fe310c2d6865d007410a9a2b + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10/b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 + languageName: node + linkType: hard + +"color-string@npm:^2.1.3": + version: 2.1.4 + resolution: "color-string@npm:2.1.4" + dependencies: + color-name: "npm:^2.0.0" + checksum: 10/689a8688ac3cd55247792c83a9db9bfe675343c7412fedba1eb748ac6a8867dd2bb3d406e309ebfe90336809ee5067c7f2cccfbd10133c5cc9ef1dba5aad58f2 + languageName: node + linkType: hard + +"color@npm:^5.0.2": + version: 5.0.3 + resolution: "color@npm:5.0.3" + dependencies: + color-convert: "npm:^3.1.3" + color-string: "npm:^2.1.3" + checksum: 10/88063ee058b995e5738092b5aa58888666275d1e967333f3814ff4fa334ce9a9e71de78a16fb1838f17c80793ea87f4878c20192037662809fe14eab2d474fd9 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10/2e969e637d05d09fa50b02d74c83a1186f6914aae89e6653b62595cc75a221464f884f55f231b8f4df7a49537fba60bdc0427acd2bf324c09a1dbb84837e36e4 + languageName: node + linkType: hard + +"commander@npm:4.1.1": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 10/3b2dc4125f387dab73b3294dbcb0ab2a862f9c0ad748ee2b27e3544d25325b7a8cdfbcc228d103a98a716960b14478114a5206b5415bd48cdafa38797891562c + languageName: node + linkType: hard + +"commander@npm:^10.0.0": + version: 10.0.1 + resolution: "commander@npm:10.0.1" + checksum: 10/8799faa84a30da985802e661cc9856adfaee324d4b138413013ef7f087e8d7924b144c30a1f1405475f0909f467665cd9e1ce13270a2f41b141dab0b7a58f3fb + languageName: node + linkType: hard + +"commander@npm:^2.19.0, commander@npm:^2.20.0": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: 10/90c5b6898610cd075984c58c4f88418a4fb44af08c1b1415e9854c03171bec31b336b7f3e4cefe33de994b3f12b03c5e2d638da4316df83593b9e82554e7e95b + languageName: node + linkType: hard + +"commander@npm:^6.0.0, commander@npm:^6.1.0": + version: 6.2.1 + resolution: "commander@npm:6.2.1" + checksum: 10/25b88c2efd0380c84f7844b39cf18510da7bfc5013692d68cdc65f764a1c34e6c8a36ea6d72b6620e3710a930cf8fab2695bdec2bf7107a0f4fa30a3ef3b7d0e + languageName: node + linkType: hard + +"commander@npm:^8.3.0": + version: 8.3.0 + resolution: "commander@npm:8.3.0" + checksum: 10/6b7b5d334483ce24bd73c5dac2eab901a7dbb25fd983ea24a1eeac6e7166bb1967f641546e8abf1920afbde86a45fbfe5812fbc69d0dc451bb45ca416a12a3a3 + languageName: node + linkType: hard + +"comment-json@npm:4.4.1": + version: 4.4.1 + resolution: "comment-json@npm:4.4.1" + dependencies: + array-timsort: "npm:^1.0.3" + core-util-is: "npm:^1.0.3" + esprima: "npm:^4.0.1" + checksum: 10/2d05701e361320c670623b01343ed9ff180f4b4a38291f19ab9e2ef5269f51c8d1011f003fceff8cbf73d293f53a8ffc8ba1f85ccb1f6d0703829155bd628bce + languageName: node + linkType: hard + +"complex.js@npm:^2.2.5": + version: 2.4.3 + resolution: "complex.js@npm:2.4.3" + checksum: 10/904a2b4a09a4cfd94d8636ceb95e15cc077dcdedd07c54e233308210fb38897338dde4e7113811e89c1cfe4c6e3ebcf11735ad5c901e27c6d7e3c132a3078e3c + languageName: node + linkType: hard + +"compress-commons@npm:^4.1.2": + version: 4.1.2 + resolution: "compress-commons@npm:4.1.2" + dependencies: + buffer-crc32: "npm:^0.2.13" + crc32-stream: "npm:^4.0.2" + normalize-path: "npm:^3.0.0" + readable-stream: "npm:^3.6.0" + checksum: 10/76fa281412e4a95f89893dc1e3399e797de20253365cf53102ac4738fa004d3540abb12c26e3a54156f8fb4e4392ef9a9c5eecbe752f3a7d30e28c808b671e1b + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10/9680699c8e2b3af0ae22592cb764acaf973f292a7b71b8a06720233011853a58e256c89216a10cbe889727532fd77f8bcd49a760cedfde271b8e006c20e079f2 + languageName: node + linkType: hard + +"concat-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "concat-stream@npm:2.0.0" + dependencies: + buffer-from: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.0.2" + typedarray: "npm:^0.0.6" + checksum: 10/250e576d0617e7c58e1c4b2dd6fe69560f316d2c962a409f9f3aac794018499ddb31948b1e4296f217008e124cd5d526432097745157fe504b5d9f3dc469eadb + languageName: node + linkType: hard + +"config-chain@npm:^1.1.13": + version: 1.1.13 + resolution: "config-chain@npm:1.1.13" + dependencies: + ini: "npm:^1.3.4" + proto-list: "npm:~1.2.1" + checksum: 10/83d22cabf709e7669f6870021c4d552e4fc02e9682702b726be94295f42ce76cfed00f70b2910ce3d6c9465d9758e191e28ad2e72ff4e3331768a90da6c1ef03 + languageName: node + linkType: hard + +"consola@npm:^3.2.3": + version: 3.4.2 + resolution: "consola@npm:3.4.2" + checksum: 10/32192c9f50d7cac27c5d7c4ecd3ff3679aea863e6bf5bd6a9cc2b05d1cd78addf5dae71df08c54330c142be8e7fbd46f051030129b57c6aacdd771efe409c4b2 + languageName: node + linkType: hard + +"console-stamp@npm:^3.0.2": + version: 3.1.2 + resolution: "console-stamp@npm:3.1.2" + dependencies: + chalk: "npm:^4.1.2" + dateformat: "npm:^4.6.3" + checksum: 10/aa6e0685f4220be28aace4e6148acd2a830b14f691906fe767649e435ea7415ba9f12391b685248fdf5864841273f6e5734c30d238fa6f3c8483593e605ea71c + languageName: node + linkType: hard + +"constantinople@npm:^4.0.1": + version: 4.0.1 + resolution: "constantinople@npm:4.0.1" + dependencies: + "@babel/parser": "npm:^7.6.0" + "@babel/types": "npm:^7.6.1" + checksum: 10/15fc9bec82711f275e35581fe97a7e7b8d30441745955023570f258bbf876f4bf3de84faa6e7a663a3048565d9cc58bde65d300f74d090faec6afe07913d584f + languageName: node + linkType: hard + +"content-disposition@npm:^0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: 10/b7f4ce176e324f19324be69b05bf6f6e411160ac94bc523b782248129eb1ef3be006f6cff431aaea5e337fe5d176ce8830b8c2a1b721626ead8933f0cbe78720 + languageName: node + linkType: hard + +"content-disposition@npm:^1.0.0": + version: 1.0.1 + resolution: "content-disposition@npm:1.0.1" + checksum: 10/0718d861dfec56f532fd9acd714f173782ce5257b243344fecab5196621746cf8623bf1c833441612f1ac84559c546b59277cf0e91c3a646b0712a806decb1c8 + languageName: node + linkType: hard + +"content-type@npm:^1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: 10/585847d98dc7fb8035c02ae2cb76c7a9bd7b25f84c447e5ed55c45c2175e83617c8813871b4ee22f368126af6b2b167df655829007b21aa10302873ea9c62662 + languageName: node + linkType: hard + +"cookie-parser@npm:^1.4.7": + version: 1.4.7 + resolution: "cookie-parser@npm:1.4.7" + dependencies: + cookie: "npm:0.7.2" + cookie-signature: "npm:1.0.6" + checksum: 10/243fa13f217e793d20a57675e6552beea08c5989fcc68495d543997a31646875335e0e82d687b42dcfd466df57891d22bae7f5ba6ab33b7705ed2dd6eb989105 + languageName: node + linkType: hard + +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: 10/f4e1b0a98a27a0e6e66fd7ea4e4e9d8e038f624058371bf4499cfcd8f3980be9a121486995202ba3fca74fbed93a407d6d54d43a43f96fd28d0bd7a06761591a + languageName: node + linkType: hard + +"cookie-signature@npm:^1.2.1": + version: 1.2.2 + resolution: "cookie-signature@npm:1.2.2" + checksum: 10/be44a3c9a56f3771aea3a8bd8ad8f0a8e2679bcb967478267f41a510b4eb5ec55085386ba79c706c4ac21605ca76f4251973444b90283e0eb3eeafe8a92c7708 + languageName: node + linkType: hard + +"cookie@npm:0.7.2, cookie@npm:^0.7.1, cookie@npm:~0.7.2": + version: 0.7.2 + resolution: "cookie@npm:0.7.2" + checksum: 10/24b286c556420d4ba4e9bc09120c9d3db7d28ace2bd0f8ccee82422ce42322f73c8312441271e5eefafbead725980e5996cc02766dbb89a90ac7f5636ede608f + languageName: node + linkType: hard + +"core-util-is@npm:^1.0.3, core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10/9de8597363a8e9b9952491ebe18167e3b36e7707569eed0ebf14f8bba773611376466ae34575bca8cfe3c767890c859c74056084738f09d4e4a6f902b2ad7d99 + languageName: node + linkType: hard + +"cors@npm:2.8.5, cors@npm:~2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: "npm:^4" + vary: "npm:^1" + checksum: 10/66e88e08edee7cbce9d92b4d28a2028c88772a4c73e02f143ed8ca76789f9b59444eed6b1c167139e76fa662998c151322720093ba229f9941365ada5a6fc2c6 + languageName: node + linkType: hard + +"cosmiconfig@npm:^8.2.0": + version: 8.3.6 + resolution: "cosmiconfig@npm:8.3.6" + dependencies: + import-fresh: "npm:^3.3.0" + js-yaml: "npm:^4.1.0" + parse-json: "npm:^5.2.0" + path-type: "npm:^4.0.0" + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: 10/91d082baca0f33b1c085bf010f9ded4af43cbedacba8821da0fb5667184d0a848addc52c31fadd080007f904a555319c238cf5f4c03e6d58ece2e4876b2e73d6 + languageName: node + linkType: hard + +"crc-32@npm:^1.2.0": + version: 1.2.2 + resolution: "crc-32@npm:1.2.2" + bin: + crc32: bin/crc32.njs + checksum: 10/824f696a5baaf617809aa9cd033313c8f94f12d15ebffa69f10202480396be44aef9831d900ab291638a8022ed91c360696dd5b1ba691eb3f34e60be8835b7c3 + languageName: node + linkType: hard + +"crc32-stream@npm:^4.0.2": + version: 4.0.3 + resolution: "crc32-stream@npm:4.0.3" + dependencies: + crc-32: "npm:^1.2.0" + readable-stream: "npm:^3.4.0" + checksum: 10/d44d0ec6f04d8a1bed899ac3e4fbb82111ed567ea6d506be39147362af45c747887fce1032f4beca1646b4824e5a9614cd3332bfa94bbc5577ca5445e7f75ddd + languageName: node + linkType: hard + +"create-require@npm:^1.1.0": + version: 1.1.1 + resolution: "create-require@npm:1.1.1" + checksum: 10/a9a1503d4390d8b59ad86f4607de7870b39cad43d929813599a23714831e81c520bddf61bcdd1f8e30f05fd3a2b71ae8538e946eb2786dc65c2bbc520f692eff + languageName: node + linkType: hard + +"cron@npm:4.3.5": + version: 4.3.5 + resolution: "cron@npm:4.3.5" + dependencies: + "@types/luxon": "npm:~3.7.0" + luxon: "npm:~3.7.0" + checksum: 10/c914bcd5ddf033c4bc2b1a86a92d96c5a000ece2488567a06ee57334820110282932cc0e8339bfba8732f9c593edf660caa0c9937cddd85a0dd7435063c8c911 + languageName: node + linkType: hard + +"cron@npm:^3.1.7": + version: 3.5.0 + resolution: "cron@npm:3.5.0" + dependencies: + "@types/luxon": "npm:~3.4.0" + luxon: "npm:~3.5.0" + checksum: 10/0e667d87c9acc162db835439bff2664483f1fcbd471ae30a26c7426c736fa1798d27067cc4d0294d8a27890a1bc6c9deeefe47811cc339f11a8ba8288f51886d + languageName: node + linkType: hard + +"cross-spawn@npm:^6.0.0": + version: 6.0.6 + resolution: "cross-spawn@npm:6.0.6" + dependencies: + nice-try: "npm:^1.0.4" + path-key: "npm:^2.0.1" + semver: "npm:^5.5.0" + shebang-command: "npm:^1.2.0" + which: "npm:^1.2.9" + checksum: 10/7abf6137b23293103a22bfeaf320f2d63faae70d97ddb4b58597237501d2efdd84cdc69a30246977e0c5f68216593894d41a7f122915dd4edf448db14c74171b + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 + languageName: node + linkType: hard + +"css-select@npm:^5.1.0": + version: 5.2.2 + resolution: "css-select@npm:5.2.2" + dependencies: + boolbase: "npm:^1.0.0" + css-what: "npm:^6.1.0" + domhandler: "npm:^5.0.2" + domutils: "npm:^3.0.1" + nth-check: "npm:^2.0.1" + checksum: 10/ebb6a88446433312d1a16301afd1c5f75090805b730dbbdccb0338b0d6ca7922410375f16dde06673ef7da086e2cf3b9ad91afe9a8e0d2ee3625795cb5e0170d + languageName: node + linkType: hard + +"css-what@npm:^6.1.0": + version: 6.2.2 + resolution: "css-what@npm:6.2.2" + checksum: 10/3c5a53be94728089bd1716f915f7f96adde5dd8bf374610eb03982266f3d860bf1ebaf108cda30509d02ef748fe33eaa59aa75911e2c49ee05a85ef1f9fb5223 + languageName: node + linkType: hard + +"date-fns-tz@npm:^3.2.0": + version: 3.2.0 + resolution: "date-fns-tz@npm:3.2.0" + peerDependencies: + date-fns: ^3.0.0 || ^4.0.0 + checksum: 10/8ab4745f00b40381220f0a7a2ec16e217cb629d4018a19047264d289dd260322baa23e19b3ed63c7e553f9ad34bea9dea105391132930a3e141e9a0a53e54af2 + languageName: node + linkType: hard + +"date-fns@npm:^4.1.0": + version: 4.1.0 + resolution: "date-fns@npm:4.1.0" + checksum: 10/d5f6e9de5bbc52310f786099e18609289ed5e30af60a71e0646784c8185ddd1d0eebcf7c96b7faaaefc4a8366f3a3a4244d099b6d0866ee2bec80d1361e64342 + languageName: node + linkType: hard + +"date-format@npm:^4.0.14": + version: 4.0.14 + resolution: "date-format@npm:4.0.14" + checksum: 10/6b07fd1df247439c53b71244e3468b93e6dfebb5d409b9328dd7b7e9ed0d2e875018e20fb1a95ae6b677dea708ec06aaa5058a7a5faa1a7f649338aabf04991a + languageName: node + linkType: hard + +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: 10/5c149c91bf9ce2142c89f84eee4c585f0cb1f6faf2536b1af89873f862666a28529d1ccafc44750aa01384da2197c4f76f4e149a3cc0c1cb2c46f5cc45f2bcb5 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.19, dayjs@npm:^1.11.9, dayjs@npm:^1.8.15, dayjs@npm:^1.8.34": + version: 1.11.19 + resolution: "dayjs@npm:1.11.19" + checksum: 10/185b820d68492b83a3ce2b8ddc7543034edc1dfd1423183f6ae4707b29929a3cc56503a81826309279f9084680c15966b99456e74cf41f7d1f6a2f98f9c7196f + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.4.0, debug@npm:^4.4.3, debug@npm:~4.4.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10/9ada3434ea2993800bd9a1e320bd4aa7af69659fb51cca685d390949434bc0a8873c21ed7c9b852af6f2455a55c6d050aa3937d52b3c69f796dab666f762acad + languageName: node + linkType: hard + +"decimal.js@npm:^10.4.3, decimal.js@npm:^10.5.0": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 10/c0d45842d47c311d11b38ce7ccc911121953d4df3ebb1465d92b31970eb4f6738a065426a06094af59bee4b0d64e42e7c8984abd57b6767c64ea90cf90bb4a69 + languageName: node + linkType: hard + +"decompress-response@npm:^6.0.0": + version: 6.0.0 + resolution: "decompress-response@npm:6.0.0" + dependencies: + mimic-response: "npm:^3.1.0" + checksum: 10/d377cf47e02d805e283866c3f50d3d21578b779731e8c5072d6ce8c13cc31493db1c2f6784da9d1d5250822120cefa44f1deab112d5981015f2e17444b763812 + languageName: node + linkType: hard + +"dedent@npm:^1.7.0": + version: 1.7.1 + resolution: "dedent@npm:1.7.1" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: 10/78785ef592e37e0b1ca7a7a5964c8f3dee1abdff46c5bb49864168579c122328f6bb55c769bc7e005046a7381c3372d3859f0f78ab083950fa146e1c24873f4f + languageName: node + linkType: hard + +"deep-extend@npm:^0.6.0": + version: 0.6.0 + resolution: "deep-extend@npm:0.6.0" + checksum: 10/7be7e5a8d468d6b10e6a67c3de828f55001b6eb515d014f7aeb9066ce36bd5717161eb47d6a0f7bed8a9083935b465bc163ee2581c8b128d29bf61092fdf57a7 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10/ec12d074aef5ae5e81fa470b9317c313142c9e8e2afe3f8efa124db309720db96d1d222b82b84c834e5f87e7a614b44a4684b6683583118b87c833b3be40d4d8 + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 10/058d9e1b0ff1a154468bf3837aea436abcfea1ba1d165ddaaf48ca93765fdd01a30d33c36173da8fbbed951dd0a267602bc782fe288b0fc4b7e1e7091afc4529 + languageName: node + linkType: hard + +"defaults@npm:^1.0.3": + version: 1.0.4 + resolution: "defaults@npm:1.0.4" + dependencies: + clone: "npm:^1.0.2" + checksum: 10/3a88b7a587fc076b84e60affad8b85245c01f60f38fc1d259e7ac1d89eb9ce6abb19e27215de46b98568dd5bc48471730b327637e6f20b0f1bc85cf00440c80a + languageName: node + linkType: hard + +"defaults@npm:^2.0.2": + version: 2.0.2 + resolution: "defaults@npm:2.0.2" + checksum: 10/fdce6a8d1ff8fbe6a8c11d8ad4c9118e455c36f96d933f950ea415d7e0add6bde4e8537dc625478e9f040315555f12a6088fe9ed11996d5dfaaa447aeb05d169 + languageName: node + linkType: hard + +"defer-to-connect@npm:^2.0.0, defer-to-connect@npm:^2.0.1": + version: 2.0.1 + resolution: "defer-to-connect@npm:2.0.1" + checksum: 10/8a9b50d2f25446c0bfefb55a48e90afd58f85b21bcf78e9207cd7b804354f6409032a1705c2491686e202e64fc05f147aa5aa45f9aa82627563f045937f5791b + languageName: node + linkType: hard + +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10/abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10/46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020 + languageName: node + linkType: hard + +"denque@npm:^2.1.0": + version: 2.1.0 + resolution: "denque@npm:2.1.0" + checksum: 10/8ea05321576624b90acfc1ee9208b8d1d04b425cf7573b9b4fa40a2c3ed4d4b0af5190567858f532f677ed2003d4d2b73c8130b34e3c7b8d5e88cdcfbfaa1fe7 + languageName: node + linkType: hard + +"depd@npm:^2.0.0, depd@npm:~2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 10/c0c8ff36079ce5ada64f46cc9d6fd47ebcf38241105b6e0c98f412e8ad91f084bcf906ff644cc3a4bd876ca27a62accb8b0fff72ea6ed1a414b89d8506f4a5ca + languageName: node + linkType: hard + +"detect-indent@npm:^6.0.0": + version: 6.1.0 + resolution: "detect-indent@npm:6.1.0" + checksum: 10/ab953a73c72dbd4e8fc68e4ed4bfd92c97eb6c43734af3900add963fd3a9316f3bc0578b018b24198d4c31a358571eff5f0656e81a1f3b9ad5c547d58b2d093d + languageName: node + linkType: hard + +"detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10/b736c8d97d5d46164c0d1bed53eb4e6a3b1d8530d460211e2d52f1c552875e706c58a5376854e4e54f8b828c9cada58c855288c968522eb93ac7696d65970766 + languageName: node + linkType: hard + +"detect-newline@npm:^3.1.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: 10/ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7 + languageName: node + linkType: hard + +"detect-node@npm:2.1.0, detect-node@npm:^2.0.4": + version: 2.1.0 + resolution: "detect-node@npm:2.1.0" + checksum: 10/832184ec458353e41533ac9c622f16c19f7c02d8b10c303dfd3a756f56be93e903616c0bb2d4226183c9351c15fc0b3dba41a17a2308262afabcfa3776e6ae6e + languageName: node + linkType: hard + +"diff@npm:^4.0.1": + version: 4.0.2 + resolution: "diff@npm:4.0.2" + checksum: 10/ec09ec2101934ca5966355a229d77afcad5911c92e2a77413efda5455636c4cf2ce84057e2d7715227a2eeeda04255b849bd3ae3a4dd22eb22e86e76456df069 + languageName: node + linkType: hard + +"display-notification@npm:2.0.0": + version: 2.0.0 + resolution: "display-notification@npm:2.0.0" + dependencies: + escape-string-applescript: "npm:^1.0.0" + run-applescript: "npm:^3.0.0" + checksum: 10/e4b75d4e909473a65a1747a6c4877f02b547525cfb79286771008cb48362608a7a7e02e6bad0c2e2ff38f304420ae42f8d856b025a5595eee7cf4eacd315f364 + languageName: node + linkType: hard + +"doctypes@npm:^1.1.0": + version: 1.1.0 + resolution: "doctypes@npm:1.1.0" + checksum: 10/6e6c2d1a80f2072dc4831994c914c44455e341c5ab18c16797368a0afd59d7c22f3335805ba2c1dd2931e9539d1ba8b613b7650dc63f6ab56b77b8d888055de8 + languageName: node + linkType: hard + +"docxtemplater@npm:^3.63.2": + version: 3.67.6 + resolution: "docxtemplater@npm:3.67.6" + dependencies: + "@xmldom/xmldom": "npm:^0.9.8" + checksum: 10/d23d09866bab9f7153fce900a992da7c713d09ec02a062abb4a73740cb30388d58c82b09bdbe9fa9a3d15c096cf96f92b3ca87a36f1a46643109aa2c886b7a94 + languageName: node + linkType: hard + +"dom-serializer@npm:^1.0.1": + version: 1.4.1 + resolution: "dom-serializer@npm:1.4.1" + dependencies: + domelementtype: "npm:^2.0.1" + domhandler: "npm:^4.2.0" + entities: "npm:^2.0.0" + checksum: 10/53b217bcfed4a0f90dd47f34f239b1c81fff53ffa39d164d722325817fdb554903b145c2d12c8421ce0df7d31c1b180caf7eacd3c86391dd925f803df8027dcc + languageName: node + linkType: hard + +"dom-serializer@npm:^2.0.0": + version: 2.0.0 + resolution: "dom-serializer@npm:2.0.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.2" + entities: "npm:^4.2.0" + checksum: 10/e3bf9027a64450bca0a72297ecdc1e3abb7a2912268a9f3f5d33a2e29c1e2c3502c6e9f860fc6625940bfe0cfb57a44953262b9e94df76872fdfb8151097eeb3 + languageName: node + linkType: hard + +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: 10/ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 + languageName: node + linkType: hard + +"domhandler@npm:^3.3.0": + version: 3.3.0 + resolution: "domhandler@npm:3.3.0" + dependencies: + domelementtype: "npm:^2.0.1" + checksum: 10/31baccfeb2354477f90c5f6ab2e3606122228996fb87534750b7ceef3f8eebf8ae9599b02dc551eaaa532c874f964c331b1f76e651643a6048489b73cc68ea7e + languageName: node + linkType: hard + +"domhandler@npm:^4.2.0": + version: 4.3.1 + resolution: "domhandler@npm:4.3.1" + dependencies: + domelementtype: "npm:^2.2.0" + checksum: 10/e0d2af7403997a3ca040a9ace4a233b75ebe321e0ef628b417e46d619d65d47781b2f2038b6c2ef6e56e73e66aec99caf6a12c7e687ecff18ef74af6dfbde5de + languageName: node + linkType: hard + +"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: "npm:^2.3.0" + checksum: 10/809b805a50a9c6884a29f38aec0a4e1b4537f40e1c861950ed47d10b049febe6b79ab72adaeeebb3cc8fc1cd33f34e97048a72a9265103426d93efafa78d3e96 + languageName: node + linkType: hard + +"domutils@npm:^2.4.2": + version: 2.8.0 + resolution: "domutils@npm:2.8.0" + dependencies: + dom-serializer: "npm:^1.0.1" + domelementtype: "npm:^2.2.0" + domhandler: "npm:^4.2.0" + checksum: 10/1f316a03f00b09a8893d4a25d297d5cbffd02c564509dede28ef72d5ce38d93f6d61f1de88d439f31b14a1d9b42f587ed711b9e8b1b4d3bf6001399832bfc4e0 + languageName: node + linkType: hard + +"domutils@npm:^3.0.1, domutils@npm:^3.1.0": + version: 3.2.2 + resolution: "domutils@npm:3.2.2" + dependencies: + dom-serializer: "npm:^2.0.0" + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + checksum: 10/2e08842151aa406f50fe5e6d494f4ec73c2373199fa00d1f77b56ec604e566b7f226312ae35ab8160bb7f27a27c7285d574c8044779053e499282ca9198be210 + languageName: node + linkType: hard + +"dotenv-expand@npm:12.0.1": + version: 12.0.1 + resolution: "dotenv-expand@npm:12.0.1" + dependencies: + dotenv: "npm:^16.4.5" + checksum: 10/ceae1314c3c537c5a8d7f621013603d844e02290ad20b937cc59d7236aa6feb1a0dc94a0085d22d24da3a1cab2fef426d29b08034be5d46f46e4c8a5313014fa + languageName: node + linkType: hard + +"dotenv@npm:16.4.7": + version: 16.4.7 + resolution: "dotenv@npm:16.4.7" + checksum: 10/f13bfe97db88f0df4ec505eeffb8925ec51f2d56a3d0b6d916964d8b4af494e6fb1633ba5d09089b552e77ab2a25de58d70259b2c5ed45ec148221835fc99a0c + languageName: node + linkType: hard + +"dotenv@npm:^16.4.5, dotenv@npm:^16.6.1": + version: 16.6.1 + resolution: "dotenv@npm:16.6.1" + checksum: 10/1d1897144344447ffe62aa1a6d664f4cd2e0784e0aff787eeeec1940ded32f8e4b5b506d665134fc87157baa086fce07ec6383970a2b6d2e7985beaed6a4cc14 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10/5add88a3d68d42d6e6130a0cac450b7c2edbe73364bbd2fc334564418569bea97c6943a8fcd70e27130bf32afc236f30982fc4905039b703f23e9e0433c29934 + languageName: node + linkType: hard + +"duplexer2@npm:~0.1.4": + version: 0.1.4 + resolution: "duplexer2@npm:0.1.4" + dependencies: + readable-stream: "npm:^2.0.2" + checksum: 10/f60ff8b8955f992fd9524516e82faa5662d7aca5b99ee71c50bbbe1a3c970fafacb35d526d8b05cef8c08be56eed3663c096c50626c3c3651a52af36c408bf4d + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 10/9b1d3e1baefeaf7d70799db8774149cef33b97183a6addceeba0cf6b85ba23ee2686f302f14482006df32df75d32b17c509c143a3689627929e4a8efaf483952 + languageName: node + linkType: hard + +"ecdsa-sig-formatter@npm:1.0.11, ecdsa-sig-formatter@npm:^1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10/878e1aab8a42773320bc04c6de420bee21aebd71810e40b1799880a8a1c4594bcd6adc3d4213a0fb8147d4c3f529d8f9a618d7f59ad5a9a41b142058aceda23f + languageName: node + linkType: hard + +"editorconfig@npm:^1.0.4": + version: 1.0.4 + resolution: "editorconfig@npm:1.0.4" + dependencies: + "@one-ini/wasm": "npm:0.1.1" + commander: "npm:^10.0.0" + minimatch: "npm:9.0.1" + semver: "npm:^7.5.3" + bin: + editorconfig: bin/editorconfig + checksum: 10/bd0a7236f31a7f54801cb6f3222508d4f872a24e440bef30ee29f4ba667c0741724e52e0ad521abe3409b12cdafd8384bb751de9b2a2ee5f845c740edd2e742f + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: 10/1b4cac778d64ce3b582a7e26b218afe07e207a0f9bfe13cc7395a6d307849cfe361e65033c3251e00c27dd060cab43014c2d6b2647676135e18b77d2d05b3f4f + languageName: node + linkType: hard + +"ejs@npm:^3.1.10": + version: 3.1.10 + resolution: "ejs@npm:3.1.10" + dependencies: + jake: "npm:^10.8.5" + bin: + ejs: bin/cli.js + checksum: 10/a9cb7d7cd13b7b1cd0be5c4788e44dd10d92f7285d2f65b942f33e127230c054f99a42db4d99f766d8dbc6c57e94799593ee66a14efd7c8dd70c4812bf6aa384 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.263": + version: 1.5.267 + resolution: "electron-to-chromium@npm:1.5.267" + checksum: 10/05e55e810cb6a3cda8d29dfdeec7ac0e59727a77a796a157f1a1d65edac16d45eed69ed5c99e354872ab16c48967c2d0a0600653051ae380a3b7a4d6210b1e60 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: 10/c72d67a6821be15ec11997877c437491c313d924306b8da5d87d2a2bcc2cec9903cb5b04ee1a088460501d8e5b44f10df82fdc93c444101a7610b80c8b6938e1 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: 10/915acf859cea7131dac1b2b5c9c8e35c4849e325a1d114c30adb8cd615970f6dca0e27f64f3a4949d7d6ed86ecd79a1c5c63f02e697513cddd7b5835c90948b8 + languageName: node + linkType: hard + +"enabled@npm:2.0.x": + version: 2.0.0 + resolution: "enabled@npm:2.0.0" + checksum: 10/9d256d89f4e8a46ff988c6a79b22fa814b4ffd82826c4fdacd9b42e9b9465709d3b748866d0ab4d442dfc6002d81de7f7b384146ccd1681f6a7f868d2acca063 + languageName: node + linkType: hard + +"encodeurl@npm:^2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + +"encoding-japanese@npm:2.2.0": + version: 2.2.0 + resolution: "encoding-japanese@npm:2.2.0" + checksum: 10/e77259312054ed0f3cdbb5b35d56d244ef4a56779d47e279e7b9da96e0f81b71b0ee2a74709eb047b06d1365f857501cfdbe62bd4e782a412123fd5933feda6c + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10/bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f + languageName: node + linkType: hard + +"end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1": + version: 1.4.5 + resolution: "end-of-stream@npm:1.4.5" + dependencies: + once: "npm:^1.4.0" + checksum: 10/1e0cfa6e7f49887544e03314f9dfc56a8cb6dde910cbb445983ecc2ff426fc05946df9d75d8a21a3a64f2cecfe1bf88f773952029f46756b2ed64a24e95b1fb8 + languageName: node + linkType: hard + +"engine.io-parser@npm:~5.2.1": + version: 5.2.3 + resolution: "engine.io-parser@npm:5.2.3" + checksum: 10/eb0023fff5766e7ae9d59e52d92df53fea06d472cfd7b52e5d2c36b4c1dbf78cab5fde1052bcb3d4bb85bdb5aee10ae85d8a1c6c04676dac0c6cdf16bcba6380 + languageName: node + linkType: hard + +"engine.io@npm:~6.6.0": + version: 6.6.5 + resolution: "engine.io@npm:6.6.5" + dependencies: + "@types/cors": "npm:^2.8.12" + "@types/node": "npm:>=10.0.0" + accepts: "npm:~1.3.4" + base64id: "npm:2.0.0" + cookie: "npm:~0.7.2" + cors: "npm:~2.8.5" + debug: "npm:~4.4.1" + engine.io-parser: "npm:~5.2.1" + ws: "npm:~8.18.3" + checksum: 10/d48f8c4240185c018c4d5608fa1641dbd640c10dda7ae24cdca57c5e6938e47bead110f1435925822923444590d2b63c7aebe43149fe9978714fee960923a23b + languageName: node + linkType: hard + +"enhanced-resolve@npm:^5.17.3, enhanced-resolve@npm:^5.7.0": + version: 5.18.4 + resolution: "enhanced-resolve@npm:5.18.4" + dependencies: + graceful-fs: "npm:^4.2.4" + tapable: "npm:^2.2.0" + checksum: 10/dcd477cb694d9cc84109a03269c13d3da0851d50099fd3fa7c56b2867dd720d59c7f1431bd47c9cad2825ad52588bd71d3a68cf1e5ee0bc57551d8a3fab4e6f2 + languageName: node + linkType: hard + +"entities@npm:^2.0.0": + version: 2.2.0 + resolution: "entities@npm:2.2.0" + checksum: 10/2c765221ee324dbe25e1b8ca5d1bf2a4d39e750548f2e85cbf7ca1d167d709689ddf1796623e66666ae747364c11ed512c03b48c5bbe70968d30f2a4009509b7 + languageName: node + linkType: hard + +"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 + languageName: node + linkType: hard + +"entities@npm:^6.0.0": + version: 6.0.1 + resolution: "entities@npm:6.0.1" + checksum: 10/62af1307202884349d2867f0aac5c60d8b57102ea0b0e768b16246099512c28e239254ad772d6834e7e14cb1b6f153fc3d0c031934e3183b086c86d3838d874a + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10/65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e + languageName: node + linkType: hard + +"err-code@npm:^1.0.0": + version: 1.1.2 + resolution: "err-code@npm:1.1.2" + checksum: 10/f2bd853e355a8eb2e29316405916fd83d8d4fdb6ddc980b9a4275748ecce6a58b48fdc8240f509743a7f47184eac1d1773ac5495f587e03118989bac98c5a3d9 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10/1d20d825cdcce8d811bfbe86340f4755c02655a7feb2f13f8c880566d9d72a3f6c92c192a6867632e490d6da67b678271f46e01044996a6443e870331100dfdd + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10/ae3939fd4a55b1404e877df2080c6b59acc516d5b7f08a181040f78f38b4e2399633bfed2d9a21b91c803713fff7295ac70bebd8f3657ef352a95c2cd9aa2e4b + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10/f8dc9e660d90919f11084db0a893128f3592b781ce967e4fccfb8f3106cb83e400a4032c559184ec52ee1dbd4b01e7776c7cd0b3327b1961b1a4a7008920fe78 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 + languageName: node + linkType: hard + +"es-module-lexer@npm:^1.2.1": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10/b6f3e576a3fed4d82b0d0ad4bbf6b3a5ad694d2e7ce8c4a069560da3db6399381eaba703616a182b16dde50ce998af64e07dcf49f2ae48153b9e07be3f107087 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10/54fe77de288451dae51c37bfbfe3ec86732dc3778f98f3eb3bdb4bf48063b2c0b8f9c93542656986149d08aa5be3204286e2276053d19582b76753f1a2728867 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/86814bf8afbcd8966653f731415888019d4bc4aca6b6c354132a7a75bb87566751e320369654a101d23a91c87a85c79b178bcf40332839bd347aff437c4fb65f + languageName: node + linkType: hard + +"escalade@npm:^3.1.1, escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10/9d7169e3965b2f9ae46971afa392f6e5a25545ea30f2e2dd99c9b0a95a3f52b5653681a84f5b2911a413ddad2d7a93d3514165072f349b5ffc59c75a899970d6 + languageName: node + linkType: hard + +"escape-goat@npm:^3.0.0": + version: 3.0.0 + resolution: "escape-goat@npm:3.0.0" + checksum: 10/6719196d073cc72d0bbe079646d6fa32f226f24fd7d00c1a71fa375bd4c5b8999050021d9e62c232a8874230328ebf89a5c8bd76fb72f7ccd6229efbe5abd04e + languageName: node + linkType: hard + +"escape-html@npm:^1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 10/6213ca9ae00d0ab8bccb6d8d4e0a98e76237b2410302cf7df70aaa6591d509a2a37ce8998008cbecae8fc8ffaadf3fb0229535e6a145f3ce0b211d060decbb24 + languageName: node + linkType: hard + +"escape-latex@npm:^1.2.0": + version: 1.2.0 + resolution: "escape-latex@npm:1.2.0" + checksum: 10/73a787319f0965ecb8244bb38bf3a3cba872f0b9a5d3da8821140e9f39fe977045dc953a62b1a2bed4d12bfccbe75a7d8ec786412bf00739eaa2f627d0a8e0d6 + languageName: node + linkType: hard + +"escape-string-applescript@npm:^1.0.0": + version: 1.0.0 + resolution: "escape-string-applescript@npm:1.0.0" + checksum: 10/2835d891d3d0ca287506c78e3f3cc42f1f773c5b2d25f26a2c37d105641be4a7737386babc1495b64dae2edb5a0abe112dc1c6b3582aaef106ecd70e050c28ce + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 10/6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10/98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5 + languageName: node + linkType: hard + +"eslint-config-prettier@npm:^10.1.5": + version: 10.1.8 + resolution: "eslint-config-prettier@npm:10.1.8" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: 10/03f8e6ea1a6a9b8f9eeaf7c8c52a96499ec4b275b9ded33331a6cc738ed1d56de734097dbd0091f136f0e84bc197388bd8ec22a52a4658105883f8c8b7d8921a + languageName: node + linkType: hard + +"eslint-plugin-prettier@npm:^5.4.0": + version: 5.5.4 + resolution: "eslint-plugin-prettier@npm:5.5.4" + dependencies: + prettier-linter-helpers: "npm:^1.0.0" + synckit: "npm:^0.11.7" + peerDependencies: + "@types/eslint": ">=8.0.0" + eslint: ">=8.0.0" + eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" + prettier: ">=3.0.0" + peerDependenciesMeta: + "@types/eslint": + optional: true + eslint-config-prettier: + optional: true + checksum: 10/5e39e3b7046d4ba0e1111cc2048630ee9d0aa5d5bb00d6230bef56893fdae37cbe2261babfb26db350cc2ad517c81d283b3f8b04cfee4e5aef7cd4bee72f90de + languageName: node + linkType: hard + +"eslint-scope@npm:5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^4.1.1" + checksum: 10/c541ef384c92eb5c999b7d3443d80195fcafb3da335500946f6db76539b87d5826c8f2e1d23bf6afc3154ba8cd7c8e566f8dc00f1eea25fdf3afc8fb9c87b238 + languageName: node + linkType: hard + +"eslint-scope@npm:^8.4.0": + version: 8.4.0 + resolution: "eslint-scope@npm:8.4.0" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10/e8e611701f65375e034c62123946e628894f0b54aa8cb11abe224816389abe5cd74cf16b62b72baa36504f22d1a958b9b8b0169b82397fe2e7997674c0d09b06 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10/3f357c554a9ea794b094a09bd4187e5eacd1bc0d0653c3adeb87962c548e6a1ab8f982b86963ae1337f5d976004146536dcee5d0e2806665b193fbfbf1a9231b + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10/3ee00fc6a7002d4b0ffd9dc99e13a6a7882c557329e6c25ab254220d71e5c9c4f89dca4695352949ea678eb1f3ba912a18ef8aac0a7fe094196fd92f441bfce2 + languageName: node + linkType: hard + +"eslint@npm:^9.27.0": + version: 9.39.2 + resolution: "eslint@npm:9.39.2" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.1" + "@eslint/config-array": "npm:^0.21.1" + "@eslint/config-helpers": "npm:^0.4.2" + "@eslint/core": "npm:^0.17.0" + "@eslint/eslintrc": "npm:^3.3.1" + "@eslint/js": "npm:9.39.2" + "@eslint/plugin-kit": "npm:^0.4.1" + "@humanfs/node": "npm:^0.16.6" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^8.4.0" + eslint-visitor-keys: "npm:^4.2.1" + espree: "npm:^10.4.0" + esquery: "npm:^1.5.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^8.0.0" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10/53ff0e9c8264e7e8d40d50fdc0c0df0b701cfc5289beedfb686c214e3e7b199702f894bbd1bb48653727bb1ecbd1147cf5f555a4ae71e1daf35020cdc9072d9f + languageName: node + linkType: hard + +"espree@npm:^10.0.1, espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" + dependencies: + acorn: "npm:^8.15.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10/9b355b32dbd1cc9f57121d5ee3be258fab87ebeb7c83fc6c02e5af1a74fc8c5ba79fe8c663e69ea112c3e84a1b95e6a2067ac4443ee7813bb85ac7581acb8bf9 + languageName: node + linkType: hard + +"esprima@npm:^1.2.0": + version: 1.2.5 + resolution: "esprima@npm:1.2.5" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10/839aad5916d05d3a82ccf3adaf67c2b5df69278fd7168347346e7af298dc7fbfbfd7bc5e27e38031a584d50d28e37da35d711b2f5d5376794f84b1bd8e559665 + languageName: node + linkType: hard + +"esprima@npm:^4.0.1": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: 10/f1d3c622ad992421362294f7acf866aa9409fbad4eb2e8fa230bd33944ce371d32279667b242d8b8907ec2b6ad7353a717f3c0e60e748873a34a7905174bc0eb + languageName: node + linkType: hard + +"esquery@npm:^1.5.0": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10/4afaf3089367e1f5885caa116ef386dffd8bfd64da21fd3d0e56e938d2667cfb2e5400ab4a825aa70e799bb3741e5b5d63c0b94d86e2d4cf3095c9e64b2f5a15 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10/44ffcd89e714ea6b30143e7f119b104fc4d75e77ee913f34d59076b40ef2d21967f84e019f84e1fd0465b42cdbf725db449f232b5e47f29df29ed76194db8e16 + languageName: node + linkType: hard + +"estraverse@npm:^1.5.0": + version: 1.9.3 + resolution: "estraverse@npm:1.9.3" + checksum: 10/682a7e2fda17fd3e892b78a8347d055f923465598f5d713354aefd53a3348b2a1a6ee8df41031d8f5ad9802cfd27c29caac84c2f58ce3b2df659d43d668c870b + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: 10/3f67ad02b6dbfaddd9ea459cf2b6ef4ecff9a6082a7af9d22e445b9abc082ad9ca47e1825557b293fcdae477f4714e561123e30bb6a5b2f184fb2bad4a9497eb + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10/37cbe6e9a68014d34dbdc039f90d0baf72436809d02edffcc06ba3c2a12eb298048f877511353b130153e532aac8d68ba78430c0dd2f44806ebc7c014b01585e + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10/b23acd24791db11d8f65be5ea58fd9a6ce2df5120ae2da65c16cfc5331ff59d5ac4ef50af66cd4bde238881503ec839928a0135b99a036a9cdfa22d17fd56cdb + languageName: node + linkType: hard + +"etag@npm:^1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 10/571aeb3dbe0f2bbd4e4fadbdb44f325fc75335cd5f6f6b6a091e6a06a9f25ed5392f0863c5442acb0646787446e816f13cbfc6edce5b07658541dff573cab1ff + languageName: node + linkType: hard + +"eventemitter2@npm:6.4.9": + version: 6.4.9 + resolution: "eventemitter2@npm:6.4.9" + checksum: 10/b829b1c6b11e15926b635092b5ad62b4463d1c928859831dcae606e988cf41893059e3541f5a8209d21d2f15314422ddd4d84d20830b4bf44978608d15b06b08 + languageName: node + linkType: hard + +"events-universal@npm:^1.0.0": + version: 1.0.1 + resolution: "events-universal@npm:1.0.1" + dependencies: + bare-events: "npm:^2.7.0" + checksum: 10/71b2e6079b4dc030c613ef73d99f1acb369dd3ddb6034f49fd98b3e2c6632cde9f61c15fb1351004339d7c79672252a4694ecc46a6124dc794b558be50a83867 + languageName: node + linkType: hard + +"events@npm:^3.2.0": + version: 3.3.0 + resolution: "events@npm:3.3.0" + checksum: 10/a3d47e285e28d324d7180f1e493961a2bbb4cad6412090e4dec114f4db1f5b560c7696ee8e758f55e23913ede856e3689cd3aa9ae13c56b5d8314cd3b3ddd1be + languageName: node + linkType: hard + +"exceljs@npm:^4.4.0": + version: 4.4.0 + resolution: "exceljs@npm:4.4.0" + dependencies: + archiver: "npm:^5.0.0" + dayjs: "npm:^1.8.34" + fast-csv: "npm:^4.3.1" + jszip: "npm:^3.10.1" + readable-stream: "npm:^3.6.0" + saxes: "npm:^5.0.1" + tmp: "npm:^0.2.0" + unzipper: "npm:^0.10.11" + uuid: "npm:^8.3.0" + checksum: 10/2d310146130b2af25b9a553185062a4095f6fee9a931c18a22b8dccd8a244056f3d2766f44227b9a43c0f52d651d05c5c96291fd38b8ed0a0322afab683fd80c + languageName: node + linkType: hard + +"execa@npm:^0.10.0": + version: 0.10.0 + resolution: "execa@npm:0.10.0" + dependencies: + cross-spawn: "npm:^6.0.0" + get-stream: "npm:^3.0.0" + is-stream: "npm:^1.1.0" + npm-run-path: "npm:^2.0.0" + p-finally: "npm:^1.0.0" + signal-exit: "npm:^3.0.0" + strip-eof: "npm:^1.0.0" + checksum: 10/8aa9865625b2f359f6c5e5c7a5b89d53cdc2f232b56c493034c7f350b51ebeae2281e83e4ba0a795d170b5c2771626d9b56d3225236f3edc8df467cc8908627e + languageName: node + linkType: hard + +"execa@npm:^5.0.0, execa@npm:^5.1.1": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10/8ada91f2d70f7dff702c861c2c64f21dfdc1525628f3c0454fd6f02fce65f7b958616cbd2b99ca7fa4d474e461a3d363824e91b3eb881705231abbf387470597 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10/ca25962b4bbab943b7c4ed0b5228e263833a5063c65e1cdeac4be9afad350aae5466e8e619b5051f4f8d37b2144a2d6e8fcc771b6cc82934f7dade2f964f652c + languageName: node + linkType: hard + +"express-rate-limit@npm:^8.2.1": + version: 8.2.1 + resolution: "express-rate-limit@npm:8.2.1" + dependencies: + ip-address: "npm:10.0.1" + peerDependencies: + express: ">= 4.11" + checksum: 10/7cbf70df2e88e590e463d2d8f93380775b2ea181d97f2c50c2ff9f2c666c247f83109a852b21d9c99ccc5762119101f281f54a27252a2f1a0a918be6d71f955b + languageName: node + linkType: hard + +"express@npm:5.2.1, express@npm:^5.1.0": + version: 5.2.1 + resolution: "express@npm:5.2.1" + dependencies: + accepts: "npm:^2.0.0" + body-parser: "npm:^2.2.1" + content-disposition: "npm:^1.0.0" + content-type: "npm:^1.0.5" + cookie: "npm:^0.7.1" + cookie-signature: "npm:^1.2.1" + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + finalhandler: "npm:^2.1.0" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.0" + merge-descriptors: "npm:^2.0.0" + mime-types: "npm:^3.0.0" + on-finished: "npm:^2.4.1" + once: "npm:^1.4.0" + parseurl: "npm:^1.3.3" + proxy-addr: "npm:^2.0.7" + qs: "npm:^6.14.0" + range-parser: "npm:^1.2.1" + router: "npm:^2.2.0" + send: "npm:^1.1.0" + serve-static: "npm:^2.2.0" + statuses: "npm:^2.0.1" + type-is: "npm:^2.0.1" + vary: "npm:^1.1.2" + checksum: 10/4aa545d89702ac83f645c77abda1b57bcabe288f0b380fb5580fac4e323ea0eb533005c8e666b4e19152fb16d4abf11ba87b22aa9a10857a0485cd86b94639bd + languageName: node + linkType: hard + +"ext-list@npm:^2.0.0": + version: 2.2.2 + resolution: "ext-list@npm:2.2.2" + dependencies: + mime-db: "npm:^1.28.0" + checksum: 10/fe69fedbef044e14d4ce9e84c6afceb696ba71500c15b8d0ce0a1e280237e17c95031b3d62d5e597652fea0065b9bf957346b3900d989dff59128222231ac859 + languageName: node + linkType: hard + +"ext-name@npm:^5.0.0": + version: 5.0.0 + resolution: "ext-name@npm:5.0.0" + dependencies: + ext-list: "npm:^2.0.0" + sort-keys-length: "npm:^1.0.0" + checksum: 10/f598269bd5de4295540ea7d6f8f6a01d82a7508f148b7700a05628ef6121648d26e6e5e942049e953b3051863df6b54bd8fe951e7877f185e34ace5d44370b33 + languageName: node + linkType: hard + +"extend-object@npm:^1.0.0": + version: 1.0.0 + resolution: "extend-object@npm:1.0.0" + checksum: 10/a63a60dbab4c5ded795fa79a08371118c63452faa75a604420b7cd681f468d7391fbe8fa43f5a369dc281e72f9e8d27385a9081c44b5f3a0976f51deb2b9e791 + languageName: node + linkType: hard + +"extend-shallow@npm:^2.0.1": + version: 2.0.1 + resolution: "extend-shallow@npm:2.0.1" + dependencies: + is-extendable: "npm:^0.1.0" + checksum: 10/8fb58d9d7a511f4baf78d383e637bd7d2e80843bd9cd0853649108ea835208fb614da502a553acc30208e1325240bb7cc4a68473021612496bb89725483656d8 + languageName: node + linkType: hard + +"extend@npm:^3.0.2": + version: 3.0.2 + resolution: "extend@npm:3.0.2" + checksum: 10/59e89e2dc798ec0f54b36d82f32a27d5f6472c53974f61ca098db5d4648430b725387b53449a34df38fd0392045434426b012f302b3cc049a6500ccf82877e4e + languageName: node + linkType: hard + +"fast-csv@npm:^4.3.1": + version: 4.3.6 + resolution: "fast-csv@npm:4.3.6" + dependencies: + "@fast-csv/format": "npm:4.3.5" + "@fast-csv/parse": "npm:4.3.6" + checksum: 10/eaa7ae48b3c7087f01a4827c5e0ad630685d0fada2f93489b2da1dcecd56b758eeb445a245f48a43e18815a03e8b848ecbc3951a65e60fed381d9056d9aa6768 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10/e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d + languageName: node + linkType: hard + +"fast-diff@npm:^1.1.2": + version: 1.3.0 + resolution: "fast-diff@npm:1.3.0" + checksum: 10/9e57415bc69cd6efcc720b3b8fe9fdaf42dcfc06f86f0f45378b1fa512598a8aac48aa3928c8751d58e2f01bb4ba4f07e4f3d9bc0d57586d45f1bd1e872c6cde + languageName: node + linkType: hard + +"fast-fifo@npm:^1.2.0, fast-fifo@npm:^1.3.2": + version: 1.3.2 + resolution: "fast-fifo@npm:1.3.2" + checksum: 10/6bfcba3e4df5af7be3332703b69a7898a8ed7020837ec4395bb341bd96cc3a6d86c3f6071dd98da289618cf2234c70d84b2a6f09a33dd6f988b1ff60d8e54275 + languageName: node + linkType: hard + +"fast-glob@npm:^3.3.3": + version: 3.3.3 + resolution: "fast-glob@npm:3.3.3" + dependencies: + "@nodelib/fs.stat": "npm:^2.0.2" + "@nodelib/fs.walk": "npm:^1.2.3" + glob-parent: "npm:^5.1.2" + merge2: "npm:^1.3.0" + micromatch: "npm:^4.0.8" + checksum: 10/dcc6432b269762dd47381d8b8358bf964d8f4f60286ac6aa41c01ade70bda459ff2001b516690b96d5365f68a49242966112b5d5cc9cd82395fa8f9d017c90ad + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10/2c20055c1fa43c922428f16ca8bb29f2807de63e5c851f665f7ac9790176c01c3b40335257736b299764a8d383388dabc73c8083b8e1bc3d99f0a941444ec60e + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10/eb7e220ecf2bab5159d157350b81d01f75726a4382f5a9266f42b9150c4523b9795f7f5d9fbbbeaeac09a441b2369f05ee02db48ea938584205530fe5693cfe1 + languageName: node + linkType: hard + +"fast-safe-stringify@npm:2.1.1, fast-safe-stringify@npm:^2.1.1": + version: 2.1.1 + resolution: "fast-safe-stringify@npm:2.1.1" + checksum: 10/dc1f063c2c6ac9533aee14d406441f86783a8984b2ca09b19c2fe281f9ff59d315298bc7bc22fd1f83d26fe19ef2f20e2ddb68e96b15040292e555c5ced0c1e4 + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.1.0 + resolution: "fast-uri@npm:3.1.0" + checksum: 10/818b2c96dc913bcf8511d844c3d2420e2c70b325c0653633f51821e4e29013c2015387944435cd0ef5322c36c9beecc31e44f71b257aeb8e0b333c1d62bb17c2 + languageName: node + linkType: hard + +"fast-xml-parser@npm:5.2.5": + version: 5.2.5 + resolution: "fast-xml-parser@npm:5.2.5" + dependencies: + strnum: "npm:^2.1.0" + bin: + fxparser: src/cli/cli.js + checksum: 10/305017cff6968a34cbac597317be1516e85c44f650f30d982c84f8c30043e81fd38d39a8810d570136c921399dd43b9ac4775bdfbbbcfee96456f3c086b48bdd + languageName: node + linkType: hard + +"fast-xml-parser@npm:^4.1.3": + version: 4.5.3 + resolution: "fast-xml-parser@npm:4.5.3" + dependencies: + strnum: "npm:^1.1.1" + bin: + fxparser: src/cli/cli.js + checksum: 10/ca22bf9d65c10b8447c1034c13403e90ecee210e2b3852690df3d8a42b8a46ec655fae7356096abd98a15b89ddaf11878587b1773e0c3be4cbc2ac4af4c7bf95 + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.20.1 + resolution: "fastq@npm:1.20.1" + dependencies: + reusify: "npm:^1.0.4" + checksum: 10/ab2fe3a7a108112e7752cfe7fc11683c21e595913a6a593ad0b4415f31dddbfc283775ab66f2c8ccea6ab7cfc116157cbddcfae9798d9de98d08fe0a2c3e97b2 + languageName: node + linkType: hard + +"fd-package-json@npm:^2.0.0": + version: 2.0.0 + resolution: "fd-package-json@npm:2.0.0" + dependencies: + walk-up-path: "npm:^4.0.0" + checksum: 10/e595a1a23f8e208815cdcf26c92218240da00acce80468324408dc4a5cb6c26b6efb5076f0458a02f044562a1e60253731187a627d5416b4961468ddfc0ae426 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/14ca1c9f0a0e8f4f2e9bf4e8551065a164a09545dae548c12a18d238b72e51e5a7b39bd8e5494b56463a0877672d0a6c1ef62c6fa0677db1b0c847773be939b1 + languageName: node + linkType: hard + +"fecha@npm:^4.2.0": + version: 4.2.3 + resolution: "fecha@npm:4.2.3" + checksum: 10/534ce630c8f63c116292145607fc18c0f06bfa2fd74094357bf65daacc5d3f4f2b285bf8eb112c3bbf98c5caa6d386cced797f44b9b1b33da0c0a81020444826 + languageName: node + linkType: hard + +"fflate@npm:^0.8.2": + version: 0.8.2 + resolution: "fflate@npm:0.8.2" + checksum: 10/2bd26ba6d235d428de793c6a0cd1aaa96a06269ebd4e21b46c8fd1bd136abc631acf27e188d47c3936db090bf3e1ede11d15ce9eae9bffdc4bfe1b9dc66ca9cb + languageName: node + linkType: hard + +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" + dependencies: + flat-cache: "npm:^4.0.0" + checksum: 10/afe55c4de4e0d226a23c1eae62a7219aafb390859122608a89fa4df6addf55c7fd3f1a2da6f5b41e7cdff496e4cf28bbd215d53eab5c817afa96d2b40c81bfb0 + languageName: node + linkType: hard + +"file-type@npm:21.2.0": + version: 21.2.0 + resolution: "file-type@npm:21.2.0" + dependencies: + "@tokenizer/inflate": "npm:^0.4.1" + strtok3: "npm:^10.3.4" + token-types: "npm:^6.1.1" + uint8array-extras: "npm:^1.4.0" + checksum: 10/62262834abe03b5346a18a9a9e7c6dc0b53f07f79ae82157d2bf4ffd4061d643d03897b0e13c64fae32dd6466c8d62b96d2ff62c14584e987c4a240f8eba5425 + languageName: node + linkType: hard + +"file-type@npm:^20.5.0": + version: 20.5.0 + resolution: "file-type@npm:20.5.0" + dependencies: + "@tokenizer/inflate": "npm:^0.2.6" + strtok3: "npm:^10.2.0" + token-types: "npm:^6.0.0" + uint8array-extras: "npm:^1.4.0" + checksum: 10/1cc1ccd7cf76086e10b65cba88c708e0653676fbae900107deeb91c46de011acd1492200bf47e75cddf395de27dbe8584ca042f4cfa4a1efdf933644b7143f1d + languageName: node + linkType: hard + +"filelist@npm:^1.0.4": + version: 1.0.4 + resolution: "filelist@npm:1.0.4" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10/4b436fa944b1508b95cffdfc8176ae6947b92825483639ef1b9a89b27d82f3f8aa22b21eed471993f92709b431670d4e015b39c087d435a61e1bb04564cf51de + languageName: node + linkType: hard + +"filename-reserved-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "filename-reserved-regex@npm:3.0.0" + checksum: 10/1803e19ce64d7cb88ee5a1bd3ce282470a5c263987269222426d889049fc857e302284fa71937de9582eba7a9f39539557d45e0562f2fa51cade8efc68c65dd9 + languageName: node + linkType: hard + +"filenamify@npm:^6.0.0": + version: 6.0.0 + resolution: "filenamify@npm:6.0.0" + dependencies: + filename-reserved-regex: "npm:^3.0.0" + checksum: 10/5914b64a760d49323d0454efb1f5e33338d3840df447f40556fc68730c4649797451931d60035c66068dacf326f045a912287ce8b63e15a5fba311a961f8f4b1 + languageName: node + linkType: hard + +"fill-range@npm:^7.1.1": + version: 7.1.1 + resolution: "fill-range@npm:7.1.1" + dependencies: + to-regex-range: "npm:^5.0.1" + checksum: 10/a7095cb39e5bc32fada2aa7c7249d3f6b01bd1ce461a61b0adabacccabd9198500c6fb1f68a7c851a657e273fce2233ba869638897f3d7ed2e87a2d89b4436ea + languageName: node + linkType: hard + +"finalhandler@npm:^2.1.0": + version: 2.1.1 + resolution: "finalhandler@npm:2.1.1" + dependencies: + debug: "npm:^4.4.0" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + on-finished: "npm:^2.4.1" + parseurl: "npm:^1.3.3" + statuses: "npm:^2.0.1" + checksum: 10/f4ba75c23408d8f9d393c3e875b9452e84d68c925411a6e67b7efa678b0bed5075ef33def4bb65ed8e0dd37c92a3ea354bcbde07303cd4dc2550e12b95885067 + languageName: node + linkType: hard + +"find-package-json@npm:^1.2.0": + version: 1.2.0 + resolution: "find-package-json@npm:1.2.0" + checksum: 10/437825175752a7f522089e13876d1f78c6dc2768d8b6cd13736dea7abfc0a0482bc07169c27c4a99c82142c2e83aeddf3ef5c111ba6ae7aaa4719d9e6a80c683 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10/07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095 + languageName: node + linkType: hard + +"find-versions@npm:^5.0.0": + version: 5.1.0 + resolution: "find-versions@npm:5.1.0" + dependencies: + semver-regex: "npm:^4.0.5" + checksum: 10/680bdb0081f631f7bfb6f0f8edcfa0b74ab8cabc82097a4527a37b0d042aabc56685bf459ff27991eab0baddc04eb8e3bba8a2869f5004ecf7cdd2779b6e51de + languageName: node + linkType: hard + +"fixpack@npm:^4.0.0": + version: 4.0.0 + resolution: "fixpack@npm:4.0.0" + dependencies: + alce: "npm:1.2.0" + chalk: "npm:^3.0.0" + detect-indent: "npm:^6.0.0" + detect-newline: "npm:^3.1.0" + extend-object: "npm:^1.0.0" + rc: "npm:^1.2.8" + bin: + fixpack: bin/fixpack + checksum: 10/b893044e0a3dddc791e6afbb659e83f314ffce464afe42453fbf362820a98dd4d833854364e8f2391cfe80c38c5a0e120548c544893fb59ff01c376501bbb847 + languageName: node + linkType: hard + +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10/58ce851d9045fffc7871ce2bd718bc485ad7e777bf748c054904b87c351ff1080c2c11da00788d78738bfb51b71e4d5ea12d13b98eb36e3358851ffe495b62dc + languageName: node + linkType: hard + +"flatted@npm:^3.2.7, flatted@npm:^3.2.9": + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10/8c96c02fbeadcf4e8ffd0fa24983241e27698b0781295622591fc13585e2f226609d95e422bcf2ef044146ffacb6b68b1f20871454eddf75ab3caa6ee5f4a1fe + languageName: node + linkType: hard + +"fn.name@npm:1.x.x": + version: 1.1.0 + resolution: "fn.name@npm:1.1.0" + checksum: 10/000198af190ae02f0138ac5fa4310da733224c628e0230c81e3fff7c4e094af7e0e8bb9f4357cabd21db601759d89f3445da744afbae20623cfa41edf3888397 + languageName: node + linkType: hard + +"follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.15.6": + version: 1.15.11 + resolution: "follow-redirects@npm:1.15.11" + peerDependenciesMeta: + debug: + optional: true + checksum: 10/07372fd74b98c78cf4d417d68d41fdaa0be4dcacafffb9e67b1e3cf090bc4771515e65020651528faab238f10f9b9c0d9707d6c1574a6c0387c5de1042cde9ba + languageName: node + linkType: hard + +"for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10/330cc2439f85c94f4609de3ee1d32c5693ae15cdd7fe3d112c4fd9efd4ce7143f2c64ef6c2c9e0cfdb0058437f33ef05b5bdae5b98fcc903fb2143fbaf0fea0f + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" + dependencies: + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 10/427b33f997a98073c0424e5c07169264a62cda806d8d2ded159b5b903fdfc8f0a1457e06b5fc35506497acb3f1e353f025edee796300209ac6231e80edece835 + languageName: node + linkType: hard + +"fork-ts-checker-webpack-plugin@npm:9.1.0": + version: 9.1.0 + resolution: "fork-ts-checker-webpack-plugin@npm:9.1.0" + dependencies: + "@babel/code-frame": "npm:^7.16.7" + chalk: "npm:^4.1.2" + chokidar: "npm:^4.0.1" + cosmiconfig: "npm:^8.2.0" + deepmerge: "npm:^4.2.2" + fs-extra: "npm:^10.0.0" + memfs: "npm:^3.4.1" + minimatch: "npm:^3.0.4" + node-abort-controller: "npm:^3.0.1" + schema-utils: "npm:^3.1.1" + semver: "npm:^7.3.5" + tapable: "npm:^2.2.1" + peerDependencies: + typescript: ">3.6.0" + webpack: ^5.11.0 + checksum: 10/1d24387224f7d49a17f7e44c9150971172f34ae30c4b1f581b8af967e73e8f36a434ed56f78aa45fd8cf0833c73a1b020102cc61070d7dc630b70c21c9770a1b + languageName: node + linkType: hard + +"form-data-encoder@npm:^2.1.2": + version: 2.1.4 + resolution: "form-data-encoder@npm:2.1.4" + checksum: 10/3778e7db3c21457296e6fdbc4200642a6c01e8be9297256e845ee275f9ddaecb5f49bfb0364690ad216898c114ec59bf85f01ec823a70670b8067273415d62f6 + languageName: node + linkType: hard + +"form-data@npm:2.5.1": + version: 2.5.1 + resolution: "form-data@npm:2.5.1" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.6" + mime-types: "npm:^2.1.12" + checksum: 10/2e2e5e927979ba3623f9b4c4bcc939275fae3f2dea9dafc8db3ca656a3d75476605de2c80f0e6f1487987398e056f0b4c738972d6e1edd83392d5686d0952eed + languageName: node + linkType: hard + +"form-data@npm:^4.0.2, form-data@npm:^4.0.4": + version: 4.0.5 + resolution: "form-data@npm:4.0.5" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.2" + mime-types: "npm:^2.1.12" + checksum: 10/52ecd6e927c8c4e215e68a7ad5e0f7c1031397439672fd9741654b4a94722c4182e74cc815b225dcb5be3f4180f36428f67c6dd39eaa98af0dcfdd26c00c19cd + languageName: node + linkType: hard + +"formatly@npm:^0.3.0": + version: 0.3.0 + resolution: "formatly@npm:0.3.0" + dependencies: + fd-package-json: "npm:^2.0.0" + bin: + formatly: bin/index.mjs + checksum: 10/0e5a9cbb826d93171b00c283e20e6a564a16e7bc3839e695790347a1f23e3536a88d613f5cabd07403d60b7bdffe179987c88b1fc2900a9be49eea01ffbe4244 + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 10/29ba9fd347117144e97cbb8852baae5e8b2acb7d1b591ef85695ed96f5b933b1804a7fac4a15dd09ca7ac7d0cdc104410e8102aae2dd3faa570a797ba07adb81 + languageName: node + linkType: hard + +"fraction.js@npm:^5.2.1": + version: 5.3.4 + resolution: "fraction.js@npm:5.3.4" + checksum: 10/ef2c4bc81b2484065f8f7e4c2498f3fdfe6d233b8e7c7f75e3683ed10698536129b2c2dbd6c3f788ca4a020ec07116dd909a91036a364c98dc802b5003bfc613 + languageName: node + linkType: hard + +"fresh@npm:^2.0.0": + version: 2.0.0 + resolution: "fresh@npm:2.0.0" + checksum: 10/44e1468488363074641991c1340d2a10c5a6f6d7c353d89fd161c49d120c58ebf9890720f7584f509058385836e3ce50ddb60e9f017315a4ba8c6c3461813bfc + languageName: node + linkType: hard + +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 10/18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d + languageName: node + linkType: hard + +"fs-extra@npm:^10.0.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: 10/05ce2c3b59049bcb7b52001acd000e44b3c4af4ec1f8839f383ef41ec0048e3cfa7fd8a637b1bddfefad319145db89be91f4b7c1db2908205d38bf91e7d1d3b7 + languageName: node + linkType: hard + +"fs-extra@npm:^8.1.0": + version: 8.1.0 + resolution: "fs-extra@npm:8.1.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^4.0.0" + universalify: "npm:^0.1.0" + checksum: 10/6fb12449f5349be724a138b4a7b45fe6a317d2972054517f5971959c26fbd17c0e145731a11c7324460262baa33e0a799b183ceace98f7a372c95fbb6f20f5de + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10/af143246cf6884fe26fa281621d45cfe111d34b30535a475bfa38dafe343dadb466c047a924ffc7d6b7b18265df4110224ce3803806dbb07173bf2087b648d7f + languageName: node + linkType: hard + +"fs-monkey@npm:^1.0.4": + version: 1.1.0 + resolution: "fs-monkey@npm:1.1.0" + checksum: 10/1c6da5d07f6c91e31fd9bcd68909666e18fa243c7af6697e9d2ded16d4ee87cc9c2b67889b19f98211006c228d1915e1beb0678b4080778fb52539ef3e4eab6c + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10/e703107c28e362d8d7b910bbcbfd371e640a3bb45ae157a362b5952c0030c0b6d4981140ec319b347bce7adc025dd7813da1ff908a945ac214d64f5402a51b96 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10/4c1ade961ded57cdbfbb5cac5106ec17bc8bccd62e16343c569a0ceeca83b9dfef87550b4dc5cbb89642da412b20c5071f304c8c464b80415446e8e155a038c0 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"fstream@npm:^1.0.12": + version: 1.0.12 + resolution: "fstream@npm:1.0.12" + dependencies: + graceful-fs: "npm:^4.1.2" + inherits: "npm:~2.0.0" + mkdirp: "npm:>=0.5 0" + rimraf: "npm:2" + checksum: 10/eadba4375e952f3f7e9d34d822cfa1592134173033bafef42aa23d5f09bf373e4eb77e097883c0a9136ad7e7d3b49bb14f0e8dfaa489abd5139b5a3c961787b6 + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 + languageName: node + linkType: hard + +"gaxios@npm:^6.0.0, gaxios@npm:^6.0.3, gaxios@npm:^6.1.1": + version: 6.7.1 + resolution: "gaxios@npm:6.7.1" + dependencies: + extend: "npm:^3.0.2" + https-proxy-agent: "npm:^7.0.1" + is-stream: "npm:^2.0.0" + node-fetch: "npm:^2.6.9" + uuid: "npm:^9.0.1" + checksum: 10/c85599162208884eadee91215ebbfa1faa412551df4044626cb561300e15193726e8f23d63b486533e066dadad130f58ed872a23acab455238d8d48b531a0695 + languageName: node + linkType: hard + +"gcp-metadata@npm:^6.1.0": + version: 6.1.1 + resolution: "gcp-metadata@npm:6.1.1" + dependencies: + gaxios: "npm:^6.1.1" + google-logging-utils: "npm:^0.0.2" + json-bigint: "npm:^1.0.0" + checksum: 10/f6b1a604d5888db261a9a3ca0a494338b5cdbf815efa393aa38051d814387545bbfd9f25874bf8ea36441f2052625add42658e8973648e53f9b90f151b4bad1b + languageName: node + linkType: hard + +"generate-password@npm:^1.7.1": + version: 1.7.1 + resolution: "generate-password@npm:1.7.1" + checksum: 10/e892bfe38ef2f31efb724870514c2615af233b7d45a773e523cf926ffc4233e94cb254f22c83c4c472d322238ad7828898064f4a5d08d5bb5c09717309df0c4e + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10/eb7e7eb896c5433f3d40982b2ccacdb3dd990dd3499f14040e002b5d54572476513be8a2e6f9609f6e41ab29f2c4469307611ddbfc37ff4e46b765c326663805 + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10/b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10/bb579dda84caa4a3a41611bdd483dade7f00f246f2a7992eb143c5861155290df3fdb48a8406efa3dfb0b434e2c8fafa4eebd469e409d0439247f85fc3fa2cc1 + languageName: node + linkType: hard + +"get-port@npm:5.1.1": + version: 5.1.1 + resolution: "get-port@npm:5.1.1" + checksum: 10/0162663ffe5c09e748cd79d97b74cd70e5a5c84b760a475ce5767b357fb2a57cb821cee412d646aa8a156ed39b78aab88974eddaa9e5ee926173c036c0713787 + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10/4fc96afdb58ced9a67558698b91433e6b037aaa6f1493af77498d7c85b141382cf223c0e5946f334fb328ee85dfe6edd06d218eaf09556f4bc4ec6005d7f5f7b + languageName: node + linkType: hard + +"get-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "get-stream@npm:3.0.0" + checksum: 10/de14fbb3b4548ace9ab6376be852eef9898c491282e29595bc908a1814a126d3961b11cd4b7be5220019fe3b2abb84568da7793ad308fc139925a217063fa159 + languageName: node + linkType: hard + +"get-stream@npm:^5.1.0": + version: 5.2.0 + resolution: "get-stream@npm:5.2.0" + dependencies: + pump: "npm:^3.0.0" + checksum: 10/13a73148dca795e41421013da6e3ebff8ccb7fba4d2f023fd0c6da2c166ec4e789bec9774a73a7b49c08daf2cae552f8a3e914042ac23b5f59dd278cc8f9cbfb + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0, get-stream@npm:^6.0.1": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10/781266d29725f35c59f1d214aedc92b0ae855800a980800e2923b3fbc4e56b3cb6e462c42e09a1cf1a00c64e056a78fa407cbe06c7c92b7e5cd49b4b85c2a497 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: "npm:^4.0.1" + checksum: 10/32cd106ce8c0d83731966d31517adb766d02c3812de49c30cfe0675c7c0ae6630c11214c54a5ae67aca882cf738d27fd7768f21aa19118b9245950554be07247 + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10/c13ee97978bef4f55106b71e66428eb1512e71a7466ba49025fc2aec59a5bfb0954d5abd58fc5ee6c9b076eef4e1f6d3375c2e964b88466ca390da4419a786a8 + languageName: node + linkType: hard + +"glob-to-regexp@npm:^0.4.1": + version: 0.4.1 + resolution: "glob-to-regexp@npm:0.4.1" + checksum: 10/9009529195a955c40d7b9690794aeff5ba665cc38f1519e111c58bb54366fd0c106bde80acf97ba4e533208eb53422c83b136611a54c5fefb1edd8dc267cb62e + languageName: node + linkType: hard + +"glob@npm:10.3.12": + version: 10.3.12 + resolution: "glob@npm:10.3.12" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^2.3.6" + minimatch: "npm:^9.0.1" + minipass: "npm:^7.0.4" + path-scurry: "npm:^1.10.2" + bin: + glob: dist/esm/bin.mjs + checksum: 10/9e8186abc22dc824b5dd86cefd8e6b5621a72d1be7f68bacc0fd681e8c162ec5546660a6ec0553d6a74757a585e655956c7f8f1a6d24570e8d865c307323d178 + languageName: node + linkType: hard + +"glob@npm:13.0.0, glob@npm:^13.0.0": + version: 13.0.0 + resolution: "glob@npm:13.0.0" + dependencies: + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + path-scurry: "npm:^2.0.0" + checksum: 10/de390721d29ee1c9ea41e40ec2aa0de2cabafa68022e237dc4297665a5e4d650776f2573191984ea1640aba1bf0ea34eddef2d8cbfbfc2ad24b5fb0af41d8846 + languageName: node + linkType: hard + +"glob@npm:^10.3.10, glob@npm:^10.4.2, glob@npm:^10.5.0": + version: 10.5.0 + resolution: "glob@npm:10.5.0" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 10/ab3bccfefcc0afaedbd1f480cd0c4a2c0e322eb3f0aa7ceaa31b3f00b825069f17cf0f1fc8b6f256795074b903f37c0ade37ddda6a176aa57f1c2bbfe7240653 + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.2.3": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^3.1.1" + once: "npm:^1.3.0" + path-is-absolute: "npm:^1.0.0" + checksum: 10/59452a9202c81d4508a43b8af7082ca5c76452b9fcc4a9ab17655822e6ce9b21d4f8fbadabe4fe3faef448294cec249af305e2cd824b7e9aaf689240e5e96a7b + languageName: node + linkType: hard + +"globals@npm:^14.0.0": + version: 14.0.0 + resolution: "globals@npm:14.0.0" + checksum: 10/03939c8af95c6df5014b137cac83aa909090c3a3985caef06ee9a5a669790877af8698ab38007e4c0186873adc14c0b13764acc754b16a754c216cc56aa5f021 + languageName: node + linkType: hard + +"google-auth-library@npm:^9.0.0, google-auth-library@npm:^9.7.0": + version: 9.15.1 + resolution: "google-auth-library@npm:9.15.1" + dependencies: + base64-js: "npm:^1.3.0" + ecdsa-sig-formatter: "npm:^1.0.11" + gaxios: "npm:^6.1.1" + gcp-metadata: "npm:^6.1.0" + gtoken: "npm:^7.0.0" + jws: "npm:^4.0.0" + checksum: 10/6b977dd20f4f1ab6b2d2b78650d1e1c79ca84b951720b1064b85ebbb32af469547db7505a6609265e806be11c823bd6e07323b5073a98729b43b29fe34f05717 + languageName: node + linkType: hard + +"google-logging-utils@npm:^0.0.2": + version: 0.0.2 + resolution: "google-logging-utils@npm:0.0.2" + checksum: 10/f8f5ec3087ef4563d12ee1afc603e6b42b4d703c1f10c9f37b3080e6f4a2e9554e0fd9dcdce97ded5a46ead465c706ff2bc791ad2ca478ed8dc62fdc4b06cac6 + languageName: node + linkType: hard + +"googleapis-common@npm:^7.0.0": + version: 7.2.0 + resolution: "googleapis-common@npm:7.2.0" + dependencies: + extend: "npm:^3.0.2" + gaxios: "npm:^6.0.3" + google-auth-library: "npm:^9.7.0" + qs: "npm:^6.7.0" + url-template: "npm:^2.0.8" + uuid: "npm:^9.0.0" + checksum: 10/4b914be6681f2a5a02bd0954a4a5cee1725d8623cb9d0a7c2fd7132de110e8d5707566cba39784e58147be39e74bc5513ad30fdcdaa6edcbb47ecf687003cb6c + languageName: node + linkType: hard + +"googleapis@npm:^149.0.0": + version: 149.0.0 + resolution: "googleapis@npm:149.0.0" + dependencies: + google-auth-library: "npm:^9.0.0" + googleapis-common: "npm:^7.0.0" + checksum: 10/097bc81fe87da09e959f37ac9b1a488c75d1d29169d3100ab1df6382ec7610735351026139daca65486d2eeb1d9c4568ff896a4472cb29039eb8a77754dcde9f + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10/94e296d69f92dc1c0768fcfeecfb3855582ab59a7c75e969d5f96ce50c3d201fd86d5a2857c22565764d5bb8a816c7b1e58f133ec318cd56274da36c5e3fb1a1 + languageName: node + linkType: hard + +"got@npm:^11.8.6": + version: 11.8.6 + resolution: "got@npm:11.8.6" + dependencies: + "@sindresorhus/is": "npm:^4.0.0" + "@szmarczak/http-timer": "npm:^4.0.5" + "@types/cacheable-request": "npm:^6.0.1" + "@types/responselike": "npm:^1.0.0" + cacheable-lookup: "npm:^5.0.3" + cacheable-request: "npm:^7.0.2" + decompress-response: "npm:^6.0.0" + http2-wrapper: "npm:^1.0.0-beta.5.2" + lowercase-keys: "npm:^2.0.0" + p-cancelable: "npm:^2.0.0" + responselike: "npm:^2.0.0" + checksum: 10/a30c74029d81bd5fe50dea1a0c970595d792c568e188ff8be254b5bc11e6158d1b014570772d4a30d0a97723e7dd34e7c8cc1a2f23018f60aece3070a7a5c2a5 + languageName: node + linkType: hard + +"got@npm:^13.0.0": + version: 13.0.0 + resolution: "got@npm:13.0.0" + dependencies: + "@sindresorhus/is": "npm:^5.2.0" + "@szmarczak/http-timer": "npm:^5.0.1" + cacheable-lookup: "npm:^7.0.0" + cacheable-request: "npm:^10.2.8" + decompress-response: "npm:^6.0.0" + form-data-encoder: "npm:^2.1.2" + get-stream: "npm:^6.0.1" + http2-wrapper: "npm:^2.1.10" + lowercase-keys: "npm:^3.0.0" + p-cancelable: "npm:^3.0.0" + responselike: "npm:^3.0.0" + checksum: 10/35ac9fe37daca3d0a4f90305d8e64626268ef5a42584f5bcb42eea3cb9bbeb691cf9041d5ea72133a7295d1291684789a3148ff89a95f3d3ce3d0ebb6fb2f680 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.11, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10/bf152d0ed1dc159239db1ba1f74fdbc40cb02f626770dcd5815c427ce0688c2635a06ed69af364396da4636d0408fcf7d4afdf7881724c3307e46aff30ca49e2 + languageName: node + linkType: hard + +"gtoken@npm:^7.0.0": + version: 7.1.0 + resolution: "gtoken@npm:7.1.0" + dependencies: + gaxios: "npm:^6.0.0" + jws: "npm:^4.0.0" + checksum: 10/640392261e55c9242137a81a4af8feb053b57061762cedddcbb6a0d62c2314316161808ac2529eea67d06d69fdc56d82361af50f2d840a04a87ea29e124d7382 + languageName: node + linkType: hard + +"handlebars@npm:^4.7.8": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: 10/bd528f4dd150adf67f3f857118ef0fa43ff79a153b1d943fa0a770f2599e38b25a7a0dbac1a3611a4ec86970fd2325a81310fb788b5c892308c9f8743bd02e11 + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 10/4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10/261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10/2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10/959385c98696ebbca51e7534e0dc723ada325efa3475350951363cce216d27373e0259b63edb599f72eb94d6cde8577b4b2375f080b303947e560f85692834fa + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe + languageName: node + linkType: hard + +"hash.js@npm:^1.1.7": + version: 1.1.7 + resolution: "hash.js@npm:1.1.7" + dependencies: + inherits: "npm:^2.0.3" + minimalistic-assert: "npm:^1.0.1" + checksum: 10/0c89ee4006606a40f92df5cc3c263342e7fea68110f3e9ef032bd2083650430505db01b6b7926953489517d4027535e4fdc7f970412893d3031c361d3ec8f4b3 + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10/7898a9c1788b2862cf0f9c345a6bec77ba4a0c0983c7f19d610c382343d4f98fa260686b225dfb1f88393a66679d2ec58ee310c1d6868c081eda7918f32cc70a + languageName: node + linkType: hard + +"he@npm:1.2.0, he@npm:^1.2.0": + version: 1.2.0 + resolution: "he@npm:1.2.0" + bin: + he: bin/he + checksum: 10/d09b2243da4e23f53336e8de3093e5c43d2c39f8d0d18817abfa32ce3e9355391b2edb4bb5edc376aea5d4b0b59d6a0482aab4c52bc02ef95751e4b818e847f1 + languageName: node + linkType: hard + +"heapdump@npm:^0.3.15": + version: 0.3.15 + resolution: "heapdump@npm:0.3.15" + dependencies: + nan: "npm:^2.13.2" + node-gyp: "npm:latest" + checksum: 10/0e042e4bdc5439a8d83bdc405a8a175add563cf135a0ae0347dbc7587b3a224765a4954e454a9ee087bde349bc426f2538adc8cc66270feb2bbd924d7aae3583 + languageName: node + linkType: hard + +"helmet@npm:^8.1.0": + version: 8.1.0 + resolution: "helmet@npm:8.1.0" + checksum: 10/262e678d340bb102158f4e6ba1469a7cda1643329a50863f8fe704d5b60d9ebe6ea1e7793ed51224ae464f197dedf2fe8035df278c042e32ebc95b237fa41ad0 + languageName: node + linkType: hard + +"html-entities@npm:^2.3.6": + version: 2.6.0 + resolution: "html-entities@npm:2.6.0" + checksum: 10/06d4e7a3ba6243bba558af176e56f85e09894b26d911bc1ef7b2b9b3f18b46604360805b32636f080e954778e9a34313d1982479a05a5aa49791afd6a4229346 + languageName: node + linkType: hard + +"html-minifier@npm:^4.0.0": + version: 4.0.0 + resolution: "html-minifier@npm:4.0.0" + dependencies: + camel-case: "npm:^3.0.0" + clean-css: "npm:^4.2.1" + commander: "npm:^2.19.0" + he: "npm:^1.2.0" + param-case: "npm:^2.1.1" + relateurl: "npm:^0.2.7" + uglify-js: "npm:^3.5.1" + bin: + html-minifier: ./cli.js + checksum: 10/a1a49ee78a41eb3232f7aa51be25092d7634548e8996577b2bdab22dc9ac736594d35aab7fdf81fb5a0da11f7bc688f500c297b24fd312d48c0ce8739ed4f06f + languageName: node + linkType: hard + +"html-to-text@npm:9.0.5, html-to-text@npm:^9.0.5": + version: 9.0.5 + resolution: "html-to-text@npm:9.0.5" + dependencies: + "@selderee/plugin-htmlparser2": "npm:^0.11.0" + deepmerge: "npm:^4.3.1" + dom-serializer: "npm:^2.0.0" + htmlparser2: "npm:^8.0.2" + selderee: "npm:^0.11.0" + checksum: 10/e5991f9946dd0e5c91c4ed863c71a4feaef3d5ce85cd8684fb0f2fc175b1ccee323bb97a1773b6bebc47ac7963dbbfd1fc81b024adff705ae7c0e08992d1dba5 + languageName: node + linkType: hard + +"htmlparser2@npm:^5.0.0": + version: 5.0.1 + resolution: "htmlparser2@npm:5.0.1" + dependencies: + domelementtype: "npm:^2.0.1" + domhandler: "npm:^3.3.0" + domutils: "npm:^2.4.2" + entities: "npm:^2.0.0" + checksum: 10/789904b9a9f3d2f2d6b63e32aeb8b4cc03d6f5f751c5db86fbdf32c83570f5a5fc827fe664701ec4c5f940c49cf73ceffc93658ce116225f949e29a67dcc8629 + languageName: node + linkType: hard + +"htmlparser2@npm:^8.0.1, htmlparser2@npm:^8.0.2": + version: 8.0.2 + resolution: "htmlparser2@npm:8.0.2" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.0.1" + entities: "npm:^4.4.0" + checksum: 10/ea5512956eee06f5835add68b4291d313c745e8407efa63848f4b8a90a2dee45f498a698bca8614e436f1ee0cfdd609938b71d67c693794545982b76e53e6f11 + languageName: node + linkType: hard + +"htmlparser2@npm:^9.1.0": + version: 9.1.0 + resolution: "htmlparser2@npm:9.1.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.1.0" + entities: "npm:^4.5.0" + checksum: 10/6352fa2a5495781fa9a02c9049908334cd068ff36d753870d30cd13b841e99c19646717567a2f9e9c44075bbe43d364e102f9d013a731ce962226d63746b794f + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10/4efd2dfcfeea9d5e88c84af450b9980be8a43c2c8179508b1c57c7b4421c855f3e8efe92fa53e0b3f4a43c85824ada930eabbc306d1b3beab750b6dcc5187693 + languageName: node + linkType: hard + +"http-errors@npm:^2.0.0, http-errors@npm:^2.0.1, http-errors@npm:~2.0.1": + version: 2.0.1 + resolution: "http-errors@npm:2.0.1" + dependencies: + depd: "npm:~2.0.0" + inherits: "npm:~2.0.4" + setprototypeof: "npm:~1.2.0" + statuses: "npm:~2.0.2" + toidentifier: "npm:~1.0.1" + checksum: 10/9fe31bc0edf36566c87048aed1d3d0cbe03552564adc3541626a0613f542d753fbcb13bdfcec0a3a530dbe1714bb566c89d46244616b66bddd26ac413b06a207 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10/d062acfa0cb82beeb558f1043c6ba770ea892b5fb7b28654dbc70ea2aeea55226dd34c02a294f6c1ca179a5aa483c4ea641846821b182edbd9cc5d89b54c6848 + languageName: node + linkType: hard + +"http2-wrapper@npm:^1.0.0-beta.5.2": + version: 1.0.3 + resolution: "http2-wrapper@npm:1.0.3" + dependencies: + quick-lru: "npm:^5.1.1" + resolve-alpn: "npm:^1.0.0" + checksum: 10/8097ee2699440c2e64bda52124990cc5b0fb347401c7797b1a0c1efd5a0f79a4ebaa68e8a6ac3e2dde5f09460c1602764da6da2412bad628ed0a3b0ae35e72d4 + languageName: node + linkType: hard + +"http2-wrapper@npm:^2.1.10": + version: 2.2.1 + resolution: "http2-wrapper@npm:2.2.1" + dependencies: + quick-lru: "npm:^5.1.1" + resolve-alpn: "npm:^1.2.0" + checksum: 10/e7a5ac6548318e83fc0399cd832cdff6bbf902b165d211cad47a56ee732922e0aa1107246dd884b12532a1c4649d27c4d44f2480911c65202e93c90bde8fa29d + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10/f0dce7bdcac5e8eaa0be3c7368bb8836ed010fb5b6349ffb412b172a203efe8f807d9a6681319105ea1b6901e1972c7b5ea899672a7b9aad58309f766dcbe0df + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.4": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10/784b628cbd55b25542a9d85033bdfd03d4eda630fb8b3c9477959367f3be95dc476ed2ecbb9836c359c7c698027fc7b45723a302324433590f45d6c1706e8c13 + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10/df59be9e0af479036798a881d1f136c4a29e0b518d4abb863afbd11bf30efa3eeb1d0425fc65942dcc05ab3bf40205ea436b0ff389f2cd20b75b8643d539bf86 + languageName: node + linkType: hard + +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10/24e3292dd3dadaa81d065c6f8c41b274a47098150d444b96e5f53b4638a9a71482921ea6a91a1f59bb71d9796de25e04afd05919fa64c360347ba65d3766f10f + languageName: node + linkType: hard + +"iconv-lite@npm:0.7.0": + version: 0.7.0 + resolution: "iconv-lite@npm:0.7.0" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10/5bfc897fedfb7e29991ae5ef1c061ed4f864005f8c6d61ef34aba6a3885c04bd207b278c0642b041383aeac2d11645b4319d0ca7b863b0be4be0cde1c9238ca7 + languageName: node + linkType: hard + +"iconv-lite@npm:0.7.1": + version: 0.7.1 + resolution: "iconv-lite@npm:0.7.1" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10/bf9bfa10132b591d7b43cc2cc6700cd72d848b7943c3f38db05527d04c224eb44d1d92101f76d25eac7fabc67cb918320b93808b0be47e45cf7257df0a5ec54c + languageName: node + linkType: hard + +"iconv-lite@npm:^0.7.0, iconv-lite@npm:~0.7.0": + version: 0.7.2 + resolution: "iconv-lite@npm:0.7.2" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10/24c937b532f868e938386b62410b303b7c767ce3d08dc2829cbe59464d5a26ef86ae5ad1af6b34eec43ddfea39e7d101638644b0178d67262fa87015d59f983a + languageName: node + linkType: hard + +"iconv-lite@npm:~0.4.13": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: 10/6d3a2dac6e5d1fb126d25645c25c3a1209f70cceecc68b8ef51ae0da3cdc078c151fade7524a30b12a3094926336831fca09c666ef55b37e2c69638b5d6bd2e3 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10/d9f2557a59036f16c282aaeb107832dc957a93d73397d89bbad4eb1130560560eb695060145e8e6b3b498b15ab95510226649a0b8f52ae06583575419fe10fc4 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 + languageName: node + linkType: hard + +"ignore@npm:^7.0.5": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10/f134b96a4de0af419196f52c529d5c6120c4456ff8a6b5a14ceaaa399f883e15d58d2ce651c9b69b9388491d4669dda47285d307e827de9304a53a1824801bc6 + languageName: node + linkType: hard + +"imap-simple@npm:^5.1.0": + version: 5.1.0 + resolution: "imap-simple@npm:5.1.0" + dependencies: + iconv-lite: "npm:~0.4.13" + imap: "npm:^0.8.18" + nodeify: "npm:^1.0.0" + quoted-printable: "npm:^1.0.0" + utf8: "npm:^2.1.1" + uuencode: "npm:0.0.4" + checksum: 10/4f540427034cb5d8e635bb4e7583ed38945ae38eebc77fc2a5e29d670f2621491500d690cb7636fb20e62b3e879b37024f10d4368daaf24f668d4cdd534d41cf + languageName: node + linkType: hard + +"imap@npm:^0.8.18": + version: 0.8.19 + resolution: "imap@npm:0.8.19" + dependencies: + readable-stream: "npm:1.1.x" + utf7: "npm:>=1.0.2" + checksum: 10/d1531c7d3e1c896e048e2f4b22871b66cf93f029cae37c9d61558e4ba8ee34b9c657040eb4cad053f752e6ad3584ceab1bbc94999d4d9aa9e79e3c0bc211547a + languageName: node + linkType: hard + +"imapflow@npm:^1.0.187": + version: 1.2.6 + resolution: "imapflow@npm:1.2.6" + dependencies: + "@zone-eu/mailsplit": "npm:5.4.8" + encoding-japanese: "npm:2.2.0" + iconv-lite: "npm:0.7.1" + libbase64: "npm:1.3.0" + libmime: "npm:5.3.7" + libqp: "npm:2.1.1" + nodemailer: "npm:7.0.12" + pino: "npm:10.1.0" + socks: "npm:2.8.7" + checksum: 10/2d2157b04c2b80ee7f0ea4286e79f0af0e987c69680bf3b41c96fc37185281e7192e5f2e14475c45283f964e548d6a3e97b1dda22d35573eb331bfed0dd7e4e1 + languageName: node + linkType: hard + +"immediate@npm:~3.0.5": + version: 3.0.6 + resolution: "immediate@npm:3.0.6" + checksum: 10/f9b3486477555997657f70318cc8d3416159f208bec4cca3ff3442fd266bc23f50f0c9bd8547e1371a6b5e82b821ec9a7044a4f7b944798b25aa3cc6d5e63e62 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10/a06b19461b4879cc654d46f8a6244eb55eb053437afd4cbb6613cad6be203811849ed3e4ea038783092879487299fda24af932b86bdfff67c9055ba3612b8c87 + languageName: node + linkType: hard + +"import-in-the-middle@npm:^1.13.0": + version: 1.15.0 + resolution: "import-in-the-middle@npm:1.15.0" + dependencies: + acorn: "npm:^8.14.0" + acorn-import-attributes: "npm:^1.9.5" + cjs-module-lexer: "npm:^1.2.2" + module-details-from-path: "npm:^1.0.3" + checksum: 10/a1ff65ea557ffe67e63dd67b411255fedd8622b324ff22ba99f4436b8fcf74da0333e62b4e8142f447e5db64a42ec9e65f926d50fa55e89c4e4d64626d8cf5f8 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10/2d30b157a91fe1c1d7c6f653cbf263f039be6c5bfa959245a16d4ee191fc0f2af86c08545b6e6beeb041c56b574d2d5b9f95343d378ab49c0f37394d541e7fc8 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10/d2ebd65441a38c8336c223d1b80b921b9fa737e37ea466fd7e253cb000c64ae1f17fa59e68130ef5bda92cfd8d36b83d37dab0eb0a4558bcfec8e8cdfd2dcb67 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.0, inherits@npm:~2.0.1, inherits@npm:~2.0.3, inherits@npm:~2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10/cd45e923bee15186c07fa4c89db0aace24824c482fb887b528304694b2aa6ff8a898da8657046a5dcf3e46cd6db6c61629551f9215f208d7c3f157cf9b290521 + languageName: node + linkType: hard + +"ini@npm:^1.3.4, ini@npm:~1.3.0": + version: 1.3.8 + resolution: "ini@npm:1.3.8" + checksum: 10/314ae176e8d4deb3def56106da8002b462221c174ddb7ce0c49ee72c8cd1f9044f7b10cc555a7d8850982c3b9ca96fc212122749f5234bc2b6fb05fb942ed566 + languageName: node + linkType: hard + +"inspect-with-kind@npm:^1.0.5": + version: 1.0.5 + resolution: "inspect-with-kind@npm:1.0.5" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10/2124548720116dc86f0ce1601e7a7e87ba146b934c4bd324d7ed2e93860c8a2e992c42617e71a33da88d49458e96f330cfcafdd4d0c2bf95484ff16e61abf31c + languageName: node + linkType: hard + +"ioredis@npm:^5.6.1": + version: 5.9.1 + resolution: "ioredis@npm:5.9.1" + dependencies: + "@ioredis/commands": "npm:1.5.0" + cluster-key-slot: "npm:^1.1.0" + debug: "npm:^4.3.4" + denque: "npm:^2.1.0" + lodash.defaults: "npm:^4.2.0" + lodash.isarguments: "npm:^3.1.0" + redis-errors: "npm:^1.2.0" + redis-parser: "npm:^3.0.0" + standard-as-callback: "npm:^2.1.0" + checksum: 10/ea2853b6b342dbc279cc2828ae52d851271616560c1c242f23dba06313478a3f4e8582d198ff4568bc3934b3b6f4f52e1db95ea4f82f98374c9dfb5061291831 + languageName: node + linkType: hard + +"ip-address@npm:10.0.1": + version: 10.0.1 + resolution: "ip-address@npm:10.0.1" + checksum: 10/09731acda32cd8e14c46830c137e7e5940f47b36d63ffb87c737331270287d631cf25aa95570907a67d3f919fdb25f4470c404eda21e62f22e0a55927f4dd0fb + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10/a6979629d1ad9c1fb424bc25182203fad739b40225aebc55ec6243bbff5035faf7b9ed6efab3a097de6e713acbbfde944baacfa73e11852bb43989c45a68d79e + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 10/864d0cced0c0832700e9621913a6429ccdc67f37c1bd78fb8c6789fff35c9d167cb329134acad2290497a53336813ab4798d2794fd675d5eb33b5fdf0982b9ca + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10/73ced84fa35e59e2c57da2d01e12cd01479f381d7f122ce41dcbb713f09dbfc651315832cd2bf8accba7681a69e4d6f1e03941d94dd10040d415086360e7005e + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: "npm:^2.0.0" + checksum: 10/078e51b4f956c2c5fd2b26bb2672c3ccf7e1faff38e0ebdba45612265f4e3d9fc3127a1fa8370bbf09eab61339203c3d3b7af5662cbf8be4030f8fac37745b0e + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10/48a9297fb92c99e9df48706241a189da362bff3003354aea4048bd5f7b2eb0d823cd16d0a383cece3d76166ba16d85d9659165ac6fcce1ac12e6c649d66dbdb9 + languageName: node + linkType: hard + +"is-core-module@npm:^2.16.1": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10/452b2c2fb7f889cbbf7e54609ef92cf6c24637c568acc7e63d166812a0fb365ae8a504c333a29add8bdb1686704068caa7f4e4b639b650dde4f00a038b8941fb + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 10/3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 + languageName: node + linkType: hard + +"is-electron@npm:^2.2.0": + version: 2.2.2 + resolution: "is-electron@npm:2.2.2" + checksum: 10/de5aa8bd8d72c96675b8d0f93fab4cc21f62be5440f65bc05c61338ca27bd851a64200f31f1bf9facbaa01b3dbfed7997b2186741d84b93b63e0aff1db6a9494 + languageName: node + linkType: hard + +"is-expression@npm:^4.0.0": + version: 4.0.0 + resolution: "is-expression@npm:4.0.0" + dependencies: + acorn: "npm:^7.1.1" + object-assign: "npm:^4.1.1" + checksum: 10/0f01d0ff53fbbec36abae8fbb7ef056c6d024f7128646856a3e6c500b205788d3e0f337025e72df979d7d7cf4674a00370633d7f8974c668b2d3fdb7e8a83bdb + languageName: node + linkType: hard + +"is-extendable@npm:^0.1.0": + version: 0.1.1 + resolution: "is-extendable@npm:0.1.1" + checksum: 10/3875571d20a7563772ecc7a5f36cb03167e9be31ad259041b4a8f73f33f885441f778cee1f1fe0085eb4bc71679b9d8c923690003a36a6a5fdf8023e6e3f0672 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10/df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 10/44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10/3ed74f2b0cdf4f401f38edb0442ddfde3092d79d7d35c9919c86641efdbcbb32e45aa3c0f70ce5eecc946896cd5a0f26e4188b9f2b881876f7cb6c505b82da11 + languageName: node + linkType: hard + +"is-interactive@npm:^1.0.0": + version: 1.0.0 + resolution: "is-interactive@npm:1.0.0" + checksum: 10/824808776e2d468b2916cdd6c16acacebce060d844c35ca6d82267da692e92c3a16fdba624c50b54a63f38bdc4016055b6f443ce57d7147240de4f8cdabaf6f9 + languageName: node + linkType: hard + +"is-invalid-path@npm:^1.0.2": + version: 1.0.2 + resolution: "is-invalid-path@npm:1.0.2" + checksum: 10/8776ef093ed57b6ca618ce5f4eaae8da9f4c96d3c14def25e1fa135ebc9b56ad86ba67656d3282a22a26b299d2d3b83eb4edcc0bf85db7487de66225da570043 + languageName: node + linkType: hard + +"is-network-error@npm:^1.0.0": + version: 1.3.0 + resolution: "is-network-error@npm:1.3.0" + checksum: 10/56dc0b8ed9c0bb72202058f172ad0c3121cf68772e8cbba343d3775f6e2ec7877d423cbcea45f4cedcd345de8693de1b52dfe0c6fc15d652c4aa98c2abf0185a + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 10/6a6c3383f68afa1e05b286af866017c78f1226d43ac8cb064e115ff9ed85eb33f5c4f7216c96a71e4dfea289ef52c5da3aef5bbfade8ffe47a0465d70c0c8e86 + languageName: node + linkType: hard + +"is-plain-obj@npm:^1.0.0, is-plain-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "is-plain-obj@npm:1.1.0" + checksum: 10/0ee04807797aad50859652a7467481816cbb57e5cc97d813a7dcd8915da8195dc68c436010bf39d195226cde6a2d352f4b815f16f26b7bf486a5754290629931 + languageName: node + linkType: hard + +"is-promise@npm:^2.0.0": + version: 2.2.2 + resolution: "is-promise@npm:2.2.2" + checksum: 10/18bf7d1c59953e0ad82a1ed963fb3dc0d135c8f299a14f89a17af312fc918373136e56028e8831700e1933519630cc2fd4179a777030330fde20d34e96f40c78 + languageName: node + linkType: hard + +"is-promise@npm:^4.0.0": + version: 4.0.0 + resolution: "is-promise@npm:4.0.0" + checksum: 10/0b46517ad47b00b6358fd6553c83ec1f6ba9acd7ffb3d30a0bf519c5c69e7147c132430452351b8a9fc198f8dd6c4f76f8e6f5a7f100f8c77d57d9e0f4261a8a + languageName: node + linkType: hard + +"is-promise@npm:~1, is-promise@npm:~1.0.0": + version: 1.0.1 + resolution: "is-promise@npm:1.0.1" + checksum: 10/75e6fac7e60e7fa979bf7a53cb7d42f3fd0991795cad6e195196fded7acbc7609e22230435a435b0924037030bdc32b0bc97f593ff2a362a69ddde1bc1fb08ef + languageName: node + linkType: hard + +"is-regex@npm:^1.0.3": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10/c42b7efc5868a5c9a4d8e6d3e9816e8815c611b09535c00fead18a1138455c5cb5e1887f0023a467ad3f9c419d62ba4dc3d9ba8bafe55053914d6d6454a945d2 + languageName: node + linkType: hard + +"is-stream@npm:^1.1.0": + version: 1.1.0 + resolution: "is-stream@npm:1.1.0" + checksum: 10/351aa77c543323c4e111204482808cfad68d2e940515949e31ccd0b010fc13d5fba4b9c230e4887fd24284713040f43e542332fbf172f6b9944b7d62e389c0ec + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0, is-stream@npm:^2.0.1": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10/b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.14": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10/e8cf60b9ea85667097a6ad68c209c9722cfe8c8edf04d6218366469e51944c5cc25bae45ffb845c23f811d262e4314d3b0168748eb16711aa34d12724cdf0735 + languageName: node + linkType: hard + +"is-unicode-supported@npm:^0.1.0": + version: 0.1.0 + resolution: "is-unicode-supported@npm:0.1.0" + checksum: 10/a2aab86ee7712f5c2f999180daaba5f361bdad1efadc9610ff5b8ab5495b86e4f627839d085c6530363c6d6d4ecbde340fb8e54bdb83da4ba8e0865ed5513c52 + languageName: node + linkType: hard + +"is-wsl@npm:^2.1.1": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: "npm:^2.0.0" + checksum: 10/20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 + languageName: node + linkType: hard + +"isarray@npm:0.0.1": + version: 0.0.1 + resolution: "isarray@npm:0.0.1" + checksum: 10/49191f1425681df4a18c2f0f93db3adb85573bcdd6a4482539d98eac9e705d8961317b01175627e860516a2fc45f8f9302db26e5a380a97a520e272e2a40a8d4 + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10/1d8bc7911e13bb9f105b1b3e0b396c787a9e63046af0b8fe0ab1414488ab06b2b099b87a2d8a9e31d21c9a6fad773c7fc8b257c4880f2d957274479d28ca3414 + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10/f032df8e02dce8ec565cf2eb605ea939bdccea528dbcf565cdf92bfa2da9110461159d86a537388ef1acef8815a330642d7885b29010e8f7eac967c9993b65ab + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10/7c9f715c03aff08f35e98b1fadae1b9267b38f0615d501824f9743f3aab99ef10e303ce7db3f186763a0b70a19de5791ebfc854ff884d5a8c4d92211f642ec92 + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10/7fe1931ee4e88eb5aa524cd3ceb8c882537bc3a81b02e438b240e47012eef49c86904d0f0e593ea7c3a9996d18d0f1f3be8d3eaa92333977b0c3a9d353d5563e + languageName: node + linkType: hard + +"iterare@npm:1.2.1": + version: 1.2.1 + resolution: "iterare@npm:1.2.1" + checksum: 10/ee8322dd9d92e86d8653c899df501c58c5b8e90d6767cf2af0b6d6dc5a4b9b7ed8bce936976f4f4c3a55be110a300c8a7d71967d03f72e104e8db66befcfd874 + languageName: node + linkType: hard + +"jackspeak@npm:^2.3.6": + version: 2.3.6 + resolution: "jackspeak@npm:2.3.6" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10/6e6490d676af8c94a7b5b29b8fd5629f21346911ebe2e32931c2a54210134408171c24cee1a109df2ec19894ad04a429402a8438cbf5cc2794585d35428ace76 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 10/96f8786eaab98e4bf5b2a5d6d9588ea46c4d06bbc4f2eb861fdd7b6b182b16f71d8a70e79820f335d52653b16d4843b29dd9cdcf38ae80406756db9199497cf3 + languageName: node + linkType: hard + +"jake@npm:^10.8.5": + version: 10.9.4 + resolution: "jake@npm:10.9.4" + dependencies: + async: "npm:^3.2.6" + filelist: "npm:^1.0.4" + picocolors: "npm:^1.1.1" + bin: + jake: bin/cli.js + checksum: 10/97e48f73f5e315a3b6e1a48b4bcc0cdf2c2cf82100ec9e76a032fd5d614dcd32c4315572cfcb66e9f9bdecca3900aaa61fe72b781a74b06aefd3ec4c1c917f0b + languageName: node + linkType: hard + +"javascript-natural-sort@npm:^0.7.1": + version: 0.7.1 + resolution: "javascript-natural-sort@npm:0.7.1" + checksum: 10/7bf6eab67871865d347f09a95aa770f9206c1ab0226bcda6fdd9edec340bf41111a7f82abac30556aa16a21cfa3b2b1ca4a362c8b73dd5ce15220e5d31f49d79 + languageName: node + linkType: hard + +"jest-worker@npm:^27.4.5": + version: 27.5.1 + resolution: "jest-worker@npm:27.5.1" + dependencies: + "@types/node": "npm:*" + merge-stream: "npm:^2.0.0" + supports-color: "npm:^8.0.0" + checksum: 10/06c6e2a84591d9ede704d5022fc13791e8876e83397c89d481b0063332abbb64c0f01ef4ca7de520b35c7a1058556078d6bdc3631376f4e9ffb42316c1a8488e + languageName: node + linkType: hard + +"jiti@npm:^2.6.0": + version: 2.6.1 + resolution: "jiti@npm:2.6.1" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10/8cd72c5fd03a0502564c3f46c49761090f6dadead21fa191b73535724f095ad86c2fa89ee6fe4bc3515337e8d406cc8fb2d37b73fa0c99a34584bac35cd4a4de + languageName: node + linkType: hard + +"js-beautify@npm:^1.6.14": + version: 1.15.4 + resolution: "js-beautify@npm:1.15.4" + dependencies: + config-chain: "npm:^1.1.13" + editorconfig: "npm:^1.0.4" + glob: "npm:^10.4.2" + js-cookie: "npm:^3.0.5" + nopt: "npm:^7.2.1" + bin: + css-beautify: js/bin/css-beautify.js + html-beautify: js/bin/html-beautify.js + js-beautify: js/bin/js-beautify.js + checksum: 10/89f874f994a409868c74d23bdf3869281a25804dd4f77c4eac170cdee671dfb3248370c3b686ea03bb9a7cc7141769c4f450ad85e9158fbed3d7d78c330ae9a1 + languageName: node + linkType: hard + +"js-cookie@npm:^3.0.5": + version: 3.0.5 + resolution: "js-cookie@npm:3.0.5" + checksum: 10/366494b1630b9fb8abaef3659748db5dfd52c58c6fc3459b9f0a03b492593bc1b01c6dfcc066b46f6413c28edb3a00cc68fb61ea8cdf6991bedf1f100f8a389d + languageName: node + linkType: hard + +"js-stringify@npm:^1.0.2": + version: 1.0.2 + resolution: "js-stringify@npm:1.0.2" + checksum: 10/f9701d9e535d3ac0f62bbf2624b76c5d0af5b889187232817ae284a41ba21fd7a8b464c2dce3815d8cf52c8bea3480be6b368cfc2c67da799cad458058e8bbf5 + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10/af37d0d913fb56aec6dc0074c163cc71cd23c0b8aad5c2350747b6721d37ba118af35abdd8b33c47ec2800de07dedb16a527ca9c530ee004093e04958bd0cbf2 + languageName: node + linkType: hard + +"js-yaml@npm:4.1.1, js-yaml@npm:^4.1.0, js-yaml@npm:^4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10/a52d0519f0f4ef5b4adc1cde466cb54c50d56e2b4a983b9d5c9c0f2f99462047007a6274d7e95617a21d3c91fde3ee6115536ed70991cd645ba8521058b78f77 + languageName: node + linkType: hard + +"json-bigint@npm:^1.0.0": + version: 1.0.0 + resolution: "json-bigint@npm:1.0.0" + dependencies: + bignumber.js: "npm:^9.0.0" + checksum: 10/cd3973b88e5706f8f89d2a9c9431f206ef385bd5c584db1b258891a5e6642507c32316b82745239088c697f5ddfe967351e1731f5789ba7855aed56ad5f70e1f + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10/82876154521b7b68ba71c4f969b91572d1beabadd87bd3a6b236f85fbc7dc4695089191ed60bb59f9340993c51b33d479f45b6ba9f3548beb519705281c32c3c + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0, json-parse-even-better-errors@npm:^2.3.1": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10/5f3a99009ed5f2a5a67d06e2f298cc97bc86d462034173308156f15b43a6e850be8511dc204b9b94566305da2947f7d90289657237d210351a39059ff9d666cf + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10/7486074d3ba247769fda17d5181b345c9fb7d12e0da98b22d1d71a5db9698d8b4bd900a3ec1a4ffdd60846fc2556274a5c894d0c48795f14cb03aeae7b55260b + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10/02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10/12786c2e2f22c27439e6db0532ba321f1d0617c27ad8cb1c352a0e9249a50182fd1ba8b52a18899291604b0c32eafa8afd09e51203f19109a0537f68db2b652d + languageName: node + linkType: hard + +"json-stringify-safe@npm:^5.0.0": + version: 5.0.1 + resolution: "json-stringify-safe@npm:5.0.1" + checksum: 10/59169a081e4eeb6f9559ae1f938f656191c000e0512aa6df9f3c8b2437a4ab1823819c6b9fd1818a4e39593ccfd72e9a051fdd3e2d1e340ed913679e888ded8c + languageName: node + linkType: hard + +"json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10/1db67b853ff0de3534085d630691d3247de53a2ed1390ba0ddff681ea43e9b3e30ecbdb65c5e9aab49435e44059c23dbd6fee8ee619419ba37465bb0dd7135da + languageName: node + linkType: hard + +"jsonc-parser@npm:3.3.1": + version: 3.3.1 + resolution: "jsonc-parser@npm:3.3.1" + checksum: 10/9b0dc391f20b47378f843ef1e877e73ec652a5bdc3c5fa1f36af0f119a55091d147a86c1ee86a232296f55c929bba174538c2bf0312610e0817a22de131cc3f4 + languageName: node + linkType: hard + +"jsonfile@npm:^4.0.0": + version: 4.0.0 + resolution: "jsonfile@npm:4.0.0" + dependencies: + graceful-fs: "npm:^4.1.6" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10/17796f0ab1be8479827d3683433f97ebe0a1c6932c3360fa40348eac36904d69269aab26f8b16da311882d94b42e9208e8b28e490bf926364f3ac9bff134c226 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.2.0 + resolution: "jsonfile@npm:6.2.0" + dependencies: + graceful-fs: "npm:^4.1.6" + universalify: "npm:^2.0.0" + dependenciesMeta: + graceful-fs: + optional: true + checksum: 10/513aac94a6eff070767cafc8eb4424b35d523eec0fcd8019fe5b975f4de5b10a54640c8d5961491ddd8e6f562588cf62435c5ddaf83aaf0986cd2ee789e0d7b9 + languageName: node + linkType: hard + +"jsonschema@npm:^1.4.1": + version: 1.5.0 + resolution: "jsonschema@npm:1.5.0" + checksum: 10/46bf49b388ba922073bcb3c8d5e90af9d29fc8303dc866fd440182c88d6b4fd2807679fd39cdefb4113156d104ea47da9c0ff4bbcb0032c9fa29461cb1a92182 + languageName: node + linkType: hard + +"jsonwebtoken@npm:8.5.1": + version: 8.5.1 + resolution: "jsonwebtoken@npm:8.5.1" + dependencies: + jws: "npm:^3.2.2" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^5.6.0" + checksum: 10/a7b52ea570f70bea183ceca970c003f223d9d3425d72498002e9775485c7584bfa3751d1c7291dbb59738074cba288effe73591b87bec5d467622ab3a156fdb6 + languageName: node + linkType: hard + +"jsonwebtoken@npm:9.0.2": + version: 9.0.2 + resolution: "jsonwebtoken@npm:9.0.2" + dependencies: + jws: "npm:^3.2.2" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^7.5.4" + checksum: 10/6e9b6d879cec2b27f2f3a88a0c0973edc7ba956a5d9356b2626c4fddfda969e34a3832deaf79c3e1c6c9a525bc2c4f2c2447fa477f8ac660f0017c31a59ae96b + languageName: node + linkType: hard + +"jsonwebtoken@npm:9.0.3, jsonwebtoken@npm:^9.0.2": + version: 9.0.3 + resolution: "jsonwebtoken@npm:9.0.3" + dependencies: + jws: "npm:^4.0.1" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^7.5.4" + checksum: 10/a67a276db41fbfb458ebdc4938d5d7b01d4743e16bda0f25ac01996fe5b5819d66656153f6cfce19b4680b79ae9f9ca185965defc22e77e0abddf443573238d6 + languageName: node + linkType: hard + +"jstransformer@npm:1.0.0": + version: 1.0.0 + resolution: "jstransformer@npm:1.0.0" + dependencies: + is-promise: "npm:^2.0.0" + promise: "npm:^7.0.1" + checksum: 10/7bca6e2e2fb4b6e65e567965e0370488699eb05cbbf27e6cb2ee2a89912d9c238aceb5c63216d834c50a000ed991b2bbd703b5432351aa2a15ee979b5180e652 + languageName: node + linkType: hard + +"jszip@npm:^3.10.1": + version: 3.10.1 + resolution: "jszip@npm:3.10.1" + dependencies: + lie: "npm:~3.3.0" + pako: "npm:~1.0.2" + readable-stream: "npm:~2.3.6" + setimmediate: "npm:^1.0.5" + checksum: 10/bfbfbb9b0a27121330ac46ab9cdb3b4812433faa9ba4a54742c87ca441e31a6194ff70ae12acefa5fe25406c432290e68003900541d948a169b23d30c34dd984 + languageName: node + linkType: hard + +"juice@npm:^10.0.0": + version: 10.0.1 + resolution: "juice@npm:10.0.1" + dependencies: + cheerio: "npm:1.0.0-rc.12" + commander: "npm:^6.1.0" + mensch: "npm:^0.3.4" + slick: "npm:^1.12.2" + web-resource-inliner: "npm:^6.0.1" + bin: + juice: bin/juice + checksum: 10/e881bc266fdebbb527e18bd13dbdd329e5b8f09db068a4c57d6c53881dff8debc5f0445f21054ce033999aaff5204485e3315cf9770802be979a5048eb752976 + languageName: node + linkType: hard + +"jwa@npm:^1.4.2": + version: 1.4.2 + resolution: "jwa@npm:1.4.2" + dependencies: + buffer-equal-constant-time: "npm:^1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10/a46c9ddbcc226d9e85e13ef96328c7d331abddd66b5a55ec44bcf4350464a6125385ac9c1e64faa0fae8d586d90a14d6b5e96c73f0388970a3918d5252efb0f3 + languageName: node + linkType: hard + +"jwa@npm:^2.0.1": + version: 2.0.1 + resolution: "jwa@npm:2.0.1" + dependencies: + buffer-equal-constant-time: "npm:^1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10/b04312a1de85f912b96aa3a7211717b8336945fab5b4f7cbc7800f4c80934060c0a3111576fad8d76e41ad62887d6da4b21fd4c47e45c174197f8be7dc0c1694 + languageName: node + linkType: hard + +"jws@npm:^3.2.2": + version: 3.2.3 + resolution: "jws@npm:3.2.3" + dependencies: + jwa: "npm:^1.4.2" + safe-buffer: "npm:^5.0.1" + checksum: 10/707387dd1cabcc3d9c2818f773cfaac7ede66e79ca11bbd159285a88cf5d8e8f355afcb8ee373e7bb0fcf9b7a2df015b22c50f27842f2c77453f04cd9f8f4009 + languageName: node + linkType: hard + +"jws@npm:^4.0.0, jws@npm:^4.0.1": + version: 4.0.1 + resolution: "jws@npm:4.0.1" + dependencies: + jwa: "npm:^2.0.1" + safe-buffer: "npm:^5.0.1" + checksum: 10/75d7b157489fa9a72023712c58a7a7706c7e2b10eec27fabd3bb9cae0c9e492251ab72527d20a8a5f5726196f0508c320c643fddff7076657f6bca16d0ceeeeb + languageName: node + linkType: hard + +"jwt-decode@npm:^4.0.0": + version: 4.0.0 + resolution: "jwt-decode@npm:4.0.0" + checksum: 10/87b569e4a9a0067fb0d592bcf3b2ac3e638e49beee28620eeb07bef1b4470f4077dea68c15d191dd68e076846c3af8394be3bcaecffedc6e97433b221fdbbcf3 + languageName: node + linkType: hard + +"keyv@npm:^4.0.0, keyv@npm:^4.5.3, keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10/167eb6ef64cc84b6fa0780ee50c9de456b422a1e18802209234f7c2cf7eae648c7741f32e50d7e24ccb22b24c13154070b01563d642755b156c357431a191e75 + languageName: node + linkType: hard + +"kind-of@npm:^6.0.2": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 10/5873d303fb36aad875b7538798867da2ae5c9e328d67194b0162a3659a627d22f742fc9c4ae95cd1704132a24b00cae5041fc00c0f6ef937dc17080dc4dbb962 + languageName: node + linkType: hard + +"knip@npm:^5.59.1": + version: 5.80.2 + resolution: "knip@npm:5.80.2" + dependencies: + "@nodelib/fs.walk": "npm:^1.2.3" + fast-glob: "npm:^3.3.3" + formatly: "npm:^0.3.0" + jiti: "npm:^2.6.0" + js-yaml: "npm:^4.1.1" + minimist: "npm:^1.2.8" + oxc-resolver: "npm:^11.15.0" + picocolors: "npm:^1.1.1" + picomatch: "npm:^4.0.1" + smol-toml: "npm:^1.5.2" + strip-json-comments: "npm:5.0.3" + zod: "npm:^4.1.11" + peerDependencies: + "@types/node": ">=18" + typescript: ">=5.0.4 <7" + bin: + knip: bin/knip.js + knip-bun: bin/knip-bun.js + checksum: 10/5cc2c6dbd32ae102776e7fbc4e9be0ab082e392e2f8a72efa12f1dbf3235d72cf2140cfe4c7475255e416c63e68ae60ff8b62efccb96d34bf412f4740b8fc796 + languageName: node + linkType: hard + +"kuler@npm:^2.0.0": + version: 2.0.0 + resolution: "kuler@npm:2.0.0" + checksum: 10/9e10b5a1659f9ed8761d38df3c35effabffbd19fc6107324095238e4ef0ff044392cae9ac64a1c2dda26e532426485342226b93806bd97504b174b0dcf04ed81 + languageName: node + linkType: hard + +"lazystream@npm:^1.0.0": + version: 1.0.1 + resolution: "lazystream@npm:1.0.1" + dependencies: + readable-stream: "npm:^2.0.5" + checksum: 10/35f8cf8b5799c76570b211b079d4d706a20cbf13a4936d44cc7dbdacab1de6b346ab339ed3e3805f4693155ee5bbebbda4050fa2b666d61956e89a573089e3d4 + languageName: node + linkType: hard + +"leac@npm:^0.6.0": + version: 0.6.0 + resolution: "leac@npm:0.6.0" + checksum: 10/bfe6aa128ca98664f124096f65584778194a8e1ddebf77d315fd9681be849c1619b0a9d9f4743e67aea0298808bd69ef25bd74320cad50a80be6852714627870 + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10/2e4720ff79f21ae08d42374b0a5c2f664c5be8b6c8f565bb4e1315c96ed3a8acaa9de788ffed82d7f2378cf36958573de07ef92336cb5255ed74d08b8318c9ee + languageName: node + linkType: hard + +"libbase64@npm:1.3.0": + version: 1.3.0 + resolution: "libbase64@npm:1.3.0" + checksum: 10/e90f06c7c7af754521d254c53ef847bab8ca354ee38f4c5d866c3fc77b4cb7a2ab77aa220c0df433f8d17abd0e884cf322b2771d84c2f65760e22012dfecaa67 + languageName: node + linkType: hard + +"libmime@npm:5.3.7": + version: 5.3.7 + resolution: "libmime@npm:5.3.7" + dependencies: + encoding-japanese: "npm:2.2.0" + iconv-lite: "npm:0.6.3" + libbase64: "npm:1.3.0" + libqp: "npm:2.1.1" + checksum: 10/9f148cd94256f604e71af60c159ad08b8914029727220b2a048e1c95957b099a41fb0c8a225dd1af87d5dfd4fa7c26afd7cf83d395894b7214e6827d7e25511b + languageName: node + linkType: hard + +"libphonenumber-js@npm:^1.11.1, libphonenumber-js@npm:^1.12.8": + version: 1.12.34 + resolution: "libphonenumber-js@npm:1.12.34" + checksum: 10/e0cbeb17076669fd10d9db0cb91be8fdd25b5379b094187c93db96540b8004b16a539e8f81ef9215685cba4d51a6287206cd26181d57b1df79a99a32624021fd + languageName: node + linkType: hard + +"libqp@npm:2.1.1": + version: 2.1.1 + resolution: "libqp@npm:2.1.1" + checksum: 10/cc59bd6ea61604b0505651fa79b722ff69d39b1fc609dad3ad4a20a51c6eb904babc0d1a59699a55a628063f31540232845bab471910d92c8b607120f563089e + languageName: node + linkType: hard + +"lie@npm:~3.3.0": + version: 3.3.0 + resolution: "lie@npm:3.3.0" + dependencies: + immediate: "npm:~3.0.5" + checksum: 10/f335ce67fe221af496185d7ce39c8321304adb701e122942c495f4f72dcee8803f9315ee572f5f8e8b08b9e8d7195da91b9fad776e8864746ba8b5e910adf76e + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10/0c37f9f7fa212b38912b7145e1cd16a5f3cd34d782441c3e6ca653485d326f58b3caccda66efce1c5812bde4961bbde3374fae4b0d11bf1226152337f3894aa5 + languageName: node + linkType: hard + +"linkify-it@npm:5.0.0": + version: 5.0.0 + resolution: "linkify-it@npm:5.0.0" + dependencies: + uc.micro: "npm:^2.0.0" + checksum: 10/ef3b7609dda6ec0c0be8a7b879cea195f0d36387b0011660cd6711bba0ad82137f59b458b7e703ec74f11d88e7c1328e2ad9b855a8500c0ded67461a8c4519e6 + languageName: node + linkType: hard + +"liquidjs@npm:^10.11.1": + version: 10.24.0 + resolution: "liquidjs@npm:10.24.0" + dependencies: + commander: "npm:^10.0.0" + bin: + liquid: bin/liquid.js + liquidjs: bin/liquid.js + checksum: 10/2ba7424a3decc9d6b9c8b937c4d8104ab45e42043279175bc116d2fc75b6ed5fbeff11552e5f53f581fc57467622081e590aba878cc20b5da41d9914ec248b70 + languageName: node + linkType: hard + +"listenercount@npm:~1.0.1": + version: 1.0.1 + resolution: "listenercount@npm:1.0.1" + checksum: 10/208c6d2b57dc16c22cc71b58a7debb6f4612a79de211b76e251efee8eb03b9f6acd4651399016ef9c15ff6a3dedfd7acc96064acddce0dbe627e2d8478034d3d + languageName: node + linkType: hard + +"load-esm@npm:1.0.3": + version: 1.0.3 + resolution: "load-esm@npm:1.0.3" + checksum: 10/6949e8c253dddccca2a0ded1e9e0bbbc81924439d99e3a7d0f946ba6cdcd16de22c3cef28d997fd950befda0826ca65c3f913d9ba893d50e9cfc0bbd9a2a1e90 + languageName: node + linkType: hard + +"loader-runner@npm:^4.3.1": + version: 4.3.1 + resolution: "loader-runner@npm:4.3.1" + checksum: 10/d77127497c3f91fdba351e3e91156034e6e590e9f050b40df6c38ac16c54b5c903f7e2e141e09fefd046ee96b26fb50773c695ebc0aa205a4918683b124b04ba + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10/72eb661788a0368c099a184c59d2fee760b3831c9c1c33955e8a19ae4a21b4116e53fa736dc086cdeb9fce9f7cc508f2f92d2d3aae516f133e16a2bb59a39f5a + languageName: node + linkType: hard + +"lodash.camelcase@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.camelcase@npm:4.3.0" + checksum: 10/c301cc379310441dc73cd6cebeb91fb254bea74e6ad3027f9346fc43b4174385153df420ffa521654e502fd34c40ef69ca4e7d40ee7129a99e06f306032bfc65 + languageName: node + linkType: hard + +"lodash.defaults@npm:^4.2.0": + version: 4.2.0 + resolution: "lodash.defaults@npm:4.2.0" + checksum: 10/6a2a9ea5ad7585aff8d76836c9e1db4528e5f5fa50fc4ad81183152ba8717d83aef8aec4fa88bf3417ed946fd4b4358f145ee08fbc77fb82736788714d3e12db + languageName: node + linkType: hard + +"lodash.difference@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.difference@npm:4.5.0" + checksum: 10/b22adb1be9c60e5997b8b483f8bab19878cb40eda65437907958e5d27990214716e1b00ebe312a97f47e63d8b891e4ae30947d08e1f0861ccdb9462f56ab9d77 + languageName: node + linkType: hard + +"lodash.escaperegexp@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.escaperegexp@npm:4.1.2" + checksum: 10/6d99452b1cfd6073175a9b741a9b09ece159eac463f86f02ea3bee2e2092923fce812c8d2bf446309cc52d1d61bf9af51c8118b0d7421388e6cead7bd3798f0f + languageName: node + linkType: hard + +"lodash.flatten@npm:^4.4.0": + version: 4.4.0 + resolution: "lodash.flatten@npm:4.4.0" + checksum: 10/a2b192f220b0b6c78a6c0175e96bad888b9e0f2a887a8e8c1d0c29d03231fbf110bbb9be0d9de5f936537d143eeb9d5b4f44c4a44f5592c195bf2fae6a6b1e3a + languageName: node + linkType: hard + +"lodash.groupby@npm:^4.6.0": + version: 4.6.0 + resolution: "lodash.groupby@npm:4.6.0" + checksum: 10/98bd04e58ce4cebb2273010352508b5ea12025e94fcfd70c84c8082ef3b0689178e8e6dd53bff919f525fae9bd67b4aba228d606b75a967f30e84ec9610b5de1 + languageName: node + linkType: hard + +"lodash.includes@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.includes@npm:4.3.0" + checksum: 10/45e0a7c7838c931732cbfede6327da321b2b10482d5063ed21c020fa72b09ca3a4aa3bda4073906ab3f436cf36eb85a52ea3f08b7bab1e0baca8235b0e08fe51 + languageName: node + linkType: hard + +"lodash.isarguments@npm:^3.1.0": + version: 3.1.0 + resolution: "lodash.isarguments@npm:3.1.0" + checksum: 10/e5186d5fe0384dcb0652501d9d04ebb984863ebc9c9faa2d4b9d5dfd81baef9ffe8e2887b9dc471d62ed092bc0788e5f1d42e45c72457a2884bbb54ac132ed92 + languageName: node + linkType: hard + +"lodash.isboolean@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isboolean@npm:3.0.3" + checksum: 10/b70068b4a8b8837912b54052557b21fc4774174e3512ed3c5b94621e5aff5eb6c68089d0a386b7e801d679cd105d2e35417978a5e99071750aa2ed90bffd0250 + languageName: node + linkType: hard + +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: 10/82fc58a83a1555f8df34ca9a2cd300995ff94018ac12cc47c349655f0ae1d4d92ba346db4c19bbfc90510764e0c00ddcc985a358bdcd4b3b965abf8f2a48a214 + languageName: node + linkType: hard + +"lodash.isfunction@npm:^3.0.9": + version: 3.0.9 + resolution: "lodash.isfunction@npm:3.0.9" + checksum: 10/99e54c34b1e8a9ba75c034deb39cedbd2aca7af685815e67a2a8ec4f73ec9748cda6ebee5a07d7de4b938e90d421fd280e9c385cc190f903ac217ac8aff30314 + languageName: node + linkType: hard + +"lodash.isinteger@npm:^4.0.4": + version: 4.0.4 + resolution: "lodash.isinteger@npm:4.0.4" + checksum: 10/c971f5a2d67384f429892715550c67bac9f285604a0dd79275fd19fef7717aec7f2a6a33d60769686e436ceb9771fd95fe7fcb68ad030fc907d568d5a3b65f70 + languageName: node + linkType: hard + +"lodash.isnil@npm:^4.0.0": + version: 4.0.0 + resolution: "lodash.isnil@npm:4.0.0" + checksum: 10/ebf8df69879badd6ad99c4f64c54c470248df5cf92b208ca730861b1d8ac058da7b632ac811d18b0929d93cbac8d8fc866e781ee816b0142c56952e85edc682f + languageName: node + linkType: hard + +"lodash.isnumber@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isnumber@npm:3.0.3" + checksum: 10/913784275b565346255e6ae6a6e30b760a0da70abc29f3e1f409081585875105138cda4a429ff02577e1bc0a7ae2a90e0a3079a37f3a04c3d6c5aaa532f4cab2 + languageName: node + linkType: hard + +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 10/29c6351f281e0d9a1d58f1a4c8f4400924b4c79f18dfc4613624d7d54784df07efaff97c1ff2659f3e085ecf4fff493300adc4837553104cef2634110b0d5337 + languageName: node + linkType: hard + +"lodash.isstring@npm:^4.0.1": + version: 4.0.1 + resolution: "lodash.isstring@npm:4.0.1" + checksum: 10/eaac87ae9636848af08021083d796e2eea3d02e80082ab8a9955309569cb3a463ce97fd281d7dc119e402b2e7d8c54a23914b15d2fc7fff56461511dc8937ba0 + languageName: node + linkType: hard + +"lodash.isundefined@npm:^3.0.1": + version: 3.0.1 + resolution: "lodash.isundefined@npm:3.0.1" + checksum: 10/52b4d99a47bd41daa4e2860200258f56b1f2c99263c11a5f607fbbd91d6447fe674bdafc172735d099908a09136d4a0f98cf79715e38ca4b490fdda7162be289 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10/d0ea2dd0097e6201be083865d50c3fb54fbfbdb247d9cc5950e086c991f448b7ab0cdab0d57eacccb43473d3f2acd21e134db39f22dac2d6c9ba6bf26978e3d6 + languageName: node + linkType: hard + +"lodash.mergewith@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.mergewith@npm:4.6.2" + checksum: 10/aea75a4492541a4902ac7e551dc6c54b722da0c187f84385d02e8fc33a7ae3454b837822446e5f63fcd5ad1671534ea408740b776670ea4d9c7890b10105fce0 + languageName: node + linkType: hard + +"lodash.once@npm:^4.0.0": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: 10/202f2c8c3d45e401b148a96de228e50ea6951ee5a9315ca5e15733d5a07a6b1a02d9da1e7fdf6950679e17e8ca8f7190ec33cae47beb249b0c50019d753f38f3 + languageName: node + linkType: hard + +"lodash.union@npm:^4.6.0": + version: 4.6.0 + resolution: "lodash.union@npm:4.6.0" + checksum: 10/175f5786efc527238c1350ce561c28e5ba527b5957605f9e5b8a804fce78801d09ced7b72de0302325e5b14c711f94690b1a733c13ad3674cc1a76e1172db1f8 + languageName: node + linkType: hard + +"lodash.uniq@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.uniq@npm:4.5.0" + checksum: 10/86246ca64ac0755c612e5df6d93cfe92f9ecac2e5ff054b965efbbb1d9a647b6310969e78545006f70f52760554b03233ad0103324121ae31474c20d5f7a2812 + languageName: node + linkType: hard + +"lodash@npm:4.17.21, lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10/c08619c038846ea6ac754abd6dd29d2568aa705feb69339e836dfa8d8b09abbb2f859371e86863eda41848221f9af43714491467b5b0299122431e202bb0c532 + languageName: node + linkType: hard + +"log-symbols@npm:^4.1.0": + version: 4.1.0 + resolution: "log-symbols@npm:4.1.0" + dependencies: + chalk: "npm:^4.1.0" + is-unicode-supported: "npm:^0.1.0" + checksum: 10/fce1497b3135a0198803f9f07464165e9eb83ed02ceb2273930a6f8a508951178d8cf4f0378e9d28300a2ed2bc49050995d2bd5f53ab716bb15ac84d58c6ef74 + languageName: node + linkType: hard + +"log4js@npm:^6.9.1": + version: 6.9.1 + resolution: "log4js@npm:6.9.1" + dependencies: + date-format: "npm:^4.0.14" + debug: "npm:^4.3.4" + flatted: "npm:^3.2.7" + rfdc: "npm:^1.3.0" + streamroller: "npm:^3.1.5" + checksum: 10/421fb9c1e5a8859a810a40c9ee01fb8e4dfc2fed838049946e67c0064d197bdf76ca43b8fc45df50c5d709e6fc4f218d314f189a0feb8be0c48bdae80cb0934c + languageName: node + linkType: hard + +"logform@npm:^2.7.0": + version: 2.7.0 + resolution: "logform@npm:2.7.0" + dependencies: + "@colors/colors": "npm:1.6.0" + "@types/triple-beam": "npm:^1.3.2" + fecha: "npm:^4.2.0" + ms: "npm:^2.1.1" + safe-stable-stringify: "npm:^2.3.1" + triple-beam: "npm:^1.3.0" + checksum: 10/4b861bfd67efe599ab41113ae3ffe92b1873bf86793fb442f58971852430d8f416f9904da69e5043071fb3725690e2499a13acbfe92a57ba7d21690004f9edc0 + languageName: node + linkType: hard + +"long@npm:^4.0.0": + version: 4.0.0 + resolution: "long@npm:4.0.0" + checksum: 10/8296e2ba7bab30f9cfabb81ebccff89c819af6a7a78b4bb5a70ea411aa764ee0532f7441381549dfa6a1a98d72abe9138bfcf99f4fa41238629849bc035b845b + languageName: node + linkType: hard + +"long@npm:^5.0.0": + version: 5.3.2 + resolution: "long@npm:5.3.2" + checksum: 10/b6b55ddae56fcce2864d37119d6b02fe28f6dd6d9e44fd22705f86a9254b9321bd69e9ffe35263b4846d54aba197c64882adcb8c543f2383c1e41284b321ea64 + languageName: node + linkType: hard + +"lossless-json@npm:^4.0.1": + version: 4.3.0 + resolution: "lossless-json@npm:4.3.0" + checksum: 10/a984a882c79b6e62a917d0202518472c17587bdee002f1427d7ec61115f9fbdeb5f0baa9f0e9fff8d9dbacd17da6b6c910012b2dcab69dd33d151cb7c13d5a37 + languageName: node + linkType: hard + +"lower-case@npm:^1.1.1": + version: 1.1.4 + resolution: "lower-case@npm:1.1.4" + checksum: 10/0c4aebc459ba330bcc38d20cad26ee33111155ed09c09e7d7ec395997277feee3a4d8db541ed5ca555f20ddc5c65a3b23648d18fcd2a950376da6d0c2e01416e + languageName: node + linkType: hard + +"lowercase-keys@npm:^2.0.0": + version: 2.0.0 + resolution: "lowercase-keys@npm:2.0.0" + checksum: 10/1c233d2da35056e8c49fae8097ee061b8c799b2f02e33c2bf32f9913c7de8fb481ab04dab7df35e94156c800f5f34e99acbf32b21781d87c3aa43ef7b748b79e + languageName: node + linkType: hard + +"lowercase-keys@npm:^3.0.0": + version: 3.0.0 + resolution: "lowercase-keys@npm:3.0.0" + checksum: 10/67a3f81409af969bc0c4ca0e76cd7d16adb1e25aa1c197229587eaf8671275c8c067cd421795dbca4c81be0098e4c426a086a05e30de8a9c587b7a13c0c7ccc5 + languageName: node + linkType: hard + +"lru-cache@npm:^10.2.0": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: 10/e6e90267360476720fa8e83cc168aa2bf0311f3f2eea20a6ba78b90a885ae72071d9db132f40fda4129c803e7dcec3a6b6a6fbb44ca90b081630b810b5d6a41a + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.4 + resolution: "lru-cache@npm:11.2.4" + checksum: 10/3b2da74c0b6653767f8164c38c4c4f4d7f0cc10c62bfa512663d94a830191ae6a5af742a8d88a8b30d5f9974652d3adae53931f32069139ad24fa2a18a199aca + languageName: node + linkType: hard + +"luxon@npm:~3.5.0": + version: 3.5.0 + resolution: "luxon@npm:3.5.0" + checksum: 10/48f86e6c1c96815139f8559456a3354a276ba79bcef0ae0d4f2172f7652f3ba2be2237b0e103b8ea0b79b47715354ac9fac04eb1db3485dcc72d5110491dd47f + languageName: node + linkType: hard + +"luxon@npm:~3.7.0": + version: 3.7.2 + resolution: "luxon@npm:3.7.2" + checksum: 10/b24cd205ed306ce7415991687897dcc4027921ae413c9116590bc33a95f93b86ce52cf74ba72b4f5c5ab1c10090517f54ac8edfb127c049e0bf55b90dc2260be + languageName: node + linkType: hard + +"lvovich@npm:^2.0.2": + version: 2.1.0 + resolution: "lvovich@npm:2.1.0" + checksum: 10/4ed068e8adc9e84f3bfca60cbce3b4f8c07c56c2a94496a010bc1070d53be3e8c82fe1ed31bac6c1e22986b8ef5d95dc774337d3f6e2b0d063097a5028e48375 + languageName: node + linkType: hard + +"magic-string@npm:0.30.17": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 10/2f71af2b0afd78c2e9012a29b066d2c8ba45a9cd0c8070f7fd72de982fb1c403b4e3afdb1dae00691d56885ede66b772ef6bedf765e02e3a7066208fe2fec4aa + languageName: node + linkType: hard + +"mailparser@npm:^3.7.1, mailparser@npm:^3.7.3": + version: 3.9.1 + resolution: "mailparser@npm:3.9.1" + dependencies: + "@zone-eu/mailsplit": "npm:5.4.8" + encoding-japanese: "npm:2.2.0" + he: "npm:1.2.0" + html-to-text: "npm:9.0.5" + iconv-lite: "npm:0.7.0" + libmime: "npm:5.3.7" + linkify-it: "npm:5.0.0" + nodemailer: "npm:7.0.11" + punycode.js: "npm:2.3.1" + tlds: "npm:1.261.0" + checksum: 10/6b090c6f22834294820d14027a2b319c59fcb83b5ffa0c8346abb2da961daf8ae276ff51412a4d9019c10dba14287f01b96aa2372ee5e9eecffe865bffa37589 + languageName: node + linkType: hard + +"make-dir@npm:^1.3.0": + version: 1.3.0 + resolution: "make-dir@npm:1.3.0" + dependencies: + pify: "npm:^3.0.0" + checksum: 10/c564f6e7bb5ace1c02ad56b3a5f5e07d074af0c0b693c55c7b2c2b148882827c8c2afc7b57e43338a9f90c125b58d604e8cf3e6990a48bf949dfea8c79668c0b + languageName: node + linkType: hard + +"make-error@npm:^1.1.1": + version: 1.3.6 + resolution: "make-error@npm:1.3.6" + checksum: 10/b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" + dependencies: + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^13.0.0" + checksum: 10/78da4fc1df83cb596e2bae25aa0653b8a9c6cbdd6674a104894e03be3acfcd08c70b78f06ef6407fbd6b173f6a60672480d78641e693d05eb71c09c13ee35278 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10/11df2eda46d092a6035479632e1ec865b8134bdfc4bd9e571a656f4191525404f13a283a515938c3a8de934dbfd9c09674d9da9fa831e6eb7e22b50b197d2edd + languageName: node + linkType: hard + +"mathjs@npm:^14.5.1": + version: 14.9.1 + resolution: "mathjs@npm:14.9.1" + dependencies: + "@babel/runtime": "npm:^7.26.10" + complex.js: "npm:^2.2.5" + decimal.js: "npm:^10.4.3" + escape-latex: "npm:^1.2.0" + fraction.js: "npm:^5.2.1" + javascript-natural-sort: "npm:^0.7.1" + seedrandom: "npm:^3.0.5" + tiny-emitter: "npm:^2.1.0" + typed-function: "npm:^4.2.1" + bin: + mathjs: bin/cli.js + checksum: 10/fe99edd5a27ebeb01a44338220016053f7982bcc0195f173a3835d75766d7296c10b1d3deab508abaecc53f2999778a1cd7178b5eac4043538bdee1411aa50b4 + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: 10/38e0984db39139604756903a01397e29e17dcb04207bb3e081412ce725ab17338ecc47220c1b186b6bbe79a658aad1b0d41142884f5a481f36290cdefbe6aa46 + languageName: node + linkType: hard + +"media-typer@npm:^1.1.0": + version: 1.1.0 + resolution: "media-typer@npm:1.1.0" + checksum: 10/a58dd60804df73c672942a7253ccc06815612326dc1c0827984b1a21704466d7cde351394f47649e56cf7415e6ee2e26e000e81b51b3eebb5a93540e8bf93cbd + languageName: node + linkType: hard + +"memfs@npm:^3.4.1": + version: 3.5.3 + resolution: "memfs@npm:3.5.3" + dependencies: + fs-monkey: "npm:^1.0.4" + checksum: 10/7c9cdb453a6b06e87f11e2dbe6c518fd3c1c1581b370ffa24f42f3fd5b1db8c2203f596e43321a0032963f3e9b66400f2c3cf043904ac496d6ae33eafd0878fe + languageName: node + linkType: hard + +"mensch@npm:^0.3.4": + version: 0.3.4 + resolution: "mensch@npm:0.3.4" + checksum: 10/b6178a81a8059e0bf7ed62651aaab3cb1cfc68da41e0a9681cffd06083bd8d14c0e79b7493fc7efcf63c112c08efd584371208efc93cbae12d613dbc297a1d03 + languageName: node + linkType: hard + +"merge-descriptors@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-descriptors@npm:2.0.0" + checksum: 10/e383332e700a94682d0125a36c8be761142a1320fc9feeb18e6e36647c9edf064271645f5669b2c21cf352116e561914fd8aa831b651f34db15ef4038c86696a + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10/6fa4dcc8d86629705cea944a4b88ef4cb0e07656ebf223fa287443256414283dd25d91c1cd84c77987f2aec5927af1a9db6085757cb43d90eb170ebf4b47f4f4 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 10/7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.8": + version: 4.0.8 + resolution: "micromatch@npm:4.0.8" + dependencies: + braces: "npm:^3.0.3" + picomatch: "npm:^2.3.1" + checksum: 10/6bf2a01672e7965eb9941d1f02044fad2bd12486b5553dc1116ff24c09a8723157601dc992e74c911d896175918448762df3b3fd0a6b61037dd1a9766ddfbf58 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10/54bb60bf39e6f8689f6622784e668a3d7f8bed6b0d886f5c3c446cb3284be28b30bf707ed05d0fe44a036f8469976b2629bbea182684977b084de9da274694d7 + languageName: node + linkType: hard + +"mime-db@npm:^1.28.0, mime-db@npm:^1.54.0": + version: 1.54.0 + resolution: "mime-db@npm:1.54.0" + checksum: 10/9e7834be3d66ae7f10eaa69215732c6d389692b194f876198dca79b2b90cbf96688d9d5d05ef7987b20f749b769b11c01766564264ea5f919c88b32a29011311 + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10/89aa9651b67644035de2784a6e665fc685d79aba61857e02b9c8758da874a754aed4a9aced9265f5ed1171fd934331e5516b84a7f0218031b6fa0270eca1e51a + languageName: node + linkType: hard + +"mime-types@npm:^3.0.0, mime-types@npm:^3.0.1, mime-types@npm:^3.0.2": + version: 3.0.2 + resolution: "mime-types@npm:3.0.2" + dependencies: + mime-db: "npm:^1.54.0" + checksum: 10/9db0ad31f5eff10ee8f848130779b7f2d056ddfdb6bda696cb69be68d486d33a3457b4f3f9bdeb60d0736edb471bd5a7c0a384375c011c51c889fd0d5c3b893e + languageName: node + linkType: hard + +"mime@npm:^2.4.6": + version: 2.6.0 + resolution: "mime@npm:2.6.0" + bin: + mime: cli.js + checksum: 10/7da117808b5cd0203bb1b5e33445c330fe213f4d8ee2402a84d62adbde9716ca4fb90dd6d9ab4e77a4128c6c5c24a9c4c9f6a4d720b095b1b342132d02dba58d + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10/d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a + languageName: node + linkType: hard + +"mimic-response@npm:^1.0.0": + version: 1.0.1 + resolution: "mimic-response@npm:1.0.1" + checksum: 10/034c78753b0e622bc03c983663b1cdf66d03861050e0c8606563d149bc2b02d63f62ce4d32be4ab50d0553ae0ffe647fc34d1f5281184c6e1e8cf4d85e8d9823 + languageName: node + linkType: hard + +"mimic-response@npm:^3.1.0": + version: 3.1.0 + resolution: "mimic-response@npm:3.1.0" + checksum: 10/7e719047612411fe071332a7498cf0448bbe43c485c0d780046c76633a771b223ff49bd00267be122cedebb897037fdb527df72335d0d0f74724604ca70b37ad + languageName: node + linkType: hard + +"mimic-response@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-response@npm:4.0.0" + checksum: 10/33b804cc961efe206efdb1fca6a22540decdcfce6c14eb5c0c50e5ae9022267ab22ce8f5568b1f7247ba67500fe20d523d81e0e9f009b321ccd9d472e78d1850 + languageName: node + linkType: hard + +"minimalistic-assert@npm:^1.0.1": + version: 1.0.1 + resolution: "minimalistic-assert@npm:1.0.1" + checksum: 10/cc7974a9268fbf130fb055aff76700d7e2d8be5f761fb5c60318d0ed010d839ab3661a533ad29a5d37653133385204c503bfac995aaa4236f4e847461ea32ba7 + languageName: node + linkType: hard + +"minimatch@npm:9.0.1": + version: 9.0.1 + resolution: "minimatch@npm:9.0.1" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/b4e98f4dc740dcf33999a99af23ae6e5e1c47632f296dc95cb649a282150f92378d41434bf64af4ea2e5975255a757d031c3bf014bad9214544ac57d97f3ba63 + languageName: node + linkType: hard + +"minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10/110f38921ea527022e90f7a5f43721838ac740d0a0c26881c03b57c261354fb9a0430e40b2c56dfcea2ef3c773768f27210d1106f1f2be19cde3eea93f26f45e + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10/e0b25b04cd4ec6732830344e5739b13f8690f8a012d73445a4a19fbc623f5dd481ef7a5827fde25954cd6026fede7574cc54dc4643c99d6c6b653d6203f94634 + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/126b36485b821daf96d33b5c821dac600cc1ab36c87e7a532594f9b1652b1fa89a1eebcaad4dff17c764dce1a7ac1531327f190fed5f97d8f6e5f889c116c429 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.1, minimatch@npm:^9.0.3, minimatch@npm:^9.0.4, minimatch@npm:^9.0.5": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10/dd6a8927b063aca6d910b119e1f2df6d2ce7d36eab91de83167dd136bb85e1ebff97b0d3de1cb08bd1f7e018ca170b4962479fefab5b2a69e2ae12cb2edc8348 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.5, minimist@npm:^1.2.6, minimist@npm:^1.2.8": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10/908491b6cc15a6c440ba5b22780a0ba89b9810e1aea684e253e43c4e3b8d56ec1dcdd7ea96dde119c29df59c936cde16062159eae4225c691e19c70b432b6e6f + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10/b251bceea62090f67a6cced7a446a36f4cd61ee2d5cea9aee7fff79ba8030e416327a1c5aa2908dc22629d06214b46d88fdab8c51ac76bacbf5703851b5ad342 + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass-fetch@npm:5.0.0" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10/4fb7dca630a64e6970a8211dade505bfe260d0b8d60beb348dcdfb95fe35ef91d977b29963929c9017ae0805686aa3f413107dc6bc5deac9b9e26b0b41c3b86c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10/56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10/b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10/40982d8d836a52b0f37049a0a7e5d0f089637298e6d9b45df9c115d4f0520682a78258905e5c8b180fb41b593b0a82cc1361d2c74b45f7ada66334f84d1ecfdd + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10/a5c6ef069f70d9a524d3428af39f2b117ff8cd84172e19b754e7264a33df460873e6eb3d6e55758531580970de50ae950c496256bb4ad3691a2974cddff189f0 + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10/c25f0ee8196d8e6036661104bacd743785b2599a21de5c516b32b3fa2b83113ac89a2358465bc04956baab37ffb956ae43be679b2262bf7be15fce467ccd7950 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10/f47365cc2cb7f078cbe7e046eb52655e2e7e97f8c0a9a674f4da60d94fb0624edfcec9b5db32e8ba5a99a5f036f595680ae6fe02a262beaa73026e505cc52f99 + languageName: node + linkType: hard + +"mjml-accordion@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-accordion@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/94e548f63abab0574c4a9940959fc71732edfac7dc62aa3065ab87b8ec32b81f3b283cd3b1e17a191449d10600e7b87fc3f65aded4a4737113577e4f26740cf2 + languageName: node + linkType: hard + +"mjml-body@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-body@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/141d23937aceebeec89a13c3c59fa2ba1563ecaa071cc119204aee40f50502f382a2a49801a4aaf4acc430e7ebf2fcc69af4ae93a69730861f9a4c321f4efa9b + languageName: node + linkType: hard + +"mjml-button@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-button@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/0fd36c33207d2c3e94b5ec2a5646abeae680db0521aed495c55859fe31cc8aaeca9524b0ef3630ee1ff2eb6ddcb135f1e5a38a2fdf27be94d2c0d7eae5069a42 + languageName: node + linkType: hard + +"mjml-carousel@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-carousel@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/9347e7860cd7f5ad5969dded9aae3fe513865fb5ae2d3ee717a77989e622449394aa4e2f360ff148941f2f622c8caebdfceb86b5441c347ece06e6ec8cbabfa3 + languageName: node + linkType: hard + +"mjml-cli@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-cli@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + chokidar: "npm:^3.0.0" + glob: "npm:^10.3.10" + html-minifier: "npm:^4.0.0" + js-beautify: "npm:^1.6.14" + lodash: "npm:^4.17.21" + minimatch: "npm:^9.0.3" + mjml-core: "npm:4.18.0" + mjml-migrate: "npm:4.18.0" + mjml-parser-xml: "npm:4.18.0" + mjml-validator: "npm:4.18.0" + yargs: "npm:^17.7.2" + bin: + mjml-cli: bin/mjml + checksum: 10/4f9029dc15debc32b71bb5b6195762a439acb4ac04d6ff36dab57a6c062ebfe91bc84c8bebd9b954be5d9469d60983f88156a7d653cf483876986dadf9644d18 + languageName: node + linkType: hard + +"mjml-column@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-column@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/7e246b9890f9cd07a6c89ff202eb8e500186e74daf226e606a0fec01b8d6c7fcabf2ad0dfdfa8bd134accd2190ff190c2185281611a37519f193fd354b6e1873 + languageName: node + linkType: hard + +"mjml-core@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-core@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + cheerio: "npm:1.0.0-rc.12" + detect-node: "npm:^2.0.4" + html-minifier: "npm:^4.0.0" + js-beautify: "npm:^1.6.14" + juice: "npm:^10.0.0" + lodash: "npm:^4.17.21" + mjml-migrate: "npm:4.18.0" + mjml-parser-xml: "npm:4.18.0" + mjml-validator: "npm:4.18.0" + checksum: 10/f0e848f00baeebf14e4a479d3aed942eb687ebacdbaf7b08e2b93e2367f2b1d2149224c9f0815d708f3597827fcb5fd35605b448a96ad176fd8d8ab1f982d60d + languageName: node + linkType: hard + +"mjml-divider@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-divider@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/148d8cc56e8a980a66159cfcbe3c0364a2e3faf87060b37bd44867ffc32367f7286de84ce7edd315b9add53171414a0296456b3fc0088e01adc3decfeec69780 + languageName: node + linkType: hard + +"mjml-group@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-group@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/5346ac84aa69b4232d278968db0a1e739c1b2c7d03e700db5e0d1a8130249ed343fa2c7db58798538082422324091c3d2aee738e923b82d8855c435d99814b81 + languageName: node + linkType: hard + +"mjml-head-attributes@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head-attributes@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/b153bb54bc6499971849d26dc307dae09aa34e79db01f49e302158e31a6d841de195fdee6666de992e0b401b822291a552ea334cb5a3ca7824df68cb601acdb8 + languageName: node + linkType: hard + +"mjml-head-breakpoint@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head-breakpoint@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/8831ddcffd9c3afba11709b9fe76581f42609d47f713b8008dde527ee3a8b2b2ce45d7848c56b1a2816f0df91b148d84a6ddb0eebd0113d2a4d1f71ade54c23e + languageName: node + linkType: hard + +"mjml-head-font@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head-font@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/2435b44c803f357d144509e226fa71d8575e22d5307aff97459c9152b8be7b9989c8c6641200b32f7199c1b43dc982ef97972a8c05549d9b6b10091f54d7cb3b + languageName: node + linkType: hard + +"mjml-head-html-attributes@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head-html-attributes@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/4e18cc47c2af2b55b3d1d2a1826a13cc996b3c36bff8f34ba3f70bc4b31cfa90b77d2de89cf48f9073da1f7cd3a93b3b219516146b78badc43d1932f963b8cc0 + languageName: node + linkType: hard + +"mjml-head-preview@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head-preview@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/ba1dd6eefe79387acb1c33be5170dbd4a18a499d005bfa81565763153f8bcd23d361d61b221de8951f8408fc01ce9710a1211e090db1d6f18f8b9ac762f73873 + languageName: node + linkType: hard + +"mjml-head-style@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head-style@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/0a3807072e0c7a32db8372cfeb207d12213ac60e03fd9a569b3b08f5c2783b3c7d99671c4266a27a5146df05375213e1b2333ccae7833d5ff12afa8a9d2a88b6 + languageName: node + linkType: hard + +"mjml-head-title@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head-title@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/a7ef5d0f444b14303f12d17af97282122917e62a260e01e6f3957dfef7de563cdd20b27bbbecee7f99de47ae109233f8c723db17043f1224c35e5a0326792cdc + languageName: node + linkType: hard + +"mjml-head@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-head@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/16c56f89cc9b9dfc00c99a4cd7c1411ea9a4b869119ab7c4280e97a2df2aecb509d07a5c220e5f621cf8dd9935843222bb76bede9f636830a4a896ee3912ec1c + languageName: node + linkType: hard + +"mjml-hero@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-hero@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/70082908fb1c7d51a198b96532e92041d64dac50cf96c8aabe90c5f47cf9f211c3535e270f3d0693faeb49824bd413b422e321f0ac76e95db8966368cb0cf2dc + languageName: node + linkType: hard + +"mjml-image@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-image@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/431454c2cababe5743475bd470367a9912366f5da6307efc871de14f0aea7f75829f6b0ee775664fedd4d3719c2698527cda0aacd9ede0c97737bceef8bd3349 + languageName: node + linkType: hard + +"mjml-migrate@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-migrate@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + js-beautify: "npm:^1.6.14" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + mjml-parser-xml: "npm:4.18.0" + yargs: "npm:^17.7.2" + bin: + migrate: lib/cli.js + checksum: 10/7604317170d7e5f7dd59c2a33a969a21372abc532fa7a5f3c599cc1c729bda6f409cea343a1292fa288703f4386a221854127b9f6050ad25e5637e6484362167 + languageName: node + linkType: hard + +"mjml-navbar@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-navbar@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/26e0d84254ba80945973e55bf0172dac88841ac1e3e2ed5e05ef75e0a283ede219cab5b17668ecc28a7fbaa97e693f4cd9a756faeb720dae1f4472daaf54f682 + languageName: node + linkType: hard + +"mjml-parser-xml@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-parser-xml@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + detect-node: "npm:2.1.0" + htmlparser2: "npm:^9.1.0" + lodash: "npm:^4.17.21" + checksum: 10/1ef8041f66345532f0de326692b4d5869ffd16190bd156ac5d341452442c74b08e930e4aa050629a97c28e1354e55ec8def684f9cc818d30a612bf4e9a5b6e07 + languageName: node + linkType: hard + +"mjml-preset-core@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-preset-core@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + mjml-accordion: "npm:4.18.0" + mjml-body: "npm:4.18.0" + mjml-button: "npm:4.18.0" + mjml-carousel: "npm:4.18.0" + mjml-column: "npm:4.18.0" + mjml-divider: "npm:4.18.0" + mjml-group: "npm:4.18.0" + mjml-head: "npm:4.18.0" + mjml-head-attributes: "npm:4.18.0" + mjml-head-breakpoint: "npm:4.18.0" + mjml-head-font: "npm:4.18.0" + mjml-head-html-attributes: "npm:4.18.0" + mjml-head-preview: "npm:4.18.0" + mjml-head-style: "npm:4.18.0" + mjml-head-title: "npm:4.18.0" + mjml-hero: "npm:4.18.0" + mjml-image: "npm:4.18.0" + mjml-navbar: "npm:4.18.0" + mjml-raw: "npm:4.18.0" + mjml-section: "npm:4.18.0" + mjml-social: "npm:4.18.0" + mjml-spacer: "npm:4.18.0" + mjml-table: "npm:4.18.0" + mjml-text: "npm:4.18.0" + mjml-wrapper: "npm:4.18.0" + checksum: 10/84e996c6388707de581bdc63b881e9b1e3233c68ad7c172f5eed3f568663c7aa15a184f3ac6dd6f41f62d5b157846775a64ef48cdb4370f8f5f10216d2e40df7 + languageName: node + linkType: hard + +"mjml-raw@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-raw@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/b2b1d8db92a5a3c1336b5558d3876bafa0671fbc2ca6321c44b27bc280620f0140e95943f18f33a96ea5291738e26158339be7d68adaf4df15b5a7a86a73ff90 + languageName: node + linkType: hard + +"mjml-section@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-section@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/3da4864a64e9e2dbc7737cbd26a012ef22ada309192915aeec6cf0ada41ee7a8749f73fccb79ec1e615a49104295340c43136d8f6817aa8187c263b86d6988d1 + languageName: node + linkType: hard + +"mjml-social@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-social@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/f520f7a6a15da5649c6a42de1fe9b736ff057b03d16043c4b945b5f45ed506d1735642981868de0464c37b641df2b70dac51c8077af8e993ab3f26c56b69ac6e + languageName: node + linkType: hard + +"mjml-spacer@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-spacer@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/4aa0766e78fc76be521d92bc5307d37c8c9518da06a515251bc2de4cc5742e53c16d913480d8339183f6d525d84ed0b9b28cd8a407eeda03614cb040269bc241 + languageName: node + linkType: hard + +"mjml-table@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-table@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/e242fbe7b4d17357ddc805baea135300928583bcdd57c9e5688fd2fd2b04c473f8f146ecea85a2e4e83efdfc157f4eba365f0cc992896211e0ec3c966fef5ee2 + languageName: node + linkType: hard + +"mjml-text@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-text@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + checksum: 10/6b5d3232c54bb240fefbd7841ea625dccede2e2bf8c0c39a6d15445c06e8b116f744ece7da3fea8b1df68ae5959d3f7176d966e7a8df0b9e5bc254aa40c1f93d + languageName: node + linkType: hard + +"mjml-validator@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-validator@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + checksum: 10/ceffc44afd9510a59090ff02a216052559f71c046402737712d3f616d034f0edc713c48db88fc36c092e3bc4d8da42f284e823546eb1f464626be2d88933d99f + languageName: node + linkType: hard + +"mjml-wrapper@npm:4.18.0": + version: 4.18.0 + resolution: "mjml-wrapper@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + lodash: "npm:^4.17.21" + mjml-core: "npm:4.18.0" + mjml-section: "npm:4.18.0" + checksum: 10/6745b43a742a82bc2e25e06814be41788c5d0dfeb1e8d8bd216997ec67bbec1b4534c20c3d737ae48edfb4cf8b41348ff079b47028be572e78e3534053b89b39 + languageName: node + linkType: hard + +"mjml@npm:^4.15.3": + version: 4.18.0 + resolution: "mjml@npm:4.18.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + mjml-cli: "npm:4.18.0" + mjml-core: "npm:4.18.0" + mjml-migrate: "npm:4.18.0" + mjml-preset-core: "npm:4.18.0" + mjml-validator: "npm:4.18.0" + bin: + mjml: bin/mjml + checksum: 10/2be22b2cfdf152b248d8509f09a1b71ec46e793511798badba979db9d4ac9ac9f98940a759f587412c0f9658fc0cb8908b18bb2c3a9e9fc74b783e0595725a0a + languageName: node + linkType: hard + +"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 10/3f4e088208270bbcc148d53b73e9a5bd9eef05ad2cbf3b3d0ff8795278d50dd1d11a8ef1875ff5aea3fa888931f95bfcb2ad5b7c1061cfefd6284d199e6776ac + languageName: node + linkType: hard + +"mkdirp@npm:>=0.5 0, mkdirp@npm:^0.5.6": + version: 0.5.6 + resolution: "mkdirp@npm:0.5.6" + dependencies: + minimist: "npm:^1.2.6" + bin: + mkdirp: bin/cmd.js + checksum: 10/0c91b721bb12c3f9af4b77ebf73604baf350e64d80df91754dc509491ae93bf238581e59c7188360cec7cb62fc4100959245a42cfe01834efedc5e9d068376c2 + languageName: node + linkType: hard + +"module-details-from-path@npm:^1.0.3": + version: 1.0.4 + resolution: "module-details-from-path@npm:1.0.4" + checksum: 10/2ebfada5358492f6ab496b70f70a1042f2ee7a4c79d29467f59ed6704f741fb4461d7cecb5082144ed39a05fec4d19e9ff38b731c76228151be97227240a05b2 + languageName: node + linkType: hard + +"ms@npm:^2.1.1, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + +"multer@npm:2.0.2, multer@npm:^2.0.0": + version: 2.0.2 + resolution: "multer@npm:2.0.2" + dependencies: + append-field: "npm:^1.0.0" + busboy: "npm:^1.6.0" + concat-stream: "npm:^2.0.0" + mkdirp: "npm:^0.5.6" + object-assign: "npm:^4.1.1" + type-is: "npm:^1.6.18" + xtend: "npm:^4.0.2" + checksum: 10/4bdcb07138cf72f93adc08a0dc27c058faab9f6721067a58f394fa546d73d11b7f100cdd66e733d649d184f9d1b402065f6888b31ec427409f056ee92c4367a6 + languageName: node + linkType: hard + +"mute-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "mute-stream@npm:2.0.0" + checksum: 10/d2e4fd2f5aa342b89b98134a8d899d8ef9b0a6d69274c4af9df46faa2d97aeb1f2ce83d867880d6de63643c52386579b99139801e24e7526c3b9b0a6d1e18d6c + languageName: node + linkType: hard + +"nan@npm:^2.13.2, nan@npm:^2.22.2": + version: 2.24.0 + resolution: "nan@npm:2.24.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/479f6960119b5ef9b488c14e9069eb534c3545d50b621f51b247d1e3b40828ee619c4d9f8efe30786c5b18c21c60b3cda3f0d0b92e9a3a26cb3e4ab5492a7032 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10/23ad088b08f898fc9b53011d7bb78ec48e79de7627e01ab5518e806033861bef68d5b0cd0e2205c2f36690ac9571ff6bcb05eb777ced2eeda8d4ac5b44592c3d + languageName: node + linkType: hard + +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 10/2723fb822a17ad55c93a588a4bc44d53b22855bf4be5499916ca0cab1e7165409d0b288ba2577d7b029f10ce18cf2ed8e703e5af31c984e1e2304277ef979837 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10/b5734e87295324fabf868e36fb97c84b7d7f3156ec5f4ee5bf6e488079c11054f818290fc33804cef7b1ee21f55eeb14caea83e7dafae6492a409b3e573153e5 + languageName: node + linkType: hard + +"neo-async@npm:^2.6.2": + version: 2.6.2 + resolution: "neo-async@npm:2.6.2" + checksum: 10/1a7948fea86f2b33ec766bc899c88796a51ba76a4afc9026764aedc6e7cde692a09067031e4a1bf6db4f978ccd99e7f5b6c03fe47ad9865c3d4f99050d67e002 + languageName: node + linkType: hard + +"nest-winston@npm:^1.10.2": + version: 1.10.2 + resolution: "nest-winston@npm:1.10.2" + dependencies: + fast-safe-stringify: "npm:^2.1.1" + peerDependencies: + "@nestjs/common": ^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 + winston: ^3.0.0 + checksum: 10/b3f8a16b8cafb6acd7d8a0e68b3434f4ac693d2efa050df0b3d633467510ceef2b2971024ac7d9fba631d0eaa24d38a97bd4098cdbd2e282a7fd3b80abf00098 + languageName: node + linkType: hard + +"newrelic@npm:^12.20.0": + version: 12.25.0 + resolution: "newrelic@npm:12.25.0" + dependencies: + "@grpc/grpc-js": "npm:^1.13.2" + "@grpc/proto-loader": "npm:^0.7.5" + "@newrelic/fn-inspect": "npm:^4.4.0" + "@newrelic/native-metrics": "npm:^11.1.0" + "@newrelic/security-agent": "npm:^2.4.2" + "@opentelemetry/api": "npm:^1.9.0" + "@opentelemetry/core": "npm:^2.0.0" + "@opentelemetry/exporter-metrics-otlp-proto": "npm:^0.201.1" + "@opentelemetry/resources": "npm:^2.0.1" + "@opentelemetry/sdk-metrics": "npm:^2.0.1" + "@opentelemetry/sdk-trace-base": "npm:^2.0.0" + "@prisma/prisma-fmt-wasm": "npm:^4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085" + "@tyriar/fibonacci-heap": "npm:^2.0.7" + concat-stream: "npm:^2.0.0" + https-proxy-agent: "npm:^7.0.1" + import-in-the-middle: "npm:^1.13.0" + json-bigint: "npm:^1.0.0" + json-stringify-safe: "npm:^5.0.0" + module-details-from-path: "npm:^1.0.3" + readable-stream: "npm:^3.6.1" + require-in-the-middle: "npm:^7.4.0" + semver: "npm:^7.5.2" + winston-transport: "npm:^4.5.0" + dependenciesMeta: + "@newrelic/fn-inspect": + optional: true + "@newrelic/native-metrics": + optional: true + "@prisma/prisma-fmt-wasm": + optional: true + bin: + newrelic-naming-rules: bin/test-naming-rules.js + checksum: 10/7c1e8fe093dce405a60d08176277aa2003fec86136bb9a65af54084669497d26fc50b09bdf0e041e1a59c70debfe0f777a49cfd78eb4ab214eb8b88bba22e25d + languageName: node + linkType: hard + +"nice-try@npm:^1.0.4": + version: 1.0.5 + resolution: "nice-try@npm:1.0.5" + checksum: 10/0b4af3b5bb5d86c289f7a026303d192a7eb4417231fe47245c460baeabae7277bcd8fd9c728fb6bd62c30b3e15cd6620373e2cf33353b095d8b403d3e8a15aff + languageName: node + linkType: hard + +"no-case@npm:^2.2.0": + version: 2.3.2 + resolution: "no-case@npm:2.3.2" + dependencies: + lower-case: "npm:^1.1.1" + checksum: 10/a92fc7c10f40477bb69c3ca00e2a12fd08f838204bcef66233cbe8a36c0ec7938ba0cdf3f0534b38702376cbfa26270130607c0b8460ea87f44d474919c39c91 + languageName: node + linkType: hard + +"node-abi@npm:^3.3.0": + version: 3.85.0 + resolution: "node-abi@npm:3.85.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/1d0fb9e7922431663db700aa7087c0e49bae5fd041efe2d7463d808e58be1594af8046e835fe1b17ca69f214b6327e89fb0dd5192f0e7a9bf624f5509a9104b7 + languageName: node + linkType: hard + +"node-abort-controller@npm:^3.0.1": + version: 3.1.1 + resolution: "node-abort-controller@npm:3.1.1" + checksum: 10/0a2cdb7ec0aeaf3cb31e1ca0e192f5add48f1c5c9c9ed822129f9dddbd9432f69b7425982f94ce803c56a2104884530aa67cd57696e5774b2e5b8ec2f58de042 + languageName: node + linkType: hard + +"node-addon-api@npm:^8.3.0": + version: 8.5.0 + resolution: "node-addon-api@npm:8.5.0" + dependencies: + node-gyp: "npm:latest" + checksum: 10/9a893f4f835fbc3908e0070f7bcacf36e37fd06be8008409b104c30df4092a0d9a29927b3a74cdbc1d34338274ba4116d597a41f573e06c29538a1a70d07413f + languageName: node + linkType: hard + +"node-emoji@npm:1.11.0": + version: 1.11.0 + resolution: "node-emoji@npm:1.11.0" + dependencies: + lodash: "npm:^4.17.21" + checksum: 10/1d7ae9bcb0f23d7cdfcac5c3a90a6fd6ec584e6f7c70ff073f6122bfbed6c06284da7334092500d24e14162f5c4016e5dcd3355753cbd5b7e60de560a973248d + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.9": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10/b24f8a3dc937f388192e59bcf9d0857d7b6940a2496f328381641cb616efccc9866e89ec43f2ec956bbd6c3d3ee05524ce77fe7b29ccd34692b3a16f237d6676 + languageName: node + linkType: hard + +"node-forge@npm:^1.2.1": + version: 1.3.3 + resolution: "node-forge@npm:1.3.3" + checksum: 10/f41c31b9296771a4b8c955d58417471712f54f324603a35f8e6cbac19d5e6eaaf5fd5fd14584dfedecbf46a05438ded6eee60a5f2f0822fc5061aaa073cfc75d + languageName: node + linkType: hard + +"node-gyp-build@npm:^4.8.1, node-gyp-build@npm:^4.8.4": + version: 4.8.4 + resolution: "node-gyp-build@npm:4.8.4" + bin: + node-gyp-build: bin.js + node-gyp-build-optional: optional.js + node-gyp-build-test: build-test.js + checksum: 10/6a7d62289d1afc419fc8fc9bd00aa4e554369e50ca0acbc215cb91446148b75ff7e2a3b53c2c5b2c09a39d416d69f3d3237937860373104b5fe429bf30ad9ac5 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.1.0 + resolution: "node-gyp@npm:12.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.2" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10/d93079236cef1dd7fa4df683708d8708ad255c55865f6656664c8959e4d3963d908ac48e8f9f341705432e979dbbf502a40d68d65a17fe35956a5a05ba6c1cb4 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10/f6c78ddb392ae500719644afcbe68a9ea533242c02312eb6a34e8478506eb7482a3fb709c70235b01c32fe65625b68dfa9665113f816d87f163bc3819b62b106 + languageName: node + linkType: hard + +"nodeify@npm:^1.0.0": + version: 1.0.1 + resolution: "nodeify@npm:1.0.1" + dependencies: + is-promise: "npm:~1.0.0" + promise: "npm:~1.3.0" + checksum: 10/2d61f77a43ba0d580fc9615c5112d891605baa56f13d871a9f9736a92a80d974f77fa9ad3a4698214929216e79f583c18fd17e6363934f73d39cc69319f244de + languageName: node + linkType: hard + +"nodemailer@npm:7.0.11": + version: 7.0.11 + resolution: "nodemailer@npm:7.0.11" + checksum: 10/2ad4dd56a4caf84a83aa6f4378ded26d5ef8a644ca3be09c3b4fb2255d861369e620f29be6c3c97148ac4a50aa5fdff6240b9d60805362bd99ca15f2ea62e8a2 + languageName: node + linkType: hard + +"nodemailer@npm:7.0.12, nodemailer@npm:^7.0.3": + version: 7.0.12 + resolution: "nodemailer@npm:7.0.12" + checksum: 10/31a2713be588fb6849d05698cb355a88902a64e17e63b1a5c8a10db3acc4349c329c3bbd83bc5bdefda92f8df5e0297364502f93042c0bae87c48e5652b663b7 + languageName: node + linkType: hard + +"nodemailer@npm:^6.9.13": + version: 6.10.1 + resolution: "nodemailer@npm:6.10.1" + checksum: 10/d9911701641e06143a2deb0bd5deb518310972316c6e6eabc594af24353b0d67867f26cb8d72b0cfa385abef945149ac51ae40a40d2199e1088aef5829e58a3d + languageName: node + linkType: hard + +"nopt@npm:^7.2.1": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" + dependencies: + abbrev: "npm:^2.0.0" + bin: + nopt: bin/nopt.js + checksum: 10/95a1f6dec8a81cd18cdc2fed93e6f0b4e02cf6bdb4501c848752c6e34f9883d9942f036a5e3b21a699047d8a448562d891e67492df68ec9c373e6198133337ae + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10/56a1ccd2ad711fb5115918e2c96828703cddbe12ba2c3bd00591758f6fa30e6f47dd905c59dbfcf9b773f3a293b45996609fb6789ae29d6bfcc3cf3a6f7d9fda + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 10/88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20 + languageName: node + linkType: hard + +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 10/5ae699402c9d5ffa330adc348fcd6fc6e6a155ab7c811b96e30b7ecab60ceef821d8f86443869671dda71bbc47f4b9625739c82ad247e883e9aefe875bfb8659 + languageName: node + linkType: hard + +"normalize-url@npm:^8.0.0": + version: 8.1.1 + resolution: "normalize-url@npm:8.1.1" + checksum: 10/a96519b53692f33d5de19a8aa04fe9de4b8de1c126da89f1c47a24e48d457008867193cd9c19218810426ffabe190034249e1b82f0751c8992f2a9f716304ce0 + languageName: node + linkType: hard + +"npm-run-path@npm:^2.0.0": + version: 2.0.2 + resolution: "npm-run-path@npm:2.0.2" + dependencies: + path-key: "npm:^2.0.0" + checksum: 10/acd5ad81648ba4588ba5a8effb1d98d2b339d31be16826a118d50f182a134ac523172101b82eab1d01cb4c2ba358e857d54cfafd8163a1ffe7bd52100b741125 + languageName: node + linkType: hard + +"npm-run-path@npm:^3.1.0": + version: 3.1.0 + resolution: "npm-run-path@npm:3.1.0" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10/141e0b8f0e3b137347a2896572c9a84701754dda0670d3ceb8c56a87702ee03c26227e4517ab93f2904acfc836547315e740b8289bb24ca0cd8ba2b198043b0f + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10/5374c0cea4b0bbfdfae62da7bbdf1e1558d338335f4cacf2515c282ff358ff27b2ecb91ffa5330a8b14390ac66a1e146e10700440c1ab868208430f56b5f4d23 + languageName: node + linkType: hard + +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: "npm:^1.0.0" + checksum: 10/5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3 + languageName: node + linkType: hard + +"number-to-words-ru@npm:^2.4.1": + version: 2.4.1 + resolution: "number-to-words-ru@npm:2.4.1" + dependencies: + "@ungap/structured-clone": "npm:^1.2.0" + checksum: 10/8a0d20e179bc2f8dcb57aa292c7b0079a7c9729a30bd7f2d33f2f2546e3ecdd97744bd4d48d28a70b28ef17caef7645675f7dc9713cb03a9c9c23c4441f87466 + languageName: node + linkType: hard + +"object-assign@npm:^4, object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10/fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f + languageName: node + linkType: hard + +"object-hash@npm:3.0.0": + version: 3.0.0 + resolution: "object-hash@npm:3.0.0" + checksum: 10/f498d456a20512ba7be500cef4cf7b3c183cc72c65372a549c9a0e6dd78ce26f375e9b1315c07592d3fde8f10d5019986eba35970570d477ed9a2a702514432a + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10/aa13b1190ad3e366f6c83ad8a16ed37a19ed57d267385aa4bfdccda833d7b90465c057ff6c55d035a6b2e52c1a2295582b294217a0a3a1ae7abdd6877ef781fb + languageName: node + linkType: hard + +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.2 + resolution: "on-exit-leak-free@npm:2.1.2" + checksum: 10/f7b4b7200026a08f6e4a17ba6d72e6c5cbb41789ed9cf7deaf9d9e322872c7dc5a7898549a894651ee0ee9ae635d34a678115bf8acdfba8ebd2ba2af688b563c + languageName: node + linkType: hard + +"on-finished@npm:^2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 10/8e81472c5028125c8c39044ac4ab8ba51a7cdc19a9fbd4710f5d524a74c6d8c9ded4dd0eed83f28d3d33ac1d7a6a439ba948ccb765ac6ce87f30450a26bfe2ea + languageName: node + linkType: hard + +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: "npm:1" + checksum: 10/cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 + languageName: node + linkType: hard + +"one-time@npm:^1.0.0": + version: 1.0.0 + resolution: "one-time@npm:1.0.0" + dependencies: + fn.name: "npm:1.x.x" + checksum: 10/64d0160480eeae4e3b2a6fc0a02f452e05bb0cc8373a4ed56a4fc08c3939dcb91bc20075003ed499655bd16919feb63ca56f86eee7932c5251f7d629b55dfc90 + languageName: node + linkType: hard + +"onetime@npm:^5.1.0, onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10/e9fd0695a01cf226652f0385bf16b7a24153dbbb2039f764c8ba6d2306a8506b0e4ce570de6ad99c7a6eb49520743afdb66edd95ee979c1a342554ed49a9aadd + languageName: node + linkType: hard + +"open@npm:7": + version: 7.4.2 + resolution: "open@npm:7.4.2" + dependencies: + is-docker: "npm:^2.0.0" + is-wsl: "npm:^2.1.1" + checksum: 10/4fc02ed3368dcd5d7247ad3566433ea2695b0713b041ebc0eeb2f0f9e5d4e29fc2068f5cdd500976b3464e77fe8b61662b1b059c73233ccc601fe8b16d6c1cd6 + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10/a8398559c60aef88d7f353a4f98dcdff6090a4e70f874c827302bf1213d9106a1c4d5fcb68dacb1feb3c30a04c4102f41047aa55d4c576b863d6fc876e001af6 + languageName: node + linkType: hard + +"ora@npm:5.4.1": + version: 5.4.1 + resolution: "ora@npm:5.4.1" + dependencies: + bl: "npm:^4.1.0" + chalk: "npm:^4.1.0" + cli-cursor: "npm:^3.1.0" + cli-spinners: "npm:^2.5.0" + is-interactive: "npm:^1.0.0" + is-unicode-supported: "npm:^0.1.0" + log-symbols: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + wcwidth: "npm:^1.0.1" + checksum: 10/8d071828f40090a8e1c6e8f350c6eb065808e9ab2b3e57fa37e0d5ae78cb46dac00117c8f12c3c8b8da2923454afbd8265e08c10b69881170c5b269f451e7fef + languageName: node + linkType: hard + +"oxc-resolver@npm:^11.15.0": + version: 11.16.2 + resolution: "oxc-resolver@npm:11.16.2" + dependencies: + "@oxc-resolver/binding-android-arm-eabi": "npm:11.16.2" + "@oxc-resolver/binding-android-arm64": "npm:11.16.2" + "@oxc-resolver/binding-darwin-arm64": "npm:11.16.2" + "@oxc-resolver/binding-darwin-x64": "npm:11.16.2" + "@oxc-resolver/binding-freebsd-x64": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm-gnueabihf": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm-musleabihf": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-arm64-musl": "npm:11.16.2" + "@oxc-resolver/binding-linux-ppc64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-riscv64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-riscv64-musl": "npm:11.16.2" + "@oxc-resolver/binding-linux-s390x-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-x64-gnu": "npm:11.16.2" + "@oxc-resolver/binding-linux-x64-musl": "npm:11.16.2" + "@oxc-resolver/binding-openharmony-arm64": "npm:11.16.2" + "@oxc-resolver/binding-wasm32-wasi": "npm:11.16.2" + "@oxc-resolver/binding-win32-arm64-msvc": "npm:11.16.2" + "@oxc-resolver/binding-win32-ia32-msvc": "npm:11.16.2" + "@oxc-resolver/binding-win32-x64-msvc": "npm:11.16.2" + dependenciesMeta: + "@oxc-resolver/binding-android-arm-eabi": + optional: true + "@oxc-resolver/binding-android-arm64": + optional: true + "@oxc-resolver/binding-darwin-arm64": + optional: true + "@oxc-resolver/binding-darwin-x64": + optional: true + "@oxc-resolver/binding-freebsd-x64": + optional: true + "@oxc-resolver/binding-linux-arm-gnueabihf": + optional: true + "@oxc-resolver/binding-linux-arm-musleabihf": + optional: true + "@oxc-resolver/binding-linux-arm64-gnu": + optional: true + "@oxc-resolver/binding-linux-arm64-musl": + optional: true + "@oxc-resolver/binding-linux-ppc64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-gnu": + optional: true + "@oxc-resolver/binding-linux-riscv64-musl": + optional: true + "@oxc-resolver/binding-linux-s390x-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-gnu": + optional: true + "@oxc-resolver/binding-linux-x64-musl": + optional: true + "@oxc-resolver/binding-openharmony-arm64": + optional: true + "@oxc-resolver/binding-wasm32-wasi": + optional: true + "@oxc-resolver/binding-win32-arm64-msvc": + optional: true + "@oxc-resolver/binding-win32-ia32-msvc": + optional: true + "@oxc-resolver/binding-win32-x64-msvc": + optional: true + checksum: 10/a410cb2b336c8e4cc6dfd7a4f49f4264281b8e03588b88cef9a78c8ff9537b42dc46f986878a94f4d4ad9db4fff416f31512aa033cf36f8b8800c4d512280bdf + languageName: node + linkType: hard + +"p-cancelable@npm:^2.0.0, p-cancelable@npm:^2.1.1": + version: 2.1.1 + resolution: "p-cancelable@npm:2.1.1" + checksum: 10/7f1b64db17fc54acf359167d62898115dcf2a64bf6b3b038e4faf36fc059e5ed762fb9624df8ed04b25bee8de3ab8d72dea9879a2a960cd12e23c420a4aca6ed + languageName: node + linkType: hard + +"p-cancelable@npm:^3.0.0": + version: 3.0.0 + resolution: "p-cancelable@npm:3.0.0" + checksum: 10/a5eab7cf5ac5de83222a014eccdbfde65ecfb22005ee9bc242041f0b4441e07fac7629432c82f48868aa0f8413fe0df6c6067c16f76bf9217cd8dc651923c93d + languageName: node + linkType: hard + +"p-event@npm:4.2.0": + version: 4.2.0 + resolution: "p-event@npm:4.2.0" + dependencies: + p-timeout: "npm:^3.1.0" + checksum: 10/d03238ff31f5694f11bd7dcc0eae16c35b1ffb8cad4e5263d5422ba0bd6736dbfdb33b72745ecb6b06b98494db80f49f12c14f5e8da1212bf6a424609ad8d885 + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10/93a654c53dc805dd5b5891bab16eb0ea46db8f66c4bfd99336ae929323b1af2b70a8b0654f8f1eae924b2b73d037031366d645f1fd18b3d30cbd15950cc4b1d4 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10/7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10/1623088f36cf1cbca58e9b61c4e62bf0c60a07af5ae1ca99a720837356b5b6c5ba3eb1b2127e47a06865fee59dd0453cad7cc844cda9d5a62ac1a5a51b7c86d3 + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10/ef48c3b2e488f31c693c9fcc0df0ef76518cf6426a495cf9486ebbb0fd7f31aef7f90e96f72e0070c0ff6e3177c9318f644b512e2c29e3feee8d7153fcb6782e + languageName: node + linkType: hard + +"p-retry@npm:^6.0.1": + version: 6.2.1 + resolution: "p-retry@npm:6.2.1" + dependencies: + "@types/retry": "npm:0.12.2" + is-network-error: "npm:^1.0.0" + retry: "npm:^0.13.1" + checksum: 10/7104ef13703b155d70883b0d3654ecc03148407d2711a4516739cf93139e8bec383451e14925e25e3c1ae04dbace3ed53c26dc3853c1e9b9867fcbdde25f4cdc + languageName: node + linkType: hard + +"p-timeout@npm:^3.0.0, p-timeout@npm:^3.1.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 10/3dd0eaa048780a6f23e5855df3dd45c7beacff1f820476c1d0d1bcd6648e3298752ba2c877aa1c92f6453c7dd23faaf13d9f5149fc14c0598a142e2c5e8d649c + languageName: node + linkType: hard + +"p-wait-for@npm:3.2.0": + version: 3.2.0 + resolution: "p-wait-for@npm:3.2.0" + dependencies: + p-timeout: "npm:^3.0.0" + checksum: 10/7f6840e9233d96836a233dee9c5b1c711bfdf76228d0eaea68fa74a79418f5eef6dc1915dadd28d17312a99f6a53daa308b1c18b779bc9c1dabcae3d6b35a086 + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0, package-json-from-dist@npm:^1.0.1": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 10/58ee9538f2f762988433da00e26acc788036914d57c71c246bf0be1b60cdbd77dd60b6a3e1a30465f0b248aeb80079e0b34cb6050b1dfa18c06953bb1cbc7602 + languageName: node + linkType: hard + +"pako@npm:^2.1.0": + version: 2.1.0 + resolution: "pako@npm:2.1.0" + checksum: 10/38a04991d0ec4f4b92794a68b8c92bf7340692c5d980255c92148da96eb3e550df7a86a7128b5ac0c65ecddfe5ef3bbe9c6dab13e1bc315086e759b18f7c1401 + languageName: node + linkType: hard + +"pako@npm:~1.0.2": + version: 1.0.11 + resolution: "pako@npm:1.0.11" + checksum: 10/1ad07210e894472685564c4d39a08717e84c2a68a70d3c1d9e657d32394ef1670e22972a433cbfe48976cb98b154ba06855dcd3fcfba77f60f1777634bec48c0 + languageName: node + linkType: hard + +"param-case@npm:^2.1.1": + version: 2.1.1 + resolution: "param-case@npm:2.1.1" + dependencies: + no-case: "npm:^2.2.0" + checksum: 10/3a63dcb8d8dc7995a612de061afdc7bb6fe7bd0e6db994db8d4cae999ed879859fd24389090e1a0d93f4c9207ebf8c048c870f468a3f4767161753e03cb9ab58 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10/6ba8b255145cae9470cf5551eb74be2d22281587af787a2626683a6c20fbb464978784661478dd2a3f1dad74d1e802d403e1b03c1a31fab310259eec8ac560ff + languageName: node + linkType: hard + +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10/62085b17d64da57f40f6afc2ac1f4d95def18c4323577e1eced571db75d9ab59b297d1d10582920f84b15985cbfc6b6d450ccbf317644cfa176f3ed982ad87e2 + languageName: node + linkType: hard + +"parse5-htmlparser2-tree-adapter@npm:^7.0.0": + version: 7.1.0 + resolution: "parse5-htmlparser2-tree-adapter@npm:7.1.0" + dependencies: + domhandler: "npm:^5.0.3" + parse5: "npm:^7.0.0" + checksum: 10/75910af9137451e9c53e1e0d712f7393f484e89e592b1809ee62ad6cedd61b98daeaa5206ff5d9f06778002c91fac311afedde4880e1916fdb44fa71199dae73 + languageName: node + linkType: hard + +"parse5@npm:^7.0.0": + version: 7.3.0 + resolution: "parse5@npm:7.3.0" + dependencies: + entities: "npm:^6.0.0" + checksum: 10/b0e48be20b820c655b138b86fa6fb3a790de6c891aa2aba536524f8027b4dca4fe538f11a0e5cf2f6f847d120dbb9e4822dcaeb933ff1e10850a2ef0154d1d88 + languageName: node + linkType: hard + +"parseley@npm:^0.12.0": + version: 0.12.1 + resolution: "parseley@npm:0.12.1" + dependencies: + leac: "npm:^0.6.0" + peberminta: "npm:^0.9.0" + checksum: 10/64788dbe1fbbc231e0fef235357823e03ca3d915693b2109ad862293aad5d091e902fd7cf6f54763728e758a228d06497c787a9af0dfdacd6a941bc2dbf2019e + languageName: node + linkType: hard + +"parseurl@npm:^1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 10/407cee8e0a3a4c5cd472559bca8b6a45b82c124e9a4703302326e9ab60fc1081442ada4e02628efef1eb16197ddc7f8822f5a91fd7d7c86b51f530aedb17dfa2 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10/505807199dfb7c50737b057dd8d351b82c033029ab94cb10a657609e00c1bc53b951cfdbccab8de04c5584d5eff31128ce6afd3db79281874a5ef2adbba55ed1 + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 10/060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8 + languageName: node + linkType: hard + +"path-key@npm:^2.0.0, path-key@npm:^2.0.1": + version: 2.0.1 + resolution: "path-key@npm:2.0.1" + checksum: 10/6e654864e34386a2a8e6bf72cf664dcabb76574dd54013add770b374384d438aca95f4357bb26935b514a4e4c2c9b19e191f2200b282422a76ee038b9258c5e7 + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10/55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10/49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a + languageName: node + linkType: hard + +"path-scurry@npm:^1.10.2, path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 10/5e8845c159261adda6f09814d7725683257fcc85a18f329880ab4d7cc1d12830967eae5d5894e453f341710d5484b8fdbbd4d75181b4d6e1eb2f4dc7aeadc434 + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10/1e9c74e9ccf94d7c16056a5cb2dba9fa23eec1bc221ab15c44765486b9b9975b4cd9a4d55da15b96eadf67d5202e9a2f1cec9023fbb35fe7d9ccd0ff1891f88b + languageName: node + linkType: hard + +"path-to-regexp@npm:8.3.0, path-to-regexp@npm:^8.0.0, path-to-regexp@npm:^8.2.0": + version: 8.3.0 + resolution: "path-to-regexp@npm:8.3.0" + checksum: 10/568f148fc64f5fd1ecebf44d531383b28df924214eabf5f2570dce9587a228e36c37882805ff02d71c6209b080ea3ee6a4d2b712b5df09741b67f1f3cf91e55a + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10/5b1e2daa247062061325b8fdbfd1fb56dde0a448fb1455453276ea18c60685bdad23a445dc148cf87bc216be1573357509b7d4060494a6fd768c7efad833ee45 + languageName: node + linkType: hard + +"peberminta@npm:^0.9.0": + version: 0.9.0 + resolution: "peberminta@npm:0.9.0" + checksum: 10/b396cf8bac836b3cfe9315e6c94747fa02bb68b252a4b176c0f7d6a3fcbdc5e9f23c3523480c91244b42f87be63f200d775587b039abe4b4731915480da2528d + languageName: node + linkType: hard + +"pend@npm:~1.2.0": + version: 1.2.0 + resolution: "pend@npm:1.2.0" + checksum: 10/6c72f5243303d9c60bd98e6446ba7d30ae29e3d56fdb6fae8767e8ba6386f33ee284c97efe3230a0d0217e2b1723b8ab490b1bbf34fcbb2180dbc8a9de47850d + languageName: node + linkType: hard + +"pg-cloudflare@npm:^1.2.7": + version: 1.2.7 + resolution: "pg-cloudflare@npm:1.2.7" + checksum: 10/3d171407cbce36436c461200666ba6bd884bfe98016972760a797cec850199b8024a40055d80322c5fc02909e1533a144e9b108a99f7d7e21d0c42612f9821fb + languageName: node + linkType: hard + +"pg-connection-string@npm:^2.9.1": + version: 2.9.1 + resolution: "pg-connection-string@npm:2.9.1" + checksum: 10/40e9e9cd752121e72bff18d83e6c7ecda9056426815a84294de018569a319293c924704c8b7f0604fdc588835c7927647dea4f3c87a014e715bcbb17d794e9f0 + languageName: node + linkType: hard + +"pg-int8@npm:1.0.1": + version: 1.0.1 + resolution: "pg-int8@npm:1.0.1" + checksum: 10/a1e3a05a69005ddb73e5f324b6b4e689868a447c5fa280b44cd4d04e6916a344ac289e0b8d2695d66e8e89a7fba023affb9e0e94778770ada5df43f003d664c9 + languageName: node + linkType: hard + +"pg-pool@npm:^3.10.1": + version: 3.10.1 + resolution: "pg-pool@npm:3.10.1" + peerDependencies: + pg: ">=8.0" + checksum: 10/b389a714be59ebe53ec412cbff513191cc0b7a203faa5d26416b6a038cafdfe30fbf1a5936b77bb76109c49bd7c4a116870a5a46a45796b1b34c96f016d7fbe2 + languageName: node + linkType: hard + +"pg-protocol@npm:^1.10.3": + version: 1.10.3 + resolution: "pg-protocol@npm:1.10.3" + checksum: 10/31da85319084c03f403efee7accce9786964df82a7feb60e6bd77b71f1e622c74a2a644a2bc434389d0ab92e5abdeedea69ebdb53b1897d9f01d2a1f51a8a2fe + languageName: node + linkType: hard + +"pg-types@npm:2.2.0": + version: 2.2.0 + resolution: "pg-types@npm:2.2.0" + dependencies: + pg-int8: "npm:1.0.1" + postgres-array: "npm:~2.0.0" + postgres-bytea: "npm:~1.0.0" + postgres-date: "npm:~1.0.4" + postgres-interval: "npm:^1.1.0" + checksum: 10/87a84d4baa91378d3a3da6076c69685eb905d1087bf73525ae1ba84b291b9dd8738c6716b333d8eac6cec91bf087237adc3e9281727365e9cbab0d9d072778b1 + languageName: node + linkType: hard + +"pg@npm:^8.16.0": + version: 8.16.3 + resolution: "pg@npm:8.16.3" + dependencies: + pg-cloudflare: "npm:^1.2.7" + pg-connection-string: "npm:^2.9.1" + pg-pool: "npm:^3.10.1" + pg-protocol: "npm:^1.10.3" + pg-types: "npm:2.2.0" + pgpass: "npm:1.0.5" + peerDependencies: + pg-native: ">=3.0.1" + dependenciesMeta: + pg-cloudflare: + optional: true + peerDependenciesMeta: + pg-native: + optional: true + checksum: 10/6a2885a3f581d6c6dddddf5a4bb2790ee84f402ed7d73ece8b6bc102c58c17e4c5f17894c241633aa2f1d4fedd8f2401a80a9a02ef18bb57d05cbbfd8a53ca4d + languageName: node + linkType: hard + +"pgpass@npm:1.0.5": + version: 1.0.5 + resolution: "pgpass@npm:1.0.5" + dependencies: + split2: "npm:^4.1.0" + checksum: 10/0a6f3bf76e36bdb3c20a7e8033140c732767bba7e81f845f7489fc3123a2bd6e3b8e704f08cba86b117435414b5d2422e20ba9d5f2efb6f0c75c9efca73e8e87 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10/e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + +"picomatch@npm:4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 10/60c2595003b05e4535394d1da94850f5372c9427ca4413b71210f437f7b2ca091dbd611c45e8b37d10036fa8eade25c1b8951654f9d3973bfa66a2ff4d3b08bc + languageName: node + linkType: hard + +"picomatch@npm:^4.0.1, picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10/57b99055f40b16798f2802916d9c17e9744e620a0db136554af01d19598b96e45e2f00014c91d1b8b13874b80caa8c295b3d589a3f72373ec4aaf54baa5962d5 + languageName: node + linkType: hard + +"pify@npm:^3.0.0": + version: 3.0.0 + resolution: "pify@npm:3.0.0" + checksum: 10/668c1dc8d9fc1b34b9ce3b16ba59deb39d4dc743527bf2ed908d2b914cb8ba40aa5ba6960b27c417c241531c5aafd0598feeac2d50cb15278cf9863fa6b02a77 + languageName: node + linkType: hard + +"pino-abstract-transport@npm:^2.0.0": + version: 2.0.0 + resolution: "pino-abstract-transport@npm:2.0.0" + dependencies: + split2: "npm:^4.0.0" + checksum: 10/e5699ecb06c7121055978e988e5cecea5b6892fc2589c64f1f86df5e7386bbbfd2ada268839e911b021c6b3123428aed7c6be3ac7940eee139556c75324c7e83 + languageName: node + linkType: hard + +"pino-std-serializers@npm:^7.0.0": + version: 7.1.0 + resolution: "pino-std-serializers@npm:7.1.0" + checksum: 10/6e27f6f885927b6df3b424ddb8a9e0e9854f3b59f4abd51afa74e1c2cf33436a505277b004bb00ce61884a962c8fdfd977391205c7baab885d6afb35fce7396a + languageName: node + linkType: hard + +"pino@npm:10.1.0": + version: 10.1.0 + resolution: "pino@npm:10.1.0" + dependencies: + "@pinojs/redact": "npm:^0.4.0" + atomic-sleep: "npm:^1.0.0" + on-exit-leak-free: "npm:^2.1.0" + pino-abstract-transport: "npm:^2.0.0" + pino-std-serializers: "npm:^7.0.0" + process-warning: "npm:^5.0.0" + quick-format-unescaped: "npm:^4.0.3" + real-require: "npm:^0.2.0" + safe-stable-stringify: "npm:^2.3.1" + sonic-boom: "npm:^4.0.1" + thread-stream: "npm:^3.0.0" + bin: + pino: bin.js + checksum: 10/6f9c86572669842950887cf1cd45fc977903ce1be0a6e32b98918c87d1914ff8854395dc64388d75a583672439997422975977275ed92c14a2f6ee1a26b07ccf + languageName: node + linkType: hard + +"piscina@npm:^4.3.1": + version: 4.9.2 + resolution: "piscina@npm:4.9.2" + dependencies: + "@napi-rs/nice": "npm:^1.0.1" + dependenciesMeta: + "@napi-rs/nice": + optional: true + checksum: 10/2ae40fc2ba54d230c8372757608458af113ae2b5e66cb4f75bb8ec87f69c1209ab5deec41d471b3649e45571b6c9b04a2aca5d8d3459364bdc69a4dbf9c3d205 + languageName: node + linkType: hard + +"pizzip@npm:^3.2.0": + version: 3.2.0 + resolution: "pizzip@npm:3.2.0" + dependencies: + pako: "npm:^2.1.0" + checksum: 10/55667e01238735ca0ab9777286a533b898e26ad7c62438a485e2a330988b52dca8dff7b8d9675213fa1ef937d5f65478fef84378e6fb9c5877635cbc8c5e07ac + languageName: node + linkType: hard + +"pluralize@npm:8.0.0": + version: 8.0.0 + resolution: "pluralize@npm:8.0.0" + checksum: 10/17877fdfdb7ddb3639ce257ad73a7c51a30a966091e40f56ea9f2f545b5727ce548d4928f8cb3ce38e7dc0c5150407d318af6a4ed0ea5265d378473b4c2c61ec + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10/2f44137b8d3dd35f4a7ba7469eec1cd9cfbb46ec164b93a5bc1f4c3d68599c9910ee3b91da1d28b4560e9cc8414c3cd56fedc07259c67e52cc774476270d3302 + languageName: node + linkType: hard + +"postgres-array@npm:~2.0.0": + version: 2.0.0 + resolution: "postgres-array@npm:2.0.0" + checksum: 10/aff99e79714d1271fe942fec4ffa2007b755e7e7dc3d2feecae3f1ceecb86fd3637c8138037fc3d9e7ec369231eeb136843c0b25927bf1ce295245a40ef849b4 + languageName: node + linkType: hard + +"postgres-bytea@npm:~1.0.0": + version: 1.0.1 + resolution: "postgres-bytea@npm:1.0.1" + checksum: 10/fc5fa49f59ac1f0eba841db55bd6b6c2232d1575d1734311e2097a2d5fd8b58e1239cbd64eeaf0b6752268fe7d2819e002bf90b0afd333be9f2b9d157d2cd7e7 + languageName: node + linkType: hard + +"postgres-date@npm:~1.0.4": + version: 1.0.7 + resolution: "postgres-date@npm:1.0.7" + checksum: 10/571ef45bec4551bb5d608c31b79987d7a895141f7d6c7b82e936a52d23d97474c770c6143e5cf8936c1cdc8b0dfd95e79f8136bf56a90164182a60f242c19f2b + languageName: node + linkType: hard + +"postgres-interval@npm:^1.1.0": + version: 1.2.0 + resolution: "postgres-interval@npm:1.2.0" + dependencies: + xtend: "npm:^4.0.0" + checksum: 10/746b71f93805ae33b03528e429dc624706d1f9b20ee81bf743263efb6a0cd79ae02a642a8a480dbc0f09547b4315ab7df6ce5ec0be77ed700bac42730f5c76b2 + languageName: node + linkType: hard + +"prebuildify@npm:^6.0.1": + version: 6.0.1 + resolution: "prebuildify@npm:6.0.1" + dependencies: + minimist: "npm:^1.2.5" + mkdirp-classic: "npm:^0.5.3" + node-abi: "npm:^3.3.0" + npm-run-path: "npm:^3.1.0" + pump: "npm:^3.0.0" + tar-fs: "npm:^2.1.0" + bin: + prebuildify: bin.js + checksum: 10/3dbec178a5bc41690a8ae9b7bb808637f8aae42fe649cff0b373b502ebd54e11cab12e49dbdbf067f4c21866420f593933ec5b100d31c647dedcfd3ff6653c53 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10/0b9d2c76801ca652a7f64892dd37b7e3fab149a37d2424920099bf894acccc62abb4424af2155ab36dea8744843060a2d8ddc983518d0b1e22265a22324b72ed + languageName: node + linkType: hard + +"prettier-linter-helpers@npm:^1.0.0": + version: 1.0.1 + resolution: "prettier-linter-helpers@npm:1.0.1" + dependencies: + fast-diff: "npm:^1.1.2" + checksum: 10/2dc35f5036a35f4c4f5e645887edda1436acb63687a7f12b2383e0a6f3c1f76b8a0a4709fe4d82e19157210feb5984b159bb714d43290022911ab53d606474ec + languageName: node + linkType: hard + +"prettier@npm:^3.5.3": + version: 3.7.4 + resolution: "prettier@npm:3.7.4" + bin: + prettier: bin/prettier.cjs + checksum: 10/b4d00ea13baed813cb777c444506632fb10faaef52dea526cacd03085f01f6db11fc969ccebedf05bf7d93c3960900994c6adf1b150e28a31afd5cfe7089b313 + languageName: node + linkType: hard + +"pretty-bytes@npm:^5.6.0": + version: 5.6.0 + resolution: "pretty-bytes@npm:5.6.0" + checksum: 10/9c082500d1e93434b5b291bd651662936b8bd6204ec9fa17d563116a192d6d86b98f6d328526b4e8d783c07d5499e2614a807520249692da9ec81564b2f439cd + languageName: node + linkType: hard + +"preview-email@npm:^3.0.19": + version: 3.1.0 + resolution: "preview-email@npm:3.1.0" + dependencies: + ci-info: "npm:^3.8.0" + display-notification: "npm:2.0.0" + fixpack: "npm:^4.0.0" + get-port: "npm:5.1.1" + mailparser: "npm:^3.7.1" + nodemailer: "npm:^6.9.13" + open: "npm:7" + p-event: "npm:4.2.0" + p-wait-for: "npm:3.2.0" + pug: "npm:^3.0.3" + uuid: "npm:^9.0.1" + checksum: 10/7d0f6fce0850848d93ac8961e674a8b5f42871f00b910bceec0adb90eaf6c27ec4f3451ef5cd29b7a4e494e31d16ee2b5c7a7c1f3a476b654e04857d02b2702e + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10/9033f30f168ed5a0991b773d0c50ff88384c4738e9a0a67d341de36bf7293771eed648ab6a0562f62276da12fde91f3bbfc75ffff6e71ad49aafd74fc646be66 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10/1d38588e520dab7cea67cbbe2efdd86a10cc7a074c09657635e34f035277b59fbb57d09d8638346bf7090f8e8ebc070c96fa5fd183b777fff4f5edff5e9466cf + languageName: node + linkType: hard + +"process-warning@npm:^5.0.0": + version: 5.0.0 + resolution: "process-warning@npm:5.0.0" + checksum: 10/10f3e00ac9fc1943ec4566ff41fff2b964e660f853c283e622257719839d340b4616e707d62a02d6aa0038761bb1fa7c56bc7308d602d51bd96f05f9cd305dcd + languageName: node + linkType: hard + +"promise-retry@npm:^1.1.1": + version: 1.1.1 + resolution: "promise-retry@npm:1.1.1" + dependencies: + err-code: "npm:^1.0.0" + retry: "npm:^0.10.0" + checksum: 10/4a30e33b09150608e052a7e6750c63ba3feb30e0049e1c01e3068d5611102c72abc4340bc542295d5efe83110a068ddd3cc1a0d5b057bcf03041f29fae575b52 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10/96e1a82453c6c96eef53a37a1d6134c9f2482f94068f98a59145d0986ca4e497bf110a410adf73857e588165eab3899f0ebcf7b3890c1b3ce802abc0d65967d4 + languageName: node + linkType: hard + +"promise@npm:^7.0.1": + version: 7.3.1 + resolution: "promise@npm:7.3.1" + dependencies: + asap: "npm:~2.0.3" + checksum: 10/37dbe58ca7b0716cc881f0618128f1fd6ff9c46cdc529a269fd70004e567126a449a94e9428e2d19b53d06182d11b45d0c399828f103e06b2bb87643319bd2e7 + languageName: node + linkType: hard + +"promise@npm:~1.3.0": + version: 1.3.0 + resolution: "promise@npm:1.3.0" + dependencies: + is-promise: "npm:~1" + checksum: 10/f7b0264e2591bbcd557141c86407c3754266b5229d5ca401162de0e6a174a7c7fd9123458d4c9c2976d9b94a2d36673c146c3f8cb0106ad68476421f32a8d9ec + languageName: node + linkType: hard + +"proto-list@npm:~1.2.1": + version: 1.2.4 + resolution: "proto-list@npm:1.2.4" + checksum: 10/9cc3b46d613fa0d637033b225db1bc98e914c3c05864f7adc9bee728192e353125ef2e49f71129a413f6333951756000b0e54f299d921f02d3e9e370cc994100 + languageName: node + linkType: hard + +"protobufjs@npm:^7.2.5, protobufjs@npm:^7.3.0, protobufjs@npm:^7.5.3": + version: 7.5.4 + resolution: "protobufjs@npm:7.5.4" + dependencies: + "@protobufjs/aspromise": "npm:^1.1.2" + "@protobufjs/base64": "npm:^1.1.2" + "@protobufjs/codegen": "npm:^2.0.4" + "@protobufjs/eventemitter": "npm:^1.1.0" + "@protobufjs/fetch": "npm:^1.1.0" + "@protobufjs/float": "npm:^1.0.2" + "@protobufjs/inquire": "npm:^1.1.0" + "@protobufjs/path": "npm:^1.1.2" + "@protobufjs/pool": "npm:^1.1.0" + "@protobufjs/utf8": "npm:^1.1.0" + "@types/node": "npm:>=13.7.0" + long: "npm:^5.0.0" + checksum: 10/88d677bb6f11a2ecec63fdd053dfe6d31120844d04e865efa9c8fbe0674cd077d6624ecfdf014018a20dcb114ae2a59c1b21966dd8073e920650c71370966439 + languageName: node + linkType: hard + +"proxy-addr@npm:^2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: 10/f24a0c80af0e75d31e3451398670d73406ec642914da11a2965b80b1898ca6f66a0e3e091a11a4327079b2b268795f6fa06691923fef91887215c3d0e8ea3f68 + languageName: node + linkType: hard + +"proxy-from-env@npm:^1.1.0": + version: 1.1.0 + resolution: "proxy-from-env@npm:1.1.0" + checksum: 10/f0bb4a87cfd18f77bc2fba23ae49c3b378fb35143af16cc478171c623eebe181678f09439707ad80081d340d1593cd54a33a0113f3ccb3f4bc9451488780ee23 + languageName: node + linkType: hard + +"pug-attrs@npm:^3.0.0": + version: 3.0.0 + resolution: "pug-attrs@npm:3.0.0" + dependencies: + constantinople: "npm:^4.0.1" + js-stringify: "npm:^1.0.2" + pug-runtime: "npm:^3.0.0" + checksum: 10/2ca2d34de3065239f01f0fc3c0e104c17f7a7105684d088bb71df623005a45f40a2301e65f49ec4581bb31794c74e691862643d4e34062d1509e92fa56a15aa5 + languageName: node + linkType: hard + +"pug-code-gen@npm:^3.0.3": + version: 3.0.3 + resolution: "pug-code-gen@npm:3.0.3" + dependencies: + constantinople: "npm:^4.0.1" + doctypes: "npm:^1.1.0" + js-stringify: "npm:^1.0.2" + pug-attrs: "npm:^3.0.0" + pug-error: "npm:^2.1.0" + pug-runtime: "npm:^3.0.1" + void-elements: "npm:^3.1.0" + with: "npm:^7.0.0" + checksum: 10/1918b2a75794b730ee29fc2278658ff2ccb74445742c175c55b18e414cf038e5ac5802e71db070b08f92c5304a66e141dc2261e401be4d5884f1c0bcfb3194ee + languageName: node + linkType: hard + +"pug-error@npm:^2.0.0, pug-error@npm:^2.1.0": + version: 2.1.0 + resolution: "pug-error@npm:2.1.0" + checksum: 10/9aefacfa156f0eb439ddab86c7136f998a532481a80665c9fb6b998afeea5bc8c4f83eb6ad8a4c7804c44927737df913b768b713995e6892112bbc05762e5415 + languageName: node + linkType: hard + +"pug-filters@npm:^4.0.0": + version: 4.0.0 + resolution: "pug-filters@npm:4.0.0" + dependencies: + constantinople: "npm:^4.0.1" + jstransformer: "npm:1.0.0" + pug-error: "npm:^2.0.0" + pug-walk: "npm:^2.0.0" + resolve: "npm:^1.15.1" + checksum: 10/ca8b7ffede57d13679ec8c3ee2791feabb7ab3972e02f16fffe328ab9de42961758c3115b0536b2e6cf14a4dc2b2381a172adba84423be4137298fd59ff92853 + languageName: node + linkType: hard + +"pug-lexer@npm:^5.0.1": + version: 5.0.1 + resolution: "pug-lexer@npm:5.0.1" + dependencies: + character-parser: "npm:^2.2.0" + is-expression: "npm:^4.0.0" + pug-error: "npm:^2.0.0" + checksum: 10/18d74a2dfbee892a71ca973e72be60acc36a30b5b7325e2cd723691779e505bfecd2206453b09c2b7f868af9ec0204ed4ea7a26c2a835172a22618b350b6aeb1 + languageName: node + linkType: hard + +"pug-linker@npm:^4.0.0": + version: 4.0.0 + resolution: "pug-linker@npm:4.0.0" + dependencies: + pug-error: "npm:^2.0.0" + pug-walk: "npm:^2.0.0" + checksum: 10/423f62e8600fb66c785ef4e11d9a7833a959677d67443980fd66248da56cebed0a8867f7aa78dc5631803cd1ce71a00b0abf78229b2a2d2ec8779a8b3afb5079 + languageName: node + linkType: hard + +"pug-load@npm:^3.0.0": + version: 3.0.0 + resolution: "pug-load@npm:3.0.0" + dependencies: + object-assign: "npm:^4.1.1" + pug-walk: "npm:^2.0.0" + checksum: 10/1800ec51994c92338401bcf79bbfa0d5ef9aa312bc415c2618263d6c04d1d7c5be5ac4a333c47a0eaa823f6231b4ade1a1c40f5784b99eb576d25853597bff2f + languageName: node + linkType: hard + +"pug-parser@npm:^6.0.0": + version: 6.0.0 + resolution: "pug-parser@npm:6.0.0" + dependencies: + pug-error: "npm:^2.0.0" + token-stream: "npm:1.0.0" + checksum: 10/4c23e154ea2c8c4355ee0291fefa7210f24beecff7c4af2d1e8b7e86ce2923d3213f31bbd9e33bd6703c25ea625dc6494a2ca68a21dcb105b5ac9204248cf4a8 + languageName: node + linkType: hard + +"pug-runtime@npm:^3.0.0, pug-runtime@npm:^3.0.1": + version: 3.0.1 + resolution: "pug-runtime@npm:3.0.1" + checksum: 10/d34ee1b95121576bd389dccd2f6d7dc6fb0bf24963d2b7d1471795d35d8fba90ee8e16c2e022084bdc2f2cfbd56aaa2f452ea872135baf54dbb54a0d5aedd856 + languageName: node + linkType: hard + +"pug-strip-comments@npm:^2.0.0": + version: 2.0.0 + resolution: "pug-strip-comments@npm:2.0.0" + dependencies: + pug-error: "npm:^2.0.0" + checksum: 10/2cfcbf506c14bb3e64204a1d93f12ca61658d2540475b0f0911c35531ad28421e8d1e73a646d841d58cfa2c20f8593c52e492dfe5b6bec968e20b614e4dea1e4 + languageName: node + linkType: hard + +"pug-walk@npm:^2.0.0": + version: 2.0.0 + resolution: "pug-walk@npm:2.0.0" + checksum: 10/bee64e133b711e1ed58022c0869b59e62f9f3ebb7084293857f074120b3cb588e7b8f74c4566426bf2b26dc1ec176ca6b64a2d1e53782f3fbbe039c5d4816638 + languageName: node + linkType: hard + +"pug@npm:^3.0.2, pug@npm:^3.0.3": + version: 3.0.3 + resolution: "pug@npm:3.0.3" + dependencies: + pug-code-gen: "npm:^3.0.3" + pug-filters: "npm:^4.0.0" + pug-lexer: "npm:^5.0.1" + pug-linker: "npm:^4.0.0" + pug-load: "npm:^3.0.0" + pug-parser: "npm:^6.0.0" + pug-runtime: "npm:^3.0.1" + pug-strip-comments: "npm:^2.0.0" + checksum: 10/a88364757512e3b9af024c008f23b910de049659655b5d9e6ca42f996d7849ce1aab059f61e2d44ccce0dde5ff291995682338c285e9f76f3e5bfa02de9c481b + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.3 + resolution: "pump@npm:3.0.3" + dependencies: + end-of-stream: "npm:^1.1.0" + once: "npm:^1.3.1" + checksum: 10/52843fc933b838c0330f588388115a1b28ef2a5ffa7774709b142e35431e8ab0c2edec90de3fa34ebb72d59fef854f151eea7dfc211b6dcf586b384556bd2f39 + languageName: node + linkType: hard + +"punycode.js@npm:2.3.1": + version: 2.3.1 + resolution: "punycode.js@npm:2.3.1" + checksum: 10/f0e946d1edf063f9e3d30a32ca86d8ff90ed13ca40dad9c75d37510a04473340cfc98db23a905cc1e517b1e9deb0f6021dce6f422ace235c60d3c9ac47c5a16a + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10/febdc4362bead22f9e2608ff0171713230b57aff9dddc1c273aa2a651fbd366f94b7d6a71d78342a7c0819906750351ca7f2edd26ea41b626d87d6a13d1bd059 + languageName: node + linkType: hard + +"qs@npm:^6.11.0, qs@npm:^6.14.0, qs@npm:^6.14.1, qs@npm:^6.7.0, qs@npm:^6.9.4": + version: 6.14.1 + resolution: "qs@npm:6.14.1" + dependencies: + side-channel: "npm:^1.1.0" + checksum: 10/34b5ab00a910df432d55180ef39c1d1375e550f098b5ec153b41787f1a6a6d7e5f9495593c3b112b77dbc6709d0ae18e55b82847a4c2bbbb0de1e8ccbb1794c5 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: 10/72900df0616e473e824202113c3df6abae59150dfb73ed13273503127235320e9c8ca4aaaaccfd58cf417c6ca92a6e68ee9a5c3182886ae949a768639b388a7b + languageName: node + linkType: hard + +"quick-format-unescaped@npm:^4.0.3": + version: 4.0.4 + resolution: "quick-format-unescaped@npm:4.0.4" + checksum: 10/591eca457509a99368b623db05248c1193aa3cedafc9a077d7acab09495db1231017ba3ad1b5386e5633271edd0a03b312d8640a59ee585b8516a42e15438aa7 + languageName: node + linkType: hard + +"quick-lru@npm:^5.1.1": + version: 5.1.1 + resolution: "quick-lru@npm:5.1.1" + checksum: 10/a516faa25574be7947969883e6068dbe4aa19e8ef8e8e0fd96cddd6d36485e9106d85c0041a27153286b0770b381328f4072aa40d3b18a19f5f7d2b78b94b5ed + languageName: node + linkType: hard + +"quoted-printable@npm:^1.0.0, quoted-printable@npm:^1.0.1": + version: 1.0.1 + resolution: "quoted-printable@npm:1.0.1" + dependencies: + utf8: "npm:^2.1.0" + bin: + quoted-printable: bin/quoted-printable + checksum: 10/d58db7b5bcf59c0f173f0f184cb26ac43ae698a1bbf8cd355f79bfb702aa4ac79c8d1f8a6dbcdfa843a833dfa03130c43c8711a94375c7ac6ec3c55f9f2b22af + languageName: node + linkType: hard + +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: "npm:^5.1.0" + checksum: 10/4efd1ad3d88db77c2d16588dc54c2b52fd2461e70fe5724611f38d283857094fe09040fa2c9776366803c3152cf133171b452ef717592b65631ce5dc3a2bdafc + languageName: node + linkType: hard + +"range-parser@npm:^1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 10/ce21ef2a2dd40506893157970dc76e835c78cf56437e26e19189c48d5291e7279314477b06ac38abd6a401b661a6840f7b03bd0b1249da9b691deeaa15872c26 + languageName: node + linkType: hard + +"raw-body@npm:^3.0.1": + version: 3.0.2 + resolution: "raw-body@npm:3.0.2" + dependencies: + bytes: "npm:~3.1.2" + http-errors: "npm:~2.0.1" + iconv-lite: "npm:~0.7.0" + unpipe: "npm:~1.0.0" + checksum: 10/4168c82157bd69175d5bd960e59b74e253e237b358213694946a427a6f750a18b8e150f036fed3421b3e83294b071a4e2bb01037a79ccacdac05360c63d3ebba + languageName: node + linkType: hard + +"rc@npm:^1.2.8": + version: 1.2.8 + resolution: "rc@npm:1.2.8" + dependencies: + deep-extend: "npm:^0.6.0" + ini: "npm:~1.3.0" + minimist: "npm:^1.2.0" + strip-json-comments: "npm:~2.0.1" + bin: + rc: ./cli.js + checksum: 10/5c4d72ae7eec44357171585938c85ce066da8ca79146b5635baf3d55d74584c92575fa4e2c9eac03efbed3b46a0b2e7c30634c012b4b4fa40d654353d3c163eb + languageName: node + linkType: hard + +"readable-stream@npm:1.1.x": + version: 1.1.14 + resolution: "readable-stream@npm:1.1.14" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.1" + isarray: "npm:0.0.1" + string_decoder: "npm:~0.10.x" + checksum: 10/1aa2cf4bd02f9ab3e1d57842a43a413b52be5300aa089ad1f2e3cea00684532d73edc6a2ba52b0c3210d8b57eb20a695a6d2b96d1c6085ee979c6021ad48ad20 + languageName: node + linkType: hard + +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10/8500dd3a90e391d6c5d889256d50ec6026c059fadee98ae9aa9b86757d60ac46fff24fafb7a39fa41d54cb39d8be56cc77be202ebd4cd8ffcf4cb226cbaa40d4 + languageName: node + linkType: hard + +"readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0, readable-stream@npm:^3.6.1, readable-stream@npm:^3.6.2": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: "npm:^2.0.3" + string_decoder: "npm:^1.1.1" + util-deprecate: "npm:^1.0.1" + checksum: 10/d9e3e53193adcdb79d8f10f2a1f6989bd4389f5936c6f8b870e77570853561c362bee69feca2bbb7b32368ce96a85504aa4cedf7cf80f36e6a9de30d64244048 + languageName: node + linkType: hard + +"readdir-glob@npm:^1.1.2": + version: 1.1.3 + resolution: "readdir-glob@npm:1.1.3" + dependencies: + minimatch: "npm:^5.1.0" + checksum: 10/ca3a20aa1e715d671302d4ec785a32bf08e59d6d0dd25d5fc03e9e5a39f8c612cdf809ab3e638a79973db7ad6868492edf38504701e313328e767693671447d6 + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10/7b817c265940dba90bb9c94d82920d76c3a35ea2d67f9f9d8bd936adcfe02d50c802b14be3dd2e725e002dddbe2cc1c7a0edfb1bc3a365c9dfd5a61e612eea1e + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: "npm:^2.2.1" + checksum: 10/196b30ef6ccf9b6e18c4e1724b7334f72a093d011a99f3b5920470f0b3406a51770867b3e1ae9711f227ef7a7065982f6ee2ce316746b2cb42c88efe44297fe7 + languageName: node + linkType: hard + +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: 10/ddf44ee76301c774e9c9f2826da8a3c5c9f8fc87310f4a364e803ef003aa1a43c378b4323051ced212097fff1af459070f4499338b36a7469df1d4f7e8c0ba4c + languageName: node + linkType: hard + +"redis-errors@npm:^1.0.0, redis-errors@npm:^1.2.0": + version: 1.2.0 + resolution: "redis-errors@npm:1.2.0" + checksum: 10/001c11f63ddd52d7c80eb4f4ede3a9433d29a458a7eea06b9154cb37c9802a218d93b7988247aa8c958d4b5d274b18354e8853c148f1096fda87c6e675cfd3ee + languageName: node + linkType: hard + +"redis-parser@npm:^3.0.0": + version: 3.0.0 + resolution: "redis-parser@npm:3.0.0" + dependencies: + redis-errors: "npm:^1.0.0" + checksum: 10/b10846844b4267f19ce1a6529465819c3d78c3e89db7eb0c3bb4eb19f83784797ec411274d15a77dbe08038b48f95f76014b83ca366dc955a016a3a0a0234650 + languageName: node + linkType: hard + +"reflect-metadata@npm:^0.2.1, reflect-metadata@npm:^0.2.2": + version: 0.2.2 + resolution: "reflect-metadata@npm:0.2.2" + checksum: 10/1c93f9ac790fea1c852fde80c91b2760420069f4862f28e6fae0c00c6937a56508716b0ed2419ab02869dd488d123c4ab92d062ae84e8739ea7417fae10c4745 + languageName: node + linkType: hard + +"relateurl@npm:^0.2.7": + version: 0.2.7 + resolution: "relateurl@npm:0.2.7" + checksum: 10/f5d6ba58f2a5d5076389090600c243a0ba7072bcf347490a09e4241e2427ccdb260b4e22cea7be4f1fcd3c2bf05908b1e0d0bc9605e3199d4ecf37af1d5681fa + languageName: node + linkType: hard + +"request-ip@npm:^3.3.0": + version: 3.3.0 + resolution: "request-ip@npm:3.3.0" + checksum: 10/9ca26f814201da19cb6f1a18da4f036803b770665ec0e7c556ea975ba553321922a5f04909f6dfc2371f695ca8aaa3c66f02c00a5e902c76435029804cdc4964 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10/a72468e2589270d91f06c7d36ec97a88db53ae5d6fe3787fadc943f0b0276b10347f89b363b2a82285f650bdcc135ad4a257c61bdd4d00d6df1fa24875b0ddaf + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10/839a3a890102a658f4cb3e7b2aa13a1f80a3a976b512020c3d1efc418491c48a886b6e481ea56afc6c4cb5eef678f23b2a4e70575e7534eccadf5e30ed2e56eb + languageName: node + linkType: hard + +"require-in-the-middle@npm:^7.4.0": + version: 7.5.2 + resolution: "require-in-the-middle@npm:7.5.2" + dependencies: + debug: "npm:^4.3.5" + module-details-from-path: "npm:^1.0.3" + resolve: "npm:^1.22.8" + checksum: 10/d8f137d72eec1c53987647d19cd3bd2c64d5417bcd06b9ac8f7a14e83924c1e7636e327df7d96066a2b446b41f50d0bc1856a521388d5e90ba5c3b18dd5ab4e8 + languageName: node + linkType: hard + +"resolve-alpn@npm:^1.0.0, resolve-alpn@npm:^1.2.0": + version: 1.2.1 + resolution: "resolve-alpn@npm:1.2.1" + checksum: 10/744e87888f0b6fa0b256ab454ca0b9c0b80808715e2ef1f3672773665c92a941f6181194e30ccae4a8cd0adbe0d955d3f133102636d2ee0cca0119fec0bc9aec + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10/91eb76ce83621eea7bbdd9b55121a5c1c4a39e54a9ce04a9ad4517f102f8b5131c2cf07622c738a6683991bf54f2ce178f5a42803ecbd527ddc5105f362cc9e3 + languageName: node + linkType: hard + +"resolve@npm:^1.15.1, resolve@npm:^1.22.8": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/e1b2e738884a08de03f97ee71494335eba8c2b0feb1de9ae065e82c48997f349f77a2b10e8817e147cf610bfabc4b1cb7891ee8eaf5bf80d4ad514a34c4fab0a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.15.1#optional!builtin, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10/fd342cad25e52cd6f4f3d1716e189717f2522bfd6641109fe7aa372f32b5714a296ed7c238ddbe7ebb0c1ddfe0b7f71c9984171024c97cf1b2073e3e40ff71a8 + languageName: node + linkType: hard + +"responselike@npm:^2.0.0": + version: 2.0.1 + resolution: "responselike@npm:2.0.1" + dependencies: + lowercase-keys: "npm:^2.0.0" + checksum: 10/b122535466e9c97b55e69c7f18e2be0ce3823c5d47ee8de0d9c0b114aa55741c6db8bfbfce3766a94d1272e61bfb1ebf0a15e9310ac5629fbb7446a861b4fd3a + languageName: node + linkType: hard + +"responselike@npm:^3.0.0": + version: 3.0.0 + resolution: "responselike@npm:3.0.0" + dependencies: + lowercase-keys: "npm:^3.0.0" + checksum: 10/e0cc9be30df4f415d6d83cdede3c5c887cd4a73e7cc1708bcaab1d50a28d15acb68460ac5b02bcc55a42f3d493729c8856427dcf6e57e6e128ad05cba4cfb95e + languageName: node + linkType: hard + +"restore-cursor@npm:^3.1.0": + version: 3.1.0 + resolution: "restore-cursor@npm:3.1.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10/f877dd8741796b909f2a82454ec111afb84eb45890eb49ac947d87991379406b3b83ff9673a46012fca0d7844bb989f45cc5b788254cf1a39b6b5a9659de0630 + languageName: node + linkType: hard + +"retry@npm:^0.10.0": + version: 0.10.1 + resolution: "retry@npm:0.10.1" + checksum: 10/97d165ac7d70c74754ac0855f5b1c9fc31f90aa6ecb10b0702dca3abe8b78e3c7fc18ba0a4e8da928cf7bcae1c92ba7902843127f29b630b5ac986e9b2d4e2e4 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10/1f914879f97e7ee931ad05fe3afa629bd55270fc6cf1c1e589b6a99fab96d15daad0fa1a52a00c729ec0078045fe3e399bd4fd0c93bcc906957bdc17f89cb8e6 + languageName: node + linkType: hard + +"retry@npm:^0.13.1": + version: 0.13.1 + resolution: "retry@npm:0.13.1" + checksum: 10/6125ec2e06d6e47e9201539c887defba4e47f63471db304c59e4b82fc63c8e89ca06a77e9d34939a9a42a76f00774b2f46c0d4a4cbb3e287268bd018ed69426d + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.1.0 + resolution: "reusify@npm:1.1.0" + checksum: 10/af47851b547e8a8dc89af144fceee17b80d5beaf5e6f57ed086432d79943434ff67ca526e92275be6f54b6189f6920a24eace75c2657eed32d02c400312b21ec + languageName: node + linkType: hard + +"rfdc@npm:^1.3.0": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10/2f3d11d3d8929b4bfeefc9acb03aae90f971401de0add5ae6c5e38fec14f0405e6a4aad8fdb76344bfdd20c5193110e3750cbbd28ba86d73729d222b6cf4a729 + languageName: node + linkType: hard + +"rimraf@npm:2": + version: 2.7.1 + resolution: "rimraf@npm:2.7.1" + dependencies: + glob: "npm:^7.1.3" + bin: + rimraf: ./bin.js + checksum: 10/4586c296c736483e297da7cffd19475e4a3e41d07b1ae124aad5d687c79e4ffa716bdac8732ed1db942caf65271cee9dd39f8b639611de161a2753e2112ffe1d + languageName: node + linkType: hard + +"rimraf@npm:^6.0.1": + version: 6.1.2 + resolution: "rimraf@npm:6.1.2" + dependencies: + glob: "npm:^13.0.0" + package-json-from-dist: "npm:^1.0.1" + bin: + rimraf: dist/esm/bin.mjs + checksum: 10/add8e566fe903f59d7b55c6c2382320c48302778640d1951baf247b3b451af496c2dee7195c204a8c646fd6327feadd1f5b61ce68c1362d4898075a726d83cc6 + languageName: node + linkType: hard + +"ringbufferjs@npm:^2.0.0": + version: 2.0.0 + resolution: "ringbufferjs@npm:2.0.0" + checksum: 10/d33628f0f273f53e6b914f1add674dcade478bf8c520ceb9f7bfdc36ce656701dcd49e45dcc819615a44b80f874c9aeb935ec5138190a576dc4b0d0756719cfe + languageName: node + linkType: hard + +"router@npm:^2.2.0": + version: 2.2.0 + resolution: "router@npm:2.2.0" + dependencies: + debug: "npm:^4.4.0" + depd: "npm:^2.0.0" + is-promise: "npm:^4.0.0" + parseurl: "npm:^1.3.3" + path-to-regexp: "npm:^8.0.0" + checksum: 10/8949bd1d3da5403cc024e2989fee58d7fda0f3ffe9f2dc5b8a192f295f400b3cde307b0b554f7d44851077640f36962ca469a766b3d57410d7d96245a7ba6c91 + languageName: node + linkType: hard + +"run-applescript@npm:^3.0.0": + version: 3.2.0 + resolution: "run-applescript@npm:3.2.0" + dependencies: + execa: "npm:^0.10.0" + checksum: 10/fbd3fa1c3749f3f1504c27b4ab954952517d8c3275326131f204f5f2b2428b990812c17a342f109ede1432174ba10c1acb2b98f4f68e0c77c3aacd2fcf829444 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: "npm:^1.2.2" + checksum: 10/cb4f97ad25a75ebc11a8ef4e33bb962f8af8516bb2001082ceabd8902e15b98f4b84b4f8a9b222e5d57fc3bd1379c483886ed4619367a7680dad65316993021d + languageName: node + linkType: hard + +"rxjs@npm:*, rxjs@npm:^7.8.2": + version: 7.8.2 + resolution: "rxjs@npm:7.8.2" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/03dff09191356b2b87d94fbc1e97c4e9eb3c09d4452399dddd451b09c2f1ba8d56925a40af114282d7bc0c6fe7514a2236ca09f903cf70e4bbf156650dddb49d + languageName: node + linkType: hard + +"rxjs@npm:7.8.1": + version: 7.8.1 + resolution: "rxjs@npm:7.8.1" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10/b10cac1a5258f885e9dd1b70d23c34daeb21b61222ee735d2ec40a8685bdca40429000703a44f0e638c27a684ac139e1c37e835d2a0dc16f6fc061a138ae3abb + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10/32872cd0ff68a3ddade7a7617b8f4c2ae8764d8b7d884c651b74457967a9e0e886267d3ecc781220629c44a865167b61c375d2da6c720c840ecd73f45d5d9451 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10/7eb5b48f2ed9a594a4795677d5a150faa7eb54483b2318b568dc0c4fc94092a6cce5be02c7288a0500a156282f5276d5688bce7259299568d1053b2150ef374a + languageName: node + linkType: hard + +"safe-stable-stringify@npm:^2.3.1": + version: 2.5.0 + resolution: "safe-stable-stringify@npm:2.5.0" + checksum: 10/2697fa186c17c38c3ca5309637b4ac6de2f1c3d282da27cd5e1e3c88eca0fb1f9aea568a6aabdf284111592c8782b94ee07176f17126031be72ab1313ed46c5c + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10/7eaf7a0cf37cc27b42fb3ef6a9b1df6e93a1c6d98c6c6702b02fe262d5fcbd89db63320793b99b21cb5348097d0a53de81bd5f4e8b86e20cc9412e3f1cfb4e83 + languageName: node + linkType: hard + +"saxes@npm:^5.0.1": + version: 5.0.1 + resolution: "saxes@npm:5.0.1" + dependencies: + xmlchars: "npm:^2.2.0" + checksum: 10/148b5f98fdd45df25fa1abef35d72cdf6457ac5aef3b7d59d60f770af09d8cf6e7e3a074197071222441d68670fd3198590aba9985e37c4738af2df2f44d0686 + languageName: node + linkType: hard + +"schema-utils@npm:^3.1.1": + version: 3.3.0 + resolution: "schema-utils@npm:3.3.0" + dependencies: + "@types/json-schema": "npm:^7.0.8" + ajv: "npm:^6.12.5" + ajv-keywords: "npm:^3.5.2" + checksum: 10/2c7bbb1da967fdfd320e6cea538949006ec6e8c13ea560a4f94ff2c56809a8486fa5ec419e023452501a6befe1ca381e409c2798c24f4993c7c4094d97fdb258 + languageName: node + linkType: hard + +"schema-utils@npm:^4.3.0, schema-utils@npm:^4.3.3": + version: 4.3.3 + resolution: "schema-utils@npm:4.3.3" + dependencies: + "@types/json-schema": "npm:^7.0.9" + ajv: "npm:^8.9.0" + ajv-formats: "npm:^2.1.1" + ajv-keywords: "npm:^5.1.0" + checksum: 10/dba77a46ad7ff0c906f7f09a1a61109e6cb56388f15a68070b93c47a691f516c6a3eb454f81a8cceb0a0e55b87f8b05770a02bfb1f4e0a3143b5887488b2f900 + languageName: node + linkType: hard + +"scmp@npm:^2.1.0": + version: 2.1.0 + resolution: "scmp@npm:2.1.0" + checksum: 10/1a21c91d98891e61b411bf3c494482c8b47a8adece11356569c28328d983b98089eec19ff77005b74ec536ddac6ea2b7679a72b80e6a04774fc18a4a1f37f939 + languageName: node + linkType: hard + +"seedrandom@npm:^3.0.5": + version: 3.0.5 + resolution: "seedrandom@npm:3.0.5" + checksum: 10/acad5e516c04289f61c2fb9848f449b95f58362b75406b79ec51e101ec885293fc57e3675d2f39f49716336559d7190f7273415d185fead8cd27b171ebf7d8fb + languageName: node + linkType: hard + +"seek-bzip@npm:^2.0.0": + version: 2.0.0 + resolution: "seek-bzip@npm:2.0.0" + dependencies: + commander: "npm:^6.0.0" + bin: + seek-bunzip: bin/seek-bunzip + seek-table: bin/seek-bzip-table + checksum: 10/38d49a2091ea4a01835662f606076cf032bae63480a10c84eb61dd810286f9ab24d275000a4a17e2efadcfa27bcf2b0dbeff7dabf9011487922f75bad6e57871 + languageName: node + linkType: hard + +"selderee@npm:^0.11.0": + version: 0.11.0 + resolution: "selderee@npm:0.11.0" + dependencies: + parseley: "npm:^0.12.0" + checksum: 10/9f697a00b8270354777a8423e555fd3168abead1304b8d267412877a4b007830624d8aa562eb29a3ec2d9d2f7f977808d17b790b9c210a7d828c12ed9ef0f1f0 + languageName: node + linkType: hard + +"semver-regex@npm:^4.0.5": + version: 4.0.5 + resolution: "semver-regex@npm:4.0.5" + checksum: 10/b9e5c0573c4a997fb7e6e76321385d254797e86c8dba5e23f3cd8cf8f40b40414097a51514e5fead61dcb88ff10d3676355c01e2040f3c68f6c24bfd2073da2e + languageName: node + linkType: hard + +"semver-truncate@npm:^3.0.0": + version: 3.0.0 + resolution: "semver-truncate@npm:3.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10/d8c23812218ff147f512ac4830e86860a377dba8a9733ae97d816102aca33236fa1c44c06544727153fffb93d15d0e45c49b2c40a7964aa3671769e9aed2f3f9 + languageName: node + linkType: hard + +"semver@npm:^5.5.0, semver@npm:^5.6.0": + version: 5.7.2 + resolution: "semver@npm:5.7.2" + bin: + semver: bin/semver + checksum: 10/fca14418a174d4b4ef1fecb32c5941e3412d52a4d3d85165924ce3a47fbc7073372c26faf7484ceb4bbc2bde25880c6b97e492473dc7e9708fdfb1c6a02d546e + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.7.3": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10/8dbc3168e057a38fc322af909c7f5617483c50caddba135439ff09a754b20bdd6482a5123ff543dad4affa488ecf46ec5fb56d61312ad20bb140199b88dfaea9 + languageName: node + linkType: hard + +"semver@npm:~5.3.0": + version: 5.3.0 + resolution: "semver@npm:5.3.0" + bin: + semver: ./bin/semver + checksum: 10/ff3ac60aaa4855a723cc5784c43cf34674096b823037e0e7bb84aa7612acf9093c55c1b47c431f5ebb0ba74299e6d555e89ade74f2e69c348e58eecbd6d61b5e + languageName: node + linkType: hard + +"send@npm:^1.1.0, send@npm:^1.2.0": + version: 1.2.1 + resolution: "send@npm:1.2.1" + dependencies: + debug: "npm:^4.4.3" + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + etag: "npm:^1.8.1" + fresh: "npm:^2.0.0" + http-errors: "npm:^2.0.1" + mime-types: "npm:^3.0.2" + ms: "npm:^2.1.3" + on-finished: "npm:^2.4.1" + range-parser: "npm:^1.2.1" + statuses: "npm:^2.0.2" + checksum: 10/274f842d69ccfa49d4940a85598c6825da58dee6cb8ea33b08d5bd3988e6a82267c4d7c32b23d0e4706aad076ee95b1edfa13f859877db9b589829019397e355 + languageName: node + linkType: hard + +"serialize-javascript@npm:^6.0.2": + version: 6.0.2 + resolution: "serialize-javascript@npm:6.0.2" + dependencies: + randombytes: "npm:^2.1.0" + checksum: 10/445a420a6fa2eaee4b70cbd884d538e259ab278200a2ededd73253ada17d5d48e91fb1f4cd224a236ab62ea7ba0a70c6af29fc93b4f3d3078bf7da1c031fde58 + languageName: node + linkType: hard + +"serve-static@npm:^2.2.0": + version: 2.2.1 + resolution: "serve-static@npm:2.2.1" + dependencies: + encodeurl: "npm:^2.0.0" + escape-html: "npm:^1.0.3" + parseurl: "npm:^1.3.3" + send: "npm:^1.2.0" + checksum: 10/71500fe80cc7163fec04e4297de7591ad1cb682d137fc030e7a53e57040fda5187e8082a9c1b2ef37f1d3f9c27c9a94d4ba61806ebc28938ba4a7c8947c9f71e + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d + languageName: node + linkType: hard + +"setimmediate@npm:^1.0.5, setimmediate@npm:~1.0.4": + version: 1.0.5 + resolution: "setimmediate@npm:1.0.5" + checksum: 10/76e3f5d7f4b581b6100ff819761f04a984fa3f3990e72a6554b57188ded53efce2d3d6c0932c10f810b7c59414f85e2ab3c11521877d1dea1ce0b56dc906f485 + languageName: node + linkType: hard + +"setprototypeof@npm:~1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 10/fde1630422502fbbc19e6844346778f99d449986b2f9cdcceb8326730d2f3d9964dbcb03c02aaadaefffecd0f2c063315ebea8b3ad895914bf1afc1747fc172e + languageName: node + linkType: hard + +"sha.js@npm:^2.4.12": + version: 2.4.12 + resolution: "sha.js@npm:2.4.12" + dependencies: + inherits: "npm:^2.0.4" + safe-buffer: "npm:^5.2.1" + to-buffer: "npm:^1.2.0" + bin: + sha.js: bin.js + checksum: 10/39c0993592c2ab34eb2daae2199a2a1d502713765aecb611fd97c0c4ab7cd53e902d628e1962aaf384bafd28f55951fef46dcc78799069ce41d74b03aa13b5a7 + languageName: node + linkType: hard + +"sharp@npm:^0.34.2": + version: 0.34.5 + resolution: "sharp@npm:0.34.5" + dependencies: + "@img/colour": "npm:^1.0.0" + "@img/sharp-darwin-arm64": "npm:0.34.5" + "@img/sharp-darwin-x64": "npm:0.34.5" + "@img/sharp-libvips-darwin-arm64": "npm:1.2.4" + "@img/sharp-libvips-darwin-x64": "npm:1.2.4" + "@img/sharp-libvips-linux-arm": "npm:1.2.4" + "@img/sharp-libvips-linux-arm64": "npm:1.2.4" + "@img/sharp-libvips-linux-ppc64": "npm:1.2.4" + "@img/sharp-libvips-linux-riscv64": "npm:1.2.4" + "@img/sharp-libvips-linux-s390x": "npm:1.2.4" + "@img/sharp-libvips-linux-x64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.2.4" + "@img/sharp-linux-arm": "npm:0.34.5" + "@img/sharp-linux-arm64": "npm:0.34.5" + "@img/sharp-linux-ppc64": "npm:0.34.5" + "@img/sharp-linux-riscv64": "npm:0.34.5" + "@img/sharp-linux-s390x": "npm:0.34.5" + "@img/sharp-linux-x64": "npm:0.34.5" + "@img/sharp-linuxmusl-arm64": "npm:0.34.5" + "@img/sharp-linuxmusl-x64": "npm:0.34.5" + "@img/sharp-wasm32": "npm:0.34.5" + "@img/sharp-win32-arm64": "npm:0.34.5" + "@img/sharp-win32-ia32": "npm:0.34.5" + "@img/sharp-win32-x64": "npm:0.34.5" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.7.3" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + checksum: 10/d62bc638c8ad382dffc266beeaffab71457d592abeb6fdf95b512e6dcbce0abf47b8d903b4ea081f012ceb40e4462f1e219184c729329146df32a5ccec2c231f + languageName: node + linkType: hard + +"shebang-command@npm:^1.2.0": + version: 1.2.0 + resolution: "shebang-command@npm:1.2.0" + dependencies: + shebang-regex: "npm:^1.0.0" + checksum: 10/9eed1750301e622961ba5d588af2212505e96770ec376a37ab678f965795e995ade7ed44910f5d3d3cb5e10165a1847f52d3348c64e146b8be922f7707958908 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10/6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa + languageName: node + linkType: hard + +"shebang-regex@npm:^1.0.0": + version: 1.0.0 + resolution: "shebang-regex@npm:1.0.0" + checksum: 10/404c5a752cd40f94591dfd9346da40a735a05139dac890ffc229afba610854d8799aaa52f87f7e0c94c5007f2c6af55bdcaeb584b56691926c5eaf41dc8f1372 + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10/1a2bcae50de99034fcd92ad4212d8e01eedf52c7ec7830eedcf886622804fe36884278f2be8be0ea5fde3fd1c23911643a4e0f726c8685b61871c8908af01222 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10/603b928997abd21c5a5f02ae6b9cc36b72e3176ad6827fab0417ead74580cc4fb4d5c7d0a8a2ff4ead34d0f9e35701ed7a41853dac8a6d1a664fcce1a044f86f + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10/5771861f77feefe44f6195ed077a9e4f389acc188f895f570d56445e251b861754b547ea9ef73ecee4e01fdada6568bfe9020d2ec2dfc5571e9fa1bbc4a10615 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10/a815c89bc78c5723c714ea1a77c938377ea710af20d4fb886d362b0d1f8ac73a17816a5f6640f354017d7e292a43da9c5e876c22145bac00b76cfb3468001736 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10/7d53b9db292c6262f326b6ff3bc1611db84ece36c2c7dc0e937954c13c73185b0406c56589e2bb8d071d6fee468e14c39fb5d203ee39be66b7b8174f179afaba + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10/c9fa63bbbd7431066174a48ba2dd9986dfd930c3a8b59de9c29d7b6854ec1c12a80d15310869ea5166d413b99f041bfa3dd80a7947bcd44ea8e6eb3ffeabfa1f + languageName: node + linkType: hard + +"slash@npm:3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 10/94a93fff615f25a999ad4b83c9d5e257a7280c90a32a7cb8b4a87996e4babf322e469c42b7f649fd5796edd8687652f3fb452a86dc97a816f01113183393f11c + languageName: node + linkType: hard + +"slick@npm:^1.12.2": + version: 1.12.2 + resolution: "slick@npm:1.12.2" + checksum: 10/381ae61b8efb62f6df35926f9d9ae61f7e8fdfbdeef0f2ab41bfec7d89495251667afb572a90d429336d15bae0aa011914e2f354bd57b20f8e49063c682de37f + languageName: node + linkType: hard + +"slugify@npm:^1.6.6": + version: 1.6.6 + resolution: "slugify@npm:1.6.6" + checksum: 10/d0737cdedc834c50f74227bc1a1cf4f449f3575893f031b0e8c59f501c73526c866a23e47261b262c7acdaaaaf30d6f9e8aaae22772b3f56e858ac84c35efa7b + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10/927484aa0b1640fd9473cee3e0a0bcad6fce93fd7bbc18bac9ad0c33686f5d2e2c422fba24b5899c184524af01e11dd2bd051c2bf2b07e47aff8ca72cbfc60d2 + languageName: node + linkType: hard + +"smol-toml@npm:^1.5.2": + version: 1.6.0 + resolution: "smol-toml@npm:1.6.0" + checksum: 10/965315168134bdcc410cda1b71a5e79cb72efd4891584a4cfb1430795437f83ed3bfc40c36dede94023682d2f9aac3b6d52d191036ffc474692da68c4a2292c8 + languageName: node + linkType: hard + +"socket.io-adapter@npm:~2.5.2": + version: 2.5.6 + resolution: "socket.io-adapter@npm:2.5.6" + dependencies: + debug: "npm:~4.4.1" + ws: "npm:~8.18.3" + checksum: 10/2bbefcc6f3d5dedab3105af03091b8863079173ab5610118d0ce94a0cf40fd87956c304f4f06445e361296b1966034be1ff0ba4e87b3c2baec216bbdec43b6e6 + languageName: node + linkType: hard + +"socket.io-parser@npm:~4.2.4": + version: 4.2.5 + resolution: "socket.io-parser@npm:4.2.5" + dependencies: + "@socket.io/component-emitter": "npm:~3.1.0" + debug: "npm:~4.4.1" + checksum: 10/612b3ba068327cbdca043d07f8d96da587a18e0b3e00f002b6476c22410c891abafc44a3f009abd014f2de42b348032f465a7b19771151728f6361ed116423d2 + languageName: node + linkType: hard + +"socket.io@npm:4.8.3, socket.io@npm:^4.8.1": + version: 4.8.3 + resolution: "socket.io@npm:4.8.3" + dependencies: + accepts: "npm:~1.3.4" + base64id: "npm:~2.0.0" + cors: "npm:~2.8.5" + debug: "npm:~4.4.1" + engine.io: "npm:~6.6.0" + socket.io-adapter: "npm:~2.5.2" + socket.io-parser: "npm:~4.2.4" + checksum: 10/ccd3eb0d191b3338056b678e676407a0dc565ab2a49a1aefd4b0771bd1d95d71cb564d3766040346669144c8a3b425bede54e0aad8102aa6c7d91dacb7c6992f + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10/ee99e1dacab0985b52cbe5a75640be6e604135e9489ebdc3048635d186012fbaecc20fbbe04b177dee434c319ba20f09b3e7dfefb7d932466c0d707744eac05c + languageName: node + linkType: hard + +"socks@npm:2.8.7, socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10/d19366c95908c19db154f329bbe94c2317d315dc933a7c2b5101e73f32a555c84fb199b62174e1490082a593a4933d8d5a9b297bde7d1419c14a11a965f51356 + languageName: node + linkType: hard + +"sonic-boom@npm:^4.0.1": + version: 4.2.0 + resolution: "sonic-boom@npm:4.2.0" + dependencies: + atomic-sleep: "npm:^1.0.0" + checksum: 10/385ef7fb5ea5976c1d2a1fef0b6df8df6b7caba8696d2d67f689d60c05e3ea2d536752ce7e1c69b9fad844635f1036d07c446f8e8149f5c6a80e0040a455b310 + languageName: node + linkType: hard + +"sort-keys-length@npm:^1.0.0": + version: 1.0.1 + resolution: "sort-keys-length@npm:1.0.1" + dependencies: + sort-keys: "npm:^1.0.0" + checksum: 10/f9acac5fb31580a9e3d43b419dc86a1b75e85b79036a084d95dd4d1062b621c9589906588ac31e370a0dd381be46d8dbe900efa306d087ca9c912d7a59b5a590 + languageName: node + linkType: hard + +"sort-keys@npm:^1.0.0": + version: 1.1.2 + resolution: "sort-keys@npm:1.1.2" + dependencies: + is-plain-obj: "npm:^1.0.0" + checksum: 10/0ac2ea2327d92252f07aa7b2f8c7023a1f6ce3306439a3e81638cce9905893c069521d168f530fb316d1a929bdb052b742969a378190afaef1bc64fa69e29576 + languageName: node + linkType: hard + +"source-map-support@npm:~0.5.20": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: "npm:^1.0.0" + source-map: "npm:^0.6.0" + checksum: 10/8317e12d84019b31e34b86d483dd41d6f832f389f7417faf8fc5c75a66a12d9686e47f589a0554a868b8482f037e23df9d040d29387eb16fa14cb85f091ba207 + languageName: node + linkType: hard + +"source-map@npm:0.7.4": + version: 0.7.4 + resolution: "source-map@npm:0.7.4" + checksum: 10/a0f7c9b797eda93139842fd28648e868a9a03ea0ad0d9fa6602a0c1f17b7fb6a7dcca00c144476cccaeaae5042e99a285723b1a201e844ad67221bf5d428f1dc + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 10/59ef7462f1c29d502b3057e822cdbdae0b0e565302c4dd1a95e11e793d8d9d62006cdc10e0fd99163ca33ff2071360cf50ee13f90440806e7ed57d81cba2f7ff + languageName: node + linkType: hard + +"source-map@npm:^0.7.3": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 10/c8d2da7c57c14f3fd7568f764b39ad49bbf9dd7632b86df3542b31fed117d4af2fb74a4f886fc06baf7a510fee68e37998efc3080aacdac951c36211dc29a7a3 + languageName: node + linkType: hard + +"split2@npm:^4.0.0, split2@npm:^4.1.0": + version: 4.2.0 + resolution: "split2@npm:4.2.0" + checksum: 10/09bbefc11bcf03f044584c9764cd31a252d8e52cea29130950b26161287c11f519807c5e54bd9e5804c713b79c02cefe6a98f4688630993386be353e03f534ab + languageName: node + linkType: hard + +"split@npm:^1.0.1": + version: 1.0.1 + resolution: "split@npm:1.0.1" + dependencies: + through: "npm:2" + checksum: 10/12f4554a5792c7e98bb3e22b53c63bfa5ef89aa704353e1db608a55b51f5b12afaad6e4a8ecf7843c15f273f43cdadd67b3705cc43d48a75c2cf4641d51f7e7a + languageName: node + linkType: hard + +"sql-highlight@npm:^6.1.0": + version: 6.1.0 + resolution: "sql-highlight@npm:6.1.0" + checksum: 10/6cd92e7ca3046563f3daf2086adc4c2e1ce43784e59827a12bb9e569bf915eace1d800713f4d2798fc7d475f64852bf08001dca8dd409e9895ba5e0e170b94ff + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.0 + resolution: "ssri@npm:13.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10/fd59bfedf0659c1b83f6e15459162da021f08ec0f5834dd9163296f8b77ee82f9656aa1d415c3d3848484293e0e6aefdd482e863e52ddb53d520bb73da1eeec1 + languageName: node + linkType: hard + +"stack-trace@npm:0.0.10, stack-trace@npm:0.0.x": + version: 0.0.10 + resolution: "stack-trace@npm:0.0.10" + checksum: 10/7bd633f0e9ac46e81a0b0fe6538482c1d77031959cf94478228731709db4672fbbed59176f5b9a9fd89fec656b5dae03d084ef2d1b0c4c2f5683e05f2dbb1405 + languageName: node + linkType: hard + +"standard-as-callback@npm:^2.1.0": + version: 2.1.0 + resolution: "standard-as-callback@npm:2.1.0" + checksum: 10/88bec83ee220687c72d94fd86a98d5272c91d37ec64b66d830dbc0d79b62bfa6e47f53b71646011835fc9ce7fae62739545d13124262b53be4fbb3e2ebad551c + languageName: node + linkType: hard + +"statuses@npm:^2.0.1, statuses@npm:^2.0.2, statuses@npm:~2.0.2": + version: 2.0.2 + resolution: "statuses@npm:2.0.2" + checksum: 10/6927feb50c2a75b2a4caab2c565491f7a93ad3d8dbad7b1398d52359e9243a20e2ebe35e33726dee945125ef7a515e9097d8a1b910ba2bbd818265a2f6c39879 + languageName: node + linkType: hard + +"streamroller@npm:^3.1.5": + version: 3.1.5 + resolution: "streamroller@npm:3.1.5" + dependencies: + date-format: "npm:^4.0.14" + debug: "npm:^4.3.4" + fs-extra: "npm:^8.1.0" + checksum: 10/2e4fe61ab91d24e6a9add67418ca9b8e19bc49f4037e1f8b7ae2e480a1d7750423f470d111d138d921a538ae4777c4eb15b00f9cc2a0d4fd72829687889b0c63 + languageName: node + linkType: hard + +"streamsearch@npm:^1.1.0": + version: 1.1.0 + resolution: "streamsearch@npm:1.1.0" + checksum: 10/612c2b2a7dbcc859f74597112f80a42cbe4d448d03da790d5b7b39673c1197dd3789e91cd67210353e58857395d32c1e955a9041c4e6d5bae723436b3ed9ed14 + languageName: node + linkType: hard + +"streamx@npm:^2.15.0": + version: 2.23.0 + resolution: "streamx@npm:2.23.0" + dependencies: + events-universal: "npm:^1.0.0" + fast-fifo: "npm:^1.3.2" + text-decoder: "npm:^1.1.0" + checksum: 10/4969d7032b16497172afa2f8ac889d137764963ae564daf1611a03225dd62d9316d51de8098b5866d21722babde71353067184e7a3e9795d6dc17c902904a780 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.2, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 10/e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: 10/7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193 + languageName: node + linkType: hard + +"string.fromcodepoint@npm:^0.2.1": + version: 0.2.1 + resolution: "string.fromcodepoint@npm:0.2.1" + checksum: 10/6ba80f70c3e2a36dab87f5d68168936403295a73838564e701f5c861d397d77d9e97b0e2aa0f3c163a25a96c785dcc2145452b220753fb7b3e6c6fe431c9c411 + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: "npm:~5.2.0" + checksum: 10/54d23f4a6acae0e93f999a585e673be9e561b65cd4cca37714af1e893ab8cd8dfa52a9e4f58f48f87b4a44918d3a9254326cb80ed194bf2e4c226e2b21767e56 + languageName: node + linkType: hard + +"string_decoder@npm:~0.10.x": + version: 0.10.31 + resolution: "string_decoder@npm:0.10.31" + checksum: 10/cc43e6b1340d4c7843da0e37d4c87a4084c2342fc99dcf6563c3ec273bb082f0cbd4ebf25d5da19b04fb16400d393885fda830be5128e1c416c73b5a6165f175 + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10/7c41c17ed4dea105231f6df208002ebddd732e8e9e2d619d133cecd8e0087ddfd9587d2feb3c8caf3213cbd841ada6d057f5142cae68a4e62d3540778d9819b4 + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 10/ae3b5436d34fadeb6096367626ce987057713c566e1e7768818797e00ac5d62023d0f198c4e681eae9e20701721980b26a64a8f5b91238869592a9c6800719a2 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1": + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10/db0e3f9654e519c8a33c50fc9304d07df5649388e7da06d3aabf66d29e5ad65d5e6315d8519d409c15b32fa82c1df7e11ed6f8cd50b0e4404463f0c9d77c8d0b + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10/8d50ff27b7ebe5ecc78f1fe1e00fcdff7af014e73cf724b46fb81ef889eeb1015fc5184b64e81a2efe002180f3ba431bdd77e300da5c6685d702780fbf0c8d5b + languageName: node + linkType: hard + +"strip-dirs@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-dirs@npm:3.0.0" + dependencies: + inspect-with-kind: "npm:^1.0.5" + is-plain-obj: "npm:^1.1.0" + checksum: 10/630c16035f4e8638bcb55523a3a016668b82b526fbde818b45cfd15c2fed506e2784153932c9d4a6d9758cc2c07a69a9533c7faffad2594dd601378d613e1b67 + languageName: node + linkType: hard + +"strip-eof@npm:^1.0.0": + version: 1.0.0 + resolution: "strip-eof@npm:1.0.0" + checksum: 10/40bc8ddd7e072f8ba0c2d6d05267b4e0a4800898c3435b5fb5f5a21e6e47dfaff18467e7aa0d1844bb5d6274c3097246595841fbfeb317e541974ee992cac506 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10/69412b5e25731e1938184b5d489c32e340605bb611d6140344abc3421b7f3c6f9984b21dff296dfcf056681b82caa3bb4cc996a965ce37bcfad663e92eae9c64 + languageName: node + linkType: hard + +"strip-json-comments@npm:5.0.3": + version: 5.0.3 + resolution: "strip-json-comments@npm:5.0.3" + checksum: 10/3ccbf26f278220f785e4b71f8a719a6a063d72558cc63cb450924254af258a4f4c008b8c9b055373a680dc7bd525be9e543ad742c177f8a7667e0b726258e0e4 + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10/492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 + languageName: node + linkType: hard + +"strip-json-comments@npm:~2.0.1": + version: 2.0.1 + resolution: "strip-json-comments@npm:2.0.1" + checksum: 10/1074ccb63270d32ca28edfb0a281c96b94dc679077828135141f27d52a5a398ef5e78bcf22809d23cadc2b81dfbe345eb5fd8699b385c8b1128907dec4a7d1e1 + languageName: node + linkType: hard + +"stripe@npm:^17.7.0": + version: 17.7.0 + resolution: "stripe@npm:17.7.0" + dependencies: + "@types/node": "npm:>=8.1.0" + qs: "npm:^6.11.0" + checksum: 10/376f945f9c194c8ea2d47d1fda50d141ed985cbb6f94041e11880084f830e502962d434967f1c90a3cb27a9b6e26c606f2830d9448bde636ebd6c067d5cbfecc + languageName: node + linkType: hard + +"strnum@npm:^1.1.1": + version: 1.1.2 + resolution: "strnum@npm:1.1.2" + checksum: 10/ccd6297a1fdaf0fc8ea0ea904acdae76878d49a4b0d98a70155df4bc081fd88eac5ec99fb150f3d1d1af065c1898d38420705259ba6c39aa850c671bcd54e35d + languageName: node + linkType: hard + +"strnum@npm:^2.1.0": + version: 2.1.2 + resolution: "strnum@npm:2.1.2" + checksum: 10/7d894dff385e3a5c5b29c012cf0a7ea7962a92c6a299383c3d6db945ad2b6f3e770511356a9774dbd54444c56af1dc7c435dad6466c47293c48173274dd6c631 + languageName: node + linkType: hard + +"strtok3@npm:^10.2.0, strtok3@npm:^10.3.4": + version: 10.3.4 + resolution: "strtok3@npm:10.3.4" + dependencies: + "@tokenizer/token": "npm:^0.3.0" + checksum: 10/53be14a567dca149be56cb072eaa3c0fffd70d066acf800cf588b91558c6d475364ff8d550524ce0499fc4873a4b0d42ad8c542bfdb9fb39cba520ef2e2e9818 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: "npm:^3.0.0" + checksum: 10/5f505c6fa3c6e05873b43af096ddeb22159831597649881aeb8572d6fe3b81e798cc10840d0c9735e0026b250368851b7f77b65e84f4e4daa820a4f69947f55b + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/c8bb7afd564e3b26b50ca6ee47572c217526a1389fe018d00345856d4a9b08ffbd61fadaf283a87368d94c3dcdb8f5ffe2650a5a65863e21ad2730ca0f05210a + languageName: node + linkType: hard + +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10/157b534df88e39c5518c5e78c35580c1eca848d7dbaf31bbe06cdfc048e22c7ff1a9d046ae17b25691128f631a51d9ec373c1b740c12ae4f0de6e292037e4282 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10/a9dc19ae2220c952bd2231d08ddeecb1b0328b61e72071ff4000c8384e145cc07c1c0bdb3b5a1cb06e186a7b2790f1dee793418b332f6ddf320de25d9125be7e + languageName: node + linkType: hard + +"swagger-ui-dist@npm:5.31.0": + version: 5.31.0 + resolution: "swagger-ui-dist@npm:5.31.0" + dependencies: + "@scarf/scarf": "npm:=1.4.0" + checksum: 10/79a3fc72823c9f6340184a9702f86d5f251ebff41841d734627ad1c6f4ca3553fb888f2fa33cd911506101026fad386350095ee255e6916ebeba8fd5d8114894 + languageName: node + linkType: hard + +"symbol-observable@npm:4.0.0": + version: 4.0.0 + resolution: "symbol-observable@npm:4.0.0" + checksum: 10/983aef3912ad080fc834b9ad115d44bc2994074c57cea4fb008e9f7ab9bb4118b908c63d9edc861f51257bc0595025510bdf7263bb09d8953a6929f240165c24 + languageName: node + linkType: hard + +"synckit@npm:^0.11.7": + version: 0.11.11 + resolution: "synckit@npm:0.11.11" + dependencies: + "@pkgr/core": "npm:^0.2.9" + checksum: 10/6ecd88212b5be80004376b6ea74babcba284566ff59a50d8803afcaa78c165b5d268635c1dd84532ee3f690a979409e1eda225a8a35bed2d135ffdcea06ce7b0 + languageName: node + linkType: hard + +"tapable@npm:^2.2.0, tapable@npm:^2.2.1, tapable@npm:^2.3.0": + version: 2.3.0 + resolution: "tapable@npm:2.3.0" + checksum: 10/496a841039960533bb6e44816a01fffc2a1eb428bb2051ecab9e87adf07f19e1f937566cbbbb09dceff31163c0ffd81baafcad84db900b601f0155dd0b37e9f2 + languageName: node + linkType: hard + +"tar-fs@npm:^2.1.0": + version: 2.1.4 + resolution: "tar-fs@npm:2.1.4" + dependencies: + chownr: "npm:^1.1.1" + mkdirp-classic: "npm:^0.5.2" + pump: "npm:^3.0.0" + tar-stream: "npm:^2.1.4" + checksum: 10/bdf7e3cb039522e39c6dae3084b1bca8d7bcc1de1906eae4a1caea6a2250d22d26dcc234118bf879b345d91ebf250a744b196e379334a4abcbb109a78db7d3be + languageName: node + linkType: hard + +"tar-stream@npm:^2.1.4, tar-stream@npm:^2.2.0": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: "npm:^4.0.3" + end-of-stream: "npm:^1.4.1" + fs-constants: "npm:^1.0.0" + inherits: "npm:^2.0.3" + readable-stream: "npm:^3.1.1" + checksum: 10/1a52a51d240c118cbcd30f7368ea5e5baef1eac3e6b793fb1a41e6cd7319296c79c0264ccc5859f5294aa80f8f00b9239d519e627b9aade80038de6f966fec6a + languageName: node + linkType: hard + +"tar-stream@npm:^3.1.7": + version: 3.1.7 + resolution: "tar-stream@npm:3.1.7" + dependencies: + b4a: "npm:^1.6.4" + fast-fifo: "npm:^1.2.0" + streamx: "npm:^2.15.0" + checksum: 10/b21a82705a72792544697c410451a4846af1f744176feb0ff11a7c3dd0896961552e3def5e1c9a6bbee4f0ae298b8252a1f4c9381e9f991553b9e4847976f05c + languageName: node + linkType: hard + +"tar@npm:^7.5.2": + version: 7.5.2 + resolution: "tar@npm:7.5.2" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10/dbad9c9a07863cd1bdf8801d563b3280aa7dd0f4a6cead779ff7516d148dc80b4c04639ba732d47f91f04002f57e8c3c6573a717d649daecaac74ce71daa7ad3 + languageName: node + linkType: hard + +"terser-webpack-plugin@npm:^5.3.11": + version: 5.3.16 + resolution: "terser-webpack-plugin@npm:5.3.16" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.25" + jest-worker: "npm:^27.4.5" + schema-utils: "npm:^4.3.0" + serialize-javascript: "npm:^6.0.2" + terser: "npm:^5.31.1" + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 10/09dfbff602acfa114cdd174254b69a04adbc47856021ab351e37982202fd1ec85e0b62ffd5864c98beb8e96aef2f43da490b3448b4541db539c2cff6607394a6 + languageName: node + linkType: hard + +"terser@npm:^5.31.1": + version: 5.44.1 + resolution: "terser@npm:5.44.1" + dependencies: + "@jridgewell/source-map": "npm:^0.3.3" + acorn: "npm:^8.15.0" + commander: "npm:^2.20.0" + source-map-support: "npm:~0.5.20" + bin: + terser: bin/terser + checksum: 10/516ece205b7db778c4eddb287a556423cb776b7ca591b06270e558a76aa2d57c8d71d9c3c4410b276d3426beb03516fff7d96ff8b517e10730a72908810c6e33 + languageName: node + linkType: hard + +"text-decoder@npm:^1.1.0": + version: 1.2.3 + resolution: "text-decoder@npm:1.2.3" + dependencies: + b4a: "npm:^1.6.4" + checksum: 10/bcdec33c0f070aeac38e46e4cafdcd567a58473ed308bdf75260bfbd8f7dc76acbc0b13226afaec4a169d0cb44cec2ab89c57b6395ccf02e941eaebbe19e124a + languageName: node + linkType: hard + +"text-hex@npm:1.0.x": + version: 1.0.0 + resolution: "text-hex@npm:1.0.0" + checksum: 10/1138f68adc97bf4381a302a24e2352f04992b7b1316c5003767e9b0d3367ffd0dc73d65001ea02b07cd0ecc2a9d186de0cf02f3c2d880b8a522d4ccb9342244a + languageName: node + linkType: hard + +"thread-stream@npm:^3.0.0": + version: 3.1.0 + resolution: "thread-stream@npm:3.1.0" + dependencies: + real-require: "npm:^0.2.0" + checksum: 10/ea2d816c4f6077a7062fac5414a88e82977f807c82ee330938fb9691fe11883bb03f078551c0518bb649c239e47ba113d44014fcbb5db42c5abd5996f35e4213 + languageName: node + linkType: hard + +"through@npm:2, through@npm:^2.3.8": + version: 2.3.8 + resolution: "through@npm:2.3.8" + checksum: 10/5da78346f70139a7d213b65a0106f3c398d6bc5301f9248b5275f420abc2c4b1e77c2abc72d218dedc28c41efb2e7c312cb76a7730d04f9c2d37d247da3f4198 + languageName: node + linkType: hard + +"tiny-emitter@npm:^2.1.0": + version: 2.1.0 + resolution: "tiny-emitter@npm:2.1.0" + checksum: 10/75633f4de4f47f43af56aff6162f25b87be7efc6f669fda256658f3c3f4a216f23dc0d13200c6fafaaf1b0c7142f0201352fb06aec0b77f68aea96be898f4516 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10/d72bd826a8b0fa5fa3929e7fe5ba48fceb2ae495df3a231b6c5408cd7d8c00b58ab5a9c2a76ba56a62ee9b5e083626f1f33599734bed1ffc4b792406408f0ca2 + languageName: node + linkType: hard + +"tlds@npm:1.261.0": + version: 1.261.0 + resolution: "tlds@npm:1.261.0" + bin: + tlds: bin.js + checksum: 10/2dfb8c7e0c0c1fe8fd9966ead43f71c36c974aa498b4e290fb383ceb3be3b20b9517041560b966993fb30abd451a83bf37119fa8d99809ce01bbf5b21dd6c6d3 + languageName: node + linkType: hard + +"tmp@npm:^0.2.0": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10/dd4b78b32385eab4899d3ae296007b34482b035b6d73e1201c4a9aede40860e90997a1452c65a2d21aee73d53e93cd167d741c3db4015d90e63b6d568a93d7ec + languageName: node + linkType: hard + +"to-buffer@npm:^1.2.0": + version: 1.2.2 + resolution: "to-buffer@npm:1.2.2" + dependencies: + isarray: "npm:^2.0.5" + safe-buffer: "npm:^5.2.1" + typed-array-buffer: "npm:^1.0.3" + checksum: 10/69d806c20524ff1e4c44d49276bc96ff282dcae484780a3974e275dabeb75651ea430b074a2a4023701e63b3e1d87811cd82c0972f35280fe5461710e4872aba + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: "npm:^7.0.0" + checksum: 10/10dda13571e1f5ad37546827e9b6d4252d2e0bc176c24a101252153ef435d83696e2557fe128c4678e4e78f5f01e83711c703eef9814eb12dab028580d45980a + languageName: node + linkType: hard + +"toidentifier@npm:~1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 10/952c29e2a85d7123239b5cfdd889a0dde47ab0497f0913d70588f19c53f7e0b5327c95f4651e413c74b785147f9637b17410ac8c846d5d4a20a5a33eb6dc3a45 + languageName: node + linkType: hard + +"token-stream@npm:1.0.0": + version: 1.0.0 + resolution: "token-stream@npm:1.0.0" + checksum: 10/e8adb56f31b813b6157130e7fc2fe14eb60e7cbf7b746e70e8293c7e55664d8e7ad5d93d7ae3aa4cad7fcb2b0aaf59dad6f2fd4ee0269204e55af5b05bc369e2 + languageName: node + linkType: hard + +"token-types@npm:^6.0.0, token-types@npm:^6.1.1": + version: 6.1.2 + resolution: "token-types@npm:6.1.2" + dependencies: + "@borewit/text-codec": "npm:^0.2.1" + "@tokenizer/token": "npm:^0.3.0" + ieee754: "npm:^1.2.1" + checksum: 10/0c7811a2da5a0ca474c795d883d871a184d1d54f67058d66084110f0b246fff66151885dbcb91d66533e776478bf57f3b4fac69ce03b805a0e1060def87947de + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10/8f1f5aa6cb232f9e1bdc86f485f916b7aa38caee8a778b378ffec0b70d9307873f253f5cbadbe2955ece2ac5c83d0dc14a77513166ccd0a0c7fe197e21396695 + languageName: node + linkType: hard + +"traverse@npm:>=0.3.0 <0.4": + version: 0.3.9 + resolution: "traverse@npm:0.3.9" + checksum: 10/ffbb8460a934f271b7b7ae654e676f740d81037d6c20ab9fd05781cfdf644929f494399b5cb3aa3db4ab69cbfef06ff8f885560d523ca49b7da33763f6c4c9f1 + languageName: node + linkType: hard + +"triple-beam@npm:^1.3.0": + version: 1.4.1 + resolution: "triple-beam@npm:1.4.1" + checksum: 10/2e881a3e8e076b6f2b85b9ec9dd4a900d3f5016e6d21183ed98e78f9abcc0149e7d54d79a3f432b23afde46b0885bdcdcbff789f39bc75de796316961ec07f61 + languageName: node + linkType: hard + +"ts-api-utils@npm:^2.4.0": + version: 2.4.0 + resolution: "ts-api-utils@npm:2.4.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10/d6b2b3b6caad8d2f4ddc0c3785d22bb1a6041773335a1c71d73a5d67d11d993763fe8e4faefc4a4d03bb42b26c6126bbcf2e34826baed1def5369d0ebad358fa + languageName: node + linkType: hard + +"ts-node@npm:^10.9.2": + version: 10.9.2 + resolution: "ts-node@npm:10.9.2" + dependencies: + "@cspotcode/source-map-support": "npm:^0.8.0" + "@tsconfig/node10": "npm:^1.0.7" + "@tsconfig/node12": "npm:^1.0.7" + "@tsconfig/node14": "npm:^1.0.0" + "@tsconfig/node16": "npm:^1.0.2" + acorn: "npm:^8.4.1" + acorn-walk: "npm:^8.1.1" + arg: "npm:^4.1.0" + create-require: "npm:^1.1.0" + diff: "npm:^4.0.1" + make-error: "npm:^1.1.1" + v8-compile-cache-lib: "npm:^3.0.1" + yn: "npm:3.1.1" + peerDependencies: + "@swc/core": ">=1.2.50" + "@swc/wasm": ">=1.2.50" + "@types/node": "*" + typescript: ">=2.7" + peerDependenciesMeta: + "@swc/core": + optional: true + "@swc/wasm": + optional: true + bin: + ts-node: dist/bin.js + ts-node-cwd: dist/bin-cwd.js + ts-node-esm: dist/bin-esm.js + ts-node-script: dist/bin-script.js + ts-node-transpile-only: dist/bin-transpile.js + ts-script: dist/bin-script-deprecated.js + checksum: 10/a91a15b3c9f76ac462f006fa88b6bfa528130dcfb849dd7ef7f9d640832ab681e235b8a2bc58ecde42f72851cc1d5d4e22c901b0c11aa51001ea1d395074b794 + languageName: node + linkType: hard + +"tsconfig-paths-webpack-plugin@npm:4.2.0": + version: 4.2.0 + resolution: "tsconfig-paths-webpack-plugin@npm:4.2.0" + dependencies: + chalk: "npm:^4.1.0" + enhanced-resolve: "npm:^5.7.0" + tapable: "npm:^2.2.1" + tsconfig-paths: "npm:^4.1.2" + checksum: 10/946f23a38a404bf2d3803b60b5af1d7a6cc85bed411c9feefa707656efd9007cdcee7eb0e860ca8690ba479810c7b94ce026f6ac70daa6c803e55aac809c86c4 + languageName: node + linkType: hard + +"tsconfig-paths@npm:4.2.0, tsconfig-paths@npm:^4.1.2": + version: 4.2.0 + resolution: "tsconfig-paths@npm:4.2.0" + dependencies: + json5: "npm:^2.2.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10/5e55cc2fb6b800eb72011522e10edefccb45b1f9af055681a51354c9b597d1390c6fa9cc356b8c7529f195ac8a90a78190d563159f3a1eed10e01bbd4d01a8ab + languageName: node + linkType: hard + +"tslib@npm:2.8.1, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.6.2, tslib@npm:^2.8.1": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10/3e2e043d5c2316461cb54e5c7fe02c30ef6dccb3384717ca22ae5c6b5bc95232a6241df19c622d9c73b809bea33b187f6dbc73030963e29950c2141bc32a79f7 + languageName: node + linkType: hard + +"twilio@npm:^5.7.0": + version: 5.11.2 + resolution: "twilio@npm:5.11.2" + dependencies: + axios: "npm:^1.12.0" + dayjs: "npm:^1.11.9" + https-proxy-agent: "npm:^5.0.0" + jsonwebtoken: "npm:^9.0.2" + qs: "npm:^6.9.4" + scmp: "npm:^2.1.0" + xmlbuilder: "npm:^13.0.2" + checksum: 10/f56899942ad395d1adc55ebc7efa5658eb09e64400b3ca9bd6afd248b8e36172b9c4a09232d043524d35f8214b264f08c3163d75a9c97c71c88109cab5515030 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10/14687776479d048e3c1dbfe58a2409e00367810d6960c0f619b33793271ff2a27f81b52461f14a162f1f89a9b1d8da1b237fc7c99b0e1fdcec28ec63a86b1fec + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 10/8907e16284b2d6cfa4f4817e93520121941baba36b39219ea36acfe64c86b9dbc10c9941af450bd60832c8f43464974d51c0957f9858bc66b952b66b6914cbb9 + languageName: node + linkType: hard + +"type-is@npm:^1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: 10/0bd9eeae5efd27d98fd63519f999908c009e148039d8e7179a074f105362d4fcc214c38b24f6cda79c87e563cbd12083a4691381ed28559220d4a10c2047bed4 + languageName: node + linkType: hard + +"type-is@npm:^2.0.1": + version: 2.0.1 + resolution: "type-is@npm:2.0.1" + dependencies: + content-type: "npm:^1.0.5" + media-typer: "npm:^1.1.0" + mime-types: "npm:^3.0.0" + checksum: 10/bacdb23c872dacb7bd40fbd9095e6b2fca2895eedbb689160c05534d7d4810a7f4b3fd1ae87e96133c505958f6d602967a68db5ff577b85dd6be76eaa75d58af + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10/3fb91f0735fb413b2bbaaca9fabe7b8fc14a3fa5a5a7546bab8a57e755be0e3788d893195ad9c2b842620592de0e68d4c077d4c2c41f04ec25b8b5bb82fa9a80 + languageName: node + linkType: hard + +"typed-duration@npm:^1.0.12": + version: 1.0.13 + resolution: "typed-duration@npm:1.0.13" + checksum: 10/d9f1c6a754711a6f90209e42a8c3eca9be18591f94e3591bd47b90888d68d999f6eee3b73d9b4615cae3140ea76ae4f295ed7bcfa3e703e8ec7b600b62386606 + languageName: node + linkType: hard + +"typed-emitter@npm:^2.1.0": + version: 2.1.0 + resolution: "typed-emitter@npm:2.1.0" + dependencies: + rxjs: "npm:*" + dependenciesMeta: + rxjs: + optional: true + checksum: 10/95821a9e05784b972cc9d152891fd12a56cb4b1a7c57e768c02bea6a8984da7aff8f19404a7b69eea11fae2a3b6c0c510a4c510f575f50162c759ae9059f2520 + languageName: node + linkType: hard + +"typed-env@npm:^2.0.0": + version: 2.0.0 + resolution: "typed-env@npm:2.0.0" + checksum: 10/cae6c8928aaa0a5dd4a60079ff9e4113ba56f7276b368794e6e97eba298c7a7d02f0097e93dc053a2c7e8f5137f9227951819c5be539deb2f882859fa3100427 + languageName: node + linkType: hard + +"typed-function@npm:^4.2.1": + version: 4.2.2 + resolution: "typed-function@npm:4.2.2" + checksum: 10/6d0eb312d2fa2c4b9af7a6cba5f3e9123e1e7c0b2521a7cd30a1140f862ffb94ad44353b2798a8c160fb40a9b341f7870d3c05589a7b54472514ff6f1b12e6ac + languageName: node + linkType: hard + +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 10/2cc1bcf7d8c1237f6a16c04efc06637b2c5f2d74e58e84665445cf87668b85a21ab18dd751fa49eee6ae024b70326635d7b79ad37b1c370ed2fec6aeeeb52714 + languageName: node + linkType: hard + +"typeorm-naming-strategies@npm:^4.1.0": + version: 4.1.0 + resolution: "typeorm-naming-strategies@npm:4.1.0" + peerDependencies: + typeorm: ^0.2.0 || ^0.3.0 + checksum: 10/9654f386915532b134e00d10fa50b75d2c63d462a4acafad8d67071548cb41447b67bc5029ea07afae043f504a3e76c0f7526fc16c40081a70ade2a862a92164 + languageName: node + linkType: hard + +"typeorm@npm:^0.3.24": + version: 0.3.28 + resolution: "typeorm@npm:0.3.28" + dependencies: + "@sqltools/formatter": "npm:^1.2.5" + ansis: "npm:^4.2.0" + app-root-path: "npm:^3.1.0" + buffer: "npm:^6.0.3" + dayjs: "npm:^1.11.19" + debug: "npm:^4.4.3" + dedent: "npm:^1.7.0" + dotenv: "npm:^16.6.1" + glob: "npm:^10.5.0" + reflect-metadata: "npm:^0.2.2" + sha.js: "npm:^2.4.12" + sql-highlight: "npm:^6.1.0" + tslib: "npm:^2.8.1" + uuid: "npm:^11.1.0" + yargs: "npm:^17.7.2" + peerDependencies: + "@google-cloud/spanner": ^5.18.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + "@sap/hana-client": ^2.14.22 + better-sqlite3: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 + ioredis: ^5.0.4 + mongodb: ^5.8.0 || ^6.0.0 + mssql: ^9.1.1 || ^10.0.0 || ^11.0.0 || ^12.0.0 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 || ^5.0.14 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 || ^3.0.0 + peerDependenciesMeta: + "@google-cloud/spanner": + optional: true + "@sap/hana-client": + optional: true + better-sqlite3: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + bin: + typeorm: cli.js + typeorm-ts-node-commonjs: cli-ts-node-commonjs.js + typeorm-ts-node-esm: cli-ts-node-esm.js + checksum: 10/4eb217d65414291fb226267d903d123a16a9eb090b4e8f8da2dfe2f64680265823bea3712d363d31fe96c6dc2cef00edd545f42e63ec65b8a1899a1b455f757b + languageName: node + linkType: hard + +"typescript-eslint@npm:^8.33.0": + version: 8.53.0 + resolution: "typescript-eslint@npm:8.53.0" + dependencies: + "@typescript-eslint/eslint-plugin": "npm:8.53.0" + "@typescript-eslint/parser": "npm:8.53.0" + "@typescript-eslint/typescript-estree": "npm:8.53.0" + "@typescript-eslint/utils": "npm:8.53.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10/b4731161a4fec6ce9110e54407a50733e15b0eb48cb9636906a54068af10f8102a7018b9e5db6264604050955a03d3649a79c4869d43bcce215358a8a8a03f96 + languageName: node + linkType: hard + +"typescript@npm:5.9.3, typescript@npm:^5.8.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/c089d9d3da2729fd4ac517f9b0e0485914c4b3c26f80dc0cffcb5de1719a17951e92425d55db59515c1a7ddab65808466debb864d0d56dcf43f27007d0709594 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A5.9.3#optional!builtin, typescript@patch:typescript@npm%3A^5.8.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10/696e1b017bc2635f4e0c94eb4435357701008e2f272f553d06e35b494b8ddc60aa221145e286c28ace0c89ee32827a28c2040e3a69bdc108b1a5dc8fb40b72e3 + languageName: node + linkType: hard + +"uc.micro@npm:^2.0.0": + version: 2.1.0 + resolution: "uc.micro@npm:2.1.0" + checksum: 10/37197358242eb9afe367502d4638ac8c5838b78792ab218eafe48287b0ed28aaca268ec0392cc5729f6c90266744de32c06ae938549aee041fc93b0f9672d6b2 + languageName: node + linkType: hard + +"uglify-js@npm:^3.1.4, uglify-js@npm:^3.5.1": + version: 3.19.3 + resolution: "uglify-js@npm:3.19.3" + bin: + uglifyjs: bin/uglifyjs + checksum: 10/6b9639c1985d24580b01bb0ab68e78de310d38eeba7db45bec7850ab4093d8ee464d80ccfaceda9c68d1c366efbee28573b52f95e69ac792354c145acd380b11 + languageName: node + linkType: hard + +"uid@npm:2.0.2": + version: 2.0.2 + resolution: "uid@npm:2.0.2" + dependencies: + "@lukeed/csprng": "npm:^1.0.0" + checksum: 10/18f6da43d8e1b8643077e8123f877b4506759d9accc15337140a1bf7c99f299a66e88b27ab4c640e66e6a10f19e3a85afa45fdf830dd4bab7570d07a3d51e073 + languageName: node + linkType: hard + +"uint8array-extras@npm:^1.4.0": + version: 1.5.0 + resolution: "uint8array-extras@npm:1.5.0" + checksum: 10/94fd56a2dda6a7445f5176f301f491814c87757d38e4b3c932299ab54d69ec504830e5d5c18ffa20cf694a69a210315be8b4a2c9952c6334da817ea2d2e1dce0 + languageName: node + linkType: hard + +"unbzip2-stream@npm:^1.4.3": + version: 1.4.3 + resolution: "unbzip2-stream@npm:1.4.3" + dependencies: + buffer: "npm:^5.2.1" + through: "npm:^2.3.8" + checksum: 10/4ffc0e14f4af97400ed0f37be83b112b25309af21dd08fa55c4513e7cb4367333f63712aec010925dbe491ef6e92db1248e1e306e589f9f6a8da8b3a9c4db90b + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10/ec8f41aa4359d50f9b59fa61fe3efce3477cc681908c8f84354d8567bb3701fafdddf36ef6bff307024d3feb42c837cf6f670314ba37fc8145e219560e473d14 + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10/db43439f69c2d94cc29f75cbfe9de86df87061d6b0c577ebe9bb3255f49b22c50162a7d7eb413b0458b6510b8ca299ac7cff38c3a29fbd31af9f504bcf7fbc0d + languageName: node + linkType: hard + +"unescape-js@npm:^1.1.4": + version: 1.1.4 + resolution: "unescape-js@npm:1.1.4" + dependencies: + string.fromcodepoint: "npm:^0.2.1" + checksum: 10/97acf60a8f6c170f8a66b48b71f5c56bda728c2ff6b08c3443c5f21635bf5fa38a4265bcfcf46d17cb6ac9bbb8b913a34b1abc5cfe8db5d7cc5c8eecb1817472 + languageName: node + linkType: hard + +"unescape@npm:^1.0.1": + version: 1.0.1 + resolution: "unescape@npm:1.0.1" + dependencies: + extend-shallow: "npm:^2.0.1" + checksum: 10/0d89b0f55e08a2843e635f1ccf8472a35b367c41d9a8014dd7de5cc3af710a6e988a950b86b6229e143147ade21772f2d72054bc846f4972eb448df472b856ec + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10/a5f67085caef74bdd2a6869a200ed5d68d171f5cc38435a836b5fd12cce4e4eb55e6a190298035c325053a5687ed7a3c96f0a91e82215fd14729769d9ac57d9b + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10/b78ed9d5b01ff465f80975f17387750ed3639909ac487fa82c4ae4326759f6de87c2131c0c39eca4c68cf06c537a8d104fba1dfc8a30308f99bc505345e1eba3 + languageName: node + linkType: hard + +"universalify@npm:^0.1.0": + version: 0.1.2 + resolution: "universalify@npm:0.1.2" + checksum: 10/40cdc60f6e61070fe658ca36016a8f4ec216b29bf04a55dce14e3710cc84c7448538ef4dad3728d0bfe29975ccd7bfb5f414c45e7b78883567fb31b246f02dff + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.1 + resolution: "universalify@npm:2.0.1" + checksum: 10/ecd8469fe0db28e7de9e5289d32bd1b6ba8f7183db34f3bfc4ca53c49891c2d6aa05f3fb3936a81285a905cc509fb641a0c3fc131ec786167eff41236ae32e60 + languageName: node + linkType: hard + +"unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 10/4fa18d8d8d977c55cb09715385c203197105e10a6d220087ec819f50cb68870f02942244f1017565484237f1f8c5d3cd413631b1ae104d3096f24fdfde1b4aa2 + languageName: node + linkType: hard + +"unzipper@npm:^0.10.11": + version: 0.10.14 + resolution: "unzipper@npm:0.10.14" + dependencies: + big-integer: "npm:^1.6.17" + binary: "npm:~0.3.0" + bluebird: "npm:~3.4.1" + buffer-indexof-polyfill: "npm:~1.0.0" + duplexer2: "npm:~0.1.4" + fstream: "npm:^1.0.12" + graceful-fs: "npm:^4.2.2" + listenercount: "npm:~1.0.1" + readable-stream: "npm:~2.3.6" + setimmediate: "npm:~1.0.4" + checksum: 10/3f7b44f3c7253bc08da2988baf559f00b261c5340625e6e5206c5d73b4dea409b89caae4048346cf9f215d3cdf930e3bdee98edac5e0abc843eed765c52b398d + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10/059f774300efb4b084a49293143c511f3ae946d40397b5c30914e900cd5691a12b8e61b41dd54ed73d3b56c8204165a0333107dd784ccf8f8c81790bcc423175 + languageName: node + linkType: hard + +"upper-case@npm:^1.1.1": + version: 1.1.3 + resolution: "upper-case@npm:1.1.3" + checksum: 10/fc4101fdcd783ee963d49d279186688d4ba2fab90e78dbd001ad141522a66ccfe310932f25e70d5211b559ab205be8c24bf9c5520c7ab7dcd0912274c6d976a3 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10/b271ca7e3d46b7160222e3afa3e531505161c9a4e097febae9664e4b59912f4cbe94861361a4175edac3a03fee99d91e44b6a58c17a634bc5a664b19fc76fbcb + languageName: node + linkType: hard + +"url-template@npm:^2.0.8": + version: 2.0.8 + resolution: "url-template@npm:2.0.8" + checksum: 10/fc6a4cf6c3c3c3d7f0a0bb4405c41b81934e583b454e52ace7b2e5d7ed32ec9c2970ff1826d240c5823955fcb13531a1fc4ff6ba4569b1886a2976665353e952 + languageName: node + linkType: hard + +"utf7@npm:>=1.0.2": + version: 1.0.2 + resolution: "utf7@npm:1.0.2" + dependencies: + semver: "npm:~5.3.0" + checksum: 10/510dacbecdbdbd9750e4372fe8477538aa90b9de93f5df24c7f0ab836f8404c8f08f5f2797c1e0d6788fa6c450445943d24c55b2f8cd3dc094748dab2ed75d8e + languageName: node + linkType: hard + +"utf8@npm:^2.1.0, utf8@npm:^2.1.1": + version: 2.1.2 + resolution: "utf8@npm:2.1.2" + checksum: 10/7b35ff1203d2e0209db7a28f6852cb64ff292a3bdc3b6a9c52ff2d69abcfeff992647e8d4dc787c4f1c9374613ad11f7e9c682df66985dfa516b8f8c69855860 + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10/474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 + languageName: node + linkType: hard + +"uuencode@npm:0.0.4": + version: 0.0.4 + resolution: "uuencode@npm:0.0.4" + checksum: 10/a76cc12fae707dc04496493d68d26d01f3c0a44528cbb10b4ca51abc98578df5b692f718c54b3072ad819e7c75dd91c3b783e4a2ac982404af77946da235c8ea + languageName: node + linkType: hard + +"uuid@npm:^11.1.0": + version: 11.1.0 + resolution: "uuid@npm:11.1.0" + bin: + uuid: dist/esm/bin/uuid + checksum: 10/d2da43b49b154d154574891ced66d0c83fc70caaad87e043400cf644423b067542d6f3eb641b7c819224a7cd3b4c2f21906acbedd6ec9c6a05887aa9115a9cf5 + languageName: node + linkType: hard + +"uuid@npm:^8.3.0": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10/9a5f7aa1d6f56dd1e8d5f2478f855f25c645e64e26e347a98e98d95781d5ed20062d6cca2eecb58ba7c84bc3910be95c0451ef4161906abaab44f9cb68ffbdd1 + languageName: node + linkType: hard + +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 10/9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 + languageName: node + linkType: hard + +"v8-compile-cache-lib@npm:^3.0.1": + version: 3.0.1 + resolution: "v8-compile-cache-lib@npm:3.0.1" + checksum: 10/88d3423a52b6aaf1836be779cab12f7016d47ad8430dffba6edf766695e6d90ad4adaa3d8eeb512cc05924f3e246c4a4ca51e089dccf4402caa536b5e5be8961 + languageName: node + linkType: hard + +"valid-data-url@npm:^3.0.0": + version: 3.0.1 + resolution: "valid-data-url@npm:3.0.1" + checksum: 10/06584294fb4c9550f0aaa56470f8d748f4ebfc3ed230707db5559754719a66fc37f299b5a79b914375b8198d90f8a51e0401375391938caf8dc8e442308aab9e + languageName: node + linkType: hard + +"validator@npm:^13.15.20": + version: 13.15.26 + resolution: "validator@npm:13.15.26" + checksum: 10/22488ae718ca724eda81b7c8bf505005d4d70cb6ff9a319f48fd897a31d40fd9a2971af4a3288667a04c56b4f95912555495519d54a5d8d63c2572bf4970081a + languageName: node + linkType: hard + +"vary@npm:^1, vary@npm:^1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: 10/31389debef15a480849b8331b220782230b9815a8e0dbb7b9a8369559aed2e9a7800cd904d4371ea74f4c3527db456dc8e7ac5befce5f0d289014dbdf47b2242 + languageName: node + linkType: hard + +"void-elements@npm:^3.1.0": + version: 3.1.0 + resolution: "void-elements@npm:3.1.0" + checksum: 10/0390f818107fa8fce55bb0a5c3f661056001c1d5a2a48c28d582d4d847347c2ab5b7f8272314cac58acf62345126b6b09bea623a185935f6b1c3bbce0dfd7f7f + languageName: node + linkType: hard + +"walk-up-path@npm:^4.0.0": + version: 4.0.0 + resolution: "walk-up-path@npm:4.0.0" + checksum: 10/6a230b20e5de296895116dc12b09dafaec1f72b8060c089533d296e241aff059dfaebe0d015c77467f857e4b40c78e08f7481add76f340233a1f34fa8af9ed63 + languageName: node + linkType: hard + +"watchpack@npm:^2.4.4": + version: 2.5.0 + resolution: "watchpack@npm:2.5.0" + dependencies: + glob-to-regexp: "npm:^0.4.1" + graceful-fs: "npm:^4.1.2" + checksum: 10/6793335adecc06944430db1c9dbbb960d357019a3456fa2a5e38a96c0320a9d4444198f3d4a513c9b58306ddd89f2a1754f99056e4b71c512260436287c58361 + languageName: node + linkType: hard + +"wcwidth@npm:^1.0.1": + version: 1.0.1 + resolution: "wcwidth@npm:1.0.1" + dependencies: + defaults: "npm:^1.0.3" + checksum: 10/182ebac8ca0b96845fae6ef44afd4619df6987fe5cf552fdee8396d3daa1fb9b8ec5c6c69855acb7b3c1231571393bd1f0a4cdc4028d421575348f64bb0a8817 + languageName: node + linkType: hard + +"web-resource-inliner@npm:^6.0.1": + version: 6.0.1 + resolution: "web-resource-inliner@npm:6.0.1" + dependencies: + ansi-colors: "npm:^4.1.1" + escape-goat: "npm:^3.0.0" + htmlparser2: "npm:^5.0.0" + mime: "npm:^2.4.6" + node-fetch: "npm:^2.6.0" + valid-data-url: "npm:^3.0.0" + checksum: 10/179ee600b6d73d889ff4a9944553b6096384b837f97b75cddde3a15bb8847a6116d4bfc4bd54ca171bd34f81cb7c8b669162433207fb748f3e2704e63e3a97f9 + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10/b65b9f8d6854572a84a5c69615152b63371395f0c5dcd6729c45789052296df54314db2bc3e977df41705eacb8bc79c247cee139a63fa695192f95816ed528ad + languageName: node + linkType: hard + +"webpack-node-externals@npm:3.0.0": + version: 3.0.0 + resolution: "webpack-node-externals@npm:3.0.0" + checksum: 10/1a08102f73be2d6e787d16cf677f98c413076f35f379d64a4c83aa83769099b38091a4592953fac5b2eb0c7e3eb1977f6b901ef2cba531d458e32665314b8025 + languageName: node + linkType: hard + +"webpack-sources@npm:^3.3.3": + version: 3.3.3 + resolution: "webpack-sources@npm:3.3.3" + checksum: 10/ec5d72607e8068467370abccbfff855c596c098baedbe9d198a557ccf198e8546a322836a6f74241492576adba06100286592993a62b63196832cdb53c8bae91 + languageName: node + linkType: hard + +"webpack@npm:5.103.0": + version: 5.103.0 + resolution: "webpack@npm:5.103.0" + dependencies: + "@types/eslint-scope": "npm:^3.7.7" + "@types/estree": "npm:^1.0.8" + "@types/json-schema": "npm:^7.0.15" + "@webassemblyjs/ast": "npm:^1.14.1" + "@webassemblyjs/wasm-edit": "npm:^1.14.1" + "@webassemblyjs/wasm-parser": "npm:^1.14.1" + acorn: "npm:^8.15.0" + acorn-import-phases: "npm:^1.0.3" + browserslist: "npm:^4.26.3" + chrome-trace-event: "npm:^1.0.2" + enhanced-resolve: "npm:^5.17.3" + es-module-lexer: "npm:^1.2.1" + eslint-scope: "npm:5.1.1" + events: "npm:^3.2.0" + glob-to-regexp: "npm:^0.4.1" + graceful-fs: "npm:^4.2.11" + json-parse-even-better-errors: "npm:^2.3.1" + loader-runner: "npm:^4.3.1" + mime-types: "npm:^2.1.27" + neo-async: "npm:^2.6.2" + schema-utils: "npm:^4.3.3" + tapable: "npm:^2.3.0" + terser-webpack-plugin: "npm:^5.3.11" + watchpack: "npm:^2.4.4" + webpack-sources: "npm:^3.3.3" + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 10/0018e77d159da412aa8cc1c3ac1d7c0b44228d0f5ce3939b4f424c04feba69747d8490541bcf8143b358a64afbbd69daad95e573ec9c4a90a99bef55d51dd43e + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10/f95adbc1e80820828b45cc671d97da7cd5e4ef9deb426c31bcd5ab00dc7103042291613b3ef3caec0a2335ed09e0d5ed026c940755dbb6d404e2b27f940fdf07 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10/12be30fb88567f9863186bee1777f11bea09dd59ed8b3ce4afa7dd5cade75e2f4cc56191a2da165113cc7cf79987ba021dac1e22b5b62aa7e5c56949f2469a68 + languageName: node + linkType: hard + +"which@npm:^1.2.9": + version: 1.3.1 + resolution: "which@npm:1.3.1" + dependencies: + isexe: "npm:^2.0.0" + bin: + which: ./bin/which + checksum: 10/549dcf1752f3ee7fbb64f5af2eead4b9a2f482108b7de3e85c781d6c26d8cf6a52d37cfbe0642a155fa6470483fe892661a859c03157f24c669cf115f3bbab5e + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10/4782f8a1d6b8fc12c65e968fea49f59752bf6302dc43036c3bf87da718a80710f61a062516e9764c70008b487929a73546125570acea95c5b5dcc8ac3052c70f + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10/df19b2cd8aac94b333fa29b42e8e371a21e634a742a3b156716f7752a5afe1d73fb5d8bce9b89326f453d96879e8fe626eb421e0117eb1a3ce9fd8c97f6b7db9 + languageName: node + linkType: hard + +"widest-line@npm:^3.1.0": + version: 3.1.0 + resolution: "widest-line@npm:3.1.0" + dependencies: + string-width: "npm:^4.0.0" + checksum: 10/03db6c9d0af9329c37d74378ff1d91972b12553c7d72a6f4e8525fe61563fa7adb0b9d6e8d546b7e059688712ea874edd5ded475999abdeedf708de9849310e0 + languageName: node + linkType: hard + +"win-ca@npm:3.5.1": + version: 3.5.1 + resolution: "win-ca@npm:3.5.1" + dependencies: + is-electron: "npm:^2.2.0" + make-dir: "npm:^1.3.0" + node-forge: "npm:^1.2.1" + split: "npm:^1.0.1" + checksum: 10/e7b8f3ddbe3c989eb19e53143b54dcec9c7d11f06ff373cb70a7310aaecef6c6a2e2c229a8300f8baa974f235169a179f046f4a3d3e93e7d4a285f774f70e489 + languageName: node + linkType: hard + +"winston-transport@npm:^4.5.0, winston-transport@npm:^4.9.0": + version: 4.9.0 + resolution: "winston-transport@npm:4.9.0" + dependencies: + logform: "npm:^2.7.0" + readable-stream: "npm:^3.6.2" + triple-beam: "npm:^1.3.0" + checksum: 10/5946918720baadd7447823929e94cf0935f92c4cff6d9451c6fcb009bd9d20a3b3df9ad606109e79d1e9f4d2ff678477bf09f81cfefce2025baaf27a617129bb + languageName: node + linkType: hard + +"winston@npm:^3.14.2, winston@npm:^3.17.0": + version: 3.19.0 + resolution: "winston@npm:3.19.0" + dependencies: + "@colors/colors": "npm:^1.6.0" + "@dabh/diagnostics": "npm:^2.0.8" + async: "npm:^3.2.3" + is-stream: "npm:^2.0.0" + logform: "npm:^2.7.0" + one-time: "npm:^1.0.0" + readable-stream: "npm:^3.4.0" + safe-stable-stringify: "npm:^2.3.1" + stack-trace: "npm:0.0.x" + triple-beam: "npm:^1.3.0" + winston-transport: "npm:^4.9.0" + checksum: 10/8279e221d8017da601a725939d31d65de71504d8328051312a85b1b4d7ddc68634329f8d611fb1ff91cb797643409635f3e97ef5b4a650c587639e080af76b7b + languageName: node + linkType: hard + +"with@npm:^7.0.0": + version: 7.0.2 + resolution: "with@npm:7.0.2" + dependencies: + "@babel/parser": "npm:^7.9.6" + "@babel/types": "npm:^7.9.6" + assert-never: "npm:^1.2.1" + babel-walk: "npm:3.0.0-canary-5" + checksum: 10/06ad978f9ac11268186060af7e19ff33bcfe1631ecbce4c676cb43168e3d6e62a5c6e697bd6dda8570c93b117a9b12980061ff66c3428a4e5cdcf0637e8ed81f + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10/1ec6f6089f205f83037be10d0c4b34c9183b0b63fca0834a5b3cee55dd321429d73d40bb44c8fc8471b5203d6e8f8275717f49a8ff4b2b0ab41d7e1b563e0854 + languageName: node + linkType: hard + +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 10/497d40beb2bdb08e6d38754faa17ce20b0bf1306327f80cb777927edb23f461ee1f6bc659b3c3c93f26b08e1cf4b46acc5bae8fda1f0be3b5ab9a1a0211034cd + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10/cebdaeca3a6880da410f75209e68cd05428580de5ad24535f22696d7d9cab134d1f8498599f344c3cf0fb37c1715807a183778d8c648d6cc0cb5ff2bb4236540 + languageName: node + linkType: hard + +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10/0d64f2d438e0b555e693b95aee7b2689a12c3be5ac458192a1ce28f542a6e9e59ddfecc37520910c2c88eb1f82a5411260566dba5064e8f9895e76e169e76187 + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 10/7b1e4b35e9bb2312d2ee9ee7dc95b8cb5f8b4b5a89f7dde5543fe66c1e3715663094defa50d75454ac900bd210f702d575f15f3f17fa9ec0291806d2578d1ddf + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 10/159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 + languageName: node + linkType: hard + +"written-number@npm:^0.11.1": + version: 0.11.1 + resolution: "written-number@npm:0.11.1" + checksum: 10/6e5d82a42ff77a5b85e8f0b29fb5d1a5c264ef1b3272b5671373eae7fecb0b0cc106afc5582e5e87ee5dee3d0986b49436a7fd9dc9b362a52b36840eaecec44e + languageName: node + linkType: hard + +"ws@npm:^8.17.1": + version: 8.19.0 + resolution: "ws@npm:8.19.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/26e4901e93abaf73af9f26a93707c95b4845e91a7a347ec8c569e6e9be7f9df066f6c2b817b2d685544e208207898a750b78461e6e8d810c11a370771450c31b + languageName: node + linkType: hard + +"ws@npm:~8.18.3": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10/725964438d752f0ab0de582cd48d6eeada58d1511c3f613485b5598a83680bedac6187c765b0fe082e2d8cc4341fc57707c813ae780feee82d0c5efe6a4c61b6 + languageName: node + linkType: hard + +"xmlbuilder@npm:^13.0.2": + version: 13.0.2 + resolution: "xmlbuilder@npm:13.0.2" + checksum: 10/84671f47fbc28e56dc2fca9b29a2a62e3c2e28f5fcaa986b11c9a7df7385fc921e923cd1c3da83ae0d58b255bc2369feffdcd20600b4125391c79be02eedca15 + languageName: node + linkType: hard + +"xmlchars@npm:^2.2.0": + version: 2.2.0 + resolution: "xmlchars@npm:2.2.0" + checksum: 10/4ad5924974efd004a47cce6acf5c0269aee0e62f9a805a426db3337af7bcbd331099df174b024ace4fb18971b8a56de386d2e73a1c4b020e3abd63a4a9b917f1 + languageName: node + linkType: hard + +"xtend@npm:^4.0.0, xtend@npm:^4.0.2": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 10/ac5dfa738b21f6e7f0dd6e65e1b3155036d68104e67e5d5d1bde74892e327d7e5636a076f625599dc394330a731861e87343ff184b0047fef1360a7ec0a5a36a + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 10/5f1b5f95e3775de4514edbb142398a2c37849ccfaf04a015be5d75521e9629d3be29bd4432d23c57f37e5b61ade592fb0197022e9993f81a06a5afbdcda9346d + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10/4cb02b42b8a93b5cf50caf5d8e9beb409400a8a4d85e83bb0685c1457e9ac0b7a00819e9f5991ac25ffabb56a78e2f017c1acc010b3a1babfe6de690ba531abd + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10/1884d272d485845ad04759a255c71775db0fac56308764b4c77ea56a20d56679fad340213054c8c9c9c26fcfd4c4b2a90df993b7e0aaf3cdb73c618d1d1a802a + languageName: node + linkType: hard + +"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: 10/9dc2c217ea3bf8d858041252d43e074f7166b53f3d010a8c711275e09cd3d62a002969a39858b92bbda2a6a63a585c7127014534a560b9c69ed2d923d113406e + languageName: node + linkType: hard + +"yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10/abb3e37678d6e38ea85485ed86ebe0d1e3464c640d7d9069805ea0da12f69d5a32df8e5625e370f9c96dd1c2dc088ab2d0a4dd32af18222ef3c4224a19471576 + languageName: node + linkType: hard + +"yauzl@npm:^3.1.2": + version: 3.2.0 + resolution: "yauzl@npm:3.2.0" + dependencies: + buffer-crc32: "npm:~0.2.3" + pend: "npm:~1.2.0" + checksum: 10/a3cd2bfcf7590673bb35750f2a4e5107e3cc939d32d98a072c0673fe42329e390f471b4a53dbbd72512229099b18aa3b79e6ddb87a73b3a17446080c903a2c4b + languageName: node + linkType: hard + +"yn@npm:3.1.1": + version: 3.1.1 + resolution: "yn@npm:3.1.1" + checksum: 10/2c487b0e149e746ef48cda9f8bad10fc83693cd69d7f9dcd8be4214e985de33a29c9e24f3c0d6bcf2288427040a8947406ab27f7af67ee9456e6b84854f02dd6 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10/f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 + languageName: node + linkType: hard + +"yoctocolors-cjs@npm:^2.1.3": + version: 2.1.3 + resolution: "yoctocolors-cjs@npm:2.1.3" + checksum: 10/b2144b38807673a4254dae06fe1a212729550609e606289c305e45c585b36fab1dbba44fe6cde90db9b28be465ec63f4c2a50867aeec6672f6bc36b6c9a361a0 + languageName: node + linkType: hard + +"zip-stream@npm:^4.1.0": + version: 4.1.1 + resolution: "zip-stream@npm:4.1.1" + dependencies: + archiver-utils: "npm:^3.0.4" + compress-commons: "npm:^4.1.2" + readable-stream: "npm:^3.6.0" + checksum: 10/33bd5ee7017656c2ad728b5d4ba510e15bd65ce1ec180c5bbdc7a5f063256353ec482e6a2bc74de7515219d8494147924b9aae16e63fdaaf37cdf7d1ee8df125 + languageName: node + linkType: hard + +"zod@npm:^4, zod@npm:^4.1.11": + version: 4.3.5 + resolution: "zod@npm:4.3.5" + checksum: 10/3148bd52e56ab7c1641ec397e6be6eddbb1d8f5db71e95baab9bb9622a0ea49d8a385885fc1c22b90fa6d8c5234e051f4ef5d469cfe3fb90198d5a91402fd89c + languageName: node + linkType: hard diff --git a/crm.mcmed.ru b/crm.mcmed.ru new file mode 100644 index 0000000..5a4f6e2 --- /dev/null +++ b/crm.mcmed.ru @@ -0,0 +1,143 @@ +upstream crm { + server 127.0.0.1:8000; +} + +# Основной HTTPS сервер с улучшенной безопасностью +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + server_name crm.mcmed.ru; + + root /opt/crm/frontend/build; + + # SSL конфигурация + ssl_certificate /etc/letsencrypt/live/crm.mcmed.ru/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/crm.mcmed.ru/privkey.pem; + ssl_trusted_certificate /etc/letsencrypt/live/crm.mcmed.ru/chain.pem; + + # Современные SSL настройки + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_stapling on; + ssl_stapling_verify on; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://www.googletagmanager.com https://www.google-analytics.com https://analytics.google.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com; img-src 'self' data: blob: http://crm.mcmed.ru https://crm.mcmed.ru https://www.google-analytics.com https://analytics.google.com https://*; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' http://crm.mcmed.ru https://crm.mcmed.ru http://crm.mcmed.ru https://crm.mcmed.ru https://www.google-analytics.com https://analytics.google.com ws: wss:; frame-src 'self' https://www.googletagmanager.com; object-src 'none'; base-uri 'self'; form-action 'self'; worker-src 'self' blob: https://cdnjs.cloudflare.com;" always; + add_header X-Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://fonts.googleapis.com https://www.googletagmanager.com https://www.google-analytics.com https://analytics.google.com https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com; img-src 'self' data: blob: http://crm.mcmed.ru https://crm.mcmed.ru https://www.google-analytics.com https://analytics.google.com https://*; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' http://crm.mcmed.ru https://crm.mcmed.ru http://crm.mcmed.ru https://crm.mcmed.ru https://www.google-analytics.com https://analytics.google.com ws: wss:; frame-src 'self' https://www.googletagmanager.com; object-src 'none'; base-uri 'self'; form-action 'self'; worker-src 'self' blob: https://cdnjs.cloudflare.com;" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=()" always; + + # Блокировка доступа к служебным файлам + location ~ /(\.env|\.git|\.svn|\.htaccess|\.htpasswd|config|backup|dump|phpinfo|wp-config) { + deny all; + access_log off; + log_not_found off; + } + + # Блокировка доступа к robots.txt + location = /robots.txt { + deny all; + access_log off; + log_not_found off; + } + + # Блокировка доступа к sitemap.xml + location = /sitemap.xml { + deny all; + access_log off; + log_not_found off; + } + + # Защита от атак на API + location /api/ { + # Ограничение методов + limit_except GET POST PUT DELETE OPTIONS { + deny all; + } + + # Rate limiting completely disabled - no limits on requests + # limit_req zone=api_limit burst=100 nodelay; + + # Connection limiting completely disabled - no limits on connections + # limit_conn conn_limit 50; + + # Large request body size allowed + #client_max_body_size 50M; + + # Generous timeout for slow clients + #client_body_timeout 30s; + + # Проксирование на бэкенд + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_buffering off; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + proxy_pass http://crm; + } + + # Основная конфигурация фронтенда + location / { + try_files $uri $uri/ /index.html; + + # Кэширование статических файлов + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + } + } + + # Блокировка доступа к favicon.ico + location = /favicon.ico { + return 404; + } + + # Защита от атак на сервер + server_tokens off; + add_header X-Powered-By "CRM Security Server" always; + + # Логирование + access_log /var/log/nginx/crm.access.log; + error_log /var/log/nginx/crm.error.log; + + # Ограничение размера тела запроса + client_max_body_size 2000M; + + # Connection limiting completely disabled - no limits on connections + # limit_conn conn_limit 50; +} + +# Сервер для Let's Encrypt ACME challenges +server { + listen 80; + listen [::]:80; + server_name crm.mcmed.ru; + root /var/www/certbot; + + location ^~ /.well-known/acme-challenge/ { + # Используем root без изменений + try_files $uri =404; + + # Отключаем логи для этих запросов + access_log off; + log_not_found off; + } + + location / { + return 301 https://$host$request_uri; + } +} diff --git a/frontend/.gitattributes b/frontend/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/frontend/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..7a3c339 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,26 @@ +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.idea +.vscode + +storybook-static diff --git a/frontend/.gitlab-ci.yml b/frontend/.gitlab-ci.yml new file mode 100644 index 0000000..a30d60d --- /dev/null +++ b/frontend/.gitlab-ci.yml @@ -0,0 +1,177 @@ +stages: + - prepare + - build + - deploy + +default: + image: kroniak/ssh-client + interruptible: true + before_script: + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config + - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa + - chmod 400 ~/.ssh/id_rsa + +# Vercel +vercel: + image: node:18-alpine + stage: deploy + dependencies: [] + rules: + - if: $CI_COMMIT_BRANCH != "main" && $CI_COMMIT_BRANCH != "develop" && $CI_COMMIT_BRANCH !~ /^release/ + when: manual + script: + - npm install --global vercel + - vercel pull --yes --environment=preview --token=$VERCEL_TOKEN + - vercel build --token=$VERCEL_TOKEN + - vercel deploy --prebuilt --token=$VERCEL_TOKEN + environment: + name: vercel + +# Storybook +storybook: + stage: deploy + rules: + - if: $CI_COMMIT_BRANCH == "develop" + when: manual + variables: + SSH_PRIVATE_KEY: $STAGING_SSH_PRIVATE_KEY + script: + - tar -czf sources.tar.gz . + - scp -r sources.tar.gz $STAGING_USER@$STAGING_SERVER:/amwork/storybook + - ssh $STAGING_USER@$STAGING_SERVER "cd /amwork/storybook && ./deploy.sh" + environment: + name: staging + url: https://amwork.dev + +.prepare_script: &prepare_script + - tar -czf sources.tar.gz . + - scp -r sources.tar.gz $USER@$SERVER:$FOLDER + +.build_script: &build_script + - ssh $USER@$SERVER "cd $FOLDER && ./build.sh" + +.deploy_script: &deploy_script + - ssh $USER@$SERVER "cd $FOLDER && ./deploy.sh && ./update-version.sh" + +.staging_job: + rules: + - if: $CI_COMMIT_BRANCH == "develop" + variables: + SSH_PRIVATE_KEY: $STAGING_SSH_PRIVATE_KEY + USER: $STAGING_USER + SERVER: $STAGING_SERVER + FOLDER: /amwork/frontend + environment: + name: staging + url: https://amwork.dev + +.amwork_job: + rules: + - if: $CI_COMMIT_BRANCH == "main" + variables: + SSH_PRIVATE_KEY: $AMWORK_PROD_SSH_PRIVATE_KEY + USER: $AMWORK_PROD_USER + SERVER: $AMWORK_PROD_SERVER + FOLDER: /amwork/frontend + environment: + name: production/amwork + url: https://amwork.com + +.mywork_job: + rules: + - if: $CI_COMMIT_BRANCH == "main" + variables: + SSH_PRIVATE_KEY: $MYWORK_PROD_SSH_PRIVATE_KEY + USER: $MYWORK_PROD_USER + SERVER: $MYWORK_PROD_SERVER + FOLDER: /mywork/frontend + environment: + name: production/mywork + url: https://mywork.app + +# Staging +staging_prepare: + extends: .staging_job + stage: prepare + script: *prepare_script +staging_build: + extends: .staging_job + stage: build + variables: + GIT_STRATEGY: none + script: *build_script +staging_deploy: + extends: .staging_job + stage: deploy + variables: + GIT_STRATEGY: none + script: *deploy_script + +# Amwork Production +amwork_prepare: + extends: .amwork_job + stage: prepare + script: *prepare_script +amwork_build: + extends: .amwork_job + stage: build + variables: + GIT_STRATEGY: none + script: *build_script +amwork_deploy: + extends: .amwork_job + stage: deploy + when: manual + variables: + GIT_STRATEGY: none + script: *deploy_script + +# Mywork Production +mywork_prepare: + extends: .mywork_job + stage: prepare + variables: + FOLDER: /apps/mywork/frontend + script: *prepare_script +mywork_build: + extends: .mywork_job + stage: build + variables: + GIT_STRATEGY: none + FOLDER: /apps/mywork/frontend + script: *build_script +mywork_deploy: + extends: .mywork_job + stage: deploy + when: manual + variables: + GIT_STRATEGY: none + FOLDER: /apps/mywork/frontend + script: *deploy_script + +# Platforma500 Production +platforma500_prepare: + extends: .mywork_job + stage: prepare + needs: [mywork_prepare] + variables: + FOLDER: /apps/platforma500/frontend + script: *prepare_script +platforma500_build: + extends: .mywork_job + stage: build + needs: [mywork_build] + variables: + GIT_STRATEGY: none + FOLDER: /apps/platforma500/frontend + script: *build_script +platforma500_deploy: + extends: .mywork_job + stage: deploy + when: manual + variables: + GIT_STRATEGY: none + FOLDER: /apps/platforma500/frontend + script: *deploy_script \ No newline at end of file diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000..e8bcf6c --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps=true +@amwork:registry=https://gitlab.amwork.app/api/v4/projects/13/packages/npm/ diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..be3f780 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,24 @@ +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.idea +.vscode \ No newline at end of file diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json new file mode 100644 index 0000000..5099e17 --- /dev/null +++ b/frontend/.prettierrc.json @@ -0,0 +1,22 @@ +{ + "tabWidth": 2, + "printWidth": 100, + "singleQuote": true, + "arrowParens": "avoid", + "trailingComma": "es5", + "plugins": ["prettier-plugin-organize-imports"], + "overrides": [ + { + "files": "*.ts", + "options": { + "parser": "typescript" + } + }, + { + "files": "*.js", + "options": { + "parser": "babel" + } + } + ] +} diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts new file mode 100644 index 0000000..2651fd6 --- /dev/null +++ b/frontend/.storybook/main.ts @@ -0,0 +1,24 @@ +import type { StorybookConfig } from '@storybook/react-vite'; + +const config: StorybookConfig = { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + + framework: { + name: '@storybook/react-vite', + options: {}, + }, + + docs: {}, + + typescript: { + reactDocgen: 'react-docgen-typescript', + }, +}; + +export default config; diff --git a/frontend/.storybook/preview.ts b/frontend/.storybook/preview.ts new file mode 100644 index 0000000..db6be1a --- /dev/null +++ b/frontend/.storybook/preview.ts @@ -0,0 +1,62 @@ +import type { Preview } from '@storybook/react'; +import { HttpStatusCode } from 'axios'; +import { I18nDecorator } from '../src/app/config/storybook/I18nDecorator'; +import { RouterDecorator } from '../src/app/config/storybook/RouterDecorator'; +import { StyleDecorator } from '../src/app/config/storybook/StyleDecorator'; + +// Basic authentication logic +const SB_USERNAME = 'admin'; +const SB_PASSWORD = 'amwork'; + +const inputUsername = window.prompt('Enter username:'); +const inputPassword = window.prompt('Enter password:'); + +if (inputUsername !== SB_USERNAME || inputPassword !== SB_PASSWORD) { + document.body.innerHTML = `Unauthorized ${HttpStatusCode.Unauthorized}. Invalid credentials.`; + + throw new Error(`Unauthorized ${HttpStatusCode.Unauthorized}. Invalid credentials.`); +} + +const preview: Preview = { + decorators: [StyleDecorator, RouterDecorator, I18nDecorator], + + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + backgrounds: { + default: 'default', + values: [ + { + name: 'default', + value: '#f9fafb', + }, + { name: 'dark', value: '#1f2937' }, + ], + }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, + + tags: ['autodocs'], +}; + +export const globalTypes = { + locale: { + name: 'Locale', + description: 'Internationalization locale', + toolbar: { + icon: 'globe', + items: [ + { value: 'en', title: 'English' }, + { value: 'ru', title: 'Russian' }, + { value: 'fr', title: 'French' }, + ], + showName: true, + }, + }, +}; + +export default preview; diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..6ce91e5 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,93 @@ +# Amwork Frontend + +## Prepare the environment + +### Clone the repo + +```bash +# Create project directory +mkdir amwork + +# Go to project directory +cd amwork + +# Clone the repo +git clone https://gitlab.com/amwork/amwork-frontend.git . + +# Install dependencies +npm install +``` + +### Start dev server + +```bash +npm start +``` + +### Start the dev server in production mode + +```bash +npm run serve +``` + +### Build for production + +```bash +npm run build +``` + +## Proxy and reverse proxy + +### Run reverse proxy + +```bash +docker-compose up -d +``` + +### Rebuild reverse proxy + +```bash +docker-compose stop $@ && docker-compose rm -f $@ && docker-compose build $@ && docker-compose up -d $@ +``` + +## Documentation and storybook + +Storybook deployment is available at [https://amwork.dev/storybook/](https://amwork.dev/storybook/). + +### Run storybook + +```bash +npm run storybook +``` + +### Build storybook + +```bash +npm run storybook:build +``` + +### Code and exports analysis + +More about [knip](https://github.com/webpro/knip). + +```bash +npm run knip +``` + +### Storybook credentials + +```text +login: admin +password: amwork +``` + +You can as well see them in `.storybook/preview.ts` file. + +### Finale + +This piece of software was made with soul and love. +Unfortunately, the project was suspended on 24 of September 2025. +I was the last engineer to maintain it. +Please contact me if you have any questions. + +— Egor Kondratev, kondratevegor04@gmail.com \ No newline at end of file diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..ee1fb43 --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,107 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import { fixupConfigRules, fixupPluginRules } from "@eslint/compat"; +import i18Next from "eslint-plugin-i18next"; +import stylistic from "@stylistic/eslint-plugin"; +import regexp from "eslint-plugin-regexp"; +import reactHooksExtra from "eslint-plugin-react-hooks-extra"; +import tsParser from "@typescript-eslint/parser"; +import tsPlugin from "@typescript-eslint/eslint-plugin"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import js from "@eslint/js"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}); + +export default defineConfig([globalIgnores([ + "node_modules", + ".pnp", + "**/.pnp.js", + "coverage", + "build", + "src/declarations/voxengine.d.ts", + "**/.DS_Store", + "**/.env.local", + "**/.env.development.local", + "**/.env.test.local", + "**/.env.production.local", + "**/npm-debug.log*", + "**/yarn-debug.log*", + "**/yarn-error.log*", + "**/.idea", + "**/.vscode", +]), { + extends: fixupConfigRules(compat.extends( + "react-app", + "plugin:i18next/recommended", + "plugin:@tanstack/eslint-plugin-query/recommended", + "plugin:storybook/recommended", + "plugin:regexp/recommended", + )), + + plugins: { + i18next: fixupPluginRules(i18Next), + "@stylistic": stylistic, + regexp: fixupPluginRules(regexp), + "react-hooks-extra": reactHooksExtra, + "@typescript-eslint": fixupPluginRules(tsPlugin), + }, + + languageOptions: { + parser: tsParser, + }, + + rules: { + "no-redeclare": 0, + + "line-comment-position": ["warn", { + position: "above", + }], + + "padding-line-between-statements": ["warn", { + blankLine: "always", + prev: "*", + next: ["return", "if", "do", "while", "for", "switch", "try", "with"], + }, { + blankLine: "always", + prev: ["if", "do", "while", "for", "switch", "try", "with"], + next: "*", + }, { + blankLine: "any", + prev: ["const", "let", "var"], + next: ["if", "do", "while", "for", "switch", "try", "with"], + }], + + "react/button-has-type": 1, + + "react/function-component-definition": [1, { + namedComponents: "arrow-function", + }], + + "i18next/no-literal-string": [1, { + markupOnly: true, + ignoreAttribute: ["aria-label"], + }], + + "react-hooks-extra/no-direct-set-state-in-use-effect": "warn", + + "@typescript-eslint/no-unused-vars": [ + "error", + { + args: "all", + argsIgnorePattern: "^_", + caughtErrors: "all", + caughtErrorsIgnorePattern: "^e", + destructuredArrayIgnorePattern: "^_", + varsIgnorePattern: "^_", + ignoreRestSiblings: true + } + ] + }, +}]); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..3becacf --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..4312d90 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,15539 @@ +{ + "name": "amwork-frontend", + "version": "3.14.3", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "amwork-frontend", + "version": "3.14.3", + "hasInstallScript": true, + "dependencies": { + "@babel/plugin-proposal-decorators": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.27.0", + "@babel/preset-typescript": "^7.27.0", + "@directus/sdk": "^19.1.0", + "@emoji-mart/data": "^1.2.1", + "@emoji-mart/react": "^1.1.1", + "@emotion/react": "^11.14.0", + "@emotion/utils": "^1.4.2", + "@formkit/auto-animate": "^0.8.2", + "@fullcalendar/core": "^6.1.17", + "@fullcalendar/daygrid": "^6.1.17", + "@fullcalendar/interaction": "^6.1.17", + "@fullcalendar/list": "^6.1.17", + "@fullcalendar/multimonth": "^6.1.17", + "@fullcalendar/react": "^6.1.17", + "@fullcalendar/resource": "^6.1.17", + "@fullcalendar/resource-daygrid": "^6.1.17", + "@fullcalendar/resource-timegrid": "^6.1.17", + "@fullcalendar/resource-timeline": "^6.1.17", + "@fullcalendar/scrollgrid": "^6.1.17", + "@fullcalendar/timegrid": "^6.1.17", + "@hello-pangea/dnd": "^18.0.1", + "@mantine/core": "^7.17.4", + "@mantine/dates": "^7.17.4", + "@mantine/hooks": "^7.17.4", + "@mantine/tiptap": "^7.17.4", + "@newrelic/browser-agent": "^1.287.0", + "@react-pdf-viewer/core": "3.12.0", + "@react-pdf-viewer/default-layout": "3.12.0", + "@react-pdf/renderer": "^4.3.0", + "@tabler/icons": "^3.31.0", + "@tabler/icons-react": "^3.31.0", + "@tanstack/react-query": "^5.74.3", + "@tanstack/react-table": "^8.21.3", + "@tiptap/extension-highlight": "^2.11.7", + "@tiptap/extension-link": "^2.11.7", + "@tiptap/extension-placeholder": "^2.11.7", + "@tiptap/extension-subscript": "^2.11.7", + "@tiptap/extension-superscript": "^2.11.7", + "@tiptap/extension-text-align": "^2.11.7", + "@tiptap/extension-text-style": "^2.11.7", + "@tiptap/extension-underline": "^2.11.7", + "@tiptap/pm": "^2.11.7", + "@tiptap/react": "^2.11.7", + "@tiptap/starter-kit": "^2.11.7", + "@typescript-eslint/eslint-plugin": "^8.30.1", + "axios": "^1.8.4", + "bowser": "^2.11.0", + "bpmn-js": "^18.4.0", + "bpmn-js-color-picker": "^0.7.1", + "chart.js": "^4.4.9", + "country-code-to-flag-emoji": "^2.0.0", + "date-fns": "^4.1.0", + "dayjs": "^1.11.13", + "decimal.js": "^10.5.0", + "diagram-js": "^15.2.4", + "diagram-js-grid": "^1.1.0", + "diagram-js-minimap": "^5.2.0", + "dompurify": "^3.2.5", + "emoji-mart": "^5.6.0", + "file-saver": "^2.0.5", + "framer-motion": "^12.7.3", + "get-blob-duration": "^1.2.0", + "html-to-text": "^9.0.5", + "i18next": "^25.0.0", + "i18next-browser-languagedetector": "^8.0.4", + "i18next-http-backend": "^3.0.2", + "js-cookie": "^3.0.5", + "js-file-download": "^0.4.12", + "libphonenumber-js": "^1.12.6", + "mac-scrollbar": "^0.13.8", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", + "nodemailer": "^6.10.1", + "normalize.css": "^8.0.1", + "numeralize-ru": "^2.0.0", + "p-limit": "^6.2.0", + "path-to-regexp": "^8.2.0", + "pdfjs-dist": "3.11.174", + "phone-number-to-timezone": "^1.0.8", + "qrcode": "^1.5.4", + "react": "^19.1.0", + "react-avatar-editor": "^13.0.2", + "react-chartjs-2": "^5.3.0", + "react-collapsible": "^2.10.0", + "react-countup": "^6.5.3", + "react-custom-scrollbars-2": "^4.5.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dom": "^19.1.0", + "react-gtm-module": "^2.0.11", + "react-helmet": "^6.1.0", + "react-i18next": "^15.4.1", + "react-phone-input-2": "^2.15.1", + "react-player": "^2.16.0", + "react-resizable-panels": "^2.1.7", + "react-rnd": "^10.5.2", + "react-router-dom": "^7.5.0", + "react-swipeable": "^7.0.2", + "react-textarea-autosize": "^8.5.9", + "react-toastify": "10.0.6", + "reflect-metadata": "0.2.2", + "socket.io-client": "^4.8.1", + "styled-components": "^5.3.11", + "usehooks-ts": "^3.1.1", + "uuid": "^11.1.0", + "voximplant-websdk": "^4.8.9-2892", + "xlsx": "^0.18.5", + "zeebe-bpmn-moddle": "^1.9.0", + "zod": "^3.24.2" + }, + "devDependencies": { + "@babel/plugin-transform-class-properties": "^7.25.9", + "@eslint/compat": "^1.2.8", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "^9.24.0", + "@storybook/addon-essentials": "^8.6.12", + "@storybook/addon-interactions": "^8.6.12", + "@storybook/addon-links": "^8.6.12", + "@storybook/blocks": "^8.6.12", + "@storybook/react": "^8.6.12", + "@storybook/react-vite": "^8.6.12", + "@stylistic/eslint-plugin": "^4.2.0", + "@tanstack/eslint-plugin-query": "^5.73.3", + "@tanstack/react-query-devtools": "^5.74.3", + "@total-typescript/ts-reset": "^0.6.1", + "@types/html-to-text": "^9.0.4", + "@types/js-cookie": "^3.0.6", + "@types/node": "^22.14.1", + "@types/nodemailer": "^6.4.17", + "@types/qrcode": "^1.5.5", + "@types/react": "^19.1.2", + "@types/react-avatar-editor": "^13.0.4", + "@types/react-dom": "^19.1.2", + "@types/react-gtm-module": "^2.0.4", + "@types/react-helmet": "^6.1.11", + "@types/styled-components": "5.1.34", + "@types/uuid": "^10.0.0", + "@typescript-eslint/parser": "^8.30.1", + "@vitejs/plugin-react": "^4.4.0", + "babel-plugin-styled-components": "^2.1.4", + "eslint": "^9.24.0", + "eslint-config-react-app": "^7.0.1", + "eslint-plugin-i18next": "^6.1.1", + "eslint-plugin-react-hooks-extra": "^1.48.1", + "eslint-plugin-regexp": "^2.7.0", + "eslint-plugin-storybook": "^0.12.0", + "knip": "^5.50.4", + "nano-staged": "^0.8.0", + "prettier": "^3.5.3", + "prettier-plugin-organize-imports": "^4.1.0", + "simple-git-hooks": "^2.12.1", + "storybook": "^8.6.12", + "typescript": "^5.8.3", + "vite": "^6.3.0", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-svgr": "^4.3.0", + "vite-tsconfig-paths": "^5.1.4" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", + "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "dev": true + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/eslint-parser": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz", + "integrity": "sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dependencies": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", + "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/helper-replace-supers": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/traverse": "^7.27.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", + "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", + "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", + "dependencies": { + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", + "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-wrap-function": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", + "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.25.9", + "@babel/helper-optimise-call-expression": "^7.25.9", + "@babel/traverse": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", + "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", + "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", + "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", + "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", + "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", + "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", + "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-decorators": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", + "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.26.0.tgz", + "integrity": "sha512-B+O2DnPc0iG+YXFqOxv2WNuNU97ToWjOomUQ78DouOENWUaM5sVrmet9mcomUGQFwpJd//gvUagXBSdzO1fRKg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", + "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", + "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", + "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz", + "integrity": "sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", + "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz", + "integrity": "sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", + "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", + "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9", + "@babel/traverse": "^7.25.9", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", + "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/template": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", + "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", + "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", + "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", + "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", + "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.26.5.tgz", + "integrity": "sha512-eGK26RsbIkYUns3Y8qKl362juDDYK+wEdPGHGrhzUl6CewZFo55VZ7hg+CyMFU4dd5QQakBN86nBMpRsFpRvbQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/plugin-syntax-flow": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", + "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", + "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", + "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", + "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", + "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", + "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", + "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", + "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", + "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", + "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", + "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.26.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz", + "integrity": "sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", + "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", + "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", + "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-replace-supers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", + "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz", + "integrity": "sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", + "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz", + "integrity": "sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", + "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", + "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz", + "integrity": "sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz", + "integrity": "sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz", + "integrity": "sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz", + "integrity": "sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", + "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", + "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", + "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.9.tgz", + "integrity": "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", + "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", + "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", + "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", + "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", + "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", + "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-create-class-features-plugin": "^7.27.0", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", + "@babel/plugin-syntax-typescript": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", + "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", + "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", + "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", + "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", + "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.25.9", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.25.9", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.25.9", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.25.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.25.9", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.25.9", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.25.9", + "@babel/plugin-transform-typeof-symbol": "^7.25.9", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.6", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.38.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.26.3.tgz", + "integrity": "sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-transform-react-display-name": "^7.25.9", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@babel/plugin-transform-react-jsx-development": "^7.25.9", + "@babel/plugin-transform-react-pure-annotations": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.0.tgz", + "integrity": "sha512-vxaPFfJtHhgeOVXRKuHpHPAOgymmy8V8I65T1q53R7GCZlefKeCaTyDs3zOPHTTbmquvNlQYC5klEvWsBAtrBQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-syntax-jsx": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-typescript": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", + "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bpmn-io/diagram-js-ui": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bpmn-io/diagram-js-ui/-/diagram-js-ui-0.2.3.tgz", + "integrity": "sha512-OGyjZKvGK8tHSZ0l7RfeKhilGoOGtFDcoqSGYkX0uhFlo99OVZ9Jn1K7TJGzcE9BdKwvA5Y5kGqHEhdTxHvFfw==", + "dependencies": { + "htm": "^3.1.1", + "preact": "^10.11.2" + } + }, + "node_modules/@directus/sdk": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/@directus/sdk/-/sdk-19.1.0.tgz", + "integrity": "sha512-Nqem9BsvvGyVtAa69mGPtoMoMVkZxdIREdsWvvTzNF4/1XqaFfEiFL7PhtUNfc46/Nufus2+QUKYQbNiAWe3ZA==", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/directus/directus?sponsor=1" + } + }, + "node_modules/@emoji-mart/data": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz", + "integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==" + }, + "node_modules/@emoji-mart/react": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz", + "integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==", + "peerDependencies": { + "emoji-mart": "^5.2", + "react": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", + "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz", + "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz", + "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz", + "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz", + "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz", + "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz", + "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz", + "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz", + "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz", + "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz", + "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz", + "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz", + "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz", + "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz", + "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz", + "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz", + "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz", + "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz", + "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz", + "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz", + "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz", + "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz", + "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz", + "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz", + "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint-react/ast": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@eslint-react/ast/-/ast-1.48.1.tgz", + "integrity": "sha512-s/05RD3EP54BigKoQ4QC7/Hb4EkzhUeXOSn3IIF15f1mhm5ieasuev0vpNZmTYBIDibq5R1OzsWu3WRWm3NJVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/eff": "1.48.1", + "@typescript-eslint/types": "^8.30.1", + "@typescript-eslint/typescript-estree": "^8.30.1", + "@typescript-eslint/utils": "^8.30.1", + "string-ts": "^2.2.1", + "ts-pattern": "^5.7.0" + }, + "engines": { + "bun": ">=1.0.15", + "node": ">=18.18.0" + } + }, + "node_modules/@eslint-react/core": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@eslint-react/core/-/core-1.48.1.tgz", + "integrity": "sha512-O2onuiYs3EB0N3FBCcHDWHNlATMyYewX4E67sJwvLi9qTfX+I4I4ZmzkLfgqSD0eCMOjukHgLV1va+QV0bkR1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "1.48.1", + "@eslint-react/eff": "1.48.1", + "@eslint-react/kit": "1.48.1", + "@eslint-react/shared": "1.48.1", + "@eslint-react/var": "1.48.1", + "@typescript-eslint/scope-manager": "^8.30.1", + "@typescript-eslint/type-utils": "^8.30.1", + "@typescript-eslint/types": "^8.30.1", + "@typescript-eslint/utils": "^8.30.1", + "birecord": "^0.1.1", + "ts-pattern": "^5.7.0" + }, + "engines": { + "bun": ">=1.0.15", + "node": ">=18.18.0" + } + }, + "node_modules/@eslint-react/eff": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@eslint-react/eff/-/eff-1.48.1.tgz", + "integrity": "sha512-6N76PNGylcTdJjdPG44NXHDYSDZBw5h4Uvrypm9/1TYKUBuNTHBpaeNgN16j/0Gkee3N8im4IefSqXsQz+Lx0g==", + "dev": true, + "license": "MIT", + "engines": { + "bun": ">=1.0.15", + "node": ">=18.18.0" + } + }, + "node_modules/@eslint-react/kit": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@eslint-react/kit/-/kit-1.48.1.tgz", + "integrity": "sha512-XX8giu1EtnP23KVBI7Co6GDWjqLL/8a/YxOjjdJStI9i4Cr0S+yzLJ4yNmcA5wPJ0YqgIXw1sd+aEVZWSWzddg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/eff": "1.48.1", + "@typescript-eslint/utils": "^8.30.1", + "@zod/mini": "^4.0.0-beta.0", + "ts-pattern": "^5.7.0" + }, + "engines": { + "bun": ">=1.0.15", + "node": ">=18.18.0" + } + }, + "node_modules/@eslint-react/shared": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@eslint-react/shared/-/shared-1.48.1.tgz", + "integrity": "sha512-Rwf0FJGTdYhdwCznTwpsvYyvwrEgKj25z6YyCdAfBwoR+Y/i28EWxjlaAjQzksffSfj44AspumRgIXI8SIgaiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/eff": "1.48.1", + "@eslint-react/kit": "1.48.1", + "@typescript-eslint/utils": "^8.30.1", + "@zod/mini": "^4.0.0-beta.0", + "ts-pattern": "^5.7.0" + }, + "engines": { + "bun": ">=1.0.15", + "node": ">=18.18.0" + } + }, + "node_modules/@eslint-react/var": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/@eslint-react/var/-/var-1.48.1.tgz", + "integrity": "sha512-irRKOTcsXoW4xbwLRQHSRukMVe6WelUdS59WZQ0/84IDBTNHJWXnMWDGnvxm6ZyDNd1THRgOCeu2kj05T2TqTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "1.48.1", + "@eslint-react/eff": "1.48.1", + "@typescript-eslint/scope-manager": "^8.30.1", + "@typescript-eslint/types": "^8.30.1", + "@typescript-eslint/utils": "^8.30.1", + "string-ts": "^2.2.1", + "ts-pattern": "^5.7.0" + }, + "engines": { + "bun": ">=1.0.15", + "node": ">=18.18.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.8.tgz", + "integrity": "sha512-LqCYHdWL/QqKIJuZ/ucMAv8d4luKGs4oCPgpt8mWztQAtPrHfXKQ/XAUc8ljCHAfJCn6SvkpTcGt5Tsh8saowA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@formkit/auto-animate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.8.2.tgz", + "integrity": "sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ==" + }, + "node_modules/@fullcalendar/core": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.17.tgz", + "integrity": "sha512-0W7lnIrv18ruJ5zeWBeNZXO8qCWlzxDdp9COFEsZnyNjiEhUVnrW/dPbjRKYpL0edGG0/Lhs0ghp1z/5ekt8ZA==", + "dependencies": { + "preact": "~10.12.1" + } + }, + "node_modules/@fullcalendar/daygrid": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.17.tgz", + "integrity": "sha512-K7m+pd7oVJ9fW4h7CLDdDGJbc9szJ1xDU1DZ2ag+7oOo1aCNLv44CehzkkknM6r8EYlOOhgaelxQpKAI4glj7A==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.17.tgz", + "integrity": "sha512-AudvQvgmJP2FU89wpSulUUjeWv24SuyCx8FzH2WIPVaYg+vDGGYarI7K6PcM3TH7B/CyaBjm5Rqw9lXgnwt5YA==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/list": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.17.tgz", + "integrity": "sha512-fkyK49F9IxwlGUBVhJGsFpd/LTi/vRVERLIAe1HmBaGkjwpxnynm8TMLb9mZip97wvDk3CmZWduMe6PxscAlow==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/multimonth": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.17.tgz", + "integrity": "sha512-ZxA9mkTzKayCdxR5je9P9++qqhSeSbuvXmvZ6doZw6omv8K52cD7XJii+P7gvxATXxtI6hg4i+DuMyOHxP1E2g==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/premium-common": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/premium-common/-/premium-common-6.1.17.tgz", + "integrity": "sha512-zoN7fMwGMcP6Xu+2YudRAGfdwD2J+V+A/xAieXgYDSZT+5ekCsjZiwb2rmvthjt+HVnuZcqs6sGp7rnJ8Ie/mA==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/react": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.17.tgz", + "integrity": "sha512-AA8soHhlfRH5dUeqHnfAtzDiXa2vrgWocJSK/F5qzw/pOxc9MqpuoS/nQBROWtHHg6yQUg3DoGqOOhi7dmylXQ==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.17", + "react": "^16.7.0 || ^17 || ^18 || ^19", + "react-dom": "^16.7.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/@fullcalendar/resource": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource/-/resource-6.1.17.tgz", + "integrity": "sha512-hWnbOWlroIN5Wt4NJmHAJh/F7ge2cV6S0PdGSmLFoZJZJA0hJX9GeYRzyz4MlUoj7f4dGzBlesy2RdC+t5FEMw==", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/resource-daygrid": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource-daygrid/-/resource-daygrid-6.1.17.tgz", + "integrity": "sha512-BghbufO4lsy/dckmPSxGzaxLH4c9oqykG01khYXRJijbls358CgSe+X9m1Jd5ntRZQU8o3U0Ma0pTZHr7Nx/6A==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.17", + "@fullcalendar/premium-common": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17", + "@fullcalendar/resource": "~6.1.17" + } + }, + "node_modules/@fullcalendar/resource-timegrid": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource-timegrid/-/resource-timegrid-6.1.17.tgz", + "integrity": "sha512-9gsR/2oKTb9zpx7z2/B5rbk2A0dmD/sfYAARSyQTuQD1CI5He8q63jdcYVzWW3CCfJa/5jHEqQaIiCKNlCq6vg==", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.17", + "@fullcalendar/resource-daygrid": "~6.1.17", + "@fullcalendar/timegrid": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17", + "@fullcalendar/resource": "~6.1.17" + } + }, + "node_modules/@fullcalendar/resource-timeline": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/resource-timeline/-/resource-timeline-6.1.17.tgz", + "integrity": "sha512-QMrtc1mLs4c6DtlBNmWICef8Lr4CmzE47uWS/rcJBd9K2kBzvusTp7AQQ1qn3RX5UnjNHqT8pkKO/wE4yspJQw==", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.17", + "@fullcalendar/scrollgrid": "~6.1.17", + "@fullcalendar/timeline": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17", + "@fullcalendar/resource": "~6.1.17" + } + }, + "node_modules/@fullcalendar/scrollgrid": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/scrollgrid/-/scrollgrid-6.1.17.tgz", + "integrity": "sha512-lzphEKwxWMS4xQVEuimzZjKFLijlSn49ExvzkYZls0VLDwOa3BYHcRlDJBjQ0LP6kauz9aatg3MfRIde/LAazA==", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.17.tgz", + "integrity": "sha512-K4PlA3L3lclLOs3IX8cvddeiJI9ZVMD7RA9IqaWwbvac771971foc9tFze9YY+Pqesf6S+vhS2dWtEVlERaGlQ==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@fullcalendar/timeline": { + "version": "6.1.17", + "resolved": "https://registry.npmjs.org/@fullcalendar/timeline/-/timeline-6.1.17.tgz", + "integrity": "sha512-UhL2OOph/S0cEKs3lzbXjS2gTxmQwaNug2XFjdljvO/ERj10v7OBXj/zvJrPyhjvWR/CSgjNgBaUpngkCu4JtQ==", + "dependencies": { + "@fullcalendar/premium-common": "~6.1.17", + "@fullcalendar/scrollgrid": "~6.1.17" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.17" + } + }, + "node_modules/@hello-pangea/dnd": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-18.0.1.tgz", + "integrity": "sha512-xojVWG8s/TGrKT1fC8K2tIWeejJYTAeJuj36zM//yEm/ZrnZUSFGS15BpO+jGZT1ybWvyXmeDJwPYb4dhWlbZQ==", + "dependencies": { + "@babel/runtime": "^7.26.7", + "css-box-model": "^1.2.1", + "raf-schd": "^4.0.3", + "react-redux": "^9.2.0", + "redux": "^5.0.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.5.0.tgz", + "integrity": "sha512-qYDdL7fPwLRI+bJNurVcis+tNgJmvWjH4YTBGXTA8xMuxFrnAz6E5o35iyzyKbq5J5Lr8mJGfrR5GXl+WGwhgQ==", + "dev": true, + "dependencies": { + "glob": "^10.0.0", + "magic-string": "^0.27.0", + "react-docgen-typescript": "^2.2.2" + }, + "peerDependencies": { + "typescript": ">= 4.3.x", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" + }, + "node_modules/@mantine/core": { + "version": "7.17.4", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.17.4.tgz", + "integrity": "sha512-Ea4M/98jxgIWCuxCdM0YIotVYjfLTGQsfIA6zDg0LsClgjo/ZLnnh4zbi+bLNgM+GGjP4ju7gv4MZvaTKuLO8g==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "clsx": "^2.1.1", + "react-number-format": "^5.4.3", + "react-remove-scroll": "^2.6.2", + "react-textarea-autosize": "8.5.9", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.17.4", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/core/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mantine/core/node_modules/type-fest": { + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mantine/dates": { + "version": "7.17.4", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.17.4.tgz", + "integrity": "sha512-6oqcmcJb0Pypju+/z6s9nEVa3B9Pdj5DTrdj/FP/RD7RFx4k7nHi+jFstn4qcso6nghRRROKMHErfqrBybjzKA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "7.17.4", + "@mantine/hooks": "7.17.4", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/dates/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.17.4", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.17.4.tgz", + "integrity": "sha512-PBcJxDAfGm8k1/JJmaDcxzRVQ3JSE1iXGktbgGz+qEOJmCxwbbAYe+CtGFFgi1xX2bPZ+7dtRr/+XFhnKtt/aw==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/tiptap": { + "version": "7.17.4", + "resolved": "https://registry.npmjs.org/@mantine/tiptap/-/tiptap-7.17.4.tgz", + "integrity": "sha512-5r6dPzw3rfcU6W+zrT06N5cFaISDwYmvVemqz1hXX1cNj7Y9UOb3ymH6lmmXsyqroUv+bHurbi/SlcYd8R0YIA==", + "license": "MIT", + "peerDependencies": { + "@mantine/core": "7.17.4", + "@mantine/hooks": "7.17.4", + "@tiptap/extension-link": ">=2.1.12", + "@tiptap/react": ">=2.1.12", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", + "dev": true, + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@newrelic/browser-agent": { + "version": "1.287.0", + "resolved": "https://registry.npmjs.org/@newrelic/browser-agent/-/browser-agent-1.287.0.tgz", + "integrity": "sha512-fwTbSGjsrY18V7XGR2o85WJsUDOO/nZnzI4EhReH27sXPD6SzilNzHXrwN39PaMNdi2iEQC17sRAoEBXMn3dbA==", + "license": "Apache-2.0", + "dependencies": { + "fflate": "0.8.2", + "rrweb": "^2.0.0-alpha.18", + "web-vitals": "4.2.4" + }, + "engines": { + "node": ">=12.17.0 < 13.0.0 || >=13.7.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-dnd/asap": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", + "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" + }, + "node_modules/@react-dnd/invariant": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", + "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" + }, + "node_modules/@react-dnd/shallowequal": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", + "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" + }, + "node_modules/@react-pdf-viewer/attachment": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/attachment/-/attachment-3.12.0.tgz", + "integrity": "sha512-mhwrYJSIpCvHdERpLUotqhMgSjhtF+BTY1Yb9Fnzpcq3gLZP+Twp5Rynq21tCrVdDizPaVY7SKu400GkgdMfZw==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/bookmark": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/bookmark/-/bookmark-3.12.0.tgz", + "integrity": "sha512-i7nEit8vIFMAES8RFGwprZ9cXOOZb9ZStPW6E6yuObJEXcvBj/ctsbBJGZxqUZOGklM0JoB7sjHyxAriHfe92A==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/core": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/core/-/core-3.12.0.tgz", + "integrity": "sha512-8MsdlQJ4jaw3GT+zpCHS33nwnvzpY0ED6DEahZg9WngG++A5RMhk8LSlxdHelwaFFHFiXBjmOaj2Kpxh50VQRg==", + "peerDependencies": { + "pdfjs-dist": "^2.16.105 || ^3.0.279", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/default-layout": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/default-layout/-/default-layout-3.12.0.tgz", + "integrity": "sha512-K2fS4+TJynHxxCBFuIDiFuAw3nqOh4bkBgtVZ/2pGvnFn9lLg46YGLMnTXCQqtyZzzXYh696jmlFViun3is4pA==", + "dependencies": { + "@react-pdf-viewer/attachment": "3.12.0", + "@react-pdf-viewer/bookmark": "3.12.0", + "@react-pdf-viewer/core": "3.12.0", + "@react-pdf-viewer/thumbnail": "3.12.0", + "@react-pdf-viewer/toolbar": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/full-screen": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/full-screen/-/full-screen-3.12.0.tgz", + "integrity": "sha512-hQouJ26QUaRBCXNMU1aI1zpJn4l4PJRvlHhuE2dZYtLl37ycjl7vBCQYZW1FwnuxMWztZsY47R43DKaZORg0pg==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/get-file": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/get-file/-/get-file-3.12.0.tgz", + "integrity": "sha512-Uhq45n2RWlZ7Ec/BtBJ0WQESRciaYIltveDXHNdWvXgFdOS8XsvB+mnTh/wzm7Cfl9hpPyzfeezifdU9AkQgQg==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/open": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/open/-/open-3.12.0.tgz", + "integrity": "sha512-vhiDEYsiQLxvZkIKT9VPYHZ1BOnv46x9eCEmRWxO1DJ8fa/GRDTA9ivXmq/ap0dGEJs6t+epleCkCEfllLR/Yw==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/page-navigation": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/page-navigation/-/page-navigation-3.12.0.tgz", + "integrity": "sha512-tVEJ48Dd5kajV1nKkrPWijglJRNBiKBTyYDKVexhiRdTHUP1f6QQXiSyDgCUb0IGSZeJzOJb1h7ApKHe8OTtuw==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/print": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/print/-/print-3.12.0.tgz", + "integrity": "sha512-xJn76CgbU/M2iNaN7wLHTg+sdOekkRMfCakFLwPrE+SR7qD6NUF4vQQKJBSVCCK5bUijzb6cWfKGfo8VA72o4Q==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/properties": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/properties/-/properties-3.12.0.tgz", + "integrity": "sha512-dYTCHtVwFNkpDo7QxL2qk/8zAKndLwdD1FFxBftl6jIlQbtvNdxkFfkv1HcQING9Ic+7DBryOiD7W0ze4IERYg==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/rotate": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/rotate/-/rotate-3.12.0.tgz", + "integrity": "sha512-yaxaMYPChvNOjR8+AxRmj0kvojyJKPq4XHEcIB2lJJgBY1Zra3mliDUP3Nlb4yV8BS9+yBqWn9U9mtnopQD+tw==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/scroll-mode": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/scroll-mode/-/scroll-mode-3.12.0.tgz", + "integrity": "sha512-okII7Xqhl6cMvl1izdEvlXNJ+vJVq/qdg53hJIDYVgBCWskLk/cpjUg/ZonBxseG9lIDP3w2VO1McT8Gn11OAg==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/search": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/search/-/search-3.12.0.tgz", + "integrity": "sha512-jAkLpis49fsDDY/HrbUZIOIhzF5vynONQNA4INQKI38r/MjveblrkNv7qbr9j5lQ/WFic5+gD1e+Mtpf1/7DiA==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/selection-mode": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/selection-mode/-/selection-mode-3.12.0.tgz", + "integrity": "sha512-yysWEu2aCtBvzSgbhgI9kT5cq2hf0FU6Z+3B7MMXz14Kxyc3y18wUqxtgbvpFEfWF0bNUUq16JtWRljtxvZ83w==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/theme": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/theme/-/theme-3.12.0.tgz", + "integrity": "sha512-cdBi+wR1VOZ6URCcO9plmAZQu4ZGFcd7HJdBe7VIFiGyrvl9I/Of74ONLycnDImSuONt8D3uNjPBLieeaShVeg==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/thumbnail": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/thumbnail/-/thumbnail-3.12.0.tgz", + "integrity": "sha512-Vc8j3bO6wumWZV4o6pAbktPWKDSC9tQAzOCJ3cof541u4i44C11ccYC4W9aNcsMMUSO3bNwAGWtP8OFthV5akQ==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/toolbar": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/toolbar/-/toolbar-3.12.0.tgz", + "integrity": "sha512-qACTU3qXHgtNK8J+T13EWio+0liilj86SJ87BdapqXynhl720OKPlSKOQqskUGqg3oTUJAhrse9XG6SFdHJx+g==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0", + "@react-pdf-viewer/full-screen": "3.12.0", + "@react-pdf-viewer/get-file": "3.12.0", + "@react-pdf-viewer/open": "3.12.0", + "@react-pdf-viewer/page-navigation": "3.12.0", + "@react-pdf-viewer/print": "3.12.0", + "@react-pdf-viewer/properties": "3.12.0", + "@react-pdf-viewer/rotate": "3.12.0", + "@react-pdf-viewer/scroll-mode": "3.12.0", + "@react-pdf-viewer/search": "3.12.0", + "@react-pdf-viewer/selection-mode": "3.12.0", + "@react-pdf-viewer/theme": "3.12.0", + "@react-pdf-viewer/zoom": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf-viewer/zoom": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/@react-pdf-viewer/zoom/-/zoom-3.12.0.tgz", + "integrity": "sha512-V0GUTyPM77+LzhoKX+T3XI10/HfGdqRTbgeP7ID60FCzcwu6kXWqJn5tzabjDKLTlFv8mJmn0aa/ppkIU97nfA==", + "dependencies": { + "@react-pdf-viewer/core": "3.12.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@react-pdf/fns": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz", + "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==" + }, + "node_modules/@react-pdf/font": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.2.tgz", + "integrity": "sha512-/dAWu7Y2RD1RxarDZ9SkYPHgBYOhmcDnet4W/qN/m8k+A2Hr3ja54GymSR7GGxWBtxjKtNauVKrTa9LS1n8WUw==", + "dependencies": { + "@react-pdf/pdfkit": "^4.0.3", + "@react-pdf/types": "^2.9.0", + "fontkit": "^2.0.2", + "is-url": "^1.2.4" + } + }, + "node_modules/@react-pdf/image": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/image/-/image-3.0.3.tgz", + "integrity": "sha512-lvP5ryzYM3wpbO9bvqLZYwEr5XBDX9jcaRICvtnoRqdJOo7PRrMnmB4MMScyb+Xw10mGeIubZAAomNAG5ONQZQ==", + "dependencies": { + "@react-pdf/png-js": "^3.0.0", + "jay-peg": "^1.1.1" + } + }, + "node_modules/@react-pdf/layout": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.0.tgz", + "integrity": "sha512-Aq+Cc6JYausWLoks2FvHe3PwK9cTuvksB2uJ0AnkKJEUtQbvCq8eCRb1bjbbwIji9OzFRTTzZij7LzkpKHjIeA==", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/image": "^3.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.0", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.0", + "emoji-regex": "^10.3.0", + "queue": "^6.0.1", + "yoga-layout": "^3.2.1" + } + }, + "node_modules/@react-pdf/pdfkit": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.0.3.tgz", + "integrity": "sha512-k+Lsuq8vTwWsCqTp+CCB4+2N+sOTFrzwGA7aw3H9ix/PDWR9QksbmNg0YkzGbLAPI6CeawmiLHcf4trZ5ecLPQ==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/png-js": "^3.0.0", + "browserify-zlib": "^0.2.0", + "crypto-js": "^4.2.0", + "fontkit": "^2.0.2", + "jay-peg": "^1.1.1", + "linebreak": "^1.1.0", + "vite-compatible-readable-stream": "^3.6.1" + } + }, + "node_modules/@react-pdf/png-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/png-js/-/png-js-3.0.0.tgz", + "integrity": "sha512-eSJnEItZ37WPt6Qv5pncQDxLJRK15eaRwPT+gZoujP548CodenOVp49GST8XJvKMFt9YqIBzGBV/j9AgrOQzVA==", + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, + "node_modules/@react-pdf/primitives": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz", + "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==" + }, + "node_modules/@react-pdf/reconciler": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@react-pdf/reconciler/-/reconciler-1.1.4.tgz", + "integrity": "sha512-oTQDiR/t4Z/Guxac88IavpU2UgN7eR0RMI9DRKvKnvPz2DUasGjXfChAdMqDNmJJxxV26mMy9xQOUV2UU5/okg==", + "dependencies": { + "object-assign": "^4.1.1", + "scheduler": "0.25.0-rc-603e6108-20241029" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/render": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.0.tgz", + "integrity": "sha512-MdWfWaqO6d7SZD75TZ2z5L35V+cHpyA43YNRlJNG0RJ7/MeVGDQv12y/BXOJgonZKkeEGdzM3EpAt9/g4E22WA==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/textkit": "^6.0.0", + "@react-pdf/types": "^2.9.0", + "abs-svg-path": "^0.1.1", + "color-string": "^1.9.1", + "normalize-svg-path": "^1.1.0", + "parse-svg-path": "^0.1.2", + "svg-arc-to-cubic-bezier": "^3.2.0" + } + }, + "node_modules/@react-pdf/renderer": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.0.tgz", + "integrity": "sha512-28gpA69fU9ZQrDzmd5xMJa1bDf8t0PT3ApUKBl2PUpoE/x4JlvCB5X66nMXrfFrgF2EZrA72zWQAkvbg7TE8zw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@react-pdf/fns": "3.1.2", + "@react-pdf/font": "^4.0.2", + "@react-pdf/layout": "^4.4.0", + "@react-pdf/pdfkit": "^4.0.3", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/reconciler": "^1.1.4", + "@react-pdf/render": "^4.3.0", + "@react-pdf/types": "^2.9.0", + "events": "^3.3.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "queue": "^6.0.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@react-pdf/stylesheet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.0.tgz", + "integrity": "sha512-BGZ2sYNUp38VJUegjva/jsri3iiRGnVNjWI+G9dTwAvLNOmwFvSJzqaCsEnqQ/DW5mrTBk/577FhDY7pv6AidA==", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "@react-pdf/types": "^2.9.0", + "color-string": "^1.9.1", + "hsl-to-hex": "^1.0.0", + "media-engine": "^1.0.3", + "postcss-value-parser": "^4.1.0" + } + }, + "node_modules/@react-pdf/textkit": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.0.0.tgz", + "integrity": "sha512-fDt19KWaJRK/n2AaFoVm31hgGmpygmTV7LsHGJNGZkgzXcFyLsx+XUl63DTDPH3iqxj3xUX128t104GtOz8tTw==", + "dependencies": { + "@react-pdf/fns": "3.1.2", + "bidi-js": "^1.0.2", + "hyphen": "^1.6.4", + "unicode-properties": "^1.4.1" + } + }, + "node_modules/@react-pdf/types": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.0.tgz", + "integrity": "sha512-ckj80vZLlvl9oYrQ4tovEaqKWP3O06Eb1D48/jQWbdwz1Yh7Y9v1cEmwlP8ET+a1Whp8xfdM0xduMexkuPANCQ==", + "dependencies": { + "@react-pdf/font": "^4.0.2", + "@react-pdf/primitives": "^4.1.1", + "@react-pdf/stylesheet": "^6.1.0" + } + }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rrweb/types": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.18.tgz", + "integrity": "sha512-iMH3amHthJZ9x3gGmBPmdfim7wLGygC2GciIkw2A6SO8giSn8PHYtRT8OKNH4V+k3SZ6RSnYHcTQxBA7pSWZ3Q==" + }, + "node_modules/@rrweb/utils": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/@rrweb/utils/-/utils-2.0.0-alpha.18.tgz", + "integrity": "sha512-qV8azQYo9RuwW4NGRtOiQfTBdHNL1B0Q//uRLMbCSjbaKqJYd88Js17Bdskj65a0Vgp2dwTLPIZ0gK47dfjfaA==" + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz", + "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==", + "dev": true + }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@storybook/addon-actions": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-8.6.12.tgz", + "integrity": "sha512-B5kfiRvi35oJ0NIo53CGH66H471A3XTzrfaa6SxXEJsgxxSeKScG5YeXcCvLiZfvANRQ7QDsmzPUgg0o3hdMXw==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@types/uuid": "^9.0.1", + "dequal": "^2.0.2", + "polished": "^4.2.2", + "uuid": "^9.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-actions/node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, + "node_modules/@storybook/addon-actions/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@storybook/addon-backgrounds": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.6.12.tgz", + "integrity": "sha512-lmIAma9BiiCTbJ8YfdZkXjpnAIrOUcgboLkt1f6XJ78vNEMnLNzD9gnh7Tssz1qrqvm34v9daDjIb+ggdiKp3Q==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "memoizerific": "^1.11.3", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-controls": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-8.6.12.tgz", + "integrity": "sha512-9VSRPJWQVb9wLp21uvpxDGNctYptyUX0gbvxIWOHMH3R2DslSoq41lsC/oQ4l4zSHVdL+nq8sCTkhBxIsjKqdQ==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "dequal": "^2.0.2", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-docs": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-8.6.12.tgz", + "integrity": "sha512-kEezQjAf/p3SpDzLABgg4fbT48B6dkT2LiZCKTRmCrJVtuReaAr4R9MMM6Jsph6XjbIj/SvOWf3CMeOPXOs9sg==", + "dev": true, + "dependencies": { + "@mdx-js/react": "^3.0.0", + "@storybook/blocks": "8.6.12", + "@storybook/csf-plugin": "8.6.12", + "@storybook/react-dom-shim": "8.6.12", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-essentials": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-essentials/-/addon-essentials-8.6.12.tgz", + "integrity": "sha512-Y/7e8KFlttaNfv7q2zoHMPdX6hPXHdsuQMAjYl5NG9HOAJREu4XBy4KZpbcozRe4ApZ78rYsN/MO1EuA+bNMIA==", + "dev": true, + "dependencies": { + "@storybook/addon-actions": "8.6.12", + "@storybook/addon-backgrounds": "8.6.12", + "@storybook/addon-controls": "8.6.12", + "@storybook/addon-docs": "8.6.12", + "@storybook/addon-highlight": "8.6.12", + "@storybook/addon-measure": "8.6.12", + "@storybook/addon-outline": "8.6.12", + "@storybook/addon-toolbars": "8.6.12", + "@storybook/addon-viewport": "8.6.12", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-highlight": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-highlight/-/addon-highlight-8.6.12.tgz", + "integrity": "sha512-9FITVxdoycZ+eXuAZL9ElWyML/0fPPn9UgnnAkrU7zkMi+Segq/Tx7y+WWanC5zfWZrXAuG6WTOYEXeWQdm//w==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-interactions": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-interactions/-/addon-interactions-8.6.12.tgz", + "integrity": "sha512-cTAJlTq6uVZBEbtwdXkXoPQ4jHOAGKQnYSezBT4pfNkdjn/FnEeaQhMBDzf14h2wr5OgBnJa6Lmd8LD9ficz4A==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "8.6.12", + "@storybook/test": "8.6.12", + "polished": "^4.2.2", + "ts-dedent": "^2.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-links": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-links/-/addon-links-8.6.12.tgz", + "integrity": "sha512-AfKujFHoAxhxq4yu+6NwylltS9lf5MPs1eLLXvOlwo3l7Y/c68OdxJ7j68vLQhs9H173WVYjKyjbjFxJWf/YYg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.6.12" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/@storybook/addon-measure": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-measure/-/addon-measure-8.6.12.tgz", + "integrity": "sha512-tACmwqqOvutaQSduw8SMb62wICaT1rWaHtMN3vtWXuxgDPSdJQxLP+wdVyRYMAgpxhLyIO7YRf++Hfha9RHgFg==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "tiny-invariant": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-outline": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-outline/-/addon-outline-8.6.12.tgz", + "integrity": "sha512-1ylwm+n1s40S91No0v9T4tCjZORu3GbnjINlyjYTDLLhQHyBQd3nWR1Y1eewU4xH4cW9SnSLcMQFS/82xHqU6A==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-toolbars": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-toolbars/-/addon-toolbars-8.6.12.tgz", + "integrity": "sha512-HEcSzo1DyFtIu5/ikVOmh5h85C1IvK9iFKSzBR6ice33zBOaehVJK+Z5f487MOXxPsZ63uvWUytwPyViGInj+g==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/addon-viewport": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/addon-viewport/-/addon-viewport-8.6.12.tgz", + "integrity": "sha512-EXK2LArAnABsPP0leJKy78L/lbMWow+EIJfytEP5fHaW4EhMR6h7Hzaqzre6U0IMMr/jVFa1ci+m0PJ0eQc2bw==", + "dev": true, + "dependencies": { + "memoizerific": "^1.11.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/blocks": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-8.6.12.tgz", + "integrity": "sha512-DohlTq6HM1jDbHYiXL4ZvZ00VkhpUp5uftzj/CZDLY1fYHRjqtaTwWm2/OpceivMA8zDitLcq5atEZN+f+siTg==", + "dev": true, + "dependencies": { + "@storybook/icons": "^1.2.12", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^8.6.12" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@storybook/builder-vite": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.6.12.tgz", + "integrity": "sha512-Gju21ud/3Qw4v2vLNaa5SuJECsI9ICNRr2G0UyCCzRvCHg8jpA9lDReu2NqhLDyFIuDG+ZYT38gcaHEUoNQ8KQ==", + "dev": true, + "dependencies": { + "@storybook/csf-plugin": "8.6.12", + "browser-assert": "^1.2.1", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12", + "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@storybook/components": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.6.12.tgz", + "integrity": "sha512-FiaE8xvCdvKC2arYusgtlDNZ77b8ysr8njAYQZwwaIHjy27TbR2tEpLDCmUwSbANNmivtc/xGEiDDwcNppMWlQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/core": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.12.tgz", + "integrity": "sha512-t+ZuDzAlsXKa6tLxNZT81gEAt4GNwsKP/Id2wluhmUWD/lwYW0uum1JiPUuanw8xD6TdakCW/7ULZc7aQUBLCQ==", + "dev": true, + "dependencies": { + "@storybook/theming": "8.6.12", + "better-opn": "^3.0.2", + "browser-assert": "^1.2.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", + "esbuild-register": "^3.5.0", + "jsdoc-type-pratt-parser": "^4.0.0", + "process": "^0.11.10", + "recast": "^0.23.5", + "semver": "^7.6.2", + "util": "^0.12.5", + "ws": "^8.2.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/@storybook/core/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/csf": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.13.tgz", + "integrity": "sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==", + "dev": true, + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.6.12.tgz", + "integrity": "sha512-6s8CnP1aoKPb3XtC0jRLUp8M5vTA8RhGAwQDKUsFpCC7g89JR9CaKs9FY2ZSzsNbjR15uASi7b3K8BzeYumYQg==", + "dev": true, + "dependencies": { + "unplugin": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true + }, + "node_modules/@storybook/icons": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-1.4.0.tgz", + "integrity": "sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta" + } + }, + "node_modules/@storybook/instrumenter": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/instrumenter/-/instrumenter-8.6.12.tgz", + "integrity": "sha512-VK5fYAF8jMwWP/u3YsmSwKGh+FeSY8WZn78flzRUwirp2Eg1WWjsqPRubAk7yTpcqcC/km9YMF3KbqfzRv2s/A==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@vitest/utils": "^2.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/manager-api": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/manager-api/-/manager-api-8.6.12.tgz", + "integrity": "sha512-O0SpISeJLNTQvhSBOsWzzkCgs8vCjOq1578rwqHlC6jWWm4QmtfdyXqnv7rR1Hk08kQ+Dzqh0uhwHx0nfwy4nQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/preview-api": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/preview-api/-/preview-api-8.6.12.tgz", + "integrity": "sha512-84FE3Hrs0AYKHqpDZOwx1S/ffOfxBdL65lhCoeI8GoWwCkzwa9zEP3kvXBo/BnEDO7nAfxvMhjASTZXbKRJh5Q==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@storybook/react": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-8.6.12.tgz", + "integrity": "sha512-NzxlHLA5DkDgZM/dMwTYinuzRs6rsUPmlqP+NIv6YaciQ4NGnTYyOC7R/SqI6HHFm8ZZ5eMYvpfiFmhZ9rU+rQ==", + "dev": true, + "dependencies": { + "@storybook/components": "8.6.12", + "@storybook/global": "^5.0.0", + "@storybook/manager-api": "8.6.12", + "@storybook/preview-api": "8.6.12", + "@storybook/react-dom-shim": "8.6.12", + "@storybook/theming": "8.6.12" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@storybook/test": "8.6.12", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.6.12", + "typescript": ">= 4.2.x" + }, + "peerDependenciesMeta": { + "@storybook/test": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@storybook/react-dom-shim": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-8.6.12.tgz", + "integrity": "sha512-51QvoimkBzYs8s3rCYnY5h0cFqLz/Mh0vRcughwYaXckWzDBV8l67WBO5Xf5nBsukCbWyqBVPpEQLww8s7mrLA==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/react-vite": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-8.6.12.tgz", + "integrity": "sha512-UA2Kule99oyFgHdhcuhrRwCKyWu/yMbqbl9U7NwowFHNwWWFjVMMir/AmfShb/H1C1DQ3LqOad6/QwJyPLjP8g==", + "dev": true, + "dependencies": { + "@joshwooding/vite-plugin-react-docgen-typescript": "0.5.0", + "@rollup/pluginutils": "^5.0.2", + "@storybook/builder-vite": "8.6.12", + "@storybook/react": "8.6.12", + "find-up": "^5.0.0", + "magic-string": "^0.30.0", + "react-docgen": "^7.0.0", + "resolve": "^1.22.8", + "tsconfig-paths": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "@storybook/test": "8.6.12", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", + "storybook": "^8.6.12", + "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "@storybook/test": { + "optional": true + } + } + }, + "node_modules/@storybook/test": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/test/-/test-8.6.12.tgz", + "integrity": "sha512-0BK1Eg+VD0lNMB1BtxqHE3tP9FdkUmohtvWG7cq6lWvMrbCmAmh3VWai3RMCCDOukPFpjabOr8BBRLVvhNpv2w==", + "dev": true, + "dependencies": { + "@storybook/global": "^5.0.0", + "@storybook/instrumenter": "8.6.12", + "@testing-library/dom": "10.4.0", + "@testing-library/jest-dom": "6.5.0", + "@testing-library/user-event": "14.5.2", + "@vitest/expect": "2.0.5", + "@vitest/spy": "2.0.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.12" + } + }, + "node_modules/@storybook/theming": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.12.tgz", + "integrity": "sha512-6VjZg8HJ2Op7+KV7ihJpYrDnFtd9D1jrQnUS8LckcpuBXrIEbaut5+34ObY8ssQnSqkk2GwIZBBBQYQBCVvkOw==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@stylistic/eslint-plugin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz", + "integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.23.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tabler/icons": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.31.0.tgz", + "integrity": "sha512-dblAdeKY3+GA1U+Q9eziZ0ooVlZMHsE8dqP0RkwvRtEsAULoKOYaCUOcJ4oW1DjWegdxk++UAt2SlQVnmeHv+g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.31.0.tgz", + "integrity": "sha512-2rrCM5y/VnaVKnORpDdAua9SEGuJKVqPtWxeQ/vUVsgaUx30LDgBZph7/lterXxDY1IKR6NO//HDhWiifXTi3w==", + "dependencies": { + "@tabler/icons": "3.31.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.73.3", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.73.3.tgz", + "integrity": "sha512-GmUtnOkRzDuNOq96g3eW5ADKC1nWfrM9RI0kRyQVr87rOl6y+PUgkuVaPxh3R2C0EVODxCS07b9aaWphidl/OA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.18.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.74.3", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.74.3.tgz", + "integrity": "sha512-Mqk+5o3qTuAiZML248XpNH8r2cOzl15+LTbUsZQEwvSvn1GU4VQhvqzAbil36p+MBxpr/58oBSnRzhrBevDhfg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.73.3", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.73.3.tgz", + "integrity": "sha512-hBQyYwsOuO7QOprK75NzfrWs/EQYjgFA0yykmcvsV62q0t6Ua97CU3sYgjHx0ZvxkXSOMkY24VRJ5uv9f5Ik4w==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.74.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.74.3.tgz", + "integrity": "sha512-QrycUn0wxjVPzITvQvOxFRdhlAwIoOQSuav7qWD4SWCoKCdLbyRZ2vji2GuBq/glaxbF4wBx3fqcYRDOt8KDTA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.74.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.74.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.74.3.tgz", + "integrity": "sha512-H7TsOBB1fRCuuawrBzKMoIszqqILr2IN5oGLYMl7QG7ERJpMdc4hH8OwzBhVxJnmKeGwgtTQgcdKepfoJCWvFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.73.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.74.3", + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz", + "integrity": "sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==", + "dev": true, + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + }, + "node_modules/@testing-library/user-event": { + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.5.2.tgz", + "integrity": "sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==", + "dev": true, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@tiptap/core": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.11.7.tgz", + "integrity": "sha512-zN+NFFxLsxNEL8Qioc+DL6b8+Tt2bmRbXH22Gk6F6nD30x83eaUSFlSv3wqvgyCq3I1i1NO394So+Agmayx6rQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.11.7.tgz", + "integrity": "sha512-liD8kWowl3CcYCG9JQlVx1eSNc/aHlt6JpVsuWvzq6J8APWX693i3+zFqyK2eCDn0k+vW62muhSBe3u09hA3Zw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.11.7.tgz", + "integrity": "sha512-VTR3JlldBixXbjpLTFme/Bxf1xeUgZZY3LTlt5JDlCW3CxO7k05CIa+kEZ8LXpog5annytZDUVtWqxrNjmsuHQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.11.7.tgz", + "integrity": "sha512-0vYqSUSSap3kk3/VT4tFE1/6StX70I3/NKQ4J68ZSFgkgyB3ZVlYv7/dY3AkEukjsEp3yN7m8Gw8ei2eEwyzwg==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.11.7.tgz", + "integrity": "sha512-WbPogE2/Q3e3/QYgbT1Sj4KQUfGAJNc5pvb7GrUbvRQsAh7HhtuO8hqdDwH8dEdD/cNUehgt17TO7u8qV6qeBw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.11.7.tgz", + "integrity": "sha512-VpPO1Uy/eF4hYOpohS/yMOcE1C07xmMj0/D989D9aS1x95jWwUVrSkwC+PlWMUBx9PbY2NRsg1ZDwVvlNKZ6yQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.11.7.tgz", + "integrity": "sha512-To/y/2H04VWqiANy53aXjV7S6fA86c2759RsH1hTIe57jA1KyE7I5tlAofljOLZK/covkGmPeBddSPHGJbz++Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.11.7.tgz", + "integrity": "sha512-95ouJXPjdAm9+VBRgFo4lhDoMcHovyl/awORDI8gyEn0Rdglt+ZRZYoySFzbVzer9h0cre+QdIwr9AIzFFbfdA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.11.7.tgz", + "integrity": "sha512-63mL+nxQILizsr5NbmgDeOjFEWi34BLt7evwL6UUZEVM15K8V1G8pD9Y0kCXrZYpHWz0tqFRXdrhDz0Ppu8oVw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.11.7.tgz", + "integrity": "sha512-DG54WoUu2vxHRVzKZiR5I5RMOYj45IlxQMkBAx1wjS0ch41W8DUYEeipvMMjCeKtEI+emz03xYUcOAP9LRmg+w==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.11.7.tgz", + "integrity": "sha512-EceesmPG7FyjXZ8EgeJPUov9G1mAf2AwdypxBNH275g6xd5dmU/KvjoFZjmQ0X1ve7mS+wNupVlGxAEUYoveew==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.11.7.tgz", + "integrity": "sha512-zTkZSA6q+F5sLOdCkiC2+RqJQN0zdsJqvFIOVFL/IDVOnq6PZO5THzwRRLvOSnJJl3edRQCl/hUgS0L5sTInGQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.11.7.tgz", + "integrity": "sha512-8kWh7y4Rd2fwxfWOhFFWncHdkDkMC1Z60yzIZWjIu72+6yQxvo8w3yeb7LI7jER4kffbMmadgcfhCHC/fkObBA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-highlight": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-highlight/-/extension-highlight-2.11.7.tgz", + "integrity": "sha512-c/NH4kIpNOWCUQv8RkFNDyOcgt+2pYFpDf0QBJmzhAuv4BIeS2bDmDtuNS7VgoWRZH+xxCNXfvm2BG+kjtipEg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.11.7.tgz", + "integrity": "sha512-Cu5x3aS13I040QSRoLdd+w09G4OCVfU+azpUqxufZxeNs9BIJC+0jowPLeOxKDh6D5GGT2A8sQtxc6a/ssbs8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.11.7.tgz", + "integrity": "sha512-uVmQwD2dzZ5xwmvUlciy0ItxOdOfQjH6VLmu80zyJf8Yu7mvwP8JyxoXUX0vd1xHpwAhgQ9/ozjIWYGIw79DPQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.11.7.tgz", + "integrity": "sha512-r985bkQfG0HMpmCU0X0p/Xe7U1qgRm2mxvcp6iPCuts2FqxaCoyfNZ8YnMsgVK1mRhM7+CQ5SEg2NOmQNtHvPw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.11.7.tgz", + "integrity": "sha512-qKIowE73aAUrnQCIifYP34xXOHOsZw46cT/LBDlb0T60knVfQoKVE4ku08fJzAV+s6zqgsaaZ4HVOXkQYLoW7g==", + "dependencies": { + "linkifyjs": "^4.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.11.7.tgz", + "integrity": "sha512-6ikh7Y+qAbkSuIHXPIINqfzmWs5uIGrylihdZ9adaIyvrN1KSnWIqrZIk/NcZTg5YFIJlXrnGSRSjb/QM3WUhw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.11.7.tgz", + "integrity": "sha512-bLGCHDMB0vbJk7uu8bRg8vES3GsvxkX7Cgjgm/6xysHFbK98y0asDtNxkW1VvuRreNGz4tyB6vkcVCfrxl4jKw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.11.7.tgz", + "integrity": "sha512-Pl3B4q6DJqTvvAdraqZaNP9Hh0UWEHL5nNdxhaRNuhKaUo7lq8wbDSIxIW3lvV0lyCs0NfyunkUvSm1CXb6d4Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-placeholder": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.11.7.tgz", + "integrity": "sha512-/06zXV4HIjYoiaUq1fVJo/RcU8pHbzx21evOpeG/foCfNpMI4xLU/vnxdUi6/SQqpZMY0eFutDqod1InkSOqsg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.11.7.tgz", + "integrity": "sha512-D6GYiW9F24bvAY7XMOARNZbC8YGPzdzWdXd8VOOJABhf4ynMi/oW4NNiko+kZ67jn3EGaKoz32VMJzNQgYi1HA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-subscript": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-subscript/-/extension-subscript-2.11.7.tgz", + "integrity": "sha512-I25ZexCddFJ9701DCCtQbX3Vtxzj5d9ss2GAXVweIUCdATCScaebsznyUQoN5papmhTxXsw5OD+K2ZHxP82pew==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-superscript": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-superscript/-/extension-superscript-2.11.7.tgz", + "integrity": "sha512-dNRpCcRJs0Qvv0sZRgbH7Y5hDVbWsGSZjtwFCs/mysPrvHqmXjzo7568kYWTggxEYxnXw6n0FfkCAEHlt0N90Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.11.7.tgz", + "integrity": "sha512-wObCn8qZkIFnXTLvBP+X8KgaEvTap/FJ/i4hBMfHBCKPGDx99KiJU6VIbDXG8d5ZcFZE0tOetK1pP5oI7qgMlQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text-align": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-2.11.7.tgz", + "integrity": "sha512-3M8zd9ROADXazVNpgR6Ejs1evSvBveN36qN4GgV71GqrNlTcjqYgQcXFLQrsd2hnE+aXir8/8bLJ+aaJXDninA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-text-style": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-style/-/extension-text-style-2.11.7.tgz", + "integrity": "sha512-LHO6DBg/9SkCQFdWlVfw9nolUmw+Cid94WkTY+7IwrpyG2+ZGQxnKpCJCKyeaFNbDoYAtvu0vuTsSXeCkgShcA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-2.11.7.tgz", + "integrity": "sha512-NtoQw6PGijOAtXC6G+0Aq0/Z5wwEjPhNHs8nsjXogfWIgaj/aI4/zfBnA06eI3WT+emMYQTl0fTc4CUPnLVU8g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.11.7.tgz", + "integrity": "sha512-7gEEfz2Q6bYKXM07vzLUD0vqXFhC5geWRA6LCozTiLdVFDdHWiBrvb2rtkL5T7mfLq03zc1QhH7rI3F6VntOEA==", + "dependencies": { + "prosemirror-changeset": "^2.2.1", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.23.0", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.4.1", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.11.7.tgz", + "integrity": "sha512-gQZEUkAoPsBptnB4T2gAtiUxswjVGhfsM9vOElQco+b11DYmy110T2Zuhg+2YGvB/CG3RoWJx34808P0FX1ijA==", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.11.7", + "@tiptap/extension-floating-menu": "^2.11.7", + "@types/use-sync-external-store": "^0.0.6", + "fast-deep-equal": "^3", + "use-sync-external-store": "^1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.11.7.tgz", + "integrity": "sha512-K+q51KwNU/l0kqRuV5e1824yOLVftj6kGplGQLvJG56P7Rb2dPbM/JeaDbxQhnHT/KDGamG0s0Po0M3pPY163A==", + "dependencies": { + "@tiptap/core": "^2.11.7", + "@tiptap/extension-blockquote": "^2.11.7", + "@tiptap/extension-bold": "^2.11.7", + "@tiptap/extension-bullet-list": "^2.11.7", + "@tiptap/extension-code": "^2.11.7", + "@tiptap/extension-code-block": "^2.11.7", + "@tiptap/extension-document": "^2.11.7", + "@tiptap/extension-dropcursor": "^2.11.7", + "@tiptap/extension-gapcursor": "^2.11.7", + "@tiptap/extension-hard-break": "^2.11.7", + "@tiptap/extension-heading": "^2.11.7", + "@tiptap/extension-history": "^2.11.7", + "@tiptap/extension-horizontal-rule": "^2.11.7", + "@tiptap/extension-italic": "^2.11.7", + "@tiptap/extension-list-item": "^2.11.7", + "@tiptap/extension-ordered-list": "^2.11.7", + "@tiptap/extension-paragraph": "^2.11.7", + "@tiptap/extension-strike": "^2.11.7", + "@tiptap/extension-text": "^2.11.7", + "@tiptap/extension-text-style": "^2.11.7", + "@tiptap/pm": "^2.11.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@total-typescript/ts-reset": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@total-typescript/ts-reset/-/ts-reset-0.6.1.tgz", + "integrity": "sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==", + "dev": true + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==" + }, + "node_modules/@types/doctrine": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", + "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.56.12", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", + "integrity": "sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", + "integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==", + "dev": true, + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, + "node_modules/@types/html-to-text": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/html-to-text/-/html-to-text-9.0.4.tgz", + "integrity": "sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==", + "dev": true + }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.14.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", + "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/qrcode": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz", + "integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-avatar-editor": { + "version": "13.0.4", + "resolved": "https://registry.npmjs.org/@types/react-avatar-editor/-/react-avatar-editor-13.0.4.tgz", + "integrity": "sha512-WQjmacEOoEYQb6CkAXYspmtruCPXFzLjEI92zV27yqveGBRkh7Quo5oYMvAgaEeqpXsGEr/wzXcdSTaH982Wtg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-gtm-module": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/react-gtm-module/-/react-gtm-module-2.0.4.tgz", + "integrity": "sha512-5wPMWsUE5AI6O0B0K1/zbs0rFHBKu+7NWXQwDXhqvA12ooLD6W1AYiWZqR4UiOd7ixZDV1H5Ys301zEsqyIfNg==", + "dev": true + }, + "node_modules/@types/react-helmet": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.11.tgz", + "integrity": "sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/resolve": { + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", + "integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@types/styled-components": { + "version": "5.1.34", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.34.tgz", + "integrity": "sha512-mmiVvwpYklFIv9E8qfxuPyIt/OuyIrn6gMOAMOFUO3WJfSrSE+sGUoa4PiZj77Ut7bKZpaa6o1fBKS/4TOEvnA==", + "dev": true, + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "optional": true + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.30.1.tgz", + "integrity": "sha512-v+VWphxMjn+1t48/jO4t950D6KR8JaJuNXzi33Ve6P8sEmPr5k6CEXjdGwT6+LodVnEa91EQCtwjWNUCPweo+Q==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/type-utils": "8.30.1", + "@typescript-eslint/utils": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", + "integrity": "sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@typescript-eslint/experimental-utils/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.30.1.tgz", + "integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.30.1.tgz", + "integrity": "sha512-+C0B6ChFXZkuaNDl73FJxRYT0G7ufVPOSQkqkpM/U198wUwUFOtgo1k/QzFh1KjpBitaK7R1tgjVz6o9HmsRPg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.30.1.tgz", + "integrity": "sha512-64uBF76bfQiJyHgZISC7vcNz3adqQKIccVoKubyQcOnNcdJBvYOILV1v22Qhsw3tw3VQu5ll8ND6hycgAR5fEA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.30.1", + "@typescript-eslint/utils": "8.30.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.30.1.tgz", + "integrity": "sha512-81KawPfkuulyWo5QdyG/LOKbspyyiW+p4vpn4bYO7DM/hZImlVnFwrpCTnmNMOt8CvLRr5ojI9nU1Ekpw4RcEw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.30.1.tgz", + "integrity": "sha512-kQQnxymiUy9tTb1F2uep9W6aBiYODgq5EMSk6Nxh4Z+BDUoYUSa029ISs5zTzKBFnexQEh71KqwjKnRz58lusQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/visitor-keys": "8.30.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.30.1.tgz", + "integrity": "sha512-T/8q4R9En2tcEsWPQgB5BQ0XJVOtfARcUvOa8yJP3fh9M/mXraLxZrkCfGb6ChrO/V3W+Xbd04RacUEqk1CFEQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.30.1", + "@typescript-eslint/types": "8.30.1", + "@typescript-eslint/typescript-estree": "8.30.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.30.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.30.1.tgz", + "integrity": "sha512-aEhgas7aJ6vZnNFC7K4/vMGDGyOiqWcYZPpIWrTKuTAlsvDNKy2GFDqh9smL+iq069ZvR0YzEeq0B8NJlLzjFA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.30.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.0.tgz", + "integrity": "sha512-x/EztcTKVj+TDeANY1WjNeYsvZjZdfWRMP/KXi5Yn8BoTzpa13ZltaQqKfvWYbX8CE10GOHHdC5v86jY9x8i/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.10", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz", + "integrity": "sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.0.5", + "@vitest/utils": "2.0.5", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/@vitest/utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz", + "integrity": "sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz", + "integrity": "sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xstate/fsm": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.6.5.tgz", + "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==" + }, + "node_modules/@zod/core": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@zod/core/-/core-0.1.0.tgz", + "integrity": "sha512-hSXsufqjH7u8DiJPT0KY1rFWIhjkdXGM8MhMLwzaeOMhxMA4bzjWLQwSoAToJunUTVrpmfdo4dUFzNaU219+VQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@zod/mini": { + "version": "4.0.0-beta.0", + "resolved": "https://registry.npmjs.org/@zod/mini/-/mini-4.0.0-beta.0.tgz", + "integrity": "sha512-ux1pJYQJO0S/uAldc0KGKiBFvqPpQqfC8vXbBJ3tDrcWCCa6/QBQPexZFn/cHscTxA/SnEJEAxa7qGTwPQC5Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zod/core": "0.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/abs-svg-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/abs-svg-path/-/abs-svg-path-0.1.1.tgz", + "integrity": "sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/add-px-to-style": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-px-to-style/-/add-px-to-style-1.0.0.tgz", + "integrity": "sha512-YMyxSlXpPjD8uWekCQGuN40lV4bnZagUwqa2m/uFv1z/tNImSk9fnXVMUI5qwME/zzI3MMQRvjZ+69zyfSSyew==" + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.2.tgz", + "integrity": "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-styled-components/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true + }, + "node_modules/babel-preset-react-app": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.0.1.tgz", + "integrity": "sha512-b0D9IZ1WhhCWkrTXyFuIIgqGzSkRIH5D5AmB0bXbzYAB1OBAwHcUeyWW2LorutLWF5btNo/N7r/cIdmvvKJlYg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/better-opn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", + "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", + "dev": true, + "dependencies": { + "open": "^8.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/birecord": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/birecord/-/birecord-0.1.1.tgz", + "integrity": "sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==", + "dev": true, + "license": "(MIT OR Apache-2.0)" + }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "node_modules/bpmn-js": { + "version": "18.4.0", + "resolved": "https://registry.npmjs.org/bpmn-js/-/bpmn-js-18.4.0.tgz", + "integrity": "sha512-Dx9f6+P9fdARKCxKy/sekXGlsR5Gx8G7GIuj0ELhXqK+h2cK7VT0TpUMkpYVEPVX7zMAxN/0RKs2D7wruS5YWg==", + "dependencies": { + "bpmn-moddle": "^9.0.1", + "diagram-js": "^15.2.4", + "diagram-js-direct-editing": "^3.2.0", + "ids": "^1.0.5", + "inherits-browser": "^0.1.0", + "min-dash": "^4.1.1", + "min-dom": "^4.2.1", + "tiny-svg": "^3.1.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/bpmn-js-color-picker": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/bpmn-js-color-picker/-/bpmn-js-color-picker-0.7.1.tgz", + "integrity": "sha512-SsDKewfopMPFTETAS1ZUWNKZvnQ7OBsODVcFEL2GzrQusW1VtnoCB2wc2vOtcafLAUPoUaRnVx5gZNtvywnJQA==", + "engines": { + "node": "*" + }, + "peerDependencies": { + "bpmn-js": ">= 14" + } + }, + "node_modules/bpmn-moddle": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/bpmn-moddle/-/bpmn-moddle-9.0.1.tgz", + "integrity": "sha512-jO2P5RBx0cZCCd+imqhpNE5anttaYuGd71u76NEA/qMZwJSW1t5ETAtw9/E2InfiPU2w0TR8oxPyopJXRc9VQg==", + "dependencies": { + "min-dash": "^4.2.1", + "moddle": "^7.0.0", + "moddle-xml": "^11.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browser-assert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", + "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==", + "dev": true + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chart.js": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/component-event": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/component-event/-/component-event-0.2.1.tgz", + "integrity": "sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/core-js-compat": { + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.40.0.tgz", + "integrity": "sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==", + "dependencies": { + "browserslist": "^4.24.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/country-code-to-flag-emoji": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/country-code-to-flag-emoji/-/country-code-to-flag-emoji-2.0.0.tgz", + "integrity": "sha512-Mn5DWG5DLF4WfvofdzLGIes2CJYQdONtFuZNzkrbY/z/ohM1p7rLeBM+ElqA0pxjryJIjoKtqLMn5gQAqSj72Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/country-code-to-flag-emoji?sponsor=1" + } + }, + "node_modules/countup.js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/countup.js/-/countup.js-2.8.0.tgz", + "integrity": "sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ==" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==" + }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "optional": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" + }, + "node_modules/diagram-js": { + "version": "15.2.4", + "resolved": "https://registry.npmjs.org/diagram-js/-/diagram-js-15.2.4.tgz", + "integrity": "sha512-8v0U8AY6a5Vd6Cys5MdN7+yYRhs294/Q4ixkER/3v2ZPSbVCnK9XfbeYbB5QQfoCMkBnY7WBbLbcjvRNHTxnpw==", + "dependencies": { + "@bpmn-io/diagram-js-ui": "^0.2.3", + "clsx": "^2.1.0", + "didi": "^10.2.2", + "inherits-browser": "^0.1.0", + "min-dash": "^4.1.0", + "min-dom": "^4.2.1", + "object-refs": "^0.4.0", + "path-intersection": "^3.0.0", + "tiny-svg": "^3.1.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/diagram-js-direct-editing": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/diagram-js-direct-editing/-/diagram-js-direct-editing-3.2.0.tgz", + "integrity": "sha512-+pyxeQGBSdLiZX0/tmmsm2qZSvm9YtVzod5W3RMHSTR7VrkUMD6E7EX/W9JQv3ebxO7oIdqFmytmNDDpSHnYEw==", + "dependencies": { + "min-dash": "^4.0.0", + "min-dom": "^4.2.1" + }, + "engines": { + "node": "*" + }, + "peerDependencies": { + "diagram-js": "*" + } + }, + "node_modules/diagram-js-grid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/diagram-js-grid/-/diagram-js-grid-1.1.0.tgz", + "integrity": "sha512-hnqRrWjbMA8YsBqaJe/GVIyJBITPSmUfsQVfN78jjtn1Elw5FZu838kVYQ/+FBOaTOxFixjyKgSGXc847uH2JA==", + "dependencies": { + "min-dash": "^4.1.1", + "tiny-svg": "^3.0.1" + } + }, + "node_modules/diagram-js-minimap": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diagram-js-minimap/-/diagram-js-minimap-5.2.0.tgz", + "integrity": "sha512-QEvHEeBEyRorcIWb3jyw2hd9XEWEQ+cpt3RmtOhbHk0aZgrwXRz3e7Xoeh0LiyYKozQlCtvkxwWX9GecpfxUkw==", + "dependencies": { + "min-dash": "^4.2.1", + "min-dom": "^4.2.1", + "tiny-svg": "^3.1.2" + } + }, + "node_modules/diagram-js/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/didi": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/didi/-/didi-10.2.2.tgz", + "integrity": "sha512-l8NYkYFXV1izHI65EyT8EXOjUZtKmQkHLTT89cSP7HU5J/G7AOj0dXKtLc04EXYlga99PBY18IPjOeZ+c3DI4w==", + "engines": { + "node": ">= 16" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dnd-core": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", + "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", + "dependencies": { + "@react-dnd/asap": "^5.0.1", + "@react-dnd/invariant": "^4.0.1", + "redux": "^4.2.0" + } + }, + "node_modules/dnd-core/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true + }, + "node_modules/dom-css": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dom-css/-/dom-css-2.1.0.tgz", + "integrity": "sha512-w9kU7FAbaSh3QKijL6n59ofAhkkmMJ31GclJIz/vyQdjogfyxcB6Zf8CZyibOERI5o0Hxz30VmJS7+7r5fEj2Q==", + "dependencies": { + "add-px-to-style": "1.0.0", + "prefix-style": "2.0.1", + "to-camel-case": "1.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/domify/-/domify-1.4.2.tgz", + "integrity": "sha512-m4yreHcUWHBncGVV7U+yQzc12vIlq0jMrtHZ5mW6dQMiL/7skSYNVX9wqKwOtyO9SGCgevrAFEgOCAHmamHTUA==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dompurify": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.5.tgz", + "integrity": "sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/easy-table": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", + "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "optionalDependencies": { + "wcwidth": "^1.0.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.82", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.82.tgz", + "integrity": "sha512-Zq16uk1hfQhyGx5GpwPAYDwddJuSGhtRhgOA2mCxANYaDT79nAeGnaXogMGng4KqLaJUVnOnuL0+TDop9nLOiA==" + }, + "node_modules/emoji-mart": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", + "integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==" + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==" + }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.0.tgz", + "integrity": "sha512-Ujz8Al/KfOVR7fkaghAB1WvnLsdYxHDWmfoi2vlA2jZWRg31XhIC1a4B+/I24muD8iSbHxJ1JkrfqmWb65P/Mw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", + "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.1", + "@esbuild/android-arm": "0.25.1", + "@esbuild/android-arm64": "0.25.1", + "@esbuild/android-x64": "0.25.1", + "@esbuild/darwin-arm64": "0.25.1", + "@esbuild/darwin-x64": "0.25.1", + "@esbuild/freebsd-arm64": "0.25.1", + "@esbuild/freebsd-x64": "0.25.1", + "@esbuild/linux-arm": "0.25.1", + "@esbuild/linux-arm64": "0.25.1", + "@esbuild/linux-ia32": "0.25.1", + "@esbuild/linux-loong64": "0.25.1", + "@esbuild/linux-mips64el": "0.25.1", + "@esbuild/linux-ppc64": "0.25.1", + "@esbuild/linux-riscv64": "0.25.1", + "@esbuild/linux-s390x": "0.25.1", + "@esbuild/linux-x64": "0.25.1", + "@esbuild/netbsd-arm64": "0.25.1", + "@esbuild/netbsd-x64": "0.25.1", + "@esbuild/openbsd-arm64": "0.25.1", + "@esbuild/openbsd-x64": "0.25.1", + "@esbuild/sunos-x64": "0.25.1", + "@esbuild/win32-arm64": "0.25.1", + "@esbuild/win32-ia32": "0.25.1", + "@esbuild/win32-x64": "0.25.1" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", + "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/eslint-parser": "^7.16.3", + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/eslint-plugin": "^5.5.0", + "@typescript-eslint/parser": "^5.5.0", + "babel-preset-react-app": "^10.0.1", + "confusing-browser-globals": "^1.0.11", + "eslint-plugin-flowtype": "^8.0.3", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jest": "^25.3.0", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.27.1", + "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-testing-library": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "eslint": "^8.0.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-react-app/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-react-app/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-config-react-app/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-8.0.3.tgz", + "integrity": "sha512-dX8l6qUL6O+fYPtpNRideCFSpmWOUVx5QcaGLVqe/vlDiBSe4vYljDWDETwnyFzpl7By/WVIu6rcrniCgH9BqQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "string-natural-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@babel/plugin-syntax-flow": "^7.14.5", + "@babel/plugin-transform-react-jsx": "^7.14.9", + "eslint": "^8.1.0" + } + }, + "node_modules/eslint-plugin-i18next": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-i18next/-/eslint-plugin-i18next-6.1.1.tgz", + "integrity": "sha512-/Vy6BfX44njxpRnbJm7bbph0KaNJF2eillqN5W+u03hHuxmh9BjtjdPSrI9HPtyoEbG4j5nBn9gXm/dg99mz3Q==", + "dev": true, + "dependencies": { + "lodash": "^4.17.21", + "requireindex": "~1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", + "dev": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "25.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz", + "integrity": "sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^4.0.0 || ^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", + "integrity": "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-hooks-extra": { + "version": "1.48.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks-extra/-/eslint-plugin-react-hooks-extra-1.48.1.tgz", + "integrity": "sha512-tVHhNvgyMR78RKmaQ0YBXdfUPhc+oH46SvU7usWDyGzhRiwM4uYq5FTLy5hwCagi/bsXvpkprwlSZRuhBPvP3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "1.48.1", + "@eslint-react/core": "1.48.1", + "@eslint-react/eff": "1.48.1", + "@eslint-react/kit": "1.48.1", + "@eslint-react/shared": "1.48.1", + "@eslint-react/var": "1.48.1", + "@typescript-eslint/scope-manager": "^8.30.1", + "@typescript-eslint/type-utils": "^8.30.1", + "@typescript-eslint/types": "^8.30.1", + "@typescript-eslint/utils": "^8.30.1", + "string-ts": "^2.2.1", + "ts-pattern": "^5.7.0" + }, + "engines": { + "bun": ">=1.0.15", + "node": ">=18.18.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": "^4.9.5 || ^5.3.3" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": false + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-regexp": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-2.7.0.tgz", + "integrity": "sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "comment-parser": "^1.4.0", + "jsdoc-type-pratt-parser": "^4.0.0", + "refa": "^0.12.1", + "regexp-ast-analysis": "^0.7.1", + "scslre": "^0.3.0" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "eslint": ">=8.44.0" + } + }, + "node_modules/eslint-plugin-storybook": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.12.0.tgz", + "integrity": "sha512-Lg5I0+npTgiYgZ4KSvGWGDFZi3eOCNJPaWX0c9rTEEXC5wvooOClsP9ZtbI4hhFKyKgYR877KiJxbRTSJq9gWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@storybook/csf": "^0.1.11", + "@typescript-eslint/utils": "^8.8.1", + "ts-dedent": "^2.2.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.1.tgz", + "integrity": "sha512-5eX9e1Kc2PqVRed3taaLnAAqPZGEX75C+M/rXzUAI3wIg/ZxzUm1OVAwfe/O+vE+6YXOLetSe9g5GKD2ecXipw==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^5.58.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0", + "npm": ">=6" + }, + "peerDependencies": { + "eslint": "^7.5.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-plugin-testing-library/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/framer-motion": { + "version": "12.7.3", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.7.3.tgz", + "integrity": "sha512-dNT4l5gEnUo2ytXLUBUf6AI21dZ77TMclDKE3ElaIHZ8m90nJ/NCcExW51zdSIaS0RhAS5iXcF7bEIxZe8XG2g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.7.3", + "motion-utils": "^12.7.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-blob-duration": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-blob-duration/-/get-blob-duration-1.2.0.tgz", + "integrity": "sha512-2xNJa+oKznR21eC2ThMzw4a1931a3ogA8aHoY92xruZufc/02G7pl/P793GJZytkyI8xMJ2DepEQ7MWvg/tn/Q==", + "dependencies": { + "@babel/runtime": "7.11.2" + } + }, + "node_modules/get-blob-duration/node_modules/@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/get-blob-duration/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hsl-to-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-to-hex/-/hsl-to-hex-1.0.0.tgz", + "integrity": "sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==", + "dependencies": { + "hsl-to-rgb-for-reals": "^1.1.0" + } + }, + "node_modules/hsl-to-rgb-for-reals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hsl-to-rgb-for-reals/-/hsl-to-rgb-for-reals-1.1.1.tgz", + "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==" + }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==" + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hyphen": { + "version": "1.10.6", + "resolved": "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz", + "integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==" + }, + "node_modules/i18next": { + "version": "25.0.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.0.0.tgz", + "integrity": "sha512-POPvwjOPR1GQvRnbikTMPEhQD+ekd186MHE6NtVxl3Lby+gPp0iq60eCqGrY6wfRnp1lejjFNu0EKs1afA322w==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.4.tgz", + "integrity": "sha512-f3frU3pIxD50/Tz20zx9TD9HobKYg47fmAETb117GKGPrhwcSSPJDoCposXlVycVebQ9GQohC3Efbpq7/nnJ5w==", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", + "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, + "node_modules/ids": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ids/-/ids-1.0.5.tgz", + "integrity": "sha512-XQ0yom/4KWTL29sLG+tyuycy7UmeaM/79GRtSJq6IG9cJGIPeBz5kwDCguie3TwxaMNIc3WtPi0cTa1XYHicpw==" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/inherits-browser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/inherits-browser/-/inherits-browser-0.1.0.tgz", + "integrity": "sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-async-function": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.0.tgz", + "integrity": "sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jay-peg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jay-peg/-/jay-peg-1.1.1.tgz", + "integrity": "sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==", + "dependencies": { + "restructure": "^3.0.0" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-file-download": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", + "integrity": "sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/knip": { + "version": "5.50.4", + "resolved": "https://registry.npmjs.org/knip/-/knip-5.50.4.tgz", + "integrity": "sha512-In+GjPpd2P3IDZnBBP4QF27vhQOhuBkICiuN9j+DMOf/m/qAFLGcbvuAGxco8IDvf26pvBnfeSmm1f6iNCkgOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + }, + { + "type": "polar", + "url": "https://polar.sh/webpro-nl" + } + ], + "license": "ISC", + "dependencies": { + "@nodelib/fs.walk": "^1.2.3", + "easy-table": "1.2.0", + "enhanced-resolve": "^5.18.1", + "fast-glob": "^3.3.3", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "minimist": "^1.2.8", + "picocolors": "^1.1.0", + "picomatch": "^4.0.1", + "pretty-ms": "^9.0.0", + "smol-toml": "^1.3.1", + "strip-json-comments": "5.0.1", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": ">=18.18.0" + }, + "peerDependencies": { + "@types/node": ">=18", + "typescript": ">=5.0.4" + } + }, + "node_modules/knip/node_modules/strip-json-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.1.tgz", + "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz", + "integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw==" + }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.2.0.tgz", + "integrity": "sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==" + }, + "node_modules/load-script": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", + "integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==" + }, + "node_modules/lodash.startswith": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.startswith/-/lodash.startswith-4.2.1.tgz", + "integrity": "sha512-XClYR1h4/fJ7H+mmCKppbiBmljN/nGs73iq2SjCT9SF4CBPoUHzLvWmH1GtZMhMBZSiRkHXfeA2RY1eIlJ75ww==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/mac-scrollbar": { + "version": "0.13.8", + "resolved": "https://registry.npmjs.org/mac-scrollbar/-/mac-scrollbar-0.13.8.tgz", + "integrity": "sha512-lpu9fV8lx7oUHzM4CwgbMOXlLJ+1OGdmxwXmreadq8+8pmnFAEJ1khUdqpFOhbD32vJORLu6RE8GO2+ODpf12w==", + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-or-similar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", + "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, + "node_modules/media-engine": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", + "integrity": "sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==" + }, + "node_modules/memoizerific": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", + "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, + "dependencies": { + "map-or-similar": "^1.5.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-dash": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/min-dash/-/min-dash-4.2.2.tgz", + "integrity": "sha512-qbhSYUxk6mBaF096B3JOQSumXbKWHenmT97cSpdNzgkWwGjhjhE/KZODCoDNhI2I4C9Cb6R/Q13S4BYkUSXoXQ==" + }, + "node_modules/min-dom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/min-dom/-/min-dom-4.2.1.tgz", + "integrity": "sha512-TMoL8SEEIhUWYgkj7XMSgxmwSyGI+4fP2KFFGnN3FbHfbGHVdsLYSz8LoIsgPhz4dWRmLvxWWSMgzZMJW5sZuA==", + "dependencies": { + "component-event": "^0.2.1", + "domify": "^1.4.1", + "min-dash": "^4.2.1" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mobx": { + "version": "6.13.7", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.7.tgz", + "integrity": "sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react-lite": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.1.0.tgz", + "integrity": "sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==", + "dependencies": { + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/moddle": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/moddle/-/moddle-7.2.0.tgz", + "integrity": "sha512-x1+JREThy7JBOBR3g2hbOnOfrlC/YAWXX9RzrSZS5HhqeuBly9H/PCtOBtcQs+Y2sjRAXF+WTNSgHvn8Uq+6Yw==", + "dependencies": { + "min-dash": "^4.2.1" + } + }, + "node_modules/moddle-xml": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/moddle-xml/-/moddle-xml-11.0.0.tgz", + "integrity": "sha512-L3Sseepfcq9Uy0iIfqEDTXSoYLva1Y/JGbN/4AMOeQ6cqbu8Ma/SDJIdOFm7smsAa64j2z3SwCGG3FIilQVnUg==", + "dependencies": { + "min-dash": "^4.0.0", + "saxen": "^10.0.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "moddle": ">= 6.2.0" + } + }, + "node_modules/motion-dom": { + "version": "12.7.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.7.3.tgz", + "integrity": "sha512-IjMt1YJHrvyvruFvmpmd6bGXXGCvmygrnvSb3aZ8KhOzF4H3PulU+cMBzH+U8TBJHjC/mnmJFRIA1Cu4vBfcBA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.7.2" + } + }, + "node_modules/motion-utils": { + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.7.2.tgz", + "integrity": "sha512-XhZwqctxyJs89oX00zn3OGCuIIpVevbTa+u82usWBC6pSHUd2AoNWiYa7Du8tJxJy9TFbZ82pcn5t7NOm1PHAw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nan": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nano-staged": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/nano-staged/-/nano-staged-0.8.0.tgz", + "integrity": "sha512-QSEqPGTCJbkHU2yLvfY6huqYPjdBrOaTMKatO1F8nCSrkQGXeKwtCiCnsdxnuMhbg3DTVywKaeWLGCE5oJpq0g==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0" + }, + "bin": { + "nano-staged": "lib/bin.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + }, + "node_modules/nodemailer": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", + "integrity": "sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-svg-path": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/normalize-svg-path/-/normalize-svg-path-1.1.0.tgz", + "integrity": "sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==", + "dependencies": { + "svg-arc-to-cubic-bezier": "^3.0.0" + } + }, + "node_modules/normalize.css": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize.css/-/normalize.css-8.0.1.tgz", + "integrity": "sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==" + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/numeralize-ru": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/numeralize-ru/-/numeralize-ru-2.0.0.tgz", + "integrity": "sha512-6EDlOoJ/Bf7vhNzotsbDo9pWQBMd3aYDom3yAl2lj6sYeNEcNbZFbuWgstBq2w2HGXoBOhvSWdNhicZSetTiFQ==" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-refs": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-refs/-/object-refs-0.4.0.tgz", + "integrity": "sha512-6kJqKWryKZmtte6QYvouas0/EIJKPI1/MMIuRsiBlNuhIMfqYTggzX2F1AJ2+cDs288xyi9GL7FyasHINR98BQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-svg-path": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/parse-svg-path/-/parse-svg-path-0.1.2.tgz", + "integrity": "sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==" + }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-intersection": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/path-intersection/-/path-intersection-3.1.0.tgz", + "integrity": "sha512-3xS3lvv/vuwm5aH2BVvNRvnvwR2Drde7jQClKpCXTYXIMMjcw/EnMhzCgeHwqbCpzi760PEfAkU53vSIlrNr9A==", + "engines": { + "node": ">= 14.20" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/pdfjs-dist": { + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" + } + }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/phone-number-to-timezone": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/phone-number-to-timezone/-/phone-number-to-timezone-1.0.8.tgz", + "integrity": "sha512-pWfc0zqgB7pGhrvUVNF5cL+6txkwRQVZzKSNYlWTXostjG0kLCIAgbdx4TbBQ04J0B2waaC182s6BT8YzwzbZw==" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/preact": { + "version": "10.12.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz", + "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prefix-style": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/prefix-style/-/prefix-style-2.0.1.tgz", + "integrity": "sha512-gdr1MBNVT0drzTq95CbSNdsrBDoHGlb2aDJP/FoY+1e+jSDPOb1Cv554gH2MGiSr2WTcXi/zu+NaFzfcHQkfBQ==" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz", + "integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==", + "dev": true, + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.1.0" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true + }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prosemirror-changeset": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", + "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.6.2.tgz", + "integrity": "sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", + "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", + "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.1.tgz", + "integrity": "sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", + "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", + "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.1.tgz", + "integrity": "sha512-Sl+oMfMtAjWtlcZoj/5L/Q39MpEnVZ840Xo330WJWUvgyhNmLBLN7MsHn07s53nG/KImevWHSE6fEj4q/GihHw==", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.20.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", + "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.24.1.tgz", + "integrity": "sha512-YM053N+vTThzlWJ/AtPtF1j0ebO36nvbmDy4U7qA2XQB8JVaQp1FmB9Jhrps8s+z+uxhhVTny4m20ptUvhk0Mg==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.3.tgz", + "integrity": "sha512-h+H0OQwZVqMon1PNn0AG9cTfx513zgIG2DY00eJ00Yvgb3UD+GQ/VlWW5rcaxacpCGT1Yx8nuhwXk4+QbXUfJA==", + "dependencies": { + "prosemirror-model": "^1.19.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.0.tgz", + "integrity": "sha512-gg1tAfH1sqpECdhIHOA/aLg2VH3ROKBWQ4m8Qp9mBKrOxQRW61zc+gMCI8nh22gnBzd1t2u1/NPLmO3nAa3ssg==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.6.4.tgz", + "integrity": "sha512-TkDY3Gw52gRFRfRn2f4wJv5WOgAOXLJA2CQJYIJ5+kdFbfj3acR4JUW6LX2e1hiEBiUwvEhzH5a3cZ5YSztpIA==", + "dependencies": { + "prosemirror-keymap": "^1.2.2", + "prosemirror-model": "^1.24.1", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.37.2" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.38.0.tgz", + "integrity": "sha512-O45kxXQTaP9wPdXhp8TKqCR+/unS/gnfg9Q93svQcB3j0mlp2XSPAmsPefxHADwzC+fbNS404jqRxm3UQaGvgw==", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dependencies": { + "inherits": "~2.0.3" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, + "node_modules/re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-avatar-editor": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-13.0.2.tgz", + "integrity": "sha512-a4ajbi7lwDh98kgEtSEeKMu0vs0CHTczkq4Xcxr1EiwMFH1GlgHCEtwGU8q/H5W8SeLnH4KPK8LUjEEaZXklxQ==", + "dependencies": { + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/runtime": "^7.12.5", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-chartjs-2": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", + "peerDependencies": { + "chart.js": "^4.1.1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-collapsible": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/react-collapsible/-/react-collapsible-2.10.0.tgz", + "integrity": "sha512-kEVsmlFfXBMTCnU5gwIv19MdmPAhbIPzz5Er37TiJSzRKS0IHrqAKQyQeHEmtoGIQMTcVI46FzE4z3NlVTx77A==", + "peerDependencies": { + "react": "~15 || ~16 || ~17 || ~18", + "react-dom": "~15 || ~16 || ~17 || ~18" + } + }, + "node_modules/react-countup": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/react-countup/-/react-countup-6.5.3.tgz", + "integrity": "sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==", + "dependencies": { + "countup.js": "^2.8.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/react-custom-scrollbars-2": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/react-custom-scrollbars-2/-/react-custom-scrollbars-2-4.5.0.tgz", + "integrity": "sha512-/z0nWAeXfMDr4+OXReTpYd1Atq9kkn4oI3qxq3iMXGQx1EEfwETSqB8HTAvg1X7dEqcCachbny1DRNGlqX5bDQ==", + "dependencies": { + "dom-css": "^2.0.0", + "prop-types": "^15.5.10", + "raf": "^3.1.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-dnd": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", + "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", + "dependencies": { + "@react-dnd/invariant": "^4.0.1", + "@react-dnd/shallowequal": "^4.0.1", + "dnd-core": "^16.0.1", + "fast-deep-equal": "^3.1.3", + "hoist-non-react-statics": "^3.3.2" + }, + "peerDependencies": { + "@types/hoist-non-react-statics": ">= 3.3.1", + "@types/node": ">= 12", + "@types/react": ">= 16", + "react": ">= 16.14" + }, + "peerDependenciesMeta": { + "@types/hoist-non-react-statics": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-dnd-html5-backend": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", + "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", + "dependencies": { + "dnd-core": "^16.0.1" + } + }, + "node_modules/react-docgen": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-7.1.0.tgz", + "integrity": "sha512-APPU8HB2uZnpl6Vt/+0AFoVYgSRtfiP6FLrZgPPTDmqSb2R4qZRbgd0A3VzIFxDt5e+Fozjx79WjLWnF69DK8g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.18.9", + "@babel/traverse": "^7.18.9", + "@babel/types": "^7.18.9", + "@types/babel__core": "^7.18.0", + "@types/babel__traverse": "^7.18.0", + "@types/doctrine": "^0.0.9", + "@types/resolve": "^1.20.2", + "doctrine": "^3.0.0", + "resolve": "^1.22.1", + "strip-indent": "^4.0.0" + }, + "engines": { + "node": ">=16.14.0" + } + }, + "node_modules/react-docgen-typescript": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", + "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", + "dev": true, + "peerDependencies": { + "typescript": ">= 4.3.x" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-gtm-module": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/react-gtm-module/-/react-gtm-module-2.0.11.tgz", + "integrity": "sha512-8gyj4TTxeP7eEyc2QKawEuQoAZdjKvMY4pgWfycGmqGByhs17fR+zEBs0JUDq4US/l+vbTl+6zvUIx27iDo/Vw==" + }, + "node_modules/react-helmet": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", + "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "dependencies": { + "object-assign": "^4.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.1.1", + "react-side-effect": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.3.0" + } + }, + "node_modules/react-i18next": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.1.tgz", + "integrity": "sha512-ahGab+IaSgZmNPYXdV1n+OYky95TGpFwnKRflX/16dY04DsYYKHtVLjeny7sBSCREEcoMbAgSkFiGLF5g5Oofw==", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-number-format": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.3.tgz", + "integrity": "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-phone-input-2": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/react-phone-input-2/-/react-phone-input-2-2.15.1.tgz", + "integrity": "sha512-W03abwhXcwUoq+vUFvC6ch2+LJYMN8qSOiO889UH6S7SyMCQvox/LF3QWt+cZagZrRdi5z2ON3omnjoCUmlaYw==", + "dependencies": { + "classnames": "^2.2.6", + "lodash.debounce": "^4.0.8", + "lodash.memoize": "^4.1.2", + "lodash.reduce": "^4.6.0", + "lodash.startswith": "^4.2.1", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0", + "react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" + } + }, + "node_modules/react-player": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz", + "integrity": "sha512-mAIPHfioD7yxO0GNYVFD1303QFtI3lyyQZLY229UEAp/a10cSW+hPcakg0Keq8uWJxT2OiT/4Gt+Lc9bD6bJmQ==", + "dependencies": { + "deepmerge": "^4.0.0", + "load-script": "^1.0.0", + "memoize-one": "^5.1.1", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.0.1" + }, + "peerDependencies": { + "react": ">=16.6.0" + } + }, + "node_modules/react-player/node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-resizable-panels": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.7.tgz", + "integrity": "sha512-JtT6gI+nURzhMYQYsx8DKkx6bSoOGFp7A3CwMrOb8y5jFHFyqwo9m68UhmXRw57fRVJksFn1TSlm3ywEQ9vMgA==", + "peerDependencies": { + "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/react-rnd": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz", + "integrity": "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==", + "dependencies": { + "re-resizable": "6.11.2", + "react-draggable": "4.4.6", + "tslib": "2.6.2" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-rnd/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/react-router": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz", + "integrity": "sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.2.tgz", + "integrity": "sha512-yk1XW8Fj7gK7flpYBXF3yzd2NbX6P7Kxjvs2b5nu1M04rb5pg/Zc4fGdBNTeT4eDYL2bvzWNyKaIMJX/RKHTTg==", + "license": "MIT", + "dependencies": { + "react-router": "7.5.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-side-effect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", + "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-swipeable": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/react-swipeable/-/react-swipeable-7.0.2.tgz", + "integrity": "sha512-v1Qx1l+aC2fdxKa9aKJiaU/ZxmJ5o98RMoFwUqAAzVWUcxgfHFXDDruCKXhw6zIYXm6V64JiHgP9f6mlME5l8w==", + "peerDependencies": { + "react": "^16.8.3 || ^17 || ^18 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-toastify": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz", + "integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-toastify/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/recast/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/refa": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", + "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.8.0" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regexp-ast-analysis": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", + "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.1" + }, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/requireindex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz", + "integrity": "sha512-LBnkqsDE7BZKvqylbmn7lTIVdpx4K/QCduRATpO5R+wtPmky/a8pN1bO2D6wXppn1497AJF9mNjqAXr6bdl9jg==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==" + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" + }, + "node_modules/rrdom": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.0.0-alpha.18.tgz", + "integrity": "sha512-fSFzFFxbqAViITyYVA4Z0o5G6p1nEqEr/N8vdgSKie9Rn0FJxDSNJgjV0yiCIzcDs0QR+hpvgFhpbdZ6JIr5Nw==", + "dependencies": { + "rrweb-snapshot": "^2.0.0-alpha.18" + } + }, + "node_modules/rrweb": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.0.0-alpha.18.tgz", + "integrity": "sha512-1mjZcB+LVoGSx1+i9E2ZdAP90fS3MghYVix2wvGlZvrgRuLCbTCCOZMztFCkKpgp7/EeCdYM4nIHJkKX5J1Nmg==", + "dependencies": { + "@rrweb/types": "^2.0.0-alpha.18", + "@rrweb/utils": "^2.0.0-alpha.18", + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "mitt": "^3.0.0", + "rrdom": "^2.0.0-alpha.18", + "rrweb-snapshot": "^2.0.0-alpha.18" + } + }, + "node_modules/rrweb-snapshot": { + "version": "2.0.0-alpha.18", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.0.0-alpha.18.tgz", + "integrity": "sha512-hBHZL/NfgQX6wO1D9mpwqFu1NJPpim+moIcKhFEjVTZVRUfCln+LOugRc4teVTCISYHN8Cw5e2iNTWCSm+SkoA==", + "dependencies": { + "postcss": "^8.4.38" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/saxen": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/saxen/-/saxen-10.0.0.tgz", + "integrity": "sha512-RXsmWok/SAWqOG/f5ADEz51DN9WtZEzqih3e08ranldcaXekxjx8NBKjGh/y5hlowjo0JH/LekBu6gtPFD1G6g==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/scheduler": { + "version": "0.25.0-rc-603e6108-20241029", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", + "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==" + }, + "node_modules/scslre": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", + "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.8.0", + "refa": "^0.12.0", + "regexp-ast-analysis": "^0.7.0" + }, + "engines": { + "node": "^14.0.0 || >=16.0.0" + } + }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-git-hooks": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.12.1.tgz", + "integrity": "sha512-NB3V4XyCOrWTIhjh85DyEoVlM3adHWwqQXKYHmuegy/108bJPP6YxuPGm4ZKBq1+GVKRbKJuzNY//09cMJYp+A==", + "dev": true, + "hasInstallScript": true, + "bin": { + "simple-git-hooks": "cli.js" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/smol-toml": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.3.1.tgz", + "integrity": "sha512-tEYNll18pPKHroYSmLLrksq233j021G0giwW7P3D24jC54pQ5W5BXMsQ/Mvw1OJCmEYDgY+lrzT+3nNUtoNfXQ==", + "dev": true, + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/storybook": { + "version": "8.6.12", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.12.tgz", + "integrity": "sha512-Z/nWYEHBTLK1ZBtAWdhxC0l5zf7ioJ7G4+zYqtTdYeb67gTnxNj80gehf8o8QY9L2zA2+eyMRGLC2V5fI7Z3Tw==", + "dev": true, + "dependencies": { + "@storybook/core": "8.6.12" + }, + "bin": { + "getstorybook": "bin/index.cjs", + "sb": "bin/index.cjs", + "storybook": "bin/index.cjs" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-natural-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/string-natural-compare/-/string-natural-compare-3.0.1.tgz", + "integrity": "sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==", + "dev": true + }, + "node_modules/string-ts": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/string-ts/-/string-ts-2.2.1.tgz", + "integrity": "sha512-Q2u0gko67PLLhbte5HmPfdOjNvUKbKQM+mCNQae6jE91DmoFHY6HH9GcdqCeNx87DZ2KKjiFxmA0R/42OneGWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.0.0.tgz", + "integrity": "sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/styled-components/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/styled-components/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-arc-to-cubic-bezier": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svg-arc-to-cubic-bezier/-/svg-arc-to-cubic-bezier-3.2.0.tgz", + "integrity": "sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==" + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "node_modules/tiny-svg": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tiny-svg/-/tiny-svg-3.1.3.tgz", + "integrity": "sha512-9mwnPqXInRsBmH/DO6NMxBE++9LsqpVXQSSTZGc5bomoKKvL5OX/Hlotw7XVXP6XLRcHWIzZpxfovGqWKgCypQ==" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, + "node_modules/to-camel-case": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-camel-case/-/to-camel-case-1.0.0.tgz", + "integrity": "sha512-nD8pQi5H34kyu1QDMFjzEIYqk0xa9Alt6ZfrdEMuHCFOfTLhDG5pgTu/aAM9Wt9lXILwlXmWP43b8sav0GNE8Q==", + "dependencies": { + "to-space-case": "^1.0.0" + } + }, + "node_modules/to-no-case": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", + "integrity": "sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-space-case": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz", + "integrity": "sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==", + "dependencies": { + "to-no-case": "^1.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-api-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-pattern": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.7.0.tgz", + "integrity": "sha512-0/FvIG4g3kNkYgbNwBBW5pZBkfpeYQnH+2AA3xmjkCAit/DSDPKmgwC3fKof4oYUq6gupClVOJlFl+939VRBMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tsconfck": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.4.tgz", + "integrity": "sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.0.tgz", + "integrity": "sha512-q6ayo8DWoPZT0VdG4u3D3uxcgONP3Mevx2i2b0434cwWBoL+aelL1DzkXI6w3PhTZzUeR2kaVlZn70iCiseP6w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/usehooks-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz", + "integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-compatible-readable-stream": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/vite-compatible-readable-stream/-/vite-compatible-readable-stream-3.6.1.tgz", + "integrity": "sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vite-plugin-eslint": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz", + "integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^4.2.1", + "@types/eslint": "^8.4.5", + "rollup": "^2.77.2" + }, + "peerDependencies": { + "eslint": ">=7", + "vite": ">=2" + } + }, + "node_modules/vite-plugin-eslint/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/vite-plugin-eslint/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vite-plugin-eslint/node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/vite-plugin-svgr": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", + "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=2.6.0" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/voximplant-websdk": { + "version": "4.8.9-2892", + "resolved": "https://registry.npmjs.org/voximplant-websdk/-/voximplant-websdk-4.8.9-2892.tgz", + "integrity": "sha512-7xqOKfands8srlMX7WoO/cTmnoPQ0bfIk5sTa19sNXMziTCHEFQNa98AbHrhUT3gJ0BgMxFtti4jaXVmIlsS0g==" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "optional": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-vitals": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz", + "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" + }, + "node_modules/which-typed-array": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==" + }, + "node_modules/zeebe-bpmn-moddle": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/zeebe-bpmn-moddle/-/zeebe-bpmn-moddle-1.9.0.tgz", + "integrity": "sha512-Y9ncIdP4m1PKbIBDqSghwZud2eiiBpfygE0bTApGqtnGlJMA/6Xanl/J7ujxG5zREoAliwf6rJyJFk3FZ75AYg==" + }, + "node_modules/zod": { + "version": "3.24.2", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", + "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.4.0.tgz", + "integrity": "sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==", + "dev": true, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.18.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..66180ea --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,205 @@ +{ + "name": "amwork-frontend", + "version": "3.14.3", + "private": true, + "type": "module", + "main": "./src/index.tsx", + "scripts": { + "start": "vite", + "build": "NODE_OPTIONS=--max_old_space_size=8192 vite build", + "debug": "VITE_REACT_QUERY_DEVTOOLS_ENABLED=true vite", + "serve": "vite preview", + "lint": "eslint --max-warnings=0 src", + "lint:fix": "eslint --max-warnings=0 src --fix", + "ts": "NODE_OPTIONS=--max_old_space_size=8192 tsc --noEmit", + "storybook": "storybook dev -p 6006 --no-open", + "storybook:build": "NODE_OPTIONS=--max_old_space_size=5120 storybook build", + "postinstall": "npx simple-git-hooks", + "knip": "knip --config src/app/config/knip/knip.ts" + }, + "dependencies": { + "@babel/plugin-proposal-decorators": "^7.25.9", + "@babel/plugin-transform-typescript": "^7.27.0", + "@babel/preset-typescript": "^7.27.0", + "@directus/sdk": "^17.0.0", + "@emoji-mart/data": "^1.2.1", + "@emoji-mart/react": "^1.1.1", + "@emotion/react": "^11.14.0", + "@emotion/utils": "^1.4.2", + "@formkit/auto-animate": "^0.8.2", + "@fullcalendar/core": "^6.1.17", + "@fullcalendar/daygrid": "^6.1.17", + "@fullcalendar/interaction": "^6.1.17", + "@fullcalendar/list": "^6.1.17", + "@fullcalendar/multimonth": "^6.1.17", + "@fullcalendar/react": "^6.1.17", + "@fullcalendar/resource": "^6.1.17", + "@fullcalendar/resource-daygrid": "^6.1.17", + "@fullcalendar/resource-timegrid": "^6.1.17", + "@fullcalendar/resource-timeline": "^6.1.17", + "@fullcalendar/scrollgrid": "^6.1.17", + "@fullcalendar/timegrid": "^6.1.17", + "@hello-pangea/dnd": "^18.0.1", + "@mantine/core": "^7.17.4", + "@mantine/dates": "^7.17.4", + "@mantine/hooks": "^7.17.4", + "@mantine/tiptap": "^7.17.4", + "@newrelic/browser-agent": "^1.287.0", + "@react-pdf-viewer/core": "3.12.0", + "@react-pdf-viewer/default-layout": "3.12.0", + "@react-pdf/renderer": "^4.3.0", + "@tabler/icons": "^3.31.0", + "@tabler/icons-react": "^3.31.0", + "@tanstack/react-query": "^5.74.3", + "@tanstack/react-table": "^8.21.3", + "@tiptap/extension-highlight": "^2.11.7", + "@tiptap/extension-link": "^2.11.7", + "@tiptap/extension-placeholder": "^2.11.7", + "@tiptap/extension-subscript": "^2.11.7", + "@tiptap/extension-superscript": "^2.11.7", + "@tiptap/extension-text-align": "^2.11.7", + "@tiptap/extension-text-style": "^2.11.7", + "@tiptap/extension-underline": "^2.11.7", + "@tiptap/pm": "^2.11.7", + "@tiptap/react": "^2.11.7", + "@tiptap/starter-kit": "^2.11.7", + "@typescript-eslint/eslint-plugin": "^8.30.1", + "axios": "^1.8.4", + "bowser": "^2.11.0", + "bpmn-js": "^18.4.0", + "bpmn-js-color-picker": "^0.7.1", + "chart.js": "^4.4.9", + "country-code-to-flag-emoji": "^2.0.0", + "date-fns": "^4.1.0", + "dayjs": "^1.11.13", + "decimal.js": "^10.5.0", + "diagram-js": "^15.2.4", + "diagram-js-grid": "^1.1.0", + "diagram-js-minimap": "^5.2.0", + "dompurify": "^3.2.5", + "emoji-mart": "^5.6.0", + "file-saver": "^2.0.5", + "framer-motion": "^12.7.3", + "get-blob-duration": "^1.2.0", + "html-to-text": "^9.0.5", + "i18next": "^25.0.0", + "i18next-browser-languagedetector": "^8.0.4", + "i18next-http-backend": "^3.0.2", + "js-cookie": "^3.0.5", + "js-file-download": "^0.4.12", + "libphonenumber-js": "^1.12.6", + "mac-scrollbar": "^0.13.8", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", + "nodemailer": "^6.10.1", + "normalize.css": "^8.0.1", + "numeralize-ru": "^2.0.0", + "p-limit": "^6.2.0", + "path-to-regexp": "^8.2.0", + "pdfjs-dist": "3.11.174", + "phone-number-to-timezone": "^1.0.8", + "qrcode": "^1.5.4", + "react": "^19.1.0", + "react-avatar-editor": "^13.0.2", + "react-chartjs-2": "^5.3.0", + "react-collapsible": "^2.10.0", + "react-countup": "^6.5.3", + "react-custom-scrollbars-2": "^4.5.0", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", + "react-dom": "^19.1.0", + "react-gtm-module": "^2.0.11", + "react-helmet": "^6.1.0", + "react-i18next": "^15.4.1", + "react-phone-input-2": "^2.15.1", + "react-player": "^2.16.0", + "react-resizable-panels": "^2.1.7", + "react-rnd": "^10.5.2", + "react-router-dom": "^6.0.0", + "react-swipeable": "^7.0.2", + "react-textarea-autosize": "^8.5.9", + "react-toastify": "10.0.6", + "reflect-metadata": "0.2.2", + "socket.io-client": "^4.8.1", + "styled-components": "^5.3.11", + "usehooks-ts": "^3.1.1", + "uuid": "^11.1.0", + "voximplant-websdk": "^4.8.9-2892", + "xlsx": "^0.18.5", + "zeebe-bpmn-moddle": "^1.9.0", + "zod": "^3.24.2" + }, + "devDependencies": { + "@babel/plugin-transform-class-properties": "^7.25.9", + "@eslint/compat": "^1.2.8", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "^9.24.0", + "@storybook/addon-essentials": "^8.6.12", + "@storybook/addon-interactions": "^8.6.12", + "@storybook/addon-links": "^8.6.12", + "@storybook/blocks": "^8.6.12", + "@storybook/react": "^8.6.12", + "@storybook/react-vite": "^8.6.12", + "@stylistic/eslint-plugin": "^4.2.0", + "@tanstack/eslint-plugin-query": "^5.73.3", + "@tanstack/react-query-devtools": "^5.74.3", + "@total-typescript/ts-reset": "^0.6.1", + "@types/html-to-text": "^9.0.4", + "@types/js-cookie": "^3.0.6", + "@types/node": "^22.14.1", + "@types/nodemailer": "^6.4.17", + "@types/qrcode": "^1.5.5", + "@types/react": "^19.1.2", + "@types/react-avatar-editor": "^13.0.4", + "@types/react-dom": "^19.1.2", + "@types/react-gtm-module": "^2.0.4", + "@types/react-helmet": "^6.1.11", + "@types/styled-components": "5.1.34", + "@types/uuid": "^10.0.0", + "@typescript-eslint/parser": "^8.30.1", + "@vitejs/plugin-react": "^4.4.0", + "babel-plugin-styled-components": "^2.1.4", + "eslint": "^9.24.0", + "eslint-config-react-app": "^7.0.1", + "eslint-plugin-i18next": "^6.1.1", + "eslint-plugin-react-hooks-extra": "^1.48.1", + "eslint-plugin-regexp": "^2.7.0", + "eslint-plugin-storybook": "^0.12.0", + "knip": "^5.50.4", + "nano-staged": "^0.8.0", + "prettier": "^3.5.3", + "prettier-plugin-organize-imports": "^4.1.0", + "simple-git-hooks": "^2.12.1", + "storybook": "^8.6.12", + "typescript": "^5.8.3", + "vite": "^6.3.0", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-svgr": "^4.3.0", + "vite-tsconfig-paths": "^5.1.4" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "resolutions": { + "styled-components": "5.3.11" + }, + "nano-staged": { + "*.{ts,tsx,js,jsx}": [ + "prettier --write", + "eslint --fix" + ] + }, + "simple-git-hooks": { + "pre-commit": "./node_modules/.bin/nano-staged", + "pre-push": "npm run ts" + } +} diff --git a/frontend/public/favicons/amwork/apple_touch_120x120.png b/frontend/public/favicons/amwork/apple_touch_120x120.png new file mode 100644 index 0000000..b695cfa Binary files /dev/null and b/frontend/public/favicons/amwork/apple_touch_120x120.png differ diff --git a/frontend/public/favicons/amwork/apple_touch_152x152.png b/frontend/public/favicons/amwork/apple_touch_152x152.png new file mode 100644 index 0000000..404e4b4 Binary files /dev/null and b/frontend/public/favicons/amwork/apple_touch_152x152.png differ diff --git a/frontend/public/favicons/amwork/apple_touch_167x167.png b/frontend/public/favicons/amwork/apple_touch_167x167.png new file mode 100644 index 0000000..da27c4a Binary files /dev/null and b/frontend/public/favicons/amwork/apple_touch_167x167.png differ diff --git a/frontend/public/favicons/amwork/apple_touch_180x180.png b/frontend/public/favicons/amwork/apple_touch_180x180.png new file mode 100644 index 0000000..8325e20 Binary files /dev/null and b/frontend/public/favicons/amwork/apple_touch_180x180.png differ diff --git a/frontend/public/favicons/amwork/favicon_16x16.png b/frontend/public/favicons/amwork/favicon_16x16.png new file mode 100644 index 0000000..a394312 Binary files /dev/null and b/frontend/public/favicons/amwork/favicon_16x16.png differ diff --git a/frontend/public/favicons/amwork/favicon_16x16.svg b/frontend/public/favicons/amwork/favicon_16x16.svg new file mode 100644 index 0000000..1e00b92 --- /dev/null +++ b/frontend/public/favicons/amwork/favicon_16x16.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/frontend/public/favicons/amwork/favicon_32x32.png b/frontend/public/favicons/amwork/favicon_32x32.png new file mode 100644 index 0000000..6cc097d Binary files /dev/null and b/frontend/public/favicons/amwork/favicon_32x32.png differ diff --git a/frontend/public/favicons/amwork/favicon_32x32.svg b/frontend/public/favicons/amwork/favicon_32x32.svg new file mode 100644 index 0000000..a421045 --- /dev/null +++ b/frontend/public/favicons/amwork/favicon_32x32.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/frontend/public/favicons/favicon.png b/frontend/public/favicons/favicon.png new file mode 100644 index 0000000..fa30fc1 Binary files /dev/null and b/frontend/public/favicons/favicon.png differ diff --git a/frontend/public/favicons/mywork/apple_touch_120x120.png b/frontend/public/favicons/mywork/apple_touch_120x120.png new file mode 100644 index 0000000..b695cfa Binary files /dev/null and b/frontend/public/favicons/mywork/apple_touch_120x120.png differ diff --git a/frontend/public/favicons/mywork/apple_touch_152x152.png b/frontend/public/favicons/mywork/apple_touch_152x152.png new file mode 100644 index 0000000..404e4b4 Binary files /dev/null and b/frontend/public/favicons/mywork/apple_touch_152x152.png differ diff --git a/frontend/public/favicons/mywork/apple_touch_167x167.png b/frontend/public/favicons/mywork/apple_touch_167x167.png new file mode 100644 index 0000000..da27c4a Binary files /dev/null and b/frontend/public/favicons/mywork/apple_touch_167x167.png differ diff --git a/frontend/public/favicons/mywork/apple_touch_180x180.png b/frontend/public/favicons/mywork/apple_touch_180x180.png new file mode 100644 index 0000000..8325e20 Binary files /dev/null and b/frontend/public/favicons/mywork/apple_touch_180x180.png differ diff --git a/frontend/public/favicons/mywork/favicon_16x16.png b/frontend/public/favicons/mywork/favicon_16x16.png new file mode 100644 index 0000000..a394312 Binary files /dev/null and b/frontend/public/favicons/mywork/favicon_16x16.png differ diff --git a/frontend/public/favicons/mywork/favicon_16x16.svg b/frontend/public/favicons/mywork/favicon_16x16.svg new file mode 100644 index 0000000..1e00b92 --- /dev/null +++ b/frontend/public/favicons/mywork/favicon_16x16.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/frontend/public/favicons/mywork/favicon_32x32.png b/frontend/public/favicons/mywork/favicon_32x32.png new file mode 100644 index 0000000..6cc097d Binary files /dev/null and b/frontend/public/favicons/mywork/favicon_32x32.png differ diff --git a/frontend/public/favicons/mywork/favicon_32x32.svg b/frontend/public/favicons/mywork/favicon_32x32.svg new file mode 100644 index 0000000..a421045 --- /dev/null +++ b/frontend/public/favicons/mywork/favicon_32x32.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/frontend/public/favicons/proma/apple_touch_120x120.png b/frontend/public/favicons/proma/apple_touch_120x120.png new file mode 100644 index 0000000..f58c0b4 Binary files /dev/null and b/frontend/public/favicons/proma/apple_touch_120x120.png differ diff --git a/frontend/public/favicons/proma/apple_touch_152x152.png b/frontend/public/favicons/proma/apple_touch_152x152.png new file mode 100644 index 0000000..544a3d8 Binary files /dev/null and b/frontend/public/favicons/proma/apple_touch_152x152.png differ diff --git a/frontend/public/favicons/proma/apple_touch_167x167.png b/frontend/public/favicons/proma/apple_touch_167x167.png new file mode 100644 index 0000000..ca4a7d2 Binary files /dev/null and b/frontend/public/favicons/proma/apple_touch_167x167.png differ diff --git a/frontend/public/favicons/proma/apple_touch_180x180.png b/frontend/public/favicons/proma/apple_touch_180x180.png new file mode 100644 index 0000000..36e02cc Binary files /dev/null and b/frontend/public/favicons/proma/apple_touch_180x180.png differ diff --git a/frontend/public/favicons/proma/favicon_16x16.png b/frontend/public/favicons/proma/favicon_16x16.png new file mode 100644 index 0000000..ea15f6e Binary files /dev/null and b/frontend/public/favicons/proma/favicon_16x16.png differ diff --git a/frontend/public/favicons/proma/favicon_16x16.svg b/frontend/public/favicons/proma/favicon_16x16.svg new file mode 100644 index 0000000..538e9d2 --- /dev/null +++ b/frontend/public/favicons/proma/favicon_16x16.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/frontend/public/favicons/proma/favicon_32x32.png b/frontend/public/favicons/proma/favicon_32x32.png new file mode 100644 index 0000000..e8e75bf Binary files /dev/null and b/frontend/public/favicons/proma/favicon_32x32.png differ diff --git a/frontend/public/favicons/proma/favicon_32x32.svg b/frontend/public/favicons/proma/favicon_32x32.svg new file mode 100644 index 0000000..d70cfe3 --- /dev/null +++ b/frontend/public/favicons/proma/favicon_32x32.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/frontend/public/fonts/Geologica/SemiBold/GeologicaSemiBold.ttf b/frontend/public/fonts/Geologica/SemiBold/GeologicaSemiBold.ttf new file mode 100644 index 0000000..ccb8cb6 Binary files /dev/null and b/frontend/public/fonts/Geologica/SemiBold/GeologicaSemiBold.ttf differ diff --git a/frontend/public/fonts/Nunito/Regular/NunitoRegular.ttf b/frontend/public/fonts/Nunito/Regular/NunitoRegular.ttf new file mode 100644 index 0000000..9411bfb Binary files /dev/null and b/frontend/public/fonts/Nunito/Regular/NunitoRegular.ttf differ diff --git a/frontend/public/fonts/Nunito/SemiBold/NunitoSemiBold.ttf b/frontend/public/fonts/Nunito/SemiBold/NunitoSemiBold.ttf new file mode 100644 index 0000000..1326a7d Binary files /dev/null and b/frontend/public/fonts/Nunito/SemiBold/NunitoSemiBold.ttf differ diff --git a/frontend/public/fonts/TimesNewRoman/Bold/TimesNewRomanBold.ttf b/frontend/public/fonts/TimesNewRoman/Bold/TimesNewRomanBold.ttf new file mode 100644 index 0000000..578542c Binary files /dev/null and b/frontend/public/fonts/TimesNewRoman/Bold/TimesNewRomanBold.ttf differ diff --git a/frontend/public/fonts/TimesNewRoman/Regular/TimesNewRomanRegular.ttf b/frontend/public/fonts/TimesNewRoman/Regular/TimesNewRomanRegular.ttf new file mode 100644 index 0000000..d7969c3 Binary files /dev/null and b/frontend/public/fonts/TimesNewRoman/Regular/TimesNewRomanRegular.ttf differ diff --git a/frontend/public/images/bpmn/en/bpmn.svg b/frontend/public/images/bpmn/en/bpmn.svg new file mode 100644 index 0000000..2981415 --- /dev/null +++ b/frontend/public/images/bpmn/en/bpmn.svg @@ -0,0 +1,370 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/bpmn/es/bpmn.svg b/frontend/public/images/bpmn/es/bpmn.svg new file mode 100644 index 0000000..d9dee48 --- /dev/null +++ b/frontend/public/images/bpmn/es/bpmn.svg @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/bpmn/fr/bpmn.svg b/frontend/public/images/bpmn/fr/bpmn.svg new file mode 100644 index 0000000..be9916f --- /dev/null +++ b/frontend/public/images/bpmn/fr/bpmn.svg @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/bpmn/pl/bpmn.svg b/frontend/public/images/bpmn/pl/bpmn.svg new file mode 100644 index 0000000..8d30c75 --- /dev/null +++ b/frontend/public/images/bpmn/pl/bpmn.svg @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/bpmn/ru/bpmn.svg b/frontend/public/images/bpmn/ru/bpmn.svg new file mode 100644 index 0000000..7e265dc --- /dev/null +++ b/frontend/public/images/bpmn/ru/bpmn.svg @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/public/images/builder/deals-view/board.png b/frontend/public/images/builder/deals-view/board.png new file mode 100644 index 0000000..684aa73 Binary files /dev/null and b/frontend/public/images/builder/deals-view/board.png differ diff --git a/frontend/public/images/builder/deals-view/list.png b/frontend/public/images/builder/deals-view/list.png new file mode 100644 index 0000000..e230905 Binary files /dev/null and b/frontend/public/images/builder/deals-view/list.png differ diff --git a/frontend/public/images/builder/scheduler-view/board.png b/frontend/public/images/builder/scheduler-view/board.png new file mode 100644 index 0000000..45f9319 Binary files /dev/null and b/frontend/public/images/builder/scheduler-view/board.png differ diff --git a/frontend/public/images/builder/scheduler-view/schedule.png b/frontend/public/images/builder/scheduler-view/schedule.png new file mode 100644 index 0000000..9cdbfdb Binary files /dev/null and b/frontend/public/images/builder/scheduler-view/schedule.png differ diff --git a/frontend/public/images/components/ImportEntitiesModal/import_entities.svg b/frontend/public/images/components/ImportEntitiesModal/import_entities.svg new file mode 100644 index 0000000..e6ee757 --- /dev/null +++ b/frontend/public/images/components/ImportEntitiesModal/import_entities.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/images/dashboard/chart_plug.png b/frontend/public/images/dashboard/chart_plug.png new file mode 100644 index 0000000..624c594 Binary files /dev/null and b/frontend/public/images/dashboard/chart_plug.png differ diff --git a/frontend/public/images/demo/analytics.png b/frontend/public/images/demo/analytics.png new file mode 100644 index 0000000..2c33f52 Binary files /dev/null and b/frontend/public/images/demo/analytics.png differ diff --git a/frontend/public/images/demo/mail.png b/frontend/public/images/demo/mail.png new file mode 100644 index 0000000..309591c Binary files /dev/null and b/frontend/public/images/demo/mail.png differ diff --git a/frontend/public/images/invoice/mywork_signature.png b/frontend/public/images/invoice/mywork_signature.png new file mode 100644 index 0000000..146318c Binary files /dev/null and b/frontend/public/images/invoice/mywork_signature.png differ diff --git a/frontend/public/images/invoice/mywork_square_logo.png b/frontend/public/images/invoice/mywork_square_logo.png new file mode 100644 index 0000000..222f3e6 Binary files /dev/null and b/frontend/public/images/invoice/mywork_square_logo.png differ diff --git a/frontend/public/images/invoice/mywork_stamp.png b/frontend/public/images/invoice/mywork_stamp.png new file mode 100644 index 0000000..7d161ae Binary files /dev/null and b/frontend/public/images/invoice/mywork_stamp.png differ diff --git a/frontend/public/images/login/en/login.png b/frontend/public/images/login/en/login.png new file mode 100644 index 0000000..a34fe93 Binary files /dev/null and b/frontend/public/images/login/en/login.png differ diff --git a/frontend/public/images/login/login.svg b/frontend/public/images/login/login.svg new file mode 100644 index 0000000..b3ab5ec --- /dev/null +++ b/frontend/public/images/login/login.svg @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/images/login/ru/login.png b/frontend/public/images/login/ru/login.png new file mode 100644 index 0000000..3874e94 Binary files /dev/null and b/frontend/public/images/login/ru/login.png differ diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json new file mode 100644 index 0000000..c4e3c02 --- /dev/null +++ b/frontend/public/locales/en/common.json @@ -0,0 +1,395 @@ +{ + "connect_with_google": "Connect with Google", + "responsible": "Responsible", + "all_cards": "All Cards", + "apply_to_all_button": { + "apply": "Apply", + "apply_to_all": "Apply to All", + "apply_to_all_accounts": "Apply table settings for all users:", + "apply_warning_title": "Are you sure you want to apply your local table settings for all users?", + "apply_warning_annotation": "This action will overwrite all table settings for other user in the system. It cannot be undone." + }, + "version_modal": { + "title": "✨ {{company}} just got better!", + "annotation": "Update to the version {{version}}!", + "update": "Update" + }, + "reload_modal": { + "title": "Page is outdated!", + "annotation": "The data on this page may be outdated. Refresh to ensure everything works properly.", + "update": "Refresh" + }, + "select_stage": "Select stage", + "new_activity_type": "New activity type", + "video_error": "Failed to download and play video file", + "image_error": "Failed to download and preview image", + "view_document": "View: {{document}}", + "object": "object", + "all_day": "All day", + "supervisor": "Supervisor", + "current_chat_user_hint": "You can't remove yourself from chat", + "coming_soon": "Coming Soon...", + "field_readonly": "This field is readonly", + "now": "Now", + "default": "Default", + "exact_time": "Exact Time", + "card_copy": "This copy was created by change stage automation", + "customize": "Customize", + "day_char": "d", + "hour_char": "h", + "minute_char": "m", + "days": ["day", "days", "days"], + "minutes": ["minute", "minutes", "minutes"], + "hours": ["hour", "hours", "hours"], + "seconds": ["second", "seconds", "seconds"], + "files_count": ["{{count}} file", "{{count}} files", "{{count}} files"], + "loading_title": "Loading...", + "changes_unsaved_blocker": { + "title": "Changes have not been saved", + "annotation": "Would you like to save before closing, or exit without saving?", + "approve_title": "Save", + "cancel_title": "Exit Without Saving" + }, + "browser_not_supported_title": "Your browser is not supported.", + "browser_not_supported_annotation": "To use {{company}}, we recommend using the latest version of Chrome, Firefox or Safari. This will ensure the best experience possible. You can find the instructions below.", + "duplicate_title": "Card already exists", + "duplicate_warning_annotation": "Do you want to create a duplicate or select an existing card?", + "select_existing": "Select Existing", + "duplicate_warning_cancel_title": "Create Duplicate", + "duplicate_forbidden_annotation": "The administrator has set a prohibition on creating duplicate contacts. Please select an existing card or use a different unique number.", + "add_as_new": "Create Duplicate", + "tasks_total": ["Tasks", "Tasks", "Tasks"], + "activities_total": ["Activities", "Activities", "Activities"], + "visits_total": ["Visit", "Visits", "Visits"], + "trial_left": { + "one_day": "Trial: 1 day", + "several_days": ["Trial: {{left}} days", "Trial: {{left}} days", "Trial: {{left}} days"] + }, + "meta_description": "{{company}} is a powerful and flexible workspace builder designed specifically for small and medium businesses.", + "international_number": "International number", + "phone_number": "Phone number", + "no_available_groups": "No available groups", + "add_new_group": "Add new group", + "schedule": "Schedule", + "board": "Board", + "settings": "Settings", + "visits": "Meetings", + "orders": "Orders", + "row": "Row", + "column": "Column", + "external_link": "External link", + "loading": "Loading", + "products": "Products", + "for_sales": "For sales", + "rentals": "Rentals", + "title": "Title", + "name": "Name", + "max_length": "Maximum {{length}} characters", + "activities": "Activities", + "project_tasks_board": "Board", + "tasks_board": "Tasks board", + "tasks_list": "List", + "time_board": "Time board", + "trial": "Trial", + "show_less": "Show less", + "show_more": "Show more", + "search": "Search", + "clear_search": "Clear search", + "profile": "Profile", + "avatar_alt": "{{firstName}} avatar", + "log_out": "Log out", + "select_date": "Select date", + "task_title": "Task title", + "select": "Select", + "overview": "Overview", + "new_board": "New board", + "enter_html_markup": "Enter HTML markup", + "filter": "Filter", + "notifications": "Notifications", + "mail": "Mail", + "multichat": "Multi Messenger", + "total": "Total: {{total}}", + "select_period": "Select period", + "select_groups": "Select groups", + "select_group": "Select group", + "delay": "Delay", + "hide_sidebar": "Hide sidebar", + "show_sidebar": "Show sidebar", + "site_form": "Web Forms", + "workspace_tags": { + "site_form": "Web Form", + "headless_site_form": "Web Form via API", + "online_booking_site_form": "Online Booking Form", + "project": "Projects and Tasks", + "universal": "Universal Module", + "partner": "Partners", + "deal": "CRM", + "contact": "Contacts", + "company": "Companies", + "supplier": "Suppliers", + "contractor": "Contractors", + "hr": "Hiring", + "products_for_sales": "Inventory Management", + "products_rentals": "Lease Management", + "visits": "Appointment Scheduling" + }, + "sidebar": { + "builder": "Builder", + "tasks_and_activities": "Tasks & Activities", + "mail": "Mail", + "settings": "Settings", + "companies": "Companies", + "contacts": "Contacts", + "multichat": "Multi Messenger" + }, + "buttons": { + "save_and_leave": "Save and Leave", + "create": "Create", + "cancel": "Cancel", + "save": "Save", + "delete": "Delete", + "add": "Add", + "edit": "Edit", + "ok": "Ok", + "activate": "Activate", + "deactivate": "Deactivate", + "back": "Back", + "clear": "Clear", + "continue": "Continue" + }, + "profile_modal": { + "app_version": "App Version", + "app_version_hint": "The current version of the application.", + "clear_cache": "Clear Cache", + "clear_cache_title": "Clear Application Cache", + "clear_cache_hint": "This action will clear all non-essential client cache data and reload the page.", + "password": "Password", + "profile": "Your Profile", + "avatar": "avatar", + "first_name": "First Name", + "first_name_hint": "Maximum {{length}} characters", + "last_name": "Last Name", + "last_name_hint": "Maximum {{length}} characters", + "change_avatar": "Change", + "add_avatar": "Add Photo", + "delete_avatar": "Delete", + "avatar_annotation": "Photo must be in PNG, JPG format. Resolution no more than 1024px*1024px.", + "date_of_birth": "Date of Birth", + "phone": "Phone", + "employment_start": "Employment Start Date", + "email": "Email Address", + "current_password": "Current Password", + "new_password": "New Password", + "confirm_password": "Confirm Password", + "change_password": "Change Password", + "upload_avatar": "Upload Avatar", + "passwords_do_not_match": "Passwords do not match", + "incorrect_password": "Incorrect password", + "image_edit_modal": { + "annotation": "Adjust the photo display for your profile. Drag the slider to zoom in and out of the photo." + } + }, + "form": { + "my_select": { + "no_options": "No options", + "placeholder": "Select", + "search": "Search...", + "select_user": "Select user", + "select_all": "Select all", + "clear_selection": "Clear selection" + }, + "key_value_input": { + "key": "Key", + "value": "Value", + "add": "Add" + }, + "week_intervals_input": { + "mo": "Mon", + "tu": "Tue", + "we": "Wed", + "th": "Thu", + "fr": "Fri", + "sa": "Sat", + "su": "Sun", + "to": "to", + "unavailable": "Unavailable" + } + }, + "subscription_period_modal": { + "contact_admin": "Contact the account administrator to submit a subscription payment request.", + "trial_left": "You have {{left}} day(s) of free trial left", + "trial_over": "Your free trial has expired", + "subscription_left": "You have {{left}} day(s) of subscription left", + "subscription_over": "Your subscription has expired", + "trial_sub_title": "Choose a plan and continue using {{company}} Workspace.", + "subscription_sub_title": "Renew your subscription to continue accessing the {{company}} Workspace.", + "select_plan": "Select a plan" + }, + "trial_period_over_modal": { + "send": "Send", + "title": "Please contact us to pay for {{company}}", + "name": "* Name", + "email": "* Email", + "phone": "* Phone", + "number_of_users": "* Number of users", + "subscribe": "Subscribe", + "yearly": "Yearly -20%", + "monthly": "Monthly", + "choose_plan": "* Choose your plan", + "trial_left": "You have {{left}} days of trial left", + "trial_left_one": "You have {{left}} day of trial left", + "trial_over": "Your trial period is over", + "subscription_over": "Your subscription period is over", + "placeholders": { + "name": "Your name" + } + }, + "update_task_modal": { + "title": "Title", + "linked_card": "Linked Card", + "stage": "Stage", + "add_description": "Add description", + "no_description": "No description", + "not_found": "This task was not found", + "close": "Close", + "description": "Description", + "subtasks": "Subtasks", + "comments": "Comments", + "planned_time": "Estimated Time", + "board_name": "Board Name", + "assignee": "Assignee", + "start_date": "Start Date", + "end_date": "End Date", + "files": "Files", + "attach_files": "Attach files", + "reporter": "Reporter: {{name}}, {{date}} at {{time}}", + "reporter_noun": "Reporter", + "enter_description": "Enter description", + "new_comment": "New comment", + "placeholders": { + "board": "Select board", + "search_card": "Search card" + } + }, + "delete_demo": { + "button_text": "Delete demo", + "modal_title": "Do you really want to delete demo data?", + "modal_annotation": "All demo data will be deleted. This action cannot be undone." + }, + "filter_drawer_controls": { + "clear": "Clear", + "save_filter_settings": "Save filter settings", + "error_message": "Unfortunately, the filter was not applied. Please try again." + }, + "period_types": { + "all_time": "All time", + "today": "Today", + "yesterday": "Yesterday", + "current_week": "Current week", + "last_week": "Last week", + "current_month": "Current month", + "last_month": "Last month", + "current_quarter": "Current quarter", + "last_quarter": "Last quarter", + "placeholders": { + "select_period": "Select period" + } + }, + "file_size_warning_modal": { + "title": "File Too Large", + "annotation": "Maximum file size for upload is {{maxSizeMb}} MB. Please compress it or choose another.", + "cancel": "Cancel" + }, + "player": { + "default": "Default", + "fast": "Fast", + "super_fast": "Super fast" + }, + "languages": { + "en": "English", + "fr": "French", + "ru": "Russian", + "es": "Spanish", + "pt": "Portuguese", + "ar": "Arabic", + "tr": "Turkish", + "vi": "Vietnamese", + "az": "Azerbaijani", + "uk": "Ukrainian", + "id": "Indonesian" + }, + "calendar": { + "month": "Month", + "week": "Week", + "day": "Day", + "agenda": "Agenda", + "more": "more", + "users": "Users", + "today": "Today", + "all": "All tasks", + "completed": "Completed tasks", + "pending": "Pending tasks", + "colored": "Colored", + "colorless": "Colorless", + "show_weekends": "Show weekend", + "time_scale": "Time scale", + "from": "From", + "to": "To", + "stage_select": "Stage" + }, + "page_title": { + "system": { + "browser_not_supported": { + "page_title": "Browser not supported" + } + }, + "builder": { + "production": "Production Processes", + "builder": "Builder", + "site_form": "Builder | Web Forms", + "workspace": "Your Workspace", + "crm": "Builder | CRM", + "project_management": "Builder | Projects and Tasks", + "product_management_for_sales": "Builder | Inventory Management", + "product_management_rentals": "Builder | Lease Management", + "scheduler": "Builder | Appointment Scheduling", + "supplier_management": "Builder | Suppliers", + "contractor_management": "Builder | Contractors", + "partner_management": "Builder | Partners", + "hr_management": "Builder | Hiring", + "contact": "Builder | Contacts", + "company": "Builder | Companies", + "universal_module": "Builder | Universal Module", + "products": { + "rental": "Builder | Lease Management", + "sale": "Builder | Inventory Management" + } + }, + "time_board": "Time Board", + "activities_board": "Activities board", + "settings": { + "general_settings": "Settings | General settings", + "user_list": "Settings | Users — Configuring Users", + "user_departments": "Settings | Users — Groups", + "user_edit": "Settings | Users — Edit user", + "calls": { + "account": "Settings | Calls — Account", + "users": "Settings | Calls — Users", + "scenarios": "Settings | Calls — Configuring", + "sip_registrations": "Settings | Calls — SIP Registrations" + }, + "mailing": "Settings | Email Settings", + "integrations": "Settings | Integrations", + "billing": "Settings | Billing", + "api_access": "Settings | API Access", + "superadmin": "Settings | Admin Panel", + "document": { + "templates": "Settings | Documents — Document templates", + "creation": "Settings | Documents — Document creation fields" + } + }, + "mailing": "E-mail and Messenger", + "orders": "Orders", + "notes": "Notes" + } +} diff --git a/frontend/public/locales/en/component.card.json b/frontend/public/locales/en/component.card.json new file mode 100644 index 0000000..a5c18f3 --- /dev/null +++ b/frontend/public/locales/en/component.card.json @@ -0,0 +1,344 @@ +{ + "card": { + "status": { + "active": "Active", + "liquidating": "Liquidating", + "liquidated": "Liquidated", + "bankrupt": "Bankrupt", + "reorganizing": "Reorganizing" + }, + "type": { + "legal": "Legal", + "individual": "Individual" + }, + "branch_type": { + "main": "Main", + "branch": "Branch" + }, + "opf": { + "bank": "Bank", + "bank_branch": "Bank Branch", + "nko": "Non-Banking Credit Organization (NKO)", + "nko_branch": "NKO Branch", + "rkc": "Settlement and Cash Center", + "cbr": "Bank of Russia Department (March 2021)", + "treasury": "Treasury Department (March 2021)", + "other": "Other" + }, + "days": ["day", "days", "days"], + "creation_date": "Created", + "in_work": "In Work", + "shipping_date": "Shipped", + "closing_date": "Closed", + "open_chat": "Open chat", + "analytics": "Analytics", + "card_focus": "Focus (highlights the card on board and list)", + "field_formula_circular_dependency_warning": { + "warning_title": "Unable to save changes", + "warning_annotation": "You are trying to update the formula field \"{{fieldName}}\" with usage of another formula that creates a circular dependency (one formula depends on another formula which depends on it). Please update the formula settings to resolve the circular dependency and try again.", + "cancel_changes": "Cancel Changes", + "continue": "Continue" + }, + "field_used_in_formula_warning": { + "warning_title": "Unable to save changes", + "warning_annotation": "You're trying to delete field \"{{fieldName}}\" which is used in formula. Remove it from formula first and try again.", + "cancel_changes": "Cancel Changes", + "continue": "Continue" + }, + "mutation_warning": { + "title": { + "change_stage": "Unable to change stage", + "save_changes": "Unable to save changes" + }, + "annotation": "Some mandatory fields are not filled in. Please fill them in and try again.", + "continue": "Continue" + }, + "change_linked_responsibles_modal": { + "header": "Change Responsible User", + "annotation": "Select linked cards where you also want to change the responsible user.", + "change_responsible_in_chats": "Add responsible user to card chats", + "chats_hint": "The new responsible user will be added to chats of linked cards where they are not yet present." + }, + "owner": "Owner", + "delete_warning_title": "Do you really want to delete the entity?", + "delete_warning_caption": "This action cannot be undone.", + "files": "Files", + "from_card_or_linked_entities": "From card or linked entities", + "recent": "Recent", + "change_board": "Change board", + "placeholders": { + "name": "Enter name..." + }, + "ui": { + "requisites_codes": { + "ie_name": "Individual Entrepreneur First Name", + "ie_surname": "Individual Entrepreneur Last Name", + "ie_patronymic": "Individual Entrepreneur Middle Name", + "org_name": "Company Name", + "org_tin": "Company TIN", + "org_trrc": "Company TRRC", + "org_psrn": "PSRN", + "org_type": "Company Type", + "org_full_name": "Full Company Name", + "org_short_name": "Short Company Name", + "org_management_name": "Director Name", + "org_management_post": "Director Position", + "org_management_start_date": "Director Start Date", + "org_branch_count": "Branch Count", + "org_branch_type": "Branch Type", + "org_address": "Company Address", + "org_reg_date": "Registration Date", + "org_liquidation_date": "Liquidation Date", + "org_status": "Company Status", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Average Number of Employees", + "org_extra_founders": "Company Founders", + "org_extra_managers": "Company Managers", + "org_extra_capital": "Authorized Capital", + "org_extra_licenses": "Licenses", + "org_extra_phones": "Phone Numbers", + "org_extra_emails": "Email Addresses", + "bank_name": "Bank Name", + "bank_bic": "Bank BIC", + "bank_swift": "SWIFT", + "bank_tin": "Bank TIN", + "bank_trrc": "Bank TRRC", + "bank_correspondent_acc": "Correspondent Account", + "bank_payment_city": "Payment Order City", + "bank_opf_type": "Credit Organization Type", + "bank_checking_account": "Checking Account" + }, + "analytics": "Analytics", + "requisites": "Requisites", + "card_page_header": { + "overview": "Overview", + "products": "Products", + "create_new_order": "Create new order", + "orders": "Orders", + "board": "Board", + "list": "List", + "calendar": "Calendar", + "timeline": "Gantt", + "relocate_card_button": "Relocate Card", + "order_denied": "You don't have permission to create orders. Contact account administrator for access." + }, + "actions_dropdown": { + "delete_entity": "Delete entity", + "fine_tune": "Fine tune" + }, + "mini_card": { + "enter_name": "Enter {{name}} name", + "unpin": "Unpin", + "owner": "Owner", + "stage": "Stage", + "placeholders": { + "select_stage": "Select stage" + }, + "fields": { + "value": "Value", + "participants": "Participants", + "start_date": "Start Date", + "end_date": "End Date", + "description": "Description" + } + }, + "stages": { + "disabled_while_adding": "Status change will be available after the card is created" + }, + "feed": { + "unknown": "Unknown 👤", + "readonly": "You can not add elements to this card", + "disabled_while_adding": "Adding items to the card will be available after it is created", + "notes": "Notes", + "activities": "Activities", + "tasks": "Tasks", + "amwork_messenger": "{{company}} Messenger", + "documents": "Documents", + "create_documents_tab": "Create Documents", + "create_chat": "Create a Chat with the Team", + "add_visit": "Schedule a Meeting", + "share_documents": "Share generated documents", + "empty": "There's nothing here yet", + "add_activity": { + "placeholder": "Activity description" + }, + "add_note": { + "add_note": "Add note" + }, + "add_task_modal": { + "dates_error": "End date must be later than start date.", + "tab": "Add Task", + "task_name": "Task Name *", + "planned_time": "Estimated Time", + "board_name": "Board Name", + "assignee": "Assignee", + "start_date": "Start Date", + "end_date": "End Date", + "description": "Description", + "subtasks": "Subtasks", + "placeholder": "Add title...", + "add_board": "Add board", + "save": "Save", + "new_task": "New task", + "repeating_task": { + "title": "Repeat Task", + "hint": "Set the number and interval for repeat tasks if needed. Repeat tasks will be created with the same settings. If a task has a start or end time, it will be scheduled with the set interval, taking workdays into account.", + "count": "Count", + "interval": "Interval", + "interval_disabled_tooltip": "Set the start or end date of the task", + "intervals": { + "none": "No interval", + "day": "Daily", + "week": "Weekly", + "month": "Monthly" + } + }, + "placeholders": { + "board": "Select board", + "subtask": "New subtask" + } + }, + "filter": { + "all": "All", + "tasks": "Tasks", + "activities": "Activities", + "notes": "Notes", + "mail": "Mail", + "files": "Files", + "calls": "Calls" + }, + "create_documents": { + "order": "Order-{{number}}", + "documents": "Documents", + "create_documents": "Create Documents", + "create_new_documents": "Create new documents", + "select_all": "Select all", + "clear_selection": "Clear selection", + "send_by_email": "Send by email", + "add_template": "Add template", + "invalid_template_title": "Template Error", + "invalid_template_annotation": "Document cannot be generated due to syntax errors in the template. Update the template or choose another.", + "hints": { + "cancel": "Cancel", + "generate": "Generate", + "generate_anyway": "Generate anyway", + "docx_format": "DOCX Format", + "pdf_format": "PDF Format", + "no_documents_title": "No templates available", + "no_documents_annotation": "No document templates available for this entity. Create new or modify existing ones in Documents on Settings page.", + "creating_documents_title": "Creating your documents", + "creating_documents_annotation": "It may take some time, but after that you can mail or download the documents.", + "select_template_title": "Select a template", + "select_template_annotation": "To select a template, open the dropdown list and choose a desired file.", + "format_title": "Format", + "issues_identified": "Several issues have been identified", + "invalid_tags_annotation": "There are some codes that appear to be invalid or are associated with an empty field:", + "format_annotation": "Select the format(s) in which you want to generate the document." + }, + "placeholders": { + "select_order": "Select order", + "select_template": "Select template", + "invalid_tags_annotation": "Some codes are invalid or associated with an empty field:" + } + }, + "activity_block": { + "creator": "Assignee", + "start_date": "Date", + "plan_time": "Time" + }, + "creator_tag": { + "task_setter_at_time": "{{taskSetter}} at {{time}}" + }, + "files_block": { + "unknown_file": "Unknown file", + "downloaded_file": "{{companyName}} – Downloaded file", + "attachment": "attachment", + "attachments": "attachments", + "download_all": "Download all" + }, + "note_block": { + "block_title": "Note", + "creation_date": "Creation date", + "message": "Message", + "to_recipient": "to {{recipient}}" + }, + "task_block": { + "creator": "Assignee", + "start_date": "Start Date", + "end_date": "End Date", + "no_due_date": "No due date" + }, + "mail_block": { + "sender": "Sender", + "recipient": "Recipient", + "date": "Date", + "seen": "Seen" + }, + "document_block": { + "creation_date": "Creation Date", + "docs": "Docs", + "annotation": "{{creator}} created {{documentName}} on {{date}}" + }, + "call_block": { + "start_date": "Date", + "who_called": "Caller", + "who_got_call": "Callee", + "did_not_get_through": "Failed to get through", + "time": { + "d": "d", + "h": "h", + "m": "m", + "s": "s" + }, + "status": { + "missed_call": "Missed Call", + "incoming_call": "Incoming Call", + "outgoing_call": "Outgoing Call" + }, + "comment": "Comment" + }, + "common": { + "author": "Author", + "reporter": "Reporter", + "created_at": "{{day}} at {{time}}", + "result": "Result", + "functional_options": { + "edit": "Edit", + "delete": "Delete" + }, + "item_tag": { + "tasks": { + "resolved": "Completed Task", + "expired": "Overdue Task", + "future": "Upcoming Task", + "today": "Today's Task" + }, + "activities": { + "resolved": "Completed Activity", + "expired": "Overdue Activity", + "future": "Upcoming Activity", + "today": "Today's Activity" + } + }, + "activity_type_picker": { + "add_new_type": "Add new type", + "warn_title": "Do you really wish to delete this type of activity?", + "warn_annotation": "You will no longer be able to create this type of activity.", + "placeholders": { + "select_activity_type": "Select activity type" + } + }, + "user_picker": { + "add": "Add", + "placeholder": "Select user" + } + } + } + } + } +} diff --git a/frontend/public/locales/en/component.entity-board.json b/frontend/public/locales/en/component.entity-board.json new file mode 100644 index 0000000..9e2f945 --- /dev/null +++ b/frontend/public/locales/en/component.entity-board.json @@ -0,0 +1,23 @@ +{ + "entity_board": { + "ui": { + "project_entity_card_item": { + "start_date": "Start Date", + "end_date": "End Date", + "titles": { + "not_resolved": "Not resolved tasks", + "upcoming": "Upcoming tasks", + "overdue": "Overdue tasks", + "today": "Today tasks", + "resolved": "Resolved tasks" + } + }, + "common_entity_card": { + "no_tasks": "No tasks", + "task_today": "Task today", + "overdue_task": "Overdue task", + "upcoming_task": "Upcoming task" + } + } + } +} diff --git a/frontend/public/locales/en/component.section.json b/frontend/public/locales/en/component.section.json new file mode 100644 index 0000000..21660c3 --- /dev/null +++ b/frontend/public/locales/en/component.section.json @@ -0,0 +1,191 @@ +{ + "section": { + "section_header_with_boards": { + "board": "Board", + "list": "List", + "dashboard": "Dashboard", + "reports": "Reports", + "timeline": "Gantt", + "automation": "Automation", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Soon...)", + "tooltips": { + "reports_denied": "You don't have permission to view reports. Contact account administrator for access.", + "dashboard_denied": "You don't have permission to view dashboard. Contact account administrator for access." + } + }, + "empty_projects_block": { + "annotation": "You don't have any projects yet or nothing has been found with the selected filters." + }, + "common": { + "add_card": "Add card", + "new_stage": "New Stage", + "create_button_tooltip": { + "universal": "Add Card", + "partner": "Add Partner", + "deal": "Add Deal", + "contact": "Add Contact", + "company": "Add Company", + "supplier": "Add Supplier", + "contractor": "Add Contractor", + "hr": "Add Candidate", + "project": "Add Project" + }, + "cards_total": { + "universal": ["Cards", "Cards", "Cards"], + "partner": ["Partners", "Partners", "Partners"], + "customer": ["Customers", "Customers", "Customers"], + "deal": ["Cards", "Cards", "Cards"], + "contact": ["Contacts", "Contacts", "Contacts"], + "company": ["Companies", "Companies", "Companies"], + "supplier": ["Suppliers", "Suppliers", "Suppliers"], + "contractor": ["Contractors", "Contractors", "Contractors"], + "hr": ["Candidates", "Candidates", "Candidates"], + "project": ["Projects", "Projects", "Projects"] + }, + "filter_button": { + "ui": { + "filter_drawer": { + "closed_at": "Closing Date", + "just_my_cards": "Just my Cards", + "sorting": "Sorting", + "creation_date": "Creation Date", + "owners": "Owners", + "stages": "Stages", + "tasks": "Tasks", + "placeholders": { + "selected_options": "Selected options", + "search_by_card_name": "Search by card name" + }, + "sorting_options": { + "manual": "Manual", + "created_asc": "Created: from old to new", + "created_desc": "Created: from new to old", + "name_asc": "Name: ascending", + "name_desc": "Name: descending" + }, + "tasks_options": { + "all": "All cards", + "with_task": "With tasks", + "without_task": "Without tasks", + "overdue_task": "With overdue tasks", + "today_task": "With today tasks" + }, + "field_filter_string": { + "is_empty": "Is empty", + "is_not_empty": "Is not empty", + "contains": "Contains...", + "placeholders": { + "value": "Value" + } + }, + "field_filter_exists": { + "is_empty": "Is empty", + "is_not_empty": "Is not empty" + } + }, + "date_period_picker": { + "date_period_segmented_control": { + "period": "Period", + "period_hint": "For a selected period", + "from": "From", + "from_hint": "From selected date", + "to": "To", + "to_hint": "Before selected date" + }, + "placeholders": { + "select_period": "Select period" + } + }, + "field_filter_items": { + "field_filter_boolean": { + "switch_on": "Switch ON", + "switch_off": "Switch OFF" + }, + "field_filter_number": { + "from": "From", + "to": "To" + } + } + } + }, + "settings_button": { + "module_settings": "Module settings", + "settings": "Settings", + "import": "Import", + "get_import_template": "Get import template", + "board_settings": "Board settings", + "table_settings": "Table settings", + "upload_file": "Upload file", + "sales_pipeline_settings": "Sales pipeline settings", + "request_setup": "Request {{company}} setup", + "import_entities_modal": { + "title": "Import from Excel", + "annotation": "To import your database into {{company}}, follow a few simple steps:", + "perform_import": "Perform import", + "setting_up_file_to_import": { + "title": "Setting up a file to import", + "step1": "Download an example template file;", + "step2": "Enter your data into a table that matches the {{company}} field structure;", + "step3": "After completing the column matching settings, you can save the settings as an import template." + }, + "import_file": { + "title": "Import file", + "step1": "Click \"Upload file\";", + "step2": "In the window that opens, select the previously filled template;", + "step3": "After downloading, click the \"Perform import\" button." + } + }, + "import_in_background_info_modal": { + "title": "Import from Excel", + "content": "The import process is currently running in the background. You may continue working in {{company}} as usual, and we will notify you once the process has been completed.", + "ok": "Ok" + } + }, + "search_box": { + "nothing_found": "Nothing was found", + "placeholder": "Search..." + }, + "section_table_settings_sidebar": { + "table_settings": "Table Settings", + "display_columns": "Display Columns" + } + }, + "section_table": { + "hidden": "Hidden", + "hidden_hint": "You can not view and access this field because it was hidden in the module field settings by the account administrator.", + "empty": "You have not added any cards yet", + "all_columns_hidden": "All columns are hidden in table settings", + "name": "Name", + "owner": "Owner", + "stage": "Stage", + "date_created": "Date created", + "value": "Value", + "participants": "Participants", + "start_date": "Start date", + "end_date": "End date", + "description": "Description", + "placeholders": { + "participants": "Add participants" + }, + "select_all": { + "title": "Mass selection", + "description": "Apply to all cards ({{count}}) or only for the current page?", + "all_elements": "For all cards", + "for_page": "Only for the current page" + }, + "batch_actions": { + "cards_selected": "Cards selected: {{count}}", + "change_responsible": "Change responsible", + "change_stage": "Change stage", + "delete": "Delete", + "change_stage_annotation": "Select a new stage for the selected cards.", + "action_cannot_be_undone": "This action can not be undone.", + "change_responsible_annotation": "Select a new responsible user for the selected cards.\nThis action can not be undone.", + "delete_warning_title": "Delete selected cards", + "delete_warning_annotation": "Are you sure you want to delete the selected cards? This action can not be undone.", + "select_linked_entities_annotation": "Change the responsible user in the linked cards" + } + } + } +} diff --git a/frontend/public/locales/en/module.automation.json b/frontend/public/locales/en/module.automation.json new file mode 100644 index 0000000..6f3f6d8 --- /dev/null +++ b/frontend/public/locales/en/module.automation.json @@ -0,0 +1,368 @@ +{ + "automation": { + "modals": { + "add_activity_automation_modal": { + "current_responsible_user": "Current responsible user", + "activity_default_name": "Activity #{{id}}", + "title_add": "Add Create Activity Automation", + "title_update": "Update Create Activity Automation", + "create_activity": "Create Activity *", + "responsible_user": "Responsible User *", + "due_date": "Due Date", + "defer_start": "Start Date", + "activity_type": "Activity Type *", + "description": "Description", + "current_responsible_user_hint": "Select the user responsible for the activity. The current responsible user is the one assigned to the card where the activity will be created.", + "placeholders": { + "select_activity_type": "Select activity type" + } + }, + "add_task_automation_modal": { + "task_default_name": "Task #{{id}}", + "title_add": "Add Create Task Automation", + "title_update": "Update Create Task Automation", + "create_task": "Create Task *", + "due_date": "Due Date", + "defer_start": "Start Date", + "responsible_user": "Responsible User *", + "name_and_description": "Name * and Description", + "current_responsible_user": "Current responsible user", + "current_responsible_user_hint": "Select the user responsible for the task. The current responsible user is the one assigned to the card where the task will be created.", + "placeholders": { + "task_name": "Task name", + "select_user": "Select user" + } + }, + "change_responsible_automation_modal": { + "change_responsible_default_name": "Responsible #{{id}}", + "title_add": "Add Change Responsible Automation", + "title_update": "Update Change Responsible Automation", + "responsible_user": "Change Responsible User To *", + "placeholders": { + "select_responsible_user": "Select new responsible" + } + }, + "change_stage_automation_modal": { + "change_stage_default_name": "Stage #{{id}}", + "title_add": "Add Change Stage Automation", + "title_update": "Update Change Stage Automation", + "change_stage": "Change Stage *", + "cards_copies": "Cards Copies", + "stage": "Sales Pipeline and Stage *", + "automation_copy": { + "move_label": "Do not create a copy of the deal when changing its status", + "copy_original_label": "Create a copy of the deal and move the original deal to the new status when changing its status", + "copy_new_label": "Create a copy of the deal when changing its status, but leave the original deal at its current status.", + "hint": "When this option is enabled, the system will automatically create a copy of the card at its current status with each automatic status change. This is essential for accurate reporting and sales planning metrics, particularly when the status change is associated with final system statuses such as 'Successfully Completed' or 'Unsuccessfully Completed'." + }, + "placeholders": { + "select_stage": "Select stage" + } + }, + "change_linked_stage_automation_modal": { + "change_stage_default_name": "Linked Stage #{{id}}", + "title_add": "Add Change Linked Stage Automation", + "title_update": "Update Change Linked Stage Automation", + "change_stage": "Change Stage *", + "cards_copies": "Cards Copies", + "entity_type": "Module *", + "stage": "Sales Pipeline and Stage *", + "no_stages": "The selected module has no statuses. Select another module to create automation.", + "automation_copy": { + "move_label": "Do not create a copy of the deal when changing its status", + "copy_original_label": "Create a copy of the deal and move the original deal to the new status when changing its status", + "copy_new_label": "Create a copy of the deal when changing its status, but leave the original deal at its current status.", + "hint": "When this option is enabled, the system will automatically create a copy of the card at its current status with each automatic status change. This is essential for accurate reporting and sales planning metrics, particularly when the status change is associated with final system statuses such as 'Successfully Completed' or 'Unsuccessfully Completed'." + }, + "placeholders": { + "select_stage": "Select stage" + } + }, + "send_email_automation_modal": { + "configure_mailbox_limits": "Configure mailbox limits", + "max_number_of_emails_per_day": "Maximum number of emails per day", + "send_email_default_name": "Email #{{id}}", + "title_add": "Add Email Automation", + "title_update": "Update Email Automation", + "title_hint": "Email messages will only be sent to contact cards that have a filled Email field.", + "email_text": "Email text *", + "recipient_name": "- Recipient name", + "recipient_name_hint": "You can set the placeholder {{contact_name}} in the email text, and it will be replaced with the contact's name from the card.", + "user_name": "- User name", + "user_phone": "- User phone", + "send_with_html": "Send email with HTML markup", + "sender_email": "Sender's Mail *", + "sender_name": "Sender's Name *", + "send": "Send", + "emails_per_day": "emails per day", + "emails_per_day_hint": "The email limit parameter is necessary to avoid being banned by the email service provider. Each email service sets its own limits on sending messages. For example, the basic version of Google Workspace allows sending 500 messages per day from one mailbox.", + "mailing_parameters": "Mailing Parameters *", + "options": { + "addresses": "Shipping Addresses *", + "all_addresses": "Send to all cards and email addresses", + "fine_tune_addresses": "Customize...", + "main_entity": "Send to main card", + "main_entity_hint": "Choose whether to send to all addresses in the main card or just the first one.", + "main_entity_all": "to all addresses", + "main_entity_only_first": "only to the first address", + "contact": "Send to contact card", + "contact_hint": "Choose whether to send to all related contact cards or just the first one, and whether to send to all addresses in the card or only the first.", + "contact_all_entities_all_values": "to all addresses in all contact cards", + "contact_all_entities_first_value": "only to the first address in all contact cards", + "contact_first_entity_all_values": "to all addresses in the first contact card only", + "contact_first_entity_first_value": "only to the first address in the first contact card", + "company": "Send to company card", + "company_hint": "Choose whether to send to all related company cards or just the first one, and whether to send to all addresses in the card or only the first.", + "company_all_entities_all_values": "to all addresses in all company cards", + "company_all_entities_first_value": "only to the first address in all company cards", + "company_first_entity_all_values": "to all addresses in the first company card only", + "company_first_entity_first_value": "only to the first address in the first company card" + }, + "placeholders": { + "title": "Enter title", + "email_text": "Enter email text", + "email_markup": "Enter HTML markup", + "select_email_address": "Select email address", + "select_user": "Select user" + } + }, + "create_entity_automation_modal": { + "create_entity_default_name": "Card #{{id}}", + "title_add": "Create Linked Card Automation", + "title_update": "Update Linked Card Automation", + "create_card": "Create Linked Card *", + "select_entity_type": "Module", + "select_entity_type_hint": "Select the module where the card will be created. To avoid cycles, the card cannot be created in the current module.", + "select_stage": "Board or Sales Funnel & Status", + "responsible_user": "Assigned User", + "responsible_user_hint": "Select the user responsible for the task. The current responsible user manages the card where the task will be created.", + "current_responsible_user": "Current Responsible User", + "entity_name": "Card Name", + "placeholders": { + "select_entity_type": "Select Module", + "entity_name": "Enter Card Name" + } + }, + "send_internal_chat_automation_modal": { + "send_internal_chat_default_name": "Chat #{{id}}", + "title_add": "Create Chat Message Automation", + "title_update": "Update Chat Message Automation", + "sender": "Sender", + "responsible_user_hint": "Select the sender of the message. The current responsible user is the one responsible for the card from which the automation was created.", + "current_responsible_user": "Current Responsible User", + "recipients": "Recipients", + "message": "Message Text", + "placeholders": { + "recipients": "Select users", + "message": "Enter message" + } + }, + "send_external_chat_automation_modal": { + "send_external_chat_default_name": "External chat #{{id}}", + "title_add": "Create external chat automation", + "title_update": "Update external chat automation", + "sender": "Sender *", + "responsible_user_hint": "Select the message sender. The current responsible user is the one assigned to the card from which this automation was created.", + "current_responsible_user": "Current responsible user", + "provider": "Chat provider *", + "message": "Message text *", + "send_to_chats": "Send to card chats", + "send_to_phone_numbers": "Send to phone numbers", + "add_phone_number": "Add number", + "hint_chats": "The message will be sent to all external card chats in this module based on the selected settings", + "hint_phone_numbers": "The message will be sent to the selected phone numbers", + "phone_numbers_disabled": "Sending by phone numbers is only available for Whatsapp and Telegram providers", + "placeholders": { + "message": "Enter a message" + }, + "errors": { + "phone": "Phone number is missing" + }, + "options": { + "addresses": "Target chats *", + "all_addresses": "Send to all cards and all external chats", + "fine_tune_addresses": "Configure...", + "main_entity": "Send to main card", + "main_entity_hint": "Choose whether to send the message to all chats in the main card or only to the first.", + "main_entity_all": "to all chats", + "main_entity_only_first": "to the first chat only", + "contact": "Send to contact card", + "contact_hint": "Choose whether to send the message to all linked contact cards or only to the first, and whether to send to all chats in the card or only to the first.", + "contact_all_entities_all_values": "to all chats in all contact cards", + "contact_all_entities_first_value": "to the first chat in all contact cards only", + "contact_first_entity_all_values": "to all chats in the first contact card", + "contact_first_entity_first_value": "to the first chat in the first contact card", + "company": "Send to company card", + "company_hint": "Choose whether to send the message to all linked company cards or only to the first, and whether to send to all chats in the card or only to the first.", + "company_all_entities_all_values": "to all chats in all company cards", + "company_all_entities_first_value": "to the first chat in all company cards only", + "company_first_entity_all_values": "to all chats in the first company card", + "company_first_entity_first_value": "to the first chat in the first company card" + } + }, + "request_http_automation_modal": { + "http_request_default_name": "HTTP Request #{{id}}", + "title_add": "Create HTTP Request Automation", + "title_update": "Update HTTP Request Automation", + "http_request": "HTTP Request Parameters", + "url": "URL *", + "method": "Method *", + "headers": "Headers", + "params": "Query Parameters" + }, + "automation_modal_template": { + "apply_trigger_hint": "When this option is selected, the automation will be applied to all existing cards in this status. Otherwise, it will only apply to new cards entering this status. This action can be performed each time the automation is saved.", + "apply_trigger_hint_list": "When this option is selected, the automation will be applied to all existing cards. Otherwise, it will only apply to new cards. This action can be performed each time the automation is saved.", + "automation_name": "Automation Name *", + "trigger": "Trigger *", + "conditions": "Conditions", + "delay": "Delay", + "delay_one_stage_title": "Delay only while in status", + "delay_one_stage_hint": "The automation will run only if the card stays in this status until the delay ends. If the card moves, it won’t trigger.", + "delay_all_stages_title": "Delay regardless of status", + "delay_all_stages_hint": "The automation will run after the delay, even if the card moves to a different status.", + "enable": "Enable Automation", + "enable_annotation": "You can enable and disable automation", + "apply_trigger": "Apply the trigger to the current objects in the stage", + "apply_trigger_list": "Apply the trigger to the current objects", + "delete_automation": "Delete automation", + "on": "ON", + "placeholders": { + "name": "Name" + } + }, + "delete_automation_modal": { + "title": "Do you really want to delete this automation?", + "annotation": "This action cannot be undone" + }, + "common": { + "use_get_chats_template_options": { + "recipient_name": "- Recipient name", + "recipient_name_hint": "You can set the placeholder {{contact_name}} in the chat message text, and it will be replaced with the contact's name from the card." + }, + "trigger_select": { + "exact_time_of_automation": "Exact Time of Automation", + "exact_time": "Exact time: {{day}} at {{time}}", + "when_transitioning": "When transitioning or creating", + "at_the_transition": "At the transition to the stage", + "when_creating": "When creating in the stage", + "when_creating_list": "When creating", + "when_responsible_changes": "When responsible user changes", + "placeholder": "Select trigger" + }, + "conditions_block": { + "field": "Field *", + "condition": "Condition *", + "entity_type_field_filter_string": { + "is_empty": "Is empty", + "is_not_empty": "Is not empty", + "contains": "Contains...", + "placeholders": { + "value": "Value" + } + }, + "entity_type_field_filter_number": { + "placeholders": { + "from": "From", + "to": "To" + } + }, + "entity_type_field_filter_boolean": { + "switch_on": "Switch ON", + "switch_off": "Switch OFF" + }, + "fields_conditions": "Fields Conditions", + "add_field_condition": "Add field condition", + "responsible_users": "Responsible Users", + "responsible": "Responsible", + "budget": "Budget", + "value": "Value", + "from": "from", + "to": "to", + "placeholders": { + "selected_participants": "Selected participants", + "selected_options": "Selected options", + "add_condition": "Add condition", + "select_responsible": "Select responsible" + } + }, + "description_block": { + "placeholders": { + "description": "Description" + } + }, + "due_date_select": { + "due_date": "Due date", + "options": { + "during_this_week": "During this week", + "in_3_days": "In 3 days", + "in_a_day": "In a day", + "end_of_the_day": "End of the day", + "at_the_time_of_creation": "At the time of creation" + } + }, + "defer_start_select": { + "defer_start": "Start date", + "options": { + "in_an_hour": "In an hour", + "in_a_day": "In a day", + "in_3_days": "In 3 days" + } + }, + "delay_select": { + "no_delay": "No Delay", + "5_minutes": "5 minutes", + "10_minutes": "10 minutes", + "15_minutes": "15 minutes", + "30_minutes": "30 minutes", + "1_hour": "1 hour" + }, + "template_list": { + "name": "Name", + "owner": "Owner", + "tooltip": "Field codes will be replaced with corresponding card field values", + "show_templates": "Show field codes", + "hide_templates": "Hide field codes" + } + } + }, + "external_providers_select": { + "add_new_provider": "Add provider", + "no_available_providers": "No available providers" + }, + "sidebar": { + "title": "Automation", + "create_task": "Task", + "task_create": "Task", + "create_activity": "Activity", + "activity_create": "Activity", + "change_stage": "Change Stage", + "entity_stage_change": "Change Stage", + "entity_linked_stage_change": "Change Stage in Linked Card", + "entity_responsible_change": "Change Responsible", + "send_email": "Email", + "email_send": "Email", + "chat_send_amwork": "Message in Chat", + "chat_send_external": "Message in External Chat", + "entity_create": "Linked Card", + "http_call": "HTTP Request", + "automation": "Automation" + }, + "block": { + "create_task": "Task", + "task_create": "Task", + "create_activity": "Activity", + "activity_create": "Activity", + "change_stage": "Change Stage", + "entity_stage_change": "Change Stage", + "entity_linked_stage_change": "Change Linked Stage", + "entity_responsible_change": "Responsible", + "send_email": "Email", + "email_send": "Email", + "entity_create": "Card", + "chat_send_amwork": "Chat", + "chat_send_external": "External Chat", + "http_call": "HTTP Request" + } + } +} diff --git a/frontend/public/locales/en/module.bpmn.json b/frontend/public/locales/en/module.bpmn.json new file mode 100644 index 0000000..a1e3da9 --- /dev/null +++ b/frontend/public/locales/en/module.bpmn.json @@ -0,0 +1,174 @@ +{ + "bpmn": { + "hooks": { + "use_get_service_task_options": { + "actions": { + "task_create": "Create Task", + "activity_create": "Create Activity", + "entity_stage_change": "Change Stage", + "entity_linked_stage_change": "Change Stage in Linked Card", + "entity_responsible_change": "Change Responsible", + "email_send": "Send Email", + "entity_create": "Create Card", + "chat_send_amwork": "Send Message to Chat", + "chat_send_external": "Send Message to External Chat", + "http_call": "Send HTTP Request" + } + }, + "use_bpmn_automations_columns": { + "name": "Name", + "created_by": "Created By", + "active": "Active", + "delete": "Delete", + "status": "Status" + }, + "use_get_workspace_event_options": { + "events": { + "create": "Card Created Event", + "change_responsible": "Owner Changed Event", + "change_stage": "Stage Changed Event" + } + } + }, + "pages": { + "bpmn_automations_page": { + "automation_process_schema_error_warning": { + "continue": "Continue", + "title": "Failed to start process \"{{name}}\"", + "annotation": "BPMN schema validation failed. Please check your process and try again." + }, + "service_task_popup_template": { + "task_name": "Service Task Name", + "placeholders": { + "task_name": "Name" + } + }, + "workspace_sequence_flow_popup": { + "module": "Select Module", + "module_hint": "Select module where specified conditions should be met", + "stage": "Select Stage", + "sequence_flow": "Sequence Flow", + "flow_name": "Flow Name", + "conditions": "Conditions", + "placeholders": { + "flow_name": "Name" + } + }, + "create_automation_process_modal": { + "create": "Create", + "title": "Create New Automation Process", + "name": "Automation Name", + "new_process": "New Process #{{number}}", + "placeholders": { + "name": "Name" + } + }, + "bpmn_automations_processes_modeler": { + "add_conditions": "Add Conditions...", + "center_view": "Center View", + "pallete": { + "create_workspace_parallel_gateway": "Create Parallel Gateway", + "create_workspace_start_event": "Create Start Event", + "create_workspace_end_event": "Create End Event", + "create_workspace_exclusive_gateway": "Create Exclusive Gateway", + "create_workspace_data_object": "Create Data Object", + "create_workspace_data_store": "Create Data Store", + "create_workspace_participant_expanded": "Create Expanded Participant", + "create_workspace_group": "Create Group" + }, + "color_picker": { + "default_color": "Default Color", + "blue_color": "Blue Color", + "orange_color": "Orange Color", + "green_color": "Green Color", + "red_color": "Red Color", + "purple_color": "Purple Color" + }, + "context_pad": { + "delay": "Append Delay", + "append_end_event": "Append End Event", + "append_gateway": "Append Gateway", + "append_text_annotation": "Append Text Annotation", + "replace": "Replace", + "delete": "Delete", + "set_color": "Set Color", + "connect": "Connect", + "task_create": "Create Task", + "activity_create": "Create Activity", + "entity_stage_change": "Change Stage", + "entity_linked_stage_change": "Change Stage in Linked Card", + "entity_responsible_change": "Change Responsible", + "email_send": "Send Email", + "entity_create": "Create Card", + "chat_send_amwork": "Send Message to Chat", + "chat_send_external": "Send Message to External Chat", + "http_call": "Send HTTP Request" + }, + "toggle_default_context_pad_entry": "Toggle Flow Default Condition", + "open_minimap": "Open Minimap", + "close_minimap": "Close Minimap", + "activate_hand_tool": "Activate Hand Tool", + "activate_lasso_tool": "Activate Lasso Tool", + "active_create_remove_space_tool": "Activate Create/Remove Space Tool", + "activate_global_connect_tool": "Activate Global Connect Tool", + "process_controls": { + "save_and_run": "Save and Run", + "save_draft": "Save Draft", + "start_process": "Start Process", + "stop_process": "Stop Process", + "cancel": "Cancel" + }, + "create_workspace_start_event": "Create Start Event", + "create_workspace_delay_event": "Create Delay Event", + "create_workspace_service_task": "Create Service Task" + }, + "workspace_delay_event_popup": { + "delay": "Delay", + "delay_label": "Delay *", + "delay_name": "Delay Name", + "placeholders": { + "delay_name": "Name" + } + }, + "delete_bpmn_automation_warning_modal": { + "title": "Are you sure you want to delete \"{{name}}\" BPMN Automation?", + "annotation": "This action cannot be undone." + }, + "create_workspace_service_task_menu": { + "workspace_service_tasks": "Service Tasks" + }, + "create_workspace_start_event_menu": { + "workspace_events": "Events" + }, + "workspace_start_event_popup": { + "event_name": "Event Name", + "event_type": "Select Event Type *", + "module": "Select Module *", + "module_hint": "Select module where event will be triggered", + "placeholders": { + "event_name": "Name" + } + }, + "bpmn_automations_settings_header": { + "create_bpmn_automation": "Create BPMN Automation", + "bpmn_automations": "BPMN 2.0 Automations", + "back": "Back" + }, + "bpmn_automations_table": { + "no_bpmn_automations_block": { + "annotation1": "You have no processes yet", + "annotation2": "Use the", + "annotation3": "button to add an automation." + } + }, + "bpmn_splashscreen": { + "heading": "Professional BPM Automation", + "price_ru": "for 99 ₽ per user per month, billed annually", + "price_us": "for $1 per user per month, billed annually", + "form_button": "Request Access", + "form_header": "Enable BPM Automation" + } + } + } + } +} diff --git a/frontend/public/locales/en/module.builder.json b/frontend/public/locales/en/module.builder.json new file mode 100644 index 0000000..a0ea96a --- /dev/null +++ b/frontend/public/locales/en/module.builder.json @@ -0,0 +1,867 @@ +{ + "builder": { + "components": { + "common": { + "next": "Next", + "back": "Back", + "save": "Save", + "workspace_builder": "Workspace Builder", + "your_workspace": "Your Workspace", + "no_links": "No available modules to link." + } + }, + "pages": { + "site_form_builder_page": { + "steps": { + "save_and_leave": "Save and Leave", + "default_title": "Web Form", + "default_form_title": "Contact Us", + "default_consent_text": "We use cookies to improve our website. Cookies provide a more personalized experience for you and web analytics for us. Read more in our Privacy Policy.", + "default_consent_link_text": "Privacy Policy", + "default_gratitude_header": "Thank You!", + "default_gratitude_text": "Our managers will contact you during business hours.", + "default_form_button_text": "Submit", + "default_client_button_text": "Open Form", + "common_error": "Please fill out all required fields.", + "step1": { + "title": "Step 1", + "description": "Basic Settings", + "error": "Please fill out all required fields in the first step." + }, + "step2": { + "title": "Step 2", + "description": "Form Fields", + "error": "Please fill out all required fields in the second step." + }, + "step3": { + "title": "Step 3", + "description": "Data Policy and Thank You Page", + "consent_error": "Please fill out all required fields in the 'Privacy Policy' section in the third step." + }, + "step4": { + "title": "Step 4", + "description": "Form Design" + }, + "step5": { + "title": "Step 5", + "description": "Form Installation Script" + } + }, + "site_form_builder_step1": { + "title": "Step 1. Form Setup", + "title_input_name": "Enter form name", + "choose_main_card": "Choose the main card that will be created after submitting the form", + "choose_linked_cards": "Choose the linked cards that will be created after submitting the form", + "linked_entities_select": "Select which modules to create cards in after form submission from your site", + "choose_board_annotation": "Select the board where the card will be created", + "card_settings": "Configure card creation", + "responsible_user": "Responsible user", + "check_duplicates": "Prevent duplicates", + "check_duplicates_hint": "If enabled, submitting the form with an existing email or phone number will link to the existing card instead of creating a new one.", + "yes": "Yes", + "placeholders": { + "title_input": "Form name", + "responsible_select": "Responsible", + "board": "Board" + } + }, + "site_form_builder_step2": { + "sidebar": { + "title": "Form Elements", + "header_title": "Form Header", + "header_hint": "Only one allowed.", + "delimiter_title": "Separator", + "delimiter_hint": "Line to separate different parts of the form.", + "field_attributes": { + "title": "Field Attributes", + "hint": "Title displayed above the field, description inside it.", + "label_name": "Title", + "placeholder_name": "Description" + }, + "card_name_block": { + "card_title": "Card Name", + "company_title": "Company Name", + "contact_title": "Full Name", + "card_text": "Name", + "company_text": "Name", + "contact_text": "Full Name" + }, + "field_elements": { + "no_available_fields": "No available fields", + "email": "Email", + "phone": "Phone", + "short_text": "Short Text", + "long_text": "Long Text", + "number": "Number", + "value": "Value", + "date": "Date", + "select": "Select", + "multiselect": "Multiselect", + "colored_select": "Colored select", + "colored_multiselect": "Colored multiselect", + "link": "Link", + "file": "File", + "switch": "Switch" + } + }, + "tree": { + "element_name": { + "placeholder": "Enter form title" + }, + "entity_field_element": { + "placeholder": "Enter field description", + "template": { + "company_name": "Name", + "contact_name": "Full Name", + "card_name": "Card Name", + "short_text": "Short Text", + "long_text": "Long Text", + "field": "Field", + "delimiter": "Separator", + "file": "File", + "schedule": "Select Calendar", + "schedule_performer": "Select Performer", + "schedule_date": "Select Date", + "schedule_time": "Select Time", + "placeholder": "Enter field name", + "required": "Required Field", + "validation": "Field Validation", + "field_type": "Field Type:", + "linked_with": "Linked with Module:" + } + } + } + }, + "site_form_builder_step3": { + "show_in_form_control": { + "yes": "Yes" + }, + "consent_block": { + "title": "Data Processing Policy", + "controls": { + "show_consent_title": "Show Privacy Policy", + "text_title": "Privacy Policy Text", + "text_hint": "Text will be displayed at the end of the form.", + "link_text_title": "Link Text", + "link_text_hint": "Text displayed on the link", + "link_title": "Link", + "link_hint": "Enter the URL of the Privacy Policy in the format https://example.com/.", + "consent_by_default_title": "Checkbox checked by default", + "placeholders": { + "text": "Privacy Policy Text", + "link_text": "Enter link text", + "link": "Enter link" + } + }, + "skeleton": { + "button_text": "Submit" + } + }, + "gratitude_block": { + "title": "Thank You Page", + "show_gratitude_title": "Show Thank You Page", + "gratitude_text": "Thank You Text", + "placeholders": { + "header": "Enter title", + "text": "Enter thank you text" + } + } + }, + "site_form_builder_step4": { + "sidebar": { + "custom_css_block": { + "custom_css": "Custom CSS", + "enable_custom_css": "Enable Custom CSS", + "placeholders": { + "custom_css": "Custom CSS rules" + } + }, + "advanced_settings": "Advanced Settings", + "modal_overlay_customization_block": { + "modal_overlay": "Popup Overlay", + "overlay_display": "Display Overlay", + "background_color": "Background Color", + "opacity": "Opacity" + }, + "switch_label": "Enable", + "title": "Form Design", + "description": "The changes take effect after saving.", + "powered_by_logo_block": { + "title": "{{company}} Logo", + "switch_label": "Enable" + }, + "header_customization_block": { + "title": "Header", + "text_color": "Text Color", + "text_align_title": "Alignment", + "text_align_left": "Left", + "text_align_center": "Center", + "text_align_right": "Right" + }, + "fields_customization_block": { + "title": "Fields", + "background_color": "Background Color", + "title_color": "Title Color", + "text_color": "Field Text Color" + }, + "form_button_customization_block": { + "title": "Button", + "background_color": "Button Color", + "text_color": "Text Color", + "border": "Corner Radius", + "border_rounded": "Rounded", + "border_squared": "Squared", + "size": "Button Size", + "small": "Small", + "medium": "Medium", + "large": "Large", + "text_input": "Button Text", + "hint": "Submit button located at the end of the form", + "placeholders": { + "text_input": "Submit" + } + }, + "client_button_customization_block": { + "title": "Website Button", + "background_color": "Button Color", + "text_color": "Button Text Color", + "border": "Corners", + "border_rounded": "Rounded", + "border_squared": "Square", + "size": "Button Size", + "small": "Small", + "medium": "Medium", + "large": "Large", + "text_input": "Button Text", + "hint": "Button that opens popup window with the form", + "placeholders": { + "text_input": "Open" + } + }, + "form_layout_customization_block": { + "title": "Form", + "view_title": "View", + "view_built_in": "Embedded", + "view_modal": "Popup", + "position_title": "Form Position", + "position_left": "Left", + "position_center": "Center", + "position_right": "Right", + "border_radius_title": "Border Radius", + "border_rounded": "Rounded", + "border_none": "None", + "orientation_title": "Orientation", + "orientation_horizontal": "Horizontal", + "orientation_vertical": "Vertical", + "max_width": "Max Width", + "max_height": "Max Height", + "background_color": "Background Color" + }, + "size_customization_element_template": { + "switch_label": "Enable", + "placeholders": { + "input": "Enter value" + } + } + }, + "preview": { + "iframe_title": "{{companyName}} – Form Preview", + "error": "Error while loading preview", + "open_in_a_new_tab": "Open in a new tab", + "open_preview": "Open preview", + "phone": "Phone", + "tablet": "Tablet", + "computer": "Computer", + "form": "Form", + "gratitude": "Thank you page", + "button": "Button" + } + }, + "site_form_builder_step5": { + "title": "Step 5. Copy the form script for your site", + "copy_form_code": "Copy form script", + "form_code": "Form script", + "multiform_mode": "Multi-Form Compatibility Mode", + "multiform_mode_hint": "Enable this mode if the page where you plan to insert the form already has another form created with our builder. This setting helps avoid display conflicts.", + "form_will_be_mounted_here": "The form will be embedded here", + "public_link_title": "Public Form Page", + "public_link_annotation": "Use the link below to access the public page for your form. If you don't need to embed it on your website, share this page with your users so they can fill it out. The form is ready to use and will automatically update when changes are made.", + "instruction_title": "Instructions for integrating the form on your site", + "view_form": "View form", + "static_site_integration_title": "Integration on static sites and sites created through builders", + "first_point": { + "title": "1. Adding a Form via CSS Class", + "annotation": "Assign the class {{mountClass}} to the elements on your site where you want to embed the form. Insert the form code copied above into the tag or at the very end of the tag. The form will appear inside the elements with the specified class. If the element already has content, the form will be added at the end of that content. If you choose to display the form as a popup, a button will be embedded in the specified location. Clicking this button will open a popup with the form. This is the preferred method for embedding forms on your site." + }, + "second_point": { + "title": "2. Adding a Form without CSS Classes", + "annotation": "If you cannot assign a class to the elements, insert the form code in the place on your site where you want it to appear. The form code can be embedded in any location on your site. In this case, the form will be displayed where the code is inserted. This method allows embedding only one form per page." + }, + "ssr_integration_title": "Integration on SSR sites (e.g., created with Next.js, Astro, etc.)", + "ssr_integration_annotation": "To avoid issues with hydration (loading scripts into HTML markup), it is recommended to use the method of specifying the class workspace-form-builder-mount-container for the element where you want to embed the form (described in more detail in point 2 above). In this case, the form script should preferably be placed in the tag.", + "analytics": { + "title": "Tracking Events and Goals", + "annotation": "The form supports sending events and goals to analytics systems. If your site has Google Analytics or Yandex.Metrika installed, you can track user interactions with the form. To do this, configure events or goals in your analytics system and link them to the identifiers listed in the table.", + "table_title": "Tracked Events", + "event_title": "Name", + "event_id": "Goal ID", + "form_view": "Form View", + "form_start": "Form Start", + "field_fill": "Fill “{{field}}” field", + "form_submit": "Form Submit" + } + } + }, + "headless_site_form_builder_page": { + "steps": { + "save_and_leave": "Save and Exit", + "default_title": "Website Form for API Integration", + "common_error": "Please fill in all required fields.", + "step1": { + "title": "Step 1", + "description": "Basic Setup", + "error": "Please complete all required fields in Step 1." + }, + "step2": { + "title": "Step 2", + "description": "Form Fields" + }, + "step3": { + "title": "Step 3", + "description": "Form Integration Guide" + } + }, + "site_form_builder_step3": { + "title": "Step 3. Integrate the Form with Your Website", + "json_integration_title": "Sending Data in JSON Format", + "api_integration_instruction": "This method is suitable if you process forms on your own server. To send user data from your form to {{company}}, make a request with the parameters listed below. Include the user data in the request body in JSON format. An example request body is provided below.", + "api_integration_request_annotation": "Method: POST\nHeader: Content-Type: application/json\nURL: {{endpoint}}", + "request_parameters_title": "Request Parameters", + "request_body_title": "Request Body", + "field_value_explanation": "In the value field, include the data entered by the user. Formats for each field are described below.", + "field_value_title": "Field Values", + "field_value": "Field value for {{label}}", + "response_title": "Response Format", + "response_explanation": "If the form is submitted successfully, the server returns an HTTP status code 201.", + "formdata_integration_title": "Sending Data in FormData Format", + "formdata_integration_instruction": "This method is suitable for sending forms from websites built with tools like Tilda. Data is sent in x-www-form-urlencoded format. To send data correctly, simply provide the webhook URL below and assign variables to the fields.", + "formdata_webhook_url": "Webhook URL", + "formdata_fields_title": "Field Variables", + "formdata_fields_explanation": "Each form field must have a name (variable) to map it to the correct field in the system. In this case, field variables are their numeric IDs. To send data correctly, assign each form field the variable specified in the table below.", + "fields_table": { + "field_title": "Field Name", + "field_format": "Field Format", + "field_example": "Example", + "field_id": "Variable (Field ID)", + "format": { + "text": "String enclosed in quotes", + "link": "String without spaces enclosed in quotes", + "phone": "String without spaces enclosed in quotes", + "email": "String without spaces enclosed in quotes", + "value": "String enclosed in quotes", + "select": "Numeric ID of the selected value", + "switch": "Boolean value without quotes", + "number": "Number without quotes", + "multiselect": "Array of numeric IDs for selected values", + "date": "Date string in ISO 8601 format enclosed in quotes" + }, + "example": { + "text": "\"User-entered value\"", + "link": "\"https://example.com\"", + "phone": "\"12345678910\"", + "email": "\"form@example.com\"", + "value": "\"User-entered value\"", + "select": "34", + "switch": "true", + "number": "42", + "multiselect": "[26, 27]", + "date": "\"2024-12-31T00:00:00\"" + } + } + } + }, + "online_booking_site_form_builder_page": { + "steps": { + "save_and_leave": "Save & Exit", + "default_title": "Online Booking Form", + "default_form_title": "Online Booking", + "default_consent_text": "We use cookies to improve your experience and analyze website traffic. Learn more in our Privacy Policy.", + "default_consent_link_text": "Privacy Policy", + "default_gratitude_header": "Thank You!", + "default_gratitude_text": "Our team will contact you to confirm your booking.", + "default_form_button_text": "Submit", + "default_client_button_text": "Open Form", + "schedule_field_title": "Calendar", + "schedule_field_placeholder": "Select a calendar...", + "schedule_performer_field_title": "Performer", + "schedule_performer_field_placeholder": "Select a performer...", + "schedule_date_field_title": "Booking Date", + "schedule_date_field_placeholder": "Select a date...", + "schedule_time_field_title": "Booking Time", + "schedule_time_field_placeholder": "Select a time...", + "common_error": "Please fill in all required fields.", + "step1": { + "title": "Step 1", + "description": "Basic Setup", + "error": "Please complete all required fields in Step 1." + }, + "step2": { + "title": "Step 2", + "description": "Form Fields", + "error": "Please complete all required fields in Step 2." + }, + "step3": { + "title": "Step 3", + "description": "Privacy Policy & Confirmation Page", + "consent_error": "Please complete all required fields in the Privacy Policy section." + }, + "step4": { + "title": "Step 4", + "description": "Form Design" + }, + "step5": { + "title": "Step 5", + "description": "Embed Script" + } + }, + "online_booking_site_form_builder_step1": { + "title": "Step 1. Form Setup", + "title_input_name": "Enter Form Name", + "choose_schedulers": "Select schedulers for booking entries", + "choose_linked_cards": "Select linked records to create after booking", + "linked_entities_select": "Choose modules for record creation after form submission", + "choose_board_annotation": "Select a board for record creation", + "no_schedules": "No schedulers created yet. To set up an online booking form, create an “Appointment Scheduling” module and configure online booking settings.", + "no_linked_cards": "No available modules for linked cards. To create a link, select a scheduler with an associated module. If multiple calendars are selected, ensure they all link to the same module.", + "limit_days_name": "Booking days limit", + "card_settings": "Configure card creation", + "responsible_user": "Responsible user", + "check_duplicates": "Prevent duplicates", + "check_duplicates_hint": "If enabled, submitting the form with an existing email or phone number will link to the existing card instead of creating a new one.", + "yes": "Yes", + "placeholders": { + "title_input": "Form Name", + "responsible_select": "Owner", + "board": "Board", + "limit_days": "From 1 to 730" + } + } + }, + "workspace_editor_page": { + "edit": "Edit", + "delete": "Delete", + "warn_title": "Warning!", + "warn_annotation": "Deleting the module will permanently delete all associated data. Are you sure you want to proceed?", + "open_module": "Open module", + "entity_type_field_used_in_formula_warning_modal": { + "title": "Failed to delete this module", + "annotation": "Some fields in this module are linked to a formula in another module. Please update the formula settings and try again.", + "continue": "Continue" + } + }, + "scheduler_builder_page": { + "steps": { + "save_error": "Specify module name and select at least one user on the first step.", + "step1": { + "label": "1st step", + "description": "Setup a new module" + }, + "step2": { + "label": "2nd step", + "description": "Link scheduler to other module in your workspace" + }, + "step3": { + "label": "3rd step", + "description": "Integrate scheduler to products module in your workspace" + }, + "default_title": "Meeting Scheduler" + }, + "scheduler_builder_step1": { + "title": "Personalize the module", + "name_the_module": "Name the Module", + "name_the_module_hint": "This name will be displayed in the left sidebar. You will be able to change it in the future.", + "choose_icon": "Choose an icon for the left sidebar", + "choose_icon_hint": "This icon will be displayed in the left sidebar. You will be able to change it in the future.", + "for_users": "For users", + "for_user_groups": "For user groups", + "view_type": "Select the type of Meeting Schedule", + "view_type_hint": "Select the type of view – Side Time View (Vertical Time View) or Top Time View (Horizontal Time View).", + "schedule": "Side Time View", + "schedule_img_alt": "{{company}} – Schedule interface preview", + "board": "Top Time View", + "board_img_alt": "{{company}} – Board interface preview", + "enter_the_interval": "Enter the interval", + "maximum_number_of_records": "Maximum number of records", + "error": "Please fill in all the required fields.", + "change_anyway": "Change anyway", + "warning_title": "Are you sure you want to change this parameter?", + "warning_annotation": "All existing appointments will be deleted.", + "for_whom": "Select users or user groups", + "for_whom_hint": "Select scheduler view type – for users or user group.", + "no_duration": "No", + "minutes": "minutes", + "schedule_params_title": "Schedule & Online Booking", + "schedule_params_hint": "Set up availability and meeting parameters for the calendar. They will be used to display the schedule and manage online bookings.", + "time_buffer_before": "Buffer time before meeting", + "time_buffer_after": "Buffer time after meeting", + "intervals_source": "Use availability from", + "performers_intervals": "users", + "scheduler_intervals": "this scheduler", + "placeholders": { + "interval": "Interval", + "unlimited": "Unlimited", + "module_name": "Module name" + }, + "change_type_warning": { + "title": "Warning!", + "annotation": "Changing the calendar type will permanently delete all visit records. Are you sure you want to proceed?", + "approve": "Confirm" + }, + "change_performers_warning": { + "title": "Warning!", + "annotation": "Removing a performer from the calendar will permanently delete all of their visit records. Are you sure you want to proceed?", + "approve": "Confirm" + } + }, + "scheduler_builder_step2": { + "title": "Link to other module", + "do_not_link": "Do not link to other module", + "duplicate_title": "Block Duplicate Meetings", + "duplicate_hint": "When enabled, this setting prevents scheduling multiple meetings for the same linked card on the same day." + }, + "scheduler_builder_step3": { + "title": "Integrate with products module", + "do_not_integrate": "Do not integrate with products module" + } + }, + "products_section_builder_page": { + "delay_select": { + "no_return": "Do not return", + "no_return_minified": "Do not", + "24_hours": "24 hours", + "24_hours_minified": "24h", + "48_hours": "48 hours", + "48_hours_minified": "48h", + "72_hours": "72 hours", + "72_hours_minified": "72h", + "7_days": "7 days", + "7_days_minified": "7d", + "10_days": "10 days", + "10_days_minified": "10d", + "14_days": "14 days", + "14_days_minified": "14d", + "28_days": "28 days", + "28_days_minified": "28d", + "30_days": "30 days", + "30_days_minified": "30d" + }, + "steps": { + "save_common_error": "Failed to save. Some steps are missing required information.", + "save_warehouses_error": "You need to create at least one warehouse to continue. Alternatively, you can disable warehouses.", + "step1": { + "label": "1st step", + "description": "Setup a new module" + }, + "step2": { + "label": "2nd step", + "description": "Create product categories (product groups)" + }, + "step3": { + "label": "3rd step", + "description": "Setup warehouses for you products" + }, + "step4": { + "rentals": { + "label": "4th step", + "description": "Configure schedule settings for your rental products" + }, + "sales": { + "label": "4th step", + "description": "Link to other modules in your workspace" + } + }, + "step5": { + "label": "5th step", + "description": "Link to other modules in your workspace" + }, + "default_titles": { + "sale": "Warehouse & Product Management", + "rental": "Rental Management" + } + }, + "product_section_builder_step1": { + "title": "Personalize the module", + "name_the_section": "Name the Module", + "name_the_section_hint": "This name will be displayed in the left sidebar. You will be able to change it in the future.", + "choose_icon": "Choose an icon for the left sidebar", + "choose_icon_hint": "This icon will be displayed in the left sidebar. You will be able to change it in the future.", + "error": "Module name must be specified.", + "placeholders": { + "section_name": "Module name" + } + }, + "product_section_builder_step3": { + "cancel_after_label": "Set the period for automatic release of item reservations in orders", + "cancel_after_hint": "You can configure the automatic cancellation of item reservations in orders. This helps prevent inaccurate inventory levels, which can occur when items are reserved in deals but these deals are not processed or have been canceled.", + "update_error": "Failed to save your choice. Try again.", + "warehouses_error": "You need to create at least one warehouse to continue. Alternatively, you can disable warehouses.", + "enable_warehouses_label": "Enable warehouses", + "enable_warehouses_hint": "Enable warehouses to track your products’ stock. If this option is enabled you will need to create at least one warehouse to continue.", + "enable_barcodes_label": "Enable barcodes", + "enable_barcodes_hint": "Enable barcodes to track your products’ stock. This option will allow you to scan product barcodes.", + "on": "ON" + }, + "products_section_builder_link_sections_step": { + "title": "Link to other modules", + "cards": "Cards", + "schedulers": "Schedulers", + "do_not_integrate": "Do not integrate with schedulers module" + }, + "products_section_builder_schedule_settings_step": { + "title": "Schedule settings", + "rental_duration_interval": "Rental duration interval", + "rental_duration_interval_hint": "Select the interval for the rental duration.", + "start_of_rental_interval": "Start of rental interval", + "start_of_rental_interval_hint": "Specify the time when the rental starts.", + "twenty_four_hours": "24 hours" + } + }, + "et_section_builder_page": { + "steps": { + "requisites_codes": { + "ie_name": "Individual Entrepreneur First Name", + "ie_surname": "Individual Entrepreneur Last Name", + "ie_patronymic": "Individual Entrepreneur Middle Name", + "org_name": "Company Name", + "org_tin": "Company TIN", + "org_trrc": "Company TRRC", + "org_psrn": "PSRN", + "org_type": "Company Type", + "org_full_name": "Full Company Name", + "org_short_name": "Short Company Name", + "org_management_name": "Director Name", + "org_management_post": "Director Position", + "org_management_start_date": "Director Start Date", + "org_branch_count": "Branch Count", + "org_branch_type": "Branch Type", + "org_address": "Company Address", + "org_reg_date": "Registration Date", + "org_liquidation_date": "Liquidation Date", + "org_status": "Company Status", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Average Number of Employees", + "org_extra_founders": "Company Founders", + "org_extra_managers": "Company Managers", + "org_extra_capital": "Authorized Capital", + "org_extra_licenses": "Licenses", + "org_extra_phones": "Phone Numbers", + "org_extra_emails": "Email Addresses", + "bank_name": "Bank Name", + "bank_bic": "Bank BIC", + "bank_swift": "SWIFT", + "bank_tin": "Bank TIN", + "bank_trrc": "Bank TRRC", + "bank_correspondent_acc": "Correspondent Account", + "bank_payment_city": "Payment Order City", + "bank_opf_type": "Credit Organization Type", + "bank_checking_account": "Checking Account" + }, + "save_error": "Failed to save. Some steps are missing required information.", + "details": "Details", + "analytics": "Analytics", + "requisites": "Requisites", + "step1": { + "label": "1st step", + "description": "Setup a new module" + }, + "step2": { + "label": "2nd step", + "description": "Choose name and create fields for your new card" + }, + "step3": { + "label": "3rd step", + "description": "Add functionality to the card" + }, + "step4": { + "label": "4th step", + "description": "Link to other modules in your workspace" + }, + "default_titles": { + "builder": "Builder", + "project": "Projects and Tasks", + "production": "Production", + "universal": "Universal Module", + "partner": "Partners", + "deal": "CRM", + "contact": "Contacts", + "company": "Companies", + "supplier": "Suppliers", + "contractor": "Contractors", + "hr": "Hiring" + } + }, + "tasks_fields_options": { + "planned_time": "Estimated Time", + "board_name": "Board Name", + "assignee": "Assignee", + "start_date": "Start Date", + "end_date": "End Date", + "description": "Description", + "subtasks": "Subtasks" + }, + "et_section_builder_step1": { + "title": "Personalize the module", + "name_the_section": "Name the Module", + "name_the_section_hint": "This name will be displayed in the left sidebar. You will be able to change it in the future.", + "choose_icon": "Choose an icon for the left sidebar", + "choose_icon_hint": "This icon will be displayed in the left sidebar. You will be able to change it in the future.", + "type_of_display": "Type of display", + "type_of_display_hint": "Select the type of interface – list or board/pipeline or both.", + "board": "Board", + "board_img_alt": "{{company}} – Board interface preview", + "list": "List", + "list_img_alt": "{{company}} – List interface preview", + "placeholders": { + "section_name": "Module name" + } + }, + "et_section_builder_step2": { + "title": "Configure the parameters of the new card", + "name_the_card": "Name the Card", + "name_the_card_hint": "Name of the Card.", + "fine_tuning": "Card fine tuning", + "create_fields": "Create fields for your new card", + "create_fields_hint": "Fields are the main informative elements of the card, you can created various groups and types of fields.", + "placeholders": { + "card_name": "Card name" + } + }, + "et_section_builder_step3": { + "title": "Add functionality to the card", + "error": "Please, select at least one feature.", + "features": { + "saveFiles": "Files", + "tasks": "Tasks", + "notes": "Notes", + "chat": "Chat", + "activities": "Activities", + "documents": "Create documents", + "products": "Products" + } + }, + "et_section_builder_step4": { + "title": "Link to other modules", + "cards": "Cards", + "products": "Products", + "schedulers": "Schedulers", + "do_not_integrate": "Do not integrate with schedulers module" + }, + "entity_type_used_in_formula_warning_modal": { + "title": "Module is used in formula", + "annotation": "Unable to unlink module, because it's field is used in formula." + } + }, + "builder_journey_picker_page": { + "request_bpmn_form_modal": { + "error": "There was an issue processing your request. Please ensure all information is correct and try again later.", + "send_request": "Submit Request", + "header": "BPMN – Leave a Request and We Will Contact You", + "full_name": "Full Name *", + "phone": "Phone *", + "email": "Email *", + "comment": "Comment", + "placeholders": { + "full_name": "John Smith", + "comment": "Additional information" + }, + "us_price_full": "Connect BPMN for $1 a month per user!", + "ru_price_full": "Connect BPMN for 99₽ a month per user!" + }, + "request_additional_storage_modal": { + "error": "An error occurred while submitting your request. Check your information and try again.", + "send_request": "Submit Request", + "header": "Request for Additional Storage", + "full_name": "Full Name *", + "phone": "Phone *", + "email": "Email *", + "comment": "Comment", + "placeholders": { + "full_name": "John Doe", + "comment": "Additional information" + }, + "ten_gb_ru_description": "Get 10 GB of extra storage for 100₽/month!", + "ten_gb_us_description": "Get 10 GB of extra storage for $1/month!", + "one_hundred_gb_ru_description": "Get 100 GB of extra storage for 500₽/month!", + "one_hundred_gb_us_description": "Get 100 GB of extra storage for $5/month!", + "one_tb_ru_description": "Get 1 TB of extra storage for 1500₽/month!", + "one_tb_us_description": "Get 1 TB of extra storage for $15/month!" + }, + "create_a_new_module": "Create a new module", + "module_names": { + "bpmn": { + "us_price_tag": "$1/Mo.", + "ru_price_tag": "99₽/Mo." + }, + "wazzup": "Wazzup", + "main_modules": "Select The Functional Module You Want to Create", + "additional_modules": "Additional Functional Modules", + "marketplace": "Marketplace", + "crm": "CRM", + "project_management": "Projects and Tasks", + "production": "Production", + "product_management_for_sales": "Inventory Management", + "product_management_rentals": "Lease Management", + "scheduler": "Appointment Scheduling", + "supplier_management": "Suppliers", + "contractor_management": "Contractors", + "partner_management": "Partners", + "hr_management": "Hiring", + "contact": "Contacts", + "company": "Companies", + "universal_module": "Universal Module", + "finances": "Financial Management", + "automatisation": "BPMN Automation and Orchestration", + "marketing": "Marketing Management", + "rentals": "Rentals", + "for_sales": "For sales", + "soon": "Soon", + "chosen": "Selected", + "telephony": "PBX telephony", + "mailing": "Email Service", + "messenger": "Multi Messenger", + "documents": "Document Generation", + "forms": "Web Forms", + "headless_forms": "Web Forms via API", + "online_booking": "Online Booking Forms", + "website_chat": "Website Chat", + "tilda": "Tilda Publishing", + "wordpress": "WordPress", + "twilio": "Whatsapp Business", + "fb_messenger": "Facebook Messenger", + "salesforce": "Salesforce", + "make": "Make", + "apix_drive": "ApiX-Drive", + "albato": "Albato", + "one_c": "1C", + "additional_storage": "Additional Storage", + "ten_gb": "10 GB of additional storage", + "one_hundred_gb": "100 GB of additional storage", + "one_tb": "1 TB of additional storage", + "storage": { + "ten_gb_ru_price_tag": "100₽/Mo.", + "ten_gb_us_price_tag": "$1/Mo.", + "one_hundred_gb_ru_price_tag": "500₽/Mo.", + "one_hundred_gb_us_price_tag": "$5/Mo.", + "one_tb_ru_price_tag": "1500₽/Mo.", + "one_tb_us_price_tag": "$15/Mo." + } + } + } + } + } +} diff --git a/frontend/public/locales/en/module.fields.json b/frontend/public/locales/en/module.fields.json new file mode 100644 index 0000000..ef9bc93 --- /dev/null +++ b/frontend/public/locales/en/module.fields.json @@ -0,0 +1,122 @@ +{ + "fields": { + "formula_warning": "Changing the formula will recalculate the value in this field. To preserve current data, consider creating a new field.", + "project_fields_block": { + "owner": "Owner", + "value": "Value", + "participants": "Participants", + "start_date": "Start Date", + "end_date": "End Date", + "description": "Description", + "placeholders": { + "participants": "Add participants" + } + }, + "field_value": { + "ogrn": "OGRN", + "tin": "TIN", + "trrc": "TRRC", + "already_have_an_active_call": "You already have an active call", + "recent": "Recent", + "no_available_phone_numbers": "No available phone numbers", + "value_field_formula_calculation": "This field is currently calculated by a formula, it cannot be edited manually. Value: {{value}}.", + "readonly": "This field is readonly", + "important_field": "Important Field", + "mandatory_field": "Mandatory Field", + "important_field_completed": "Important Field Completed", + "mandatory_field_completed": "Mandatory Field Completed", + "local_time": "Approximate local time of the phone number country capital", + "connect_telephony": "Connect to telephony to enable call functionality.", + "chat_unavailable": "It looks like you don't have access to this chat. Contact your administrator to connect." + }, + "components": { + "field_formula_settings_button": { + "no_available_fields_for_formula": "No available budget, number or formula fields", + "save_first": "This formula can be configured in the card", + "customize": "Customizing Formula: {{fieldName}}", + "formula_hint": "This field is intended for entering a formula. The formula may include the values provided below, as well as other numeric fields and formulas, including fields from other functional modules. Click on the desired value or select the field.", + "field": "Field", + "close": "Close" + }, + "field_settings_modal": { + "number": "Number", + "currency": "Currency", + "format_title": "Select display format for this field", + "ordinary_field": "Ordinary Field", + "save_first": "This field can be configured only after saving in the card", + "important_field": "Important Field", + "important_field_hint": "You can make a field important, and then it will have a yellow light bulb icon as a reminder that it needs to be filled in. Once filled, it will display a green check mark. If you need a field to be mandatory and prevent moving the deal to the next stage without filling it out, then select the \"mandatory field\" function.", + "select_pipeline_and_statuses": "Select Pipeline and Statuses", + "mandatory_field": "Mandatory Field", + "mandatory_field_hint": "You can set up a mandatory field for completion. In this case, users won't be able to move to the next status in the pipeline. Select the required pipeline and the status from which the field must be filled.", + "user_exclusion": "User Exclusion", + "user_exclusion_hint": "You can exclude certain users who will be allowed not to fill in this field. This might be necessary for administrators.", + "editing_restriction": "Editing Restriction", + "editing_restriction_hint": "You can prohibit selected users from editing this field. They will be able to see it, but will not be able to edit it.", + "select_users": "Select Users", + "hide_field": "Hide Field for Users", + "hide_field_hint": "You can hide this field from selected users; it will be inaccessible to them.", + "field_visibility_in_pipeline": "Hide Field in Pipeline", + "field_visibility_in_pipeline_hint": "You can customize the field visibility for each pipeline and stage. Below you can select in which stages and pipelines the field will be hidden.", + "customize": "Customizing Field: {{fieldName}}" + }, + "edit_fields": { + "add_bank_requisites": "Add bank details", + "add_org_requisites": "Add organization and entrepreneur details", + "add_org_stat_codes": "Add organization and entrepreneur statistical codes", + "add_additional_org_requisites": "Add additional organization and entrepreneur fields", + "add_field": "Add field", + "add_utm_fields": "Add UTM Fields", + "add_ga_fields": "Add Google Analytics Fields", + "add_ym_fields": "Add Yandex.Metrica Fields", + "add_fb_fields": "Add Facebook Analytics Fields", + "select_options": { + "max_length": "Maximum {{length}} characters", + "add_option": "Add option", + "cancel": "Cancel", + "add": "Add", + "no_options": "No options", + "placeholders": { + "search": "Search...", + "option": "Option" + } + } + }, + "field_form_group": { + "max_length": "Maximum {{length}} characters", + "text": "Text", + "number": "Number", + "multitext": "Multitext", + "select": "Select", + "multiselect": "Multiselect", + "switch": "Switch", + "formula": "Formula", + "phone": "Phone", + "email": "E-mail", + "value": "Value", + "date": "Date", + "link": "Link", + "file": "File", + "richtext": "Formatted text", + "participant": "Participant", + "participants": "Participants", + "colored_select": "Colored select", + "colored_multiselect": "Colored multiselect", + "checked_multiselect": "Checklist", + "checklist": "Multitext with checkboxes", + "field_name": "Field name", + "analytics_field": "Analytics field" + }, + "show_fields": { + "no_fields": "No fields" + }, + "show_files": { + "empty": "There are no files yet", + "attach": "Attach files" + }, + "file_field_value_comp": { + "attach_files": "Attach files" + } + } + } +} diff --git a/frontend/public/locales/en/module.mailing.json b/frontend/public/locales/en/module.mailing.json new file mode 100644 index 0000000..3b6faf3 --- /dev/null +++ b/frontend/public/locales/en/module.mailing.json @@ -0,0 +1,234 @@ +{ + "mailing": { + "unknown_file": "Unknown file", + "modals": { + "send_email_modal": { + "to": "To:", + "copy": "Copy:", + "hidden_copy": "Hidden copy:", + "from": "From:", + "subject": "Subject:", + "attachment": "attachment", + "attachments": "attachments", + "delete_all": "Delete all", + "send": "Send", + "error": "Failed to send message! Please try again later.", + "send_with_html": "Send email with HTML markup", + "send_with_html_hint": "Select this option if you want to send an email with HTML markup. This is useful if you want to opt out of the default {{company}}'s formatting features of the email.", + "components": { + "changes_not_saved_modal": { + "title": "Changes are not saved", + "annotation": "Continue editing?", + "approve": "Delete draft" + }, + "invalid_email_address_modal": { + "title": "Email \"{{email}}\" does not appear to be a valid address", + "annotation": "Verify the address and try again.", + "approve": "Send anyway" + }, + "send_email_settings_dropdown": { + "show_copy": "Show Copy address field", + "show_hidden_copy": "Show Hidden Copy address field" + }, + "editors": { + "email_signature_editor": { + "placeholder": "Add signature", + "no_signature": "No signature" + }, + "email_text_editor": { + "placeholders": { + "new_message": "New message", + "enter_html_markup": "Enter HTML markup" + } + } + } + } + } + }, + "pages": { + "mailing_settings_page": { + "soon": "Soon...", + "add_button": "Add Mailbox", + "mail_templates": "Mail Templates", + "templates_caption": "You can create email templates that can be used to be send to a client. After adding the template, you can get access to sending emails.", + "setup_signature": "Set up a signature", + "add_template": "Add template", + "components": { + "mailbox_item": { + "draft_text": "Draft", + "draft_hint": "Mailbox connection is not finished. Please, enter missing settings.", + "inactive_text": "Inactive", + "inactive_hint": "Mailbox is inactive. {{error}}.", + "sync_text": "Synchronization", + "sync_hint": "Mailbox is being synchronized. Please, wait.", + "active_text": "Active" + } + }, + "modals": { + "delete_mailbox_modal": { + "delete": "Delete", + "title": "Do you really want to delete this mailbox?", + "save_correspondence_annotation": "Save correspondence history for this email?", + "save_correspondence": "Save correspondence history" + }, + "mailbox_address_modal": { + "continue": "Continue", + "title": "Connect mailbox", + "placeholder": "Your email", + "caption1": "Connect a corporate mailbox with a shared access, which receives requests from customers, or a personal mailbox of one of your employees.", + "caption2": "Emails sent to this mailbox will be automatically attached to contacts. You can create a deal directly from the list of emails.", + "caption3": "If you're having trouble connecting, try enabling access for your email client.", + "cancel": "Cancel", + "google_caption1": "{{company}} use and transfer of information received from Google APIs to any other app will adhere to", + "google_policy_link": "Google API Services User Data Policy", + "google_caption2": ", including the Limited Use requirements." + }, + "mailbox_provider_modal": { + "title": "Choose provider", + "caption": "Select your email provider below. If you do not see your email service in the list, press Manual and manually configure your mailbox.", + "manual": "Manual" + }, + "update_mailbox_modal": { + "max_number_of_emails_per_day": "Maximum number of emails per day", + "emails_per_day": "emails per day", + "emails_per_day_hint": "The email limit parameter is necessary to avoid being banned by the email service provider. Each email service sets its own limits on sending messages. For example, the basic version of Google Workspace allows sending 500 messages per day from one mailbox.", + "email_readonly_title": "You cannot edit the address of an already created mailbox", + "title_connect": "Connect mailbox", + "title_edit": "Edit mailbox settings", + "encryption": "Encryption", + "owner": "Select the owner of the mail", + "for_whom_available": "Select for whom email is available", + "synchronize": "Synchronize emails from the last 7 days", + "create_entities_annotation": "Configure the options below to automatically create a card when receiving an email from a new contact.", + "create_entities_label": "Create card on incoming email", + "delete": "Delete mailbox", + "reconnect": "Reconnect mailbox", + "placeholders": { + "password": "Password", + "imap": "IMAP server", + "port": "Port", + "smtp": "SMTP server", + "owner": "Select owner" + } + }, + "mailbox_signature_modal": { + "title": "Signatures", + "mailbox_signature_editor": { + "available_in_mailboxes": "Available in mailboxes:", + "delete": "Delete", + "save": "Save", + "warning_title": "Delete signature", + "warning_annotation": "Are you sure you want to delete this signature?", + "save_as_html": "Save as HTML markup", + "save_as_html_hint": "Select this option if you want to save the signature as HTML markup. This is useful if you want to opt out of the default {{company}}'s formatting features of the signature.", + "placeholders": { + "signature_name": "Signature name", + "your_signature": "Your signature", + "select_mailboxes": "Select mailboxes" + } + } + } + } + }, + "mailing_page": { + "title": "E-mail and Messenger", + "no_email": "No email yet", + "components": { + "section": { + "inbox": "Inbox", + "sent": "Sent", + "spam": "Spam", + "trash": "Trash", + "draft": "Draft", + "flagged": "Flagged", + "archive": "Archive", + "all": "All Mail", + "mailbox": "Mailbox {{number}}" + }, + "message_panel": { + "no_messages": "No messages", + "demo_message_subject": "✉️ {{company}}: Corporate Email Solution", + "demo_message_snippet": "Dear Customer, Introducing a revolutionary corporate email solution that will undoubtedly capture your attention." + }, + "attachments_block": { + "attachment": "attachment", + "attachments": "attachments", + "download_all": "Download all" + }, + "reply_controls": { + "reply": "Reply", + "reply_all": "Reply all", + "forward": "Forward" + }, + "create_message_button": { + "title": "New message" + }, + "no_mailboxes_panel_block": { + "title": "Click the button below to add your mailbox." + }, + "thread": { + "unknown": "Unknown 👤", + "no_selected_message": "No selected message", + "from": "From", + "subject": "Subject", + "reply": "Reply", + "reply_all": "Reply all", + "forward": "Forward", + "add_task": "Add Task", + "add_contact": "Add contact", + "spam": "Spam", + "unspam": "Unspam", + "move_to_inbox": "Move to inbox", + "trash": "Trash", + "close": "Close", + "unseen": "Unseen", + "user": "User", + "date": "{{day}} at {{time}}", + "amwork_workspace": "{{company}} Workspace", + "demo_message_title": "✉️ {{company}}: Corporate Email Solution", + "demo_message_snippet": "Dear Customer, introducing a revolutionary corporate email solution that will undoubtedly capture your attention. Discover new possibilities and elevate your corporate communication to a new level with the integration of email into the {{company}} workspace!", + "dear_customer": "Dear Customer,", + "demo_message_intro": "Introducing a revolutionary corporate email solution that will undoubtedly capture your attention.Discover new possibilities and elevate your corporate communication to a new level with the integration of email into the {{company}} workspace! Connect any corporate or personal mailbox, and enjoy not only the familiar email functions but also a multitude of additional tools. Create tasks, leads, projects, and more directly from an email, maintaining full control over all interactions with clients, partners, and projects. Effortlessly send messages from lead, deal, partner, or project cards, and they will automatically be saved in the corresponding card. This way, you can easily read and analyze the correspondence history for a specific deal or project. Work more effectively as a team by granting access to a shared mailbox and managing communications together with your colleagues. Unlock new horizons with {{company}} and boost your business productivity!", + "email_functionality": "📬 Email functionality:", + "email_functionality_ul": "
  • API integration with Gmail
  • IMAP integration
  • Integration of emails from the past 7 days for newly added mailboxes
  • Incoming and outgoing emails
  • Grouping of emails into threads/chains by sender
  • Ability to create a task from an email
  • Ability to create a contact from an email
  • Automatic lead creation from emails
  • Attachment of incoming and outgoing emails to the history/feed of a card
  • Attachment of incoming and outgoing emails to the history/feed of a card
  • Ability to write an email from a card
  • Creation of custom folders
  • Use of multiple mailboxes
  • Collaborative mailbox management
  • Display of emails in HTML format
  • Collaborative mailbox management
  • Email search
  • Spam
  • Deleted items
  • Drafts
  • Creation of signatures
  • Carbon copy recipients
  • Blind carbon copy recipients
  • File attachments
", + "reach_out": "If you have any questions, feel free to reach out to us –", + "sincerely_amwork": "Sincerely, your {{company}} team." + }, + "modals": { + "create_contact_modal": { + "create": "Create", + "title": "Create cards from mail message", + "create_contact": "Create contact for mail", + "create_lead": "Create lead for mail", + "open_entity": "Open newly created entity", + "placeholders": { + "where_contact": "Select where to create new contact", + "where_lead": "Select where to create new lead", + "board_for_lead": "Select board for new lead" + } + }, + "link_contact_modal": { + "link": "Link", + "title": "Link Card", + "select_module": "Select Module", + "search_card": "Select Card", + "open_entity": "Open Linked Card", + "placeholders": { + "module": "Module...", + "search_card": "Search by name..." + } + } + } + }, + "hooks": { + "use_message_controls": { + "unknown": "Unknown 👤", + "reply_to": "Reply to", + "reply_all": "Reply all", + "forward": "Forward" + } + } + } + } + } +} diff --git a/frontend/public/locales/en/module.multichat.json b/frontend/public/locales/en/module.multichat.json new file mode 100644 index 0000000..713f271 --- /dev/null +++ b/frontend/public/locales/en/module.multichat.json @@ -0,0 +1,71 @@ +{ + "multichat": { + "amwork_messenger": "{{company}} Messenger", + "components": { + "multichat_control": { + "not_found": "Not found", + "chats": "Chats", + "messages": "Messages", + "users": "Users", + "owner": "Owner", + "supervisor": "Supervisor", + "no_chats_yet": "No chats yet", + "create_card": "Create a Card", + "link_card": "Link a Card", + "card": "Card", + "ui": { + "deleted_user": "Deleted User 👻", + "multichat_control_header": { + "title": "{{company}} Multi Messenger" + }, + "create_amwork_chat_button": { + "label": "New {{company}} chat", + "modals": { + "select_contact_title": "Select one user or a group of users", + "continue": "Continue", + "customize_chat": "Customize chat", + "placeholders": { + "group_name": "Group name" + } + } + }, + "chats_header_providers_tabs": { + "all_chats": "All Chats" + }, + "chat_message_context_menu": { + "reply": "Reply", + "copy": "Copy text", + "edit": "Edit", + "delete": "Delete" + }, + "chat_message_reply_block": { + "editing_message": "Editing message" + }, + "send_chat_message_block": { + "placeholders": { + "message": "Message" + } + }, + "no_selected_chat_plug": { + "title": "Select a chat to start messaging" + }, + "chats_panel_block": { + "you": "You" + }, + "send_chat_message_with_files_modal": { + "send_files": "Send files" + }, + "chat_message_item": { + "you": "You" + }, + "chat_controls_menu": { + "mark_as_read": "Mark as Read", + "delete_chat": "Delete Chat", + "delete_warning_title": "Are you sure you want to delete this chat?", + "delete_warning_annotation": "All messages will be removed. This action cannot be undone." + } + } + } + } + } +} diff --git a/frontend/public/locales/en/module.notes.json b/frontend/public/locales/en/module.notes.json new file mode 100644 index 0000000..7de2d2c --- /dev/null +++ b/frontend/public/locales/en/module.notes.json @@ -0,0 +1,49 @@ +{ + "notes": { + "notes_page": { + "module_name": "Notes", + "add_note": "Create Note", + "new_note_heading": "New Note" + }, + "toolbar": { + "undo": "Undo", + "redo": "Redo", + "bold": "Semibold", + "italic": "Italic", + "underline": "Underline", + "strike": "Strikethrough", + "bullet_list": "Bullet List", + "ordered_list": "Ordered List", + "text": "Text", + "heading": "Title", + "subheading": "Heading", + "heading_3": "Subheading", + "more": "More...", + "expand": "Expand", + "minimize": "Minimize" + }, + "editor": { + "recently_edited": "Just now", + "quick_note": "Quick Note", + "last_changed": "Last changes: ", + "duplicate": "Duplicate", + "put_into_folder": "Restore", + "delete": "Delete note" + }, + "selector": { + "new_note_heading": "New Note", + "saved_indicator": "Changes are saved", + "pinned": "Pinned", + "today": "Today", + "yesterday": "Yesterday", + "earlier": "Earlier" + }, + "block": { + "unnamed_note": "Unnamed Note" + }, + "folders": { + "all": "All", + "recently_deleted": "Recently Deleted" + } + } +} diff --git a/frontend/public/locales/en/module.notifications.json b/frontend/public/locales/en/module.notifications.json new file mode 100644 index 0000000..d8c8d02 --- /dev/null +++ b/frontend/public/locales/en/module.notifications.json @@ -0,0 +1,79 @@ +{ + "notifications": { + "tags": { + "task_new": "New task", + "task_overdue": "Task", + "task_before_start": "Task", + "task_overdue_employee": "Task", + "activity_new": "New activity", + "activity_overdue": "Activity", + "activity_before_start": "Activity", + "activity_overdue_employee": "Activity", + "task_comment_new": "New task comment", + "chat_message_new": "New chat message", + "mail_new": "New mail", + "entity_note_new": "New note", + "entity_new": "New {{entityTypeName}}", + "entity_responsible_change": "New responsible {{entityTypeName}}", + "entity_import_completed": "Import", + "yesterday": "Yesterday at {{time}}", + "date": "{{date}} at {{time}}" + }, + "components": { + "delay_select": { + "no_delay": "No Delay", + "5_minutes": "5 minutes", + "10_minutes": "10 minutes", + "15_minutes": "15 minutes", + "30_minutes": "30 minutes", + "1_hour": "1 hour" + }, + "notifications_panel": { + "mute_notifications": "Mute notifications sound", + "unmute_notifications": "Unmute notifications sound", + "no_notifications": "You have no notifications yet", + "ui": { + "block_header_annotation": { + "overdue": "Overdue", + "overdue_employee": "{{employee}} is overdue", + "after_time": "After {{time}}", + "from_employee": "from {{employee}}", + "system_notice": "System notice", + "message": "Message", + "yesterday": "Yesterday at {{time}}", + "date": "{{date}} at {{time}}" + }, + "notification_settings": { + "title": "Notifications" + }, + "notification_settings_modal": { + "title": "Notifications Setup", + "enable_popup": "Pop-up Notifications", + "new": "New", + "new_mail": "New Mail", + "new_chat_message": "New Chat Message", + "new_note": "New Note", + "entity_responsible_change": "Entity Responsible Change", + "entity_import_complete": "Entity Import Complete", + "new_task_comment": "New Task Comment", + "unknown": "Unknown", + "new_task": "New Task", + "overdue_task": "Overdue Task", + "before_task_start": "Before the start of a Task", + "overdue_task_employee": "Overdue Task for Employee(s)", + "new_activity": "New Activity", + "overdue_activity": "Overdue Activity", + "before_activity_start": "Before the start of an Activity", + "overdue_activity_employee": "Overdue Activity for Employee(s)", + "placeholders": { + "select": "Select" + } + }, + "read_all_button": { + "read_all": "Read all" + } + } + } + } + } +} diff --git a/frontend/public/locales/en/module.products.json b/frontend/public/locales/en/module.products.json new file mode 100644 index 0000000..450af15 --- /dev/null +++ b/frontend/public/locales/en/module.products.json @@ -0,0 +1,502 @@ +{ + "products": { + "components": { + "common": { + "readonly_stocks": "You can't edit stocks on this status", + "readonly_delete": "You can't delete items from order on this status", + "select_the_warehouse_to_write_off": "Select the warehouse to draw stock from", + "return_stocks_warning_modal": { + "title": "Return stocks", + "annotation": "Would you like to return the stocks to the warehouse?", + "approve_title": "Return", + "cancel_title": "Do not return" + }, + "delete_order_warning_modal": { + "title": "Attention", + "annotation": "Would you like to delete the order? This action cannot be undone." + }, + "delete_order_or_clear_items_warning_modal": { + "title": "Attention", + "annotation": "Would you like to delete the entire order or remove all items from it? This action cannot be undone.", + "approve_title": "Delete order", + "cancel_title": "Clear items" + }, + "rental_order_status_select": { + "formed": "Formed", + "accepted_to_warehouse": "Accepted to warehouse", + "sent_to_warehouse": "Sent to warehouse", + "delivered": "Delivered", + "reserved": "Reserved", + "shipped": "Shipped", + "cancelled": "Cancelled", + "returned": "Returned", + "placeholders": { + "select_status": "Select status" + } + }, + "order_status_select": { + "placeholders": { + "select_status": "Select status" + }, + "statuses": { + "reserved": "Reserved", + "sent_for_shipment": "Sent for shipment", + "shipped": "Shipped", + "cancelled": "Cancelled", + "returned": "Returned" + } + }, + "products_tab_selector": { + "shipments": "Shipments", + "timetable": "Calendar", + "products": "Products" + }, + "products_category_select": { + "no_categories": "No Categories", + "placeholders": { + "select_category": "Category" + } + }, + "products_warehouse_select": { + "no_warehouses": "No Warehouses", + "placeholders": { + "select_warehouse": "Warehouse" + } + }, + "products_settings_button": { + "settings": "Settings", + "module_settings": "Module settings", + "table_settings": "Table settings", + "report_settings": "Report settings" + }, + "calendar": { + "month": "Month", + "week": "Week", + "days": { + "short": { + "mo": "Mo", + "tu": "Tu", + "we": "We", + "th": "Th", + "fr": "Fr", + "sa": "Sa", + "su": "Su" + }, + "long": { + "mo": "Monday", + "tu": "Tuesday", + "we": "Wednesday", + "th": "Thursday", + "fr": "Friday", + "sa": "Saturday", + "su": "Sunday" + } + }, + "resources_label": "Products", + "statuses": { + "reserved": "Reserved", + "rented": "Rent" + }, + "fields": { + "phone": "Phone", + "mail": "Mail", + "shifts": "Number of days", + "status": "Status", + "period": "Rent period" + } + } + } + }, + "pages": { + "card_products_orders_page": { + "ui": { + "empty": "No orders in this section yet" + }, + "hooks": { + "use_products_section_orders_columns": { + "name": "Order name", + "warehouse": "Warehouse", + "status": "Status", + "created_at": "Created at", + "shipped_at": "Shipped at", + "creator": "Creator", + "order": "Order-{{number}}" + }, + "use_rental_products_section_orders_columns": { + "name": "Name", + "status": "Status", + "created_at": "Created at", + "creator": "Creator", + "order": "Order-{{number}}", + "shipment": "Shipment for order #{{number}}", + "linked_entity": "Linked entity" + } + } + }, + "card_products_order_page": { + "templates": { + "products_order_block_template": { + "new_order": "New Order", + "save": "Save", + "cancel": "Cancel", + "added_to": "Added to {{entity_name}}", + "empty": "You have not yet added products to the order" + }, + "products_warehouse_block_template": { + "product_management_for_sales": "Inventory Management", + "product_management_rentals": "Lease Management", + "products": "Products", + "warehouse": "Warehouse", + "add": "Add", + "reset": "Reset", + "empty": "Warehouse is empty", + "warehouse_select_hint": "The warehouse selected in this section must match the ordered warehouse in the top (order) section. If you wish to select a different warehouse here, you must first clear or change the chosen warehouse in the top section. Any warehouse selected here will be automatically inferred in the top section." + } + }, + "common": { + "products_order_price_head_cell": { + "price": "Price," + }, + "products_order_max_discount_hint": "The maximum discount is", + "products_order_tax_header_cell": { + "tax": "Tax,", + "tax_included": "Included", + "tax_excluded": "Excluded" + }, + "products_order_total_block": { + "total": "Total: {{total}}" + }, + "remove_selected_block": { + "remove_selected": "Remove selected" + } + }, + "hooks": { + "use_price_cell_columns": { + "name_fallback": "No price name", + "name": "Name", + "price": "Price", + "currency": "Currency" + } + }, + "ui": { + "card_rental_products_order_component": { + "hooks": { + "use_rental_card_order_columns": { + "name": "Name", + "discount": "Discount", + "availability": "Availability", + "amount": "Amount" + }, + "use_rental_warehouse_columns": { + "name": "Name", + "price": "Price", + "category": "Category", + "availability": "Availability" + } + }, + "ui": { + "rental_availability_cell": { + "available": "Available", + "reserved": "Reserved", + "rent": "Rent" + }, + "rental_order_periods_control": { + "new_period": "New period", + "periods_selected": "Periods selected: {{count}}", + "add_new_period": "Add new period", + "hint_text": "You're able to choose or modify periods exclusively during the creation of new orders or for orders with the 'Formed' status.", + "placeholders": { + "select_periods": "Select periods" + } + }, + "card_rental_products_order_block_header": { + "placeholders": { + "select_order_warehouse": "Select order warehouse" + } + } + } + }, + "card_products_order_component": { + "hooks": { + "use_available_columns": { + "unknown": "Unknown...", + "name": "Name", + "stock": "Stock", + "reserved": "Reserved", + "available": "Available" + }, + "use_card_order_columns": { + "name": "Name", + "discount": "Discount", + "tax": "Tax,", + "quantity": "Quantity", + "amount": "Amount" + }, + "use_reservations_columns": { + "warehouse_name": "Warehouse name", + "stock": "Stock", + "reserved": "Reserved", + "available": "Available", + "quantity": "Quantity" + }, + "use_warehouse_columns": { + "name": "Name", + "price": "Price", + "category": "Category", + "available": "Available", + "quantity": "Quantity" + } + }, + "ui": { + "available_head_cell": { + "available": "Available", + "available_hint": "Hover over a cell in this column for detailed information on warehouse stock." + }, + "card_products_order_block_header": { + "cancel_after_hint": "You can configure the automatic cancellation of item reservations in orders. This helps prevent inaccurate inventory levels, which can occur when items are reserved in deals but these deals are not processed or have been cancelled.", + "placeholders": { + "select_order_warehouse": "Select order warehouse" + } + }, + "add_stock_placeholder": { + "add_stock": "Add stock to warehouse" + } + } + } + } + }, + "product_categories_page": { + "title": "Create a Category (Product Group)", + "placeholders": { + "category_name": "Category name", + "subcategory_name": "Subcategory name" + }, + "buttons": { + "add_category": "Add Category", + "add_subcategory": "Add Subcategory" + }, + "ui": { + "delete_category_warning_modal": { + "cannot_be_undone": "This action cannot be undone.", + "title": "Are you sure you want to delete {{name}}?", + "annotation": "All subcategories will be deleted." + } + } + }, + "product_page": { + "product_description": "Product Description", + "sku_already_exists_warning": "Product with SKU \"{{sku}}\" already exists", + "sku": "SKU", + "unit": "Unit", + "tax": "Tax", + "category": "Category", + "warehouse": "Warehouse", + "ui": { + "add_warehouse_placeholder": { + "add_warehouse": "Add warehouse" + }, + "product_actions_dropdown": { + "delete_product": "Delete product" + }, + "product_description": { + "placeholders": { + "add_description": "Add description" + } + }, + "product_feed": { + "calendar": "Calendar", + "prices": "Prices", + "stocks": "Stocks", + "images": "Images", + "add_price": "Add price", + "add_image": "Add image", + "delete_image": "Delete image", + "delete_images": "Delete images", + "placeholders": { + "name": "Price name", + "unit_price": "Unit price" + }, + "labels": { + "maximum_discount": "Maximum Discount on Product", + "product_cost": "Product Cost" + } + }, + "product_name_block": { + "placeholders": { + "product_name": "Product name" + } + } + } + }, + "warehouses_page": { + "page_title": "Set Up Your Warehouses", + "placeholders": { + "name": "Warehouse name", + "select_warehouse": "Select Warehouse" + }, + "add_warehouse": "Add warehouse", + "ui": { + "delete_warehouse_modal": { + "title": "Are you sure you want to delete {{name}}?", + "annotation": "All stocks within the warehouse will be cleared.", + "annotation_move_stocks": "All stocks within the warehouse will be cleared. Alternatively, you have the option to transfer all stocks to another warehouse if available.", + "placeholders": { + "move_stocks_to": "Move stocks to..." + } + } + } + }, + "products_page": { + "no_warehouses_or_categories": "No warehouses or categories to display filters", + "title": { + "sale": "Sale", + "rental": "Rental", + "shipments": "Shipments", + "calendar": "Calendar" + }, + "tabs": { + "products": "Products", + "timetable": "Calendar", + "shipments": "Shipments", + "reports": "Reports" + }, + "all_columns_hidden": "All columns are hidden in table settings", + "table_settings": "Table Settings", + "display_columns": "Display Columns", + "add_product": "Add product", + "empty": "You have not added any products yet", + "show_empty_resources": "Show empty resources", + "hide_empty_resources": "Hide empty resources", + "hooks": { + "use_products_columns": { + "name": "Name", + "prices": "Prices", + "sku": "SKU", + "tax": "Tax", + "stocks": "Stocks", + "unit": "Unit", + "availability": "Availability" + }, + "use_product_stocks_columns": { + "warehouse": "Warehouse", + "reserved": "Reserved", + "available": "Available", + "stock": "Stock" + }, + "use_create_stocks_columns": { + "warehouse": "Warehouse", + "stock": "Stock" + } + }, + "ui": { + "add_product_modal": { + "changes_not_saved_warning_title": "Are you sure you want to close this window?", + "changes_not_saved_warning_annotation": "All changes will be lost.", + "changes_not_saved_warning_approve_title": "Close anyway", + "barcodes_hint": "You can scan barcodes with a scanner or manually enter them in this field. Each barcode in a section must be unique.", + "sku_already_exists_warning": "Product with SKU \"{{sku}}\" already exists", + "types": { + "product": "Product", + "service": "Service", + "kit": "Kit" + }, + "add_product": "Add Product", + "name": "Name", + "type": "Type", + "add_photo": "Add photo", + "description": "Description", + "sku": "SKU", + "unit": "Unit", + "tax": "Tax", + "category": "Category", + "prices": "Prices", + "stocks": "Stocks" + }, + "photo_block_item": { + "alt": "Product {{name}}" + }, + "product_price_list": { + "add_price": "Add price" + }, + "product_search_block": { + "placeholders": { + "search_products": "Search Products" + } + }, + "product_stocks_modal": { + "title": "Set product stocks in each warehouse" + }, + "create_stocks_modal": { + "title": "Specify product stocks" + } + } + }, + "shipment_page": { + "hooks": { + "use_shipment_columns": { + "name": "Name", + "sku": "SKU", + "available": "Available", + "quantity": "Quantity" + }, + "use_rental_shipment_columns": { + "name": "Name", + "sku": "SKU", + "tax": "Tax", + "discount": "Discount", + "total": "Total", + "quantity": "Quantity" + } + }, + "ui": { + "shipment_page_secondary_header": { + "order": "Order-{{number}}" + }, + "rental_shipment_page_secondary_header": { + "order": "Order-{{number}}" + }, + "some_products_not_checked_warning_modal": { + "title": "Some products are not checked", + "annotation": "You're trying to change status of the order some products of which are not checked. Do you want to change status anyway?", + "approve_title": "Change anyway" + }, + "product_barcodes_control": { + "barcode": "Barcode", + "modals": { + "product_is_not_in_order_warning_modal": { + "title": "Product is not in this order", + "approve_title": "Add product to order", + "annotation": "The product associated with the barcode \"{{barcode}}\" is not included in this order. You have the option to add it manually or continue without it." + }, + "product_does_not_exist_warning_modal": { + "title": "Product does not exist", + "approve_title": "Create product", + "annotation": "Product with barcode \"{{barcode}}\" does not exist in this product section. You can manually create it on products page." + } + } + } + } + }, + "shipments_page": { + "hooks": { + "use_shipments_columns": { + "unknown": "Unknown...", + "name": "Name", + "shipment": "Shipment for order #{{number}}", + "warehouse": "Warehouse", + "shipped_at": "Shipped at", + "created_at": "Created at", + "status": "Status", + "linked_entity": "Linked entity" + } + }, + "ui": { + "shipments_table": { + "all_columns_hidden": "All columns are hidden in table settings", + "empty": "No Scheduled Shipments" + } + } + } + } + } +} diff --git a/frontend/public/locales/en/module.reporting.json b/frontend/public/locales/en/module.reporting.json new file mode 100644 index 0000000..49427ab --- /dev/null +++ b/frontend/public/locales/en/module.reporting.json @@ -0,0 +1,432 @@ +{ + "reporting": { + "templates": { + "components": { + "report_table": { + "empty": "No available data for this period" + }, + "report_settings_drawer": { + "table_settings": "Table Settings", + "display_columns": "Display Columns" + } + }, + "comparative_report_template": { + "placeholders": { + "users": "Users", + "pipeline": "Sales pipelines", + "month": "Select month", + "year": "Select year" + } + }, + "general_report_template": { + "apply_filter": "Apply Filter", + "reset": "Reset", + "stages": { + "all": "All Stages", + "open": "Open Stages", + "lost": "Lost Stages", + "won": "Won Stages" + }, + "placeholders": { + "users": "Users", + "pipeline": "Sales pipelines", + "stage": "Stages", + "warehouse": "Warehouses", + "category": "Categories", + "field_or_responsible": "Field or Responsible" + } + }, + "calls_report_template": { + "directions": { + "all": "All Calls", + "incoming": "Incoming Calls", + "outgoing": "Outgoing Calls" + }, + "placeholders": { + "users": "Users", + "pipeline": "Sales pipelines", + "directions": "Call type", + "duration": "Call duration", + "only_missed": "Only missed calls" + } + } + }, + "hooks": { + "use_get_products_general_report_columns": { + "name": "Name", + "sold": "Total Sold", + "shipped": "Shipped", + "open": "Open Deals", + "lost": "Lost Deals", + "all": "Total Deals", + "average_products": "Average Number of Products per Deal", + "average_budget": "Average Value of Successful Deals", + "average_term": "Average Deal Duration" + }, + "use_calls_history_report_columns": { + "timestamps": "Call Date", + "type": "Caller and Recipient", + "result": "Call Duration and Playback", + "unknown": "Unknown Number", + "caller": "Caller", + "callee": "Callee" + }, + "use_get_general_report_columns": { + "result": "Result", + "all": "All", + "open": "Open", + "expired": "Expired", + "completed": "Completed", + "won": "Won", + "lost": "Lost", + "groups": "Groups", + "users": "Users", + "cards": "Cards (Leads)", + "tasks": "Tasks", + "activities": "Activities", + "average_check": "Average Check", + "average_term": "Average Term (Days)", + "switch_on": "Switch (On)", + "switch_off": "Switch (Off)", + "calls": "Calls", + "total": "Total Calls", + "average": "Average Calls", + "incoming": "Incoming Calls", + "incoming_average": "Average Incoming Calls", + "outgoing": "Outgoing Calls", + "outgoing_average": "Average Outgoing Calls", + "missed": "Missed Calls", + "min": "min", + "undefined_client": "Appointment scheduling without a deal" + }, + "use_get_projects_report_columns": { + "hours": "h", + "min": "min", + "users": "Users", + "opened": "Open Tasks", + "done": "Completed Tasks", + "overdue": "Overdue Tasks", + "planned": "Planned Time", + "completion_percent": "Project Completion Percentage", + "project_name": "Project Name", + "stage": "Project Status" + }, + "use_get_customer_report_columns": { + "name": "Name", + "sold": "Total Sold", + "products_quantity": "Quantity of Products", + "opened": "In Progress (Open Deals)", + "lost": "Lost Deals", + "all": "Total Deals (Open and Closed)", + "average_quantity": "Average Number of Purchases (Deals)", + "average_budget": "Average Budget of Successful Deals", + "average_duration": "Average Duration of Successful Deals (Days)" + }, + "use_get_schedule_report_columns": { + "groups": "Groups", + "users": "Users", + "sold": "Total Sold", + "total": "Total Appointments", + "scheduled": "Scheduled", + "confirmed": "Confirmed", + "completed": "Completed", + "cancelled": "Cancelled" + }, + "use_get_comparative_report_columns": { + "all": "All", + "open": "Open", + "won": "Won", + "lost": "Lost", + "week": "Week {{number}}", + "quarter": "Quarter {{number}}", + "users": "Users", + "days": { + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday" + }, + "months": { + "january": "January", + "february": "February", + "march": "March", + "april": "April", + "may": "May", + "june": "June", + "july": "July", + "august": "August", + "september": "September", + "october": "October", + "november": "November", + "december": "December" + } + } + }, + "pages": { + "reports_page": { + "components": { + "row_title_cell": { + "total": "Total", + "without_group": "Without Group", + "empty_user": "Unassigned" + }, + "row_event_cell": { + "to": "to" + }, + "reports_navigation_sidebar": { + "total": "Total", + "export_xlsx": "Export to XLSX", + "export_table": "Exported from {{company}} on {{date}}", + "unfold_filters_menu": "Unfold filters menu", + "fold_filters_menu": "Fold filters menu", + "hide_sidebar": "Hide sidebar", + "show_sidebar": "Show sidebar", + "schedules": "Visits", + "title": { + "universal": "Reports", + "project": "Projects Reports", + "deal": "Deals Reports" + }, + "general_report": "General Report", + "comparison_of_periods": "Comparison of Periods", + "telephony": "Calls", + "schedule": "Visits", + "projects": "Projects", + "users": "By Users", + "rating": "By Rating", + "groups": "By Groups", + "days": "By Days", + "weeks": "By Weeks", + "months": "By Months", + "quarters": "By Quarters", + "years": "By Years", + "callsUsers": "By Users", + "callsGroups": "By Departments", + "callHistory": "Call Log Report", + "scheduleClient": "By Customers", + "scheduleDepartment": "By Groups", + "scheduleOwner": "By Schedulers", + "schedulePerformer": "By Specialists", + "customer_reports": "Contacts and Companies", + "customerContact": "By Contacts", + "customerCompany": "By Companies", + "customerContactCompany": "By Contacts and Companies", + "products": "Products", + "productsCategories": "By Categories", + "productsUsers": "By Users" + } + } + }, + "dashboard_page": { + "filter": { + "placeholders": { + "select_users": "Users", + "select_sales_pipeline": "Sales pipelines", + "all": "All", + "all_active": "All Active", + "open": "Open", + "open_active": "Open and Active", + "closed": "Closed", + "created": "Created" + }, + "dashboard_type_tooltip": { + "all": { + "text": "Includes all deals that existed during the period, regardless of activity. Covers:", + "list_1": "✓ Deals created during the period.", + "list_2": "✓ Deals closed (won or lost) during the period.", + "list_3": "✓ Deals that existed before and remained open during the period.", + "list_4": "✗ Does not exclude inactive deals (no stage changes)." + }, + "all_active": { + "text": "Includes only deals where at least one stage change occurred during the period. Covers:", + "list_1": "✓ Previously created deals with stage changes in the period.", + "list_2": "✓ Deals created during the period, if they moved stages.", + "list_3": "✓ Deals closed during the period, if they had at least one stage change before closing.", + "list_4": "✗ Excludes inactive deals (no stage movement)." + }, + "open": { + "text": "Includes deals that were not closed (not marked as Won or Lost) by the end of the period. Covers:", + "list_1": "✓ Previously created deals that remain open.", + "list_2": "✓ Deals created during the period and still open at the end.", + "list_3": "✗ Does not include closed deals." + }, + "open_active": { + "text": "Includes only open deals that had activity (stage changes) during the period. Covers:", + "list_1": "✓ Open deals with stage changes in the period.", + "list_2": "✓ New deals if they moved stages.", + "list_3": "✗ Excludes open deals without stage changes.", + "list_4": "✗ Excludes closed deals." + }, + "closed": { + "text": "Includes only deals that were closed (marked as Won or Lost) during the period. Covers:", + "list_1": "✓ Previously created deals that were closed in the period.", + "list_2": "✓ Deals created and closed within the period.", + "list_3": "✗ Excludes all open deals." + }, + "created": { + "text": "Includes all deals created during the period, regardless of their current status. Covers:", + "list_1": "✓ Deals created during the period and still open.", + "list_2": "✓ Deals created and closed within the period.", + "list_3": "✗ Does not include deals created before the period." + } + } + }, + "days": { + "one_day": "day", + "several_days": ["days", "days", "days"] + }, + "auto_update_select": { + "auto_update": "Auto-update", + "modes": { + "never": "Never", + "minute": "1 minute", + "ten_minutes": "10 minutes", + "thirty_minutes": "30 minutes", + "hour": "1 hour" + } + }, + "sales_goal_chart": { + "title_for_sales": "Sales Goal", + "title": "Goal", + "hint": "Sales Goal", + "settings_tip": "Settings", + "plug_text_for_sales": "Set Goals", + "plug_text": "Set up a goal" + }, + "traffic_light_report": { + "title": "Traffic Light Report", + "subtitle": "Today's Plan Completion", + "hint": { + "line1": "The “traffic light” report is a tool that shows how the sales department is tracking against the sales plan for the day.", + "line2": "The formula for calculating the fulfillment of the sales plan for today is:", + "line3": "Percentage of fulfillment = (Actual sales divided by Plan for the Current Day) × 100%, where:", + "list": { + "point1": "Actual sales are the amount you have already sold at the current moment.", + "point2": "The plan for the current day is part of the overall sales plan for the month, calculated for the current day." + }, + "line4": "Example of calculation:", + "line5": "Suppose your sales plan for the month is $10,000. Today is the 10th, which means the month has passed approximately 1/3. Your plan for today is:", + "line6": "Plan for the Current Day = ($10,000 divided by 30 days) × 10 days = $3,333.33", + "line7": "You have sold goods for $3,000.", + "line8": "Your percentage of plan fulfilled is:", + "line9": "Percentage of Fulfillment = ($3,000 divided by $3,333.33) × 100% ≈ 90%", + "line10": "Thus, your plan is 90% complete, and in the 'traffic light' report you will see the green color, as the fulfillment of the plan is going successfully." + }, + "plug_text": "Set Goals" + }, + "top_sellers": { + "title_for_sales": "Top 5 Contributing", + "title": "Top 5 Leaders", + "subtitle_for_sales": "Sales Leaders", + "hint": "Top 5 Contributing Sales Leaders", + "others": "Others", + "plug_text": "When you start using {{company}}, the statistics for the Top 5 sales leaders will appear here", + "plug_text_for_orders": "Once you start using {{company}}, statistics for the Top 5 user leaders will appear here", + "plug_text_for_candidates": "Once you start using {{company}}, statistics for the Top 5 user leaders will appear here" + }, + "analytics": { + "total_leads": "Total Leads", + "total_orders": "Total Orders", + "total_candidates": "Total Candidates", + "new_leads": "New Leads", + "new_orders": "New Orders", + "new_candidates": "New Candidates", + "won_leads": "Won", + "completed_orders": "Completed", + "hired_candidates": "Hired Candidates", + "lost_leads": "Lost", + "failed_orders": "Failed", + "rejected_candidates": "Rejected Candidates", + "total_tasks": "Total Tasks", + "completed_tasks": "Completed Tasks", + "expired_tasks": "Expired Tasks", + "no_tasks": "Leads: No Tasks", + "cards_no_tasks": "Cards: No Tasks", + "total_activities": "Total Activities", + "completed_activities": "Completed Activities", + "expired_activities": "Expired Activities", + "no_activities": "Leads: No Activities", + "cards_no_activities": "Cards: No Activities" + }, + "rating": { + "title": "Rating", + "hint": "Rating" + }, + "leads_status_chart": { + "title": "Leads Status Overview:", + "title_for_orders": "Orders Status indicators:", + "title_for_candidates": "Candidates Status Indicators:", + "subtitle": "Open, Lost, Won", + "subtitle_for_orders": "Open, Failed, Completed", + "subtitle_for_candidates": "Open, Rejected Candidates, Hired candidates", + "hint": "Leads Status Overview", + "won": "Won", + "completed": "Completed", + "hired_candidates": "Hired candidates", + "lost": "Lost", + "failed": "Failed", + "rejected_candidates": "Rejected Candidates", + "opened": "Open" + }, + "sales_pipeline_indicators": { + "title": "Conversion Pipeline", + "total_sales": "Total Wins", + "conversion": "Win Conversion", + "average_amount": "Average Amount", + "average_term": "Average Duration", + "days": ["day", "days", "days"] + }, + "switch": { + "deals_count": "Deals Count", + "orders_count": "Orders Count", + "sales_value": "Sales Value", + "orders_value": "Orders Value" + } + }, + "goal_settings_page": { + "title": "Goal settings", + "total": "Total", + "back_button": "Dashboard", + "users_select": "Users", + "period_type": { + "month": "Month", + "quarter": "Quarter" + }, + "change_period_modal": { + "title": "Warning!", + "annotation": "Changing the period will result in the loss of goals that you have set up earlier. Are you sure you want to continue?", + "approve": "Yes", + "cancel": "No" + }, + "periods": { + "months": { + "january": "January", + "february": "February", + "march": "March", + "april": "April", + "may": "May", + "june": "June", + "july": "July", + "august": "August", + "september": "September", + "october": "October", + "november": "November", + "december": "December" + }, + "quarters": { + "quarter1": "Quarter 1", + "quarter2": "Quarter 2", + "quarter3": "Quarter 3", + "quarter4": "Quarter 4" + } + }, + "form_header_amount": "Amount", + "form_header_quantity": "Transactions", + "button_save": "Save" + } + } + } +} diff --git a/frontend/public/locales/en/module.scheduler.json b/frontend/public/locales/en/module.scheduler.json new file mode 100644 index 0000000..d09915e --- /dev/null +++ b/frontend/public/locales/en/module.scheduler.json @@ -0,0 +1,219 @@ +{ + "scheduler": { + "pages": { + "scheduler_board_view_page": { + "create_appointment": "Create Meeting", + "stats_footer": { + "assigned": { + "label": "Assigned", + "hint": "Unconfirmed visits" + }, + "confirmed": { + "label": "Confirmed", + "hint": "Confirmed visits" + }, + "completed": { + "label": "Completed", + "hint": "Completed visits" + }, + "not_took_place": { + "label": "No-Show", + "hint": "Past visits not marked as completed" + }, + "not_scheduled": { + "label": "No Follow-Up", + "hint": "Visits with no follow-up scheduled" + }, + "newbies": { + "label": "First-Time Visit", + "hint": "First-time visitors" + }, + "total": { + "label": "Total", + "hint": "Total number of visits" + } + } + }, + "appointment_card_list_page": { + "visit_date": "Visit date" + }, + "scheduler_schedule_view_page": { + "sync": "Synchronize", + "report_settings": "Report settings", + "module_settings": "Module settings", + "stats_settings": "Statistics settings", + "settings": "Settings", + "report": "Report", + "overview": "Calendar", + "responsible": "Responsible", + "stage": "Stage", + "not_provided": "Not provided", + "statuses": { + "scheduled": "Scheduled", + "confirmed": "Confirmed", + "completed": "Completed", + "cancelled": "Cancelled" + }, + "created": "Created", + "visit": "Meeting {{number}}", + "description": "Description", + "email": "Email", + "phone": "Phone", + "price": "Price", + "quantity": "Quantity", + "discount": "Discount", + "new_event": "New Meeting", + "create_appointment": "Create Meeting", + "tooltips": { + "reports_denied": "You don't have permission to view reports. Contact account administrator for access." + }, + "placeholders": { + "search_visits": "Search Meetings" + }, + "hooks": { + "use_appointments_history_services_columns": { + "name": "Name", + "price": "Price", + "quantity": "Quantity", + "discount": "Discount", + "amount": "Amount" + }, + "use_appointments_history_columns": { + "date": "Date", + "time": "Time", + "performer": "Performer", + "services": "Services", + "total": "Total", + "status": "Status" + }, + "use_appointment_service_block_columns": { + "discount": "Discount", + "quantity": "Quantity", + "amount": "Amount" + } + }, + "ui": { + "add_appointment_modal": { + "error": "Unable to create the meeting. This could be due to either an attempt to create a meeting that overlaps with an existing one or end date is earlier than start date.", + "visits_history_empty": "Meetings history is empty", + "no_planned_visits": "No planned meetings", + "save_changes": "Save changes", + "planned_visit_title": "{{date}} from {{startTime}} to {{endTime}}", + "general_information": "General Information", + "planned_visits": "Planned Meetings", + "visits_history": "Meetings History", + "title": "Title", + "visit": "Meeting #{{number}}", + "edit_visit": "Edit meeting – {{name}}", + "new_visit": "Schedule a Meeting", + "visit_parameters": "Meeting Parameters", + "status": "Status", + "scheduler": "Meeting Scheduler", + "select_users_group": "Select Users Group", + "select_user": "Select User", + "description": "Description", + "from": "From:", + "to": "To:", + "date_and_time": "Date & Time", + "addition_of_services": "Addition of Services", + "add_service": "Add Service", + "no_services": "No services", + "warning_title": "Changes are not saved", + "warning_annotation": "Are you sure you want to close this window? All changes wll be lost.", + "close": "Close", + "delete": "Delete", + "count": "Total Visits", + "last_visit": "Last Visit", + "completed_count": [ + "{{count}} appointment held", + "{{count}} appointments held", + "{{count}} appointments held" + ], + "placeholders": { + "select_time_period": "Time period", + "title": "Meeting title", + "entity_name": "Entity name", + "search_services": "Search services to add", + "user": "User", + "users_group": "Users Group", + "appointment_notes": "Meeting Note" + }, + "repeating_appointments_block": { + "header": "Recurring Visits", + "hint": "Set up the interval and the number of repeat appointments, if necessary. Meetings will be scheduled on the business days specified in the settings.", + "interval": "Interval", + "count": "Count", + "intervals": { + "none": "No Repeat", + "day": "Daily", + "week": "Weekly", + "month": "Monthly" + }, + "dates_display": { + "one_visit": "The recurring visit is scheduled for {{date}} at {{time}}", + "visits_list": "Recurring visits are scheduled for {{dates}} at {{time}}", + "and": " and ", + "visits_interval_day": "Recurring visits are scheduled daily from {{from}} to {{to}} at {{time}}", + "visits_interval_week": "Recurring visits are scheduled weekly from {{from}} to {{to}} at {{time}}", + "visits_interval_month": "Recurring visits are scheduled monthly from {{from}} to {{to}} at {{time}}" + }, + "list": { + "title": "Recurring Meetings", + "hint": "Select dates for recurring meetings if needed.", + "dates_select": "Meeting Dates", + "add_new_appointment": "Add Meeting", + "new_appointment": "New Meeting", + "placeholders": { + "dates_select": "Select dates" + } + } + }, + "batch_cancel": { + "cancel_all": "Cancel All", + "warning_title": "Are you sure you want to cancel all scheduled meetings?", + "warning_annotation": [ + "{{count}} meeting will be canceled.", + "{{count}} meetings will be canceled.", + "{{count}} meetings will be canceled." + ], + "back": "Back", + "cancel": "Cancel" + }, + "duplicate_warning_modal": { + "move": "Move", + "same_time_title": "A meeting is already scheduled for this time", + "same_day_title": "A meeting is already scheduled for this day", + "same_time_annotation": "This calendar doesn’t allow duplicate meetings on the same day. Edit the existing meeting or change the date of this one.", + "same_day_annotation": "This calendar doesn’t allow duplicate meetings on the same day. Would you like to move the meeting scheduled for {{time}}?" + }, + "intersect_warning_modal": { + "title": "Meeting Overlap", + "annotation": "The meeting overlaps with an already scheduled one in this calendar. Choose a different time or change the responsible." + } + }, + "stats_settings_drawer": { + "title": "Statistics Settings", + "description": "Displayed statistic values", + "stats": { + "assigned": "Assigned", + "confirmed": "Confirmed", + "completed": "Completed", + "not_took_place": "No-Show", + "not_scheduled": "No Follow-Up", + "newbies": "First-Time Visit", + "total": "Total" + } + }, + "local_time_warning": { + "local_correction": [ + "Local time adjustment: {{hours}} hour", + "Local time adjustment: {{hours}} hours", + "Local time adjustment: {{hours}} hours" + ], + "hint": "You are not in the time zone specified in the system settings, so the working time in the calendar and the meeting time are shifted by the specified number of hours. If you think this is a mistake, change the time zone in the settings or contact support." + } + } + } + } + } +} diff --git a/frontend/public/locales/en/module.telephony.json b/frontend/public/locales/en/module.telephony.json new file mode 100644 index 0000000..0606a6b --- /dev/null +++ b/frontend/public/locales/en/module.telephony.json @@ -0,0 +1,242 @@ +{ + "telephony": { + "pages": { + "calls_configuring_scenarios_page": { + "title": "Configuring the operation of the IVR in the CRM module", + "failed_to_reach": "Unreachable", + "creates_manually": "The user manually creates a contact and deal card upon receiving an incoming call", + "creates_manually_hint": "When a call comes in from an unknown number, a window for the incoming call appears for the user. The user can answer the call, and if they deem this call as a potential business opportunity, they can create a contact card or both a contact and deal card with just two clicks during the conversation. This method is preferable to automatically generating cards since you might receive calls not just from potential clients, but from various other businesses. This approach helps avoid the creation of unnecessary contact and deal cards.", + "components": { + "configuring_scenarios_header_controls": { + "cancel": "Cancel", + "save_scenarios": "Save scenarios", + "failed_to_reach": "Unreachable" + }, + "entity_scenario_radio_group": { + "automatically_create": "Automatically create", + "contact_or_company": "Contact or Company", + "deal": "Deal", + "select_contact_or_company_first": "Select contact or company first", + "deal_pipeline": "Sales pipeline", + "select_deal_first": "Select deal first", + "placeholders": { + "select_deal": "Select deal", + "select_pipeline": "Select pipeline" + } + }, + "incoming_calls_block": { + "incoming_calls": "Incoming calls" + }, + "incoming_known_missing_scenario_block": { + "missed_from_known_number": "Missed call from a known number" + }, + "incoming_unknown_missing_scenario_block": { + "missed_call_from_unknown_number": "Missed call from an unknown number", + "auto_create": "Automatically create", + "contact_or_company": "Contact or Company", + "responsible": "Responsible", + "select_contact_or_company_first": "Select contact or company first", + "deal_pipeline": "Deal pipeline", + "select_deal_first": "Select deal first", + "placeholders": { + "select_responsible": "Select responsible", + "select_deal": "Select deal", + "select_pipeline": "Select pipeline" + } + }, + "incoming_unknown_scenario_block": { + "call_from_unknown_number": "From an unknown number" + }, + "task_and_activities_scenario_group": { + "task": "Task", + "select_contact_or_company_first": "Select contact or company first", + "title": "Automatically create a task or activity", + "do_not_create": "Do not create", + "activity": "Activity", + "activity_type": "Activity type", + "complete": "Complete within", + "description": "Description", + "task_title": "Task title", + "minutes": "minutes", + "placeholders": { + "activity_description": "Activity description", + "task_description": "Task description", + "title": "Title" + } + }, + "outgoing_calls_block": { + "outgoing_calls": "Outgoing calls", + "failed_to_reach": "Unreachable" + }, + "outgoing_unanswered_scenario_block": { + "failed_to_reach": "Unreachable", + "unanswered_outgoing_calls": "Unanswered outgoing calls", + "create_note": "Create a note in the contact/deal card history", + "placeholders": { + "note_content": "Note content" + } + }, + "outgoing_unknown_scenario_block": { + "call_to_unknown_number": "To unknown number", + "creates_manually": "The user manually creates a contact and deal card when making an outgoing call" + } + } + }, + "calls_sip_registrations_page": { + "provider": "Provider", + "removed_or_detached": "Removed or detached", + "removed_or_detached_hint": "This SIP registration is no longer available because it was removed or detached on Voximplant platform. To continue using this SIP registration, you need to attach it to your application again, or remove it and create a new one. Contact support {{mail}} for more information.", + "users": "Users", + "users_hint": "Select users who will have access to this SIP registration", + "default_name": "SIP Registration #{{number}}", + "name": "Registration Name *", + "title_annotation": "You can find the SIP registration instructions in the \"Integrations\" section by clicking the \"Install\" button in the \"Telephony and PBX\" group.", + "link_to_vx_portal": "Link to Voximplant Portal", + "providers": { + "uis": "UIS", + "zadarma": "Zadarma", + "mango_office": "Mango Office", + "beeline": "Beeline", + "mts": "MTS", + "mgts": "MGTS", + "tele2": "Tele2", + "megafon": "Megafon", + "rostelecom": "Rostelecom", + "unknown": "Unknown" + }, + "annotation": "When creating a SIP registration, the monthly fee will be charged immediately from your Voximplant account. Check the amount on the Voximplant portal. Creation may take a few minutes.", + "last_updated": "Last updated: {{lastUpdated}}", + "delete_warning": { + "title": "Are you sure you want to delete this SIP registration?", + "annotation": "This action can not be undone. You will be able to restore the SIP registration only by creating a new one." + }, + "registration_successful": "Registration successful", + "error_annotation": "Make sure you entered the correct credentials or check your Voximplant account for more details.", + "empty": "There are no SIP Registrations yet", + "save_error": "Registration could not be saved. Please ensure all entered data is correct.", + "add": "Add", + "title": "SIP Registrations", + "edit_sip_registration": "Edit SIP Registration", + "add_sip_registration": "Add SIP Registration", + "proxy": "Proxy *", + "sip_user_name": "SIP User Name *", + "password": "Password", + "outbound_proxy": "Outbound Proxy", + "auth_user": "Auth User", + "auth_user_hint": "Usually it is the same as the user name.", + "placeholders": { + "all_users": "All users", + "name": "New SIP Registration", + "proxy": "sip.provider.org", + "sip_user_name": "user_name", + "password": "********", + "outbound_proxy": "outbound.provider.org", + "auth_user": "auth_user_name" + } + }, + "calls_settings_users_page": { + "empty": "You have not added any users yet", + "users": "Users", + "add_user": "Add User", + "remove_user": "Remove user", + "active": "Active", + "create": "Create", + "open_sip_settings": "Open SIP Settings", + "continue": "Continue", + "sip_settings_title": "SIP Settings – {{userName}}", + "sensitive_warning": "This is sensitive information, make sure not to share it with third parties. Hover the password to reveal it.", + "user_name": "User Name", + "domain": "Domain", + "password": "Password", + "remove_warning_title": "Remove {{userName}}?", + "remove_warning_annotation": "This action can not be undone. You will be able to restore the user only by creating a new one.", + "remove": "Remove" + }, + "calls_settings_account_page": { + "synchronise_with_vx": "Synchronize with Voximplant", + "delete_warning_modal": { + "title": "Are you sure you want to delete {{phoneNumber}} phone number?", + "annotation": "This action can not be undone." + }, + "unknown": "Unknown", + "region": "Region", + "phone_number": "Phone number", + "users": "Users", + "state": "State", + "connect_phone_number": "Connect", + "disconnect_phone_number": "Disconnect", + "delete_phone_number": "Delete", + "phone_numbers": "Phone Numbers", + "phone_numbers_annotation1": "Connect all available Voximplant phone numbers to your account.", + "phone_numbers_annotation2": "You can add active operators in the users column so they can access connected number.", + "phone_numbers_annotation3": "ℹ️ When deleting or detaching a number directly in the Voximplant application, don't forget to remove it from the list of numbers below or press the synchronization button which will appear in this case.", + "connect_all_available_phone_numbers": "Connect all available numbers", + "empty_phone_numbers": "No available or connected phone numbers", + "placeholders": { + "all_users": "All users" + }, + "connect_telephony_title": "Connect Telephony and Unlock More Opportunities with {{company}}", + "connect_telephony_warning": "Please note: Clicking the button will automatically create a Voximplant account linked to {{company}}. Do not manually sign up for a separate Voximplant account, as it won't be associated with {{company}}.", + "connect": "Connect Telephony", + "account": "Account", + "voximplant_balance": "Voximplant balance (Soon...)", + "recharge": "Recharge", + "subscription_fee": "Subscription fee (Soon...)", + "available_numbers": "Available Numbers: {{amount}}", + "account_is_not_approved": "Account is not approved", + "not_approved_annotation": "Without confirming your account, you won't be able to use connected phone numbers.", + "approve": "Approve" + } + }, + "components": { + "telephony_button": { + "telephony": "Telephony", + "not_connected": "Connect to telephony to enable call functionality." + }, + "telephony_modal": { + "connect_to_transfer": "You should connect to the call first to transfer it", + "no_operators_to_transfer_call_to": "There are no operators to transfer the call to", + "coming_soon": "Coming soon...", + "unknown_number": "Unknown number", + "amwork_calls": "{{company}} Calls", + "active_call_control": { + "cancel": "Cancel", + "transfer": "Transfer", + "add_to_call": "Add to call", + "end_call": "End call", + "calling": "calling", + "mute": "Mute", + "unmute": "Unmute" + }, + "incoming_call_control": { + "transfer": "Transfer", + "add_to_call": "Add to call", + "accept": "Accept", + "incoming_call": "incoming call", + "mute": "Mute", + "unmute": "Unmute", + "end_call": "End call", + "decline": "Decline" + }, + "outgoing_call_initializer": { + "outgoing_number": "Your Outgoing Number", + "no_available_phone_numbers": "No available phone numbers", + "keys": "Keys", + "recent": "Recent", + "outgoing": "Outgoing call", + "incoming": "Incoming call", + "yesterday": "Yesterday", + "today": "Today", + "no_calls": "No calls" + }, + "call_control_template": { + "call_from": "Call From: {{number}}", + "create": "Create", + "placeholders": { + "select_card": "Select card" + } + } + } + } + } +} diff --git a/frontend/public/locales/en/module.tutorial.json b/frontend/public/locales/en/module.tutorial.json new file mode 100644 index 0000000..4db4e42 --- /dev/null +++ b/frontend/public/locales/en/module.tutorial.json @@ -0,0 +1,55 @@ +{ + "tutorial": { + "tutorial_drawer": { + "title": "Knowledge Base", + "empty": "There is nothing here yet.", + "create_tutorial_group_block": { + "create_group": "Create a group", + "save": "Save", + "cancel": "Cancel" + }, + "create_tutorial_item_block": { + "create_link": "Create a link", + "save": "Save", + "cancel": "Cancel" + }, + "tutorial_edit_group_item_form": { + "placeholders": { + "name": "Name", + "link": "Link", + "all": "All" + } + }, + "tutorial_edit_group_items_forms": { + "name": "Name", + "link": "Link", + "users": "Users", + "products": "Products", + "products_hint": "Select products where this link will be displayed. For example, if tasks are selected – link will be displayed only in the tasks section." + }, + "tutorial_group_name_block": { + "placeholders": { + "group_name": "Group name" + } + }, + "hooks": { + "use_get_tutorial_products_options": { + "product_types": { + "builder": "Builder", + "task": "Tasks", + "mail": "Mail", + "multi_messenger": "Multi Messenger", + "settings": "Settings" + } + }, + "use_get_tutorial_products_groups": { + "groups": { + "entity_type": "Modules", + "products_section": "Products", + "scheduler": "Schedules" + } + } + } + } + } +} diff --git a/frontend/public/locales/en/page.board-settings.json b/frontend/public/locales/en/page.board-settings.json new file mode 100644 index 0000000..4f05f20 --- /dev/null +++ b/frontend/public/locales/en/page.board-settings.json @@ -0,0 +1,35 @@ +{ + "board_settings": { + "ui": { + "board_settings_header": { + "create_bpmn_automation": "Create BPMN 2.0 Automation", + "bpmn_automations": "BPMN 2.0 Automations", + "header": "Board Settings", + "delete_board": "Delete board", + "title": "Warning!", + "annotation": "Deleting the board will permanently delete all cards on it. Are you sure you want to proceed?", + "save": "Save", + "cancel": "Cancel", + "leave": "Leave Settings" + }, + "stage_name_hint": "Maximum {{length}} characters" + }, + "entity_type_board_settings_page": { + "automation_new": "Automation 2.0", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Soon...)", + "warning_title": "You can't delete the last board", + "warning_annotation": "If you would like to delete this board, please create a new one first." + }, + "task_board_settings_page": { + "warning_title": "You can't delete the system board", + "warning_annotation": "This board is system and can't be deleted." + }, + "delete_stage_warning_modal": { + "warning_title": "Are you sure you want to remove this stage?", + "warning_annotation": "Choose a stage where you wish to transfer existing items.", + "delete": "Delete", + "placeholder": "Stage to transfer" + } + } +} diff --git a/frontend/public/locales/en/page.dashboard.json b/frontend/public/locales/en/page.dashboard.json new file mode 100644 index 0000000..e92aaad --- /dev/null +++ b/frontend/public/locales/en/page.dashboard.json @@ -0,0 +1,18 @@ +{ + "dashboard_page": { + "top_sellers": { + "title": "Rating", + "hint": "Lorem ipsum dolor sim amet, consectetur adipiscing elit." + }, + "analytics": { + "total_entities": "Total", + "won_entities": "Won", + "lost_entities": "Lost", + "new_entities": "New", + "total_tasks": "Total Tasks", + "completed_tasks": "Completed Tasks", + "expired_tasks": "Expired Tasks", + "no_tasks": "No Tasks" + } + } +} diff --git a/frontend/public/locales/en/page.login.json b/frontend/public/locales/en/page.login.json new file mode 100644 index 0000000..597cd98 --- /dev/null +++ b/frontend/public/locales/en/page.login.json @@ -0,0 +1,24 @@ +{ + "login": { + "login_form": { + "title": "Log in to your account", + "login": "Log in", + "invalid": "Invalid credentials", + "or": "Or", + "caption": "No account yet?", + "sign_up": "Sign up", + "forgot_your_password": "Forgot your password?", + "placeholders": { + "login": "E-mail", + "password": "Password" + }, + "invalid_email_error": "Invalid email format", + "invalid_password_error": "Password is required" + }, + "left_block": { + "title": "Welcome", + "annotation": "Great work, with pleasure!", + "image_alt": "{{company}} no-code constructor, make your own unique CRM" + } + } +} diff --git a/frontend/public/locales/en/page.settings.json b/frontend/public/locales/en/page.settings.json new file mode 100644 index 0000000..28d3c13 --- /dev/null +++ b/frontend/public/locales/en/page.settings.json @@ -0,0 +1,902 @@ +{ + "settings_page": { + "app_sumo_tiers_data": { + "feature_1": "Unlimited functional products in the builder", + "feature_2": "Unlimited pipeline or board", + "feature_3": "Unlimited custom fields", + "feature_4": "Automation", + "feature_5": "Lifetime access", + "storage": "{{storage}} GB Storage per user" + }, + "templates": { + "document_creation_fields_page": { + "ui": { + "system_section": { + "name": "System fields", + "current_date": "Current date", + "document_number": "Document number" + }, + "entity_type_section": { + "name": "Name", + "owner": "Owner" + }, + "selector": { + "words": "Words" + } + } + }, + "document_templates_page": { + "order_fields": { + "order": "Order", + "order_items": "Order Items", + "specific_order_item": "Order Item", + "order_number": "Order Number", + "order_amount": "Order Amount", + "order_currency": "Order Currency", + "order_item": { + "number": "Number", + "name": "Name", + "price": "Price", + "currency": "Currency", + "discount": "Discount", + "tax": "Tax", + "quantity": "Quantity", + "amount": "Amount" + }, + "text_block_1": "You can fill in the document with data about all the products and services of the order in the form of text or a table. You can also add data only for a specific order item.", + "text_block_2": "To fill in the document with data about all the order items in text form, you need to generate the required text in the document, and to insert data about order items, you need to use special codes mentioned above.", + "text_block_3": "IMPORTANT: At the beginning of the order items list block, you need to insert a special code {#order.products}. At the end of the block, you need to insert a special symbol {/}. The text of the block between the symbols {#order.products} and {/} will be repeated in the created document as many times as there are items in the selected order.", + "text_example": "Text Example", + "text_block_4": "{name} in quantity {quantity} at the price of {price}", + "text_block_5": "To fill in the document with data about all the order items in table form, you need to create a table in the template. If necessary, column names can be specified in the table header. The table, in addition to the header, should contain a row with the necessary fields for filling in.", + "text_block_6": "IMPORTANT: The first cell of the row must contain the code {#order.products} at the very beginning of the cell; the last cell must contain the code {/} at the very end of the cell. These codes are needed to generate the table and will not be displayed in the final document.", + "table_example": "Table Example", + "text_block_7": "To fill in the document with data about a specific order item, you need to use special symbols.", + "text_block_8": "The symbol [0] specifies which order item number will be selected to insert data into the document. Item numbers start from 0 for the first order item." + }, + "no_document_templates": "You don't have any document templates yet.", + "can_create_new_there": "To add a template, click the \"Add document template\" button.", + "add_document_template": "Add document template", + "ui": { + "document_template_item": { + "available_in_sections": "Available in modules", + "creator": "Creator", + "access_rights": "Access rights", + "warning_title": "Are you sure you want to delete this template", + "warning_annotation": "This action cannot be undone.", + "placeholders": { + "select_sections": "Select modules" + } + } + } + } + }, + "billing_page": { + "trial_in_progress": "Your trial is live!", + "trial_over": "Your trial period is over!", + "subscription_over": "Your subscription has ended!", + "request_mywork_billing_form_modal": { + "error": "There was an issue processing your request. Please ensure all information is correct and try again later.", + "send_request": "Submit Request", + "header": "Leave a Request and We Will Contact You", + "full_name": "Full Name *", + "phone": "Phone *", + "email": "Email *", + "number_of_users": "Number of Users", + "comment": "Comment", + "placeholders": { + "full_name": "John Smith", + "comment": "Additional information" + } + }, + "for_all": "For all users", + "request_billing_modal": { + "send": "Send", + "title": "Plan \"{{planName}}\" – Contact Form", + "name": "Name *", + "phone": "Phone *", + "email": "Email *", + "number_of_users": "Number of users *", + "comment": "Comment", + "placeholders": { + "additional_info": "Additional information" + } + }, + "request_button": "Leave request", + "feedback_annotation": "You have selected the {{planName}} plan, please leave a request and we will contact you soon.", + "free_trial_duration": "Free trial – 14 days", + "started_in": "Started in:", + "expires_in": "Expires in:", + "users_limit": "Users limit:", + "renew": "Renew the subscription", + "upgrade": "Upgrade", + "open_stripe_portal": "Open Stripe portal", + "manage_your_subscription": "💸 Manage your subscription on Stripe Customer Portal", + "number_of_users": "Number of users", + "finish_order": "Finish order", + "save_percentage": "Save {{percentage}}", + "per_user_month": "per user / month", + "select_this_plan": "Select plan", + "selected": "Selected", + "1_month": "1 month", + "12_month": "12 months", + "save_up_to": "Save up to {{percent}}%", + "total": "Total: {{amount}}", + "payment_success": "Payment success! You will receive an email confirmation.", + "set_free_plan_warning_modal": { + "title": "Important!", + "annotation": { + "text1": "The user limit of your subscription plan has been exceeded. Additional accounts will be automatically deleted in accordance with the terms of the plan. You can manually delete the necessary accounts", + "link": "here", + "text2": "Your other system data will remain unaffected." + }, + "approve": "Подтвердить", + "cancel": "Отменить" + }, + "lifetime_promo_plans": { + "discount": "{{percent}}% Off", + "lifetime_subscription": "Lifetime Subscription", + "black_text": "", + "until": "until", + "business_plan": "Starter", + "business_plan_description": "Everything you need to manage sales and projects with confidence.", + "advanced_plan": "Business", + "advanced_plan_description": "Full functionality with automation and advanced settings.", + "price_rise_counter": { + "months": ["month", "months", "months"], + "days": ["day", "days", "days"], + "hours": ["hour", "hours", "hours"], + "minutes": ["minute", "minutes", "minutes"], + "sale_ends": "Offer ends in:" + }, + "lifetime_deal_plan_block": { + "lifetime_subscription": "Lifetime Subscription" + }, + "annual_deal_pricing_block": { + "annual_subscription": "1 Year Subscription" + }, + "monthly_deal_pricing_block": { + "monthly_subscription": "1 Month Subscription", + "number_of_users": "Number of users", + "per_user": "/mo per user", + "minimum_period": "minimum subscription period: 6 months", + "buy": "Buy" + }, + "pricing_block_with_users_selector": { + "users": ["user", "users", "users"], + "lifetime_postfix": " forever", + "packages": "User plans", + "per_user": "/mo per user", + "price_description": "user / month, billed annually", + "fix_price": "Fixed price", + "big_savings": "Huge savings", + "savings": "Savings: ", + "buy": "Buy" + } + } + }, + "edit_groups_page": { + "add_new_group": "Add new group", + "subgroup": "Subgroup", + "delete_warning_title": "Are you sure you want to delete {{name}}?", + "delete_warning_annotation1": "All staff from this group", + "delete_warning_annotation2": "(and its subgroups if there are any)", + "delete_warning_annotation3": "can be conditionally assigned to another group or subgroup.", + "add_subgroup": "Add subgroup", + "save": "Save", + "cancel": "Cancel", + "delete": "Delete", + "working_time": "Working Time", + "to": "to", + "placeholders": { + "group": "Enter name", + "select_group": "Select group", + "select_subgroup": "Select subgroup", + "new_subgroup": "New subgroup" + } + }, + "edit_user_page": { + "save": "Save", + "save_and_add": "Save and add", + "first_name": "First Name *", + "first_name_hint": "Maximum {{length}} characters", + "last_name": "Last Name *", + "last_name_hint": "Maximum {{length}} characters", + "email": "Email *", + "phone_number": "Phone", + "password": "Password *", + "group": "Group", + "owner": "Owner", + "owner_hint": "The owner is the super administrator and owner of the account. The owner cannot be deleted, you cannot take away rights or forbid anything. Only one user can be the owner of an account.", + "admin": "Administrator", + "admin_hint": "The administrator has unlimited rights to manage and configure the account.", + "email_error": "This email is already linked to another {{company}} account. To create the user, please opt for a different email address.", + "unknown_error": "An unknown error occurred while editing user. Please check that the fields are filled in correctly and try again.", + "position": "Position", + "visible_users": "Visible Users", + "visible_users_hint": "The users which current user would be able to select from the list", + "working_time": "Working Hours", + "to": "to", + "group_working_time": "Group Working Hours", + "group_working_time_hint": "The user will inherit the working hours of the group they belong to. If the group has no working hours set, the company's working hours will be used.", + "user_calendar_title": "Online Booking Schedule", + "user_calendar_hint": "This schedule defines available time slots for online booking. If no schedule is set, availability will be determined by the calendar.", + "delete_user_calendar": "Delete", + "add_user_calendar": "Create Schedule", + "time_buffer_before": "Buffer Before Meeting", + "time_buffer_after": "Buffer After Meeting", + "appointment_limit": "Daily Appointment Limit", + "schedule": "Schedule", + "no_duration": "None", + "minutes": "minutes", + "placeholders": { + "all_users": "All Users", + "manager": "Manager", + "password": "Come up with a password", + "new_password": "New password", + "group": "Select Group" + }, + "ui": { + "menu_accesses_item": { + "title": "Menu Accesses", + "denied": "Denied", + "responsible": "Responsible", + "allowed": "Allowed" + }, + "object_permissions_list": { + "tasks": "Tasks", + "activities": "Activities" + }, + "object_permissions_item": { + "hint": "You can configure permissions for a user. You can configure user permissions to create, view, edit and delete objects in {{title}} section.", + "create": "Create", + "view": "View", + "edit": "Edit", + "delete": "Delete", + "report": "Report", + "dashboard": "Dashboard", + "denied": "Denied", + "responsible": "Responsible", + "subdepartment": "Subgroup", + "department": "Group", + "allowed": "Allowed" + }, + "products_permissions_item": { + "warehouses_title": "Warehouse Access", + "warehouses_hint": "Select warehouses that the user can access to create orders, view products, and shipments.", + "hint": "You can configure permissions for a user.", + "create_product": "Create product", + "view_product": "View product", + "edit_product": "Edit product", + "create_order": "Create order", + "shipment": "Shipment", + "delete": "Delete", + "denied": "Denied", + "allowed": "Allowed", + "placeholders": { + "all_warehouses": "All warehouses" + } + } + } + }, + "general_settings_page": { + "enable": "Enable", + "contact_duplicates_hint": "Enable this option to warn users when creating a new contact or company with an existing phone number or email address.", + "contact_duplicates": "Warn about Contact Duplicates", + "date_format": "Date format", + "auto": "Automatically", + "phone_format": "Phone Format", + "international": "International", + "free": "Free", + "company": "Company", + "domain": "Domain Name", + "upload_logo": "Upload New Logo", + "delete_logo": "Delete Logo", + "logo_caption": "The preferred size for the logo is 111px by 22px", + "language": "Language", + "time_zone": "Time Zone", + "working_days": "Working Days", + "start_of_week": "Start of the Week", + "Monday": "Monday", + "Tuesday": "Tuesday", + "Wednesday": "Wednesday", + "Thursday": "Thursday", + "Friday": "Friday", + "Saturday": "Saturday", + "Sunday": "Sunday", + "currency": "Currency", + "working_time": "Working Time", + "to": "to", + "number_format": "Number Format", + "currency_select": "Currency Selection", + "currencies": { + "USD": "US Dollar, USD", + "EUR": "Euro, EUR", + "GBP": "British Pound, GBP", + "JPY": "Japanese Yen, JPY", + "CNY": "Chinese Yuan, CNY", + "INR": "Indian Rupee, INR", + "RUB": "Russian Ruble, RUB", + "MXN": "Mexican Peso, MXN", + "BRL": "Brazilian Real, BRL", + "ZAR": "South African Rand, ZAR", + "AUD": "Australian Dollar, AUD", + "CAD": "Canadian Dollar, CAD", + "AED": "UAE Dirham, AED", + "CHF": "Swiss Franc, CHF", + "TRY": "Turkish Lira, TRY", + "UAH": "Ukrainian Hryvnia, UAH", + "KRW": "South Korean Won, KRW", + "NZD": "New Zealand Dollar, NZD", + "NOK": "Norwegian Krone, NOK", + "SEK": "Swedish Krona, SEK", + "DKK": "Danish Krone, DKK", + "PLN": "Polish Zloty, PLN", + "CZK": "Czech Koruna, CZK", + "HUF": "Hungarian Forint, HUF", + "IDR": "Indonesian Rupiah, IDR", + "ILS": "Israeli New Shekel, ILS", + "MYR": "Malaysian Ringgit, MYR", + "PHP": "Philippine Peso, PHP", + "SGD": "Singapore Dollar, SGD", + "THB": "Thai Baht, THB", + "KZT": "Kazakhstani Tenge, KZT", + "CLP": "Chilean Peso, CLP", + "CRC": "Costa Rican Colón, CRC", + "COP": "Colombian Peso, COP", + "BOB": "Bolivian Boliviano, BOB", + "HKD": "Hong Kong Dollar, HKD", + "SAR": "Saudi Riyal, SAR", + "VND": "Vietnamese Dong, VND", + "EGP": "Egyptian Pound, EGP", + "KWD": "Kuwaiti Dinar, KWD", + "PKR": "Pakistani Rupee, PKR", + "LKR": "Sri Lankan Rupee, LKR", + "BDT": "Bangladeshi Taka, BDT", + "NGN": "Nigerian Naira, NGN", + "GHS": "Ghanaian Cedi, GHS", + "TWD": "New Taiwan Dollar, TWD", + "MAD": "Moroccan Dirham, MAD", + "ARS": "Argentine Peso, ARS", + "PEN": "Peruvian Nuevo Sol, PEN", + "UYU": "Uruguayan Peso, UYU", + "BGN": "Bulgarian Lev, BGN", + "RON": "Romanian Leu, RON", + "LBP": "Lebanese Pound, LBP" + } + }, + "integrations_page": { + "calendars_and_tasks": "Calendars and Tasks", + "telephony_integration_guide_international": { + "modal_title": "Voximplant Telephony Integration", + "continue": "Continue", + "title": "Instructions for Integrating Voximplant Telephony", + "step1": { + "title": "Step 1 – Creating an Account", + "item1": "To connect the Voximplant telephony integration, navigate to the \"Settings\" → \"Calls\" → \"Account\" tab and click the \"Connect Telephony\" button.", + "item2": "Information about your created Voximplant account will appear on the page. To complete the integration, you will need to confirm the account by clicking the \"Approve\" link at the top of the screen." + }, + "step2": { + "title": "Step 2 – Balance Replenishment", + "annotation": "Once you open the Voximplant portal, you will need to add funds to your account. It is recommended to add at least $10. This will activate your account, and the funds will be used for purchasing phone numbers and billing call minutes.", + "operator_site_and_billing": "Operator site and billing" + }, + "step3": { + "title": "Step 3 – Purchasing Numbers and Getting Started", + "annotation1": "After completing account activation, you need to provide {{companyName}} support ({{mail}}) with the following details so we can initialize your telephony and prepare it for operation:", + "item1": "The city and country for number section", + "item2": "Working hours: which days of the week and times are considered working hours, your time zone", + "item3": "Greeting text at the beginning of the call during working hours", + "item4": "Message text for calls outside working hours", + "item5": "Voice option for the message – male or female", + "item6": "The required quantity of phone numbers", + "annotation2": "After receiving this information, we will process your request as soon as possible." + }, + "step4": { + "title": "Step 4 – Connecting Numbers in the {{companyName}}", + "annotation1": "Once you have completed account activation and we have purchased the desired numbers for you, you will see them in the \"Settings\" → \"Calls\" → \"Account\" tab. To start working, you need to click the \"Connect\" button next to the desired numbers and select the users who will have access to these numbers.", + "annotation2": "Done! Your telephony is all set up and ready for use." + }, + "step5": { + "title": "Step 5 – Settings and Additional Information", + "annotation1": "In the \"Settings\" → \"Calls\" → \"Users\" tab, you can connect and disconnect system users to telephony, and view SIP data. In the \"Configuring\" tab, you can configure telephony integration scenarios with the CRM module. In the \"SIP Registrations\" tab, you can add and manage SIP registrations of PBX/VPBX.", + "annotation2": "Please note that with active use of telephony, it is recommended to periodically refresh the browser tab, as inactive/stale tabs (those opened more than 3 days ago without interaction) may cause you to miss some calls.", + "annotation3": "Additionally, you must grant the browser access to the microphone to make outgoing calls.", + "grant_chrome_access": "Granting access in Google Chrome", + "grant_mozilla_access": "Granting access in Mozilla Firefox", + "grant_safari_access": "Granting access in Safari", + "annotation4": "Typically, the browser will automatically request the necessary permissions when you attempt to make an outgoing call." + } + }, + "providers_sip_registration_items_list": { + "sip_registration_guide_modal": { + "modal_title": "SIP Registration Connection Guide", + "continue": "Continue", + "step1": { + "title": "Step 1 – Gather Necessary Information", + "annotation1": "Before you start connecting the SIP registration, make sure that your account already has an active Voximplant telephony integration. If it is not yet connected, follow the instructions below:", + "annotation2": "To connect the SIP registration, you need to know the following SIP information about your PBX/VPBX:", + "item1": "Proxy *", + "item2": "SIP Username *", + "item3": "Password", + "item4": "Outbound Proxy", + "item5": "Authentication User", + "annotation3": "This information can be found in your VPBX personal account or requested directly from the operator." + }, + "step2": { + "title": "Step 2 – Connect the Registration", + "item1": "Click the \"Continue\" button at the bottom of this window or go to the \"Settings\" tab → \"Calls\" → \"SIP Registrations\" and click the \"Add SIP Registration\" button.", + "item2": "Enter all the necessary data and click \"Add\".", + "item3": "Note that immediately after creating the registration, a small amount will be charged from your Voximplant account. This will continue to be charged every month for the use of the registration. Detailed information can be found at the link below in the \"Prices\" → \"Features\" → \"SIP Registration\" tab. The link will appear only if you already have a Voximplant account.", + "voximplant_billing_rates": "Voximplant Service Rates", + "item4": "After successful connection, you will see your integration. It can be edited by simply clicking on it. There is no separate fee for editing.", + "item5": "To delete a SIP registration, click the \"Trash\" button at the end of the SIP Registration block in the “SIP Registration” tab.", + "annotation": "For other issues related to SIP registrations, contact us at {{mail}}." + } + }, + "another_pbx": "Connect another PBX/VPBX", + "beeline": "Connect Beeline PBX/VPBX", + "mts": "Connect MTS PBX/VPBX", + "mgts": "Connect MGTS PBX", + "tele2": "Connect Tele2 Corporate PBX", + "megafon": "Connect MegaFon PBX/VPBX", + "rostelecom": "Connect Rostelecom Office PBX/VPBX", + "mango_office": "Connect Mango Office VPBX", + "uis": "Connect UIS VPBX", + "zadarma": "Connect Zadarma VPBX" + }, + "telephony_and_pbx": "Telephony and PBX", + "integrations": "Integrations", + "crm": "CRM", + "process_automation": "Process Automation", + "site_forms": "Web Forms", + "messenger": "Socials and Messengers", + "continue": "Continue", + "save": "Save", + "manage_connected_accounts": "Manage connected accounts", + "ui": { + "google_calendar": { + "google_calendar_manage_modal": { + "title": "{{company}} Google Calendar Integrations" + }, + "google_calendar_connect_modal": { + "do_not_select": "Do Not Select", + "integration_name_readonly_hint": "You can not change the name of this integration once it's created.", + "save": "Save", + "connect_calendar": "Connect calendar", + "finish_integration_annotation": "Finish integration set up by filling in all the required fields.", + "new_integration": "New integration #{{number}}", + "integration_name": "Integration Name *", + "calendar": "Google Calendar *", + "calendar_hint": "Select specific calendar from your Google Calendar account", + "task_board": "Task Board *", + "schedule": "Scheduler {{company}} *", + "linked_task_boards_annotation": "You can add additional task boards to sync with the selected calendar. Tasks from these boards will be synchronized with Google Calendar.", + "linked_schedules_annotation": "You can add additional calendars to sync with the selected calendar. Visits from these calendars will be synchronized with Google Calendar.", + "select_all": "Select All", + "sync_events": "Synchronize events starting from today", + "additional_task_boards": "Additional Task Boards", + "additional_schedules": "Additional Schedules", + "responsible_user": "Responsible User *", + "error_message": "Failed to process Google Calendar code!", + "title": "Connect to Google Calendar", + "feature": "You will be able to perform a two-way synchronization for your tasks and visits with your Google Calendar", + "annotation": "Authorize access to your Google Calendar account.", + "placeholders": { + "integration_name": "Google Calendar integration", + "schedules": "Select schedules", + "task_boards": "Select task boards" + } + }, + "google_calendar_modal_template": { + "title": "{{company}} Google Calendar Integration" + }, + "google_calendar_item": { + "description": "Sync your meetings and tasks" + } + }, + "common": { + "form": { + "account_activity": "Account activity", + "account_activity_annotation": "You can activate or suspend your account", + "available": "Select for whom messenger is available *", + "all": "All", + "users": "Users", + "on": "ON", + "no_create": "Do not create", + "messenger_leads_title": "Lead creation settings", + "messenger_leads_annotation": "Set the parameters below to automatically create a lead when a new contact sends a message.", + "create_entities": "Create a lead on incoming message", + "create_contact": "Module for contact/company", + "create_lead": "Module for deal lead", + "lead_stage": "Deal lead status", + "lead_name": "Deal lead name", + "lead_owner": "Lead owner *", + "check_duplicates": "Avoid duplicates", + "check_active_lead": "Create a new lead if the current one is inactive", + "placeholders": { + "select_module": "Select a module", + "select_users": "Select users", + "select_user": "Select a user", + "lead_name": "Enter a name" + } + }, + "modals": { + "changes_not_saved_warning_modal": { + "title": "Are you sure you want to close this window?", + "annotation": "All changes will be lost.", + "close": "Close" + }, + "integration_account_item": { + "delete_warning_title": "Are you sure you want to delete {{title}} account?", + "delete_warning_annotation": "All chats and messages related to this communication channel will be deleted. This action cannot be undone.", + "active": "Active", + "active_hint": "You account is up and running. You can suspend it in the provider settings by clicking the pencil icon.", + "inactive": "Inactive", + "inactive_hint": "You account is suspended. You can activate it in the provider settings by clicking the pencil icon.", + "deleted": "Deleted", + "deleted_hint": "You account is deleted.", + "draft": "Draft", + "draft_hint": "You account is not connected yet." + }, + "integration_accounts_list": { + "add_account": "Add account" + } + } + }, + "integration_item": { + "install": "Install", + "manage": "Manage" + }, + "messages_per_day_select": { + "label": "Daily Message Limit for Automation *", + "annotation": "messages per day", + "hint": "Maximum number of messages that can be sent daily via automation from this account. This limit is necessary to prevent the account from being blocked by the messenger due to bulk messaging." + }, + "salesforce": { + "salesforce_item": { + "description": "One workspace for every team" + }, + "salesforce_modal": { + "title": "Integration with Salesforce", + "connected": "Connected", + "not_connected": "Not connected", + "disconnect": "Disconnect", + "add_integration": "Add integration", + "my_domain_name": "My domain name", + "app_key": "App consumer key", + "app_secret": "App consumer secret", + "connect": "Connect", + "caption": "Keep using Salesforce for customers, and use {{company}} to manage the rest of your business processes. Drive projects in {{company}}. Manage contractors and suppliers in {{company}}." + } + }, + "fb_messenger": { + "fb_messenger_manage_modal": { + "title": "Facebook Messenger API integration" + }, + "fb_messenger_item": { + "title": "Manage Facebook interactions, convert chats to leads" + }, + "fb_messenger_modal_template": { + "title": "Facebook Messenger Integration" + }, + "fb_messenger_first_info_modal": { + "title": "Get an official Facebook Messenger Business account to start talking to your customers using the {{company}} Multimessenger.", + "feature1": "Manage all Facebook Messenger conversations with other channels in one place", + "feature2": "Collaborate with your team members on incoming conversations", + "feature3": "Easily create new contacts, leads and deals directly from conversations", + "feature4": "Use Facebook Messenger message templates to send relevant and timely notifications", + "learn_more": "Learn more about how to manage your Facebook Messenger Integration" + }, + "fb_messenger_finish_modal": { + "supervisors": "Select users who have access to all client chats", + "supervisors_hint": "Select users who will have access to all employee chats with clients. These could be managers or employees who work together with clients.", + "save": "Save", + "title": "You've already connected your Facebook account to {{company}}", + "subtitle": "Finish setting up your account. Change the name and accessibility settings as needed.", + "name": "Name *", + "responsible_users": "Responsible users for new leads *", + "learn_more": "Learn more about how to manage your Facebook Messenger Integration", + "placeholders": { + "name": "Product name", + "select_users": "Select users" + } + } + }, + "wazzup": { + "wazzup_manage_modal": { + "title": "{{company}} Wazzup Integration" + }, + "wazzup_item": { + "title": "Wazzup Integration in Just 5 Minutes, WhatsApp & Telegram" + }, + "wazzup_modal_template": { + "title": "{{company}} Wazzup Integration" + }, + "wazzup_first_info_modal": { + "title": "Reach out to your customers directly through {{company}} on social media and messaging platforms.", + "feature1": "Sales team members can message via WhatsApp, Telegram using {{company}}", + "feature2": "Managers can only see their conversations, while supervisors have access to all chats", + "feature3": "Contacts and deals with new clients are automatically generated", + "feature4": "All communications are securely stored in {{company}}", + "feature5": "Utilize a single number for the entire sales department and set up automated responses", + "annotation": "Stay connected with clients wherever you are. With the Wazzup mobile app, you can contact clients on the go, and all your conversations will be saved in {{company}} and accessible from your computer.", + "learn_more": "Learn more about Wazzup" + }, + "wazzup_connect_modal": { + "save": "Save", + "update": "Update", + "title": "Authorize your Wazzup account", + "subtitle": "Access your Wazzup Account settings and copy the API credentials.", + "name": "Name *", + "api_key": "API Key *", + "channel": "Wazzup channel *", + "responsible_users": "Responsible users for new leads *", + "supervisors": "Select users who have access to all client chats", + "supervisors_hint": "Select users who will have access to all employee chats with clients. These could be managers or employees who work together with clients.", + "placeholders": { + "api_key": "API key", + "name": "Product name", + "select_users": "Select users", + "channel": "Select Wazzup channel" + } + } + }, + "whatsapp": { + "whatsapp_manage_modal": { + "title": "WhatsApp Business API Integration by Twilio" + }, + "whatsapp_item": { + "title": "Direct WhatsApp Business Messages through {{company}}" + }, + "whatsapp_modal_template": { + "title": "WhatsApp Business API Integration by Twilio" + }, + "whatsapp_first_info_modal": { + "title": "Get an official WhatsApp Business account (provided by Twilio) to start talking to your customers using the {{company}} Multimessenger", + "feature1": "Manage all WhatsApp conversations with other channels in one place", + "feature2": "Collaborate with your team members on incoming conversations", + "feature3": "Easily create new contacts, leads and deals directly from conversations", + "feature4": "Use WhatsApp message templates to send relevant and timely notifications", + "learn_more": "Learn more about WhatsApp Business API by Twilio" + }, + "whatsapp_second_info_modal": { + "title": "Things to keep in mind before you continue", + "step1": "You should purchase a new phone number in Twilio (or transfer your current phone number from the WhatsApp mobile app or WhatsApp Business app) to create a WhatsApp Business API account.", + "link1": "How to move an approved WhatsApp number to Twilio", + "step2": "WhatsApp and Twilio may charge you with an additional fee for using the WhatsApp Business API.", + "link2": "Learn more about pricing" + }, + "whatsapp_third_info_modal": { + "title": "Set up Twilio account with your WhatsApp number", + "subtitle": "Twilio needs to approve your WhatsApp number before it can be used in {{company}}. Follow these steps to avoid any unnecessary delays.", + "step1": "Set up with", + "step2": "Request access from Twilio to enable the phone numbers for WhatsApp:", + "step2_1_1": "Make sure you have the phone number provided by Twilio ready to get the number of WhatsApp. Fill out", + "step2_1_2": "Twilio Request Access", + "step2_1_3": "form with your up-to-date information, including your Facebook Business Manager ID.", + "step2_2_1": "Refer to Twilio’s", + "step2_2_2": "documentation", + "step2_2_3": "for more information.", + "step3": "Submit a WhatsApp Sender request in the Twilio console.", + "step4_1": "Once you've submitted the Request Access form, you'll receive a pre-approval email from Twilio. Check out this", + "step4_2": "reference", + "step4_3": "link for the next steps.", + "step5": "Allow Twilio to send a message on your behalf in the Facebook Business Manager console." + }, + "whatsapp_connect_modal": { + "supervisors": "Select users who have access to all client chats", + "supervisors_hint": "Select users who will have access to all employee chats with clients. These could be managers or employees who work together with clients.", + "save": "Save", + "update": "Update", + "title": "Authorize your WhatsApp account by Twilio", + "subtitle": "Access your Twilio Account settings and copy the API credentials.", + "name": "Name *", + "sid": "Account SID *", + "auth_token": "Auth token *", + "phone": "Authorized WhatsApp number *", + "responsible_users": "Responsible users for new leads *", + "learn_more": "Learn more about how to set up your WhatsApp account with Twilio", + "placeholders": { + "name": "Product name", + "code": "Product code", + "token": "Token", + "number": "Number", + "select_users": "Select users" + } + } + }, + "tilda": { + "tilda_item": { + "description": "Form integration with Tilda" + }, + "tilda_manual_modal": { + "close": "Close", + "title": "Form Integration with Tilda", + "info_title": "Integrate a form from the {{company}} builder with your Tilda site to send submissions directly to the system.", + "step_1_title": "Step 1 — Prepare the form", + "step_1_description": "In the {{company}} form builder, create a form with API integration. Set up the form in step one and add the required fields in step two.", + "form_builder_link": "Form builder with API integration", + "step_2_title": "Step 2 — Configuring the form in Tilda", + "step_2_item_1": "Go to Site Settings → Forms → Webhook.", + "step_2_item_2": "Enter the webhook URL from step 3 of the form builder as the script address.", + "step_2_item_3": "Save the changes.", + "step_3_title": "Step 3 — Configuring field variables", + "step_3_item_1": "For the form you want to integrate, select Webhook as the data receiver.", + "step_3_item_2": "Assign variables (identifiers) to form fields from the table in step 3 of the form builder.", + "step_3_item_3": "Test the form. If it works correctly, publish the page.", + "tilda_manual_link": "Tilda setup guide", + "support": "If you experience issues integrating the form, contact support." + } + }, + "wordpress": { + "wordpress_item": { + "description": "Form integration with Wordpress" + }, + "wordpress_manual_modal": { + "close": "Close", + "title": "Form Integration with Wordpress", + "info_title": "Integrate a form from the {{company}} builder with your Wordpress site to send submissions directly to the system.", + "step_1_title": "Step 1 — Prepare the form", + "step_1_description": "In the {{company}} form builder, create the form for your website. Customize the fields on step 2 and style the form on step 4 if needed.", + "form_builder_link": "Form Builder", + "step_2_title": "Step 2 — Add the form container", + "step_2_item_1": "Open the Wordpress admin panel.", + "step_2_item_2": "If possible, back up or duplicate the page to allow for easy rollback.", + "step_2_item_3": "If you have an existing form and want to replace it, remove it, leaving an empty block. If there’s no form, add an empty block where you want the form to appear.", + "step_2_item_4": "Go to the 'Advanced' menu in the block settings on the right side of the editor and assign the class name workspace-form-builder-mount-container.", + "step_3_title": "Step 3 — Add the form code", + "step_3_item_1": "Add a 'Custom HTML' block to the page.", + "step_3_item_2": "Open the 'Custom HTML' block and paste the code from step 5 in the form builder.", + "step_3_item_3": "Test the form. If it works correctly, publish the page.", + "wordpress_manual_link": "Wordpress HTML Code Insertion Guide", + "additional_steps": "Additional steps", + "styling": "You can customize the form’s appearance on step 4 in the form builder. If more customization is needed, enable 'Custom CSS'. Each element has a clear and stable CSS class format, e.g., .workspace-form__TextInput--Input. You can edit the form in the builder even after the page is published, and changes will apply immediately after saving.", + "multiform": "You can publish multiple identical forms on a single page. In this case, only one 'Custom HTML' block is needed, but there can be multiple containers. If you need different forms from the builder on the same page, enable 'multi-form compatibility mode' in the builder. Then, insert each form’s code into its respective 'Custom HTML' block. The container’s CSS class will be unique — copy it from the instructions in step 5 of the form builder.", + "support": "If you experience issues integrating the form, contact support." + } + }, + "albato": { + "albato_item": { + "description": "Automate complex workflows with Albato" + }, + "albato_manual_modal": { + "close": "Close", + "header_title": "Integration Guide for {{company}} with Albato", + "integration_title": "Use Albato to connect {{company}} with thousands of other apps.", + "step_1_title": "Step 1: Create a Workflow in Albato", + "albato_website": "Albato Website", + "step_1_part_1": "Sign up for Albato if you don’t have an account yet.", + "step_1_part_2": "Create a new workflow. Add an action and select Mywork from the service list.", + "step_1_part_3": "Choose the desired action from the list.", + "step_2_title": "Step 2: Connect Your Mywork Account", + "step_2_part_1": "Add a new connection for Mywork.", + "step_2_part_2": "Enter your 'API Key,' which can be found in the 'API Access' section of your Mywork account settings.", + "step_2_part_3": "Fill in the 'Email' and 'Password' fields with your Mywork account credentials.", + "step_2_part_4": "Enter your 'Subdomain,' found in the 'General Settings' section of your Mywork account. Use only the highlighted part without '.mywork.app'.", + "api_access_link": "API Access", + "step_3_title": "Step 3: Configure Automation Actions", + "step_3": "During action setup, you’ll see a form with fields to complete. These fields match the data required for the action (e.g., assignee, task name, or description). You can populate these fields dynamically with data from previous actions or triggers. Some data, like assignees, can be fetched from your Mywork account. If data is missing, click 'Refresh List' to update.", + "step_4_title": "Step 4: Testing", + "step_4": "Before activating the workflow, test it by clicking the test button. You can review submitted data in the workflow log. For any questions, contact Mywork support." + } + }, + "request_integration": { + "description": "Request integration development", + "request": "Request" + } + } + }, + "users_settings_page": { + "user": "User", + "total": "Total", + "create_button_tooltip": "Add User", + "ui": { + "remove_user_modal": { + "remove": "Remove", + "title": "Are you sure you wish to remove this user?", + "annotation": "Transfer ownership in all items of this user with another one.", + "placeholder": "Select responsible..." + }, + "user_item": { + "remove": "Remove", + "user_role": { + "owner": "Owner", + "admin": "Admin", + "user": "User", + "partner": "Partner" + } + } + } + }, + "account_api_access_page": { + "title": "API Access", + "annotation": "You can use the API key to create external integrations with the system. Store this key in a secure place and do not share it with anyone. If the key is compromised, regenerate it.", + "create_api_key": "Create API Key", + "recreate_api_key": "Regenerate", + "warning_title": "API Key Regeneration", + "warning_annotation": "The current key will be deleted and replaced with a new one. After regenerating, update the key in all external integrations to ensure they continue working.", + "created_at": "Created on {{date}} at {{time}}", + "api_tokens_list": { + "title": "Authorization Tokens", + "annotation": "Create authorization tokens for external integrations. A token replaces the standard login and password authentication. You will see the token only once after creation, so save it securely. If compromised, delete it and generate a new one.", + "empty_user_tokens": "No authorization tokens created.", + "add_user_token": "Add Token", + "copy": "Copy Token", + "access_token": "Save this token — you won’t be able to see it again:", + "name": "Name", + "expires_at": "Expiration Date", + "table": { + "name": "Name", + "created_at": "Created", + "expires_at": "Expires", + "last_used_at": "Last Used", + "never": "Never", + "actions": "Actions", + "delete": "Delete" + } + } + }, + "settings_page_template": { + "modules_settings": "Modules Settings", + "sip_registrations": "SIP Registrations", + "title": "Settings", + "users": "Users", + "general": "General Settings", + "email": "Email Settings", + "integrations": "Integrations", + "billing": "Billing", + "api_access": "API Access", + "documents": "Documents", + "calls": "Calls", + "account": "Account", + "superadmin": "Admin Panel", + "configuring_scenarios": "Configuring", + "schemas": "Schemas", + "configure_users": "Configuring Users", + "groups": "Groups", + "document_templates": "Document Templates", + "document_creation_fields": "Document Creation Fields" + }, + "add_user_to_plan_modal": { + "send": "Send", + "title": "To add a user, please contact us", + "name": "* Name", + "phone": "* Phone", + "email": "* Email", + "number_of_users": "* Number of users", + "placeholder": "Your name" + }, + "request_setup_form": { + "button": { + "request_setup": "Request Setup", + "request_billing_help": "Request Billing Help", + "request_integration": "Request Integration Development", + "request_telephony": "Request Telephony Setup", + "request_api_integration": "Request API Integration" + }, + "modal": { + "request_setup": "Request Setup for {{company}}", + "request_billing_help": "Request Billing Help for {{company}}", + "request_integration": "Request Integration Development", + "request_telephony": "Request Telephony Setup", + "request_api_integration": "Request API Integration", + "full_name": "Full Name *", + "phone": "Phone *", + "email": "E-mail *", + "comment": "Request", + "send_request": "Submit", + "placeholders": { + "full_name": "Enter your full name", + "comment": { + "request_setup": "Briefly describe your system setup request. We will contact you to assist with implementation.", + "request_billing_help": "Briefly describe the issue with your subscription. We will contact you to resolve it.", + "request_integration": "Briefly describe your integration development request. We will contact you to discuss the details.", + "request_telephony": "Briefly describe your telephony request. We will contact you to assist with setup for your needs.", + "request_api_integration": "Briefly describe your API integration request. We will contact you to assist with the integration for your needs." + } + } + } + } + } +} diff --git a/frontend/public/locales/en/page.system.json b/frontend/public/locales/en/page.system.json new file mode 100644 index 0000000..4f8e47c --- /dev/null +++ b/frontend/public/locales/en/page.system.json @@ -0,0 +1,19 @@ +{ + "error_page": { + "title": "Something went wrong", + "annotation": "We are sorry, but something went wrong. We have been notified about this issue and we will take a look at it shortly. Please, go to the home page or try to reload the page.", + "show_error": "Show error message", + "home": "Home Page", + "reload": "Reload the Page" + }, + "forbidden_page": { + "title": "Access to this page is forbidden.", + "back": "Go Back", + "home": "Home Page" + }, + "not_found_page": { + "title": "This page could not be found.", + "home": "Home Page", + "back": "Go Back" + } +} diff --git a/frontend/public/locales/en/page.tasks.json b/frontend/public/locales/en/page.tasks.json new file mode 100644 index 0000000..30aa920 --- /dev/null +++ b/frontend/public/locales/en/page.tasks.json @@ -0,0 +1,135 @@ +{ + "tasks_page": { + "tasks_page_by_deadline": { + "unallocated": "Unallocated", + "overdue": "Overdue", + "today": "Today", + "tomorrow": "Tomorrow", + "upcoming": "Upcoming", + "resolved": "Done", + "board": "Board", + "calendar": "Calendar" + }, + "common": { + "tasks_page_template": { + "create_new": "Create new task" + }, + "ui": { + "empty_list_block": { + "annotation1": "You have no tasks yet or nothing was found with the selected filters.", + "annotation2": "Use the", + "annotation3": "button to add a task." + }, + "tasks_page_header": { + "timeline": "Gantt", + "title": "Tasks & Activities", + "board": "Board", + "list": "List", + "calendar": "Calendar", + "user_select_button": "Share", + "settings_button": { + "sync": "Synchronize", + "table_settings": "Table settings", + "settings": "Settings", + "board_settings": "Board settings" + }, + "create_button_tooltip": "Add Task" + }, + "tasks_list_settings_drawer": { + "table_settings": "Table Settings", + "display_columns": "Display Columns" + }, + "tasks_filter_drawer": { + "just_my_tasks": "Just my Tasks", + "sorting": "Sorting", + "done": "Done", + "sorting_options": { + "manual": "Manual", + "created_asc": "Created: from old to new", + "created_desc": "Created: from new to old" + }, + "created_at": "Creation Date", + "start_date": "Start Date", + "end_date": "End Date", + "resolve_date": "Completion Date", + "assignee": "Assignee", + "reporter": "Reporter", + "type": "Type", + "stage": "Stage", + "groups": "Groups", + "linked_cards": "Linked cards", + "placeholders": { + "card_name": "Card name", + "search_by_task_name": "Search by task name", + "search_by_activity_name": "Search by activity name" + } + } + } + }, + "tasks_page_calendar": { + "new_event": "New task", + "no_events": "There are no tasks for this period.", + "all_day": "All-day" + }, + "tasks_page_list": { + "all_columns_hidden": "All columns are hidden in table settings" + }, + "activity_item": { + "no_card_access": "You can't access this card", + "drag_disabled": "You don't have permission to move this card. Contact account administrator for access." + }, + "tasks_page_timeline": { + "annotation": { + "minute": ["minute", "minutes", "minutes"], + "hour": ["hour", "hours", "hours"], + "day": ["day", "days", "days"] + }, + "today": "Today", + "view": { + "fifteen_minutes": "15 Minutes", + "hour": "Hour", + "day": "Day", + "week": "Week", + "month": "Month", + "quarter": "Quarter", + "half_year": "Half year" + }, + "format": { + "major_format": { + "fifteen_minutes": "YYYY, MMMM D", + "hour": "YYYY, MMMM D", + "day": "YYYY, MMMM", + "week": "YYYY, MMMM", + "month": "YYYY", + "quarter": "YYYY", + "half_year": "YYYY" + }, + "minor_format": { + "fifteen_minutes": "HH:mm", + "hour": "HH", + "day": "D", + "week": "wo [week]", + "month": "MMMM", + "quarter": "[Q]Q", + "half_year": "YYYY-" + } + } + }, + "task_item": { + "planned_time": "Estimated Time:", + "completed": "Completed", + "complete": "Complete", + "delete_task": "Delete task", + "copy_link": "Copy link", + "drag_disabled": "You don't have permission to move this task. Contact account administrator for access.", + "date": "{{date}} at {{time}}" + }, + "time_allocation_card": { + "users": "Users", + "planned_time": "Estimated Time", + "total": "Total", + "hour": "h", + "minute": "m" + } + } +} diff --git a/frontend/public/locales/en/store.field-groups-store.json b/frontend/public/locales/en/store.field-groups-store.json new file mode 100644 index 0000000..2a806b2 --- /dev/null +++ b/frontend/public/locales/en/store.field-groups-store.json @@ -0,0 +1,9 @@ +{ + "field_groups_store": { + "requisites": "Requisites", + "analytics": "Analytics", + "errors": { + "create_at_least_one_field": "Please, create at least one field in each group" + } + } +} diff --git a/frontend/public/locales/en/store.fields-store.json b/frontend/public/locales/en/store.fields-store.json new file mode 100644 index 0000000..0e0f582 --- /dev/null +++ b/frontend/public/locales/en/store.fields-store.json @@ -0,0 +1,8 @@ +{ + "fields_store": { + "errors": { + "create_at_least_one_field": "Please, create at least one field", + "duplicate_name": "Field names must be unique: {{fieldName}}" + } + } +} diff --git a/frontend/public/locales/es/common.json b/frontend/public/locales/es/common.json new file mode 100644 index 0000000..1b5d55a --- /dev/null +++ b/frontend/public/locales/es/common.json @@ -0,0 +1,395 @@ +{ + "connect_with_google": "Conectar con Google", + "responsible": "Responsable", + "all_cards": "Todas las Tarjetas", + "apply_to_all_button": { + "apply": "Aplicar", + "apply_to_all": "Aplicar a todos", + "apply_to_all_accounts": "Aplicar configuración de la tabla para todos los usuarios:", + "apply_warning_title": "¿Está seguro de que desea aplicar su configuración local de la tabla para todos los usuarios?", + "apply_warning_annotation": "Esta acción sobrescribirá todas las configuraciones de la tabla para otros usuarios en el sistema. No se puede deshacer." + }, + "version_modal": { + "title": "✨ ¡{{company}} acaba de mejorar!", + "annotation": "¡Actualiza a la versión {{version}}!", + "update": "Actualizar" + }, + "reload_modal": { + "title": "¡La página está desactualizada!", + "annotation": "Los datos en esta página pueden estar desactualizados. Recárgala para asegurar el funcionamiento correcto.", + "update": "Recargar" + }, + "select_stage": "Seleccionar etapa", + "new_activity_type": "Nuevo tipo de actividad", + "video_error": "Error al descargar y reproducir el archivo de video", + "image_error": "Error al descargar y prévisualiser la imagen", + "view_document": "Ver: {{document}}", + "object": "objeto", + "all_day": "Todo el día", + "supervisor": "Supervisor", + "current_chat_user_hint": "No puedes eliminarte del chat", + "coming_soon": "Próximamente...", + "field_readonly": "Este campo es solo de lectura", + "now": "Ahora", + "default": "Por defecto", + "exact_time": "Hora Exacta", + "card_copy": "Esta copia fue creada por la automatización de cambio de etapa", + "customize": "Personalizar", + "day_char": "d", + "hour_char": "h", + "minute_char": "m", + "days": ["día", "días", "días"], + "minutes": ["minuto", "minutos", "minutos"], + "hours": ["hora", "horas", "horas"], + "seconds": ["segundo", "segundos", "segundos"], + "files_count": ["{{count}} archivo", "{{count}} archivos", "{{count}} archivos"], + "loading_title": "Cargando...", + "changes_unsaved_blocker": { + "title": "Cambios no guardados", + "annotation": "¿Deseas guardar antes de cerrar o salir sin guardar?", + "approve_title": "Guardar", + "cancel_title": "Salir sin guardar" + }, + "browser_not_supported_title": "Tu navegador no es compatible.", + "browser_not_supported_annotation": "Para usar {{company}}, te recomendamos usar la última versión de Chrome, Firefox o Safari. Esto garantizará la mejor experiencia posible. Puedes encontrar las instrucciones abajo.", + "duplicate_title": "La tarjeta ya existe", + "duplicate_warning_annotation": "¿Deseas crear un duplicado o seleccionar una tarjeta existente?", + "select_existing": "Seleccionar existente", + "duplicate_warning_cancel_title": "Crear duplicado", + "duplicate_forbidden_annotation": "El administrador ha prohibido la creación de contactos duplicados. Selecciona una tarjeta existente o usa un número único diferente.", + "add_as_new": "Crear duplicado", + "tasks_total": ["Tareas", "Tareas", "Tareas"], + "activities_total": ["Actividades", "Actividades", "Actividades"], + "visits_total": ["Visita", "Visitas", "Visitas"], + "trial_left": { + "one_day": "Prueba: 1 día", + "several_days": ["Prueba: {{left}} días", "Prueba: {{left}} días", "Prueba: {{left}} días"] + }, + "meta_description": "{{company}} es un constructor de espacios de trabajo potente y flexible diseñado específicamente para pequeñas y medianas empresas.", + "international_number": "Número internacional", + "phone_number": "Número de teléfono", + "no_available_groups": "No hay grupos disponibles", + "add_new_group": "Añadir nuevo grupo", + "schedule": "Horario", + "board": "Tablero", + "settings": "Configuración", + "visits": "Reuniones", + "orders": "Pedidos", + "row": "Fila", + "column": "Columna", + "external_link": "Enlace externo", + "loading": "Cargando", + "products": "Productos", + "for_sales": "Para ventas", + "rentals": "Alquileres", + "title": "Título", + "name": "Nombre", + "max_length": "Máximo {{length}} caracteres", + "activities": "Actividades", + "project_tasks_board": "Tablero", + "tasks_board": "Tablero de tareas", + "tasks_list": "Lista", + "time_board": "Tablero de tiempo", + "trial": "Prueba", + "show_less": "Mostrar menos", + "show_more": "Mostrar más", + "search": "Buscar", + "clear_search": "Limpiar búsqueda", + "profile": "Perfil", + "avatar_alt": "{{firstName}} avatar", + "log_out": "Cerrar sesión", + "select_date": "Seleccionar fecha", + "task_title": "Título de la tarea", + "select": "Seleccionar", + "overview": "Resumen", + "new_board": "Nuevo tablero", + "enter_html_markup": "Introducir marcado HTML", + "filter": "Filtrar", + "notifications": "Notificaciones", + "mail": "Correo", + "multichat": "Multi Messenger", + "total": "Total: {{total}}", + "select_period": "Seleccionar período", + "select_groups": "Seleccionar grupos", + "select_group": "Seleccionar grupo", + "delay": "Retraso", + "hide_sidebar": "Ocultar barra lateral", + "show_sidebar": "Mostrar barra lateral", + "site_form": "Formularios en el Sitio Web", + "workspace_tags": { + "site_form": "Formulario de sitio", + "headless_site_form": "Formulario en el sitio vía API", + "online_booking_site_form": "Formulario de reserva en línea", + "project": "Proyectos y Tareas", + "universal": "Módulo Universal", + "partner": "Socios", + "deal": "CRM", + "contact": "Contactos", + "company": "Empresas", + "supplier": "Proveedores", + "contractor": "Contratistas", + "hr": "Contratación de Personal", + "products_for_sales": "Gestión de Inventario", + "products_rentals": "Gestión de Alquileres", + "visits": "Planificación de citas" + }, + "sidebar": { + "builder": "Constructor", + "tasks_and_activities": "Tareas y Actividades", + "mail": "Correo", + "settings": "Configuración", + "companies": "Empresas", + "contacts": "Contactos", + "multichat": "Multi Messenger" + }, + "buttons": { + "save_and_leave": "Guardar y Salir", + "create": "Crear", + "cancel": "Cancelar", + "save": "Guardar", + "delete": "Eliminar", + "add": "Añadir", + "edit": "Editar", + "ok": "Ok", + "activate": "Activar", + "deactivate": "Desactivar", + "back": "Atrás", + "clear": "Limpiar", + "continue": "Continuar" + }, + "profile_modal": { + "app_version": "Versión de la App", + "app_version_hint": "La versión actual de la aplicación.", + "clear_cache": "Limpiar Caché", + "clear_cache_title": "Limpiar Caché de la Aplicación", + "clear_cache_hint": "Esta acción limpiará todos los datos de caché no esenciales del cliente y recargará la página.", + "password": "Contraseña", + "profile": "Tu Perfil", + "avatar": "avatar", + "first_name": "Nombre", + "first_name_hint": "Máximo {{length}} caracteres", + "last_name": "Apellido", + "last_name_hint": "Máximo {{length}} caracteres", + "change_avatar": "Cambiar", + "add_avatar": "Añadir Foto", + "delete_avatar": "Eliminar", + "avatar_annotation": "La foto debe estar en formato PNG, JPG. Resolución no mayor a 1024px*1024px.", + "date_of_birth": "Fecha de Nacimiento", + "phone": "Teléfono", + "employment_start": "Fecha de Inicio de Empleo", + "email": "Correo Electrónico", + "current_password": "Contraseña Actual", + "new_password": "Nueva Contraseña", + "confirm_password": "Confirmar Contraseña", + "change_password": "Cambiar Contraseña", + "upload_avatar": "Subir Avatar", + "passwords_do_not_match": "Las contraseñas no coinciden", + "incorrect_password": "Contraseña incorrecta", + "image_edit_modal": { + "annotation": "Ajusta la visualización de la foto para tu perfil. Arrastra el control deslizante para acercar y alejar la foto." + } + }, + "form": { + "my_select": { + "no_options": "No hay opciones", + "placeholder": "Seleccionar", + "search": "Buscar...", + "select_user": "Seleccionar usuario", + "select_all": "Seleccionar todo", + "clear_selection": "Limpiar selección" + }, + "key_value_input": { + "key": "Clave", + "value": "Valor", + "add": "Agregar" + }, + "week_intervals_input": { + "mo": "Lun", + "tu": "Mar", + "we": "Mié", + "th": "Jue", + "fr": "Vie", + "sa": "Sáb", + "su": "Dom", + "to": "a", + "unavailable": "Fuera de horario" + } + }, + "subscription_period_modal": { + "contact_admin": "Póngase en contacto con el administrador de la cuenta para enviar una solicitud de pago de suscripción.", + "trial_left": "Te quedan {{left}} día(s) de prueba gratuita", + "trial_over": "Tu prueba gratuita ha expirado", + "subscription_left": "Te quedan {{left}} día(s) de suscripción", + "subscription_over": "Tu suscripción ha expirado", + "trial_sub_title": "Elige un plan y continúa usando {{company}} Workspace.", + "subscription_sub_title": "Renueva tu suscripción para seguir accediendo a {{company}} Workspace.", + "select_plan": "Seleccionar un plan" + }, + "trial_period_over_modal": { + "send": "Enviar", + "title": "Por favor contáctenos para pagar por {{company}}", + "name": "* Nombre", + "email": "* Correo electrónico", + "phone": "* Teléfono", + "number_of_users": "* Número de usuarios", + "subscribe": "Suscribirse", + "yearly": "Anual -20%", + "monthly": "Mensual", + "choose_plan": "* Elige tu plan", + "trial_left": "Te quedan {{left}} días de prueba", + "trial_left_one": "Te queda {{left}} día de prueba", + "trial_over": "Tu período de prueba ha terminado", + "subscription_over": "Tu período de suscripción ha terminado", + "placeholders": { + "name": "Tu nombre" + } + }, + "update_task_modal": { + "title": "Título", + "linked_card": "Tarjeta vinculada", + "stage": "Etapa", + "add_description": "Añadir descripción", + "no_description": "Sin descripción", + "not_found": "No se encontró esta tarea", + "close": "Cerrar", + "description": "Descripción", + "subtasks": "Subareas", + "comments": "Comentarios", + "planned_time": "Tiempo Estimado", + "board_name": "Nombre del Tablero", + "assignee": "Asignado a", + "start_date": "Fecha de Inicio", + "end_date": "Fecha de Fin", + "files": "Archivos", + "attach_files": "Adjuntar archivos", + "reporter": "Reportero: {{name}}, {{date}} a los {{time}}", + "reporter_noun": "Reportero", + "enter_description": "Ingresar descripción", + "new_comment": "Nuevo comentario", + "placeholders": { + "board": "Seleccionar tablero", + "search_card": "Buscar tarjeta" + } + }, + "delete_demo": { + "button_text": "Eliminar demo", + "modal_title": "¿Realmente deseas eliminar los datos de demostración?", + "modal_annotation": "Todos los datos de demostración serán eliminados. Esta acción no se puede deshacer." + }, + "filter_drawer_controls": { + "clear": "Limpiar", + "save_filter_settings": "Guardar configuración", + "error_message": "Lamentablemente, el filtro no se aplicó. Por favor, inténtalo de nuevo." + }, + "period_types": { + "all_time": "Todo el tiempo", + "today": "Hoy", + "yesterday": "Ayer", + "current_week": "Semana actual", + "last_week": "Semana pasada", + "current_month": "Mes actual", + "last_month": "Mes pasado", + "current_quarter": "Trimestre actual", + "last_quarter": "Trimestre pasado", + "placeholders": { + "select_period": "Seleccionar período" + } + }, + "file_size_warning_modal": { + "title": "Archivo demasiado grande", + "annotation": "El tamaño máximo del archivo para cargar es de {{maxSizeMb}} MB. Comprime el archivo o selecciona otro.", + "cancel": "Cancelar" + }, + "player": { + "default": "Por defecto", + "fast": "Rápido", + "super_fast": "Súper rápido" + }, + "languages": { + "en": "Inglés", + "fr": "Francés", + "ru": "Ruso", + "es": "Español", + "pt": "Portugués", + "ar": "Árabe", + "tr": "Turco", + "vi": "Vietnamita", + "az": "Azerí", + "uk": "Ucraniano", + "id": "Indonesio" + }, + "calendar": { + "month": "Mes", + "week": "Semana", + "day": "Día", + "agenda": "Agenda", + "more": "más", + "users": "Usuarios", + "today": "Hoy", + "all": "Todas las tareas", + "completed": "Tareas completadas", + "pending": "Tareas pendientes", + "colored": "Coloridas", + "colorless": "Sin color", + "show_weekends": "Mostrar fines de semana", + "time_scale": "Escala de tiempo", + "from": "Desde", + "to": "Hasta", + "stage_select": "Etapa" + }, + "page_title": { + "system": { + "browser_not_supported": { + "page_title": "Navegador no compatible" + } + }, + "builder": { + "production": "Procesos de Producción", + "builder": "Constructor", + "workspace": "Tu Espacio de Trabajo", + "crm": "Constructor | CRM", + "site_form": "Constructor | Formulario de sitio", + "project_management": "Constructor | Proyectos y Tareas", + "product_management_for_sales": "Constructor | Gestión de Inventario", + "product_management_rentals": "Constructor | Gestión de Alquileres", + "scheduler": "Constructor | Planificación de citas", + "supplier_management": "Constructor | Proveedores", + "contractor_management": "Constructor | Contratistas", + "partner_management": "Constructor | Socios", + "hr_management": "Constructor | Contratación de Personal", + "contact": "Constructor | Contactos", + "company": "Constructor | Empresas", + "universal_module": "Constructor | Módulo Universal", + "products": { + "rental": "Constructor | Gestión de Alquileres", + "sale": "Constructor | Gestión de Inventario" + } + }, + "time_board": "Tablero de Tiempo", + "activities_board": "Tablero de Actividades", + "settings": { + "general_settings": "Configuración | Configuración General", + "user_list": "Configuración | Usuarios — Configuración de Usuarios", + "user_departments": "Configuración | Usuarios — Grupos", + "user_edit": "Configuración | Usuarios — Editar Usuario", + "calls": { + "account": "Configuración | Llamadas — Cuenta", + "users": "Configuración | Llamadas — Usuarios", + "scenarios": "Configuración | Llamadas — Configuración", + "sip_registrations": "Configuración | Llamadas — Registros SIP" + }, + "mailing": "Configuración | Configuración de Correo", + "integrations": "Configuración | Integraciones", + "billing": "Configuración | Facturación", + "api_access": "Configuración | Acceso a la API", + "superadmin": "Settings | Admin Panel", + "document": { + "templates": "Configuración | Documentos — Plantillas de Documentos", + "creation": "Configuración | Documentos — Campos de Creación de Documentos" + } + }, + "mailing": "Correo y Mensajería", + "orders": "Pedidos", + "notes": "Notas" + } +} diff --git a/frontend/public/locales/es/component.card.json b/frontend/public/locales/es/component.card.json new file mode 100644 index 0000000..b9a51a1 --- /dev/null +++ b/frontend/public/locales/es/component.card.json @@ -0,0 +1,344 @@ +{ + "card": { + "status": { + "active": "Activa", + "liquidating": "En liquidación", + "liquidated": "Liquidada", + "bankrupt": "Bancarrota", + "reorganizing": "En proceso de fusión con otra entidad jurídica, seguida de liquidación" + }, + "type": { + "legal": "Persona jurídica", + "individual": "Empresario individual" + }, + "branch_type": { + "main": "Oficina central", + "branch": "Sucursal" + }, + "opf": { + "bank": "Banco", + "bank_branch": "Sucursal bancaria", + "nko": "Organización de crédito no bancario (NKO)", + "nko_branch": "Sucursal NKO", + "rkc": "Centro de liquidación y caja", + "cbr": "Departamento del Banco de Rusia (marzo 2021)", + "treasury": "Departamento del Tesoro (marzo 2021)", + "other": "Otro" + }, + "days": ["día", "días", "días"], + "creation_date": "Creado", + "in_work": "En Trabajo", + "shipping_date": "Enviado", + "closing_date": "Cerrado", + "open_chat": "Abrir chat", + "analytics": "Analista", + "card_focus": "Enfocar (resalta la tarjeta en el tablero y la lista)", + "field_formula_circular_dependency_warning": { + "warning_title": "No se pueden guardar los cambios", + "warning_annotation": "Está intentando actualizar el campo de fórmula \"{{fieldName}}\" utilizando otra fórmula que crea una dependencia circular (una fórmula depende de otra fórmula que depende de ella). Por favor, actualice la configuración de la fórmula para resolver la dependencia circular e intente nuevamente.", + "cancel_changes": "Cancelar Cambios", + "continue": "Continuar" + }, + "field_used_in_formula_warning": { + "warning_title": "No se pueden guardar los cambios", + "warning_annotation": "Está intentando eliminar el campo \"{{fieldName}}\" que se usa en una fórmula. Elimínelo de la fórmula primero e intente nuevamente.", + "cancel_changes": "Cancelar Cambios", + "continue": "Continuar" + }, + "mutation_warning": { + "title": { + "change_stage": "No se puede cambiar la etapa", + "save_changes": "No se pueden guardar los cambios" + }, + "annotation": "Faltan algunos campos obligatorios. Por favor, complétalos e intente nuevamente.", + "continue": "Continuar" + }, + "change_linked_responsibles_modal": { + "header": "Cambio de responsable", + "annotation": "Selecciona las tarjetas vinculadas en las que también deseas cambiar el responsable", + "change_responsible_in_chats": "Agregar responsable a los chats de las tarjetas", + "chats_hint": "El nuevo responsable será agregado a los chats de las tarjetas vinculadas en las que aún no esté presente." + }, + "owner": "Propietario", + "delete_warning_title": "¿Realmente desea eliminar la entidad?", + "delete_warning_caption": "Esta acción no se puede deshacer.", + "files": "Archivos", + "from_card_or_linked_entities": "De la tarjeta o entidades vinculadas", + "recent": "Reciente", + "change_board": "Cambiar tablero", + "placeholders": { + "name": "Ingrese nombre..." + }, + "ui": { + "requisites_codes": { + "ie_name": "Nombre del Empresario Individual", + "ie_surname": "Apellido del Empresario Individual", + "ie_patronymic": "Segundo Nombre del Empresario Individual", + "org_name": "Nombre de la Empresa", + "org_tin": "NIF de la Empresa", + "org_trrc": "KPP de la Empresa", + "org_psrn": "PSRN", + "org_type": "Tipo de Empresa", + "org_full_name": "Nombre Completo de la Empresa", + "org_short_name": "Nombre Corto de la Empresa", + "org_management_name": "Nombre del Director", + "org_management_post": "Cargo del Director", + "org_management_start_date": "Fecha de Inicio del Director", + "org_branch_count": "Número de Sucursales", + "org_branch_type": "Tipo de Sucursal", + "org_address": "Dirección de la Empresa", + "org_reg_date": "Fecha de Registro", + "org_liquidation_date": "Fecha de Liquidación", + "org_status": "Estado de la Empresa", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Número Medio de Empleados", + "org_extra_founders": "Fundadores de la Empresa", + "org_extra_managers": "Directivos de la Empresa", + "org_extra_capital": "Capital Social", + "org_extra_licenses": "Licencias", + "org_extra_phones": "Números de Teléfono", + "org_extra_emails": "Direcciones de Correo Electrónico", + "bank_name": "Nombre del Banco", + "bank_bic": "BIC del Banco", + "bank_swift": "SWIFT", + "bank_tin": "NIF del Banco", + "bank_trrc": "KPP del Banco", + "bank_correspondent_acc": "Cuenta Corresponsal", + "bank_payment_city": "Ciudad para la Orden de Pago", + "bank_opf_type": "Tipo de Organización Bancaria", + "bank_checking_account": "Cuenta de Cheque" + }, + "analytics": "Analítica", + "requisites": "Requisitos", + "card_page_header": { + "overview": "Resumen", + "products": "Productos", + "create_new_order": "Crear nuevo pedido", + "orders": "Pedidos", + "board": "Tablero", + "list": "Lista", + "calendar": "Calendario", + "timeline": "Gantt", + "relocate_card_button": "Mover Tarjeta", + "order_denied": "No tiene permiso para crear un pedido. Póngase en contacto con el administrador de la cuenta para obtener acceso." + }, + "actions_dropdown": { + "delete_entity": "Eliminar entidad", + "fine_tune": "Ajustar" + }, + "mini_card": { + "enter_name": "Ingrese nombre de {{name}}", + "unpin": "Desanclar", + "owner": "Propietario", + "stage": "Etapa", + "placeholders": { + "select_stage": "Seleccione etapa" + }, + "fields": { + "value": "Valor", + "participants": "Participantes", + "start_date": "Fecha de Inicio", + "end_date": "Fecha de Fin", + "description": "Descripción" + } + }, + "stages": { + "disabled_while_adding": "El cambio de estado estará disponible después de crear la tarjeta" + }, + "feed": { + "unknown": "Desconocido 👤", + "readonly": "No puede agregar elementos a esta tarjeta", + "disabled_while_adding": "Agregar elementos a la tarjeta estará disponible después de crearla", + "notes": "Notas", + "activities": "Actividades", + "tasks": "Tareas", + "amwork_messenger": "Mensajero de {{company}}", + "documents": "Documentos", + "create_documents_tab": "Crear Documentos", + "create_chat": "Crear un Chat con el Equipo", + "add_visit": "Programar una Reunión", + "share_documents": "Compartir documentos generados", + "empty": "Aquí no hay nada todavía", + "add_activity": { + "placeholder": "Descripción de la actividad" + }, + "add_note": { + "add_note": "Agregar nota" + }, + "add_task_modal": { + "dates_error": "La fecha de fin debe ser posterior a la fecha de inicio.", + "tab": "Agregar Tarea", + "task_name": "Nombre de la Tarea *", + "planned_time": "Tiempo Estimado", + "board_name": "Nombre del Tablero", + "assignee": "Asignado a", + "start_date": "Fecha de Inicio", + "end_date": "Fecha de Fin", + "description": "Descripción", + "subtasks": "Subareas", + "placeholder": "Agregar título...", + "add_board": "Agregar tablero", + "save": "Guardar", + "new_task": "Nueva tarea", + "repeating_task": { + "title": "Repetir Tarea", + "hint": "Establezca la cantidad y el intervalo de tareas repetitivas, si es necesario. Las tareas se crearán con estas configuraciones. Si la tarea tiene hora de inicio o fin, se programarán respetando el intervalo y días laborales.", + "count": "Cantidad", + "interval": "Intervalo", + "interval_disabled_tooltip": "Establezca la hora de Inicio o la hora de finalización de la tarea", + "intervals": { + "none": "Sin intervalo", + "day": "Diario", + "week": "Semanal", + "month": "Mensual" + } + }, + "placeholders": { + "board": "Seleccione tablero", + "subtask": "Nueva subarea" + } + }, + "filter": { + "all": "Todo", + "tasks": "Tareas", + "activities": "Actividades", + "notes": "Notas", + "mail": "Correo", + "files": "Archivos", + "calls": "Llamadas" + }, + "create_documents": { + "order": "Pedido-{{number}}", + "documents": "Documentos", + "create_documents": "Crear Documentos", + "create_new_documents": "Crear nuevos documentos", + "select_all": "Seleccionar todo", + "clear_selection": "Limpiar selección", + "send_by_email": "Enviar por correo", + "add_template": "Agregar plantilla", + "invalid_template_title": "Error en la plantilla", + "invalid_template_annotation": "No se puede generar el documento debido a errores de sintaxis en la plantilla. Actualiza la plantilla o elige otra.", + "hints": { + "cancel": "Cancelar", + "generate": "Generar", + "generate_anyway": "Generar de todos modos", + "docx_format": "Formato DOCX", + "pdf_format": "Formato PDF", + "no_documents_title": "No hay plantillas disponibles", + "no_documents_annotation": "No hay plantillas de documentos disponibles para esta entidad. Cree nuevas o modifique las existentes en Documentos en la página de Configuración.", + "creating_documents_title": "Creando sus documentos", + "creating_documents_annotation": "Esto puede tardar un poco, pero después podrá enviar o descargar los documentos.", + "select_template_title": "Seleccione una plantilla", + "select_template_annotation": "Para seleccionar una plantilla, abra la lista desplegable y elija el archivo deseado.", + "format_title": "Formato", + "issues_identified": "Se han identificado varios problemas", + "invalid_tags_annotation": "Hay algunos códigos que parecen ser inválidos o están asociados con un campo vacío:", + "format_annotation": "Seleccione el(los) formato(s) en el que desea generar el documento." + }, + "placeholders": { + "select_order": "Seleccione pedido", + "select_template": "Seleccione plantilla", + "invalid_tags_annotation": "Algunos códigos son inválidos o están asociados con un campo vacío:" + } + }, + "activity_block": { + "creator": "Asignado a", + "start_date": "Fecha", + "plan_time": "Hora" + }, + "creator_tag": { + "task_setter_at_time": "{{taskSetter}} a las {{time}}" + }, + "files_block": { + "unknown_file": "Archivo desconocido", + "downloaded_file": "{{companyName}} – Archivo descargado", + "attachment": "adjunto", + "attachments": "adjuntos", + "download_all": "Descargar todo" + }, + "note_block": { + "block_title": "Nota", + "creation_date": "Fecha de creación", + "message": "Mensaje", + "to_recipient": "a {{recipient}}" + }, + "task_block": { + "creator": "Asignado a", + "start_date": "Fecha de Inicio", + "end_date": "Fecha de Fin", + "no_due_date": "Sin fecha límite" + }, + "mail_block": { + "sender": "Remitente", + "recipient": "Destinatario", + "date": "Fecha", + "seen": "Visto" + }, + "document_block": { + "creation_date": "Fecha de Creación", + "docs": "Docs", + "annotation": "{{creator}} creó {{documentName}} el {{date}}" + }, + "call_block": { + "start_date": "Fecha", + "who_called": "L’amante", + "who_got_call": "Receptor de la llamada", + "did_not_get_through": "No se pudo comunicar", + "time": { + "d": "d", + "h": "h", + "m": "m", + "s": "s" + }, + "status": { + "missed_call": "Llamada Perdida", + "incoming_call": "Llamada Entrante", + "outgoing_call": "Llamada Saliente" + }, + "comment": "Comentario" + }, + "common": { + "author": "Autor", + "reporter": "Reportero", + "created_at": "{{day}} a las {{time}}", + "result": "Resultado", + "functional_options": { + "edit": "Editar", + "delete": "Eliminar" + }, + "item_tag": { + "tasks": { + "resolved": "Tarea Completada", + "expired": "Tarea Vencida", + "future": "Tarea Futura", + "today": "Tarea de Hoy" + }, + "activities": { + "resolved": "Actividad Completada", + "expired": "Actividad Vencida", + "future": "Actividad Futura", + "today": "Actividad de Hoy" + } + }, + "activity_type_picker": { + "add_new_type": "Agregar nuevo tipo", + "warn_title": "¿Realmente desea eliminar este tipo de actividad?", + "warn_annotation": "Ya no podrá crear este tipo de actividad.", + "placeholders": { + "select_activity_type": "Seleccione tipo de actividad" + } + }, + "user_picker": { + "add": "Agregar", + "placeholder": "Seleccione usuario" + } + } + } + } + } +} diff --git a/frontend/public/locales/es/component.entity-board.json b/frontend/public/locales/es/component.entity-board.json new file mode 100644 index 0000000..9d4db84 --- /dev/null +++ b/frontend/public/locales/es/component.entity-board.json @@ -0,0 +1,23 @@ +{ + "entity_board": { + "ui": { + "project_entity_card_item": { + "start_date": "Fecha de Inicio", + "end_date": "Fecha de Fin", + "titles": { + "not_resolved": "Tareas no resueltas", + "upcoming": "Próximas tareas", + "overdue": "Tareas vencidas", + "today": "Tareas de hoy", + "resolved": "Tareas resueltas" + } + }, + "common_entity_card": { + "no_tasks": "Sin tareas", + "task_today": "Tarea de hoy", + "overdue_task": "Tarea vencida", + "upcoming_task": "Próxima tarea" + } + } + } +} diff --git a/frontend/public/locales/es/component.section.json b/frontend/public/locales/es/component.section.json new file mode 100644 index 0000000..d99f0ae --- /dev/null +++ b/frontend/public/locales/es/component.section.json @@ -0,0 +1,191 @@ +{ + "section": { + "section_header_with_boards": { + "board": "Tablero", + "list": "Lista", + "dashboard": "Tablero", + "reports": "Informes", + "timeline": "Gantt", + "automation": "Automatización", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Próximamente...)", + "tooltips": { + "reports_denied": "No tiene permiso para ver informes. Póngase en contacto con el administrador de la cuenta para obtener acceso.", + "dashboard_denied": "No tiene permiso para ver tablero. Póngase en contacto con el administrador de la cuenta para obtener acceso." + } + }, + "empty_projects_block": { + "annotation": "Todavía no tiene proyectos o no se ha encontrado nada con los filtros seleccionados." + }, + "common": { + "add_card": "Agregar tarjeta", + "new_stage": "Nueva Etapa", + "create_button_tooltip": { + "universal": "Agregar Tarjeta", + "partner": "Agregar Socio", + "deal": "Agregar Negocio", + "contact": "Agregar Contacto", + "company": "Agregar Empresa", + "supplier": "Agregar Proveedor", + "contractor": "Agregar Contratista", + "hr": "Agregar Candidato", + "project": "Agregar Proyecto" + }, + "cards_total": { + "universal": ["Tarjetas", "Tarjetas", "Tarjetas"], + "partner": ["Socios", "Socios", "Socios"], + "customer": ["Clientes", "Clientes", "Clientes"], + "deal": ["Tarjetas", "Tarjetas", "Tarjetas"], + "contact": ["Contactos", "Contactos", "Contactos"], + "company": ["Empresas", "Empresas", "Empresas"], + "supplier": ["Proveedores", "Proveedores", "Proveedores"], + "contractor": ["Contratistas", "Contratistas", "Contratistas"], + "hr": ["Candidatos", "Candidatos", "Candidatos"], + "project": ["Proyectos", "Proyectos", "Proyectos"] + }, + "filter_button": { + "ui": { + "filter_drawer": { + "closed_at": "Fecha de Cierre", + "just_my_cards": "Solo mis Tarjetas", + "sorting": "Ordenar", + "creation_date": "Fecha de Creación", + "owners": "Propietarios", + "stages": "Etapas", + "tasks": "Tareas", + "placeholders": { + "selected_options": "Opciones seleccionadas", + "search_by_card_name": "Buscar por nombre de tarjeta" + }, + "tasks_options": { + "all": "Todas las tarjetas", + "with_task": "Con tareas", + "without_task": "Sin tareas", + "overdue_task": "Con tareas vencidas", + "today_task": "Con tareas para hoy" + }, + "sorting_options": { + "manual": "Manual", + "created_asc": "Creado: de antiguo a nuevo", + "created_desc": "Creado: de nuevo a antiguo", + "name_asc": "Título: ascendente", + "name_desc": "Título: descendente" + }, + "field_filter_string": { + "is_empty": "Está vacío", + "is_not_empty": "No está vacío", + "contains": "Contiene...", + "placeholders": { + "value": "Valor" + } + }, + "field_filter_exists": { + "is_empty": "Está vacío", + "is_not_empty": "No está vacío" + } + }, + "date_period_picker": { + "date_period_segmented_control": { + "period": "Período", + "period_hint": "Para un período seleccionado", + "from": "Desde", + "from_hint": "Desde la fecha seleccionada", + "to": "Hasta", + "to_hint": "Antes de la fecha seleccionada" + }, + "placeholders": { + "select_period": "Seleccionar período" + } + }, + "field_filter_items": { + "field_filter_boolean": { + "switch_on": "Encender", + "switch_off": "Apagar" + }, + "field_filter_number": { + "from": "Desde", + "to": "Hasta" + } + } + } + }, + "settings_button": { + "module_settings": "Configuración del módulo", + "settings": "Configuraciones", + "import": "Importar", + "get_import_template": "Obtener plantilla de importación", + "board_settings": "Configuración del tablero", + "table_settings": "Configuración de la tabla", + "upload_file": "Subir archivo", + "sales_pipeline_settings": "Configuración del embudo de ventas", + "request_setup": "Solicitar configuración de {{company}}", + "import_entities_modal": { + "title": "Importar desde Excel", + "annotation": "Para importar su base de datos a {{company}}, siga unos sencillos pasos:", + "perform_import": "Realizar importación", + "setting_up_file_to_import": { + "title": "Configuración de un archivo para importar", + "step1": "Descargue un archivo de plantilla de ejemplo;", + "step2": "Ingrese sus datos en una tabla que coincida con la estructura de campos de {{company}};", + "step3": "Después de completar la configuración de la coincidencia de columnas, puede guardar la configuración como una plantilla de importación." + }, + "import_file": { + "title": "Importar archivo", + "step1": "Haga clic en \"Subir archivo\";", + "step2": "En la ventana que se abre, seleccione la plantilla previamente completada;", + "step3": "Después de descargar, haga clic en el botón \"Realizar importación\"." + } + }, + "import_in_background_info_modal": { + "title": "Importar desde Excel", + "content": "El proceso de importación se está ejecutando en segundo plano. Puede continuar trabajando en {{company}} como de costumbre, y le notificaremos una vez que se haya completado el proceso.", + "ok": "Aceptar" + } + }, + "search_box": { + "nothing_found": "No se encontró nada", + "placeholder": "Buscar..." + }, + "section_table_settings_sidebar": { + "table_settings": "Configuración de la tabla", + "display_columns": "Mostrar columnas" + } + }, + "section_table": { + "hidden": "Oculto", + "hidden_hint": "No puedes ver ni acceder a este campo porque fue ocultado en la configuración de campos del módulo por el administrador de la cuenta.", + "empty": "Aún no ha agregado ninguna tarjeta", + "all_columns_hidden": "Todas las columnas están ocultas en la configuración de la tabla", + "name": "Nombre", + "owner": "Propietario", + "stage": "Etapa", + "date_created": "Fecha de creación", + "value": "Valor", + "participants": "Participantes", + "start_date": "Fecha de inicio", + "end_date": "Fecha de fin", + "description": "Descripción", + "placeholders": { + "participants": "Agregar participantes" + }, + "select_all": { + "title": "Selección masiva", + "description": "¿Aplicar para todas las tarjetas ({{count}}) o solo para la página actual?", + "all_elements": "Para todas las tarjetas", + "for_page": "Sólo para la página actual" + }, + "batch_actions": { + "cards_selected": "Tarjetas seleccionadas: {{count}}", + "change_responsible": "Cambiar responsable", + "change_stage": "Cambiar etapa", + "delete": "Eliminar", + "change_stage_annotation": "Seleccione una nueva etapa para las tarjetas seleccionadas. Esta acción no se puede deshacer.", + "change_responsible_annotation": "Seleccione un nuevo usuario responsable para las tarjetas seleccionadas.", + "action_cannot_be_undone": "Esta acción no se puede deshacer.", + "delete_warning_title": "Eliminar tarjetas seleccionadas", + "delete_warning_annotation": "¿Está seguro de que desea eliminar las tarjetas seleccionadas? Esta acción no se puede deshacer.", + "select_linked_entities_annotation": "Cambiar el responsable en las tarjetas vinculadas" + } + } + } +} diff --git a/frontend/public/locales/es/module.automation.json b/frontend/public/locales/es/module.automation.json new file mode 100644 index 0000000..c0afb3d --- /dev/null +++ b/frontend/public/locales/es/module.automation.json @@ -0,0 +1,368 @@ +{ + "automation": { + "modals": { + "add_activity_automation_modal": { + "current_responsible_user": "Usuario responsable actual", + "activity_default_name": "Actividad #{{id}}", + "title_add": "Agregar Automatización de Creación de Actividad", + "title_update": "Actualizar Automatización de Creación de Actividad", + "create_activity": "Crear Actividad *", + "responsible_user": "Usuario Responsable *", + "due_date": "Fecha de Vencimiento", + "defer_start": "Fecha de Inicio", + "activity_type": "Tipo de Actividad *", + "description": "Descripción", + "current_responsible_user_hint": "Seleccione al usuario responsable de la actividad. El usuario responsable actual es el asignado a la tarjeta donde se creará la actividad.", + "placeholders": { + "select_activity_type": "Seleccionar tipo de actividad" + } + }, + "add_task_automation_modal": { + "task_default_name": "Tarea #{{id}}", + "title_add": "Agregar Automatización de Creación de Tarea", + "title_update": "Actualizar Automatización de Creación de Tarea", + "create_task": "Crear Tarea *", + "due_date": "Fecha de Vencimiento", + "defer_start": "Fecha de Inicio", + "responsible_user": "Usuario Responsable *", + "name_and_description": "Nombre * y Descripción", + "current_responsible_user": "Usuario responsable actual", + "current_responsible_user_hint": "Seleccione al usuario responsable de la tarea. El usuario responsable actual es el asignado a la tarjeta donde se creará la tarea.", + "placeholders": { + "task_name": "Nombre de la tarea", + "select_user": "Seleccionar usuario" + } + }, + "change_responsible_automation_modal": { + "change_responsible_default_name": "Responsable #{{id}}", + "title_add": "Agregar automatización de cambio de responsable", + "title_update": "Actualizar automatización de cambio de responsable", + "responsible_user": "Cambiar usuario responsable a *", + "placeholders": { + "select_responsible_user": "Seleccione el nuevo responsable" + } + }, + "change_stage_automation_modal": { + "change_stage_default_name": "Etapa #{{id}}", + "title_add": "Agregar Automatización de Cambio de Etapa", + "title_update": "Actualizar Automatización de Cambio de Etapa", + "change_stage": "Cambiar Etapa *", + "cards_copies": "Copias de Tarjetas", + "stage": "Embudo de Ventas y Etapa *", + "automation_copy": { + "move_label": "No crear una copia del negocio al cambiar su estado", + "copy_original_label": "Crear una copia del negocio y mover el negocio original al nuevo estado al cambiar su estado", + "copy_new_label": "Crear una copia del negocio al cambiar su estado, pero dejar el negocio original en su estado actual.", + "hint": "Cuando esta opción está habilitada, el sistema creará automáticamente una copia de la tarjeta en su estado actual con cada cambio de estado automático. Esto es esencial para una precisión en los informes y métricas de planificación de ventas, particularmente cuando el cambio de estado está asociado con estados finales del sistema como 'Completado con Éxito' o 'Completado sin Éxito'." + }, + "placeholders": { + "select_stage": "Seleccionar etapa" + } + }, + "change_linked_stage_automation_modal": { + "change_stage_default_name": "Etapa #{{id}}", + "title_add": "Agregar Automatización de Cambio de Etapa", + "title_update": "Actualizar Automatización de Cambio de Etapa", + "change_stage": "Cambiar Etapa *", + "cards_copies": "Copias de Tarjetas", + "entity_type": "Module *", + "stage": "Embudo de Ventas y Etapa *", + "no_stages": "El módulo seleccionado no tiene estado. Seleccione otro módulo para crear la automatización.", + "automation_copy": { + "move_label": "No crear una copia del negocio al cambiar su estado", + "copy_original_label": "Crear una copia del negocio y mover el negocio original al nuevo estado al cambiar su estado", + "copy_new_label": "Crear una copia del negocio al cambiar su estado, pero dejar el negocio original en su estado actual.", + "hint": "Cuando esta opción está habilitada, el sistema creará automáticamente una copia de la tarjeta en su estado actual con cada cambio de estado automático. Esto es esencial para una precisión en los informes y métricas de planificación de ventas, particularmente cuando el cambio de estado está asociado con estados finales del sistema como 'Completado con Éxito' o 'Completado sin Éxito'." + }, + "placeholders": { + "select_stage": "Seleccionar etapa" + } + }, + "send_email_automation_modal": { + "configure_mailbox_limits": "Configurar límites de buzón", + "max_number_of_emails_per_day": "Número máximo de correos electrónicos por día", + "send_email_default_name": "Correo electrónico #{{id}}", + "title_add": "Agregar Automatización de Email", + "title_update": "Actualizar Automatización de Email", + "title_hint": "Los mensajes de correo electrónico solo se enviarán a tarjetas de contacto que tengan un campo de Email completado.", + "email_text": "Texto del Email *", + "recipient_name": "- Nombre del destinatario", + "recipient_name_hint": "Puede establecer el marcador de posición {{contact_name}} en el texto del correo electrónico, y será reemplazado con el nombre del contacto de la tarjeta.", + "user_name": "- Nombre del usuario", + "user_phone": "- Teléfono del usuario", + "send_with_html": "Enviar correo electrónico con formato HTML", + "sender_email": "Correo electrónico del remitente *", + "sender_name": "Nombre del remitente *", + "send": "Enviar", + "emails_per_day": "correos electrónicos por día", + "emails_per_day_hint": "El parámetro de límite de correos electrónicos es necesario para evitar ser bloqueado por el proveedor del servicio de correo. Cada servicio de correo establece sus propios límites para el envío de mensajes. Por ejemplo, la versión básica de Google Workspace permite enviar 500 mensajes por día desde un buzón.", + "mailing_parameters": "Parámetros de Envío *", + "options": { + "addresses": "Direcciones de envío *", + "all_addresses": "Enviar a todas las tarjetas y todas las direcciones de e-mail", + "fine_tune_addresses": "Configurar...", + "main_entity": "Enviar a la tarjeta principal", + "main_entity_hint": "Seleccione si desea enviar el correo a todas las direcciones en la tarjeta principal o solo a la primera.", + "main_entity_all": "a todas las direcciones", + "main_entity_only_first": "solo a la primera dirección", + "contact": "Enviar a la tarjeta de contacto", + "contact_hint": "Seleccione si desea enviar el correo a todas las tarjetas de tipo 'Contacto' o solo a la primera, así como enviar a todas las direcciones en la tarjeta o solo a la primera.", + "contact_all_entities_all_values": "a todas las direcciones en todas las tarjetas de contactos", + "contact_all_entities_first_value": "solo a la primera dirección en todas las tarjetas de contactos", + "contact_first_entity_all_values": "a todas las direcciones solo en la primera tarjeta de contacto", + "contact_first_entity_first_value": "solo a la primera dirección en la primera tarjeta de contacto", + "company": "Enviar a la tarjeta de empresa", + "company_hint": "Seleccione si desea enviar el correo a todas las tarjetas de tipo 'Empresa' o solo a la primera, así como enviar a todas las direcciones en la tarjeta o solo a la primera.", + "company_all_entities_all_values": "a todas las direcciones en todas las tarjetas de empresas", + "company_all_entities_first_value": "solo a la primera dirección en todas las tarjetas de empresas", + "company_first_entity_all_values": "a todas las direcciones solo en la primera tarjeta de empresa", + "company_first_entity_first_value": "solo a la primera dirección en la primera tarjeta de empresa" + }, + "placeholders": { + "title": "Ingrese el título", + "email_text": "Ingrese el texto del correo electrónico", + "email_markup": "Ingrese el formato HTML", + "select_email_address": "Seleccionar dirección de correo electrónico", + "select_user": "Seleccionar usuario" + } + }, + "create_entity_automation_modal": { + "create_entity_default_name": "Tarjeta #{{id}}", + "title_add": "Crear automatización de tarjeta relacionada", + "title_update": "Actualizar automatización de tarjeta relacionada", + "create_card": "Crear tarjeta relacionada *", + "select_entity_type": "Módulo", + "select_entity_type_hint": "Seleccione el módulo en el que se creará la tarjeta. Para evitar bucles, la tarjeta no se puede crear en el módulo actual.", + "select_stage": "Tablero o embudo de ventas y estado", + "responsible_user": "Usuario responsable", + "responsible_user_hint": "Seleccione el usuario encargado de la tarea. El usuario responsable actual es quien gestiona la tarjeta donde se creará la tarea.", + "current_responsible_user": "Usuario responsable actual", + "entity_name": "Nombre de la tarjeta", + "placeholders": { + "select_entity_type": "Seleccione un módulo", + "entity_name": "Ingrese el nombre de la tarjeta" + } + }, + "send_internal_chat_automation_modal": { + "send_internal_chat_default_name": "Chat #{{id}}", + "title_add": "Crear automatización para mensaje en chat", + "title_update": "Actualizar automatización para mensaje en chat", + "sender": "Remitente", + "responsible_user_hint": "Seleccione el remitente del mensaje. El usuario actual responsable es el responsable de la tarjeta a partir de la cual se crea la automatización.", + "current_responsible_user": "Usuario responsable actual", + "recipients": "Destinatarios", + "message": "Texto del mensaje", + "placeholders": { + "recipients": "Seleccione usuarios", + "message": "Escriba el mensaje" + } + }, + "send_external_chat_automation_modal": { + "send_external_chat_default_name": "Chat externo #{{id}}", + "title_add": "Crear automatización de envío de chat externo", + "title_update": "Actualizar automatización de envío de chat externo", + "sender": "Remitente *", + "responsible_user_hint": "Seleccione al remitente del mensaje. El usuario responsable actual es quien está a cargo de la tarjeta desde donde se creó la automatización.", + "current_responsible_user": "Usuario responsable actual", + "provider": "Proveedor de chat *", + "message": "Mensaje *", + "send_to_chats": "Enviar a chats de tarjetas", + "send_to_phone_numbers": "Enviar a números de teléfono", + "add_phone_number": "Añadir número", + "hint_chats": "El mensaje se enviará a todos los chats externos de las tarjetas de este módulo según la configuración seleccionada", + "hint_phone_numbers": "El mensaje se enviará a los números seleccionados", + "phone_numbers_disabled": "El envío a través de números de Teléfono solo está disponible para los proveedores de Whatsapp y Telegram", + "placeholders": { + "message": "Escribe un mensaje" + }, + "errors": { + "phone": "El número de teléfono está vacío" + }, + "options": { + "addresses": "Chats de destino *", + "all_addresses": "Enviar a todas las tarjetas y chats externos", + "fine_tune_addresses": "Configurar...", + "main_entity": "Enviar a la tarjeta principal", + "main_entity_hint": "Elija si desea enviar el mensaje a todos los chats de la tarjeta principal o solo al primero.", + "main_entity_all": "a todos los chats", + "main_entity_only_first": "solo al primer chat", + "contact": "Enviar a la tarjeta de contacto", + "contact_hint": "Elija si desea enviar el mensaje a todas las tarjetas de tipo 'Contacto' o solo a la primera, y si se enviará a todos los chats en la tarjeta o solo al primero.", + "contact_all_entities_all_values": "a todos los chats en todas las tarjetas de contacto", + "contact_all_entities_first_value": "solo al primer chat en todas las tarjetas de contacto", + "contact_first_entity_all_values": "a todos los chats solo en la primera tarjeta de contacto", + "contact_first_entity_first_value": "solo al primer chat en la primera tarjeta de contacto", + "company": "Enviar a la tarjeta de empresa", + "company_hint": "Elija si desea enviar el mensaje a todas las tarjetas de tipo 'Empresa' o solo a la primera, y si se enviará a todos los chats en la tarjeta o solo al primero.", + "company_all_entities_all_values": "a todos los chats en todas las tarjetas de empresa", + "company_all_entities_first_value": "solo al primer chat en todas las tarjetas de empresa", + "company_first_entity_all_values": "a todos los chats solo en la primera tarjeta de empresa", + "company_first_entity_first_value": "solo al primer chat en la primera tarjeta de empresa" + } + }, + "request_http_automation_modal": { + "http_request_default_name": "Solicitud HTTP #{{id}}", + "title_add": "Crear automatización de solicitud HTTP", + "title_update": "Actualizar automatización de solicitud HTTP", + "http_request": "Parámetros de la solicitud HTTP", + "url": "URL *", + "method": "Método *", + "headers": "Encabezados", + "params": "Parámetros de consulta" + }, + "automation_modal_template": { + "apply_trigger_hint": "Cuando se selecciona esta opción, la automatización se aplicará a todas las tarjetas existentes en este estado. De lo contrario, solo se aplicará a las nuevas tarjetas que ingresen a este estado. Esta acción se puede realizar cada vez que se guarda la automatización.", + "apply_trigger_hint_list": "Cuando se selecciona esta opción, la automatización se aplicará a todas las tarjetas existentes. De lo contrario, solo se aplicará a las nuevas tarjetas. Esta acción se puede realizar cada vez que se guarda la automatización.", + "automation_name": "Nombre de la automatización *", + "trigger": "Disparador *", + "conditions": "Condiciones", + "delay": "Retraso", + "delay_one_stage_title": "Retraso vinculado al estado", + "delay_one_stage_hint": "La automatización se ejecutará solo si la tarjeta permanece en este estado hasta que termine el retraso. Si cambia de estado, no se activará.", + "delay_all_stages_title": "Retraso sin vinculación al estado", + "delay_all_stages_hint": "La automatización se ejecutará después del retraso, incluso si la tarjeta cambia de estado.", + "enable": "Habilitar automatización", + "enable_annotation": "Puede habilitar y deshabilitar la automatización", + "apply_trigger": "Aplicar el disparador a los objetos actuales en la etapa", + "apply_trigger_list": "Aplicar el disparador a los objetos actuales", + "delete_automation": "Eliminar automatización", + "on": "ENCENDIDO", + "placeholders": { + "name": "Nombre" + } + }, + "delete_automation_modal": { + "title": "¿Realmente desea eliminar esta automatización?", + "annotation": "Esta acción no se puede deshacer" + }, + "common": { + "use_get_chats_template_options": { + "recipient_name": "- Nombre del destinatario", + "recipient_name_hint": "Puedes establecer el marcador de posición {{contact_name}} en el texto del mensaje de chat, y se reemplazará con el nombre del contacto de la tarjeta." + }, + "trigger_select": { + "exact_time_of_automation": "Hora Exacta de Automatización", + "exact_time": "Hora exacta: {{day}} a las {{time}}", + "when_transitioning": "Al transicionar o crear", + "at_the_transition": "Al transicionar a la etapa", + "when_creating": "Al crear en la etapa", + "when_creating_list": "Al crear", + "when_responsible_changes": "Cuando cambia el usuario responsable", + "placeholder": "Seleccionar disparador" + }, + "conditions_block": { + "field": "Campo *", + "condition": "Condición *", + "entity_type_field_filter_string": { + "is_empty": "Está vacío", + "is_not_empty": "No está vacío", + "contains": "Contiene...", + "placeholders": { + "value": "Valor" + } + }, + "entity_type_field_filter_number": { + "placeholders": { + "from": "Desde", + "to": "Hasta" + } + }, + "entity_type_field_filter_boolean": { + "switch_on": "Encender", + "switch_off": "Apagar" + }, + "fields_conditions": "Condiciones de campos", + "add_field_condition": "Agregar condición de campo", + "responsible_users": "Usuarios responsables", + "responsible": "Responsable", + "budget": "Presupuesto", + "value": "Valor", + "from": "de", + "to": "a", + "placeholders": { + "selected_participants": "Participantes seleccionados", + "selected_options": "Opciones seleccionadas", + "add_condition": "Agregar condición", + "select_responsible": "Seleccionar responsables" + } + }, + "description_block": { + "placeholders": { + "description": "Descripción" + } + }, + "due_date_select": { + "due_date": "Fecha de vencimiento", + "options": { + "during_this_week": "Durante esta semana", + "in_3_days": "En 3 días", + "in_a_day": "En un día", + "end_of_the_day": "Fin del día", + "at_the_time_of_creation": "Al momento de la creación" + } + }, + "defer_start_select": { + "defer_start": "Fecha de Inicio", + "options": { + "in_an_hour": "En una hora", + "in_a_day": "En un día", + "in_3_days": "En 3 días" + } + }, + "delay_select": { + "no_delay": "Sin Retraso", + "5_minutes": "5 minutos", + "10_minutes": "10 minutos", + "15_minutes": "15 minutos", + "30_minutes": "30 minutos", + "1_hour": "1 hora" + }, + "template_list": { + "name": "Nombre", + "owner": "Responsable", + "tooltip": "Los códigos de campo serán reemplazados por los valores correspondientes de la tarjeta", + "show_templates": "Mostrar códigos de campo", + "hide_templates": "Ocultar códigos de campo" + } + } + }, + "external_providers_select": { + "add_new_provider": "Agregar proveedor", + "no_available_providers": "No hay proveedores disponibles" + }, + "sidebar": { + "title": "Automatización", + "create_task": "Tarea", + "task_create": "Tarea", + "create_activity": "Actividad", + "activity_create": "Actividad", + "change_stage": "Cambiar etapa", + "entity_stage_change": "Cambiar etapa", + "entity_linked_stage_change": "Cambiar etapa en la tarjeta vinculada", + "entity_responsible_change": "Cambiar responsable", + "send_email": "Correo", + "email_send": "Correo", + "chat_send_amwork": "Mensaje en chat", + "chat_send_external": "Mensaje en chat externo", + "entity_create": "Tarjeta relacionada", + "http_call": "Solicitud HTTP", + "automation": "Automatización" + }, + "block": { + "create_task": "Tarea", + "task_create": "Tarea", + "create_activity": "Actividad", + "activity_create": "Actividad", + "change_stage": "Cambiar etapa", + "entity_stage_change": "Cambiar etapa", + "entity_linked_stage_change": "Cambiar vinculada etapa", + "entity_responsible_change": "Responsable", + "send_email": "Correo", + "email_send": "Correo", + "entity_create": "Tarjeta", + "chat_send_amwork": "Chat", + "chat_send_external": "Chat externo", + "http_call": "Solicitud HTTP" + } + } +} diff --git a/frontend/public/locales/es/module.bpmn.json b/frontend/public/locales/es/module.bpmn.json new file mode 100644 index 0000000..76ea4ef --- /dev/null +++ b/frontend/public/locales/es/module.bpmn.json @@ -0,0 +1,174 @@ +{ + "bpmn": { + "hooks": { + "use_get_service_task_options": { + "actions": { + "task_create": "Crear Tarea", + "activity_create": "Crear Actividad", + "entity_stage_change": "Cambiar Fase", + "entity_linked_stage_change": "Cambiar etapa en la tarjeta vinculada", + "entity_responsible_change": "Cambiar Responsable", + "email_send": "Enviar Correo Electrónico", + "entity_create": "Crear Carta", + "chat_send_amwork": "Enviar Mensaje al Chat", + "chat_send_external": "Enviar Mensaje al Chat Externo", + "http_call": "Enviar solicitud HTTP" + } + }, + "use_bpmn_automations_columns": { + "name": "Nombre", + "created_by": "Creado Por", + "active": "Activo", + "delete": "Eliminar", + "status": "Estado" + }, + "use_get_workspace_event_options": { + "events": { + "create": "Evento de Tarjeta Creada", + "change_responsible": "Evento de Cambio de Responsable", + "change_stage": "Evento de Cambio de Fase" + } + } + }, + "pages": { + "bpmn_automations_page": { + "automation_process_schema_error_warning": { + "continue": "Continuar", + "title": "No se pudo activar el proceso \"{{name}}\"", + "annotation": "Falló la validación del esquema BPMN. Por favor, revise su proceso e inténtelo de nuevo." + }, + "service_task_popup_template": { + "task_name": "Nombre de la Tarea de Servicio", + "placeholders": { + "task_name": "Nombre" + } + }, + "workspace_sequence_flow_popup": { + "module": "Seleccionar Módulo", + "module_hint": "Seleccionar módulo donde se deben cumplir las condiciones especificadas", + "stage": "Seleccionar Fase", + "sequence_flow": "Flujo de Secuencia", + "flow_name": "Nombre del Flujo", + "conditions": "Condiciones", + "placeholders": { + "flow_name": "Nombre" + } + }, + "create_automation_process_modal": { + "create": "Crear", + "title": "Crear Nuevo Proceso de Automatización", + "name": "Nombre de la Automatización", + "new_process": "Nuevo Proceso #{{number}}", + "placeholders": { + "name": "Nombre" + } + }, + "bpmn_automations_processes_modeler": { + "add_conditions": "Agregar Condiciones...", + "center_view": "Vista Centrada", + "pallete": { + "create_workspace_parallel_gateway": "Crear Pasarela Paralela", + "create_workspace_start_event": "Crear Evento de Inicio", + "create_workspace_end_event": "Crear Evento de Fin", + "create_workspace_exclusive_gateway": "Crear Pasarela Exclusiva", + "create_workspace_data_object": "Crear Objeto de Datos", + "create_workspace_data_store": "Crear Almacén de Datos", + "create_workspace_participant_expanded": "Crear Participante Expandido", + "create_workspace_group": "Crear Grupo" + }, + "color_picker": { + "default_color": "Color Predeterminado", + "blue_color": "Color Azul", + "orange_color": "Color Naranja", + "green_color": "Color Verde", + "red_color": "Color Rojo", + "purple_color": "Color Púrpura" + }, + "context_pad": { + "delay": "Añadir Retraso", + "append_end_event": "Añadir Evento de Fin", + "append_gateway": "Añadir Pasarela", + "append_text_annotation": "Añadir Anotación de Texto", + "replace": "Reemplazar", + "delete": "Eliminar", + "set_color": "Establecer Color", + "connect": "Conectar", + "task_create": "Crear Tarea", + "activity_create": "Crear Actividad", + "entity_stage_change": "Cambiar Fase", + "entity_linked_stage_change": "Cambiar etapa en la tarjeta vinculada", + "entity_responsible_change": "Cambiar Responsable", + "email_send": "Enviar Correo Electrónico", + "entity_create": "Crear Carta", + "chat_send_amwork": "Enviar Mensaje al Chat", + "chat_send_external": "Enviar Mensaje al Chat Externo", + "http_call": "Enviar solicitud HTTP" + }, + "toggle_default_context_pad_entry": "Alternar Condición Predeterminada del Flujo", + "open_minimap": "Abrir Minimap", + "close_minimap": "Cerrar Minimap", + "activate_hand_tool": "Activar Herramienta Mano", + "activate_lasso_tool": "Activar Herramienta Lazo", + "active_create_remove_space_tool": "Activar Herramienta Crear/Eliminar Espacio", + "activate_global_connect_tool": "Activar Herramienta Conectar Global", + "process_controls": { + "save_and_run": "Guardar y Ejecutar", + "save_draft": "Guardar Borrador", + "start_process": "Iniciar Proceso", + "stop_process": "Detener Proceso", + "cancel": "Cancelar" + }, + "create_workspace_start_event": "Crear Evento de Inicio", + "create_workspace_delay_event": "Crear Evento de Retraso", + "create_workspace_service_task": "Crear Tarea de Servicio" + }, + "workspace_delay_event_popup": { + "delay": "Retraso", + "delay_label": "Retraso *", + "delay_name": "Nombre del Retraso", + "placeholders": { + "delay_name": "Nombre" + } + }, + "delete_bpmn_automation_warning_modal": { + "title": "¿Está seguro de que desea eliminar la Automatización BPMN \"{{name}}\"?", + "annotation": "Esta acción no se puede deshacer." + }, + "create_workspace_service_task_menu": { + "workspace_service_tasks": "Tareas de Servicio" + }, + "create_workspace_start_event_menu": { + "workspace_events": "Eventos" + }, + "workspace_start_event_popup": { + "event_name": "Nombre del Evento", + "event_type": "Seleccionar Tipo de Evento *", + "module": "Seleccionar Módulo *", + "module_hint": "Seleccionar módulo donde se activará el evento", + "placeholders": { + "event_name": "Nombre" + } + }, + "bpmn_automations_settings_header": { + "create_bpmn_automation": "Crear Automatización BPMN", + "bpmn_automations": "Automatizaciones BPMN 2.0", + "back": "Atrás" + }, + "bpmn_automations_table": { + "no_bpmn_automations_block": { + "annotation1": "Aún no tiene procesos", + "annotation2": "Utilice el", + "annotation3": "botón para añadir una automatización." + } + }, + "bpmn_splashscreen": { + "heading": "Automatización BPM Profesional", + "price_ru": "por 99 ₽/mes por usuario, pagando anualmente", + "price_us": "por $1/mes por usuario, pagando anualmente", + "form_button": "Solicitar conexión", + "form_header": "Activar automatización BPM" + } + } + } + } +} diff --git a/frontend/public/locales/es/module.builder.json b/frontend/public/locales/es/module.builder.json new file mode 100644 index 0000000..c7e3ecc --- /dev/null +++ b/frontend/public/locales/es/module.builder.json @@ -0,0 +1,867 @@ +{ + "builder": { + "components": { + "common": { + "next": "Siguiente", + "back": "Atrás", + "save": "Guardar", + "workspace_builder": "Constructor de Espacios de Trabajo", + "your_workspace": "Tu Espacio de Trabajo", + "no_links": "No hay módulos disponibles para enlazar." + } + }, + "pages": { + "site_form_builder_page": { + "steps": { + "save_and_leave": "Guardar y Salir", + "default_title": "Formulario en el sitio", + "default_form_title": "Contáctenos", + "default_consent_text": "Utilizamos cookies para mejorar el sitio. Las cookies ofrecen una experiencia más personalizada para ti y análisis web para nosotros. Consulta nuestra Política de privacidad para más detalles.", + "default_consent_link_text": "Política de privacidad", + "default_gratitude_header": "¡Gracias!", + "default_gratitude_text": "Nuestros gestores se pondrán en contacto contigo durante el horario laboral.", + "default_form_button_text": "Enviar", + "default_client_button_text": "Abrir formulario", + "common_error": "Rellena todos los campos obligatorios.", + "step1": { + "title": "Paso 1", + "description": "Configuración básica", + "error": "Rellena todos los campos obligatorios en el primer paso." + }, + "step2": { + "title": "Paso 2", + "description": "Campos del formulario", + "error": "Rellena todos los campos obligatorios en el segundo paso." + }, + "step3": { + "title": "Paso 3", + "description": "Política de datos y página de agradecimiento", + "consent_error": "Rellena todos los campos obligatorios en el bloque 'Política de privacidad' en el tercer paso." + }, + "step4": { + "title": "Paso 4", + "description": "Diseño del formulario" + }, + "step5": { + "title": "Paso 5", + "description": "Script de instalación del formulario" + } + }, + "site_form_builder_step1": { + "title": "Paso 1. Configuración del formulario", + "title_input_name": "Introduce el nombre del formulario", + "choose_main_card": "Elige la tarjeta principal que se creará después de enviar el formulario", + "choose_linked_cards": "Elige las tarjetas vinculadas que se crearán después de enviar el formulario", + "linked_entities_select": "Selecciona los módulos donde se crearán las fichas tras enviar el formulario desde tu sitio", + "choose_board_annotation": "Selecciona el tablero donde se creará la ficha", + "card_settings": "Configurar creación de tarjeta", + "responsible_user": "Usuario responsable", + "check_duplicates": "Evitar duplicados", + "check_duplicates_hint": "Si esta opción está activada, el envío del formulario con un email o teléfono ya registrado no creará una nueva tarjeta, sino que vinculará la existente.", + "yes": "Sí", + "placeholders": { + "title_input": "Nombre del formulario", + "responsible_select": "Responsable", + "board": "Tablero" + } + }, + "site_form_builder_step2": { + "sidebar": { + "title": "Elementos del formulario", + "header_title": "Encabezado del formulario", + "header_hint": "Solo puede haber uno.", + "delimiter_title": "Separador", + "delimiter_hint": "Línea para separar las secciones del formulario.", + "field_attributes": { + "title": "Atributos del campo", + "hint": "El título se muestra sobre el campo, la descripción dentro.", + "label_name": "Título", + "placeholder_name": "Descripción" + }, + "card_name_block": { + "card_title": "Nombre de la tarjeta", + "company_title": "Nombre de la empresa", + "contact_title": "Nombre completo", + "card_text": "Nombre", + "company_text": "Nombre", + "contact_text": "Nombre completo" + }, + "field_elements": { + "no_available_fields": "No hay campos disponibles", + "email": "Correo electrónico", + "phone": "Teléfono", + "short_text": "Texto corto", + "long_text": "Texto largo", + "number": "Número", + "value": "Valor", + "date": "Fecha", + "select": "Seleccionar", + "multiselect": "Selección múltiple", + "colored_select": "Selección de color", + "colored_multiselect": "Selección múltiple de color", + "link": "Enlace", + "file": "Fichero", + "switch": "Interruptor" + } + }, + "tree": { + "element_name": { + "placeholder": "Introduce el título del formulario" + }, + "entity_field_element": { + "placeholder": "Introduce la descripción del campo", + "template": { + "company_name": "Nombre", + "contact_name": "Nombre completo", + "card_name": "Nombre de la ficha", + "short_text": "Texto corto", + "long_text": "Texto largo", + "field": "Campo", + "delimiter": "Separador", + "file": "Archivo", + "schedule": "Seleccionar calendario", + "schedule_performer": "Seleccionar profesional", + "schedule_date": "Seleccionar fecha", + "schedule_time": "Seleccionar hora", + "placeholder": "Introduce el nombre del campo", + "required": "Campo obligatorio", + "validation": "Validación del campo", + "field_type": "Tipo de campo:", + "linked_with": "Relacionado con el módulo:" + } + } + } + }, + "site_form_builder_step3": { + "show_in_form_control": { + "yes": "Sí" + }, + "consent_block": { + "title": "Política de tratamiento de datos", + "controls": { + "show_consent_title": "Mostrar política de privacidad", + "text_title": "Texto de la política de privacidad", + "text_hint": "El texto se mostrará al final del formulario.", + "link_text_title": "Texto del enlace", + "link_text_hint": "Texto que se muestra en el enlace", + "link_title": "Enlace", + "link_hint": "Introduce la URL de la política de privacidad en formato https://example.com/.", + "consent_by_default_title": "Casilla marcada por defecto", + "placeholders": { + "text": "Texto de la política de privacidad", + "link_text": "Introduce el texto del enlace", + "link": "Introduce el enlace" + } + }, + "skeleton": { + "button_text": "Enviar" + } + }, + "gratitude_block": { + "title": "Página de agradecimiento", + "show_gratitude_title": "Mostrar página de agradecimiento", + "gratitude_text": "Texto de agradecimiento", + "placeholders": { + "header": "Introduce el título", + "text": "Introduce el texto de agradecimiento" + } + } + }, + "site_form_builder_step4": { + "sidebar": { + "custom_css_block": { + "custom_css": "CSS personalizado", + "enable_custom_css": "Activar CSS personalizado", + "placeholders": { + "custom_css": "Reglas CSS personalizadas" + } + }, + "advanced_settings": "Configuración avanzada", + "modal_overlay_customization_block": { + "modal_overlay": "Superposición modal", + "overlay_display": "Mostrar superposición", + "background_color": "Color de fondo", + "opacity": "Opacidad" + }, + "switch_label": "Activar", + "title": "Diseño del formulario", + "description": "Los cambios entrarán en vigor después del guardado.", + "powered_by_logo_block": { + "title": "{{company}} Logo", + "switch_label": "Activar" + }, + "header_customization_block": { + "title": "Encabezado", + "text_color": "Color del texto", + "text_align_title": "Alineación", + "text_align_left": "A la izquierda", + "text_align_center": "Centrado", + "text_align_right": "A la derecha" + }, + "fields_customization_block": { + "title": "Campos", + "background_color": "Color de fondo", + "title_color": "Color del título", + "text_color": "Color del texto en los campos" + }, + "form_button_customization_block": { + "title": "Botón", + "background_color": "Color del botón", + "text_color": "Color del texto", + "border": "Esquinas redondeadas", + "border_rounded": "Redondeado", + "border_squared": "Cuadrado", + "size": "Tamaño del botón", + "small": "Pequeño", + "medium": "Mediano", + "large": "Grande", + "text_input": "Texto del botón", + "hint": "Botón de envío, ubicado al final del formulario", + "placeholders": { + "text_input": "Enviar" + } + }, + "client_button_customization_block": { + "title": "Botón para el sitio web", + "background_color": "Color del botón", + "text_color": "Color del texto del botón", + "border": "Bordes", + "border_rounded": "Redondeados", + "border_squared": "Cuadrados", + "size": "Tamaño del botón", + "small": "Pequeño", + "medium": "Mediano", + "large": "Grande", + "text_input": "Texto del botón", + "hint": "Botón que abre una ventana modal con el formulario", + "placeholders": { + "text_input": "Abrir" + } + }, + "form_layout_customization_block": { + "title": "Formulario", + "view_title": "Vista", + "view_built_in": "Integrado", + "view_modal": "Ventana emergente", + "position_title": "Posición del formulario", + "position_left": "A la izquierda", + "position_center": "Centrado", + "position_right": "A la derecha", + "border_radius_title": "Redondeo de bordes", + "border_rounded": "Redondeados", + "border_none": "Sin redondeo", + "orientation_title": "Orientación", + "orientation_horizontal": "Horizontal", + "orientation_vertical": "Vertical", + "max_width": "Ancho máximo", + "max_height": "Altura máxima", + "background_color": "Color de fondo" + }, + "size_customization_element_template": { + "switch_label": "Activar", + "placeholders": { + "input": "Introduce el valor" + } + } + }, + "preview": { + "iframe_title": "{{companyName}} – Vista previa de formulario", + "error": "Error al cargar la vista previa", + "open_in_a_new_tab": "Abrir en una nueva pestaña", + "open_preview": "Abrir vista previa", + "phone": "Teléfono", + "tablet": "Tableta", + "computer": "Ordenador", + "form": "Formulario", + "gratitude": "Página de agradecimiento", + "button": "Botón" + } + }, + "site_form_builder_step5": { + "title": "Paso 5. Copie el código del formulario para su sitio", + "copy_form_code": "Copiar código del formulario", + "form_code": "Código del formulario", + "multiform_mode": "Modo de compatibilidad múltiple", + "multiform_mode_hint": "Active este modo si en la página donde planea insertar el formulario ya hay otro formulario creado con nuestro constructor. Esta configuración evitará conflictos de visualización.", + "form_will_be_mounted_here": "El formulario se integrará aquí", + "public_link_title": "Página pública del formulario", + "public_link_annotation": "Utiliza el enlace abajo para acceder a la página pública de tu formulario. Si no necesitas incrustarlo en tu sitio web, comparte esta página con tus usuarios para que puedan completarlo. El formulario ya está configurado y listo, y se actualizará automáticamente con cualquier cambio.", + "instruction_title": "Instrucciones para integrar el formulario en su sitio", + "view_form": "Ver formulario", + "static_site_integration_title": "Integración en sitios estáticos y sitios creados con constructores", + "first_point": { + "title": "1. Añadir un formulario mediante una clase CSS", + "annotation": "Asigne la clase {{mountClass}} a los elementos de su sitio donde desea integrar el formulario. Pegue el código del formulario copiado arriba en la etiqueta o al final de la etiqueta . El formulario se mostrará dentro de los elementos con la clase especificada. Si el elemento ya tiene contenido, el formulario aparecerá al final de ese contenido. Si eligió mostrar el formulario como una ventana emergente, se integrará un botón en el lugar indicado, y al hacer clic se abrirá una ventana emergente con el formulario. Este es el método preferido para insertar el formulario en el sitio." + }, + "second_point": { + "title": "2. Añadir un formulario sin clases CSS", + "annotation": "Si no puede asignar una clase a los elementos, inserte el código del formulario en el lugar de su sitio donde desea que se muestre. El código del formulario se puede incrustar en cualquier lugar de su sitio. En este caso, el formulario se mostrará en el lugar donde se haya insertado el código. Este método permite incrustar un solo formulario por página." + }, + "ssr_integration_title": "Integración en sitios SSR (por ejemplo, creados con Next.js, Astro, etc.)", + "ssr_integration_annotation": "Para evitar problemas con la hidratación (carga de scripts en el marcado HTML), se recomienda utilizar el método de especificar la clase workspace-form-builder-mount-container para el elemento donde desea integrar el formulario (descrito con más detalle en el punto 2 anterior). En este caso, el script del formulario debe colocarse preferiblemente en la etiqueta .", + "analytics": { + "title": "Seguimiento de eventos y objetivos", + "annotation": "El formulario permite el envío de eventos y objetivos a sistemas de análisis. Si tienes instalado Google Analytics o Yandex.Metrika en tu sitio, puedes rastrear las interacciones de los usuarios con el formulario. Para hacerlo, configura los eventos u objetivos en tu sistema de análisis y vincúlalos con los identificadores listados en la tabla.", + "table_title": "Eventos Rastreados", + "event_title": "Nombre", + "event_id": "ID del Objetivo", + "form_view": "Vista del Formulario", + "form_start": "Inicio de Relleno del Formulario", + "field_fill": "Relleno del Campo “{{field}}”", + "form_submit": "Envío del Formulario" + } + } + }, + "headless_site_form_builder_page": { + "steps": { + "save_and_leave": "Guardar y salir", + "default_title": "Formulario en el sitio para integración vía API", + "common_error": "Complete todos los campos obligatorios.", + "step1": { + "title": "Paso 1", + "description": "Configuración básica", + "error": "Complete todos los campos obligatorios en el paso 1." + }, + "step2": { + "title": "Paso 2", + "description": "Campos del formulario" + }, + "step3": { + "title": "Paso 3", + "description": "Instrucciones para integrar el formulario" + } + }, + "site_form_builder_step3": { + "title": "Paso 3. Integra el formulario en tu sitio", + "json_integration_title": "Envío de datos en formato JSON", + "api_integration_instruction": "Este método es adecuado si procesa los formularios en su propio servidor. Para enviar datos de usuario desde su formulario a {{company}}, debe realizar una solicitud con los parámetros indicados a continuación. En el cuerpo de la solicitud, incluya los datos del usuario en formato JSON. Un ejemplo del cuerpo de la solicitud se presenta a continuación.", + "api_integration_request_annotation": "Método: POST\nEncabezado: Content-Type: application/json\nURL: {{endpoint}}", + "request_parameters_title": "Parámetros de la solicitud", + "request_body_title": "Cuerpo de la solicitud", + "field_value_explanation": "En el campo value, introduce el valor ingresado por el usuario. Los formatos de valores para cada campo se indican a continuación.", + "field_value_title": "Valores de los campos", + "field_value": "Valor del campo {{label}}", + "response_title": "Formato de respuesta", + "response_explanation": "Si el formulario se envía con éxito, el servidor responde con el código HTTP 201.", + "formdata_integration_title": "Envío de datos en formato FormData", + "formdata_integration_instruction": "Este método es adecuado para enviar formularios desde sitios creados con herramientas como Tilda. Los datos se envían en formato x-www-form-urlencoded. Para un envío correcto, simplemente especifique la URL del webhook a continuación y asigne variables a los campos.", + "formdata_webhook_url": "URL del webhook", + "formdata_fields_title": "Variables de los campos", + "formdata_fields_explanation": "Cada campo del formulario debe tener un nombre (variable) para asociarlo con el campo correspondiente en el sistema. En este caso, las variables de los campos son sus identificadores numéricos. Para enviar los datos correctamente, asigne a cada campo del formulario la variable indicada en la tabla a continuación.", + "fields_table": { + "field_title": "Nombre del campo", + "field_format": "Formato del campo", + "field_example": "Ejemplo", + "field_id": "Variable (ID del campo)", + "format": { + "text": "Cadena de texto entre comillas", + "link": "Cadena de texto sin espacios entre comillas", + "phone": "Cadena de texto sin espacios entre comillas", + "email": "Cadena de texto sin espacios entre comillas", + "value": "Cadena de texto entre comillas", + "select": "Número identificador del valor seleccionado por el usuario", + "switch": "Valor booleano sin comillas", + "number": "Número sin comillas", + "multiselect": "Array de números identificadores de los valores seleccionados por el usuario", + "date": "Cadena con fecha en formato ISO 8601 entre comillas" + }, + "example": { + "text": "\"Valor ingresado por el usuario\"", + "link": "\"https://example.com\"", + "phone": "\"12345678910\"", + "email": "\"form@example.com\"", + "value": "\"Valor ingresado por el usuario\"", + "select": "34", + "switch": "true", + "number": "42", + "multiselect": "[26, 27]", + "date": "\"2024-12-31T00:00:00\"" + } + } + } + }, + "online_booking_site_form_builder_page": { + "steps": { + "save_and_leave": "Guardar y salir", + "default_title": "Formulario de reserva", + "default_form_title": "Reserva en línea", + "default_consent_text": "Usamos cookies para mejorar el sitio web. Las cookies nos ayudan a brindarte una experiencia más personalizada y a obtener análisis web. Consulta nuestra Política de privacidad para más detalles.", + "default_consent_link_text": "Política de privacidad", + "default_gratitude_header": "¡Gracias!", + "default_gratitude_text": "Nuestros agentes te contactarán para confirmar tu reserva.", + "default_form_button_text": "Enviar", + "default_client_button_text": "Abrir formulario", + "schedule_field_title": "Calendario", + "schedule_field_placeholder": "Selecciona un calendario...", + "schedule_performer_field_title": "Profesional", + "schedule_performer_field_placeholder": "Selecciona un profesional...", + "schedule_date_field_title": "Fecha", + "schedule_date_field_placeholder": "Selecciona una fecha...", + "schedule_time_field_title": "Hora", + "schedule_time_field_placeholder": "Selecciona una hora...", + "common_error": "Completa todos los campos obligatorios.", + "step1": { + "title": "Paso 1", + "description": "Configuración básica", + "error": "Completa todos los campos obligatorios en el primer paso." + }, + "step2": { + "title": "Paso 2", + "description": "Campos del formulario", + "error": "Completa todos los campos obligatorios en el segundo paso." + }, + "step3": { + "title": "Paso 3", + "description": "Política de privacidad y página de agradecimiento", + "consent_error": "Completa todos los campos obligatorios en la sección de «Política de privacidad» del tercer paso." + }, + "step4": { + "title": "Paso 4", + "description": "Diseño del formulario" + }, + "step5": { + "title": "Paso 5", + "description": "Código de instalación del formulario" + } + }, + "online_booking_site_form_builder_step1": { + "title": "Paso 1. Configuración del formulario", + "title_input_name": "Ingresa el nombre del formulario", + "choose_schedulers": "Selecciona los calendarios donde se crearán las reservas", + "choose_linked_cards": "Selecciona las tarjetas vinculadas que se crearán después de la reserva", + "linked_entities_select": "Elige en qué módulos se crearán tarjetas después del envío del formulario", + "choose_board_annotation": "Selecciona el tablero donde se crearán las tarjetas", + "no_schedules": "Aún no has creado calendarios de reservas. Para habilitar el formulario de reserva en línea, crea el módulo «Calendario de reservas» y configura las opciones necesarias.", + "no_linked_cards": "No hay módulos disponibles para crear tarjetas vinculadas. Para establecer una relación, selecciona un calendario vinculado a un módulo. Si seleccionaste varios calendarios, asegúrate de que todos estén vinculados al mismo módulo.", + "limit_days_name": "Límite de días para reservas", + "card_settings": "Configurar creación de tarjeta", + "responsible_user": "Usuario responsable", + "check_duplicates": "Evitar duplicados", + "check_duplicates_hint": "Si esta opción está activada, el envío del formulario con un email o teléfono ya registrado no creará una nueva tarjeta, sino que vinculará la existente.", + "yes": "Sí", + "placeholders": { + "title_input": "Nombre del formulario", + "responsible_select": "Responsable", + "board": "Tablero", + "limit_days": "De 1 a 730" + } + } + }, + "workspace_editor_page": { + "edit": "Editar", + "delete": "Eliminar", + "warn_title": "¡Atención!", + "warn_annotation": "Eliminar el módulo eliminará permanentemente toda la información relacionada. ¿Está seguro de que desea continuar?", + "open_module": "Abrir módulo", + "entity_type_field_used_in_formula_warning_modal": { + "title": "No se pudo eliminar este módulo", + "annotation": "Algunos campos en este módulo están vinculados a una fórmula en otro módulo. Por favor, actualice la configuración de la fórmula y vuelva a intentarlo.", + "continue": "Continuar" + } + }, + "scheduler_builder_page": { + "steps": { + "save_error": "Especifique el nombre del módulo y seleccione al menos un usuario en el primer paso.", + "step1": { + "label": "1er paso", + "description": "Configurar un nuevo módulo" + }, + "step2": { + "label": "2do paso", + "description": "Vincular el programador a otro módulo en tu espacio de trabajo" + }, + "step3": { + "label": "3er paso", + "description": "Integrar el programador en el módulo de productos en tu espacio de trabajo" + }, + "default_title": "Programador de Reuniones" + }, + "scheduler_builder_step1": { + "title": "Personalizar el módulo", + "name_the_module": "Nombre del Módulo", + "name_the_module_hint": "Este nombre se mostrará en la barra lateral izquierda. Podrás cambiarlo en el futuro.", + "choose_icon": "Elige un ícono para la barra lateral izquierda", + "choose_icon_hint": "Este ícono se mostrará en la barra lateral izquierda. Podrás cambiarlo en el futuro.", + "for_users": "Para usuarios", + "for_user_groups": "Para grupos de usuarios", + "view_type": "Selecciona el tipo de vista del Programador de Reuniones", + "view_type_hint": "Selecciona el tipo de vista – Vista de Tiempo Lateral (Vista de Tiempo Vertical) o Vista de Tiempo Superior (Vista de Tiempo Horizontal).", + "schedule": "Vista de Tiempo Lateral", + "schedule_img_alt": "{{company}} – Vista previa de la interfaz de programación", + "board": "Vista de Tiempo Superior", + "board_img_alt": "{{company}} – Vista previa de la interfaz de tablero", + "enter_the_interval": "Ingresa el intervalo", + "maximum_number_of_records": "Número máximo de registros", + "error": "Por favor, complete todos los campos requeridos.", + "change_anyway": "Cambiar de todos modos", + "warning_title": "¿Está seguro de que desea cambiar este parámetro?", + "warning_annotation": "Todas las citas existentes serán eliminadas.", + "for_whom": "Selecciona usuarios o grupos de usuarios", + "for_whom_hint": "Selecciona el tipo de vista del programador – para usuarios o grupos de usuarios.", + "no_duration": "No", + "minutes": "minutos", + "schedule_params_title": "Agenda y reservas online", + "schedule_params_hint": "Configura la disponibilidad y los parámetros de reuniones para el calendario. Se usarán para mostrar la agenda y gestionar las reservas online.", + "time_buffer_before": "Tiempo de pausa antes", + "time_buffer_after": "Tiempo de pausa después", + "intervals_source": "Usar disponibilidad de", + "performers_intervals": "usuarios", + "scheduler_intervals": "este calendario", + "placeholders": { + "interval": "Intervalo", + "unlimited": "Ilimitado", + "module_name": "Nombre del módulo" + }, + "change_type_warning": { + "title": "¡Atención!", + "annotation": "Cambiar el tipo de calendario eliminará permanentemente todos los registros de visitas. ¿Está seguro de que desea continuar?", + "approve": "Confirmar" + }, + "change_performers_warning": { + "title": "¡Atención!", + "annotation": "Eliminar un responsable del calendario eliminará permanentemente todos sus registros de visitas. ¿Está seguro de que desea continuar?", + "approve": "Confirmar" + } + }, + "scheduler_builder_step2": { + "title": "Vincular a otro módulo", + "do_not_link": "No vincular a otro módulo", + "duplicate_title": "Prohibir duplicados de reuniones", + "duplicate_hint": "Al activar esta opción, se evitará programar varias reuniones para la misma tarjeta en un mismo día en el calendario." + }, + "scheduler_builder_step3": { + "title": "Integrar con el módulo de productos", + "do_not_integrate": "No integrar con el módulo de productos" + } + }, + "products_section_builder_page": { + "delay_select": { + "no_return": "No devolver", + "no_return_minified": "No devolver", + "24_hours": "24 horas", + "24_hours_minified": "24h", + "48_hours": "48 horas", + "48_hours_minified": "48h", + "72_hours": "72 horas", + "72_hours_minified": "72h", + "7_days": "7 días", + "7_days_minified": "7d", + "10_days": "10 días", + "10_days_minified": "10d", + "14_days": "14 días", + "14_days_minified": "14d", + "28_days": "28 días", + "28_days_minified": "28d", + "30_days": "30 días", + "30_days_minified": "30d" + }, + "steps": { + "save_common_error": "Error al guardar. Faltan algunos pasos con información requerida.", + "save_warehouses_error": "Necesitas crear al menos un almacén para continuar. Alternativamente, puedes deshabilitar los almacenes.", + "step1": { + "label": "1er paso", + "description": "Configurar un nuevo módulo" + }, + "step2": { + "label": "2do paso", + "description": "Crear categorías de productos (grupos de productos)" + }, + "step3": { + "label": "3er paso", + "description": "Configurar almacenes para tus productos" + }, + "step4": { + "rentals": { + "label": "4to paso", + "description": "Configurar ajustes de programación para tus productos de alquiler" + }, + "sales": { + "label": "4to paso", + "description": "Vincular a otros módulos en tu espacio de trabajo" + } + }, + "step5": { + "label": "5to paso", + "description": "Vincular a otros módulos en tu espacio de trabajo" + }, + "default_titles": { + "sale": "Gestión de Almacenes y Productos", + "rental": "Gestión de Alquileres" + } + }, + "product_section_builder_step1": { + "title": "Personalizar el módulo", + "name_the_section": "Nombre del Módulo", + "name_the_section_hint": "Este nombre se mostrará en la barra lateral izquierda. Podrás cambiarlo en el futuro.", + "choose_icon": "Elige un ícono para la barra lateral izquierda", + "choose_icon_hint": "Este ícono se mostrará en la barra lateral izquierda. Podrás cambiarlo en el futuro.", + "error": "Se debe especificar el nombre del módulo.", + "placeholders": { + "section_name": "Nombre del módulo" + } + }, + "product_section_builder_step3": { + "cancel_after_label": "Establecer el período para la liberación automática de reservas de artículos en pedidos", + "cancel_after_hint": "Puedes configurar la cancelación automática de reservas de artículos en pedidos. Esto ayuda a prevenir niveles de inventario inexactos, que pueden ocurrir cuando los artículos están reservados en negocios pero estos negocios no se procesan o han sido cancelados.", + "update_error": "No se pudo guardar su elección. Inténtelo de nuevo.", + "warehouses_error": "Necesitas crear al menos un almacén para continuar. Alternativamente, puedes deshabilitar los almacenes.", + "enable_warehouses_label": "Habilitar almacenes", + "enable_warehouses_hint": "Habilita almacenes para rastrear el stock de tus productos. Si esta opción está habilitada, necesitarás crear al menos un almacén para continuar.", + "enable_barcodes_label": "Habilitar códigos de barras", + "enable_barcodes_hint": "Habilita códigos de barras para rastrear el stock de tus productos. Esta opción te permitirá escanear los códigos de barras de los productos.", + "on": "ENCENDIDO" + }, + "products_section_builder_link_sections_step": { + "title": "Vincular a otros módulos", + "cards": "Tarjetas", + "schedulers": "Programadores", + "do_not_integrate": "No integrar con el módulo de programadores" + }, + "products_section_builder_schedule_settings_step": { + "title": "Configuración de programación", + "rental_duration_interval": "Intervalo de duración del alquiler", + "rental_duration_interval_hint": "Selecciona el intervalo para la duración del alquiler.", + "start_of_rental_interval": "Inicio del intervalo de alquiler", + "start_of_rental_interval_hint": "Especifica la hora cuando comienza el alquiler.", + "twenty_four_hours": "24 horas" + } + }, + "et_section_builder_page": { + "steps": { + "requisites_codes": { + "ie_name": "Nombre del Empresario Individual", + "ie_surname": "Apellido del Empresario Individual", + "ie_patronymic": "Segundo Nombre del Empresario Individual", + "org_name": "Nombre de la Empresa", + "org_tin": "NIF de la Empresa", + "org_trrc": "KPP de la Empresa", + "org_psrn": "PSRN", + "org_type": "Tipo de Empresa", + "org_full_name": "Nombre Completo de la Empresa", + "org_short_name": "Nombre Corto de la Empresa", + "org_management_name": "Nombre del Director", + "org_management_post": "Cargo del Director", + "org_management_start_date": "Fecha de Inicio del Director", + "org_branch_count": "Número de Sucursales", + "org_branch_type": "Tipo de Sucursal", + "org_address": "Dirección de la Empresa", + "org_reg_date": "Fecha de Registro", + "org_liquidation_date": "Fecha de Liquidación", + "org_status": "Estado de la Empresa", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Número Medio de Empleados", + "org_extra_founders": "Fundadores de la Empresa", + "org_extra_managers": "Directivos de la Empresa", + "org_extra_capital": "Capital Social", + "org_extra_licenses": "Licencias", + "org_extra_phones": "Números de Teléfono", + "org_extra_emails": "Direcciones de Correo Electrónico", + "bank_name": "Nombre del Banco", + "bank_bic": "BIC del Banco", + "bank_swift": "SWIFT", + "bank_tin": "NIF del Banco", + "bank_trrc": "KPP del Banco", + "bank_correspondent_acc": "Cuenta Corresponsal", + "bank_payment_city": "Ciudad para la Orden de Pago", + "bank_opf_type": "Tipo de Organización Bancaria", + "bank_checking_account": "Cuenta de Cheque" + }, + "save_error": "Error al guardar. Faltan algunos pasos con información requerida.", + "details": "Detalles", + "analytics": "Analista", + "requisites": "Requisitos", + "step1": { + "label": "1er paso", + "description": "Configurar un nuevo módulo" + }, + "step2": { + "label": "2do paso", + "description": "Elige nombre y crea campos para tu nueva tarjeta" + }, + "step3": { + "label": "3er paso", + "description": "Agregar funcionalidad a la tarjeta" + }, + "step4": { + "label": "4to paso", + "description": "Vincular a otros módulos en tu espacio de trabajo" + }, + "default_titles": { + "builder": "Constructor", + "project": "Proyectos y Tareas", + "production": "Producción", + "universal": "Módulo Universal", + "partner": "Socios", + "deal": "CRM", + "contact": "Contactos", + "company": "Empresas", + "supplier": "Proveedores", + "contractor": "Contratistas", + "hr": "Contratación de Personal" + } + }, + "tasks_fields_options": { + "planned_time": "Tiempo Estimado", + "board_name": "Nombre del Tablero", + "assignee": "Asignado a", + "start_date": "Fecha de Inicio", + "end_date": "Fecha de Fin", + "description": "Descripción", + "subtasks": "Subareas" + }, + "et_section_builder_step1": { + "title": "Personalizar el módulo", + "name_the_section": "Nombre del Módulo", + "name_the_section_hint": "Este nombre se mostrará en la barra lateral izquierda. Podrás cambiarlo en el futuro.", + "choose_icon": "Elige un ícono para la barra lateral izquierda", + "choose_icon_hint": "Este ícono se mostrará en la barra lateral izquierda. Podrás cambiarlo en el futuro.", + "type_of_display": "Tipo de visualización", + "type_of_display_hint": "Selecciona el tipo de interfaz – lista o tablero/embudo o ambos.", + "board": "Tablero", + "board_img_alt": "{{company}} – Vista previa de la interfaz de tablero", + "list": "Lista", + "list_img_alt": "{{company}} – Vista previa de la interfaz de lista", + "placeholders": { + "section_name": "Nombre del módulo" + } + }, + "et_section_builder_step2": { + "title": "Configurar los parámetros de la nueva tarjeta", + "name_the_card": "Nombre de la Tarjeta", + "name_the_card_hint": "Nombre de la Tarjeta.", + "fine_tuning": "Ajuste fino de la tarjeta", + "create_fields": "Crear campos para tu nueva tarjeta", + "create_fields_hint": "Los campos son los principales elementos informativos de la tarjeta, puedes crear varios grupos y tipos de campos.", + "placeholders": { + "card_name": "Nombre de la tarjeta" + } + }, + "et_section_builder_step3": { + "title": "Agregar funcionalidad a la tarjeta", + "error": "Por favor, seleccione al menos una función.", + "features": { + "saveFiles": "Archivos", + "tasks": "Tareas", + "notes": "Notas", + "chat": "Chat", + "activities": "Actividades", + "documents": "Crear documentos", + "products": "Productos" + } + }, + "et_section_builder_step4": { + "title": "Vincular a otros módulos", + "cards": "Tarjetas", + "products": "Productos", + "schedulers": "Programadores", + "do_not_integrate": "No integrar con el módulo de programadores" + }, + "entity_type_used_in_formula_warning_modal": { + "title": "El módulo se utiliza en la fórmula", + "annotation": "No se puede eliminar la asociación con el módulo porque su campo se usa en la fórmula." + } + }, + "builder_journey_picker_page": { + "request_bpmn_form_modal": { + "error": "Se produjo un error al enviar su solicitud. Asegúrese de que la información es correcta y vuelva a intentarlo más tarde.", + "send_request": "Enviar solicitud", + "header": "BPMN – Deje una solicitud y nos pondremos en contacto con usted", + "full_name": "Nombre completo *", + "phone": "Teléfono *", + "email": "Correo electrónico *", + "comment": "Comentario", + "placeholders": { + "full_name": "Juan Pérez", + "comment": "Información adicional" + }, + "us_price_full": "Conectar BPMN por $1 al mes por usuario!", + "ru_price_full": "Conectar BPMN por 99₽ al mes por usuario!" + }, + "request_additional_storage_modal": { + "error": "Ocurrió un error al enviar tu solicitud. Verifica tu información e inténtalo de nuevo.", + "send_request": "Enviar solicitud", + "header": "Almacenamiento adicional – Envía tu solicitud y nos pondremos en contacto", + "full_name": "Nombre completo *", + "phone": "Teléfono *", + "email": "Correo *", + "comment": "Comentario", + "placeholders": { + "full_name": "Juan Pérez", + "comment": "Información adicional" + }, + "ten_gb_ru_description": "Añade 10 GB de almacenamiento por 100₽/mes.", + "ten_gb_us_description": "Añade 10 GB de almacenamiento por $1/mes.", + "one_hundred_gb_ru_description": "Añade 100 GB de almacenamiento por 500₽/mes.", + "one_hundred_gb_us_description": "Añade 100 GB de almacenamiento por $5/mes.", + "one_tb_ru_description": "Añade 1 TB de almacenamiento por 1500₽/mes.", + "one_tb_us_description": "Añade 1 TB de almacenamiento por $15/mes." + }, + "create_a_new_module": "Crear un nuevo módulo", + "module_names": { + "bpmn": { + "us_price_tag": "$1/M.", + "ru_price_tag": "99₽/M." + }, + "wazzup": "Wazzup", + "main_modules": "Selecciona el módulo funcional que deseas crear", + "additional_modules": "Módulos funcionales adicionales", + "marketplace": "Mercado", + "crm": "CRM", + "project_management": "Proyectos y Tareas", + "production": "Producción", + "product_management_for_sales": "Gestión de Inventario", + "product_management_rentals": "Gestión de Alquileres", + "scheduler": "Planificación de citas", + "supplier_management": "Proveedores", + "contractor_management": "Contratistas", + "partner_management": "Socios", + "hr_management": "Contratación de Personal", + "contact": "Contactos", + "company": "Empresas", + "universal_module": "Módulo Universal", + "finances": "Gestión Financiera", + "automatisation": "Automatización y Orquestación BPMN", + "marketing": "Gestión de Marketing", + "rentals": "Alquileres", + "for_sales": "Para ventas", + "soon": "Pronto", + "chosen": "Seleccionado", + "telephony": "PBX telefonía", + "mailing": "Servicio de Correo Electrónico", + "messenger": "Multi Messenger", + "documents": "Generación de Documentos", + "forms": "Formularios en el Sitio Web", + "headless_forms": "Formularios en el Sitio vía API", + "online_booking": "Formularios de reserva en línea", + "website_chat": "Chat en el Sitio Web", + "tilda": "Tilda Publishing", + "wordpress": "WordPress", + "twilio": "Whatsapp Business", + "fb_messenger": "Facebook Messenger", + "salesforce": "Salesforce", + "make": "Make", + "apix_drive": "ApiX-Drive", + "albato": "Albato", + "one_c": "1C", + "additional_storage": "Almacenamiento adicional", + "ten_gb": "10 GB de almacenamiento adicional", + "one_hundred_gb": "100 GB de almacenamiento adicional", + "one_tb": "1 TB de almacenamiento adicional", + "storage": { + "ten_gb_ru_price_tag": "100₽/M.", + "ten_gb_us_price_tag": "$1/M.", + "one_hundred_gb_ru_price_tag": "500₽/M.", + "one_hundred_gb_us_price_tag": "$5/M.", + "one_tb_ru_price_tag": "1500₽/M.", + "one_tb_us_price_tag": "$15/M." + } + } + } + } + } +} diff --git a/frontend/public/locales/es/module.fields.json b/frontend/public/locales/es/module.fields.json new file mode 100644 index 0000000..89ff53c --- /dev/null +++ b/frontend/public/locales/es/module.fields.json @@ -0,0 +1,122 @@ +{ + "fields": { + "formula_warning": "Cambiar la fórmula recalculera el valor en este campo. Para conservar los datos actuales, considere crear un nuevo campo.", + "project_fields_block": { + "owner": "Propietario", + "value": "Valor", + "participants": "Participantes", + "start_date": "Fecha de Inicio", + "end_date": "Fecha de Fin", + "description": "Descripción", + "placeholders": { + "participants": "Agregar participantes" + } + }, + "field_value": { + "ogrn": "OGRN", + "tin": "TIN", + "trrc": "TRRC", + "already_have_an_active_call": "Ya tienes una llamada activa", + "recent": "Reciente", + "no_available_phone_numbers": "No hay números disponibles", + "value_field_formula_calculation": "Este campo está actualmente calculado por una fórmula, no puede ser editado manualmente. Valor: {{value}}.", + "readonly": "Este campo es solo de lectura", + "important_field": "Campo Importante", + "mandatory_field": "Campo Obligatorio", + "important_field_completed": "Campo Importante Completado", + "mandatory_field_completed": "Campo Obligatorio Completado", + "local_time": "Hora local aproximada de la capital del país del número de teléfono", + "connect_telephony": "Conectes a la telefonía para habilitar la funcionalidad de llamadas.", + "chat_unavailable": "Parece que no tienes acceso a este chat. Póngase en contacto con el administrador para conectarse." + }, + "components": { + "field_formula_settings_button": { + "no_available_fields_for_formula": "No hay campos de presupuesto, números o fórmulas disponibles", + "save_first": "Esta fórmula puede configurarse en la tarjeta", + "customize": "Personalizar Fórmula: {{fieldName}}", + "formula_hint": "Este campo está destinado a ingresar una fórmula. La fórmula puede incluir los valores proporcionados a continuación, así como otros campos numéricos y fórmulas, incluidos campos de otros módulos funcionales. Haga clic en el valor deseado o seleccione el campo.", + "field": "Campo", + "close": "Cerrar" + }, + "field_settings_modal": { + "number": "Número", + "currency": "Moneda", + "format_title": "Seleccione el formato de visualización para este campo", + "ordinary_field": "Campo Ordinario", + "save_first": "Este campo solo puede configurarse después de guardarlo en la tarjeta", + "important_field": "Campo Importante", + "important_field_hint": "Puede hacer que un campo sea importante, y entonces tendrá un icono de bombilla amarilla como recordatorio de que debe ser completado. Una vez completado, mostrará una marca de verificación verde. Si necesita que un campo sea obligatorio y evitar mover el negocio a la siguiente etapa sin completarlo, entonces seleccione la función de \"campo obligatorio\".", + "select_pipeline_and_statuses": "Seleccione Pipeline y Estados", + "mandatory_field": "Campo Obligatorio", + "mandatory_field_hint": "Puede configurar un campo obligatorio para su finalización. En este caso, los usuarios no podrán pasar al siguiente estado en el pipeline. Seleccione el pipeline requerido y el estado desde el cual debe completarse el campo.", + "user_exclusion": "Exclusión de Usuarios", + "user_exclusion_hint": "Puede excluir a ciertos usuarios que podrán no completar este campo. Esto puede ser necesario para los administradores.", + "editing_restriction": "Restricción de Edición", + "editing_restriction_hint": "Puede prohibir que los usuarios seleccionados editen este campo. Podrán verlo, pero no podrán editarlo.", + "select_users": "Seleccionar Usuarios", + "hide_field": "Ocultar Campo para Usuarios", + "hide_field_hint": "Puede ocultar este campo a usuarios seleccionados; será inaccesible para ellos.", + "field_visibility_in_pipeline": "Ocultar Campo en el Pipeline", + "field_visibility_in_pipeline_hint": "Puede personalizar la visibilidad del campo para cada pipeline y etapa. A continuación, puede seleccionar en qué etapas y pipelines se ocultará el campo.", + "customize": "Personalizar Campo: {{fieldName}}" + }, + "edit_fields": { + "add_bank_requisites": "Añadir datos bancarios", + "add_org_requisites": "Añadir datos de organizaciones y empresarios", + "add_org_stat_codes": "Añadir códigos estadísticos de organizaciones y empresarios", + "add_additional_org_requisites": "Añadir campos adicionales para organizaciones y empresarios", + "add_field": "Agregar campo", + "add_utm_fields": "Agregar campos UTM", + "add_ga_fields": "Agregar campos de Google Analytics", + "add_ym_fields": "Agregar campos de Yandex.Metrica", + "add_fb_fields": "Agregar campos de Facebook Analytics", + "select_options": { + "max_length": "Máximo {{length}} caracteres", + "add_option": "Agregar opción", + "cancel": "Cancelar", + "add": "Agregar", + "no_options": "Sin opciones", + "placeholders": { + "search": "Buscar...", + "option": "Opción" + } + } + }, + "field_form_group": { + "max_length": "Máximo {{length}} caracteres", + "text": "Texto", + "number": "Número", + "multitext": "Texto múltiple", + "select": "Seleccionar", + "multiselect": "Selección múltiple", + "switch": "Interruptor", + "formula": "Fórmula", + "phone": "Teléfono", + "email": "Correo electrónico", + "value": "Valor", + "date": "Fecha", + "link": "Enlace", + "file": "Fichero", + "richtext": "Texto con formato", + "participant": "Participante", + "participants": "Participantes", + "colored_select": "Selección de color", + "colored_multiselect": "Selección múltiple de color", + "checked_multiselect": "Lista de verificación", + "checklist": "Texto multilínea con casillas", + "field_name": "Nombre del campo", + "analytics_field": "Campo de análisis" + }, + "show_fields": { + "no_fields": "No hay campos" + }, + "show_files": { + "empty": "Aún no hay archivos", + "attach": "Adjuntar archivos" + }, + "file_field_value_comp": { + "attach_files": "Adjuntar archivos" + } + } + } +} diff --git a/frontend/public/locales/es/module.mailing.json b/frontend/public/locales/es/module.mailing.json new file mode 100644 index 0000000..f454e9b --- /dev/null +++ b/frontend/public/locales/es/module.mailing.json @@ -0,0 +1,234 @@ +{ + "mailing": { + "unknown_file": "Archivo desconocido", + "modals": { + "send_email_modal": { + "to": "Para:", + "copy": "Copia:", + "hidden_copy": "Copia oculta:", + "from": "De:", + "subject": "Asunto:", + "attachment": "adjunto", + "attachments": "adjuntos", + "delete_all": "Eliminar todo", + "send": "Enviar", + "error": "¡No se pudo enviar el mensaje! Por favor, inténtelo de nuevo más tarde.", + "send_with_html": "Enviar correo con formato HTML", + "send_with_html_hint": "Seleccione esta opción si desea enviar un correo con formato HTML. Esto es útil si desea optar por no usar las funciones de formato predeterminadas de {{company}}.", + "components": { + "changes_not_saved_modal": { + "title": "Los cambios no están guardados", + "annotation": "¿Continuar editando?", + "approve": "Eliminar borrador" + }, + "invalid_email_address_modal": { + "title": "El correo \"{{email}}\" no parece ser una dirección válida", + "annotation": "Verifique la dirección y vuelva a intentarlo.", + "approve": "Enviar de todos modos" + }, + "send_email_settings_dropdown": { + "show_copy": "Mostrar campo de dirección de Copia", + "show_hidden_copy": "Mostrar campo de dirección de Copia oculta" + }, + "editors": { + "email_signature_editor": { + "placeholder": "Agregar firma", + "no_signature": "Sin firma" + }, + "email_text_editor": { + "placeholders": { + "new_message": "Nuevo mensaje", + "enter_html_markup": "Ingrese formato HTML" + } + } + } + } + } + }, + "pages": { + "mailing_settings_page": { + "soon": "Próximamente...", + "add_button": "Agregar Buzón", + "mail_templates": "Plantillas de Correo", + "templates_caption": "Puede crear plantillas de correo que se pueden enviar a un cliente. Después de agregar la plantilla, puede acceder al envío de correos.", + "setup_signature": "Configurar una firma", + "add_template": "Agregar plantilla", + "components": { + "mailbox_item": { + "draft_text": "Borrador", + "draft_hint": "La conexión del buzón no está terminada. Por favor, ingrese los ajustes faltantes.", + "inactive_text": "Inactivo", + "inactive_hint": "El buzón está inactivo. {{error}}.", + "sync_text": "Sincronización", + "sync_hint": "El buzón se está sincronizando. Por favor, espere.", + "active_text": "Activo" + } + }, + "modals": { + "delete_mailbox_modal": { + "delete": "Eliminar", + "title": "¿Realmente desea eliminar este buzón?", + "save_correspondence_annotation": "¿Guardar el historial de correspondencia para este correo?", + "save_correspondence": "Guardar historial de correspondencia para este correo" + }, + "mailbox_address_modal": { + "continue": "Continuar", + "title": "Conectar buzón", + "placeholder": "Su correo electrónico", + "caption1": "Conecte un buzón corporativo con acceso compartido, que recibe solicitudes de clientes, o un buzón personal de uno de sus empleados.", + "caption2": "Los correos enviados a este buzón se adjuntarán automáticamente a los contactos. Puede crear un negocio directamente desde la lista de correos.", + "caption3": "Si tiene problemas para conectar, intente habilitar el acceso para su cliente de correo.", + "cancel": "Cancelar", + "google_caption1": "El uso y la transferencia de información recibida de las API de Google por parte de {{company}} a cualquier otra aplicación se adherirá a", + "google_policy_link": "la Política de Datos del Usuario de los Servicios API de Google", + "google_caption2": ", incluyendo los requisitos de Uso Limitado." + }, + "mailbox_provider_modal": { + "title": "Elija proveedor", + "caption": "Seleccione su proveedor de correo electrónico a continuación. Si no ve su servicio de correo en la lista, presione Manual y configure su buzón manualmente.", + "manual": "Manual" + }, + "update_mailbox_modal": { + "max_number_of_emails_per_day": "Número máximo de correos electrónicos por día", + "emails_per_day": "correos electrónicos por día", + "emails_per_day_hint": "El parámetro de límite de correos electrónicos es necesario para evitar ser bloqueado por el proveedor del servicio de correo. Cada servicio de correo establece sus propios límites para el envío de mensajes. Por ejemplo, la versión básica de Google Workspace permite enviar 500 mensajes por día desde un buzón.", + "email_readonly_title": "No puede editar la dirección de un buzón ya creado", + "title_connect": "Conectar buzón", + "title_edit": "Editar buzón de correo", + "encryption": "Cifrado", + "owner": "Seleccionar el propietario del correo", + "for_whom_available": "Seleccionar para quién está disponible el correo", + "synchronize": "Sincronizar correos de los últimos 7 días", + "create_entities_annotation": "Configura las opciones a continuación para crear automáticamente una ficha al recibir un correo de un nuevo contacto.", + "create_entities_label": "Crear ficha con correo entrante", + "delete": "Eliminar buzón", + "reconnect": "Recolectar buzón", + "placeholders": { + "password": "Contraseña", + "imap": "Servidor IMAP", + "port": "Puerto", + "smtp": "Servidor SMTP", + "owner": "Seleccionar propietario" + } + }, + "mailbox_signature_modal": { + "title": "Firmas", + "mailbox_signature_editor": { + "available_in_mailboxes": "Disponible en correos:", + "delete": "Eliminar", + "save": "Guardar", + "warning_title": "Eliminar firma", + "warning_annotation": "¿Está seguro de que desea eliminar esta firma?", + "save_as_html": "Guardar como formato HTML", + "save_as_html_hint": "Seleccione esta opción si desea guardar la firma como formato HTML. Esto es útil si desea optar por no usar las funciones de formato predeterminadas de {{company}}.", + "placeholders": { + "signature_name": "Nombre de la firma", + "your_signature": "Su firma", + "select_mailboxes": "Seleccionar buzones" + } + } + } + } + }, + "mailing_page": { + "title": "Correo Electrónico y Mensajero", + "no_email": "Aún no hay correos", + "components": { + "section": { + "inbox": "Bandeja de entrada", + "sent": "Enviados", + "spam": "Spam", + "trash": "Papelera", + "draft": "Borrador", + "flagged": "Marcados", + "archive": "Archivo", + "all": "Todo el correo", + "mailbox": "Buzón {{number}}" + }, + "message_panel": { + "no_messages": "No hay mensajes", + "demo_message_subject": "✉️ {{company}}: Solución de Correo Corporativo", + "demo_message_snippet": "Estimado Cliente, Presentamos una solución revolucionaria de correo corporativo que sin duda captará su atención." + }, + "attachments_block": { + "attachment": "adjunto", + "attachments": "adjuntos", + "download_all": "Descargar todo" + }, + "reply_controls": { + "reply": "Responder", + "reply_all": "Responder a todos", + "forward": "Reenviar" + }, + "create_message_button": { + "title": "Nuevo mensaje" + }, + "no_mailboxes_panel_block": { + "title": "Haga clic en el botón de abajo para agregar su buzón." + }, + "thread": { + "unknown": "Desconocido 👤", + "no_selected_message": "No hay mensaje seleccionado", + "from": "De", + "subject": "Asunto", + "reply": "Responder", + "reply_all": "Responder a todos", + "forward": "Reenviar", + "add_task": "Agregar Tarea", + "add_contact": "Agregar contacto", + "spam": "Spam", + "unspam": "No es spam", + "move_to_inbox": "Mover a la bandeja de entrada", + "trash": "Papelera", + "close": "Cerrar", + "unseen": "No visto", + "user": "Usuario", + "date": "{{day}} a las {{time}}", + "amwork_workspace": "Espacio de trabajo de {{company}}", + "demo_message_title": "✉️ {{company}}: Solución de Correo Corporativo", + "demo_message_snippet": "Estimado Cliente, presentamos una solución revolucionaria de correo corporativo que sin duda captará su atención. Descubra nuevas posibilidades y eleve su comunicación corporativa a un nuevo nivel con la integración del correo en el espacio de trabajo de {{company}}.", + "dear_customer": "Estimado Cliente,", + "demo_message_intro": "Presentamos una solución revolucionaria de correo corporativo que sin duda captará su atención. Descubra nuevas posibilidades y eleve su comunicación corporativa a un nuevo nivel con la integración del correo en el espacio de trabajo de {{company}}. Conecte cualquier buzón corporativo o personal, y disfrute no solo de las funciones familiares del correo electrónico, sino también de una multitud de herramientas adicionales. Cree tareas, leads, proyectos y más directamente desde un correo, manteniendo el control total sobre todas las interacciones con clientes, socios y proyectos. Envíe mensajes fácilmente desde tarjetas de leads, negocios, socios o proyectos, y se guardarán automáticamente en la tarjeta correspondiente. De esta manera, puede leer y analizar fácilmente el historial de correspondencia para un negocio o proyecto específico. Trabaje de manera más efectiva en equipo otorgando acceso a un buzón compartido y gestionando las comunicaciones junto con sus colegas. ¡Descubra nuevos horizontes con {{company}} y aumente la productividad de su negocio!", + "email_functionality": "📬 Funcionalidad del correo:", + "email_functionality_ul": "
  • Integración API con Gmail
  • Integración IMAP
  • Integración de correos de los últimos 7 días para buzones recién añadidos
  • Correos entrantes y salientes
  • Agrupación de correos en hilos/cadenas por remitente
  • Capacidad de crear una tarea desde un correo
  • Capacidad de crear un contacto desde un correo
  • Creación automática de leads desde correos
  • Adjuntar correos entrantes y salientes al historial/alimentación de una tarjeta
  • Adjuntar correos entrantes y salientes al historial/alimentación de una tarjeta
  • Capacidad de escribir un correo desde una tarjeta
  • Creación de carpetas personalizadas
  • Uso de múltiples buzones
  • Gestión colaborativa de buzones
  • Visualización de correos en formato HTML
  • Gestión colaborativa de buzones
  • Búsqueda de correos
  • Spam
  • Elementos eliminados
  • Borradores
  • Creación de firmas
  • Destinatarios en copia
  • Destinatarios en copia oculta
  • Adjuntos de archivos
", + "reach_out": "Si tiene alguna pregunta, no dude en comunicarse con nosotros –", + "sincerely_amwork": "Atentamente, su equipo de {{company}}." + }, + "modals": { + "create_contact_modal": { + "create": "Crear", + "title": "Crear tarjetas de un correo", + "create_contact": "Crear contacto para el correo", + "create_lead": "Crear lead para el correo", + "open_entity": "Abrir entidad recién creada", + "placeholders": { + "where_contact": "Seleccione dónde crear nuevo contacto", + "where_lead": "Seleccione dónde crear nuevo lead", + "board_for_lead": "Seleccione tablero para nuevo lead" + } + }, + "link_contact_modal": { + "link": "Vincular", + "title": "Vincular tarjeta", + "select_module": "Seleccionar módulo", + "search_card": "Seleccionar tarjeta", + "open_entity": "Abrir tarjeta vinculada", + "placeholders": { + "module": "Módulo...", + "search_card": "Buscar por nombre..." + } + } + } + }, + "hooks": { + "use_message_controls": { + "unknown": "Desconocido 👤", + "reply_to": "Responder a", + "reply_all": "Responder a todos", + "forward": "Reenviar" + } + } + } + } + } +} diff --git a/frontend/public/locales/es/module.multichat.json b/frontend/public/locales/es/module.multichat.json new file mode 100644 index 0000000..2c99a3d --- /dev/null +++ b/frontend/public/locales/es/module.multichat.json @@ -0,0 +1,71 @@ +{ + "multichat": { + "amwork_messenger": "Mensajero de {{company}}", + "components": { + "multichat_control": { + "not_found": "No encontrado", + "chats": "Chats", + "messages": "Mensajes", + "users": "Usuarios", + "owner": "Propietario", + "supervisor": "Supervisor", + "no_chats_yet": "Aún no hay chats", + "create_card": "Crear una Tarjeta", + "link_card": "Ajustar una Tarjeta", + "card": "Tarjeta", + "ui": { + "deleted_user": "Usuario eliminado 👻", + "multichat_control_header": { + "title": "Multi Mensajero de {{company}}" + }, + "create_amwork_chat_button": { + "label": "Nuevo chat de {{company}}", + "modals": { + "select_contact_title": "Seleccione un usuario o un grupo de usuarios", + "continue": "Continuar", + "customize_chat": "Personalizar chat", + "placeholders": { + "group_name": "Nombre del grupo" + } + } + }, + "chats_header_providers_tabs": { + "all_chats": "Todos los Chats" + }, + "chat_message_context_menu": { + "reply": "Responder", + "copy": "Copiar texto", + "edit": "Corregir", + "delete": "Eliminar" + }, + "chat_message_reply_block": { + "editing_message": "Edición del mensaje" + }, + "send_chat_message_block": { + "placeholders": { + "message": "Mensaje" + } + }, + "no_selected_chat_plug": { + "title": "Seleccione un chat para comenzar a enviar mensajes" + }, + "chats_panel_block": { + "you": "Tú" + }, + "send_chat_message_with_files_modal": { + "send_files": "Enviar archivos" + }, + "chat_message_item": { + "you": "Tú" + }, + "chat_controls_menu": { + "mark_as_read": "Marcar como leído", + "delete_chat": "Eliminar chat", + "delete_warning_title": "¿Está seguro de que desea eliminar el chat?", + "delete_warning_annotation": "Todos los mensajes se eliminarán. Esta acción no se puede deshacer." + } + } + } + } + } +} diff --git a/frontend/public/locales/es/module.notes.json b/frontend/public/locales/es/module.notes.json new file mode 100644 index 0000000..8f75935 --- /dev/null +++ b/frontend/public/locales/es/module.notes.json @@ -0,0 +1,49 @@ +{ + "notes": { + "notes_page": { + "module_name": "Notas", + "add_note": "Crear Nota", + "new_note_heading": "Nueva Nota" + }, + "toolbar": { + "undo": "Deshacer", + "redo": "Rehacer", + "bold": "Negrita", + "italic": "Cursiva", + "underline": "Subrayado", + "strike": "Tachado", + "bullet_list": "Lista con viñetas", + "ordered_list": "Lista numerada", + "text": "Texto", + "heading": "Título", + "subheading": "Encabezado", + "heading_3": "Subencabezado", + "more": "Más...", + "expand": "Expandir", + "minimize": "Minimizar" + }, + "editor": { + "recently_edited": "Justo ahora", + "quick_note": "Nota Rápida", + "last_changed": "Últimos cambios: ", + "duplicate": "Duplicar", + "put_into_folder": "Restaurar", + "delete": "Eliminar nota" + }, + "selector": { + "new_note_heading": "Nueva Nota", + "saved_indicator": "Los cambios están guardados", + "pinned": "Fijada", + "today": "Hoy", + "yesterday": "Ayer", + "earlier": "Anteriormente" + }, + "block": { + "unnamed_note": "Nota Sin Nombre" + }, + "folders": { + "all": "Todas", + "recently_deleted": "Eliminadas Recientemente" + } + } +} diff --git a/frontend/public/locales/es/module.notifications.json b/frontend/public/locales/es/module.notifications.json new file mode 100644 index 0000000..71f89e5 --- /dev/null +++ b/frontend/public/locales/es/module.notifications.json @@ -0,0 +1,79 @@ +{ + "notifications": { + "tags": { + "task_new": "Nueva tarea", + "task_overdue": "Tarea", + "task_before_start": "Tarea", + "task_overdue_employee": "Tarea", + "activity_new": "Nueva actividad", + "activity_overdue": "Actividad", + "activity_before_start": "Actividad", + "activity_overdue_employee": "Actividad", + "task_comment_new": "Nuevo comentario", + "chat_message_new": "Nuevo mensaje", + "mail_new": "Nuevo correo", + "entity_note_new": "Nueva nota", + "entity_new": "Nuevo {{entityTypeName}}", + "entity_responsible_change": "Nuevo responsable {{entityTypeName}}", + "entity_import_completed": "Importación", + "yesterday": "Ayer a las {{time}}", + "date": "{{date}} a las {{time}}" + }, + "components": { + "delay_select": { + "no_delay": "Sin retraso", + "5_minutes": "5 minutos", + "10_minutes": "10 minutos", + "15_minutes": "15 minutos", + "30_minutes": "30 minutos", + "1_hour": "1 hora" + }, + "notifications_panel": { + "mute_notifications": "Silenciar sonido de notificaciones", + "unmute_notifications": "Activar sonido de notificaciones", + "no_notifications": "Aún no tienes notificaciones", + "ui": { + "block_header_annotation": { + "overdue": "Atrasado", + "overdue_employee": "{{employee}} está atrasado", + "after_time": "Después de {{time}}", + "from_employee": "de {{employee}}", + "system_notice": "Aviso del sistema", + "message": "Mensaje", + "yesterday": "Ayer a las {{time}}", + "date": "{{date}} a las {{time}}" + }, + "notification_settings": { + "title": "Notificaciones" + }, + "notification_settings_modal": { + "title": "Configuración de Notificaciones", + "enable_popup": "Notificaciones emergentes", + "new": "Nuevo", + "new_mail": "Nuevo Correo", + "new_chat_message": "Nuevo Mensaje de Chat", + "new_note": "Nueva Nota", + "entity_responsible_change": "Cambio de Responsable de Entidad", + "entity_import_complete": "Importación de Entidad Completa", + "new_task_comment": "Nuevo Comentario en Tarea", + "unknown": "Desconocido", + "new_task": "Nueva Tarea", + "overdue_task": "Tarea Atrasada", + "before_task_start": "Antes del inicio de una Tarea", + "overdue_task_employee": "Tarea Atrasada para Empleado(s)", + "new_activity": "Nueva Actividad", + "overdue_activity": "Actividad Atrasada", + "before_activity_start": "Antes del inicio de una Actividad", + "overdue_activity_employee": "Actividad Atrasada para Empleado(s)", + "placeholders": { + "select": "Seleccionar" + } + }, + "read_all_button": { + "read_all": "Leer todas" + } + } + } + } + } +} diff --git a/frontend/public/locales/es/module.products.json b/frontend/public/locales/es/module.products.json new file mode 100644 index 0000000..566433d --- /dev/null +++ b/frontend/public/locales/es/module.products.json @@ -0,0 +1,502 @@ +{ + "products": { + "components": { + "common": { + "readonly_stocks": "No puedes editar existencias en este estado", + "readonly_delete": "No puedes eliminar artículos del pedido en este estado", + "select_the_warehouse_to_write_off": "Selecciona el almacén del que se descontarán las existencias", + "return_stocks_warning_modal": { + "title": "Devolver existencias", + "annotation": "¿Deseas devolver las existencias al almacén?", + "approve_title": "Devolver", + "cancel_title": "No devolver" + }, + "delete_order_warning_modal": { + "title": "Atención", + "annotation": "¿Deseas eliminar el pedido? Esta acción no se puede deshacer." + }, + "delete_order_or_clear_items_warning_modal": { + "title": "Atención", + "annotation": "¿Deseas eliminar el pedido completo o eliminar todos los artículos? Esta acción no se puede deshacer.", + "approve_title": "Eliminar pedido", + "cancel_title": "Eliminar artículos" + }, + "rental_order_status_select": { + "formed": "Formado", + "accepted_to_warehouse": "Aceptado en almacén", + "sent_to_warehouse": "Enviado a almacén", + "delivered": "Entregado", + "reserved": "Reservado", + "shipped": "Enviado", + "cancelled": "Cancelado", + "returned": "Devuelto", + "placeholders": { + "select_status": "Seleccionar estado" + } + }, + "order_status_select": { + "placeholders": { + "select_status": "Seleccionar estado" + }, + "statuses": { + "reserved": "Reservado", + "sent_for_shipment": "Enviado para envío", + "shipped": "Enviado", + "cancelled": "Cancelado", + "returned": "Devuelto" + } + }, + "products_tab_selector": { + "shipments": "Envíos", + "timetable": "Calendario", + "products": "Productos" + }, + "products_category_select": { + "no_categories": "Sin Categorías", + "placeholders": { + "select_category": "Categoría" + } + }, + "products_warehouse_select": { + "no_warehouses": "Sin Almacenes", + "placeholders": { + "select_warehouse": "Almacén" + } + }, + "products_settings_button": { + "settings": "Configuraciones", + "module_settings": "Configuración del módulo", + "table_settings": "Configuración de la tabla", + "report_settings": "Configuración de informes" + }, + "calendar": { + "month": "Mes", + "week": "Semana", + "days": { + "short": { + "mo": "Lu", + "tu": "Ma", + "we": "Mi", + "th": "Ju", + "fr": "Vi", + "sa": "Sa", + "su": "Do" + }, + "long": { + "mo": "Lunes", + "tu": "Martes", + "we": "Miércoles", + "th": "Jueves", + "fr": "Viernes", + "sa": "Sábado", + "su": "Domingo" + } + }, + "resources_label": "Productos", + "statuses": { + "reserved": "Reservado", + "rented": "Alquilado" + }, + "fields": { + "phone": "Teléfono", + "mail": "Correo", + "shifts": "Número de días", + "status": "Estado", + "period": "Período de alquiler" + } + } + } + }, + "pages": { + "card_products_orders_page": { + "ui": { + "empty": "Aún no hay pedidos en esta sección" + }, + "hooks": { + "use_products_section_orders_columns": { + "name": "Nombre del pedido", + "warehouse": "Almacén", + "status": "Estado", + "created_at": "Creado el", + "shipped_at": "Enviado el", + "creator": "Creador", + "order": "Pedido-{{number}}" + }, + "use_rental_products_section_orders_columns": { + "name": "Nombre", + "status": "Estado", + "created_at": "Creado el", + "creator": "Creador", + "order": "Pedido-{{number}}", + "shipment": "Envío para pedido #{{number}}", + "linked_entity": "Entidad vinculada" + } + } + }, + "card_products_order_page": { + "templates": { + "products_order_block_template": { + "new_order": "Nuevo Pedido", + "save": "Guardar", + "cancel": "Cancelar", + "added_to": "Agregado a {{entity_name}}", + "empty": "Aún no has agregado productos al pedido" + }, + "products_warehouse_block_template": { + "product_management_for_sales": "Gestión de Inventario", + "product_management_rentals": "Gestión de Alquileres", + "products": "Productos", + "warehouse": "Almacén", + "add": "Agregar", + "reset": "Reiniciar", + "empty": "El almacén está vacío", + "warehouse_select_hint": "El almacén seleccionado en esta sección debe coincidir con el almacén ordenado en la sección superior (pedido). Si deseas seleccionar un almacén diferente aquí, primero debes borrar o cambiar el almacén elegido en la sección superior. Cualquier almacén seleccionado aquí se inferirá automáticamente en la sección superior." + } + }, + "common": { + "products_order_price_head_cell": { + "price": "Precio," + }, + "products_order_max_discount_hint": "El descuento máximo es", + "products_order_tax_header_cell": { + "tax": "Impuesto,", + "tax_included": "Incluido", + "tax_excluded": "Excluido" + }, + "products_order_total_block": { + "total": "Total: {{total}}" + }, + "remove_selected_block": { + "remove_selected": "Eliminar seleccionados" + } + }, + "hooks": { + "use_price_cell_columns": { + "name_fallback": "Sin nombre de precio", + "name": "Nombre", + "price": "Precio", + "currency": "Moneda" + } + }, + "ui": { + "card_rental_products_order_component": { + "hooks": { + "use_rental_card_order_columns": { + "name": "Nombre", + "discount": "Descuento", + "availability": "Disponibilidad", + "amount": "Cantidad" + }, + "use_rental_warehouse_columns": { + "name": "Nombre", + "price": "Precio", + "category": "Categoría", + "availability": "Disponibilidad" + } + }, + "ui": { + "rental_availability_cell": { + "available": "Disponible", + "reserved": "Reservado", + "rent": "Alquiler" + }, + "rental_order_periods_control": { + "new_period": "Nuevo período", + "periods_selected": "Períodos seleccionados: {{count}}", + "add_new_period": "Agregar nuevo período", + "hint_text": "Puedes elegir o modificar períodos exclusivamente durante la creación de nuevos pedidos o para pedidos con el estado 'Formado'.", + "placeholders": { + "select_periods": "Seleccionar períodos" + } + }, + "card_rental_products_order_block_header": { + "placeholders": { + "select_order_warehouse": "Seleccionar almacén del pedido" + } + } + } + }, + "card_products_order_component": { + "hooks": { + "use_available_columns": { + "unknown": "Desconocido...", + "name": "Nombre", + "stock": "Existencias", + "reserved": "Reservado", + "available": "Disponible" + }, + "use_card_order_columns": { + "name": "Nombre", + "discount": "Descuento", + "tax": "Impuesto,", + "quantity": "Cantidad", + "amount": "Monto" + }, + "use_reservations_columns": { + "warehouse_name": "Nombre del almacén", + "stock": "Existencias", + "reserved": "Reservado", + "available": "Disponible", + "quantity": "Cantidad" + }, + "use_warehouse_columns": { + "name": "Nombre", + "price": "Precio", + "category": "Categoría", + "available": "Disponible", + "quantity": "Cantidad" + } + }, + "ui": { + "available_head_cell": { + "available": "Disponible", + "available_hint": "Pasa el cursor sobre una celda en esta columna para obtener información detallada sobre las existencias del almacén." + }, + "card_products_order_block_header": { + "cancel_after_hint": "Puedes configurar la cancelación automática de las reservas de artículos en pedidos. Esto ayuda a prevenir niveles de inventario inexactos, que pueden ocurrir cuando los artículos están reservados en negocios pero estos no se procesan o han sido cancelados.", + "placeholders": { + "select_order_warehouse": "Seleccionar almacén del pedido" + } + }, + "add_stock_placeholder": { + "add_stock": "Agregar existencias al almacén" + } + } + } + } + }, + "product_categories_page": { + "title": "Crear una Categoría (Grupo de Productos)", + "placeholders": { + "category_name": "Nombre de la categoría", + "subcategory_name": "Nombre de la subkategoria" + }, + "buttons": { + "add_category": "Agregar Categoría", + "add_subcategory": "Agregar Subkategoria" + }, + "ui": { + "delete_category_warning_modal": { + "cannot_be_undone": "Esta acción no se puede deshacer.", + "title": "¿Estás seguro de que deseas eliminar {{name}}?", + "annotation": "Todas las subcategories serán eliminadas." + } + } + }, + "product_page": { + "product_description": "Descripción del Producto", + "sku_already_exists_warning": "El producto con SKU \"{{sku}}\" ya existe", + "sku": "SKU", + "unit": "Unidad", + "tax": "Impuesto", + "category": "Categoría", + "warehouse": "Almacén", + "ui": { + "add_warehouse_placeholder": { + "add_warehouse": "Agregar almacén" + }, + "product_actions_dropdown": { + "delete_product": "Eliminar producto" + }, + "product_description": { + "placeholders": { + "add_description": "Agregar descripción" + } + }, + "product_feed": { + "calendar": "Calendario", + "prices": "Precios", + "stocks": "Existencias", + "images": "Imágenes", + "add_price": "Agregar precio", + "add_image": "Agregar imagen", + "delete_image": "Eliminar imagen", + "delete_images": "Eliminar imágenes", + "placeholders": { + "name": "Nombre del precio", + "unit_price": "Precio unitario" + }, + "labels": { + "maximum_discount": "Descuento máximo en producto", + "product_cost": "Costo del Producto" + } + }, + "product_name_block": { + "placeholders": { + "product_name": "Nombre del producto" + } + } + } + }, + "warehouses_page": { + "page_title": "Configura tus Almacenes", + "placeholders": { + "name": "Nombre del almacén", + "select_warehouse": "Seleccionar Almacén" + }, + "add_warehouse": "Agregar almacén", + "ui": { + "delete_warehouse_modal": { + "title": "¿Estás seguro de que deseas eliminar {{name}}?", + "annotation": "Todas las existencias dentro del almacén serán eliminadas.", + "annotation_move_stocks": "Todas las existencias dentro del almacén serán eliminadas. Alternativamente, tienes la opción de transferir todas las existencias a otro almacén si está disponible.", + "placeholders": { + "move_stocks_to": "Mover existencias a..." + } + } + } + }, + "products_page": { + "no_warehouses_or_categories": "No hay almacenes o categorías para mostrar los filtros", + "title": { + "sale": "Venta", + "rental": "Alquiler", + "shipments": "Envíos", + "calendar": "Calendario" + }, + "tabs": { + "products": "Productos", + "timetable": "Calendario", + "shipments": "Envíos", + "reports": "Informes" + }, + "all_columns_hidden": "Todas las columnas están ocultas en la configuración de la tabla", + "table_settings": "Configuración de la Tabla", + "display_columns": "Mostrar Columnas", + "add_product": "Agregar producto", + "empty": "Aún no has agregado productos", + "show_empty_resources": "Mostrar recursos vacíos", + "hide_empty_resources": "Ocultar recursos vacíos", + "hooks": { + "use_products_columns": { + "name": "Nombre", + "prices": "Precios", + "sku": "SKU", + "tax": "Impuesto", + "stocks": "Existencias", + "unit": "Unidad", + "availability": "Disponibilidad" + }, + "use_product_stocks_columns": { + "warehouse": "Almacén", + "reserved": "Reservado", + "available": "Disponible", + "stock": "Existencias" + }, + "use_create_stocks_columns": { + "warehouse": "Almacén", + "stock": "Existencias" + } + }, + "ui": { + "add_product_modal": { + "changes_not_saved_warning_title": "¿Estás seguro de que deseas cerrar esta ventana?", + "changes_not_saved_warning_annotation": "Todos los cambios se perderán.", + "changes_not_saved_warning_approve_title": "Cerrar de todos modos", + "barcodes_hint": "Puedes escanear códigos de barras con un escáner o ingresarlos manualmente en este campo. Cada código de barras en una sección debe ser único.", + "sku_already_exists_warning": "El producto con SKU \"{{sku}}\" ya existe", + "types": { + "product": "Producto", + "service": "Servicio", + "kit": "Kit" + }, + "add_product": "Agregar Producto", + "name": "Nombre", + "type": "Tipo", + "add_photo": "Agregar foto", + "description": "Descripción", + "sku": "SKU", + "unit": "Unidad", + "tax": "Impuesto", + "category": "Categoría", + "prices": "Precios", + "stocks": "Existencias" + }, + "photo_block_item": { + "alt": "Producto {{name}}" + }, + "product_price_list": { + "add_price": "Agregar precio" + }, + "product_search_block": { + "placeholders": { + "search_products": "Buscar Productos" + } + }, + "product_stocks_modal": { + "title": "Establecer existencias del producto en cada almacén" + }, + "create_stocks_modal": { + "title": "Especificar existencias del producto" + } + } + }, + "shipment_page": { + "hooks": { + "use_shipment_columns": { + "name": "Nombre", + "sku": "SKU", + "available": "Disponible", + "quantity": "Cantidad" + }, + "use_rental_shipment_columns": { + "name": "Nombre", + "sku": "SKU", + "tax": "Impuesto", + "discount": "Descuento", + "total": "Total", + "quantity": "Cantidad" + } + }, + "ui": { + "shipment_page_secondary_header": { + "order": "Pedido-{{number}}" + }, + "rental_shipment_page_secondary_header": { + "order": "Pedido-{{number}}" + }, + "some_products_not_checked_warning_modal": { + "title": "Algunos productos no están verificados", + "annotation": "Estás tratando de cambiar el estado del pedido, pero algunos productos no están verificados. ¿Deseas cambiar el estado de todos modos?", + "approve_title": "Cambiar de todos modos" + }, + "product_barcodes_control": { + "barcode": "Código de barras", + "modals": { + "product_is_not_in_order_warning_modal": { + "title": "El producto no está en este pedido", + "approve_title": "Agregar producto al pedido", + "annotation": "El producto asociado con el código de barras \"{{barcode}}\" no está incluido en este pedido. Tienes la opción de agregarlo manualmente o continuar sin él." + }, + "product_does_not_exist_warning_modal": { + "title": "El producto no existe", + "approve_title": "Crear producto", + "annotation": "El producto con código de barras \"{{barcode}}\" no existe en esta sección de productos. Puedes crearlo manualmente en la página de productos." + } + } + } + } + }, + "shipments_page": { + "hooks": { + "use_shipments_columns": { + "unknown": "Desconocido...", + "name": "Nombre", + "shipment": "Envío para pedido #{{number}}", + "warehouse": "Almacén", + "shipped_at": "Enviado el", + "created_at": "Creado el", + "status": "Estado", + "linked_entity": "Entidad vinculada" + } + }, + "ui": { + "shipments_table": { + "all_columns_hidden": "Todas las columnas están ocultas en la configuración de la tabla", + "empty": "No hay Envíos Programados" + } + } + } + } + } +} diff --git a/frontend/public/locales/es/module.reporting.json b/frontend/public/locales/es/module.reporting.json new file mode 100644 index 0000000..4603ba4 --- /dev/null +++ b/frontend/public/locales/es/module.reporting.json @@ -0,0 +1,433 @@ +{ + "reporting": { + "templates": { + "components": { + "report_table": { + "empty": "No hay datos disponibles para este período" + }, + "report_settings_drawer": { + "table_settings": "Configuración de la Tabla", + "display_columns": "Mostrar Columnas" + } + }, + "comparative_report_template": { + "placeholders": { + "users": "Usuarios", + "pipeline": "Pipelines de ventas", + "month": "Seleccionar mes", + "year": "Seleccionar año" + } + }, + "general_report_template": { + "apply_filter": "Aplicar filtro", + "reset": "Reiniciar", + "stages": { + "all": "Todas las Etapas", + "open": "Etapas Abiertas", + "lost": "Etapas Perdidas", + "won": "Etapas Ganadas" + }, + "placeholders": { + "users": "Usuarios", + "pipeline": "Pipelines de ventas", + "stage": "Etapas", + "warehouse": "Almacenes", + "category": "Categorías", + "field_or_responsible": "Campo o Responsable" + } + }, + "calls_report_template": { + "directions": { + "all": "Todas las Llamadas", + "incoming": "Llamadas Entrantes", + "outgoing": "Llamadas Salientes" + }, + "placeholders": { + "users": "Usuarios", + "pipeline": "Pipelines de ventas", + "directions": "Tipo de llamada", + "duration": "Duración de la llamada", + "only_missed": "Solo llamadas perdidas" + } + } + }, + "hooks": { + "use_get_products_general_report_columns": { + "name": "Nombre", + "sold": "Total Vendido", + "shipped": "Enviado", + "open": "Negocios Abiertos", + "lost": "Negocios Perdidos", + "all": "Total de Negocios", + "average_products": "Promedio de Productos por Negocio", + "average_budget": "Valor Promedio de Negocios Exitosos", + "average_term": "Duración Promedio del Negocio" + }, + "use_calls_history_report_columns": { + "timestamps": "Fecha de la Llamada", + "type": "Llamador y Destinatario", + "result": "Duración de la Llamada y Reproducción", + "unknown": "Número Desconocido", + "caller": "Llamador", + "callee": "Destinatario" + }, + "use_get_general_report_columns": { + "result": "Resultado", + "all": "Todos", + "open": "Abiertos", + "expired": "Vencidos", + "completed": "Completados", + "won": "Ganados", + "lost": "Perdidos", + "groups": "Grupos", + "users": "Usuarios", + "cards": "Tarjetas (Leads)", + "tasks": "Tareas", + "activities": "Actividades", + "average_check": "Cheque Promedio", + "average_term": "Término Promedio (Días)", + "switch_on": "Encendido", + "switch_off": "Apagado", + "calls": "Llamadas", + "total": "Total de Llamadas", + "average": "Promedio de Llamadas", + "incoming": "Llamadas Entrantes", + "incoming_average": "Promedio de Llamadas Entrantes", + "outgoing": "Llamadas Salientes", + "outgoing_average": "Promedio de Llamadas Salientes", + "missed": "Llamadas Perdidas", + "min": "min", + "undefined_client": "Programación de citas sin un negocio" + }, + "use_get_projects_report_columns": { + "hours": "h", + "min": "min", + "users": "Usuarios", + "opened": "Tareas Abiertas", + "done": "Tareas Completadas", + "overdue": "Tareas Vencidas", + "planned": "Tiempo Planificado", + "completion_percent": "Porcentaje de Completitud del Proyecto", + "project_name": "Nombre del Proyecto", + "stage": "Estado del Proyecto" + }, + "use_get_customer_report_columns": { + "name": "Nombre", + "sold": "Total Vendido", + "products_quantity": "Cantidad de Productos", + "opened": "En Progreso (Negocios Abiertos)", + "lost": "Negocios Perdidos", + "all": "Total de Negocios (Abiertos y Cerrados)", + "average_quantity": "Promedio de Compras (Negocios)", + "average_budget": "Presupuesto Promedio de Negocios Exitosos", + "average_duration": "Duración Promedio de Negocios Exitosos (Días)" + }, + "use_get_schedule_report_columns": { + "groups": "Grupos", + "users": "Usuarios", + "sold": "Total Vendido", + "total": "Total de Citas", + "scheduled": "Programadas", + "confirmed": "Confirmadas", + "completed": "Completadas", + "cancelled": "Canceladas" + }, + "use_get_comparative_report_columns": { + "all": "Todos", + "open": "Abiertos", + "won": "Ganados", + "lost": "Perdidos", + "week": "Semana {{number}}", + "quarter": "Trimestre {{number}}", + "users": "Usuarios", + "days": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo" + }, + "months": { + "january": "Enero", + "february": "Febrero", + "march": "Marzo", + "april": "Abril", + "may": "Mayo", + "june": "Junio", + "july": "Julio", + "august": "Agosto", + "september": "Septiembre", + "october": "Octubre", + "november": "Noviembre", + "december": "Diciembre" + } + } + }, + "pages": { + "reports_page": { + "components": { + "row_title_cell": { + "total": "Total", + "without_group": "Sin grupo", + "empty_user": "Usuario no asignado" + }, + "row_event_cell": { + "to": "a" + }, + "reports_navigation_sidebar": { + "total": "Total", + "export_xlsx": "Exportar a XLSX", + "export_table": "Exportado desde {{company}} en {{date}}", + "unfold_filters_menu": "Desplegar menú de filtros", + "fold_filters_menu": "Plegar menú de filtros", + "hide_sidebar": "Ocultar barra lateral", + "show_sidebar": "Mostrar barra lateral", + "schedules": "Visitas", + "title": { + "universal": "Informes", + "project": "Informes de Proyectos", + "deal": "Informes de Negocios" + }, + "general_report": "Informe General", + "comparison_of_periods": "Comparación de Períodos", + "telephony": "Llamadas", + "schedule": "Visitas", + "projects": "Proyectos", + "users": "Por Usuarios", + "rating": "Por Calificación", + "groups": "Por Grupos", + "days": "Por Días", + "weeks": "Por Semanas", + "months": "Por Meses", + "quarters": "Por Trimestres", + "years": "Por Años", + "callsUsers": "Por Usuarios", + "callsGroups": "Por Departamentos", + "callHistory": "Informe de Registro de Llamadas", + "scheduleClient": "Por Clientes", + "scheduleDepartment": "Por Grupos", + "scheduleOwner": "Por Planificadores", + "schedulePerformer": "Por Especialistas", + "customer_reports": "Contactos y Empresas", + "customerContact": "Por Contactos", + "customerCompany": "Por Empresas", + "customerContactCompany": "Por Contactos y Empresas", + "products": "Productos", + "productsCategories": "Por Categorías", + "productsUsers": "Por Usuarios" + } + } + }, + "dashboard_page": { + "filter": { + "placeholders": { + "select_users": "Usuarios", + "select_sales_pipeline": "Pipelines de ventas", + "all": "Todos", + "all_active": "Todos activos", + "open": "Abiertos", + "open_active": "Abierto y activo", + "closed": "Cerrados", + "created": "Creado" + }, + "dashboard_type_tooltip": { + "all": { + "text": "Este informe incluye todas las oportunidades que existieron durante el período del informe, independientemente de su actividad. Incluye:", + "list_1": "✓ Oportunidades creadas en el período.", + "list_2": "✓ Oportunidades cerradas (ganadas o perdidas) en el período.", + "list_3": "✓ Oportunidades que ya existían y permanecieron abiertas en el período.", + "list_4": "✗ No excluye oportunidades sin actividad (sin cambios de etapa en el embudo de ventas)." + }, + "all_active": { + "text": "Este informe incluye solo las oportunidades en las que al menos una vez se cambió de etapa durante el período. Incluye:", + "list_1": "✓ Oportunidades creadas antes del período, pero con cambios de etapa en el período.", + "list_2": "✓ Oportunidades creadas en el período si tuvieron movimiento entre etapas.", + "list_3": "✓ Oportunidades cerradas en el período si hubo al menos un cambio de etapa antes del cierre.", + "list_4": "✗ Excluye oportunidades sin actividad (sin movimiento entre etapas)." + }, + "open": { + "text": "Este informe incluye oportunidades que no fueron cerradas (no alcanzaron los estados 'Ganada' o 'Perdida') al final del período. Incluye:", + "list_1": "✓ Oportunidades creadas anteriormente que siguen abiertas.", + "list_2": "✓ Oportunidades creadas en el período y que permanecen abiertas al final del período.", + "list_3": "✗ No incluye oportunidades cerradas." + }, + "open_active": { + "text": "Este informe incluye solo oportunidades abiertas con actividad (cambio de etapa) durante el período del informe. Incluye:", + "list_1": "✓ Oportunidades abiertas con cambios de etapa en el período.", + "list_2": "✓ Oportunidades nuevas si tuvieron movimiento entre etapas.", + "list_3": "✗ Excluye oportunidades abiertas sin cambios de etapa.", + "list_4": "✗ Excluye oportunidades cerradas." + }, + "closed": { + "text": "Este informe incluye solo oportunidades que fueron cerradas (con estado 'Ganada' o 'Perdida') durante el período. Incluye:", + "list_1": "✓ Oportunidades creadas antes y cerradas en el período.", + "list_2": "✓ Oportunidades creadas y cerradas en el período.", + "list_3": "✗ Excluye todas las oportunidades abiertas." + }, + "created": { + "text": "Este informe incluye todas las oportunidades creadas en el período del informe, sin importar su estado actual. Incluye:", + "list_1": "✓ Oportunidades creadas en el período y aún abiertas.", + "list_2": "✓ Oportunidades creadas y cerradas en el período.", + "list_3": "✗ No incluye oportunidades creadas antes del período." + } + } + + }, + "days": { + "one_day": "día", + "several_days": ["días", "días", "días"] + }, + "auto_update_select": { + "auto_update": "Autoactualizar", + "modes": { + "never": "Nunca", + "minute": "1 minuto", + "ten_minutes": "10 minutos", + "thirty_minutes": "30 minutos", + "hour": "1 hora" + } + }, + "sales_goal_chart": { + "title_for_sales": "Meta de Ventas", + "title": "Meta", + "hint": "Meta de Ventas", + "settings_tip": "Configuraciones", + "plug_text_for_sales": "Establecer Metas", + "plug_text": "Configurar una meta" + }, + "traffic_light_report": { + "title": "Informe de Semáforo", + "subtitle": "Cumplimiento del Plan de Hoy", + "hint": { + "line1": "El informe de 'semáforo' es una herramienta que muestra cómo el departamento de ventas está siguiendo el plan de ventas del día.", + "line2": "La fórmula para calcular el cumplimiento del plan de ventas de hoy es:", + "line3": "Porcentaje de cumplimiento = (Ventas reales dividido por Plan para el Día Actual) × 100%, donde:", + "list": { + "point1": "Las ventas reales son la cantidad que ya has vendido hasta el momento.", + "point2": "El plan para el día actual es parte del plan de ventas general del mes, calculado para el día actual." + }, + "line4": "Ejemplo de cálculo:", + "line5": "Supongamos que tu plan de ventas para el mes es $10,000. Hoy es el día 10, lo que significa que ha pasado aproximadamente 1/3 del mes. Tu plan para hoy es:", + "line6": "Plan para el Día Actual = ($10,000 dividido por 30 días) × 10 días = $3,333.33", + "line7": "Has vendido productos por $3,000.", + "line8": "Tu porcentaje de cumplimiento del plan es:", + "line9": "Porcentaje de Cumplimiento = ($3,000 dividido por $3,333.33) × 100% ≈ 90%", + "line10": "Así, tu plan está al 90% completo, y en el informe de 'semáforo' verás el color verde, ya que el cumplimiento del plan va bien." + }, + "plug_text": "Establecer Metas" + }, + "top_sellers": { + "title_for_sales": "Top 5 Contribuyentes", + "title": "Top 5 Líderes", + "subtitle_for_sales": "Líderes en Ventas", + "hint": "Top 5 Líderes en Ventas", + "others": "Otros", + "plug_text": "Cuando comiences a usar {{company}}, las estadísticas para los 5 principales líderes en ventas aparecerán aquí", + "plug_text_for_orders": "Una vez que comiences a usar {{company}}, las estadísticas para los 5 principales líderes de usuarios aparecerán aquí", + "plug_text_for_candidates": "Una vez que comiences a usar {{company}}, las estadísticas para los 5 principales líderes de usuarios aparecerán aquí" + }, + "analytics": { + "total_leads": "Total de Leads", + "total_orders": "Total de Pedidos", + "total_candidates": "Total de Candidatos", + "new_leads": "Nuevos Leads", + "new_orders": "Nuevos Pedidos", + "new_candidates": "Nuevos Candidatos", + "won_leads": "Ganados", + "completed_orders": "Completados", + "hired_candidates": "Candidatos Contratados", + "lost_leads": "Perdidos", + "failed_orders": "Fallidos", + "rejected_candidates": "Candidatos Rechazados", + "total_tasks": "Total de Tareas", + "completed_tasks": "Tareas Completadas", + "expired_tasks": "Tareas Vencidas", + "no_tasks": "Leads: Sin Tareas", + "cards_no_tasks": "Tarjetas: Sin Tareas", + "total_activities": "Total de Actividades", + "completed_activities": "Actividades Completadas", + "expired_activities": "Actividades Vencidas", + "no_activities": "Leads: Sin Actividades", + "cards_no_activities": "Tarjetas: Sin Actividades" + }, + "rating": { + "title": "Calificación", + "hint": "Calificación" + }, + "leads_status_chart": { + "title": "Resumen del Estado de Leads:", + "title_for_orders": "Indicadores del Estado de Pedidos:", + "title_for_candidates": "Indicadores del Estado de Candidatos:", + "subtitle": "Abiertos, Perdidos, Ganados", + "subtitle_for_orders": "Abiertos, Fallidos, Completados", + "subtitle_for_candidates": "Abiertos, Candidatos Rechazados, Candidatos Contratados", + "hint": "Resumen del Estado de Leads", + "won": "Ganados", + "completed": "Completados", + "hired_candidates": "Candidatos Contratados", + "lost": "Perdidos", + "failed": "Fallidos", + "rejected_candidates": "Candidatos Rechazados", + "opened": "Abiertos" + }, + "sales_pipeline_indicators": { + "title": "Pipeline de Conversión", + "total_sales": "Total de Ganancias", + "conversion": "Conversión de Ganancias", + "average_amount": "Monto Promedio", + "average_term": "Duración Promedio", + "days": ["día", "días", "días"] + }, + "switch": { + "deals_count": "Cantidad de Negocios", + "orders_count": "Cantidad de Pedidos", + "sales_value": "Valor de Ventas", + "orders_value": "Valor de Pedidos" + } + }, + "goal_settings_page": { + "title": "Configuración de Metas", + "total": "Total", + "back_button": "Dashboard", + "users_select": "Usuarios", + "period_type": { + "month": "Mes", + "quarter": "Trimestre" + }, + "change_period_modal": { + "title": "¡Advertencia!", + "annotation": "Cambiar el período resultará en la pérdida de metas que hayas configurado anteriormente. ¿Estás seguro de que deseas continuar?", + "approve": "Sí", + "cancel": "No" + }, + "periods": { + "months": { + "january": "Enero", + "february": "Febrero", + "march": "Marzo", + "april": "Abril", + "may": "Mayo", + "june": "Junio", + "july": "Julio", + "august": "Agosto", + "september": "Septiembre", + "october": "Octubre", + "november": "Noviembre", + "december": "Diciembre" + }, + "quarters": { + "quarter1": "Trimestre 1", + "quarter2": "Trimestre 2", + "quarter3": "Trimestre 3", + "quarter4": "Trimestre 4" + } + }, + "form_header_amount": "Monto", + "form_header_quantity": "Transacciones", + "button_save": "Guardar" + } + } + } +} diff --git a/frontend/public/locales/es/module.scheduler.json b/frontend/public/locales/es/module.scheduler.json new file mode 100644 index 0000000..6e6ba8b --- /dev/null +++ b/frontend/public/locales/es/module.scheduler.json @@ -0,0 +1,219 @@ +{ + "scheduler": { + "pages": { + "scheduler_board_view_page": { + "create_appointment": "Crear Reunión", + "stats_footer": { + "assigned": { + "label": "Asignado", + "hint": "Visitas no confirmadas" + }, + "confirmed": { + "label": "Confirmado", + "hint": "Visitas confirmadas" + }, + "completed": { + "label": "Completada", + "hint": "Visitas realizadas" + }, + "not_took_place": { + "label": "No realizada", + "hint": "Visitas pasadas no marcadas como realizadas" + }, + "not_scheduled": { + "label": "Sin próxima cita", + "hint": "Visitas sin fecha de próxima cita" + }, + "newbies": { + "label": "Primera visita", + "hint": "Visitantes por primera vez" + }, + "total": { + "label": "Total", + "hint": "Cantidad total de visitas" + } + } + }, + "appointment_card_list_page": { + "visit_date": "Fecha de visita" + }, + "scheduler_schedule_view_page": { + "sync": "Sincronizar", + "report_settings": "Configuraciones de informes", + "module_settings": "Configuraciones del módulo", + "stats_settings": "Configuración de estadísticas", + "settings": "Configuraciones", + "report": "Informe", + "overview": "Calendario", + "responsible": "Responsable", + "stage": "Etapa", + "not_provided": "No proporcionado", + "statuses": { + "scheduled": "Programado", + "confirmed": "Confirmado", + "completed": "Completado", + "cancelled": "Cancelado" + }, + "created": "Creado", + "visit": "Reunión {{number}}", + "description": "Descripción", + "email": "Correo electrónico", + "phone": "Teléfono", + "price": "Precio", + "quantity": "Cantidad", + "discount": "Descuento", + "new_event": "Nueva Reunión", + "create_appointment": "Crear Reunión", + "tooltips": { + "reports_denied": "No tiene permiso para ver informes. Póngase en contacto con el administrador de la cuenta para obtener acceso." + }, + "placeholders": { + "search_visits": "Buscar Reuniones" + }, + "hooks": { + "use_appointments_history_services_columns": { + "name": "Nombre", + "price": "Precio", + "quantity": "Cantidad", + "discount": "Descuento", + "amount": "Monto" + }, + "use_appointments_history_columns": { + "date": "Fecha", + "time": "Hora", + "performer": "Encargado", + "services": "Servicios", + "total": "Total", + "status": "Estado" + }, + "use_appointment_service_block_columns": { + "discount": "Descuento", + "quantity": "Cantidad", + "amount": "Monto" + } + }, + "ui": { + "add_appointment_modal": { + "error": "No se puede crear la reunión. Esto puede deberse a un intento de crear una reunión que se superpone con una existente o a que la fecha de finalización es anterior a la fecha de inicio.", + "visits_history_empty": "El historial de reuniones está vacío", + "no_planned_visits": "No hay reuniones planificadas", + "save_changes": "Guardar cambios", + "planned_visit_title": "{{date}} de {{startTime}} a {{endTime}}", + "general_information": "Información General", + "planned_visits": "Reuniones Planificadas", + "visits_history": "Historial de Reuniones", + "title": "Título", + "visit": "Reunión #{{number}}", + "edit_visit": "Editar reunión – {{name}}", + "new_visit": "Programar una Reunión", + "visit_parameters": "Parámetros de la Reunión", + "status": "Estado", + "scheduler": "Programador de Reuniones", + "select_users_group": "Seleccionar Grupo de Usuarios", + "select_user": "Seleccionar Usuario", + "description": "Descripción", + "from": "De:", + "to": "A:", + "date_and_time": "Fecha y Hora", + "addition_of_services": "Adición de Servicios", + "add_service": "Agregar Servicio", + "no_services": "Sin servicios", + "warning_title": "Los cambios no están guardados", + "warning_annotation": "¿Estás seguro de que deseas cerrar esta ventana? Todos los cambios se perderán.", + "close": "Cerrar", + "delete": "Eliminar", + "count": "Total de Reuniones", + "last_visit": "Última Reunión", + "completed_count": [ + "Se realizó {{count}} reunión", + "Se realizaron {{count}} reuniones", + "Se realizaron {{count}} reuniones" + ], + "placeholders": { + "select_time_period": "Período de tiempo", + "title": "Título de la reunión", + "entity_name": "Nombre de la entidad", + "search_services": "Buscar servicios para agregar", + "user": "Usuario", + "users_group": "Grupo de Usuarios", + "appointment_notes": "Nota de la Reunión" + }, + "repeating_appointments_block": { + "header": "Visitas Recurrentes", + "hint": "Configure el intervalo y el número de citas repetidas, si es necesario. Las citas se programarán para los días hábiles especificados en la configuración.", + "interval": "Intervalo", + "count": "Cantidad", + "intervals": { + "none": "No Repetir", + "day": "Diariamente", + "week": "Semanalmente", + "month": "Mensualmente" + }, + "dates_display": { + "one_visit": "La visita recurrente está programada para {{date}} a las {{time}}", + "visits_list": "Las visitas recurrentes están programadas para {{dates}} a las {{time}}", + "and": " y ", + "visits_interval_day": "Las visitas recurrentes están programadas diariamente desde {{from}} hasta {{to}} a las {{time}}", + "visits_interval_week": "Las visitas recurrentes están programadas semanalmente desde {{from}} hasta {{to}} a las {{time}}", + "visits_interval_month": "Las visitas recurrentes están programadas mensualmente desde {{from}} hasta {{to}} a las {{time}}" + }, + "list": { + "title": "Reuniones Recurrentes", + "hint": "Seleccione las fechas de las reuniones recurrentes si es necesario.", + "dates_select": "Fechas de Reuniones", + "add_new_appointment": "Agregar Reunión", + "new_appointment": "Nueva Reunión", + "placeholders": { + "dates_select": "Seleccione fechas" + } + } + }, + "batch_cancel": { + "cancel_all": "Cancelar todo", + "warning_title": "¿Seguro que deseas cancelar todas las citas programadas?", + "warning_annotation": [ + "Se cancelará {{count}} cita.", + "Se cancelarán {{count}} citas.", + "Se cancelarán {{count}} citas." + ], + "back": "Atrás", + "cancel": "Cancelar" + }, + "duplicate_warning_modal": { + "move": "Mover", + "same_time_title": "Ya hay una reunión programada a esta hora", + "same_day_title": "Ya hay una reunión programada en este día", + "same_time_annotation": "En este calendario no se permiten reuniones duplicadas el mismo día. Edita la reunión existente o cambia la fecha de esta.", + "same_day_annotation": "En este calendario no se permiten reuniones duplicadas el mismo día. ¿Deseas mover la reunión programada a las {{time}}?" + }, + "intersect_warning_modal": { + "title": "Superposición de citas", + "annotation": "La cita se superpone con otra ya programada en este calendario. Elige otro horario o cambia el responsable." + } + }, + "stats_settings_drawer": { + "title": "Configuración de estadísticas", + "description": "Valores de estadísticas mostrados", + "stats": { + "assigned": "Asignado", + "confirmed": "Confirmado", + "completed": "Completada", + "not_took_place": "No realizada", + "not_scheduled": "Sin próxima cita", + "newbies": "Primera visita", + "total": "Total" + } + }, + "local_time_warning": { + "local_correction": [ + "Ajuste de hora local: {{hours}} hora", + "Ajuste de hora local: {{hours}} horas", + "Ajuste de hora local: {{hours}} horas" + ], + "hint": "No se encuentra en la zona horaria especificada en la configuración del sistema, por lo que el tiempo de trabajo en el calendario y la hora de las reuniones se desplaza al número especificado de horas. Si cree que es un error, cambie la zona horaria en la configuración o póngase en contacto con el soporte." + } + } + } + } + } +} diff --git a/frontend/public/locales/es/module.telephony.json b/frontend/public/locales/es/module.telephony.json new file mode 100644 index 0000000..1831acc --- /dev/null +++ b/frontend/public/locales/es/module.telephony.json @@ -0,0 +1,242 @@ +{ + "telephony": { + "pages": { + "calls_configuring_scenarios_page": { + "title": "Configuración del funcionamiento del IVR en el módulo CRM", + "failed_to_reach": "Inalcanzable", + "creates_manually": "El usuario crea manualmente una tarjeta de contacto y de negocio al recibir una llamada entrante", + "creates_manually_hint": "Cuando entra una llamada de un número desconocido, aparece una ventana para la llamada entrante para el usuario. El usuario puede responder la llamada y, si considera que esta llamada es una oportunidad de negocio, puede crear una tarjeta de contacto o ambas, una tarjeta de contacto y de negocio, con solo dos clics durante la conversación. Este método es preferible a generar tarjetas automáticamente ya que podrías recibir llamadas no solo de clientes potenciales, sino de varios otros negocios. Este enfoque ayuda a evitar la creación de tarjetas de contacto y de negocio innecesarias.", + "components": { + "configuring_scenarios_header_controls": { + "cancel": "Cancelar", + "save_scenarios": "Guardar escenarios", + "failed_to_reach": "Inalcanzable" + }, + "entity_scenario_radio_group": { + "automatically_create": "Crear automáticamente", + "contact_or_company": "Contacto o Empresa", + "deal": "Negocio", + "select_contact_or_company_first": "Seleccionar contacto o empresa primero", + "deal_pipeline": "Pipeline de ventas", + "select_deal_first": "Seleccionar negocio primero", + "placeholders": { + "select_deal": "Seleccionar negocio", + "select_pipeline": "Seleccionar pipeline" + } + }, + "incoming_calls_block": { + "incoming_calls": "Llamadas entrantes" + }, + "incoming_known_missing_scenario_block": { + "missed_from_known_number": "Llamada perdida de un número conocido" + }, + "incoming_unknown_missing_scenario_block": { + "missed_call_from_unknown_number": "Llamada perdida de un número desconocido", + "auto_create": "Crear automáticamente", + "contact_or_company": "Contacto o Empresa", + "responsible": "Responsable", + "select_contact_or_company_first": "Seleccionar contacto o empresa primero", + "deal_pipeline": "Pipeline de negocio", + "select_deal_first": "Seleccionar negocio primero", + "placeholders": { + "select_responsible": "Seleccionar responsable", + "select_deal": "Seleccionar negocio", + "select_pipeline": "Seleccionar pipeline" + } + }, + "incoming_unknown_scenario_block": { + "call_from_unknown_number": "De un número desconocido" + }, + "task_and_activities_scenario_group": { + "task": "Tarea", + "select_contact_or_company_first": "Seleccionar contacto o empresa primero", + "title": "Crear automáticamente una tarea o actividad", + "do_not_create": "No crear", + "activity": "Actividad", + "activity_type": "Tipo de actividad", + "complete": "Completar dentro de", + "description": "Descripción", + "task_title": "Título de la tarea", + "minutes": "minutos", + "placeholders": { + "activity_description": "Descripción de la actividad", + "task_description": "Descripción de la tarea", + "title": "Título" + } + }, + "outgoing_calls_block": { + "outgoing_calls": "Llamadas salientes", + "failed_to_reach": "Inalcanzable" + }, + "outgoing_unanswered_scenario_block": { + "failed_to_reach": "Inalcanzable", + "unanswered_outgoing_calls": "Llamadas salientes no contestadas", + "create_note": "Crear una nota en la historia del contacto/negocio", + "placeholders": { + "note_content": "Contenido de la nota" + } + }, + "outgoing_unknown_scenario_block": { + "call_to_unknown_number": "A número desconocido", + "creates_manually": "El usuario crea manualmente una tarjeta de contacto y de negocio al realizar una llamada saliente" + } + } + }, + "calls_sip_registrations_page": { + "provider": "Proveedor", + "removed_or_detached": "Eliminado o desvinculado", + "removed_or_detached_hint": "Esta inscripción SIP ya no está disponible porque fue eliminada o desvinculada en la plataforma de Voximplant. Para continuar usando esta inscripción SIP, debes volver a vincularla a tu aplicación o eliminarla y crear una nueva. Contacta al soporte {{mail}} para obtener más información.", + "users": "Usuarios", + "users_hint": "Selecciona los usuarios que tendrán acceso a este registro SIP", + "default_name": "Registro SIP #{{number}}", + "name": "Nombre de registro *", + "title_annotation": "Puedes encontrar las instrucciones de registro SIP en la sección \"Integraciones\" haciendo clic en el botón \"Instalar\" en el grupo \"Telefonía y PBX\".", + "link_to_vx_portal": "Enlace a la Portal de Voximplant", + "providers": { + "uis": "UIS", + "zadarma": "Zadarma", + "mango_office": "Mango Office", + "beeline": "Beeline", + "mts": "MTS", + "mgts": "MGTS", + "tele2": "Tele2", + "megafon": "Megafon", + "rostelecom": "Rostelecom", + "unknown": "Desconocido" + }, + "annotation": "Al crear un registro SIP desde su cuenta de Voximplant, se cobrará inmediatamente la tarifa mensual. Consulte el importe en el portal de Voximplant. La creación puede tardar unos minutos.", + "last_updated": "Última actualización: {{lastUpdated}}", + "delete_warning": { + "title": "¿Está seguro de que desea eliminar este registro SIP?", + "annotation": "Esta acción no se puede deshacer. Podrá restaurar el registro SIP solo creando uno nuevo." + }, + "registration_successful": "Registro exitoso", + "error_annotation": "Asegúrate de haber ingresado las credenciales correctas o verifica tu cuenta de Voximplant para obtener más detalles.", + "empty": "Aún no hay registros SIP", + "save_error": "No se pudo guardar el registro. Por favor, asegúrese de que todos los datos ingresados sean correctos.", + "add": "Agregar", + "title": "Registros SIP", + "edit_sip_registration": "Editar Registro SIP", + "add_sip_registration": "Agregar Registro SIP", + "proxy": "Proxy *", + "sip_user_name": "Nombre de usuario SIP *", + "password": "Contraseña", + "outbound_proxy": "Proxy saliente", + "auth_user": "Usuario de autenticación", + "auth_user_hint": "Generalmente es el mismo que el nombre de usuario.", + "placeholders": { + "all_users": "Todos los usuarios", + "name": "Nuevo registro SIP", + "proxy": "sip.provider.org", + "sip_user_name": "user_name", + "password": "********", + "outbound_proxy": "outbound.provider.org", + "auth_user": "auth_user_name" + } + }, + "calls_settings_users_page": { + "empty": "No has añadido aún ningún usuario", + "users": "Usuarios", + "add_user": "Agregar Usuario", + "remove_user": "Eliminar usuario", + "active": "Activo", + "create": "Crear", + "open_sip_settings": "Abrir Configuración SIP", + "continue": "Continuar", + "sip_settings_title": "Configuración SIP – {{userName}}", + "sensitive_warning": "Esta es información sensible, asegúrate de no compartirla con terceros. Pasa el cursor sobre la contraseña para revelarla.", + "user_name": "Nombre de Usuario", + "domain": "Dominio", + "password": "Contraseña", + "remove_warning_title": "¿Eliminar a {{userName}}?", + "remove_warning_annotation": "Esta acción no se puede deshacer. Solo podrás restaurar al usuario creando uno nuevo.", + "remove": "Eliminar" + }, + "calls_settings_account_page": { + "synchronise_with_vx": "Sincronizar con Voximplant", + "delete_warning_modal": { + "title": "¿Está seguro de que desea eliminar el número de teléfono {{phoneNumber}}?", + "annotation": "Esta acción no se puede deshacer." + }, + "unknown": "Desconocido", + "region": "Región", + "phone_number": "Número de teléfono", + "users": "Usuarios", + "state": "Estado", + "connect_phone_number": "Conectar", + "disconnect_phone_number": "Desconectar", + "delete_phone_number": "Eliminar", + "phone_numbers": "Números de teléfono", + "phone_numbers_annotation1": "Conecte todos los números de teléfono de Voximplant disponibles a su cuenta.", + "phone_numbers_annotation2": "Puede agregar operadores activos en la columna de usuarios para que puedan acceder al número conectado.", + "phone_numbers_annotation3": "ℹ️ Al eliminar o desvincular un número directamente en la aplicación Voximplant, no olvides eliminarlo de la lista de números o presionar el botón de sincronización que aparecerá en este caso.", + "connect_all_available_phone_numbers": "Conectar todos los números disponibles", + "empty_phone_numbers": "No hay números de teléfono disponibles o conectados", + "placeholders": { + "all_users": "Todos los usuarios" + }, + "connect_telephony_title": "Conecta la telefonía y obtén más oportunidades con {{company}}", + "connect_telephony_warning": "Tenga en cuenta: Al hacer clic en el botón, se creará automáticamente una cuenta de Voximplant vinculada a {{company}}. No se registre manualmente, ya que esa cuenta no estará asociada con {{company}}.", + "connect": "Conectar Telefonía", + "account": "Cuenta", + "voximplant_balance": "Saldo de Voximplant (Próximamente...)", + "recharge": "Recargar", + "subscription_fee": "Cuota de suscripción (Próximamente...)", + "available_numbers": "Números disponibles: {{amount}}", + "account_is_not_approved": "Cuenta no aprobada", + "not_approved_annotation": "Sin confirmar tu cuenta, no podrás usar los números de teléfono conectados.", + "approve": "Aprobar" + } + }, + "components": { + "telephony_button": { + "telephony": "Telefonía", + "not_connected": "Conéctate a la telefonía para habilitar la funcionalidad de llamadas." + }, + "telephony_modal": { + "connect_to_transfer": "Debe conectarse primero a la llamada para transferirla", + "no_operators_to_transfer_call_to": "No hay operadores para transferir la llamada a", + "coming_soon": "Próximamente...", + "unknown_number": "Número desconocido", + "amwork_calls": "Llamadas de {{company}}", + "active_call_control": { + "cancel": "Cancelar", + "transfer": "Transferir", + "add_to_call": "Agregar a la llamada", + "end_call": "Finalizar llamada", + "calling": "llamando", + "mute": "Silenciar", + "unmute": "Activar sonido" + }, + "incoming_call_control": { + "transfer": "Transferir", + "add_to_call": "Agregar a la llamada", + "accept": "Aceptar", + "incoming_call": "llamada entrante", + "mute": "Silenciar", + "unmute": "Activar sonido", + "end_call": "Finalizar llamada", + "decline": "Rechazar" + }, + "outgoing_call_initializer": { + "outgoing_number": "Número de salida", + "no_available_phone_numbers": "No hay números disponibles", + "keys": "Teclas", + "recent": "Recientes", + "outgoing": "Llamada saliente", + "incoming": "Llamada entrante", + "yesterday": "Ayer", + "today": "Hoy", + "no_calls": "Sin llamadas" + }, + "call_control_template": { + "call_from": "Llamada de: {{number}}", + "create": "Crear", + "placeholders": { + "select_card": "Seleccionar tarjeta" + } + } + } + } + } +} diff --git a/frontend/public/locales/es/module.tutorial.json b/frontend/public/locales/es/module.tutorial.json new file mode 100644 index 0000000..a94e025 --- /dev/null +++ b/frontend/public/locales/es/module.tutorial.json @@ -0,0 +1,55 @@ +{ + "tutorial": { + "tutorial_drawer": { + "title": "Base de Conocimientos", + "empty": "Aquí no hay nada todavía.", + "create_tutorial_group_block": { + "create_group": "Crear grupo", + "save": "Guardar", + "cancel": "Cancelar" + }, + "create_tutorial_item_block": { + "create_link": "Crear enlace", + "save": "Guardar", + "cancel": "Cancelar" + }, + "tutorial_edit_group_item_form": { + "placeholders": { + "name": "Nombre", + "link": "Enlace", + "all": "Todos" + } + }, + "tutorial_edit_group_items_forms": { + "name": "Nombre", + "link": "Enlace", + "users": "Usuarios", + "products": "Productos", + "products_hint": "Selecciona productos donde se mostrará este enlace. Por ejemplo, si se seleccionan tareas, el enlace se mostrará solo en la sección de tareas." + }, + "tutorial_group_name_block": { + "placeholders": { + "group_name": "Nombre del grupo" + } + }, + "hooks": { + "use_get_tutorial_products_options": { + "product_types": { + "builder": "Constructor", + "task": "Tareas", + "mail": "Correo", + "multi_messenger": "Multi Messenger", + "settings": "Configuraciones" + } + }, + "use_get_tutorial_products_groups": { + "groups": { + "entity_type": "Módulos", + "products_section": "Productos", + "scheduler": "Horarios" + } + } + } + } + } +} diff --git a/frontend/public/locales/es/page.board-settings.json b/frontend/public/locales/es/page.board-settings.json new file mode 100644 index 0000000..b2dc4fb --- /dev/null +++ b/frontend/public/locales/es/page.board-settings.json @@ -0,0 +1,33 @@ +{ + "board_settings": { + "ui": { + "board_settings_header": { + "header": "Configuración del tablero", + "delete_board": "Eliminar tablero", + "title": "¡Atención!", + "annotation": "Eliminar el tablero eliminará permanentemente todas las tarjetas en él. ¿Está seguro de que desea continuar?", + "save": "Guardar", + "cancel": "Cancelar", + "leave": "Salir de la configuración" + }, + "stage_name_hint": "Máximo {{length}} caracteres" + }, + "entity_type_board_settings_page": { + "automation_new": "Automatización 2.0", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Próximamente...)", + "warning_title": "No puedes eliminar el último tablero", + "warning_annotation": "Si deseas eliminar este tablero, por favor crea uno nuevo primero." + }, + "task_board_settings_page": { + "warning_title": "No puedes eliminar el tablero del sistema", + "warning_annotation": "Este tablero es del sistema y no se puede eliminar." + }, + "delete_stage_warning_modal": { + "warning_title": "¿Estás seguro de que deseas eliminar esta etapa?", + "warning_annotation": "Elige una etapa a la que deseas transferir los elementos existentes.", + "delete": "Eliminar", + "placeholder": "La etapa para transferir" + } + } +} diff --git a/frontend/public/locales/es/page.dashboard.json b/frontend/public/locales/es/page.dashboard.json new file mode 100644 index 0000000..e101884 --- /dev/null +++ b/frontend/public/locales/es/page.dashboard.json @@ -0,0 +1,18 @@ +{ + "dashboard_page": { + "top_sellers": { + "title": "Calificación", + "hint": "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + }, + "analytics": { + "total_entities": "Total", + "won_entities": "Ganados", + "lost_entities": "Perdidos", + "new_entities": "Nuevos", + "total_tasks": "Total de Tareas", + "completed_tasks": "Tareas Completadas", + "expired_tasks": "Tareas Vencidas", + "no_tasks": "Sin Tareas" + } + } +} diff --git a/frontend/public/locales/es/page.login.json b/frontend/public/locales/es/page.login.json new file mode 100644 index 0000000..b978a52 --- /dev/null +++ b/frontend/public/locales/es/page.login.json @@ -0,0 +1,24 @@ +{ + "login": { + "login_form": { + "title": "Inicia sesión en tu cuenta", + "login": "Iniciar sesión", + "invalid": "Credenciales inválidas", + "or": "O", + "caption": "¿Aún no tienes cuenta?", + "sign_up": "Regístrate", + "forgot_your_password": "¿Olvidaste tu contraseña?", + "placeholders": { + "login": "Correo electrónico", + "password": "Contraseña" + }, + "invalid_email_error": "Formato de correo electrónico inválido", + "invalid_password_error": "Se requiere contraseña" + }, + "left_block": { + "title": "Bienvenido", + "annotation": "¡Gran trabajo, con placer!", + "image_alt": "{{company}} constructor sin código, crea tu propio CRM único" + } + } +} diff --git a/frontend/public/locales/es/page.settings.json b/frontend/public/locales/es/page.settings.json new file mode 100644 index 0000000..86aa98c --- /dev/null +++ b/frontend/public/locales/es/page.settings.json @@ -0,0 +1,902 @@ +{ + "settings_page": { + "app_sumo_tiers_data": { + "feature_1": "Productos funcionales ilimitados en el creador", + "feature_2": "Tubería o tablero ilimitado", + "feature_3": "Campos personalizados ilimitados", + "feature_4": "Automatización", + "feature_5": "Acceso de por vida", + "storage": "{{storage}} GB de almacenamiento por usuario" + }, + "templates": { + "document_creation_fields_page": { + "ui": { + "system_section": { + "name": "Campos del sistema", + "current_date": "Fecha actual", + "document_number": "Número de documento" + }, + "entity_type_section": { + "name": "Nombre", + "owner": "Propietario" + }, + "selector": { + "words": "Palabras" + } + } + }, + "document_templates_page": { + "order_fields": { + "order": "Orden", + "order_items": "Elementos del pedido", + "specific_order_item": "Elemento del pedido", + "order_number": "Número de pedido", + "order_amount": "Monto del pedido", + "order_currency": "Moneda del pedido", + "order_item": { + "number": "Número", + "name": "Nombre", + "price": "Precio", + "currency": "Moneda", + "discount": "Descuento", + "tax": "Impuesto", + "quantity": "Cantidad", + "amount": "Monto" + }, + "text_block_1": "Puedes llenar el documento con datos sobre todos los productos y servicios del pedido en forma de texto o una tabla. También puedes agregar datos solo para un elemento específico del pedido.", + "text_block_2": "Para llenar el documento con datos sobre todos los elementos del pedido en forma de texto, necesitas generar el texto requerido en el documento, y para insertar datos sobre los elementos del pedido, necesitas usar los códigos especiales mencionados anteriormente.", + "text_block_3": "IMPORTANTE: Al comienzo del bloque de la lista de elementos del pedido, necesitas insertar un código especial {#order.products}. Al final del bloque, necesitas insertar un símbolo especial {/}. El texto del bloque entre los símbolos {#order.products} y {/} se repetirá en el documento creado tantas veces como elementos haya en el pedido seleccionado.", + "text_example": "Ejemplo de Texto", + "text_block_4": "{name} en cantidad {quantity} al precio de {price}", + "text_block_5": "Para llenar el documento con datos sobre todos los elementos del pedido en forma de tabla, necesitas crear una tabla en la plantilla. Si es necesario, los nombres de las columnas se pueden especificar en el encabezado de la tabla. La tabla, además del encabezado, debe contener una fila con los campos necesarios para llenar.", + "text_block_6": "IMPORTANTE: La primera celda de la fila debe contener el código {#order.products} al comienzo de la celda; la última celda debe contener el código {/} al final de la celda. Estos códigos son necesarios para generar la tabla y no se mostrarán en el documento final.", + "table_example": "Ejemplo de Tabla", + "text_block_7": "Para llenar el documento con datos sobre un elemento específico del pedido, necesitas usar símbolos especiales.", + "text_block_8": "El símbolo [0] especifica qué número de elemento del pedido se seleccionará para insertar datos en el documento. Los números de los elementos comienzan desde 0 para el primer elemento del pedido." + }, + "no_document_templates": "Aún no tienes plantillas de documentos.", + "can_create_new_there": "Para agregar una plantilla, haz clic en el botón \"Agregar plantilla de documento\".", + "add_document_template": "Agregar plantilla de documento", + "ui": { + "document_template_item": { + "available_in_sections": "Disponible en módulos", + "creator": "Creador", + "access_rights": "Derechos de acceso", + "warning_title": "¿Estás seguro de que deseas eliminar esta plantilla?", + "warning_annotation": "Esta acción no se puede deshacer.", + "placeholders": { + "select_sections": "Seleccionar módulos" + } + } + } + } + }, + "billing_page": { + "trial_in_progress": "¡Tu prueba está activa!", + "trial_over": "¡Su periodo de prueba ha terminado!", + "subscription_over": "¡Su suscripción ha terminado!", + "request_mywork_billing_form_modal": { + "error": "Se produjo un error al enviar su solicitud. Asegúrese de que la información es correcta y vuelva a intentarlo más tarde.", + "send_request": "Enviar solicitud", + "header": "Deje una solicitud y nos pondremos en contacto con usted", + "full_name": "Nombre completo *", + "phone": "Teléfono *", + "email": "Correo electrónico *", + "number_of_users": "Número de usuarios", + "comment": "Comentario", + "placeholders": { + "full_name": "Juan Pérez", + "comment": "Información adicional" + } + }, + "for_all": "Para todos los usuarios", + "request_billing_modal": { + "send": "Enviar", + "title": "Plan \"{{planName}}\" – Formulario de Contacto", + "name": "Nombre *", + "phone": "Teléfono *", + "email": "Correo electrónico *", + "number_of_users": "Número de usuarios *", + "comment": "Comentario", + "placeholders": { + "additional_info": "Información adicional" + } + }, + "request_button": "Dejar solicitud", + "feedback_annotation": "Has seleccionado el plan {{planName}}, por favor deja una solicitud y nos pondremos en contacto contigo pronto.", + "free_trial_duration": "Prueba gratuita – 14 días", + "started_in": "Iniciado en:", + "expires_in": "Expira en:", + "users_limit": "Límite de usuarios:", + "renew": "Renovar la suscripción", + "upgrade": "Actualizar", + "open_stripe_portal": "Abrir portal de Stripe", + "manage_your_subscription": "💸 Gestiona tu suscripción en el Portal del Cliente de Stripe", + "number_of_users": "Número de usuarios", + "finish_order": "Finalizar pedido", + "save_percentage": "Ahorra {{percentage}}", + "per_user_month": "por usuario / mes", + "select_this_plan": "Seleccionar plan", + "selected": "Seleccionado", + "1_month": "1 mes", + "12_month": "12 meses", + "save_up_to": "Ahorra hasta {{percent}}%", + "total": "Total: {{amount}}", + "payment_success": "¡Pago exitoso! Recibirás una confirmación por correo electrónico.", + "set_free_plan_warning_modal": { + "title": "¡Importante!", + "annotation": { + "text1": "El límite de usuarios de tu plan de suscripción ha sido excedido. Las cuentas adicionales se eliminarán automáticamente de acuerdo con los términos del plan. Puedes eliminar manualmente las cuentas necesarias", + "link": "aquí", + "text2": "Tus otros datos del sistema no se verán afectados." + }, + "approve": "Confirmar", + "cancel": "Cancelar" + }, + "lifetime_promo_plans": { + "discount": "{{percent}}% de descuento", + "lifetime_subscription": "en suscripción de por vida", + "black_text": "", + "until": "hasta", + "business_plan": "Starter", + "business_plan_description": "Todo lo necesario para gestionar ventas y proyectos con confianza.", + "advanced_plan": "Business", + "advanced_plan_description": "Funcionalidad completa con automatización y configuraciones avanzadas.", + "price_rise_counter": { + "months": ["mes", "meses", "meses"], + "days": ["día", "días", "días"], + "hours": ["hora", "horas", "horas"], + "minutes": ["minuto", "minutos", "minutos"], + "sale_ends": "La oferta termina en:" + }, + "lifetime_deal_plan_block": { + "lifetime_subscription": "Suscripción de por vida" + }, + "annual_deal_pricing_block": { + "annual_subscription": "1 año de suscripción" + }, + "monthly_deal_pricing_block": { + "monthly_subscription": "1 mes de suscripción", + "number_of_users": "Número de usuarios", + "per_user": "/mes por usuario", + "minimum_period": "período mínimo de suscripción: 6 meses", + "buy": "Comprar" + }, + "pricing_block_with_users_selector": { + "users": ["usuario", "usuarios", "usuarios"], + "lifetime_postfix": " para siempre", + "packages": "Planes por usuario", + "per_user": "/mes por usuario", + "price_description": "usuario / mes, facturación anual", + "fix_price": "Precio fijo", + "big_savings": "Ahorro excepcional", + "savings": "Ahorro: ", + "buy": "Comprar" + } + } + }, + "edit_groups_page": { + "add_new_group": "Agregar nuevo grupo", + "subgroup": "Subgrupo", + "delete_warning_title": "¿Estás seguro de que deseas eliminar {{name}}?", + "delete_warning_annotation1": "Todo el personal de este grupo", + "delete_warning_annotation2": "(y sus subgrupos si los hay)", + "delete_warning_annotation3": "se puede asignar condicionalmente a otro grupo o subgrupo.", + "add_subgroup": "Agregar subgrupo", + "save": "Guardar", + "cancel": "Cancelar", + "delete": "Eliminar", + "working_time": "Horario de trabajo", + "to": "a", + "placeholders": { + "group": "Ingrese el nombre", + "select_group": "Seleccionar grupo", + "select_subgroup": "Seleccionar subgrupo", + "new_subgroup": "Nuevo subgrupo" + } + }, + "edit_user_page": { + "save": "Guardar", + "save_and_add": "Guardar y agregar", + "first_name": "Nombre *", + "first_name_hint": "Máximo {{length}} caracteres", + "last_name": "Apellido *", + "last_name_hint": "Máximo {{length}} caracteres", + "email": "Correo electrónico *", + "phone_number": "Teléfono", + "password": "Contraseña *", + "group": "Grupo", + "owner": "Propietario", + "owner_hint": "El propietario es el superadministrador y dueño de la cuenta. El propietario no puede ser eliminado, no puedes quitarle derechos ni prohibirle nada. Solo un usuario puede ser el propietario de una cuenta.", + "admin": "Administrador", + "admin_hint": "El administrador tiene derechos ilimitados para gestionar y configurar la cuenta.", + "email_error": "Este correo electrónico ya está vinculado a otra cuenta de {{company}}. Para crear el usuario, por favor opta por una dirección de correo electrónico diferente.", + "unknown_error": "Ocurrió un error desconocido al editar el usuario. Por favor, verifica que los campos estén llenos correctamente e inténtalo de nuevo.", + "position": "Posición", + "visible_users": "Usuarios visibles", + "visible_users_hint": "Los usuarios que el usuario actual podrá seleccionar de la lista", + "working_time": "Horario laboral", + "to": "hasta", + "group_working_time": "Horario laboral del grupo", + "group_working_time_hint": "El usuario heredará el horario laboral del grupo al que pertenece. Si el grupo no tiene un horario configurado, se usará el horario de la empresa.", + "user_calendar_title": "Horario para reservas en línea", + "user_calendar_hint": "Este horario se usa en los formularios de reserva en línea para definir los intervalos disponibles. Si no hay un horario, se usará el calendario para determinar la disponibilidad.", + "delete_user_calendar": "Eliminar", + "add_user_calendar": "Crear horario", + "time_buffer_before": "Tiempo de descanso antes", + "time_buffer_after": "Tiempo de descanso después", + "appointment_limit": "Límite de citas por día", + "schedule": "Horario", + "no_duration": "No hay", + "minutes": "minutos", + "placeholders": { + "all_users": "Todos los usuarios", + "manager": "Gerente", + "password": "Crear una contraseña", + "new_password": "Nueva contraseña", + "group": "Seleccionar grupo" + }, + "ui": { + "menu_accesses_item": { + "title": "Accesos al menú", + "denied": "Denegado", + "responsible": "Responsable", + "allowed": "Permitido" + }, + "object_permissions_list": { + "tasks": "Tareas", + "activities": "Actividades" + }, + "object_permissions_item": { + "hint": "Puedes configurar permisos para un usuario. Puedes configurar permisos para crear, ver, editar y eliminar objetos en la sección {{title}}.", + "create": "Crear", + "view": "Ver", + "edit": "Editar", + "delete": "Eliminar", + "report": "Informes", + "dashboard": "Tablero", + "denied": "Denegado", + "responsible": "Responsable", + "subdepartment": "Subgrupo", + "department": "Grupo", + "allowed": "Permitido" + }, + "products_permissions_item": { + "warehouses_title": "Acceso a Almacenes", + "warehouses_hint": "Selecciona los almacenes que el usuario puede acceder para crear pedidos, ver productos y envíos.", + "hint": "Puedes configurar permisos para un usuario.", + "create_product": "Crear producto", + "view_product": "Ver producto", + "edit_product": "Editar producto", + "create_order": "Crear pedido", + "shipment": "Envío", + "delete": "Eliminar", + "denied": "Denegado", + "allowed": "Permitido", + "placeholders": { + "all_warehouses": "Todos los almacenes" + } + } + } + }, + "general_settings_page": { + "enable": "Habilitar", + "contact_duplicates_hint": "Activa esta opción para advertir a los usuarios al crear un contacto o empresa con un número de teléfono o correo electrónico ya existente.", + "contact_duplicates": "Advertir sobre duplicados de contactos", + "date_format": "Formato de fecha", + "auto": "Automáticamente", + "phone_format": "Formato de teléfono", + "international": "Internacional", + "free": "Gratis", + "company": "Empresa", + "domain": "Nombre de dominio", + "upload_logo": "Subir nuevo logotipo", + "delete_logo": "Eliminar logotipo", + "logo_caption": "El tamaño preferido para el logotipo es 111px por 22px", + "language": "Idioma", + "time_zone": "Zona horaria", + "working_days": "Días laborables", + "start_of_week": "Inicio de la semana", + "Monday": "Lunes", + "Tuesday": "Martes", + "Wednesday": "Miércoles", + "Thursday": "Jueves", + "Friday": "Viernes", + "Saturday": "Sábado", + "Sunday": "Domingo", + "currency": "Moneda", + "working_time": "Horario de trabajo", + "to": "a", + "number_format": "Formato de número", + "currency_select": "Selección de moneda", + "currencies": { + "USD": "Dólar estadounidense, USD", + "EUR": "Euro, EUR", + "GBP": "Libra esterlina, GBP", + "JPY": "Yen japonés, JPY", + "CNY": "Yuan chino, CNY", + "INR": "Rupia india, INR", + "RUB": "Rublo ruso, RUB", + "MXN": "Peso mexicano, MXN", + "BRL": "Real brasileño, BRL", + "ZAR": "Rand sudafricano, ZAR", + "AUD": "Dólar australiano, AUD", + "CAD": "Dólar canadiense, CAD", + "AED": "Dirham de los EAU, AED", + "CHF": "Franco suizo, CHF", + "TRY": "Lira turca, TRY", + "UAH": "Grivna ucraniana, UAH", + "KRW": "Won surcoreano, KRW", + "NZD": "Dólar neozelandés, NZD", + "NOK": "Corona noruega, NOK", + "SEK": "Corona sueca, SEK", + "DKK": "Corona danesa, DKK", + "PLN": "Zloty polaco, PLN", + "CZK": "Corona checa, CZK", + "HUF": "Forint húngaro, HUF", + "IDR": "Rupia indonesia, IDR", + "ILS": "Nuevo shéquel israelí, ILS", + "MYR": "Ringgit malayo, MYR", + "PHP": "Peso filipino, PHP", + "SGD": "Dólar de Singapur, SGD", + "THB": "Baht tailandés, THB", + "KZT": "Tengue kazajo, KZT", + "CRC": "Colón costarricense, CRC", + "COP": "Peso colombiano, COP", + "BOB": "Boliviano, BOB", + "HKD": "Dólar de Hong Kong, HKD", + "SAR": "Rial saudí, SAR", + "VND": "Dong vietnamita, VND", + "EGP": "Libra egipcia, EGP", + "KWD": "Dinar kuwaití, KWD", + "PKR": "Rupia pakistaní, PKR", + "LKR": "Rupia de Sri Lanka, LKR", + "BDT": "Taka de Bangladesh, BDT", + "NGN": "Naira nigeriana, NGN", + "GHS": "Cedi ghanés, GHS", + "TWD": "Nuevo dólar taiwanés, TWD", + "MAD": "Dirham marroquí, MAD", + "ARS": "Peso argentino, ARS", + "PEN": "Nuevo sol peruano, PEN", + "UYU": "Peso uruguayo, UYU", + "BGN": "Lev búlgaro, BGN", + "RON": "Leu rumano, RON", + "LBP": "Libra libanesa, LBP" + } + }, + "integrations_page": { + "calendars_and_tasks": "Calendarios y tareas", + "telephony_integration_guide_international": { + "modal_title": "Integración de Telefonía Voximplant", + "continue": "Continuar", + "title": "Instrucciones para integrar la telefonía Voximplant", + "step1": { + "title": "Paso 1 – Creación de una cuenta", + "item1": "Para conectar la integración de telefonía Voximplant, navegue a \"Configuración\" → \"Llamadas\" → pestaña \"Cuenta\" y haga clic en el botón \"Conectar Telefonía\".", + "item2": "La información sobre su cuenta de Voximplant creada aparecerá en la página. Para completar la integración, deberá confirmar la cuenta haciendo clic en el enlace \"Aprobar\" en la parte superior de la pantalla." + }, + "step2": { + "title": "Paso 2 – Reposición de saldo", + "annotation": "Una vez que abra el portal de Voximplant, deberá agregar fondos a su cuenta. Se recomienda agregar al menos $10. Esto activará su cuenta y los fondos se utilizarán para comprar números de teléfono y facturar los minutos de llamada.", + "operator_site_and_billing": "Sitio del operador y facturación" + }, + "step3": { + "title": "Paso 3 – Compra de números y comienzo", + "annotation1": "Después de completar la activación de la cuenta, debe proporcionar al soporte de {{companyName}} ({{mail}}) los siguientes detalles para que podamos inicializar su telefonía y prepararla para operar:", + "item1": "La ciudad y el país para la selección del número", + "item2": "Horas laborales: qué días de la semana y horas se consideran horas laborales, su zona horaria", + "item3": "Texto de saludo al comienzo de la llamada durante las horas laborales", + "item4": "Texto del mensaje para llamadas fuera de las horas laborales", + "item5": "Opción de voz para el mensaje – masculino o femenino", + "item6": "Cantidad requerida de números de teléfono", + "annotation2": "Después de recibir esta información, procesaremos su solicitud lo antes posible." + }, + "step4": { + "title": "Paso 4 – Conexión de números en {{companyName}}", + "annotation1": "Una vez que haya completado la activación de la cuenta y hayamos comprado los números deseados para usted, los verá en la pestaña \"Configuración\" → \"Llamadas\" → \"Cuenta\". Para empezar a trabajar, debe hacer clic en el botón \"Conectar\" junto a los números deseados y seleccionar los usuarios que tendrán acceso a estos números.", + "annotation2": "¡Listo! Su telefonía está configurada y lista para usar." + }, + "step5": { + "title": "Paso 5 – Configuración e información adicional", + "annotation1": "En la pestaña \"Configuración\" → \"Llamadas\" → \"Usuarios\", puede conectar y desconectar a los usuarios del sistema a la telefonía y ver los datos SIP. En la pestaña \"Configuración\", puede configurar los escenarios de integración de telefonía con el módulo CRM. En la pestaña \"Registros SIP\", puede agregar y administrar los registros SIP de PBX/VPBX.", + "annotation2": "Tenga en cuenta que con el uso activo de la telefonía, se recomienda actualizar periódicamente la pestaña del navegador, ya que las pestañas inactivas/obsoletas (abiertas hace más de 3 días sin interacción) pueden hacer que pierda algunas llamadas.", + "annotation3": "Además, debe otorgar al navegador acceso al micrófono para realizar llamadas salientes.", + "grant_chrome_access": "Conceder acceso en Google Chrome", + "grant_mozilla_access": "Conceder acceso en Mozilla Firefox", + "grant_safari_access": "Conceder acceso en Safari", + "annotation4": "Normalmente, el navegador solicitará automáticamente los permisos necesarios cuando intente realizar una llamada saliente." + } + }, + "providers_sip_registration_items_list": { + "sip_registration_guide_modal": { + "modal_title": "Guía de Conexión de Registro SIP", + "continue": "Continuar", + "step1": { + "title": "Paso 1 – Reunir la Información Necesaria", + "annotation1": "Antes de comenzar a conectar el registro SIP, asegúrate de que tu cuenta ya tenga una integración activa con la telefonía Voximplant. Si aún no está conectada, sigue las instrucciones a continuación:", + "annotation2": "Para conectar el registro SIP, necesitas conocer la siguiente información SIP sobre tu PBX/VPBX:", + "item1": "Proxy *", + "item2": "Nombre de usuario SIP *", + "item3": "Contraseña", + "item4": "Proxy de salida", + "item5": "Usuario de autenticación", + "annotation3": "Esta información se puede encontrar en tu cuenta personal de VPBX o solicitarla directamente al operador." + }, + "step2": { + "title": "Paso 2 – Conectar el Registro", + "item1": "Haz clic en el botón \"Continuar\" en la parte inferior de esta ventana o ve a la pestaña \"Configuración\" → \"Llamadas\" → \"Registros SIP\" y haz clic en el botón \"Agregar Registro SIP\".", + "item2": "Ingresa todos los datos necesarios y haz clic en \"Agregar\".", + "item3": "Ten en cuenta que inmediatamente después de crear el registro, se cobrará una pequeña cantidad de tu cuenta de Voximplant. Esto se cobrará cada mes por el uso del registro. Puedes encontrar información detallada en el siguiente enlace en la pestaña \"Precios\" → \"Funciones\" → \"Registro SIP\". El enlace aparecerá solo si ya tienes una cuenta de Voximplant.", + "voximplant_billing_rates": "Tarifas de servicio de Voximplant", + "item4": "Después de una conexión exitosa, verás tu integración. Puede ser editada simplemente haciendo clic en ella. No hay tarifa adicional por editar.", + "item5": "Para eliminar un registro SIP, haz clic en el botón \"Papelera\" al final del bloque de Registro SIP en la pestaña “Registro SIP”.", + "annotation": "Para otros problemas relacionados con los registros SIP, contáctanos en {{mail}}." + } + }, + "another_pbx": "Conectar otro PBX/VPBX", + "beeline": "Conectar PBX/VPBX de Beeline", + "mts": "Conectar PBX/VPBX de MTS", + "mgts": "Conectar PBX de MGTS", + "tele2": "Conectar PBX Corporativo de Tele2", + "megafon": "Conectar PBX/VPBX de MegaFon", + "rostelecom": "Conectar PBX de Oficina/VPBX de Rostelecom", + "mango_office": "Conectar VPBX de Mango Office", + "uis": "Conectar VPBX de UIS", + "zadarma": "Conectar VPBX de Zadarma" + }, + "telephony_and_pbx": "Telefonía y PBX", + "integrations": "Integraciones", + "crm": "CRM", + "process_automation": "Automatización de procesos", + "site_forms": "Formularios en el Sitio Web", + "messenger": "Redes sociales y Mensajeros", + "continue": "Continuar", + "save": "Guardar", + "manage_connected_accounts": "Gestionar cuentas conectadas", + "ui": { + "google_calendar": { + "google_calendar_manage_modal": { + "title": "Integraciones de Google Calendar de {{company}}" + }, + "google_calendar_connect_modal": { + "do_not_select": "No seleccionar", + "integration_name_readonly_hint": "No puedes cambiar el nombre de esta integración una vez que se cree.", + "save": "Guardar", + "connect_calendar": "Conectar calendario", + "finish_integration_annotation": "Completa la configuración de la integración rellenando todos los campos obligatorios.", + "new_integration": "Nueva integración #{{number}}", + "integration_name": "Nombre de la integración *", + "calendar": "Google Calendario *", + "calendar_hint": "Selecciona un calendario específico de tu cuenta de Google Calendar", + "task_board": "Tablero de tareas *", + "schedule": "Planificador {{company}} *", + "linked_task_boards_annotation": "Puedes añadir tableros de tareas adicionales para sincronizarlos con el calendario seleccionado. Las tareas de estos tableros se sincronizarán con Google Calendar.", + "linked_schedules_annotation": "Puedes añadir calendarios adicionales para sincronizarlos con el calendario seleccionado. Las visitas de estos calendarios se sincronizarán con Google Calendar.", + "select_all": "Seleccionar todo", + "sync_events": "Sincronizar eventos a partir de hoy", + "additional_task_boards": "Tableros de tareas adicionales", + "additional_schedules": "Calendarios adicionales", + "responsible_user": "Usuario responsable *", + "error_message": "¡No se pudo procesar el código de Google Calendar!", + "title": "Conectar con Google Calendar", + "feature": "Podrás realizar una sincronización bidireccional de tus tareas y visitas con Google Calendar", + "annotation": "Autoriza el acceso a tu cuenta de Google Calendar.", + "placeholders": { + "integration_name": "Integración de Google Calendar", + "schedules": "Seleccionar horarios", + "task_boards": "Seleccionar tableros de tareas" + } + }, + "google_calendar_modal_template": { + "title": "Integración de Google Calendar de {{company}}" + }, + "google_calendar_item": { + "description": "Sincronizar tus reuniones y tareas" + } + }, + "common": { + "form": { + "account_activity": "Actividad de la cuenta", + "account_activity_annotation": "Puedes activar o suspender tu cuenta", + "available": "Seleccionar para quién está disponible el mensajero *", + "all": "Todos", + "users": "Usuarios", + "on": "ENCENDIDO", + "no_create": "No crear", + "messenger_leads_title": "Configura la creación de registros", + "messenger_leads_annotation": "Define los parámetros para que, al recibir un mensaje de un nuevo contacto, se cree un registro automáticamente.", + "create_entities": "Crear registro al recibir un mensaje", + "create_contact": "Módulo para contacto/empresa", + "create_lead": "Módulo para oportunidad", + "lead_stage": "Estado de la oportunidad", + "lead_name": "Nombre de la oportunidad", + "lead_owner": "Responsable de la oportunidad *", + "check_duplicates": "Evitar duplicados", + "check_active_lead": "Crear nueva si la actual está inactiva", + "placeholders": { + "select_module": "Selecciona un módulo", + "select_users": "Selecciona usuarios", + "select_user": "Selecciona un usuario", + "lead_name": "Ingresa un nombre" + } + }, + "modals": { + "changes_not_saved_warning_modal": { + "title": "¿Estás seguro de que deseas cerrar esta ventana?", + "annotation": "Todos los cambios se perderán.", + "close": "Cerrar" + }, + "integration_account_item": { + "delete_warning_title": "¿Estás seguro de que deseas eliminar la cuenta de {{title}}?", + "delete_warning_annotation": "Todos los chats y mensajes relacionados con este canal de comunicación serán eliminados. Esta acción no se puede deshacer.", + "active": "Activo", + "active_hint": "Tu cuenta está activa. Puedes suspenderla en la configuración del proveedor haciendo clic en el icono del lápiz.", + "inactive": "Inactivo", + "inactive_hint": "Tu cuenta está suspendida. Puedes activarla en la configuración del proveedor haciendo clic en el icono del lápiz.", + "deleted": "Eliminado", + "deleted_hint": "Tu cuenta está eliminada.", + "draft": "Borrador", + "draft_hint": "Tu cuenta aún no está conectada." + }, + "integration_accounts_list": { + "add_account": "Agregar cuenta" + } + } + }, + "integration_item": { + "install": "Instalar", + "manage": "Gestionar" + }, + "messages_per_day_select": { + "label": "Límite diario de mensajes enviados por automatización *", + "annotation": "mensajes por día", + "hint": "Cantidad máxima de mensajes que se pueden enviar por día a través de esta cuenta. Este límite es necesario para evitar que la cuenta sea bloqueada por el mensajero debido a envíos masivos." + }, + "salesforce": { + "salesforce_item": { + "description": "Un espacio de trabajo para cada equipo" + }, + "salesforce_modal": { + "title": "Integración con Salesforce", + "connected": "Conectado", + "not_connected": "No conectado", + "disconnect": "Desconectar", + "add_integration": "Agregar integración", + "my_domain_name": "Mi nombre de dominio", + "app_key": "Clave del consumidor de la aplicación", + "app_secret": "Secreto del consumidor de la aplicación", + "connect": "Conectar", + "caption": "Sigue usando Salesforce para los clientes y usa {{company}} para gestionar el resto de tus procesos comerciales. Impulsa proyectos en {{company}}. Gestiona contratistas y proveedores en {{company}}." + } + }, + "fb_messenger": { + "fb_messenger_manage_modal": { + "title": "Integración de la API de Facebook Messenger" + }, + "fb_messenger_item": { + "title": "Gestiona interacciones de Facebook, convierte chats en leads" + }, + "fb_messenger_modal_template": { + "title": "Integración de Facebook Messenger" + }, + "fb_messenger_first_info_modal": { + "title": "Obtén una cuenta oficial de Facebook Messenger Business para empezar a hablar con tus clientes usando el Multimensajero de {{company}}.", + "feature1": "Gestiona todas las conversaciones de Facebook Messenger con otros canales en un solo lugar", + "feature2": "Colabora con los miembros de tu equipo en conversaciones entrantes", + "feature3": "Crea fácilmente nuevos contactos, leads y tratos directamente desde las conversaciones", + "feature4": "Usa plantillas de mensajes de Facebook Messenger para enviar notificaciones relevantes y oportunas", + "learn_more": "Aprende más sobre cómo gestionar tu integración de Facebook Messenger" + }, + "fb_messenger_finish_modal": { + "supervisors": "Seleccionar usuarios que tienen acceso a todos los chats con clientes", + "supervisors_hint": "Seleccionar usuarios que tendrán acceso a todos los chats de empleados con clientes. Estos podrían ser gerentes o empleados que trabajen junto con los clientes.", + "save": "Guardar", + "title": "Ya has conectado tu cuenta de Facebook a {{company}}", + "subtitle": "Termina de configurar tu cuenta. Cambia el nombre y la configuración de accesibilidad según sea necesario.", + "name": "Nombre *", + "responsible_users": "Usuarios responsables de nuevos leads *", + "learn_more": "Aprende más sobre cómo gestionar tu integración de Facebook Messenger", + "placeholders": { + "name": "Nombre del producto", + "select_users": "Seleccionar usuarios" + } + } + }, + "wazzup": { + "wazzup_manage_modal": { + "title": "Integración de Wazzup de {{company}}" + }, + "wazzup_item": { + "title": "Integración de Wazzup en solo 5 minutos, WhatsApp y Telegram" + }, + "wazzup_modal_template": { + "title": "Integración de Wazzup de {{company}}" + }, + "wazzup_first_info_modal": { + "title": "Comunícate directamente con tus clientes a través de {{company}} en redes sociales y plataformas de mensajería.", + "feature1": "Los miembros del equipo de ventas pueden enviar mensajes a través de WhatsApp, Telegram usando {{company}}", + "feature2": "Los gerentes solo pueden ver sus conversaciones, mientras que los supervisores tienen acceso a todos los chats", + "feature3": "Los contactos y tratos con nuevos clientes se generan automáticamente", + "feature4": "Todas las comunicaciones se almacenan de manera segura en {{company}}", + "feature5": "Utiliza un solo número para todo el departamento de ventas y configura respuestas automáticas", + "annotation": "Mantente conectado con los clientes dondequiera que estés. Con la aplicación móvil de Wazzup, puedes contactar a los clientes sobre la marcha, y todas tus conversaciones se guardarán en {{company}} y serán accesibles desde tu computadora.", + "learn_more": "Aprende más sobre Wazzup" + }, + "wazzup_connect_modal": { + "save": "Guardar", + "update": "Actualizar", + "title": "Autoriza tu cuenta de Wazzup", + "subtitle": "Accede a la configuración de tu cuenta de Wazzup y copia las credenciales de la API.", + "name": "Nombre *", + "api_key": "Clave API *", + "channel": "Canal de Wazzup *", + "responsible_users": "Usuarios responsables de nuevos leads *", + "supervisors": "Seleccionar usuarios que tienen acceso a todos los chats con clientes", + "supervisors_hint": "Seleccionar usuarios que tendrán acceso a todos los chats de empleados con clientes. Estos podrían ser gerentes o empleados que trabajen junto con los clientes.", + "placeholders": { + "api_key": "Clave API", + "name": "Nombre del producto", + "select_users": "Seleccionar usuarios", + "channel": "Seleccionar canal de Wazzup" + } + } + }, + "whatsapp": { + "whatsapp_manage_modal": { + "title": "Integración de la API de WhatsApp Business por Twilio" + }, + "whatsapp_item": { + "title": "Mensajes directos de WhatsApp Business a través de {{company}}" + }, + "whatsapp_modal_template": { + "title": "Integración de la API de WhatsApp Business por Twilio" + }, + "whatsapp_first_info_modal": { + "title": "Obtén una cuenta oficial de WhatsApp Business (proporcionada por Twilio) para empezar a hablar con tus clientes usando el Multimensajero de {{company}}", + "feature1": "Gestiona todas las conversaciones de WhatsApp con otros canales en un solo lugar", + "feature2": "Colabora con los miembros de tu equipo en conversaciones entrantes", + "feature3": "Crea fácilmente nuevos contactos, leads y tratos directamente desde las conversaciones", + "feature4": "Usa plantillas de mensajes de WhatsApp para enviar notificaciones relevantes y oportunas", + "learn_more": "Aprende más sobre la API de WhatsApp Business por Twilio" + }, + "whatsapp_second_info_modal": { + "title": "Cosas a tener en cuenta antes de continuar", + "step1": "Deberías comprar un nuevo número de teléfono en Twilio (o transferir tu número de teléfono actual de la aplicación móvil de WhatsApp o la aplicación de WhatsApp Business) para crear una cuenta de la API de WhatsApp Business.", + "link1": "Cómo mover un número de WhatsApp aprobado a Twilio", + "step2": "WhatsApp y Twilio pueden cobrarte una tarifa adicional por usar la API de WhatsApp Business.", + "link2": "Aprende más sobre los precios" + }, + "whatsapp_third_info_modal": { + "title": "Configura la cuenta de Twilio con tu número de WhatsApp", + "subtitle": "Twilio necesita aprobar tu número de WhatsApp antes de que pueda usarse en {{company}}. Sigue estos pasos para evitar cualquier retraso innecesario.", + "step1": "Configura con", + "step2": "Solicita acceso a Twilio para habilitar los números de teléfono para WhatsApp:", + "step2_1_1": "Asegúrate de tener listo el número de teléfono proporcionado por Twilio para obtener el número de WhatsApp. Completa", + "step2_1_2": "el formulario de Solicitud de Acceso a Twilio", + "step2_1_3": "con tu información actualizada, incluyendo tu ID de Administrador Comercial de Facebook.", + "step2_2_1": "Consulta la", + "step2_2_2": "documentación", + "step2_2_3": "de Twilio para más información.", + "step3": "Envía una solicitud de remitente de WhatsApp en la consola de Twilio.", + "step4_1": "Una vez que hayas enviado el formulario de Solicitud de Acceso, recibirás un correo electrónico de pre-aprobación de Twilio. Consulta este", + "step4_2": "enlace de referencia", + "step4_3": "para los siguientes pasos.", + "step5": "Permite que Twilio envíe un mensaje en tu nombre en la consola del Administrador Comercial de Facebook." + }, + "whatsapp_connect_modal": { + "supervisors": "Seleccionar usuarios que tienen acceso a todos los chats con clientes", + "supervisors_hint": "Seleccionar usuarios que tendrán acceso a todos los chats de empleados con clientes. Estos podrían ser gerentes o empleados que trabajen junto con los clientes.", + "save": "Guardar", + "update": "Actualizar", + "title": "Autoriza tu cuenta de WhatsApp por Twilio", + "subtitle": "Accede a la configuración de tu cuenta de Twilio y copia las credenciales de la API.", + "name": "Nombre *", + "sid": "SID de la cuenta *", + "auth_token": "Token de autenticación *", + "phone": "Número de WhatsApp autorizado *", + "responsible_users": "Usuarios responsables de nuevos leads *", + "learn_more": "Aprende más sobre cómo configurar tu cuenta de WhatsApp con Twilio", + "placeholders": { + "name": "Nombre del producto", + "code": "Código del producto", + "token": "Token", + "number": "Número", + "select_users": "Seleccionar usuarios" + } + } + }, + "tilda": { + "tilda_item": { + "description": "Integración de formularios en sitio con Tilda" + }, + "tilda_manual_modal": { + "close": "Cerrar", + "title": "Integración de formularios en sitio con Tilda", + "info_title": "Integra el formulario del constructor {{company}} en tu sitio Tilda para que las solicitudes lleguen directo al sistema.", + "step_1_title": "Paso 1 — Preparación del formulario", + "step_1_description": "En el constructor de formularios de {{company}} debes crear un formulario con integración API. Configura el formulario en el primer paso y añade los campos necesarios en el segundo.", + "form_builder_link": "Constructor de formularios con integración API", + "step_2_title": "Paso 2 — Configuración en Tilda", + "step_2_item_1": "Abre Configuración del sitio → Formularios → Webhook.", + "step_2_item_2": "Introduce la URL del webhook del paso 3 del constructor de formularios como dirección del script.", + "step_2_item_3": "Guarda los cambios.", + "step_3_title": "Paso 3 — Configuración de variables", + "step_3_item_1": "En el formulario que quieres integrar, selecciona Webhook como servicio de recepción de datos.", + "step_3_item_2": "Asigna variables (identificadores) a los campos del formulario usando la tabla del paso 3 del constructor.", + "step_3_item_3": "Prueba el formulario. Si todo funciona correctamente, publica la página.", + "tilda_manual_link": "Guía de configuración de Tilda", + "support": "Si tienes problemas con la integración, contacta al soporte." + } + }, + "wordpress": { + "wordpress_item": { + "description": "Integración de formularios en sitio con Wordpress" + }, + "wordpress_manual_modal": { + "close": "Cerrar", + "title": "Integración de formularios en sitio con Wordpress", + "info_title": "Integra el formulario del constructor {{company}} en tu sitio Wordpress para que las solicitudes lleguen directo al sistema.", + "step_1_title": "Paso 1 — Preparación del formulario", + "step_1_description": "Crea un formulario en el constructor de {{company}}. Configura los campos en el segundo paso y personaliza el diseño en el cuarto paso.", + "form_builder_link": "Constructor de formularios", + "step_2_title": "Paso 2 — Agregar el contenedor de formulario", + "step_2_item_1": "Abre el panel de administración de Wordpress.", + "step_2_item_2": "Haz una copia de seguridad o duplica la página, si es posible.", + "step_2_item_3": "Si ya tienes un formulario, elimínalo y deja un bloque vacío. Si no tienes uno, añade un bloque vacío donde quieras insertar el formulario.", + "step_2_item_4": "En el menú 'Opciones adicionales', asigna la clase CSS workspace-form-builder-mount-container.", + "step_3_title": "Paso 3 — Agregar el código del formulario", + "step_3_item_1": "Añade un bloque 'HTML personalizado' en la página.", + "step_3_item_2": "Copia el código del quinto paso del constructor e insértalo en el bloque HTML personalizado.", + "step_3_item_3": "Verifica que el formulario funcione correctamente. Luego, publica la página.", + "wordpress_manual_link": "Instrucciones de Wordpress para insertar código HTML", + "additional_steps": "Pasos adicionales", + "styling": "Puedes personalizar el estilo del formulario en el cuarto paso. Si necesitas más opciones, activa el 'CSS personalizado'. Cada elemento tiene una clase CSS predefinida en formato .workspace-form__TextInput--Input. Los cambios en el formulario se aplican automáticamente al guardar.", + "multiform": "Puedes publicar varias versiones del mismo formulario en una página. Solo necesitas un bloque 'HTML personalizado', pero puedes tener varios contenedores. Si deseas agregar diferentes formularios en la misma página, habilita el 'modo de compatibilidad'. Luego, inserta el código correspondiente para cada formulario en su propio bloque. La clase CSS será única para cada contenedor.", + "support": "Si tienes problemas con la integración, contacta al soporte." + } + }, + "albato": { + "albato_item": { + "description": "Automatiza procesos complejos con Albato" + }, + "albato_manual_modal": { + "close": "Cerrar", + "header_title": "Guía de integración de {{company}} con Albato", + "integration_title": "Utiliza Albato para conectar {{company}} con miles de servicios.", + "step_1_title": "Paso 1: Crear una integración en Albato", + "albato_website": "Sitio de Albato", + "step_1_part_1": "Crea una cuenta en Albato si aún no la tienes.", + "step_1_part_2": "Crea una nueva integración. Agrega una acción y en el menú de servicios busca y selecciona Mywork.", + "step_1_part_3": "Selecciona la acción adecuada de la lista ofrecida.", + "step_2_title": "Paso 2: Conectar tu cuenta de Mywork", + "step_2_part_1": "Agrega una nueva conexión para Mywork.", + "step_2_part_2": "Completa el campo 'Clave API'. La clave está en la configuración de tu cuenta Mywork, en 'Acceso API'.", + "step_2_part_3": "Rellena los campos 'Correo electrónico' y 'Contraseña' con los datos de tu cuenta Mywork.", + "step_2_part_4": "Completa el campo 'Subdominio'. Encuéntralo en 'Configuraciones generales' de tu cuenta Mywork. Ingresa solo la parte resaltada, sin '.mywork.app'.", + "api_access_link": "Acceso API", + "step_3_title": "Paso 3: Configurar acciones de automatización", + "step_3": "En la configuración de la acción se muestra un formulario con campos a completar. Estos corresponden a los datos necesarios para ejecutar la acción (por ejemplo, usuario responsable, nombre de la tarea o texto). Los datos pueden llenarse dinámicamente desde acciones previas o el disparador. Algunos datos se pueden extraer de un directorio (como el usuario responsable) y se cargarán desde tu cuenta Mywork. Si faltan datos, presiona 'Actualizar lista'.", + "step_4_title": "Paso 4: Pruebas", + "step_4": "Antes de activar la integración, pruébala usando el botón correspondiente. Los datos enviados al sistema estarán disponibles en el registro de la integración. Si tienes preguntas sobre la integración, contacta al soporte técnico de Mywork." + } + }, + + "request_integration": { + "description": "Solicitar desarrollo de integración", + "request": "Solicitar" + } + } + }, + "users_settings_page": { + "user": "Usuario", + "total": "Total", + "create_button_tooltip": "Agregar usuario", + "ui": { + "remove_user_modal": { + "remove": "Eliminar", + "title": "¿Estás seguro de que deseas eliminar a este usuario?", + "annotation": "Transfiere la propiedad de todos los elementos de este usuario a otro.", + "placeholder": "Seleccionar responsable..." + }, + "user_item": { + "remove": "Eliminar", + "user_role": { + "owner": "Propietario", + "admin": "Administrador", + "user": "Usuario", + "partner": "Socio" + } + } + } + }, + "account_api_access_page": { + "title": "Acceso a la API", + "annotation": "Puedes usar la clave API para crear integraciones externas con el sistema. Guarda esta clave en un lugar seguro y no la compartas con nadie. Si la clave se compromete, vuelve a generarla.", + "create_api_key": "Crear clave API", + "recreate_api_key": "Regenerar", + "warning_title": "Regeneración de la clave API", + "warning_annotation": "La clave actual será eliminada y reemplazada por una nueva. Después de regenerarla, actualiza la clave en todas las integraciones externas para que sigan funcionando.", + "created_at": "Creada el {{date}} a las {{time}}", + "api_tokens_list": { + "title": "Tokens de autorización", + "annotation": "Cree tokens de autorización para integraciones externas con el sistema. Un token reemplaza el inicio de sesión con usuario y contraseña. Una vez creado, el token solo se mostrará una vez, guárdelo en un lugar seguro. Si se compromete, elimínelo y genere uno nuevo.", + "empty_user_tokens": "No hay tokens de autorización creados.", + "add_user_token": "Agregar token", + "copy": "Copiar token", + "access_token": "Guarde este token, no podrá verlo nuevamente:", + "name": "Nombre", + "expires_at": "Fecha de expiración", + "table": { + "name": "Nombre", + "created_at": "Creado", + "expires_at": "Expira", + "last_used_at": "Último uso", + "never": "Nunca", + "actions": "Acciones", + "delete": "Eliminar" + } + } + }, + "settings_page_template": { + "modules_settings": "Configuración de módulos", + "sip_registrations": "Registros SIP", + "title": "Configuración", + "users": "Usuarios", + "general": "Configuración general", + "email": "Configuración de correo", + "integrations": "Integraciones", + "billing": "Facturación", + "api_access": "Acceso a la API", + "documents": "Documentos", + "calls": "Llamadas", + "account": "Cuenta", + "superadmin": "Admin Panel", + "configuring_scenarios": "Configuración", + "schemas": "Esquemas", + "configure_users": "Configuración de usuarios", + "groups": "Grupos", + "document_templates": "Plantillas de documentos", + "document_creation_fields": "Campos de creación de documentos" + }, + "add_user_to_plan_modal": { + "send": "Enviar", + "title": "Para agregar un usuario, por favor contáctenos", + "name": "* Nombre", + "phone": "* Teléfono", + "email": "* Correo electrónico", + "number_of_users": "* Número de usuarios", + "placeholder": "Tu nombre" + }, + "request_setup_form": { + "button": { + "request_setup": "Solicitar configuración", + "request_billing_help": "Solicitar ayuda con la suscripción", + "request_integration": "Solicitar desarrollo de integración", + "request_telephony": "Solicitar configuración de telefonía", + "request_api_integration": "Solicitar integración API" + }, + "modal": { + "request_setup": "Solicitar configuración de {{company}}", + "request_billing_help": "Solicitar ayuda con la suscripción de {{company}}", + "request_integration": "Solicitar desarrollo de integración", + "request_telephony": "Solicitar configuración de telefonía", + "request_api_integration": "Solicitar integración API", + "full_name": "Nombre *", + "phone": "Teléfono *", + "email": "Correo electrónico *", + "comment": "Consulta", + "send_request": "Enviar", + "placeholders": { + "full_name": "Ingrese su nombre", + "comment": { + "request_setup": "Describa brevemente su solicitud de configuración. Nos pondremos en contacto con usted para adaptar el sistema a sus necesidades.", + "request_billing_help": "Describa brevemente el problema con su suscripción. Nos pondremos en contacto con usted para resolverlo.", + "request_integration": "Describa brevemente su solicitud de integración. Nos pondremos en contacto para coordinar los detalles.", + "request_telephony": "Describa brevemente su solicitud de telefonía. Nos pondremos en contacto con usted para implementar la solución según sus necesidades.", + "request_api_integration": "Describa brevemente su solicitud de integración API. Nos pondremos en contacto con usted para implementar el sistema según sus necesidades." + } + } + } + } + } +} diff --git a/frontend/public/locales/es/page.system.json b/frontend/public/locales/es/page.system.json new file mode 100644 index 0000000..1c8cfb2 --- /dev/null +++ b/frontend/public/locales/es/page.system.json @@ -0,0 +1,19 @@ +{ + "error_page": { + "title": "Algo salió mal", + "annotation": "Lo sentimos, pero algo salió mal. Hemos sido notificados sobre este problema y lo revisaremos en breve. Por favor, ve a la página principal o intenta recargar la página.", + "show_error": "Mostrar mensaje de error", + "home": "Página principal", + "reload": "Recargar la página" + }, + "forbidden_page": { + "title": "El acceso a esta página está prohibido.", + "back": "Regresar", + "home": "Página principal" + }, + "not_found_page": { + "title": "No se pudo encontrar esta página.", + "home": "Página principal", + "back": "Regresar" + } +} diff --git a/frontend/public/locales/es/page.tasks.json b/frontend/public/locales/es/page.tasks.json new file mode 100644 index 0000000..0560806 --- /dev/null +++ b/frontend/public/locales/es/page.tasks.json @@ -0,0 +1,135 @@ +{ + "tasks_page": { + "tasks_page_by_deadline": { + "unallocated": "Sin asignar", + "overdue": "Atrasado", + "today": "Hoy", + "tomorrow": "Mañana", + "upcoming": "Próximos", + "resolved": "Completado", + "board": "Tablero", + "calendar": "Calendario" + }, + "common": { + "tasks_page_template": { + "create_new": "Crear nueva tarea" + }, + "ui": { + "empty_list_block": { + "annotation1": "Aún no tienes tareas o no se encontró nada con los filtros seleccionados.", + "annotation2": "Usa el", + "annotation3": "botón para añadir una tarea." + }, + "tasks_page_header": { + "title": "Tareas y Actividades", + "timeline": "Gantt", + "board": "Tablero", + "list": "Lista", + "calendar": "Calendario", + "user_select_button": "Compartir", + "settings_button": { + "sync": "Sincronizar", + "table_settings": "Configuración de la tabla", + "settings": "Configuraciones", + "board_settings": "Configuración del tablero" + }, + "create_button_tooltip": "Añadir tarea" + }, + "tasks_list_settings_drawer": { + "table_settings": "Configuración de la tabla", + "display_columns": "Mostrar columnas" + }, + "tasks_filter_drawer": { + "just_my_tasks": "Solo mis tareas", + "sorting": "Ordenar", + "done": "Hecho", + "sorting_options": { + "manual": "Manual", + "created_asc": "Creado: de antiguo a nuevo", + "created_desc": "Creado: de nuevo a antiguo" + }, + "created_at": "Fecha de creación", + "start_date": "Fecha de inicio", + "end_date": "Fecha de fin", + "resolve_date": "Fecha de finalización", + "assignee": "Asignado a", + "reporter": "Reportero", + "type": "Tipo", + "stage": "Etapa", + "groups": "Grupos", + "linked_cards": "Tarjetas vinculadas", + "placeholders": { + "card_name": "Nombre de la tarjeta", + "search_by_task_name": "Buscar por nombre de tarea", + "search_by_activity_name": "Buscar por nombre de actividad" + } + } + } + }, + "tasks_page_calendar": { + "new_event": "Nueva tarea", + "no_events": "No hay tareas para este periodo.", + "all_day": "Todo el día" + }, + "tasks_page_list": { + "all_columns_hidden": "Todas las columnas están ocultas en la configuración de la tabla" + }, + "activity_item": { + "no_card_access": "No puedes acceder a esta tarjeta", + "drag_disabled": "No tiene permiso para mover esta tarjeta. Póngase en contacto con el administrador de la cuenta para obtener acceso." + }, + "tasks_page_timeline": { + "annotation": { + "minute": ["minuto", "minutos", "minutos"], + "hour": ["hora", "horas", "horas"], + "day": ["día", "días", "días"] + }, + "today": "Hoy", + "view": { + "fifteen_minutes": "15 Minutos", + "hour": "Hora", + "day": "Día", + "week": "Semana", + "month": "Mes", + "quarter": "Trimestre", + "half_year": "Medio año" + }, + "format": { + "major_format": { + "fifteen_minutes": "YYYY, MMMM D", + "hour": "YYYY, MMMM D", + "day": "YYYY, MMMM", + "week": "YYYY, MMMM", + "month": "YYYY", + "quarter": "YYYY", + "half_year": "YYYY" + }, + "minor_format": { + "fifteen_minutes": "HH:mm", + "hour": "HH", + "day": "D", + "week": "wo [semana]", + "month": "MMMM", + "quarter": "[T]Q", + "half_year": "YYYY-" + } + } + }, + "task_item": { + "planned_time": "Tiempo estimado:", + "completed": "Completado", + "complete": "Completar", + "delete_task": "Eliminar tarea", + "copy_link": "Copiar enlace", + "drag_disabled": "No tiene permiso para mover esta tarea. Póngase en contacto con el administrador de la cuenta para obtener acceso.", + "date": "{{date}} a las {{time}}" + }, + "time_allocation_card": { + "users": "Usuarios", + "planned_time": "Tiempo estimado", + "total": "Total", + "hour": "h", + "minute": "m" + } + } +} diff --git a/frontend/public/locales/es/store.field-groups-store.json b/frontend/public/locales/es/store.field-groups-store.json new file mode 100644 index 0000000..f1eb0c1 --- /dev/null +++ b/frontend/public/locales/es/store.field-groups-store.json @@ -0,0 +1,9 @@ +{ + "field_groups_store": { + "requisites": "Requisitos", + "analytics": "Analista", + "errors": { + "create_at_least_one_field": "Por favor, crea al menos un campo en cada grupo" + } + } +} diff --git a/frontend/public/locales/es/store.fields-store.json b/frontend/public/locales/es/store.fields-store.json new file mode 100644 index 0000000..d15cb09 --- /dev/null +++ b/frontend/public/locales/es/store.fields-store.json @@ -0,0 +1,8 @@ +{ + "fields_store": { + "errors": { + "create_at_least_one_field": "Por favor, crea al menos un campo", + "duplicate_name": "Los nombres de campo deben ser únicos: {{fieldName}}" + } + } +} diff --git a/frontend/public/locales/fr/common.json b/frontend/public/locales/fr/common.json new file mode 100644 index 0000000..41060d4 --- /dev/null +++ b/frontend/public/locales/fr/common.json @@ -0,0 +1,395 @@ +{ + "connect_with_google": "Connecter avec Google", + "responsible": "Responsable", + "all_cards": "Toutes Les Cartes", + "apply_to_all_button": { + "apply": "Appliquer", + "apply_to_all": "Appliquer à tous", + "apply_to_all_accounts": "Appliquer les paramètres du tableau pour tous les utilisateurs :", + "apply_warning_title": "Êtes-vous sûr de vouloir appliquer vos paramètres locaux du tableau pour tous les utilisateurs ?", + "apply_warning_annotation": "Cette action écrasera tous les paramètres du tableau pour les autres utilisateurs du système. Elle ne peut pas être annulée." + }, + "version_modal": { + "title": "✨ {{company}} vient de s'améliorer !", + "annotation": "Mettez à jour vers la version {{version}} !", + "update": "Mettre à jour" + }, + "reload_modal": { + "title": "La page est obsolète !", + "annotation": "Les données de cette page peuvent ne plus être à jour. Rechargez-la pour garantir un fonctionnement optimal.", + "update": "Recharger" + }, + "select_stage": "Sélectionner l'étape", + "new_activity_type": "Nouveau type d'activité", + "video_error": "Impossible de télécharger et de lire le fichier vidéo", + "image_error": "Impossible de télécharger et de prévisualiser l'image", + "view_document": "Voir : {{document}}", + "object": "objet", + "all_day": "Toute la journée", + "supervisor": "Superviseur", + "current_chat_user_hint": "Vous ne pouvez pas vous retirer du chat", + "coming_soon": "À venir...", + "field_readonly": "Ce champ est en lecture seule", + "now": "Maintenant", + "default": "Par défaut", + "exact_time": "Heure exacte", + "card_copy": "Cette copie a été créée par l'automatisation du changement d'étape", + "customize": "Personnaliser", + "day_char": "j", + "hour_char": "h", + "minute_char": "m", + "days": ["jour", "jours", "jours"], + "minutes": ["minute", "minutes", "minutes"], + "hours": ["heure", "heures", "heures"], + "seconds": ["seconde", "secondes", "secondes"], + "files_count": ["{{count}} fichier", "{{count}} fichiers", "{{count}} fichiers"], + "loading_title": "Chargement...", + "changes_unsaved_blocker": { + "title": "Les modifications n'ont pas été enregistrées", + "annotation": "Souhaitez-vous enregistrer avant de fermer ou quitter sans enregistrer?", + "approve_title": "Enregistrer", + "cancel_title": "Quitter sans enregistrer" + }, + "browser_not_supported_title": "Votre navigateur n'est pas pris en charge.", + "browser_not_supported_annotation": "Pour utiliser {{company}}, nous recommandons d'utiliser la dernière version de Chrome, Firefox ou Safari. Cela garantira la meilleure expérience possible. Vous pouvez trouver les instructions ci-dessous.", + "duplicate_title": "La carte existe déjà", + "duplicate_warning_annotation": "Voulez-vous créer un duplicata ou sélectionner une carte existante?", + "select_existing": "Sélectionner existante", + "duplicate_warning_cancel_title": "Créer un duplicata", + "duplicate_forbidden_annotation": "L'administrateur a interdit la création de contacts en double. Veuillez sélectionner une carte existante ou utiliser un autre numéro unique.", + "add_as_new": "Créer un duplicata", + "tasks_total": ["Tâches", "Tâches", "Tâches"], + "activities_total": ["Activités", "Activités", "Activités"], + "visits_total": ["Visite", "Visites", "Visites"], + "trial_left": { + "one_day": "Essai : 1 jour", + "several_days": ["Essai : {{left}} jours", "Essai : {{left}} jours", "Essai : {{left}} jours"] + }, + "meta_description": "{{company}} est un créateur d'espace de travail puissant et flexible conçu spécialement pour les petites et moyennes entreprises.", + "international_number": "Numéro international", + "phone_number": "Numéro de téléphone", + "no_available_groups": "Aucun groupe disponible", + "add_new_group": "Ajouter un nouveau groupe", + "schedule": "Calendrier", + "board": "Tableau", + "settings": "Paramètres", + "visits": "Réunions", + "orders": "Commandes", + "row": "Ligne", + "column": "Colonne", + "external_link": "Lien externe", + "loading": "Chargement", + "products": "Produits", + "for_sales": "À vendre", + "rentals": "Locations", + "title": "Titre", + "name": "Nom", + "max_length": "Maximum {{length}} caractères", + "activities": "Activités", + "project_tasks_board": "Tableau", + "tasks_board": "Tableau des tâches", + "tasks_list": "Liste", + "time_board": "Tableau de temps", + "trial": "Essai", + "show_less": "Afficher moins", + "show_more": "Afficher plus", + "search": "Rechercher", + "clear_search": "Effacer la recherche", + "profile": "Profil", + "avatar_alt": "Avatar de {{firstName}}", + "log_out": "Déconnexion", + "select_date": "Sélectionner une date", + "task_title": "Titre de la tâche", + "select": "Sélectionner", + "overview": "Aperçu", + "new_board": "Nouveau tableau", + "enter_html_markup": "Entrez le balisage HTML", + "filter": "Filtrer", + "notifications": "Notifications", + "mail": "Mail", + "multichat": "Multi Messagerie", + "total": "Total : {{total}}", + "select_period": "Sélectionner une période", + "select_groups": "Sélectionner des groupes", + "select_group": "Sélectionner un groupe", + "delay": "Délai", + "hide_sidebar": "Masquer la barre latérale", + "show_sidebar": "Afficher la barre latérale", + "site_form": "Formulaires Web", + "workspace_tags": { + "site_form": "Formulaire Web", + "headless_site_form": "Formulaire Web via API", + "online_booking_site_form": "Formulaire de réservation en ligne", + "project": "Projets et tâches", + "universal": "Module universel", + "partner": "Partenaires", + "deal": "CRM", + "contact": "Contacts", + "company": "Entreprises", + "supplier": "Fournisseurs", + "contractor": "Sous-traitants", + "hr": "Recrutement", + "products_for_sales": "Gestion des stocks", + "products_rentals": "Gestion des locations", + "visits": "Planification des rendez-vous" + }, + "sidebar": { + "builder": "Créateur", + "tasks_and_activities": "Tâches et Activités", + "mail": "Mail", + "settings": "Paramètres", + "companies": "Entreprises", + "contacts": "Contacts", + "multichat": "Multi Messagerie" + }, + "buttons": { + "save_and_leave": "Enregistrer et Quitter", + "create": "Créer", + "cancel": "Annuler", + "save": "Enregistrer", + "delete": "Supprimer", + "add": "Ajouter", + "edit": "Éditer", + "ok": "Ok", + "activate": "Activer", + "deactivate": "Désactiver", + "back": "Retour", + "clear": "Effacer", + "continue": "Continuer" + }, + "profile_modal": { + "app_version": "Version de l'application", + "app_version_hint": "La version actuelle de l'application.", + "clear_cache": "Effacer le cache", + "clear_cache_title": "Effacer le cache de l'application", + "clear_cache_hint": "Cette action effacera toutes les données de cache client non essentielles et rechargera la page.", + "password": "Mot de passe", + "profile": "Votre profil", + "avatar": "avatar", + "first_name": "Prénom", + "first_name_hint": "Maximum {{length}} caractères", + "last_name": "Nom", + "last_name_hint": "Maximum {{length}} caractères", + "change_avatar": "Changer", + "add_avatar": "Ajouter une photo", + "delete_avatar": "Supprimer", + "avatar_annotation": "La photo doit être au format PNG ou JPG. Résolution maximale de 1024px*1024px.", + "date_of_birth": "Date de naissance", + "phone": "Téléphone", + "employment_start": "Date de début d'emploi", + "email": "Adresse e-mail", + "current_password": "Mot de passe actuel", + "new_password": "Nouveau mot de passe", + "confirm_password": "Confirmer le mot de passe", + "change_password": "Changer le mot de passe", + "upload_avatar": "Télécharger un avatar", + "passwords_do_not_match": "Les mots de passe ne correspondent pas", + "incorrect_password": "Mot de passe incorrect", + "image_edit_modal": { + "annotation": "Ajustez l'affichage de la photo pour votre profil. Faites glisser le curseur pour zoomer ou dézoomer la photo." + } + }, + "form": { + "my_select": { + "no_options": "Aucune option", + "placeholder": "Sélectionner", + "search": "Rechercher...", + "select_user": "Sélectionner un utilisateur", + "select_all": "Sélectionner tout", + "clear_selection": "Effacer la sélection" + }, + "key_value_input": { + "key": "Clé", + "value": "Valeur", + "add": "Ajouter" + }, + "week_intervals_input": { + "mo": "Lun", + "tu": "Mar", + "we": "Mer", + "th": "Jeu", + "fr": "Ven", + "sa": "Sam", + "su": "Dim", + "to": "à", + "unavailable": "Hors horaires" + } + }, + "subscription_period_modal": { + "contact_admin": "Contactez l'administrateur du compte pour soumettre une demande de paiement d'abonnement.", + "trial_left": "Il vous reste {{left}} jour(s) d'essai gratuit", + "trial_over": "Votre essai gratuit a expiré", + "subscription_left": "Il vous reste {{left}} jour(s) d'abonnement", + "subscription_over": "Votre abonnement a expiré", + "trial_sub_title": "Choisissez un plan et continuez à utiliser {{company}} Workspace.", + "subscription_sub_title": "Renouvelez votre abonnement pour continuer à accéder à {{company}} Workspace.", + "select_plan": "Sélectionner un plan" + }, + "trial_period_over_modal": { + "send": "Envoyer", + "title": "Veuillez nous contacter pour payer {{company}}", + "name": "* Nom", + "email": "* E-mail", + "phone": "* Téléphone", + "number_of_users": "* Nombre d'utilisateurs", + "subscribe": "S'abonner", + "yearly": "Annuel -20%", + "monthly": "Mensuel", + "choose_plan": "* Choisissez votre plan", + "trial_left": "Il vous reste {{left}} jours d'essai", + "trial_left_one": "Il vous reste {{left}} jour d'essai", + "trial_over": "Votre période d'essai est terminée", + "subscription_over": "Votre période d'abonnement est terminée", + "placeholders": { + "name": "Votre nom" + } + }, + "update_task_modal": { + "title": "Titre", + "linked_card": "Carte liée", + "stage": "Étape", + "add_description": "Ajouter une description", + "no_description": "Pas de description", + "not_found": "Cette tâche n'a pas été trouvée", + "close": "Fermer", + "description": "Description", + "subtasks": "Sous-tâches", + "comments": "Commentaires", + "planned_time": "Temps estimé", + "board_name": "Nom du tableau", + "assignee": "Assigné", + "start_date": "Date de début", + "end_date": "Date de fin", + "files": "Fichiers", + "attach_files": "Joindre des fichiers", + "reporter": "Rapporteur: {{name}}, {{date}} à {{time}}", + "reporter_noun": "Rapporteur", + "enter_description": "Entrer la description", + "new_comment": "Nouveau commentaire", + "placeholders": { + "board": "Sélectionner un tableau", + "search_card": "Rechercher une carte" + } + }, + "delete_demo": { + "button_text": "Supprimer la démo", + "modal_title": "Voulez-vous vraiment supprimer les données de démonstration?", + "modal_annotation": "Toutes les données de démonstration seront supprimées. Cette action ne peut pas être annulée." + }, + "filter_drawer_controls": { + "clear": "Effacer", + "save_filter_settings": "Enregistrer les paramètres de filtre", + "error_message": "Malheureusement, le filtre n'a pas été appliqué. Veuillez réessayer." + }, + "period_types": { + "all_time": "Tout le temps", + "today": "Aujourd'hui", + "yesterday": "Hier", + "current_week": "Semaine en cours", + "last_week": "Semaine dernière", + "current_month": "Mois en cours", + "last_month": "Mois dernier", + "current_quarter": "Trimestre en cours", + "last_quarter": "Trimestre dernier", + "placeholders": { + "select_period": "Sélectionner une période" + } + }, + "file_size_warning_modal": { + "title": "Fichier trop volumineux", + "annotation": "La taille maximale du fichier à télécharger est de {{maxSizeMb}} MB. Compressez-le ou choisissez un autre.", + "cancel": "Annuler" + }, + "player": { + "default": "Par défaut", + "fast": "Rapide", + "super_fast": "Très rapide" + }, + "languages": { + "en": "Anglais", + "fr": "Français", + "ru": "Russe", + "es": "Espagnol", + "pt": "Portugais", + "ar": "Arabe", + "tr": "Turc", + "vi": "Vietnamien", + "az": "Azéri", + "uk": "Ukrainien", + "id": "Indonésien" + }, + "calendar": { + "month": "Mois", + "week": "Semaine", + "day": "Jour", + "agenda": "Agenda", + "more": "plus", + "users": "Utilisateurs", + "today": "Aujourd'hui", + "all": "Toutes les tâches", + "completed": "Tâches terminées", + "pending": "Tâches en attente", + "colored": "Coloré", + "colorless": "Incolore", + "show_weekends": "Afficher le week-end", + "time_scale": "Échelle de temps", + "from": "De", + "to": "À", + "stage_select": "Sélectionner l'étape" + }, + "page_title": { + "system": { + "browser_not_supported": { + "page_title": "Navigateur non pris en charge" + } + }, + "builder": { + "production": "Processus de Production", + "builder": "Créateur", + "site_form": "Créateur | Formulaires Web", + "workspace": "Votre Espace de Travail", + "crm": "Créateur | CRM", + "project_management": "Créateur | Projets et Tâches", + "product_management_for_sales": "Créateur | Gestion des Stocks", + "product_management_rentals": "Créateur | Gestion des Locations", + "scheduler": "Créateur | Planification des Rendez-vous", + "supplier_management": "Créateur | Fournisseurs", + "contractor_management": "Créateur | Sous-traitants", + "partner_management": "Créateur | Partenaires", + "hr_management": "Créateur | Recrutement", + "contact": "Créateur | Contacts", + "company": "Créateur | Entreprises", + "universal_module": "Créateur | Module Universel", + "products": { + "rental": "Créateur | Gestion des Locations", + "sale": "Créateur | Gestion des Stocks" + } + }, + "time_board": "Tableau de Temps", + "activities_board": "Tableau des Activités", + "settings": { + "general_settings": "Paramètres | Paramètres Généraux", + "user_list": "Paramètres | Utilisateurs — Configuration des Utilisateurs", + "user_departments": "Paramètres | Utilisateurs — Groupes", + "user_edit": "Paramètres | Utilisateurs — Modifier l'utilisateur", + "calls": { + "account": "Paramètres | Appels — Compte", + "users": "Paramètres | Appels — Utilisateurs", + "scenarios": "Paramètres | Appels — Configuration", + "sip_registrations": "Paramètres | Appels — Enregistrements SIP" + }, + "mailing": "Paramètres | Paramètres de Courrier", + "integrations": "Paramètres | Intégrations", + "billing": "Paramètres | Facturation", + "api_access": "Paramètres | Accès à l'API", + "superadmin": "Settings | Admin Panel", + "document": { + "templates": "Paramètres | Documents — Modèles de Documents", + "creation": "Paramètres | Documents — Champs de Création de Documents" + } + }, + "mailing": "E-mail et Messagerie", + "orders": "Commandes", + "notes": "Notes" + } +} diff --git a/frontend/public/locales/fr/component.card.json b/frontend/public/locales/fr/component.card.json new file mode 100644 index 0000000..89be86e --- /dev/null +++ b/frontend/public/locales/fr/component.card.json @@ -0,0 +1,344 @@ +{ + "card": { + "status": { + "active": "Active", + "liquidating": "En liquidation", + "liquidated": "Liquidée", + "bankrupt": "Faillite", + "reorganizing": "En cours de fusion avec une autre entité juridique, suivie d’une liquidation" + }, + "type": { + "legal": "Personne morale", + "individual": "Entrepreneur individuel" + }, + "branch_type": { + "main": "Siège social", + "branch": "Filiale" + }, + "opf": { + "bank": "Banque", + "bank_branch": "Agence bancaire", + "nko": "Organisation de crédit non bancaire (NKO)", + "nko_branch": "Agence NKO", + "rkc": "Centre de règlement et de caisse", + "cbr": "Direction de la Banque de Russie (mars 2021)", + "treasury": "Direction du Trésor (mars 2021)", + "other": "Autre" + }, + "days": ["jour", "jours", "jours"], + "creation_date": "Créé", + "in_work": "En cours", + "shipping_date": "Expédié", + "closing_date": "Fermé", + "open_chat": "Ouvrir le chat", + "analytics": "Analyste", + "card_focus": "Centrer (met en surbrillance la carte dans le tableau et la liste)", + "field_formula_circular_dependency_warning": { + "warning_title": "Impossible d'enregistrer les modifications", + "warning_annotation": "Vous essayez de mettre à jour le champ de formule \"{{fieldName}}\" en utilisant une autre formule qui crée une dépendance circulaire (une formule dépend d'une autre formule qui en dépend). Veuillez mettre à jour les paramètres de la formule pour résoudre la dépendance circulaire et réessayer.", + "cancel_changes": "Annuler les modifications", + "continue": "Continuer" + }, + "field_used_in_formula_warning": { + "warning_title": "Impossible d'enregistrer les modifications", + "warning_annotation": "Vous essayez de supprimer le champ \"{{fieldName}}\" qui est utilisé dans une formule. Supprimez-le d'abord de la formule et réessayez.", + "cancel_changes": "Annuler les modifications", + "continue": "Continuer" + }, + "mutation_warning": { + "title": { + "change_stage": "Impossible de changer l'étape", + "save_changes": "Impossible d'enregistrer les modifications" + }, + "annotation": "Certains champs obligatoires ne sont pas remplis. Veuillez les remplir et réessayer.", + "continue": "Continuer" + }, + "change_linked_responsibles_modal": { + "header": "Changement de responsable", + "annotation": "Sélectionnez les cartes liées où vous souhaitez aussi changer de responsable.", + "change_responsible_in_chats": "Ajouter le responsable aux discussions des cartes", + "chats_hint": "Le nouveau responsable sera ajouté aux discussions des cartes liées où il n'est pas encore présent." + }, + "owner": "Propriétaire", + "delete_warning_title": "Voulez-vous vraiment supprimer l'entité?", + "delete_warning_caption": "Cette action ne peut pas être annulée.", + "files": "Fichiers", + "from_card_or_linked_entities": "Depuis la carte ou les entités liées", + "recent": "Récent", + "change_board": "Changer de tableau", + "placeholders": { + "name": "Entrez le nom..." + }, + "ui": { + "requisites_codes": { + "ie_name": "Prénom de l'Entrepreneur Individuel", + "ie_surname": "Nom de l'Entrepreneur Individuel", + "ie_patronymic": "Deuxième Prénom de l'Entrepreneur Individuel", + "org_name": "Nom de l'Entreprise", + "org_tin": "NIF de l'Entreprise", + "org_trrc": "KPP de l'Entreprise", + "org_psrn": "PSRN", + "org_type": "Type d'Entreprise", + "org_full_name": "Nom Complet de l'Entreprise", + "org_short_name": "Nom Abrégé de l'Entreprise", + "org_management_name": "Nom du Directeur", + "org_management_post": "Poste du Directeur", + "org_management_start_date": "Date de Prise de Fonction du Directeur", + "org_branch_count": "Nombre de Succursales", + "org_branch_type": "Type de Succursale", + "org_address": "Adresse de l'Entreprise", + "org_reg_date": "Date d'Enregistrement", + "org_liquidation_date": "Date de Liquidation", + "org_status": "Statut de l'Entreprise", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Nombre Moyen d'Employés", + "org_extra_founders": "Fondateurs de l'Entreprise", + "org_extra_managers": "Dirigeants de l'Entreprise", + "org_extra_capital": "Capital Social", + "org_extra_licenses": "Licences", + "org_extra_phones": "Numéros de Téléphone", + "org_extra_emails": "Adresses Email", + "bank_name": "Nom de la Banque", + "bank_bic": "BIC de la Banque", + "bank_swift": "SWIFT", + "bank_tin": "NIF de la Banque", + "bank_trrc": "KPP de la Banque", + "bank_correspondent_acc": "Compte Correspondant", + "bank_payment_city": "Ville pour l'Ordre de Paiement", + "bank_opf_type": "Type d'Organisation Bancaire", + "bank_checking_account": "Compte de Compte" + }, + "analytics": "Analytics", + "requisites": "Requisites", + "card_page_header": { + "overview": "Aperçu", + "products": "Produits", + "create_new_order": "Créer une nouvelle commande", + "orders": "Commandes", + "board": "Tableau", + "list": "Liste", + "calendar": "Calendrier", + "timeline": "Gantt", + "relocate_card_button": "Relocaliser la carte", + "order_denied": "Vous n'êtes pas autorisé à créer une commande. Contactez l'administrateur du compte pour y accéder." + }, + "actions_dropdown": { + "delete_entity": "Supprimer l'entité", + "fine_tune": "Ajuster" + }, + "mini_card": { + "enter_name": "Entrez le nom de {{name}}", + "unpin": "Détacher", + "owner": "Propriétaire", + "stage": "Étape", + "placeholders": { + "select_stage": "Sélectionnez l'étape" + }, + "fields": { + "value": "Valeur", + "participants": "Participants", + "start_date": "Date de début", + "end_date": "Date de fin", + "description": "Description" + } + }, + "stages": { + "disabled_while_adding": "Le changement de statut sera disponible après la création de la fiche" + }, + "feed": { + "unknown": "Inconnu 👤", + "readonly": "Vous ne pouvez pas ajouter d'éléments à cette carte", + "disabled_while_adding": "L'ajout d’éléments à la fiche sera disponible après sa création", + "notes": "Notes", + "activities": "Activités", + "tasks": "Tâches", + "amwork_messenger": "Messagerie de {{company}}", + "documents": "Documents", + "create_documents_tab": "Créer des documents", + "create_chat": "Créer un chat avec l'équipe", + "add_visit": "Planifier une réunion", + "share_documents": "Partager des documents générés", + "empty": "Il n'y a encore rien ici", + "add_activity": { + "placeholder": "Description de l'activité" + }, + "add_note": { + "add_note": "Ajouter une note" + }, + "add_task_modal": { + "dates_error": "La date de fin doit être ultérieure à la date de début.", + "tab": "Ajouter une tâche", + "task_name": "Nom de la tâche *", + "planned_time": "Temps estimé", + "board_name": "Nom du tableau", + "assignee": "Attribué à", + "start_date": "Date de début", + "end_date": "Date de fin", + "description": "Description", + "subtasks": "Sous-tâches", + "placeholder": "Ajouter un titre...", + "add_board": "Ajouter un tableau", + "save": "Enregistrer", + "new_task": "Nouvelle tâche", + "repeating_task": { + "title": "Répéter la tâche", + "hint": "Définissez le nombre et l’intervalle des tâches répétées, si nécessaire. Les tâches seront créées avec ces paramètres. Si la tâche a une heure de début ou de fin, elles seront planifiées en respectant l’intervalle et les jours ouvrables.", + "count": "Quantité", + "interval": "Intervalle", + "interval_disabled_tooltip": "Définissez l'heure de début ou de fin de la tâche", + "intervals": { + "none": "Sans intervalle", + "day": "Tous les jours", + "week": "Chaque semaine", + "month": "Chaque mois" + } + }, + "placeholders": { + "board": "Sélectionnez le tableau", + "subtask": "Nouvelle sous-tâche" + } + }, + "filter": { + "all": "Tout", + "tasks": "Tâches", + "activities": "Activités", + "notes": "Notes", + "mail": "Courriel", + "files": "Fichiers", + "calls": "Appels" + }, + "create_documents": { + "order": "Commande-{{number}}", + "documents": "Documents", + "create_documents": "Créer des documents", + "create_new_documents": "Créer de nouveaux documents", + "select_all": "Tout sélectionner", + "clear_selection": "Effacer la sélection", + "send_by_email": "Envoyer par courriel", + "add_template": "Ajouter un modèle", + "invalid_template_title": "Erreur dans le modèle", + "invalid_template_annotation": "Impossible de générer le document en raison d'erreurs de syntaxe dans le modèle. Mettez à jour le modèle ou choisissez-en un autre.", + "hints": { + "cancel": "Annuler", + "generate": "Générer", + "generate_anyway": "Générer quand même", + "docx_format": "Format DOCX", + "pdf_format": "Format PDF", + "no_documents_title": "Aucun modèle disponible", + "no_documents_annotation": "Aucun modèle de document disponible pour cette entité. Créez-en de nouveaux ou modifiez les existants dans Documents sur la page des paramètres.", + "creating_documents_title": "Création de vos documents", + "creating_documents_annotation": "Cela peut prendre un certain temps, mais après vous pourrez envoyer ou télécharger les documents.", + "select_template_title": "Sélectionnez un modèle", + "select_template_annotation": "Pour sélectionner un modèle, ouvrez la liste déroulante et choisissez le fichier souhaité.", + "format_title": "Format", + "issues_identified": "Plusieurs problèmes ont été identifiés", + "invalid_tags_annotation": "Il y a des codes qui semblent invalides ou associés à un champ vide :", + "format_annotation": "Sélectionnez le(s) format(s) dans lequel vous souhaitez générer le document." + }, + "placeholders": { + "select_order": "Sélectionnez la commande", + "select_template": "Sélectionnez le modèle", + "invalid_tags_annotation": "Certains codes sont invalides ou associés à un champ vide :" + } + }, + "activity_block": { + "creator": "Assigné à", + "start_date": "Date", + "plan_time": "Heure" + }, + "creator_tag": { + "task_setter_at_time": "{{taskSetter}} à {{time}}" + }, + "files_block": { + "unknown_file": "Fichier inconnu", + "downloaded_file": "{{companyName}} – Fichier téléchargé", + "attachment": "pièce jointe", + "attachments": "pièces jointes", + "download_all": "Tout télécharger" + }, + "note_block": { + "block_title": "Note", + "creation_date": "Date de création", + "message": "Message", + "to_recipient": "à {{recipient}}" + }, + "task_block": { + "creator": "Assigné à", + "start_date": "Date de début", + "end_date": "Date de fin", + "no_due_date": "Pas de date limite" + }, + "mail_block": { + "sender": "Expéditeur", + "recipient": "Destinataire", + "date": "Date", + "seen": "Vu" + }, + "document_block": { + "creation_date": "Date de création", + "docs": "Docs", + "annotation": "{{creator}} a créé {{documentName}} le {{date}}" + }, + "call_block": { + "start_date": "Date", + "who_called": "Appelant", + "who_got_call": "Destinataire de l'appel", + "did_not_get_through": "N'a pas pu passer", + "time": { + "d": "j", + "h": "h", + "m": "m", + "s": "s" + }, + "status": { + "missed_call": "Appel manqué", + "incoming_call": "Appel entrant", + "outgoing_call": "Appel sortant" + }, + "comment": "Commentaire" + }, + "common": { + "author": "Auteur", + "reporter": "Reporter", + "created_at": "{{day}} à {{time}}", + "result": "Résultat", + "functional_options": { + "edit": "Éditer", + "delete": "Supprimer" + }, + "item_tag": { + "tasks": { + "resolved": "Tâche terminée", + "expired": "Tâche expirée", + "future": "Tâche à venir", + "today": "Tâche d'aujourd'hui" + }, + "activities": { + "resolved": "Activité terminée", + "expired": "Activité expirée", + "future": "Activité à venir", + "today": "Activité d'aujourd'hui" + } + }, + "activity_type_picker": { + "add_new_type": "Ajouter un nouveau type", + "warn_title": "Voulez-vous vraiment supprimer ce type d'activité?", + "warn_annotation": "Vous ne pourrez plus créer ce type d'activité.", + "placeholders": { + "select_activity_type": "Sélectionnez le type d'activité" + } + }, + "user_picker": { + "add": "Ajouter", + "placeholder": "Sélectionnez un utilisateur" + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/component.entity-board.json b/frontend/public/locales/fr/component.entity-board.json new file mode 100644 index 0000000..85ba019 --- /dev/null +++ b/frontend/public/locales/fr/component.entity-board.json @@ -0,0 +1,23 @@ +{ + "entity_board": { + "ui": { + "project_entity_card_item": { + "start_date": "Date de début", + "end_date": "Date de fin", + "titles": { + "not_resolved": "Tâches non résolues", + "upcoming": "Prochaines tâches", + "overdue": "Tâches en retard", + "today": "Tâches d'aujourd'hui", + "resolved": "Tâches résolues" + } + }, + "common_entity_card": { + "no_tasks": "Pas de tâches", + "task_today": "Tâche d'aujourd'hui", + "overdue_task": "Tâche en retard", + "upcoming_task": "Prochaine tâche" + } + } + } +} diff --git a/frontend/public/locales/fr/component.section.json b/frontend/public/locales/fr/component.section.json new file mode 100644 index 0000000..ac51df5 --- /dev/null +++ b/frontend/public/locales/fr/component.section.json @@ -0,0 +1,191 @@ +{ + "section": { + "section_header_with_boards": { + "board": "Tableau", + "list": "Liste", + "dashboard": "Tableau de bord", + "reports": "Rapports", + "timeline": "Gantt", + "automation": "Automatisation", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Bientôt...)", + "tooltips": { + "reports_denied": "Vous n'êtes pas autorisé à afficher les rapports. Contactez l'administrateur du compte pour y accéder.", + "dashboard_denied": "Vous n'êtes pas autorisé à afficher le tableau de bord. Contactez l'administrateur du compte pour y accéder." + } + }, + "empty_projects_block": { + "annotation": "Vous n'avez pas encore de projets ou rien n'a été trouvé avec les filtres sélectionnés." + }, + "common": { + "add_card": "Ajouter une carte", + "new_stage": "Nouvelle Étape", + "create_button_tooltip": { + "universal": "Ajouter une Carte", + "partner": "Ajouter un Partenaire", + "deal": "Ajouter une Affaire", + "contact": "Ajouter un Contact", + "company": "Ajouter une Entreprise", + "supplier": "Ajouter un Fournisseur", + "contractor": "Ajouter un Sous-traitant", + "hr": "Ajouter un Candidat", + "project": "Ajouter un Projet" + }, + "cards_total": { + "universal": ["Cartes", "Cartes", "Cartes"], + "partner": ["Partenaires", "Partenaires", "Partenaires"], + "customer": ["Clients", "Clients", "Clients"], + "deal": ["Cartes", "Cartes", "Cartes"], + "contact": ["Contacts", "Contacts", "Contacts"], + "company": ["Entreprises", "Entreprises", "Entreprises"], + "supplier": ["Fournisseurs", "Fournisseurs", "Fournisseurs"], + "contractor": ["Sous-traitants", "Sous-traitants", "Sous-traitants"], + "hr": ["Candidats", "Candidats", "Candidats"], + "project": ["Projets", "Projets", "Projets"] + }, + "filter_button": { + "ui": { + "filter_drawer": { + "closed_at": "Date de Clôture", + "just_my_cards": "Seulement mes Cartes", + "sorting": "Trier", + "creation_date": "Date de Création", + "owners": "Propriétaires", + "stages": "Étapes", + "tasks": "Tâches", + "placeholders": { + "selected_options": "Options sélectionnées", + "search_by_card_name": "Rechercher par nom de carte" + }, + "sorting_options": { + "manual": "Manuel", + "created_asc": "Créé : du plus ancien au plus récent", + "created_desc": "Créé : du plus récent au plus ancien", + "name_asc": "Titre: ascendant", + "name_desc": "Titre: décroissant" + }, + "tasks_options": { + "all": "Toutes les cartes", + "with_task": "Avec tâches", + "without_task": "Sans tâches", + "overdue_task": "Avec tâches en retard", + "today_task": "Avec tâches du jour" + }, + "field_filter_string": { + "is_empty": "Est vide", + "is_not_empty": "N'est pas vide", + "contains": "Contient...", + "placeholders": { + "value": "Valeur" + } + }, + "field_filter_exists": { + "is_empty": "Est vide", + "is_not_empty": "N'est pas vide" + } + }, + "date_period_picker": { + "date_period_segmented_control": { + "period": "Période", + "period_hint": "Pour une période sélectionnée", + "from": "De", + "from_hint": "À partir de la date sélectionnée", + "to": "À", + "to_hint": "Avant la date sélectionnée" + }, + "placeholders": { + "select_period": "Sélectionner une période" + } + }, + "field_filter_items": { + "field_filter_boolean": { + "switch_on": "Allumer", + "switch_off": "Éteindre" + }, + "field_filter_number": { + "from": "De", + "to": "À" + } + } + } + }, + "settings_button": { + "module_settings": "Paramètres du module", + "settings": "Paramètres", + "import": "Importer", + "get_import_template": "Obtenir le modèle d'importation", + "board_settings": "Paramètres du tableau", + "table_settings": "Paramètres du tableau", + "upload_file": "Télécharger un fichier", + "sales_pipeline_settings": "Paramètres du pipeline de vente", + "request_setup": "Demander la configuration de {{company}}", + "import_entities_modal": { + "title": "Importer depuis Excel", + "annotation": "Pour importer votre base de données dans {{company}}, suivez quelques étapes simples :", + "perform_import": "Réaliser l'importation", + "setting_up_file_to_import": { + "title": "Configuration d'un fichier à importer", + "step1": "Téléchargez un fichier modèle d'exemple;", + "step2": "Entrez vos données dans un tableau correspondant à la structure de champs de {{company}};", + "step3": "Après avoir terminé les paramètres de correspondance des colonnes, vous pouvez enregistrer les paramètres comme modèle d'importation." + }, + "import_file": { + "title": "Importer un fichier", + "step1": "Cliquez sur \"Télécharger un fichier\";", + "step2": "Dans la fenêtre qui s'ouvre, sélectionnez le modèle préalablement rempli;", + "step3": "Après le téléchargement, cliquez sur le bouton \"Réaliser l'importation\"." + } + }, + "import_in_background_info_modal": { + "title": "Importer depuis Excel", + "content": "Le processus d'importation est actuellement en cours d'exécution en arrière-plan. Vous pouvez continuer à travailler dans {{company}} comme d'habitude, et nous vous informerons une fois le processus terminé.", + "ok": "Ok" + } + }, + "search_box": { + "nothing_found": "Rien n'a été trouvé", + "placeholder": "Rechercher..." + }, + "section_table_settings_sidebar": { + "table_settings": "Paramètres du tableau", + "display_columns": "Afficher les colonnes" + } + }, + "section_table": { + "hidden": "Masqué", + "hidden_hint": "Vous ne pouvez pas voir ni accéder à ce champ, car il a été masqué dans les paramètres des champs du module par l'administrateur du compte.", + "empty": "Vous n'avez encore ajouté aucune carte", + "all_columns_hidden": "Toutes les colonnes sont cachées dans les paramètres du tableau", + "name": "Nom", + "owner": "Propriétaire", + "stage": "Étape", + "date_created": "Date de création", + "value": "Valeur", + "participants": "Participants", + "start_date": "Date de début", + "end_date": "Date de fin", + "description": "Description", + "placeholders": { + "participants": "Ajouter des participants" + }, + "select_all": { + "title": "Sélection de masse", + "description": "Appliquer à toutes les cartes ({{count}}) ou seulement pour la page actuelle ?", + "all_elements": "Pour toutes les cartes", + "for_page": "Seulement pour la page actuelle" + }, + "batch_actions": { + "cards_selected": "Cartes sélectionnées : {{count}}", + "change_responsible": "Changer de responsable", + "change_stage": "Changer d'étape", + "delete": "Supprimer", + "change_stage_annotation": "Sélectionnez une nouvelle étape pour les cartes sélectionnées. Cette action ne peut pas être annulée.", + "change_responsible_annotation": "Sélectionnez un nouveau responsable pour les cartes sélectionnées.", + "action_cannot_be_undone": "Cette action ne peut pas être annulée.", + "delete_warning_title": "Supprimer les cartes sélectionnées", + "delete_warning_annotation": "Êtes-vous sûr de vouloir supprimer les cartes sélectionnées ? Cette action ne peut pas être annulée.", + "select_linked_entities_annotation": "Changer de responsable dans les cartes liées" + } + } + } +} diff --git a/frontend/public/locales/fr/module.automation.json b/frontend/public/locales/fr/module.automation.json new file mode 100644 index 0000000..1ce5bc5 --- /dev/null +++ b/frontend/public/locales/fr/module.automation.json @@ -0,0 +1,368 @@ +{ + "automation": { + "modals": { + "add_activity_automation_modal": { + "current_responsible_user": "Utilisateur responsable actuel", + "activity_default_name": " Activité #{{id}}", + "title_add": "Ajouter une automatisation de création d'activité", + "title_update": "Mettre à jour l'automatisation de création d'activité", + "create_activity": "Créer une activité *", + "responsible_user": "Utilisateur responsable *", + "due_date": "Date d'échéance", + "defer_start": "Date de début", + "activity_type": "Type d'activité *", + "description": "Description", + "current_responsible_user_hint": "Sélectionnez l'utilisateur responsable de l'activité. L'utilisateur actuellement responsable est celui assigné à la carte où l'activité sera créée.", + "placeholders": { + "select_activity_type": "Sélectionner le type d'activité" + } + }, + "add_task_automation_modal": { + "task_default_name": "Tâche #{{id}}", + "title_add": "Ajouter une automatisation de création de tâche", + "title_update": "Mettre à jour l'automatisation de création de tâche", + "create_task": "Créer une tâche *", + "due_date": "Date d'échéance", + "defer_start": "Date de début", + "responsible_user": "Utilisateur responsable *", + "name_and_description": "Nom * et description", + "current_responsible_user": "Utilisateur responsable actuel", + "current_responsible_user_hint": "Sélectionnez l'utilisateur responsable de la tâche. L'utilisateur actuellement responsable est celui assigné à la carte où la tâche sera créée.", + "placeholders": { + "task_name": "Nom de la tâche", + "select_user": "Sélectionner un utilisateur" + } + }, + "change_responsible_automation_modal": { + "change_responsible_default_name": "Responsable #{{id}}", + "title_add": "Ajouter une automatisation de changement de responsable", + "title_update": "Mettre à jour l'automatisation de changement de responsable", + "responsible_user": "Changer l'utilisateur responsable en *", + "placeholders": { + "select_responsible_user": "Sélectionnez le nouvel responsable" + } + }, + "change_stage_automation_modal": { + "change_stage_default_name": "Étape #{{id}}", + "title_add": "Ajouter une automatisation de changement d'étape", + "title_update": "Mettre à jour l'automatisation de changement d'étape", + "change_stage": "Changer d'étape *", + "cards_copies": "Copies de cartes", + "stage": "Pipeline de vente et étape *", + "automation_copy": { + "move_label": "Ne pas créer une copie de l'affaire en changeant son état", + "copy_original_label": "Créer une copie de l'affaire et déplacer l'affaire originale au nouvel état en changeant son état", + "copy_new_label": "Créer une copie de l'affaire en changeant son état, mais laisser l'affaire originale dans son état actuel.", + "hint": "Lorsque cette option est activée, le système créera automatiquement une copie de la carte dans son état actuel à chaque changement d'état automatique. Cela est essentiel pour une précision dans les rapports et les métriques de planification des ventes, en particulier lorsque le changement d'état est associé à des états finaux du système comme 'Terminé avec succès' ou 'Terminé sans succès'." + }, + "placeholders": { + "select_stage": "Sélectionner l'étape" + } + }, + "change_linked_stage_automation_modal": { + "change_stage_default_name": "Étape #{{id}}", + "title_add": "Ajouter une automatisation de changement d'étape", + "title_update": "Mettre à jour l'automatisation de changement d'étape", + "change_stage": "Changer d'étape *", + "cards_copies": "Copies de cartes", + "entity_type": "Module *", + "stage": "Pipeline de vente et étape *", + "no_stages": "Le module sélectionné n'a pas de statut. Sélectionnez un autre module pour créer l'automatisation.", + "automation_copy": { + "move_label": "Ne pas créer une copie de l'affaire en changeant son état", + "copy_original_label": "Créer une copie de l'affaire et déplacer l'affaire originale au nouvel état en changeant son état", + "copy_new_label": "Créer une copie de l'affaire en changeant son état, mais laisser l'affaire originale dans son état actuel.", + "hint": "Lorsque cette option est activée, le système créera automatiquement une copie de la carte dans son état actuel à chaque changement d'état automatique. Cela est essentiel pour une précision dans les rapports et les métriques de planification des ventes, en particulier lorsque le changement d'état est associé à des états finaux du système comme 'Terminé avec succès' ou 'Terminé sans succès'." + }, + "placeholders": { + "select_stage": "Sélectionner l'étape" + } + }, + "send_email_automation_modal": { + "configure_mailbox_limits": "Configurer les limites de boîte aux lettres", + "max_number_of_emails_per_day": "Nombre maximal d'emails par jour", + "send_email_default_name": "Email #{{id}}", + "title_add": "Ajouter une automatisation d'email", + "title_update": "Mettre à jour l'automatisation d'email", + "title_hint": "Les messages électroniques ne seront envoyés qu'aux cartes de contact qui ont un champ email rempli.", + "email_text": "Texte de l'email *", + "recipient_name": "- Nom du destinataire", + "recipient_name_hint": "Vous pouvez définir l'espace réservé {{contact_name}} dans le texte de l'email, et il sera remplacé par le nom du contact de la carte.", + "user_name": "- Nom de l'utilisateur", + "user_phone": "- Téléphone de l'utilisateur", + "send_with_html": "Envoyer l'email avec le format HTML", + "sender_email": "Email de l'expéditeur *", + "sender_name": "Nom de l'expéditeur *", + "send": "Envoyer", + "emails_per_day": "emails par jour", + "emails_per_day_hint": "Le paramètre de limite d'emails est nécessaire pour éviter d'être bloqué par le fournisseur de service de messagerie. Chaque service de messagerie fixe ses propres limites pour l'envoi de messages. Par exemple, la version de base de Google Workspace permet d'envoyer 500 messages par jour depuis une boîte aux lettres.", + "mailing_parameters": "Paramètres d'envoi *", + "options": { + "addresses": "Adresses d'envoi *", + "all_addresses": "Envoyer à toutes les cartes et toutes les adresses e-mail", + "fine_tune_addresses": "Configurer...", + "main_entity": "Envoyer à la carte principale", + "main_entity_hint": "Choisissez d'envoyer le mail à toutes les adresses de la carte principale ou seulement à la première.", + "main_entity_all": "à toutes les adresses", + "main_entity_only_first": "uniquement à la première adresse", + "contact": "Envoyer à la carte de contact", + "contact_hint": "Choisissez d'envoyer le mail à toutes les cartes de type 'Contact' ou seulement à la première, ainsi qu'à toutes les adresses de la carte ou seulement à la première.", + "contact_all_entities_all_values": "à toutes les adresses dans toutes les cartes de contacts", + "contact_all_entities_first_value": "uniquement à la première adresse dans toutes les cartes de contacts", + "contact_first_entity_all_values": "à toutes les adresses uniquement dans la première carte de contact", + "contact_first_entity_first_value": "uniquement à la première adresse dans la première carte de contact", + "company": "Envoyer à la carte d'entreprise", + "company_hint": "Choisissez d'envoyer le mail à toutes les cartes de type 'Entreprise' ou seulement à la première, ainsi qu'à toutes les adresses de la carte ou seulement à la première.", + "company_all_entities_all_values": "à toutes les adresses dans toutes les cartes d'entreprises", + "company_all_entities_first_value": "uniquement à la première adresse dans toutes les cartes d'entreprises", + "company_first_entity_all_values": "à toutes les adresses uniquement dans la première carte d'entreprise", + "company_first_entity_first_value": "uniquement à la première adresse dans la première carte d'entreprise" + }, + "placeholders": { + "title": "Entrer le titre", + "email_text": "Entrer le texte de l'email", + "email_markup": "Entrer le format HTML", + "select_email_address": "Sélectionner une adresse email", + "select_user": "Sélectionner un utilisateur" + } + }, + "create_entity_automation_modal": { + "create_entity_default_name": "Carte #{{id}}", + "title_add": "Créer une automatisation de carte associée", + "title_update": "Mettre à jour l'automatisation de carte associée", + "create_card": "Créer une carte associée *", + "select_entity_type": "Module", + "select_entity_type_hint": "Sélectionnez le module dans lequel la carte sera créée. Pour éviter les cycles, vous ne pouvez pas créer de carte dans le module actuel.", + "select_stage": "Tableau ou pipeline de vente et statut", + "responsible_user": "Utilisateur responsable", + "responsible_user_hint": "Sélectionnez l'utilisateur responsable de la tâche. L'utilisateur responsable actuel est celui qui gère la carte où la tâche sera créée.", + "current_responsible_user": "Utilisateur responsable actuel", + "entity_name": "Nom de la carte", + "placeholders": { + "select_entity_type": "Sélectionnez un module", + "entity_name": "Entrez le nom de la carte" + } + }, + "send_internal_chat_automation_modal": { + "send_internal_chat_default_name": "Chat #{{id}}", + "title_add": "Créer une automatisation d'envoi de message dans le chat", + "title_update": "Mettre à jour l'automatisation d'envoi de message dans le chat", + "sender": "Expéditeur", + "responsible_user_hint": "Sélectionnez l'expéditeur du message. L'utilisateur responsable actuel est le responsable de la carte à partir de laquelle l'automatisation a été créée.", + "current_responsible_user": "Utilisateur responsable actuel", + "recipients": "Destinataires", + "message": "Texte du message", + "placeholders": { + "recipients": "Sélectionnez des utilisateurs", + "message": "Saisissez le message" + } + }, + "send_external_chat_automation_modal": { + "send_external_chat_default_name": "Chat externe #{{id}}", + "title_add": "Créer une automatisation d'envoi de chat externe", + "title_update": "Mettre à jour l'automatisation d'envoi de chat externe", + "sender": "Expéditeur *", + "responsible_user_hint": "Sélectionnez l'expéditeur du message. L'utilisateur responsable actuel est celui assigné à la fiche d'où l'automatisation est créée.", + "current_responsible_user": "Utilisateur responsable actuel", + "provider": "Fournisseur de chat *", + "message": "Message *", + "send_to_chats": "Envoyer aux chats des fiches", + "send_to_phone_numbers": "Envoyer aux numéros de téléphone", + "add_phone_number": "Ajouter un numéro", + "hint_chats": "Le message sera envoyé à tous les chats externes des fiches de ce module selon les paramètres choisis", + "hint_phone_numbers": "Le message sera envoyé aux numéros sélectionnés", + "phone_numbers_disabled": "L'envoi par numéros de téléphone est uniquement disponible pour les fournisseurs Whatsapp et Telegram", + "placeholders": { + "message": "Saisissez un message" + }, + "errors": { + "phone": "Le numéro de téléphone est manquant" + }, + "options": { + "addresses": "Chats de destination *", + "all_addresses": "Envoyer à toutes les fiches et tous les chats externes", + "fine_tune_addresses": "Configurer...", + "main_entity": "Envoyer à la fiche principale", + "main_entity_hint": "Choisissez d'envoyer le message à tous les chats de la fiche principale ou uniquement au premier.", + "main_entity_all": "à tous les chats", + "main_entity_only_first": "uniquement au premier chat", + "contact": "Envoyer à la fiche de contact", + "contact_hint": "Choisissez d'envoyer le message à toutes les fiches de type « Contact » ou uniquement à la première, et d'envoyer à tous les chats de la fiche ou uniquement au premier.", + "contact_all_entities_all_values": "à tous les chats dans toutes les fiches de contact", + "contact_all_entities_first_value": "uniquement au premier chat dans toutes les fiches de contact", + "contact_first_entity_all_values": "à tous les chats dans la première fiche de contact", + "contact_first_entity_first_value": "uniquement au premier chat dans la première fiche de contact", + "company": "Envoyer à la fiche de société", + "company_hint": "Choisissez d'envoyer le message à toutes les fiches de type « Société » ou uniquement à la première, et d'envoyer à tous les chats de la fiche ou uniquement au premier.", + "company_all_entities_all_values": "à tous les chats dans toutes les fiches de société", + "company_all_entities_first_value": "uniquement au premier chat dans toutes les fiches de société", + "company_first_entity_all_values": "à tous les chats dans la première fiche de société", + "company_first_entity_first_value": "uniquement au premier chat dans la première fiche de société" + } + }, + "request_http_automation_modal": { + "http_request_default_name": "Requête HTTP #{{id}}", + "title_add": "Créer une automatisation de requête HTTP", + "title_update": "Mettre à jour l'automatisation de requête HTTP", + "http_request": "Paramètres de la requête HTTP", + "url": "URL *", + "method": "Méthode *", + "headers": "En-têtes", + "params": "Paramètres de requête" + }, + "automation_modal_template": { + "apply_trigger_hint": "Lorsque cette option est sélectionnée, l'automatisation sera appliquée à toutes les cartes existantes dans ce statut. Sinon, elle ne s'appliquera qu'aux nouvelles cartes entrant dans ce statut. Cette action peut être effectuée à chaque enregistrement de l'automatisation.", + "apply_trigger_hint_list": "Lorsque cette option est sélectionnée, l'automatisation sera appliquée à toutes les cartes existantes. Sinon, elle ne s'appliquera qu'aux nouvelles cartes. Cette action peut être effectuée à chaque enregistrement de l'automatisation.", + "automation_name": "Nom de l'automatisation *", + "trigger": "Déclencheur *", + "conditions": "Conditions", + "delay": "Délai", + "delay_one_stage_title": "Retard lié au statut", + "delay_one_stage_hint": "L’automatisation s’exécute uniquement si la carte reste à ce statut jusqu’à la fin du retard. En cas de changement, elle ne se déclenche pas.", + "delay_all_stages_title": "Retard sans lien avec le statut", + "delay_all_stages_hint": "L’automatisation s’exécute après le retard, même si la carte change de statut.", + "enable": "Activer l'automatisation", + "enable_annotation": "Vous pouvez activer et désactiver l'automatisation", + "apply_trigger": "Appliquer le déclencheur aux objets actuels dans l'étape", + "apply_trigger_list": "Appliquer le déclencheur aux objets actuels", + "delete_automation": "Supprimer l'automatisation", + "on": "ACTIVÉ", + "placeholders": { + "name": "Nom" + } + }, + "delete_automation_modal": { + "title": "Voulez-vous vraiment supprimer cette automatisation?", + "annotation": "Cette action ne peut pas être annulée" + }, + "common": { + "use_get_chats_template_options": { + "recipient_name": "- Nom du destinataire", + "recipient_name_hint": "Vous pouvez définir l'espace réservé {{contact_name}} dans le texte du message de chat, et il sera remplacé par le nom du contact de la carte." + }, + "trigger_select": { + "exact_time_of_automation": "Heure exacte de l'automatisation", + "exact_time": "Heure exacte: {{day}} à {{time}}", + "when_transitioning": "Lors de la transition ou de la création", + "at_the_transition": "Lors de la transition à l'étape", + "when_creating": "Lors de la création dans l'étape", + "when_creating_list": "Lors de la création", + "when_responsible_changes": "Lorsque l'utilisateur responsable change", + "placeholder": "Sélectionner le déclencheur" + }, + "conditions_block": { + "field": "Champ *", + "condition": "Condition *", + "entity_type_field_filter_string": { + "is_empty": "Est vide", + "is_not_empty": "N'est pas vide", + "contains": "Contient...", + "placeholders": { + "value": "Valeur" + } + }, + "entity_type_field_filter_number": { + "placeholders": { + "from": "De", + "to": "À" + } + }, + "entity_type_field_filter_boolean": { + "switch_on": "Allumer", + "switch_off": "Éteindre" + }, + "fields_conditions": "Conditions de champ", + "add_field_condition": "Ajouter une condition de champ", + "responsible_users": "Utilisateurs responsables", + "responsible": "Responsable", + "budget": "Budget", + "value": "Valeur", + "from": "de", + "to": "à", + "placeholders": { + "selected_participants": "Participants sélectionnés", + "selected_options": "Options sélectionnées", + "add_condition": "Ajouter une condition", + "select_responsible": "Sélectionner les responsables" + } + }, + "description_block": { + "placeholders": { + "description": "Description" + } + }, + "due_date_select": { + "due_date": "Date d'échéance", + "options": { + "during_this_week": "Durant cette semaine", + "in_3_days": "Dans 3 jours", + "in_a_day": "Dans un jour", + "end_of_the_day": "Fin de journée", + "at_the_time_of_creation": "Au moment de la création" + } + }, + "defer_start_select": { + "defer_start": "Date de début", + "options": { + "in_an_hour": "Dans une heure", + "in_a_day": "Dans un jour", + "in_3_days": "Dans 3 jours" + } + }, + "delay_select": { + "no_delay": "Sans Délai", + "5_minutes": "5 minutes", + "10_minutes": "10 minutes", + "15_minutes": "15 minutes", + "30_minutes": "30 minutes", + "1_hour": "1 heure" + }, + "template_list": { + "name": "Nom", + "owner": "Responsable", + "tooltip": "Les codes de champ seront remplacés par les valeurs correspondantes de la fiche", + "show_templates": "Afficher les codes de champ", + "hide_templates": "Masquer les codes de champ" + } + } + }, + "external_providers_select": { + "add_new_provider": "Ajouter un fournisseur", + "no_available_providers": "Aucun fournisseur disponible" + }, + "sidebar": { + "title": "Automatisation", + "create_task": "Tâche", + "task_create": "Tâche", + "create_activity": "Activité", + "activity_create": "Activité", + "change_stage": "Changer d'étape", + "entity_stage_change": "Changer d'étape", + "entity_linked_stage_change": "Changer d'étape dans la carte liée", + "entity_responsible_change": "Changer de responsable", + "send_email": "Email", + "email_send": "Email", + "chat_send_amwork": "Message dans le chat", + "chat_send_external": "Message dans le chat externe", + "entity_create": "Carte associée", + "http_call": "Requête HTTP", + "automation": "Automatisation" + }, + "block": { + "create_task": "Tâche", + "task_create": "Tâche", + "create_activity": "Activité", + "activity_create": "Activité", + "change_stage": "Changer d'étape", + "entity_stage_change": "Changer d'étape", + "entity_linked_stage_change": "Changer d'étape liée", + "entity_responsible_change": "Responsable", + "send_email": "Email", + "email_send": "Email", + "entity_create": "Carte", + "chat_send_amwork": "Chat", + "chat_send_external": "Chat externe", + "http_call": "Requête HTTP" + } + } +} diff --git a/frontend/public/locales/fr/module.bpmn.json b/frontend/public/locales/fr/module.bpmn.json new file mode 100644 index 0000000..bddd029 --- /dev/null +++ b/frontend/public/locales/fr/module.bpmn.json @@ -0,0 +1,174 @@ +{ + "bpmn": { + "hooks": { + "use_get_service_task_options": { + "actions": { + "task_create": "Créer Tâche", + "activity_create": "Créer Activité", + "entity_stage_change": "Changer de Phase", + "entity_linked_stage_change": "Changer d'étape dans la carte liée", + "entity_responsible_change": "Changer de Responsable", + "email_send": "Envoyer Email", + "entity_create": "Créer Carte", + "chat_send_amwork": "Envoyer un Message au Chat", + "chat_send_external": "Envoyer un Message au Chat Externe", + "http_call": "Envoyer une requête HTTP" + } + }, + "use_bpmn_automations_columns": { + "name": "Nom", + "created_by": "Créé Par", + "active": "Actif", + "delete": "Supprimer", + "status": "Statut" + }, + "use_get_workspace_event_options": { + "events": { + "create": "Événement Carte Créée", + "change_responsible": "Événement Propriétaire Changé", + "change_stage": "Événement Phase Changée" + } + } + }, + "pages": { + "bpmn_automations_page": { + "automation_process_schema_error_warning": { + "continue": "Continuer", + "title": "Échec de l'activation du processus \"{{name}}\"", + "annotation": "La validation du schéma BPMN a échoué. Veuillez vérifier votre processus et réessayer." + }, + "service_task_popup_template": { + "task_name": "Nom de la Tâche de Service", + "placeholders": { + "task_name": "Nom" + } + }, + "workspace_sequence_flow_popup": { + "module": "Sélectionner Module", + "module_hint": "Sélectionner le module où les conditions spécifiées doivent être remplies", + "stage": "Sélectionner Phase", + "sequence_flow": "Flux de Séquence", + "flow_name": "Nom du Flux", + "conditions": "Conditions", + "placeholders": { + "flow_name": "Nom" + } + }, + "create_automation_process_modal": { + "create": "Créer", + "title": "Créer un Nouveau Processus d'Automatisation", + "name": "Nom de l'Automatisation", + "new_process": "Nouveau Processus #{{number}}", + "placeholders": { + "name": "Nom" + } + }, + "bpmn_automations_processes_modeler": { + "add_conditions": "Ajouter des Conditions...", + "center_view": "Vue Centrale", + "pallete": { + "create_workspace_parallel_gateway": "Créer Passerelle Parallèle", + "create_workspace_start_event": "Créer Événement de Début", + "create_workspace_end_event": "Créer Événement de Fin", + "create_workspace_exclusive_gateway": "Créer Passerelle Exclusive", + "create_workspace_data_object": "Créer Objet de Données", + "create_workspace_data_store": "Créer Stockage de Données", + "create_workspace_participant_expanded": "Créer Participant Étendu", + "create_workspace_group": "Créer Groupe" + }, + "color_picker": { + "default_color": "Couleur par Défaut", + "blue_color": "Couleur Bleue", + "orange_color": "Couleur Orange", + "green_color": "Couleur Verte", + "red_color": "Couleur Rouge", + "purple_color": "Couleur Violette" + }, + "context_pad": { + "delay": "Ajouter Délai", + "append_end_event": "Ajouter Événement de Fin", + "append_gateway": "Ajouter Passerelle", + "append_text_annotation": "Ajouter Annotation Texte", + "replace": "Remplacer", + "delete": "Supprimer", + "set_color": "Définir la Couleur", + "connect": "Connecter", + "task_create": "Créer Tâche", + "activity_create": "Créer Activité", + "entity_stage_change": "Changer de Phase", + "entity_linked_stage_change": "Changer d'étape dans la carte liée", + "entity_responsible_change": "Changer de Responsable", + "email_send": "Envoyer Email", + "entity_create": "Créer Carte", + "chat_send_amwork": "Envoyer un Message au Chat", + "chat_send_external": "Envoyer un Message au Chat Externe", + "http_call": "Envoyer une requête HTTP" + }, + "toggle_default_context_pad_entry": "Basculer Condition par Défaut du Flux", + "open_minimap": "Ouvrir Mini-carte", + "close_minimap": "Fermer Mini-carte", + "activate_hand_tool": "Activer Outil Main", + "activate_lasso_tool": "Activer Outil Lasso", + "active_create_remove_space_tool": "Activer Outil Créer/Supprimer Espace", + "activate_global_connect_tool": "Activer Outil Connecteur Global", + "process_controls": { + "save_and_run": "Enregistrer et Exécuter", + "save_draft": "Enregistrer Brouillon", + "start_process": "Démarrer Processus", + "stop_process": "Arrêter Processus", + "cancel": "Annuler" + }, + "create_workspace_start_event": "Créer Événement de Début", + "create_workspace_delay_event": "Créer Événement de Délai", + "create_workspace_service_task": "Créer Tâche de Service" + }, + "workspace_delay_event_popup": { + "delay": "Délai", + "delay_label": "Délai *", + "delay_name": "Nom du Délai", + "placeholders": { + "delay_name": "Nom" + } + }, + "delete_bpmn_automation_warning_modal": { + "title": "Êtes-vous sûr de vouloir supprimer l'Automatisation BPMN \"{{name}}\"?", + "annotation": "Cette action est irréversible." + }, + "create_workspace_service_task_menu": { + "workspace_service_tasks": "Tâches de Service" + }, + "create_workspace_start_event_menu": { + "workspace_events": "Événements" + }, + "workspace_start_event_popup": { + "event_name": "Nom de l'Événement", + "event_type": "Sélectionner le Type d'Événement *", + "module": "Sélectionner le Module *", + "module_hint": "Sélectionner le module où l'événement sera déclenché", + "placeholders": { + "event_name": "Nom" + } + }, + "bpmn_automations_settings_header": { + "create_bpmn_automation": "Créer Automatisation BPMN", + "bpmn_automations": "Automatisations BPMN 2.0", + "back": "Retour" + }, + "bpmn_automations_table": { + "no_bpmn_automations_block": { + "annotation1": "Vous n'avez encore aucun processus", + "annotation2": "Utilisez le", + "annotation3": "bouton pour ajouter une automatisation." + } + }, + "bpmn_splashscreen": { + "heading": "Automatisation BPM Professionnelle", + "price_ru": "pour 99 ₽/mois par utilisateur, avec paiement annuel", + "price_us": "pour 1 $/mois par utilisateur, avec paiement annuel", + "form_button": "Demander l'activation", + "form_header": "Activer l'automatisation BPM" + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.builder.json b/frontend/public/locales/fr/module.builder.json new file mode 100644 index 0000000..f9b20ca --- /dev/null +++ b/frontend/public/locales/fr/module.builder.json @@ -0,0 +1,867 @@ +{ + "builder": { + "components": { + "common": { + "next": "Suivant", + "back": "Retour", + "save": "Enregistrer", + "workspace_builder": "Créateur d'Espaces de Travail", + "your_workspace": "Votre Espace de Travail", + "no_links": "Aucun module disponible pour lier." + } + }, + "pages": { + "site_form_builder_page": { + "steps": { + "save_and_leave": "Enregistrer et Quitter", + "default_title": "Formulaire sur le site", + "default_form_title": "Contactez-nous", + "default_consent_text": "Nous utilisons des cookies pour améliorer le site. Les cookies offrent une expérience plus personnalisée pour vous et des analyses web pour nous. Consultez notre Politique de confidentialité pour plus de détails.", + "default_consent_link_text": "Politique de confidentialité", + "default_gratitude_header": "Merci !", + "default_gratitude_text": "Nos gestionnaires vous contacteront pendant les heures de travail.", + "default_form_button_text": "Envoyer", + "default_client_button_text": "Ouvrir le formulaire", + "common_error": "Veuillez remplir tous les champs obligatoires.", + "step1": { + "title": "Étape 1", + "description": "Configuration de base", + "error": "Veuillez remplir tous les champs obligatoires à la première étape." + }, + "step2": { + "title": "Étape 2", + "description": "Champs du formulaire", + "error": "Veuillez remplir tous les champs obligatoires à la deuxième étape." + }, + "step3": { + "title": "Étape 3", + "description": "Politique de données et page de remerciement", + "consent_error": "Veuillez remplir tous les champs obligatoires dans le bloc 'Politique de confidentialité' à la troisième étape." + }, + "step4": { + "title": "Étape 4", + "description": "Conception du formulaire" + }, + "step5": { + "title": "Étape 5", + "description": "Script d'installation du formulaire" + } + }, + "site_form_builder_step1": { + "title": "Étape 1. Configuration du formulaire", + "title_input_name": "Entrez le nom du formulaire", + "choose_main_card": "Choisissez la carte principale qui sera créée après l'envoi du formulaire", + "choose_linked_cards": "Choisissez les cartes liées qui seront créées après l'envoi du formulaire", + "linked_entities_select": "Sélectionnez les modules où les fiches seront créées après l'envoi du formulaire depuis votre site", + "choose_board_annotation": "Sélectionnez le tableau où la fiche sera créée", + "card_settings": "Configurer la création de carte", + "responsible_user": "Utilisateur responsable", + "check_duplicates": "Éviter les doublons", + "check_duplicates_hint": "Si cette option est activée, l’envoi du formulaire avec un email ou un téléphone existant ne créera pas une nouvelle carte mais liera celle déjà enregistrée.", + "yes": "Oui", + "placeholders": { + "title_input": "Nom du formulaire", + "responsible_select": "Responsable", + "board": "Tableau" + } + }, + "site_form_builder_step2": { + "sidebar": { + "title": "Éléments du formulaire", + "header_title": "En-tête du formulaire", + "header_hint": "Un seul autorisé.", + "delimiter_title": "Séparateur", + "delimiter_hint": "Ligne pour séparer les sections du formulaire.", + "field_attributes": { + "title": "Attributs du champ", + "hint": "Le titre est affiché au-dessus du champ, la description à l'intérieur.", + "label_name": "Titre", + "placeholder_name": "Description" + }, + "card_name_block": { + "card_title": "Nom de la carte", + "company_title": "Nom de l'entreprise", + "contact_title": "Nom complet", + "card_text": "Nom", + "company_text": "Nom", + "contact_text": "Nom complet" + }, + "field_elements": { + "no_available_fields": "Aucun champ disponible", + "email": "Email", + "phone": "Téléphone", + "short_text": "Texte court", + "long_text": "Texte long", + "number": "Nombre", + "value": "Valeur", + "date": "Date", + "select": "Sélectionner", + "multiselect": "Sélection multiple", + "colored_select": "Sélection de couleur", + "colored_multiselect": "Sélection multiple de couleur", + "link": "Lien", + "file": "Fichier", + "switch": "Interrupteur" + } + }, + "tree": { + "element_name": { + "placeholder": "Entrez le titre du formulaire" + }, + "entity_field_element": { + "placeholder": "Entrez la description du champ", + "template": { + "company_name": "Nom", + "contact_name": "Nom complet", + "card_name": "Nom de la fiche", + "short_text": "Texte court", + "long_text": "Texte long", + "field": "Champ", + "delimiter": "Séparateur", + "file": "Fichier", + "schedule": "Sélection du calendrier", + "schedule_performer": "Sélection du prestataire", + "schedule_date": "Sélection de la date", + "schedule_time": "Sélection de l'heure", + "placeholder": "Entrez le nom du champ", + "required": "Champ obligatoire", + "validation": "Validation du champ", + "field_type": "Type de champ :", + "linked_with": "Lié au module :" + } + } + } + }, + "site_form_builder_step3": { + "show_in_form_control": { + "yes": "Oui" + }, + "consent_block": { + "title": "Politique de traitement des données", + "controls": { + "show_consent_title": "Afficher la politique de confidentialité", + "text_title": "Texte de la politique de confidentialité", + "text_hint": "Le texte sera affiché à la fin du formulaire.", + "link_text_title": "Texte du lien", + "link_text_hint": "Texte affiché sur le lien", + "link_title": "Lien", + "link_hint": "Entrez l'URL de la politique de confidentialité au format https://example.com/.", + "consent_by_default_title": "Case cochée par défaut", + "placeholders": { + "text": "Texte de la politique de confidentialité", + "link_text": "Entrez le texte du lien", + "link": "Entrez le lien" + } + }, + "skeleton": { + "button_text": "Envoyer" + } + }, + "gratitude_block": { + "title": "Page de remerciement", + "show_gratitude_title": "Afficher la page de remerciement", + "gratitude_text": "Texte de remerciement", + "placeholders": { + "header": "Entrez le titre", + "text": "Entrez le texte de remerciement" + } + } + }, + "site_form_builder_step4": { + "sidebar": { + "custom_css_block": { + "custom_css": "CSS personnalisé", + "enable_custom_css": "Activer le CSS personnalisé", + "placeholders": { + "custom_css": "Règles CSS personnalisées" + } + }, + "advanced_settings": "Paramètres avancés", + "modal_overlay_customization_block": { + "modal_overlay": "Superposition modale", + "overlay_display": "Afficher la superposition", + "background_color": "Couleur de fond", + "opacity": "Opacité" + }, + "switch_label": "Activer", + "title": "Conception du formulaire", + "description": "Les modifications prendront effet après l'enregistrement.", + "powered_by_logo_block": { + "title": "Logo {{company}}", + "switch_label": "Activer" + }, + "header_customization_block": { + "title": "En-tête", + "text_color": "Couleur du texte", + "text_align_title": "Alignement", + "text_align_left": "À gauche", + "text_align_center": "Centré", + "text_align_right": "À droite" + }, + "fields_customization_block": { + "title": "Champs", + "background_color": "Couleur de fond", + "title_color": "Couleur du titre", + "text_color": "Couleur du texte dans les champs" + }, + "form_button_customization_block": { + "title": "Bouton", + "background_color": "Couleur du bouton", + "text_color": "Couleur du texte", + "border": "Coins arrondis", + "border_rounded": "Arrondi", + "border_squared": "Carré", + "size": "Taille du bouton", + "small": "Petit", + "medium": "Moyen", + "large": "Grand", + "text_input": "Texte du bouton", + "hint": "Bouton de soumission, situé à la fin du formulaire", + "placeholders": { + "text_input": "Envoyer" + } + }, + "client_button_customization_block": { + "title": "Bouton pour le site", + "background_color": "Couleur du bouton", + "text_color": "Couleur du texte du bouton", + "border": "Bords", + "border_rounded": "Arrondis", + "border_squared": "Carrés", + "size": "Taille du bouton", + "small": "Petit", + "medium": "Moyen", + "large": "Grand", + "text_input": "Texte du bouton", + "hint": "Bouton ouvrant une fenêtre modale avec le formulaire", + "placeholders": { + "text_input": "Ouvrir" + } + }, + "form_layout_customization_block": { + "title": "Formulaire", + "view_title": "Vue", + "view_built_in": "Intégré", + "view_modal": "Fenêtre modale", + "position_title": "Position du formulaire", + "position_left": "À gauche", + "position_center": "Centré", + "position_right": "À droite", + "border_radius_title": "Arrondi des bords", + "border_rounded": "Arrondis", + "border_none": "Sans arrondi", + "orientation_title": "Orientation", + "orientation_horizontal": "Horizontale", + "orientation_vertical": "Verticale", + "max_width": "Largeur maximale", + "max_height": "Hauteur maximale", + "background_color": "Couleur de fond" + }, + "size_customization_element_template": { + "switch_label": "Activer", + "placeholders": { + "input": "Entrez la valeur" + } + } + }, + "preview": { + "iframe_title": "{{companyName}} – Aperçu du formulaire", + "error": "Erreur lors du chargement de l'aperçu", + "open_in_a_new_tab": "Ouvrir dans un nouvel onglet", + "open_preview": "Ouvrir l'aperçu", + "phone": "Téléphone", + "tablet": "Tablette", + "computer": "Ordinateur", + "form": "Formulaire", + "gratitude": "Page de remerciement", + "button": "Bouton" + } + }, + "site_form_builder_step5": { + "title": "Étape 5. Copiez le code du formulaire pour votre site", + "copy_form_code": "Copier le code du formulaire", + "form_code": "Code du formulaire", + "multiform_mode": "Mode de compatibilité multiple", + "multiform_mode_hint": "Activez ce mode si la page où vous souhaitez insérer le formulaire contient déjà un autre formulaire créé avec notre constructeur. Cela évitera les conflits d'affichage.", + "form_will_be_mounted_here": "Le formulaire sera intégré ici", + "public_link_title": "Page publique du formulaire", + "public_link_annotation": "Utilisez le lien ci-dessous pour accéder à la page publique de votre formulaire. Si vous ne souhaitez pas l’intégrer sur votre site, partagez cette page avec vos utilisateurs pour qu’ils puissent le remplir. Le formulaire est prêt à l’emploi et sera mis à jour automatiquement en cas de modification.", + "instruction_title": "Instructions pour intégrer le formulaire sur votre site", + "view_form": "Voir le formulaire", + "static_site_integration_title": "Intégration sur les sites statiques et créés avec des constructeurs", + "first_point": { + "title": "1. Ajouter un formulaire via une classe CSS", + "annotation": "Attribuez la classe {{mountClass}} aux éléments de votre site où vous souhaitez intégrer le formulaire. Collez le code du formulaire ci-dessus dans la balise ou à la fin de la balise . Le formulaire s'affichera à l'intérieur des éléments avec la classe spécifiée. Si l'élément a déjà du contenu, le formulaire apparaîtra à la fin de ce contenu. Si vous avez choisi d'afficher le formulaire sous forme de fenêtre popup, un bouton sera intégré à l'emplacement indiqué, et un clic ouvrira une fenêtre popup avec le formulaire. C'est la méthode préférée pour intégrer le formulaire sur le site." + }, + "second_point": { + "title": "2. Ajouter un formulaire sans classes CSS", + "annotation": "Si vous ne pouvez pas attribuer de classe aux éléments, insérez le code du formulaire à l'endroit de votre site où vous souhaitez l'afficher. Le code du formulaire peut être intégré à n'importe quel endroit de votre site. Dans ce cas, le formulaire s'affichera à l'endroit où le code a été inséré. Cette méthode permet d'intégrer un seul formulaire par page." + }, + "ssr_integration_title": "Intégration sur les sites SSR (par exemple, créés avec Next.js, Astro, etc.)", + "ssr_integration_annotation": "Pour éviter les problèmes de réhydratation (chargement des scripts dans le balisage HTML), il est recommandé d'utiliser la méthode de spécification de la classe workspace-form-builder-mount-container pour l'élément où vous souhaitez intégrer le formulaire (décrite plus en détail au point 2 ci-dessus). Dans ce cas, le script du formulaire doit être de préférence placé dans la balise .", + "analytics": { + "title": "Suivi des événements et objectifs", + "annotation": "Ce formulaire prend en charge l'envoi d'événements et d'objectifs aux systèmes d'analyse. Si Google Analytics ou Yandex.Metrika est installé sur votre site, vous pouvez suivre les actions des utilisateurs sur le formulaire. Pour cela, configurez les événements ou objectifs dans votre système d'analyse et associez-les aux identifiants indiqués dans le tableau.", + "table_title": "Événements Suivis", + "event_title": "Nom", + "event_id": "ID de l'objectif", + "form_view": "Vue du formulaire", + "form_start": "Début du remplissage", + "field_fill": "Remplissage du champ “{{field}}”", + "form_submit": "Envoi du formulaire" + } + } + }, + "headless_site_form_builder_page": { + "steps": { + "save_and_leave": "Enregistrer et quitter", + "default_title": "Formulaire sur site pour intégration via API", + "common_error": "Veuillez remplir tous les champs obligatoires.", + "step1": { + "title": "Étape 1", + "description": "Configuration de base", + "error": "Veuillez remplir tous les champs obligatoires à l'étape 1." + }, + "step2": { + "title": "Étape 2", + "description": "Champs du formulaire" + }, + "step3": { + "title": "Étape 3", + "description": "Instructions pour l'intégration du formulaire" + } + }, + "site_form_builder_step3": { + "title": "Étape 3. Intégrer le formulaire sur le site", + "json_integration_title": "Envoi des données au format JSON", + "api_integration_instruction": "Cette méthode convient si vous traitez les formulaires sur votre propre serveur. Pour envoyer les données utilisateur de votre formulaire à {{company}}, effectuez une requête avec les paramètres indiqués ci-dessous. Dans le corps de la requête, incluez les données utilisateur au format JSON. Un exemple de corps de requête est fourni ci-dessous.", + "api_integration_request_annotation": "Méthode : POST\nEn-tête : Content-Type: application/json\nURL : {{endpoint}}", + "request_parameters_title": "Paramètres de la requête", + "request_body_title": "Corps de la requête", + "field_value_explanation": "Dans le champ value, saisissez la valeur entrée par l'utilisateur. Les formats de valeurs pour chaque champ sont indiqués ci-dessous.", + "field_value_title": "Valeurs des champs", + "field_value": "Valeur du champ {{label}}", + "response_title": "Format de réponse", + "response_explanation": "En cas de succès, le serveur renvoie un code HTTP 201.", + "formdata_integration_title": "Envoi des données au format FormData", + "formdata_integration_instruction": "Cette méthode convient pour envoyer des formulaires depuis des sites créés avec des outils comme Tilda. Les données sont envoyées au format x-www-form-urlencoded. Pour un envoi correct, indiquez simplement l'URL du webhook ci-dessous et attribuez des variables aux champs.", + "formdata_webhook_url": "URL du webhook", + "formdata_fields_title": "Variables des champs", + "formdata_fields_explanation": "Chaque champ du formulaire doit avoir un nom (variable) pour être associé au champ correspondant dans le système. Ici, les variables des champs sont leurs identifiants numériques. Pour un envoi correct, attribuez à chaque champ du formulaire la variable indiquée dans le tableau ci-dessous.", + "fields_table": { + "field_title": "Nom du champ", + "field_format": "Format du champ", + "field_example": "Exemple", + "field_id": "Variable (ID du champ)", + "format": { + "text": "Chaîne de texte entre guillemets", + "link": "Chaîne de texte sans espaces entre guillemets", + "phone": "Chaîne de texte sans espaces entre guillemets", + "email": "Chaîne de texte sans espaces entre guillemets", + "value": "Chaîne de texte entre guillemets", + "select": "Numéro identifiant la valeur sélectionnée par l'utilisateur", + "switch": "Valeur booléenne sans guillemets", + "number": "Nombre sans guillemets", + "multiselect": "Tableau de numéros identifiant les valeurs sélectionnées par l'utilisateur", + "date": "Chaîne de date ISO 8601 entre guillemets" + }, + "example": { + "text": "\"Valeur saisie par l'utilisateur\"", + "link": "\"https://example.com\"", + "phone": "\"12345678910\"", + "email": "\"form@example.com\"", + "value": "\"Valeur saisie par l'utilisateur\"", + "select": "34", + "switch": "true", + "number": "42", + "multiselect": "[26, 27]", + "date": "\"2024-12-31T00:00:00\"" + } + } + } + }, + "online_booking_site_form_builder_page": { + "steps": { + "save_and_leave": "Enregistrer et quitter", + "default_title": "Formulaire de réservation", + "default_form_title": "Réservation en ligne", + "default_consent_text": "Nous utilisons des cookies pour améliorer votre expérience et réaliser des analyses web. Pour en savoir plus, consultez notre Politique de confidentialité.", + "default_consent_link_text": "Politique de confidentialité", + "default_gratitude_header": "Merci !", + "default_gratitude_text": "Nos conseillers vous contacteront pour confirmer votre réservation.", + "default_form_button_text": "Envoyer", + "default_client_button_text": "Ouvrir le formulaire", + "schedule_field_title": "Calendrier", + "schedule_field_placeholder": "Sélectionnez un calendrier...", + "schedule_performer_field_title": "Prestataire", + "schedule_performer_field_placeholder": "Sélectionnez un prestataire...", + "schedule_date_field_title": "Date", + "schedule_date_field_placeholder": "Sélectionnez une date...", + "schedule_time_field_title": "Heure", + "schedule_time_field_placeholder": "Sélectionnez une heure...", + "common_error": "Veuillez remplir tous les champs obligatoires.", + "step1": { + "title": "Étape 1", + "description": "Configuration de base", + "error": "Veuillez remplir tous les champs obligatoires de la première étape." + }, + "step2": { + "title": "Étape 2", + "description": "Champs du formulaire", + "error": "Veuillez remplir tous les champs obligatoires de la deuxième étape." + }, + "step3": { + "title": "Étape 3", + "description": "Politique de confidentialité et page de remerciement", + "consent_error": "Veuillez compléter tous les champs obligatoires dans la section « Politique de confidentialité » de la troisième étape." + }, + "step4": { + "title": "Étape 4", + "description": "Design du formulaire" + }, + "step5": { + "title": "Étape 5", + "description": "Script d'installation du formulaire" + } + }, + "online_booking_site_form_builder_step1": { + "title": "Étape 1. Paramètres du formulaire", + "title_input_name": "Saisissez le nom du formulaire", + "choose_schedulers": "Sélectionnez les calendriers où enregistrer les réservations", + "choose_linked_cards": "Sélectionnez les fiches liées à créer après la réservation", + "linked_entities_select": "Sélectionnez les modules où créer les fiches après soumission du formulaire", + "choose_board_annotation": "Sélectionnez le tableau où créer la fiche", + "no_schedules": "Vous n’avez pas encore créé de calendrier. Pour activer la réservation en ligne, ajoutez un module « Calendrier » et configurez les paramètres de réservation.", + "no_linked_cards": "Aucun module disponible pour créer des fiches liées. Sélectionnez un calendrier associé à un module. Si plusieurs calendriers sont sélectionnés, assurez-vous qu'ils sont liés au même module.", + "limit_days_name": "Limite de jours pour la réservation", + "card_settings": "Configurer la création de carte", + "responsible_user": "Utilisateur responsable", + "check_duplicates": "Éviter les doublons", + "check_duplicates_hint": "Si cette option est activée, l’envoi du formulaire avec un email ou un téléphone existant ne créera pas une nouvelle carte mais liera celle déjà enregistrée.", + "yes": "Oui", + "placeholders": { + "title_input": "Nom du formulaire", + "responsible_select": "Responsable", + "board": "Tableau", + "limit_days": "De 1 à 730" + } + } + }, + "workspace_editor_page": { + "edit": "Éditer", + "delete": "Supprimer", + "warn_title": "Attention !", + "warn_annotation": "La suppression du module entraînera la perte définitive de toutes les informations associées. Êtes-vous sûr de vouloir continuer ?", + "open_module": "Ouvrir le module", + "entity_type_field_used_in_formula_warning_modal": { + "title": "Impossible de supprimer ce module", + "annotation": "Certains champs de ce module sont liés à une formule dans un autre module. Veuillez mettre à jour les paramètres de la formule et réessayer.", + "continue": "Continuer" + } + }, + "scheduler_builder_page": { + "steps": { + "save_error": "Spécifiez le nom du module et sélectionnez au moins un utilisateur à la première étape.", + "step1": { + "label": "1ère étape", + "description": "Configurer un nouveau module" + }, + "step2": { + "label": "2ème étape", + "description": "Lier le programmateur à un autre module dans votre espace de travail" + }, + "step3": { + "label": "3ème étape", + "description": "Intégrer le programmateur dans le module des produits de votre espace de travail" + }, + "default_title": "Programmateur de réunions" + }, + "scheduler_builder_step1": { + "title": "Personnaliser le module", + "name_the_module": "Nom du module", + "name_the_module_hint": "Ce nom sera affiché dans la barre latérale gauche. Vous pourrez le changer plus tard.", + "choose_icon": "Choisissez une icône pour la barre latérale gauche", + "choose_icon_hint": "Cette icône sera affichée dans la barre latérale gauche. Vous pourrez la changer plus tard.", + "for_users": "Pour les utilisateurs", + "for_user_groups": "Pour les groupes d'utilisateurs", + "view_type": "Sélectionnez le type de vue du Programmateur de réunions", + "view_type_hint": "Sélectionnez le type de vue – Vue de Temps Latérale (Vue de Temps Verticale) ou Vue de Temps Supérieure (Vue de Temps Horizontale).", + "schedule": "Vue de Temps Latérale", + "schedule_img_alt": "{{company}} – Aperçu de l'interface de programmation", + "board": "Vue de Temps Supérieure", + "board_img_alt": "{{company}} – Aperçu de l'interface de tableau", + "enter_the_interval": "Entrez l'intervalle", + "maximum_number_of_records": "Nombre maximum d'enregistrements", + "error": "Veuillez remplir tous les champs obligatoires.", + "change_anyway": "Changer quand même", + "warning_title": "Êtes-vous sûr de vouloir changer ce paramètre ?", + "warning_annotation": "Toutes les rendez-vous existants seront supprimés.", + "for_whom": "Sélectionnez des utilisateurs ou des groupes d'utilisateurs", + "for_whom_hint": "Sélectionnez le type de vue du programmateur – pour les utilisateurs ou les groupes d'utilisateurs.", + "no_duration": "Non", + "minutes": "minutes", + "schedule_params_title": "Planning et réservation en ligne", + "schedule_params_hint": "Configurez la disponibilité et les paramètres des réunions pour l’agenda. Ils seront utilisés pour afficher le calendrier et gérer les réservations en ligne.", + "time_buffer_before": "Pause avant la réunion", + "time_buffer_after": "Pause après la réunion", + "intervals_source": "Utiliser la disponibilité de", + "performers_intervals": "utilisateurs", + "scheduler_intervals": "cet agenda", + "placeholders": { + "interval": "Intervalle", + "unlimited": "Illimité", + "module_name": "Nom du module" + }, + "change_type_warning": { + "title": "Attention !", + "annotation": "Changer le type de calendrier supprimera définitivement tous les enregistrements de visites. Êtes-vous sûr de vouloir continuer ?", + "approve": "Confirmer" + }, + "change_performers_warning": { + "title": "Attention !", + "annotation": "Supprimer un responsable du calendrier supprimera définitivement tous ses enregistrements de visites. Êtes-vous sûr de vouloir continuer ?", + "approve": "Confirmer" + } + }, + "scheduler_builder_step2": { + "title": "Lier à un autre module", + "do_not_link": "Ne pas lier à un autre module", + "duplicate_title": "Interdire les doublons de réunions", + "duplicate_hint": "En activant cette option, le calendrier empêchera plusieurs réunions pour la même fiche liée le même jour." + }, + "scheduler_builder_step3": { + "title": "Intégrer au module des produits", + "do_not_integrate": "Ne pas intégrer au module des produits" + } + }, + "products_section_builder_page": { + "delay_select": { + "no_return": "Pas de retour", + "no_return_minified": "Pas de retour", + "24_hours": "24 heures", + "24_hours_minified": "24h", + "48_hours": "48 heures", + "48_hours_minified": "48h", + "72_hours": "72 heures", + "72_hours_minified": "72h", + "7_days": "7 jours", + "7_days_minified": "7j", + "10_days": "10 jours", + "10_days_minified": "10j", + "14_days": "14 jours", + "14_days_minified": "14j", + "28_days": "28 jours", + "28_days_minified": "28j", + "30_days": "30 jours", + "30_days_minified": "30j" + }, + "steps": { + "save_common_error": "Erreur lors de l'enregistrement. Certaines étapes manquent d'informations requises.", + "save_warehouses_error": "Vous devez créer au moins un entrepôt pour continuer. Alternativement, vous pouvez désactiver les entrepôts.", + "step1": { + "label": "1ère étape", + "description": "Configurer un nouveau module" + }, + "step2": { + "label": "2ème étape", + "description": "Créer des catégories de produits (groupes de produits)" + }, + "step3": { + "label": "3ème étape", + "description": "Configurer les entrepôts pour vos produits" + }, + "step4": { + "rentals": { + "label": "4ème étape", + "description": "Configurer les paramètres de programmation pour vos produits de location" + }, + "sales": { + "label": "4ème étape", + "description": "Lier à d'autres modules dans votre espace de travail" + } + }, + "step5": { + "label": "5ème étape", + "description": "Lier à d'autres modules dans votre espace de travail" + }, + "default_titles": { + "sale": "Gestion des Entrepôts et Produits", + "rental": "Gestion des Locations" + } + }, + "product_section_builder_step1": { + "title": "Personnaliser le module", + "name_the_section": "Nom du module", + "name_the_section_hint": "Ce nom sera affiché dans la barre latérale gauche. Vous pourrez le changer plus tard.", + "choose_icon": "Choisissez une icône pour la barre latérale gauche", + "choose_icon_hint": "Cette icône sera affichée dans la barre latérale gauche. Vous pourrez la changer plus tard.", + "error": "Le nom du module doit être spécifié.", + "placeholders": { + "section_name": "Nom du module" + } + }, + "product_section_builder_step3": { + "cancel_after_label": "Définir la période pour la libération automatique des réservations d'articles dans les commandes", + "cancel_after_hint": "Vous pouvez configurer l'annulation automatique des réservations d'articles dans les commandes. Cela aide à prévenir les niveaux de stock inexactes, qui peuvent se produire lorsque les articles sont réservés dans des affaires mais que ces affaires ne sont pas traitées ou ont été annulées.", + "update_error": "Impossible d'enregistrer votre choix. Veuillez réessayer.", + "warehouses_error": "Vous devez créer au moins un entrepôt pour continuer. Alternativement, vous pouvez désactiver les entrepôts.", + "enable_warehouses_label": "Activer les entrepôts", + "enable_warehouses_hint": "Activez les entrepôts pour suivre le stock de vos produits. Si cette option est activée, vous devrez créer au moins un entrepôt pour continuer.", + "enable_barcodes_label": "Activer les codes-barres", + "enable_barcodes_hint": "Activez les codes-barres pour suivre le stock de vos produits. Cette option vous permettra de scanner les codes-barres des produits.", + "on": "ACTIVÉ" + }, + "products_section_builder_link_sections_step": { + "title": "Lier à d'autres modules", + "cards": "Cartes", + "schedulers": "Programmateurs", + "do_not_integrate": "Ne pas intégrer au module des programmateurs" + }, + "products_section_builder_schedule_settings_step": { + "title": "Paramètres de programmation", + "rental_duration_interval": "Intervalle de durée de location", + "rental_duration_interval_hint": "Sélectionnez l'intervalle pour la durée de location.", + "start_of_rental_interval": "Début de l'intervalle de location", + "start_of_rental_interval_hint": "Spécifiez l'heure à laquelle commence la location.", + "twenty_four_hours": "24 heures" + } + }, + "et_section_builder_page": { + "steps": { + "requisites_codes": { + "ie_name": "Prénom de l'Entrepreneur Individuel", + "ie_surname": "Nom de l'Entrepreneur Individuel", + "ie_patronymic": "Deuxième Prénom de l'Entrepreneur Individuel", + "org_name": "Nom de l'Entreprise", + "org_tin": "NIF de l'Entreprise", + "org_trrc": "KPP de l'Entreprise", + "org_psrn": "PSRN", + "org_type": "Type d'Entreprise", + "org_full_name": "Nom Complet de l'Entreprise", + "org_short_name": "Nom Abrégé de l'Entreprise", + "org_management_name": "Nom du Directeur", + "org_management_post": "Poste du Directeur", + "org_management_start_date": "Date de Prise de Fonction du Directeur", + "org_branch_count": "Nombre de Succursales", + "org_branch_type": "Type de Succursale", + "org_address": "Adresse de l'Entreprise", + "org_reg_date": "Date d'Enregistrement", + "org_liquidation_date": "Date de Liquidation", + "org_status": "Statut de l'Entreprise", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Nombre Moyen d'Employés", + "org_extra_founders": "Fondateurs de l'Entreprise", + "org_extra_managers": "Dirigeants de l'Entreprise", + "org_extra_capital": "Capital Social", + "org_extra_licenses": "Licences", + "org_extra_phones": "Numéros de Téléphone", + "org_extra_emails": "Adresses Email", + "bank_name": "Nom de la Banque", + "bank_bic": "BIC de la Banque", + "bank_swift": "SWIFT", + "bank_tin": "NIF de la Banque", + "bank_trrc": "KPP de la Banque", + "bank_correspondent_acc": "Compte Correspondant", + "bank_payment_city": "Ville pour l'Ordre de Paiement", + "bank_opf_type": "Type d'Organisation Bancaire", + "bank_checking_account": "Compte de Compte" + }, + "save_error": "Erreur lors de l'enregistrement. Certaines étapes manquent d'informations requises.", + "details": "Détails", + "analytics": "Analyste", + "requisites": "Requis", + "step1": { + "label": "1ère étape", + "description": "Configurer un nouveau module" + }, + "step2": { + "label": "2ème étape", + "description": "Choisir un nom et créer des champs pour votre nouvelle carte" + }, + "step3": { + "label": "3ème étape", + "description": "Ajouter des fonctionnalités à la carte" + }, + "step4": { + "label": "4ème étape", + "description": "Lier à d'autres modules dans votre espace de travail" + }, + "default_titles": { + "builder": "Créateur", + "project": "Projets et Tâches", + "production": "Production", + "universal": "Module Universel", + "partner": "Partenaires", + "deal": "CRM", + "contact": "Contacts", + "company": "Entreprises", + "supplier": "Fournisseurs", + "contractor": "Sous-traitants", + "hr": "Recrutement" + } + }, + "tasks_fields_options": { + "planned_time": "Temps Estimé", + "board_name": "Nom du Tableau", + "assignee": "Assigné à", + "start_date": "Date de Début", + "end_date": "Date de Fin", + "description": "Description", + "subtasks": "Sous-tâches" + }, + "et_section_builder_step1": { + "title": "Personnaliser le module", + "name_the_section": "Nom du module", + "name_the_section_hint": "Ce nom sera affiché dans la barre latérale gauche. Vous pourrez le changer plus tard.", + "choose_icon": "Choisissez une icône pour la barre latérale gauche", + "choose_icon_hint": "Cette icône sera affichée dans la barre latérale gauche. Vous pourrez la changer plus tard.", + "type_of_display": "Type d'affichage", + "type_of_display_hint": "Sélectionnez le type d'interface – liste ou tableau/entonnoir ou les deux.", + "board": "Tableau", + "board_img_alt": "{{company}} – Aperçu de l'interface de tableau", + "list": "Liste", + "list_img_alt": "{{company}} – Aperçu de l'interface de liste", + "placeholders": { + "section_name": "Nom du module" + } + }, + "et_section_builder_step2": { + "title": "Configurer les paramètres de la nouvelle carte", + "name_the_card": "Nom de la Carte", + "name_the_card_hint": "Nom de la Carte.", + "fine_tuning": "Ajustement fin de la carte", + "create_fields": "Créer des champs pour votre nouvelle carte", + "create_fields_hint": "Les champs sont les principaux éléments informatifs de la carte, vous pouvez créer plusieurs groupes et types de champs.", + "placeholders": { + "card_name": "Nom de la carte" + } + }, + "et_section_builder_step3": { + "title": "Ajouter des fonctionnalités à la carte", + "error": "Veuillez sélectionner au moins une fonction.", + "features": { + "saveFiles": "Fichiers", + "tasks": "Tâches", + "notes": "Notes", + "chat": "Chat", + "activities": "Activités", + "documents": "Créer des documents", + "products": "Produits" + } + }, + "et_section_builder_step4": { + "title": "Lier à d'autres modules", + "cards": "Cartes", + "products": "Produits", + "schedulers": "Programmateurs", + "do_not_integrate": "Ne pas intégrer au module des programmateurs" + }, + "entity_type_used_in_formula_warning_modal": { + "title": "Le module est utilisé dans la formule", + "annotation": "Vous ne pouvez pas supprimer la relation avec le module, car son champ est utilisé dans la formule." + } + }, + "builder_journey_picker_page": { + "request_bpmn_form_modal": { + "error": "Une erreur s'est produite lors de l'envoi de votre demande. Veuillez vérifier que toutes les informations sont correctes et réessayez plus tard.", + "send_request": "Soumettre la demande", + "header": "BPMN – Laissez une demande et nous vous contacterons", + "full_name": "Nom complet *", + "phone": "Téléphone *", + "email": "Email *", + "comment": "Commentaire", + "placeholders": { + "full_name": "Jean Dupont", + "comment": "Informations supplémentaires" + }, + "us_price_full": "Connecter BPMN pour $1 par mois par utilisateur!", + "ru_price_full": "Connecter BPMN pour 99₽ par mois par utilisateur!" + }, + "request_additional_storage_modal": { + "error": "Une erreur est survenue lors de l’envoi de votre demande. Vérifiez vos informations et réessayez.", + "send_request": "Envoyer la demande", + "header": "Stockage supplémentaire – Envoyez votre demande et nous vous contacterons", + "full_name": "Nom complet *", + "phone": "Téléphone *", + "email": "E-mail *", + "comment": "Commentaire", + "placeholders": { + "full_name": "Jean Dupont", + "comment": "Informations supplémentaires" + }, + "ten_gb_ru_description": "Ajoutez 10 Go de stockage pour 100₽/mois.", + "ten_gb_us_description": "Ajoutez 10 Go de stockage pour $1/mois.", + "one_hundred_gb_ru_description": "Ajoutez 100 Go de stockage pour 500₽/mois.", + "one_hundred_gb_us_description": "Ajoutez 100 Go de stockage pour $5/mois.", + "one_tb_ru_description": "Ajoutez 1 To de stockage pour 1500₽/mois.", + "one_tb_us_description": "Ajoutez 1 To de stockage pour $15/mois." + }, + "create_a_new_module": "Créer un nouveau module", + "module_names": { + "bpmn": { + "us_price_tag": "$1/Mo.", + "ru_price_tag": "99₽/Mo." + }, + "wazzup": "Wazzup", + "main_modules": "Sélectionnez le module fonctionnel que vous souhaitez créer", + "additional_modules": "Modules fonctionnels supplémentaires", + "marketplace": "Place de marché", + "crm": "CRM", + "project_management": "Projets et Tâches", + "production": "Production", + "product_management_for_sales": "Gestion des Stocks", + "product_management_rentals": "Gestion des Locations", + "scheduler": "Planification des rendez-vous", + "supplier_management": "Fournisseurs", + "contractor_management": "Sous-traitants", + "partner_management": "Partenaires", + "hr_management": "Recrutement", + "contact": "Contacts", + "company": "Entreprises", + "universal_module": "Module Universel", + "finances": "Gestion Financière", + "automatisation": "Automatisation et Orchestration BPMN", + "marketing": "Gestion du Marketing", + "rentals": "Locations", + "for_sales": "Pour les ventes", + "soon": "Bientôt", + "chosen": "Sélectionné", + "telephony": "PBX téléphonie", + "mailing": "Service de Messagerie", + "messenger": "Multi Messagerie", + "documents": "Génération de Documents", + "forms": "Formulaires sur le Site Web", + "headless_forms": "Formulaires sur le Site via API", + "online_booking": "Formulaires de réservation en ligne", + "website_chat": "Chat sur le Site Web", + "tilda": "Tilda Publishing", + "wordpress": "WordPress", + "twilio": "Whatsapp Business", + "fb_messenger": "Facebook Messenger", + "salesforce": "Salesforce", + "make": "Make", + "apix_drive": "ApiX-Drive", + "albato": "Albato", + "one_c": "1C", + "additional_storage": "Stockage supplémentaire", + "ten_gb": "10 Go de stockage supplémentaire", + "one_hundred_gb": "100 Go de stockage supplémentaire", + "one_tb": "1 To de stockage supplémentaire", + "storage": { + "ten_gb_ru_price_tag": "100₽/Mo.", + "ten_gb_us_price_tag": "$1/Mo.", + "one_hundred_gb_ru_price_tag": "500₽/Mo.", + "one_hundred_gb_us_price_tag": "$5/Mo.", + "one_tb_ru_price_tag": "1500₽/Mo.", + "one_tb_us_price_tag": "$15/Mo." + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.fields.json b/frontend/public/locales/fr/module.fields.json new file mode 100644 index 0000000..21f2102 --- /dev/null +++ b/frontend/public/locales/fr/module.fields.json @@ -0,0 +1,122 @@ +{ + "fields": { + "formula_warning": "Modifier la formule recalculera la valeur dans ce champ. Pour conserver les données actuelles, envisagez de créer un nouveau champ.", + "project_fields_block": { + "owner": "Propriétaire", + "value": "Valeur", + "participants": "Participants", + "start_date": "Date de début", + "end_date": "Date de fin", + "description": "Description", + "placeholders": { + "participants": "Ajouter des participants" + } + }, + "field_value": { + "ogrn": "OGRN", + "tin": "TIN", + "trrc": "TRRC", + "already_have_an_active_call": "Vous avez déjà une appel actif", + "recent": "Récent", + "no_available_phone_numbers": "Aucun numéro disponible", + "value_field_formula_calculation": "Ce champ est actuellement calculé par une formule, il ne peut pas être modifié manuellement. Valeur: {{value}}.", + "readonly": "Ce champ est en lecture seule", + "important_field": "Champ Important", + "mandatory_field": "Champ Obligatoire", + "important_field_completed": "Champ Important Complété", + "mandatory_field_completed": "Champ Obligatoire Complété", + "local_time": "Heure locale approximative de la capitale du pays du numéro de téléphone", + "connect_telephony": "Connectez la téléphonie pour activer la fonctionnalité d'appels.", + "chat_unavailable": "On dirait que vous n'avez pas accès à ce chat. Contactez votre administrateur pour vous connecter." + }, + "components": { + "field_formula_settings_button": { + "no_available_fields_for_formula": "Aucun champ de budget, de nombre ou de formule disponible", + "save_first": "Cette formule peut être configurée sur la fiche", + "customize": "Personnaliser la Formule : {{fieldName}}", + "formula_hint": "Ce champ est destiné à entrer une formule. La formule peut inclure les valeurs fournies ci-dessous, ainsi que d'autres champs numériques et formules, y compris des champs d'autres modules fonctionnels. Cliquez sur la valeur souhaitée ou sélectionnez le champ.", + "field": "Champ", + "close": "Fermer" + }, + "field_settings_modal": { + "number": "Nombre", + "currency": "Devise", + "format_title": "Sélectionnez le format d'affichage pour ce champ", + "ordinary_field": "Champ Ordinaire", + "save_first": "Ce champ ne peut être configuré qu'après l'avoir enregistré sur la fiche", + "important_field": "Champ Important", + "important_field_hint": "Vous pouvez rendre un champ important, il aura alors une icône d'ampoule jaune comme rappel qu'il doit être complété. Une fois complété, il affichera une coche verte. Si vous avez besoin qu'un champ soit obligatoire et éviter de passer à l'étape suivante sans le compléter, alors sélectionnez la fonction de \"champ obligatoire\".", + "select_pipeline_and_statuses": "Sélectionnez Pipeline et États", + "mandatory_field": "Champ Obligatoire", + "mandatory_field_hint": "Vous pouvez configurer un champ obligatoire pour sa complétion. Dans ce cas, les utilisateurs ne pourront pas passer à l'état suivant dans le pipeline. Sélectionnez le pipeline requis et l'état à partir duquel le champ doit être complété.", + "user_exclusion": "Exclusion d'Utilisateurs", + "user_exclusion_hint": "Vous pouvez exclure certains utilisateurs qui pourront ne pas compléter ce champ. Cela peut être nécessaire pour les administrateurs.", + "editing_restriction": "Restriction d'Édition", + "editing_restriction_hint": "Vous pouvez interdire à certains utilisateurs de modifier ce champ. Ils pourront le voir, mais pas le modifier.", + "select_users": "Sélectionner les Utilisateurs", + "hide_field": "Masquer le Champ pour les Utilisateurs", + "hide_field_hint": "Vous pouvez masquer ce champ pour certains utilisateurs; il sera inaccessible pour eux.", + "field_visibility_in_pipeline": "Masquer le champ dans le pipeline", + "field_visibility_in_pipeline_hint": "Vous pouvez personnaliser la visibilité du champ pour chaque pipeline et étape. Ci-dessous, vous pouvez sélectionner dans quelles étapes et pipelines le champ sera masqué.", + "customize": "Personnaliser le Champ : {{fieldName}}" + }, + "edit_fields": { + "add_bank_requisites": "Ajouter les coordonnées bancaires", + "add_org_requisites": "Ajouter les informations des organisations et entrepreneurs", + "add_org_stat_codes": "Ajouter les codes statistiques des organisations et entrepreneurs", + "add_additional_org_requisites": "Ajouter des champs supplémentaires pour les organisations et entrepreneurs", + "add_field": "Ajouter un champ", + "add_utm_fields": "Ajouter des champs UTM", + "add_ga_fields": "Ajouter des champs Google Analytics", + "add_ym_fields": "Ajouter des champs Yandex.Metrica", + "add_fb_fields": "Ajouter des champs Facebook Analytics", + "select_options": { + "max_length": "Maximum {{length}} caractères", + "add_option": "Ajouter une option", + "cancel": "Annuler", + "add": "Ajouter", + "no_options": "Aucune option", + "placeholders": { + "search": "Rechercher...", + "option": "Option" + } + } + }, + "field_form_group": { + "max_length": "Maximum {{length}} caractères", + "text": "Texte", + "number": "Nombre", + "multitext": "Texte multiple", + "select": "Sélectionner", + "multiselect": "Sélection multiple", + "switch": "Interrupteur", + "formula": "Formule", + "phone": "Téléphone", + "email": "Email", + "value": "Valeur", + "date": "Date", + "link": "Lien", + "file": "Fichier", + "richtext": "Texte formaté", + "participant": "Participant", + "participants": "Participants", + "colored_select": "Sélection de couleur", + "colored_multiselect": "Sélection multiple de couleur", + "checked_multiselect": "Liste de contrôle", + "checklist": "Texte multiple avec cases", + "field_name": "Nom du champ", + "analytics_field": "Champ d'analyse" + }, + "show_fields": { + "no_fields": "Aucun champ" + }, + "show_files": { + "empty": "Aucun fichier pour le moment", + "attach": "Joindre des fichiers" + }, + "file_field_value_comp": { + "attach_files": "Joindre des fichiers" + } + } + } +} diff --git a/frontend/public/locales/fr/module.mailing.json b/frontend/public/locales/fr/module.mailing.json new file mode 100644 index 0000000..945e478 --- /dev/null +++ b/frontend/public/locales/fr/module.mailing.json @@ -0,0 +1,234 @@ +{ + "mailing": { + "unknown_file": "Fichier inconnu", + "modals": { + "send_email_modal": { + "to": "À :", + "copy": "Copie :", + "hidden_copy": "Copie cachée :", + "from": "De :", + "subject": "Objet :", + "attachment": "pièce jointe", + "attachments": "pièces jointes", + "delete_all": "Tout supprimer", + "send": "Envoyer", + "error": "Impossible d'envoyer le message ! Veuillez réessayer plus tard.", + "send_with_html": "Envoyer le courrier au format HTML", + "send_with_html_hint": "Sélectionnez cette option si vous souhaitez envoyer un courrier au format HTML. Ceci est utile si vous souhaitez ne pas utiliser les fonctions de formatage par défaut de {{company}}.", + "components": { + "changes_not_saved_modal": { + "title": "Les modifications ne sont pas enregistrées", + "annotation": "Continuer à modifier ?", + "approve": "Supprimer le brouillon" + }, + "invalid_email_address_modal": { + "title": "L'email \"{{email}}\" ne semble pas être une adresse valide", + "annotation": "Vérifiez l'adresse et réessayez.", + "approve": "Envoyer quand même" + }, + "send_email_settings_dropdown": { + "show_copy": "Afficher le champ adresse de copie", + "show_hidden_copy": "Afficher le champ adresse de copie cachée" + }, + "editors": { + "email_signature_editor": { + "placeholder": "Ajouter une signature", + "no_signature": "Pas de signature" + }, + "email_text_editor": { + "placeholders": { + "new_message": "Nouveau message", + "enter_html_markup": "Entrez le format HTML" + } + } + } + } + } + }, + "pages": { + "mailing_settings_page": { + "soon": "Bientôt...", + "add_button": "Ajouter Boîte de Réception", + "mail_templates": "Modèles de Courrier", + "templates_caption": "Vous pouvez créer des modèles de courrier qui peuvent être envoyés à un client. Après avoir ajouté le modèle, vous pouvez accéder à l'envoi de courriers.", + "setup_signature": "Configurer une signature", + "add_template": "Ajouter un modèle", + "components": { + "mailbox_item": { + "draft_text": "Brouillon", + "draft_hint": "La connexion de la boîte de réception n'est pas terminée. Veuillez saisir les paramètres manquants.", + "inactive_text": "Inactif", + "inactive_hint": "La boîte de réception est inactive. {{error}}.", + "sync_text": "Synchronisation", + "sync_hint": "La boîte de réception est en cours de synchronisation. Veuillez patienter.", + "active_text": "Actif" + } + }, + "modals": { + "delete_mailbox_modal": { + "delete": "Supprimer", + "title": "Voulez-vous vraiment supprimer cette boîte de réception ?", + "save_correspondence_annotation": "Enregistrer l'historique des correspondances pour cet email ?", + "save_correspondence": "Sauvegarder l'historique des correspondances pour cet email" + }, + "mailbox_address_modal": { + "continue": "Continuer", + "title": "Connecter la boîte de réception", + "placeholder": "Votre email", + "caption1": "Connectez une boîte de réception d'entreprise avec accès partagé, qui reçoit les demandes des clients, ou une boîte de réception personnelle d'un de vos employés.", + "caption2": "Les emails envoyés à cette boîte de réception seront automatiquement attachés aux contacts. Vous pouvez créer une affaire directement depuis la liste de courriers.", + "caption3": "Si vous avez des problèmes de connexion, essayez d'activer l'accès pour votre client de messagerie.", + "cancel": "Annuler", + "google_caption1": "L'utilisation et le transfert d'informations reçues des API Google par {{company}} à toute autre application adhéreront à", + "google_policy_link": "la Politique de Données des Utilisateurs des Services API de Google", + "google_caption2": ", y compris les exigences d'Utilisation Limitée." + }, + "mailbox_provider_modal": { + "title": "Choisir un fournisseur", + "caption": "Sélectionnez votre fournisseur de courrier ci-dessous. Si vous ne voyez pas votre service de courrier dans la liste, appuyez sur Manuel et configurez votre boîte de réception manuellement.", + "manual": "Manuel" + }, + "update_mailbox_modal": { + "max_number_of_emails_per_day": "Nombre maximum d'emails par jour", + "emails_per_day": "emails par jour", + "emails_per_day_hint": "Le paramètre de limite d'emails est nécessaire pour éviter d'être bloqué par le fournisseur de service de messagerie. Chaque service de messagerie fixe ses propres limites pour l'envoi de messages. Par exemple, la version de base de Google Workspace permet d'envoyer 500 messages par jour depuis une boîte aux lettres.", + "email_readonly_title": "Vous ne pouvez pas modifier l'adresse d'une boîte aux lettres déjà créée", + "title_connect": "Connecter la boîte de réception", + "title_edit": "Modifier la boîte de réception de courrier", + "encryption": "Cryptage", + "owner": "Sélectionner le propriétaire de l'email", + "for_whom_available": "Sélectionner pour qui l'email est disponible", + "synchronize": "Synchroniser les emails des 7 derniers jours", + "create_entities_annotation": "Configurez les options ci-dessous pour créer automatiquement une fiche lors de la réception d’un e-mail d’un nouveau contact.", + "create_entities_label": "Créer une fiche à la réception d’un e-mail", + "delete": "Supprimer", + "reconnect": "Reconnecter la boîte de réception", + "placeholders": { + "password": "Mot de passe", + "imap": "Serveur IMAP", + "port": "Port", + "smtp": "Serveur SMTP", + "owner": "Sélectionner le propriétaire" + } + }, + "mailbox_signature_modal": { + "title": "Signatures", + "mailbox_signature_editor": { + "available_in_mailboxes": "Disponible dans les courriers :", + "delete": "Supprimer", + "save": "Enregistrer", + "warning_title": "Supprimer la signature", + "warning_annotation": "Êtes-vous sûr de vouloir supprimer cette signature ?", + "save_as_html": "Enregistrer au format HTML", + "save_as_html_hint": "Sélectionnez cette option si vous souhaitez enregistrer la signature au format HTML. Ceci est utile si vous souhaitez ne pas utiliser les fonctions de formatage par défaut de {{company}}.", + "placeholders": { + "signature_name": "Nom de la signature", + "your_signature": "Votre signature", + "select_mailboxes": "Sélectionner les boîtes de réception" + } + } + } + } + }, + "mailing_page": { + "title": "Courrier Électronique et Messagerie", + "no_email": "Pas encore de courriers", + "components": { + "section": { + "inbox": "Boîte de réception", + "sent": "Envoyés", + "spam": "Spam", + "trash": "Corbeille", + "draft": "Brouillon", + "flagged": "Marqués", + "archive": "Archive", + "all": "Tous les mails", + "mailbox": "Boîte de réception {{number}}" + }, + "message_panel": { + "no_messages": "Aucun message", + "demo_message_subject": "✉️ {{company}} : Solution de Courrier d'Entreprise", + "demo_message_snippet": "Cher Client, Nous présentons une solution de courrier d'entreprise révolutionnaire qui captera sans aucun doute votre attention." + }, + "attachments_block": { + "attachment": "pièce jointe", + "attachments": "pièces jointes", + "download_all": "Tout télécharger" + }, + "reply_controls": { + "reply": "Répondre", + "reply_all": "Répondre à tous", + "forward": "Transférer" + }, + "create_message_button": { + "title": "Nouveau message" + }, + "no_mailboxes_panel_block": { + "title": "Cliquez sur le bouton ci-dessous pour ajouter votre boîte de réception." + }, + "thread": { + "unknown": "Inconnu 👤", + "no_selected_message": "Aucun message sélectionné", + "from": "De", + "subject": "Objet", + "reply": "Répondre", + "reply_all": "Répondre à tous", + "forward": "Transférer", + "add_task": "Ajouter une Tâche", + "add_contact": "Ajouter un contact", + "spam": "Spam", + "unspam": "Non spam", + "move_to_inbox": "Déplacer vers la boîte de réception", + "trash": "Corbeille", + "close": "Fermer", + "unseen": "Non vu", + "user": "Utilisateur", + "date": "{{day}} à {{time}}", + "amwork_workspace": "Espace de travail de {{company}}", + "demo_message_title": "✉️ {{company}} : Solution de Courrier d'Entreprise", + "demo_message_snippet": "Cher Client, nous présentons une solution de courrier d'entreprise révolutionnaire qui captera sans aucun doute votre attention. Découvrez de nouvelles possibilités et élevez votre communication d'entreprise à un nouveau niveau avec l'intégration du courrier dans l'espace de travail de {{company}}.", + "dear_customer": "Cher Client,", + "demo_message_intro": "Nous présentons une solution de courrier d'entreprise révolutionnaire qui captera sans aucun doute votre attention. Découvrez de nouvelles possibilités et élevez votre communication d'entreprise à un nouveau niveau avec l'intégration du courrier dans l'espace de travail de {{company}}. Connectez toute boîte de réception d'entreprise ou personnelle, et profitez non seulement des fonctions familières du courrier électronique, mais aussi d'une multitude d'outils supplémentaires. Créez des tâches, des prospects, des projets et plus directement depuis un courrier, tout en gardant le contrôle total sur toutes les interactions avec les clients, les partenaires et les projets. Envoyez des messages facilement depuis des fiches de prospects, d'affaires, de partenaires ou de projets, et ils seront automatiquement enregistrés dans la fiche correspondante. De cette manière, vous pouvez lire et analyser facilement l'historique de correspondance pour une affaire ou un projet spécifique. Travaillez plus efficacement en équipe en accordant l'accès à une boîte de réception partagée et en gérant les communications avec vos collègues. Découvrez de nouveaux horizons avec {{company}} et augmentez la productivité de votre entreprise !", + "email_functionality": "📬 Fonctionnalité du courrier :", + "email_functionality_ul": "
  • Intégration API avec Gmail
  • Intégration IMAP
  • Intégration des courriers des 7 derniers jours pour les boîtes de réception récemment ajoutées
  • Courriers entrants et sortants
  • Regroupement des courriers en fils/chaînes par expéditeur
  • Possibilité de créer une tâche depuis un courrier
  • Possibilité de créer un contact depuis un courrier
  • Création automatique de prospects depuis les courriers
  • Joindre les courriers entrants et sortants à l'historique/flux d'une fiche
  • Capacité d'écrire un courrier depuis une fiche
  • Création de dossiers personnalisés
  • Utilisation de plusieurs boîtes de réception
  • Gestion collaborative des boîtes de réception
  • Affichage des courriers au format HTML
  • Gestion collaborative des boîtes de réception
  • Recherche de courriers
  • Spam
  • Éléments supprimés
  • Brouillons
  • Création de signatures
  • Destinataires en copie
  • Destinataires en copie cachée
  • Pièces jointes
", + "reach_out": "Si vous avez des questions, n'hésitez pas à nous contacter –", + "sincerely_amwork": "Cordialement, votre équipe {{company}}." + }, + "modals": { + "create_contact_modal": { + "create": "Créer", + "title": "Créer des cartes à partir du courrier", + "create_contact": "Créer un contact pour le courrier", + "create_lead": "Créer un prospect pour le courrier", + "open_entity": "Ouvrir l'entité nouvellement créée", + "placeholders": { + "where_contact": "Sélectionnez où créer le nouveau contact", + "where_lead": "Sélectionnez où créer le nouveau prospect", + "board_for_lead": "Sélectionnez le tableau pour le nouveau prospect" + } + }, + "link_contact_modal": { + "link": "Lier", + "title": "Lier une fiche", + "select_module": "Sélectionner un module", + "search_card": "Sélectionner une fiche", + "open_entity": "Ouvrir la fiche liée", + "placeholders": { + "module": "Module...", + "search_card": "Rechercher par nom..." + } + } + } + }, + "hooks": { + "use_message_controls": { + "unknown": "Inconnu 👤", + "reply_to": "Répondre à", + "reply_all": "Répondre à tous", + "forward": "Transférer" + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.multichat.json b/frontend/public/locales/fr/module.multichat.json new file mode 100644 index 0000000..6868252 --- /dev/null +++ b/frontend/public/locales/fr/module.multichat.json @@ -0,0 +1,71 @@ +{ + "multichat": { + "amwork_messenger": "Messagerie de {{company}}", + "components": { + "multichat_control": { + "not_found": "Non trouvé", + "chats": "Chats", + "messages": "Messages", + "users": "Utilisateurs", + "owner": "Propriétaire", + "supervisor": "Superviseur", + "no_chats_yet": "Pas encore de chats", + "create_card": "Créer une Fiche", + "link_card": "Lier une Fiche", + "card": "Fiche", + "ui": { + "deleted_user": "Utilisateur supprimé 👻", + "multichat_control_header": { + "title": "Multi Messagerie de {{company}}" + }, + "create_amwork_chat_button": { + "label": "Nouveau chat de {{company}}", + "modals": { + "select_contact_title": "Sélectionnez un utilisateur ou un groupe d'utilisateurs", + "continue": "Continuer", + "customize_chat": "Personnaliser le chat", + "placeholders": { + "group_name": "Nom du groupe" + } + } + }, + "chats_header_providers_tabs": { + "all_chats": "Tous les Chats" + }, + "chat_message_context_menu": { + "reply": "Répondre", + "copy": "Copier le texte", + "edit": "Éditer", + "delete": "Supprimer" + }, + "chat_message_reply_block": { + "editing_message": "Modification d’un message" + }, + "send_chat_message_block": { + "placeholders": { + "message": "Message" + } + }, + "no_selected_chat_plug": { + "title": "Sélectionnez un chat pour commencer à envoyer des messages" + }, + "chats_panel_block": { + "you": "Vous" + }, + "send_chat_message_with_files_modal": { + "send_files": "Envoyer des fichiers" + }, + "chat_message_item": { + "you": "Vous" + }, + "chat_controls_menu": { + "mark_as_read": "Marquer comme lu", + "delete_chat": "Supprimer le chat", + "delete_warning_title": "Êtes-vous sûr de vouloir supprimer le chat?", + "delete_warning_annotation": "Tous les messages seront supprimés. Cette action est irréversible." + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.notes.json b/frontend/public/locales/fr/module.notes.json new file mode 100644 index 0000000..3def9b3 --- /dev/null +++ b/frontend/public/locales/fr/module.notes.json @@ -0,0 +1,49 @@ +{ + "notes": { + "notes_page": { + "module_name": "Notes", + "add_note": "Créer une Note", + "new_note_heading": "Nouvelle Note" + }, + "toolbar": { + "undo": "Annuler", + "redo": "Rétablir", + "bold": "Gras", + "italic": "Italique", + "underline": "Souligné", + "strike": "Barré", + "bullet_list": "Liste à puces", + "ordered_list": "Liste numérotée", + "text": "Texte", + "heading": "Titre", + "subheading": "Sous-titre", + "heading_3": "Sous-sous-titre", + "more": "Plus...", + "expand": "Agrandir", + "minimize": "Réduire" + }, + "editor": { + "recently_edited": "À l'instant", + "quick_note": "Note Rapide", + "last_changed": "Dernières modifications : ", + "duplicate": "Dupliquer", + "put_into_folder": "Restaurer", + "delete": "Supprimer la note" + }, + "selector": { + "new_note_heading": "Nouvelle Note", + "saved_indicator": "Les modifications sont enregistrées", + "pinned": "Épinglée", + "today": "Aujourd'hui", + "yesterday": "Hier", + "earlier": "Plus tôt" + }, + "block": { + "unnamed_note": "Note Sans Nom" + }, + "folders": { + "all": "Toutes", + "recently_deleted": "Supprimées Récemment" + } + } +} diff --git a/frontend/public/locales/fr/module.notifications.json b/frontend/public/locales/fr/module.notifications.json new file mode 100644 index 0000000..55e60ee --- /dev/null +++ b/frontend/public/locales/fr/module.notifications.json @@ -0,0 +1,79 @@ +{ + "notifications": { + "tags": { + "task_new": "Nouvelle tâche", + "task_overdue": "Tâche", + "task_before_start": "Tâche", + "task_overdue_employee": "Tâche", + "activity_new": "Nouvelle activité", + "activity_overdue": "Activité", + "activity_before_start": "Activité", + "activity_overdue_employee": "Activité", + "task_comment_new": "Nouveau commentaire", + "chat_message_new": "Nouveau message", + "mail_new": "Nouveau courriel", + "entity_note_new": "Nouvelle note", + "entity_new": "Nouveau {{entityTypeName}}", + "entity_responsible_change": "Nouveau responsable {{entityTypeName}}", + "entity_import_completed": "Importation", + "yesterday": "Hier à {{time}}", + "date": "{{date}} à {{time}}" + }, + "components": { + "delay_select": { + "no_delay": "Pas de délai", + "5_minutes": "5 minutes", + "10_minutes": "10 minutes", + "15_minutes": "15 minutes", + "30_minutes": "30 minutes", + "1_hour": "1 heure" + }, + "notifications_panel": { + "mute_notifications": "Désactiver le son des notifications", + "unmute_notifications": "Activer le son des notifications", + "no_notifications": "Vous n'avez pas encore de notifications", + "ui": { + "block_header_annotation": { + "overdue": "En retard", + "overdue_employee": "{{employee}} est en retard", + "after_time": "Après {{time}}", + "from_employee": "de {{employee}}", + "system_notice": "Avis du système", + "message": "Message", + "yesterday": "Hier à {{time}}", + "date": "{{date}} à {{time}}" + }, + "notification_settings": { + "title": "Notifications" + }, + "notification_settings_modal": { + "title": "Paramètres de Notifications", + "enable_popup": "Notifications popup", + "new": "Nouveau", + "new_mail": "Nouveau Courriel", + "new_chat_message": "Nouveau Message de Chat", + "new_note": "Nouvelle Note", + "entity_responsible_change": "Changement de Responsable d'Entité", + "entity_import_complete": "Importation d'Entité Terminée", + "new_task_comment": "Nouveau Commentaire sur Tâche", + "unknown": "Inconnu", + "new_task": "Nouvelle Tâche", + "overdue_task": "Tâche en Retard", + "before_task_start": "Avant le Début d'une Tâche", + "overdue_task_employee": "Tâche en Retard pour Employé(s)", + "new_activity": "Nouvelle Activité", + "overdue_activity": "Activité en Retard", + "before_activity_start": "Avant le Début d'une Activité", + "overdue_activity_employee": "Activité en Retard pour Employé(s)", + "placeholders": { + "select": "Sélectionner" + } + }, + "read_all_button": { + "read_all": "Tout lire" + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.products.json b/frontend/public/locales/fr/module.products.json new file mode 100644 index 0000000..408ecd6 --- /dev/null +++ b/frontend/public/locales/fr/module.products.json @@ -0,0 +1,502 @@ +{ + "products": { + "components": { + "common": { + "readonly_stocks": "Vous ne pouvez pas modifier les stocks dans cet état", + "readonly_delete": "Vous ne pouvez pas supprimer des articles de la commande dans cet état", + "select_the_warehouse_to_write_off": "Sélectionnez l'entrepôt à déduire des stocks", + "return_stocks_warning_modal": { + "title": "Retourner les stocks", + "annotation": "Voulez-vous retourner les stocks à l'entrepôt ?", + "approve_title": "Retourner", + "cancel_title": "Ne pas retourner" + }, + "delete_order_warning_modal": { + "title": "Attention", + "annotation": "Voulez-vous supprimer la commande ? Cette action est irréversible." + }, + "delete_order_or_clear_items_warning_modal": { + "title": "Attention", + "annotation": "Voulez-vous supprimer la commande entière ou supprimer tous les articles ? Cette action est irréversible.", + "approve_title": "Supprimer la commande", + "cancel_title": "Supprimer les articles" + }, + "rental_order_status_select": { + "formed": "Formé", + "accepted_to_warehouse": "Accepté en entrepôt", + "sent_to_warehouse": "Envoyé à l'entrepôt", + "delivered": "Livré", + "reserved": "Réservé", + "shipped": "Expédié", + "cancelled": "Annulé", + "returned": "Retourné", + "placeholders": { + "select_status": "Sélectionner l'état" + } + }, + "order_status_select": { + "placeholders": { + "select_status": "Sélectionner l'état" + }, + "statuses": { + "reserved": "Réservé", + "sent_for_shipment": "Envoyé pour expédition", + "shipped": "Expédié", + "cancelled": "Annulé", + "returned": "Retourné" + } + }, + "products_tab_selector": { + "shipments": "Expéditions", + "timetable": "Calendrier", + "products": "Produits" + }, + "products_category_select": { + "no_categories": "Aucune catégorie", + "placeholders": { + "select_category": "Catégorie" + } + }, + "products_warehouse_select": { + "no_warehouses": "Aucun entrepôt", + "placeholders": { + "select_warehouse": "Entrepôt" + } + }, + "products_settings_button": { + "settings": "Paramètres", + "module_settings": "Paramètres du module", + "table_settings": "Paramètres du tableau", + "report_settings": "Paramètres des rapports" + }, + "calendar": { + "month": "Mois", + "week": "Semaine", + "days": { + "short": { + "mo": "Lu", + "tu": "Ma", + "we": "Me", + "th": "Je", + "fr": "Ve", + "sa": "Sa", + "su": "Di" + }, + "long": { + "mo": "Lundi", + "tu": "Mardi", + "we": "Mercredi", + "th": "Jeudi", + "fr": "Vendredi", + "sa": "Samedi", + "su": "Dimanche" + } + }, + "resources_label": "Produits", + "statuses": { + "reserved": "Réservé", + "rented": "Loué" + }, + "fields": { + "phone": "Téléphone", + "mail": "Email", + "shifts": "Nombre de jours", + "status": "État", + "period": "Période de location" + } + } + } + }, + "pages": { + "card_products_orders_page": { + "ui": { + "empty": "Il n'y a pas encore de commandes dans cette section" + }, + "hooks": { + "use_products_section_orders_columns": { + "name": "Nom de la commande", + "warehouse": "Entrepôt", + "status": "État", + "created_at": "Créé le", + "shipped_at": "Expédié le", + "creator": "Créateur", + "order": "Commande-{{number}}" + }, + "use_rental_products_section_orders_columns": { + "name": "Nom", + "status": "État", + "created_at": "Créé le", + "creator": "Créateur", + "order": "Commande-{{number}}", + "shipment": "Expédition pour commande #{{number}}", + "linked_entity": "Entité liée" + } + } + }, + "card_products_order_page": { + "templates": { + "products_order_block_template": { + "new_order": "Nouvelle Commande", + "save": "Enregistrer", + "cancel": "Annuler", + "added_to": "Ajouté à {{entity_name}}", + "empty": "Vous n'avez pas encore ajouté de produits à la commande" + }, + "products_warehouse_block_template": { + "product_management_for_sales": "Gestion des Stocks", + "product_management_rentals": "Gestion des Locations", + "products": "Produits", + "warehouse": "Entrepôt", + "add": "Ajouter", + "reset": "Réinitialiser", + "empty": "L'entrepôt est vide", + "warehouse_select_hint": "L'entrepôt sélectionné dans cette section doit correspondre à l'entrepôt commandé dans la section supérieure (commande). Si vous souhaitez sélectionner un entrepôt différent ici, vous devez d'abord supprimer ou modifier l'entrepôt choisi dans la section supérieure. Tout entrepôt sélectionné ici sera automatiquement pris en compte dans la section supérieure." + } + }, + "common": { + "products_order_price_head_cell": { + "price": "Prix," + }, + "products_order_max_discount_hint": "La remise maximale est", + "products_order_tax_header_cell": { + "tax": "Taxe,", + "tax_included": "Inclus", + "tax_excluded": "Exclu" + }, + "products_order_total_block": { + "total": "Total : {{total}}" + }, + "remove_selected_block": { + "remove_selected": "Supprimer sélectionnés" + } + }, + "hooks": { + "use_price_cell_columns": { + "name_fallback": "Sans nom de prix", + "name": "Nom", + "price": "Prix", + "currency": "Devise" + } + }, + "ui": { + "card_rental_products_order_component": { + "hooks": { + "use_rental_card_order_columns": { + "name": "Nom", + "discount": "Remise", + "availability": "Disponibilité", + "amount": "Quantité" + }, + "use_rental_warehouse_columns": { + "name": "Nom", + "price": "Prix", + "category": "Catégorie", + "availability": "Disponibilité" + } + }, + "ui": { + "rental_availability_cell": { + "available": "Disponible", + "reserved": "Réservé", + "rent": "Location" + }, + "rental_order_periods_control": { + "new_period": "Nouvelle période", + "periods_selected": "Périodes sélectionnées : {{count}}", + "add_new_period": "Ajouter une nouvelle période", + "hint_text": "Vous pouvez choisir ou modifier les périodes uniquement lors de la création de nouvelles commandes ou pour les commandes avec l'état 'Formé'.", + "placeholders": { + "select_periods": "Sélectionner des périodes" + } + }, + "card_rental_products_order_block_header": { + "placeholders": { + "select_order_warehouse": "Sélectionner l'entrepôt de la commande" + } + } + } + }, + "card_products_order_component": { + "hooks": { + "use_available_columns": { + "unknown": "Inconnu...", + "name": "Nom", + "stock": "Stocks", + "reserved": "Réservé", + "available": "Disponible" + }, + "use_card_order_columns": { + "name": "Nom", + "discount": "Remise", + "tax": "Taxe,", + "quantity": "Quantité", + "amount": "Montant" + }, + "use_reservations_columns": { + "warehouse_name": "Nom de l'entrepôt", + "stock": "Stocks", + "reserved": "Réservé", + "available": "Disponible", + "quantity": "Quantité" + }, + "use_warehouse_columns": { + "name": "Nom", + "price": "Prix", + "category": "Catégorie", + "available": "Disponible", + "quantity": "Quantité" + } + }, + "ui": { + "available_head_cell": { + "available": "Disponible", + "available_hint": "Survolez une cellule dans cette colonne pour obtenir des informations détaillées sur les stocks de l'entrepôt." + }, + "card_products_order_block_header": { + "cancel_after_hint": "Vous pouvez configurer l'annulation automatique des réservations d'articles dans les commandes. Cela aide à prévenir les niveaux de stocks incorrects, qui peuvent se produire lorsque les articles sont réservés dans les affaires mais ne sont pas traités ou ont été annulés.", + "placeholders": { + "select_order_warehouse": "Sélectionner l'entrepôt de la commande" + } + }, + "add_stock_placeholder": { + "add_stock": "Ajouter des stocks à l'entrepôt" + } + } + } + } + }, + "product_categories_page": { + "title": "Créer une catégorie (groupe de produits)", + "placeholders": { + "category_name": "Nom de la catégorie", + "subcategory_name": "Nom de la sous-catégorie" + }, + "buttons": { + "add_category": "Ajouter une catégorie", + "add_subcategory": "Ajouter une sous-catégorie" + }, + "ui": { + "delete_category_warning_modal": { + "cannot_be_undone": "Cette action est irréversible.", + "title": "Êtes-vous sûr de vouloir supprimer {{name}} ?", + "annotation": "Toutes les sous-catégories seront supprimées." + } + } + }, + "product_page": { + "product_description": "Description du produit", + "sku_already_exists_warning": "Le produit avec le SKU \"{{sku}}\" existe déjà", + "sku": "SKU", + "unit": "Unité", + "tax": "Taxe", + "category": "Catégorie", + "warehouse": "Entrepôt", + "ui": { + "add_warehouse_placeholder": { + "add_warehouse": "Ajouter un entrepôt" + }, + "product_actions_dropdown": { + "delete_product": "Supprimer le produit" + }, + "product_description": { + "placeholders": { + "add_description": "Ajouter une description" + } + }, + "product_feed": { + "calendar": "Calendrier", + "prices": "Prix", + "stocks": "Stocks", + "images": "Images", + "add_price": "Ajouter un prix", + "add_image": "Ajouter une image", + "delete_image": "Supprimer l'image", + "delete_images": "Supprimer les images", + "placeholders": { + "name": "Nom du prix", + "unit_price": "Prix unitaire" + }, + "labels": { + "maximum_discount": "Remise maximale sur le produit", + "product_cost": "Coût du produit" + } + }, + "product_name_block": { + "placeholders": { + "product_name": "Nom du produit" + } + } + } + }, + "warehouses_page": { + "page_title": "Configurer vos entrepôts", + "placeholders": { + "name": "Nom de l'entrepôt", + "select_warehouse": "Sélectionner l'entrepôt" + }, + "add_warehouse": "Ajouter un entrepôt", + "ui": { + "delete_warehouse_modal": { + "title": "Êtes-vous sûr de vouloir supprimer {{name}} ?", + "annotation": "Tous les stocks dans l'entrepôt seront supprimés.", + "annotation_move_stocks": "Tous les stocks dans l'entrepôt seront supprimés. Alternativement, vous avez l'option de transférer tous les stocks à un autre entrepôt si disponible.", + "placeholders": { + "move_stocks_to": "Déplacer les stocks vers..." + } + } + } + }, + "products_page": { + "no_warehouses_or_categories": "Aucun entrepôt ou catégorie à afficher pour les filtres", + "title": { + "sale": "Vente", + "rental": "Location", + "shipments": "Expéditions", + "calendar": "Calendrier" + }, + "tabs": { + "products": "Produits", + "timetable": "Calendrier", + "shipments": "Expéditions", + "reports": "Rapports" + }, + "all_columns_hidden": "Toutes les colonnes sont cachées dans les paramètres du tableau", + "table_settings": "Paramètres du tableau", + "display_columns": "Afficher les colonnes", + "add_product": "Ajouter un produit", + "empty": "Vous n'avez pas encore ajouté de produits", + "show_empty_resources": "Afficher les ressources vides", + "hide_empty_resources": "Cacher les ressources vides", + "hooks": { + "use_products_columns": { + "name": "Nom", + "prices": "Prix", + "sku": "SKU", + "tax": "Taxe", + "stocks": "Stocks", + "unit": "Unité", + "availability": "Disponibilité" + }, + "use_product_stocks_columns": { + "warehouse": "Entrepôt", + "reserved": "Réservé", + "available": "Disponible", + "stock": "Stocks" + }, + "use_create_stocks_columns": { + "warehouse": "Entrepôt", + "stock": "Stocks" + } + }, + "ui": { + "add_product_modal": { + "changes_not_saved_warning_title": "Êtes-vous sûr de vouloir fermer cette fenêtre ?", + "changes_not_saved_warning_annotation": "Tous les changements seront perdus.", + "changes_not_saved_warning_approve_title": "Fermer quand même", + "barcodes_hint": "Vous pouvez scanner des codes-barres avec un scanner ou les entrer manuellement dans ce champ. Chaque code-barres dans une section doit être unique.", + "sku_already_exists_warning": "Le produit avec le SKU \"{{sku}}\" existe déjà", + "types": { + "product": "Produit", + "service": "Service", + "kit": "Kit" + }, + "add_product": "Ajouter un produit", + "name": "Nom", + "type": "Type", + "add_photo": "Ajouter une photo", + "description": "Description", + "sku": "SKU", + "unit": "Unité", + "tax": "Taxe", + "category": "Catégorie", + "prices": "Prix", + "stocks": "Stocks" + }, + "photo_block_item": { + "alt": "Produit {{name}}" + }, + "product_price_list": { + "add_price": "Ajouter un prix" + }, + "product_search_block": { + "placeholders": { + "search_products": "Rechercher des produits" + } + }, + "product_stocks_modal": { + "title": "Définir les stocks du produit dans chaque entrepôt" + }, + "create_stocks_modal": { + "title": "Spécifier les stocks du produit" + } + } + }, + "shipment_page": { + "hooks": { + "use_shipment_columns": { + "name": "Nom", + "sku": "SKU", + "available": "Disponible", + "quantity": "Quantité" + }, + "use_rental_shipment_columns": { + "name": "Nom", + "sku": "SKU", + "tax": "Taxe", + "discount": "Remise", + "total": "Total", + "quantity": "Quantité" + } + }, + "ui": { + "shipment_page_secondary_header": { + "order": "Commande-{{number}}" + }, + "rental_shipment_page_secondary_header": { + "order": "Commande-{{number}}" + }, + "some_products_not_checked_warning_modal": { + "title": "Certains produits ne sont pas vérifiés", + "annotation": "Vous essayez de changer l'état de la commande, mais certains produits ne sont pas vérifiés. Voulez-vous changer l'état quand même ?", + "approve_title": "Changer quand même" + }, + "product_barcodes_control": { + "barcode": "Code-barres", + "modals": { + "product_is_not_in_order_warning_modal": { + "title": "Le produit n'est pas dans cette commande", + "approve_title": "Ajouter le produit à la commande", + "annotation": "Le produit associé au code-barres \"{{barcode}}\" n'est pas inclus dans cette commande. Vous avez la possibilité de l'ajouter manuellement ou de continuer sans lui." + }, + "product_does_not_exist_warning_modal": { + "title": "Le produit n'existe pas", + "approve_title": "Créer le produit", + "annotation": "Le produit avec le code-barres \"{{barcode}}\" n'existe pas dans cette section de produits. Vous pouvez le créer manuellement sur la page des produits." + } + } + } + } + }, + "shipments_page": { + "hooks": { + "use_shipments_columns": { + "unknown": "Inconnu...", + "name": "Nom", + "shipment": "Expédition pour commande #{{number}}", + "warehouse": "Entrepôt", + "shipped_at": "Expédié le", + "created_at": "Créé le", + "status": "État", + "linked_entity": "Entité liée" + } + }, + "ui": { + "shipments_table": { + "all_columns_hidden": "Toutes les colonnes sont cachées dans les paramètres du tableau", + "empty": "Aucune expédition prévue" + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.reporting.json b/frontend/public/locales/fr/module.reporting.json new file mode 100644 index 0000000..461d733 --- /dev/null +++ b/frontend/public/locales/fr/module.reporting.json @@ -0,0 +1,432 @@ +{ + "reporting": { + "templates": { + "components": { + "report_table": { + "empty": "Aucune donnée disponible pour cette période" + }, + "report_settings_drawer": { + "table_settings": "Paramètres du tableau", + "display_columns": "Afficher les colonnes" + } + }, + "comparative_report_template": { + "placeholders": { + "users": "Utilisateurs", + "pipeline": "Pipelines de vente", + "month": "Sélectionner le mois", + "year": "Sélectionner l'année" + } + }, + "general_report_template": { + "apply_filter": "Appliquer le filtre", + "reset": "Réinitialiser", + "stages": { + "all": "Toutes les étapes", + "open": "Étapes ouvertes", + "lost": "Étapes perdues", + "won": "Étapes gagnées" + }, + "placeholders": { + "users": "Utilisateurs", + "pipeline": "Pipelines de vente", + "stage": "Étapes", + "warehouse": "Entrepôts", + "category": "Catégories", + "field_or_responsible": "Champ ou Responsable" + } + }, + "calls_report_template": { + "directions": { + "all": "Tous les appels", + "incoming": "Appels entrants", + "outgoing": "Appels sortants" + }, + "placeholders": { + "users": "Utilisateurs", + "pipeline": "Pipelines de vente", + "directions": "Type d'appel", + "duration": "Durée de l'appel", + "only_missed": "Appels manqués uniquement" + } + } + }, + "hooks": { + "use_get_products_general_report_columns": { + "name": "Nom", + "sold": "Total vendu", + "shipped": "Expédié", + "open": "Affaires ouvertes", + "lost": "Affaires perdues", + "all": "Total des affaires", + "average_products": "Moyenne de produits par affaire", + "average_budget": "Valeur moyenne des affaires gagnées", + "average_term": "Durée moyenne de l'affaire" + }, + "use_calls_history_report_columns": { + "timestamps": "Date de l'appel", + "type": "Appelant et destinataire", + "result": "Durée de l'appel et lecture", + "unknown": "Numéro inconnu", + "caller": "Appelant", + "callee": "Destinataire" + }, + "use_get_general_report_columns": { + "result": "Résultat", + "all": "Tous", + "open": "Ouverts", + "expired": "Expirés", + "completed": "Terminés", + "won": "Gagnés", + "lost": "Perdus", + "groups": "Groupes", + "users": "Utilisateurs", + "cards": "Cartes (Leads)", + "tasks": "Tâches", + "activities": "Activités", + "average_check": "Panier moyen", + "average_term": "Durée moyenne (jours)", + "switch_on": "Allumé", + "switch_off": "Éteint", + "calls": "Appels", + "total": "Total des appels", + "average": "Moyenne des appels", + "incoming": "Appels entrants", + "incoming_average": "Moyenne des appels entrants", + "outgoing": "Appels sortants", + "outgoing_average": "Moyenne des appels sortants", + "missed": "Appels manqués", + "min": "min", + "undefined_client": "Programmation des rendez-vous sans une affaire" + }, + "use_get_projects_report_columns": { + "hours": "h", + "min": "min", + "users": "Utilisateurs", + "opened": "Tâches ouvertes", + "done": "Tâches terminées", + "overdue": "Tâches en retard", + "planned": "Temps planifié", + "completion_percent": "Pourcentage de réalisation du projet", + "project_name": "Nom du projet", + "stage": "État du projet" + }, + "use_get_customer_report_columns": { + "name": "Nom", + "sold": "Total vendu", + "products_quantity": "Quantité de produits", + "opened": "En cours (affaires ouvertes)", + "lost": "Affaires perdues", + "all": "Total des affaires (ouvertes et fermées)", + "average_quantity": "Moyenne des achats (affaires)", + "average_budget": "Budget moyen des affaires gagnées", + "average_duration": "Durée moyenne des affaires gagnées (jours)" + }, + "use_get_schedule_report_columns": { + "groups": "Groupes", + "users": "Utilisateurs", + "sold": "Total vendu", + "total": "Total des rendez-vous", + "scheduled": "Planifiés", + "confirmed": "Confirmés", + "completed": "Terminés", + "cancelled": "Annulés" + }, + "use_get_comparative_report_columns": { + "all": "Tous", + "open": "Ouverts", + "won": "Gagnés", + "lost": "Perdus", + "week": "Semaine {{number}}", + "quarter": "Trimestre {{number}}", + "users": "Utilisateurs", + "days": { + "monday": "Lundi", + "tuesday": "Mardi", + "wednesday": "Mercredi", + "thursday": "Jeudi", + "friday": "Vendredi", + "saturday": "Samedi", + "sunday": "Dimanche" + }, + "months": { + "january": "Janvier", + "february": "Février", + "march": "Mars", + "april": "Avril", + "may": "Mai", + "june": "Juin", + "july": "Juillet", + "august": "Août", + "september": "Septembre", + "october": "Octobre", + "november": "Novembre", + "december": "Décembre" + } + } + }, + "pages": { + "reports_page": { + "components": { + "row_title_cell": { + "total": "Total", + "without_group": "Sans groupe", + "empty_user": "Aucun utilisateur affecté" + }, + "row_event_cell": { + "to": "à" + }, + "reports_navigation_sidebar": { + "total": "Total", + "export_xlsx": "Exporter XLSX", + "export_table": "Exporté depuis {{company}} le {{date}}", + "unfold_filters_menu": "Déplier le menu des filtres", + "fold_filters_menu": "Replier le menu des filtres", + "hide_sidebar": "Cacher la barre latérale", + "show_sidebar": "Afficher la barre latérale", + "schedules": "Visites", + "title": { + "universal": "Rapports", + "project": "Rapports de projets", + "deal": "Rapports d'affaires" + }, + "general_report": "Rapport général", + "comparison_of_periods": "Comparaison des périodes", + "telephony": "Appels", + "schedule": "Visites", + "projects": "Projets", + "users": "Par utilisateurs", + "rating": "Par classement", + "groups": "Par groupes", + "days": "Par jours", + "weeks": "Par semaines", + "months": "Par mois", + "quarters": "Par trimestres", + "years": "Par années", + "callsUsers": "Par utilisateurs", + "callsGroups": "Par départements", + "callHistory": "Rapport de l'historique des appels", + "scheduleClient": "Par clients", + "scheduleDepartment": "Par groupes", + "scheduleOwner": "Par planificateurs", + "schedulePerformer": "Par spécialistes", + "customer_reports": "Contacts et entreprises", + "customerContact": "Par contacts", + "customerCompany": "Par entreprises", + "customerContactCompany": "Par contacts et entreprises", + "products": "Produits", + "productsCategories": "Par catégories", + "productsUsers": "Par utilisateurs" + } + } + }, + "dashboard_page": { + "filter": { + "placeholders": { + "select_users": "Utilisateurs", + "select_sales_pipeline": "Pipelines de vente", + "all": "Tous", + "all_active": "Tous actifs", + "open": "Ouverts", + "open_active": "Ouvert et actif", + "closed": "Fermés", + "created": "Créé" + }, + "dashboard_type_tooltip": { + "all": { + "text": "Ce rapport inclut toutes les affaires existant pendant la période, quelle que soit leur activité. Il comprend :", + "list_1": "✓ Affaires créées pendant la période.", + "list_2": "✓ Affaires clôturées (gagnées ou perdues) pendant la période.", + "list_3": "✓ Affaires existantes avant la période et restées ouvertes.", + "list_4": "✗ N'exclut pas les affaires sans activité (sans mouvement dans le pipeline)." + }, + "all_active": { + "text": "Ce rapport inclut uniquement les affaires ayant changé de phase au moins une fois pendant la période. Il comprend :", + "list_1": "✓ Affaires créées avant la période mais ayant bougé dans le pipeline.", + "list_2": "✓ Affaires créées pendant la période si elles ont bougé.", + "list_3": "✓ Affaires clôturées pendant la période si elles ont changé de phase avant clôture.", + "list_4": "✗ Exclut les affaires sans activité (sans changement de phase)." + }, + "open": { + "text": "Ce rapport inclut les affaires non clôturées (ni gagnées ni perdues) à la fin de la période. Il comprend :", + "list_1": "✓ Affaires créées avant la période et toujours ouvertes.", + "list_2": "✓ Affaires créées pendant la période et non clôturées à la fin.", + "list_3": "✗ N'inclut pas les affaires clôturées." + }, + "open_active": { + "text": "Ce rapport inclut uniquement les affaires ouvertes avec une activité (changement de phase) pendant la période. Il comprend :", + "list_1": "✓ Affaires ouvertes ayant changé de phase.", + "list_2": "✓ Nouvelles affaires ayant bougé dans le pipeline.", + "list_3": "✗ Exclut les affaires ouvertes sans mouvement.", + "list_4": "✗ Exclut les affaires clôturées." + }, + "closed": { + "text": "Ce rapport inclut uniquement les affaires clôturées (gagnées ou perdues) pendant la période. Il comprend :", + "list_1": "✓ Affaires créées avant et clôturées pendant la période.", + "list_2": "✓ Affaires créées et clôturées pendant la période.", + "list_3": "✗ Exclut toutes les affaires ouvertes." + }, + "created": { + "text": "Ce rapport inclut toutes les affaires créées pendant la période, quel que soit leur statut actuel. Il comprend :", + "list_1": "✓ Affaires créées pendant la période et restées ouvertes.", + "list_2": "✓ Affaires créées et clôturées pendant la période.", + "list_3": "✗ N'inclut pas les affaires créées avant la période." + } + } + }, + "days": { + "one_day": "jour", + "several_days": ["jours", "jours", "jours"] + }, + "auto_update_select": { + "auto_update": "Mise à jour automatique", + "modes": { + "never": "Jamais", + "minute": "1 minute", + "ten_minutes": "10 minutes", + "thirty_minutes": "30 minutes", + "hour": "1 heure" + } + }, + "sales_goal_chart": { + "title_for_sales": "Objectif de ventes", + "title": "Objectif", + "hint": "Objectif de ventes", + "settings_tip": "Paramètres", + "plug_text_for_sales": "Définir les objectifs", + "plug_text": "Configurer un objectif" + }, + "traffic_light_report": { + "title": "Rapport de feu de signalisation", + "subtitle": "Réalisation du plan d'aujourd'hui", + "hint": { + "line1": "Le rapport 'feu de signalisation' est un outil qui montre comment le département des ventes respecte le plan de vente du jour.", + "line2": "La formule pour calculer la réalisation du plan de vente du jour est la suivante :", + "line3": "Pourcentage de réalisation = (Ventes réelles divisées par Plan pour le jour actuel) × 100 %, où :", + "list": { + "point1": "Les ventes réelles sont le montant que vous avez déjà vendu jusqu'à présent.", + "point2": "Le plan pour le jour actuel fait partie du plan de vente général du mois, calculé pour le jour actuel." + }, + "line4": "Exemple de calcul :", + "line5": "Supposons que votre plan de vente pour le mois soit de 10 000 $. Aujourd'hui est le jour 10, ce qui signifie qu'environ 1/3 du mois est écoulé. Votre plan pour aujourd'hui est :", + "line6": "Plan pour le jour actuel = (10 000 $ divisé par 30 jours) × 10 jours = 3 333,33 $", + "line7": "Vous avez vendu des produits pour 3 000 $.", + "line8": "Votre pourcentage de réalisation du plan est :", + "line9": "Pourcentage de réalisation = (3 000 $ divisé par 3 333,33 $) × 100 % ≈ 90 %", + "line10": "Ainsi, votre plan est réalisé à 90 %, et dans le rapport 'feu de signalisation' vous verrez la couleur verte, car la réalisation du plan se passe bien." + }, + "plug_text": "Définir les objectifs" + }, + "top_sellers": { + "title_for_sales": "Top 5 des contributeurs", + "title": "Top 5 des leaders", + "subtitle_for_sales": "Leaders des ventes", + "hint": "Top 5 des leaders des ventes", + "others": "Autres", + "plug_text": "Lorsque vous commencerez à utiliser {{company}}, les statistiques des 5 principaux leaders des ventes apparaîtront ici", + "plug_text_for_orders": "Une fois que vous commencerez à utiliser {{company}}, les statistiques des 5 principaux leaders des utilisateurs apparaîtront ici", + "plug_text_for_candidates": "Une fois que vous commencerez à utiliser {{company}}, les statistiques des 5 principaux leaders des utilisateurs apparaîtront ici" + }, + "analytics": { + "total_leads": "Total des leads", + "total_orders": "Total des commandes", + "total_candidates": "Total des candidats", + "new_leads": "Nouveaux leads", + "new_orders": "Nouvelles commandes", + "new_candidates": "Nouveaux candidats", + "won_leads": "Gagnés", + "completed_orders": "Terminés", + "hired_candidates": "Candidats embauchés", + "lost_leads": "Perdus", + "failed_orders": "Échoués", + "rejected_candidates": "Candidats rejetés", + "total_tasks": "Total des tâches", + "completed_tasks": "Tâches terminées", + "expired_tasks": "Tâches en retard", + "no_tasks": "Leads : Sans tâches", + "cards_no_tasks": "Cartes : Sans tâches", + "total_activities": "Total des activités", + "completed_activities": "Activités terminées", + "expired_activities": "Activités en retard", + "no_activities": "Leads : Sans activités", + "cards_no_activities": "Cartes : Sans activités" + }, + "rating": { + "title": "Classement", + "hint": "Classement" + }, + "leads_status_chart": { + "title": "Résumé de l'état des leads :", + "title_for_orders": "Indicateurs de l'état des commandes :", + "title_for_candidates": "Indicateurs de l'état des candidats :", + "subtitle": "Ouverts, Perdus, Gagnés", + "subtitle_for_orders": "Ouverts, Échoués, Terminés", + "subtitle_for_candidates": "Ouverts, Candidats rejetés, Candidats embauchés", + "hint": "Résumé de l'état des leads", + "won": "Gagnés", + "completed": "Terminés", + "hired_candidates": "Candidats embauchés", + "lost": "Perdus", + "failed": "Échoués", + "rejected_candidates": "Candidats rejetés", + "opened": "Ouverts" + }, + "sales_pipeline_indicators": { + "title": "Pipeline de conversion", + "total_sales": "Total des gains", + "conversion": "Conversion des gains", + "average_amount": "Montant moyen", + "average_term": "Durée moyenne", + "days": ["jour", "jours", "jours"] + }, + "switch": { + "deals_count": "Nombre d'affaires", + "orders_count": "Nombre de commandes", + "sales_value": "Valeur des ventes", + "orders_value": "Valeur des commandes" + } + }, + "goal_settings_page": { + "title": "Paramètres des objectifs", + "total": "Total", + "back_button": "Tableau de bord", + "users_select": "Utilisateurs", + "period_type": { + "month": "Mois", + "quarter": "Trimestre" + }, + "change_period_modal": { + "title": "Attention !", + "annotation": "Changer la période entraînera la perte des objectifs que vous avez précédemment définis. Êtes-vous sûr de vouloir continuer ?", + "approve": "Oui", + "cancel": "Non" + }, + "periods": { + "months": { + "january": "Janvier", + "february": "Février", + "march": "Mars", + "april": "Avril", + "may": "Mai", + "june": "Juin", + "july": "Juillet", + "august": "Août", + "september": "Septembre", + "october": "Octobre", + "november": "Novembre", + "december": "Décembre" + }, + "quarters": { + "quarter1": "Trimestre 1", + "quarter2": "Trimestre 2", + "quarter3": "Trimestre 3", + "quarter4": "Trimestre 4" + } + }, + "form_header_amount": "Montant", + "form_header_quantity": "Transactions", + "button_save": "Enregistrer" + } + } + } +} diff --git a/frontend/public/locales/fr/module.scheduler.json b/frontend/public/locales/fr/module.scheduler.json new file mode 100644 index 0000000..0c2c5e4 --- /dev/null +++ b/frontend/public/locales/fr/module.scheduler.json @@ -0,0 +1,219 @@ +{ + "scheduler": { + "pages": { + "scheduler_board_view_page": { + "create_appointment": "Créer une réunion", + "stats_footer": { + "assigned": { + "label": "Assigné", + "hint": "Visites non confirmées" + }, + "confirmed": { + "label": "Confirmé", + "hint": "Visites confirmées" + }, + "completed": { + "label": "Terminée", + "hint": "Visites achevées" + }, + "not_took_place": { + "label": "Non réalisée", + "hint": "Visites passées non marquées comme réalisées" + }, + "not_scheduled": { + "label": "Sans prochain rendez-vous", + "hint": "Visites sans date de rendez-vous futur" + }, + "newbies": { + "label": "Première visite", + "hint": "Nouveaux visiteurs" + }, + "total": { + "label": "Total", + "hint": "Nombre total de visites" + } + } + }, + "appointment_card_list_page": { + "visit_date": "Date de visite" + }, + "scheduler_schedule_view_page": { + "sync": "Synchroniser", + "report_settings": "Paramètres de rapport", + "module_settings": "Paramètres du module", + "stats_settings": "Paramètres statistiques", + "settings": "Paramètres", + "report": "Rapport", + "overview": "Calendrier", + "responsible": "Responsable", + "stage": "Étape", + "not_provided": "Non fourni", + "statuses": { + "scheduled": "Planifié", + "confirmed": "Confirmé", + "completed": "Terminé", + "cancelled": "Annulé" + }, + "created": "Créé", + "visit": "Réunion {{number}}", + "description": "Description", + "email": "Email", + "phone": "Téléphone", + "price": "Prix", + "quantity": "Quantité", + "discount": "Remise", + "new_event": "Nouvelle Réunion", + "create_appointment": "Créer une réunion", + "tooltips": { + "reports_denied": "Vous n'êtes pas autorisé à afficher les rapports. Contactez l'administrateur du compte pour y accéder." + }, + "placeholders": { + "search_visits": "Rechercher des réunions" + }, + "hooks": { + "use_appointments_history_services_columns": { + "name": "Nom", + "price": "Prix", + "quantity": "Quantité", + "discount": "Remise", + "amount": "Montant" + }, + "use_appointments_history_columns": { + "date": "Date", + "time": "Heure", + "performer": "Intervenant", + "services": "Services", + "total": "Total", + "status": "Statut" + }, + "use_appointment_service_block_columns": { + "discount": "Remise", + "quantity": "Quantité", + "amount": "Montant" + } + }, + "ui": { + "add_appointment_modal": { + "error": "Impossible de créer la réunion. Cela peut être dû soit à une tentative de créer une réunion qui chevauche une réunion existante, soit à une date de fin antérieure à la date de début.", + "visits_history_empty": "Historique des réunions est vide", + "no_planned_visits": "Aucune réunion prévue", + "save_changes": "Enregistrer les modifications", + "planned_visit_title": "{{date}} de {{startTime}} à {{endTime}}", + "general_information": "Informations générales", + "planned_visits": "Réunions prévues", + "visits_history": "Historique des réunions", + "title": "Titre", + "visit": "Réunion #{{number}}", + "edit_visit": "Modifier la réunion – {{name}}", + "new_visit": "Planifier une réunion", + "visit_parameters": "Paramètres de la réunion", + "status": "Statut", + "scheduler": "Planificateur de réunions", + "select_users_group": "Sélectionner un groupe d'utilisateurs", + "select_user": "Sélectionner un utilisateur", + "description": "Description", + "from": "De :", + "to": "À :", + "date_and_time": "Date & Heure", + "addition_of_services": "Ajout de services", + "add_service": "Ajouter un service", + "no_services": "Aucun service", + "warning_title": "Modifications non enregistrées", + "warning_annotation": "Êtes-vous sûr de vouloir fermer cette fenêtre ? Toutes les modifications seront perdues.", + "close": "Fermer", + "delete": "Supprimer", + "count": "Total des réunions", + "last_visit": "Dernière réunion", + "completed_count": [ + "{{count}} réunion a eu lieu", + "{{count}} réunions ont eu lieu", + "{{count}} réunions ont eu lieu" + ], + "placeholders": { + "select_time_period": "Période de temps", + "title": "Titre de la réunion", + "entity_name": "Nom de l'entité", + "search_services": "Rechercher des services à ajouter", + "user": "Utilisateur", + "users_group": "Groupe d'utilisateurs", + "appointment_notes": "Note de réunion" + }, + "repeating_appointments_block": { + "header": "Visites Répétées", + "hint": "Configurez l'intervalle et le nombre de rendez-vous répétés si nécessaire. Les rendez-vous seront programmés pour les jours ouvrables spécifiés dans les paramètres.", + "interval": "Intervalle", + "count": "Nombre", + "intervals": { + "none": "Ne pas répéter", + "day": "Chaque jour", + "week": "Une fois par semaine", + "month": "Une fois par mois" + }, + "dates_display": { + "one_visit": "La visite répétée est prévue le {{date}} à {{time}}", + "visits_list": "Les visites répétées sont prévues pour {{dates}} à {{time}}", + "and": " et ", + "visits_interval_day": "Les visites répétées sont prévues chaque jour du {{from}} au {{to}} à {{time}}", + "visits_interval_week": "Les visites répétées sont prévues une fois par semaine du {{from}} au {{to}} à {{time}}", + "visits_interval_month": "Les visites répétées sont prévues une fois par mois du {{from}} au {{to}} à {{time}}" + }, + "list": { + "title": "Réunions récurrentes", + "hint": "Sélectionnez les dates des réunions récurrentes si nécessaire.", + "dates_select": "Dates des réunions", + "add_new_appointment": "Ajouter une réunion", + "new_appointment": "Nouvelle réunion", + "placeholders": { + "dates_select": "Sélectionnez des dates" + } + } + }, + "batch_cancel": { + "cancel_all": "Annuler tout", + "warning_title": "Voulez-vous vraiment annuler tous les rendez-vous prévus ?", + "warning_annotation": [ + "{{count}} rendez-vous sera annulé.", + "{{count}} rendez-vous seront annulés.", + "{{count}} rendez-vous seront annulés." + ], + "back": "Retour", + "cancel": "Annuler" + }, + "duplicate_warning_modal": { + "move": "Déplacer", + "same_time_title": "Un rendez-vous est déjà prévu à cette heure", + "same_day_title": "Un rendez-vous est déjà prévu ce jour-là", + "same_time_annotation": "Il est interdit de créer des doublons le même jour dans ce calendrier. Modifiez le rendez-vous existant ou changez la date de celui-ci.", + "same_day_annotation": "Il est interdit de créer des doublons le même jour dans ce calendrier. Voulez-vous déplacer le rendez-vous prévu à {{time}} ?" + }, + "intersect_warning_modal": { + "title": "Chevauchement de rendez-vous", + "annotation": "Le rendez-vous chevauche un créneau déjà planifié dans ce calendrier. Choisissez un autre horaire ou modifiez le responsable." + } + }, + "stats_settings_drawer": { + "title": "Paramètres des statistiques", + "description": "Valeurs des statistiques affichées", + "stats": { + "assigned": "Assigné", + "confirmed": "Confirmé", + "completed": "Terminée", + "not_took_place": "Non réalisée", + "not_scheduled": "Sans prochain rendez-vous", + "newbies": "Première visite", + "total": "Total" + } + }, + "local_time_warning": { + "local_correction": [ + "Correction d'heure locale: {{hours}} heure", + "Correction d'heure locale: {{hours}} heures", + "Correction d'heure locale: {{hours}} heures" + ], + "hint": "Vous n'êtes pas dans le fuseau horaire spécifié dans les paramètres système, de sorte que les heures de travail dans le calendrier et les heures de réunion sont décalées du nombre d'heures spécifié. Si vous pensez qu'il s'agit d'une erreur, changez le fuseau horaire dans les paramètres ou contactez l'assistance." + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.telephony.json b/frontend/public/locales/fr/module.telephony.json new file mode 100644 index 0000000..81f8df2 --- /dev/null +++ b/frontend/public/locales/fr/module.telephony.json @@ -0,0 +1,242 @@ +{ + "telephony": { + "pages": { + "calls_configuring_scenarios_page": { + "title": "Configuration du fonctionnement de l'IVR dans le module CRM", + "failed_to_reach": "Inaccessible", + "creates_manually": "L'utilisateur crée manuellement une fiche de contact et d'affaire lors de la réception d'un appel entrant", + "creates_manually_hint": "Lorsqu'un appel provient d'un numéro inconnu, une fenêtre d'appel entrant apparaît pour l'utilisateur. L'utilisateur peut répondre à l'appel et, s'il juge que cet appel représente une opportunité commerciale, il peut créer une fiche de contact ou une fiche de contact et d'affaire en deux clics pendant la conversation. Cette méthode est préférable à la génération automatique de fiches car vous pourriez recevoir des appels non seulement de clients potentiels, mais aussi de diverses autres entreprises. Cette approche aide à éviter la création de fiches de contact et d'affaire inutiles.", + "components": { + "configuring_scenarios_header_controls": { + "cancel": "Annuler", + "save_scenarios": "Enregistrer les scénarios", + "failed_to_reach": "Inaccessible" + }, + "entity_scenario_radio_group": { + "automatically_create": "Créer automatiquement", + "contact_or_company": "Contact ou Entreprise", + "deal": "Affaire", + "select_contact_or_company_first": "Sélectionner d'abord le contact ou l'entreprise", + "deal_pipeline": "Pipeline de vente", + "select_deal_first": "Sélectionner d'abord l'affaire", + "placeholders": { + "select_deal": "Sélectionner l'affaire", + "select_pipeline": "Sélectionner le pipeline" + } + }, + "incoming_calls_block": { + "incoming_calls": "Appels entrants" + }, + "incoming_known_missing_scenario_block": { + "missed_from_known_number": "Appel manqué d'un numéro connu" + }, + "incoming_unknown_missing_scenario_block": { + "missed_call_from_unknown_number": "Appel manqué d'un numéro inconnu", + "auto_create": "Créer automatiquement", + "contact_or_company": "Contact ou Entreprise", + "responsible": "Responsable", + "select_contact_or_company_first": "Sélectionner d'abord le contact ou l'entreprise", + "deal_pipeline": "Pipeline de vente", + "select_deal_first": "Sélectionner d'abord l'affaire", + "placeholders": { + "select_responsible": "Sélectionner le responsable", + "select_deal": "Sélectionner l'affaire", + "select_pipeline": "Sélectionner le pipeline" + } + }, + "incoming_unknown_scenario_block": { + "call_from_unknown_number": "D'un numéro inconnu" + }, + "task_and_activities_scenario_group": { + "task": "Tâche", + "select_contact_or_company_first": "Sélectionner d'abord le contact ou l'entreprise", + "title": "Créer automatiquement une tâche ou une activité", + "do_not_create": "Ne pas créer", + "activity": "Activité", + "activity_type": "Type d'activité", + "complete": "Compléter dans", + "description": "Description", + "task_title": "Titre de la tâche", + "minutes": "minutes", + "placeholders": { + "activity_description": "Description de l'activité", + "task_description": "Description de la tâche", + "title": "Titre" + } + }, + "outgoing_calls_block": { + "outgoing_calls": "Appels sortants", + "failed_to_reach": "Inaccessible" + }, + "outgoing_unanswered_scenario_block": { + "failed_to_reach": "Inaccessible", + "unanswered_outgoing_calls": "Appels sortants sans réponse", + "create_note": "Créer une note dans l'historique du contact/affaire", + "placeholders": { + "note_content": "Contenu de la note" + } + }, + "outgoing_unknown_scenario_block": { + "call_to_unknown_number": "Vers un numéro inconnu", + "creates_manually": "L'utilisateur crée manuellement une fiche de contact et d'affaire lors de l'émission d'un appel sortant" + } + } + }, + "calls_sip_registrations_page": { + "provider": "Fournisseur", + "removed_or_detached": "Supprimé ou détaché", + "removed_or_detached_hint": "Cette inscription SIP n'est plus disponible car elle a été supprimée ou détachée sur la plateforme Voximplant. Pour continuer à utiliser cette inscription SIP, vous devez la rattacher à votre application ou la supprimer et en créer une nouvelle. Contactez le support {{mail}} pour plus d'informations.", + "users": "Utilisateurs", + "users_hint": "Sélectionnez les utilisateurs qui auront accès à cet enregistrement SIP", + "default_name": "Enregistrement SIP #{{number}}", + "name": "Nom de l'enregistrement *", + "title_annotation": "Vous trouverez les instructions d'enregistrement SIP dans la section \"Intégrations\" en cliquant sur le bouton \"Installer\" dans le groupe \"Téléphonie et PBX\".", + "link_to_vx_portal": "Lien vers le Portail Voximplant", + "providers": { + "uis": "UIS", + "zadarma": "Zadarma", + "mango_office": "Mango Office", + "beeline": "Beeline", + "mts": "MTS", + "mgts": "MGTS", + "tele2": "Tele2", + "megafon": "Megafon", + "rostelecom": "Rostelecom", + "unknown": "Inconnu" + }, + "annotation": "Lors de la création d'une inscription SIP depuis votre compte Voximplant, les frais mensuels seront immédiatement facturés. Vérifiez le montant sur le portail Voximplant. La création peut prendre quelques minutes.", + "last_updated": "Dernière mise à jour : {{lastUpdated}}", + "delete_warning": { + "title": "Êtes-vous sûr de vouloir supprimer cette inscription SIP ?", + "annotation": "Cette action est irréversible. Vous pourrez restaurer l'inscription SIP uniquement en en créant une nouvelle." + }, + "registration_successful": "Inscription réussie", + "error_annotation": "Assurez-vous d'avoir saisi les identifiants corrects ou vérifiez votre compte Voximplant pour plus de détails.", + "empty": "Il n'y a pas encore d'enregistrements SIP", + "save_error": "L'inscription n'a pas pu être sauvegardée. Veuillez vous assurer que toutes les données saisies sont correctes.", + "add": "Ajouter", + "title": "Enregistrements SIP", + "edit_sip_registration": "Modifier l'enregistrement SIP", + "add_sip_registration": "Ajouter un enregistrement SIP", + "proxy": "Proxy *", + "sip_user_name": "Nom d'utilisateur SIP *", + "password": "Mot de passe", + "outbound_proxy": "Proxy sortant", + "auth_user": "Utilisateur authentifié", + "auth_user_hint": "Généralement, il s'agit du même nom d'utilisateur.", + "placeholders": { + "all_users": "Tous les utilisateurs", + "name": "Nouvel enregistrement SIP", + "proxy": "sip.provider.org", + "sip_user_name": "user_name", + "password": "********", + "outbound_proxy": "outbound.provider.org", + "auth_user": "auth_user_name" + } + }, + "calls_settings_users_page": { + "empty": "Vous n'avez pas encore ajouté d'utilisateur", + "users": "Utilisateurs", + "add_user": "Ajouter un utilisateur", + "remove_user": "Supprimer l'utilisateur", + "active": "Actif", + "create": "Créer", + "open_sip_settings": "Ouvrir les paramètres SIP", + "continue": "Continuer", + "sip_settings_title": "Paramètres SIP – {{userName}}", + "sensitive_warning": "Il s'agit d'informations sensibles, veillez à ne pas les partager avec des tiers. Survolez le mot de passe pour le révéler.", + "user_name": "Nom de l'utilisateur", + "domain": "Domaine", + "password": "Mot de passe", + "remove_warning_title": "Supprimer {{userName}} ?", + "remove_warning_annotation": "Cette action est irréversible. Vous pourrez restaurer l'utilisateur uniquement en en créant un nouveau.", + "remove": "Supprimer" + }, + "calls_settings_account_page": { + "synchronise_with_vx": "Synchroniser avec Voximplant", + "delete_warning_modal": { + "title": "Êtes-vous sûr de vouloir supprimer le numéro de téléphone {{phoneNumber}} ?", + "annotation": "Cette action est irréversible." + }, + "unknown": "Inconnu", + "region": "Région", + "phone_number": "Numéro de téléphone", + "users": "Utilisateurs", + "state": "État", + "connect_phone_number": "Connecter", + "disconnect_phone_number": "Déconnecter", + "delete_phone_number": "Supprimer", + "phone_numbers": "Numéros de téléphone", + "phone_numbers_annotation1": "Connectez tous les numéros de téléphone Voximplant disponibles à votre compte.", + "phone_numbers_annotation2": "Vous pouvez ajouter des opérateurs actifs dans la colonne des utilisateurs afin qu'ils puissent accéder aux numéros connectés.", + "phone_numbers_annotation3": "ℹ️ Lors de la suppression ou du détachement d'un numéro directement dans l'application Voximplant, n'oubliez pas de le retirer de la liste des numéros ou d'appuyer sur le bouton de synchronisation qui apparaîtra dans ce cas.", + "connect_all_available_phone_numbers": "Connecter tous les numéros disponibles", + "empty_phone_numbers": "Aucun numéro de téléphone disponible ou connecté", + "placeholders": { + "all_users": "Tous les utilisateurs" + }, + "connect_telephony_title": "Connectez la téléphonie et obtenez plus d'opportunités avec {{company}}", + "connect_telephony_warning": "Veuillez noter : En cliquant sur le bouton, un compte Voximplant lié à {{company}} sera automatiquement créé. Ne vous inscrivez pas manuellement, car ce compte ne sera pas associé à {{company}}.", + "connect": "Connecter la téléphonie", + "account": "Compte", + "voximplant_balance": "Solde Voximplant (Bientôt...)", + "recharge": "Recharger", + "subscription_fee": "Frais d'abonnement (Bientôt...)", + "available_numbers": "Numéros disponibles: {{amount}}", + "account_is_not_approved": "Le compte n'est pas approuvé", + "not_approved_annotation": "Sans confirmation de votre compte, vous ne pourrez pas utiliser les numéros de téléphone connectés.", + "approve": "Approuver" + } + }, + "components": { + "telephony_button": { + "telephony": "Téléphonie", + "not_connected": "Connectez-vous à la téléphonie pour activer la fonctionnalité d'appel." + }, + "telephony_modal": { + "connect_to_transfer": "Vous devez d'abord vous connecter à l'appel pour le transférer", + "no_operators_to_transfer_call_to": "Il n'y a aucun opérateur vers qui transférer l'appel", + "coming_soon": "Bientôt disponible...", + "unknown_number": "Numéro inconnu", + "amwork_calls": "Appels {{company}}", + "active_call_control": { + "cancel": "Annuler", + "transfer": "Transférer", + "add_to_call": "Ajouter à l'appel", + "end_call": "Terminer l'appel", + "calling": "appel en cours", + "mute": "Muet", + "unmute": "Réactiver le son" + }, + "incoming_call_control": { + "transfer": "Transférer", + "add_to_call": "Ajouter à l'appel", + "accept": "Accepter", + "incoming_call": "appel entrant", + "mute": "Muet", + "unmute": "Réactiver le son", + "end_call": "Terminer l'appel", + "decline": "Décliner" + }, + "outgoing_call_initializer": { + "outgoing_number": "Votre numéro sortant", + "no_available_phone_numbers": "Aucun numéro de téléphone disponible", + "keys": "Touches", + "recent": "Récents", + "outgoing": "Appel sortant", + "incoming": "Appel entrant", + "yesterday": "Hier", + "today": "Aujourd'hui", + "no_calls": "Aucun appel" + }, + "call_control_template": { + "call_from": "Appel de : {{number}}", + "create": "Créer", + "placeholders": { + "select_card": "Sélectionner la fiche" + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/module.tutorial.json b/frontend/public/locales/fr/module.tutorial.json new file mode 100644 index 0000000..4e04b80 --- /dev/null +++ b/frontend/public/locales/fr/module.tutorial.json @@ -0,0 +1,55 @@ +{ + "tutorial": { + "tutorial_drawer": { + "title": "Base de connaissances", + "empty": "Il n'y a encore rien ici.", + "create_tutorial_group_block": { + "create_group": "Créer un groupe", + "save": "Enregistrer", + "cancel": "Annuler" + }, + "create_tutorial_item_block": { + "create_link": "Créer un lien", + "save": "Enregistrer", + "cancel": "Annuler" + }, + "tutorial_edit_group_item_form": { + "placeholders": { + "name": "Nom", + "link": "Lien", + "all": "Tous" + } + }, + "tutorial_edit_group_items_forms": { + "name": "Nom", + "link": "Lien", + "users": "Utilisateurs", + "products": "Produits", + "products_hint": "Sélectionnez les produits où ce lien sera affiché. Par exemple, si les tâches sont sélectionnées, le lien ne sera affiché que dans la section des tâches." + }, + "tutorial_group_name_block": { + "placeholders": { + "group_name": "Nom du groupe" + } + }, + "hooks": { + "use_get_tutorial_products_options": { + "product_types": { + "builder": "Constructeur", + "task": "Tâches", + "mail": "Mail", + "multi_messenger": "Multi Messenger", + "settings": "Paramètres" + } + }, + "use_get_tutorial_products_groups": { + "groups": { + "entity_type": "Modules", + "products_section": "Produits", + "scheduler": "Calendriers" + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/page.board-settings.json b/frontend/public/locales/fr/page.board-settings.json new file mode 100644 index 0000000..b24e3e8 --- /dev/null +++ b/frontend/public/locales/fr/page.board-settings.json @@ -0,0 +1,33 @@ +{ + "board_settings": { + "ui": { + "board_settings_header": { + "header": "Paramètres du tableau", + "delete_board": "Supprimer le tableau", + "title": "Attention !", + "annotation": "La suppression du tableau entraînera la perte définitive de toutes les cartes. Êtes-vous sûr de vouloir continuer ?", + "save": "Enregistrer", + "cancel": "Annuler", + "leave": "Quitter les paramètres" + }, + "stage_name_hint": "Maximum {{length}} caractères" + }, + "entity_type_board_settings_page": { + "automation_new": "Automatisation 2.0", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Bientôt...)", + "warning_title": "Vous ne pouvez pas supprimer le dernier tableau", + "warning_annotation": "Si vous souhaitez supprimer ce tableau, veuillez en créer un nouveau d'abord." + }, + "task_board_settings_page": { + "warning_title": "Vous ne pouvez pas supprimer le tableau système", + "warning_annotation": "Ce tableau est un tableau système et ne peut pas être supprimé." + }, + "delete_stage_warning_modal": { + "warning_title": "Êtes-vous sûr de vouloir supprimer cette étape ?", + "warning_annotation": "Choisissez une étape vers laquelle vous souhaitez transférer les éléments existants.", + "delete": "Supprimer", + "placeholder": "L'étape pour transférer" + } + } +} diff --git a/frontend/public/locales/fr/page.dashboard.json b/frontend/public/locales/fr/page.dashboard.json new file mode 100644 index 0000000..e311cd2 --- /dev/null +++ b/frontend/public/locales/fr/page.dashboard.json @@ -0,0 +1,18 @@ +{ + "dashboard_page": { + "top_sellers": { + "title": "Classement", + "hint": "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + }, + "analytics": { + "total_entities": "Total", + "won_entities": "Gagné", + "lost_entities": "Perdu", + "new_entities": "Nouveau", + "total_tasks": "Total des tâches", + "completed_tasks": "Tâches terminées", + "expired_tasks": "Tâches expirées", + "no_tasks": "Aucune tâche" + } + } +} diff --git a/frontend/public/locales/fr/page.login.json b/frontend/public/locales/fr/page.login.json new file mode 100644 index 0000000..901cf89 --- /dev/null +++ b/frontend/public/locales/fr/page.login.json @@ -0,0 +1,24 @@ +{ + "login": { + "login_form": { + "title": "Connectez-vous à votre compte", + "login": "Se connecter", + "invalid": "Identifiants invalides", + "or": "Ou", + "caption": "Pas encore de compte ?", + "sign_up": "S'inscrire", + "forgot_your_password": "Mot de passe oublié ?", + "placeholders": { + "login": "E-mail", + "password": "Mot de passe" + }, + "invalid_email_error": "Format d'email invalide", + "invalid_password_error": "Mot de passe requis" + }, + "left_block": { + "title": "Bienvenue", + "annotation": "Du bon travail, avec plaisir !", + "image_alt": "{{company}} constructeur sans code, créez votre propre CRM unique" + } + } +} diff --git a/frontend/public/locales/fr/page.settings.json b/frontend/public/locales/fr/page.settings.json new file mode 100644 index 0000000..44a42c5 --- /dev/null +++ b/frontend/public/locales/fr/page.settings.json @@ -0,0 +1,902 @@ +{ + "settings_page": { + "app_sumo_tiers_data": { + "feature_1": "Produits fonctionnels illimités dans le créateur", + "feature_2": "Pipeline ou tableau illimité", + "feature_3": "Champs personnalisés illimités", + "feature_4": "Automatisation", + "feature_5": "Accès à vie", + "storage": "{{storage}} Go de stockage par utilisateur" + }, + "templates": { + "document_creation_fields_page": { + "ui": { + "system_section": { + "name": "Champs système", + "current_date": "Date actuelle", + "document_number": "Numéro du document" + }, + "entity_type_section": { + "name": "Nom", + "owner": "Propriétaire" + }, + "selector": { + "words": "Mots" + } + } + }, + "document_templates_page": { + "order_fields": { + "order": "Commande", + "order_items": "Articles de commande", + "specific_order_item": "Article de commande", + "order_number": "Numéro de commande", + "order_amount": "Montant de la commande", + "order_currency": "Devise de la commande", + "order_item": { + "number": "Numéro", + "name": "Nom", + "price": "Prix", + "currency": "Devise", + "discount": "Remise", + "tax": "Taxe", + "quantity": "Quantité", + "amount": "Montant" + }, + "text_block_1": "Vous pouvez remplir le document avec des données sur tous les produits et services de la commande sous forme de texte ou de tableau. Vous pouvez également ajouter des données uniquement pour un article de commande spécifique.", + "text_block_2": "Pour remplir le document avec des données sur tous les articles de la commande sous forme de texte, vous devez générer le texte requis dans le document, et pour insérer des données sur les articles de commande, vous devez utiliser les codes spéciaux mentionnés ci-dessus.", + "text_block_3": "IMPORTANT : Au début du bloc de liste des articles de commande, vous devez insérer un code spécial {#order.products}. À la fin du bloc, vous devez insérer un symbole spécial {/}. Le texte du bloc entre les symboles {#order.products} et {/} sera répété dans le document créé autant de fois qu'il y a d'articles dans la commande sélectionnée.", + "text_example": "Exemple de texte", + "text_block_4": "{name} en quantité de {quantity} au prix de {price}", + "text_block_5": "Pour remplir le document avec des données sur tous les articles de commande sous forme de tableau, vous devez créer un tableau dans le modèle. Si nécessaire, les noms des colonnes peuvent être spécifiés dans l'en-tête du tableau. Le tableau, en plus de l'en-tête, doit contenir une ligne avec les champs nécessaires pour le remplissage.", + "text_block_6": "IMPORTANT : La première cellule de la ligne doit contenir le code {#order.products} au tout début de la cellule ; la dernière cellule doit contenir le code {/} à la toute fin de la cellule. Ces codes sont nécessaires pour générer le tableau et ne seront pas affichés dans le document final.", + "table_example": "Exemple de tableau", + "text_block_7": "Pour remplir le document avec des données sur un article de commande spécifique, vous devez utiliser des symboles spéciaux.", + "text_block_8": "Le symbole [0] spécifie quel numéro d'article de commande sera sélectionné pour insérer des données dans le document. Les numéros des articles commencent à 0 pour le premier article de commande." + }, + "no_document_templates": "Vous n'avez pas encore de modèles de documents.", + "can_create_new_there": "Pour ajouter un modèle, cliquez sur le bouton \"Ajouter un modèle de document\".", + "add_document_template": "Ajouter un modèle de document", + "ui": { + "document_template_item": { + "available_in_sections": "Disponible dans les modules", + "creator": "Créateur", + "access_rights": "Droits d'accès", + "warning_title": "Êtes-vous sûr de vouloir supprimer ce modèle", + "warning_annotation": "Cette action est irréversible.", + "placeholders": { + "select_sections": "Sélectionner les modules" + } + } + } + } + }, + "billing_page": { + "trial_in_progress": "Votre essai est en cours !", + "trial_over": "Votre période d'essai est terminée !", + "subscription_over": "Votre abonnement est terminé !", + "request_mywork_billing_form_modal": { + "error": "Une erreur s'est produite lors de l'envoi de votre demande. Veuillez vérifier que toutes les informations sont correctes et réessayez plus tard.", + "send_request": "Soumettre la demande", + "header": "Laissez une demande et nous vous contacterons", + "full_name": "Nom complet *", + "phone": "Téléphone *", + "email": "Email *", + "number_of_users": "Nombre d'utilisateurs", + "comment": "Commentaire", + "placeholders": { + "full_name": "Jean Dupont", + "comment": "Informations supplémentaires" + } + }, + "for_all": "Pour tous les utilisateurs", + "request_billing_modal": { + "send": "Envoyer", + "title": "Plan \"{{planName}}\" – Formulaire de contact", + "name": "Nom *", + "phone": "Téléphone *", + "email": "Email *", + "number_of_users": "Nombre d'utilisateurs *", + "comment": "Commentaire", + "placeholders": { + "additional_info": "Informations supplémentaires" + } + }, + "request_button": "Laisser une demande", + "feedback_annotation": "Vous avez sélectionné le plan {{planName}}, veuillez laisser une demande et nous vous contacterons bientôt.", + "free_trial_duration": "Essai gratuit – 14 jours", + "started_in": "Commencé le :", + "expires_in": "Expire le :", + "users_limit": "Limite d'utilisateurs :", + "renew": "Renouveler l'abonnement", + "upgrade": "Mettre à niveau", + "open_stripe_portal": "Ouvrir le portail Stripe", + "manage_your_subscription": "💸 Gérer votre abonnement sur le portail client Stripe", + "number_of_users": "Nombre d'utilisateurs", + "finish_order": "Terminer la commande", + "save_percentage": "Économisez {{percentage}}", + "per_user_month": "par utilisateur / mois", + "select_this_plan": "Sélectionner le plan", + "selected": "Sélectionné", + "1_month": "1 mois", + "12_month": "12 mois", + "save_up_to": "Économisez jusqu'à {{percent}}%", + "total": "Total : {{amount}}", + "payment_success": "Paiement réussi ! Vous recevrez une confirmation par email.", + "set_free_plan_warning_modal": { + "title": "Important !", + "annotation": { + "text1": "La limite d'utilisateurs de votre plan d'abonnement a été dépassée. Les comptes supplémentaires seront automatiquement supprimés conformément aux termes du plan. Vous pouvez supprimer manuellement les comptes nécessaires", + "link": "ici", + "text2": "Les autres données de votre système resteront inchangées." + }, + "approve": "Confirmer", + "cancel": "Annuler" + }, + "lifetime_promo_plans": { + "discount": "{{percent}}% de réduction", + "lifetime_subscription": "sur l'abonnement à vie", + "black_text": "", + "until": "jusqu'au", + "business_plan": "Starter", + "business_plan_description": "Tout le nécessaire pour gérer vos ventes et projets avec assurance.", + "advanced_plan": "Business", + "advanced_plan_description": "Fonctionnalités complètes avec automatisation et options avancées.", + "price_rise_counter": { + "months": ["mois", "mois", "mois"], + "days": ["jour", "jours", "jours"], + "hours": ["heure", "heures", "heures"], + "minutes": ["minute", "minutes", "minutes"], + "sale_ends": "L'offre se termine dans :" + }, + "lifetime_deal_plan_block": { + "lifetime_subscription": "Abonnement à vie" + }, + "annual_deal_pricing_block": { + "annual_subscription": "1 an d'abonnement" + }, + "monthly_deal_pricing_block": { + "monthly_subscription": "1 mois d'abonnement", + "number_of_users": "Nombre d'utilisateurs", + "per_user": "/mois par utilisateur", + "minimum_period": "période minimale d'abonnement : 6 mois", + "buy": "Acheter" + }, + "pricing_block_with_users_selector": { + "users": ["utilisateur", "utilisateurs", "utilisateurs"], + "lifetime_postfix": " pour toujours", + "packages": "Forfaits utilisateurs", + "per_user": "/mois par utilisateur", + "price_description": "utilisateur / mois, facturation annuelle", + "fix_price": "Prix fixe", + "big_savings": "Économies exceptionnelles", + "savings": "Économies : ", + "buy": "Acheter" + } + } + }, + "edit_groups_page": { + "add_new_group": "Ajouter un nouveau groupe", + "subgroup": "Sous-groupe", + "delete_warning_title": "Êtes-vous sûr de vouloir supprimer {{name}} ?", + "delete_warning_annotation1": "Tout le personnel de ce groupe", + "delete_warning_annotation2": "(et ses sous-groupes s'il y en a)", + "delete_warning_annotation3": "peuvent être conditionnellement assignés à un autre groupe ou sous-groupe.", + "add_subgroup": "Ajouter un sous-groupe", + "save": "Enregistrer", + "cancel": "Annuler", + "delete": "Supprimer", + "working_time": "Heures de travail", + "to": "à", + "placeholders": { + "group": "Entrez le nom", + "select_group": "Sélectionner le groupe", + "select_subgroup": "Sélectionner le sous-groupe", + "new_subgroup": "Nouveau sous-groupe" + } + }, + "edit_user_page": { + "save": "Enregistrer", + "save_and_add": "Enregistrer et ajouter", + "first_name": "Prénom *", + "first_name_hint": "Maximum {{length}} caractères", + "last_name": "Nom *", + "last_name_hint": "Maximum {{length}} caractères", + "email": "Email *", + "phone_number": "Téléphone", + "password": "Mot de passe *", + "group": "Groupe", + "owner": "Propriétaire", + "owner_hint": "Le propriétaire est le super administrateur et le propriétaire du compte. Le propriétaire ne peut pas être supprimé, vous ne pouvez pas retirer ses droits ou lui interdire quoi que ce soit. Un seul utilisateur peut être le propriétaire d'un compte.", + "admin": "Administrateur", + "admin_hint": "L'administrateur a des droits illimités pour gérer et configurer le compte.", + "email_error": "Cet email est déjà lié à un autre compte {{company}}. Pour créer l'utilisateur, veuillez choisir une autre adresse email.", + "unknown_error": "Une erreur inconnue s'est produite lors de la modification de l'utilisateur. Veuillez vérifier que les champs sont remplis correctement et réessayer.", + "position": "Position", + "visible_users": "Utilisateurs visibles", + "visible_users_hint": "Les utilisateurs que l'utilisateur actuel pourra sélectionner dans la liste", + "working_time": "Heures de travail", + "to": "à", + "group_working_time": "Heures de travail du groupe", + "group_working_time_hint": "L'utilisateur héritera des heures de travail du groupe auquel il appartient. Si le groupe n'a pas d'heures configurées, celles de l'entreprise seront utilisées.", + "user_calendar_title": "Horaires de réservation en ligne", + "user_calendar_hint": "Ces horaires définissent les créneaux disponibles pour les réservations en ligne. S'ils ne sont pas définis, le calendrier sera utilisé pour déterminer la disponibilité.", + "delete_user_calendar": "Supprimer", + "add_user_calendar": "Créer un horaire", + "time_buffer_before": "Pause avant le rendez-vous", + "time_buffer_after": "Pause après le rendez-vous", + "appointment_limit": "Limite de rendez-vous par jour", + "schedule": "Horaires", + "no_duration": "Pas", + "minutes": "minutes", + "placeholders": { + "all_users": "Tous les utilisateurs", + "manager": "Directeur", + "password": "Créer un mot de passe", + "new_password": "Nouveau mot de passe", + "group": "Sélectionner le groupe" + }, + "ui": { + "menu_accesses_item": { + "title": "Accès au menu", + "denied": "Refusé", + "responsible": "Responsable", + "allowed": "Autorisé" + }, + "object_permissions_list": { + "tasks": "Tâches", + "activities": "Activités" + }, + "object_permissions_item": { + "hint": "Vous pouvez configurer les autorisations pour un utilisateur. Vous pouvez configurer les autorisations de l'utilisateur pour créer, voir, modifier et supprimer des objets dans la section {{title}}.", + "create": "Créer", + "view": "Voir", + "edit": "Modifier", + "delete": "Supprimer", + "report": "Rapports", + "dashboard": "Tableau de bord", + "denied": "Refusé", + "responsible": "Responsable", + "subdepartment": "Sous-groupe", + "department": "Groupe", + "allowed": "Autorisé" + }, + "products_permissions_item": { + "warehouses_title": "Accès aux Entrepôts", + "warehouses_hint": "Sélectionnez les entrepôts auxquels l'utilisateur a accès pour créer des commandes, consulter les produits et les expéditions.", + "hint": "Vous pouvez configurer les autorisations pour un utilisateur.", + "create_product": "Créer un produit", + "view_product": "Voir le produit", + "edit_product": "Modifier le produit", + "create_order": "Créer une commande", + "shipment": "Expédition", + "delete": "Supprimer", + "denied": "Refusé", + "allowed": "Autorisé", + "placeholders": { + "all_warehouses": "Tous les entrepôts" + } + } + } + }, + "general_settings_page": { + "enable": "Activer", + "contact_duplicates_hint": "Activez cette option pour avertir les utilisateurs lors de la création d’un contact ou d’une entreprise avec un numéro de téléphone ou une adresse e-mail existants.", + "contact_duplicates": "Avertir des doublons de contacts", + "date_format": "Format de date", + "auto": "Automatiquement", + "phone_format": "Format de téléphone", + "international": "International", + "free": "Gratuit", + "company": "Entreprise", + "domain": "Nom de domaine", + "upload_logo": "Télécharger un nouveau logo", + "delete_logo": "Supprimer le logo", + "logo_caption": "La taille préférée pour le logo est de 111px par 22px", + "language": "Langue", + "time_zone": "Fuseau horaire", + "working_days": "Jours ouvrables", + "start_of_week": "Début de semaine", + "Monday": "Lundi", + "Tuesday": "Mardi", + "Wednesday": "Mercredi", + "Thursday": "Jeudi", + "Friday": "Vendredi", + "Saturday": "Samedi", + "Sunday": "Dimanche", + "currency": "Devise", + "working_time": "Heures de travail", + "to": "à", + "number_format": "Format des numéros", + "currency_select": "Sélection de la devise", + "currencies": { + "USD": "Dollar américain, USD", + "EUR": "Euro, EUR", + "GBP": "Livre sterling, GBP", + "JPY": "Yen japonais, JPY", + "CNY": "Yuan chinois, CNY", + "INR": "Roupie indienne, INR", + "RUB": "Rouble russe, RUB", + "MXN": "Peso mexicain, MXN", + "BRL": "Réal brésilien, BRL", + "ZAR": "Rand sud-africain, ZAR", + "AUD": "Dollar australien, AUD", + "CAD": "Dollar canadien, CAD", + "AED": "Dirham des Émirats arabes unis, AED", + "CHF": "Franc suisse, CHF", + "TRY": "Livre turque, TRY", + "UAH": "Hryvnia ukrainienne, UAH", + "KRW": "Won sud-coréen, KRW", + "NZD": "Dollar néo-zélandais, NZD", + "NOK": "Couronne norvégienne, NOK", + "SEK": "Couronne suédoise, SEK", + "DKK": "Couronne danoise, DKK", + "PLN": "Zloty polonais, PLN", + "CZK": "Couronne tchèque, CZK", + "HUF": "Forint hongrois, HUF", + "IDR": "Roupie indonésienne, IDR", + "ILS": "Nouveau shekel israélien, ILS", + "MYR": "Ringgit malaisien, MYR", + "PHP": "Peso philippin, PHP", + "SGD": "Dollar de Singapour, SGD", + "THB": "Baht thaïlandais, THB", + "KZT": "Tenge kazakh, KZT", + "CLP": "Peso chilien, CLP", + "CRC": "Colón costaricien, CRC", + "COP": "Peso colombien, COP", + "BOB": "Boliviano, BOB", + "HKD": "Dollar de Hong Kong, HKD", + "SAR": "Rial saoudien, SAR", + "VND": "Dong vietnamien, VND", + "EGP": "Livre égyptienne, EGP", + "KWD": "Dinar koweïtien, KWD", + "PKR": "Roupie pakistanaise, PKR", + "LKR": "Roupie de Sri Lanka, LKR", + "BDT": "Taka bangladaise, BDT", + "NGN": "Naira nigérian, NGN", + "GHS": "Cedi ghanéen, GHS", + "TWD": "Nouveau dollar taïwanais, TWD", + "MAD": "Dirham marocain, MAD", + "ARS": "Peso argentin, ARS", + "PEN": "Sol nouveau péruvien, PEN", + "UYU": "Peso uruguayen, UYU", + "BGN": "Lev bulgare, BGN", + "RON": "Leu roumain, RON", + "LBP": "Livre libanaise, LBP" + } + }, + "integrations_page": { + "calendars_and_tasks": "Calendriers et tâches", + "telephony_integration_guide_international": { + "modal_title": "Intégration de la téléphonie Voximplant", + "continue": "Continuer", + "title": "Instructions pour intégrer la téléphonie Voximplant", + "step1": { + "title": "Étape 1 – Création d'un compte", + "item1": "Pour connecter l'intégration téléphonique Voximplant, allez dans \"Paramètres\" → \"Appels\" → onglet \"Compte\" et cliquez sur le bouton \"Connecter la téléphonie\".", + "item2": "Les informations sur votre compte Voximplant créé apparaîtront sur la page. Pour terminer l'intégration, vous devrez confirmer le compte en cliquant sur le lien \"Approuver\" en haut de l'écran." + }, + "step2": { + "title": "Étape 2 – Rechargement du solde", + "annotation": "Une fois que vous avez ouvert le portail Voximplant, vous devrez ajouter des fonds à votre compte. Il est recommandé d'ajouter au moins 10 $. Cela activera votre compte et les fonds seront utilisés pour acheter des numéros de téléphone et facturer les minutes d'appel.", + "operator_site_and_billing": "Site de l'opérateur et facturation" + }, + "step3": { + "title": "Étape 3 – Achat de numéros et démarrage", + "annotation1": "Après avoir activé votre compte, vous devez fournir au support {{companyName}} ({{mail}}) les informations suivantes afin que nous puissions initialiser votre téléphonie et la préparer à fonctionner :", + "item1": "La ville et le pays pour la sélection du numéro", + "item2": "Heures de travail : quels jours de la semaine et heures sont considérés comme heures de travail, votre fuseau horaire", + "item3": "Texte d'accueil au début de l'appel pendant les heures de travail", + "item4": "Texte du message pour les appels en dehors des heures de travail", + "item5": "Option de voix pour le message – homme ou femme", + "item6": "La quantité requise de numéros de téléphone", + "annotation2": "Après avoir reçu ces informations, nous traiterons votre demande dès que possible." + }, + "step4": { + "title": "Étape 4 – Connexion des numéros dans {{companyName}}", + "annotation1": "Une fois que vous avez activé le compte et que nous avons acheté les numéros souhaités pour vous, vous les verrez dans l'onglet \"Paramètres\" → \"Appels\" → \"Compte\". Pour commencer à travailler, vous devez cliquer sur le bouton \"Connecter\" à côté des numéros souhaités et sélectionner les utilisateurs qui auront accès à ces numéros.", + "annotation2": "Terminé ! Votre téléphonie est configurée et prête à l'emploi." + }, + "step5": { + "title": "Étape 5 – Paramètres et informations supplémentaires", + "annotation1": "Dans l'onglet \"Paramètres\" → \"Appels\" → \"Utilisateurs\", vous pouvez connecter et déconnecter les utilisateurs du système à la téléphonie, et afficher les données SIP. Dans l'onglet \"Configuration\", vous pouvez configurer les scénarios d'intégration de la téléphonie avec le module CRM. Dans l'onglet \"Enregistrements SIP\", vous pouvez ajouter et gérer les enregistrements SIP de PBX/VPBX.", + "annotation2": "Veuillez noter qu'avec une utilisation active de la téléphonie, il est recommandé de rafraîchir périodiquement l'onglet du navigateur, car les onglets inactifs/périmés (ouverts il y a plus de 3 jours sans interaction) peuvent vous faire manquer certains appels.", + "annotation3": "De plus, vous devez accorder au navigateur l'accès au microphone pour effectuer des appels sortants.", + "grant_chrome_access": "Accorder l'accès dans Google Chrome", + "grant_mozilla_access": "Accorder l'accès dans Mozilla Firefox", + "grant_safari_access": "Accorder l'accès dans Safari", + "annotation4": "En général, le navigateur demandera automatiquement les autorisations nécessaires lorsque vous essayez de passer un appel sortant." + } + }, + "providers_sip_registration_items_list": { + "sip_registration_guide_modal": { + "modal_title": "Guide de connexion de l'enregistrement SIP", + "continue": "Continuer", + "step1": { + "title": "Étape 1 – Rassembler les informations nécessaires", + "annotation1": "Avant de commencer à connecter l'enregistrement SIP, assurez-vous que votre compte dispose déjà d'une intégration téléphonique Voximplant active. Si elle n'est pas encore connectée, suivez les instructions ci-dessous :", + "annotation2": "Pour connecter l'enregistrement SIP, vous devez connaître les informations SIP suivantes concernant votre PBX/VPBX :", + "item1": "Proxy *", + "item2": "Nom d'utilisateur SIP *", + "item3": "Mot de passe", + "item4": "Proxy sortant", + "item5": "Utilisateur d'authentification", + "annotation3": "Ces informations se trouvent dans votre compte personnel VPBX ou peuvent être demandées directement à l'opérateur." + }, + "step2": { + "title": "Étape 2 – Connecter l'enregistrement", + "item1": "Cliquez sur le bouton \"Continuer\" en bas de cette fenêtre ou allez dans l'onglet \"Paramètres\" → \"Appels\" → \"Enregistrements SIP\" et cliquez sur le bouton \"Ajouter un enregistrement SIP\".", + "item2": "Entrez toutes les données nécessaires et cliquez sur \"Ajouter\".", + "item3": "Notez que dès la création de l'enregistrement, un petit montant sera débité de votre compte Voximplant. Cela sera prélevé chaque mois pour l'utilisation de l'enregistrement. Vous trouverez des informations détaillées dans le lien ci-dessous dans l'onglet \"Tarifs\" → \"Fonctionnalités\" → \"Enregistrement SIP\". Le lien apparaîtra uniquement si vous avez déjà un compte Voximplant.", + "voximplant_billing_rates": "Tarifs des services Voximplant", + "item4": "Après une connexion réussie, vous verrez votre intégration. Elle peut être modifiée simplement en cliquant dessus. Il n'y a pas de frais supplémentaires pour la modification.", + "item5": "Pour supprimer un enregistrement SIP, cliquez sur le bouton \"Corbeille\" à la fin du bloc d'enregistrement SIP dans l'onglet \"Enregistrement SIP\".", + "annotation": "Pour d'autres problèmes liés aux enregistrements SIP, contactez-nous à {{mail}}." + } + }, + "another_pbx": "Connectez un autre PBX/VPBX", + "beeline": "Connectez le PBX/VPBX de Beeline", + "mts": "Connectez le PBX/VPBX de MTS", + "mgts": "Connectez le PBX de MGTS", + "tele2": "Connectez le PBX Corporate de Tele2", + "megafon": "Connectez le PBX/VPBX de MegaFon", + "rostelecom": "Connectez le PBX de Bureau/VPBX de Rostelecom", + "mango_office": "Connectez le VPBX de Mango Office", + "uis": "Connectez le VPBX de UIS", + "zadarma": "Connectez le VPBX de Zadarma" + }, + "telephony_and_pbx": "Téléphonie et PBX", + "integrations": "Intégrations", + "crm": "CRM", + "process_automation": "Automatisation des processus", + "site_forms": "Formulaires Web", + "messenger": "Réseaux sociaux et messageries", + "continue": "Continuer", + "save": "Enregistrer", + "manage_connected_accounts": "Gérer les comptes connectés", + "ui": { + "google_calendar": { + "google_calendar_manage_modal": { + "title": "Intégrations de Google Calendar de {{company}}" + }, + "google_calendar_connect_modal": { + "do_not_select": "Ne pas sélectionner", + "integration_name_readonly_hint": "Vous ne pouvez pas modifier le nom de cette intégration une fois qu'elle est créée.", + "save": "Enregistrer", + "connect_calendar": "Connecter le calendrier", + "finish_integration_annotation": "Terminez la configuration de l'intégration en remplissant tous les champs requis.", + "new_integration": "Nouvelle intégration #{{number}}", + "integration_name": "Nom de l'intégration *", + "calendar": "Google Calendrier *", + "calendar_hint": "Sélectionnez un calendrier spécifique de votre compte Google Agenda", + "task_board": "Tableau des tâches *", + "schedule": "Planificateur {{company}} *", + "linked_task_boards_annotation": "Vous pouvez ajouter des tableaux de tâches supplémentaires à synchroniser avec le calendrier sélectionné. Les tâches de ces tableaux seront synchronisées avec Google Agenda.", + "linked_schedules_annotation": "Vous pouvez ajouter des calendriers supplémentaires à synchroniser avec le calendrier sélectionné. Les visites de ces calendriers seront synchronisées avec Google Agenda.", + "select_all": "Tout sélectionner", + "sync_events": "Synchroniser les événements à partir d'aujourd'hui", + "additional_task_boards": "Tableaux des tâches supplémentaires", + "additional_schedules": "Calendriers supplémentaires", + "responsible_user": "Utilisateur responsable *", + "error_message": "Échec du traitement du code Google Agenda !", + "title": "Connexion à Google Agenda", + "feature": "Vous pourrez effectuer une synchronisation bidirectionnelle de vos tâches et visites avec Google Agenda", + "annotation": "Autorisez l'accès à votre compte Google Agenda.", + "placeholders": { + "integration_name": "Intégration Google Agenda", + "schedules": "Sélectionnez des plannings", + "task_boards": "Sélectionnez des tableaux des tâches" + } + }, + "google_calendar_modal_template": { + "title": "Intégration de Google Calendar de {{company}}" + }, + "google_calendar_item": { + "description": "Synchroniser vos réunions et tâches" + } + }, + "common": { + "form": { + "account_activity": "Activité du compte", + "account_activity_annotation": "Vous pouvez activer ou suspendre votre compte", + "available": "Sélectionnez pour qui le messager est disponible *", + "all": "Tous", + "users": "Utilisateurs", + "on": "ON", + "no_create": "Ne pas créer", + "messenger_leads_title": "Configurer la création de fiches", + "messenger_leads_annotation": "Définissez les paramètres pour créer automatiquement une fiche lors d’un message entrant d’un nouveau contact.", + "create_entities": "Créer une fiche à la réception d’un message", + "create_contact": "Module pour contact/entreprise", + "create_lead": "Module pour opportunité", + "lead_stage": "Statut de l’opportunité", + "lead_name": "Nom de l’opportunité", + "lead_owner": "Responsable de l’opportunité *", + "check_duplicates": "Éviter les doublons", + "check_active_lead": "Créer une nouvelle si l’actuelle est inactive", + "placeholders": { + "select_module": "Sélectionnez un module", + "select_users": "Sélectionnez des utilisateurs", + "select_user": "Sélectionnez un utilisateur", + "lead_name": "Saisissez un nom" + } + }, + "modals": { + "changes_not_saved_warning_modal": { + "title": "Êtes-vous sûr de vouloir fermer cette fenêtre ?", + "annotation": "Toutes les modifications seront perdues.", + "close": "Fermer" + }, + "integration_account_item": { + "delete_warning_title": "Êtes-vous sûr de vouloir supprimer le compte {{title}} ?", + "delete_warning_annotation": "Tous les chats et messages liés à ce canal de communication seront supprimés. Cette action est irréversible.", + "active": "Actif", + "active_hint": "Votre compte est opérationnel. Vous pouvez le suspendre dans les paramètres du fournisseur en cliquant sur l'icône du crayon.", + "inactive": "Inactif", + "inactive_hint": "Votre compte est suspendu. Vous pouvez l'activer dans les paramètres du fournisseur en cliquant sur l'icône du crayon.", + "deleted": "Supprimé", + "deleted_hint": "Votre compte est supprimé.", + "draft": "Brouillon", + "draft_hint": "Votre compte n'est pas encore connecté." + }, + "integration_accounts_list": { + "add_account": "Ajouter un compte" + } + } + }, + "integration_item": { + "install": "Installer", + "manage": "Gérer" + }, + "messages_per_day_select": { + "label": "Limite quotidienne des messages envoyés par automatisation *", + "annotation": "messages par jour", + "hint": "Nombre maximum de messages pouvant être envoyés par jour via ce compte. Cette limite est nécessaire pour éviter que le compte ne soit bloqué par le messager en raison d'envois massifs." + }, + "salesforce": { + "salesforce_item": { + "description": "Un espace de travail pour chaque équipe" + }, + "salesforce_modal": { + "title": "Intégration avec Salesforce", + "connected": "Connecté", + "not_connected": "Non connecté", + "disconnect": "Déconnecter", + "add_integration": "Ajouter une intégration", + "my_domain_name": "Mon nom de domaine", + "app_key": "Clé consommateur de l'application", + "app_secret": "Secret consommateur de l'application", + "connect": "Connecter", + "caption": "Continuez à utiliser Salesforce pour les clients, et utilisez {{company}} pour gérer le reste de vos processus commerciaux. Gérez les projets dans {{company}}. Gérez les contractants et les fournisseurs dans {{company}}." + } + }, + "fb_messenger": { + "fb_messenger_manage_modal": { + "title": "Intégration de l'API Facebook Messenger" + }, + "fb_messenger_item": { + "title": "Gérer les interactions Facebook, convertir les chats en leads" + }, + "fb_messenger_modal_template": { + "title": "Intégration Facebook Messenger" + }, + "fb_messenger_first_info_modal": { + "title": "Obtenez un compte Facebook Messenger Business officiel pour commencer à parler à vos clients en utilisant le {{company}} Multimessenger.", + "feature1": "Gérez toutes les conversations Facebook Messenger avec d'autres canaux en un seul endroit", + "feature2": "Collaborez avec les membres de votre équipe sur les conversations entrantes", + "feature3": "Créez facilement de nouveaux contacts, leads et affaires directement à partir des conversations", + "feature4": "Utilisez les modèles de messages Facebook Messenger pour envoyer des notifications pertinentes et opportunes", + "learn_more": "En savoir plus sur la gestion de votre intégration Facebook Messenger" + }, + "fb_messenger_finish_modal": { + "supervisors": "Sélectionnez les utilisateurs qui ont accès à tous les chats clients", + "supervisors_hint": "Sélectionnez les utilisateurs qui auront accès à tous les chats des employés avec les clients. Il peut s'agir de managers ou d'employés travaillant avec les clients.", + "save": "Enregistrer", + "title": "Vous avez déjà connecté votre compte Facebook à {{company}}", + "subtitle": "Terminez la configuration de votre compte. Modifiez le nom et les paramètres d'accessibilité si nécessaire.", + "name": "Nom *", + "responsible_users": "Utilisateurs responsables pour les nouveaux leads *", + "learn_more": "En savoir plus sur la gestion de votre intégration Facebook Messenger", + "placeholders": { + "name": "Nom du produit", + "select_users": "Sélectionner les utilisateurs" + } + } + }, + "wazzup": { + "wazzup_manage_modal": { + "title": "Intégration Wazzup {{company}}" + }, + "wazzup_item": { + "title": "Intégration Wazzup en 5 minutes, WhatsApp & Telegram" + }, + "wazzup_modal_template": { + "title": "Intégration Wazzup {{company}}" + }, + "wazzup_first_info_modal": { + "title": "Contactez directement vos clients via {{company}} sur les réseaux sociaux et les plateformes de messagerie.", + "feature1": "Les membres de l'équipe commerciale peuvent envoyer des messages via WhatsApp, Telegram en utilisant {{company}}", + "feature2": "Les managers peuvent uniquement voir leurs conversations, tandis que les superviseurs ont accès à tous les chats", + "feature3": "Les contacts et affaires avec de nouveaux clients sont générés automatiquement", + "feature4": "Toutes les communications sont stockées en toute sécurité dans {{company}}", + "feature5": "Utilisez un numéro unique pour l'ensemble du département des ventes et configurez des réponses automatiques", + "annotation": "Restez connecté avec les clients où que vous soyez. Avec l'application mobile Wazzup, vous pouvez contacter les clients en déplacement, et toutes vos conversations seront enregistrées dans {{company}} et accessibles depuis votre ordinateur.", + "learn_more": "En savoir plus sur Wazzup" + }, + "wazzup_connect_modal": { + "save": "Enregistrer", + "update": "Mettre à jour", + "title": "Autorisez votre compte Wazzup", + "subtitle": "Accédez aux paramètres de votre compte Wazzup et copiez les informations d'identification de l'API.", + "name": "Nom *", + "api_key": "Clé API *", + "channel": "Canal Wazzup *", + "responsible_users": "Utilisateurs responsables pour les nouveaux leads *", + "supervisors": "Sélectionnez les utilisateurs qui ont accès à tous les chats clients", + "supervisors_hint": "Sélectionnez les utilisateurs qui auront accès à tous les chats des employés avec les clients. Il peut s'agir de managers ou d'employés travaillant avec les clients.", + "placeholders": { + "api_key": "Clé API", + "name": "Nom du produit", + "select_users": "Sélectionner les utilisateurs", + "channel": "Sélectionner le canal Wazzup" + } + } + }, + "whatsapp": { + "whatsapp_manage_modal": { + "title": "Intégration de l'API WhatsApp Business par Twilio" + }, + "whatsapp_item": { + "title": "Messages directs WhatsApp Business via {{company}}" + }, + "whatsapp_modal_template": { + "title": "Intégration de l'API WhatsApp Business par Twilio" + }, + "whatsapp_first_info_modal": { + "title": "Obtenez un compte officiel WhatsApp Business (fourni par Twilio) pour commencer à parler à vos clients en utilisant le {{company}} Multimessenger", + "feature1": "Gérez toutes les conversations WhatsApp avec d'autres canaux en un seul endroit", + "feature2": "Collaborez avec les membres de votre équipe sur les conversations entrantes", + "feature3": "Créez facilement de nouveaux contacts, leads et affaires directement à partir des conversations", + "feature4": "Utilisez les modèles de messages WhatsApp pour envoyer des notifications pertinentes et opportunes", + "learn_more": "En savoir plus sur l'API WhatsApp Business par Twilio" + }, + "whatsapp_second_info_modal": { + "title": "Points à garder à l'esprit avant de continuer", + "step1": "Vous devez acheter un nouveau numéro de téléphone dans Twilio (ou transférer votre numéro de téléphone actuel depuis l'application mobile WhatsApp ou l'application WhatsApp Business) pour créer un compte API WhatsApp Business.", + "link1": "Comment transférer un numéro WhatsApp approuvé vers Twilio", + "step2": "WhatsApp et Twilio peuvent vous facturer des frais supplémentaires pour l'utilisation de l'API WhatsApp Business.", + "link2": "En savoir plus sur les tarifs" + }, + "whatsapp_third_info_modal": { + "title": "Configurer le compte Twilio avec votre numéro WhatsApp", + "subtitle": "Twilio doit approuver votre numéro WhatsApp avant qu'il puisse être utilisé dans {{company}}. Suivez ces étapes pour éviter tout retard inutile.", + "step1": "Configurer avec", + "step2": "Demandez l'accès à Twilio pour activer les numéros de téléphone pour WhatsApp :", + "step2_1_1": "Assurez-vous d'avoir le numéro de téléphone fourni par Twilio prêt à obtenir le numéro de WhatsApp. Remplissez le", + "step2_1_2": "formulaire de demande d'accès Twilio", + "step2_1_3": "avec vos informations à jour, y compris votre ID de gestionnaire commercial Facebook.", + "step2_2_1": "Consultez la", + "step2_2_2": "documentation", + "step2_2_3": "de Twilio pour plus d'informations.", + "step3": "Soumettez une demande d'expéditeur WhatsApp dans la console Twilio.", + "step4_1": "Une fois que vous avez soumis le formulaire de demande d'accès, vous recevrez un email de pré-approbation de Twilio. Consultez ce", + "step4_2": "lien de référence", + "step4_3": "pour les étapes suivantes.", + "step5": "Autorisez Twilio à envoyer un message en votre nom dans la console de gestion Facebook Business Manager." + }, + "whatsapp_connect_modal": { + "supervisors": "Sélectionnez les utilisateurs qui ont accès à tous les chats clients", + "supervisors_hint": "Sélectionnez les utilisateurs qui auront accès à tous les chats des employés avec les clients. Il peut s'agir de managers ou d'employés travaillant avec les clients.", + "save": "Enregistrer", + "update": "Mettre à jour", + "title": "Autorisez votre compte WhatsApp par Twilio", + "subtitle": "Accédez aux paramètres de votre compte Twilio et copiez les informations d'identification de l'API.", + "name": "Nom *", + "sid": "SID du compte *", + "auth_token": "Jeton d'authentification *", + "phone": "Numéro WhatsApp autorisé *", + "responsible_users": "Utilisateurs responsables pour les nouveaux leads *", + "learn_more": "En savoir plus sur la configuration de votre compte WhatsApp avec Twilio", + "placeholders": { + "name": "Nom du produit", + "code": "Code produit", + "token": "Jeton", + "number": "Numéro", + "select_users": "Sélectionner les utilisateurs" + } + } + }, + "tilda": { + "tilda_item": { + "description": "Intégration des formulaires sur un site avec Tilda" + }, + "tilda_manual_modal": { + "close": "Fermer", + "title": "Intégration des formulaires sur un site avec Tilda", + "info_title": "Intégrez un formulaire du constructeur {{company}} avec un site Tilda pour que les demandes arrivent directement dans le système.", + "step_1_title": "Étape 1 — Préparation du formulaire", + "step_1_description": "Dans le constructeur de formulaires de {{company}}, créez un formulaire avec intégration API. Configurez-le au premier étape et ajoutez les champs nécessaires au second.", + "form_builder_link": "Constructeur de formulaires avec intégration API", + "step_2_title": "Étape 2 — Configuration dans Tilda", + "step_2_item_1": "Ouvrez Paramètres du site → Formulaires → Webhook.", + "step_2_item_2": "Saisissez l’URL du webhook du 3e étape du constructeur comme adresse du script.", + "step_2_item_3": "Enregistrez les modifications.", + "step_3_title": "Étape 3 — Configuration des variables", + "step_3_item_1": "Pour le formulaire à intégrer, sélectionnez Webhook comme service de réception des données.", + "step_3_item_2": "Assignez des variables (identifiants) aux champs en utilisant le tableau du 3e étape du constructeur.", + "step_3_item_3": "Testez le formulaire. Si tout fonctionne, publiez la page.", + "tilda_manual_link": "Guide de configuration pour Tilda", + "support": "Si vous rencontrez des problèmes d'intégration, contactez le support." + } + }, + "wordpress": { + "wordpress_item": { + "description": "Intégration des formulaires sur un site avec Wordpress" + }, + "wordpress_manual_modal": { + "close": "Fermer", + "title": "Intégration des formulaires sur un site avec Wordpress", + "info_title": "Intégrez un formulaire du constructeur {{company}} avec un site Wordpress pour que les demandes arrivent directement dans le système.", + "step_1_title": "Étape 1 — Préparation du formulaire", + "step_1_description": "Créez un formulaire dans le constructeur {{company}}. Configurez les champs nécessaires à l'étape 2 et personnalisez-le à l'étape 4.", + "form_builder_link": "Constructeur de formulaires", + "step_2_title": "Étape 2 — Ajout du conteneur de formulaire", + "step_2_item_1": "Ouvrez le tableau de bord de Wordpress.", + "step_2_item_2": "Si possible, faites une copie de la page avant de la modifier.", + "step_2_item_3": "Si un formulaire existe déjà, supprimez-le et laissez un bloc vide. Sinon, ajoutez un bloc vide où vous souhaitez insérer le formulaire.", + "step_2_item_4": "Dans les paramètres du bloc, attribuez la classe CSS workspace-form-builder-mount-container.", + "step_3_title": "Étape 3 — Ajout du code du formulaire", + "step_3_item_1": "Ajoutez un bloc «HTML personnalisé».", + "step_3_item_2": "Ouvrez le bloc HTML et collez le code depuis l'étape 5 du constructeur.", + "step_3_item_3": "Testez le formulaire et publiez la page si tout fonctionne.", + "wordpress_manual_link": "Guide d'insertion de code HTML Wordpress", + "additional_steps": "Étapes supplémentaires", + "styling": "Personnalisez l'apparence à l'étape 4 du constructeur. Utilisez du CSS personnalisé si nécessaire. Chaque élément a une classe CSS lisible comme .workspace-form__TextInput--Input. Les modifications prennent effet après chaque sauvegarde.", + "multiform": "Vous pouvez publier plusieurs formulaires identiques sur une page. Le bloc HTML personnalisé doit être unique, mais les conteneurs peuvent être multiples. Pour des formulaires différents, activez le «mode multi-formulaires» et insérez un code distinct dans chaque bloc HTML.", + "support": "Si vous rencontrez des problèmes d'intégration, contactez le support." + } + }, + "albato": { + "albato_item": { + "description": "Automatisez des processus complexes avec Albato" + }, + "albato_manual_modal": { + "close": "Fermer", + "header_title": "Guide d'intégration de {{company}} avec Albato", + "integration_title": "Utilisez Albato pour connecter {{company}} à des milliers de services.", + "step_1_title": "Étape 1 : Créer une intégration sur Albato", + "albato_website": "Site d'Albato", + "step_1_part_1": "Créez un compte sur Albato si ce n'est pas encore fait.", + "step_1_part_2": "Créez une nouvelle intégration. Ajoutez une action et, dans le menu des services, recherchez et sélectionnez Mywork.", + "step_1_part_3": "Choisissez une action appropriée dans la liste proposée.", + "step_2_title": "Étape 2 : Connecter votre compte Mywork", + "step_2_part_1": "Ajoutez une nouvelle connexion pour Mywork.", + "step_2_part_2": "Renseignez le champ 'Clé API'. La clé est disponible dans les paramètres de votre compte Mywork, sous 'Accès API'.", + "step_2_part_3": "Complétez les champs 'E-mail' et 'Mot de passe' avec les informations de votre compte Mywork.", + "step_2_part_4": "Remplissez le champ 'Sous-domaine'. Vous pouvez le trouver dans les 'Paramètres généraux' de votre compte Mywork. Indiquez uniquement la partie en surbrillance, sans '.mywork.app'.", + "api_access_link": "Accès API", + "step_3_title": "Étape 3 : Configurer les actions d'automatisation", + "step_3": "Lors de la configuration d'une action, un formulaire avec des champs à remplir apparaît. Ces champs correspondent aux données nécessaires pour exécuter l'action (par exemple, utilisateur responsable, nom de la tâche ou texte). Les données peuvent être remplies dynamiquement à partir des actions précédentes ou du déclencheur. Certaines données peuvent être récupérées depuis un répertoire (par exemple, l'utilisateur responsable), et des données actualisées seront chargées depuis votre compte Mywork. Si les données sont manquantes, cliquez sur 'Mettre à jour la liste'.", + "step_4_title": "Étape 4 : Tester", + "step_4": "Avant d'activer l'intégration, testez-la en cliquant sur le bouton prévu à cet effet. Les données transmises au système sont visibles dans le journal d'intégration. Si vous avez des questions, contactez le support technique Mywork." + } + }, + "request_integration": { + "description": "Demander le développement d'une intégration", + "request": "Demander" + } + } + }, + "users_settings_page": { + "user": "Utilisateur", + "total": "Total", + "create_button_tooltip": "Ajouter un utilisateur", + "ui": { + "remove_user_modal": { + "remove": "Supprimer", + "title": "Êtes-vous sûr de vouloir supprimer cet utilisateur ?", + "annotation": "Transférez la propriété de tous les éléments de cet utilisateur à un autre.", + "placeholder": "Sélectionner le responsable..." + }, + "user_item": { + "remove": "Supprimer", + "user_role": { + "owner": "Propriétaire", + "admin": "Administrateur", + "user": "Utilisateur", + "partner": "Partenaire" + } + } + } + }, + "account_api_access_page": { + "title": "Accès à l'API", + "annotation": "Vous pouvez utiliser une clé API pour créer des intégrations externes avec le système. Conservez cette clé dans un endroit sécurisé et ne la partagez avec personne. Si la clé est compromise, régénérez-la.", + "create_api_key": "Créer une clé API", + "recreate_api_key": "Régénérer", + "warning_title": "Régénération de la clé API", + "warning_annotation": "La clé actuelle sera supprimée et remplacée par une nouvelle. Après la régénération, remplacez la clé dans toutes vos intégrations externes pour assurer leur fonctionnement.", + "created_at": "Créée le {{date}} à {{time}}", + "api_tokens_list": { + "title": "Jetons d'autorisation", + "annotation": "Créez des jetons d'autorisation pour les intégrations externes. Un jeton remplace l’authentification par identifiant et mot de passe. Une fois créé, il ne sera affiché qu’une seule fois. Conservez-le en lieu sûr. S’il est compromis, supprimez-le et générez-en un nouveau.", + "empty_user_tokens": "Aucun jeton d'autorisation créé.", + "add_user_token": "Ajouter un jeton", + "copy": "Copier le jeton", + "access_token": "Conservez ce jeton, vous ne pourrez plus le voir :", + "name": "Nom", + "expires_at": "Date d'expiration", + "table": { + "name": "Nom", + "created_at": "Créé le", + "expires_at": "Expire le", + "last_used_at": "Dernière utilisation", + "never": "Jamais", + "actions": "Actions", + "delete": "Supprimer" + } + } + }, + "settings_page_template": { + "modules_settings": "Paramètres des modules", + "sip_registrations": "Inscriptions SIP", + "title": "Paramètres", + "users": "Utilisateurs", + "general": "Paramètres généraux", + "email": "Paramètres email", + "integrations": "Intégrations", + "billing": "Facturation", + "api_access": "Accès à l'API", + "documents": "Documents", + "calls": "Appels", + "account": "Compte", + "superadmin": "Admin Panel", + "configuring_scenarios": "Configuration", + "schemas": "Schémas", + "configure_users": "Configuration des utilisateurs", + "groups": "Groupes", + "document_templates": "Modèles de documents", + "document_creation_fields": "Champs de création de documents" + }, + "add_user_to_plan_modal": { + "send": "Envoyer", + "title": "Pour ajouter un utilisateur, veuillez nous contacter", + "name": "* Nom", + "phone": "* Téléphone", + "email": "* Email", + "number_of_users": "* Nombre d'utilisateurs", + "placeholder": "Votre nom" + }, + "request_setup_form": { + "button": { + "request_setup": "Demander la configuration", + "request_billing_help": "Demander de l'aide pour l'abonnement", + "request_integration": "Demander le développement de l'intégration", + "request_telephony": "Demander la configuration de la téléphonie", + "request_api_integration": "Demander l'intégration API" + }, + "modal": { + "request_setup": "Demander la configuration de {{company}}", + "request_billing_help": "Demander de l'aide pour l'abonnement de {{company}}", + "request_integration": "Demander le développement de l'intégration", + "request_telephony": "Demander la configuration de la téléphonie", + "request_api_integration": "Demander l'intégration API", + "full_name": "Nom *", + "phone": "Téléphone *", + "email": "E-mail *", + "comment": "Demande", + "send_request": "Envoyer", + "placeholders": { + "full_name": "Entrez votre nom", + "comment": { + "request_setup": "Décrivez brièvement votre demande de configuration. Nous vous contacterons pour personnaliser le système selon vos besoins.", + "request_billing_help": "Décrivez brièvement votre problème d'abonnement. Nous vous contacterons pour le résoudre.", + "request_integration": "Décrivez brièvement votre demande d'intégration. Nous vous contacterons pour préciser les détails.", + "request_telephony": "Décrivez brièvement votre demande de téléphonie. Nous vous contacterons pour l'implémentation selon vos besoins.", + "request_api_integration": "Décrivez brièvement votre demande d'intégration API. Nous vous contacterons pour l'implémentation selon vos besoins." + } + } + } + } + } +} diff --git a/frontend/public/locales/fr/page.system.json b/frontend/public/locales/fr/page.system.json new file mode 100644 index 0000000..da06f48 --- /dev/null +++ b/frontend/public/locales/fr/page.system.json @@ -0,0 +1,19 @@ +{ + "error_page": { + "title": "Quelque chose s'est mal passé", + "annotation": "Nous sommes désolés, mais quelque chose s'est mal passé. Nous avons été informés de ce problème et nous allons l'examiner sous peu. Veuillez aller à la page d'accueil ou essayer de recharger la page.", + "show_error": "Afficher le message d'erreur", + "home": "Page d'accueil", + "reload": "Recharger la page" + }, + "forbidden_page": { + "title": "L'accès à cette page est interdit.", + "back": "Retourner", + "home": "Page d'accueil" + }, + "not_found_page": { + "title": "Cette page est introuvable.", + "home": "Page d'accueil", + "back": "Retourner" + } +} diff --git a/frontend/public/locales/fr/page.tasks.json b/frontend/public/locales/fr/page.tasks.json new file mode 100644 index 0000000..0f561a7 --- /dev/null +++ b/frontend/public/locales/fr/page.tasks.json @@ -0,0 +1,135 @@ +{ + "tasks_page": { + "tasks_page_by_deadline": { + "unallocated": "Non attribuées", + "overdue": "En retard", + "today": "Aujourd'hui", + "tomorrow": "Demain", + "upcoming": "À venir", + "resolved": "Terminé", + "board": "Tableau", + "calendar": "Calendrier" + }, + "common": { + "tasks_page_template": { + "create_new": "Créer une nouvelle tâche" + }, + "ui": { + "empty_list_block": { + "annotation1": "Vous n'avez pas encore de tâches ou rien n'a été trouvé avec les filtres sélectionnés.", + "annotation2": "Utilisez le", + "annotation3": "bouton pour ajouter une tâche." + }, + "tasks_page_header": { + "timeline": "Gantt", + "title": "Tâches & Activités", + "board": "Tableau", + "list": "Liste", + "calendar": "Calendrier", + "user_select_button": "Partager", + "settings_button": { + "sync": "Synchroniser", + "table_settings": "Paramètres de la table", + "settings": "Paramètres", + "board_settings": "Paramètres du tableau" + }, + "create_button_tooltip": "Ajouter une tâche" + }, + "tasks_list_settings_drawer": { + "table_settings": "Paramètres de la table", + "display_columns": "Afficher les colonnes" + }, + "tasks_filter_drawer": { + "just_my_tasks": "Seulement mes tâches", + "sorting": "Tri", + "done": "Terminé", + "sorting_options": { + "manual": "Manuel", + "created_asc": "Créé : du plus ancien au plus récent", + "created_desc": "Créé : du plus récent au plus ancien" + }, + "created_at": "Date de création", + "start_date": "Date de début", + "end_date": "Date de fin", + "resolve_date": "Date d'achèvement", + "assignee": "Assigné à", + "reporter": "Rapporteur", + "type": "Type", + "stage": "Étape", + "groups": "Groupes", + "linked_cards": "Cartes liées", + "placeholders": { + "card_name": "Nom de la carte", + "search_by_task_name": "Rechercher par nom de tâche", + "search_by_activity_name": "Rechercher par nom d'activité" + } + } + } + }, + "tasks_page_calendar": { + "new_event": "Nouvelle tâche", + "no_events": "Il n'y a pas de tâches pour cette période.", + "all_day": "Toute la journée" + }, + "tasks_page_list": { + "all_columns_hidden": "Toutes les colonnes sont cachées dans les paramètres de la table" + }, + "activity_item": { + "no_card_access": "Vous ne pouvez pas accéder à cette carte", + "drag_disabled": "Vous n'êtes pas autorisé à déplacer cette carte. Contactez l'administrateur du compte pour y accéder." + }, + "tasks_page_timeline": { + "annotation": { + "minute": ["minute", "minutes", "minutes"], + "hour": ["heure", "heures", "heures"], + "day": ["jour", "jours", "jours"] + }, + "today": "Aujourd'hui", + "view": { + "fifteen_minutes": "15 Minutes", + "hour": "Heure", + "day": "Jour", + "week": "Semaine", + "month": "Mois", + "quarter": "Trimestre", + "half_year": "Semestre" + }, + "format": { + "major_format": { + "fifteen_minutes": "YYYY, MMMM D", + "hour": "YYYY, MMMM D", + "day": "YYYY, MMMM", + "week": "YYYY, MMMM", + "month": "YYYY", + "quarter": "YYYY", + "half_year": "YYYY" + }, + "minor_format": { + "fifteen_minutes": "HH:mm", + "hour": "HH", + "day": "D", + "week": "wo [semaine]", + "month": "MMMM", + "quarter": "[T]Q", + "half_year": "YYYY-" + } + } + }, + "task_item": { + "planned_time": "Temps estimé :", + "completed": "Terminé", + "complete": "Achever", + "delete_task": "Supprimer la tâche", + "copy_link": "Copier le lien", + "drag_disabled": "Vous n'êtes pas autorisé à déplacer cette tâche. Contactez l'administrateur du compte pour y accéder.", + "date": "{{date}} à {{time}}" + }, + "time_allocation_card": { + "users": "Utilisateurs", + "planned_time": "Temps estimé", + "total": "Total", + "hour": "h", + "minute": "m" + } + } +} diff --git a/frontend/public/locales/fr/store.field-groups-store.json b/frontend/public/locales/fr/store.field-groups-store.json new file mode 100644 index 0000000..746af1a --- /dev/null +++ b/frontend/public/locales/fr/store.field-groups-store.json @@ -0,0 +1,9 @@ +{ + "field_groups_store": { + "requisites": "Requis", + "analytics": "Analyste", + "errors": { + "create_at_least_one_field": "Veuillez créer au moins un champ dans chaque groupe" + } + } +} diff --git a/frontend/public/locales/fr/store.fields-store.json b/frontend/public/locales/fr/store.fields-store.json new file mode 100644 index 0000000..c2d1eb3 --- /dev/null +++ b/frontend/public/locales/fr/store.fields-store.json @@ -0,0 +1,8 @@ +{ + "fields_store": { + "errors": { + "create_at_least_one_field": "Veuillez créer au moins un champ", + "duplicate_name": "Les noms de champs doivent être uniques: {{fieldName}}" + } + } +} diff --git a/frontend/public/locales/pl/common.json b/frontend/public/locales/pl/common.json new file mode 100644 index 0000000..d6ce302 --- /dev/null +++ b/frontend/public/locales/pl/common.json @@ -0,0 +1,395 @@ +{ + "connect_with_google": "Podłącz z Google", + "responsible": "Odpowiedzialny", + "all_cards": "Wszystkie Karty", + "apply_to_all_button": { + "apply": "Zastosuj", + "apply_to_all": "Zastosuj do wszystkich", + "apply_to_all_accounts": "Zastosuj ustawienia tabeli dla wszystkich użytkowników:", + "apply_warning_title": "Czy na pewno chcesz zastosować lokalne ustawienia tabeli dla wszystkich użytkowników?", + "apply_warning_annotation": "Ta akcja nadpisze wszystkie ustawienia tabeli dla innych użytkowników w systemie. Tej operacji nie można cofnąć." + }, + "version_modal": { + "title": "✨ {{company}} właśnie się poprawił!", + "annotation": "Zaktualizuj do wersji {{version}}!", + "update": "Zaktualizuj" + }, + "reload_modal": { + "title": "Strona jest nieaktualna!", + "annotation": "Dane na tej stronie mogą być nieaktualne. Odśwież, aby zapewnić prawidłowe działanie.", + "update": "Odśwież" + }, + "select_stage": "Wybierz etap", + "new_activity_type": "Nowy typ aktywności", + "video_error": "Nie udało się pobrać i odtworzyć pliku wideo", + "image_error": "Nie udało się pobrać i wyświetlić podglądu obrazu", + "view_document": "Widok: {{document}}", + "object": "obiekt", + "all_day": "Wszystkie dni", + "supervisor": "Nadzorca", + "current_chat_user_hint": "Nie możesz usunąć siebie z czatu", + "coming_soon": "Wkrótce...", + "field_readonly": "To pole jest tylko do odczytu", + "now": "Teraz", + "default": "Domyślnie", + "exact_time": "Dokładny czas", + "card_copy": "Ta kopia została utworzona przez automatyzację zmiany etapu", + "customize": "Dostosuj", + "day_char": "d", + "hour_char": "g", + "minute_char": "m", + "days": ["dzień", "dni", "dni"], + "minutes": ["minuta", "minuty", "minut"], + "hours": ["godzina", "godziny", "godzin"], + "seconds": ["sekunda", "sekundy", "sekund"], + "files_count": ["{{count}} plik", "{{count}} pliki", "{{count}} plików"], + "loading_title": "Ładowanie...", + "changes_unsaved_blocker": { + "title": "Zmiany nie zostały zapisane", + "annotation": "Czy chcesz zapisać przed zamknięciem, czy wyjść bez zapisywania?", + "approve_title": "Zapisz", + "cancel_title": "Wyjdź bez zapisywania" + }, + "browser_not_supported_title": "Twoja przeglądarka nie jest obsługiwana.", + "browser_not_supported_annotation": "Aby korzystać z {{company}}, zalecamy używanie najnowszej wersji Chrome, Firefox lub Safari. Zapewni to najlepsze możliwe doświadczenie. Instrukcje znajdziesz poniżej.", + "duplicate_title": "Karta już istnieje", + "duplicate_warning_annotation": "Czy chcesz utworzyć duplikat czy wybrać istniejącą kartę?", + "select_existing": "Wybierz istniejącą", + "duplicate_warning_cancel_title": "Utwórz duplikat", + "duplicate_forbidden_annotation": "Administrator zabronił tworzenia duplikatów kontaktów. Proszę wybrać istniejącą kartę lub użyć innego unikalnego numeru.", + "add_as_new": "Utwórz duplikat", + "tasks_total": ["Zadania", "Zadania", "Zadań"], + "activities_total": ["Aktywności", "Aktywności", "Aktywności"], + "visits_total": ["Wizyta", "Wizyty", "Wizyt"], + "trial_left": { + "one_day": "Próba: 1 dzień", + "several_days": ["Próba: {{left}} dni", "Próba: {{left}} dni", "Próba: {{left}} dni"] + }, + "meta_description": "{{company}} to potężne i elastyczne narzędzie do tworzenia przestrzeni roboczej, zaprojektowane specjalnie dla małych i średnich przedsiębiorstw.", + "international_number": "Numer międzynarodowy", + "phone_number": "Numer telefonu", + "no_available_groups": "Brak dostępnych grup", + "add_new_group": "Dodaj nową grupę", + "schedule": "Harmonogram", + "board": "Tablica", + "settings": "Ustawienia", + "visits": "Wizytę", + "orders": "Zamówienia", + "row": "Wiersz", + "column": "Kolumna", + "external_link": "Link zewnętrzny", + "loading": "Ładowanie", + "products": "Produkty", + "for_sales": "Na sprzedaż", + "rentals": "Wynajem", + "title": "Tytuł", + "name": "Nazwa", + "max_length": "Maksymalnie {{length}} znaków", + "activities": "Aktywności", + "project_tasks_board": "Tablica", + "tasks_board": "Tablica zadań", + "tasks_list": "Lista", + "time_board": "Tablica czasu", + "trial": "Próba", + "show_less": "Pokaż mniej", + "show_more": "Pokaż więcej", + "search": "Szukaj", + "clear_search": "Wyczyść wyszukiwanie", + "profile": "Profil", + "avatar_alt": "{{firstName}} avatar", + "log_out": "Wyloguj się", + "select_date": "Wybierz datę", + "task_title": "Tytuł zadania", + "select": "Wybierz", + "overview": "Przegląd", + "new_board": "Nowa tablica", + "enter_html_markup": "Wprowadź znacznik HTML", + "filter": "Filtr", + "notifications": "Powiadomienia", + "mail": "Poczta", + "multichat": "Multi Messenger", + "total": "Razem: {{total}}", + "select_period": "Wybierz okres", + "select_groups": "Wybierz grupy", + "select_group": "Wybierz grupę", + "delay": "Opóźnienie", + "hide_sidebar": "Ukryj pasek boczny", + "show_sidebar": "Pokaż pasek boczny", + "site_form": "Formularze na stronę", + "workspace_tags": { + "site_form": "Formularz strony", + "headless_site_form": "Formularz na stronę przez API", + "online_booking_site_form": "Formularz rezerwacji online", + "project": "Projekty i zadania", + "universal": "Moduł uniwersalny", + "partner": "Partnerzy", + "deal": "CRM", + "contact": "Kontakty", + "company": "Firmy", + "supplier": "Dostawcy", + "contractor": "Wykonawcy", + "hr": "Rekrutacja", + "products_for_sales": "Zarządzanie Zapasami", + "products_rentals": "Zarządzanie wynajmem", + "visits": "Kalendarz wizyt" + }, + "sidebar": { + "builder": "Kreator", + "tasks_and_activities": "Zadania i aktywności", + "mail": "Poczta", + "settings": "Ustawienia", + "companies": "Firmy", + "contacts": "Kontakty", + "multichat": "Multi Messenger" + }, + "buttons": { + "save_and_leave": "Zapisz i opuść", + "create": "Utwórz", + "cancel": "Anuluj", + "save": "Zapisz", + "delete": "Usuń", + "add": "Dodaj", + "edit": "Edytuj", + "ok": "Ok", + "activate": "Aktywuj", + "deactivate": "Dezaktywuj", + "back": "Wstecz", + "clear": "Wyczyść", + "continue": "Kontynuuj" + }, + "profile_modal": { + "app_version": "Wersja aplikacji", + "app_version_hint": "Aktualna wersja aplikacji.", + "clear_cache": "Wyczyść pamięć", + "clear_cache_title": "Wyczyść pamięć podręczną", + "clear_cache_hint": "Ta akcja wyczyści wszystkie nieistotne dane pamięci podręcznej klienta i przeładuje stronę.", + "password": "Hasło", + "profile": "Twój profil", + "avatar": "Awatar", + "first_name": "Imię", + "first_name_hint": "Maksymalnie {{length}} znaków", + "last_name": "Nazwisko", + "last_name_hint": "Maksymalnie {{length}} znaków", + "change_avatar": "Zmień", + "add_avatar": "Dodaj zdjęcie", + "delete_avatar": "Usuń", + "avatar_annotation": "Zdjęcie musi być w formacie PNG, JPG. Rozdzielczość nie większa niż 1024px*1024px.", + "date_of_birth": "Data urodzenia", + "phone": "Telefon", + "employment_start": "Data rozpoczęcia zatrudnienia", + "email": "Adres email", + "current_password": "Aktualne hasło", + "new_password": "Nowe hasło", + "confirm_password": "Potwierdź hasło", + "change_password": "Zmień hasło", + "upload_avatar": "Prześlij awatar", + "passwords_do_not_match": "Hasła nie pasują", + "incorrect_password": "Nieprawidłowe hasło", + "image_edit_modal": { + "annotation": "Dostosuj wyświetlanie zdjęcia w swoim profilu. Przesuń suwak, aby przybliżyć lub oddalić zdjęcie." + } + }, + "form": { + "my_select": { + "no_options": "Brak opcji", + "placeholder": "Wybierz", + "search": "Szukaj...", + "select_user": "Wybierz użytkownika", + "select_all": "Wybierz wszystkie", + "clear_selection": "Wyczyść wybór" + }, + "key_value_input": { + "key": "Klucz", + "value": "Wartość", + "add": "Dodaj" + }, + "week_intervals_input": { + "mo": "Pon", + "tu": "Wt", + "we": "Śr", + "th": "Czw", + "fr": "Pt", + "sa": "Sob", + "su": "Nd", + "to": "do", + "unavailable": "Poza godzinami pracy" + } + }, + "subscription_period_modal": { + "contact_admin": "Skontaktuj się z administratorem konta, aby złożyć wniosek o opłatę za subskrypcję.", + "trial_left": "Masz {{left}} dzień(dni) darmowego okresu próbnego", + "trial_over": "Twój darmowy okres próbny wygasł", + "subscription_left": "Masz {{left}} dzień(dni) subskrypcji", + "subscription_over": "Twoja subskrypcja wygasła", + "trial_sub_title": "Wybierz plan i kontynuuj korzystanie z {{company}} Workspace.", + "subscription_sub_title": "Odnów subskrypcję, aby kontynuować dostęp do {{company}} Workspace.", + "select_plan": "Wybierz plan" + }, + "trial_period_over_modal": { + "send": "Wyślij", + "title": "Proszę skontaktuj się z nami, aby zapłacić za {{company}}", + "name": "* Imię", + "email": "* Email", + "phone": "* Telefon", + "number_of_users": "* Liczba użytkowników", + "subscribe": "Subskrybuj", + "yearly": "Rocznie -20%", + "monthly": "Miesięcznie", + "choose_plan": "* Wybierz swój plan", + "trial_left": "Masz {{left}} dni okresu próbnego", + "trial_left_one": "Masz {{left}} dzień okresu próbnego", + "trial_over": "Twój okres próbny się skończył", + "subscription_over": "Twój okres subskrypcji się skończył", + "placeholders": { + "name": "Twoje imię" + } + }, + "update_task_modal": { + "title": "Tytuł", + "linked_card": "Powiązana karta", + "stage": "Etap", + "add_description": "Dodaj opis", + "no_description": "Brak opisu", + "not_found": "Nie znaleziono tego zadania", + "close": "Zamknij", + "description": "Opis", + "subtasks": "Podzadania", + "comments": "Komentarze", + "planned_time": "Szacowany czas", + "board_name": "Nazwa tablicy", + "assignee": "Przydzielony", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "files": "Pliki", + "attach_files": "Dołącz pliki", + "reporter": "Reporter: {{name}}, {{date}} o {{time}}", + "reporter_noun": "Reporter", + "enter_description": "Wprowadź opis", + "new_comment": "Nowy komentarz", + "placeholders": { + "board": "Wybierz tablicę", + "search_card": "Szukaj karty" + } + }, + "delete_demo": { + "button_text": "Usuń demo", + "modal_title": "Czy na pewno chcesz usunąć dane demo?", + "modal_annotation": "Wszystkie dane demo zostaną usunięte. Tej akcji nie można cofnąć." + }, + "filter_drawer_controls": { + "clear": "Wyczyść", + "save_filter_settings": "Zapisz ustawienia filtra", + "error_message": "Niestety, filtr nie został zastosowany. Spróbuj ponownie." + }, + "period_types": { + "all_time": "Cały czas", + "today": "Dziś", + "yesterday": "Wczoraj", + "current_week": "Bieżący tydzień", + "last_week": "Zeszły tydzień", + "current_month": "Bieżący miesiąc", + "last_month": "Zeszły miesiąc", + "current_quarter": "Bieżący kwartał", + "last_quarter": "Zeszły kwartał", + "placeholders": { + "select_period": "Wybierz okres" + } + }, + "file_size_warning_modal": { + "title": "Plik za duży", + "annotation": "Maksymalny rozmiar pliku do przesłania to {{maxSizeMb}} MB. Skonwertuj go lub wybierz inny.", + "cancel": "Anuluj" + }, + "player": { + "default": "Domyślny", + "fast": "Szybki", + "super_fast": "Super szybki" + }, + "languages": { + "en": "Angielski", + "fr": "Francuski", + "ru": "Rosyjski", + "es": "Hiszpański", + "pt": "Portugalski", + "ar": "Arabski", + "tr": "Turecki", + "vi": "Wietnamski", + "az": "Azerski", + "uk": "Ukraiński", + "id": "Indonezyjski" + }, + "calendar": { + "month": "Miesiąc", + "week": "Tydzień", + "day": "Dzień", + "agenda": "Agenda", + "more": "więcej", + "users": "Użytkownicy", + "today": "Dziś", + "all": "Wszystkie zadania", + "completed": "Zakończone zadania", + "pending": "Oczekujące zadania", + "colored": "Kolorowe", + "colorless": "Bez koloru", + "show_weekends": "Pokaż weekend", + "time_scale": "Skala czasu", + "from": "Od", + "to": "Do", + "stage_select": "Etap" + }, + "page_title": { + "system": { + "browser_not_supported": { + "page_title": "Przeglądarka nieobsługiwana" + } + }, + "builder": { + "production": "Procesy produkcyjne", + "builder": "Kreator", + "workspace": "Twoje miejsce pracy", + "crm": "Kreator | CRM", + "site_form": "Kreator | Formularz strony", + "project_management": "Kreator | Projekty i zadania", + "product_management_for_sales": "Kreator | Zarządzanie Zapasami", + "product_management_rentals": "Kreator | Zarządzanie wynajmem", + "scheduler": "Kreator | Kalendarz wizyt", + "supplier_management": "Kreator | Dostawcy", + "contractor_management": "Kreator | Wykonawcy", + "partner_management": "Kreator | Partnerzy", + "hr_management": "Kreator | Rekrutacja", + "contact": "Kreator | Kontakty", + "company": "Kreator | Firmy", + "universal_module": "Kreator | Moduł uniwersalny", + "products": { + "rental": "Kreator | Zarządzanie wynajmem", + "sale": "Kreator | Zarządzanie Zapasami" + } + }, + "time_board": "Tablica czasu", + "activities_board": "Tablica aktywności", + "settings": { + "general_settings": "Ustawienia | Ustawienia ogólne", + "user_list": "Ustawienia | Użytkownicy — Konfiguracja użytkowników", + "user_departments": "Ustawienia | Użytkownicy — Grupy", + "user_edit": "Ustawienia | Użytkownicy — Edycja użytkownika", + "calls": { + "account": "Ustawienia | Połączenia — Konto", + "users": "Ustawienia | Połączenia — Użytkownicy", + "scenarios": "Ustawienia | Połączenia — Konfiguracja", + "sip_registrations": "Ustawienia | Połączenia — Rejestracje SIP" + }, + "mailing": "Ustawienia | Ustawienia poczty", + "integrations": "Ustawienia | Integracje", + "billing": "Ustawienia | Rozliczenia", + "api_access": "Ustawienia | Dostęp do API", + "superadmin": "Settings | Admin Panel", + "document": { + "templates": "Ustawienia | Dokumenty — Szablony dokumentów", + "creation": "Ustawienia | Dokumenty — Pola tworzenia dokumentów" + } + }, + "mailing": "E-mail i Messenger", + "orders": "Zamówienia", + "notes": "Notatki" + } +} diff --git a/frontend/public/locales/pl/component.card.json b/frontend/public/locales/pl/component.card.json new file mode 100644 index 0000000..1d5b552 --- /dev/null +++ b/frontend/public/locales/pl/component.card.json @@ -0,0 +1,344 @@ +{ + "card": { + "status": { + "active": "Aktywna", + "liquidating": "W likwidacji", + "liquidated": "Zlikwidowana", + "bankrupt": "Upadłość", + "reorganizing": "W trakcie połączenia z innym podmiotem prawnym, następnie likwidacja" + }, + "type": { + "legal": "Osoba prawna", + "individual": "Przedsiębiorca indywidualny" + }, + "branch_type": { + "main": "Siedziba główna", + "branch": "Oddział" + }, + "opf": { + "bank": "Bank", + "bank_branch": "Oddział banku", + "nko": "Niebankowa organizacja kredytowa (NKO)", + "nko_branch": "Oddział NKO", + "rkc": "Centrum rozliczeniowa-kasowe", + "cbr": "Departament Banku Rosji (marzec 2021)", + "treasury": "Departament Skarbu (marzec 2021)", + "other": "Inne" + }, + "days": ["dzień", "dni", "dni"], + "creation_date": "Utworzono", + "in_work": "W pracy", + "shipping_date": "Wysłano", + "closing_date": "Zamknięto", + "open_chat": "Otwórz czat", + "analytics": "Analityk", + "card_focus": "Skup się (wyróżnia kartę na tablicy i liście)", + "field_formula_circular_dependency_warning": { + "warning_title": "Nie można zapisać zmian", + "warning_annotation": "Próbujesz zaktualizować pole formuły \"{{fieldName}}\" używając innej formuły, która tworzy zależność cykliczną (jedna formuła zależy od innej formuły, która zależy od niej). Proszę zaktualizować ustawienia formuły, aby rozwiązać zależność cykliczną i spróbować ponownie.", + "cancel_changes": "Anuluj zmiany", + "continue": "Kontynuuj" + }, + "field_used_in_formula_warning": { + "warning_title": "Nie można zapisać zmian", + "warning_annotation": "Próbujesz usunąć pole \"{{fieldName}}\", które jest używane w formule. Usuń je najpierw z formuły i spróbuj ponownie.", + "cancel_changes": "Anuluj zmiany", + "continue": "Kontynuuj" + }, + "mutation_warning": { + "title": { + "change_stage": "Nie można zmienić etapu", + "save_changes": "Nie można zapisać zmian" + }, + "annotation": "Niektóre wymagane pola nie są wypełnione. Proszę je wypełnić i spróbować ponownie.", + "continue": "Kontynuuj" + }, + "change_linked_responsibles_modal": { + "header": "Zmiana odpowiedzialnego", + "annotation": "Wybierz powiązane karty, w których chcesz również zmienić odpowiedzialnego.", + "change_responsible_in_chats": "Dodaj odpowiedzialnego do czatów kart", + "chats_hint": "Nowy odpowiedzialny zostanie dodany do czatów powiązanych kart, w których jeszcze nie uczestniczy." + }, + "owner": "Odpowiedzialny", + "delete_warning_title": "Czy na pewno chcesz usunąć jednostkę?", + "delete_warning_caption": "Tej akcji nie można cofnąć.", + "files": "Pliki", + "from_card_or_linked_entities": "Z karty lub powiązanych jednostek", + "recent": "Ostatnie", + "change_board": "Zmień tablicę", + "placeholders": { + "name": "Wprowadź nazwę..." + }, + "ui": { + "requisites_codes": { + "ie_name": "Imię Przedsiębiorcy Indywidualnego", + "ie_surname": "Nazwisko Przedsiębiorcy Indywidualnego", + "ie_patronymic": "Drugie Imię Przedsiębiorcy Indywidualnego", + "org_name": "Nazwa Firmy", + "org_tin": "NIP Firmy", + "org_trrc": "KPP Firmy", + "org_psrn": "PSRN", + "org_type": "Typ Firmy", + "org_full_name": "Pełna Nazwa Firmy", + "org_short_name": "Krótka Nazwa Firmy", + "org_management_name": "Imię Dyrektora", + "org_management_post": "Stanowisko Dyrektora", + "org_management_start_date": "Data Rozpoczęcia Kadencji Dyrektora", + "org_branch_count": "Liczba Oddziałów", + "org_branch_type": "Typ Oddziału", + "org_address": "Adres Firmy", + "org_reg_date": "Data Rejestracji", + "org_liquidation_date": "Data Likwidacji", + "org_status": "Status Firmy", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Średnia Liczba Pracowników", + "org_extra_founders": "Założyciele Firmy", + "org_extra_managers": "Kadra Zarządzająca", + "org_extra_capital": "Kapitał Zakładowy", + "org_extra_licenses": "Licencje", + "org_extra_phones": "Numery Telefonów", + "org_extra_emails": "Adresy E-mail", + "bank_name": "Nazwa Banku", + "bank_bic": "BIC Banku", + "bank_swift": "SWIFT", + "bank_tin": "NIP Banku", + "bank_trrc": "KPP Banku", + "bank_correspondent_acc": "Konto Korespondencyjne", + "bank_payment_city": "Miasto dla Zlecenia Płatniczego", + "bank_opf_type": "Typ Instytucji Kredytowej", + "bank_checking_account": "Konto Sprawdzenie" + }, + "analytics": "Analityka", + "requisites": "Wymagania", + "card_page_header": { + "overview": "Przegląd", + "products": "Produkty", + "create_new_order": "Utwórz nowe zamówienie", + "orders": "Zamówienia", + "board": "Tablica", + "list": "Lista", + "calendar": "Kalendarz", + "timeline": "Gantt", + "relocate_card_button": "Przenieś kartę", + "order_denied": "Nie masz uprawnień do tworzenia zamówienia. Skontaktuj się z administratorem konta, aby uzyskać dostęp." + }, + "actions_dropdown": { + "delete_entity": "Usuń jednostkę", + "fine_tune": "Dopasuj" + }, + "mini_card": { + "enter_name": "Wprowadź nazwę {{name}}", + "unpin": "Odepnij", + "owner": "Odpowiedzialny", + "stage": "Etap", + "placeholders": { + "select_stage": "Wybierz etap" + }, + "fields": { + "value": "Wartość", + "participants": "Uczestnicy", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "description": "Opis" + } + }, + "stages": { + "disabled_while_adding": "Zmiana statusu będzie dostępna po utworzeniu karty" + }, + "feed": { + "unknown": "Nieznany 👤", + "readonly": "Nie możesz dodawać elementów do tej karty", + "disabled_while_adding": "Dodawanie elementów do karty będzie dostępne po jej utworzeniu", + "notes": "Notatki", + "activities": "Aktywności", + "tasks": "Zadania", + "amwork_messenger": "{{company}} Messenger", + "documents": "Dokumenty", + "create_documents_tab": "Utwórz dokumenty", + "create_chat": "Utwórz czat z zespołem", + "add_visit": "Zaplanuj wizytę", + "share_documents": "Udostępnij wygenerowane dokumenty", + "empty": "Tutaj jeszcze nic nie ma", + "add_activity": { + "placeholder": "Opis aktywności" + }, + "add_note": { + "add_note": "Dodaj notatkę" + }, + "add_task_modal": { + "dates_error": "Data zakończenia musi być późniejsza niż data rozpoczęcia.", + "tab": "Dodaj zadanie", + "task_name": "Nazwa zadania *", + "planned_time": "Szacowany czas", + "board_name": "Nazwa tablicy", + "assignee": "Przydzielony", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "description": "Opis", + "subtasks": "Podzadania", + "placeholder": "Dodaj tytuł...", + "add_board": "Dodaj tablicę", + "save": "Zapisz", + "new_task": "Nowe zadanie", + "repeating_task": { + "title": "Powtarzanie zadania", + "hint": "Ustaw liczbę i odstęp powtarzających się zadań, jeśli to konieczne. Zadania będą tworzone z tymi ustawieniami. Jeśli zadanie ma określony czas rozpoczęcia lub zakończenia, zostaną zaplanowane z uwzględnieniem odstępu i dni roboczych.", + "count": "Liczba", + "interval": "Odstęp", + "interval_disabled_tooltip": "Ustaw czas rozpoczęcia lub zakończenia zadania", + "intervals": { + "none": "Bez odstępu", + "day": "Codziennie", + "week": "Co tydzień", + "month": "Co miesiąc" + } + }, + "placeholders": { + "board": "Wybierz tablicę", + "subtask": "Nowe podzadanie" + } + }, + "filter": { + "all": "Wszystkie", + "tasks": "Zadania", + "activities": "Aktywności", + "notes": "Notatki", + "mail": "Poczta", + "files": "Pliki", + "calls": "Rozmowy" + }, + "create_documents": { + "order": "Zamówienie-{{number}}", + "documents": "Dokumenty", + "create_documents": "Utwórz dokumenty", + "create_new_documents": "Utwórz nowe dokumenty", + "select_all": "Zaznacz wszystko", + "clear_selection": "Wyczyść zaznaczenie", + "send_by_email": "Wyślij e-mailem", + "add_template": "Dodaj szablon", + "invalid_template_title": "Błąd w szablonie", + "invalid_template_annotation": "Nie można wygenerować dokumentu z powodu błędów składniowych w szablonie. Zaktualizuj szablon lub wybierz inny.", + "hints": { + "cancel": "Anuluj", + "generate": "Generuj", + "generate_anyway": "Generuj mimo to", + "docx_format": "Format DOCX", + "pdf_format": "Format PDF", + "no_documents_title": "Brak dostępnych szablonów", + "no_documents_annotation": "Brak dostępnych szablonów dokumentów dla tej jednostki. Utwórz nowe lub zmodyfikuj istniejące w Dokumentach na stronie Ustawienia.", + "creating_documents_title": "Tworzenie dokumentów", + "creating_documents_annotation": "To może chwilę potrwać, ale po zakończeniu będziesz mógł wysłać lub pobrać dokumenty.", + "select_template_title": "Wybierz szablon", + "select_template_annotation": "Aby wybrać szablon, otwórz listę rozwijaną i wybierz żądany plik.", + "format_title": "Format", + "issues_identified": "Zidentyfikowano kilka problemów", + "invalid_tags_annotation": "Istnieją kody, które wydają się być nieprawidłowe lub są powiązane z pustym polem:", + "format_annotation": "Wybierz format(y), w którym chcesz wygenerować dokument." + }, + "placeholders": { + "select_order": "Wybierz zamówienie", + "select_template": "Wybierz szablon", + "invalid_tags_annotation": "Niektóre kody są nieprawidłowe lub powiązane z pustym polem:" + } + }, + "activity_block": { + "creator": "Przydzielony", + "start_date": "Data", + "plan_time": "Czas" + }, + "creator_tag": { + "task_setter_at_time": "{{taskSetter}} o {{time}}" + }, + "files_block": { + "unknown_file": "Nieznany plik", + "downloaded_file": "{{companyName}} – Pobieranie pliku", + "attachment": "załącznik", + "attachments": "załączniki", + "download_all": "Pobierz wszystkie" + }, + "note_block": { + "block_title": "Notatka", + "creation_date": "Data utworzenia", + "message": "Wiadomość", + "to_recipient": "do {{recipient}}" + }, + "task_block": { + "creator": "Przydzielony", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "no_due_date": "Brak terminu" + }, + "mail_block": { + "sender": "Nadawca", + "recipient": "Odbiorca", + "date": "Data", + "seen": "Widoczny" + }, + "document_block": { + "creation_date": "Data utworzenia", + "docs": "Dokumenty", + "annotation": "{{creator}} utworzył {{documentName}} dnia {{date}}" + }, + "call_block": { + "start_date": "Data", + "who_called": "Dzwoniący", + "who_got_call": "Odbiorca", + "did_not_get_through": "Nie dodzwonił się", + "time": { + "d": "d", + "h": "g", + "m": "m", + "s": "s" + }, + "status": { + "missed_call": "Nieodebrane połączenie", + "incoming_call": "Połączenie przychodzące", + "outgoing_call": "Połączenie wychodzące" + }, + "comment": "Komentarz" + }, + "common": { + "author": "Autor", + "reporter": "Reporter", + "created_at": "{{day}} o {{time}}", + "result": "Wynik", + "functional_options": { + "edit": "Edytuj", + "delete": "Usuń" + }, + "item_tag": { + "tasks": { + "resolved": "Ukończone zadanie", + "expired": "Zadanie przeterminowane", + "future": "Przyszłe zadanie", + "today": "Dzisiejsze zadanie" + }, + "activities": { + "resolved": "Ukończona aktywność", + "expired": "Aktywność przeterminowana", + "future": "Przyszła aktywność", + "today": "Dzisiejsza aktywność" + } + }, + "activity_type_picker": { + "add_new_type": "Dodaj nowy typ", + "warn_title": "Czy na pewno chcesz usunąć ten typ aktywności?", + "warn_annotation": "Nie będziesz już mógł tworzyć tego typu aktywności.", + "placeholders": { + "select_activity_type": "Wybierz typ aktywności" + } + }, + "user_picker": { + "add": "Dodaj", + "placeholder": "Wybierz użytkownika" + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/component.entity-board.json b/frontend/public/locales/pl/component.entity-board.json new file mode 100644 index 0000000..974d6b3 --- /dev/null +++ b/frontend/public/locales/pl/component.entity-board.json @@ -0,0 +1,23 @@ +{ + "entity_board": { + "ui": { + "project_entity_card_item": { + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "titles": { + "not_resolved": "Nieukończone zadania", + "upcoming": "Nadchodzące zadania", + "overdue": "Przeterminowane zadania", + "today": "Dzisiejsze zadania", + "resolved": "Ukończone zadania" + } + }, + "common_entity_card": { + "no_tasks": "Brak zadań", + "task_today": "Zadanie na dziś", + "overdue_task": "Przeterminowane zadanie", + "upcoming_task": "Nadchodzące zadanie" + } + } + } +} diff --git a/frontend/public/locales/pl/component.section.json b/frontend/public/locales/pl/component.section.json new file mode 100644 index 0000000..08c6a60 --- /dev/null +++ b/frontend/public/locales/pl/component.section.json @@ -0,0 +1,190 @@ +{ + "section": { + "section_header_with_boards": { + "board": "Tablica", + "list": "Lista", + "dashboard": "Pulpit", + "reports": "Raporty", + "timeline": "Gantt", + "automation": "Automatyzacja", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Wkrótce...)", + "tooltips": { + "reports_denied": "Nie masz uprawnień do przeglądania raportów. Skontaktuj się z administratorem konta, aby uzyskać dostęp." + } + }, + "empty_projects_block": { + "annotation": "Nie masz jeszcze żadnych projektów lub nie znaleziono nic z wybranymi filtrami." + }, + "common": { + "add_card": "Dodaj kartę", + "new_stage": "Nowy Etap", + "create_button_tooltip": { + "universal": "Dodaj kartę", + "partner": "Dodaj partnera", + "deal": "Dodaj transakcję", + "contact": "Dodaj kontakt", + "company": "Dodaj firmę", + "supplier": "Dodaj dostawcę", + "contractor": "Dodaj kontrahenta", + "hr": "Dodaj kandydata", + "project": "Dodaj projekt" + }, + "cards_total": { + "universal": ["Karty", "Karty", "Kart"], + "partner": ["Partnerzy", "Partnerzy", "Partnerów"], + "customer": ["Klienci", "Klienci", "Klientów"], + "deal": ["Karty", "Karty", "Kart"], + "contact": ["Kontakty", "Kontakty", "Kontaktów"], + "company": ["Firmy", "Firmy", "Firm"], + "supplier": ["Dostawcy", "Dostawcy", "Dostawców"], + "contractor": ["Kontrahenci", "Kontrahenci", "Kontrahentów"], + "hr": ["Kandydaci", "Kandydaci", "Kandydatów"], + "project": ["Projekty", "Projekty", "Projektów"] + }, + "filter_button": { + "ui": { + "filter_drawer": { + "closed_at": "Data zamknięcia", + "just_my_cards": "Tylko moje karty", + "sorting": "Sortowanie", + "creation_date": "Data utworzenia", + "owners": "Odpowiedzialne", + "stages": "Etapy", + "tasks": "Zadania", + "placeholders": { + "selected_options": "Wybrane opcje", + "search_by_card_name": "Szukaj po nazwie karty" + }, + "sorting_options": { + "manual": "Ręcznie", + "created_asc": "Utworzono: od najstarszych do najnowszych", + "created_desc": "Utworzono: od najnowszych do najstarszych", + "name_asc": "Tytuł: rosnąco", + "name_desc": "Tytuł: malejąco" + }, + "tasks_options": { + "all": "Wszystkie karty", + "with_task": "Z zadaniami", + "without_task": "Bez zadań", + "overdue_task": "Z zaległymi zadaniami", + "today_task": "Z zadaniami na dziś" + }, + "field_filter_string": { + "is_empty": "Jest pusty", + "is_not_empty": "Nie jest pusty", + "contains": "Zawiera...", + "placeholders": { + "value": "Wartość" + } + }, + "field_filter_exists": { + "is_empty": "Jest pusty", + "is_not_empty": "Nie jest pusty" + } + }, + "date_period_picker": { + "date_period_segmented_control": { + "period": "Okres", + "period_hint": "Dla wybranego okresu", + "from": "Od", + "from_hint": "Od wybranej daty", + "to": "Do", + "to_hint": "Przed wybraną datą" + }, + "placeholders": { + "select_period": "Wybierz okres" + } + }, + "field_filter_items": { + "field_filter_boolean": { + "switch_on": "Włącz", + "switch_off": "Wyłącz" + }, + "field_filter_number": { + "from": "Od", + "to": "Do" + } + } + } + }, + "settings_button": { + "module_settings": "Ustawienia modułu", + "settings": "Ustawienia", + "import": "Importuj", + "get_import_template": "Pobierz szablon importu", + "board_settings": "Ustawienia tablicy", + "table_settings": "Ustawienia tabeli", + "upload_file": "Prześlij plik", + "sales_pipeline_settings": "Ustawienia lejka sprzedażowego", + "request_setup": "Zamów konfigurację {{company}}", + "import_entities_modal": { + "title": "Import z Excel", + "annotation": "Aby zaimportować swoją bazę danych do {{company}}, wykonaj kilka prostych kroków:", + "perform_import": "Wykonaj import", + "setting_up_file_to_import": { + "title": "Przygotowanie pliku do importu", + "step1": "Pobierz przykładowy plik szablonu;", + "step2": "Wprowadź swoje dane do tabeli, która odpowiada strukturze pól {{company}};", + "step3": "Po zakończeniu ustawień dopasowania kolumn możesz zapisać ustawienia jako szablon importu." + }, + "import_file": { + "title": "Importuj plik", + "step1": "Kliknij \"Prześlij plik\";", + "step2": "W otwartym oknie wybierz wcześniej wypełniony szablon;", + "step3": "Po pobraniu kliknij przycisk \"Wykonaj import\"." + } + }, + "import_in_background_info_modal": { + "title": "Import z Excel", + "content": "Proces importu jest obecnie uruchomiony w tle. Możesz kontynuować pracę w {{company}} jak zwykle, a my powiadomimy Cię po zakończeniu procesu.", + "ok": "Ok" + } + }, + "search_box": { + "nothing_found": "Nic nie znaleziono", + "placeholder": "Szukaj..." + }, + "section_table_settings_sidebar": { + "table_settings": "Ustawienia tabeli", + "display_columns": "Wyświetl kolumny" + } + }, + "section_table": { + "hidden": "Ukryte", + "hidden_hint": "Nie możesz wyświetlić ani uzyskać dostępu do tego pola, ponieważ zostało ono ukryte w ustawieniach pól modułu przez administratora konta.", + "empty": "Nie dodałeś jeszcze żadnych kart", + "all_columns_hidden": "Wszystkie kolumny są ukryte w ustawieniach tabeli", + "name": "Nazwa", + "owner": "Odpowiedzialny", + "stage": "Etap", + "date_created": "Data utworzenia", + "value": "Wartość", + "participants": "Uczestnicy", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "description": "Opis", + "placeholders": { + "participants": "Dodaj uczestników" + }, + "select_all": { + "title": "Masowy wybór", + "description": "Zastosuj do wszystkich kart ({{count}}) czy tylko do bieżącej strony?", + "all_elements": "Do wszystkich kart", + "for_page": "Tylko dla aktywnej strony" + }, + "batch_actions": { + "cards_selected": "Wybrane karty: {{count}}", + "change_responsible": "Zmień odpowiedzialnego", + "change_stage": "Zmień etap", + "delete": "Usuń", + "change_stage_annotation": "Wybierz nowy etap dla wybranych kart. Tej operacji nie można cofnąć.", + "change_responsible_annotation": "Wybierz nowego odpowiedzialnego użytkownika dla wybranych kart.", + "action_cannot_be_undone": "Tej operacji nie można cofnąć.", + "delete_warning_title": "Usuń wybrane karty", + "delete_warning_annotation": "Czy na pewno chcesz usunąć wybrane karty? Tej operacji nie można cofnąć.", + "select_linked_entities_annotation": "Zmień odpowiedzialnego w powiązanych kartach" + } + } + } +} diff --git a/frontend/public/locales/pl/module.automation.json b/frontend/public/locales/pl/module.automation.json new file mode 100644 index 0000000..62c974d --- /dev/null +++ b/frontend/public/locales/pl/module.automation.json @@ -0,0 +1,368 @@ +{ + "automation": { + "modals": { + "add_activity_automation_modal": { + "current_responsible_user": "Obecny odpowiedzialny użytkownik", + "activity_default_name": "Aktywność #{{id}}", + "title_add": "Dodaj automatyzację tworzenia aktywności", + "title_update": "Aktualizuj automatyzację tworzenia aktywności", + "create_activity": "Utwórz aktywność *", + "responsible_user": "Użytkownik odpowiedzialny *", + "due_date": "Termin", + "defer_start": "Data rozpoczęcia", + "activity_type": "Typ aktywności *", + "description": "Opis", + "current_responsible_user_hint": "Wybierz użytkownika odpowiedzialnego za aktywność. Obecny odpowiedzialny użytkownik to ten przypisany do karty, w której zostanie utworzona aktywność.", + "placeholders": { + "select_activity_type": "Wybierz typ aktywności" + } + }, + "add_task_automation_modal": { + "task_default_name": "Zadanie #{{id}}", + "title_add": "Dodaj automatyzację tworzenia zadania", + "title_update": "Aktualizuj automatyzację tworzenia zadania", + "create_task": "Utwórz zadanie *", + "due_date": "Termin", + "defer_start": "Data rozpoczęcia", + "responsible_user": "Użytkownik odpowiedzialny *", + "name_and_description": "Nazwa * i opis", + "current_responsible_user": "Obecny odpowiedzialny użytkownik", + "current_responsible_user_hint": "Wybierz użytkownika odpowiedzialnego za zadanie. Obecny odpowiedzialny użytkownik to ten przypisany do karty, w której zostanie utworzone zadanie.", + "placeholders": { + "task_name": "Nazwa zadania", + "select_user": "Wybierz użytkownika" + } + }, + "change_responsible_automation_modal": { + "change_responsible_default_name": "Odpowiedzialny #{{id}}", + "title_add": "Dodaj automatyzację zmiany odpowiedzialnego", + "title_update": "Zaktualizuj automatyzację zmiany odpowiedzialnego", + "responsible_user": "Zmień odpowiedzialnego użytkownika na *", + "placeholders": { + "select_responsible_user": "Wybierz nowego odpowiedzialnego" + } + }, + "change_stage_automation_modal": { + "change_stage_default_name": "Etap #{{id}}", + "title_add": "Dodaj automatyzację zmiany etapu", + "title_update": "Aktualizuj automatyzację zmiany etapu", + "change_stage": "Zmień etap *", + "cards_copies": "Kopie kart", + "stage": "Lejek sprzedażowy i etap *", + "automation_copy": { + "move_label": "Nie twórz kopii transakcji przy zmianie jej statusu", + "copy_original_label": "Utwórz kopię transakcji i przenieś oryginalną transakcję do nowego statusu przy zmianie jej statusu", + "copy_new_label": "Utwórz kopię transakcji przy zmianie jej statusu, ale pozostaw oryginalną transakcję w obecnym statusie.", + "hint": "Gdy ta opcja jest włączona, system automatycznie utworzy kopię karty w jej obecnym statusie przy każdej automatycznej zmianie statusu. Jest to niezbędne dla dokładnych raportów i metryk planowania sprzedaży, szczególnie gdy zmiana statusu jest związana z ostatecznymi statusami systemowymi, takimi jak 'Zakończono pomyślnie' lub 'Zakończono niepomyślnie'." + }, + "placeholders": { + "select_stage": "Wybierz etap" + } + }, + "change_linked_stage_automation_modal": { + "change_stage_default_name": "Etap #{{id}}", + "title_add": "Dodaj automatyzację zmiany etapu", + "title_update": "Aktualizuj automatyzację zmiany etapu", + "change_stage": "Zmień etap *", + "cards_copies": "Kopie kart", + "entity_type": "Module *", + "stage": "Lejek sprzedażowy i etap *", + "no_stages": "Wybrany moduł nie ma statusów. Wybierz inny moduł, aby utworzyć automatyzację.", + "automation_copy": { + "move_label": "Nie twórz kopii transakcji przy zmianie jej statusu", + "copy_original_label": "Utwórz kopię transakcji i przenieś oryginalną transakcję do nowego statusu przy zmianie jej statusu", + "copy_new_label": "Utwórz kopię transakcji przy zmianie jej statusu, ale pozostaw oryginalną transakcję w obecnym statusie.", + "hint": "Gdy ta opcja jest włączona, system automatycznie utworzy kopię karty w jej obecnym statusie przy każdej automatycznej zmianie statusu. Jest to niezbędne dla dokładnych raportów i metryk planowania sprzedaży, szczególnie gdy zmiana statusu jest związana z ostatecznymi statusami systemowymi, takimi jak 'Zakończono pomyślnie' lub 'Zakończono niepomyślnie'." + }, + "placeholders": { + "select_stage": "Wybierz etap" + } + }, + "send_email_automation_modal": { + "configure_mailbox_limits": "Konfiguruj limity skrzynki pocztowej", + "max_number_of_emails_per_day": "Maksymalna liczba wiadomości na dzień", + "send_email_default_name": "E-mail #{{id}}", + "title_add": "Dodaj automatyzację e-mail", + "title_update": "Aktualizuj automatyzację e-mail", + "title_hint": "Wiadomości e-mail będą wysyłane tylko do kart kontaktów, które mają wypełnione pole E-mail.", + "email_text": "Tekst e-maila *", + "recipient_name": "- Nazwa odbiorcy", + "recipient_name_hint": "Możesz ustawić symbol zastępczy {{contact_name}} w tekście e-maila, a zostanie on zastąpiony nazwą kontaktu z karty.", + "user_name": "- Nazwa użytkownika", + "user_phone": "- Telefon użytkownika", + "send_with_html": "Wyślij e-mail z HTML", + "sender_email": "E-mail nadawcy *", + "sender_name": "Nazwa nadawcy *", + "send": "Wyślij", + "emails_per_day": "e-maile dziennie", + "emails_per_day_hint": "Parametr limitu e-maili jest konieczny, aby uniknąć zablokowania przez dostawcę usługi e-mail. Każda usługa e-mail ustala swoje limity na wysyłanie wiadomości. Na przykład podstawowa wersja Google Workspace pozwala na wysyłanie 500 wiadomości dziennie z jednej skrzynki pocztowej.", + "mailing_parameters": "Parametry wysyłki *", + "options": { + "addresses": "Adresy wysyłki *", + "all_addresses": "Wysyłaj do wszystkich kart i adresów e-mail", + "fine_tune_addresses": "Dostosuj...", + "main_entity": "Wysyłaj do głównej karty", + "main_entity_hint": "Wybierz, czy wysłać e-mail do wszystkich adresów w głównej karcie, czy tylko do pierwszego.", + "main_entity_all": "do wszystkich adresów", + "main_entity_only_first": "tylko do pierwszego adresu", + "contact": "Wysyłaj do karty kontaktu", + "contact_hint": "Wybierz, czy wysłać e-mail do wszystkich kart typu 'Kontakt', czy tylko do pierwszej, oraz do wszystkich adresów w karcie lub tylko do pierwszego.", + "contact_all_entities_all_values": "do wszystkich adresów we wszystkich kartach kontaktów", + "contact_all_entities_first_value": "tylko do pierwszego adresu we wszystkich kartach kontaktów", + "contact_first_entity_all_values": "do wszystkich adresów tylko w pierwszej karcie kontaktu", + "contact_first_entity_first_value": "tylko do pierwszego adresu w pierwszej karcie kontaktu", + "company": "Wysyłaj do karty firmy", + "company_hint": "Wybierz, czy wysłać e-mail do wszystkich kart typu 'Firma', czy tylko do pierwszej, oraz do wszystkich adresów w karcie lub tylko do pierwszego.", + "company_all_entities_all_values": "do wszystkich adresów we wszystkich kartach firm", + "company_all_entities_first_value": "tylko do pierwszego adresu we wszystkich kartach firm", + "company_first_entity_all_values": "do wszystkich adresów tylko w pierwszej karcie firmy", + "company_first_entity_first_value": "tylko do pierwszego adresu w pierwszej karcie firmy" + }, + "placeholders": { + "title": "Wprowadź tytuł", + "email_text": "Wprowadź tekst e-maila", + "email_markup": "Wprowadź znacznik HTML", + "select_email_address": "Wybierz adres e-mail", + "select_user": "Wybierz użytkownika" + } + }, + "create_entity_automation_modal": { + "create_entity_default_name": "Karta #{{id}}", + "title_add": "Utwórz automatyzację powiązane karty", + "title_update": "Zaktualizuj automatyzację powiązane karty", + "create_card": "Utwórz powiązana kartę *", + "select_entity_type": "Moduł", + "select_entity_type_hint": "Wybierz moduł, w którym zostanie utworzona karta. Aby uniknąć cykli, karty nie można utworzyć w bieżącym module.", + "select_stage": "Tablica lub lejek sprzedażowy i status", + "responsible_user": "Odpowiedzialny użytkownik", + "responsible_user_hint": "Wybierz użytkownika odpowiedzialnego za zadanie. Aktualny odpowiedzialny użytkownik to ten, który odpowiada za kartę, w której zostanie utworzone zadanie.", + "current_responsible_user": "Aktualny odpowiedzialny użytkownik", + "entity_name": "Nazwa karty", + "placeholders": { + "select_entity_type": "Wybierz moduł", + "entity_name": "Wprowadź nazwę karty" + } + }, + "send_internal_chat_automation_modal": { + "send_internal_chat_default_name": "Czat #{{id}}", + "title_add": "Utwórz automatyzację wysyłania wiadomości w czacie", + "title_update": "Zaktualizuj automatyzację wysyłania wiadomości w czacie", + "sender": "Nadawca", + "responsible_user_hint": "Wybierz nadawcę wiadomości. Obecny odpowiedzialny użytkownik jest odpowiedzialny za kartę, z której zbudowana jest automatyzacja.", + "current_responsible_user": "Aktualnie odpowiedzialny użytkownik", + "recipients": "Odbiorcy", + "message": "Treść wiadomości", + "placeholders": { + "recipients": "Wybierz użytkowników", + "message": "Wpisz wiadomość" + } + }, + "send_external_chat_automation_modal": { + "send_external_chat_default_name": "Czat zewnętrzny #{{id}}", + "title_add": "Utwórz automatyzację wysyłki czatu zewnętrznego", + "title_update": "Zaktualizuj automatyzację wysyłki czatu zewnętrznego", + "sender": "Nadawca *", + "responsible_user_hint": "Wybierz nadawcę wiadomości. Aktualny odpowiedzialny użytkownik to osoba odpowiedzialna za kartę, z której utworzono automatyzację.", + "current_responsible_user": "Aktualny odpowiedzialny użytkownik", + "provider": "Dostawca czatu *", + "message": "Treść wiadomości *", + "send_to_chats": "Wyślij do czatów kart", + "send_to_phone_numbers": "Wyślij na numery telefonów", + "add_phone_number": "Dodaj numer", + "hint_chats": "Wiadomość zostanie wysłana do wszystkich zewnętrznych czatów kart tego modułu zgodnie z wybranymi ustawieniami", + "hint_phone_numbers": "Wiadomość zostanie wysłana na wybrane numery telefonów", + "phone_numbers_disabled": "Wysyłanie na numery telefonów jest dostępne tylko dla dostawców Whatsapp i Telegram", + "placeholders": { + "message": "Wpisz wiadomość" + }, + "errors": { + "phone": "Numer telefonu jest pusty" + }, + "options": { + "addresses": "Czaty docelowe *", + "all_addresses": "Wyślij do wszystkich kart i czatów zewnętrznych", + "fine_tune_addresses": "Skonfiguruj...", + "main_entity": "Wyślij do głównej karty", + "main_entity_hint": "Wybierz, czy wysyłać wiadomość do wszystkich czatów na głównej karcie, czy tylko do pierwszego.", + "main_entity_all": "do wszystkich czatów", + "main_entity_only_first": "tylko do pierwszego czatu", + "contact": "Wyślij do karty kontaktu", + "contact_hint": "Wybierz, czy wysyłać wiadomość do wszystkich kart typu „Kontakt”, czy tylko do pierwszej, oraz czy wysyłać do wszystkich czatów na karcie, czy tylko do pierwszego.", + "contact_all_entities_all_values": "do wszystkich czatów we wszystkich kartach kontaktów", + "contact_all_entities_first_value": "tylko do pierwszego czatu we wszystkich kartach kontaktów", + "contact_first_entity_all_values": "do wszystkich czatów tylko w pierwszej karcie kontaktu", + "contact_first_entity_first_value": "tylko do pierwszego czatu w pierwszej karcie kontaktu", + "company": "Wyślij do karty firmy", + "company_hint": "Wybierz, czy wysyłać wiadomość do wszystkich kart typu „Firma”, czy tylko do pierwszej, oraz czy wysyłać do wszystkich czatów na karcie, czy tylko do pierwszego.", + "company_all_entities_all_values": "do wszystkich czatów we wszystkich kartach firm", + "company_all_entities_first_value": "tylko do pierwszego czatu we wszystkich kartach firm", + "company_first_entity_all_values": "do wszystkich czatów tylko w pierwszej karcie firmy", + "company_first_entity_first_value": "tylko do pierwszego czatu w pierwszej karcie firmy" + } + }, + "request_http_automation_modal": { + "http_request_default_name": "Żądanie HTTP #{{id}}", + "title_add": "Utwórz automatyzację żądania HTTP", + "title_update": "Zaktualizuj automatyzację żądania HTTP", + "http_request": "Parametry żądania HTTP", + "url": "URL *", + "method": "Metoda *", + "headers": "Nagłówki", + "params": "Parametry zapytania" + }, + "automation_modal_template": { + "apply_trigger_hint": "Po wybraniu tej opcji automatyzacja zostanie zastosowana do wszystkich istniejących kart w tym statusie. W przeciwnym razie będzie stosowana tylko do nowych kart wchodzących w ten status. Tę akcję można wykonać za każdym razem, gdy zapisywana jest automatyzacja.", + "apply_trigger_hint_list": "Po wybraniu tej opcji automatyzacja zostanie zastosowana do wszystkich istniejących kart. W przeciwnym razie będzie stosowana tylko do nowych kart. Tę akcję można wykonać za każdym razem, gdy zapisywana jest automatyzacja.", + "automation_name": "Nazwa automatyzacji *", + "trigger": "Trigger *", + "conditions": "Warunki", + "delay": "Opóźnienie", + "delay_one_stage_title": "Opóźnienie powiązane ze statusem", + "delay_one_stage_hint": "Automatyzacja uruchomi się tylko wtedy, gdy karta pozostanie w tym statusie do końca opóźnienia. Zmiana statusu ją anuluje.", + "delay_all_stages_title": "Opóźnienie niezależne od statusu", + "delay_all_stages_hint": "Automatyzacja uruchomi się po upływie opóźnienia, nawet jeśli karta zmieni status.", + "enable": "Włącz automatyzację", + "enable_annotation": "Możesz włączyć i wyłączyć automatyzację", + "apply_trigger": "Zastosuj trigger do bieżących obiektów na etapie", + "apply_trigger_list": "Zastosuj trigger do bieżących obiektów", + "delete_automation": "Usuń automatyzację", + "on": "WŁ", + "placeholders": { + "name": "Nazwa" + } + }, + "delete_automation_modal": { + "title": "Czy na pewno chcesz usunąć tę automatyzację?", + "annotation": "Tej akcji nie można cofnąć" + }, + "common": { + "use_get_chats_template_options": { + "recipient_name": "- Nazwa odbiorcy", + "recipient_name_hint": "Możesz ustawić symboli zastępczy {{contact_name}} w tekście wiadomości czatu, a zostanie on zastąpiony nazwą kontaktu z karty." + }, + "trigger_select": { + "exact_time_of_automation": "Dokładny czas automatyzacji", + "exact_time": "Dokładny czas: {{day}} o {{time}}", + "when_transitioning": "Podczas przechodzenia lub tworzenia", + "at_the_transition": "Podczas przejścia do etapu", + "when_creating": "Podczas tworzenia na etapie", + "when_creating_list": "Podczas tworzenia", + "when_responsible_changes": "Gdy zmienia się odpowiedzialny użytkownik", + "placeholder": "Wybierz trigger" + }, + "conditions_block": { + "field": "Pole *", + "condition": "Warunek *", + "entity_type_field_filter_string": { + "is_empty": "Jest pusty", + "is_not_empty": "Nie jest pusty", + "contains": "Zawiera...", + "placeholders": { + "value": "Wartość" + } + }, + "entity_type_field_filter_number": { + "placeholders": { + "from": "Od", + "to": "Do" + } + }, + "entity_type_field_filter_boolean": { + "switch_on": "Włącz", + "switch_off": "Wyłącz" + }, + "fields_conditions": "Warunki pól", + "add_field_condition": "Dodaj warunek pól", + "responsible_users": "Odpowiedzialni użytkownicy", + "responsible": "Odpowiedzialny", + "budget": "Budżet", + "value": "Wartość", + "from": "od", + "to": "do", + "placeholders": { + "selected_participants": "Wybrani uczestnicy", + "selected_options": "Wybrane opcje", + "add_condition": "Dodaj warunek", + "select_responsible": "Wybierz odpowiedzialnych" + } + }, + "description_block": { + "placeholders": { + "description": "Opis" + } + }, + "due_date_select": { + "due_date": "Termin", + "options": { + "during_this_week": "W tym tygodniu", + "in_3_days": "Za 3 dni", + "in_a_day": "Za dzień", + "end_of_the_day": "Koniec dnia", + "at_the_time_of_creation": "W momencie tworzenia" + } + }, + "defer_start_select": { + "defer_start": "Data rozpoczęcia", + "options": { + "in_an_hour": "Za godzinę", + "in_a_day": "Pojutrze", + "in_3_days": "Po 3 dniach" + } + }, + "delay_select": { + "no_delay": "Bez opóźnienia", + "5_minutes": "5 minut", + "10_minutes": "10 minut", + "15_minutes": "15 minut", + "30_minutes": "30 minut", + "1_hour": "1 godzina" + }, + "template_list": { + "name": "Nazwa", + "owner": "Odpowiedzialny", + "tooltip": "Kody pól zostaną zastąpione odpowiednimi wartościami z karty", + "show_templates": "Pokaż kody pól", + "hide_templates": "Ukryj kody pól" + } + } + }, + "external_providers_select": { + "add_new_provider": "Dodaj dostawcę", + "no_available_providers": "Brak dostępnych dostawców" + }, + "sidebar": { + "title": "Automatyzacja", + "create_task": "Zadanie", + "task_create": "Zadanie", + "create_activity": "Aktywność", + "activity_create": "Aktywność", + "change_stage": "Zmień etap", + "entity_stage_change": "Zmień etap", + "entity_linked_stage_change": "Zmień etap w połączonej karcie", + "entity_responsible_change": "Zmień odpowiedzialnego", + "send_email": "E-mail", + "email_send": "E-mail", + "chat_send_amwork": "Wiadomość w czacie", + "chat_send_external": "Wiadomość w czacie zewnętrznym", + "entity_create": "Powiązana karta", + "http_call": "Żądanie HTTP", + "automation": "Automatyzacja" + }, + "block": { + "create_task": "Zadanie", + "task_create": "Zadanie", + "create_activity": "Aktywność", + "activity_create": "Aktywność", + "change_stage": "Zmień etap", + "entity_stage_change": "Zmień etap", + "entity_linked_stage_change": "Zmień etap połączonej", + "entity_responsible_change": "Odpowiedzialny", + "send_email": "E-mail", + "email_send": "E-mail", + "entity_create": "Karta", + "chat_send_amwork": "Czat", + "chat_send_external": "Czat zewnętrzny", + "http_call": "Żądanie HTTP" + } + } +} diff --git a/frontend/public/locales/pl/module.bpmn.json b/frontend/public/locales/pl/module.bpmn.json new file mode 100644 index 0000000..c172cdc --- /dev/null +++ b/frontend/public/locales/pl/module.bpmn.json @@ -0,0 +1,174 @@ +{ + "bpmn": { + "hooks": { + "use_get_service_task_options": { + "actions": { + "task_create": "Utwórz Zadanie", + "activity_create": "Utwórz Aktywność", + "entity_stage_change": "Zmień Etap", + "entity_linked_stage_change": "Zmień etap w połączonej karcie", + "entity_responsible_change": "Zmień Odpowiedzialnego", + "email_send": "Wyślij Email", + "entity_create": "Utwórz Kartę", + "chat_send_amwork": "Wyślij Wiadomość na Czat", + "chat_send_external": "Wyślij Wiadomość na Zewnętrzny Czat", + "http_call": "Wyślij żądanie HTTP" + } + }, + "use_bpmn_automations_columns": { + "name": "Nazwa", + "created_by": "Utworzone Przez", + "active": "Aktywne", + "delete": "Usuń", + "status": "Status" + }, + "use_get_workspace_event_options": { + "events": { + "create": "Wydarzenie Utworzenia Karty", + "change_responsible": "Wydarzenie Zmiany Właściciela", + "change_stage": "Wydarzenie Zmiany Etapu" + } + } + }, + "pages": { + "bpmn_automations_page": { + "automation_process_schema_error_warning": { + "continue": "Kontynuuj", + "title": "Nie udało się aktywować procesu \"{{name}}\"", + "annotation": "Walidacja schematu BPMN nie powiodła się. Sprawdź proces i spróbuj ponownie." + }, + "service_task_popup_template": { + "task_name": "Nazwa Zadania Serwisowego", + "placeholders": { + "task_name": "Nazwa" + } + }, + "workspace_sequence_flow_popup": { + "module": "Wybierz Moduł", + "module_hint": "Wybierz moduł, w którym muszą być spełnione określone warunki", + "stage": "Wybierz Etap", + "sequence_flow": "Przepływ Sekwencji", + "flow_name": "Nazwa Przepływu", + "conditions": "Warunki", + "placeholders": { + "flow_name": "Nazwa" + } + }, + "create_automation_process_modal": { + "create": "Utwórz", + "title": "Utwórz Nowy Proces Automatyzacji", + "name": "Nazwa Automatyzacji", + "new_process": "Nowy Proces #{{number}}", + "placeholders": { + "name": "Nazwa" + } + }, + "bpmn_automations_processes_modeler": { + "add_conditions": "Dodaj warunki...", + "center_view": "Widok Środkowy", + "pallete": { + "create_workspace_parallel_gateway": "Utwórz Bramę Paralelną", + "create_workspace_start_event": "Utwórz Zdarzenie Początkowe", + "create_workspace_end_event": "Utwórz Zdarzenie Końcowe", + "create_workspace_exclusive_gateway": "Utwórz Bramę Wyłączną", + "create_workspace_data_object": "Utwórz Obiekt Danych", + "create_workspace_data_store": "Utwórz Magazyn Danych", + "create_workspace_participant_expanded": "Utwórz Rozszerzonego Uczestnika", + "create_workspace_group": "Utwórz Grupę" + }, + "color_picker": { + "default_color": "Kolor Domyślny", + "blue_color": "Kolor Niebieski", + "orange_color": "Kolor Pomarańczowy", + "green_color": "Kolor Zielony", + "red_color": "Kolor Czerwony", + "purple_color": "Kolor Fioletowy" + }, + "context_pad": { + "delay": "Dodaj Opóźnienie", + "append_end_event": "Dołącz Zdarzenie Końcowe", + "append_gateway": "Dołącz Bramę", + "append_text_annotation": "Dołącz Adnotację Tekstową", + "replace": "Zastąp", + "delete": "Usuń", + "set_color": "Ustaw Kolor", + "connect": "Połącz", + "task_create": "Utwórz Zadanie", + "activity_create": "Utwórz Aktywność", + "entity_stage_change": "Zmień Etap", + "entity_linked_stage_change": "Zmień etap w połączonej karcie", + "entity_responsible_change": "Zmień Odpowiedzialnego", + "email_send": "Wyślij Email", + "entity_create": "Utwórz Kartę", + "chat_send_amwork": "Wyślij Wiadomość na Czat", + "chat_send_external": "Wyślij Wiadomość na Zewnętrzny Czat", + "http_call": "Wyślij żądanie HTTP" + }, + "toggle_default_context_pad_entry": "Przełącz Domyślny Warunek Przepływu", + "open_minimap": "Otwórz Mini-mapa", + "close_minimap": "Zamknij Mini-mapa", + "activate_hand_tool": "Aktywuj Narzędzie Ręczne", + "activate_lasso_tool": "Aktywuj Narzędzie Lasso", + "active_create_remove_space_tool": "Aktywuj Narzędzie Tworzenia/Usuwania Przestrzeni", + "activate_global_connect_tool": "Aktywuj Narzędzie Globalnego Połączenia", + "process_controls": { + "save_and_run": "Zapisz i Uruchom", + "save_draft": "Zapisz Szkic", + "start_process": "Rozpocznij Proces", + "stop_process": "Zatrzymaj Proces", + "cancel": "Anuluj" + }, + "create_workspace_start_event": "Utwórz Zdarzenie Początkowe", + "create_workspace_delay_event": "Utwórz Zdarzenie Opóźnienia", + "create_workspace_service_task": "Utwórz Zadanie Serwisowe" + }, + "workspace_delay_event_popup": { + "delay": "Opóźnienie", + "delay_label": "Opóźnienie *", + "delay_name": "Nazwa Opóźnienia", + "placeholders": { + "delay_name": "Nazwa" + } + }, + "delete_bpmn_automation_warning_modal": { + "title": "Czy na pewno chcesz usunąć Automatyzację BPMN \"{{name}}\"?", + "annotation": "Tej operacji nie można cofnąć." + }, + "create_workspace_service_task_menu": { + "workspace_service_tasks": "Zadania Serwisowe" + }, + "create_workspace_start_event_menu": { + "workspace_events": "Zdarzenia" + }, + "workspace_start_event_popup": { + "event_name": "Nazwa Zdarzenia", + "event_type": "Wybierz Typ Zdarzenia *", + "module": "Wybierz Moduł *", + "module_hint": "Wybierz moduł, w którym zdarzenie zostanie uruchomione", + "placeholders": { + "event_name": "Nazwa" + } + }, + "bpmn_automations_settings_header": { + "create_bpmn_automation": "Utwórz Automatyzację BPMN", + "bpmn_automations": "Automatyzacje BPMN 2.0", + "back": "Wstecz" + }, + "bpmn_automations_table": { + "no_bpmn_automations_block": { + "annotation1": "Nie masz jeszcze procesów", + "annotation2": "Użyj przycisku", + "annotation3": "aby dodać automatyzację." + } + }, + "bpmn_splashscreen": { + "heading": "Profesjonalna automatyzacja BPM", + "price_ru": "za 99 ₽/miesiąc za użytkownika, przy płatności rocznej", + "price_us": "za $1/miesiąc za użytkownika, przy płatności rocznej", + "form_button": "Złóż wniosek", + "form_header": "Aktywuj automatyzację BPM" + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.builder.json b/frontend/public/locales/pl/module.builder.json new file mode 100644 index 0000000..61a9242 --- /dev/null +++ b/frontend/public/locales/pl/module.builder.json @@ -0,0 +1,867 @@ +{ + "builder": { + "components": { + "common": { + "next": "Dalej", + "back": "Wstecz", + "save": "Zapisz", + "workspace_builder": "Kreator miejsca pracy", + "your_workspace": "Twoje miejsce pracy", + "no_links": "Brak dostępnych modułów do połączenia." + } + }, + "pages": { + "site_form_builder_page": { + "steps": { + "save_and_leave": "Zapisz i opuść", + "default_title": "Formularz na stronę", + "default_form_title": "Skontaktuj się z nami", + "default_consent_text": "Używamy plików cookie, aby poprawić działanie strony. Pliki cookie pomagają zapewnić bardziej spersonalizowane doświadczenia dla Ciebie i analizy dla nas. Więcej informacji znajdziesz w naszej Polityce prywatności.", + "default_consent_link_text": "Polityka prywatności", + "default_gratitude_header": "Dziękujemy!", + "default_gratitude_text": "Nasi menedżerowie skontaktują się z Tobą w godzinach pracy.", + "default_form_button_text": "Wyślij", + "default_client_button_text": "Otwórz formularz", + "common_error": "Wypełnij wszystkie obowiązkowe pola.", + "step1": { + "title": "Krok 1", + "description": "Podstawowa konfiguracja", + "error": "Wypełnij wszystkie obowiązkowe pola w pierwszym kroku." + }, + "step2": { + "title": "Krok 2", + "description": "Pola formularza", + "error": "Wypełnij wszystkie obowiązkowe pola w drugim kroku." + }, + "step3": { + "title": "Krok 3", + "description": "Polityka prywatności i strona z podziękowaniem", + "consent_error": "Wypełnij wszystkie obowiązkowe pola w sekcji 'Polityka prywatności' na trzecim kroku." + }, + "step4": { + "title": "Krok 4", + "description": "Projekt formularza" + }, + "step5": { + "title": "Krok 5", + "description": "Skrypt instalacji formularza" + } + }, + "site_form_builder_step1": { + "title": "Krok 1. Konfiguracja formularza", + "title_input_name": "Wpisz nazwę formularza", + "choose_main_card": "Wybierz główną kartę, która zostanie utworzona po wysłaniu formularza", + "choose_linked_cards": "Wybierz powiązane karty, które zostaną utworzone po wysłaniu formularza", + "linked_entities_select": "Wybierz moduły do tworzenia kart po przesłaniu formularza z Twojej strony", + "choose_board_annotation": "Wybierz tablicę, na której będzie tworzona karta", + "card_settings": "Konfiguracja tworzenia karty", + "responsible_user": "Odpowiedzialny użytkownik", + "check_duplicates": "Unikaj duplikatów", + "check_duplicates_hint": "Jeśli ta opcja jest włączona, przesłanie formularza z istniejącym w systemie e-mailem lub numerem telefonu nie utworzy nowej karty, lecz powiąże istniejącą.", + "yes": "Tak", + "placeholders": { + "title_input": "Nazwa formularza", + "responsible_select": "Odpowiedzialny", + "board": "Tablica" + } + }, + "site_form_builder_step2": { + "sidebar": { + "title": "Elementy formularza", + "header_title": "Nagłówek formularza", + "header_hint": "Może być tylko jeden.", + "delimiter_title": "Separator", + "delimiter_hint": "Linia oddzielająca różne sekcje formularza.", + "field_attributes": { + "title": "Atrybuty pola", + "hint": "Nagłówek jest wyświetlany nad polem, a opis wewnątrz niego.", + "label_name": "Nagłówek", + "placeholder_name": "Opis" + }, + "card_name_block": { + "card_title": "Nazwa karty", + "company_title": "Nazwa firmy", + "contact_title": "Imię i nazwisko", + "card_text": "Nazwa", + "company_text": "Nazwa", + "contact_text": "Imię i nazwisko" + }, + "field_elements": { + "no_available_fields": "Brak dostępnych pól", + "email": "E-mail", + "phone": "Telefon", + "short_text": "Krótki tekst", + "long_text": "Długi tekst", + "number": "Liczba", + "value": "Wartość", + "date": "Data", + "select": "Wybierz", + "multiselect": "Wielokrotny wybór", + "colored_select": "Kolorowy wybór", + "colored_multiselect": "Kolorowy wielokrotny wybór", + "link": "Link", + "file": "Plik", + "switch": "Przełącznik" + } + }, + "tree": { + "element_name": { + "placeholder": "Wpisz nagłówek formularza" + }, + "entity_field_element": { + "placeholder": "Wpisz opis pola", + "template": { + "company_name": "Nazwa", + "contact_name": "Imię i nazwisko", + "card_name": "Nazwa karty", + "short_text": "Krótki tekst", + "long_text": "Długi tekst", + "field": "Pole", + "delimiter": "Separator", + "file": "Plik", + "schedule": "Wybór kalendarza", + "schedule_performer": "Wybór wykonawcy", + "schedule_date": "Wybór daty", + "schedule_time": "Wybór godziny", + "placeholder": "Wpisz nazwę pola", + "required": "Obowiązkowe pole", + "validation": "Walidacja pola", + "field_type": "Typ pola:", + "linked_with": "Powiązane z modułem:" + } + } + } + }, + "site_form_builder_step3": { + "show_in_form_control": { + "yes": "Tak" + }, + "consent_block": { + "title": "Polityka przetwarzania danych osobowych", + "controls": { + "show_consent_title": "Pokaż politykę prywatności", + "text_title": "Tekst polityki prywatności", + "text_hint": "Tekst będzie wyświetlany na końcu formularza.", + "link_text_title": "Tekst linku", + "link_text_hint": "Tekst wyświetlany na linku", + "link_title": "Link", + "link_hint": "Wpisz URL polityki prywatności w formacie https://example.com/.", + "consent_by_default_title": "Checkbox zaznaczony domyślnie", + "placeholders": { + "text": "Tekst polityki prywatności", + "link_text": "Wpisz tekst linku", + "link": "Wklej link" + } + }, + "skeleton": { + "button_text": "Wyślij" + } + }, + "gratitude_block": { + "title": "Strona z podziękowaniem", + "show_gratitude_title": "Pokaż stronę z podziękowaniem", + "gratitude_text": "Tekst podziękowania", + "placeholders": { + "header": "Wpisz nagłówek", + "text": "Wpisz tekst podziękowania" + } + } + }, + "site_form_builder_step4": { + "sidebar": { + "custom_css_block": { + "custom_css": "Niestandardowy CSS", + "enable_custom_css": "Włącz niestandardowy CSS", + "placeholders": { + "custom_css": "Reguły niestandardowego CSS" + } + }, + "advanced_settings": "Ustawienia zaawansowane", + "modal_overlay_customization_block": { + "modal_overlay": "Nakładka modalna", + "overlay_display": "Wyświetlanie nakładki", + "background_color": "Kolor tła", + "opacity": "Nieprzezroczystość" + }, + "switch_label": "Włącz", + "title": "Projekt formularza", + "description": "Zmiany wchodzą w życie po zapisaniu.", + "powered_by_logo_block": { + "title": "{{company}} Logo", + "switch_label": "Włącz" + }, + "header_customization_block": { + "title": "Nagłówek", + "text_color": "Kolor tekstu", + "text_align_title": "Wyrównanie", + "text_align_left": "Do lewej", + "text_align_center": "Do środka", + "text_align_right": "Do prawej" + }, + "fields_customization_block": { + "title": "Pola", + "background_color": "Kolor tła", + "title_color": "Kolor nagłówka", + "text_color": "Kolor tekstu w polach" + }, + "form_button_customization_block": { + "title": "Przycisk", + "background_color": "Kolor przycisku", + "text_color": "Kolor tekstu przycisku", + "border": "Zaokrąglenie rogów", + "border_rounded": "Zaokrąglone", + "border_squared": "Kwadratowe", + "size": "Rozmiar przycisku", + "small": "Mały", + "medium": "Średni", + "large": "Duży", + "text_input": "Tekst przycisku", + "hint": "Przycisk wysyłania, umieszczony na końcu formularza", + "placeholders": { + "text_input": "Wyślij" + } + }, + "client_button_customization_block": { + "title": "Przycisk na stronę", + "background_color": "Kolor przycisku", + "text_color": "Kolor tekstu przycisku", + "border": "Krawędzie", + "border_rounded": "Zaokrąglone", + "border_squared": "Kwadratowe", + "size": "Rozmiar przycisku", + "small": "Mały", + "medium": "Średni", + "large": "Duży", + "text_input": "Tekst przycisku", + "hint": "Przycisk otwierający okno modalne z formularzem", + "placeholders": { + "text_input": "Otwórz" + } + }, + "form_layout_customization_block": { + "title": "Formularz", + "view_title": "Widok", + "view_built_in": "Wbudowany", + "view_modal": "Okno modalne", + "position_title": "Pozycja formularza", + "position_left": "Po lewej", + "position_center": "Do środka", + "position_right": "Po prawej", + "border_radius_title": "Zaokrąglenie krawędzi", + "border_rounded": "Zaokrąglone", + "border_none": "Bez zaokrągleń", + "orientation_title": "Orientacja", + "orientation_horizontal": "Pozioma", + "orientation_vertical": "Pionowa", + "max_width": "Maksymalna szerokość", + "max_height": "Maksymalna wysokość", + "background_color": "Kolor tła" + }, + "size_customization_element_template": { + "switch_label": "Włącz", + "placeholders": { + "input": "Wpisz wartość" + } + } + }, + "preview": { + "iframe_title": "{{companyName}} – Podgląd formularza", + "error": "Błąd podczas ładowania podglądu", + "open_in_a_new_tab": "Otwórz w nowej karcie", + "open_preview": "Otwórz podgląd", + "phone": "Telefon", + "tablet": "Tablet", + "computer": "Komputer", + "form": "Formularz", + "gratitude": "Strona podziękowania", + "button": "Przycisk" + } + }, + "site_form_builder_step5": { + "title": "Krok 5. Skopiuj kod formularza na swoją stronę", + "copy_form_code": "Skopiuj kod formularza", + "form_code": "Kod formularza", + "multiform_mode": "Tryb zgodności wielu formularzy", + "multiform_mode_hint": "Włącz ten tryb, jeśli na stronie, na którą chcesz wstawić formularz, znajduje się już inny formularz stworzony w naszym kreatorze. Umożliwi to uniknięcie konfliktów wyświetlania.", + "form_will_be_mounted_here": "Formularz zostanie osadzony tutaj", + "public_link_title": "Publiczna strona formularza", + "public_link_annotation": "Użyj poniższego linku, aby przejść do publicznej strony formularza. Jeśli nie musisz osadzać formularza na swojej stronie, udostępnij tę stronę swoim użytkownikom, aby mogli ją wypełnić. Formularz jest gotowy do użycia i aktualizuje się automatycznie po wprowadzeniu zmian.", + "instruction_title": "Instrukcje dotyczące integracji formularza na Twojej stronie", + "view_form": "Zobacz formularz", + "static_site_integration_title": "Integracja na statycznych stronach i stronach tworzonych za pomocą kreatorów", + "first_point": { + "title": "1. Dodawanie formularza za pomocą klasy CSS", + "annotation": "Przypisz klasę {{mountClass}} do elementów na swojej stronie, w których chcesz osadzić formularz. Skopiowany powyżej kod formularza wstaw do tagu lub na sam koniec tagu . Formularz wyświetli się wewnątrz elementów z określoną klasą. Jeśli element ma już zawartość, formularz pojawi się na końcu tej zawartości. Jeśli wybrałeś wyświetlanie formularza jako okno popup, w określonym miejscu zostanie osadzony przycisk, po kliknięciu którego otworzy się okno popup z formularzem. To jest preferowany sposób osadzania formularza na stronie." + }, + "second_point": { + "title": "2. Dodawanie formularza bez klas CSS", + "annotation": "Jeśli nie możesz przypisać klasy elementom, wklej kod formularza w miejsce na swojej stronie, gdzie chcesz go wyświetlić. Kod formularza można osadzić w dowolnym miejscu strony. W takim przypadku formularz zostanie wyświetlony w miejscu, gdzie wklejono kod. Tym sposobem można osadzić tylko jeden formularz na stronie." + }, + "ssr_integration_title": "Integracja na stronach z wykorzystaniem SSR (np. tworzonych za pomocą Next.js, Astro itp.)", + "ssr_integration_annotation": "Aby uniknąć problemów z hydratacją (ładowaniem skryptów w znaczniku HTML), zaleca się użycie metody określenia klasy workspace-form-builder-mount-container dla elementu, wewnątrz którego chcesz osadzić formularz (opisanej bardziej szczegółowo w punkcie 2 powyżej). W takim przypadku skrypt formularza należy umieścić najlepiej w tagu .", + "analytics": { + "title": "Śledzenie zdarzeń i celów analitycznych", + "annotation": "Formularz obsługuje wysyłanie zdarzeń i celów do systemów analitycznych. Jeśli na stronie jest zainstalowany Google Analytics lub Yandex.Metrica, możesz śledzić działania użytkowników w formularzu. Aby to zrobić, skonfiguruj zdarzenia lub cele w swoim systemie analitycznym i przypisz je do identyfikatorów wskazanych w tabeli.", + "table_title": "Śledzone zdarzenia", + "event_title": "Nazwa", + "event_id": "ID celu", + "form_view": "Wyświetlenie formularza", + "form_start": "Rozpoczęcie wypełniania", + "field_fill": "Wypełnienie pola “{{field}}”", + "form_submit": "Wysłanie formularza" + } + } + }, + "headless_site_form_builder_page": { + "steps": { + "save_and_leave": "Zapisz i wyjdź", + "default_title": "Formularz na stronę do integracji przez API", + "common_error": "Wypełnij wszystkie wymagane pola.", + "step1": { + "title": "Krok 1", + "description": "Podstawowa konfiguracja", + "error": "Wypełnij wszystkie wymagane pola w kroku 1." + }, + "step2": { + "title": "Krok 2", + "description": "Pola formularza" + }, + "step3": { + "title": "Krok 3", + "description": "Instrukcja integracji formularza" + } + }, + "site_form_builder_step3": { + "title": "Krok 3. Zintegruj formularz ze stroną", + "json_integration_title": "Wysyłanie danych w formacie JSON", + "api_integration_instruction": "Ta metoda nadaje się, jeśli przetwarzasz formularze na swoim serwerze. Aby wysłać dane użytkownika z formularza do {{company}}, należy wykonać zapytanie z parametrami wskazanymi poniżej. W treści zapytania przekaż dane użytkownika w formacie JSON. Przykład treści zapytania znajduje się poniżej.", + "api_integration_request_annotation": "Metoda: POST\nNagłówek: Content-Type: application/json\nURL: {{endpoint}}", + "request_parameters_title": "Parametry zapytania", + "request_body_title": "Treść zapytania", + "field_value_explanation": "W polu value wpisz wartość wprowadzaną przez użytkownika. Format wartości dla każdego pola znajduje się poniżej.", + "field_value_title": "Wartości pól", + "field_value": "Wartość pola {{label}}", + "response_title": "Format odpowiedzi", + "response_explanation": "W przypadku sukcesu serwer zwraca kod HTTP 201.", + "formdata_integration_title": "Wysyłanie danych w formacie FormData", + "formdata_integration_instruction": "Ta metoda nadaje się do wysyłania formularzy z witryn stworzonych za pomocą narzędzi, takich jak Tilda. Dane są przesyłane w formacie x-www-form-urlencoded. Aby wysyłać dane poprawnie, wystarczy podać poniżej URL webhooka i przypisać zmienne do pól.", + "formdata_webhook_url": "URL webhooka", + "formdata_fields_title": "Zmienne pól", + "formdata_fields_explanation": "Każde pole formularza musi mieć nazwę (zmienną), aby powiązać je z odpowiednim polem w systemie. W tym przypadku zmienne pól to ich numeryczne identyfikatory. Aby dane zostały przesłane poprawnie, przypisz każdemu polu formularza zmienną wskazaną w poniższej tabeli.", + "fields_table": { + "field_title": "Nazwa pola", + "field_format": "Format pola", + "field_example": "Przykład", + "field_id": "Zmienna (ID pola)", + "format": { + "text": "Ciąg tekstowy w cudzysłowie", + "link": "Ciąg tekstowy bez spacji w cudzysłowie", + "phone": "Ciąg tekstowy bez spacji w cudzysłowie", + "email": "Ciąg tekstowy bez spacji w cudzysłowie", + "value": "Ciąg tekstowy w cudzysłowie", + "select": "Numer identyfikujący wybraną przez użytkownika wartość", + "switch": "Wartość logiczna bez cudzysłowów", + "number": "Liczba bez cudzysłowów", + "multiselect": "Tablica numerów identyfikujących wybrane przez użytkownika wartości", + "date": "Ciąg z datą w formacie ISO 8601 ujętym w cudzysłów" + }, + "example": { + "text": "\"Wartość wprowadzona przez użytkownika\"", + "link": "\"https://example.com\"", + "phone": "\"12345678910\"", + "email": "\"form@example.com\"", + "value": "\"Wartość wprowadzona przez użytkownika\"", + "select": "34", + "switch": "true", + "number": "42", + "multiselect": "[26, 27]", + "date": "\"2024-12-31T00:00:00\"" + } + } + } + }, + "online_booking_site_form_builder_page": { + "steps": { + "save_and_leave": "Zapisz i wyjdź", + "default_title": "Formularz rezerwacji online", + "default_form_title": "Rezerwacja online", + "default_consent_text": "Używamy plików cookie, aby poprawić działanie strony. Zapewniają one spersonalizowane doświadczenie i analizę ruchu. Więcej informacji znajdziesz w naszej Polityce prywatności.", + "default_consent_link_text": "Polityka prywatności", + "default_gratitude_header": "Dziękujemy!", + "default_gratitude_text": "Nasi menedżerowie skontaktują się z Tobą, aby potwierdzić rezerwację.", + "default_form_button_text": "Wyślij", + "default_client_button_text": "Otwórz formularz", + "schedule_field_title": "Kalendarz", + "schedule_field_placeholder": "Wybierz kalendarz...", + "schedule_performer_field_title": "Wykonawca", + "schedule_performer_field_placeholder": "Wybierz wykonawcę...", + "schedule_date_field_title": "Data", + "schedule_date_field_placeholder": "Wybierz datę...", + "schedule_time_field_title": "Godzina", + "schedule_time_field_placeholder": "Wybierz godzinę...", + "common_error": "Wypełnij wszystkie wymagane pola.", + "step1": { + "title": "Krok 1", + "description": "Podstawowa konfiguracja", + "error": "Wypełnij wszystkie wymagane pola w pierwszym kroku." + }, + "step2": { + "title": "Krok 2", + "description": "Pola formularza", + "error": "Wypełnij wszystkie wymagane pola w drugim kroku." + }, + "step3": { + "title": "Krok 3", + "description": "Polityka prywatności i strona podziękowania", + "consent_error": "Wypełnij wszystkie wymagane pola w sekcji „Polityka prywatności” w trzecim kroku." + }, + "step4": { + "title": "Krok 4", + "description": "Projekt formularza" + }, + "step5": { + "title": "Krok 5", + "description": "Skrypt instalacyjny formularza" + } + }, + "online_booking_site_form_builder_step1": { + "title": "Krok 1. Konfiguracja formularza", + "title_input_name": "Wpisz nazwę formularza", + "choose_schedulers": "Wybierz kalendarze, w których będą tworzone rezerwacje", + "choose_linked_cards": "Wybierz powiązane karty tworzone po rezerwacji", + "linked_entities_select": "Wybierz moduły, w których utworzyć karty po wysłaniu formularza", + "choose_board_annotation": "Wybierz tablicę, na której utworzy się karta", + "no_schedules": "Nie utworzyłeś jeszcze żadnego kalendarza wizyt. Aby dodać formularz rezerwacji, utwórz moduł „Kalendarz wizyt” i skonfiguruj w nim rezerwacje online.", + "no_linked_cards": "Brak dostępnych modułów do tworzenia powiązanych kart. Aby dodać powiązania, wybierz kalendarz z przypisanym modułem. Jeśli wybrałeś kilka kalendarzy, upewnij się, że są one powiązane z tym samym modułem.", + "limit_days_name": "Limit dni na rezerwację", + "card_settings": "Konfiguracja tworzenia karty", + "responsible_user": "Odpowiedzialny użytkownik", + "check_duplicates": "Unikaj duplikatów", + "check_duplicates_hint": "Jeśli ta opcja jest włączona, przesłanie formularza z istniejącym w systemie e-mailem lub numerem telefonu nie utworzy nowej karty, lecz powiąże istniejącą.", + "yes": "Tak", + "placeholders": { + "title_input": "Nazwa formularza", + "responsible_select": "Odpowiedzialny", + "board": "Tablica", + "limit_days": "Od 1 do 730" + } + } + }, + "workspace_editor_page": { + "edit": "Edytuj", + "delete": "Usuń", + "warn_title": "Uwaga!", + "warn_annotation": "Usunięcie modułu spowoduje trwałe usunięcie wszystkich powiązanych danych. Czy na pewno chcesz kontynuować?", + "open_module": "Otwórz moduł", + "entity_type_field_used_in_formula_warning_modal": { + "title": "Nie udało się usunąć tego modułu", + "annotation": "Niektóre pola w tym module są powiązane z formułą w innym module. Proszę zaktualizować ustawienia formuły i spróbować ponownie.", + "continue": "Kontynuuj" + } + }, + "scheduler_builder_page": { + "steps": { + "save_error": "Podaj nazwę modułu i wybierz co najmniej jednego użytkownika w pierwszym kroku.", + "step1": { + "label": "1. krok", + "description": "Skonfiguruj nowy moduł" + }, + "step2": { + "label": "2. krok", + "description": "Połącz harmonogram z innym modułem w swoim miejscu pracy" + }, + "step3": { + "label": "3. krok", + "description": "Zintegruj harmonogram z modułem produktów w swoim miejscu pracy" + }, + "default_title": "Harmonogram wizyty" + }, + "scheduler_builder_step1": { + "title": "Personalizuj moduł", + "name_the_module": "Nazwij moduł", + "name_the_module_hint": "Ta nazwa będzie wyświetlana w lewym pasku bocznym. Będziesz mógł ją zmienić w przyszłości.", + "choose_icon": "Wybierz ikonę dla lewego paska bocznego", + "choose_icon_hint": "Ta ikona będzie wyświetlana w lewym pasku bocznym. Będziesz mógł ją zmienić w przyszłości.", + "for_users": "Dla użytkowników", + "for_user_groups": "Dla grup użytkowników", + "view_type": "Wybierz typ harmonogramu wizyty", + "view_type_hint": "Wybierz typ widoku – Widok czasu bocznego (Pionowy widok czasu) lub Widok czasu górnego (Poziomy widok czasu).", + "schedule": "Widok czasu bocznego", + "schedule_img_alt": "{{company}} – Podgląd interfejsu harmonogramu", + "board": "Widok czasu górnego", + "board_img_alt": "{{company}} – Podgląd interfejsu tablicy", + "enter_the_interval": "Wprowadź interwał", + "maximum_number_of_records": "Maksymalna liczba rekordów", + "error": "Proszę wypełnić wszystkie wymagane pola.", + "change_anyway": "Zmień mimo to", + "warning_title": "Czy na pewno chcesz zmienić ten parametr?", + "warning_annotation": "Wszystkie istniejące wizyty zostaną usunięte.", + "for_whom": "Wybierz użytkowników lub grupy użytkowników", + "for_whom_hint": "Wybierz typ widoku harmonogramu – dla użytkowników lub grupy użytkowników.", + "no_duration": "Nie", + "minutes": "minuty", + "schedule_params_title": "Harmonogram i rezerwacje online", + "schedule_params_hint": "Skonfiguruj dostępność i parametry spotkań w kalendarzu. Zostaną użyte do wyświetlania harmonogramu i zarządzania rezerwacjami online.", + "time_buffer_before": "Przerwa przed spotkaniem", + "time_buffer_after": "Przerwa po spotkaniu", + "intervals_source": "Użyj dostępności", + "performers_intervals": "użytkowników", + "scheduler_intervals": "tego kalendarza", + "placeholders": { + "interval": "Interwał", + "unlimited": "Nieograniczony", + "module_name": "Nazwa modułu" + }, + "change_type_warning": { + "title": "Uwaga!", + "annotation": "Zmiana typu kalendarza spowoduje trwałe usunięcie wszystkich zapisów wizyt. Czy na pewno chcesz kontynuować?", + "approve": "Potwierdź" + }, + "change_performers_warning": { + "title": "Uwaga!", + "annotation": "Usunięcie wykonawcy z kalendarza spowoduje trwałe usunięcie wszystkich jego zapisów wizyt. Czy na pewno chcesz kontynuować?", + "approve": "Potwierdź" + } + }, + "scheduler_builder_step2": { + "title": "Połącz z innym modułem", + "do_not_link": "Nie łącz z innym modułem", + "duplicate_title": "Zablokuj duplikaty wizyt", + "duplicate_hint": "Po włączeniu tej opcji w kalendarzu nie będzie można zaplanować kilku spotkań dla tej samej karty w jednym dniu." + }, + "scheduler_builder_step3": { + "title": "Zintegruj z modułem produktów", + "do_not_integrate": "Nie integruj z modułem produktów" + } + }, + "products_section_builder_page": { + "delay_select": { + "no_return": "Nie zwracaj", + "no_return_minified": "Nie", + "24_hours": "24 godziny", + "24_hours_minified": "24h", + "48_hours": "48 godzin", + "48_hours_minified": "48h", + "72_hours": "72 godziny", + "72_hours_minified": "72h", + "7_days": "7 dni", + "7_days_minified": "7d", + "10_days": "10 dni", + "10_days_minified": "10d", + "14_days": "14 dni", + "14_days_minified": "14d", + "28_days": "28 dni", + "28_days_minified": "28d", + "30_days": "30 dni", + "30_days_minified": "30d" + }, + "steps": { + "save_common_error": "Nie udało się zapisać. Niektóre kroki wymagają uzupełnienia informacji.", + "save_warehouses_error": "Musisz utworzyć co najmniej jeden magazyn, aby kontynuować. Alternatywnie, możesz wyłączyć magazyny.", + "step1": { + "label": "1. krok", + "description": "Skonfiguruj nowy moduł" + }, + "step2": { + "label": "2. krok", + "description": "Utwórz kategorie produktów (grupy produktów)" + }, + "step3": { + "label": "3. krok", + "description": "Skonfiguruj magazyny dla swoich produktów" + }, + "step4": { + "rentals": { + "label": "4. krok", + "description": "Skonfiguruj ustawienia harmonogramu dla swoich produktów wynajmu" + }, + "sales": { + "label": "4. krok", + "description": "Połącz z innymi modułami w swoim miejscu pracy" + } + }, + "step5": { + "label": "5. krok", + "description": "Połącz z innymi modułami w swoim miejscu pracy" + }, + "default_titles": { + "sale": "Magazyn i zarządzanie produktami", + "rental": "Zarządzanie wynajmem" + } + }, + "product_section_builder_step1": { + "title": "Personalizuj moduł", + "name_the_section": "Nazwij moduł", + "name_the_section_hint": "Ta nazwa będzie wyświetlana w lewym pasku bocznym. Będziesz mógł ją zmienić w przyszłości.", + "choose_icon": "Wybierz ikonę dla lewego paska bocznego", + "choose_icon_hint": "Ta ikona będzie wyświetlana w lewym pasku bocznym. Będziesz mógł ją zmienić w przyszłości.", + "error": "Nazwa modułu musi być podana.", + "placeholders": { + "section_name": "Nazwa modułu" + } + }, + "product_section_builder_step3": { + "cancel_after_label": "Ustaw okres automatycznego zwolnienia rezerwacji pozycji w zamówieniach", + "cancel_after_hint": "Możesz skonfigurować automatyczne anulowanie rezerwacji pozycji w zamówieniach. To pomaga zapobiec nieprawidłowym poziomom zapasów, które mogą wystąpić, gdy pozycje są zarezerwowane w transakcjach, ale te transakcje nie są przetwarzane lub zostały anulowane.", + "update_error": "Nie udało się zapisać wyboru. Spróbuj ponownie.", + "warehouses_error": "Musisz utworzyć co najmniej jeden magazyn, aby kontynuować. Alternatywnie, możesz wyłączyć magazyny.", + "enable_warehouses_label": "Włącz magazyny", + "enable_warehouses_hint": "Włącz magazyny, aby śledzić stany magazynowe swoich produktów. Jeśli ta opcja jest włączona, będziesz musiał utworzyć co najmniej jeden magazyn, aby kontynuować.", + "enable_barcodes_label": "Włącz kody kreskowe", + "enable_barcodes_hint": "Włącz kody kreskowe, aby śledzić stany magazynowe swoich produktów. Ta opcja pozwoli Ci skanować kody kreskowe produktów.", + "on": "WŁ" + }, + "products_section_builder_link_sections_step": { + "title": "Połącz z innymi modułami", + "cards": "Karty", + "schedulers": "Harmonogramy", + "do_not_integrate": "Nie integruj z modułem harmonogramów" + }, + "products_section_builder_schedule_settings_step": { + "title": "Ustawienia harmonogramu", + "rental_duration_interval": "Interwał czasu wynajmu", + "rental_duration_interval_hint": "Wybierz interwał dla czasu wynajmu.", + "start_of_rental_interval": "Początek interwału wynajmu", + "start_of_rental_interval_hint": "Określ czas, kiedy zaczyna się wynajem.", + "twenty_four_hours": "24 godziny" + } + }, + "et_section_builder_page": { + "steps": { + "requisites_codes": { + "ie_name": "Imię Przedsiębiorcy Indywidualnego", + "ie_surname": "Nazwisko Przedsiębiorcy Indywidualnego", + "ie_patronymic": "Drugie Imię Przedsiębiorcy Indywidualnego", + "org_name": "Nazwa Firmy", + "org_tin": "NIP Firmy", + "org_trrc": "KPP Firmy", + "org_psrn": "PSRN", + "org_type": "Typ Firmy", + "org_full_name": "Pełna Nazwa Firmy", + "org_short_name": "Krótka Nazwa Firmy", + "org_management_name": "Imię Dyrektora", + "org_management_post": "Stanowisko Dyrektora", + "org_management_start_date": "Data Rozpoczęcia Kadencji Dyrektora", + "org_branch_count": "Liczba Oddziałów", + "org_branch_type": "Typ Oddziału", + "org_address": "Adres Firmy", + "org_reg_date": "Data Rejestracji", + "org_liquidation_date": "Data Likwidacji", + "org_status": "Status Firmy", + "stat_okato": "OKATO", + "stat_oktmo": "OKTMO", + "stat_okpo": "OKPO", + "stat_okogu": "OKOGU", + "stat_okfs": "OKFS", + "stat_okved": "OKVED", + "org_extra_employee_count": "Średnia Liczba Pracowników", + "org_extra_founders": "Założyciele Firmy", + "org_extra_managers": "Kadra Zarządzająca", + "org_extra_capital": "Kapitał Zakładowy", + "org_extra_licenses": "Licencje", + "org_extra_phones": "Numery Telefonów", + "org_extra_emails": "Adresy E-mail", + "bank_name": "Nazwa Banku", + "bank_bic": "BIC Banku", + "bank_swift": "SWIFT", + "bank_tin": "NIP Banku", + "bank_trrc": "KPP Banku", + "bank_correspondent_acc": "Konto Korespondencyjne", + "bank_payment_city": "Miasto dla Zlecenia Płatniczego", + "bank_opf_type": "Typ Instytucji Kredytowej", + "bank_checking_account": "Konto Sprawdzenie" + }, + "save_error": "Nie udało się zapisać. Niektóre kroki wymagają uzupełnienia informacji.", + "details": "Szczegóły", + "analytics": "Analityk", + "requisites": "Wymagania", + "step1": { + "label": "1. krok", + "description": "Skonfiguruj nowy moduł" + }, + "step2": { + "label": "2. krok", + "description": "Wybierz nazwę i utwórz pola dla nowej karty" + }, + "step3": { + "label": "3. krok", + "description": "Dodaj funkcjonalność do karty" + }, + "step4": { + "label": "4. krok", + "description": "Połącz z innymi modułami w swoim miejscu pracy" + }, + "default_titles": { + "builder": "Kreator", + "project": "Projekty i zadania", + "production": "Produkcja", + "universal": "Moduł uniwersalny", + "partner": "Partnerzy", + "deal": "CRM", + "contact": "Kontakty", + "company": "Firmy", + "supplier": "Dostawcy", + "contractor": "Wykonawcy", + "hr": "Rekrutacja" + } + }, + "tasks_fields_options": { + "planned_time": "Szacowany czas", + "board_name": "Nazwa tablicy", + "assignee": "Przydzielony", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "description": "Opis", + "subtasks": "Podzadania" + }, + "et_section_builder_step1": { + "title": "Personalizuj moduł", + "name_the_section": "Nazwij moduł", + "name_the_section_hint": "Ta nazwa będzie wyświetlana w lewym pasku bocznym. Będziesz mógł ją zmienić w przyszłości.", + "choose_icon": "Wybierz ikonę dla lewego paska bocznego", + "choose_icon_hint": "Ta ikona będzie wyświetlana w lewym pasku bocznym. Będziesz mógł ją zmienić w przyszłości.", + "type_of_display": "Typ wyświetlania", + "type_of_display_hint": "Wybierz typ interfejsu – lista lub tablica/lejek lub oba.", + "board": "Tablica", + "board_img_alt": "{{company}} – Podgląd interfejsu tablicy", + "list": "Lista", + "list_img_alt": "{{company}} – Podgląd interfejsu listy", + "placeholders": { + "section_name": "Nazwa modułu" + } + }, + "et_section_builder_step2": { + "title": "Skonfiguruj parametry nowej karty", + "name_the_card": "Nazwij kartę", + "name_the_card_hint": "Nazwa karty.", + "fine_tuning": "Dopasowanie karty", + "create_fields": "Utwórz pola dla nowej karty", + "create_fields_hint": "Pola są głównymi elementami informacyjnymi karty, możesz tworzyć różne grupy i typy pól.", + "placeholders": { + "card_name": "Nazwa karty" + } + }, + "et_section_builder_step3": { + "title": "Dodaj funkcjonalność do karty", + "error": "Proszę wybrać co najmniej jedną funkcję.", + "features": { + "saveFiles": "Pliki", + "tasks": "Zadania", + "notes": "Notatki", + "chat": "Czat", + "activities": "Aktywności", + "documents": "Tworzenie dokumentów", + "products": "Produkty" + } + }, + "et_section_builder_step4": { + "title": "Połącz z innymi modułami", + "cards": "Karty", + "products": "Produkty", + "schedulers": "Harmonogramy", + "do_not_integrate": "Nie integruj z modułem harmonogramów" + }, + "entity_type_used_in_formula_warning_modal": { + "title": "Moduł jest używany we wzorze", + "annotation": "Nie można usunąć relacji z modułem, ponieważ jego pole jest używane w formule." + } + }, + "builder_journey_picker_page": { + "request_bpmn_form_modal": { + "error": "Wystąpił błąd podczas przesyłania Twojego żądania. Upewnij się, że wprowadzone informacje są prawidłowe, i spróbuj ponownie.", + "send_request": "Wyślij zgłoszenie", + "header": "BPMN – Zostaw zgłoszenie, a my się z Tobą skontaktujemy", + "full_name": "Pełne imię i nazwisko *", + "phone": "Telefon *", + "email": "Email *", + "comment": "Komentarz", + "placeholders": { + "full_name": "Jan Kowalski", + "comment": "Dodatkowe informacje" + }, + "us_price_full": "Podłącz BPMN za $1 miesiąc na miesiąc!", + "ru_price_full": "Podłącz BPMN za 99₽ miesiąc na miesiąc!" + }, + "request_additional_storage_modal": { + "error": "Wystąpił błąd podczas wysyłania zgłoszenia. Sprawdź swoje dane i spróbuj ponownie.", + "send_request": "Wyślij zgłoszenie", + "header": "Dodatkowa przestrzeń – Wyślij zgłoszenie, a skontaktujemy się z Tobą", + "full_name": "Imię i nazwisko *", + "phone": "Telefon *", + "email": "E-mail *", + "comment": "Komentarz", + "placeholders": { + "full_name": "Jan Kowalski", + "comment": "Dodatkowe informacje" + }, + "ten_gb_ru_description": "Dodaj 10 GB przestrzeni za 100₽/mies.", + "ten_gb_us_description": "Dodaj 10 GB przestrzeni za $1/mies.", + "one_hundred_gb_ru_description": "Dodaj 100 GB przestrzeni za 500₽/mies.", + "one_hundred_gb_us_description": "Dodaj 100 GB przestrzeni za $5/mies.", + "one_tb_ru_description": "Dodaj 1 TB przestrzeni za 1500₽/mies.", + "one_tb_us_description": "Dodaj 1 TB przestrzeni za $15/mies." + }, + "create_a_new_module": "Utwórz nowy moduł", + "module_names": { + "bpmn": { + "us_price_tag": "$1/M.", + "ru_price_tag": "99₽/M." + }, + "wazzup": "Wazzup", + "main_modules": "Wybierz funkcjonalny moduł, który chcesz utworzyć", + "additional_modules": "Dodatkowe moduły funkcjonalne", + "marketplace": "Rynek", + "crm": "CRM", + "project_management": "Projekty i zadania", + "production": "Produkcja", + "product_management_for_sales": "Zarządzanie Zapasami", + "product_management_rentals": "Zarządzanie Wynajmem", + "scheduler": "Kalendarz wizyt", + "supplier_management": "Dostawcy", + "contractor_management": "Wykonawcy", + "partner_management": "Partnerzy", + "hr_management": "Rekrutacja", + "contact": "Kontakty", + "company": "Firmy", + "universal_module": "Moduł Uniwersalny", + "finances": "Zarządzanie Finansami", + "automatisation": "Automatyzacja i Orkiestracja BPMN", + "marketing": "Zarządzanie Marketingiem", + "rentals": "Wynajem", + "for_sales": "Na sprzedaż", + "soon": "Wkrótce", + "chosen": "Wybrane", + "telephony": "Telefonia VoIP", + "mailing": "Serwis e-mail", + "messenger": "Multi Messenger", + "documents": "Generowanie dokumentów", + "forms": "Formularze na stronę", + "headless_forms": "Formularze na stronę przez API", + "online_booking": "Formularze rezerwacyjne online", + "website_chat": "Czaty na stronę", + "tilda": "Tilda Publishing", + "wordpress": "WordPress", + "twilio": "Whatsapp Business", + "fb_messenger": "Facebook Messenger", + "salesforce": "Salesforce", + "make": "Make", + "apix_drive": "ApiX-Drive", + "albato": "Albato", + "one_c": "1C", + "additional_storage": "Dodatkowa przestrzeń", + "ten_gb": "10 GB dodatkowej przestrzeni", + "one_hundred_gb": "100 GB dodatkowej przestrzeni", + "one_tb": "1 TB dodatkowej przestrzeni", + "storage": { + "ten_gb_ru_price_tag": "100₽/M.", + "ten_gb_us_price_tag": "$1/M.", + "one_hundred_gb_ru_price_tag": "500₽/M.", + "one_hundred_gb_us_price_tag": "$5/M.", + "one_tb_ru_price_tag": "1500₽/M.", + "one_tb_us_price_tag": "$15/M." + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.fields.json b/frontend/public/locales/pl/module.fields.json new file mode 100644 index 0000000..a87e3ed --- /dev/null +++ b/frontend/public/locales/pl/module.fields.json @@ -0,0 +1,122 @@ +{ + "fields": { + "formula_warning": "Zmiana formuły spowoduje ponowne obliczenie wartości w tym polu. Aby zachować bieżące dane, rozważ utworzenie nowego pola.", + "project_fields_block": { + "owner": "Odpowiedzialny", + "value": "Wartość", + "participants": "Uczestnicy", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "description": "Opis", + "placeholders": { + "participants": "Dodaj uczestników" + } + }, + "field_value": { + "ogrn": "OGRN", + "tin": "TIN", + "trrc": "TRRC", + "already_have_an_active_call": "Masz już aktywną połączenie", + "recent": "Ostatnie", + "no_available_phone_numbers": "Nie ma dostępnych numerów telefonu", + "value_field_formula_calculation": "To pole jest obecnie obliczone przez formule, nie może być edytowane reaktywne. Wartość: {{value}}.", + "readonly": "To pole jest tylko do odczytu", + "important_field": "Ważne pole", + "mandatory_field": "Pole obowiązkowe", + "important_field_completed": "Ważne pole wypełnione", + "mandatory_field_completed": "Pole obowiązkowe wypełnione", + "local_time": "Przybliżony lokalny czas stolicy kraju numeru telefonu", + "connect_telephony": "Podłącz do telefonii, aby włączyć funkcjonalność połączeń.", + "chat_unavailable": "Wygląda na to, że nie masz dostępu do tego czatu. Skontaktuj się z administratorem, aby się połączyć." + }, + "components": { + "field_formula_settings_button": { + "no_available_fields_for_formula": "Brak pól budżetu, liczbowego lub formułę", + "save_first": "Tę formułę można skonfigurować w karcie", + "customize": "Dostosowywanie formuły: {{fieldName}}", + "formula_hint": "To pole jest przeznaczone do wprowadzania formuły. Formuła może zawierać poniższe wartości, a także inne pola liczbowe i formuły, w tym pola z innych modułów funkcjonalnych. Kliknij żądaną wartość lub wybierz pole.", + "field": "Pole", + "close": "Zamknij" + }, + "field_settings_modal": { + "number": "Liczba", + "currency": "Waluta", + "format_title": "Wybierz format wyświetlania dla tego pole", + "ordinary_field": "Zwykłe pole", + "save_first": "To pole można skonfigurować dopiero po zapisaniu w karcie", + "important_field": "Ważne pole", + "important_field_hint": "Możesz oznaczyć pole jako ważne, wtedy będzie miało ikonę żarówki przypominającą o konieczności wypełnienia. Po wypełnieniu będzie wyświetlać zielony znacznik. Jeśli pole musi być obowiązkowe i zapobiegać przejściu do następnego etapu bez jego wypełnienia, wybierz funkcję „pole obowiązkowe”.", + "select_pipeline_and_statuses": "Wybierz lejek sprzedażowy i statusy", + "mandatory_field": "Pole obowiązkowe", + "mandatory_field_hint": "Możesz ustawić pole jako obowiązkowe do wypełnienia. W takim przypadku użytkownicy nie będą mogli przejść do następnego statusu w lejku sprzedażowym. Wybierz wymagany lejek sprzedażowy i status, od którego pole musi być wypełnione.", + "user_exclusion": "Wykluczenie użytkownika", + "user_exclusion_hint": "Możesz wykluczyć niektórych użytkowników, którzy będą mogli nie wypełniać tego pola. Może to być konieczne dla administratorów.", + "editing_restriction": "Ograniczenie edycji", + "editing_restriction_hint": "Możesz zabronić wybranym użytkownikom edytowania tego pola. Będą mogli je widzieć, ale nie będą mogli go edytować.", + "select_users": "Wybierz użytkowników", + "hide_field": "Ukryj pole dla użytkowników", + "hide_field_hint": "Możesz ukryć to pole przed wybranymi użytkownikami; będzie dla nich niedostępne.", + "field_visibility_in_pipeline": "Ukryj pole w lejku", + "field_visibility_in_pipeline_hint": "Możesz dostosować widoczność pola dla każdego lejka i etapu. Poniżej możesz wybrać, na których etapach i w których lejkach pole będzie ukryte.", + "customize": "Dostosowywanie pola: {{fieldName}}" + }, + "edit_fields": { + "add_bank_requisites": "Dodaj dane bankowe", + "add_org_requisites": "Dodaj dane organizacji i przedsiębiorców", + "add_org_stat_codes": "Dodaj kody statystyczne organizacji i przedsiębiorców", + "add_additional_org_requisites": "Dodaj dodatkowe pola dla organizacji i przedsiębiorców", + "add_field": "Dodaj pole", + "add_utm_fields": "Dodaj pola UTM", + "add_ga_fields": "Dodaj pola Google Analytics", + "add_ym_fields": "Dodaj pola Yandex.Metrica", + "add_fb_fields": "Dodaj pola Facebook Analytics", + "select_options": { + "max_length": "Maksymalnie {{length}} znaków", + "add_option": "Dodaj opcję", + "cancel": "Anuluj", + "add": "Dodaj", + "no_options": "Brak opcji", + "placeholders": { + "search": "Szukaj...", + "option": "Opcja" + } + } + }, + "field_form_group": { + "max_length": "Maksymalnie {{length}} znaków", + "text": "Tekst", + "number": "Liczba", + "multitext": "Wielotekst", + "select": "Wybierz", + "multiselect": "Wielokrotny wybór", + "switch": "Przełącznik", + "formula": "Formuła", + "phone": "Telefon", + "email": "E-mail", + "value": "Wartość", + "date": "Data", + "link": "Link", + "file": "Plik", + "richtext": "Sformatowany tekst", + "participant": "Uczestnik", + "participants": "Uczestnicy", + "colored_select": "Kolorowy wybór", + "colored_multiselect": "Kolorowy wielokrotny wybór", + "checked_multiselect": "Lista kontrolna", + "checklist": "Wielotekst z polami wyboru", + "field_name": "Nazwa pola", + "analytics_field": "Pole analityczne" + }, + "show_fields": { + "no_fields": "Brak pól" + }, + "show_files": { + "empty": "Brak plików", + "attach": "Dołącz pliki" + }, + "file_field_value_comp": { + "attach_files": "Dołącz pliki" + } + } + } +} diff --git a/frontend/public/locales/pl/module.mailing.json b/frontend/public/locales/pl/module.mailing.json new file mode 100644 index 0000000..d23ab50 --- /dev/null +++ b/frontend/public/locales/pl/module.mailing.json @@ -0,0 +1,234 @@ +{ + "mailing": { + "unknown_file": "Nieznany plik", + "modals": { + "send_email_modal": { + "to": "Do:", + "copy": "Kopia:", + "hidden_copy": "Ukryta kopia:", + "from": "Od:", + "subject": "Temat:", + "attachment": "załącznik", + "attachments": "załączniki", + "delete_all": "Usuń wszystko", + "send": "Wyślij", + "error": "Nie udało się wysłać wiadomości! Proszę spróbować ponownie później.", + "send_with_html": "Wyślij e-mail z HTML", + "send_with_html_hint": "Wybierz tę opcję, jeśli chcesz wysłać e-mail z HTML. Jest to przydatne, jeśli chcesz zrezygnować z domyślnych funkcji formatowania {{company}}.", + "components": { + "changes_not_saved_modal": { + "title": "Zmiany nie zostały zapisane", + "annotation": "Kontynuować edytowanie?", + "approve": "Usuń szkic" + }, + "invalid_email_address_modal": { + "title": "E-mail \"{{email}}\" nie wydaje się być prawidłowym adresem", + "annotation": "Sprawdź adres i spróbuj ponownie.", + "approve": "Wyślij mimo to" + }, + "send_email_settings_dropdown": { + "show_copy": "Pokaż pole adresu kopii", + "show_hidden_copy": "Pokaż pole ukrytej kopii" + }, + "editors": { + "email_signature_editor": { + "placeholder": "Dodaj podpis", + "no_signature": "Brak podpisu" + }, + "email_text_editor": { + "placeholders": { + "new_message": "Nowa wiadomość", + "enter_html_markup": "Wprowadź HTML" + } + } + } + } + } + }, + "pages": { + "mailing_settings_page": { + "soon": "Wkrótce...", + "add_button": "Dodaj skrzynkę pocztową", + "mail_templates": "Szablony e-mail", + "templates_caption": "Możesz tworzyć szablony e-mail, które mogą być używane do wysyłania klientom. Po dodaniu szablonu będziesz miał dostęp do wysyłania e-maili.", + "setup_signature": "Ustaw podpis", + "add_template": "Dodaj szablon", + "components": { + "mailbox_item": { + "draft_text": "Szkic", + "draft_hint": "Połączenie skrzynki pocztowej nie jest zakończone. Proszę wprowadzić brakujące ustawienia.", + "inactive_text": "Nieaktywna", + "inactive_hint": "Skrzynka pocztowa jest nieaktywna. {{error}}.", + "sync_text": "Synchronizacja", + "sync_hint": "Skrzynka pocztowa jest synchronizowana. Proszę czekać.", + "active_text": "Aktywna" + } + }, + "modals": { + "delete_mailbox_modal": { + "delete": "Usuń", + "title": "Czy na pewno chcesz usunąć tę skrzynkę pocztową?", + "save_correspondence_annotation": "Zapisać historię korespondencji dla tego e-maila?", + "save_correspondence": "Zapisz historię korespondencji dla tego e-maila" + }, + "mailbox_address_modal": { + "continue": "Kontynuuj", + "title": "Podłącz skrzynkę pocztową", + "placeholder": "Twój e-mail", + "caption1": "Podłącz korporacyjną skrzynkę pocztową z dostępem współdzielonym, która otrzymuje zapytania od klientów, lub osobistą skrzynkę pocztową jednego z Twoich pracowników.", + "caption2": "E-maile wysyłane na tę skrzynkę będą automatycznie dołączane do kontaktów. Możesz utworzyć transakcję bezpośrednio z listy e-maili.", + "caption3": "Jeśli masz problemy z podłączeniem, spróbuj włączyć dostęp dla swojego klienta e-mail.", + "cancel": "Anuluj", + "google_caption1": "{{company}} użycie i transfer informacji otrzymanych z Google API do jakiejkolwiek innej aplikacji będzie zgodne z", + "google_policy_link": "Polityką użytkowania danych Google API Services", + "google_caption2": ", w tym wymaganiami ograniczonego użytkowania." + }, + "mailbox_provider_modal": { + "title": "Wybierz dostawcę", + "caption": "Wybierz swojego dostawcę e-mail z poniższej listy. Jeśli nie widzisz swojej usługi e-mail na liście, naciśnij Ręcznie i ręcznie skonfiguruj skrzynkę pocztową.", + "manual": "Ręcznie" + }, + "update_mailbox_modal": { + "max_number_of_emails_per_day": "Maksymalna liczba e-mailów dziennie", + "emails_per_day": "e-maile dziennie", + "emails_per_day_hint": "Parametr limitu e-maili jest konieczny, aby uniknąć zablokowania przez dostawcę usługi e-mail. Każda usługa e-mail ustala swoje limity na wysyłanie wiadomości. Na przykład podstawowa wersja Google Workspace pozwala na wysyłanie 500 wiadomości dziennie z jednej skrzynki pocztowej.", + "email_readonly_title": "Nie możesz edytować adresu już utworzonej skrzynki pocztowej", + "title_connect": "Podłącz skrzynkę pocztową", + "title_edit": "Edytuj ustawienia skrzynki pocztowej", + "encryption": "Szyfrowanie", + "owner": "Wybierz odpowiedzialny poczty", + "for_whom_available": "Wybierz dla kogo poczta jest dostępna", + "synchronize": "Synchronizuj e-maile z ostatnich 7 dni", + "create_entities_annotation": "Skonfiguruj opcje poniżej, aby automatycznie tworzyć kartę przy odbiorze e-maila od nowego kontaktu.", + "create_entities_label": "Twórz kartę przy odbiorze e-maila", + "delete": "Usuń skrzynkę pocztową", + "reconnect": "Ponownie podłącz skrzynkę pocztową", + "placeholders": { + "password": "Hasło", + "imap": "Serwer IMAP", + "port": "Port", + "smtp": "Serwer SMTP", + "owner": "Wybierz Odpowiedzialny" + } + }, + "mailbox_signature_modal": { + "title": "Podpisy", + "mailbox_signature_editor": { + "available_in_mailboxes": "Dostępne w skrzynkach pocztowych:", + "delete": "Usuń", + "save": "Zapisz", + "warning_title": "Usuń podpis", + "warning_annotation": "Czy na pewno chcesz usunąć ten podpis?", + "save_as_html": "Zapisz jako HTML", + "save_as_html_hint": "Wybierz tę opcję, jeśli chcesz zapisać podpis jako HTML. Jest to przydatne, jeśli chcesz zrezygnować z domyślnych funkcji formatowania {{company}}.", + "placeholders": { + "signature_name": "Nazwa podpisu", + "your_signature": "Twój podpis", + "select_mailboxes": "Wybierz skrzynki pocztowe" + } + } + } + } + }, + "mailing_page": { + "title": "E-mail i Messenger", + "no_email": "Brak e-maili", + "components": { + "section": { + "inbox": "Odebrane", + "sent": "Wysłane", + "spam": "Spam", + "trash": "Kosz", + "draft": "Szkic", + "flagged": "Oznaczone", + "archive": "Archiwum", + "all": "Cała poczta", + "mailbox": "Skrzynka {{number}}" + }, + "message_panel": { + "no_messages": "Brak wiadomości", + "demo_message_subject": "✉️ {{company}}: Korporacyjne rozwiązanie e-mail", + "demo_message_snippet": "Drogi Kliencie, Przedstawiamy rewolucyjne korporacyjne rozwiązanie e-mail, które na pewno przyciągnie Twoją uwagę." + }, + "attachments_block": { + "attachment": "załącznik", + "attachments": "załączniki", + "download_all": "Pobierz wszystko" + }, + "reply_controls": { + "reply": "Odpowiedz", + "reply_all": "Odpowiedz wszystkim", + "forward": "Przekaż dalej" + }, + "create_message_button": { + "title": "Nowa wiadomość" + }, + "no_mailboxes_panel_block": { + "title": "Kliknij przycisk poniżej, aby dodać swoją skrzynkę pocztową." + }, + "thread": { + "unknown": "Nieznany 👤", + "no_selected_message": "Nie wybrano wiadomości", + "from": "Od", + "subject": "Temat", + "reply": "Odpowiedz", + "reply_all": "Odpowiedz wszystkim", + "forward": "Przekaż dalej", + "add_task": "Dodaj zadanie", + "add_contact": "Dodaj kontakt", + "spam": "Spam", + "unspam": "Nie spam", + "move_to_inbox": "Przenieś do odebranych", + "trash": "Kosz", + "close": "Zamknij", + "unseen": "Niewidoczne", + "user": "Użytkownik", + "date": "{{day}} o {{time}}", + "amwork_workspace": "{{company}} Workspace", + "demo_message_title": "✉️ {{company}}: Korporacyjne rozwiązanie e-mail", + "demo_message_snippet": "Drogi Kliencie, przedstawiamy rewolucyjne korporacyjne rozwiązanie e-mail, które na pewno przyciągnie Twoją uwagę. Odkryj nowe możliwości i podnieś swoją komunikację korporacyjną na nowy poziom dzięki integracji e-mail z przestrzenią roboczą {{company}}!", + "dear_customer": "Drogi Kliencie,", + "demo_message_intro": "Przedstawiamy rewolucyjne korporacyjne rozwiązanie e-mail, które na pewno przyciągnie Twoją uwagę. Odkryj nowe możliwości i podnieś swoją komunikację korporacyjną na nowy poziom dzięki integracji e-mail z przestrzenią roboczą {{company}}! Podłącz dowolną korporacyjną lub osobistą skrzynkę pocztową i ciesz się nie tylko znanymi funkcjami e-mail, ale także wieloma dodatkowymi narzędziami. Twórz zadania, leady, projekty i inne bezpośrednio z e-maila, utrzymując pełną kontrolę nad wszystkimi interakcjami z klientami, partnerami i projektami. Bez wysiłku wysyłaj wiadomości z kart leadów, transakcji, partnerów lub projektów, a będą one automatycznie zapisywane w odpowiedniej karcie. W ten sposób możesz łatwo przeczytać i przeanalizować historię korespondencji dla konkretnej transakcji lub projektu. Pracuj bardziej efektywnie jako zespół, udzielając dostępu do wspólnej skrzynki pocztowej i zarządzając komunikacją razem ze swoimi kolegami. Otwórz nowe horyzonty z {{company}} i zwiększ swoją produktywność biznesową!", + "email_functionality": "📬 Funkcjonalność e-mail:", + "email_functionality_ul": "
  • Integracja API z Gmail
  • Integracja IMAP
  • Integracja e-maili z ostatnich 7 dni dla nowo dodanych skrzynek pocztowych
  • Przychodzące i wychodzące e-maile
  • Grupowanie e-maili w wątki/łańcuchy według nadawcy
  • Możliwość tworzenia zadania z e-maila
  • Możliwość tworzenia kontaktu z e-maila
  • Automatyczne tworzenie leadów z e-maili
  • Dołączanie przychodzących i wychodzących e-maili do historii/feed karty
  • Możliwość pisania e-maila z karty
  • Tworzenie niestandardowych folderów
  • Użycie wielu skrzynek pocztowych
  • Wspólne zarządzanie skrzynką pocztową
  • Wyświetlanie e-maili w formacie HTML
  • Wspólne zarządzanie skrzynką pocztową
  • Wyszukiwanie e-maili
  • Spam
  • Usunięte pozycje
  • Szkice
  • Tworzenie podpisów
  • Adresaci kopii
  • Adresaci ukrytej kopii
  • Załączniki plików
", + "reach_out": "Jeśli masz jakieś pytania, skontaktuj się z nami –", + "sincerely_amwork": "Z poważaniem, zespół {{company}}." + }, + "modals": { + "create_contact_modal": { + "create": "Utwórz", + "title": "Utwórz karty z wiadomości e-mail", + "create_contact": "Utwórz kontakt dla e-maila", + "create_lead": "Utwórz lead dla e-maila", + "open_entity": "Otwórz nowo utworzoną jednostkę", + "placeholders": { + "where_contact": "Wybierz, gdzie utworzyć nowy kontakt", + "where_lead": "Wybierz, gdzie utworzyć nowy lead", + "board_for_lead": "Wybierz tablicę dla nowego leada" + } + }, + "link_contact_modal": { + "link": "Połącz", + "title": "Połącz kartę", + "select_module": "Wybierz moduł", + "search_card": "Wybierz kartę", + "open_entity": "Otwórz połączoną kartę", + "placeholders": { + "module": "Moduł...", + "search_card": "Wpisz nazwę, aby wyszukać..." + } + } + } + }, + "hooks": { + "use_message_controls": { + "unknown": "Nieznany 👤", + "reply_to": "Odpowiedz do", + "reply_all": "Odpowiedz wszystkim", + "forward": "Przekaż dalej" + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.multichat.json b/frontend/public/locales/pl/module.multichat.json new file mode 100644 index 0000000..7ae1690 --- /dev/null +++ b/frontend/public/locales/pl/module.multichat.json @@ -0,0 +1,71 @@ +{ + "multichat": { + "amwork_messenger": "{{company}} Messenger", + "components": { + "multichat_control": { + "not_found": "Nie znaleziono", + "chats": "Czaty", + "messages": "Wiadomości", + "users": "Użytkownicy", + "owner": "Odpowiedzialny", + "supervisor": "Supervisor", + "no_chats_yet": "Brak czatów", + "create_card": "Utwórz kartę", + "link_card": "Przypnij kartę", + "card": "Karta", + "ui": { + "deleted_user": "Usunięty użytkownik 👻", + "multichat_control_header": { + "title": "{{company}} Multi Messenger" + }, + "create_amwork_chat_button": { + "label": "Nowy czat {{company}}", + "modals": { + "select_contact_title": "Wybierz jednego użytkownika lub grupę użytkowników", + "continue": "Kontynuuj", + "customize_chat": "Dostosuj czat", + "placeholders": { + "group_name": "Nazwa grupy" + } + } + }, + "chats_header_providers_tabs": { + "all_chats": "Wszystkie czaty" + }, + "chat_message_context_menu": { + "reply": "Odpowiedzieć", + "copy": "Kopiuj tekst", + "edit": "Redagować", + "delete": "Usunąć" + }, + "chat_message_reply_block": { + "editing_message": "Edycja wiadomości" + }, + "send_chat_message_block": { + "placeholders": { + "message": "Wiadomość" + } + }, + "no_selected_chat_plug": { + "title": "Wybierz czat, aby rozpocząć rozmowę" + }, + "chats_panel_block": { + "you": "Ty" + }, + "send_chat_message_with_files_modal": { + "send_files": "Wyślij pliki" + }, + "chat_message_item": { + "you": "Ty" + }, + "chat_controls_menu": { + "mark_as_read": "Oznacz jako przeczytane", + "delete_chat": "Usuń czat", + "delete_warning_title": "Czy na pewno chcesz usunąć czat?", + "delete_warning_annotation": "Wszystkie wiadomości zostaną usunięte. Tej akcji nie można cofnąć." + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.notes.json b/frontend/public/locales/pl/module.notes.json new file mode 100644 index 0000000..3087e22 --- /dev/null +++ b/frontend/public/locales/pl/module.notes.json @@ -0,0 +1,49 @@ +{ + "notes": { + "notes_page": { + "module_name": "Notatki", + "add_note": "Utwórz notatkę", + "new_note_heading": "Nowa notatka" + }, + "toolbar": { + "undo": "Cofnij", + "redo": "Ponów", + "bold": "Pogrubienie", + "italic": "Kursywa", + "underline": "Podkreślenie", + "strike": "Przekreślenie", + "bullet_list": "Lista punktowana", + "ordered_list": "Lista numerowana", + "text": "Tekst", + "heading": "Tytuł", + "subheading": "Nagłówek", + "heading_3": "Podgłówek", + "more": "Więcej...", + "expand": "Rozwiń", + "minimize": "Zwiń" + }, + "editor": { + "recently_edited": "Przed chwilą", + "quick_note": "Szybka notatka", + "last_changed": "Ostatnie zmiany: ", + "duplicate": "Duplikuj", + "put_into_folder": "Przywróć", + "delete": "Usuń notatkę" + }, + "selector": { + "new_note_heading": "Nowa notatka", + "saved_indicator": "Zmiany zostały zapisane", + "pinned": "Przypięte", + "today": "Dzisiaj", + "yesterday": "Wczoraj", + "earlier": "Wcześniej" + }, + "block": { + "unnamed_note": "Bez nazwy" + }, + "folders": { + "all": "Wszystkie", + "recently_deleted": "Ostatnio usunięte" + } + } +} diff --git a/frontend/public/locales/pl/module.notifications.json b/frontend/public/locales/pl/module.notifications.json new file mode 100644 index 0000000..4126ae3 --- /dev/null +++ b/frontend/public/locales/pl/module.notifications.json @@ -0,0 +1,79 @@ +{ + "notifications": { + "tags": { + "task_new": "Nowe zadanie", + "task_overdue": "Zadanie", + "task_before_start": "Zadanie", + "task_overdue_employee": "Zadanie", + "activity_new": "Nowa aktywność", + "activity_overdue": "Aktywność", + "activity_before_start": "Aktywność", + "activity_overdue_employee": "Aktywność", + "task_comment_new": "Nowy komentarz", + "chat_message_new": "Nowa wiadomość", + "mail_new": "Nowa poczta", + "entity_note_new": "Nowa notatka", + "entity_new": "Nowy {{entityTypeName}}", + "entity_responsible_change": "Nowy odpowiedzialny {{entityTypeName}}", + "entity_import_completed": "Import", + "yesterday": "Wczoraj o {{time}}", + "date": "{{date}} o {{time}}" + }, + "components": { + "delay_select": { + "no_delay": "Bez opóźnienia", + "5_minutes": "5 minut", + "10_minutes": "10 minut", + "15_minutes": "15 minut", + "30_minutes": "30 minut", + "1_hour": "1 godzina" + }, + "notifications_panel": { + "mute_notifications": "Wycisz dźwięk powiadomień", + "unmute_notifications": "Włącz dźwięk powiadomień", + "no_notifications": "Nie masz jeszcze żadnych powiadomień", + "ui": { + "block_header_annotation": { + "overdue": "Przeterminowane", + "overdue_employee": "{{employee}} jest przeterminowany", + "after_time": "Po {{time}}", + "from_employee": "od {{employee}}", + "system_notice": "Powiadomienie systemowe", + "message": "Wiadomość", + "yesterday": "Wczoraj o {{time}}", + "date": "{{date}} o {{time}}" + }, + "notification_settings": { + "title": "Powiadomienia" + }, + "notification_settings_modal": { + "title": "Ustawienia powiadomień", + "enable_popup": "Powiadomienia pop-up", + "new": "Nowe", + "new_mail": "Nowa poczta", + "new_chat_message": "Nowa wiadomość na czacie", + "new_note": "Nowa notatka", + "entity_responsible_change": "Zmiana odpowiedzialności za jednostkę", + "entity_import_complete": "Import jednostki zakończony", + "new_task_comment": "Nowy komentarz do zadania", + "unknown": "Nieznane", + "new_task": "Nowe zadanie", + "overdue_task": "Przeterminowane zadanie", + "before_task_start": "Przed rozpoczęciem zadania", + "overdue_task_employee": "Przeterminowane zadanie dla pracownika(ów)", + "new_activity": "Nowa aktywność", + "overdue_activity": "Przeterminowana aktywność", + "before_activity_start": "Przed rozpoczęciem aktywności", + "overdue_activity_employee": "Przeterminowana aktywność dla pracownika(ów)", + "placeholders": { + "select": "Wybierz" + } + }, + "read_all_button": { + "read_all": "Przeczytaj wszystko" + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.products.json b/frontend/public/locales/pl/module.products.json new file mode 100644 index 0000000..dc43e55 --- /dev/null +++ b/frontend/public/locales/pl/module.products.json @@ -0,0 +1,502 @@ +{ + "products": { + "components": { + "common": { + "readonly_stocks": "Nie możesz edytować zapasów na tym statusie", + "readonly_delete": "Nie możesz usuwać pozycji z zamówienia na tym statusie", + "select_the_warehouse_to_write_off": "Wybierz magazyn do odpisania zapasów", + "return_stocks_warning_modal": { + "title": "Zwróć zapasy", + "annotation": "Czy chcesz zwrócić zapasy do magazynu?", + "approve_title": "Zwróć", + "cancel_title": "Nie zwracaj" + }, + "delete_order_warning_modal": { + "title": "Uwaga", + "annotation": "Czy chcesz usunąć zamówienie? Tej akcji nie można cofnąć." + }, + "delete_order_or_clear_items_warning_modal": { + "title": "Uwaga", + "annotation": "Czy chcesz usunąć całe zamówienie lub usunąć wszystkie pozycje? Tej akcji nie można cofnąć.", + "approve_title": "Usuń zamówienie", + "cancel_title": "Wyczyść pozycje" + }, + "rental_order_status_select": { + "formed": "Utworzone", + "accepted_to_warehouse": "Przyjęte do magazynu", + "sent_to_warehouse": "Wysłane do magazynu", + "delivered": "Dostarczone", + "reserved": "Zarezerwowane", + "shipped": "Wysłane", + "cancelled": "Anulowane", + "returned": "Zwrócone", + "placeholders": { + "select_status": "Wybierz status" + } + }, + "order_status_select": { + "placeholders": { + "select_status": "Wybierz status" + }, + "statuses": { + "reserved": "Zarezerwowane", + "sent_for_shipment": "Wysłane do wysyłki", + "shipped": "Wysłane", + "cancelled": "Anulowane", + "returned": "Zwrócone" + } + }, + "products_tab_selector": { + "shipments": "Wysyłki", + "timetable": "Kalendarz", + "products": "Produkty" + }, + "products_category_select": { + "no_categories": "Brak kategorii", + "placeholders": { + "select_category": "Kategoria" + } + }, + "products_warehouse_select": { + "no_warehouses": "Brak magazynów", + "placeholders": { + "select_warehouse": "Magazyn" + } + }, + "products_settings_button": { + "settings": "Ustawienia", + "module_settings": "Ustawienia modułu", + "table_settings": "Ustawienia tabeli", + "report_settings": "Ustawienia raportu" + }, + "calendar": { + "month": "Miesiąc", + "week": "Tydzień", + "days": { + "short": { + "mo": "Pn", + "tu": "Wt", + "we": "Śr", + "th": "Cz", + "fr": "Pt", + "sa": "So", + "su": "Nd" + }, + "long": { + "mo": "Poniedziałek", + "tu": "Wtorek", + "we": "Środa", + "th": "Czwartek", + "fr": "Piątek", + "sa": "Sobota", + "su": "Niedziela" + } + }, + "resources_label": "Produkty", + "statuses": { + "reserved": "Zarezerwowane", + "rented": "Wynajem" + }, + "fields": { + "phone": "Telefon", + "mail": "Mail", + "shifts": "Liczba dni", + "status": "Status", + "period": "Okres wynajmu" + } + } + } + }, + "pages": { + "card_products_orders_page": { + "ui": { + "empty": "Brak zamówień w tej sekcji" + }, + "hooks": { + "use_products_section_orders_columns": { + "name": "Nazwa zamówienia", + "warehouse": "Magazyn", + "status": "Status", + "created_at": "Utworzone", + "shipped_at": "Wysłane", + "creator": "Twórca", + "order": "Zamówienie-{{number}}" + }, + "use_rental_products_section_orders_columns": { + "name": "Nazwa", + "status": "Status", + "created_at": "Utworzone", + "creator": "Twórca", + "order": "Zamówienie-{{number}}", + "shipment": "Wysyłka dla zamówienia #{{number}}", + "linked_entity": "Powiązana jednostka" + } + } + }, + "card_products_order_page": { + "templates": { + "products_order_block_template": { + "new_order": "Nowe zamówienie", + "save": "Zapisz", + "cancel": "Anuluj", + "added_to": "Dodane do {{entity_name}}", + "empty": "Nie dodałeś jeszcze produktów do zamówienia" + }, + "products_warehouse_block_template": { + "product_management_for_sales": "Zarządzanie Zapasami", + "product_management_rentals": "Zarządzanie Wynajmem", + "products": "Produkty", + "warehouse": "Magazyn", + "add": "Dodaj", + "reset": "Resetuj", + "empty": "Magazyn jest pusty", + "warehouse_select_hint": "Magazyn wybrany w tej sekcji musi odpowiadać zamówionemu magazynowi w górnej sekcji (zamówienie). Jeśli chcesz wybrać inny magazyn tutaj, musisz najpierw wyczyścić lub zmienić wybrany magazyn w górnej sekcji. Każdy magazyn wybrany tutaj zostanie automatycznie przeniesiony do górnej sekcji." + } + }, + "common": { + "products_order_price_head_cell": { + "price": "Cena," + }, + "products_order_max_discount_hint": "Maksymalna zniżka wynosi", + "products_order_tax_header_cell": { + "tax": "Podatek,", + "tax_included": "Wliczone", + "tax_excluded": "Wyłączone" + }, + "products_order_total_block": { + "total": "Łącznie: {{total}}" + }, + "remove_selected_block": { + "remove_selected": "Usuń wybrane" + } + }, + "hooks": { + "use_price_cell_columns": { + "name_fallback": "Brak nazwy ceny", + "name": "Nazwa", + "price": "Cena", + "currency": "Waluta" + } + }, + "ui": { + "card_rental_products_order_component": { + "hooks": { + "use_rental_card_order_columns": { + "name": "Nazwa", + "discount": "Zniżka", + "availability": "Dostępność", + "amount": "Ilość" + }, + "use_rental_warehouse_columns": { + "name": "Nazwa", + "price": "Cena", + "category": "Kategoria", + "availability": "Dostępność" + } + }, + "ui": { + "rental_availability_cell": { + "available": "Dostępne", + "reserved": "Zarezerwowane", + "rent": "Wynajem" + }, + "rental_order_periods_control": { + "new_period": "Nowy okres", + "periods_selected": "Wybrane okresy: {{count}}", + "add_new_period": "Dodaj nowy okres", + "hint_text": "Możesz wybrać lub zmodyfikować okresy wyłącznie podczas tworzenia nowych zamówień lub dla zamówień ze statusem 'Utworzone'.", + "placeholders": { + "select_periods": "Wybierz okresy" + } + }, + "card_rental_products_order_block_header": { + "placeholders": { + "select_order_warehouse": "Wybierz magazyn zamówienia" + } + } + } + }, + "card_products_order_component": { + "hooks": { + "use_available_columns": { + "unknown": "Nieznany...", + "name": "Nazwa", + "stock": "Stan", + "reserved": "Zarezerwowane", + "available": "Dostępne" + }, + "use_card_order_columns": { + "name": "Nazwa", + "discount": "Zniżka", + "tax": "Podatek,", + "quantity": "Ilość", + "amount": "Kwota" + }, + "use_reservations_columns": { + "warehouse_name": "Nazwa magazynu", + "stock": "Stan", + "reserved": "Zarezerwowane", + "available": "Dostępne", + "quantity": "Ilość" + }, + "use_warehouse_columns": { + "name": "Nazwa", + "price": "Cena", + "category": "Kategoria", + "available": "Dostępne", + "quantity": "Ilość" + } + }, + "ui": { + "available_head_cell": { + "available": "Dostępne", + "available_hint": "Najedź na komórkę w tej kolumnie, aby uzyskać szczegółowe informacje o stanie magazynu." + }, + "card_products_order_block_header": { + "cancel_after_hint": "Możesz skonfigurować automatyczne anulowanie rezerwacji pozycji w zamówieniach. To pomaga zapobiec nieprawidłowym poziomom zapasów, które mogą wystąpić, gdy pozycje są zarezerwowane w transakcjach, ale te transakcje nie są przetwarzane lub zostały anulowane.", + "placeholders": { + "select_order_warehouse": "Wybierz magazyn zamówienia" + } + }, + "add_stock_placeholder": { + "add_stock": "Dodaj zapas do magazynu" + } + } + } + } + }, + "product_categories_page": { + "title": "Utwórz kategorię (grupę produktów)", + "placeholders": { + "category_name": "Nazwa kategorii", + "subcategory_name": "Nazwa podkategorii" + }, + "buttons": { + "add_category": "Dodaj kategorię", + "add_subcategory": "Dodaj podkategorię" + }, + "ui": { + "delete_category_warning_modal": { + "cannot_be_undone": "Tej akcji nie można cofnąć.", + "title": "Czy na pewno chcesz usunąć {{name}}?", + "annotation": "Wszystkie podkategorie zostaną usunięte." + } + } + }, + "product_page": { + "product_description": "Opis produktu", + "sku_already_exists_warning": "Produkt z SKU \"{{sku}}\" już istnieje", + "sku": "SKU", + "unit": "Jednostka", + "tax": "Podatek", + "category": "Kategoria", + "warehouse": "Magazyn", + "ui": { + "add_warehouse_placeholder": { + "add_warehouse": "Dodaj magazyn" + }, + "product_actions_dropdown": { + "delete_product": "Usuń produkt" + }, + "product_description": { + "placeholders": { + "add_description": "Dodaj opis" + } + }, + "product_feed": { + "calendar": "Kalendarz", + "prices": "Ceny", + "stocks": "Zapas", + "images": "Zdjęcia", + "add_price": "Dodaj cenę", + "add_image": "Dodaj zdjęcie", + "delete_image": "Usuń zdjęcie", + "delete_images": "Usuń zdjęcia", + "placeholders": { + "name": "Nazwa ceny", + "unit_price": "Cena jednostkowa" + }, + "labels": { + "maximum_discount": "Maksymalna zniżka na produkt", + "product_cost": "Koszt produktu" + } + }, + "product_name_block": { + "placeholders": { + "product_name": "Nazwa produktu" + } + } + } + }, + "warehouses_page": { + "page_title": "Skonfiguruj swoje magazyny", + "placeholders": { + "name": "Nazwa magazynu", + "select_warehouse": "Wybierz magazyn" + }, + "add_warehouse": "Dodaj magazyn", + "ui": { + "delete_warehouse_modal": { + "title": "Czy na pewno chcesz usunąć {{name}}?", + "annotation": "Wszystkie zapasy w magazynie zostaną wyczyszczone.", + "annotation_move_stocks": "Wszystkie zapasy w magazynie zostaną wyczyszczone. Alternatywnie, masz możliwość przeniesienia wszystkich zapasów do innego magazynu, jeśli jest dostępny.", + "placeholders": { + "move_stocks_to": "Przenieś zapasy do..." + } + } + } + }, + "products_page": { + "no_warehouses_or_categories": "Brak magazynów lub kategorii do wyświetlania filtrów", + "title": { + "sale": "Sprzedaż", + "rental": "Wynajem", + "shipments": "Wysyłki", + "calendar": "Kalendarz" + }, + "tabs": { + "products": "Produkty", + "timetable": "Kalendarz", + "shipments": "Wysyłki", + "reports": "Raporty" + }, + "all_columns_hidden": "Wszystkie kolumny są ukryte w ustawieniach tabeli", + "table_settings": "Ustawienia tabeli", + "display_columns": "Wyświetl kolumny", + "add_product": "Dodaj produkt", + "empty": "Nie dodałeś jeszcze żadnych produktów", + "show_empty_resources": "Pokaż puste zasoby", + "hide_empty_resources": "Ukryj puste zasoby", + "hooks": { + "use_products_columns": { + "name": "Nazwa", + "prices": "Ceny", + "sku": "SKU", + "tax": "Podatek", + "stocks": "Zapas", + "unit": "Jednostka", + "availability": "Dostępność" + }, + "use_product_stocks_columns": { + "warehouse": "Magazyn", + "reserved": "Zarezerwowane", + "available": "Dostępne", + "stock": "Stan" + }, + "use_create_stocks_columns": { + "warehouse": "Magazyn", + "stock": "Stan" + } + }, + "ui": { + "add_product_modal": { + "changes_not_saved_warning_title": "Czy na pewno chcesz zamknąć to okno?", + "changes_not_saved_warning_annotation": "Wszystkie zmiany zostaną utracone.", + "changes_not_saved_warning_approve_title": "Zamknij mimo to", + "barcodes_hint": "Możesz skanować kody kreskowe skanerem lub wprowadzać je ręcznie w tym polu. Każdy kod kreskowy w sekcji musi być unikalny.", + "sku_already_exists_warning": "Produkt z SKU \"{{sku}}\" już istnieje", + "types": { + "product": "Produkt", + "service": "Usługa", + "kit": "Zestaw" + }, + "add_product": "Dodaj produkt", + "name": "Nazwa", + "type": "Typ", + "add_photo": "Dodaj zdjęcie", + "description": "Opis", + "sku": "SKU", + "unit": "Jednostka", + "tax": "Podatek", + "category": "Kategoria", + "prices": "Ceny", + "stocks": "Zapas" + }, + "photo_block_item": { + "alt": "Produkt {{name}}" + }, + "product_price_list": { + "add_price": "Dodaj cenę" + }, + "product_search_block": { + "placeholders": { + "search_products": "Szukaj produktów" + } + }, + "product_stocks_modal": { + "title": "Ustaw zapasy produktów w każdym magazynie" + }, + "create_stocks_modal": { + "title": "Określ zapasy produktów" + } + } + }, + "shipment_page": { + "hooks": { + "use_shipment_columns": { + "name": "Nazwa", + "sku": "SKU", + "available": "Dostępne", + "quantity": "Ilość" + }, + "use_rental_shipment_columns": { + "name": "Nazwa", + "sku": "SKU", + "tax": "Podatek", + "discount": "Zniżka", + "total": "Łącznie", + "quantity": "Ilość" + } + }, + "ui": { + "shipment_page_secondary_header": { + "order": "Zamówienie-{{number}}" + }, + "rental_shipment_page_secondary_header": { + "order": "Zamówienie-{{number}}" + }, + "some_products_not_checked_warning_modal": { + "title": "Niektóre produkty nie są sprawdzone", + "annotation": "Próbujesz zmienić status zamówienia, w którym niektóre produkty nie są sprawdzone. Czy chcesz zmienić status mimo to?", + "approve_title": "Zmień mimo to" + }, + "product_barcodes_control": { + "barcode": "Kod kreskowy", + "modals": { + "product_is_not_in_order_warning_modal": { + "title": "Produkt nie jest w tym zamówieniu", + "approve_title": "Dodaj produkt do zamówienia", + "annotation": "Produkt związany z kodem kreskowym \"{{barcode}}\" nie jest uwzględniony w tym zamówieniu. Masz możliwość dodania go ręcznie lub kontynuowania bez niego." + }, + "product_does_not_exist_warning_modal": { + "title": "Produkt nie istnieje", + "approve_title": "Utwórz produkt", + "annotation": "Produkt z kodem kreskowym \"{{barcode}}\" nie istnieje w tej sekcji produktów. Możesz ręcznie utworzyć go na stronie produktów." + } + } + } + } + }, + "shipments_page": { + "hooks": { + "use_shipments_columns": { + "unknown": "Nieznany...", + "name": "Nazwa", + "shipment": "Wysyłka dla zamówienia #{{number}}", + "warehouse": "Magazyn", + "shipped_at": "Wysłane", + "created_at": "Utworzone", + "status": "Status", + "linked_entity": "Powiązana jednostka" + } + }, + "ui": { + "shipments_table": { + "all_columns_hidden": "Wszystkie kolumny są ukryte w ustawieniach tabeli", + "empty": "Brak zaplanowanych wysyłek" + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.reporting.json b/frontend/public/locales/pl/module.reporting.json new file mode 100644 index 0000000..b27e3b0 --- /dev/null +++ b/frontend/public/locales/pl/module.reporting.json @@ -0,0 +1,432 @@ +{ + "reporting": { + "templates": { + "components": { + "report_table": { + "empty": "Brak dostępnych danych na ten okres" + }, + "report_settings_drawer": { + "table_settings": "Ustawienia tabeli", + "display_columns": "Wyświetl kolumny" + } + }, + "comparative_report_template": { + "placeholders": { + "users": "Użytkownicy", + "pipeline": "Lejki sprzedażowe", + "month": "Wybierz miesiąc", + "year": "Wybierz rok" + } + }, + "general_report_template": { + "apply_filter": "Zastosuj filtr", + "reset": "Resetuj", + "stages": { + "all": "Wszystkie etapy", + "open": "Otwarte etapy", + "lost": "Utracone etapy", + "won": "Wygrane etapy" + }, + "placeholders": { + "users": "Użytkownicy", + "pipeline": "Lejki sprzedażowe", + "stage": "Etapy", + "warehouse": "Magazyny", + "category": "Kategorie", + "field_or_responsible": "Pole lub odpowiedzialny" + } + }, + "calls_report_template": { + "directions": { + "all": "Wszystkie połączenia", + "incoming": "Połączenia przychodzące", + "outgoing": "Połączenia wychodzące" + }, + "placeholders": { + "users": "Użytkownicy", + "pipeline": "Lejki sprzedażowe", + "directions": "Typ połączenia", + "duration": "Czas trwania połączenia", + "only_missed": "Tylko nieodebrane połączenia" + } + } + }, + "hooks": { + "use_get_products_general_report_columns": { + "name": "Nazwa", + "sold": "Sprzedano łącznie", + "shipped": "Wysłane", + "open": "Otwarte transakcje", + "lost": "Utracone transakcje", + "all": "Łącznie transakcji", + "average_products": "Średnia liczba produktów na transakcję", + "average_budget": "Średnia wartość udanych transakcji", + "average_term": "Średni czas trwania transakcji" + }, + "use_calls_history_report_columns": { + "timestamps": "Data połączenia", + "type": "Dzwoniący i odbiorca", + "result": "Czas trwania i odtwarzanie", + "unknown": "Nieznany numer", + "caller": "Dzwoniący", + "callee": "Odbiorca" + }, + "use_get_general_report_columns": { + "result": "Wynik", + "all": "Wszystkie", + "open": "Otwarte", + "expired": "Przeterminowane", + "completed": "Zakończone", + "won": "Wygrane", + "lost": "Utracone", + "groups": "Grupy", + "users": "Użytkownicy", + "cards": "Karty (Leadów)", + "tasks": "Zadania", + "activities": "Aktywności", + "average_check": "Średnia wartość", + "average_term": "Średni czas trwania (dni)", + "switch_on": "Przełącz (Włączone)", + "switch_off": "Przełącz (Wyłączone)", + "calls": "Połączenia", + "total": "Łącznie połączeń", + "average": "Średnia liczba połączeń", + "incoming": "Połączenia przychodzące", + "incoming_average": "Średnia liczba połączeń przychodzących", + "outgoing": "Połączenia wychodzące", + "outgoing_average": "Średnia liczba połączeń wychodzących", + "missed": "Nieodebrane połączenia", + "min": "min", + "undefined_client": "Planowanie wizyty bez transakcji" + }, + "use_get_projects_report_columns": { + "hours": "h", + "min": "min", + "users": "Użytkownicy", + "opened": "Otwarte zadania", + "done": "Zakończone zadania", + "overdue": "Przeterminowane zadania", + "planned": "Planowany czas", + "completion_percent": "Procent ukończenia projektu", + "project_name": "Nazwa projektu", + "stage": "Status projektu" + }, + "use_get_customer_report_columns": { + "name": "Nazwa", + "sold": "Sprzedano łącznie", + "products_quantity": "Liczba produktów", + "opened": "W toku (otwarte transakcje)", + "lost": "Utracone transakcje", + "all": "Łącznie transakcji (otwarte i zamknięte)", + "average_quantity": "Średnia liczba zakupów (transakcji)", + "average_budget": "Średni budżet udanych transakcji", + "average_duration": "Średni czas trwania udanych transakcji (dni)" + }, + "use_get_schedule_report_columns": { + "groups": "Grupy", + "users": "Użytkownicy", + "sold": "Sprzedano łącznie", + "total": "Łącznie wizyty", + "scheduled": "Zaplanowane", + "confirmed": "Potwierdzone", + "completed": "Zakończone", + "cancelled": "Anulowane" + }, + "use_get_comparative_report_columns": { + "all": "Wszystkie", + "open": "Otwarte", + "won": "Wygrane", + "lost": "Utracone", + "week": "Tydzień {{number}}", + "quarter": "Kwartał {{number}}", + "users": "Użytkownicy", + "days": { + "monday": "Poniedziałek", + "tuesday": "Wtorek", + "wednesday": "Środa", + "thursday": "Czwartek", + "friday": "Piątek", + "saturday": "Sobota", + "sunday": "Niedziela" + }, + "months": { + "january": "Styczeń", + "february": "Luty", + "march": "Marzec", + "april": "Kwiecień", + "may": "Maj", + "june": "Czerwiec", + "july": "Lipiec", + "august": "Sierpień", + "september": "Wrzesień", + "october": "Październik", + "november": "Listopad", + "december": "Grudzień" + } + } + }, + "pages": { + "reports_page": { + "components": { + "row_title_cell": { + "total": "Łącznie", + "without_group": "Bez grupy", + "empty_user": "Użytkownik nie jest przypisany" + }, + "row_event_cell": { + "to": "do" + }, + "reports_navigation_sidebar": { + "total": "Łącznie", + "export_xlsx": "Eksport do XLSX", + "export_table": "Eksportowane z {{company}} W dniu {{date}}", + "unfold_filters_menu": "Rozwiń menu filtrów", + "fold_filters_menu": "Zwiń menu filtrów", + "hide_sidebar": "Ukryj pasek boczny", + "show_sidebar": "Pokaż pasek boczny", + "schedules": "Wizyty", + "title": { + "universal": "Raporty", + "project": "Raporty projektów", + "deal": "Raporty transakcji" + }, + "general_report": "Raport ogólny", + "comparison_of_periods": "Porównanie okresów", + "telephony": "Połączenia", + "schedule": "Wizyty", + "projects": "Projekty", + "users": "Użytkownicy", + "rating": "Oceny", + "groups": "Grupy", + "days": "Dni", + "weeks": "Tygodnie", + "months": "Miesiące", + "quarters": "Kwartały", + "years": "Lata", + "callsUsers": "Po użytkownikach", + "callsGroups": "Po działach", + "callHistory": "Raport z historii połączeń", + "scheduleClient": "Po klientach", + "scheduleDepartment": "Po grupach", + "scheduleOwner": "Po konsultantam", + "schedulePerformer": "Po specjalistach", + "customer_reports": "Kontakty i firmy", + "customerContact": "Po kontaktach", + "customerCompany": "Po firmach", + "customerContactCompany": "Po kontaktach i firmach", + "products": "Produkty", + "productsCategories": "Po kategoriach", + "productsUsers": "Po użytkownikach" + } + } + }, + "dashboard_page": { + "filter": { + "placeholders": { + "select_users": "Użytkownicy", + "select_sales_pipeline": "Lejki sprzedażowe", + "all": "Wszystkie", + "all_active": "Wszystkie aktywne", + "open": "Otwarte", + "open_active": "Otwarty i aktywny", + "closed": "Zamknięte", + "created": "Stworzony" + }, + "dashboard_type_tooltip": { + "all": { + "text": "Ten raport obejmuje wszystkie transakcje istniejące w danym okresie, niezależnie od ich aktywności. Obejmuje:", + "list_1": "✓ Transakcje utworzone w okresie.", + "list_2": "✓ Transakcje zamknięte (wygrane lub przegrane) w okresie.", + "list_3": "✓ Transakcje istniejące wcześniej i nadal otwarte w okresie.", + "list_4": "✗ Nie wyklucza transakcji bez aktywności (bez zmian etapów w lejku sprzedaży)." + }, + "all_active": { + "text": "Ten raport obejmuje tylko transakcje, w których etap zmienił się przynajmniej raz w okresie. Obejmuje:", + "list_1": "✓ Transakcje utworzone wcześniej, jeśli miały zmiany etapów w okresie.", + "list_2": "✓ Transakcje utworzone w okresie, jeśli zmieniały etapy.", + "list_3": "✓ Transakcje zamknięte w okresie, jeśli przed zamknięciem nastąpiła zmiana etapu.", + "list_4": "✗ Wyklucza transakcje bez aktywności (bez zmian etapów)." + }, + "open": { + "text": "Ten raport obejmuje transakcje, które nie zostały zamknięte (nie osiągnęły statusu „Wygrana” ani „Przegrana”) na koniec okresu. Obejmuje:", + "list_1": "✓ Transakcje utworzone wcześniej, które nadal są otwarte.", + "list_2": "✓ Transakcje utworzone w okresie i nadal otwarte na jego koniec.", + "list_3": "✗ Nie uwzględnia zamkniętych transakcji." + }, + "open_active": { + "text": "Ten raport obejmuje tylko otwarte transakcje, w których była aktywność (zmiana etapu) w okresie. Obejmuje:", + "list_1": "✓ Otwarte transakcje, w których etap zmieniał się w okresie.", + "list_2": "✓ Nowe transakcje, jeśli zmieniały etapy.", + "list_3": "✗ Wyklucza transakcje, które nie zostały zamknięte, ale nie miały zmian etapów.", + "list_4": "✗ Wyklucza zamknięte transakcje." + }, + "closed": { + "text": "Ten raport obejmuje tylko transakcje, które zostały zamknięte (osiągnęły status „Wygrana” lub „Przegrana”) w okresie. Obejmuje:", + "list_1": "✓ Transakcje utworzone wcześniej i zamknięte w okresie.", + "list_2": "✓ Transakcje utworzone i zamknięte w okresie.", + "list_3": "✗ Wyklucza wszystkie otwarte transakcje." + }, + "created": { + "text": "Ten raport obejmuje wszystkie transakcje utworzone w danym okresie, niezależnie od ich aktualnego statusu. Obejmuje:", + "list_1": "✓ Transakcje utworzone w okresie i nadal otwarte.", + "list_2": "✓ Transakcje utworzone i zamknięte w okresie.", + "list_3": "✗ Nie obejmuje transakcji utworzonych przed okresem." + } + } + }, + "days": { + "one_day": "dzień", + "several_days": ["dni", "dni", "dni"] + }, + "auto_update_select": { + "auto_update": "Auto-aktualizacja", + "modes": { + "never": "Nigdy", + "minute": "1 minuta", + "ten_minutes": "10 minut", + "thirty_minutes": "30 minut", + "hour": "1 godzina" + } + }, + "sales_goal_chart": { + "title_for_sales": "Cel sprzedaży", + "title": "Cel", + "hint": "Cel sprzedaży", + "settings_tip": "Ustawienia", + "plug_text_for_sales": "Ustaw cele", + "plug_text": "Ustaw cel" + }, + "traffic_light_report": { + "title": "Raport świetlny", + "subtitle": "Realizacja dzisiejszego planu", + "hint": { + "line1": "Raport „świetlny” to narzędzie, które pokazuje, jak dział sprzedaży realizuje plan sprzedaży na dzień.", + "line2": "Formuła obliczania realizacji planu sprzedaży na dziś to:", + "line3": "Procent realizacji = (Rzeczywista sprzedaż podzielona przez Plan na bieżący dzień) × 100%, gdzie:", + "list": { + "point1": "Rzeczywista sprzedaż to kwota, którą już sprzedano na dany moment.", + "point2": "Plan na bieżący dzień to część całkowitego planu sprzedaży na miesiąc, obliczona na bieżący dzień." + }, + "line4": "Przykład obliczenia:", + "line5": "Załóżmy, że twój plan sprzedaży na miesiąc wynosi 10 000 $. Dziś jest 10, co oznacza, że miesiąc minął w przybliżeniu 1/3. Twój plan na dziś to:", + "line6": "Plan na bieżący dzień = (10 000 $ podzielone przez 30 dni) × 10 dni = 3 333,33 $", + "line7": "Sprzedałeś towary za 3 000 $.", + "line8": "Twój procent realizacji planu wynosi:", + "line9": "Procent realizacji = (3 000 $ podzielone przez 3 333,33 $) × 100% ≈ 90%", + "line10": "W ten sposób twój plan jest realizowany w 90%, a w raporcie „świetlnym” zobaczysz zielony kolor, ponieważ realizacja planu przebiega pomyślnie." + }, + "plug_text": "Ustaw cele" + }, + "top_sellers": { + "title_for_sales": "Top 5 przyczyniających się", + "title": "Top 5 liderów", + "subtitle_for_sales": "Liderzy sprzedaży", + "hint": "Top 5 przyczyniających się liderów sprzedaży", + "others": "Inni", + "plug_text": "Gdy zaczniesz korzystać z {{company}}, statystyki dla Top 5 liderów sprzedaży pojawią się tutaj", + "plug_text_for_orders": "Po rozpoczęciu korzystania z {{company}} statystyki dla Top 5 liderów użytkowników pojawią się tutaj", + "plug_text_for_candidates": "Po rozpoczęciu korzystania z {{company}} statystyki dla Top 5 liderów użytkowników pojawią się tutaj" + }, + "analytics": { + "total_leads": "Łącznie leadów", + "total_orders": "Łącznie zamówień", + "total_candidates": "Łącznie kandydatów", + "new_leads": "Nowe leady", + "new_orders": "Nowe zamówienia", + "new_candidates": "Nowi kandydaci", + "won_leads": "Wygrane", + "completed_orders": "Zakończone", + "hired_candidates": "Zatrudnieni kandydaci", + "lost_leads": "Utracone", + "failed_orders": "Nieudane", + "rejected_candidates": "Odrzuceni kandydaci", + "total_tasks": "Łącznie zadań", + "completed_tasks": "Zakończone zadania", + "expired_tasks": "Przeterminowane zadania", + "no_tasks": "Leady: Brak zadań", + "cards_no_tasks": "Karty: Brak zadań", + "total_activities": "Łącznie aktywności", + "completed_activities": "Zakończone aktywności", + "expired_activities": "Przeterminowane aktywności", + "no_activities": "Leady: Brak aktywności", + "cards_no_activities": "Karty: Brak aktywności" + }, + "rating": { + "title": "Ocena", + "hint": "Ocena" + }, + "leads_status_chart": { + "title": "Przegląd statusu leadów:", + "title_for_orders": "Wskaźniki statusu zamówień:", + "title_for_candidates": "Wskaźniki statusu kandydatów:", + "subtitle": "Otwarte, Utracone, Wygrane", + "subtitle_for_orders": "Otwarte, Nieudane, Zakończone", + "subtitle_for_candidates": "Otwarte, Odrzuceni kandydaci, Zatrudnieni kandydaci", + "hint": "Przegląd statusu leadów", + "won": "Wygrane", + "completed": "Zakończone", + "hired_candidates": "Zatrudnieni kandydaci", + "lost": "Utracone", + "failed": "Nieudane", + "rejected_candidates": "Odrzuceni kandydaci", + "opened": "Otwarte" + }, + "sales_pipeline_indicators": { + "title": "Lejek konwersji", + "total_sales": "Łącznie wygrane", + "conversion": "Konwersja wygranych", + "average_amount": "Średnia kwota", + "average_term": "Średni czas trwania", + "days": ["dzień", "dni", "dni"] + }, + "switch": { + "deals_count": "Liczba transakcji", + "orders_count": "Liczba zamówień", + "sales_value": "Wartość sprzedaży", + "orders_value": "Wartość zamówień" + } + }, + "goal_settings_page": { + "title": "Ustawienia celów", + "total": "Łącznie", + "back_button": "Pulpit", + "users_select": "Użytkownicy", + "period_type": { + "month": "Miesiąc", + "quarter": "Kwartał" + }, + "change_period_modal": { + "title": "Ostrzeżenie!", + "annotation": "Zmiana okresu spowoduje utratę celów, które ustawiłeś wcześniej. Czy na pewno chcesz kontynuować?", + "approve": "Tak", + "cancel": "Nie" + }, + "periods": { + "months": { + "january": "Styczeń", + "february": "Luty", + "march": "Marzec", + "april": "Kwiecień", + "may": "Maj", + "june": "Czerwiec", + "july": "Lipiec", + "august": "Sierpień", + "september": "Wrzesień", + "october": "Październik", + "november": "Listopad", + "december": "Grudzień" + }, + "quarters": { + "quarter1": "Kwartał 1", + "quarter2": "Kwartał 2", + "quarter3": "Kwartał 3", + "quarter4": "Kwartał 4" + } + }, + "form_header_amount": "Kwota", + "form_header_quantity": "Transakcje", + "button_save": "Zapisz" + } + } + } +} diff --git a/frontend/public/locales/pl/module.scheduler.json b/frontend/public/locales/pl/module.scheduler.json new file mode 100644 index 0000000..2e46ac0 --- /dev/null +++ b/frontend/public/locales/pl/module.scheduler.json @@ -0,0 +1,219 @@ +{ + "scheduler": { + "pages": { + "scheduler_board_view_page": { + "create_appointment": "Utwórz wizytę", + "stats_footer": { + "assigned": { + "label": "Umówione", + "hint": "Niepotwierdzone wizyty" + }, + "confirmed": { + "label": "Potwierdzone", + "hint": "Potwierdzone wizyty" + }, + "completed": { + "label": "Zrealizowane", + "hint": "Zakończone wizyty" + }, + "not_took_place": { + "label": "Nieodbyta", + "hint": "Wizyty minione, nieoznaczone jako odbyty" + }, + "not_scheduled": { + "label": "Bez kolejnej wizyty", + "hint": "Wizyty bez ustalonego terminu kolejnej wizyty" + }, + "newbies": { + "label": "Pierwsza wizyta", + "hint": "Nowi goście" + }, + "total": { + "label": "Razem", + "hint": "Łączna liczba wizyt" + } + } + }, + "appointment_card_list_page": { + "visit_date": "Data wizyty" + }, + "scheduler_schedule_view_page": { + "sync": "Synchronizuj", + "report_settings": "Ustawienia raportu", + "module_settings": "Ustawienia modułu", + "stats_settings": "Ustawienia statystyk", + "settings": "Ustawienia", + "report": "Raport", + "overview": "Kalendarz", + "responsible": "Odpowiedzialny", + "stage": "Etap", + "not_provided": "Nie podano", + "statuses": { + "scheduled": "Zaplanowane", + "confirmed": "Potwierdzone", + "completed": "Zakończone", + "cancelled": "Anulowane" + }, + "created": "Utworzono", + "visit": "Wizytę {{number}}", + "description": "Opis", + "email": "E-mail", + "phone": "Telefon", + "price": "Cena", + "quantity": "Ilość", + "discount": "Zniżka", + "new_event": "Nowe wizytę", + "create_appointment": "Utwórz wizytę", + "tooltips": { + "reports_denied": "Nie masz uprawnień do przeglądania raportów. Skontaktuj się z administratorem konta, aby uzyskać dostęp." + }, + "placeholders": { + "search_visits": "Szukaj wizyty" + }, + "hooks": { + "use_appointments_history_services_columns": { + "name": "Nazwa", + "price": "Cena", + "quantity": "Ilość", + "discount": "Zniżka", + "amount": "Kwota" + }, + "use_appointments_history_columns": { + "date": "Data", + "time": "Czas", + "performer": "Wykonawca", + "services": "Usługi", + "total": "Łącznie", + "status": "Status" + }, + "use_appointment_service_block_columns": { + "discount": "Zniżka", + "quantity": "Ilość", + "amount": "Kwota" + } + }, + "ui": { + "add_appointment_modal": { + "error": "Nie można utworzyć wizyty. Może to być spowodowane próbą utworzenia wizyty nakładającego się na istniejące lub wcześniejszą datą zakończenia niż rozpoczęcia.", + "visits_history_empty": "Historia wizyty jest pusta", + "no_planned_visits": "Brak zaplanowanych wizyty", + "save_changes": "Zapisz zmiany", + "planned_visit_title": "{{date}} od {{startTime}} do {{endTime}}", + "general_information": "Informacje ogólne", + "planned_visits": "Zaplanowane wizyty", + "visits_history": "Historia wizyty", + "title": "Tytuł", + "visit": "Wizytę #{{number}}", + "edit_visit": "Edytuj wizytę – {{name}}", + "new_visit": "Zaplanuj wizytę", + "visit_parameters": "Parametry wizyta", + "status": "Status", + "scheduler": "Harmonogram wizyty", + "select_users_group": "Wybierz grupę użytkowników", + "select_user": "Wybierz użytkownika", + "description": "Opis", + "from": "Od:", + "to": "Do:", + "date_and_time": "Data i czas", + "addition_of_services": "Dodanie usług", + "add_service": "Dodaj usługę", + "no_services": "Brak usług", + "warning_title": "Zmiany nie są zapisane", + "warning_annotation": "Czy na pewno chcesz zamknąć to okno? Wszystkie zmiany zostaną utracone.", + "close": "Zamknij", + "delete": "Usuń", + "count": "Łącznie wizyty", + "last_visit": "Ostatnie wizytę", + "completed_count": [ + "Odbyło się {{count}} spotkanie", + "Odbyły się {{count}} spotkania", + "Odbyło się {{count}} spotkań" + ], + "placeholders": { + "select_time_period": "Okres czasu", + "title": "Tytuł wizyty", + "entity_name": "Nazwa jednostki", + "search_services": "Szukaj usług do dodania", + "user": "Użytkownik", + "users_group": "Grupa użytkowników", + "appointment_notes": "Notatka do wizyta" + }, + "repeating_appointments_block": { + "header": "Powtarzające się wizyty", + "hint": "W razie potrzeby dostosuj interwał i liczbę powtarzanych spotkań. Spotkania zostaną zaplanowane na dni robocze określone w Ustawieniach.", + "interval": "Interwał", + "count": "Liczba", + "intervals": { + "none": "Nie powtarzać", + "day": "Codziennie", + "week": "Raz w tygodniu", + "month": "Raz w miesiącu" + }, + "dates_display": { + "one_visit": "Powtarzająca się wizyta zaplanowana na {{date}} o {{time}}", + "visits_list": "Powtarzające się wizyty zaplanowane na {{dates}} o {{time}}", + "and": " i ", + "visits_interval_day": "Powtarzające się wizyty codziennie od {{from}} do {{to}} o {{time}}", + "visits_interval_week": "Powtarzające się wizyty raz w tygodniu od {{from}} do {{to}} o {{time}}", + "visits_interval_month": "Powtarzające się wizyty raz w miesiącu od {{from}} do {{to}} o {{time}}" + }, + "list": { + "title": "Powtarzające się spotkania", + "hint": "Wybierz daty powtarzających się spotkań, jeśli są potrzebne.", + "dates_select": "Daty spotkań", + "add_new_appointment": "Dodaj spotkanie", + "new_appointment": "Nowe spotkanie", + "placeholders": { + "dates_select": "Wybierz daty" + } + } + }, + "batch_cancel": { + "cancel_all": "Anuluj wszystkie", + "warning_title": "Czy na pewno chcesz anulować wszystkie zaplanowane wizyty?", + "warning_annotation": [ + "Anulowana zostanie {{count}} wizyta.", + "Anulowane zostaną {{count}} wizyty.", + "Anulowanych zostanie {{count}} wizyt." + ], + "back": "Powrót", + "cancel": "Cofnąć" + }, + "duplicate_warning_modal": { + "move": "Przenieś", + "same_time_title": "Spotkanie już zaplanowane na tę godzinę", + "same_day_title": "Spotkanie już zaplanowane na ten dzień", + "same_time_annotation": "W tym kalendarzu nie można tworzyć powtórnych spotkań tego samego dnia. Edytuj istniejące spotkanie lub zmień datę tego spotkania.", + "same_day_annotation": "W tym kalendarzu nie można tworzyć powtórnych spotkań tego samego dnia. Czy chcesz przenieść spotkanie zaplanowane na {{time}}?" + }, + "intersect_warning_modal": { + "title": "Nakładanie się spotkań", + "annotation": "Spotkanie nakłada się czasowo na już zaplanowane w tym kalendarzu. Wybierz inny termin lub zmień osobę odpowiedzialną." + } + }, + "stats_settings_drawer": { + "title": "Ustawienia statystyk", + "description": "Wyświetlane wartości statystyk", + "stats": { + "assigned": "Przydzielone", + "confirmed": "Potwierdzone", + "completed": "Zrealizowane", + "not_took_place": "Nieodbyta", + "not_scheduled": "Bez kolejnej wizyty", + "newbies": "Pierwsza wizyta", + "total": "Razem" + } + }, + "local_time_warning": { + "local_correction": [ + "Korekta czasu lokalnego: {{hours}} godzina", + "Korekta czasu lokalnego: {{hours}} godziny", + "Korekta czasu lokalnego: {{hours}} godzin" + ], + "hint": "Nie znajdujesz się w strefie czasowej określonej w ustawieniach systemu, więc godziny pracy w kalendarzu i godziny spotkań są przesunięte o określoną liczbę godzin. Jeśli wydaje ci się, że to błąd, zmień strefę czasową w ustawieniach lub skontaktuj się z działem wsparcia." + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.telephony.json b/frontend/public/locales/pl/module.telephony.json new file mode 100644 index 0000000..4a6f8d9 --- /dev/null +++ b/frontend/public/locales/pl/module.telephony.json @@ -0,0 +1,242 @@ +{ + "telephony": { + "pages": { + "calls_configuring_scenarios_page": { + "title": "Konfiguracja działania IVR w module CRM", + "failed_to_reach": "Nieosiągalny", + "creates_manually": "Użytkownik ręcznie tworzy kartę kontaktu i transakcji przy odbieraniu połączenia przychodzącego", + "creates_manually_hint": "Kiedy nadchodzi połączenie z nieznanego numeru, pojawia się okno połączenia przychodzącego dla użytkownika. Użytkownik może odebrać połączenie, a jeśli uzna je za potencjalną okazję biznesową, może w trakcie rozmowy utworzyć kartę kontaktu lub zarówno kartę kontaktu, jak i transakcji, za pomocą dwóch kliknięć. Ta metoda jest preferowana w stosunku do automatycznego generowania kart, ponieważ możesz otrzymywać połączenia nie tylko od potencjalnych klientów, ale także od różnych innych firm. To podejście pomaga uniknąć tworzenia niepotrzebnych kart kontaktu i transakcji.", + "components": { + "configuring_scenarios_header_controls": { + "cancel": "Anuluj", + "save_scenarios": "Zapisz scenariusze", + "failed_to_reach": "Nieosiągalny" + }, + "entity_scenario_radio_group": { + "automatically_create": "Automatycznie utwórz", + "contact_or_company": "Kontakt lub firma", + "deal": "Transakcja", + "select_contact_or_company_first": "Najpierw wybierz kontakt lub firmę", + "deal_pipeline": "Lejek sprzedażowy", + "select_deal_first": "Najpierw wybierz transakcję", + "placeholders": { + "select_deal": "Wybierz transakcję", + "select_pipeline": "Wybierz lejek sprzedażowy" + } + }, + "incoming_calls_block": { + "incoming_calls": "Połączenia przychodzące" + }, + "incoming_known_missing_scenario_block": { + "missed_from_known_number": "Nieodebrane połączenie z znanego numeru" + }, + "incoming_unknown_missing_scenario_block": { + "missed_call_from_unknown_number": "Nieodebrane połączenie z nieznanego numeru", + "auto_create": "Automatycznie utwórz", + "contact_or_company": "Kontakt lub firma", + "responsible": "Odpowiedzialny", + "select_contact_or_company_first": "Najpierw wybierz kontakt lub firmę", + "deal_pipeline": "Lejek sprzedażowy", + "select_deal_first": "Najpierw wybierz transakcję", + "placeholders": { + "select_responsible": "Wybierz odpowiedzialnego", + "select_deal": "Wybierz transakcję", + "select_pipeline": "Wybierz lejek sprzedażowy" + } + }, + "incoming_unknown_scenario_block": { + "call_from_unknown_number": "Połączenie z nieznanego numeru" + }, + "task_and_activities_scenario_group": { + "task": "Zadanie", + "select_contact_or_company_first": "Najpierw wybierz kontakt lub firmę", + "title": "Automatycznie utwórz zadanie lub aktywność", + "do_not_create": "Nie twórz", + "activity": "Aktywność", + "activity_type": "Typ aktywności", + "complete": "Ukończ w ciągu", + "description": "Opis", + "task_title": "Tytuł zadania", + "minutes": "minut", + "placeholders": { + "activity_description": "Opis aktywności", + "task_description": "Opis zadania", + "title": "Tytuł" + } + }, + "outgoing_calls_block": { + "outgoing_calls": "Połączenia wychodzące", + "failed_to_reach": "Nieosiągalny" + }, + "outgoing_unanswered_scenario_block": { + "failed_to_reach": "Nieosiągalny", + "unanswered_outgoing_calls": "Nieodebrane połączenia wychodzące", + "create_note": "Utwórz notatkę w historii karty kontaktu/transakcji", + "placeholders": { + "note_content": "Treść notatki" + } + }, + "outgoing_unknown_scenario_block": { + "call_to_unknown_number": "Połączenie na nieznany numer", + "creates_manually": "Użytkownik ręcznie tworzy kartę kontaktu i transakcji podczas wykonywania połączenia wychodzącego" + } + } + }, + "calls_sip_registrations_page": { + "provider": "Dostawca", + "removed_or_detached": "Usunięto lub odłączono", + "removed_or_detached_hint": "Ta rejestracja SIP nie jest już dostępna, ponieważ została usunięta lub odłączona na platformie Voximplant. Aby kontynuować korzystanie z tej rejestracji SIP, należy ponownie podłączyć ją do aplikacji lub usunąć i utworzyć nową. Skontaktuj się z pomocą techniczną {{mail}}, aby uzyskać więcej informacji.", + "users": "Użytkownicy", + "users_hint": "Wybierz użytkowników, którzy mają dostęp do tej rejestracji SIP", + "default_name": "SIP Rejestracja #{{number}}", + "name": "Nazwa rejestracji *", + "title_annotation": "Instrukcje rejestracji SIP znajdziesz w sekcji \"Integracje\", klikając przycisk \"Zainstaluj\" w grupie \"Telefonia i PBX\".", + "link_to_vx_portal": "Link do Portalu Voximplant", + "providers": { + "uis": "UIS", + "zadarma": "Zadarma", + "mango_office": "Mango Office", + "beeline": "Beeline", + "mts": "MTS", + "mgts": "MGTS", + "tele2": "Tele2", + "megafon": "Megafon", + "rostelecom": "Rostelecom", + "unknown": "Nieznany" + }, + "annotation": "Podczas tworzenia rejestracji SIP z konta Voximplant opłata miesięczna zostanie naliczona natychmiast. Sprawdź kwotę na portalu Voximplant. Tworzenie może zająć kilka minut.", + "last_updated": "Ostatnia aktualizacja: {{lastUpdated}}", + "delete_warning": { + "title": "Czy na pewno chcesz usunąć tę rejestrację SIP?", + "annotation": "Tej akcji nie można cofnąć. Będziesz mógł przywrócić rejestrację SIP tylko poprzez utworzenie nowej." + }, + "registration_successful": "Rejestracja zakończona sukcesem", + "error_annotation": "Upewnij się, że wprowadziłeś poprawne dane uwierzytelniające lub sprawdź swoje konto Voximplant, aby uzyskać więcej szczegółów.", + "empty": "Nie ma jeszcze rejestracji SIP", + "save_error": "Nie udało się zapisać rejestracji. Upewnij się, że wszystkie wprowadzone dane są poprawne.", + "add": "Dodaj", + "title": "Rejestracje SIP", + "edit_sip_registration": "Edytuj rejestrację SIP", + "add_sip_registration": "Dodaj rejestrację SIP", + "proxy": "Proxy *", + "sip_user_name": "Nazwa użytkownika SIP *", + "password": "Hasło", + "outbound_proxy": "Proxy wyjściowy", + "auth_user": "Użytkownik autoryzowany", + "auth_user_hint": "Zwykle jest to ten sam nazwa użytkownika.", + "placeholders": { + "all_users": "Wszyscy użytkownicy", + "name": "Nowa rejestracja SIP", + "proxy": "sip.provider.org", + "sip_user_name": "user_name", + "password": "********", + "outbound_proxy": "outbound.provider.org", + "auth_user": "auth_user_name" + } + }, + "calls_settings_users_page": { + "empty": "Nie dodałeś jeszcze żadnych użytkowników", + "users": "Użytkownicy", + "add_user": "Dodaj użytkownika", + "remove_user": "Usuń użytkownika", + "active": "Aktywny", + "create": "Utwórz", + "open_sip_settings": "Otwórz ustawienia SIP", + "continue": "Kontynuuj", + "sip_settings_title": "Ustawienia SIP – {{userName}}", + "sensitive_warning": "To są wrażliwe informacje, upewnij się, że nie udostępniasz ich osobom trzecim. Najedź na hasło, aby je wyświetlić.", + "user_name": "Nazwa użytkownika", + "domain": "Domena", + "password": "Hasło", + "remove_warning_title": "Usunąć {{userName}}?", + "remove_warning_annotation": "Tej akcji nie można cofnąć. Będziesz mógł przywrócić użytkownika tylko poprzez utworzenie nowego.", + "remove": "Usuń" + }, + "calls_settings_account_page": { + "synchronise_with_vx": "Synchronizuj z Voximplant", + "delete_warning_modal": { + "title": "Czy na pewno chcesz usunąć numer telefonu {{phoneNumber}}?", + "annotation": "Tej akcji nie można cofnąć." + }, + "unknown": "Nieznany", + "region": "Region", + "phone_number": "Numer telefonu", + "users": "Użytkownicy", + "state": "Stan", + "connect_phone_number": "Połącz", + "disconnect_phone_number": "Rozłącz", + "delete_phone_number": "Usuń", + "phone_numbers": "Numery telefonów", + "phone_numbers_annotation1": "Połącz wszystkie dostępne numery telefonów Voximplant z swoim kontem.", + "phone_numbers_annotation2": "Możesz dodać aktywnych operatorów w kolumnie użytkowników, aby mieli dostęp do połączonego numeru.", + "phone_numbers_annotation3": "ℹ️ Usuwając lub odłączając numer bezpośrednio w aplikacji Voximplant, nie zapomnij usunąć go z listy numerów lub naciśnij przycisk synchronizacji który pojawi się w tym przypadku.", + "connect_all_available_phone_numbers": "Połącz wszystkie dostępne numery", + "empty_phone_numbers": "Brak dostępnych lub połączonych numerów telefonów", + "placeholders": { + "all_users": "Wszyscy użytkownicy" + }, + "connect_telephony_title": "Podłącz telekomunikację i uzyskaj więcej możliwości z {{company}}", + "connect_telephony_warning": "Uwaga: Kliknięcie przycisku automatycznie utworzy konto Voximplant powiązane z {{company}}. Nie rejestruj się ręcznie, ponieważ to konto nie będzie powiązane z {{company}}.", + "connect": "Podłącz telekomunikację", + "account": "Konto", + "voximplant_balance": "Saldo Voximplant (Wkrótce...)", + "recharge": "Doładuj", + "subscription_fee": "Opłata abonamentowa (Wkrótce...)", + "available_numbers": "Dostępne numery: {{amount}}", + "account_is_not_approved": "Konto nie jest zatwierdzone", + "not_approved_annotation": "Bez potwierdzenia konta nie będziesz mógł korzystać z podłączonych numerów telefonów.", + "approve": "Zatwierdź" + } + }, + "components": { + "telephony_button": { + "telephony": "Telekomunikacja", + "not_connected": "Podłącz telekomunikację, aby umożliwić funkcjonalność połączeń." + }, + "telephony_modal": { + "connect_to_transfer": "Musisz połączyć połączenie, aby transferować", + "no_operators_to_transfer_call_to": "Nie ma operatorów do transferowania połączenia do", + "coming_soon": "Prójrze...", + "unknown_number": "Nieznany numer", + "amwork_calls": "Połączenia {{company}}", + "active_call_control": { + "cancel": "Anuluj", + "transfer": "Transferuj", + "add_to_call": "Dodaj", + "end_call": "Zakończ", + "calling": "dzwoni", + "mute": "Wycisz", + "unmute": "Wyłącz wyciszenie" + }, + "incoming_call_control": { + "transfer": "Transferuj", + "add_to_call": "Dodaj", + "accept": "Odbierz", + "incoming_call": "połączenie przychodzące", + "mute": "Wycisz", + "unmute": "Wyłącz wyciszenie", + "end_call": "Zakończ", + "decline": "Odrzuć" + }, + "outgoing_call_initializer": { + "outgoing_number": "Numer odbiorcy", + "no_available_phone_numbers": "Brak dostępnych numery telefonów", + "keys": "Klucze", + "recent": "Ostatnie", + "outgoing": "Połączenie wychodzące", + "incoming": "Połączenie przychodzące", + "yesterday": "Wczoraj", + "today": "Dzisiaj", + "no_calls": "Brak połączeń" + }, + "call_control_template": { + "call_from": "Połączenie od: {{number}}", + "create": "Utwórz", + "placeholders": { + "select_card": "Wybierz kartę" + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/module.tutorial.json b/frontend/public/locales/pl/module.tutorial.json new file mode 100644 index 0000000..425f2b6 --- /dev/null +++ b/frontend/public/locales/pl/module.tutorial.json @@ -0,0 +1,55 @@ +{ + "tutorial": { + "tutorial_drawer": { + "title": "Baza Wiedzy", + "empty": "Nie ma tu jeszcze nic.", + "create_tutorial_group_block": { + "create_group": "Utwórz grupę", + "save": "Zapisz", + "cancel": "Anuluj" + }, + "create_tutorial_item_block": { + "create_link": "Utwórz link", + "save": "Zapisz", + "cancel": "Anuluj" + }, + "tutorial_edit_group_item_form": { + "placeholders": { + "name": "Nazwa", + "link": "Link", + "all": "Wszystkie" + } + }, + "tutorial_edit_group_items_forms": { + "name": "Nazwa", + "link": "Link", + "users": "Użytkownicy", + "products": "Produkty", + "products_hint": "Wybierz produkty, w których ten link będzie wyświetlany. Na przykład, jeśli wybrane są zadania – link będzie wyświetlany tylko w sekcji zadań." + }, + "tutorial_group_name_block": { + "placeholders": { + "group_name": "Nazwa grupy" + } + }, + "hooks": { + "use_get_tutorial_products_options": { + "product_types": { + "builder": "Kreator", + "task": "Zadania", + "mail": "Poczta", + "multi_messenger": "Multi Messenger", + "settings": "Ustawienia" + } + }, + "use_get_tutorial_products_groups": { + "groups": { + "entity_type": "Moduły", + "products_section": "Produkty", + "scheduler": "Harmonogramy" + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/page.board-settings.json b/frontend/public/locales/pl/page.board-settings.json new file mode 100644 index 0000000..fd4dd79 --- /dev/null +++ b/frontend/public/locales/pl/page.board-settings.json @@ -0,0 +1,33 @@ +{ + "board_settings": { + "ui": { + "board_settings_header": { + "header": "Ustawienia tablicy", + "delete_board": "Usuń tablicę", + "title": "Uwaga!", + "annotation": "Usunięcie tablicy spowoduje trwałe usunięcie wszystkich kart. Czy na pewno chcesz kontynuować?", + "save": "Zapisz", + "cancel": "Anuluj", + "leave": "Wyjść z ustawień" + }, + "stage_name_hint": "Maksymalnie {{length}} znaków" + }, + "entity_type_board_settings_page": { + "automation_new": "Automatyzacja 2.0", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Wkrótce...)", + "warning_title": "Nie możesz usunąć ostatniej tablicy", + "warning_annotation": "Jeśli chcesz usunąć tę tablicę, najpierw utwórz nową." + }, + "task_board_settings_page": { + "warning_title": "Nie możesz usunąć tablicy systemowej", + "warning_annotation": "Ta tablica jest systemowa i nie może zostać usunięta." + }, + "delete_stage_warning_modal": { + "warning_title": "Czy na pewno chcesz usunąć ten etap?", + "warning_annotation": "Wybierz etap, do którego chcesz przenieść istniejące elementy.", + "delete": "Usuń", + "placeholder": "Etap do przeniesienia" + } + } +} diff --git a/frontend/public/locales/pl/page.dashboard.json b/frontend/public/locales/pl/page.dashboard.json new file mode 100644 index 0000000..7402b13 --- /dev/null +++ b/frontend/public/locales/pl/page.dashboard.json @@ -0,0 +1,18 @@ +{ + "dashboard_page": { + "top_sellers": { + "title": "Ranking", + "hint": "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + }, + "analytics": { + "total_entities": "Łącznie", + "won_entities": "Wygrane", + "lost_entities": "Przegrane", + "new_entities": "Nowe", + "total_tasks": "Łącznie zadań", + "completed_tasks": "Zakończone zadania", + "expired_tasks": "Przeterminowane zadania", + "no_tasks": "Brak zadań" + } + } +} diff --git a/frontend/public/locales/pl/page.login.json b/frontend/public/locales/pl/page.login.json new file mode 100644 index 0000000..420c8a6 --- /dev/null +++ b/frontend/public/locales/pl/page.login.json @@ -0,0 +1,24 @@ +{ + "login": { + "login_form": { + "title": "Zaloguj się na swoje konto", + "login": "Zaloguj się", + "invalid": "Nieprawidłowe dane", + "or": "Lub", + "caption": "Nie masz jeszcze konta?", + "sign_up": "Zarejestruj się", + "forgot_your_password": "Zapomniałeś hasła?", + "placeholders": { + "login": "E-mail", + "password": "Hasło" + }, + "invalid_email_error": "Nieprawidłowy format e-mail", + "invalid_password_error": "Hasło jest wymagane" + }, + "left_block": { + "title": "Witamy", + "annotation": "Świetna praca, z przyjemnością!", + "image_alt": "{{company}} konstruktor bez kodu, stwórz swoje własne unikalne CRM" + } + } +} diff --git a/frontend/public/locales/pl/page.settings.json b/frontend/public/locales/pl/page.settings.json new file mode 100644 index 0000000..a121936 --- /dev/null +++ b/frontend/public/locales/pl/page.settings.json @@ -0,0 +1,901 @@ +{ + "settings_page": { + "app_sumo_tiers_data": { + "feature_1": "Nielimitowane produkty funkcjonalne w kreatorze", + "feature_2": "Nielimitowany pipeline lub tablica", + "feature_3": "Nielimitowane pola niestandardowe", + "feature_4": "Automatyzacja", + "feature_5": "Dostęp dożywotni", + "storage": "{{storage}} GB miejsca na użytkownika" + }, + "templates": { + "document_creation_fields_page": { + "ui": { + "system_section": { + "name": "Pola systemowe", + "current_date": "Bieżąca data", + "document_number": "Numer dokumentu" + }, + "entity_type_section": { + "name": "Nazwa", + "owner": "Odpowiedzialny" + }, + "selector": { + "words": "Słowa" + } + } + }, + "document_templates_page": { + "order_fields": { + "order": "Zamówienie", + "order_items": "Pozycje zamówienia", + "specific_order_item": "Pozycja zamówienia", + "order_number": "Numer zamówienia", + "order_amount": "Kwota zamówienia", + "order_currency": "Waluta zamówienia", + "order_item": { + "number": "Numer", + "name": "Nazwa", + "price": "Cena", + "currency": "Waluta", + "discount": "Rabat", + "tax": "Podatek", + "quantity": "Ilość", + "amount": "Kwota" + }, + "text_block_1": "Możesz wypełnić dokument danymi o wszystkich produktach i usługach zamówienia w formie tekstu lub tabeli. Możesz również dodać dane tylko dla konkretnej pozycji zamówienia.", + "text_block_2": "Aby wypełnić dokument danymi o wszystkich pozycjach zamówienia w formie tekstowej, musisz wygenerować wymagany tekst w dokumencie, a aby wstawić dane o pozycjach zamówienia, musisz użyć specjalnych kodów wspomnianych powyżej.", + "text_block_3": "WAŻNE: Na początku bloku listy pozycji zamówienia należy wstawić specjalny kod {#order.products}. Na końcu bloku należy wstawić specjalny symbol {/}. Tekst bloku między symbolami {#order.products} i {/} będzie powtarzany w utworzonym dokumencie tyle razy, ile jest pozycji w wybranym zamówieniu.", + "text_example": "Przykład tekstu", + "text_block_4": "{name} w ilości {quantity} w cenie {price}", + "text_block_5": "Aby wypełnić dokument danymi o wszystkich pozycjach zamówienia w formie tabeli, musisz utworzyć tabelę w szablonie. Jeśli to konieczne, nazwy kolumn mogą być podane w nagłówku tabeli. Tabela, oprócz nagłówka, powinna zawierać wiersz z niezbędnymi polami do wypełnienia.", + "text_block_6": "WAŻNE: Pierwsza komórka wiersza musi zawierać kod {#order.products} na samym początku komórki; ostatnia komórka musi zawierać kod {/} na samym końcu komórki. Te kody są potrzebne do wygenerowania tabeli i nie będą wyświetlane w końcowym dokumencie.", + "table_example": "Przykład tabeli", + "text_block_7": "Aby wypełnić dokument danymi o konkretnej pozycji zamówienia, musisz użyć specjalnych symboli.", + "text_block_8": "Symbol [0] określa, który numer pozycji zamówienia zostanie wybrany do wstawienia danych do dokumentu. Numery pozycji zaczynają się od 0 dla pierwszej pozycji zamówienia." + }, + "no_document_templates": "Nie masz jeszcze żadnych szablonów dokumentów.", + "can_create_new_there": "Aby dodać szablon, kliknij przycisk \"Dodaj szablon dokumentu\".", + "add_document_template": "Dodaj szablon dokumentu", + "ui": { + "document_template_item": { + "available_in_sections": "Dostępne w modułach", + "creator": "Twórca", + "access_rights": "Prawa dostępu", + "warning_title": "Czy na pewno chcesz usunąć ten szablon", + "warning_annotation": "Ta akcja nie może być cofnięta.", + "placeholders": { + "select_sections": "Wybierz moduły" + } + } + } + } + }, + "billing_page": { + "trial_in_progress": "Twój okres próbny jest aktywny!", + "trial_over": "Twój okres próbny dobiegł końca!", + "subscription_over": "Twoja subskrypcja dobiegła końca!", + "request_mywork_billing_form_modal": { + "error": "Wystąpił błąd podczas przesyłania Twojego żądania. Upewnij się, że wprowadzone informacje są prawidłowe, i spróbuj ponownie.", + "send_request": "Wyślij zgłoszenie", + "header": "Zostaw zgłoszenie, a my się z Tobą skontaktujemy", + "full_name": "Pełne imię i nazwisko *", + "phone": "Telefon *", + "email": "Email *", + "number_of_users": "Liczba użytkowników", + "comment": "Komentarz", + "placeholders": { + "full_name": "Jan Kowalski", + "comment": "Dodatkowe informacje" + } + }, + "for_all": "Dla wszystkich użytkowników", + "request_billing_modal": { + "send": "Wyślij", + "title": "Plan \"{{planName}}\" – Formularz kontaktowy", + "name": "Imię *", + "phone": "Telefon *", + "email": "E-mail *", + "number_of_users": "Liczba użytkowników *", + "comment": "Komentarz", + "placeholders": { + "additional_info": "Dodatkowe informacje" + } + }, + "request_button": "Zostaw zapytanie", + "feedback_annotation": "Wybrałeś plan {{planName}}, prosimy zostawić zapytanie, a my skontaktujemy się z Tobą wkrótce.", + "free_trial_duration": "Bezpłatny okres próbny – 14 dni", + "started_in": "Rozpoczęty w:", + "expires_in": "Wygasa w:", + "users_limit": "Limit użytkowników:", + "renew": "Odnowić subskrypcję", + "upgrade": "Ulepszyć", + "open_stripe_portal": "Otwórz portal Stripe", + "manage_your_subscription": "💸 Zarządzaj swoją subskrypcją na Stripe Customer Portal", + "number_of_users": "Liczba użytkowników", + "finish_order": "Zakończ zamówienie", + "save_percentage": "Oszczędź {{percentage}}", + "per_user_month": "na użytkownika / miesiąc", + "select_this_plan": "Wybierz plan", + "selected": "Wybrany", + "1_month": "1 miesiąc", + "12_month": "12 miesięcy", + "save_up_to": "Oszczędź do {{percent}}%", + "total": "Razem: {{amount}}", + "payment_success": "Płatność zakończona sukcesem! Otrzymasz potwierdzenie e-mailem.", + "set_free_plan_warning_modal": { + "title": "Ważne!", + "annotation": { + "text1": "Limit użytkowników Twojego planu subskrypcji został przekroczony. Dodatkowe konta zostaną automatycznie usunięte zgodnie z warunkami planu. Możesz ręcznie usunąć niezbędne konta", + "link": "tutaj", + "text2": "Twoje inne dane systemowe pozostaną nienaruszone." + }, + "approve": "Potwierdzić", + "cancel": "Anulować" + }, + "lifetime_promo_plans": { + "discount": "{{percent}}% zniżki", + "lifetime_subscription": "na dożywotnią subskrypcję", + "black_text": "", + "until": "do", + "business_plan": "Starter", + "business_plan_description": "Wszystko, czego potrzebujesz do skutecznego zarządzania sprzedażą i projektami.", + "advanced_plan": "Business", + "advanced_plan_description": "Pełna funkcjonalność z automatyzacją i zaawansowanymi ustawieniami.", + "price_rise_counter": { + "months": ["miesiąc", "miesiące", "miesięcy"], + "days": ["dzień", "dni", "dni"], + "hours": ["godzina", "godziny", "godzin"], + "minutes": ["minuta", "minuty", "minut"], + "sale_ends": "Oferta kończy się za:" + }, + "lifetime_deal_plan_block": { + "lifetime_subscription": "Dożywotnia subskrypcja" + }, + "annual_deal_pricing_block": { + "annual_subscription": "1 rok subskrypcji" + }, + "monthly_deal_pricing_block": { + "monthly_subscription": "1 miesiąc subskrypcji", + "number_of_users": "Liczba użytkowników", + "per_user": "/msc na użytkownika", + "minimum_period": "minimalny okres subskrypcji: 6 miesięcy", + "buy": "Kup" + }, + "pricing_block_with_users_selector": { + "users": ["użytkownik", "użytkownika", "użytkowników"], + "lifetime_postfix": " na zawsze", + "packages": "Pakiety użytkowników", + "per_user": "/mies. za użytkownika", + "price_description": "użytkownik / miesiąc, rozliczenie roczne", + "fix_price": "Stała cena", + "big_savings": "Wyjątkowe oszczędności", + "savings": "Oszczędność: ", + "buy": "Kup" + } + } + }, + "edit_groups_page": { + "add_new_group": "Dodaj nową grupę", + "subgroup": "Podgrupa", + "delete_warning_title": "Czy na pewno chcesz usunąć {{name}}?", + "delete_warning_annotation1": "Cały personel z tej grupy", + "delete_warning_annotation2": "(i jej podgrupy, jeśli istnieją)", + "delete_warning_annotation3": "można warunkowo przypisać do innej grupy lub podgrupy.", + "add_subgroup": "Dodaj podgrupę", + "save": "Zapisz", + "cancel": "Anuluj", + "delete": "Usuń", + "working_time": "Godziny pracy", + "to": "do", + "placeholders": { + "group": "Wpisz nazwę", + "select_group": "Wybierz grupę", + "select_subgroup": "Wybierz podgrupę", + "new_subgroup": "Nowa podgrupa" + } + }, + "edit_user_page": { + "save": "Zapisz", + "save_and_add": "Zapisz i dodaj", + "first_name": "Imię *", + "first_name_hint": "Maksymalnie {{length}} znaków", + "last_name": "Nazwisko *", + "last_name_hint": "Maksymalnie {{length}} znaków", + "email": "Email *", + "phone_number": "Telefon", + "password": "Hasło *", + "group": "Grupa", + "owner": "Odpowiedzialny", + "owner_hint": "Odpowiedzialny jest super administratorem i odpowiedzialny konta. Odpowiedzialny nie może być usunięty, nie można odebrać mu praw ani niczego zabronić. Tylko jeden użytkownik może być odpowiedzialny konta.", + "admin": "Administrator", + "admin_hint": "Administrator ma nieograniczone prawa do zarządzania i konfiguracji konta.", + "email_error": "Ten email jest już powiązany z innym kontem {{company}}. Aby utworzyć użytkownika, wybierz inny adres email.", + "unknown_error": "Wystąpił nieznany błąd podczas edytowania użytkownika. Sprawdź, czy pola są poprawnie wypełnione i spróbuj ponownie.", + "position": "Stanowisko", + "visible_users": "Widoczni użytkownicy", + "visible_users_hint": "Użytkownicy, których bieżący użytkownik będzie mógł wybrać z listy", + "working_time": "Godziny pracy", + "to": "do", + "group_working_time": "Godziny pracy grupy", + "group_working_time_hint": "Użytkownik odziedziczy godziny pracy grupy, do której należy. Jeśli grupa nie ma ustawionych godzin, zostaną użyte godziny firmy.", + "user_calendar_title": "Harmonogram rezerwacji online", + "user_calendar_hint": "Ten harmonogram określa dostępne przedziały czasowe dla rezerwacji online. Jeśli go nie ma, dostępność będzie określana na podstawie kalendarza.", + "delete_user_calendar": "Usuń", + "add_user_calendar": "Utwórz harmonogram", + "time_buffer_before": "Przerwa przed spotkaniem", + "time_buffer_after": "Przerwa po spotkaniu", + "appointment_limit": "Limit spotkań dziennie", + "schedule": "Harmonogram", + "no_duration": "Nie", + "minutes": "minut", + "placeholders": { + "all_users": "Wszyscy użytkownicy", + "manager": "Menedżer", + "password": "Wymyśl hasło", + "new_password": "Nowe hasło", + "group": "Wybierz grupę" + }, + "ui": { + "menu_accesses_item": { + "title": "Dostępy do menu", + "denied": "Odmowa", + "responsible": "Odpowiedzialny", + "allowed": "Dozwolone" + }, + "object_permissions_list": { + "tasks": "Zadania", + "activities": "Aktywności" + }, + "object_permissions_item": { + "hint": "Możesz skonfigurować uprawnienia dla użytkownika. Możesz skonfigurować uprawnienia użytkownika do tworzenia, przeglądania, edytowania i usuwania obiektów w sekcji {{title}}.", + "create": "Tworzyć", + "view": "Przeglądać", + "edit": "Edytować", + "delete": "Usunąć", + "report": "Raporty", + "dashboard": "Pulpit", + "denied": "Odmowa", + "responsible": "Odpowiedzialny", + "subdepartment": "Podgrupa", + "department": "Grupa", + "allowed": "Dozwolone" + }, + "products_permissions_item": { + "warehouses_title": "Dostęp do Magazynów", + "warehouses_hint": "Wybierz magazyny, do których użytkownik ma dostęp, aby tworzyć zamówienia, przeglądać produkty i wysyłki.", + "hint": "Możesz skonfigurować uprawnienia dla użytkownika.", + "create_product": "Tworzyć produkt", + "view_product": "Przeglądać produkt", + "edit_product": "Edytować produkt", + "create_order": "Tworzyć zamówienie", + "shipment": "Wysyłka", + "delete": "Usunąć", + "denied": "Odmowa", + "allowed": "Dozwolone", + "placeholders": { + "all_warehouses": "Wszystkie magazyny" + } + } + } + }, + "general_settings_page": { + "enable": "Włączyć", + "contact_duplicates_hint": "Włącz tę opcję, aby ostrzegać użytkowników przy próbie utworzenia nowego kontaktu lub firmy z już istniejącym numerem telefonu lub adresem e-mail.", + "contact_duplicates": "Ostrzegaj o duplikatach kontaktów", + "date_format": "Format daty", + "auto": "Automatycznie", + "phone_format": "Format telefonu", + "international": "Międzynarodowy", + "free": "Darmowy", + "company": "Firma", + "domain": "Nazwa domeny", + "upload_logo": "Prześlij nowe logo", + "delete_logo": "Usuń logo", + "logo_caption": "Preferowany rozmiar logo to 111px na 22px", + "language": "Język", + "time_zone": "Strefa czasowa", + "working_days": "Dni robocze", + "start_of_week": "Początek tygodnia", + "Monday": "Poniedziałek", + "Tuesday": "Wtorek", + "Wednesday": "Środa", + "Thursday": "Czwartek", + "Friday": "Piątek", + "Saturday": "Sobota", + "Sunday": "Niedziela", + "currency": "Waluta", + "working_time": "Godziny pracy", + "to": "do", + "number_format": "Format liczby", + "currency_select": "Wybór waluty", + "currencies": { + "USD": "Dolar amerykański, USD", + "EUR": "Euro, EUR", + "GBP": "Funt brytyjski, GBP", + "JPY": "Jen japoński, JPY", + "CNY": "Juan chiński, CNY", + "INR": "Rupia indyjska, INR", + "RUB": "Rubel rosyjski, RUB", + "MXN": "Peso meksykańskie, MXN", + "BRL": "Real brazylijski, BRL", + "ZAR": "Rand południowoafrykański, ZAR", + "AUD": "Dolar australijski, AUD", + "CAD": "Dolar kanadyjski, CAD", + "AED": "Dirham ZEA, AED", + "CHF": "Frank szwajcarski, CHF", + "TRY": "Lira turecka, TRY", + "UAH": "Hrywna ukraińska, UAH", + "KRW": "Won południowokoreański, KRW", + "NZD": "Dolar nowozelandzki, NZD", + "NOK": "Korona norweska, NOK", + "SEK": "Korona szwedzka, SEK", + "DKK": "Korona duńska, DKK", + "PLN": "Złoty polski, PLN", + "CZK": "Korona czeska, CZK", + "HUF": "Forint węgierski, HUF", + "IDR": "Rupia indonezyjska, IDR", + "ILS": "Nowy szekel izraelski, ILS", + "MYR": "Ringgit malezyjski, MYR", + "PHP": "Peso filipińskie, PHP", + "SGD": "Dolar singapurski, SGD", + "THB": "Baht tajski, THB", + "KZT": "Tenge kazachstańskie, KZT", + "CRC": "Colón kostarykański, CRC", + "COP": "Peso kolumbijskie, COP", + "BOB": "Boliviano, BOB", + "HKD": "Dolar hongkoński, HKD", + "SAR": "Rial saudyjski, SAR", + "VND": "Dong wietnamski, VND", + "EGP": "Funt egipski, EGP", + "KWD": "Dinar kuwejcki, KWD", + "PKR": "Rupia pakistowska, PKR", + "LKR": "Rupia środkowska, LKR", + "BDT": "Taka bangalorska, BDT", + "NGN": "Naira nigeryjska, NGN", + "GHS": "Cedi ghański, GHS", + "TWD": "Nowy dolar tajwański, TWD", + "MAD": "Dirham marokański, MAD", + "ARS": "Peso argentyńskie, ARS", + "PEN": "Nuevo sol peruwiański, PEN", + "UYU": "Peso urugwajskie, UYU", + "BGN": "Lev bułgarski, BGN", + "RON": "Leu rumuński, RON", + "LBP": "Funt libański, LBP" + } + }, + "integrations_page": { + "calendars_and_tasks": "Kalendarze i zadania", + "telephony_integration_guide_international": { + "modal_title": "Integracja Telefonii Voximplant", + "continue": "Kontynuuj", + "title": "Instrukcje dotyczące integracji telefonii Voximplant", + "step1": { + "title": "Krok 1 – Tworzenie konta", + "item1": "Aby połączyć integrację telefonii Voximplant, przejdź do \"Ustawienia\" → \"Połączenia\" → zakładka \"Konto\" i kliknij przycisk \"Połącz telefonię\".", + "item2": "Informacje o utworzonym koncie Voximplant pojawią się na stronie. Aby zakończyć integrację, musisz potwierdzić konto, klikając link \"Zatwierdź\" u góry ekranu." + }, + "step2": { + "title": "Krok 2 – Zasilenie konta", + "annotation": "Po otwarciu portalu Voximplant musisz dodać środki do swojego konta. Zaleca się dodanie co najmniej 10 $. Aktywuje to Twoje konto, a środki zostaną wykorzystane na zakup numerów telefonicznych i naliczanie minut połączeń.", + "operator_site_and_billing": "Strona operatora i rozliczenia" + }, + "step3": { + "title": "Krok 3 – Zakup numerów i rozpoczęcie", + "annotation1": "Po zakończeniu aktywacji konta musisz dostarczyć wsparciu {{companyName}} ({{mail}}) następujące informacje, abyśmy mogli zainicjować twoją telefonię i przygotować ją do działania:", + "item1": "Miasto i kraj do wyboru numeru", + "item2": "Godziny pracy: które dni tygodnia i godziny są uznawane za godziny pracy, Twoja strefa czasowa", + "item3": "Tekst powitania na początku połączenia w godzinach pracy", + "item4": "Tekst wiadomości dla połączeń poza godzinami pracy", + "item5": "Opcja głosu dla wiadomości – męski lub żeński", + "item6": "Wymagana ilość numerów telefonicznych", + "annotation2": "Po otrzymaniu tych informacji jak najszybciej przetworzymy twoje zgłoszenie." + }, + "step4": { + "title": "Krok 4 – Podłączenie numerów w {{companyName}}", + "annotation1": "Po zakończeniu aktywacji konta i zakupie potrzebnych numerów, zobaczysz je na zakładce \"Ustawienia\" → \"Połączenia\" → \"Konto\". Aby rozpocząć pracę, musisz kliknąć przycisk \"Połącz\" obok wybranych numerów i wybrać użytkowników, którzy będą mieli dostęp do tych numerów.", + "annotation2": "Gotowe! Twoja telefonia jest skonfigurowana i gotowa do użycia." + }, + "step5": { + "title": "Krok 5 – Ustawienia i dodatkowe informacje", + "annotation1": "W zakładce \"Ustawienia\" → \"Połączenia\" → \"Użytkownicy\" możesz podłączać i odłączać użytkowników systemu do telefonii oraz przeglądać dane SIP. W zakładce \"Konfiguracja\" możesz konfigurować scenariusze integracji telefonii z modułem CRM. W zakładce \"Rejestracje SIP\" możesz dodawać i zarządzać rejestracjami SIP PBX/VPBX.", + "annotation2": "Należy pamiętać, że przy aktywnym korzystaniu z telefonii zaleca się okresowe odświeżanie karty przeglądarki, ponieważ nieaktywne/przestarzałe karty (otwarte ponad 3 dni temu bez interakcji) mogą powodować brak niektórych połączeń.", + "annotation3": "Ponadto musisz udzielić przeglądarce dostępu do mikrofonu, aby wykonywać połączenia wychodzące.", + "grant_chrome_access": "Przyznanie dostępu w Google Chrome", + "grant_mozilla_access": "Przyznanie dostępu w Mozilla Firefox", + "grant_safari_access": "Przyznanie dostępu w Safari", + "annotation4": "Zazwyczaj przeglądarka automatycznie poprosi o niezbędne uprawnienia przy próbie wykonania połączenia wychodzącego." + } + }, + "providers_sip_registration_items_list": { + "sip_registration_guide_modal": { + "modal_title": "Przewodnik połączenia rejestracji SIP", + "continue": "Kontynuuj", + "step1": { + "title": "Krok 1 – Zbierz niezbędne informacje", + "annotation1": "Przed rozpoczęciem łączenia rejestracji SIP upewnij się, że twoje konto ma już aktywną integrację z telefonią Voximplant. Jeśli nie jest jeszcze połączona, postępuj zgodnie z poniższymi instrukcjami:", + "annotation2": "Aby połączyć rejestrację SIP, musisz znać następujące informacje SIP o twojej PBX/VPBX:", + "item1": "Proxy *", + "item2": "Nazwa użytkownika SIP *", + "item3": "Hasło", + "item4": "Proxy wyjściowy", + "item5": "Użytkownik uwierzytelniający", + "annotation3": "Te informacje można znaleźć na koncie osobistym VPBX lub bezpośrednio u operatora." + }, + "step2": { + "title": "Krok 2 – Połącz rejestrację", + "item1": "Kliknij przycisk \"Kontynuuj\" na dole tego okna lub przejdź do zakładki \"Ustawienia\" → \"Połączenia\" → \"Rejestracje SIP\" i kliknij przycisk \"Dodaj rejestrację SIP\".", + "item2": "Wprowadź wszystkie niezbędne dane i kliknij \"Dodaj\".", + "item3": "Zwróć uwagę, że zaraz po utworzeniu rejestracji z twojego konta Voximplant zostanie pobrana niewielka kwota. Będzie ona pobierana co miesiąc za korzystanie z rejestracji. Szczegółowe informacje znajdziesz w poniższym linku w zakładce \"Ceny\" → \"Funkcje\" → \"Rejestracja SIP\". Link pojawi się tylko wtedy, gdy już masz konto Voximplant.", + "voximplant_billing_rates": "Stawki usług Voximplant", + "item4": "Po pomyślnym połączeniu zobaczysz swoją integrację. Można ją edytować, klikając na nią. Edycja jest bezpłatna.", + "item5": "Aby usunąć rejestrację SIP, kliknij przycisk \"Kosz\" na końcu bloku rejestracji SIP w zakładce „Rejestracja SIP”.", + "annotation": "W przypadku innych problemów związanych z rejestracjami SIP skontaktuj się z nami pod adresem {{mail}}." + } + }, + "another_pbx": "Podłącz inne PBX/VPBX", + "beeline": "Podłącz PBX/VPBX Beeline", + "mts": "Podłącz PBX/VPBX MTS", + "mgts": "Podłącz PBX MGTS", + "tele2": "Podłącz Korporacyjny PBX Tele2", + "megafon": "Podłącz PBX/VPBX MegaFon", + "rostelecom": "Podłącz Biurowy PBX/VPBX Rostelecom", + "mango_office": "Podłącz VPBX Mango Office", + "uis": "Podłącz VPBX UIS", + "zadarma": "Podłącz VPBX Zadarma" + }, + "telephony_and_pbx": "Telefonia i PBX", + "integrations": "Integracje", + "crm": "CRM", + "process_automation": "Automatyzacja procesów", + "site_forms": "Formularz strony", + "messenger": "Media społecznościowe i komunikatory", + "continue": "Kontynuuj", + "save": "Zapisz", + "manage_connected_accounts": "Zarządzaj podłączonymi kontami", + "ui": { + "google_calendar": { + "google_calendar_manage_modal": { + "title": "Integracje Google Calendar {{company}}" + }, + "google_calendar_connect_modal": { + "do_not_select": "Nie wybierz", + "integration_name_readonly_hint": "Nie możesz zmieniać nazwy tej integracji po utworzeniu.", + "save": "Zapisz", + "connect_calendar": "Połącz kalendarz", + "finish_integration_annotation": "Ukończ konfigurację integracji, wypełniając wszystkie wymagane pola.", + "new_integration": "Nowa integracja #{{number}}", + "integration_name": "Nazwa integracji *", + "calendar": "Google Kalendarz *", + "calendar_hint": "Wybierz konkretny kalendarz z konta Google Calendar", + "task_board": "Tablica zadań *", + "schedule": "Harmonogram {{company}} *", + "linked_task_boards_annotation": "Możesz dodać dodatkowe tablice zadań do synchronizacji z wybranym kalendarzem. Zadania z tych tablic będą synchronizowane z Google Calendar.", + "linked_schedules_annotation": "Możesz dodać dodatkowe kalendarze do synchronizacji z wybranym kalendarzem. Wydarzenia z tych kalendarzy będą synchronizowane z Google Calendar.", + "select_all": "Zaznacz wszystko", + "sync_events": "Synchronizuj wydarzenia od dzisiaj", + "additional_task_boards": "Dodatkowe tablice zadań", + "additional_schedules": "Dodatkowe harmonogramy", + "responsible_user": "Odpowiedzialny użytkownik *", + "error_message": "Nie udało się przetworzyć kodu Google Calendar!", + "title": "Połącz z Google Calendar", + "feature": "Będziesz mógł wykonywać dwukierunkową synchronizację swoich zadań i wydarzeń z Google Calendar", + "annotation": "Autoryzuj dostęp do swojego konta Google Calendar.", + "placeholders": { + "integration_name": "Integracja z Google Calendar", + "schedules": "Wybierz harmonogramy", + "task_boards": "Wybierz tablice zadań" + } + }, + "google_calendar_modal_template": { + "title": "Integracja Google Calendar {{company}}" + }, + "google_calendar_item": { + "description": "Synchronizuj swoje spotkania i zadania" + } + }, + "common": { + "form": { + "account_activity": "Aktywność konta", + "account_activity_annotation": "Możesz aktywować lub zawiesić swoje konto", + "available": "Wybierz, dla kogo jest dostępny komunikator *", + "all": "Wszyscy", + "users": "Użytkownicy", + "on": "WŁĄCZONE", + "no_create": "Nie twórz", + "messenger_leads_title": "Konfiguracja tworzenia rekordów", + "messenger_leads_annotation": "Ustaw parametry, aby nowy rekord był tworzony automatycznie po otrzymaniu wiadomości od nowego kontaktu.", + "create_entities": "Twórz rekord po otrzymaniu wiadomości", + "create_contact": "Moduł dla kontaktu/firmy", + "create_lead": "Moduł dla szansy sprzedaży", + "lead_stage": "Status szansy sprzedaży", + "lead_name": "Nazwa szansy sprzedaży", + "lead_owner": "Odpowiedzialny *", + "check_duplicates": "Unikaj duplikatów", + "check_active_lead": "Twórz nową, jeśli aktualna jest nieaktywna", + "placeholders": { + "select_module": "Wybierz moduł", + "select_users": "Wybierz użytkowników", + "select_user": "Wybierz użytkownika", + "lead_name": "Wpisz nazwę" + } + }, + "modals": { + "changes_not_saved_warning_modal": { + "title": "Czy na pewno chcesz zamknąć to okno?", + "annotation": "Wszystkie zmiany zostaną utracone.", + "close": "Zamknij" + }, + "integration_account_item": { + "delete_warning_title": "Czy na pewno chcesz usunąć konto {{title}}?", + "delete_warning_annotation": "Wszystkie czaty i wiadomości związane z tym kanałem komunikacyjnym zostaną usunięte. Tego działania nie można cofnąć.", + "active": "Aktywne", + "active_hint": "Twoje konto jest aktywne. Możesz je zawiesić w ustawieniach dostawcy, klikając ikonę ołówka.", + "inactive": "Nieaktywne", + "inactive_hint": "Twoje konto jest zawieszone. Możesz je aktywować w ustawieniach dostawcy, klikając ikonę ołówka.", + "deleted": "Usunięte", + "deleted_hint": "Twoje konto zostało usunięte.", + "draft": "Szkic", + "draft_hint": "Twoje konto nie jest jeszcze podłączone." + }, + "integration_accounts_list": { + "add_account": "Dodaj konto" + } + } + }, + "integration_item": { + "install": "Zainstaluj", + "manage": "Zarządzaj" + }, + "messages_per_day_select": { + "label": "Dzienny limit wiadomości wysyłanych automatycznie *", + "annotation": "wiadomości dziennie", + "hint": "Maksymalna liczba wiadomości, które mogą być wysyłane dziennie za pomocą automatyzacji z tego konta. Ograniczenie to jest konieczne, aby uniknąć zablokowania konta przez komunikator z powodu masowej wysyłki." + }, + "salesforce": { + "salesforce_item": { + "description": "Jedno miejsce pracy dla każdego zespołu" + }, + "salesforce_modal": { + "title": "Integracja z Salesforce", + "connected": "Połączony", + "not_connected": "Niepołączony", + "disconnect": "Odłącz", + "add_integration": "Dodaj integrację", + "my_domain_name": "Nazwa mojej domeny", + "app_key": "Klucz konsumenta aplikacji", + "app_secret": "Tajemnica konsumenta aplikacji", + "connect": "Połącz", + "caption": "Kontynuuj korzystanie z Salesforce dla klientów i korzystaj z {{company}} do zarządzania resztą procesów biznesowych. Zarządzaj projektami w {{company}}. Zarządzaj kontrahentami i dostawcami w {{company}}." + } + }, + "fb_messenger": { + "fb_messenger_manage_modal": { + "title": "Integracja API Facebook Messenger" + }, + "fb_messenger_item": { + "title": "Zarządzaj interakcjami na Facebooku, konwertuj czaty na leady" + }, + "fb_messenger_modal_template": { + "title": "Integracja Facebook Messenger" + }, + "fb_messenger_first_info_modal": { + "title": "Uzyskaj oficjalne konto Facebook Messenger Business, aby rozpocząć rozmowy z klientami za pomocą {{company}} Multimessenger.", + "feature1": "Zarządzaj wszystkimi rozmowami na Facebook Messenger wraz z innymi kanałami w jednym miejscu", + "feature2": "Współpracuj z członkami zespołu nad przychodzącymi rozmowami", + "feature3": "Łatwo twórz nowe kontakty, leady i transakcje bezpośrednio z rozmów", + "feature4": "Używaj szablonów wiadomości Facebook Messenger do wysyłania odpowiednich i terminowych powiadomień", + "learn_more": "Dowiedz się więcej o zarządzaniu integracją Facebook Messenger" + }, + "fb_messenger_finish_modal": { + "supervisors": "Wybierz użytkowników, którzy mają dostęp do wszystkich czatów z klientami", + "supervisors_hint": "Wybierz użytkowników, którzy będą mieli dostęp do wszystkich czatów pracowników z klientami. Mogą to być menedżerowie lub pracownicy, którzy współpracują z klientami.", + "save": "Zapisz", + "title": "Połączyłeś już swoje konto na Facebooku z {{company}}", + "subtitle": "Zakończ konfigurowanie konta. Zmień nazwę i ustawienia dostępności, jeśli to konieczne.", + "name": "Nazwa *", + "responsible_users": "Użytkownicy odpowiedzialni za nowe leady *", + "learn_more": "Dowiedz się więcej o zarządzaniu integracją Facebook Messenger", + "placeholders": { + "name": "Nazwa produktu", + "select_users": "Wybierz użytkowników" + } + } + }, + "wazzup": { + "wazzup_manage_modal": { + "title": "{{company}} Integracja Wazzup" + }, + "wazzup_item": { + "title": "Integracja Wazzup w 5 minut, WhatsApp & Telegram" + }, + "wazzup_modal_template": { + "title": "{{company}} Integracja Wazzup" + }, + "wazzup_first_info_modal": { + "title": "Skontaktuj się bezpośrednio z klientami za pośrednictwem {{company}} na platformach społecznościowych i komunikacyjnych.", + "feature1": "Członkowie zespołu sprzedaży mogą wysyłać wiadomości za pomocą WhatsApp, Telegram za pomocą {{company}}", + "feature2": "Menedżerowie mogą widzieć tylko swoje rozmowy, podczas gdy przełożeni mają dostęp do wszystkich czatów", + "feature3": "Automatycznie generowane są kontakty i transakcje z nowymi klientami", + "feature4": "Wszystkie komunikacje są bezpiecznie przechowywane w {{company}}", + "feature5": "Wykorzystaj jeden numer dla całego działu sprzedaży i skonfiguruj automatyczne odpowiedzi", + "annotation": "Bądź w kontakcie z klientami, gdziekolwiek jesteś. Dzięki aplikacji mobilnej Wazzup możesz kontaktować się z klientami w podróży, a wszystkie rozmowy będą zapisywane w {{company}} i dostępne z komputera.", + "learn_more": "Dowiedz się więcej o Wazzup" + }, + "wazzup_connect_modal": { + "save": "Zapisz", + "update": "Aktualizuj", + "title": "Autoryzuj swoje konto Wazzup", + "subtitle": "Uzyskaj dostęp do ustawień konta Wazzup i skopiuj dane API.", + "name": "Nazwa *", + "api_key": "Klucz API *", + "channel": "Kanał Wazzup *", + "responsible_users": "Użytkownicy odpowiedzialni za nowe leady *", + "supervisors": "Wybierz użytkowników, którzy mają dostęp do wszystkich czatów z klientami", + "supervisors_hint": "Wybierz użytkowników, którzy będą mieli dostęp do wszystkich czatów pracowników z klientami. Mogą to być menedżerowie lub pracownicy, którzy współpracują z klientami.", + "placeholders": { + "api_key": "Klucz API", + "name": "Nazwa produktu", + "select_users": "Wybierz użytkowników", + "channel": "Wybierz kanał Wazzup" + } + } + }, + "whatsapp": { + "whatsapp_manage_modal": { + "title": "Integracja API WhatsApp Business przez Twilio" + }, + "whatsapp_item": { + "title": "Bezpośrednie wiadomości WhatsApp Business przez {{company}}" + }, + "whatsapp_modal_template": { + "title": "Integracja API WhatsApp Business przez Twilio" + }, + "whatsapp_first_info_modal": { + "title": "Uzyskaj oficjalne konto WhatsApp Business (dostarczane przez Twilio), aby rozpocząć rozmowy z klientami za pomocą {{company}} Multimessenger", + "feature1": "Zarządzaj wszystkimi rozmowami na WhatsApp razem z innymi kanałami w jednym miejscu", + "feature2": "Współpracuj z członkami zespołu nad przychodzącymi rozmowami", + "feature3": "Łatwo twórz nowe kontakty, leady i transakcje bezpośrednio z rozmów", + "feature4": "Używaj szablonów wiadomości WhatsApp do wysyłania odpowiednich i terminowych powiadomień", + "learn_more": "Dowiedz się więcej o API WhatsApp Business przez Twilio" + }, + "whatsapp_second_info_modal": { + "title": "Rzeczy do rozważenia przed kontynuowaniem", + "step1": "Powinieneś zakupić nowy numer telefonu w Twilio (lub przenieść swój obecny numer telefonu z aplikacji mobilnej WhatsApp lub aplikacji WhatsApp Business), aby utworzyć konto API WhatsApp Business.", + "link1": "Jak przenieść zatwierdzony numer WhatsApp do Twilio", + "step2": "WhatsApp i Twilio mogą obciążyć cię dodatkowymi opłatami za korzystanie z API WhatsApp Business.", + "link2": "Dowiedz się więcej o cenach" + }, + "whatsapp_third_info_modal": { + "title": "Skonfiguruj konto Twilio z numerem WhatsApp", + "subtitle": "Twilio musi zatwierdzić Twój numer WhatsApp, zanim będzie można go używać w {{company}}. Postępuj zgodnie z tymi krokami, aby uniknąć niepotrzebnych opóźnień.", + "step1": "Skonfiguruj z", + "step2": "Poproś Twilio o dostęp, aby włączyć numery telefonów do WhatsApp:", + "step2_1_1": "Upewnij się, że masz numer telefonu dostarczony przez Twilio, aby uzyskać numer WhatsApp. Wypełnij", + "step2_1_2": "formularz prośby o dostęp Twilio", + "step2_1_3": "ze swoimi aktualnymi danymi, w tym identyfikatorem menedżera firmy Facebook.", + "step2_2_1": "Zobacz dokumentację Twilio", + "step2_2_2": "dokumentację", + "step2_2_3": "po więcej informacji.", + "step3": "Złóż prośbę o nadawcę WhatsApp w konsoli Twilio.", + "step4_1": "Po przesłaniu formularza prośby o dostęp otrzymasz e-mail z wstępną zgodą od Twilio. Zobacz ten", + "step4_2": "odniesienie", + "step4_3": "link do następnych kroków.", + "step5": "Zezwól Twilio na wysyłanie wiadomości w Twoim imieniu w konsoli menedżera firmy Facebook." + }, + "whatsapp_connect_modal": { + "supervisors": "Wybierz użytkowników, którzy mają dostęp do wszystkich czatów z klientami", + "supervisors_hint": "Wybierz użytkowników, którzy będą mieli dostęp do wszystkich czatów pracowników z klientami. Mogą to być menedżerowie lub pracownicy, którzy współpracują z klientami.", + "save": "Zapisz", + "update": "Aktualizuj", + "title": "Autoryzuj swoje konto WhatsApp przez Twilio", + "subtitle": "Uzyskaj dostęp do ustawień konta Twilio i skopiuj dane API.", + "name": "Nazwa *", + "sid": "SID konta *", + "auth_token": "Token uwierzytelniający *", + "phone": "Autoryzowany numer WhatsApp *", + "responsible_users": "Użytkownicy odpowiedzialni za nowe leady *", + "learn_more": "Dowiedz się więcej o konfiguracji konta WhatsApp za pomocą Twilio", + "placeholders": { + "name": "Nazwa produktu", + "code": "Kod produktu", + "token": "Token", + "number": "Numer", + "select_users": "Wybierz użytkowników" + } + } + }, + "tilda": { + "tilda_item": { + "description": "Integracja formularzy na stronie z Tildą" + }, + "tilda_manual_modal": { + "close": "Zamknij", + "title": "Integracja formularzy na stronie z Tildą", + "info_title": "Zintegruj formularz z {{company}} ze stroną na Tildzie, aby zapytania trafiały bezpośrednio do systemu.", + "step_1_title": "Krok 1 — Przygotowanie formularza", + "step_1_description": "W kreatorze formularzy {{company}} utwórz formularz z integracją API. Skonfiguruj formularz w pierwszym kroku i dodaj potrzebne pola w drugim.", + "form_builder_link": "Kreator formularzy z integracją API", + "step_2_title": "Krok 2 — Konfiguracja w Tildzie", + "step_2_item_1": "Otwórz Ustawienia strony → Formularze → Webhook.", + "step_2_item_2": "Podaj URL webhooka z 3. kroku kreatora formularzy jako adres skryptu.", + "step_2_item_3": "Zapisz zmiany.", + "step_3_title": "Krok 3 — Konfiguracja zmiennych pól", + "step_3_item_1": "Dla formularza, który chcesz zintegrować, wybierz Webhook jako usługę odbierającą dane.", + "step_3_item_2": "Przypisz polom formularza zmienne (identyfikatory) z tabeli w 3. kroku kreatora.", + "step_3_item_3": "Przetestuj działanie formularza. Jeśli wszystko działa poprawnie, opublikuj stronę.", + "tilda_manual_link": "Instrukcja konfiguracji Tilda", + "support": "W razie problemów skontaktuj się z pomocą techniczną." + } + }, + "wordpress": { + "wordpress_item": { + "description": "Integracja formularzy na stronie z WordPressem" + }, + "wordpress_manual_modal": { + "close": "Zamknij", + "title": "Integracja formularzy na stronie z WordPressem", + "info_title": "Zintegruj formularz z {{company}} z WordPressem, aby zapytania trafiały bezpośrednio do systemu.", + "step_1_title": "Krok 1 — Przygotowanie formularza", + "step_1_description": "W kreatorze {{company}} stwórz formularz na stronę. Skonfiguruj pola i ewentualnie dostosuj styl na 4. etapie.", + "form_builder_link": "Kreator formularzy", + "step_2_title": "Krok 2 — Dodanie kontenera formularza", + "step_2_item_1": "Otwórz panel administracyjny WordPress.", + "step_2_item_2": "Zrób kopię zapasową strony.", + "step_2_item_3": "Usuń istniejący formularz lub dodaj nowy pusty blok.", + "step_2_item_4": "W ustawieniach bloku dodaj klasę CSS workspace-form-builder-mount-container.", + "step_3_title": "Krok 3 — Dodanie kodu formularza", + "step_3_item_1": "Dodaj blok 'Niestandardowy HTML'.", + "step_3_item_2": "Wklej kod z 5. kroku kreatora.", + "step_3_item_3": "Sprawdź formularz i opublikuj stronę.", + "wordpress_manual_link": "Instrukcja wstawiania HTML od WordPress", + "additional_steps": "Dodatkowe kroki", + "styling": "Formularz można dostosować w kreatorze na 4. etapie lub użyć niestandardowego CSS.", + "multiform": "Na stronie można umieścić wiele formularzy, używając opcji 'kompatybilności wielu formularzy'.", + "support": "W razie problemów skontaktuj się z pomocą techniczną." + } + }, + "albato": { + "albato_item": { + "description": "Automatyzuj procesy dowolnej złożoności z Albato" + }, + "albato_manual_modal": { + "close": "Zamknij", + "header_title": "Instrukcja integracji {{company}} z Albato", + "integration_title": "Użyj Albato, aby połączyć {{company}} z tysiącami innych usług.", + "step_1_title": "Krok 1: Utwórz integrację w Albato", + "albato_website": "Strona Albato", + "step_1_part_1": "Załóż konto w Albato, jeśli jeszcze go nie masz.", + "step_1_part_2": "Utwórz nową integrację. Dodaj akcję i w menu usług znajdź oraz wybierz Mywork.", + "step_1_part_3": "Wybierz odpowiednią akcję z dostępnej listy.", + "step_2_title": "Krok 2: Połącz konto Mywork", + "step_2_part_1": "Dodaj nowe połączenie dla Mywork.", + "step_2_part_2": "Wypełnij pole 'Klucz API'. Klucz znajdziesz w ustawieniach konta Mywork w sekcji 'Dostęp do API'.", + "step_2_part_3": "Uzupełnij pola 'E-mail' i 'Hasło' danymi z konta Mywork.", + "step_2_part_4": "Wypełnij pole 'Subdomena'. Znajdziesz je w sekcji 'Ustawienia ogólne' konta Mywork. Podaj tylko zaznaczoną część, bez '.mywork.app'.", + "api_access_link": "Dostęp do API", + "step_3_title": "Krok 3: Konfiguracja akcji automatyzacji", + "step_3": "Podczas konfiguracji akcji zobaczysz formularz z polami do wypełnienia. Pola te odpowiadają danym wymaganym do wykonania akcji (np. osoba odpowiedzialna, nazwa zadania, treść). Możesz wprowadzić dane dynamicznie z poprzednich akcji lub wyzwalaczy. Niektóre dane można załadować z katalogu (np. osobę odpowiedzialną) — aktualne dane zostaną pobrane z konta Mywork. Jeśli brakuje danych, kliknij 'Odśwież listę'.", + "step_4_title": "Krok 4: Testowanie", + "step_4": "Przed uruchomieniem integracji przetestuj ją, klikając odpowiedni przycisk. Przesłane dane znajdziesz w dzienniku integracji. W razie pytań skontaktuj się z pomocą techniczną Mywork." + } + }, + "request_integration": { + "description": "Zamów opracowanie integracji", + "request": "Zamów" + } + } + }, + "users_settings_page": { + "user": "Użytkownik", + "total": "Razem", + "create_button_tooltip": "Dodaj użytkownika", + "ui": { + "remove_user_modal": { + "remove": "Usuń", + "title": "Czy na pewno chcesz usunąć tego użytkownika?", + "annotation": "Przenieś własność wszystkich elementów tego użytkownika do innego.", + "placeholder": "Wybierz odpowiedzialnego..." + }, + "user_item": { + "remove": "Usuń", + "user_role": { + "owner": "Właściciel", + "admin": "Administrator", + "user": "Użytkownik", + "partner": "Partner" + } + } + } + }, + "account_api_access_page": { + "title": "Dostęp do API", + "annotation": "Możesz używać klucza API do tworzenia zewnętrznych integracji z systemem. Zapisz ten klucz w bezpiecznym miejscu i nie udostępniaj go nikomu. Jeśli klucz został skompromitowany, wygeneruj nowy.", + "create_api_key": "Utwórz klucz API", + "recreate_api_key": "Wygeneruj ponownie", + "warning_title": "Ponowne wygenerowanie klucza API", + "warning_annotation": "Aktualny klucz zostanie usunięty i zastąpiony nowym. Po wygenerowaniu nowego klucza, zaktualizuj go we wszystkich zewnętrznych integracjach, aby nadal działały.", + "created_at": "Utworzono {{date}} o {{time}}", + "api_tokens_list": { + "title": "Tokeny autoryzacyjne", + "annotation": "Utwórz tokeny autoryzacyjne do integracji zewnętrznych. Token zastępuje logowanie za pomocą nazwy użytkownika i hasła. Po utworzeniu zobaczysz go tylko raz – zapisz go w bezpiecznym miejscu. Jeśli token został naruszony, usuń go i wygeneruj nowy.", + "empty_user_tokens": "Brak utworzonych tokenów autoryzacyjnych.", + "add_user_token": "Dodaj token", + "copy": "Skopiuj token", + "access_token": "Zapisz ten token – nie będzie można go ponownie wyświetlić:", + "name": "Nazwa", + "expires_at": "Data wygaśnięcia", + "table": { + "name": "Nazwa", + "created_at": "Utworzono", + "expires_at": "Wygasa", + "last_used_at": "Ostatnie użycie", + "never": "Nigdy", + "actions": "Akcje", + "delete": "Usuń" + } + } + }, + "settings_page_template": { + "modules_settings": "Ustawienia modułów", + "sip_registrations": "Rejestracje SIP", + "title": "Ustawienia", + "users": "Użytkownicy", + "general": "Ustawienia ogólne", + "email": "Ustawienia e-mail", + "integrations": "Integracje", + "billing": "Fakturowanie", + "api_access": "Dostęp do API", + "documents": "Dokumenty", + "calls": "Połączenia", + "account": "Konto", + "superadmin": "Admin Panel", + "configuring_scenarios": "Konfiguracja", + "schemas": "Schematy", + "configure_users": "Konfiguracja użytkowników", + "groups": "Grupy", + "document_templates": "Szablony dokumentów", + "document_creation_fields": "Pola tworzenia dokumentów" + }, + "add_user_to_plan_modal": { + "send": "Wyślij", + "title": "Aby dodać użytkownika, skontaktuj się z nami", + "name": "* Imię", + "phone": "* Telefon", + "email": "* E-mail", + "number_of_users": "* Liczba użytkowników", + "placeholder": "Twoje imię" + }, + "request_setup_form": { + "button": { + "request_setup": "Zamówić konfigurację", + "request_billing_help": "Poprosić o pomoc z subskrypcją", + "request_integration": "Zamówić rozwój integracji", + "request_telephony": "Zamówić konfigurację telefonii", + "request_api_integration": "Zamówić integrację API" + }, + "modal": { + "request_setup": "Zamówić konfigurację {{company}}", + "request_billing_help": "Poprosić o pomoc z subskrypcją {{company}}", + "request_integration": "Zamówić rozwój integracji", + "request_telephony": "Zamówić konfigurację telefonii", + "request_api_integration": "Zamówić integrację API", + "full_name": "Imię *", + "phone": "Telefon *", + "email": "E-mail *", + "comment": "Prośba", + "send_request": "Wyślij", + "placeholders": { + "full_name": "Wprowadź swoje imię", + "comment": { + "request_setup": "Krótko opisz zapytanie dotyczące konfiguracji systemu. Skontaktujemy się z Tobą, aby pomóc w implementacji systemu dopasowanego do Twoich potrzeb.", + "request_billing_help": "Krótko opisz problem z subskrypcją. Skontaktujemy się z Tobą, aby rozwiązać sprawę.", + "request_integration": "Krótko opisz zapytanie dotyczące rozwoju integracji. Skontaktujemy się z Tobą, aby omówić szczegóły.", + "request_telephony": "Krótko opisz zapytanie dotyczące telefonii. Skontaktujemy się z Tobą, aby pomóc w implementacji telefonii dopasowanej do Twoich potrzeb.", + "request_api_integration": "Krótko opisz zapytanie dotyczące integracji API. Skontaktujemy się z Tobą, aby pomóc w implementacji systemu dopasowanego do Twoich potrzeb." + } + } + } + } + } +} diff --git a/frontend/public/locales/pl/page.system.json b/frontend/public/locales/pl/page.system.json new file mode 100644 index 0000000..caf5c10 --- /dev/null +++ b/frontend/public/locales/pl/page.system.json @@ -0,0 +1,19 @@ +{ + "error_page": { + "title": "Coś poszło nie tak", + "annotation": "Przepraszamy, ale coś poszło nie tak. Zostaliśmy powiadomieni o tym problemie i wkrótce go sprawdzimy. Prosimy przejść na stronę główną lub spróbować ponownie załadować stronę.", + "show_error": "Pokaż komunikat o błędzie", + "home": "Strona główna", + "reload": "Załaduj stronę ponownie" + }, + "forbidden_page": { + "title": "Dostęp do tej strony jest zabroniony.", + "back": "Wróć", + "home": "Strona główna" + }, + "not_found_page": { + "title": "Nie można znaleźć tej strony.", + "home": "Strona główna", + "back": "Wróć" + } +} diff --git a/frontend/public/locales/pl/page.tasks.json b/frontend/public/locales/pl/page.tasks.json new file mode 100644 index 0000000..c0f2c1f --- /dev/null +++ b/frontend/public/locales/pl/page.tasks.json @@ -0,0 +1,135 @@ +{ + "tasks_page": { + "tasks_page_by_deadline": { + "unallocated": "Nieprzypisane", + "overdue": "Zaległe", + "today": "Dzisiaj", + "tomorrow": "Jutro", + "upcoming": "Nadchodzące", + "resolved": "Zrobione", + "board": "Tablica", + "calendar": "Kalendarz" + }, + "common": { + "tasks_page_template": { + "create_new": "Utwórz nowe zadanie" + }, + "ui": { + "empty_list_block": { + "annotation1": "Nie masz jeszcze zadań lub nic nie znaleziono przy wybranych filtrach.", + "annotation2": "Użyj przycisku", + "annotation3": "aby dodać zadanie." + }, + "tasks_page_header": { + "title": "Zadania i Aktywności", + "timeline": "Gantt", + "board": "Tablica", + "list": "Lista", + "calendar": "Kalendarz", + "user_select_button": "Udostępnij", + "settings_button": { + "sync": "Synchronizuj", + "table_settings": "Ustawienia tabeli", + "settings": "Ustawienia", + "board_settings": "Ustawienia tablicy" + }, + "create_button_tooltip": "Dodaj zadanie" + }, + "tasks_list_settings_drawer": { + "table_settings": "Ustawienia tabeli", + "display_columns": "Wyświetl kolumny" + }, + "tasks_filter_drawer": { + "just_my_tasks": "Tylko moje zadania", + "sorting": "Sortowanie", + "done": "Zrobione", + "sorting_options": { + "manual": "Ręczne", + "created_asc": "Utworzone: od najstarszych", + "created_desc": "Utworzone: od najnowszych" + }, + "created_at": "Data utworzenia", + "start_date": "Data rozpoczęcia", + "end_date": "Data zakończenia", + "resolve_date": "Data ukończenia", + "assignee": "Przypisany", + "reporter": "Reżyser", + "type": "Typ", + "stage": "Etap", + "groups": "Grupy", + "linked_cards": "Powiązane karty", + "placeholders": { + "card_name": "Nazwa karty", + "search_by_task_name": "Szukaj po nazwie zadania", + "search_by_activity_name": "Szukaj po nazwie aktywności" + } + } + } + }, + "tasks_page_calendar": { + "new_event": "Nowe zadanie", + "no_events": "Brak zadań na ten okres.", + "all_day": "Całodniowe" + }, + "tasks_page_list": { + "all_columns_hidden": "Wszystkie kolumny są ukryte w ustawieniach tabeli" + }, + "activity_item": { + "no_card_access": "Nie masz dostępu do tej karty", + "drag_disabled": "Nie masz pozwolenia na przeniesienie tej karty. Skontaktuj się z administratorem konta, aby uzyskać dostęp." + }, + "tasks_page_timeline": { + "annotation": { + "minute": ["minuta", "minuty", "minut"], + "hour": ["godzina", "godziny", "godzin"], + "day": ["dzień", "dni", "dni"] + }, + "today": "Dzisiaj", + "view": { + "fifteen_minutes": "15 minut", + "hour": "Godzina", + "day": "Dzień", + "week": "Tydzień", + "month": "Miesiąc", + "quarter": "Kwartał", + "half_year": "Półrocze" + }, + "format": { + "major_format": { + "fifteen_minutes": "YYYY, MMMM D", + "hour": "YYYY, MMMM D", + "day": "YYYY, MMMM", + "week": "YYYY, MMMM", + "month": "YYYY", + "quarter": "YYYY", + "half_year": "YYYY" + }, + "minor_format": { + "fifteen_minutes": "HH:mm", + "hour": "HH", + "day": "D", + "week": "wo [tydzień]", + "month": "MMMM", + "quarter": "[Q]Q", + "half_year": "YYYY-" + } + } + }, + "task_item": { + "planned_time": "Szacowany czas:", + "completed": "Ukończone", + "complete": "Ukończ", + "delete_task": "Usuń zadanie", + "copy_link": "Kopiuj link", + "drag_disabled": "Nie masz uprawnień do przenoszenia tego zadania. Skontaktuj się z administratorem konta, aby uzyskać dostęp.", + "date": "{{date}} o {{time}}" + }, + "time_allocation_card": { + "users": "Użytkownicy", + "planned_time": "Szacowany czas", + "total": "Razem", + "hour": "h", + "minute": "m" + } + } +} diff --git a/frontend/public/locales/pl/store.field-groups-store.json b/frontend/public/locales/pl/store.field-groups-store.json new file mode 100644 index 0000000..3ca14f8 --- /dev/null +++ b/frontend/public/locales/pl/store.field-groups-store.json @@ -0,0 +1,9 @@ +{ + "field_groups_store": { + "requisites": "Wymagania", + "analytics": "Analityk", + "errors": { + "create_at_least_one_field": "Proszę utworzyć co najmniej jedno pole w każdej grupie" + } + } +} diff --git a/frontend/public/locales/pl/store.fields-store.json b/frontend/public/locales/pl/store.fields-store.json new file mode 100644 index 0000000..72da222 --- /dev/null +++ b/frontend/public/locales/pl/store.fields-store.json @@ -0,0 +1,8 @@ +{ + "fields_store": { + "errors": { + "create_at_least_one_field": "Proszę utworzyć co najmniej jedno pole", + "duplicate_name": "Nazwy pól muszą być unikalne: {{fieldName}}" + } + } +} diff --git a/frontend/public/locales/ru/common.json b/frontend/public/locales/ru/common.json new file mode 100644 index 0000000..58d33f4 --- /dev/null +++ b/frontend/public/locales/ru/common.json @@ -0,0 +1,395 @@ +{ + "connect_with_google": "Войти через Google", + "responsible": "Ответственный", + "all_cards": "Все карточки", + "apply_to_all_button": { + "apply": "Применить", + "apply_to_all": "Применить ко всем", + "apply_to_all_accounts": "Применить настройки таблицы для всех пользователей:", + "apply_warning_title": "Вы уверены, что хотите применить локальные настройки таблицы для всех пользователей?", + "apply_warning_annotation": "Это действие перезапишет все настройки таблицы для других пользователей в системе. Отменить его невозможно." + }, + "version_modal": { + "title": "✨ {{company}} стал еще лучше!", + "annotation": "Обновите до версии {{version}}!", + "update": "Обновить" + }, + "reload_modal": { + "title": "Страница устарела!", + "annotation": "Данные на этой странице могли устареть. Перезагрузите ее, чтобы все функции работали корректно.", + "update": "Перезагрузить" + }, + "select_stage": "Выберите этап", + "new_activity_type": "Новый тип активности", + "video_error": "Не удалось загрузить и воспроизвести видеофайл", + "image_error": "Не удалось загрузить изображение", + "view_document": "Просмотр: {{document}}", + "object": "объект", + "all_day": "Весь день", + "supervisor": "Наблюдатель", + "current_chat_user_hint": "Вы не можете убрать себя из чата", + "coming_soon": "Скоро появится...", + "field_readonly": "Это поле только для просмотра", + "now": "Сейчас", + "default": "По умолчанию", + "exact_time": "Точное время", + "card_copy": "Эта копия была создана автоматизацией смены статуса", + "customize": "Настроить", + "day_char": "д", + "hour_char": "ч", + "minute_char": "м", + "days": ["день", "дня", "дней"], + "minutes": ["минута", "минуты", "минут"], + "hours": ["час", "часа", "часов"], + "seconds": ["секунда", "секунды", "секунд"], + "files_count": ["{{count}} файл", "{{count}} файла", "{{count}} файлов"], + "loading_title": "Загрузка...", + "changes_unsaved_blocker": { + "title": "Изменения не сохранены", + "annotation": "Сохранить перед закрытием или выйти без сохранения?", + "approve_title": "Сохранить", + "cancel_title": "Выйти без сохранения" + }, + "browser_not_supported_title": "Ваш браузер не поддерживается.", + "browser_not_supported_annotation": "Для использования {{company}} рекомендуется использование последней версии Chrome, Firefox или Safari. Это обеспечит наилучший пользовательский опыт. Инструкции по обновлению приведены ниже.", + "duplicate_title": "Карточка уже существует", + "duplicate_warning_annotation": "Хотите создать дубликат или выбрать существующую карточку?", + "select_existing": "Выбрать", + "duplicate_warning_cancel_title": "Создать дубликат", + "duplicate_forbidden_annotation": "Администратором установлен запрет на создание дубликатов контактов. Выберите уже существующую карточку или используйте другое уникальное значение.", + "add_as_new": "Создать дубликат", + "tasks_total": ["задача", "задачи", "задач"], + "activities_total": ["активность", "активности", "активностей"], + "visits_total": ["визит", "визита", "визитов"], + "trial_left": { + "one_day": "Демо: 1 день", + "several_days": ["Демо: {{left}} день", "Демо: {{left}} дня", "Демо: {{left}} дней"] + }, + "meta_description": "{{company}} - это мощный и гибкий конструктор рабочего пространства, специально разработанный для малых и средних предприятий.", + "international_number": "Международный номер", + "phone_number": "Номер телефона", + "no_available_groups": "Нет доступных групп", + "add_new_group": "Добавить новую группу", + "schedule": "Календарь", + "board": "Доска", + "settings": "Настройки", + "visits": "Визиты", + "orders": "Заказы", + "row": "Строка", + "column": "Колонка", + "external_link": "Внешняя ссылка", + "loading": "Загрузка", + "products": "Продукты", + "for_sales": "Продажи", + "rentals": "Аренда", + "title": "Заголовок", + "name": "Имя", + "max_length": "Максимум {{length}} символов", + "activities": "Активности", + "project_tasks_board": "Доска", + "tasks_board": "Доска задач", + "tasks_list": "Список", + "time_board": "Тайм борд", + "trial": "Демо", + "show_less": "Показать меньше", + "show_more": "Показать больше", + "search": "Поиск", + "clear_search": "Очистить поиск", + "profile": "Профиль", + "avatar_alt": "Аватар {{firstName}}", + "log_out": "Выход", + "task_title": "Название задачи", + "select_date": "Выберите дату", + "select": "Выбрать", + "overview": "Обзор", + "new_board": "Новая доска", + "enter_html_markup": "Введите HTML-разметку", + "filter": "Фильтр", + "notifications": "Уведомления", + "mail": "Почта", + "multichat": "Мульти-Мессенджер", + "total": "Всего: {{total}}", + "select_period": "Выберите период", + "select_groups": "Выберите группы", + "select_group": "Выберите группу", + "delay": "Задержка", + "hide_sidebar": "Скрыть боковую панель", + "show_sidebar": "Показать боковую панель", + "site_form": "Формы на сайт", + "workspace_tags": { + "site_form": "Форма на сайт", + "headless_site_form": "Форма на сайт по API", + "online_booking_site_form": "Форма онлайн-записи", + "project": "Проекты и задачи", + "universal": "Универсальный модуль", + "partner": "Партнеры", + "deal": "CRM", + "contact": "Контакты", + "company": "Компании", + "supplier": "Поставщики", + "contractor": "Подрядчики", + "hr": "Найм персонала", + "products_for_sales": "Товары и услуги", + "products_rentals": "Управление арендой", + "visits": "Календарь визитов" + }, + "sidebar": { + "builder": "Конструктор", + "tasks_and_activities": "Задачи & Активности", + "mail": "Почта", + "settings": "Настройки", + "companies": "Компании", + "contacts": "Контакты", + "multichat": "Мульти-Мессенджер" + }, + "buttons": { + "save_and_leave": "Сохранить и выйти", + "create": "Создать", + "cancel": "Отмена", + "save": "Сохранить", + "delete": "Удалить", + "add": "Добавить", + "edit": "Редактировать", + "ok": "Хорошо", + "activate": "Активировать", + "deactivate": "Деактивировать", + "back": "Назад", + "clear": "Очистить", + "continue": "Продолжить" + }, + "profile_modal": { + "app_version": "Версия приложения", + "app_version_hint": "Версия приложения, которую вы используете.", + "clear_cache": "Очистить кэш", + "clear_cache_title": "Очистить кэш приложения", + "clear_cache_hint": "Это действие очистит все несущественные данные клиентского кэша и перезагрузит страницу.", + "password": "Пароль", + "profile": "Ваш профиль", + "avatar": "аватар", + "first_name": "Имя", + "first_name_hint": "Максимальная длина имени {{length}} символов", + "last_name": "Фамилия", + "last_name_hint": "Максимальная длина фамилии {{length}} символов", + "change_avatar": "Изменить", + "add_avatar": "Добавить фотографию", + "delete_avatar": "Удалить", + "avatar_annotation": "Фото должно быть в формате PNG, JPG. Разрешение не более 1024px*1024px.", + "date_of_birth": "День рождения", + "phone": "Телефон", + "employment_start": "Дата начала работы", + "email": "Адрес электронной почты", + "current_password": "Текущий пароль", + "new_password": "Новый пароль", + "confirm_password": "Подтвердите пароль", + "change_password": "Изменить пароль", + "upload_avatar": "Загрузить аватар", + "passwords_do_not_match": "Пароли не совпадают", + "incorrect_password": "Неверный пароль", + "image_edit_modal": { + "annotation": "Настройте отображение фотографии для вашего профиля. Передвиньте ползунок, чтобы увеличить или уменьшить фото." + } + }, + "form": { + "my_select": { + "no_options": "Нет вариантов", + "placeholder": "Выбрать", + "search": "Поиск...", + "select_user": "Выберите пользователя", + "select_all": "Выбрать все", + "clear_selection": "Снять выделение" + }, + "key_value_input": { + "key": "Ключ", + "value": "Значение", + "add": "Добавить" + }, + "week_intervals_input": { + "mo": "Пн", + "tu": "Вт", + "we": "Ср", + "th": "Чт", + "fr": "Пт", + "sa": "Сб", + "su": "Вс", + "to": "до", + "unavailable": "Нерабочий день" + } + }, + "subscription_period_modal": { + "contact_admin": "Свяжитесь с администратором аккаунта чтобы оставить заявку на оплату подписки.", + "trial_left": "Ваш бесплатный пробный период заканчивается через {{left}} дней", + "trial_over": "Ваш бесплатный пробный период закончился", + "subscription_left": "Ваша подписка заканчивается через {{left}} дней", + "subscription_over": "Ваша подписка закончилась", + "trial_sub_title": "Выберите тариф и продолжайте использовать {{company}}.", + "subscription_sub_title": "Обновите подписку, чтобы продолжить использовать {{company}} Workspace.", + "select_plan": "Выбрать тариф" + }, + "trial_period_over_modal": { + "send": "Отправить", + "title": "Пожалуйста, свяжитесь с нами для оплаты {{company}}", + "name": "* Имя", + "email": "* Адрес электронной почты", + "phone": "* Телефон", + "number_of_users": "* Количество пользователей", + "subscribe": "Подписаться", + "yearly": "Годовой -20%", + "monthly": "Ежемесячный", + "choose_plan": "* Выберите свой план", + "trial_left": "Демо заканчивается через {{left}} дня", + "trial_left_one": "Демо заканчивается через {{left}} день", + "trial_over": "Ваш пробный период закончился", + "subscription_over": "Ваш период подписки закончился", + "placeholders": { + "name": "Ваше имя" + } + }, + "update_task_modal": { + "title": "Заголовок", + "linked_card": "Привязанная карточка", + "stage": "Статус", + "add_description": "Добавить описание", + "no_description": "Нет описания", + "not_found": "Эта задача не найдена", + "close": "Закрыть", + "description": "Описание", + "subtasks": "Подзадачи", + "comments": "Комментарии", + "planned_time": "Запланированное время", + "board_name": "Имя доски", + "assignee": "Ответственный", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "files": "Файлы", + "attach_files": "Прикрепить файлы", + "reporter": "Назначил {{name}}, {{date}} в {{time}}", + "reporter_noun": "Постановщик", + "enter_description": "Введите описание", + "new_comment": "Новый комментарий", + "placeholders": { + "board": "Выберите доску", + "search_card": "Поиск карточки" + } + }, + "delete_demo": { + "button_text": "Удалить демо", + "modal_title": "Вы уверены, что хотите удалить демо данные?", + "modal_annotation": "Все демо данные будут удалены без возможности восстановления." + }, + "filter_drawer_controls": { + "clear": "Очистить", + "save_filter_settings": "Сохранять настройки", + "error_message": "К сожалению, фильтр не был применен. Пожалуйста, попробуйте еще раз." + }, + "period_types": { + "all_time": "За все время", + "today": "Сегодня", + "yesterday": "Вчера", + "current_week": "Текущая неделя", + "last_week": "Прошлая неделя", + "current_month": "Текущий месяц", + "last_month": "Прошлый месяц", + "current_quarter": "Текущий квартал", + "last_quarter": "Прошлый квартал", + "placeholders": { + "select_period": "Выберите период" + } + }, + "file_size_warning_modal": { + "title": "Файл слишком велик", + "annotation": "Максимальный размер файла для загрузки — {{maxSizeMb}} МБ. Сожмите его или выберите другой.", + "cancel": "Отменить" + }, + "player": { + "default": "Стандартно", + "fast": "Быстро", + "super_fast": "Сверхбыстро" + }, + "languages": { + "en": "Английский", + "fr": "Французский", + "ru": "Русский", + "es": "Испанский", + "pt": "Португальский", + "ar": "Арабский", + "tr": "Турецкий", + "vi": "Вьетнамский", + "az": "Азербайджанский", + "uk": "Украинский", + "id": "Индонезийский" + }, + "calendar": { + "month": "Месяц", + "week": "Неделя", + "day": "День", + "agenda": "Расписание", + "more": "еще", + "users": "Пользователи", + "today": "Сегодня", + "all": "Все задачи", + "completed": "Завершенные", + "pending": "Незавершенные", + "colored": "Цветные", + "colorless": "Бесцветные", + "show_weekends": "Показывать выходные", + "time_scale": "Шкала времени", + "from": "С", + "to": "До", + "stage_select": "Этап" + }, + "page_title": { + "system": { + "browser_not_supported": { + "page_title": "Ваш браузер не поддерживается" + } + }, + "builder": { + "production": "Производственные процессы", + "builder": "Конструктор", + "workspace": "Ваше рабочее пространство", + "crm": "Конструктор | CRM", + "site_form": "Конструктор | Формы на сайт", + "project_management": "Конструктор | Проекты и задачи", + "product_management_for_sales": "Конструктор | Склад", + "product_management_rentals": "Конструктор | Управление арендой", + "scheduler": "Конструктор | Календарь визитов", + "supplier_management": "Конструктор | Поставщики", + "contractor_management": "Конструктор | Подрядчики", + "partner_management": "Конструктор | Партнеры", + "hr_management": "Конструктор | Найм персонала", + "contact": "Конструктор | Контакты", + "company": "Конструктор | Компании", + "universal_module": "Конструктор | Универсальный модуль", + "products": { + "rental": "Конструктор | Управление арендой", + "sale": "Конструктор | Товары и услуги" + } + }, + "time_board": "Тайм борд", + "activities_board": "Активности", + "settings": { + "general_settings": "Настройки | Основные", + "user_list": "Настройки | Пользователи — Список пользователей", + "user_departments": "Настройки | Пользователи — Группы пользователей", + "user_edit": "Настройки | Редактирование пользователя", + "calls": { + "account": "Настройки | Звонки — Аккаунт", + "users": "Настройки | Звонки — Пользователи", + "scenarios": "Настройки | Звонки — Сценарии", + "sip_registrations": "Настройки | Звонки — SIP Регистрации" + }, + "mailing": "Настройки | Почта", + "integrations": "Настройки | Интеграции", + "billing": "Настройки | Подписка", + "api_access": "Настройки | Доступ к API", + "superadmin": "Настройки | Админ-панель", + "document": { + "templates": "Настройки | Документы — Шаблоны", + "creation": "Настройки | Документы — Поля" + } + }, + "mailing": "Почта и мессенджер", + "orders": "Заказы", + "notes": "Заметки" + } +} diff --git a/frontend/public/locales/ru/component.card.json b/frontend/public/locales/ru/component.card.json new file mode 100644 index 0000000..2d99761 --- /dev/null +++ b/frontend/public/locales/ru/component.card.json @@ -0,0 +1,344 @@ +{ + "card": { + "status": { + "active": "Действующая", + "liquidating": "Ликвидируется", + "liquidated": "Ликвидирована", + "bankrupt": "Банкротство", + "reorganizing": "В процессе присоединения к другому юрлицу, с последующей ликвидацией" + }, + "type": { + "legal": "Юридическое лицо", + "individual": "Индивидуальный предприниматель" + }, + "branch_type": { + "main": "Головная организация", + "branch": "Филиал" + }, + "opf": { + "bank": "Банк", + "bank_branch": "Филиал банка", + "nko": "Небанковская кредитная организация (НКО)", + "nko_branch": "Филиал НКО", + "rkc": "Расчетно-кассовый центр", + "cbr": "Управление ЦБ РФ (март 2021)", + "treasury": "Управление Казначейства (март 2021)", + "other": "Другой" + }, + "days": ["день", "дня", "дней"], + "creation_date": "Создано", + "in_work": "В работе", + "shipping_date": "Отгружено", + "closing_date": "Закрыто", + "open_chat": "Открыть чат", + "analytics": "Аналитика", + "card_focus": "Фокусирование (выделяет карточку на доске и в списке)", + "field_formula_circular_dependency_warning": { + "warning_title": "Невозможно сохранить изменения", + "warning_annotation": "Вы пытаетесь обновить поле формулы \"{{fieldName}}\" с использованием другой формулы, которая создает циклическую зависимость (одна формула зависит от другой формулы, которая зависит от нее). Пожалуйста, обновите настройки формулы, чтобы разрешить циклическую зависимость и попробуйте снова.", + "cancel_changes": "Отменить изменения", + "continue": "Продолжить" + }, + "field_used_in_formula_warning": { + "warning_title": "Невозможно сохранить изменения", + "warning_annotation": "Вы пытаетесь удалить поле \"{{fieldName}}\", которое используется в формуле. Сначала удалите его из формулы и попробуйте снова.", + "cancel_changes": "Отменить изменения", + "continue": "Продолжить" + }, + "mutation_warning": { + "title": { + "change_stage": "Невозможно изменить этап", + "save_changes": "Невозможно сохранить изменения" + }, + "annotation": "Некоторые обязательные поля не заполнены. Пожалуйста, заполните их и повторите попытку.", + "continue": "Продолжить" + }, + "change_linked_responsibles_modal": { + "header": "Изменение ответственного", + "annotation": "Выберите связанные карточки, в которых вы также хотите изменить ответственного пользователя", + "change_responsible_in_chats": "Добавить ответственного в чаты карточек", + "chats_hint": "Новый ответственный пользователь будет добавлен в чаты связанных карточек, в которых он еще не присутствует." + }, + "owner": "Ответственный", + "delete_warning_title": "Вы действительно хотите удалить карточку?", + "delete_warning_caption": "Это действие не может быть отменено.", + "files": "Файлы", + "from_card_or_linked_entities": "Из карточки или связанных карточек", + "recent": "Недавние", + "change_board": "Изменить доску", + "placeholders": { + "name": "Введите имя..." + }, + "ui": { + "requisites_codes": { + "ie_name": "Имя ИП", + "ie_surname": "Фамилия ИП", + "ie_patronymic": "Отчество ИП", + "org_name": "Наименование компании", + "org_tin": "ИНН организации", + "org_trrc": "КПП организации", + "org_psrn": "ОГРН", + "org_type": "Тип организации", + "org_full_name": "Полное наименование", + "org_short_name": "Краткое наименование", + "org_management_name": "Имя руководителя", + "org_management_post": "Пост руководителя", + "org_management_start_date": "Дата вступления в должность руководителя", + "org_branch_count": "Количество филиалов", + "org_branch_type": "Тип подразделения", + "org_address": "Адрес организации", + "org_reg_date": "Дата регистрации", + "org_liquidation_date": "Дата ликвидации", + "org_status": "Статус организации", + "stat_okato": "ОКАТО", + "stat_oktmo": "ОКТМО", + "stat_okpo": "ОКПО", + "stat_okogu": "ОКОГУ", + "stat_okfs": "ОКФС", + "stat_okved": "ОКВЭД", + "org_extra_employee_count": "Среднесписочная численность работников", + "org_extra_founders": "Учредители компании", + "org_extra_managers": "Руководители компании", + "org_extra_capital": "Уставной капитал", + "org_extra_licenses": "Лицензии", + "org_extra_phones": "Телефоны", + "org_extra_emails": "Адреса электронной почты", + "bank_name": "Наименование банка", + "bank_bic": "БИК ЦБ РФ", + "bank_swift": "SWIFT", + "bank_tin": "ИНН банка", + "bank_trrc": "КПП банка", + "bank_correspondent_acc": "Корреспондентский счет ЦБ РФ", + "bank_payment_city": "Город для платежного поручения", + "bank_opf_type": "Тип кредитной организации", + "bank_checking_account": "Расчетный счет" + }, + "analytics": "Аналитика", + "requisites": "Реквизиты", + "card_page_header": { + "overview": "Обзор", + "products": "Продукты", + "create_new_order": "Создать новый заказ", + "orders": "Заказы", + "board": "Доска", + "list": "Список", + "calendar": "Календарь", + "timeline": "Гант", + "relocate_card_button": "Переместить", + "order_denied": "У вас нет разрешения на создание заказа. Обратитесь к администратору учетной записи для получения доступа." + }, + "actions_dropdown": { + "delete_entity": "Удалить карточку", + "fine_tune": "Тонкая настройка" + }, + "mini_card": { + "enter_name": "Введите имя {{name}}", + "unpin": "Открепить", + "owner": "Ответственный", + "stage": "Статус", + "placeholders": { + "select_stage": "Выберите статус" + }, + "fields": { + "value": "Бюджет", + "participants": "Участники", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "description": "Описание" + } + }, + "stages": { + "disabled_while_adding": "Смена статуса будет доступна после создания карточки" + }, + "feed": { + "unknown": "Неизвестный 👤", + "readonly": "Вы не можете добавлять элементы к этой карточке", + "disabled_while_adding": "Добавление элементов к карточке будет доступно после создания карточки", + "notes": "Примечания", + "activities": "Активности", + "tasks": "Задачи", + "amwork_messenger": "{{company}} мессенджер", + "documents": "Документы", + "create_chat": "Создать чат с командой", + "create_documents_tab": "Создать документы", + "add_visit": "Назначить встречу", + "share_documents": "Поделитесь сгенерированными документами", + "empty": "Здесь пока ничего нет", + "add_activity": { + "placeholder": "Описание активности" + }, + "add_note": { + "add_note": "Добавить заметку" + }, + "add_task_modal": { + "dates_error": "Дата окончания должна быть позже даты начала.", + "tab": "Добавить задачу", + "task_name": "Название задачи *", + "planned_time": "Планируемое время", + "board_name": "Название доски", + "assignee": "Исполнитель", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "description": "Описание", + "subtasks": "Подзадачи", + "placeholder": "Добавить заголовок...", + "add_board": "Добавить доску", + "save": "Сохранить", + "new_task": "Новая задача", + "repeating_task": { + "title": "Повтор задачи", + "hint": "Установите количество и интервал повторных задач, если это необходимо. Повторные задачи будут созданы с такими же настройками. Если у задачи указано время начала или время конца, задачи будут поставлены с указанным интервалом с учетом рабочих дней.", + "count": "Количество", + "interval": "Интервал", + "interval_disabled_tooltip": "Установите время начала или окончания задачи", + "intervals": { + "none": "Без интервала", + "day": "Каждый день", + "week": "Раз в неделю", + "month": "Раз в месяц" + } + }, + "placeholders": { + "board": "Выберите доску", + "subtask": "Новая подзадача" + } + }, + "filter": { + "all": "Все", + "tasks": "Задачи", + "activities": "Активности", + "notes": "Заметки", + "mail": "Почта", + "files": "Файлы", + "calls": "Звонки" + }, + "create_documents": { + "order": "Заказ-{{number}}", + "documents": "Документы", + "create_documents": "Создать документы", + "create_new_documents": "Создать новые документы", + "select_all": "Выбрать все", + "clear_selection": "Снять выделение", + "send_by_email": "Отправить по почте", + "add_template": "Добавить шаблон", + "invalid_template_title": "Ошибка в шаблоне документа", + "invalid_template_annotation": "Невозможно сгенерировать документ по данному шаблону, так как в шаблоне есть синтаксические ошибки. Обновите шаблон или выберите другой для генерации документа.", + "hints": { + "cancel": "Отмена", + "generate": "Сгенерировать", + "generate_anyway": "Все равно сгенерировать", + "docx_format": "Формат DOCX", + "pdf_format": "Формат PDF", + "no_documents_title": "Шаблоны отсутствуют", + "no_documents_annotation": "Для этой карточки нет доступных шаблонов документов. Создайте новый или измените существующий в разделе Документы на странице Настройки.", + "creating_documents_title": "Создание ваших документов", + "creating_documents_annotation": "Это может занять некоторое время, но после этого вы сможете отправить почтой или загрузить документы.", + "select_template_title": "Выбрать шаблон", + "select_template_annotation": "Чтобы выбрать шаблон, откройте выпадающий список и выберите нужный файл.", + "format_title": "Формат", + "issues_identified": "Выявлено несколько проблем", + "invalid_tags_annotation": "Некоторые коды недействительны или связаны с пустым полем:", + "format_annotation": "Выберите формат(-ы), в котором(-ых) вы хотите создать документ." + }, + "placeholders": { + "select_order": "Выбрать заказ", + "select_template": "Выбрать шаблон", + "invalid_tags_annotation": "Некоторые коды недействительны или связаны с пустым полем:" + } + }, + "activity_block": { + "creator": "Ответственный", + "start_date": "Дата", + "plan_time": "Время" + }, + "creator_tag": { + "task_setter_at_time": "{{taskSetter}} в {{time}}" + }, + "files_block": { + "unknown_file": "Неизвестный файл", + "downloaded_file": "{{companyName}} – Скачанный файл", + "attachment": "вложение", + "attachments": "вложения", + "download_all": "Скачать все" + }, + "note_block": { + "block_title": "Примечание", + "creation_date": "Дата создания", + "message": "Сообщение", + "to_recipient": "к {{recipient}}" + }, + "task_block": { + "creator": "Ответственный", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "no_due_date": "Нет даты выполнения" + }, + "mail_block": { + "sender": "Отправитель", + "recipient": "Получатель", + "date": "Дата", + "seen": "Прочитано" + }, + "document_block": { + "creation_date": "Дата создания", + "docs": "Документы", + "annotation": "{{creator}} создал(-а) {{documentName}} {{date}}" + }, + "call_block": { + "start_date": "Дата", + "who_called": "Оператор", + "who_got_call": "Кому звонили", + "did_not_get_through": "Не дозвонились", + "time": { + "d": "д", + "h": "ч", + "m": "м", + "s": "с" + }, + "status": { + "missed_call": "Пропущенный звонок", + "incoming_call": "Входящий звонок", + "outgoing_call": "Исходящий звонок" + }, + "comment": "Комментарий" + }, + "common": { + "author": "Автор", + "reporter": "Постановщик", + "created_at": "{{day}} в {{time}}", + "result": "Результат", + "functional_options": { + "edit": "Редактировать", + "delete": "Удалить" + }, + "item_tag": { + "tasks": { + "resolved": "Задача выполнена", + "expired": "Задача просрочена", + "future": "Предстоящая задача", + "today": "Задача на сегодня" + }, + "activities": { + "resolved": "Активность выполнена", + "expired": "Активность просрочена", + "future": "Предстоящая активность", + "today": "Активность на сегодня" + } + }, + "activity_type_picker": { + "add_new_type": "Добавить новый тип", + "warn_title": "Вы действительно хотите удалить этот тип активности?", + "warn_annotation": "Вы не сможете больше создать активность этого типа.", + "placeholders": { + "select_activity_type": "Выберите тип активности" + } + }, + "user_picker": { + "add": "Добавить", + "placeholder": "Выберите пользователя" + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/component.entity-board.json b/frontend/public/locales/ru/component.entity-board.json new file mode 100644 index 0000000..6a12a7c --- /dev/null +++ b/frontend/public/locales/ru/component.entity-board.json @@ -0,0 +1,23 @@ +{ + "entity_board": { + "ui": { + "project_entity_card_item": { + "start_date": "Дата начала", + "end_date": "Дата окончания", + "titles": { + "not_resolved": "Невыполненные задачи", + "upcoming": "Предстоящие задачи", + "overdue": "Просроченные задачи", + "today": "Сегодняшние задачи", + "resolved": "Выполненные задачи" + } + }, + "common_entity_card": { + "no_tasks": "Нет задач", + "task_today": "Задача на сегодня", + "overdue_task": "Просроченная задача", + "upcoming_task": "Предстоящая задача" + } + } + } +} diff --git a/frontend/public/locales/ru/component.section.json b/frontend/public/locales/ru/component.section.json new file mode 100644 index 0000000..263fb12 --- /dev/null +++ b/frontend/public/locales/ru/component.section.json @@ -0,0 +1,191 @@ +{ + "section": { + "section_header_with_boards": { + "board": "Доска", + "list": "Список", + "dashboard": "Дашборд", + "reports": "Отчеты", + "timeline": "Гант", + "automation": "Автоматизация", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Скоро...)", + "tooltips": { + "reports_denied": "У вас нет разрешения на просмотр отчетов. Обратитесь к администратору учетной записи для получения доступа.", + "dashboard_denied": "У вас нет разрешения на просмотр дашборда. Обратитесь к администратору учетной записи для получения доступа." + } + }, + "empty_projects_block": { + "annotation": "У вас пока нет проектов или ничего не было найдено с выбранными фильтрами." + }, + "common": { + "add_card": "Добавить карточку", + "new_stage": "Новый статус", + "create_button_tooltip": { + "universal": "Добавить карточку", + "partner": "Добавить партнёра", + "deal": "Добавить сделку", + "contact": "Добавить контакт", + "company": "Добавить компанию", + "supplier": "Добавить поставщика", + "contractor": "Добавить подрядчика", + "hr": "Добавить кандидата", + "project": "Добавить проект" + }, + "cards_total": { + "universal": ["карточка", "карточки", "карточек"], + "partner": ["партнёр", "партнёра", "партнёров"], + "customer": ["клиент", "клиента", "клиентов"], + "deal": ["карточка", "карточки", "карточек"], + "contact": ["контакт", "контакта", "контактов"], + "company": ["компания", "компании", "компаний"], + "supplier": ["поставщик", "поставщика", "поставщиков"], + "contractor": ["подрядчик", "подрядчика", "подрядчиков"], + "hr": ["кандидат", "кандидата", "кандидатов"], + "project": ["проект", "проекта", "проектов"] + }, + "filter_button": { + "ui": { + "filter_drawer": { + "closed_at": "Дата закрытия", + "just_my_cards": "Только мои карточки", + "sorting": "Сортировка", + "creation_date": "Дата создания", + "owners": "Ответственный", + "stages": "Статусы", + "tasks": "Задачи", + "placeholders": { + "selected_options": "Выбранные опции", + "search_by_card_name": "Искать по названию карточки" + }, + "sorting_options": { + "manual": "Ручная сортировка", + "created_asc": "Создано: от старых к новым", + "created_desc": "Создано: от новых к старым", + "name_asc": "Название: по возрастанию", + "name_desc": "Название: по убыванию" + }, + "tasks_options": { + "all": "Все карточки", + "with_task": "С задачами", + "without_task": "Без задач", + "overdue_task": "С просроченными задачами", + "today_task": "С задачами на сегодня" + }, + "field_filter_string": { + "is_empty": "Поле пустое", + "is_not_empty": "Поле не пустое", + "contains": "Поле содержит...", + "placeholders": { + "value": "Значение" + } + }, + "field_filter_exists": { + "is_empty": "Поле пустое", + "is_not_empty": "Поле не пустое" + } + }, + "date_period_picker": { + "date_period_segmented_control": { + "period": "Период", + "period_hint": "За выбранный период", + "from": "От", + "from_hint": "С выбранной даты", + "to": "До", + "to_hint": "До выбранной даты" + }, + "placeholders": { + "select_period": "Выберите период" + } + }, + "field_filter_items": { + "field_filter_boolean": { + "switch_on": "Включен", + "switch_off": "Выключен" + }, + "field_filter_number": { + "from": "От", + "to": "До" + } + } + } + }, + "settings_button": { + "module_settings": "Настройки модуля", + "settings": "Настройки", + "import": "Импорт", + "get_import_template": "Получить шаблон", + "board_settings": "Настройки доски", + "table_settings": "Настройки таблицы", + "upload_file": "Загрузить файл", + "sales_pipeline_settings": "Настройки плана продаж", + "request_setup": "Заказать настройку {{company}}", + "import_entities_modal": { + "title": "Импорт из Excel", + "annotation": "Чтобы импортировать базу данных в {{company}}, выполните несколько простых шагов:", + "perform_import": "Выполнить импорт", + "setting_up_file_to_import": { + "title": "Настройка файла для импорта", + "step1": "Скачайте образец шаблона файла;", + "step2": "Введите свои данные в таблицу, которая соответствует структуре полей {{company}};", + "step3": "После завершения настройки соответствия столбцов, вы можете сохранить настройки в виде шаблона импорта." + }, + "import_file": { + "title": "Импорт файла", + "step1": "Нажмите на кнопку \"Загрузить файл\";", + "step2": "В открывшемся окне выберите ранее заполненный шаблон;", + "step3": "После скачивания нажмите кнопку \"Выполнить импорт\"." + } + }, + "import_in_background_info_modal": { + "title": "Импорт из Excel", + "content": "Процесс импорта в настоящее время выполняется в фоновом режиме. Вы можете продолжать работать в {{company}} как обычно, и мы уведомим вас, когда процесс будет завершен.", + "ok": "Ok" + } + }, + "search_box": { + "nothing_found": "Ничего не найдено", + "placeholder": "Поиск..." + }, + "section_table_settings_sidebar": { + "table_settings": "Настройки таблицы", + "display_columns": "Отображение колонок" + } + }, + "section_table": { + "hidden": "Скрыто", + "hidden_hint": "Вы не можете просматривать и редактировать это поле, так как оно было скрыто в настройках полей модуля администратором учетной записи.", + "empty": "Вы еще не добавили ни одной карточки", + "all_columns_hidden": "Все колонки скрыты в настройках таблицы", + "name": "Имя", + "owner": "Ответственный", + "stage": "Статус", + "date_created": "Дата создания", + "value": "Бюджет", + "participants": "Участники", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "description": "Описание", + "placeholders": { + "participants": "Добавить участников" + }, + "select_all": { + "title": "Массовый выбор", + "description": "Применить для всех карточек ({{count}}) или только для текущей страницы?", + "all_elements": "Для всех карточек", + "for_page": "Только для текущей страницы" + }, + "batch_actions": { + "cards_selected": "Выбрано карточек: {{count}}", + "change_responsible": "Изменить ответственного", + "change_stage": "Изменить статус", + "delete": "Удалить", + "change_stage_annotation": "Выберите новый статус для выбранных карточек. Это действие нельзя отменить.", + "change_responsible_annotation": "Выберите нового ответственного пользователя для выбранных карточек.", + "action_cannot_be_undone": "Это действие нельзя отменить.", + "delete_warning_title": "Удалить выбранные карточки", + "delete_warning_annotation": "Вы уверены, что хотите удалить выбранные карточки? Это действие нельзя отменить.", + "select_linked_entities_annotation": "Изменить ответственного в связанных карточках" + } + } + } +} diff --git a/frontend/public/locales/ru/module.automation.json b/frontend/public/locales/ru/module.automation.json new file mode 100644 index 0000000..0c45db3 --- /dev/null +++ b/frontend/public/locales/ru/module.automation.json @@ -0,0 +1,368 @@ +{ + "automation": { + "modals": { + "add_activity_automation_modal": { + "current_responsible_user": "Текущий ответственный пользователь", + "activity_default_name": "Активность #{{id}}", + "title_add": "Добавить автоматизацию создания активности", + "title_update": "Обновить автоматизацию создания активности", + "create_activity": "Создать активность *", + "responsible_user": "Ответственный пользователь *", + "due_date": "Срок исполнения", + "defer_start": "Дата начала", + "activity_type": "Тип активности *", + "description": "Описание", + "current_responsible_user_hint": "Выберите ответственного за активность пользователя. Текущий ответственный пользователь – это ответственный за карточку, в которой будет создана активность.", + "placeholders": { + "select_activity_type": "Выберите тип активности" + } + }, + "add_task_automation_modal": { + "task_default_name": "Задача #{{id}}", + "title_add": "Добавить автоматизацию создания задачи", + "title_update": "Обновить автоматизацию создания задачи", + "create_task": "Создать задачу *", + "due_date": "Срок исполнения", + "defer_start": "Дата начала", + "responsible_user": "Ответственный пользователь *", + "name_and_description": "Название * и описание", + "current_responsible_user": "Текущий ответственный пользователь", + "current_responsible_user_hint": "Выберите ответственного за задачу пользователя. Текущий ответственный пользователь – это ответственный за карточку, в которой будет создана задача.", + "placeholders": { + "task_name": "Название задачи", + "select_user": "Выбрать пользователя" + } + }, + "change_responsible_automation_modal": { + "change_responsible_default_name": "Ответственный #{{id}}", + "title_add": "Добавить автоматизацию смены ответственного", + "title_update": "Обновить автоматизацию смены ответственного", + "responsible_user": "Изменить ответственного пользователя на *", + "placeholders": { + "select_responsible_user": "Выберите нового ответственного" + } + }, + "change_stage_automation_modal": { + "change_stage_default_name": "Статус #{{id}}", + "title_add": "Добавить автоматизацию смены статуса", + "title_update": "Обновить автоматизацию смены статуса", + "change_stage": "Изменить статус *", + "stage": "Доска или воронка продаж, и статус *", + "cards_copies": "Копии карточек", + "automation_copy": { + "move_label": "При смене статуса сделки не создавать копию сделки", + "copy_original_label": "При смене статуса сделки создавать копию сделки и оригинал сделки переносить на новый статус", + "copy_new_label": "При смене статуса сделки создавать копию сделки и оригинал сделки оставлять на текущем статусе", + "hint": "При включении этой опции система автоматически создаст копию сделки на её текущем статусе при каждой автоматической смене статуса. Это нужно для точности отчётности и показателей плана продаж, особенно когда смена статуса связана с финальными системными статусами, такими как «Успешно завершено» или «Неудачно завершено»." + }, + "placeholders": { + "select_stage": "Выберите стадию" + } + }, + "change_linked_stage_automation_modal": { + "change_stage_default_name": "Связанный статус #{{id}}", + "title_add": "Добавить автоматизацию смены статуса в связанной карточке", + "title_update": "Обновить автоматизацию смены статуса в связанной карточке", + "change_stage": "Изменить статус *", + "entity_type": "Модуль *", + "stage": "Доска или воронка продаж, и статус *", + "no_stages": "У выбранного модуля нет статусов. Выберите другой модуль для создания автоматизации.", + "cards_copies": "Копии карточек", + "automation_copy": { + "move_label": "При смене статуса сделки не создавать копию сделки", + "copy_original_label": "При смене статуса сделки создавать копию сделки и оригинал сделки переносить на новый статус", + "copy_new_label": "При смене статуса сделки создавать копию сделки и оригинал сделки оставлять на текущем статусе", + "hint": "При включении этой опции система автоматически создаст копию сделки на её текущем статусе при каждой автоматической смене статуса. Это нужно для точности отчётности и показателей плана продаж, особенно когда смена статуса связана с финальными системными статусами, такими как «Успешно завершено» или «Неудачно завершено»." + }, + "placeholders": { + "select_stage": "Выберите стадию" + } + }, + "send_email_automation_modal": { + "configure_mailbox_limits": "Настроить ограничения почтового ящика", + "max_number_of_emails_per_day": "Максимальное количество сообщений в день", + "send_email_default_name": "Почта #{{id}}", + "title_add": "Добавить автоматизацию почты", + "title_update": "Обновить автоматизацию почты", + "title_hint": "Email сообщения будут отправлены только по карточкам контактов, у которых есть заполненное поле типа электронная почта.", + "email_text": "Текст сообщения *", + "recipient_name": "- Получатель", + "recipient_name_hint": "Вы можете установить шаблонный литерал {{contact_name}} в текст письма, оно будет заменено на имя контакта из карточки.", + "user_name": "- Имя пользователя", + "user_phone": "- Телефон пользователя", + "send_with_html": "Отправить сообщение с HTML-разметкой", + "sender_email": "Ящик отправителя *", + "sender_name": "Имя отправителя *", + "send": "Отправлять", + "emails_per_day": "сообщений в день", + "emails_per_day_hint": "Параметр количество сообщений в день нужен чтобы не попасть в бан почтового сервиса. Каждый почтового сервис устанавливает свои лимиты на отправку сообщений. Например, базовая версия Google Workspace разрешает отправлять 500 сообщений в день с одного почтового ящика.", + "mailing_parameters": "Параметры рассылки *", + "options": { + "addresses": "Адреса отправки *", + "all_addresses": "Отправлять по всем карточкам и всем адресам e-mail", + "fine_tune_addresses": "Настроить...", + "main_entity": "Отправлять по основной карточке", + "main_entity_hint": "Выберите, отправлять письмо на все адреса в основной карточке или только на первый по порядку.", + "main_entity_all": "на все адреса", + "main_entity_only_first": "только на первый адрес", + "contact": "Отправлять по карточке контакта", + "contact_hint": "Выберите, отправлять письмо по всем связанным карточкам типа «Контакт» или только по первой, а также отправлять на все адреса в карточке или только на первый по порядку.", + "contact_all_entities_all_values": "на все адреса во всех карточках контактов", + "contact_all_entities_first_value": "только на первый адрес во всех карточках контактов", + "contact_first_entity_all_values": "на все адреса только в первой карточке контакта", + "contact_first_entity_first_value": "только на первый адрес в первой карточке контакта", + "company": "Отправлять по карточке компании", + "company_hint": "Выберите, отправлять письмо по всем связанным карточкам типа «Компания» или только по первой, а также отправлять на все адреса в карточке или только на первый по порядку.", + "company_all_entities_all_values": "на все адреса во всех карточках компаний", + "company_all_entities_first_value": "только на первый адрес во всех карточках компаний", + "company_first_entity_all_values": "на все адреса только в первой карточке компании", + "company_first_entity_first_value": "только на первый адрес в первой карточке компании" + }, + "placeholders": { + "title": "Введите заголовок", + "email_text": "Введите сообщение", + "email_markup": "Введите HTML-разметку", + "select_email_address": "Выберите почтовый адрес", + "select_user": "Выберите пользователя" + } + }, + "create_entity_automation_modal": { + "create_entity_default_name": "Карточка #{{id}}", + "title_add": "Создать автоматизацию связанной карточки", + "title_update": "Обновить автоматизацию связанной карточки", + "create_card": "Создать связанную карточку *", + "select_entity_type": "Модуль", + "select_entity_type_hint": "Выберите модуль, в котором будет создаваться карточка. Во избежание циклов, карточку нельзя создать в текущем модуле.", + "select_stage": "Доска или воронка продаж, и статус", + "responsible_user": "Ответственный пользователь", + "responsible_user_hint": "Выберите ответственного за задачу пользователя. Текущий ответственный пользователь – это ответственный за карточку, в которой будет создана задача.", + "current_responsible_user": "Текущий ответственный пользователь", + "entity_name": "Название карточки", + "placeholders": { + "select_entity_type": "Выберите модуль", + "entity_name": "Введите название карточки" + } + }, + "send_internal_chat_automation_modal": { + "send_internal_chat_default_name": "Чат #{{id}}", + "title_add": "Создать автоматизацию отправки сообщения в чат", + "title_update": "Обновить автоматизацию отправки сообщения в чат", + "sender": "Отправитель *", + "responsible_user_hint": "Выберите отправителя сообщения. Текущий ответственный пользователь – это ответственный за карточку, из которой создана автоматизация.", + "current_responsible_user": "Текущий ответственный пользователь", + "recipients": "Получатели *", + "message": "Текст сообщения *", + "placeholders": { + "recipients": "Выберите пользователей", + "message": "Введите сообщение" + } + }, + "send_external_chat_automation_modal": { + "send_external_chat_default_name": "Внешний чат #{{id}}", + "title_add": "Создать автоматизацию отправки сообщения во внешний чат", + "title_update": "Обновить автоматизацию отправки сообщения во внешний чат", + "sender": "Отправитель *", + "responsible_user_hint": "Выберите отправителя сообщения. Текущий ответственный пользователь – это ответственный за карточку, из которой создана автоматизация.", + "current_responsible_user": "Текущий ответственный пользователь", + "provider": "Провайдер чата *", + "message": "Текст сообщения *", + "send_to_chats": "Отправить в чаты карточек", + "send_to_phone_numbers": "Отправить по номерам телефонов", + "add_phone_number": "Добавить номер", + "hint_chats": "Сообщение будет отправлено во все внешние чаты карточек этого модуля в соответствии с выбранными настройками", + "hint_phone_numbers": "Сообщение будет отправлено на выбранные номера телефонов", + "phone_numbers_disabled": "Отправка по номерам телефонов доступна только для провайдеров Whatsapp и Telegram", + "placeholders": { + "message": "Введите сообщение" + }, + "errors": { + "phone": "Телефон не заполнен" + }, + "options": { + "addresses": "Чаты для отправки *", + "all_addresses": "Отправлять по всем карточкам и всем внешним чатам", + "fine_tune_addresses": "Настроить...", + "main_entity": "Отправлять по основной карточке", + "main_entity_hint": "Выберите, отправлять сообщение во все чаты в основной карточке или только в первый по порядку.", + "main_entity_all": "во все чаты", + "main_entity_only_first": "только в первый чат", + "contact": "Отправлять по карточке контакта", + "contact_hint": "Выберите, отправлять сообщение по всем связанным карточкам типа «Контакт» или только по первой, а также отправлять во все чаты в карточке или только в первый по порядку.", + "contact_all_entities_all_values": "во все чаты во всех карточках контактов", + "contact_all_entities_first_value": "только в первый чат во всех карточках контактов", + "contact_first_entity_all_values": "во все чаты только в первой карточке контакта", + "contact_first_entity_first_value": "только в первый чат в первой карточке контакта", + "company": "Отправлять по карточке компании", + "company_hint": "Выберите, отправлять сообщение по всем связанным карточкам типа «Компания» или только по первой, а также отправлять во все чаты в карточке или только в первый по порядку.", + "company_all_entities_all_values": "во все чаты во всех карточках компаний", + "company_all_entities_first_value": "только в первый чат во всех карточках компаний", + "company_first_entity_all_values": "во все чаты только в первой карточке компании", + "company_first_entity_first_value": "только в первый чат в первой карточке компании" + } + }, + "request_http_automation_modal": { + "http_request_default_name": "HTTP-запрос #{{id}}", + "title_add": "Создать автоматизацию отправки HTTP-запроса", + "title_update": "Обновить автоматизацию отправки HTTP-запроса", + "http_request": "Параметры HTTP-запроса", + "url": "URL *", + "method": "Метод *", + "headers": "Заголовки", + "params": "Query-параметры" + }, + "automation_modal_template": { + "apply_trigger_hint": "При выборе этой опции автоматизация будет применена ко всем существующим карточкам в этом статусе. В противном случае она будет применяться только к новым карточкам, попадающим в этот статус. Это действие можно совершить при каждом сохранении автоматизации.", + "apply_trigger_hint_list": "При выборе этой опции автоматизация будет применена ко всем существующим карточкам. В противном случае она будет применяться только к новым карточкам. Это действие можно совершить при каждом сохранении автоматизации.", + "automation_name": "Название автоматизации *", + "trigger": "Запустить автоматизацию... *", + "conditions": "Условия", + "delay": "Задержка", + "delay_one_stage_title": "Задержка с привязкой к статусу", + "delay_one_stage_hint": "Автоматизация выполнится только если карточка остается на этом статусе до окончания задержки. При смене статуса — не сработает.", + "delay_all_stages_title": "Задержка без привязки к статусу", + "delay_all_stages_hint": "Автоматизация выполнится после задержки, даже если карточка переместится на другой статус.", + "enable": "Включить автоматизацию", + "enable_annotation": "Вы можете включать и отключать автоматизацию", + "apply_trigger": "Применить триггер к существующим объектам на стадии", + "apply_trigger_list": "Применить триггер к существующим объектам", + "delete_automation": "Удалить автоматизацию", + "on": "ВКЛ", + "placeholders": { + "name": "Название" + } + }, + "delete_automation_modal": { + "title": "Вы действительно хотите удалить эту автоматизацию?", + "annotation": "Это действие нельзя отменить" + }, + "common": { + "use_get_chats_template_options": { + "recipient_name": "- Получатель", + "recipient_name_hint": "Вы можете установить шаблонный литерал {{contact_name}} в текст сообщения в чате, и оно будет заменено на имя контакта из карточки." + }, + "trigger_select": { + "exact_time_of_automation": "Точное время автоматизации", + "exact_time": "Точное время: {{day}} в {{time}}", + "when_transitioning": "При переходе или создании", + "at_the_transition": "При переходе на стадию", + "when_creating": "При создании на стадии", + "when_creating_list": "При создании", + "when_responsible_changes": "При изменении ответственного пользователя", + "placeholder": "Выберите триггер" + }, + "conditions_block": { + "field": "Поле *", + "condition": "Условие *", + "entity_type_field_filter_string": { + "is_empty": "Поле пустое", + "is_not_empty": "Поле не пустое", + "contains": "Поле содержит...", + "placeholders": { + "value": "Значение" + } + }, + "entity_type_field_filter_number": { + "placeholders": { + "from": "От", + "to": "До" + } + }, + "entity_type_field_filter_boolean": { + "switch_on": "Включен", + "switch_off": "Выключен" + }, + "fields_conditions": "Условия для полей", + "add_field_condition": "Добавить условие для поля", + "responsible_users": "Ответственные пользователи", + "responsible": "Ответственный", + "budget": "Бюджет", + "value": "Значение", + "from": "от", + "to": "до", + "placeholders": { + "selected_participants": "Выбранные участники", + "selected_options": "Выбранные опции", + "add_condition": "Добавить условие", + "select_responsible": "Выберите ответственных" + } + }, + "description_block": { + "placeholders": { + "description": "Описание" + } + }, + "due_date_select": { + "due_date": "Срок исполнения", + "options": { + "during_this_week": "В течение недели", + "in_3_days": "Через 3 дня", + "in_a_day": "Через день", + "end_of_the_day": "В конце дня", + "at_the_time_of_creation": "Во время создания" + } + }, + "defer_start_select": { + "defer_start": "Дата начала", + "options": { + "in_an_hour": "Через час", + "in_a_day": "Через день", + "in_3_days": "Через 3 дня" + } + }, + "delay_select": { + "no_delay": "Без задержки", + "5_minutes": "5 минут", + "10_minutes": "10 минут", + "15_minutes": "15 минут", + "30_minutes": "30 минут", + "1_hour": "1 час" + }, + "template_list": { + "name": "Название", + "owner": "Ответственный", + "tooltip": "Коды полей будут заменены соответствующими значениями полей карточки", + "show_templates": "Показать коды полей", + "hide_templates": "Скрыть коды полей" + } + } + }, + "external_providers_select": { + "add_new_provider": "Добавить провайдера", + "no_available_providers": "Нет доступных провайдеров" + }, + "sidebar": { + "title": "Автоматизация", + "create_task": "Задача", + "task_create": "Задача", + "create_activity": "Активность", + "activity_create": "Активность", + "change_stage": "Смена статуса", + "entity_stage_change": "Смена статуса", + "entity_linked_stage_change": "Статус связанной карточки", + "entity_responsible_change": "Смена ответственного", + "send_email": "Почта", + "email_send": "Почта", + "entity_create": "Связанная карточка", + "chat_send_amwork": "Сообщение в чате", + "chat_send_external": "Сообщение во внешнем чате", + "http_call": "HTTP-запрос", + "automation": "Автоматизация" + }, + "block": { + "create_task": "Задача", + "task_create": "Задача", + "create_activity": "Активность", + "activity_create": "Активность", + "change_stage": "Смена статуса", + "entity_linked_stage_change": "Смена статуса", + "entity_responsible_change": "Ответственный", + "entity_stage_change": "Смена статуса", + "send_email": "Почта", + "email_send": "Почта", + "chat_send_amwork": "Чат", + "chat_send_external": "Внешний чат", + "entity_create": "Карточка", + "http_call": "HTTP-запрос" + } + } +} diff --git a/frontend/public/locales/ru/module.bpmn.json b/frontend/public/locales/ru/module.bpmn.json new file mode 100644 index 0000000..6330c3f --- /dev/null +++ b/frontend/public/locales/ru/module.bpmn.json @@ -0,0 +1,174 @@ +{ + "bpmn": { + "hooks": { + "use_get_service_task_options": { + "actions": { + "task_create": "Создать задачу", + "activity_create": "Создать активность", + "entity_stage_change": "Изменить этап", + "entity_linked_stage_change": "Изменить этап связанной карточки", + "entity_responsible_change": "Изменить ответственного", + "email_send": "Отправить электронное письмо", + "entity_create": "Создать сущность", + "chat_send_amwork": "Отправить сообщение в чат", + "chat_send_external": "Отправить сообщение во внешний чат", + "http_call": "Отправить HTTP-запрос" + } + }, + "use_bpmn_automations_columns": { + "name": "Название", + "created_by": "Создано", + "active": "Активный", + "delete": "Удалить", + "status": "Статус" + }, + "use_get_workspace_event_options": { + "events": { + "create": "Событие создания карточки", + "change_responsible": "Событие смены ответственного", + "change_stage": "Событие смены этапа" + } + } + }, + "pages": { + "bpmn_automations_page": { + "automation_process_schema_error_warning": { + "continue": "Продолжить", + "title": "Не удалось запустить процесс \"{{name}}\"", + "annotation": "Ошибка проверки схемы BPMN. Проверьте процесс и повторите попытку." + }, + "service_task_popup_template": { + "task_name": "Название сервисной задачи", + "placeholders": { + "task_name": "Название" + } + }, + "workspace_sequence_flow_popup": { + "module": "Выбрать модуль", + "module_hint": "Выберите модуль, где должны быть выполнены указанные условия", + "stage": "Выбрать этап", + "sequence_flow": "Последовательность", + "flow_name": "Название последовательности", + "conditions": "Условия", + "placeholders": { + "flow_name": "Название" + } + }, + "create_automation_process_modal": { + "create": "Создать", + "title": "Создать новый процесс автоматизации", + "name": "Название автоматизации", + "new_process": "Новый процесс #{{number}}", + "placeholders": { + "name": "Название" + } + }, + "bpmn_automations_processes_modeler": { + "add_conditions": "Добавить условия...", + "center_view": "Центрировать Вид", + "pallete": { + "create_workspace_parallel_gateway": "Создать параллельный шлюз", + "create_workspace_start_event": "Создать событие начала", + "create_workspace_end_event": "Создать событие завершения", + "create_workspace_exclusive_gateway": "Создать исключающий шлюз", + "create_workspace_data_object": "Создать объект данных", + "create_workspace_data_store": "Создать хранилище данных", + "create_workspace_participant_expanded": "Создать дорожку процесса", + "create_workspace_group": "Создать группу" + }, + "color_picker": { + "default_color": "Цвет по умолчанию", + "blue_color": "Синий цвет", + "orange_color": "Оранжевый цвет", + "green_color": "Зеленый цвет", + "red_color": "Красный цвет", + "purple_color": "Фиолетовый цвет" + }, + "context_pad": { + "delay": "Добавить задержку", + "append_end_event": "Добавить событие завершения", + "append_gateway": "Добавить шлюз", + "append_text_annotation": "Добавить текстовую сноску", + "replace": "Заменить", + "delete": "Удалить", + "set_color": "Установить цвет", + "connect": "Подключить", + "task_create": "Создать задачу", + "activity_create": "Создать активность", + "entity_stage_change": "Изменить этап", + "entity_linked_stage_change": "Изменить этап связанной карточки", + "entity_responsible_change": "Изменить ответственного", + "email_send": "Отправить электронное письмо", + "entity_create": "Создать карточку", + "chat_send_amwork": "Отправить сообщение в чат", + "chat_send_external": "Отправить сообщение во внешний чат", + "http_call": "Отправить HTTP-запрос" + }, + "toggle_default_context_pad_entry": "Сделать условием по умолчанию или обычным условием", + "open_minimap": "Открыть мини-карту", + "close_minimap": "Закрыть мини-карту", + "activate_hand_tool": "Активировать инструмент \"Рука\"", + "activate_lasso_tool": "Активировать инструмент \"Лассо\"", + "active_create_remove_space_tool": "Активировать инструмент \"Создание/Удаление Пространства\"", + "activate_global_connect_tool": "Активировать инструмент \"Глобальное Подключение\"", + "process_controls": { + "save_and_run": "Сохранить и запустить", + "save_draft": "Сохранить черновик", + "start_process": "Запустить процесс", + "stop_process": "Остановить процесс", + "cancel": "Отмена" + }, + "create_workspace_start_event": "Создать событие начала", + "create_workspace_delay_event": "Создать событие задержки", + "create_workspace_service_task": "Создать сервисную задачу" + }, + "workspace_delay_event_popup": { + "delay": "Задержка", + "delay_label": "Задержка *", + "delay_name": "Название задержки", + "placeholders": { + "delay_name": "Название" + } + }, + "delete_bpmn_automation_warning_modal": { + "title": "Вы уверены, что хотите удалить автоматизацию BPMN \"{{name}}\"?", + "annotation": "Это действие невозможно отменить." + }, + "create_workspace_service_task_menu": { + "workspace_service_tasks": "Сервисные задачи" + }, + "create_workspace_start_event_menu": { + "workspace_events": "События" + }, + "workspace_start_event_popup": { + "event_name": "Название события", + "event_type": "Выберите тип события *", + "module": "Выберите модуль *", + "module_hint": "Выберите модуль, где будет запущено событие", + "placeholders": { + "event_name": "Название" + } + }, + "bpmn_automations_settings_header": { + "create_bpmn_automation": "Создать автоматизацию BPMN", + "bpmn_automations": "Автоматизация BPMN 2.0", + "back": "Назад" + }, + "bpmn_automations_table": { + "no_bpmn_automations_block": { + "annotation1": "У вас еще нет процессов", + "annotation2": "Используйте", + "annotation3": "кнопку для добавления автоматизации." + } + }, + "bpmn_splashscreen": { + "heading": "Профессиональная BPM-автоматизация", + "price_ru": "за 99 ₽/месяц за пользователя, при оплате за год", + "price_us": "за $1/месяц за пользователя, при оплате за год", + "form_button": "Запросить подключение", + "form_header": "Подключить BPM-автоматизацию" + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.builder.json b/frontend/public/locales/ru/module.builder.json new file mode 100644 index 0000000..b6a9b72 --- /dev/null +++ b/frontend/public/locales/ru/module.builder.json @@ -0,0 +1,866 @@ +{ + "builder": { + "components": { + "common": { + "next": "Далее", + "back": "Назад", + "save": "Сохранить", + "workspace_builder": "Конструктор", + "your_workspace": "Ваше рабочее пространство", + "no_links": "Нет доступных модулей для связи." + } + }, + "pages": { + "site_form_builder_page": { + "steps": { + "save_and_leave": "Сохранить и выйти", + "default_title": "Форма на сайт", + "default_form_title": "Связаться с нами", + "default_consent_text": "Мы используем файлы cookie, чтобы сделать веб-сайт лучше. Файлы cookie помогают обеспечить более персонализированный опыт для вас и веб-аналитику для нас. Подробнее читайте в нашей Политике конфиденциальности.", + "default_consent_link_text": "Политика конфиденциальности", + "default_gratitude_header": "Спасибо!", + "default_gratitude_text": "Наши менеджеры свяжутся с вами в рабочее время.", + "default_form_button_text": "Отправить", + "default_client_button_text": "Открыть форму", + "common_error": "Заполните все обязательные поля.", + "step1": { + "title": "Шаг 1", + "description": "Базовая настройка", + "error": "Заполните все обязательные поля на первом шаге." + }, + "step2": { + "title": "Шаг 2", + "description": "Поля формы", + "error": "Заполните все обязательные поля на втором шаге." + }, + "step3": { + "title": "Шаг 3", + "description": "Политика персональных данных и страница благодарности", + "consent_error": "Заполните все обязательные поля в блоке «Политика конфиденциальности» на третьем шаге." + }, + "step4": { + "title": "Шаг 4", + "description": "Дизайн формы" + }, + "step5": { + "title": "Шаг 5", + "description": "Скрипт установки формы" + } + }, + "site_form_builder_step1": { + "title": "Шаг 1. Настройка формы", + "title_input_name": "Введите название формы", + "choose_main_card": "Выберите карточку, которая будет создаваться после отправки формы", + "choose_linked_cards": "Выберите связанные карточки, которые будут создаваться после отправки формы", + "linked_entities_select": "Выберите, в каких модулях создавать карточки после отправки формы с вашего сайта", + "choose_board_annotation": "Выберите доску, где будет создаваться карточка", + "card_settings": "Настройте параметры создания карточки", + "responsible_user": "Ответственный пользователь", + "check_duplicates": "Исключать дубликаты", + "check_duplicates_hint": "Если эта настройка включена, отправка формы с уже существующими в системе email и номером телефона не будет создавать новую карточку, а привяжет существующую.", + "yes": "Да", + "placeholders": { + "title_input": "Название формы", + "responsible_select": "Ответственный", + "board": "Доска" + } + }, + "site_form_builder_step2": { + "sidebar": { + "title": "Элементы формы", + "header_title": "Заголовок формы", + "header_hint": "Может быть только один.", + "delimiter_title": "Разделитель", + "delimiter_hint": "Полоса для разделения смысловых частей формы.", + "field_attributes": { + "title": "Атрибуты поля", + "hint": "Заголовок отображается над полем, а описание — внутри него.", + "label_name": "Заголовок", + "placeholder_name": "Описание" + }, + "card_name_block": { + "card_title": "Название карточки", + "company_title": "Название компании", + "contact_title": "ФИО", + "card_text": "Название", + "company_text": "Название", + "contact_text": "ФИО" + }, + "field_elements": { + "no_available_fields": "Нет доступных полей", + "email": "Почта", + "phone": "Телефон", + "short_text": "Короткий текст", + "long_text": "Длинный текст", + "number": "Число", + "value": "Бюджет", + "date": "Дата", + "select": "Выпадающий список", + "multiselect": "Множественный выбор", + "colored_select": "Цветной выпадающий список", + "colored_multiselect": "Цветной множественный выбор в списке", + "link": "Ссылка", + "file": "Файл", + "switch": "Переключатель" + } + }, + "tree": { + "element_name": { + "placeholder": "Введите заголовок формы" + }, + "entity_field_element": { + "placeholder": "Введите описание поля", + "template": { + "company_name": "Название", + "contact_name": "ФИО", + "card_name": "Название карточки", + "short_text": "Короткий текст", + "long_text": "Длинный текст", + "field": "Поле", + "delimiter": "Разделитель", + "file": "Файл", + "schedule": "Выбор календаря", + "schedule_performer": "Выбор исполнителя", + "schedule_date": "Выбор даты записи", + "schedule_time": "Выбор времени записи", + "placeholder": "Введите название поля", + "required": "Обязательное поле", + "validation": "Валидация поля", + "field_type": "Тип поля:", + "linked_with": "Связано с модулем:" + } + } + } + }, + "site_form_builder_step3": { + "show_in_form_control": { + "yes": "Да" + }, + "consent_block": { + "title": "Политика обработки персональных данных", + "controls": { + "show_consent_title": "Показывать политику персональных данных", + "text_title": "Текст политики обработки персональных данных", + "text_hint": "Текст будет отображаться в конце формы.", + "link_text_title": "Текст на ссылке", + "link_text_hint": "Текст, отображаемый на ссылке", + "link_title": "Ссылка", + "link_hint": "Введите URL политики обработки персональных данных в формате https://example.com/.", + "consent_by_default_title": "Чекбокс отмечен по умолчанию", + "placeholders": { + "text": "Текст политики обработки персональных данных", + "link_text": "Введите текст ссылки", + "link": "Вставьте ссылку" + } + }, + "skeleton": { + "button_text": "Отправить" + } + }, + "gratitude_block": { + "title": "Страница благодарности", + "show_gratitude_title": "Показывать страницу благодарности", + "gratitude_text": "Текст благодарности", + "placeholders": { + "header": "Введите заголовок", + "text": "Введите текст благодарности" + } + } + }, + "site_form_builder_step4": { + "sidebar": { + "custom_css_block": { + "custom_css": "Пользовательский CSS", + "enable_custom_css": "Включить пользовательский CSS", + "placeholders": { + "custom_css": "Пользовательские правила CSS" + } + }, + "advanced_settings": "Расширенные настройки", + "modal_overlay_customization_block": { + "modal_overlay": "Затемнение фона всплывающего окна", + "overlay_display": "Затемнять фон", + "background_color": "Цвет фона", + "opacity": "Прозрачность" + }, + "switch_label": "Включить", + "title": "Дизайн формы", + "description": "Изменения вступают в силу после сохранения.", + "powered_by_logo_block": { + "title": "Логотип {{company}}", + "switch_label": "Включить" + }, + "header_customization_block": { + "title": "Заголовок", + "text_color": "Цвет текста", + "text_align_title": "Выравнивание", + "text_align_left": "По левому краю", + "text_align_center": "По центру", + "text_align_right": "По правому краю" + }, + "fields_customization_block": { + "title": "Поля", + "background_color": "Цвет фона", + "title_color": "Цвет названия", + "text_color": "Цвет текста в полях" + }, + "form_button_customization_block": { + "title": "Кнопка", + "background_color": "Цвет кнопки", + "text_color": "Цвет текста кнопки", + "border": "Скругления углов", + "border_rounded": "Скругленные", + "border_squared": "Квадратные", + "size": "Размер кнопки", + "small": "Маленькая", + "medium": "Средняя", + "large": "Большая", + "text_input": "Текст кнопки", + "hint": "Кнопка отправки, расположенная в конце формы", + "placeholders": { + "text_input": "Отправить" + } + }, + "client_button_customization_block": { + "title": "Кнопка для сайта", + "background_color": "Цвет кнопки", + "text_color": "Цвет текста кнопки", + "border": "Скругления углов", + "border_rounded": "Скругленные", + "border_squared": "Квадратные", + "size": "Размер кнопки", + "small": "Маленькая", + "medium": "Средняя", + "large": "Большая", + "text_input": "Текст кнопки", + "hint": "Кнопка, открывающая всплывающее окно с формой", + "placeholders": { + "text_input": "Открыть" + } + }, + "form_layout_customization_block": { + "title": "Форма", + "view_title": "Вид", + "view_built_in": "Встраиваемая", + "view_modal": "Всплывающее окно", + "position_title": "Расположение формы", + "position_left": "Слева", + "position_center": "По центру", + "position_right": "Справа", + "border_radius_title": "Скругление углов", + "border_rounded": "Скругленные", + "border_none": "Без скругления", + "orientation_title": "Ориентация", + "orientation_horizontal": "Горизонтальная", + "orientation_vertical": "Вертикальная", + "max_width": "Максимальная ширина", + "max_height": "Максимальная высота", + "background_color": "Цвет фона" + }, + "size_customization_element_template": { + "switch_label": "Включить", + "placeholders": { + "input": "Введите значение" + } + } + }, + "preview": { + "iframe_title": "{{companyName}} – Предпросмотр формы", + "error": "Ошибка при загрузке окна просмотра формы", + "open_in_a_new_tab": "Открыть в новой вкладке", + "open_preview": "Открыть предпросмотр", + "phone": "Телефон", + "tablet": "Планшет", + "computer": "Компьютер", + "form": "Форма", + "gratitude": "Страница благодарности", + "button": "Кнопка" + } + }, + "site_form_builder_step5": { + "title": "Шаг 5. Скопируйте код формы для вашего сайта", + "copy_form_code": "Скопируйте код формы", + "form_code": "Код формы", + "multiform_mode": "Режим совместимости нескольких форм", + "multiform_mode_hint": "Включите этот режим, если на странице, на которую вы планируете вставить форму, уже есть другая форма, созданная в нашем конструкторе. Эта настройка позволит избежать конфликтов отображения.", + "form_will_be_mounted_here": "Форма будет встроена сюда", + "public_link_title": "Публичная страница формы", + "public_link_annotation": "Перейдите по ссылке ниже, чтобы попасть на публичную страницу с вашей формой. Если вам не нужно размещать форму на собственном сайте, поделитесь этой страницей с вашими пользователями, чтобы они могли ее заполнить. Форма уже настроена и готова к использованию, а при внесении изменений она будет обновлена автоматически.", + "instruction_title": "Инструкция по интеграции формы на ваш сайт", + "view_form": "Просмотреть форму", + "static_site_integration_title": "Интеграция на статичные сайты и сайты, созданные через конструкторы", + "first_point": { + "title": "1. Добавление формы через CSS-класс", + "annotation": "Присвойте класс {{mountClass}} элементам на вашем сайте, внутри которых вы хотите встроить форму. Код формы, скопированный выше вставьте в тег или в самый конец тега . Форма отобразится внутри элементов с указанным классом. Если у элемента уже есть содержимое, форма появится в самом конце этого содержимого. Если вы выбрали отображение формы в виде всплывающего окна, в указанное место будет встроена кнопка, по нажатию на которую откроется всплывающее окно с формой. Это предпочтительный способ вставки формы на сайт." + }, + "second_point": { + "title": "2. Добавление формы без CSS-классов", + "annotation": "Если вы не можете присвоить элементам класс, вставьте код формы в то место на вашем сайте, где вы хотите её отобразить. Код формы можно встроить в любое место вашего сайта. В этом случае форма будет отображена в том месте, куда вставлен код. Этим способом можно вставить только одну форму на страницу." + }, + "ssr_integration_title": "Интеграция на сайты с использованием SSR (например, созданные с помощью Next.js, Astro и др.)", + "ssr_integration_annotation": "Во избежание проблем с гидрацией (загрузкой скриптов в HTML-разметку) рекомендуется использовать метод с указанием класса workspace-form-builder-mount-container для элемента, внутрь которого вы хотите встроить форму (подробнее описано в пункте 2 выше). В этом случае скрипт формы желательно поместить в тэг .", + "analytics": { + "title": "Отслеживание событий и целей аналитики", + "annotation": "Форма поддерживает отправку событий и целей в системы аналитики. Если на сайте установлен тег Google Analytics или счетчик Яндекс.Метрики, вы можете отслеживать действия пользователей с формой. Для этого настройте события или цели в своей системе аналитики и привяжите их к идентификаторам, указанным в таблице.", + "table_title": "Отслеживаемые события", + "event_title": "Название", + "event_id": "Идентификатор цели", + "form_view": "Просмотр формы", + "form_start": "Начало заполнения формы", + "field_fill": "Заполнение поля «{{field}}»", + "form_submit": "Отправка формы" + } + } + }, + "headless_site_form_builder_page": { + "steps": { + "save_and_leave": "Сохранить и выйти", + "default_title": "Форма на сайт для интеграции по API", + "common_error": "Заполните все обязательные поля.", + "step1": { + "title": "Шаг 1", + "description": "Базовая настройка", + "error": "Заполните все обязательные поля на первом шаге." + }, + "step2": { + "title": "Шаг 2", + "description": "Поля формы" + }, + "step3": { + "title": "Шаг 3", + "description": "Инструкция по интеграции формы" + } + }, + "site_form_builder_step3": { + "title": "Шаг 3. Интегрируйте форму на сайт", + "json_integration_title": "Отправка данных в формате JSON", + "api_integration_instruction": "Этот способ подходит, если вы обрабатываете формы на своем сервере. Чтобы отправить пользовательские данные из вашей формы в {{company}}, нужно отправить запрос с параметрами, указанными ниже. В теле запроса передайте пользовательские данные в формате JSON. Пример тела запроса представлен ниже.", + "api_integration_request_annotation": "Метод: POST\nЗаголовок: Content-Type: application/json\nURL: {{endpoint}}", + "request_parameters_title": "Параметры запроса", + "request_body_title": "Тело запроса", + "field_value_explanation": "В поле value записывайте значение, введеное пользователем. Форматы значений для каждого поля указаны ниже.", + "field_value_title": "Значения полей", + "field_value": "Значение поля {{label}}", + "response_title": "Формат ответа", + "response_explanation": "При успешной отправке формы сервер отвечает HTTP-кодом 201.", + "formdata_integration_title": "Отправка данных в формате FormData", + "formdata_integration_instruction": "Этот способ подходит для отправки форм с сайтов, созданных с помощью конструкторов, например Tilda. Данные отправляются в формате x-www-form-urlencoded. Для корректной отправки данных нужно лишь указать URL вебхука ниже и прописать переменные для полей.", + "formdata_webhook_url": "URL вебхука", + "formdata_fields_title": "Переменные полей", + "formdata_fields_explanation": "Каждое поле формы должно иметь свое название (переменную) для соотнесения с нужным полем в системе. В данном случае переменные полей являются их числовыми идентификаторами. Для корректной отправки данных укажите каждому полю формы переменную, указанную в таблице ниже.", + "fields_table": { + "field_title": "Название поля", + "field_format": "Формат поля", + "field_example": "Пример", + "field_id": "Переменная (идентификатор) поля", + "format": { + "text": "Текстовая строка, заключенная в кавычки", + "link": "Текстовая строка без пробелов, заключенная в кавычки", + "phone": "Текстовая строка без пробелов, заключенная в кавычки", + "email": "Текстовая строка без пробелов, заключенная в кавычки", + "value": "Текстовая строка, заключенная в кавычки", + "select": "Число-идентификатор выбранного пользователем значения", + "switch": "Булево значение без кавычек", + "number": "Число без кавычек", + "multiselect": "Массив чисел-идентификаторов выбранных пользователем значений", + "date": "Строка с датой в формате ISO 8601, заключенная в кавычки" + }, + "example": { + "text": "\"Значение, введенное пользователем\"", + "link": "\"https://example.com\"", + "phone": "\"12345678910\"", + "email": "\"form@example.com\"", + "value": "\"Значение, введенное пользователем\"", + "select": "34", + "switch": "true", + "number": "42", + "multiselect": "[26, 27]", + "date": "\"2024-12-31T00:00:00\"" + } + } + } + }, + "online_booking_site_form_builder_page": { + "steps": { + "save_and_leave": "Сохранить и выйти", + "default_title": "Форма онлайн-записи", + "default_form_title": "Онлайн-запись", + "default_consent_text": "Мы используем файлы cookie, чтобы сделать веб-сайт лучше. Файлы cookie помогают обеспечить более персонализированный опыт для вас и веб-аналитику для нас. Подробнее читайте в нашей Политике конфиденциальности.", + "default_consent_link_text": "Политика конфиденциальности", + "default_gratitude_header": "Спасибо!", + "default_gratitude_text": "Наши менеджеры свяжутся с вами, чтобы подтвердить запись.", + "default_form_button_text": "Отправить", + "default_client_button_text": "Открыть форму", + "schedule_field_title": "Календарь", + "schedule_field_placeholder": "Выберите календарь...", + "schedule_performer_field_title": "Исполнитель", + "schedule_performer_field_placeholder": "Выберите исполнителя...", + "schedule_date_field_title": "Дата записи", + "schedule_date_field_placeholder": "Выберите дату...", + "schedule_time_field_title": "Время записи", + "schedule_time_field_placeholder": "Выберите время...", + "common_error": "Заполните все обязательные поля.", + "step1": { + "title": "Шаг 1", + "description": "Базовая настройка", + "error": "Заполните все обязательные поля на первом шаге." + }, + "step2": { + "title": "Шаг 2", + "description": "Поля формы", + "error": "Заполните все обязательные поля на втором шаге." + }, + "step3": { + "title": "Шаг 3", + "description": "Политика персональных данных и страница благодарности", + "consent_error": "Заполните все обязательные поля в блоке «Политика конфиденциальности» на третьем шаге." + }, + "step4": { + "title": "Шаг 4", + "description": "Дизайн формы" + }, + "step5": { + "title": "Шаг 5", + "description": "Скрипт установки формы" + } + }, + "online_booking_site_form_builder_step1": { + "title": "Шаг 1. Настройка формы", + "title_input_name": "Введите название формы", + "choose_schedulers": "Выберите календари, в которых будет создаваться запись", + "choose_linked_cards": "Выберите связанные карточки, которые будут создаваться после создания записи", + "linked_entities_select": "Выберите, в каких модулях создавать карточки после отправки формы с вашего сайта", + "choose_board_annotation": "Выберите доску, где будет создаваться карточка", + "no_schedules": "Вы еще не создали ни одного календаря визитов. Чтобы создать форму онлайн-записи, создайте модуль «Календарь визитов» и настройте в нем параметры онлайн-записи.", + "no_linked_cards": "Нет доступных модулей для создания связанных карточек. Чтобы создать связь, выберите календарь с привязанным модулем. Если вы выбрали несколько календарей, убедитесь, что все календари связаны с одним и тем же модулем.", + "limit_days_name": "Количество дней для записи вперед", + "card_settings": "Настройте параметры создания карточки", + "responsible_user": "Ответственный пользователь", + "check_duplicates": "Исключать дубликаты", + "check_duplicates_hint": "Если эта настройка включена, отправка формы с уже существующими в системе email и номером телефона не будет создавать новую карточку, а привяжет существующую.", + "yes": "Да", + "placeholders": { + "title_input": "Название формы", + "responsible_select": "Ответственный", + "board": "Доска", + "limit_days": "От 1 до 730" + } + } + }, + "workspace_editor_page": { + "edit": "Редактировать", + "delete": "Удалить", + "warn_title": "Внимание!", + "warn_annotation": "Удаление модуля приведет к безвозвратному удалению всей связанной с ним информации. Вы уверены, что хотите продолжить?", + "open_module": "Открыть модуль", + "entity_type_field_used_in_formula_warning_modal": { + "title": "Не удалось удалить этот модуль", + "annotation": "Некоторые поля этого модуля связаны с формулой в другом модуле. Пожалуйста, обновите настройки формулы и попробуйте снова.", + "continue": "Продолжить" + } + }, + "scheduler_builder_page": { + "save_error": "Укажите название модуля и выберите хотя бы одного пользователя на первом шаге.", + "steps": { + "step1": { + "label": "1-й шаг", + "description": "Настройте новый модуль" + }, + "step2": { + "label": "2-й шаг", + "description": "Свяжите календарь визитов с другим модулем в вашем рабочем пространстве" + }, + "step3": { + "label": "3-й шаг", + "description": "Интегрируйте календарь в модуль продуктов в вашем рабочем пространстве" + }, + "default_title": "Календарь визитов" + }, + "scheduler_builder_step1": { + "title": "Персонализируйте модуль", + "name_the_module": "Назовите модуль", + "name_the_module_hint": "Это имя будет отображаться в левой боковой панели. Вы сможете изменить его в будущем.", + "choose_icon": "Выберите иконку для левой боковой панели", + "choose_icon_hint": "Эта иконка будет отображаться в левой боковой панели. Вы сможете изменить её в будущем.", + "for_users": "Для пользователей", + "for_user_groups": "Для групп пользователей", + "view_type": "Тип отображения", + "view_type_hint": "Выберите тип отображения - отображение времени слева (вертикальное отображение времени) или отображение времени сверху (горизонтальное отображение времени).", + "schedule": "Отображение времени слева", + "schedule_img_alt": "{{company}} – Превью интерфейса расписания", + "board": "Отображение времени сверху", + "board_img_alt": "{{company}} – Превью интерфейса доски", + "enter_the_interval": "Введите интервал", + "maximum_number_of_records": "Максимальное количество записей", + "error": "Пожалуйста, заполните все обязательные поля.", + "change_anyway": "Изменить", + "warning_title": "Вы уверены, что хотите изменить этот параметр?", + "warning_annotation": "Все существующие визиты будут удалены.", + "for_whom": "Выберите пользователей или группы пользователей", + "for_whom_hint": "Выберите тип отображения календаря. Для пользователей или для групп пользователей.", + "no_duration": "Нет", + "minutes": "минут", + "schedule_params_title": "Расписание и онлайн-запись", + "schedule_params_hint": "Настройте параметры встреч и расписание для календаря. Они будут использованы для отображения календаря и форм онлайн-записи.", + "time_buffer_before": "Перерыв перед встречей", + "time_buffer_after": "Перерыв после встречи", + "intervals_source": "Использовать расписание", + "performers_intervals": "пользователей", + "scheduler_intervals": "этого календаря", + "placeholders": { + "interval": "Интервал", + "unlimited": "Неограниченно", + "module_name": "Название модуля" + }, + "change_type_warning": { + "title": "Внимание!", + "annotation": "Изменение типа календаря приведет к безвозвратному удалению всех записей визитов. Вы уверены, что хотите продолжить?", + "approve": "Подтверждаю" + }, + "change_performers_warning": { + "title": "Внимание!", + "annotation": "Удаление исполнителя из календаря приведет к безвозвратному удалению всех его записей визитов. Вы уверены, что хотите продолжить?", + "approve": "Подтверждаю" + } + }, + "scheduler_builder_step2": { + "title": "Свяжите с другим модулем", + "do_not_link": "Не связывать с другим модулем", + "duplicate_title": "Запретить дубликаты встреч", + "duplicate_hint": "При включении этой настройки в календаре будет запрещено ставить несколько встреч для одной и той же связанной карточки в один день." + }, + "scheduler_builder_step3": { + "title": "Интегрируйте с модулем продуктов", + "do_not_integrate": "Не интегрировать с модулем продуктов" + } + }, + "products_section_builder_page": { + "delay_select": { + "no_return": "Без возврата", + "no_return_minified": "Без", + "24_hours": "24 часа", + "24_hours_minified": "24ч", + "48_hours": "48 часов", + "48_hours_minified": "48ч", + "72_hours": "72 часа", + "72_hours_minified": "72ч", + "7_days": "7 дней", + "7_days_minified": "7д", + "10_days": "10 дней", + "10_days_minified": "10д", + "14_days": "14 дней", + "14_days_minified": "14д", + "28_days": "28 дней", + "28_days_minified": "28д", + "30_days": "30 дней", + "30_days_minified": "30д" + }, + "steps": { + "save_common_error": "Не удалось сохранить. Некоторые шаги не содержат необходимую информацию.", + "save_warehouses_error": "Для продолжения необходимо создать как минимум один склад. Или вы можете отключить склады.", + "step1": { + "label": "1й шаг", + "description": "Настройте новый модуль" + }, + "step2": { + "label": "2й шаг", + "description": "Создайте категории продуктов (группы продуктов)" + }, + "step3": { + "label": "3й шаг", + "description": "Настройте склады для ваших продуктов" + }, + "step4": { + "rentals": { + "label": "4й шаг", + "description": "Настройте параметры расписания для ваших арендуемых продуктов" + }, + "sales": { + "label": "4й шаг", + "description": "Связь с другими модулями в вашем рабочем пространстве" + } + }, + "step5": { + "label": "5й шаг", + "description": "Связь с другими модулями в вашем рабочем пространстве" + }, + "default_titles": { + "sale": "Управление складом и товарами", + "rental": "Управление арендой" + } + }, + "product_section_builder_step1": { + "title": "Настроить модуль", + "name_the_section": "Название модуля", + "name_the_section_hint": "Это имя будет отображаться в левой боковой панели. Вы сможете изменить его в будущем.", + "choose_icon": "Выберите иконку для левой боковой панели", + "choose_icon_hint": "Эта иконка будет отображаться в левой боковой панели. Вы сможете изменить ее в будущем.", + "error": "Необходимо указать название модуля.", + "placeholders": { + "section_name": "Название модуля" + } + }, + "product_section_builder_step3": { + "cancel_after_label": "Установите период автоматической отмены резервирования товаров в заказе", + "cancel_after_hint": "Вы можете настроить автоматическую отмену резервирования товаров в заказах. Это позволяет предотвратить возникновение некорректных остатков на складе, которые могут происходить, когда товары зарезервированы в сделках, но эти сделки не обрабатываются или были отменены.", + "update_error": "Не удалось сохранить ваш выбор. Попробуйте снова.", + "warehouses_error": "Для продолжения необходимо создать как минимум один склад. Или вы можете отключить склады.", + "enable_warehouses_label": "Включить склады", + "enable_warehouses_hint": "Включите склады, чтобы отслеживать запасы ваших товаров. Если эта опция включена, вам потребуется создать как минимум один склад, чтобы продолжить.", + "enable_barcodes_label": "Включить штрих-коды", + "enable_barcodes_hint": "Включите штрих-коды для отслеживания запасов ваших продуктов. Эта опция позволит вам сканировать штрих-коды продуктов.", + "on": "ВКЛ" + }, + "products_section_builder_link_sections_step": { + "title": "Ссылка на другие модули", + "cards": "Карточки", + "schedulers": "Календари", + "do_not_integrate": "Не интегрировать с модулем календаря" + }, + "products_section_builder_schedule_settings_step": { + "title": "Настройки расписания", + "rental_duration_interval": "Интервал продолжительности аренды", + "rental_duration_interval_hint": "Выберите интервал продолжительности аренды.", + "start_of_rental_interval": "Начало интервала аренды", + "start_of_rental_interval_hint": "Укажите время начала аренды.", + "twenty_four_hours": "24 часа" + } + }, + "et_section_builder_page": { + "steps": { + "requisites_codes": { + "ie_name": "Имя ИП", + "ie_surname": "Фамилия ИП", + "ie_patronymic": "Отчество ИП", + "org_name": "Наименование компании", + "org_tin": "ИНН организации", + "org_trrc": "КПП организации", + "org_psrn": "ОГРН", + "org_type": "Тип организации", + "org_full_name": "Полное наименование", + "org_short_name": "Краткое наименование", + "org_management_name": "Имя руководителя", + "org_management_post": "Пост руководителя", + "org_management_start_date": "Дата вступления в должность руководителя", + "org_branch_count": "Количество филиалов", + "org_branch_type": "Тип подразделения", + "org_address": "Адрес организации", + "org_reg_date": "Дата регистрации", + "org_liquidation_date": "Дата ликвидации", + "org_status": "Статус организации", + "stat_okato": "ОКАТО", + "stat_oktmo": "ОКТМО", + "stat_okpo": "ОКПО", + "stat_okogu": "ОКОГУ", + "stat_okfs": "ОКФС", + "stat_okved": "ОКВЭД", + "org_extra_employee_count": "Среднесписочная численность работников", + "org_extra_founders": "Учредители компании", + "org_extra_managers": "Руководители компании", + "org_extra_capital": "Уставной капитал", + "org_extra_licenses": "Лицензии", + "org_extra_phones": "Телефоны", + "org_extra_emails": "Адреса электронной почты", + "bank_name": "Наименование банка", + "bank_bic": "БИК ЦБ РФ", + "bank_swift": "SWIFT", + "bank_tin": "ИНН банка", + "bank_trrc": "КПП банка", + "bank_correspondent_acc": "Корреспондентский счет ЦБ РФ", + "bank_payment_city": "Город для платежного поручения", + "bank_opf_type": "Тип кредитной организации", + "bank_checking_account": "Расчетный счет" + }, + "save_error": "Не удалось сохранить. На некоторых этапах отсутствует необходимая информация.", + "details": "Детали", + "analytics": "Аналитика", + "requisites": "Реквизиты", + "step1": { + "label": "1й шаг", + "description": "Настройте новый модуль" + }, + "step2": { + "label": "2й шаг", + "description": "Выберите имя и создайте поля для вашей новой карточки" + }, + "step3": { + "label": "3й шаг", + "description": "Добавьте функциональность для карточки" + }, + "step4": { + "label": "4й шаг", + "description": "Связь с другими модулями в вашем рабочем пространстве" + }, + "default_titles": { + "builder": "Конструктор", + "project": "Проекты и задачи", + "universal": "Универсальный модуль", + "partner": "Партнеры", + "deal": "CRM", + "contact": "Контакты", + "company": "Компании", + "supplier": "Поставщики", + "contractor": "Подрядчики", + "hr": "Найм персонала" + } + }, + "tasks_fields_options": { + "planned_time": "Запланированное время", + "board_name": "Название доски", + "assignee": "Ответственный", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "description": "Описание", + "subtasks": "Подзадачи" + }, + "et_section_builder_step1": { + "title": "Настроить модуль", + "name_the_section": "Название модуля", + "name_the_section_hint": "Это имя будет отображаться в левой боковой панели. Вы сможете изменить его в будущем.", + "choose_icon": "Выберите иконку для левой боковой панели", + "choose_icon_hint": "Эта иконка будет отображаться в левой боковой панели. Вы сможете изменить ее в будущем.", + "type_of_display": "Тип отображения", + "type_of_display_hint": "Выберите тип интерфейса – список или доска/колонки или оба варианта.", + "board": "Доска", + "board_img_alt": "{{company}} - Превью интерфейса доски", + "list": "Список", + "list_img_alt": "{{company}} - Превью интерфейса списка", + "placeholders": { + "section_name": "Название модуля" + } + }, + "et_section_builder_step2": { + "title": "Настройте параметры новой карточки", + "name_the_card": "Название карточки", + "name_the_card_hint": "Название карточки внутри модуля. Вы сможете изменить его в будущем.", + "fine_tuning": "Тонкая настройка карточки", + "create_fields": "Создайте поля для вашей новой карточки", + "create_fields_hint": "Поля - основные информационные элементы карточки, вы можете создавать различные группы и типы полей.", + "placeholders": { + "card_name": "Название карточки" + } + }, + "et_section_builder_step3": { + "title": "Добавьте функциональность для карточки", + "error": "Пожалуйста, выберите хотя бы одну опцию.", + "features": { + "saveFiles": "Файлы", + "tasks": "Задачи", + "notes": "Заметки", + "chat": "Чат", + "activities": "Активности", + "documents": "Создавать документы", + "products": "Продукты" + } + }, + "et_section_builder_step4": { + "title": "Связать с другими модулями", + "cards": "Карточки", + "products": "Продукты", + "schedulers": "Календари", + "do_not_integrate": "Не интегрировать с модулем календаря" + }, + "entity_type_used_in_formula_warning_modal": { + "title": "Модуль используется в формуле", + "annotation": "Невозможно удалить связь с модулем, поскольку его поле используется в формуле." + } + }, + "builder_journey_picker_page": { + "request_bpmn_form_modal": { + "error": "Произошла ошибка при отправке вашего запроса. Убедитесь, что вы ввели правильную информацию, и попробуйте снова.", + "send_request": "Оставить заявку", + "header": "BPMN – Оставьте заявку и мы свяжемся с вами", + "full_name": "Полное имя *", + "phone": "Телефон *", + "email": "Почта *", + "comment": "Комментарий", + "placeholders": { + "full_name": "Иван Иванов", + "comment": "Дополнительная информация" + }, + "us_price_full": "Подключите BPMN за $1 в месяц за пользователя!", + "ru_price_full": "Подключите BPMN за 99₽ в месяц за пользователя!" + }, + "request_additional_storage_modal": { + "error": "Произошла ошибка при отправке вашего запроса. Убедитесь, что вы ввели правильную информацию, и попробуйте снова.", + "send_request": "Оставить заявку", + "header": "Запрос дополнительного хранилища", + "full_name": "Полное имя *", + "phone": "Телефон *", + "email": "Почта *", + "comment": "Комментарий", + "placeholders": { + "full_name": "Иван Иванов", + "comment": "Дополнительная информация" + }, + "ten_gb_ru_description": "Подключите 10 Гб дополнительного хранилища за 100₽/мес!", + "ten_gb_us_description": "Подключите 10 Гб дополнительного хранилища за $1/мес!", + "one_hundred_gb_ru_description": "Подключите 100 Гб дополнительного хранилища за 500₽/мес!", + "one_hundred_gb_us_description": "Подключите 100 Гб дополнительного хранилища за $5/мес!", + "one_tb_ru_description": "Подключите 1 Тб дополнительного хранилища за 1500₽/мес!", + "one_tb_us_description": "Подключите 1 Тб дополнительного хранилища за $15/мес!" + }, + "create_a_new_module": "Создать новый модуль", + "module_names": { + "bpmn": { + "us_price_tag": "$1/мес.", + "ru_price_tag": "99₽/мес." + }, + "wazzup": "Wazzup", + "main_modules": "Выберите функциональный модуль, который вы хотите создать", + "additional_modules": "Дополнительные функциональные модули", + "marketplace": "Маркетплейс", + "crm": "CRM", + "project_management": "Проекты и задачи", + "production": "Производственные процессы", + "product_management_for_sales": "Товары и услуги", + "product_management_rentals": "Управление арендой", + "scheduler": "Календарь визитов", + "supplier_management": "Поставщики", + "contractor_management": "Подрядчики", + "partner_management": "Партнеры", + "hr_management": "Найм персонала", + "contact": "Контакты", + "company": "Компании", + "universal_module": "Универсальный модуль", + "finances": "Финансы", + "automatisation": "BPMN Автоматизация", + "marketing": "Маркетинг", + "rentals": "Аренда", + "for_sales": "Продажи", + "soon": "Скоро", + "chosen": "Выбрано", + "telephony": "АТС телефония", + "mailing": "Email-сервис", + "messenger": "Мульти Мессенджер", + "documents": "Генерация документов", + "forms": "Формы на сайт", + "headless_forms": "Формы на сайт по API", + "online_booking": "Формы онлайн-записи", + "website_chat": "Чат на сайт", + "tilda": "Tilda Publishing", + "wordpress": "WordPress", + "twilio": "Whatsapp Business", + "fb_messenger": "Facebook Messenger", + "salesforce": "Salesforce", + "make": "Make", + "apix_drive": "ApiX-Drive", + "albato": "Albato", + "one_c": "1С", + "additional_storage": "Дополнительное хранилище", + "ten_gb": "10 Гб дополнительного хранилища", + "one_hundred_gb": "100 Гб дополнительного хранилища", + "one_tb": "1 Тб дополнительного хранилища", + "storage": { + "ten_gb_ru_price_tag": "100₽/мес.", + "ten_gb_us_price_tag": "$1/мес.", + "one_hundred_gb_ru_price_tag": "500₽/мес.", + "one_hundred_gb_us_price_tag": "$5/мес.", + "one_tb_ru_price_tag": "1500₽/мес.", + "one_tb_us_price_tag": "$15/мес." + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.fields.json b/frontend/public/locales/ru/module.fields.json new file mode 100644 index 0000000..72380a3 --- /dev/null +++ b/frontend/public/locales/ru/module.fields.json @@ -0,0 +1,122 @@ +{ + "fields": { + "formula_warning": "Изменение формулы приведет к перерасчету значения в этом поле. Для сохранения текущих данных рассмотрите возможность создания нового поля.", + "project_fields_block": { + "owner": "Ответственный", + "value": "Бюджет", + "participants": "Участники", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "description": "Описание", + "placeholders": { + "participants": "Добавить участников" + } + }, + "field_value": { + "ogrn": "ОГРН", + "tin": "ИНН", + "trrc": "КПП", + "already_have_an_active_call": "У вас уже есть активный звонок", + "recent": "Недавний", + "no_available_phone_numbers": "Нет доступных номеров", + "value_field_formula_calculation": "Для этого поля настроена формула, его нельзя редактировать вручную. Значение: {{value}}.", + "readonly": "Это поле только для просмотра", + "important_field": "Важное поле", + "mandatory_field": "Обязательное поле", + "important_field_completed": "Важное поле заполнено", + "mandatory_field_completed": "Обязательное поле заполнено", + "local_time": "Предполагаемое локальное время в столице страны по номеру телефона", + "connect_telephony": "Подключитесь к телефонии, чтобы включить функцию звонков.", + "chat_unavailable": "Похоже, у вас нет доступа к этому чату. Обратитесь к администратору для подключения." + }, + "components": { + "field_formula_settings_button": { + "no_available_fields_for_formula": "Нет доступных полей с типом бюджет, число или формула", + "save_first": "Эта формула может быть настроена в карточке", + "customize": "Настройка Формулы: {{fieldName}}", + "formula_hint": "Это поле предназначено для ввода формулы. Формула может содержать значения, представленные ниже, а также другие числовые поля и формулы, включая поля из других функциональных модулей. Нажмите на нужное значение или выберите поле.", + "field": "Поле", + "close": "Закрыть" + }, + "field_settings_modal": { + "number": "Число", + "currency": "Валюта", + "format_title": "Выберите формат отображения для этого поля", + "ordinary_field": "Обычное поле", + "save_first": "Это поле можно настроить только после сохранения внутри карточки", + "important_field": "Важное поле", + "important_field_hint": "Вы можете сделать поле важным и тогда это поле будет иметь отметку желтой лампочки, как напоминание что его нужно заполнить. После заполнения будет отметка зеленой галочки. Если вам нужно чтобы поле было обязательно заполнено и не было возможности перевести сделку на следующий этап, тогда выберите функцию \"обязательное поле\".", + "select_pipeline_and_statuses": "Выберите пайплайн и статус", + "mandatory_field": "Обязательное поле", + "mandatory_field_hint": "Вы можете настроить обязательное поле для заполнения. В этом случае пользователи не смогут перейти на следующий статус в воронке. Выберите нужную воронку и статус, с которого поле нужно обязательно заполнить.", + "user_exclusion": "Исключение пользователей", + "user_exclusion_hint": "Вы можете исключить пользователей, кому будет разрешено не заполнять это поле. Возможно это понадобится для администраторов.", + "editing_restriction": "Запретить редактирование", + "editing_restriction_hint": "Вы можете запретить выбранным пользователям редактировать это поле. Они его будут видеть, но не смогут редактировать.", + "select_users": "Выберите пользователей", + "hide_field": "Скрыть поле для пользователей", + "hide_field_hint": "Вы можете скрыть это поле для выбранных пользователей, это поле будет для них не доступно.", + "field_visibility_in_pipeline": "Скрыть поле в воронке", + "field_visibility_in_pipeline_hint": "Вы можете настроить видимость поля для каждой воронки и статуса. Ниже вы можете выбрать, на каких статусах и в каких воронках поле будет скрыто.", + "customize": "Настройка поля: {{fieldName}}" + }, + "edit_fields": { + "add_bank_requisites": "Добавить банковские реквизиты", + "add_org_requisites": "Добавить реквизиты организаций и ИП", + "add_org_stat_codes": "Добавить коды статистики организаций и ИП", + "add_additional_org_requisites": "Добавить дополнительные поля организаций и ИП", + "add_field": "Добавить поле", + "add_utm_fields": "Добавить поля UTM-меток", + "add_ga_fields": "Добавить поля Google Analytics", + "add_ym_fields": "Добавить поля Яндекс.Метрики", + "add_fb_fields": "Добавить поля Facebook Analytics", + "select_options": { + "max_length": "Максимум {{length}} символов", + "add_option": "Добавить вариант", + "cancel": "Отмена", + "add": "Добавить", + "no_options": "Нет вариантов", + "placeholders": { + "search": "Поиск...", + "option": "Вариант" + } + } + }, + "field_form_group": { + "max_length": "Максимум {{length}} символов", + "text": "Текст", + "number": "Число", + "multitext": "Многострочный текст", + "select": "Выпадающий список", + "multiselect": "Множественный выбор в списке", + "switch": "Переключатель", + "formula": "Формула", + "phone": "Телефон", + "email": "Электронная почта", + "value": "Бюджет", + "date": "Дата", + "link": "Ссылка", + "file": "Файл", + "richtext": "Форматируемый текст", + "participant": "Участник", + "participants": "Участники", + "colored_select": "Цветной выпадающий список", + "colored_multiselect": "Цветной множественный выбор в списке", + "checked_multiselect": "Чек-лист", + "checklist": "Многострочный текст с флажками", + "field_name": "Название поля", + "analytics_field": "Поле аналитики" + }, + "show_fields": { + "no_fields": "Нет полей" + }, + "show_files": { + "empty": "Здесь пока нет файлов", + "attach": "Прикрепить файлы" + }, + "file_field_value_comp": { + "attach_files": "Прикрепить файлы" + } + } + } +} diff --git a/frontend/public/locales/ru/module.mailing.json b/frontend/public/locales/ru/module.mailing.json new file mode 100644 index 0000000..c1a567f --- /dev/null +++ b/frontend/public/locales/ru/module.mailing.json @@ -0,0 +1,234 @@ +{ + "mailing": { + "unknown_file": "Неизвестный файл", + "modals": { + "send_email_modal": { + "to": "Кому:", + "copy": "Копия:", + "hidden_copy": "Скрытая копия:", + "from": "От:", + "subject": "Тема:", + "attachment": "вложение", + "attachments": "вложения", + "delete_all": "Удалить все", + "send": "Отправить", + "error": "Не удалось отправить сообщение! Пожалуйста, попробуйте позже.", + "send_with_html": "Отправить письмо с HTML-разметкой", + "send_with_html_hint": "Выберите эту опцию, если вы хотите отправить электронное письмо с разметкой HTML. Это полезно, если вы хотите отказаться от предоставленных {{company}} функций форматирования электронной почты.", + "components": { + "changes_not_saved_modal": { + "title": "Изменения не сохранены", + "annotation": "Продолжить редактирование?", + "approve": "Удалить" + }, + "invalid_email_address_modal": { + "title": "Адрес электронной почты \"{{email}}\" недействительный", + "annotation": "Проверьте адрес и попробуйте еще раз.", + "approve": "Отправить в любом случае" + }, + "send_email_settings_dropdown": { + "show_copy": "Показать поле «Копия»", + "show_hidden_copy": "Показать поле «Скрытая копия»" + }, + "editors": { + "email_signature_editor": { + "placeholder": "Добавить подпись", + "no_signature": "Нет подписи" + }, + "email_text_editor": { + "placeholders": { + "new_message": "Новое сообщение", + "enter_html_markup": "Введите HTML-разметку" + } + } + } + } + } + }, + "pages": { + "mailing_settings_page": { + "soon": "Скоро...", + "add_button": "Добавить почтовый ящик", + "mail_templates": "Шаблоны электронных писем", + "templates_caption": "Вы можете создавать шаблоны электронных писем, которые можно использовать для отправки клиенту. После добавления шаблона вы сможете получить доступ к отправке электронных писем.", + "setup_signature": "Настроить подпись", + "add_template": "Добавить шаблон", + "components": { + "mailbox_item": { + "draft_text": "Черновик", + "draft_hint": "Подключение почтового ящика не завершено. Пожалуйста, введите недостающие настройки.", + "inactive_text": "Неактивный", + "inactive_hint": "Почтовый ящик неактивен. {{error}}.", + "sync_text": "Синхронизация", + "sync_hint": "Почтовый ящик синхронизируется. Пожалуйста, подождите.", + "active_text": "Активный" + } + }, + "modals": { + "delete_mailbox_modal": { + "delete": "Удалить", + "title": "Вы действительно хотите удалить этот почтовый ящик?", + "save_correspondence_annotation": "Сохранить историю переписки для этого адреса электронной почты?", + "save_correspondence": "Сохранить историю переписки" + }, + "mailbox_address_modal": { + "continue": "Продолжить", + "title": "Подключить почтовый ящик", + "placeholder": "Ваш адрес электронной почты", + "caption1": "Подключите корпоративный ящик с общим доступом, который получает запросы от клиентов, или личный ящик одного из ваших сотрудников.", + "caption2": "Электронные письма отправленные на этот почтовый ящик, будут автоматически прикрепляться к контактам. Вы можете создать сделку непосредственно из списка писем.", + "caption3": "Если у вас возникли проблемы с подключением, попробуйте включить доступ для вашего почтового клиента.", + "cancel": "Отмена", + "google_caption1": "Использование и передача информации {{company}}, полученной через API Google, в любое другое приложение будут соответствовать", + "google_policy_link": "Политике конфиденциальности данных пользователей Google API Services", + "google_caption2": "включая требования ограниченного использования." + }, + "mailbox_provider_modal": { + "title": "Выберите провайдера", + "caption": "Выберите своего провайдера электронной почты. Если его нет в списке, выберите «Ручной ввод» и настройте ящик вручную.", + "manual": "Ручной ввод" + }, + "update_mailbox_modal": { + "max_number_of_emails_per_day": "Максимальное количество сообщений в день", + "emails_per_day": "сообщений в день", + "emails_per_day_hint": "Параметр количество сообщений в день нужен чтобы не попасть в бан почтового сервиса. Каждый почтового сервис устанавливает свои лимиты на отправку сообщений. Например, базовая версия Google Workspace разрешает отправлять 500 сообщений в день с одного почтового ящика.", + "email_readonly_title": "Вы не можете редактировать адрес уже созданного почтового ящика", + "title_connect": "Подключить почтовый ящик", + "title_edit": "Изменить настройки почтового ящика", + "encryption": "Шифрование", + "owner": "Выберите владельца почты", + "for_whom_available": "Выберите кому будет доступна электронная почта", + "synchronize": "Синхронизировать электронные письма за последние 7 дней", + "create_entities_annotation": "Настройте параметры ниже, чтобы при входящем письме от нового контакта, карточка создавалась автоматически", + "create_entities_label": "Создавать карточку при входящем письме", + "delete": "Удалить ящик", + "reconnect": "Переподключить почтовый ящик", + "placeholders": { + "password": "Пароль", + "imap": "IMAP-сервер", + "port": "Порт", + "smtp": "SMTP-сервер", + "owner": "Выберите владельца" + } + }, + "mailbox_signature_modal": { + "title": "Подписи", + "mailbox_signature_editor": { + "available_in_mailboxes": "Доступно в почтовых ящиках:", + "delete": "Удалить", + "save": "Сохранить", + "warning_title": "Удалить подпись", + "warning_annotation": "Вы уверены, что хотите удалить эту подпись?", + "save_as_html": "Сохранить как разметку HTML", + "save_as_html_hint": "Выберите эту опцию, если вы хотите сохранить подпись как HTML-разметку. Это полезно, если вы хотите отказаться от функций форматирования {{company}}.", + "placeholders": { + "signature_name": "Название подписи", + "your_signature": "Ваша подпись", + "select_mailboxes": "Выберите почтовые ящики" + } + } + } + } + }, + "mailing_page": { + "title": "Почта и мессенджер", + "no_email": "Еще нет электронной почты", + "components": { + "section": { + "inbox": "Входящие", + "sent": "Отправленные", + "spam": "Спам", + "trash": "Удаленные", + "draft": "Черновики", + "flagged": "Отмеченные", + "archive": "Архив", + "all": "Вся почта", + "mailbox": "Почтовый ящик {{number}}" + }, + "message_panel": { + "no_messages": "Нет сообщений", + "demo_message_subject": "✉️ {{company}}: Корпоративное решение для электронной почты", + "demo_message_snippet": "Уважаемый клиент, представляем революционное корпоративное решение для электронной почты, которое несомненно привлечет ваше внимание." + }, + "attachments_block": { + "attachment": "вложение", + "attachments": "вложения", + "download_all": "Скачать все" + }, + "reply_controls": { + "reply": "Ответить", + "reply_all": "Ответить всем", + "forward": "Переслать" + }, + "create_message_button": { + "title": "Новое сообщение" + }, + "no_mailboxes_panel_block": { + "title": "Нажмите кнопку ниже, чтобы добавить свой почтовый ящик." + }, + "thread": { + "unknown": "Неизвестный 👤", + "no_selected_message": "Сообщение не выбрано", + "from": "От", + "subject": "Тема", + "reply": "Ответить", + "reply_all": "Ответить всем", + "forward": "Переслать", + "add_task": "Добавить задачу", + "add_contact": "Добавить контакт", + "spam": "Спам", + "unspam": "Не спам", + "move_to_inbox": "Переместить во Входящие", + "trash": "Удалить", + "close": "Закрыть", + "unseen": "Пометить непрочитанным", + "user": "Пользователь", + "date": "{{day}} в {{time}}", + "amwork_workspace": "Рабочее пространство {{company}}", + "demo_message_title": "✉️ {{company}}: Корпоративное решение для электронной почты", + "demo_message_snippet": "Уважаемый клиент, представляем революционное корпоративное решение для электронной почты, которое несомненно привлечет ваше внимание. Откройте для себя новые возможности и повысьте свою корпоративную коммуникацию на новый уровень с интеграцией электронной почты в рабочее пространство {{company}}!", + "dear_customer": "Уважаемый клиент,", + "demo_message_intro": "Представляем революционное корпоративное решение для электронной почты, которое несомненно привлечет ваше внимание. Откройте для себя новые возможности и переведите свою корпоративную коммуникацию на новый уровень с интеграцией электронной почты в рабочее пространство {{company}}! Подключайте любые корпоративные или личные почтовые ящики и наслаждайтесь не только знакомыми функциями электронной почты, но и множеством дополнительных инструментов. Создавайте задачи, лиды, проекты и многое другое непосредственно из электронного письма, сохраняя полный контроль над всеми взаимодействиями с клиентами, партнерами и проектами. Легко отправляйте сообщения из карточек лидов, сделок, партнеров или проектов, и они автоматически будут сохранены в соответствующей карточке. Таким образом, вы можете легко читать и анализировать историю переписки для конкретной сделки или проекта. Работайте более эффективно в команде, предоставляя доступ к общему почтовому ящику и управляя коммуникациями вместе с коллегами. Разблокируйте новые горизонты с {{company}} и повысьте производительность вашего бизнеса!", + "email_functionality": "📬 Возможности электронной почты:", + "email_functionality_ul": "
  • API-интеграция с Gmail
  • IMAP-интеграция
  • Интеграция электронной почты за последние 7 дней для только что добавленных почтовых ящиков
  • Входящие и исходящие электронные письма
  • Группировка электронных писем в цепочки по отправителю
  • Возможность создания задачи из электронного письма
  • Возможность создания контакта из электронного письма
  • Автоматическое создание лидов из электронных писем
  • Прикрепление входящих и исходящих электронных писем к истории/ленте карточки
  • Возможность написания электронного письма из карточки
  • Создание пользовательских папок
  • Использование нескольких почтовых ящиков
  • Совместное управление почтовыми ящиками
  • Отображение электронных писем в формате HTML
  • Поиск электронных писем
  • Спам
  • Удаленные элементы
  • Черновики
  • Создание подписей
  • Получатели в копии
  • Скрытые получатели в копии
  • Прикрепление файлов
", + "reach_out": "Если у вас остались какие-либо вопросы, мы будем рады помочь -", + "sincerely_amwork": "С уважением, ваша команда {{company}}." + }, + "modals": { + "create_contact_modal": { + "create": "Создать", + "title": "Создать карточки из письма", + "create_contact": "Создать контакт из письма", + "create_lead": "Создать лид из письма", + "open_entity": "Открыть только что созданный объект", + "placeholders": { + "where_contact": "Выберите место создания нового контакта", + "where_lead": "Выберите место создания нового лида", + "board_for_lead": "Выберите доску для нового лида" + } + }, + "link_contact_modal": { + "link": "Привязать", + "title": "Привязать карточку", + "select_module": "Выберите модуль", + "search_card": "Выберите карточку", + "open_entity": "Открыть привязанную карточку", + "placeholders": { + "module": "Модуль...", + "search_card": "Введите название для поиска..." + } + } + } + }, + "hooks": { + "use_message_controls": { + "unknown": "Неизвестный 👤", + "reply_to": "Ответить", + "reply_all": "Ответить всем", + "forward": "Переслать" + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.multichat.json b/frontend/public/locales/ru/module.multichat.json new file mode 100644 index 0000000..23ed232 --- /dev/null +++ b/frontend/public/locales/ru/module.multichat.json @@ -0,0 +1,71 @@ +{ + "multichat": { + "amwork_messenger": "{{company}} Мессенджер", + "components": { + "multichat_control": { + "not_found": "Не найдено", + "chats": "Чаты", + "messages": "Сообщения", + "users": "Пользователи", + "owner": "Владелец", + "supervisor": "Наблюдатель", + "no_chats_yet": "Пока нет чатов", + "create_card": "Создать карточку", + "link_card": "Привязать карточку", + "card": "Карточка", + "ui": { + "deleted_user": "Удаленный пользователь 👻", + "multichat_control_header": { + "title": "{{company}} Мульти-Мессенджер" + }, + "create_amwork_chat_button": { + "label": "Создать чат {{company}}", + "modals": { + "select_contact_title": "Выберите одного пользователя или группу пользователей", + "continue": "Продолжить", + "customize_chat": "Настроить чат", + "placeholders": { + "group_name": "Название группы" + } + } + }, + "chats_header_providers_tabs": { + "all_chats": "Все Чаты" + }, + "chat_message_context_menu": { + "reply": "Ответить", + "copy": "Копировать текст", + "edit": "Редактировать", + "delete": "Удалить" + }, + "chat_message_reply_block": { + "editing_message": "Редактирование сообщения" + }, + "send_chat_message_block": { + "placeholders": { + "message": "Сообщение" + } + }, + "no_selected_chat_plug": { + "title": "Выберите чат, чтобы начать общение" + }, + "chats_panel_block": { + "you": "Вы" + }, + "send_chat_message_with_files_modal": { + "send_files": "Отправить файлы" + }, + "chat_message_item": { + "you": "Вы" + }, + "chat_controls_menu": { + "mark_as_read": "Пометить как прочитанное", + "delete_chat": "Удалить чат", + "delete_warning_title": "Вы уверены, что хотите удалить чат?", + "delete_warning_annotation": "Все сообщения будут удалены. Это действие нельзя отменить." + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.notes.json b/frontend/public/locales/ru/module.notes.json new file mode 100644 index 0000000..19e08d3 --- /dev/null +++ b/frontend/public/locales/ru/module.notes.json @@ -0,0 +1,49 @@ +{ + "notes": { + "notes_page": { + "module_name": "Заметки", + "add_note": "Создать заметку", + "new_note_heading": "Новая заметка" + }, + "toolbar": { + "undo": "Отменить", + "redo": "Повторить", + "bold": "Полужирный", + "italic": "Курсив", + "underline": "Подчеркнутый", + "strike": "Зачеркнутый", + "bullet_list": "Маркированный список", + "ordered_list": "Нумерованный список", + "text": "Текст", + "heading": "Название", + "subheading": "Заголовок", + "heading_3": "Подзаголовок", + "more": "Действия...", + "expand": "На весь экран", + "minimize": "В оконный режим" + }, + "editor": { + "recently_edited": "только что", + "quick_note": "Быстрая заметка", + "last_changed": "Последние изменения: ", + "duplicate": "Дублировать", + "put_into_folder": "Восстановить", + "delete": "Удалить заметку" + }, + "selector": { + "new_note_heading": "Новая заметка", + "saved_indicator": "Изменения сохранены", + "pinned": "Закрепленные", + "today": "Сегодня", + "yesterday": "Вчера", + "earlier": "Ранее" + }, + "block": { + "unnamed_note": "Заметка без названия" + }, + "folders": { + "all": "Все", + "recently_deleted": "Недавно удаленные" + } + } +} diff --git a/frontend/public/locales/ru/module.notifications.json b/frontend/public/locales/ru/module.notifications.json new file mode 100644 index 0000000..6250e49 --- /dev/null +++ b/frontend/public/locales/ru/module.notifications.json @@ -0,0 +1,79 @@ +{ + "notifications": { + "tags": { + "task_new": "Новая задача", + "task_overdue": "Задача", + "task_before_start": "Задача", + "task_overdue_employee": "Задача", + "activity_new": "Новая активность", + "activity_overdue": "Активность", + "activity_before_start": "Активность", + "activity_overdue_employee": "Активность", + "task_comment_new": "Новый комментарий", + "chat_message_new": "Новое сообщение", + "mail_new": "Новое письмо", + "entity_note_new": "Новая заметка", + "entity_new": "Новый {{entityTypeName}}", + "entity_responsible_change": "Новый ответственный {{entityTypeName}}", + "entity_import_completed": "Импорт", + "yesterday": "Вчера в {{time}}", + "date": "{{date}} в {{time}}" + }, + "components": { + "delay_select": { + "no_delay": "Без задержки", + "5_minutes": "5 минут", + "10_minutes": "10 минут", + "15_minutes": "15 минут", + "30_minutes": "30 минут", + "1_hour": "1 час" + }, + "notifications_panel": { + "mute_notifications": "Отключить звук уведомлений", + "unmute_notifications": "Включить звук уведомлений", + "no_notifications": "У вас пока нет уведомлений", + "ui": { + "block_header_annotation": { + "overdue": "Просроченный", + "overdue_employee": "{{employee}} просрочил(-а)", + "after_time": "После {{time}}", + "from_employee": "от {{employee}}", + "system_notice": "Системное уведомление", + "message": "Сообщение", + "yesterday": "Вчера в {{time}}", + "date": "{{date}} в {{time}}" + }, + "notification_settings": { + "title": "Уведомления" + }, + "notification_settings_modal": { + "title": "Настройка уведомлений", + "enable_popup": "Всплывающие уведомления", + "new": "Новый (-ая)", + "new_mail": "Новое письмо", + "new_chat_message": "Новое сообщение в чате", + "new_note": "Новое примечание", + "entity_responsible_change": "Смена ответственного в карточке", + "entity_import_complete": "Завершен импорт карточке", + "new_task_comment": "Новый комментарий к задаче", + "unknown": "Неизвестный тип", + "new_task": "Новая задача", + "overdue_task": "Просроченная задача", + "before_task_start": "Перед началом задачи", + "overdue_task_employee": "Просроченная задача для сотрудника(ов)", + "new_activity": "Новая активность", + "overdue_activity": "Просроченное действие", + "before_activity_start": "Перед началом активности", + "overdue_activity_employee": "Просроченное действие для сотрудника(ов)", + "placeholders": { + "select": "Выбрать" + } + }, + "read_all_button": { + "read_all": "Прочитать все" + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.products.json b/frontend/public/locales/ru/module.products.json new file mode 100644 index 0000000..661cc00 --- /dev/null +++ b/frontend/public/locales/ru/module.products.json @@ -0,0 +1,502 @@ +{ + "products": { + "components": { + "common": { + "readonly_stocks": "Вы не можете изменять остатки в этом статусе", + "readonly_delete": "Вы не можете удалять товары из заказа в этом статусе", + "select_the_warehouse_to_write_off": "Выберите склад для списания", + "return_stocks_warning_modal": { + "title": "Вернуть остатки", + "annotation": "Хотели бы вы вернуть остатки на склад?", + "approve_title": "Вернуть", + "cancel_title": "Не возвращать" + }, + "delete_order_warning_modal": { + "title": "Внимание", + "annotation": "Вы уверены, что хотите удалить заказ? Это действие нельзя отменить." + }, + "delete_order_or_clear_items_warning_modal": { + "title": "Внимание", + "annotation": "Хотите удалить весь заказ или очистить все товары в нем? Это действие нельзя отменить.", + "approve_title": "Удалить заказ", + "cancel_title": "Очистить товары" + }, + "rental_order_status_select": { + "formed": "Сформировано", + "accepted_to_warehouse": "Принято на склад", + "sent_to_warehouse": "Отправлено на склад", + "delivered": "Доставлено", + "reserved": "Резерв", + "shipped": "Отгружено", + "cancelled": "Отменено", + "returned": "Возвращено", + "placeholders": { + "select_status": "Выберите статус" + } + }, + "order_status_select": { + "placeholders": { + "select_status": "Выберите статус" + }, + "statuses": { + "reserved": "Резерв", + "sent_for_shipment": "Отправлено на отгрузку", + "shipped": "Отгружено", + "cancelled": "Отменено", + "returned": "Возвращено" + } + }, + "products_tab_selector": { + "shipments": "Отгрузки", + "timetable": "Календарь", + "products": "Продукты" + }, + "products_category_select": { + "no_categories": "Нет категорий", + "placeholders": { + "select_category": "Категория" + } + }, + "products_warehouse_select": { + "no_warehouses": "Нет складов", + "placeholders": { + "select_warehouse": "Склад" + } + }, + "products_settings_button": { + "settings": "Настройки", + "module_settings": "Настройки модуля", + "table_settings": "Настройки таблицы", + "report_settings": "Настройки отчета" + }, + "calendar": { + "month": "Месяц", + "week": "Неделя", + "days": { + "short": { + "mo": "Пн", + "tu": "Вт", + "we": "Ср", + "th": "Чт", + "fr": "Пт", + "sa": "Сб", + "su": "Вс" + }, + "long": { + "mo": "Понедельник", + "tu": "Вторник", + "we": "Среда", + "th": "Четверг", + "fr": "Пятница", + "sa": "Суббота", + "su": "Воскресенье" + } + }, + "resources_label": "Товары", + "statuses": { + "reserved": "Забронировано", + "rented": "Аренда" + }, + "fields": { + "phone": "Телефон", + "mail": "Mail", + "shifts": "Количество дней", + "status": "Статус", + "period": "Период аренды" + } + } + } + }, + "pages": { + "card_products_orders_page": { + "ui": { + "empty": "Вы еще не создавали заказы в этой секции" + }, + "hooks": { + "use_products_section_orders_columns": { + "name": "Название", + "warehouse": "Склад", + "status": "Статус", + "created_at": "Создано", + "shipped_at": "Отгружено", + "creator": "Создатель", + "order": "Заказ-{{number}}" + }, + "use_rental_products_section_orders_columns": { + "name": "Название", + "status": "Статус", + "created_at": "Создано", + "creator": "Создатель", + "order": "Заказ-{{number}}", + "shipment": "Отгрузка для заказа #{{number}}", + "linked_entity": "Связанная карточка" + } + } + }, + "card_products_order_page": { + "templates": { + "products_order_block_template": { + "new_order": "Новый заказ", + "save": "Сохранить", + "cancel": "Отменить", + "added_to": "Добавлено в {{entity_name}}", + "empty": "Вы еще не добавили товары в заказ" + }, + "products_warehouse_block_template": { + "product_management_for_sales": "Товары и услуги", + "product_management_rentals": "Управление арендой", + "products": "Продукты", + "warehouse": "Склад", + "add": "Добавить", + "reset": "Сбросить", + "empty": "Склад пуст", + "warehouse_select_hint": "Выбранный склад в этом разделе должен соответствовать выбранному складу заказа в верхнем разделе (блоке заказа). Если вы хотите выбрать другой склад здесь, вы должны сначала очистить или изменить выбранный склад в верхнем разделе. Любой выбранный склад здесь будет автоматически проставляться в верхнем разделе." + } + }, + "common": { + "products_order_price_head_cell": { + "price": "Цена," + }, + "products_order_max_discount_hint": "Максимальная скидка", + "products_order_tax_header_cell": { + "tax": "Налог,", + "tax_included": "Включен", + "tax_excluded": "Исключен" + }, + "products_order_total_block": { + "total": "Итого: {{total}}" + }, + "remove_selected_block": { + "remove_selected": "Удалить выбранные" + } + }, + "hooks": { + "use_price_cell_columns": { + "name_fallback": "Без названия", + "name": "Название", + "price": "Цена", + "currency": "Валюта" + } + }, + "ui": { + "card_rental_products_order_component": { + "hooks": { + "use_rental_card_order_columns": { + "name": "Название", + "discount": "Скидка", + "availability": "Доступность", + "amount": "Сумма" + }, + "use_rental_warehouse_columns": { + "name": "Название", + "price": "Цена", + "category": "Категория", + "availability": "Доступность" + } + }, + "ui": { + "rental_availability_cell": { + "available": "Доступно", + "reserved": "Резерв", + "rent": "Аренда" + }, + "rental_order_periods_control": { + "new_period": "Новый период", + "periods_selected": "Выбранные периоды: {{count}}", + "add_new_period": "Добавить новый период", + "hint_text": "Вы можете выбирать или изменять периоды только при создании новых заказов или для заказов со статусом 'Сформирован'.", + "placeholders": { + "select_periods": "Выбрать периоды" + } + }, + "card_rental_products_order_block_header": { + "placeholders": { + "select_order_warehouse": "Выберите склад заказа" + } + } + } + }, + "card_products_order_component": { + "hooks": { + "use_available_columns": { + "unknown": "Неизвестный...", + "name": "Название", + "stock": "Остаток", + "reserved": "Резерв", + "available": "Доступно" + }, + "use_card_order_columns": { + "name": "Название", + "discount": "Скидка", + "tax": "Налог,", + "quantity": "Количество", + "amount": "Сумма" + }, + "use_reservations_columns": { + "warehouse_name": "Название склада", + "stock": "Остаток", + "reserved": "Резерв", + "available": "Доступно", + "quantity": "Количество" + }, + "use_warehouse_columns": { + "name": "Название", + "price": "Цена", + "category": "Категория", + "available": "Доступно", + "quantity": "Количество" + } + }, + "ui": { + "available_head_cell": { + "available": "Доступно", + "available_hint": "Наведите курсор на ячейку этой колонки для получения подробной информации об остатках на складе." + }, + "card_products_order_block_header": { + "cancel_after_hint": "Вы можете настроить автоматическую отмену резервирования товаров в заказах. Это позволяет предотвратить возникновение некорректных остатков на складе, которые могут происходить, когда товары зарезервированы в сделках, но эти сделки не обрабатываются или были отменены.", + "placeholders": { + "select_order_warehouse": "Выберите склад заказа" + } + }, + "add_stock_placeholder": { + "add_stock": "Добавить остатки на склад" + } + } + } + } + }, + "product_categories_page": { + "title": "Создать категорию (группу продуктов)", + "placeholders": { + "category_name": "Название категории", + "subcategory_name": "Название подкатегории" + }, + "buttons": { + "add_category": "Добавить категорию", + "add_subcategory": "Добавить подкатегорию" + }, + "ui": { + "delete_category_warning_modal": { + "cannot_be_undone": "Это действие не может быть отменено.", + "title": "Вы уверены, что хотите удалить {{name}}?", + "annotation": "Все подкатегории будут удалены." + } + } + }, + "product_page": { + "product_description": "Описание продукта", + "sku_already_exists_warning": "Продукт с артикулом \"{{sku}}\" уже существует", + "sku": "Артикул (SKU)", + "unit": "Единица", + "tax": "Налог", + "category": "Категория", + "warehouse": "Склад", + "ui": { + "add_warehouse_placeholder": { + "add_warehouse": "Добавить склад" + }, + "product_actions_dropdown": { + "delete_product": "Удалить продукт" + }, + "product_description": { + "placeholders": { + "add_description": "Добавить описание" + } + }, + "product_feed": { + "calendar": "Календарь", + "prices": "Цены", + "stocks": "Остатки", + "images": "Изображения", + "add_price": "Добавить цену", + "add_image": "Добавить изображение", + "delete_image": "Удалить изображение", + "delete_images": "Удалить изображения", + "placeholders": { + "name": "Название цены", + "unit_price": "Цена за единицу" + }, + "labels": { + "maximum_discount": "Максимальная скидка на товар", + "product_cost": "Стоимость товара" + } + }, + "product_name_block": { + "placeholders": { + "product_name": "Название продукта" + } + } + } + }, + "warehouses_page": { + "page_title": "Настройте ваши склады", + "placeholders": { + "name": "Название склада", + "select_warehouse": "Выберите склад" + }, + "add_warehouse": "Добавить склад", + "ui": { + "delete_warehouse_modal": { + "title": "Вы уверены, что хотите удалить {{name}}?", + "annotation": "Все остатки на складе будут удалены.", + "annotation_move_stocks": "Все остатки на складе будут удалены. У вас есть возможность переместить все запасы на другой склад (если в системе созданы другие склады).", + "placeholders": { + "move_stocks_to": "Переместить остатки..." + } + } + } + }, + "products_page": { + "no_warehouses_or_categories": "Нет складов или категорий товаров для отображения фильтров", + "title": { + "sale": "Продажа", + "rental": "Аренда", + "shipments": "Отгрузки", + "calendar": "Календарь" + }, + "tabs": { + "products": "Продукты", + "timetable": "Календарь", + "shipments": "Отгрузки", + "reports": "Отчеты" + }, + "all_columns_hidden": "Все колонки скрыты в настройках таблицы", + "table_settings": "Настройки таблицы", + "display_columns": "Отображение колонок", + "add_product": "Добавить продукт", + "empty": "Вы пока не добавили ни одного продукта", + "show_empty_resources": "Показать пустые строки", + "hide_empty_resources": "Скрыть пустые строки", + "hooks": { + "use_products_columns": { + "name": "Название", + "prices": "Цены", + "sku": "Артикул (SKU)", + "tax": "Налог", + "stocks": "Остаток", + "unit": "Единица", + "availability": "Доступность" + }, + "use_product_stocks_columns": { + "warehouse": "Склад", + "reserved": "Резерв", + "available": "Доступно", + "stock": "Остаток" + }, + "use_create_stocks_columns": { + "warehouse": "Склад", + "stock": "Остаток" + } + }, + "ui": { + "add_product_modal": { + "changes_not_saved_warning_title": "Вы уверены, что хотите закрыть это окно?", + "changes_not_saved_warning_annotation": "Все изменения будут потеряны.", + "changes_not_saved_warning_approve_title": "Всё равно закрыть", + "barcodes_hint": "Вы можете сканировать штрих-коды с помощью сканера или вводить их вручную в это поле. Каждый штрих-код в разделе должен быть уникальным.", + "sku_already_exists_warning": "Продукт с артикулом \"{{sku}}\" уже существует", + "types": { + "product": "Продукт", + "service": "Услуга", + "kit": "Комплект" + }, + "add_product": "Добавить продукт", + "name": "Название", + "type": "Тип", + "add_photo": "Добавить фото", + "description": "Описание", + "sku": "Артикул (SKU)", + "unit": "Единица", + "tax": "Налог", + "category": "Категория", + "prices": "Цены", + "stocks": "Остатки" + }, + "photo_block_item": { + "alt": "Продукт {{name}}" + }, + "product_price_list": { + "add_price": "Добавить цену" + }, + "product_search_block": { + "placeholders": { + "search_products": "Поиск продуктов" + } + }, + "product_stocks_modal": { + "title": "Установить остаток продукта в каждом складе" + }, + "create_stocks_modal": { + "title": "Укажите остаток продукта" + } + } + }, + "shipment_page": { + "hooks": { + "use_shipment_columns": { + "name": "Название", + "sku": "Артикул (SKU)", + "available": "Доступно", + "quantity": "Количество" + }, + "use_rental_shipment_columns": { + "name": "Наименование", + "sku": "Артикул (SKU)", + "tax": "Налог", + "discount": "Скидка", + "total": "Итого", + "quantity": "Количество" + } + }, + "ui": { + "shipment_page_secondary_header": { + "order": "Заказ-{{number}}" + }, + "rental_shipment_page_secondary_header": { + "order": "Заказ-{{number}}" + }, + "some_products_not_checked_warning_modal": { + "title": "Некоторые продукты не отмечены", + "annotation": "Вы пытаетесь изменить статус заказа, но некоторые продукты не отмечены. Вы все равно хотите изменить статус?", + "approve_title": "Все равно изменить" + }, + "product_barcodes_control": { + "barcode": "Штрих-код", + "modals": { + "product_is_not_in_order_warning_modal": { + "title": "Продукт не входит в этот заказ", + "approve_title": "Добавить в заказ", + "annotation": "Продукт со штрих-кодом \"{{barcode}}\" не включен в этот заказ. У вас есть возможность добавить его вручную или продолжить без него." + }, + "product_does_not_exist_warning_modal": { + "title": "Продукт не существует", + "approve_title": "Создать продукт", + "annotation": "Продукт с штрих-кодом \"{{barcode}}\" отсутствует в этом разделе продуктов. Вы можете создать его вручную на странице продуктов." + } + } + } + } + }, + "shipments_page": { + "hooks": { + "use_shipments_columns": { + "unknown": "Неизвестный...", + "name": "Название", + "shipment": "Отгрузка для заказа #{{number}}", + "warehouse": "Склад", + "shipped_at": "Отгружено", + "created_at": "Создано", + "status": "Статус", + "linked_entity": "Связанная карточка" + } + }, + "ui": { + "shipments_table": { + "all_columns_hidden": "Все колонки скрыты в настройках таблицы", + "empty": "Нет запланированных отгрузок" + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.reporting.json b/frontend/public/locales/ru/module.reporting.json new file mode 100644 index 0000000..9bce7ec --- /dev/null +++ b/frontend/public/locales/ru/module.reporting.json @@ -0,0 +1,435 @@ +{ + "reporting": { + "templates": { + "components": { + "report_table": { + "empty": "Нет доступных данных за этот период" + }, + "report_settings_drawer": { + "table_settings": "Настройки таблицы", + "display_columns": "Отображение колонок" + } + }, + "comparative_report_template": { + "placeholders": { + "users": "Пользователи", + "pipeline": "Доски продаж", + "month": "Выбрать месяц", + "year": "Выбрать год" + } + }, + "general_report_template": { + "apply_filter": "Применить фильтр", + "reset": "Сбросить", + "stages": { + "all": "Все этапы", + "open": "Открытые этапы", + "lost": "Проигранные этапы", + "won": "Выигранные этапы", + "warehouse": "Склады", + "category": "Категории" + }, + "placeholders": { + "users": "Пользователи", + "pipeline": "Доски продаж", + "stage": "Выбрать этапы", + "warehouse": "Склады", + "category": "Категории", + "field_or_responsible": "Поле или ответственный" + } + }, + "calls_report_template": { + "directions": { + "all": "Все звонки", + "incoming": "Входящие звонки", + "outgoing": "Исходящие звонки" + }, + "placeholders": { + "users": "Пользователи", + "pipeline": "Доски продаж", + "directions": "Вид звонка", + "duration": "Продолжительность", + "only_missed": "Только пропущенные звонки" + } + } + }, + "hooks": { + "use_get_products_general_report_columns": { + "name": "Имя", + "sold": "Итого продано", + "shipped": "Отгружено", + "open": "Открытые сделки", + "lost": "Проигранные сделки", + "all": "Всего сделок", + "average_products": "Среднее кол-во товаров в сделке", + "average_budget": "Средний чек успешных сделок", + "average_term": "Средний срок сделки (дни)" + }, + "use_calls_history_report_columns": { + "timestamps": "Дата звонка", + "type": "Тип звонка", + "result": "Результат", + "unknown": "Неизвестный номер", + "caller": "Кто звонил", + "callee": "Кому звонили" + }, + "use_get_general_report_columns": { + "result": "Результат", + "all": "Все", + "open": "Открытые", + "expired": "Просроченные", + "completed": "Завершенные", + "won": "Выигранные", + "lost": "Проигранные", + "groups": "Группы", + "users": "Пользователи", + "cards": "Карточки (Лиды)", + "tasks": "Задачи", + "activities": "Активности", + "average_check": "Средний чек", + "average_term": "Средний срок (Дни)", + "switch_on": "Переключатель (Вкл)", + "switch_off": "Переключатель (Выкл)", + "calls": "Звонки", + "total": "Общее количество звонков", + "average": "Среднее количество звонков", + "incoming": "Входящие звонки", + "incoming_average": "Среднее количество входящих звонков", + "outgoing": "Исходящие звонки", + "outgoing_average": "Среднее количество исходящих звонков", + "missed": "Пропущенные звонки", + "min": "мин", + "undefined_client": "Запись без сделки" + }, + "use_get_schedule_report_columns": { + "groups": "Группы", + "users": "Пользователи", + "sold": "Продано", + "total": "Итого записей", + "scheduled": "Назначено", + "confirmed": "Подтверждено", + "completed": "Посетил", + "cancelled": "Отменен" + }, + "use_get_customer_report_columns": { + "name": "Имя", + "sold": "Итого продано", + "products_quantity": "Количество товаров", + "opened": "В процессе (открытые сделки)", + "lost": "Проигранные сделки", + "all": "Всего сделок (открытые и закрытые)", + "average_quantity": "Среднее количество покупок (сделок)", + "average_budget": "Средний бюджет успешных сделок", + "average_duration": "Средний срок успешных сделок (дни)" + }, + "use_get_projects_report_columns": { + "hours": "ч", + "min": "мин", + "users": "Пользователи", + "opened": "Открытые задачи", + "done": "Выполненные задачи", + "overdue": "Просроченные задачи", + "planned": "Запланированное время", + "completion_percent": "Процент выполнения проекта", + "project_name": "Название проекта", + "stage": "Статус проекта" + }, + "use_get_comparative_report_columns": { + "all": "Все", + "open": "Открытые", + "won": "Выигранные", + "lost": "Проигранные", + "week": "Неделя {{number}}", + "quarter": "Квартал {{number}}", + "users": "Пользователи", + "days": { + "monday": "Понедельник", + "tuesday": "Вторник", + "wednesday": "Среда", + "thursday": "Четверг", + "friday": "Пятница", + "saturday": "Суббота", + "sunday": "Воскресенье" + }, + "months": { + "january": "Январь", + "february": "Февраль", + "march": "Март", + "april": "Апрель", + "may": "Май", + "june": "Июнь", + "july": "Июль", + "august": "Август", + "september": "Сентябрь", + "october": "Октябрь", + "november": "Ноябрь", + "december": "Декабрь" + } + } + }, + "pages": { + "reports_page": { + "components": { + "row_title_cell": { + "total": "Всего", + "without_group": "Без группы", + "empty_user": "Пользователь не назначен" + }, + "row_event_cell": { + "to": "звонит" + }, + "reports_navigation_sidebar": { + "total": "Всего", + "export_xlsx": "Экспорт в XLSX", + "export_table": "Экспортировано {{date}} с помощью {{company}}", + "unfold_filters_menu": "Развернуть меню фильтров", + "fold_filters_menu": "Свернуть меню фильтров", + "hide_sidebar": "Скрыть боковую панель", + "show_sidebar": "Показать боковую панель", + "schedules": "Визиты", + "title": { + "universal": "Отчеты", + "project": "Отчет по проектам", + "deal": "Отчет по сделкам" + }, + "general_report": "Общий отчет", + "comparison_of_periods": "Сравнение периодов", + "projects": "Проекты", + "telephony": "Звонки", + "schedule": "Визиты", + "users": "По пользователям", + "rating": "По рейтингу", + "groups": "По группам", + "days": "По дням", + "weeks": "По неделям", + "months": "По месяцам", + "quarters": "По кварталам", + "years": "По годам", + "callsUsers": "По пользователям", + "callsGroups": "По группам", + "callHistory": "Список звонков", + "scheduleClient": "По клиентам", + "scheduleDepartment": "По группам", + "scheduleOwner": "По ответственным", + "schedulePerformer": "По исполнителям", + "customer_reports": "Контакты и компании", + "customerContact": "По контактам", + "customerCompany": "По компаниям", + "customerContactCompany": "Контакты и компании", + "products": "Товары", + "productsCategories": "По категориям", + "productsUsers": "По пользователям" + } + } + }, + "dashboard_page": { + "filter": { + "placeholders": { + "select_users": "Пользователи", + "select_sales_pipeline": "Доски продаж", + "all": "Все", + "all_active": "Все активные", + "open": "Открытые", + "open_active": "Открытые и активные", + "closed": "Закрытые", + "created": "Созданные" + }, + "dashboard_type_tooltip": { + "all": { + "text": "Этот отчет включает все сделки, существовавшие в отчетном периоде, независимо от их активности. В него попадают:", + "list_1": "✓ Сделки, которые были созданы в периоде.", + "list_2": "✓ Сделки, которые были закрыты (выиграны или проиграны) в периоде.", + "list_3": "✓ Сделки, которые существовали ранее и оставались открытыми в периоде.", + "list_4": "✗ Не исключает сделки без активности (без передвижения по этапам воронки продаж)." + }, + "all_active": { + "text": "Этот отчет включает только сделки, в которых хотя бы раз изменялся этап в течение периода. В него попадают:", + "list_1": "✓ Сделки, созданные ранее, но с движением по этапам в периоде.", + "list_2": "✓ Сделки, созданные в периоде, если они перемещались по этапам.", + "list_3": "✓ Сделки, закрытые в периоде, если перед закрытием был хотя бы один переход по этапам.", + "list_4": "✗ Исключает сделки, в которых не было активности (не двигались по этапам)." + }, + "open": { + "text": "Этот отчет включает сделки, которые не были закрыты (не достигли статусов «Выиграно» или «Проиграно») к концу периода. В него попадают:", + "list_1": "✓ Сделки, созданные ранее, которые все еще открытые.", + "list_2": "✓ Сделки, созданные в периоде и не закрытые на конец периода.", + "list_3": "✗ Не учитывает закрытые сделки." + }, + "open_active": { + "text": "Этот отчет включает только незакрытые сделки, где была активность (изменение этапа) в отчетном периоде. В него попадают:", + "list_1": "✓ Открытые сделки, в которых изменялся этап в периоде.", + "list_2": "✓ Новые сделки, если они перемещались по этапам.", + "list_3": "✗ Исключает сделки, которые не закрыты, но не двигались по этапам.", + "list_4": "✗ Исключает закрытые сделки." + }, + "closed": { + "text": "Этот отчет включает только сделки, которые были закрыты (получили статус «Выиграно» или «Проиграно») в периоде. В него попадают:", + "list_1": "✓ Сделки, созданные ранее и закрытые в периоде.", + "list_2": "✓ Сделки, созданные и закрытые в периоде.", + "list_3": "✗ Исключает все открытые сделки." + }, + "created": { + "text": "Этот отчет включает все сделки, которые были созданы в отчетном периоде, независимо от их текущего статуса. В него попадают:", + "list_1": "✓ Сделки, которые были созданы в периоде и остались открытыми.", + "list_2": "✓ Сделки, которые были созданы и закрыты в периоде.", + "list_3": "✗ Не включает сделки, созданные раньше периода." + } + } + }, + "days": { + "one_day": "день", + "several_days": ["день", "дня", "дней"] + }, + "auto_update_select": { + "auto_update": "Автообновление", + "modes": { + "never": "Никогда", + "minute": "1 минута", + "ten_minutes": "10 минут", + "thirty_minutes": "30 минут", + "hour": "1 час" + } + }, + "sales_goal_chart": { + "title_for_sales": "План продаж", + "title": "Цель", + "hint": "План продаж", + "settings_tip": "Настройки", + "plug_text_for_sales": "Настройте план продаж", + "plug_text": "Настройте цель" + }, + "traffic_light_report": { + "title": "Светофор - выполнение плана", + "subtitle": "на сегодняшний день", + "hint": { + "line1": "Отчет 'светофор' — это инструмент, который показывает, как отдел продаж справляется с выполнением плана продаж на текущий день.", + "line2": "Формула расчета выполнения плана продаж на сегодняшний день:", + "line3": "Процент выполнения = (Фактические продажи разделить на План На Текущий День) × 100%, где:", + "list": { + "point1": "Фактические продажи — это сумма, которую вы уже продали на текущий момент.", + "point2": "План на текущий день — это часть общего плана продаж на месяц, рассчитанная на текущий день." + }, + "line4": "Пример расчета:", + "line5": "Допустим, ваш план продаж на месяц составляет 10 000 рублей. Сегодня 10-е число, то есть месяц прошел примерно на 1/3. Ваш план на сегодня:", + "line6": "План На Текущий День = (10000 руб. разделить на 30 дней) × 10 дней = 3333,33 руб.", + "line7": "Вы продали товаров на 3 000 руб.", + "line8": "Ваш процент выполнения плана:", + "line9": "Процент Выполнения = (3000 руб. разделить на 3333,33 руб.) × 100% ≈ 90%", + "line10": "Таким образом, ваш план выполнен на 90% и в отчете 'светофор' вы увидите зеленый цвет, так как выполнение плана идет успешно." + }, + "plug_text": "Настройте план продаж" + }, + "top_sellers": { + "title_for_sales": "Топ-5 достижений", + "title": "Топ-5 достижений", + "subtitle_for_sales": "лидеров продаж", + "hint": "Топ-5 достижений лидеров продаж", + "hint_for_supplier": "Топ-5 достижений лидеров", + "others": "Другие", + "plug_text": "Когда вы начнете использовать {{company}}, здесь появится статистика по Топ 5 лидерам продаж", + "plug_text_for_orders": "Когда вы начнете использовать {{company}}, здесь появится статистика по Топ 5 лидерам пользователей", + "plug_text_for_candidates": "Когда вы начнете использовать {{company}}, здесь появится статистика по Топ 5 лидерам пользователей" + }, + "analytics": { + "total_leads": "Всего лидов", + "total_orders": "Всего заказов", + "total_candidates": "Всего кандидатов", + "new_leads": "Новые лиды", + "new_orders": "Новые заказы", + "new_candidates": "Новые кандидаты", + "won_leads": "Успешные сделки", + "completed_orders": "Выполненные заказы", + "hired_candidates": "Нанятые кандидаты", + "lost_leads": "Потеряно лидов", + "failed_orders": "Провальные заказы", + "rejected_candidates": "Неподходящие кандидаты", + "total_tasks": "Всего задач", + "completed_tasks": "Выполненные задачи", + "expired_tasks": "Просроченные задачи", + "no_tasks": "Лиды без задач", + "cards_no_tasks": "Карточки без задач", + "total_activities": "Всего активностей", + "completed_activities": "Выполненные активности", + "expired_activities": "Просроченные активности", + "no_activities": "Лиды без активностей", + "cards_no_activities": "Карточки без активностей" + }, + "rating": { + "title": "Рейтинг", + "hint": "Рейтинг" + }, + "leads_status_chart": { + "title": "Показатели статуса сделок:", + "title_for_orders": "Показатели статуса заказов:", + "title_for_candidates": "Показатели статуса кандидатов:", + "subtitle": "Открытые, проигранные, выигранные", + "subtitle_for_orders": "Открытые, Провальные, Выполненные", + "subtitle_for_candidates": "Открытые, Неподходящие, Нанятые", + "hint": "Показатели статуса сделок", + "won": "Выигранные", + "completed": "Выполненные", + "hired_candidates": "Нанятые", + "lost": "Проигранные", + "failed": "Провальные", + "rejected_candidates": "Неподходящие", + "opened": "Открытые" + }, + "sales_pipeline_indicators": { + "title": "Конверсия воронки", + "total_sales": "Всего выиграно", + "conversion": "Конверсия в выигрыш", + "average_amount": "Средняя сумма", + "average_term": "Средний срок", + "days": ["день", "дня", "дней"] + }, + "switch": { + "deals_count": "Сделки", + "orders_count": "Количество заказов", + "sales_value": "Сумма", + "orders_value": "Сумма заказов" + } + }, + "goal_settings_page": { + "title": "Настройки цели", + "total": "Всего", + "back_button": "Дашборд", + "users_select": "Пользователи", + "period_type": { + "month": "Месяц", + "quarter": "Квартал" + }, + "change_period_modal": { + "title": "Предупреждение!", + "annotation": "Изменение периода приведет к потере целей, которые вы установили ранее. Вы уверены, что хотите продолжить?", + "approve": "Да", + "cancel": "Нет" + }, + "periods": { + "months": { + "january": "Январь", + "february": "Февраль", + "march": "Март", + "april": "Апрель", + "may": "Май", + "june": "Июнь", + "july": "Июль", + "august": "Август", + "september": "Сентябрь", + "october": "Октябрь", + "november": "Ноябрь", + "december": "Декабрь" + }, + "quarters": { + "quarter1": "Квартал 1", + "quarter2": "Квартал 2", + "quarter3": "Квартал 3", + "quarter4": "Квартал 4" + } + }, + "form_header_amount": "Сумма", + "form_header_quantity": "Количество", + "button_save": "Сохранить" + } + } + } +} diff --git a/frontend/public/locales/ru/module.scheduler.json b/frontend/public/locales/ru/module.scheduler.json new file mode 100644 index 0000000..638d87e --- /dev/null +++ b/frontend/public/locales/ru/module.scheduler.json @@ -0,0 +1,219 @@ +{ + "scheduler": { + "pages": { + "scheduler_board_view_page": { + "create_appointment": "Создать встречу", + "stats_footer": { + "assigned": { + "label": "Назначено", + "hint": "Неподтвержденные визиты" + }, + "confirmed": { + "label": "Подтверждено", + "hint": "Подтвержденные визиты" + }, + "completed": { + "label": "Встреча состоялась", + "hint": "Завершенные визиты" + }, + "not_took_place": { + "label": "Встреча не состоялась", + "hint": "Прошедшие визиты, не отмеченные как состоявшиеся" + }, + "not_scheduled": { + "label": "Без следующей записи", + "hint": "Визиты, для которых нет следующей даты записи" + }, + "newbies": { + "label": "Первый визит", + "hint": "Посетители, пришедшие впервые" + }, + "total": { + "label": "Всего", + "hint": "Общее количество визитов" + } + } + }, + "appointment_card_list_page": { + "visit_date": "Дата встречи" + }, + "scheduler_schedule_view_page": { + "sync": "Синхронизировать", + "report_settings": "Настройки отчета", + "module_settings": "Настройки модуля", + "stats_settings": "Настройки статистики", + "settings": "Настройки", + "report": "Отчет", + "overview": "Календарь", + "responsible": "Ответственный", + "stage": "Статус", + "not_provided": "Не указан", + "statuses": { + "scheduled": "Назначен", + "confirmed": "Подтвержден", + "completed": "Встреча состоялась", + "cancelled": "Отменен" + }, + "created": "Создано", + "visit": "Встреча {{number}}", + "description": "Описание", + "email": "Электронная почта", + "phone": "Телефон", + "price": "Цена", + "quantity": "Количество", + "discount": "Скидка", + "new_event": "Новая встреча", + "create_appointment": "Создать запись", + "tooltips": { + "reports_denied": "У вас нет разрешения на просмотр отчетов. Обратитесь к администратору учетной записи для получения доступа." + }, + "placeholders": { + "search_visits": "Поиск встреч" + }, + "hooks": { + "use_appointments_history_services_columns": { + "name": "Название", + "price": "Цена", + "quantity": "Количество", + "discount": "Скидка", + "amount": "Сумма" + }, + "use_appointments_history_columns": { + "date": "Дата", + "time": "Время", + "performer": "Исполнитель", + "services": "Услуги", + "total": "Итого", + "status": "Статус" + }, + "use_appointment_service_block_columns": { + "discount": "Скидка", + "quantity": "Количество", + "amount": "Сумма" + } + }, + "ui": { + "add_appointment_modal": { + "error": "Не удалось создать встречу. Это может быть вызвано попыткой создать встречу, пересекающуюся с существующим, или дата конца раньше даты начала.", + "visits_history_empty": "История встреч пуста", + "no_planned_visits": "Нет запланированных встреч", + "save_changes": "Сохранить изменения", + "planned_visit_title": "{{date}} с {{startTime}} до {{endTime}}", + "general_information": "Общая информация", + "planned_visits": "Запланированные встречи", + "visits_history": "История встреч", + "title": "Название", + "visit": "Встреча #{{number}}", + "edit_visit": "Редактировать встречу – {{name}}", + "new_visit": "Назначить встречу", + "visit_parameters": "Параметры встречи", + "status": "Статус", + "scheduler": "Планировщик встреч", + "select_users_group": "Выберите группу", + "select_user": "Выберите пользователя", + "description": "Описание", + "from": "С:", + "to": "До:", + "date_and_time": "Дата и время", + "addition_of_services": "Добавление услуг", + "add_service": "Добавить услугу", + "no_services": "Нет услуг", + "warning_title": "Изменения не сохранены", + "warning_annotation": "Вы уверены, что хотите закрыть это окно? Все изменения будут потеряны.", + "close": "Закрыть", + "delete": "Удалить", + "count": "Всего визитов", + "last_visit": "Предыдущий визит", + "completed_count": [ + "Состоялась {{count}} встреча", + "Состоялось {{count}} встречи", + "Состоялось {{count}} встреч" + ], + "placeholders": { + "select_time_period": "Выберите промежуток", + "title": "Название встречи", + "entity_name": "Имя карточки", + "search_services": "Поиск услуг для добавления", + "user": "Пользователь", + "users_group": "Группа", + "appointment_notes": "Примечания к встрече" + }, + "repeating_appointments_block": { + "header": "Повторные встречи", + "hint": "Настройте интервал и количество повторных встреч, если они необходимы. Встречи будут назначены на рабочие дни, указанные в настройках.", + "interval": "Интервал", + "count": "Количество", + "intervals": { + "none": "Не повторять", + "day": "Каждый день", + "week": "Раз в неделю", + "month": "Раз в месяц" + }, + "dates_display": { + "one_visit": "Повторная встреча будет назначена на {{date}} в {{time}}", + "visits_list": "Повторные встречи будут назначены на {{dates}} в {{time}}", + "and": " и ", + "visits_interval_day": "Повторные встречи будут назначены на каждый день с {{from}} по {{to}} в {{time}}", + "visits_interval_week": "Повторные встречи будут назначены раз в неделю с {{from}} по {{to}} в {{time}}", + "visits_interval_month": "Повторные встречи будут назначены раз в месяц с {{from}} по {{to}} в {{time}}" + }, + "list": { + "title": "Повторные встречи", + "hint": "Выберите даты повторных встреч, если они необходимы.", + "dates_select": "Даты повторных встреч", + "add_new_appointment": "Добавить встречу", + "new_appointment": "Новая встреча", + "placeholders": { + "dates_select": "Выберите даты" + } + } + }, + "batch_cancel": { + "cancel_all": "Отменить все", + "warning_title": "Вы действительно хотите отменить все запланированные встречи?", + "warning_annotation": [ + "Будет отменена {{count}} встреча.", + "Будет отменено {{count}} встречи.", + "Будет отменено {{count}} встреч." + ], + "back": "Назад", + "cancel": "Отменить" + }, + "duplicate_warning_modal": { + "move": "Перенести", + "same_time_title": "Встреча уже назначена на это время", + "same_day_title": "Встреча уже назначена на этот день", + "same_time_annotation": "В этом календаре запрещено создание повторных встреч в тот же день. Отредактируйте уже назначенную встречу или измените дату этой.", + "same_day_annotation": "В этом календаре запрещено создание повторных встреч в тот же день. Вы хотите перенести уже назначенную на {{time}} встречу?" + }, + "intersect_warning_modal": { + "title": "Пересечение встреч", + "annotation": "Встреча пересекается по времени с уже запланированной в этом календаре. Выберите другое время или измените ответственного." + } + }, + "stats_settings_drawer": { + "title": "Настройки статистики", + "description": "Отображаемые значения статистики", + "stats": { + "assigned": "Назначено", + "confirmed": "Подтверждено", + "completed": "Встреча состоялась", + "not_took_place": "Встреча не состоялась", + "not_scheduled": "Без следующей записи", + "newbies": "Первый визит", + "total": "Всего" + } + }, + "local_time_warning": { + "local_correction": [ + "Поправка на местное время: {{hours}} час", + "Поправка на местное время: {{hours}} часа", + "Поправка на местное время: {{hours}} часов" + ], + "hint": "Вы находитесь не в часовом поясе, указанном в настройках системы, поэтому рабочее время в календаре и время встреч смещено на указанное количество часов. Если вам кажется, что это ошибка, смените часовой пояс в настройках или обратитесь в поддержку." + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.telephony.json b/frontend/public/locales/ru/module.telephony.json new file mode 100644 index 0000000..228afe3 --- /dev/null +++ b/frontend/public/locales/ru/module.telephony.json @@ -0,0 +1,242 @@ +{ + "telephony": { + "pages": { + "calls_configuring_scenarios_page": { + "title": "Настройка работы АТС в модуле CRM", + "failed_to_reach": "Не дозвонились", + "creates_manually": "Пользователь самостоятельно создает карточку контакта и сделки при входящем звонке", + "creates_manually_hint": "При входящем звонке с неизвестного номера, у пользователя появляется окно входящего звонка. Пользователь может ответить на звонок и если квалифицирует этот звонок как потенциального партнера, двумя кликами может создать во время разговора карточку контакта или сразу карточки контакта и сделки. Этот вариант предпочтительнее чем автоматически создавать карточки, так как звонить вам могут не только потенциальные клиенты, но и множество других компаний. Этот способ поможет избежать создания ненужных карточек контактов и сделок.", + "components": { + "configuring_scenarios_header_controls": { + "cancel": "Отмена", + "save_scenarios": "Сохранить сценарии", + "failed_to_reach": "Не дозвонились" + }, + "entity_scenario_radio_group": { + "automatically_create": "Автоматически создавать", + "contact_or_company": "Контакт или компанию", + "deal": "Сделку", + "select_contact_or_company_first": "Сначала выберите контакт или компанию", + "deal_pipeline": "Воронка продаж", + "select_deal_first": "Сначала выберите сделку", + "placeholders": { + "select_deal": "Выберите сделку", + "select_pipeline": "Выберите воронку" + } + }, + "incoming_calls_block": { + "incoming_calls": "Входящие звонки" + }, + "incoming_known_missing_scenario_block": { + "missed_from_known_number": "С известного номера" + }, + "incoming_unknown_missing_scenario_block": { + "missed_call_from_unknown_number": "Пропущенный с неизвестного номера", + "auto_create": "Автоматически создавать", + "contact_or_company": "Контакт или компанию", + "responsible": "Ответственный", + "select_contact_or_company_first": "Сначала выберите контакт или компанию", + "deal_pipeline": "Воронка продаж", + "select_deal_first": "Сначала выберите сделку", + "placeholders": { + "select_responsible": "Выберите ответственного", + "select_deal": "Выберите сделку", + "select_pipeline": "Выберите воронку" + } + }, + "incoming_unknown_scenario_block": { + "call_from_unknown_number": "С неизвестного номера" + }, + "task_and_activities_scenario_group": { + "task": "Задача", + "select_contact_or_company_first": "Сначала выберите контакт или компанию", + "title": "Автоматически создавать задачу или активность", + "do_not_create": "Не создавать", + "activity": "Активность", + "activity_type": "Тип активности", + "complete": "Выполнить через", + "description": "Описание", + "task_title": "Название задачи", + "minutes": "минут", + "placeholders": { + "activity_description": "Описание активности", + "task_description": "Описание задачи", + "title": "Название" + } + }, + "outgoing_calls_block": { + "outgoing_calls": "Исходящие звонки", + "failed_to_reach": "Не дозвонились" + }, + "outgoing_unanswered_scenario_block": { + "unanswered_outgoing_calls": "Неотвеченные исходящие звонки", + "failed_to_reach": "Не дозвонились", + "create_note": "Создавать заметку в истории карточки контакта/сделки", + "placeholders": { + "note_content": "Описание заметки" + } + }, + "outgoing_unknown_scenario_block": { + "call_to_unknown_number": "На неизвестный номер", + "creates_manually": "Пользователь самостоятельно создает карточку контакта и сделки при исходящем звонке" + } + } + }, + "calls_sip_registrations_page": { + "provider": "Провайдер", + "removed_or_detached": "Удалено или не прикреплено", + "removed_or_detached_hint": "Эта SIP регистрация больше недоступна, так как была удалена или отсоединена на платформе Voximplant. Чтобы продолжить использование этой SIP регистрации, вам нужно снова прикрепить её к вашему приложению или удалить и создать новую. Свяжитесь с поддержкой {{mail}} для получения дополнительной информации.", + "users": "Пользователи", + "users_hint": "Выберите пользователей, которые будут иметь доступ к этой регистрации SIP", + "default_name": "SIP регистрация #{{number}}", + "name": "Название регистрации *", + "title_annotation": "Инструкцию по подключению SIP регистрации вы можете найти в разделе «Интеграции», нажав на кнопку «Установить» в группе «Телефония и АТС».", + "link_to_vx_portal": "Ссылка на портал Voximplant", + "providers": { + "uis": "UIS", + "zadarma": "Zadarma", + "mango_office": "Mango Office", + "beeline": "билайн", + "mts": "МТС", + "mgts": "МГТС", + "tele2": "Tele2", + "megafon": "Мегафон", + "rostelecom": "Ростелеком", + "unknown": "Неизвестный" + }, + "annotation": "При создании SIP регистрации с вашего аккаунта Voximplant будет сразу же списана месячная оплата за нее. Размер уточняйте на портале Voximplant. Создание может занять несколько минут.", + "last_updated": "Последнее обновление: {{lastUpdated}}", + "delete_warning": { + "title": "Вы уверены, что хотите удалить эту SIP регистрацию?", + "annotation": "Это действие нельзя отменить. Вы сможете восстановить SIP регистрацию только создав новую." + }, + "registration_successful": "Регистрация прошла успешно", + "error_annotation": "Убедитесь, что вы ввели правильные учетные данные, или проверьте свой аккаунт Voximplant для получения более подробной информации.", + "empty": "Вы еще не создали ни одной SIP регистрации", + "save_error": "Не удалось сохранить регистрацию. Пожалуйста, убедитесь, что все введенные данные верны.", + "add": "Добавить", + "title": "SIP Регистрации", + "edit_sip_registration": "Редактировать SIP регистрацию", + "add_sip_registration": "Добавить SIP регистрацию", + "proxy": "Прокси *", + "sip_user_name": "Имя SIP пользователя *", + "password": "Пароль", + "outbound_proxy": "Исходящий прокси", + "auth_user": "Пользователь аутентификации", + "auth_user_hint": "Обычно совпадает именем SIP пользователя", + "placeholders": { + "all_users": "Все пользователи", + "name": "Новая SIP регистрация", + "proxy": "sip.provider.org", + "sip_user_name": "user_name", + "password": "********", + "outbound_proxy": "outbound.provider.org", + "auth_user": "auth_user_name" + } + }, + "calls_settings_users_page": { + "empty": "Вы еще не добавили пользователей", + "users": "Пользователи", + "add_user": "Добавить пользователя", + "remove_user": "Удалить пользователя", + "active": "Активен", + "create": "Создать", + "open_sip_settings": "Открыть настройки SIP", + "continue": "Продолжить", + "sip_settings_title": "Настройки SIP – {{userName}}", + "sensitive_warning": "Это чувствительная информация, убедитесь, что не передаёте её третьим лицам. Наведите курсор на пароль, чтобы увидеть его.", + "user_name": "Имя пользователя", + "domain": "Домен", + "password": "Пароль", + "remove_warning_title": "Удалить {{userName}}?", + "remove_warning_annotation": "Это действие нельзя отменить. Вы сможете восстановить пользователя только создав нового.", + "remove": "Удалить" + }, + "calls_settings_account_page": { + "synchronise_with_vx": "Синхронизировать с Voximplant", + "delete_warning_modal": { + "title": "Вы уверены, что хотите удалить номер {{phoneNumber}}?", + "annotation": "Это действие нельзя отменить." + }, + "unknown": "Неизвестно", + "region": "Регион", + "phone_number": "Номер телефона", + "users": "Пользователи", + "state": "Статус активности", + "connect_phone_number": "Подключить", + "disconnect_phone_number": "Отключить", + "delete_phone_number": "Удалить", + "phone_numbers": "Номера телефонов", + "phone_numbers_annotation1": "Подключите все доступные номера телефонов Voximplant к своему аккаунту.", + "phone_numbers_annotation2": "Вы можете добавить активных операторов в колонке пользователей, чтобы они могли получить доступ к подключенному номеру.", + "phone_numbers_annotation3": "ℹ️ При удалении или отсоединении номера непосредственно в приложении Voximplant, не забудьте удалить его из списка номеров ниже или нажмите кнопку синхронизации, которая появится в этом случае.", + "connect_all_available_phone_numbers": "Подключить все доступные номера", + "empty_phone_numbers": "Нет доступных или подключенных номеров телефонов", + "placeholders": { + "all_users": "Все пользователи" + }, + "connect_telephony_title": "Подключите телефонию и получите больше возможностей с {{company}}", + "connect_telephony_warning": "Обратите внимание, нажатие на кнопку автоматически создаст учетную запись Voximplant, связанную с {{company}}. Не регистрируйтесь на портале Voximplant самостоятельно, так как такая учетная запись не будет связана с {{company}}.", + "connect": "Подключить телефонию", + "account": "Аккаунт", + "voximplant_balance": "Баланс Voximplant (Скоро...)", + "recharge": "Пополнить", + "subscription_fee": "Абонентская плата (Скоро...)", + "available_numbers": "Доступных номеров: {{amount}}", + "account_is_not_approved": "Аккаунт не подтвержден", + "not_approved_annotation": "Без подтверждения аккаунта вы не сможете пользоваться подключенными номерами.", + "approve": "Подтвердить" + } + }, + "components": { + "telephony_button": { + "telephony": "Телефония", + "not_connected": "Подключитесь к телефонии, чтобы активировать функцию вызова." + }, + "telephony_modal": { + "connect_to_transfer": "Вы должны сначала подключиться к вызову, чтобы перенаправить его", + "no_operators_to_transfer_call_to": "Нет доступных операторов для перенаправления вызова", + "coming_soon": "Скоро появится...", + "unknown_number": "Неизвестный номер", + "amwork_calls": "Звонки {{company}}", + "active_call_control": { + "cancel": "Отменить", + "transfer": "Перенаправить", + "add_to_call": "Добавить к вызову", + "end_call": "Завершить вызов", + "calling": "вызов", + "mute": "Отключить микрофон", + "unmute": "Включить микрофон" + }, + "incoming_call_control": { + "transfer": "Перенаправить", + "add_to_call": "Добавить", + "accept": "Принять", + "incoming_call": "входящий вызов", + "mute": "Отключить микрофон", + "unmute": "Включить микрофон", + "end_call": "Завершить вызов", + "decline": "Отклонить" + }, + "outgoing_call_initializer": { + "outgoing_number": "Ваш исходящий номер", + "no_available_phone_numbers": "Нет доступных номеров", + "keys": "Клавиши", + "recent": "Недавние", + "outgoing": "Исходящий вызов", + "incoming": "Входящий вызов", + "yesterday": "Вчера", + "today": "Сегодня", + "no_calls": "Нет звонков" + }, + "call_control_template": { + "call_from": "Вызов с номера: {{number}}", + "create": "Создать", + "placeholders": { + "select_card": "Выберите карточку" + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/module.tutorial.json b/frontend/public/locales/ru/module.tutorial.json new file mode 100644 index 0000000..df581c7 --- /dev/null +++ b/frontend/public/locales/ru/module.tutorial.json @@ -0,0 +1,55 @@ +{ + "tutorial": { + "tutorial_drawer": { + "title": "База знаний", + "empty": "Здесь еще ничего нет.", + "create_tutorial_group_block": { + "create_group": "Создать группу", + "save": "Сохранить", + "cancel": "Отмена" + }, + "create_tutorial_item_block": { + "create_link": "Создать ссылку", + "save": "Сохранить", + "cancel": "Отмена" + }, + "tutorial_edit_group_item_form": { + "placeholders": { + "name": "Имя", + "link": "Ссылка", + "all": "Все" + } + }, + "tutorial_edit_group_items_forms": { + "name": "Имя", + "link": "Ссылка", + "users": "Пользователи", + "products": "Продукты", + "products_hint": "Выберите продукты, в которых будет отображаться ссылка. Например, если выбраны задачи – ссылка будет видна только в секции задач." + }, + "tutorial_group_name_block": { + "placeholders": { + "group_name": "Название группы" + } + }, + "hooks": { + "use_get_tutorial_products_options": { + "product_types": { + "builder": "Конструктор", + "task": "Задачи", + "mail": "Почта", + "multi_messenger": "Мульти-Мессенджер", + "settings": "Настройки" + } + }, + "use_get_tutorial_products_groups": { + "groups": { + "entity_type": "Модули", + "products_section": "Продукты", + "scheduler": "Планировщики" + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/page.board-settings.json b/frontend/public/locales/ru/page.board-settings.json new file mode 100644 index 0000000..894c769 --- /dev/null +++ b/frontend/public/locales/ru/page.board-settings.json @@ -0,0 +1,33 @@ +{ + "board_settings": { + "ui": { + "board_settings_header": { + "header": "Настройки доски", + "delete_board": "Удалить доску", + "title": "Внимание!", + "annotation": "Удаление доски приведет к безвозвратному удалению всех карточек на ней. Вы уверены, что хотите продолжить?", + "save": "Сохранить", + "cancel": "Отмена", + "leave": "Выйти из настроек" + }, + "stage_name_hint": "Максимальная длина статуса {{length}} символов" + }, + "entity_type_board_settings_page": { + "automation_new": "Автоматизация 2.0", + "bpmn_2_0": "BPMN 2.0", + "bpmn_2_0_soon": "BPMN 2.0 (Скоро...)", + "warning_title": "Вы не можете удалить последнюю доску", + "warning_annotation": "Если вы хотите удалить эту доску, сначала создайте новую." + }, + "task_board_settings_page": { + "warning_title": "Вы не можете удалить системную доску", + "warning_annotation": "Эта доска является системной и не может быть удалена." + }, + "delete_stage_warning_modal": { + "warning_title": "Вы действительно хотите удалить этап?", + "warning_annotation": "Выберите этап, на который вы хотите переместить существующие элементы.", + "delete": "Удалить", + "placeholder": "Этап для перемещения" + } + } +} diff --git a/frontend/public/locales/ru/page.dashboard.json b/frontend/public/locales/ru/page.dashboard.json new file mode 100644 index 0000000..14160fb --- /dev/null +++ b/frontend/public/locales/ru/page.dashboard.json @@ -0,0 +1,18 @@ +{ + "dashboard_page": { + "top_sellers": { + "title": "Рейтинг", + "hint": "Lorem ipsum dolor sim amet, consectetur adipiscing elit." + }, + "analytics": { + "total_entities": "Всего", + "won_entities": "Выиграно", + "lost_entities": "Потеряно", + "new_entities": "Новые", + "total_tasks": "Всего задач", + "completed_tasks": "Выполненные задачи", + "expired_tasks": "Невыполненные задачи", + "no_tasks": "Нет задач" + } + } +} diff --git a/frontend/public/locales/ru/page.login.json b/frontend/public/locales/ru/page.login.json new file mode 100644 index 0000000..1ace444 --- /dev/null +++ b/frontend/public/locales/ru/page.login.json @@ -0,0 +1,24 @@ +{ + "login": { + "login_form": { + "title": "Вход в аккаунт", + "login": "Войти", + "invalid": "Неверные учетные данные", + "or": "Или", + "caption": "Ещё нет аккаунта?", + "sign_up": "Зарегистрироваться", + "forgot_your_password": "Забыли пароль?", + "placeholders": { + "login": "E-mail", + "password": "Пароль" + }, + "invalid_email_error": "Недопустимый формат электронной почты", + "invalid_password_error": "Введите пароль" + }, + "left_block": { + "title": "Добро пожаловать в {{company}}", + "annotation": "Отличной работы, с удовольствием!", + "image_alt": "Войти в личный кабинет {{company}}" + } + } +} diff --git a/frontend/public/locales/ru/page.settings.json b/frontend/public/locales/ru/page.settings.json new file mode 100644 index 0000000..b27c976 --- /dev/null +++ b/frontend/public/locales/ru/page.settings.json @@ -0,0 +1,902 @@ +{ + "settings_page": { + "app_sumo_tiers_data": { + "feature_1": "Неограниченные функциональные продукты в конструкторе", + "feature_2": "Неограниченные воронки продаж или доски", + "feature_3": "Неограниченные настраиваемые поля", + "feature_4": "Автоматизация", + "feature_5": "Пожизненный доступ", + "storage": "{{storage}} Гб хранилища для каждого пользователя" + }, + "templates": { + "document_creation_fields_page": { + "ui": { + "system_section": { + "name": "Системные поля", + "current_date": "Текущая дата", + "document_number": "Номер документа" + }, + "entity_type_section": { + "name": "Название", + "owner": "Ответственный" + }, + "selector": { + "words": "Слова" + } + } + }, + "document_templates_page": { + "order_fields": { + "order": "Заказ", + "order_items": "Позиции заказа", + "specific_order_item": "Позиция заказа", + "order_number": "Номер заказа", + "order_amount": "Сумма заказа", + "order_currency": "Валюта заказа", + "order_item": { + "number": "Номер позиции", + "name": "Название товара или услуги", + "price": "Цена", + "currency": "Валюта", + "discount": "Скидка", + "tax": "Налог", + "quantity": "Количество", + "amount": "Сумма" + }, + "text_block_1": "Заполнить документ данными о всех товарах и услугах заказа можно в виде текста или таблицы. Также можно добавить данные только по определенной позиции заказа.", + "text_block_2": "Для заполнения документа данными о всех позициях заказа в виде текста необходимо сформировать в документе требуемый текст, а для вставки данных по позициям заказа необходимо использовать специальные коды, указанные выше.", + "text_block_3": "ВАЖНО: В начале блока с перечислением позиций заказа необходимо вставить специальный код {#order.products}. В конце блока необходимо вставить специальный символ {/}. Текст блока между символами {#order.products} и {/} будет повторен в созданном документе столько раз, сколько существует позиций в выбранном заказе.", + "text_example": "Пример текста", + "text_block_4": "{name} в количестве {quantity} по цене {price}", + "text_block_5": "Для заполнения документа данными о всех позициях заказа в виде таблицы необходимо создать таблицу в шаблоне. При необходимости в заголовке таблицы можно указать названия колонок. В таблице, помимо заголовка, должна содержаться строка с необходимыми полями для заполнения.", + "text_block_6": "ВАЖНО: В первой ячейке строки должен находится код {#order.products} в самом начале ячейки; в последней ячейке должен находится код {/} в самом конце ячейки. Эти коды нужны для генерации таблицы и не будут отображаться в итоговом документе.", + "table_example": "Пример таблицы", + "text_block_7": "Для заполнения документа данными по определенной позиции заказа необходимо использовать специальные символы.", + "text_block_8": "Символ [0] указывает какой номер позиции заказа будет выбран для вставки данных в документ. Номера позиций начинаются с 0 для первой позиции заказа." + }, + "no_document_templates": "У вас пока нет шаблонов документов.", + "can_create_new_there": "Чтобы добавить шаблон, нажмите кнопку «Добавить шаблон документа».", + "add_document_template": "Добавить шаблон документа", + "ui": { + "document_template_item": { + "available_in_sections": "Доступен в модулях", + "creator": "Создатель", + "access_rights": "Права доступа", + "warning_title": "Вы уверены, что хотите удалить этот шаблон?", + "warning_annotation": "Это действие нельзя отменить.", + "placeholders": { + "select_sections": "Выберите модули" + } + } + } + } + }, + "billing_page": { + "trial_in_progress": "Ваш пробный период активирован!", + "trial_over": "Ваш пробный период подошел к концу!", + "subscription_over": "Ваша подписка подошла к концу!", + "request_mywork_billing_form_modal": { + "error": "Произошла ошибка при отправке вашего запроса. Убедитесь, что вы ввели правильную информацию, и попробуйте снова.", + "send_request": "Оставить заявку", + "header": "Оставьте заявку и мы свяжемся с вами", + "full_name": "Полное имя *", + "phone": "Телефон *", + "email": "Почта *", + "number_of_users": "Количество пользователей", + "comment": "Комментарий", + "placeholders": { + "full_name": "Иван Иванов", + "comment": "Дополнительная информация" + } + }, + "for_all": "За всех пользователей", + "request_billing_modal": { + "send": "Отправить", + "title": "Тариф \"{{planName}}\" – Форма обратной связи", + "name": "Имя *", + "phone": "Телефон *", + "email": "Эл. почта *", + "number_of_users": "Количество пользователей *", + "comment": "Комментарий", + "placeholders": { + "additional_info": "Дополнительная информация" + } + }, + "request_button": "Оставить заявку", + "request_annotation": "Вы выбрали тариф {{planName}}, оставьте заявку и мы скоро свяжемся с вами.", + "free_trial_duration": "Бесплатный период – 14 дней", + "started_in": "Начало:", + "expires_in": "Окончание:", + "users_limit": "Лимит пользователей:", + "renew": "Продлить подписку", + "upgrade": "Обновить", + "open_stripe_portal": "Открыть портал Stripe", + "manage_your_subscription": "💸 Управляйте своей подпиской на портале клиентов Stripe", + "number_of_users": "Количество пользователей", + "finish_order": "Завершить заказ", + "save_percentage": "Сэкономить {{percentage}}", + "per_user_month": "за пользователя / месяц", + "select_this_plan": "Выбрать тариф", + "selected": "Выбрано", + "1_month": "1 месяц", + "12_month": "12 месяцев", + "save_up_to": "Сэкономьте до {{percent}}%", + "total": "Итого: {{amount}}", + "payment_success": "Оплата прошла успешно! Вы получите подтверждение по электронной почте.", + "set_free_plan_warning_modal": { + "title": "Внимание!", + "annotation": { + "text1": "Лимит пользователей вашего тарифного плана превышен. Дополнительные учетные записи будут автоматически удалены в соответствии с условиями тарифа. Вы можете вручную удалить необходимые учетные записи", + "link": "здесь", + "text2": "Это не затронет все остальные данные в вашей системе." + }, + "approve": "Подтвердить", + "cancel": "Отменить" + }, + "lifetime_promo_plans": { + "discount": "Скидка {{percent}}%", + "lifetime_subscription": "на тариф", + "black_text": "забрать навсегда ", + "until": "до", + "business_plan": "Бизнес", + "business_plan_description": "Все необходимое для уверенного управления продажами и проектами.", + "advanced_plan": "Продвинутый", + "advanced_plan_description": "Полная функциональность с автоматизацией и расширенными настройками.", + "price_rise_counter": { + "months": ["месяц", "месяца", "месяцев"], + "days": ["день", "дня", "дней"], + "hours": ["час", "часа", "часов"], + "minutes": ["минуту", "минуты", "минут"], + "sale_ends": "Скидка сгорит через:" + }, + "lifetime_deal_plan_block": { + "lifetime_subscription": "Тариф Забрать Навсегда" + }, + "annual_deal_pricing_block": { + "annual_subscription": "1 год подписки" + }, + "monthly_deal_pricing_block": { + "monthly_subscription": "1 месяц подписки", + "number_of_users": "Количество пользователей", + "per_user": "/мес за пользователя", + "minimum_period": "минимальный период подписки — 6 месяцев", + "buy": "Купить" + }, + "pricing_block_with_users_selector": { + "users": ["пользователь", "пользователя", "пользователей"], + "lifetime_postfix": " навсегда", + "packages": "Пакеты пользователей", + "per_user": "/мес за пользователя", + "price_description": "пользователь / месяц, счет выставляется ежегодно", + "fix_price": "Fix - цена", + "big_savings": "Беспрецедентная экономия", + "savings": "Экономия: ", + "buy": "Купить" + } + } + }, + "edit_groups_page": { + "add_new_group": "Добавить новую группу", + "subgroup": "Подгруппа", + "delete_warning_title": "Вы уверены, что хотите удалить {{name}}?", + "delete_warning_annotation1": "Все участники этой группы", + "delete_warning_annotation2": "(а также подгрупп, если таковые имеются)", + "delete_warning_annotation3": "могут быть перемещены в другую группу или подгруппу (или в группу «Без группы», если ничего не будет выбрано).", + "add_subgroup": "Добавить подгруппу", + "save": "Сохранить", + "cancel": "Отменить", + "delete": "Удалить", + "working_time": "Рабочее время", + "to": "до", + "placeholders": { + "group": "Введите имя", + "select_group": "Выберите группу", + "select_subgroup": "Выберите подгруппу", + "new_subgroup": "Новая подгруппа" + } + }, + "edit_user_page": { + "save": "Сохранить", + "save_and_add": "Сохранить и добавить", + "first_name": "Имя *", + "first_name_hint": "Максимальная длина имени {{length}} символов", + "last_name": "Фамилия *", + "last_name_hint": "Максимальная длина фамилии {{length}} символов", + "email": "Почта *", + "phone_number": "Телефон", + "password": "Пароль *", + "group": "Группа", + "owner": "Владелец", + "owner_hint": "Владелец - это супер-администратор и владелец учетной записи. Владелец не может быть удален, вы не можете отнять у него права или запретить что-либо. Только один пользователь может быть владельцем учетной записи.", + "admin": "Администратор", + "admin_hint": "Администратор имеет неограниченные права на управление и настройку учетной записи.", + "email_error": "Этот адрес электронной почты уже используется в другой учетной записи {{company}}. Пожалуйста, используйте другой адрес для создания пользователя.", + "unknown_error": "Неизвестная ошибка произошла во время редактирования пользователя. Проверьте правильность заполнения полей и попробуйте еще раз.", + "position": "Должность", + "visible_users": "Видимость пользователей", + "visible_users_hint": "Пользователи, которых текущий пользователь сможет выбрать из списка", + "working_time": "Рабочее время", + "to": "до", + "group_working_time": "Рабочее время группы", + "group_working_time_hint": "Для пользователя будет установлено рабочее время группы, в которой он состоит. Если у группы не настроено рабочее время, будет использовано рабочее время компании.", + "user_calendar_title": "Расписание для онлайн-записи", + "user_calendar_hint": "Это расписание используется в формах онлайн-записи для определения доступных интервалов рабочего времени. Расписание может отсутствовать, тогда для определения рабочего времени используется расписание календаря.", + "delete_user_calendar": "Удалить", + "add_user_calendar": "Создать расписание", + "time_buffer_before": "Перерыв перед встречей", + "time_buffer_after": "Перерыв после встречи", + "appointment_limit": "Лимит встреч в день", + "schedule": "Расписание", + "no_duration": "Нет", + "minutes": "минут", + "placeholders": { + "all_users": "Все пользователи", + "manager": "Менеджер", + "password": "Придумайте пароль", + "new_password": "Новый пароль", + "group": "Выберите группу" + }, + "ui": { + "menu_accesses_item": { + "title": "Доступ к меню", + "denied": "Запрещено", + "responsible": "Ответственный", + "allowed": "Разрешено" + }, + "object_permissions_list": { + "tasks": "Задачи", + "activities": "Активности" + }, + "object_permissions_item": { + "hint": "Вы можете настроить разрешения для пользователя. Вы можете настроить разрешения пользователя для создания, просмотра, редактирования и удаления объектов в разделе {{title}}.", + "create": "Создание", + "view": "Просмотр", + "edit": "Изменение", + "delete": "Удаление", + "report": "Отчеты", + "dashboard": "Дашборд", + "denied": "Запрещено", + "responsible": "Ответственный", + "subdepartment": "Подгруппа", + "department": "Группа", + "allowed": "Разрешено" + }, + "products_permissions_item": { + "warehouses_title": "Доступность складов", + "warehouses_hint": "Выберите склады, к которым пользователь имеет доступ, может формировать заказы, просматривать товары и отгрузки.", + "hint": "Вы можете настроить разрешения для пользователя.", + "create_product": "Создание товаров", + "view_product": "Просмотр товаров", + "edit_product": "Изменение товаров", + "create_order": "Создание заказов", + "shipment": "Отгрузки", + "delete": "Удаление", + "denied": "Запрещено", + "allowed": "Разрешено", + "placeholders": { + "all_warehouses": "Все склады" + } + } + } + }, + "general_settings_page": { + "enable": "Включить", + "contact_duplicates_hint": "Включите опцию, чтобы предупреждать пользователей при попытке создания нового контакта или компании с уже существующими номерами телефонов или адресами электронной почты.", + "contact_duplicates": "Предупреждать о дубликатах контактов", + "date_format": "Формат даты", + "auto": "Автоматически", + "phone_format": "Формат телефона", + "international": "Международный", + "free": "Свободный", + "company": "Компания", + "domain": "Домен", + "upload_logo": "Загрузить логотип", + "delete_logo": "Удалить логотип", + "logo_caption": "Предпочтительный размер для логотипа 111px на 22px", + "language": "Язык", + "time_zone": "Часовой пояс", + "working_days": "Рабочие дни", + "start_of_week": "Начало недели", + "Monday": "Понедельник", + "Tuesday": "Вторник", + "Wednesday": "Среда", + "Thursday": "Четверг", + "Friday": "Пятница", + "Saturday": "Суббота", + "Sunday": "Воскресенье", + "currency": "Валюта", + "working_time": "Рабочее время", + "to": "до", + "number_format": "Числовой формат", + "currency_select": "Выбор валюты", + "currencies": { + "USD": "Доллар США, USD", + "EUR": "Евро, EUR", + "GBP": "Фунт стерлингов, GBP", + "JPY": "Японская йена, JPY", + "CNY": "Китайский юань, CNY", + "INR": "Индийская рупия, INR", + "RUB": "Российский рубль, RUB", + "MXN": "Мексиканское песо, MXN", + "BRL": "Бразильский реал, BRL", + "ZAR": "Южноафриканский рэнд, ZAR", + "AUD": "Австралийский доллар, AUD", + "CAD": "Канадский доллар, CAD", + "AED": "Дирхам ОАЭ, AED", + "CHF": "Швейцарский франк, CHF", + "TRY": "Турецкая лира, TRY", + "UAH": "Украинская гривна, UAH", + "KRW": "Южнокорейская вона, KRW", + "NZD": "Новозеландский доллар, NZD", + "NOK": "Норвежская крона, NOK", + "SEK": "Шведская крона, SEK", + "DKK": "Датская крона, DKK", + "PLN": "Польский злотый, PLN", + "CZK": "Чешская крона, CZK", + "HUF": "Венгерский форинт, HUF", + "IDR": "Индонезийская рупия, IDR", + "ILS": "Новый израильский шекель, ILS", + "MYR": "Малайзийский ринггит, MYR", + "PHP": "Филиппинское песо, PHP", + "SGD": "Сингапурский доллар, SGD", + "THB": "Тайский бат, THB", + "KZT": "Казахский тенге, KZT", + "CLP": "Чилийское песо, CLP", + "CRC": "Костариканский колон, CRC", + "COP": "Колумбийское песо, COP", + "BOB": "Боливийское боливиано, BOB", + "HKD": "Гонконгский доллар, HKD", + "SAR": "Саудовский риал, SAR", + "VND": "Вьетнамский донг, VND", + "EGP": "Египетский фунт, EGP", + "KWD": "Кувейтский динар, KWD", + "PKR": "Пакистанская рупия, PKR", + "LKR": "Шри-Ланкийская рупия, LKR", + "BDT": "Бангладешская така, BDT", + "NGN": "Нигерийская найра, NGN", + "GHS": "Ганский седи, GHS", + "TWD": "Новый тайваньский доллар, TWD", + "MAD": "Марокканский дирхам, MAD", + "ARS": "Аргентинское песо, ARS", + "PEN": "Новый перуанский сол, PEN", + "UYU": "Уругвайское песо, UYU", + "BGN": "Болгарский лев, BGN", + "RON": "Румынский лей, RON", + "LBP": "Ливанский фунт, LBP" + } + }, + "integrations_page": { + "calendars_and_tasks": "Календари и задачи", + "telephony_integration_guide_international": { + "modal_title": "Интеграция телефонии Voximplant", + "continue": "Продолжить", + "title": "Инструкции по интеграции телефонии Voximplant", + "step1": { + "title": "Шаг 1 – Создание учетной записи", + "item1": "Чтобы подключить интеграцию телефонии Voximplant, перейдите в \"Настройки\" → \"Звонки\" → вкладка \"Учетная запись\" и нажмите кнопку \"Подключить телефонию\".", + "item2": "Информация о созданной учетной записи Voximplant появится на странице. Чтобы завершить интеграцию, необходимо подтвердить учетную запись, нажав ссылку \"Подтвердить\" в верхней части экрана." + }, + "step2": { + "title": "Шаг 2 – Пополнение баланса", + "annotation": "После открытия портала Voximplant необходимо пополнить баланс учетной записи. Рекомендуется добавить не менее $10. Это активирует вашу учетную запись, и средства будут использованы для покупки телефонных номеров и оплаты минут звонков.", + "operator_site_and_billing": "Сайт оператора и биллинг" + }, + "step3": { + "title": "Шаг 3 – Покупка номеров и начало работы", + "annotation1": "После завершения активации учетной записи вам нужно предоставить службе поддержки {{companyName}} ({{mail}}) следующую информацию, чтобы мы могли инициализировать вашу телефонию и подготовить ее к работе:", + "item1": "Город и страна для выбора номера", + "item2": "Рабочие часы: какие дни недели и время считаются рабочими, ваш часовой пояс", + "item3": "Приветственный текст в начале звонка в рабочие часы", + "item4": "Текст сообщения для звонков вне рабочее время", + "item5": "Голос для сообщения – мужской или женский", + "item6": "Необходимое количество телефонных номеров", + "annotation2": "После получения этой информации мы обработаем ваш запрос как можно быстрее." + }, + "step4": { + "title": "Шаг 4 – Подключение номеров в {{companyName}}", + "annotation1": "После завершения активации учетной записи и покупки необходимых номеров вы увидите их на вкладке \"Настройки\" → \"Звонки\" → \"Аккаунт\". Чтобы начать работу, вам нужно нажать кнопку \"Подключить\" рядом с нужными номерами и выбрать пользователей, которые будут иметь доступ к этим номерам.", + "annotation2": "Готово! Ваша телефония настроена и готова к использованию." + }, + "step5": { + "title": "Шаг 5 – Настройки и дополнительная информация", + "annotation1": "На вкладке \"Настройки\" → \"Звонки\" → \"Пользователи\" вы можете подключать и отключать пользователей системы к телефонии и просматривать данные SIP. На вкладке \"Конфигурирование\" можно настроить сценарии интеграции телефонии с модулем CRM. На вкладке \"SIP регистрации\" можно добавлять и управлять SIP регистрациями АТС/ВАТС.", + "annotation2": "Обратите внимание, что при активном использовании телефонии рекомендуется периодически обновлять вкладку браузера, так как неактивные/устаревшие вкладки (открытые более 3 дней назад без взаимодействия) могут привести к пропуску некоторых звонков.", + "annotation3": "Также необходимо предоставить браузеру доступ к микрофону для совершения исходящих звонков.", + "grant_chrome_access": "Предоставление доступа в Google Chrome", + "grant_mozilla_access": "Предоставление доступа в Mozilla Firefox", + "grant_safari_access": "Предоставление доступа в Safari", + "annotation4": "Как правило, браузер автоматически запрашивает необходимые разрешения при попытке совершить исходящий звонок." + } + }, + "providers_sip_registration_items_list": { + "sip_registration_guide_modal": { + "modal_title": "Инструкция по подключению SIP регистрации", + "continue": "Продолжить", + "step1": { + "title": "Шаг 1 – Сбор необходимой информации", + "annotation1": "Прежде чем начать подключать SIP регистрацию убедитесь, что в вашем аккаунте уже есть активная интеграция Voximplant телефонии. Если она еще не подключена, следуйте по инструкции ниже:", + "annotation2": "Для подключения SIP регистрации вам необходимо знать следующую SIP информацию о вашей АТС/ВАТС:", + "item1": "Прокси *", + "item2": "Имя SIP пользователя *", + "item3": "Пароль", + "item4": "Исходящий прокси", + "item5": "Пользователь аутентификации", + "annotation3": "Ее можно найти в личном кабинете вашей ВАТС или запросить напрямую у оператора." + }, + "step2": { + "title": "Шаг 2 – Подключение регистрации", + "item1": "Нажмите на кнопку «Продолжить» в самом низу этого окна или перейдите во вкладку «Настройки» → «Звонки» → «SIP регистрации» и нажмите кнопку «Добавить SIP регистрацию».", + "item2": "Введите все необходимые данные и нажмите «Добавить».", + "item3": "Учтите, что сразу после создания регистрации с вашего Voximplant аккаунта спишется небольшая сумма. Далее будет происходить такое же списание за каждый месяц использования регистрации. Подробную информации мы можете найти по ссылке ниже во вкладке «Цены» → «Функции» → «SIP регистрации». Ссылка появится, только если у вас уже создан Voximplant аккаунт.", + "voximplant_billing_rates": "Стоимость услуг Voximplant", + "item4": "После успешного подключения вы увидите вашу интеграцию. Ее можно отредактировать, просто нажав на нее. Отдельная плата за редактирование не взимается. ", + "item5": "Для удаления SIP регистрации нажмите на кнопку «Корзина» в самом конце блока SIP регистрации во вкладке «SIP Регистрация».", + "annotation": "Для решения других вопросов, связанных с SIP регистрациями, свяжитесь с нами по адресу {{mail}}." + } + }, + "another_pbx": "Подключите другую АТС/ВАТС", + "beeline": "Подключите АТС/ВАТС билайн", + "mts": "Подключите АТС/ВАТС от МТС", + "mgts": "Подключите АТС от МГТС", + "tele2": "Подключите корпоративную АТС Tele2", + "megafon": "Подключите АТС/ВАТС от МегаФон", + "rostelecom": "Подключите офисную АТС/ВАТС Ростелеком", + "mango_office": "Подключите ВАТС Mango Office", + "uis": "Подключите ВАТС UIS", + "zadarma": "Подключите ВАТС Zadarma" + }, + "telephony_and_pbx": "Телефония и АТС", + "integrations": "Интеграции", + "crm": "CRM", + "process_automation": "Автоматизация процессов", + "site_forms": "Формы на сайт", + "messenger": "Социальные сети и мессенджеры", + "continue": "Продолжить", + "save": "Сохранить", + "manage_connected_accounts": "Управляйте подключенными аккаунтами", + "ui": { + "google_calendar": { + "google_calendar_manage_modal": { + "title": "{{company}} Интеграции Google Calendar" + }, + "google_calendar_connect_modal": { + "do_not_select": "Не выбирать", + "integration_name_readonly_hint": "Вы не можете изменить имя этой интеграции после ее создания.", + "save": "Сохранить", + "connect_calendar": "Подключить календарь", + "finish_integration_annotation": "Завершите настройку интеграции, заполнив все обязательные поля.", + "new_integration": "Новая интеграция #{{number}}", + "integration_name": "Название интеграции *", + "calendar": "Google Календарь *", + "calendar_hint": "Выберите конкретный календарь из вашего аккаунта Google Calendar", + "task_board": "Доска задач *", + "schedule": "Календарь визитов {{company}} *", + "linked_task_boards_annotation": "Вы можете добавить дополнительные доски задач для синхронизации с выбранным календарем. Задачи с этих досок будут синхронизированы с Google Calendar.", + "linked_schedules_annotation": "Вы можете добавить дополнительные календари визитов для синхронизации с выбранным календарем. Встречи из этих календарей будут синхронизированы с Google Calendar.", + "select_all": "Выбрать все", + "sync_events": "Синхронизировать события начиная с сегодняшнего дня", + "additional_task_boards": "Дополнительные доски задач", + "additional_schedules": "Дополнительные календари визитов", + "responsible_user": "Ответственный пользователь *", + "error_message": "Не удалось обработать код Google Calendar!", + "title": "Подключение к Google Calendar", + "feature": "Вы сможете выполнять двустороннюю синхронизацию ваших задач и встреч с Google Calendar", + "annotation": "Авторизуйте доступ к вашему аккаунту Google Calendar.", + "placeholders": { + "integration_name": "Интеграция Google Calendar", + "schedules": "Выберите расписания", + "task_boards": "Выберите доски задач" + } + }, + "google_calendar_modal_template": { + "title": "{{company}} Интеграция Google Calendar" + }, + "google_calendar_item": { + "description": "Синхронизация ваших  встреч и задач" + } + }, + "common": { + "form": { + "account_activity": "Активность аккаунта", + "account_activity_annotation": "Вы можете активировать или приостановить ваш аккаунт", + "available": "Выберите, для кого доступен мессенджер *", + "all": "Все", + "users": "Пользователи", + "on": "ВКЛ", + "no_create": "Не создавать", + "messenger_leads_title": "Настройте параметры создания карточки", + "messenger_leads_annotation": "Настройте параметры ниже, чтобы при входящем сообщении от нового контакта, карточка создавалась автоматически", + "create_entities": "Создавать карточку при входящем сообщении", + "create_contact": "Модуль для контакта/компании", + "create_lead": "Модуль для карточки сделки", + "lead_stage": "Статус карточки сделки", + "lead_name": "Название карточки сделки", + "lead_owner": "Ответственный за карточку *", + "check_duplicates": "Исключать дубликаты", + "check_active_lead": "Создавать новую карточку, если текущая закрыта", + "placeholders": { + "select_module": "Выберите модуль", + "select_users": "Выберите пользователей", + "select_user": "Выберите пользователя", + "lead_name": "Введите название" + } + }, + "modals": { + "changes_not_saved_warning_modal": { + "title": "Вы уверены, что хотите закрыть это окно?", + "annotation": "Все изменения будут потеряны.", + "close": "Закрыть" + }, + "integration_account_item": { + "delete_warning_title": "Вы уверены, что хотите удалить аккаунт {{title}}?", + "delete_warning_annotation": "Все чаты и сообщения, относящиеся к данному коммуникационному каналу, будут удалены. Это действие нельзя будет отменить.", + "active": "Активен", + "active_hint": "Ваш аккаунт активен. Вы можете приостановить его в настройках провайдера, нажав на значок карандаша.", + "inactive": "Неактивен", + "inactive_hint": "Ваш аккаунт приостановлен. Вы можете активировать его в настройках провайдера, нажав на значок карандаша.", + "deleted": "Удален", + "deleted_hint": "Ваш аккаунт удален.", + "draft": "Черновик", + "draft_hint": "Ваш аккаунт еще не подключен." + }, + "integration_accounts_list": { + "add_account": "Добавить аккаунт" + } + } + }, + "integration_item": { + "install": "Установить", + "manage": "Управлять" + }, + "messages_per_day_select": { + "label": "Дневной лимит сообщений, отправляемых автоматизацией *", + "annotation": "сообщений в день", + "hint": "Максимальное количество сообщений в день, которые могут быть отправлены с помощью автоматизации через этот аккаунт. Это ограничение необходимо, чтобы аккаунт не был заблокирован мессенджером за массовую рассылку." + }, + "salesforce": { + "salesforce_item": { + "description": "Одно рабочее пространство для каждой команды" + }, + "salesforce_modal": { + "title": "Интеграция с Salesforce", + "connected": "Подключено", + "not_connected": "Не подключено", + "disconnect": "Отключить", + "add_integration": "Добавить интеграцию", + "my_domain_name": "Домен", + "app_key": "Ключ пользователя приложения", + "app_secret": "Секретный ключ пользователя приложения", + "connect": "Подключить", + "caption": "Используйте Salesforce для работы с клиентами, а {{company}} - для управления остальными бизнес-процессами. Управляйте проектами в {{company}}. Управляйте подрядчиками и поставщиками в {{company}}." + } + }, + "fb_messenger": { + "fb_messenger_manage_modal": { + "title": "Интеграция с Facebook Messenger" + }, + "fb_messenger_item": { + "title": "Взаимодействуйте с Facebook, преобразуйте чаты в лиды" + }, + "fb_messenger_modal_template": { + "title": "Интеграция с Facebook Messenger" + }, + "fb_messenger_first_info_modal": { + "title": "Получите официальный бизнес-аккаунт Facebook Messenger, чтобы начать общение с вашими клиентами с помощью {{company}} Multimessenger.", + "feature1": "Управляйте всеми разговорами в Facebook Messenger и другими каналами в одном месте", + "feature2": "Сотрудничайте с членами команды по входящим разговорам", + "feature3": "Легко создавайте новые контакты, потенциальных клиентов и сделки непосредственно из разговоров", + "feature4": "Используйте шаблоны сообщений Facebook Messenger для отправки актуальных и своевременных уведомлений", + "learn_more": "Узнайте больше о том, как управлять интеграцией Facebook Messenger" + }, + "fb_messenger_finish_modal": { + "supervisors": "Выберите пользователей, которым доступны все чаты с клиентами", + "supervisors_hint": "Выберите пользователей, которым будут доступны все чаты сотрудников с клиентами. Это могут быть руководители или сотрудники, которые работают совместно с клиентами.", + "save": "Сохранить", + "title": "Вы уже подключили свой аккаунт Facebook к {{company}}", + "subtitle": "Завершите настройку своего аккаунта. При необходимости измените имя и настройки доступности.", + "name": "Имя *", + "responsible_users": "Ответственные пользователи за новых клиентов *", + "learn_more": "Узнайте больше о том, как управлять интеграцией Facebook Messenger", + "placeholders": { + "name": "Название продукта", + "select_users": "Выберите пользователей" + } + } + }, + "wazzup": { + "wazzup_manage_modal": { + "title": "{{company}} Интеграция Wazzup" + }, + "wazzup_item": { + "title": "Wazzup Интеграция за 5 минут, Telegram, ВК и другие" + }, + "wazzup_modal_template": { + "title": "{{company}} Интеграция Wazzup" + }, + "wazzup_first_info_modal": { + "title": "Пишите первым клиенту в социальных сетях и мессенджерах напрямую через {{company}}.", + "feature1": "Сотрудники отдела продаж могут отправлять сообщения в WhatsApp, Telegram, ВКонтакте и через Авито, используя {{company}}", + "feature2": "Менеджеры видят лишь свои диалоги, тогда как руководители могут просматривать все переписки", + "feature3": "Контакты и сделки с новыми клиентами формируются автоматически", + "feature4": "Все общение безопасно сохраняется в {{company}}", + "feature5": "Используйте один номер на весь отдел продаж и настройте отправку автоответов", + "annotation": "Оставайтесь в контакте с клиентами, где бы вы ни находились. С мобильного приложения Wazzup можно писать клиентам, при этом все переписки будут сохранены в {{company}} и доступны на компьютере.", + "learn_more": "Узнать больше о Wazzup" + }, + "wazzup_connect_modal": { + "save": "Сохранить", + "update": "Обновить", + "title": "Авторизуйте свою учетную запись Wazzup", + "subtitle": "Откройте настройки вашей учетной записи Wazzup и скопируйте ваш ключ API.", + "name": "Название *", + "api_key": "Ключ API *", + "channel": "Канал Wazzup *", + "responsible_users": "Ответственные пользователи за новых клиентов *", + "supervisors": "Выберите пользователей, которым доступны все чаты с клиентами", + "supervisors_hint": "Выберите пользователей, которым будут доступны все чаты сотрудников с клиентами. Это могут быть руководители или сотрудники, которые работают совместно с клиентами.", + "placeholders": { + "api_key": "Ключ API", + "name": "Название продукта", + "select_users": "Выберите пользователей", + "channel": "Выберите канал Wazzup" + } + } + }, + "whatsapp": { + "whatsapp_manage_modal": { + "title": "Интеграция API WhatsApp Business от Twilio" + }, + "whatsapp_item": { + "title": "Ведите переписку WhatsApp через {{company}}" + }, + "whatsapp_modal_template": { + "title": "Интеграция API WhatsApp Business от Twilio" + }, + "whatsapp_first_info_modal": { + "title": "Получите официальный аккаунт WhatsApp Business (предоставленный Twilio), чтобы начать общение с вашими клиентами с помощью {{company}} Multimessenger", + "feature1": "Управляйте всеми каналами разговоров в WhatsApp в одном месте", + "feature2": "Сотрудничайте с членами команды по в ведении переписки", + "feature3": "Легко создавайте новые контакты, лиды и сделки прямо из разговоров", + "feature4": "Используйте шаблоны сообщений WhatsApp для отправки актуальных и своевременных уведомлений", + "learn_more": "Узнайте больше об API WhatsApp Business от Twilio" + }, + "whatsapp_second_info_modal": { + "title": "Что нужно учесть, прежде чем продолжить", + "step1": "Вы должны приобрести новый номер телефона в Twilio (или перенести свой текущий номер телефона из мобильного приложения WhatsApp или приложения WhatsApp Business), чтобы создать учетную запись WhatsApp Business API.", + "link1": "Как перенести одобренный номер WhatsApp в Twilio", + "step2": "WhatsApp и Twilio могут взимать дополнительную плату за использование API WhatsApp Business.", + "link2": "Узнайте больше о тарифах" + }, + "whatsapp_third_info_modal": { + "title": "Настройте аккаунт Twilio с вашим номером WhatsApp", + "subtitle": "Twilio должен одобрить ваш номер WhatsApp, прежде чем вы сможете использовать его в {{company}}. Следуйте этим шагам, чтобы избежать лишних задержек.", + "step1": "Настройте", + "step2": "Запросите доступ от Twilio, чтобы разрешить использование номеров телефонов для WhatsApp:", + "step2_1_1": "Убедитесь, что у вас есть номер телефона, предоставленный Twilio, чтобы получить номер WhatsApp. Заполните", + "step2_1_2": "форму запроса доступа Twilio", + "step2_1_3": "с вашей актуальной информацией, включая ваш ID в Facebook Business Manager.", + "step2_2_1": "Ссылка на", + "step2_2_2": "документацию Twilio", + "step2_2_3": "для получения дополнительной информации.", + "step3": "Отправьте запрос на отправку сообщения WhatsApp в консоли Twilio.", + "step4_1": "После отправки формы запроса доступа вы получите электронное письмо с предварительным одобрением от Twilio. Ознакомьтесь с этой", + "step4_2": "ссылкой", + "step4_3": "для следующих шагов.", + "step5": "Разрешите Twilio отправлять сообщение от вашего имени в консоли Facebook Business Manager." + }, + "whatsapp_connect_modal": { + "supervisors": "Выберите пользователей, которым доступны все чаты с клиентами", + "supervisors_hint": "Выберите пользователей, которым будут доступны все чаты сотрудников с клиентами. Это могут быть руководители или сотрудники, которые работают совместно с клиентами.", + "save": "Сохранить", + "update": "Обновить", + "title": "Авторизуйте ваш аккаунт WhatsApp от Twilio", + "subtitle": "Откройте настройки вашей учетной записи Twilio и скопируйте учетные данные API.", + "name": "Имя *", + "sid": "SID учетной записи *", + "auth_token": "Токен авторизации *", + "phone": "Авторизованный номер WhatsApp *", + "responsible_users": "Ответственные пользователи за новые лиды *", + "learn_more": "Узнайте больше о настройке аккаунта WhatsApp с Twilio", + "placeholders": { + "name": "Название продукта", + "code": "Код продукта", + "token": "Токен", + "number": "Номер", + "select_users": "Выберите пользователей" + } + } + }, + "tilda": { + "tilda_item": { + "description": "Интеграция форм на сайт с Tilda" + }, + "tilda_manual_modal": { + "close": "Закрыть", + "title": "Интеграция форм на сайт с Tilda", + "info_title": "Интегрируйте форму на сайт из конструктора {{company}} c сайтом на Tilda, чтобы заявки приходили прямо в систему.", + "step_1_title": "Шаг 1 — Подготовка формы на сайт", + "step_1_description": "В конструкторе форм {{company}} необходимо создать форму на сайт с интеграцией по API. Настройте форму на первом шаге и добавьте в нее необходимые поля на втором шаге.", + "form_builder_link": "Конструктор форм с интеграцией по API", + "step_2_title": "Шаг 2 — Настройка формы в Тильде", + "step_2_item_1": "Откройте настройки сайта → Формы → Webhook.", + "step_2_item_2": "Укажите URL вебхука из 3-го шага конструктора форм в качестве адреса скрипта.", + "step_2_item_3": "Сохраните изменения.", + "step_3_title": "Шаг 3 — Настройка переменных полей", + "step_3_item_1": "Для формы, которую вы хотите интегрировать, укажите сервис приема данных — Webhook.", + "step_3_item_2": "Укажите для полей формы переменные (идентификаторы) из таблицы в 3-м шаге конструктора форм.", + "step_3_item_3": "Проверьте работу формы. Если все работает правильно, опубликуйте страницу.", + "tilda_manual_link": "Инструкция по настройке от Tilda", + "support": "Если у вас возникли проблемы при интеграции формы, свяжитесь с поддержкой." + } + }, + "wordpress": { + "wordpress_item": { + "description": "Интеграция форм на сайт с Wordpress" + }, + "wordpress_manual_modal": { + "close": "Закрыть", + "title": "Интеграция форм на сайт с Wordpress", + "info_title": "Интегрируйте форму на сайт из конструктора {{company}} c сайтом на Wordpress, чтобы заявки приходили прямо в систему.", + "step_1_title": "Шаг 1 — Подготовка формы на сайт", + "step_1_description": "В конструкторе форм {{company}} необходимо создать форму на сайт. Настройте форму, создайте необходимые поля на втором шаге, а также, по желанию, стилизуйте форму на 4-м шаге.", + "form_builder_link": "Конструктор форм", + "step_2_title": "Шаг 2 — Добавление контейнера формы", + "step_2_item_1": "Откройте панель управления Wordpress.", + "step_2_item_2": "По возможности создайте резервную копию страницы или дублируйте ее, чтобы иметь возможность откатить изменения.", + "step_2_item_3": "Если у вас уже есть форма и вы хотите ее заменить, удалите ее. Вместо нее должен остаться пустой блок. Если формы еще нет, добавьте пустой блок в то место, куда хотите вставить форму из конструктора.", + "step_2_item_4": "Перейдите в выпадающее меню «Дополнительно» в настройках блока справа от окна редактора. Задайте блоку название класса workspace-form-builder-mount-container.", + "step_3_title": "Шаг 3 — Добавление кода формы", + "step_3_item_1": "Добавьте на страницу блок «Произвольный HTML».", + "step_3_item_2": "Откройте блок с произвольным HTML и скопируйте в него код из 5-го шага конструктора форм.", + "step_3_item_3": "Проверьте работу формы. Если все работает правильно, опубликуйте страницу.", + "wordpress_manual_link": "Инструкция по вставке HTML-кода от Wordpress", + "additional_steps": "Дополнительные действия", + "styling": "Внешний вид формы можно изменить на 4-м шаге конструктора форм. Если параметров настройки не хватает, включите «Пользовательский CSS». Каждому элементу задан читаемый стабильный CSS-класс в формате .workspace-form__TextInput--Input. Форму можно изменять в конструкторе даже после публикации страницы. Любое изменение вступает в силу сразу после сохранения.", + "multiform": "На одной странице можно публиковать неограниченное количество одинаковых форм. При этом блок с произвольным HTML должен быть на странице один, а контейнеров может быть любое количество. Если нужно создать несколько разных форм из конструктора на одной странице, включите в конструкторе «режим совместимости нескольких форм». Тогда, для каждой уникальной формы нужно будет вставить свой код в соответствующий блок с произвольным HTML. CSS-класс для контейнера будет особый — скопируйте его из инструкции на 5-м шаге конструктора форм.", + "support": "Если у вас возникли проблемы при интеграции формы, свяжитесь с поддержкой." + } + }, + "albato": { + "albato_item": { + "description": "Автоматизируйте процессы любой сложности с Albato" + }, + "albato_manual_modal": { + "close": "Закрыть", + "header_title": "Инструкция по интеграции {{company}} с Albato", + "integration_title": "Используйте систему автоматизации Albato для связи {{company}} с тысячами других сервисов.", + "step_1_title": "Шаг 1 — Создание связки в Albato", + "albato_website": "Сайт Albato", + "step_1_part_1": "Создайте аккаунт в Albato, если он еще не создан.", + "step_1_part_2": "Создайте новую связку. Добавьте новое действие, и в меню выбора сервиса найдите и выберите Mywork.", + "step_1_part_3": "Найдите подходящее действие из списка предложенных и выберите его.", + "step_2_title": "Шаг 2 — Подключение аккаунта Mywork", + "step_2_part_1": "Добавьте новое подключение для Mywork.", + "step_2_part_2": "Заполните поле «API-ключ». Ключ можно найти в настройках вашего аккаунта Mywork в разделе «Доступ к API».", + "step_2_part_3": "Заполните поля «E-mail» и «Пароль» данными от вашего аккаунта Mywork. ", + "step_2_part_4": "Заполните поле «Поддомен». Его можно найти в разделе «Общие настройки» вашего аккаунта Mywork. Укажите только выделенную часть, без «.mywork.app».", + "api_access_link": "Доступ к API", + "step_3_title": "Шаг 3 — Настройка действий автоматизации", + "step_3": "На этапе настройки действия отображается форма с полями, которые необходимо заполнить. Эти поля соответствуют данным, которые нужны для выполнения действия (например, ответственный пользователь, название задачи или текст). Данные можно заполнять динамически, то есть передавать из прошлых действий или триггера. Некоторые данные можно заполнить из справочника (например, ответственного пользователя) — в этом случае загрузятся актуальные данные из вашего аккаунта Mywork. Если данных нет, нажмите кнопку «Обновить список».", + "step_4_title": "Шаг 4 — Тестирование", + "step_4": "Перед запуском связки обязательно протестируйте ее, нажав на соответствующую кнопку. Данные, переданные в систему, можно посмотреть в журнале связки. Если у вас возникли вопросы по работе интеграции, обратитесь в техническую поддержку Mywork." + } + }, + "request_integration": { + "description": "Заказать разработку интеграции", + "request": "Заказать" + } + } + }, + "users_settings_page": { + "user": "Пользователь", + "total": "Всего", + "create_button_tooltip": "Добавить пользователя", + "ui": { + "remove_user_modal": { + "remove": "Удалить", + "title": "Вы уверены, что хотите удалить этого пользователя?", + "annotation": "Передать владение всеми элементами этого пользователя другому.", + "placeholder": "Выберите ответственного..." + }, + "user_item": { + "remove": "Удалить", + "user_role": { + "owner": "Владелец", + "admin": "Администратор", + "user": "Пользователь", + "partner": "Партнер" + } + } + } + }, + "account_api_access_page": { + "title": "Доступ к API", + "annotation": "Вы можете использовать API-ключ для создания внешних интеграций с системой. Сохраните этот ключ в надежном месте и не делитесь им ни с кем. Если ключ был скомпрометирован, пересоздайте его.", + "create_api_key": "Создать API-ключ", + "recreate_api_key": "Пересоздать", + "warning_title": "Пересоздание API-ключа", + "warning_annotation": "Текущий ключ будет удален и заменен новым. После пересоздания замените ключ во всех внешних интеграциях, чтобы они продолжили работать.", + "created_at": "Создан {{date}} в {{time}}", + "api_tokens_list": { + "title": "Токены авторизации", + "annotation": "Вы можете создать токены авторизации для создания внешних интеграций с системой. Токен используется вместо стандартного процесса авторизации с помощью логина и пароля. После создания вы увидите токен лишь единожды, поэтому сохраните его в надежном месте. Если токен был скомпрометирован, удалите его и создайте новый.", + "empty_user_tokens": "Не создано ни одного токена авторизации.", + "add_user_token": "Добавить токен", + "copy": "Скопировать токен", + "access_token": "Сохраните этот токен — вы больше не сможете его увидеть:", + "name": "Название", + "expires_at": "Дата окончания действия", + "table": { + "name": "Название", + "created_at": "Создан", + "expires_at": "Истекает", + "last_used_at": "Последнее использование", + "never": "Никогда", + "actions": "Действия", + "delete": "Удалить" + } + } + }, + "settings_page_template": { + "modules_settings": "Настройки модулей", + "sip_registrations": "SIP регистрации", + "title": "Настройки", + "users": "Пользователи", + "general": "Общие настройки", + "email": "Электронная почта", + "integrations": "Интеграции", + "billing": "Подписка", + "api_access": "Доступ к API", + "documents": "Документы", + "calls": "Звонки", + "account": "Аккаунт", + "superadmin": "Админ-панель", + "configuring_scenarios": "Сценарии", + "schemas": "Схемы", + "configure_users": "Настройка", + "groups": "Группы", + "document_templates": "Шаблоны", + "document_creation_fields": "Поля" + }, + "add_user_to_plan_modal": { + "send": "Отправить", + "title": "Чтобы добавить пользователя, свяжитесь с нами", + "name": "* Имя", + "phone": "* Телефон", + "email": "* Электронная почта", + "number_of_users": "* Количество пользователей", + "placeholder": "Ваше имя" + }, + "request_setup_form": { + "button": { + "request_setup": "Заказать настройку", + "request_billing_help": "Запросить помощь с подпиской", + "request_integration": "Заказать разработку интеграции", + "request_telephony": "Заказать подключение телефонии", + "request_api_integration": "Заказать интеграцию" + }, + "modal": { + "request_setup": "Заказать настройку {{company}}", + "request_billing_help": "Запросить помощь с подпиской {{company}}", + "request_integration": "Заказать разработку интеграции", + "request_telephony": "Заказать подключение телефонии", + "request_api_integration": "Заказать интеграцию по API", + "full_name": "Имя *", + "phone": "Телефон *", + "email": "E-mail *", + "comment": "Запрос", + "send_request": "Отправить", + "placeholders": { + "full_name": "Введите ваше имя", + "comment": { + "request_setup": "Кратко опишите запрос по настройке системы. Мы свяжемся с вами и поможем с внедрением системы под ваши задачи.", + "request_billing_help": "Кратко опишите проблему с подпиской. Мы свяжемся с вами и поможем решить вопрос.", + "request_integration": "Кратко опишите запрос по разработке интеграции. Мы свяжемся с вами и уточним детали.", + "request_telephony": "Кратко опишите запрос по телефонии. Мы свяжемся с вами и поможем с внедрением телефонии под ваши задачи.", + "request_api_integration": "Кратко опишите запрос для интеграции по API. Мы свяжемся с вами и поможем с интеграцией системы под ваши задачи." + } + } + } + } + } +} diff --git a/frontend/public/locales/ru/page.system.json b/frontend/public/locales/ru/page.system.json new file mode 100644 index 0000000..deafe94 --- /dev/null +++ b/frontend/public/locales/ru/page.system.json @@ -0,0 +1,19 @@ +{ + "error_page": { + "title": "Что-то пошло не так", + "annotation": "Извините, но что-то пошло не так. Мы получили уведомление об этой проблеме и скоро исправим её. Пожалуйста, перейдите на домашнюю страницу или попробуйте перезагрузить страницу.", + "show_error": "Показать сообщение об ошибке", + "home": "На главную", + "reload": "Перезагрузить страницу" + }, + "forbidden_page": { + "title": "Доступ к этой странице запрещён.", + "back": "Вернуться назад", + "home": "На главную" + }, + "not_found_page": { + "title": "Запрашиваемая вами страница не найдена.", + "back": "Вернуться назад", + "home": "На главную" + } +} diff --git a/frontend/public/locales/ru/page.tasks.json b/frontend/public/locales/ru/page.tasks.json new file mode 100644 index 0000000..6d23d2d --- /dev/null +++ b/frontend/public/locales/ru/page.tasks.json @@ -0,0 +1,135 @@ +{ + "tasks_page": { + "tasks_page_by_deadline": { + "unallocated": "Не запланировано", + "overdue": "Просрочено", + "today": "Сегодня", + "tomorrow": "Завтра", + "upcoming": "Предстоящее", + "resolved": "Выполнено", + "board": "Доска", + "calendar": "Календарь" + }, + "common": { + "tasks_page_template": { + "create_new": "Новая задача" + }, + "ui": { + "empty_list_block": { + "annotation1": "У вас пока нет задач или ничего не было найдено с выбранными фильтрами.", + "annotation2": "Используйте кнопку", + "annotation3": "для создания новой задачи." + }, + "tasks_page_header": { + "timeline": "Гант", + "title": "Задачи и активности", + "board": "Доска", + "list": "Список", + "calendar": "Календарь", + "user_select_button": "Участники", + "settings_button": { + "sync": "Синхронизировать", + "table_settings": "Настройки таблицы", + "settings": "Настройки", + "board_settings": "Настройки доски" + }, + "create_button_tooltip": "Добавить задачу" + }, + "tasks_list_settings_drawer": { + "table_settings": "Настройки таблицы", + "display_columns": "Отображение колонок" + }, + "tasks_filter_drawer": { + "just_my_tasks": "Только мои задачи", + "sorting": "Сортировка", + "done": "Выполнено", + "sorting_options": { + "manual": "Ручная сортировка", + "created_asc": "Создано: от старых к новым", + "created_desc": "Создано: от новых к старым" + }, + "created_at": "Дата создания", + "start_date": "Дата начала", + "end_date": "Дата окончания", + "resolve_date": "Дата выполнения", + "assignee": "Ответственный", + "reporter": "Постановщик", + "type": "Тип", + "stage": "Этап", + "groups": "Группы", + "linked_cards": "Привязанные карточки", + "placeholders": { + "card_name": "Название карточки", + "search_by_task_name": "Искать по названию задачи", + "search_by_activity_name": "Искать по названию активности" + } + } + } + }, + "tasks_page_calendar": { + "new_event": "Новая задача", + "no_events": "Нет задач на этот период.", + "all_day": "Весь день" + }, + "tasks_page_list": { + "all_columns_hidden": "Все колонки скрыты в настройках таблицы" + }, + "activity_item": { + "no_card_access": "У вас нет доступа к этой карточке", + "drag_disabled": "У вас нет разрешения на перемещение этой карточки. Обратитесь к администратору учетной записи для получения доступа." + }, + "tasks_page_timeline": { + "annotation": { + "minute": ["минута", "минуты", "минут"], + "hour": ["час", "часа", "часов"], + "day": ["день", "дня", "дней"] + }, + "today": "Сегодня", + "view": { + "fifteen_minutes": "15 минут", + "hour": "Час", + "day": "День", + "week": "Неделя", + "month": "Месяц", + "quarter": "Квартал", + "half_year": "Полугодие" + }, + "format": { + "major_format": { + "fifteen_minutes": "D MMMM YYYY", + "hour": "D MMMM YYYY", + "day": "MMMM YYYY", + "week": "MMMM YYYY", + "month": "YYYY", + "quarter": "YYYY", + "half_year": "YYYY" + }, + "minor_format": { + "fifteen_minutes": "HH:mm", + "hour": "HH", + "day": "D", + "week": "wo [week]", + "month": "MMMM", + "quarter": "[Q]Q", + "half_year": "YYYY-" + } + } + }, + "task_item": { + "planned_time": "Планируемое время:", + "completed": "Выполнено", + "complete": "Выполнить", + "delete_task": "Удалить задачу", + "copy_link": "Копировать ссылку", + "drag_disabled": "У вас нет разрешения на перемещение этой задачи. Обратитесь к администратору учетной записи для получения доступа.", + "date": "{{date}} в {{time}}" + }, + "time_allocation_card": { + "users": "Пользователи", + "planned_time": "Планируемое время", + "total": "Всего", + "hour": "ч", + "minute": "м" + } + } +} diff --git a/frontend/public/locales/ru/store.field-groups-store.json b/frontend/public/locales/ru/store.field-groups-store.json new file mode 100644 index 0000000..cadf8ae --- /dev/null +++ b/frontend/public/locales/ru/store.field-groups-store.json @@ -0,0 +1,9 @@ +{ + "field_groups_store": { + "requisites": "Реквизиты", + "analytics": "Аналитика", + "errors": { + "create_at_least_one_field": "Создайте как минимум одно поле в каждой группе" + } + } +} diff --git a/frontend/public/locales/ru/store.fields-store.json b/frontend/public/locales/ru/store.fields-store.json new file mode 100644 index 0000000..020fe95 --- /dev/null +++ b/frontend/public/locales/ru/store.fields-store.json @@ -0,0 +1,8 @@ +{ + "fields_store": { + "errors": { + "create_at_least_one_field": "Создайте как минимум одно поле", + "duplicate_name": "Имена полей должны различаться: {{fieldName}}" + } + } +} diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt new file mode 100644 index 0000000..5caac9c --- /dev/null +++ b/frontend/public/robots.txt @@ -0,0 +1,4 @@ +# https://www.robotstxt.org/robotstxt.html + +User-agent: * +Disallow: / diff --git a/frontend/public/sounds/incoming_call.mp3 b/frontend/public/sounds/incoming_call.mp3 new file mode 100644 index 0000000..829812d Binary files /dev/null and b/frontend/public/sounds/incoming_call.mp3 differ diff --git a/frontend/public/sounds/notification.mp3 b/frontend/public/sounds/notification.mp3 new file mode 100644 index 0000000..4cb3394 Binary files /dev/null and b/frontend/public/sounds/notification.mp3 differ diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx new file mode 100644 index 0000000..98f7b60 --- /dev/null +++ b/frontend/src/app/App.tsx @@ -0,0 +1,143 @@ +import { generalSettingsStore } from '@/app'; +import { authStore } from '@/modules/auth'; +import { + ErrorBoundary, + GTMUtil, + UserRole, + WholePageLoaderWithLogo, + envUtil, + serverEventService, + useCheckBrowserSupport, +} from '@/shared'; +import { useDocumentTitle } from '@mantine/hooks'; +import { when } from 'mobx'; +import { observer } from 'mobx-react-lite'; +import { Suspense, useEffect, useMemo } from 'react'; +import { Helmet, type HelmetProps } from 'react-helmet'; +import { useTranslation } from 'react-i18next'; +import { RouterProvider } from 'react-router-dom'; +import { createRouter } from './routes'; +import { appStore } from './store'; + +const App = observer(() => { + const { i18n, t } = useTranslation(); + + useDocumentTitle(envUtil.appName); + + const isBrowserSupported = useCheckBrowserSupport(); + + const router = useMemo(() => createRouter({ isBrowserSupported }), [isBrowserSupported]); + + useEffect(() => { + GTMUtil.initializeGTM(); + + authStore.startAuth(); + + when( + () => authStore.isAuthenticated, + () => { + const { user: currentUser } = authStore; + + if (currentUser && currentUser.role !== UserRole.PARTNER) serverEventService.connect(); + } + ); + + return () => { + serverEventService.disconnect(); + }; + }, []); + + useEffect(() => { + when( + () => appStore.isLoaded, + () => { + const changeLanguage = (lng: string) => { + i18n.changeLanguage(lng); + + document.documentElement.lang = lng; + }; + + const { accountSettings } = generalSettingsStore; + + if (accountSettings && accountSettings.language !== i18n.language) + changeLanguage(accountSettings.language); + } + ); + }, [i18n]); + + const helmetHTMLAttributes = useMemo( + () => ({ lang: i18n.language }), + [i18n.language] + ); + + return ( + + }> + + + + {/* icons */} + + + + + + + + + {/* .png icons for Safari */} + + + + + + + + ); +}); + +export { App }; + +// I was here through multiple MVPs and paradigm shifts. Some code was +// written, and some architecture was developed, though not as well as it could +// have been due to a lack of time and resources. + +// If you're reading this and have any questions, feel free to reach out to me! 😎 + +// https://github.com/kr4chinin (2022-2024) diff --git a/frontend/src/app/api/ApiRoutes.ts b/frontend/src/app/api/ApiRoutes.ts new file mode 100644 index 0000000..3def184 --- /dev/null +++ b/frontend/src/app/api/ApiRoutes.ts @@ -0,0 +1,83 @@ +export enum ApiRoutes { + /* IAM START */ + + // subscription + GET_SUBSCRIPTION = '/api/iam/subscriptions', + GET_SUBSCRIPTION_FOR_ACCOUNT = '/api/iam/subscriptions/:accountId', + GET_SUBSCRIPTION_PLANS = '/api/iam/subscriptions/stripe/plans', + GET_SUBSCRIPTION_CHECKOUT_URL = '/api/iam/subscriptions/stripe/checkout', + GET_SUBSCRIPTION_PORTAL_URL = '/api/iam/subscriptions/stripe/portal', + GET_CURRENT_DISCOUNT = '/api/iam/subscriptions/discount/current', + UPDATE_SUBSCRIPTION = '/api/iam/subscriptions/:accountId', + // users + GET_USERS = '/api/iam/users', + GET_USER = '/api/iam/users/:id', + ADD_USER = '/api/iam/users', + UPDATE_USER = '/api/iam/users/:id', + DELETE_USER = '/api/iam/users/:id', + UPLOAD_USER_AVATAR = '/api/iam/users/:id/avatar', + REMOVE_USER_AVATAR = '/api/iam/users/:id/avatar', + CHANGE_USER_PASSWORD = '/api/iam/users/change-password', + // user profile + GET_USER_PROFILE = '/api/iam/users/:id/profile', + UPDATE_USER_PROFILE = '/api/iam/users/:id/profile', + + /* IAM END */ + + // builder + ADD_ENTITY_TYPE = '/api/crm/entity-types', + GET_FEATURES = '/api/crm/features', + // feed + GET_FEED_ITEMS = '/api/crm/entities/:entityId/events/:filter', + // entity types + GET_ENTITY_TYPES = '/api/crm/entity-types', + GET_ENTITY_TYPE = '/api/crm/entity-types/:id', + DELETE_ENTITY_TYPE = '/api/crm/entity-types/:id', + UPDATE_ENTITY_TYPE = '/api/crm/entity-types/:id', + UPDATE_ENTITY_TYPE_FIELDS = '/api/crm/entity-types/:id/fields', + UPDATE_FIELDS_SETTINGS = '/api/crm/entity-types/:entityTypeId/fields-settings', + // identity + GET_ALL_IDENTITY_POOLS = '/api/crm/identities/all', + GET_IDENTITY_POOL = '/api/crm/identities/:name', + // boards + GET_BOARDS = '/api/crm/boards', + GET_BOARD = '/api/crm/boards/:id', + ADD_BOARD = '/api/crm/boards', + UPDATE_BOARD = '/api/crm/boards/:id', + DELETE_BOARD = '/api/crm/boards/:id', + // stages + CREATE_STAGE = '/api/crm/boards/:boardId/stages', + GET_STAGES = '/api/crm/boards/:boardId/stages', + UPDATE_STAGE = '/api/crm/boards/:boardId/stages/:stageId', + GET_STAGE = '/api/crm/boards/:boardId/stages/:stageId', + DELETE_STAGE = '/api/crm/boards/:boardId/stages/:stageId', + // storage + GET_FILE_INFO = '/api/storage/info/:fileId', + UPLOAD_FILES = '/api/storage/upload', + DELETE_FILE = '/api/storage/file/:id', + DELETE_FILE_LINK = '/api/crm/file-link/:id', + DELETE_FILE_LINKS = '/api/crm/file-links', + // form + SEND_CONTACT_US_FORM = '/api/forms/contact-us', + // account + CREATE_ACCOUNT = '/api/iam/account', + GET_ACCOUNT = '/api/iam/account', + SEARCH_ACCOUNTS = '/api/iam/account/search', + UPLOAD_ACCOUNT_LOGO = '/api/iam/account/logo', + REMOVE_ACCOUNT_LOGO = '/api/iam/account/logo', + GET_ACCOUNT_SETTINGS = '/api/iam/account/settings', + UPDATE_ACCOUNT_SETTINGS = '/api/iam/account/settings', + // general settings + GET_DEMO_DATA_EXISTS = '/api/setup/demo-data/exists', + DELETE_DEMO_DATA = '/api/setup/demo-data', + // version + GET_LATEST_FRONTEND_VERSION = '/api/support/version/frontend/latest', + // frontend objects + GET_FRONTEND_OBJECT = '/api/frontend/objects/:key', + UPSERT_FRONTEND_OBJECT = '/api/frontend/objects', + DELETE_FRONTEND_OBJECT = '/api/frontend/objects/:key', + // dadata + // proxy on https://dadata.ru/api/ + GET_BANK_REQUISITES = '/api/data-enrichment/requisites/bank', + GET_ORG_REQUISITES = '/api/data-enrichment/requisites/org', +} diff --git a/frontend/src/app/api/AppQueryKeys.ts b/frontend/src/app/api/AppQueryKeys.ts new file mode 100644 index 0000000..cc14833 --- /dev/null +++ b/frontend/src/app/api/AppQueryKeys.ts @@ -0,0 +1,50 @@ +import type { Nullable, Optional } from '@/shared'; + +const queryKeys = { + app: ['app'], + latestFrontendVersion(currentVersion: string) { + return [...this.app, 'latest-frontend-version', currentVersion]; + }, + + stages: ['stages'], + stage({ + stageId, + boardId, + }: { + stageId: Optional>; + boardId: Optional>; + }) { + return [...this.stages, 'stage', boardId, stageId]; + }, + stagesByBoardId(boardId: Optional>) { + return [...this.stages, 'board-id', boardId]; + }, + + boards: ['boards'], + tasksBoards() { + return [...this.boards, 'tasks']; + }, + board(boardId: Optional>) { + return [...this.boards, 'board', boardId]; + }, + boardsByEntityTypeId(entityTypeId: Optional>) { + return [...this.boards, 'entity-type-id', entityTypeId]; + }, + + userProfiles: ['user-profiles'], + userProfile(id: number) { + return [...this.userProfiles, 'user-profile', id]; + }, + + fileInfo: ['file-info'], + fileInfoById(fileId: string) { + return [...this.fileInfo, fileId]; + }, + + subscriptions: ['subscriptions'], + subscriptionByAccountId(accountId: number) { + return [...this.subscriptions, accountId]; + }, +} as const; + +export const APP_QUERY_KEYS = Object.freeze(queryKeys); diff --git a/frontend/src/app/api/BaseApi/BaseApi.ts b/frontend/src/app/api/BaseApi/BaseApi.ts new file mode 100644 index 0000000..f9e0792 --- /dev/null +++ b/frontend/src/app/api/BaseApi/BaseApi.ts @@ -0,0 +1,53 @@ +import { UrlUtil } from '@/shared'; +import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios'; + +export const determineApiHost = (): string => { + const hostname = UrlUtil.getCurrentHostname(); + + // for testing local backend + if (hostname.includes('.loc')) return `http://${hostname}:8000`; + + // Use relative URLs for production (empty string means relative to current domain) + return import.meta.env.VITE_BASE_API_URL || ''; +}; + +class BaseApi { + private _axios = axios.create({ + baseURL: determineApiHost(), + headers: { + 'X-Api-Key': import.meta.env.VITE_BASE_API_KEY, + }, + }); + + get = async (url: string, config?: AxiosRequestConfig): Promise => { + return this._axios.get(url, config); + }; + + post = async (url: string, data?: any, config?: AxiosRequestConfig): Promise => { + return this._axios.post(url, data, config); + }; + + put = async (url: string, data?: any, config?: AxiosRequestConfig): Promise => { + return this._axios.put(url, data, config); + }; + + patch = async (url: string, data?: any, config?: AxiosRequestConfig): Promise => { + return this._axios.patch(url, data, config); + }; + + delete = async (url: string, config?: AxiosRequestConfig): Promise => { + return this._axios.delete(url, config); + }; + + setAuthToken = (token: string): void => { + this.setRequestHeader({ Authorization: `Bearer ${token}` }); + }; + + setRequestHeader = (data: Record): void => { + for (const key in data) { + this._axios.defaults.headers.common[key] = data[key] as string; + } + }; +} + +export const baseApi = new BaseApi(); diff --git a/frontend/src/app/api/BoardApi/BoardApi.ts b/frontend/src/app/api/BoardApi/BoardApi.ts new file mode 100644 index 0000000..5568a5b --- /dev/null +++ b/frontend/src/app/api/BoardApi/BoardApi.ts @@ -0,0 +1,53 @@ +import { Board, BoardType, UrlTemplateUtil } from '@/shared'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import { type CreateBoardDto, type UpdateBoardDto } from '../dtos'; + +class BoardApi { + getBoardsByEntityTypeId = async (entityTypeId: number): Promise => { + const response = await baseApi.get(ApiRoutes.GET_BOARDS, { + params: { recordId: entityTypeId }, + }); + + return Board.fromDtos(response.data); + }; + + getTasksBoards = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_BOARDS, { params: { type: BoardType.TASK } }); + + return Board.fromDtos(response.data); + }; + + getBoardById = async (id: number): Promise => { + const response = await baseApi.get(UrlTemplateUtil.toPath(ApiRoutes.GET_BOARD, { id })); + + return Board.fromDto(response.data); + }; + + updateBoard = async ({ + dto, + boardId, + }: { + dto: UpdateBoardDto; + boardId: number; + }): Promise => { + const response = await baseApi.patch( + UrlTemplateUtil.toPath(ApiRoutes.UPDATE_BOARD, { id: boardId }), + dto + ); + + return Board.fromDto(response.data); + }; + + addBoard = async (dto: CreateBoardDto): Promise => { + const response = await baseApi.post(ApiRoutes.ADD_BOARD, dto); + + return Board.fromDto(response.data); + }; + + deleteBoard = async (id: number): Promise => { + await baseApi.delete(UrlTemplateUtil.toPath(ApiRoutes.DELETE_BOARD, { id })); + }; +} + +export const boardApi = new BoardApi(); diff --git a/frontend/src/app/api/BoardApi/BoardApiUtil.ts b/frontend/src/app/api/BoardApi/BoardApiUtil.ts new file mode 100644 index 0000000..d2652d4 --- /dev/null +++ b/frontend/src/app/api/BoardApi/BoardApiUtil.ts @@ -0,0 +1,154 @@ +import { boardApi, type CreateBoardDto, stageApiUtil } from '@/app'; +import { queryClient } from '@/index'; +import type { Board, Nullable, Option, Optional } from '@/shared'; +import { + queryOptions, + skipToken, + useQueries, + useQuery, + type UseQueryResult, +} from '@tanstack/react-query'; +import { APP_QUERY_KEYS } from '../AppQueryKeys'; +import { UpdateBoardDto } from '../dtos'; + +const BOARDS_STALE_TIME = 10 * 60 * 1000; + +// this class encapsulate all board-related queries and helpers +// this is in fact static class, but do not refactor methods to static, +// because this will violate the rules of hooks +class BoardApiUtil { + private _getBoardQueryOptions = (boardId: Optional>) => { + return queryOptions({ + queryKey: APP_QUERY_KEYS.board(boardId), + queryFn: boardId ? async (): Promise => boardApi.getBoardById(boardId) : skipToken, + staleTime: BOARDS_STALE_TIME, + }); + }; + + private _getBoardsByEntityTypeIdQueryOptions = ({ + entityTypeId, + enabled, + }: { + entityTypeId: Optional>; + enabled?: boolean; + }) => { + return queryOptions({ + queryKey: APP_QUERY_KEYS.boardsByEntityTypeId(entityTypeId), + queryFn: entityTypeId + ? async (): Promise => boardApi.getBoardsByEntityTypeId(entityTypeId) + : skipToken, + staleTime: BOARDS_STALE_TIME, + enabled, + }); + }; + + private _getTasksBoardsQueryOptions = () => { + return queryOptions({ + queryKey: APP_QUERY_KEYS.tasksBoards(), + staleTime: BOARDS_STALE_TIME, + queryFn: boardApi.getTasksBoards, + }); + }; + + useGetBoard = (boardId: Optional>): UseQueryResult => { + return useQuery(this._getBoardQueryOptions(boardId)); + }; + + useGetBoardsByEntityTypeId = ({ + entityTypeId, + enabled, + }: { + entityTypeId: Optional>; + enabled?: boolean; + }): UseQueryResult => { + return useQuery(this._getBoardsByEntityTypeIdQueryOptions({ entityTypeId, enabled })); + }; + + useGetBoardsByEntityTypeIds = ( + entityTypeIds: number[] + ): (UseQueryResult & { et?: number })[] => { + return useQueries({ + queries: entityTypeIds.map(entityTypeId => + this._getBoardsByEntityTypeIdQueryOptions({ entityTypeId }) + ), + combine: res => res.map((r, idx) => ({ ...r, et: entityTypeIds[idx] })), + }); + }; + + useGetBoardsByEntityTypeIdOptions = ( + entityTypeId: Optional> + ): Option[] => { + const { data: boards } = useQuery(this._getBoardsByEntityTypeIdQueryOptions({ entityTypeId })); + + return ( + boards?.map>(b => ({ + value: b.id, + label: b.name, + })) ?? [] + ); + }; + + useGetTasksBoards = (): UseQueryResult => { + return useQuery(this._getTasksBoardsQueryOptions()); + }; + + getBoard = async (boardId: number): Promise => { + return await queryClient.ensureQueryData(this._getBoardQueryOptions(boardId)); + }; + + invalidateBoards = async (): Promise => { + await queryClient.invalidateQueries({ queryKey: APP_QUERY_KEYS.boards }); + }; + + updateBoard = async ({ + boardId, + dto, + }: { + boardId: number; + dto: UpdateBoardDto; + }): Promise => { + await boardApi.updateBoard({ dto, boardId }); + + this.invalidateBoards(); + }; + + addBoard = async (dto: CreateBoardDto): Promise => { + try { + await boardApi.addBoard(dto); + + await Promise.all([stageApiUtil.invalidateStages(), this.invalidateBoards()]); + } catch (e) { + throw new Error(`Error while adding board ${dto.name}: ${e}`); + } + }; + + deleteBoard = async (boardId: number): Promise => { + try { + await boardApi.deleteBoard(boardId); + } catch (e) { + console.log(`Failed to delete board: ${e}`); + } + + this.invalidateBoards(); + }; + + changeBoardSortOrder = async ({ + boardId, + newSortOrder, + }: { + boardId: number; + newSortOrder: number; + }): Promise => { + const dto = new UpdateBoardDto({ sortOrder: newSortOrder }); + + try { + await boardApi.updateBoard({ dto, boardId }); + } catch (error) { + console.error(`Failed to change board sortOrder: ${error}`); + } finally { + this.invalidateBoards(); + } + }; +} + +export const boardApiUtil = new BoardApiUtil(); diff --git a/frontend/src/app/api/DadataApi/DadataApi.ts b/frontend/src/app/api/DadataApi/DadataApi.ts new file mode 100644 index 0000000..32e466d --- /dev/null +++ b/frontend/src/app/api/DadataApi/DadataApi.ts @@ -0,0 +1,29 @@ +import type { DadataBankRequisitesSuggestion, DadataOrgRequisitesSuggestion } from '@/shared'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; + +export class DadataApi { + // proxy on https://dadata.ru/api/suggest/bank/ + getBankRequisites = async (query: string): Promise => { + const response = await baseApi.get(ApiRoutes.GET_BANK_REQUISITES, { + params: { + query, + }, + }); + + return response.data; + }; + + // proxy on https://dadata.ru/api/suggest/party/ + getOrgRequisites = async (query: string): Promise => { + const response = await baseApi.get(ApiRoutes.GET_ORG_REQUISITES, { + params: { + query, + }, + }); + + return response.data; + }; +} + +export const dadataApi = new DadataApi(); diff --git a/frontend/src/app/api/EntityTypeApi/EntityTypeApi.ts b/frontend/src/app/api/EntityTypeApi/EntityTypeApi.ts new file mode 100644 index 0000000..162d428 --- /dev/null +++ b/frontend/src/app/api/EntityTypeApi/EntityTypeApi.ts @@ -0,0 +1,67 @@ +import { type EtSectionBuilderModel } from '@/modules/builder'; +import { Field, FieldGroup, type ProjectFieldsSettings } from '@/modules/fields'; +import { EntityType, UrlTemplateUtil } from '@/shared'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import { type UpdateEntityTypeDto } from '../dtos/EntityType/UpdateEntityTypeDto'; +import { type UpdateEntityTypeFieldsModel } from '../dtos/EntityType/UpdateEntityTypeFieldsModel'; + +class EntityTypeApi { + getEntityTypes = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_ENTITY_TYPES); + + return EntityType.fromDtos(response.data); + }; + + getEntityType = async (id: number): Promise => { + const response = await baseApi.get(UrlTemplateUtil.toPath(ApiRoutes.GET_ENTITY_TYPE, { id })); + + return EntityType.fromDto(response.data); + }; + + createEntityType = async (data: EtSectionBuilderModel): Promise => { + const response = await baseApi.post(ApiRoutes.ADD_ENTITY_TYPE, data); + + return EntityType.fromDto(response.data); + }; + + deleteEntityType = async (id: number): Promise => { + await baseApi.delete(UrlTemplateUtil.toPath(ApiRoutes.DELETE_ENTITY_TYPE, { id })); + }; + + updateEntityType = async (dto: UpdateEntityTypeDto): Promise => { + const response = await baseApi.put( + UrlTemplateUtil.toPath(ApiRoutes.UPDATE_ENTITY_TYPE, { id: dto.id }), + dto + ); + + return EntityType.fromDto(response.data); + }; + + updateEntityTypeFields = async (model: UpdateEntityTypeFieldsModel): Promise => { + const response = await baseApi.put( + UrlTemplateUtil.toPath(ApiRoutes.UPDATE_ENTITY_TYPE_FIELDS, { id: model.entityTypeId }), + { + fieldGroups: FieldGroup.toDtos(model.fieldGroups), + fields: Field.toDtos(model.fields), + } + ); + + return EntityType.fromDto(response.data); + }; + + updateFieldsSettings = async ({ + entityTypeId, + fieldsSettings, + }: { + entityTypeId: number; + fieldsSettings: ProjectFieldsSettings; + }): Promise => { + await baseApi.put( + UrlTemplateUtil.toPath(ApiRoutes.UPDATE_FIELDS_SETTINGS, { entityTypeId }), + fieldsSettings + ); + }; +} + +export const entityTypeApi = new EntityTypeApi(); diff --git a/frontend/src/app/api/FeatureApi/FeatureApi.ts b/frontend/src/app/api/FeatureApi/FeatureApi.ts new file mode 100644 index 0000000..b45111b --- /dev/null +++ b/frontend/src/app/api/FeatureApi/FeatureApi.ts @@ -0,0 +1,13 @@ +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import { type FeatureDto } from '../dtos/Builder/FeatureDto'; + +class FeatureApi { + getFeatures = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_FEATURES); + + return response.data; + }; +} + +export const featureApi = new FeatureApi(); diff --git a/frontend/src/app/api/FeedbackApi/FeedbackApi.ts b/frontend/src/app/api/FeedbackApi/FeedbackApi.ts new file mode 100644 index 0000000..f50f0e8 --- /dev/null +++ b/frontend/src/app/api/FeedbackApi/FeedbackApi.ts @@ -0,0 +1,17 @@ +import { MailingApiRoutes } from '@/modules/mailing'; +import { baseApi } from '../BaseApi/BaseApi'; +import { SendFeedbackDto, type FeedbackPayload, type FeedbackType } from '../dtos'; + +class FeedbackApi { + sendFeedback = async ({ + type, + payload, + }: { + type: FeedbackType; + payload: FeedbackPayload; + }): Promise => { + await baseApi.post(MailingApiRoutes.SEND_FEEDBACK, new SendFeedbackDto({ type, payload })); + }; +} + +export const feedbackApi = new FeedbackApi(); diff --git a/frontend/src/app/api/FileApi/FileApi.ts b/frontend/src/app/api/FileApi/FileApi.ts new file mode 100644 index 0000000..ea076c8 --- /dev/null +++ b/frontend/src/app/api/FileApi/FileApi.ts @@ -0,0 +1,66 @@ +import { FileInfo, type Nullable } from '@/shared'; +import { UrlTemplateUtil } from '@/shared/lib/utils/UrlTemplateUtil'; +import fileDownload from 'js-file-download'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; + +export interface FileUploadResult { + key: string; + id: string; + downloadUrl: string; + previewUrl: Nullable; +} + +class FileApi { + getFileInfo = async (fileId: string): Promise => { + const response = await baseApi.get(UrlTemplateUtil.toPath(ApiRoutes.GET_FILE_INFO, { fileId })); + + return FileInfo.fromResultDto(response.data); + }; + + uploadFiles = async (formData: FormData): Promise => { + const response = await baseApi.post(ApiRoutes.UPLOAD_FILES, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return response.data; + }; + + downloadFile = async ({ url, fileName }: { url: string; fileName: string }): Promise => { + const response = await baseApi.get(url, { + responseType: 'blob', + }); + + fileDownload(response.data, fileName); + }; + + getMediaBlobObjectUrl = async (url: string): Promise> => { + const result = await baseApi.get(url, { + responseType: 'blob', + }); + + if (result) return URL.createObjectURL(result.data); + + return null; + }; + + deleteFile = async (fileId: string): Promise => { + await baseApi.delete(UrlTemplateUtil.toPath(ApiRoutes.DELETE_FILE, { id: fileId })); + }; + + deleteFileLink = async (id: number): Promise => { + await baseApi.delete(UrlTemplateUtil.toPath(ApiRoutes.DELETE_FILE_LINK, { id })); + }; + + deleteFileLinks = async (ids: number[]): Promise => { + await baseApi.delete(ApiRoutes.DELETE_FILE_LINKS, { + params: { + ids: ids.join(','), + }, + }); + }; +} + +export const fileApi = new FileApi(); diff --git a/frontend/src/app/api/FileApi/queries/useGetFileInfos.ts b/frontend/src/app/api/FileApi/queries/useGetFileInfos.ts new file mode 100644 index 0000000..1765e42 --- /dev/null +++ b/frontend/src/app/api/FileApi/queries/useGetFileInfos.ts @@ -0,0 +1,15 @@ +import { useQueries } from '@tanstack/react-query'; +import { APP_QUERY_KEYS } from '../../AppQueryKeys'; +import { fileApi } from '../FileApi'; + +export const useGetFileInfos = (fileIds: string[]) => + useQueries({ + queries: fileIds.map(fileId => ({ + queryKey: APP_QUERY_KEYS.fileInfoById(fileId), + queryFn: () => fileApi.getFileInfo(fileId), + })), + combine: res => ({ + data: res.map(r => r.data), + isLoading: res.some(result => result.isPending), + }), + }); diff --git a/frontend/src/app/api/FormApi/FormApi.ts b/frontend/src/app/api/FormApi/FormApi.ts new file mode 100644 index 0000000..32b9563 --- /dev/null +++ b/frontend/src/app/api/FormApi/FormApi.ts @@ -0,0 +1,47 @@ +import { envUtil, SiteFormResult } from '@/shared'; +import axios from 'axios'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import type { SendContactUsFormDto, SiteFormDataDto, SiteFormResultDto } from '../dtos'; + +class FormApi { + sendContactUsForm = async (dto: SendContactUsFormDto): Promise => { + await baseApi.post(ApiRoutes.SEND_CONTACT_US_FORM, dto); + }; + + sendRequestSetupHeadlessForm = async (dto: SiteFormDataDto): Promise => { + const response = await axios.post(envUtil.requestSetupHeadlessFormUrl, dto); + + return SiteFormResult.fromDto(response.data); + }; + + sendBpmnRequestForm = async (dto: SiteFormDataDto): Promise => { + const response = await axios.post( + envUtil.submitRequestBpmnWorkspaceFormUrl, + dto + ); + + return SiteFormResult.fromDto(response.data); + }; + + sendAdditionalStorageRequestForm = async (dto: SiteFormDataDto): Promise => { + const response = await axios.post( + envUtil.submitRequestAdditionalStorageWorkspaceFormUrl, + dto + ); + + return SiteFormResult.fromDto(response.data); + }; + + // This is is a new headless mywork form that is used to generate invoice + sendMyworkInvoiceForm = async (dto: SiteFormDataDto): Promise => { + const response = await axios.post( + envUtil.submitRequestMyworkInvoiceWorkspaceFormUrl, + dto + ); + + return SiteFormResult.fromDto(response.data); + }; +} + +export const formApi = new FormApi(); diff --git a/frontend/src/app/api/FrontendObjectsApi/FrontendObjectsApi.ts b/frontend/src/app/api/FrontendObjectsApi/FrontendObjectsApi.ts new file mode 100644 index 0000000..3957808 --- /dev/null +++ b/frontend/src/app/api/FrontendObjectsApi/FrontendObjectsApi.ts @@ -0,0 +1,31 @@ +import { FrontendObject, UrlTemplateUtil, type Nullable } from '@/shared'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import type { CreateFrontendObjectDto } from '../dtos'; + +class FrontendObjectsApi { + // null – object not found by the key + getFrontendObject = async ( + key: string + ): Promise>> => { + const response = await baseApi.get( + UrlTemplateUtil.toPath(ApiRoutes.GET_FRONTEND_OBJECT, { key }) + ); + + return response.data ? FrontendObject.fromDto(response.data) : null; + }; + + upsertFrontendObject = async ( + dto: CreateFrontendObjectDto + ): Promise> => { + const response = await baseApi.post(ApiRoutes.UPSERT_FRONTEND_OBJECT, dto); + + return FrontendObject.fromDto(response.data); + }; + + deleteFrontendObject = async (key: string): Promise => { + await baseApi.delete(UrlTemplateUtil.toPath(ApiRoutes.DELETE_FRONTEND_OBJECT, { key })); + }; +} + +export const frontendObjectsApi = new FrontendObjectsApi(); diff --git a/frontend/src/app/api/GeneralSettingsApi/GeneralSettingsApi.ts b/frontend/src/app/api/GeneralSettingsApi/GeneralSettingsApi.ts new file mode 100644 index 0000000..669326f --- /dev/null +++ b/frontend/src/app/api/GeneralSettingsApi/GeneralSettingsApi.ts @@ -0,0 +1,60 @@ +import { Account, AccountSettings } from '@/modules/settings'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import type { UpdateAccountSettingsDto } from '../dtos'; + +class GeneralSettingsApi { + getAccount = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_ACCOUNT); + + return Account.fromDto(response.data); + }; + + searchAccounts = async (search: string): Promise => { + const response = await baseApi.get(ApiRoutes.SEARCH_ACCOUNTS, { params: { search } }); + + return Account.fromDtos(response.data); + }; + + getAccountSettings = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_ACCOUNT_SETTINGS); + + return AccountSettings.fromDto(response.data); + }; + + updateAccountSettings = async (dto: UpdateAccountSettingsDto): Promise => { + const response = await baseApi.put(ApiRoutes.UPDATE_ACCOUNT_SETTINGS, dto); + + return AccountSettings.fromDto(response.data); + }; + + uploadAccountLogo = async (formData: FormData): Promise => { + const response = await baseApi.post(ApiRoutes.UPLOAD_ACCOUNT_LOGO, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); + + return Account.fromDto(response.data); + }; + + removeAccountLogo = async (): Promise => { + const response = await baseApi.delete(ApiRoutes.REMOVE_ACCOUNT_LOGO); + + return Account.fromDto(response.data); + }; + + getDemoDataExists = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_DEMO_DATA_EXISTS); + + return Boolean(response.data); + }; + + deleteDemoData = async (): Promise => { + const response = await baseApi.delete(ApiRoutes.DELETE_DEMO_DATA); + + return AccountSettings.fromDto(response.data); + }; +} + +export const generalSettingsApi = new GeneralSettingsApi(); diff --git a/frontend/src/app/api/IdentityApi/IdentityApi.ts b/frontend/src/app/api/IdentityApi/IdentityApi.ts new file mode 100644 index 0000000..429fc7e --- /dev/null +++ b/frontend/src/app/api/IdentityApi/IdentityApi.ts @@ -0,0 +1,32 @@ +import { UrlTemplateUtil } from '@/shared/lib/utils/UrlTemplateUtil'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; + +export enum SequenceName { + FIELD = 'field_id_seq', + FIELD_GROUP = 'field_group_id_seq', + FIELD_OPTION = 'field_option_id_seq', +} + +export interface IdentityPool { + name: string; + values: number[]; +} + +class IdentityApi { + getAllIdPools = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_ALL_IDENTITY_POOLS); + + return response.data; + }; + + getIdPoolValues = async (name: SequenceName): Promise => { + const response = await baseApi.get( + UrlTemplateUtil.toPath(ApiRoutes.GET_IDENTITY_POOL, { name }) + ); + + return response.data; + }; +} + +export const identityApi = new IdentityApi(); diff --git a/frontend/src/app/api/StageApi/StageApi.ts b/frontend/src/app/api/StageApi/StageApi.ts new file mode 100644 index 0000000..6f61b32 --- /dev/null +++ b/frontend/src/app/api/StageApi/StageApi.ts @@ -0,0 +1,76 @@ +import { Stage, UrlTemplateUtil } from '@/shared'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import { CreateStageDto, UpdateStageDto } from '../dtos'; + +class StageApi { + createStage = async ({ + boardId, + dto, + }: { + boardId: number; + dto: CreateStageDto; + }): Promise => { + const response = await baseApi.post( + UrlTemplateUtil.toPath(ApiRoutes.CREATE_STAGE, { boardId }), + dto + ); + + return Stage.fromDto(response.data); + }; + + getBoardStages = async (boardId: number): Promise => { + const response = await baseApi.get( + UrlTemplateUtil.toPath(ApiRoutes.GET_STAGES, { boardId: boardId }) + ); + + return Stage.fromDtos(response.data); + }; + + getStageById = async ({ + stageId, + boardId, + }: { + stageId: number; + boardId: number; + }): Promise => { + const response = await baseApi.get( + UrlTemplateUtil.toPath(ApiRoutes.GET_STAGE, { boardId, stageId }) + ); + + return Stage.fromDto(response.data); + }; + + updateStage = async ({ + boardId, + stageId, + dto, + }: { + boardId: number; + stageId: number; + dto: UpdateStageDto; + }): Promise => { + const response = await baseApi.patch( + UrlTemplateUtil.toPath(ApiRoutes.UPDATE_STAGE, { boardId, stageId }), + dto + ); + + return Stage.fromDto(response.data); + }; + + deleteStage = async ({ + boardId, + stageId, + newStageId, + }: { + boardId: number; + stageId: number; + newStageId: number; + }): Promise => { + await baseApi.delete(UrlTemplateUtil.toPath(ApiRoutes.DELETE_STAGE, { boardId, stageId }), { + params: { newStageId }, + }); + }; +} + +export const stageApi = new StageApi(); diff --git a/frontend/src/app/api/StageApi/StageApiUtil.ts b/frontend/src/app/api/StageApi/StageApiUtil.ts new file mode 100644 index 0000000..1f45096 --- /dev/null +++ b/frontend/src/app/api/StageApi/StageApiUtil.ts @@ -0,0 +1,149 @@ +import { CreateStageDto, UpdateStageDto } from '@/app'; +import { queryClient } from '@/index'; +import type { Nullable, Optional, Stage } from '@/shared'; +import { + queryOptions, + skipToken, + useQueries, + useQuery, + type UseQueryResult, +} from '@tanstack/react-query'; +import { APP_QUERY_KEYS } from '../AppQueryKeys'; +import { stageApi } from './StageApi'; + +const STAGES_STALE_TIME = 10 * 60 * 1000; + +// this class encapsulate all stages-related queries and helpers +// this is in fact static class, but do not refactor methods to static, +// because this will violate the rules of hooks +class StageApiUtil { + private _getStageQueryOptions = ({ + stageId, + boardId, + enabled, + }: { + stageId: Optional>; + boardId: Optional>; + enabled?: boolean; + }) => { + return queryOptions({ + queryKey: APP_QUERY_KEYS.stage({ stageId, boardId }), + queryFn: + stageId && boardId + ? async (): Promise => await stageApi.getStageById({ stageId, boardId }) + : skipToken, + staleTime: STAGES_STALE_TIME, + enabled, + }); + }; + + private _getStagesByBoardIdQueryOptions = ({ + boardId, + enabled, + }: { + boardId: Optional>; + enabled?: boolean; + }) => { + return queryOptions({ + queryKey: APP_QUERY_KEYS.stagesByBoardId(boardId), + queryFn: boardId + ? async (): Promise => await stageApi.getBoardStages(boardId) + : skipToken, + staleTime: STAGES_STALE_TIME, + enabled, + }); + }; + + useGetStage = ({ + stageId, + boardId, + enabled, + }: { + stageId: Optional>; + boardId: Optional>; + enabled?: boolean; + }): UseQueryResult => { + return useQuery(this._getStageQueryOptions({ stageId, boardId, enabled })); + }; + + useGetStagesByBoardId = ({ + boardId, + enabled, + }: { + boardId: Optional>; + enabled?: boolean; + }): UseQueryResult => { + return useQuery(this._getStagesByBoardIdQueryOptions({ boardId, enabled })); + }; + + useGetStagesByBoardIds = (boardIds: number[]): Stage[] => { + return useQueries({ + queries: boardIds.map(boardId => this._getStagesByBoardIdQueryOptions({ boardId })), + combine: res => { + if (res.some(r => r.isLoading)) return []; + + const data: Stage[] = []; + + res.forEach(r => { + if (r.data) data.push(...r.data); + }); + + return data; + }, + }); + }; + + getStagesByBoardId = async (boardId: Optional>): Promise => { + return await queryClient.ensureQueryData(this._getStagesByBoardIdQueryOptions({ boardId })); + }; + + invalidateStages = async (): Promise => { + await queryClient.refetchQueries({ queryKey: APP_QUERY_KEYS.stages }); + }; + + createStage = async ({ + boardId, + dto, + }: { + boardId: number; + dto: CreateStageDto; + }): Promise => { + const stage = await stageApi.createStage({ boardId, dto }); + + this.invalidateStages(); + + return stage; + }; + + updateStage = async ({ + boardId, + stageId, + dto, + }: { + boardId: number; + stageId: number; + dto: UpdateStageDto; + }): Promise => { + const stage = await stageApi.updateStage({ boardId, stageId, dto }); + + this.invalidateStages(); + + return stage; + }; + + deleteStage = async ({ + boardId, + stageId, + newStageId, + }: { + boardId: number; + stageId: number; + newStageId: number; + }): Promise => { + await stageApi.deleteStage({ boardId, stageId, newStageId }); + + this.invalidateStages(); + }; +} + +export const stageApiUtil = new StageApiUtil(); diff --git a/frontend/src/app/api/SubscriptionApi/SubscriptionApi.ts b/frontend/src/app/api/SubscriptionApi/SubscriptionApi.ts new file mode 100644 index 0000000..d3b61c3 --- /dev/null +++ b/frontend/src/app/api/SubscriptionApi/SubscriptionApi.ts @@ -0,0 +1,78 @@ +import { UpdateSubscriptionDto } from '@/app'; +import { CurrentDiscount } from '@/modules/settings'; +import { Nullable, UrlTemplateUtil } from '@/shared'; +import { Subscription } from '../../../shared/lib/models/Subscription/Subscription'; +import { SubscriptionPlan } from '../../../shared/lib/models/Subscription/SubscriptionPlan'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; + +class SubscriptionApi { + getSubscription = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_SUBSCRIPTION); + + return Subscription.fromDto(response.data); + }; + + getSubscriptionForAccount = async (id: number): Promise => { + const response = await baseApi.get( + UrlTemplateUtil.toPath(ApiRoutes.GET_SUBSCRIPTION_FOR_ACCOUNT, { accountId: id }) + ); + + return Subscription.fromDto(response.data); + }; + + getSubscriptionPlans = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_SUBSCRIPTION_PLANS); + + return SubscriptionPlan.fromDtos(response.data); + }; + + getCheckoutUrl = async ({ + amount, + priceId, + productId, + couponId, + numberOfUsers, + }: { + numberOfUsers: number; + amount?: number; + priceId?: string; + couponId?: string; + productId?: string; + }): Promise => { + const response = await baseApi.get(ApiRoutes.GET_SUBSCRIPTION_CHECKOUT_URL, { + params: { productId, amount, priceId, couponId, numberOfUsers }, + }); + + return response.data; + }; + + getPortalUrl = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_SUBSCRIPTION_PORTAL_URL); + + return response.data; + }; + + getCurrentDiscount = async (): Promise> => { + const response = await baseApi.get(ApiRoutes.GET_CURRENT_DISCOUNT); + + return response.data ? CurrentDiscount.fromDto(response.data) : null; + }; + + updateSubscription = async ({ + accountId, + dto, + }: { + accountId: number; + dto: UpdateSubscriptionDto; + }): Promise => { + const response = await baseApi.patch( + UrlTemplateUtil.toPath(ApiRoutes.UPDATE_SUBSCRIPTION, { accountId }), + dto + ); + + return Subscription.fromDto(response.data); + }; +} + +export const subscriptionApi = new SubscriptionApi(); diff --git a/frontend/src/app/api/SubscriptionApi/queries/useGetSubscriptionForAccount.ts b/frontend/src/app/api/SubscriptionApi/queries/useGetSubscriptionForAccount.ts new file mode 100644 index 0000000..cd774f9 --- /dev/null +++ b/frontend/src/app/api/SubscriptionApi/queries/useGetSubscriptionForAccount.ts @@ -0,0 +1,11 @@ +import { Optional } from '@/shared'; +import { useQuery } from '@tanstack/react-query'; +import { APP_QUERY_KEYS } from '../../AppQueryKeys'; +import { subscriptionApi } from '../SubscriptionApi'; + +export const useGetSubscriptionForAccount = (accountId: Optional) => + useQuery({ + queryKey: APP_QUERY_KEYS.subscriptionByAccountId(accountId ?? -1), + queryFn: () => subscriptionApi.getSubscriptionForAccount(accountId ?? -1), + enabled: Boolean(accountId), + }); diff --git a/frontend/src/app/api/SubscriptionApi/queries/useUpdateSubscription.ts b/frontend/src/app/api/SubscriptionApi/queries/useUpdateSubscription.ts new file mode 100644 index 0000000..aaac1ad --- /dev/null +++ b/frontend/src/app/api/SubscriptionApi/queries/useUpdateSubscription.ts @@ -0,0 +1,23 @@ +import { Subscription } from '@/shared'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { APP_QUERY_KEYS } from '../../AppQueryKeys'; +import { UpdateSubscriptionDto } from '../../dtos'; +import { subscriptionApi } from '../SubscriptionApi'; + +export const useUpdateSubscription = (accountId: number) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationKey: APP_QUERY_KEYS.subscriptionByAccountId(accountId), + mutationFn: (dto: UpdateSubscriptionDto) => + subscriptionApi.updateSubscription({ accountId, dto: dto }), + onMutate: async (): Promise => { + await queryClient.cancelQueries({ + queryKey: APP_QUERY_KEYS.subscriptionByAccountId(accountId), + }); + }, + onSuccess: async (subscription: Subscription): Promise => { + queryClient.setQueryData(APP_QUERY_KEYS.subscriptionByAccountId(accountId), subscription); + }, + }); +}; diff --git a/frontend/src/app/api/UserApi/UserApi.ts b/frontend/src/app/api/UserApi/UserApi.ts new file mode 100644 index 0000000..3f69e31 --- /dev/null +++ b/frontend/src/app/api/UserApi/UserApi.ts @@ -0,0 +1,78 @@ +import { UrlTemplateUtil, User } from '@/shared'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import type { ChangeUserPasswordDto, CreateUserDto, UpdateUserDto, UserDto } from '../dtos'; + +class UserApi { + getUserById = async (userId: number): Promise => { + const response = await baseApi.get(UrlTemplateUtil.toPath(ApiRoutes.GET_USER, { id: userId })); + + return User.fromDto(response.data); + }; + + getUsers = async (): Promise => { + const response = await baseApi.get(ApiRoutes.GET_USERS); + + return User.fromDtos(response.data); + }; + + addUser = async (dto: CreateUserDto): Promise => { + const response = await baseApi.post(ApiRoutes.ADD_USER, dto); + + return User.fromDto(response.data); + }; + + updateUser = async ({ id, dto }: { id: number; dto: UpdateUserDto }): Promise => { + const response = await baseApi.put(UrlTemplateUtil.toPath(ApiRoutes.UPDATE_USER, { id }), dto); + + return User.fromDto(response.data); + }; + + deleteUser = async ({ + userId, + newUserId, + }: { + userId: number; + newUserId?: number; + }): Promise => { + await baseApi.delete(UrlTemplateUtil.toPath(ApiRoutes.DELETE_USER, { id: userId }), { + params: { newUserId }, + }); + }; + + uploadUserAvatar = async ({ + userId, + formData, + }: { + userId: number; + formData: FormData; + }): Promise => { + const response = await baseApi.post( + UrlTemplateUtil.toPath(ApiRoutes.UPLOAD_USER_AVATAR, { id: userId }), + formData, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + } + ); + + return User.fromDto(response.data as UserDto); + }; + + removeUserAvatar = async (userId: number): Promise => { + const response = await baseApi.delete( + UrlTemplateUtil.toPath(ApiRoutes.REMOVE_USER_AVATAR, { id: userId }) + ); + + return User.fromDto(response.data); + }; + + changeUserPassword = async (dto: ChangeUserPasswordDto): Promise => { + const response = await baseApi.post(ApiRoutes.CHANGE_USER_PASSWORD, dto); + + return response.data; + }; +} + +export const userApi = new UserApi(); diff --git a/frontend/src/app/api/UserProfileApi/UserProfileApi.ts b/frontend/src/app/api/UserProfileApi/UserProfileApi.ts new file mode 100644 index 0000000..baf8f44 --- /dev/null +++ b/frontend/src/app/api/UserProfileApi/UserProfileApi.ts @@ -0,0 +1,28 @@ +import { UrlTemplateUtil } from '@/shared/lib/utils/UrlTemplateUtil'; +import { UserProfile } from '../../../shared/lib/models/Profile/UserProfile'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; +import { type UpdateUserProfileDto } from '../dtos/Profile/UpdateUserProfileDto'; +import { type UserProfileDto } from '../dtos/Profile/UserProfileDto'; + +class UserProfileApi { + getUserProfile = async (id: number): Promise => { + const response = await baseApi.get(UrlTemplateUtil.toPath(ApiRoutes.GET_USER_PROFILE, { id })); + + const dto: UserProfileDto = response.data; + + return new UserProfile(dto); + }; + + updateUserProfile = async ({ + id, + dto, + }: { + id: number; + dto: UpdateUserProfileDto; + }): Promise => { + await baseApi.patch(UrlTemplateUtil.toPath(ApiRoutes.UPDATE_USER_PROFILE, { id }), dto); + }; +} + +export const userProfileApi = new UserProfileApi(); diff --git a/frontend/src/app/api/UserProfileApi/helpers/invalidateUserProfilesInCache.ts b/frontend/src/app/api/UserProfileApi/helpers/invalidateUserProfilesInCache.ts new file mode 100644 index 0000000..015ec95 --- /dev/null +++ b/frontend/src/app/api/UserProfileApi/helpers/invalidateUserProfilesInCache.ts @@ -0,0 +1,5 @@ +import { queryClient } from '@/index'; +import { APP_QUERY_KEYS } from '../../AppQueryKeys'; + +export const invalidateUserProfilesInCache = () => + queryClient.invalidateQueries({ queryKey: APP_QUERY_KEYS.userProfiles }); diff --git a/frontend/src/app/api/UserProfileApi/queries/useGetUserProfiles.ts b/frontend/src/app/api/UserProfileApi/queries/useGetUserProfiles.ts new file mode 100644 index 0000000..db995db --- /dev/null +++ b/frontend/src/app/api/UserProfileApi/queries/useGetUserProfiles.ts @@ -0,0 +1,19 @@ +import { useQueries } from '@tanstack/react-query'; +import { APP_QUERY_KEYS } from '../../AppQueryKeys'; +import { userProfileApi } from '../UserProfileApi'; + +export const useGetUserProfiles = ({ userIds }: { userIds: number[] }) => { + return useQueries({ + queries: userIds.map(userId => ({ + queryKey: APP_QUERY_KEYS.userProfile(userId), + queryFn: () => userProfileApi.getUserProfile(userId), + })), + combine: res => ({ + data: res.map((result, idx) => ({ + userId: userIds[idx]!, + settings: result.data, + })), + isLoading: res.some(result => result.isPending), + }), + }); +}; diff --git a/frontend/src/app/api/UtilityApi/UtilityApi.ts b/frontend/src/app/api/UtilityApi/UtilityApi.ts new file mode 100644 index 0000000..6d2234e --- /dev/null +++ b/frontend/src/app/api/UtilityApi/UtilityApi.ts @@ -0,0 +1,17 @@ +import { baseApi } from '../BaseApi/BaseApi'; + +const IP_REGISTRY = 'https://api.ipregistry.co'; + +class UtilityApi { + getCountryCode = async (): Promise => { + const response = await baseApi.get(IP_REGISTRY, { + params: { + key: import.meta.env.VITE_IP_REGISTRY_API_KEY, + }, + }); + + return response.data.location.country.code.toLowerCase(); + }; +} + +export const utilityApi = new UtilityApi(); diff --git a/frontend/src/app/api/VersionApi/VersionApi.ts b/frontend/src/app/api/VersionApi/VersionApi.ts new file mode 100644 index 0000000..53fda07 --- /dev/null +++ b/frontend/src/app/api/VersionApi/VersionApi.ts @@ -0,0 +1,17 @@ +import { type Nullable, Version } from '@/shared'; +import { ApiRoutes } from '../ApiRoutes'; +import { baseApi } from '../BaseApi/BaseApi'; + +class VersionApi { + getLatestFrontendVersion = async (currentVersion: string): Promise> => { + const response = await baseApi.get(ApiRoutes.GET_LATEST_FRONTEND_VERSION, { + params: { + currentVersion, + }, + }); + + return response.data ? Version.fromDto(response.data) : null; + }; +} + +export const versionApi = new VersionApi(); diff --git a/frontend/src/app/api/VersionApi/queries/useGetLatestFrontendVersionPolling.ts b/frontend/src/app/api/VersionApi/queries/useGetLatestFrontendVersionPolling.ts new file mode 100644 index 0000000..0917655 --- /dev/null +++ b/frontend/src/app/api/VersionApi/queries/useGetLatestFrontendVersionPolling.ts @@ -0,0 +1,12 @@ +import { useQuery } from '@tanstack/react-query'; +import { APP_QUERY_KEYS } from '../../AppQueryKeys'; +import { versionApi } from '../VersionApi'; + +export const useGetLatestFrontendVersionPolling = (currentVersion: string) => + useQuery({ + queryKey: APP_QUERY_KEYS.latestFrontendVersion(currentVersion), + queryFn: () => versionApi.getLatestFrontendVersion(currentVersion), + // refetch every 10 minutes + refetchInterval: 10 * 60 * 1000, + refetchOnWindowFocus: true, + }); diff --git a/frontend/src/app/api/dtos/Board/BoardDto.ts b/frontend/src/app/api/dtos/Board/BoardDto.ts new file mode 100644 index 0000000..d272b5d --- /dev/null +++ b/frontend/src/app/api/dtos/Board/BoardDto.ts @@ -0,0 +1,16 @@ +import { type Nullable } from '@/shared'; +import { type BoardType } from '../../../../shared/lib/models/Board/BoardType'; +import { type UserRights } from '../../../../shared/lib/models/Permission/UserRights'; + +export interface BoardDto { + id: number; + name: string; + type: BoardType; + recordId: Nullable; + isSystem: boolean; + ownerId: Nullable; + participantIds: Nullable; + sortOrder: number; + taskBoardId: Nullable; + userRights: UserRights; +} diff --git a/frontend/src/app/api/dtos/Board/CreateBoardDto.ts b/frontend/src/app/api/dtos/Board/CreateBoardDto.ts new file mode 100644 index 0000000..9447c34 --- /dev/null +++ b/frontend/src/app/api/dtos/Board/CreateBoardDto.ts @@ -0,0 +1,38 @@ +import type { Nullable } from '@/shared'; +import { BoardType } from '../../../../shared'; + +export class CreateBoardDto { + name: string; + // if not provided, will be calculated on backend + sortOrder?: number; + type: BoardType; + recordId: Nullable; + + constructor({ name, sortOrder, type, recordId }: CreateBoardDto) { + this.name = name; + this.sortOrder = sortOrder; + this.type = type; + this.recordId = recordId; + } + + static forEntityType({ + name, + sortOrder, + entityTypeId, + }: { + name: string; + sortOrder?: number; + entityTypeId: number; + }): CreateBoardDto { + return new CreateBoardDto({ + name, + sortOrder, + type: BoardType.ENTITY_TYPE, + recordId: entityTypeId, + }); + } + + static forTasks({ name, sortOrder }: { name: string; sortOrder: number }): CreateBoardDto { + return new CreateBoardDto({ name, sortOrder, type: BoardType.TASK, recordId: null }); + } +} diff --git a/frontend/src/app/api/dtos/Board/UpdateBoardDto.ts b/frontend/src/app/api/dtos/Board/UpdateBoardDto.ts new file mode 100644 index 0000000..f6382a7 --- /dev/null +++ b/frontend/src/app/api/dtos/Board/UpdateBoardDto.ts @@ -0,0 +1,9 @@ +export class UpdateBoardDto { + name: string; + sortOrder: number; + participantIds: number[]; + + constructor(data: Partial) { + Object.assign(this, data); + } +} diff --git a/frontend/src/app/api/dtos/Builder/FeatureDto.ts b/frontend/src/app/api/dtos/Builder/FeatureDto.ts new file mode 100644 index 0000000..d67f308 --- /dev/null +++ b/frontend/src/app/api/dtos/Builder/FeatureDto.ts @@ -0,0 +1,15 @@ +import { type FeatureCode } from '../../../../shared/lib/models/Feature/FeatureCode'; + +export class FeatureDto { + id: number; + name: string; + code: FeatureCode; + isEnabled: boolean; + + constructor({ id, name, code, isEnabled }: FeatureDto) { + this.id = id; + this.name = name; + this.code = code; + this.isEnabled = isEnabled; + } +} diff --git a/frontend/src/app/api/dtos/CreateContactAndLeadDto.ts b/frontend/src/app/api/dtos/CreateContactAndLeadDto.ts new file mode 100644 index 0000000..aad0cf9 --- /dev/null +++ b/frontend/src/app/api/dtos/CreateContactAndLeadDto.ts @@ -0,0 +1,13 @@ +import type { Nullable } from '@/shared'; + +export class CreateContactAndLeadDto { + contactTypeId: number; + leadTypeId: Nullable; + leadBoardId: Nullable; + + constructor({ contactTypeId, leadTypeId, leadBoardId }: CreateContactAndLeadDto) { + this.contactTypeId = contactTypeId; + this.leadTypeId = leadTypeId; + this.leadBoardId = leadBoardId; + } +} diff --git a/frontend/src/app/api/dtos/Entity/EntityDto.ts b/frontend/src/app/api/dtos/Entity/EntityDto.ts new file mode 100644 index 0000000..39d0c26 --- /dev/null +++ b/frontend/src/app/api/dtos/Entity/EntityDto.ts @@ -0,0 +1,25 @@ +import type { EntityLinkDto } from '@/app'; +import type { FieldValueDto } from '@/modules/fields'; +import type { ChatDto } from '@/modules/multichat'; +import type { ExternalEntity, Nullable, UserRights } from '@/shared'; + +export interface EntityDto { + id: number; + name: string; + createdBy: number; + createdAt: string; + entityTypeId: number; + userRights: UserRights; + boardId: Nullable; + responsibleUserId: number; + stageId: Nullable; + entityLinks: EntityLinkDto[]; + copiedFrom: Nullable; + copiedCount: Nullable; + externalEntities: ExternalEntity[]; + fieldValues: FieldValueDto[]; + lastShipmentDate?: string; + closedAt?: string; + chats?: ChatDto[]; + focused?: boolean; +} diff --git a/frontend/src/app/api/dtos/EntityEvent/ContactInfoDto.ts b/frontend/src/app/api/dtos/EntityEvent/ContactInfoDto.ts new file mode 100644 index 0000000..a924083 --- /dev/null +++ b/frontend/src/app/api/dtos/EntityEvent/ContactInfoDto.ts @@ -0,0 +1,8 @@ +import { type Nullable } from '@/shared'; + +export interface ContactInfoDto { + id: number; + name: string; + phone: Nullable; + email: Nullable; +} diff --git a/frontend/src/app/api/dtos/EntityEvent/EntityEventItemDto.ts b/frontend/src/app/api/dtos/EntityEvent/EntityEventItemDto.ts new file mode 100644 index 0000000..8b20823 --- /dev/null +++ b/frontend/src/app/api/dtos/EntityEvent/EntityEventItemDto.ts @@ -0,0 +1,20 @@ +import { type CallDirection, type CallStatus } from '@/modules/telephony'; +import { type Nullable } from '@/shared'; +import { type EntityInfoDto } from './EntityInfoDto'; + +export interface EntityEventItemDto { + id: number; + sessionId: string; + callId: string; + userId: number; + entityId: number; + direction: CallDirection; + phoneNumber: string; + duration: Nullable; + status: CallStatus; + failureReason: Nullable; + recordUrl: Nullable; + createdAt: string; + entityInfo: EntityInfoDto; + comment?: string; +} diff --git a/frontend/src/app/api/dtos/EntityEvent/EntityInfoDto.ts b/frontend/src/app/api/dtos/EntityEvent/EntityInfoDto.ts new file mode 100644 index 0000000..47ee680 --- /dev/null +++ b/frontend/src/app/api/dtos/EntityEvent/EntityInfoDto.ts @@ -0,0 +1,10 @@ +import { type Nullable } from '@/shared'; +import { type ContactInfoDto } from './ContactInfoDto'; + +export interface EntityInfoDto { + id: number; + name: string; + entityTypeId: number; + hasAccess: boolean; + contact: Nullable; +} diff --git a/frontend/src/app/api/dtos/EntityLink/EntityLinkDto.ts b/frontend/src/app/api/dtos/EntityLink/EntityLinkDto.ts new file mode 100644 index 0000000..772adbe --- /dev/null +++ b/frontend/src/app/api/dtos/EntityLink/EntityLinkDto.ts @@ -0,0 +1,15 @@ +import { type ObjectState } from '@/shared'; + +export class EntityLinkDto { + sourceId: number; + targetId: number; + sortOrder: number; + state: ObjectState; + + constructor({ sourceId, targetId, sortOrder, state }: EntityLinkDto) { + this.sourceId = sourceId; + this.targetId = targetId; + this.sortOrder = sortOrder; + this.state = state; + } +} diff --git a/frontend/src/app/api/dtos/EntityType/EntityTypeDto.ts b/frontend/src/app/api/dtos/EntityType/EntityTypeDto.ts new file mode 100644 index 0000000..969c7e2 --- /dev/null +++ b/frontend/src/app/api/dtos/EntityType/EntityTypeDto.ts @@ -0,0 +1,17 @@ +import type { FieldDto, FieldGroupDto } from '@/modules/fields'; +import type { EntityCategory, EntityTypeLink, FeatureCode, Section } from '@/shared'; + +export interface EntityTypeDto { + id: number; + name: string; + section: Section; + createdAt: string; + sortOrder: number; + fields: FieldDto[]; + featureCodes: FeatureCode[]; + fieldGroups: FieldGroupDto[]; + linkedSchedulerIds: number[]; + entityCategory: EntityCategory; + linkedProductsSectionIds: number[]; + linkedEntityTypes: EntityTypeLink[]; +} diff --git a/frontend/src/app/api/dtos/EntityType/UpdateEntityTypeDto.ts b/frontend/src/app/api/dtos/EntityType/UpdateEntityTypeDto.ts new file mode 100644 index 0000000..ffa84f0 --- /dev/null +++ b/frontend/src/app/api/dtos/EntityType/UpdateEntityTypeDto.ts @@ -0,0 +1,49 @@ +import { type FieldDto, type FieldGroupDto, type ProjectFieldsSettings } from '@/modules/fields'; +import { type TaskFieldCode } from '@/modules/tasks'; +import { type EntityCategory, type EntityTypeLink, type FeatureCode, type Section } from '@/shared'; + +export class UpdateEntityTypeDto { + id: number; + name: string; + entityCategory: EntityCategory; + section: Section; + fieldGroups: FieldGroupDto[]; + fields: FieldDto[]; + linkedEntityTypes: EntityTypeLink[]; + featureCodes: FeatureCode[]; + taskSettingsActiveFields?: TaskFieldCode[]; + linkedProductsSectionIds?: number[]; + linkedSchedulerIds?: number[]; + fieldsSettings?: ProjectFieldsSettings; + sortOrder?: number; + + constructor({ + id, + name, + entityCategory, + section, + fieldGroups, + fields, + linkedEntityTypes, + featureCodes, + taskSettingsActiveFields, + linkedProductsSectionIds, + linkedSchedulerIds, + fieldsSettings, + sortOrder, + }: UpdateEntityTypeDto) { + this.id = id; + this.name = name; + this.entityCategory = entityCategory; + this.section = section; + this.fieldGroups = fieldGroups; + this.fields = fields; + this.linkedEntityTypes = linkedEntityTypes; + this.featureCodes = featureCodes; + this.taskSettingsActiveFields = taskSettingsActiveFields; + this.linkedProductsSectionIds = linkedProductsSectionIds; + this.linkedSchedulerIds = linkedSchedulerIds; + this.fieldsSettings = fieldsSettings; + this.sortOrder = sortOrder; + } +} diff --git a/frontend/src/app/api/dtos/EntityType/UpdateEntityTypeFieldsModel.ts b/frontend/src/app/api/dtos/EntityType/UpdateEntityTypeFieldsModel.ts new file mode 100644 index 0000000..7a3f510 --- /dev/null +++ b/frontend/src/app/api/dtos/EntityType/UpdateEntityTypeFieldsModel.ts @@ -0,0 +1,14 @@ +import { type Field } from '../../../../modules/fields/shared/lib/models/Field/Field'; +import { type FieldGroup } from '../../../../modules/fields/shared/lib/models/FieldGroup/FieldGroup'; + +export class UpdateEntityTypeFieldsModel { + entityTypeId: number; + fieldGroups: FieldGroup[]; + fields: Field[]; + + constructor({ entityTypeId, fieldGroups, fields }: UpdateEntityTypeFieldsModel) { + this.entityTypeId = entityTypeId; + this.fieldGroups = fieldGroups; + this.fields = fields; + } +} diff --git a/frontend/src/app/api/dtos/FeedItem/FeedItemDto.ts b/frontend/src/app/api/dtos/FeedItem/FeedItemDto.ts new file mode 100644 index 0000000..b141b74 --- /dev/null +++ b/frontend/src/app/api/dtos/FeedItem/FeedItemDto.ts @@ -0,0 +1,8 @@ +import { type FeedItemType } from '../../../../modules/card/shared/lib/models/FeedItemType'; + +export interface FeedItemDto { + id: number; + type: FeedItemType; + data: object; + createdAt: string; +} diff --git a/frontend/src/app/api/dtos/Feedback/FeedbackType.ts b/frontend/src/app/api/dtos/Feedback/FeedbackType.ts new file mode 100644 index 0000000..442f8ec --- /dev/null +++ b/frontend/src/app/api/dtos/Feedback/FeedbackType.ts @@ -0,0 +1,4 @@ +export enum FeedbackType { + TRIAL_EXPIRED = 'trial_expired', + USER_LIMIT = 'user_limit', +} diff --git a/frontend/src/app/api/dtos/Feedback/SendFeedbackDto.ts b/frontend/src/app/api/dtos/Feedback/SendFeedbackDto.ts new file mode 100644 index 0000000..2fd2c80 --- /dev/null +++ b/frontend/src/app/api/dtos/Feedback/SendFeedbackDto.ts @@ -0,0 +1,15 @@ +import { type FeedbackType } from './FeedbackType'; +import { type TrialExpiredFeedback } from './TrialExpiredFeedback'; +import { type UserLimitFeedback } from './UserLimitFeedback'; + +export type FeedbackPayload = TrialExpiredFeedback | UserLimitFeedback; + +export class SendFeedbackDto { + type: FeedbackType; + payload: FeedbackPayload; + + constructor({ type, payload }: SendFeedbackDto) { + this.type = type; + this.payload = payload; + } +} diff --git a/frontend/src/app/api/dtos/Feedback/TrialExpiredFeedback.ts b/frontend/src/app/api/dtos/Feedback/TrialExpiredFeedback.ts new file mode 100644 index 0000000..d9d056a --- /dev/null +++ b/frontend/src/app/api/dtos/Feedback/TrialExpiredFeedback.ts @@ -0,0 +1,17 @@ +export class TrialExpiredFeedback { + name: string; + phone: string; + email: string; + userNumber: string; + subscribe: string; + plan: string; + + constructor({ name, phone, email, userNumber, subscribe, plan }: TrialExpiredFeedback) { + this.name = name; + this.phone = phone; + this.email = email; + this.userNumber = userNumber; + this.subscribe = subscribe; + this.plan = plan; + } +} diff --git a/frontend/src/app/api/dtos/Feedback/UserLimitFeedback.ts b/frontend/src/app/api/dtos/Feedback/UserLimitFeedback.ts new file mode 100644 index 0000000..914263b --- /dev/null +++ b/frontend/src/app/api/dtos/Feedback/UserLimitFeedback.ts @@ -0,0 +1,13 @@ +export class UserLimitFeedback { + name: string; + phone: string; + email: string; + userNumber: string; + + constructor({ name, phone, email, userNumber }: UserLimitFeedback) { + this.name = name; + this.phone = phone; + this.email = email; + this.userNumber = userNumber; + } +} diff --git a/frontend/src/app/api/dtos/FileInfoResult/FileInfoResultDto.ts b/frontend/src/app/api/dtos/FileInfoResult/FileInfoResultDto.ts new file mode 100644 index 0000000..5283dfe --- /dev/null +++ b/frontend/src/app/api/dtos/FileInfoResult/FileInfoResultDto.ts @@ -0,0 +1,11 @@ +import type { Nullable } from '@/shared'; + +export interface FileInfoResultDto { + id: string; + fileName: string; + fileSize: number; + mimeType: string; + downloadUrl: Nullable; + previewUrl: Nullable; + createdAt: string; +} diff --git a/frontend/src/app/api/dtos/FileLink/FileLinkDto.ts b/frontend/src/app/api/dtos/FileLink/FileLinkDto.ts new file mode 100644 index 0000000..a961918 --- /dev/null +++ b/frontend/src/app/api/dtos/FileLink/FileLinkDto.ts @@ -0,0 +1,13 @@ +import { type Nullable } from '@/shared'; + +export interface FileLinkDto { + id: number; + fileId: string; + fileName: string; + fileSize: number; + fileType: string; + downloadUrl: string; + previewUrl: Nullable; + createdAt: string; + createdBy: number; +} diff --git a/frontend/src/app/api/dtos/Form/SendContactUsFormDto.ts b/frontend/src/app/api/dtos/Form/SendContactUsFormDto.ts new file mode 100644 index 0000000..50d6c0b --- /dev/null +++ b/frontend/src/app/api/dtos/Form/SendContactUsFormDto.ts @@ -0,0 +1,17 @@ +import type { Nullable } from '@/shared'; + +export class SendContactUsFormDto { + name: string; + phone: string; + email: string; + comment: Nullable; + ref: Nullable; + + constructor({ name, phone, email, comment, ref }: SendContactUsFormDto) { + this.name = name; + this.phone = phone; + this.email = email; + this.comment = comment; + this.ref = ref; + } +} diff --git a/frontend/src/app/api/dtos/FrontendObject/CreateFrontendObjectDto.ts b/frontend/src/app/api/dtos/FrontendObject/CreateFrontendObjectDto.ts new file mode 100644 index 0000000..6c8fdea --- /dev/null +++ b/frontend/src/app/api/dtos/FrontendObject/CreateFrontendObjectDto.ts @@ -0,0 +1,9 @@ +export class CreateFrontendObjectDto { + key: string; + value: T; + + constructor({ key, value }: CreateFrontendObjectDto) { + this.key = key; + this.value = value; + } +} diff --git a/frontend/src/app/api/dtos/FrontendObject/FrontendObjectDto.ts b/frontend/src/app/api/dtos/FrontendObject/FrontendObjectDto.ts new file mode 100644 index 0000000..f837244 --- /dev/null +++ b/frontend/src/app/api/dtos/FrontendObject/FrontendObjectDto.ts @@ -0,0 +1,5 @@ +export interface FrontendObjectDto { + key: string; + value: T; + createdAt: string; +} diff --git a/frontend/src/app/api/dtos/GeneralSettings/AccountDto.ts b/frontend/src/app/api/dtos/GeneralSettings/AccountDto.ts new file mode 100644 index 0000000..ba7474d --- /dev/null +++ b/frontend/src/app/api/dtos/GeneralSettings/AccountDto.ts @@ -0,0 +1,9 @@ +import type { Nullable } from '@/shared'; + +export interface AccountDto { + id: number; + subdomain: string; + createdAt: string; + companyName: string; + logoUrl: Nullable; +} diff --git a/frontend/src/app/api/dtos/GeneralSettings/AccountSettingsDto.ts b/frontend/src/app/api/dtos/GeneralSettings/AccountSettingsDto.ts new file mode 100644 index 0000000..145cf36 --- /dev/null +++ b/frontend/src/app/api/dtos/GeneralSettings/AccountSettingsDto.ts @@ -0,0 +1,16 @@ +import type { Currency, DateFormat, Language, Nullable, PhoneFormat, WeekDays } from '@/shared'; + +export interface AccountSettingsDto { + language: Language; + isBpmnEnable: boolean; + phoneFormat: PhoneFormat; + allowDuplicates: boolean; + timeZone: Nullable; + currency: Nullable; + numberFormat: Nullable; + startOfWeek: Nullable; + dateFormat: Nullable; + workingDays: Nullable; + workingTimeTo: Nullable; + workingTimeFrom: Nullable; +} diff --git a/frontend/src/app/api/dtos/GeneralSettings/UpdateAccountSettingsDto.ts b/frontend/src/app/api/dtos/GeneralSettings/UpdateAccountSettingsDto.ts new file mode 100644 index 0000000..56de184 --- /dev/null +++ b/frontend/src/app/api/dtos/GeneralSettings/UpdateAccountSettingsDto.ts @@ -0,0 +1,15 @@ +import type { Currency, DateFormat, Language, Nullable, PhoneFormat, WeekDays } from '@/shared'; + +export interface UpdateAccountSettingsDto { + language: Language; + phoneFormat: PhoneFormat; + allowDuplicates: boolean; + timeZone: Nullable; + currency: Nullable; + numberFormat: Nullable; + startOfWeek: Nullable; + dateFormat: Nullable; + workingDays: Nullable; + workingTimeTo: Nullable; + workingTimeFrom: Nullable; +} diff --git a/frontend/src/app/api/dtos/Permission/ObjectPermissionDto.ts b/frontend/src/app/api/dtos/Permission/ObjectPermissionDto.ts new file mode 100644 index 0000000..7017c33 --- /dev/null +++ b/frontend/src/app/api/dtos/Permission/ObjectPermissionDto.ts @@ -0,0 +1,70 @@ +import { type Nullable, PermissionLevel, type PermissionObjectType } from '@/shared'; + +export class ObjectPermissionDto { + objectType: PermissionObjectType; + objectId: Nullable; + createPermission: PermissionLevel; + viewPermission: PermissionLevel; + editPermission: PermissionLevel; + deletePermission: PermissionLevel; + reportPermission: PermissionLevel; + dashboardPermission: PermissionLevel; + + constructor({ + objectType, + objectId, + createPermission, + viewPermission, + editPermission, + deletePermission, + reportPermission, + dashboardPermission, + }: ObjectPermissionDto) { + this.objectType = objectType; + this.objectId = objectId; + this.createPermission = createPermission; + this.viewPermission = viewPermission; + this.editPermission = editPermission; + this.deletePermission = deletePermission; + this.reportPermission = reportPermission; + this.dashboardPermission = dashboardPermission; + } + + static createAllAllowed({ + objectId, + objectType, + }: { + objectId: Nullable; + objectType: PermissionObjectType; + }): ObjectPermissionDto { + return new ObjectPermissionDto({ + objectId, + objectType, + viewPermission: PermissionLevel.ALLOWED, + editPermission: PermissionLevel.ALLOWED, + createPermission: PermissionLevel.ALLOWED, + deletePermission: PermissionLevel.ALLOWED, + reportPermission: PermissionLevel.ALLOWED, + dashboardPermission: PermissionLevel.ALLOWED, + }); + } + + static createAllDenied({ + objectId, + objectType, + }: { + objectId: Nullable; + objectType: PermissionObjectType; + }): ObjectPermissionDto { + return new ObjectPermissionDto({ + objectId, + objectType, + viewPermission: PermissionLevel.DENIED, + editPermission: PermissionLevel.DENIED, + createPermission: PermissionLevel.DENIED, + deletePermission: PermissionLevel.DENIED, + reportPermission: PermissionLevel.DENIED, + dashboardPermission: PermissionLevel.DENIED, + }); + } +} diff --git a/frontend/src/app/api/dtos/Profile/UpdateUserProfileDto.ts b/frontend/src/app/api/dtos/Profile/UpdateUserProfileDto.ts new file mode 100644 index 0000000..98717b7 --- /dev/null +++ b/frontend/src/app/api/dtos/Profile/UpdateUserProfileDto.ts @@ -0,0 +1,15 @@ +import type { Nullable } from '@/shared'; + +export class UpdateUserProfileDto { + birthDate?: Nullable; + employmentDate?: Nullable; + workingTimeFrom?: Nullable; + workingTimeTo?: Nullable; + + constructor({ birthDate, employmentDate, workingTimeFrom, workingTimeTo }: UpdateUserProfileDto) { + this.birthDate = birthDate; + this.employmentDate = employmentDate; + this.workingTimeFrom = workingTimeFrom; + this.workingTimeTo = workingTimeTo; + } +} diff --git a/frontend/src/app/api/dtos/Profile/UserProfileDto.ts b/frontend/src/app/api/dtos/Profile/UserProfileDto.ts new file mode 100644 index 0000000..b66fd01 --- /dev/null +++ b/frontend/src/app/api/dtos/Profile/UserProfileDto.ts @@ -0,0 +1,23 @@ +import type { Nullable } from '@/shared'; + +export class UserProfileDto { + userId: number; + birthDate: Nullable; + employmentDate: Nullable; + workingTimeFrom: Nullable; + workingTimeTo: Nullable; + + constructor({ + userId, + birthDate, + employmentDate, + workingTimeFrom, + workingTimeTo, + }: UserProfileDto) { + this.userId = userId; + this.birthDate = birthDate; + this.employmentDate = employmentDate; + this.workingTimeFrom = workingTimeFrom; + this.workingTimeTo = workingTimeTo; + } +} diff --git a/frontend/src/app/api/dtos/SiteForm/SiteFormAnalyticDataDto.ts b/frontend/src/app/api/dtos/SiteForm/SiteFormAnalyticDataDto.ts new file mode 100644 index 0000000..f8ccc81 --- /dev/null +++ b/frontend/src/app/api/dtos/SiteForm/SiteFormAnalyticDataDto.ts @@ -0,0 +1,11 @@ +import type { Nullable } from '@/shared'; + +export class SiteFormAnalyticDataDto { + code: string; + value: Nullable; + + constructor({ code, value }: SiteFormAnalyticDataDto) { + this.code = code; + this.value = value; + } +} diff --git a/frontend/src/app/api/dtos/SiteForm/SiteFormDataDto.ts b/frontend/src/app/api/dtos/SiteForm/SiteFormDataDto.ts new file mode 100644 index 0000000..d3d00fd --- /dev/null +++ b/frontend/src/app/api/dtos/SiteForm/SiteFormDataDto.ts @@ -0,0 +1,13 @@ +import type { Nullable } from '@/shared'; +import type { SiteFormAnalyticDataDto } from './SiteFormAnalyticDataDto'; +import type { SiteFormFieldDataDto } from './SiteFormFieldDataDto'; + +export class SiteFormDataDto { + fields?: Nullable; + analytics?: Nullable; + + constructor({ fields, analytics }: SiteFormDataDto) { + this.fields = fields; + this.analytics = analytics; + } +} diff --git a/frontend/src/app/api/dtos/SiteForm/SiteFormFieldDataDto.ts b/frontend/src/app/api/dtos/SiteForm/SiteFormFieldDataDto.ts new file mode 100644 index 0000000..939374b --- /dev/null +++ b/frontend/src/app/api/dtos/SiteForm/SiteFormFieldDataDto.ts @@ -0,0 +1,9 @@ +export class SiteFormFieldDataDto { + id: number; + value: unknown; + + constructor({ id, value }: SiteFormFieldDataDto) { + this.id = id; + this.value = value; + } +} diff --git a/frontend/src/app/api/dtos/SiteForm/SiteFormResultDto.ts b/frontend/src/app/api/dtos/SiteForm/SiteFormResultDto.ts new file mode 100644 index 0000000..4a30baf --- /dev/null +++ b/frontend/src/app/api/dtos/SiteForm/SiteFormResultDto.ts @@ -0,0 +1,11 @@ +import type { Nullable } from '@/shared'; + +export class SiteFormResultDto { + result: boolean; + message: Nullable; + + constructor({ result, message }: SiteFormResultDto) { + this.result = result; + this.message = message; + } +} diff --git a/frontend/src/app/api/dtos/Stage/CreateStageDto.ts b/frontend/src/app/api/dtos/Stage/CreateStageDto.ts new file mode 100644 index 0000000..9c1614c --- /dev/null +++ b/frontend/src/app/api/dtos/Stage/CreateStageDto.ts @@ -0,0 +1,17 @@ +import type { Nullable, StageCode } from '@/shared'; + +export class CreateStageDto { + name: string; + color: string; + code: Nullable; + sortOrder?: number; + isSystem?: boolean; + + constructor({ name, color, code, sortOrder, isSystem }: CreateStageDto) { + this.name = name; + this.color = color; + this.code = code; + this.sortOrder = sortOrder; + this.isSystem = isSystem; + } +} diff --git a/frontend/src/app/api/dtos/Stage/StageDto.ts b/frontend/src/app/api/dtos/Stage/StageDto.ts new file mode 100644 index 0000000..696559c --- /dev/null +++ b/frontend/src/app/api/dtos/Stage/StageDto.ts @@ -0,0 +1,23 @@ +import type { Nullable, ObjectState, StageCode } from '@/shared'; + +export class StageDto { + id: number; + name: string; + color: string; + boardId: number; + isSystem: boolean; + sortOrder: number; + state: ObjectState; + code: Nullable; + + constructor({ id, name, color, code, isSystem, sortOrder, boardId, state }: StageDto) { + this.id = id; + this.name = name; + this.code = code; + this.color = color; + this.state = state; + this.boardId = boardId; + this.isSystem = isSystem; + this.sortOrder = sortOrder; + } +} diff --git a/frontend/src/app/api/dtos/Stage/UpdateStageDto.ts b/frontend/src/app/api/dtos/Stage/UpdateStageDto.ts new file mode 100644 index 0000000..b1c32a1 --- /dev/null +++ b/frontend/src/app/api/dtos/Stage/UpdateStageDto.ts @@ -0,0 +1,19 @@ +import { Nullable, StageCode } from '@/shared'; + +export class UpdateStageDto { + id: number; + name?: string; + color?: string; + code?: Nullable; + isSystem?: boolean; + sortOrder?: number; + + constructor({ id, name, color, code, isSystem, sortOrder }: UpdateStageDto) { + this.id = id; + this.name = name; + this.color = color; + this.code = code; + this.isSystem = isSystem; + this.sortOrder = sortOrder; + } +} diff --git a/frontend/src/app/api/dtos/Subscription/SubscriptionDto.ts b/frontend/src/app/api/dtos/Subscription/SubscriptionDto.ts new file mode 100644 index 0000000..85cbc2e --- /dev/null +++ b/frontend/src/app/api/dtos/Subscription/SubscriptionDto.ts @@ -0,0 +1,12 @@ +import type { AppSumoTiers, Nullable } from '@/shared'; + +export interface SubscriptionDto { + isTrial: boolean; + isValid: boolean; + createdAt: string; + userLimit: number; + isExternal: boolean; + expiredAt: Nullable; + planName: AppSumoTiers | string; + firstVisit?: Nullable; +} diff --git a/frontend/src/app/api/dtos/Subscription/SubscriptionFeatureDto.ts b/frontend/src/app/api/dtos/Subscription/SubscriptionFeatureDto.ts new file mode 100644 index 0000000..64d6e2f --- /dev/null +++ b/frontend/src/app/api/dtos/Subscription/SubscriptionFeatureDto.ts @@ -0,0 +1,4 @@ +export class SubscriptionFeatureDto { + name: string; + available: boolean; +} diff --git a/frontend/src/app/api/dtos/Subscription/SubscriptionPlanDto.ts b/frontend/src/app/api/dtos/Subscription/SubscriptionPlanDto.ts new file mode 100644 index 0000000..d3b57f1 --- /dev/null +++ b/frontend/src/app/api/dtos/Subscription/SubscriptionPlanDto.ts @@ -0,0 +1,16 @@ +import type { Nullable } from '@/shared'; +import type { SubscriptionFeatureDto } from './SubscriptionFeatureDto'; +import type { SubscriptionPriceDto } from './SubscriptionPriceDto'; + +export class SubscriptionPlanDto { + id: string; + name: string; + order: number; + code?: string; + isDefault: boolean; + userLimit: Nullable; + description: Nullable; + prices: SubscriptionPriceDto[]; + features: SubscriptionFeatureDto[]; + defaultPriceId: Nullable = null; +} diff --git a/frontend/src/app/api/dtos/Subscription/SubscriptionPriceDto.ts b/frontend/src/app/api/dtos/Subscription/SubscriptionPriceDto.ts new file mode 100644 index 0000000..b2a1c01 --- /dev/null +++ b/frontend/src/app/api/dtos/Subscription/SubscriptionPriceDto.ts @@ -0,0 +1,15 @@ +import { type BillingInterval } from '../../../../shared/lib/models/Subscription/BillingInterval'; + +export class SubscriptionPriceDto { + id: string; + amount: number; + currency: string; + interval: BillingInterval; + + constructor({ id, amount, currency, interval }: SubscriptionPriceDto) { + this.id = id; + this.amount = amount; + this.currency = currency; + this.interval = interval; + } +} diff --git a/frontend/src/app/api/dtos/Subscription/UpdateSubscriptionDto.ts b/frontend/src/app/api/dtos/Subscription/UpdateSubscriptionDto.ts new file mode 100644 index 0000000..7560dde --- /dev/null +++ b/frontend/src/app/api/dtos/Subscription/UpdateSubscriptionDto.ts @@ -0,0 +1,9 @@ +export interface UpdateSubscriptionDto { + isTrial?: boolean; + periodStart?: string; + periodEnd?: string; + userLimit?: number; + planName?: string; + externalCustomerId?: string | null; + firstVisit?: string; +} diff --git a/frontend/src/app/api/dtos/User/ChangeUserPasswordDto.ts b/frontend/src/app/api/dtos/User/ChangeUserPasswordDto.ts new file mode 100644 index 0000000..0331ef3 --- /dev/null +++ b/frontend/src/app/api/dtos/User/ChangeUserPasswordDto.ts @@ -0,0 +1,9 @@ +export class ChangeUserPasswordDto { + currentPassword: string; + newPassword: string; + + constructor({ currentPassword, newPassword }: ChangeUserPasswordDto) { + this.currentPassword = currentPassword; + this.newPassword = newPassword; + } +} diff --git a/frontend/src/app/api/dtos/User/CreateUserDto.ts b/frontend/src/app/api/dtos/User/CreateUserDto.ts new file mode 100644 index 0000000..15012b6 --- /dev/null +++ b/frontend/src/app/api/dtos/User/CreateUserDto.ts @@ -0,0 +1,39 @@ +import type { Nullable, UserRole } from '@/shared'; +import type { ObjectPermissionDto } from '../Permission/ObjectPermissionDto'; + +export class CreateUserDto { + firstName: string; + lastName: string; + email: string; + phone: Nullable; + password: string; + role: UserRole; + departmentId: Nullable; + position: Nullable; + objectPermissions: ObjectPermissionDto[]; + accessibleUserIds?: Nullable; + + constructor({ + firstName, + lastName, + email, + phone, + password, + role, + departmentId, + position, + objectPermissions, + accessibleUserIds, + }: CreateUserDto) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phone = phone; + this.password = password; + this.role = role; + this.departmentId = departmentId; + this.position = position; + this.objectPermissions = objectPermissions; + this.accessibleUserIds = accessibleUserIds; + } +} diff --git a/frontend/src/app/api/dtos/User/UpdateUserDto.ts b/frontend/src/app/api/dtos/User/UpdateUserDto.ts new file mode 100644 index 0000000..0d10765 --- /dev/null +++ b/frontend/src/app/api/dtos/User/UpdateUserDto.ts @@ -0,0 +1,58 @@ +import type { Nullable, User, UserRole } from '@/shared'; +import type { ObjectPermissionDto } from '../Permission/ObjectPermissionDto'; + +export class UpdateUserDto { + firstName: string; + lastName: string; + email: string; + phone: Nullable; + password: Nullable; + role: UserRole; + avatarUrl: Nullable; + departmentId: Nullable; + position: Nullable; + objectPermissions: ObjectPermissionDto[]; + accessibleUserIds?: Nullable; + + constructor({ + firstName, + lastName, + email, + phone, + password, + role, + avatarUrl, + departmentId, + position, + objectPermissions, + accessibleUserIds, + }: UpdateUserDto) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phone = phone; + this.password = password; + this.role = role; + this.avatarUrl = avatarUrl; + this.departmentId = departmentId; + this.position = position; + this.objectPermissions = objectPermissions; + this.accessibleUserIds = accessibleUserIds; + } + + static fromExistingUser(user: User): UpdateUserDto { + return new UpdateUserDto({ + firstName: user.firstName, + lastName: user.lastName || '', + email: user.email, + phone: user.phone, + password: null, + role: user.role, + avatarUrl: user.avatarUrl, + departmentId: user.departmentId, + position: user.position, + objectPermissions: user.objectPermissions, + accessibleUserIds: user.accessibleUserIds, + }); + } +} diff --git a/frontend/src/app/api/dtos/User/UserDto.ts b/frontend/src/app/api/dtos/User/UserDto.ts new file mode 100644 index 0000000..7fac8f0 --- /dev/null +++ b/frontend/src/app/api/dtos/User/UserDto.ts @@ -0,0 +1,19 @@ +import type { Nullable, UserRole } from '@/shared'; +import type { ObjectPermissionDto } from '../Permission/ObjectPermissionDto'; + +export interface UserDto { + id: number; + firstName: string; + lastName: Nullable; + email: string; + phone: Nullable; + role: UserRole; + isActive: boolean; + departmentId: Nullable; + avatarUrl: Nullable; + position: Nullable; + objectPermissions?: ObjectPermissionDto[]; + analyticsId: string; + accessibleUserIds: number[]; + isPlatformAdmin?: boolean; +} diff --git a/frontend/src/app/api/dtos/Version/VersionDto.ts b/frontend/src/app/api/dtos/Version/VersionDto.ts new file mode 100644 index 0000000..912f38b --- /dev/null +++ b/frontend/src/app/api/dtos/Version/VersionDto.ts @@ -0,0 +1,5 @@ +export interface VersionDto { + id: number; + version: string; + date: string; +} diff --git a/frontend/src/app/api/dtos/index.tsx b/frontend/src/app/api/dtos/index.tsx new file mode 100644 index 0000000..07c3c78 --- /dev/null +++ b/frontend/src/app/api/dtos/index.tsx @@ -0,0 +1,46 @@ +export { SequenceName } from '../IdentityApi/IdentityApi'; +export type { BoardDto } from './Board/BoardDto'; +export { CreateBoardDto } from './Board/CreateBoardDto'; +export { UpdateBoardDto } from './Board/UpdateBoardDto'; +export { FeatureDto } from './Builder/FeatureDto'; +export { CreateContactAndLeadDto } from './CreateContactAndLeadDto'; +export type { EntityDto } from './Entity/EntityDto'; +export type { ContactInfoDto } from './EntityEvent/ContactInfoDto'; +export type { EntityEventItemDto } from './EntityEvent/EntityEventItemDto'; +export type { EntityInfoDto } from './EntityEvent/EntityInfoDto'; +export { EntityLinkDto } from './EntityLink/EntityLinkDto'; +export type { EntityTypeDto } from './EntityType/EntityTypeDto'; +export { UpdateEntityTypeDto } from './EntityType/UpdateEntityTypeDto'; +export { UpdateEntityTypeFieldsModel } from './EntityType/UpdateEntityTypeFieldsModel'; +export { FeedbackType } from './Feedback/FeedbackType'; +export { SendFeedbackDto, type FeedbackPayload } from './Feedback/SendFeedbackDto'; +export { TrialExpiredFeedback } from './Feedback/TrialExpiredFeedback'; +export { UserLimitFeedback } from './Feedback/UserLimitFeedback'; +export type { FeedItemDto } from './FeedItem/FeedItemDto'; +export type { FileInfoResultDto } from './FileInfoResult/FileInfoResultDto'; +export type { FileLinkDto } from './FileLink/FileLinkDto'; +export { SendContactUsFormDto } from './Form/SendContactUsFormDto'; +export { CreateFrontendObjectDto } from './FrontendObject/CreateFrontendObjectDto'; +export type { FrontendObjectDto } from './FrontendObject/FrontendObjectDto'; +export type { AccountDto } from './GeneralSettings/AccountDto'; +export type { AccountSettingsDto } from './GeneralSettings/AccountSettingsDto'; +export type { UpdateAccountSettingsDto } from './GeneralSettings/UpdateAccountSettingsDto'; +export { ObjectPermissionDto } from './Permission/ObjectPermissionDto'; +export { UpdateUserProfileDto } from './Profile/UpdateUserProfileDto'; +export { UserProfileDto } from './Profile/UserProfileDto'; +export { SiteFormDataDto } from './SiteForm/SiteFormDataDto'; +export { SiteFormFieldDataDto } from './SiteForm/SiteFormFieldDataDto'; +export { SiteFormResultDto } from './SiteForm/SiteFormResultDto'; +export { CreateStageDto } from './Stage/CreateStageDto'; +export { StageDto } from './Stage/StageDto'; +export { UpdateStageDto } from './Stage/UpdateStageDto'; +export type { SubscriptionDto } from './Subscription/SubscriptionDto'; +export type { SubscriptionFeatureDto } from './Subscription/SubscriptionFeatureDto'; +export type { SubscriptionPlanDto } from './Subscription/SubscriptionPlanDto'; +export type { SubscriptionPriceDto } from './Subscription/SubscriptionPriceDto'; +export type { UpdateSubscriptionDto } from './Subscription/UpdateSubscriptionDto'; +export { ChangeUserPasswordDto } from './User/ChangeUserPasswordDto'; +export { CreateUserDto } from './User/CreateUserDto'; +export { UpdateUserDto } from './User/UpdateUserDto'; +export type { UserDto } from './User/UserDto'; +export type { VersionDto } from './Version/VersionDto'; diff --git a/frontend/src/app/api/index.tsx b/frontend/src/app/api/index.tsx new file mode 100644 index 0000000..1d70f81 --- /dev/null +++ b/frontend/src/app/api/index.tsx @@ -0,0 +1,27 @@ +export { ApiRoutes } from './ApiRoutes'; +export { baseApi, determineApiHost } from './BaseApi/BaseApi'; +export { boardApi } from './BoardApi/BoardApi'; +export { boardApiUtil } from './BoardApi/BoardApiUtil'; +export { dadataApi } from './DadataApi/DadataApi'; +export * from './dtos'; +export { entityTypeApi } from './EntityTypeApi/EntityTypeApi'; +export { featureApi } from './FeatureApi/FeatureApi'; +export { feedbackApi } from './FeedbackApi/FeedbackApi'; +export { fileApi, type FileUploadResult } from './FileApi/FileApi'; +export { useGetFileInfos } from './FileApi/queries/useGetFileInfos'; +export { formApi } from './FormApi/FormApi'; +export { frontendObjectsApi } from './FrontendObjectsApi/FrontendObjectsApi'; +export { generalSettingsApi } from './GeneralSettingsApi/GeneralSettingsApi'; +export { identityApi } from './IdentityApi/IdentityApi'; +export type { IdentityPool } from './IdentityApi/IdentityApi'; +export { stageApi } from './StageApi/StageApi'; +export { stageApiUtil } from './StageApi/StageApiUtil'; +export { useGetSubscriptionForAccount } from './SubscriptionApi/queries/useGetSubscriptionForAccount'; +export { useUpdateSubscription } from './SubscriptionApi/queries/useUpdateSubscription'; +export { subscriptionApi } from './SubscriptionApi/SubscriptionApi'; +export { userApi } from './UserApi/UserApi'; +export { invalidateUserProfilesInCache } from './UserProfileApi/helpers/invalidateUserProfilesInCache'; +export { useGetUserProfiles } from './UserProfileApi/queries/useGetUserProfiles'; +export { userProfileApi } from './UserProfileApi/UserProfileApi'; +export { utilityApi } from './UtilityApi/UtilityApi'; +export { useGetLatestFrontendVersionPolling } from './VersionApi/queries/useGetLatestFrontendVersionPolling'; diff --git a/frontend/src/app/config/i18n/i18n.ts b/frontend/src/app/config/i18n/i18n.ts new file mode 100644 index 0000000..21c7fd7 --- /dev/null +++ b/frontend/src/app/config/i18n/i18n.ts @@ -0,0 +1,56 @@ +import i18n from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import Backend, { type HttpBackendOptions } from 'i18next-http-backend'; +import { initReactI18next } from 'react-i18next'; + +// to load namespaces in parallel on application start +const ns: string[] = [ + 'common', + 'module.automation', + 'module.bpmn', + 'module.builder', + 'module.fields', + 'module.mailing', + 'module.notifications', + 'module.multichat', + 'module.notes', + 'module.products', + 'module.reporting', + 'module.scheduler', + 'module.telephony', + 'module.tutorial', + 'page.board-settings', + 'page.login', + 'page.settings', + 'page.system', + 'page.tasks', + 'store.field-groups-store', + 'store.fields-store', + 'component.card', + 'component.entity-board', + 'component.section', +]; + +i18n + .use(Backend) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + debug: false, + fallbackLng: 'en', + defaultNS: 'common', + load: 'languageOnly', + maxParallelReads: 32, + + ns, + + interpolation: { + escapeValue: false, + }, + + backend: { + loadPath: '/locales/{{lng}}/{{ns}}.json', + }, + }); + +export { i18n }; diff --git a/frontend/src/app/config/knip/knip.ts b/frontend/src/app/config/knip/knip.ts new file mode 100644 index 0000000..ee7b17c --- /dev/null +++ b/frontend/src/app/config/knip/knip.ts @@ -0,0 +1,49 @@ +import type { KnipConfig } from 'knip'; + +// https://knip.dev/overview/configuration +const config: KnipConfig = { + entry: ['src/index.tsx'], + project: ['src/**/*.ts', 'src/**/*.tsx'], + ignore: [ + 'src/app/config', + '.storybook', + 'src/shared/assets/docs-images/index.tsx', + 'src/shared/lib/hooks/useTracePropsUpdate.ts', + 'src/shared/lib/helpers/capitalizeFirstLetter.ts', + 'src/modules/scheduler/api/ScheduleAppointmentApi/queries/useGetSchedulerTotalVisits.ts', + 'src/modules/scheduler/api/ScheduleApi/helpers/invalidateScheduleInCache.ts', + 'src/modules/multichat/api/ChatProviderApi/queries/useGetExternalChatProviders.ts', + ], + exclude: [ + // https://github.com/webpro/knip#reading-the-report + 'enumMembers', + 'nsExports', + 'nsTypes', + 'classMembers', + ], + ignoreExportsUsedInFile: true, + includeEntryExports: true, + ignoreDependencies: [ + // deps + 'file-saver', + '@babel/plugin-proposal-decorators', + '@babel/plugin-transform-class-properties', + '@babel/plugin-transform-typescript', + '@babel/preset-typescript', + '@emotion/react', + '@emotion/utils', + '@tabler/icons', + 'normalize.css', + '@tiptap/extension-text-style', + // dev deps + '@storybook/blocks', + 'babel-plugin-styled-components', + '@react-pdf-viewer/default-layout', + // unlisted deps + '@tanstack/table-core', + '@tanstack/table-core', + '@tiptap/core', + ], +}; + +export default config; diff --git a/frontend/src/app/config/newrelic/newrelic.ts b/frontend/src/app/config/newrelic/newrelic.ts new file mode 100644 index 0000000..6adcef4 --- /dev/null +++ b/frontend/src/app/config/newrelic/newrelic.ts @@ -0,0 +1,37 @@ +import { envUtil } from '@/shared'; + +const config = { + init: { + session_replay: { + enabled: true, + block_selector: '', + mask_text_selector: '*', + sampling_rate: 10.0, + error_sampling_rate: 100.0, + mask_all_inputs: true, + collect_fonts: true, + inline_images: false, + inline_stylesheet: true, + mask_input_options: {}, + }, + distributed_tracing: { enabled: true }, + privacy: { cookies_enabled: true }, + ajax: { deny_list: ['bam.eu01.nr-data.net'] }, + }, + info: { + beacon: 'bam.eu01.nr-data.net', + errorBeacon: 'bam.eu01.nr-data.net', + licenseKey: envUtil.newRelicLicenseKey, + applicationID: envUtil.newRelicApplicationId, + sa: 1, + }, + loader_config: { + accountID: envUtil.newRelicAccountId, + trustKey: envUtil.newRelicAccountId, + agentID: envUtil.newRelicApplicationId, + licenseKey: envUtil.newRelicLicenseKey, + applicationID: envUtil.newRelicApplicationId, + }, +}; + +export default config; diff --git a/frontend/src/app/config/storybook/I18nDecorator.tsx b/frontend/src/app/config/storybook/I18nDecorator.tsx new file mode 100644 index 0000000..4ca8982 --- /dev/null +++ b/frontend/src/app/config/storybook/I18nDecorator.tsx @@ -0,0 +1,20 @@ +import type { Decorator } from '@storybook/react'; +import { Suspense, useEffect } from 'react'; +import { I18nextProvider } from 'react-i18next'; +import { i18n } from '../i18n/i18n'; + +export const I18nDecorator: Decorator = (StoryComponent, context) => { + const { locale } = context.globals; + + useEffect(() => { + i18n.changeLanguage(locale); + }, [locale]); + + return ( + + + + + + ); +}; diff --git a/frontend/src/app/config/storybook/RouterDecorator.tsx b/frontend/src/app/config/storybook/RouterDecorator.tsx new file mode 100644 index 0000000..185e53e --- /dev/null +++ b/frontend/src/app/config/storybook/RouterDecorator.tsx @@ -0,0 +1,8 @@ +import type { Decorator } from '@storybook/react'; +import { BrowserRouter } from 'react-router-dom'; + +export const RouterDecorator: Decorator = StoryComponent => ( + + + +); diff --git a/frontend/src/app/config/storybook/StyleDecorator.tsx b/frontend/src/app/config/storybook/StyleDecorator.tsx new file mode 100644 index 0000000..402b762 --- /dev/null +++ b/frontend/src/app/config/storybook/StyleDecorator.tsx @@ -0,0 +1,4 @@ +import type { Decorator } from '@storybook/react'; +import '../../styles/main.css'; + +export const StyleDecorator: Decorator = StoryComponent => ; diff --git a/frontend/src/app/index.tsx b/frontend/src/app/index.tsx new file mode 100644 index 0000000..a6fcd84 --- /dev/null +++ b/frontend/src/app/index.tsx @@ -0,0 +1,4 @@ +export * from './api'; +export { App } from './App'; +export * from './routes'; +export * from './store'; diff --git a/frontend/src/app/pages/HomePage/HomePage.tsx b/frontend/src/app/pages/HomePage/HomePage.tsx new file mode 100644 index 0000000..2296b5d --- /dev/null +++ b/frontend/src/app/pages/HomePage/HomePage.tsx @@ -0,0 +1,42 @@ +import { authStore } from '@/modules/auth'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useEffect } from 'react'; +import { Navigate, useNavigate } from 'react-router-dom'; +import { SectionView, WholePageLoaderWithLogo } from '../../../shared'; +import { routes } from '../../routes'; +import { appStore, entityTypeStore } from '../../store'; + +export const HomePage = observer(() => { + const navigate = useNavigate(); + + const redirect = useCallback((): void => { + const firstEt = entityTypeStore.getAvailableEntityTypes()[0]; + + if (!firstEt) { + navigate(routes.timeBoard()); + + return; + } + + if (firstEt.section.view === SectionView.BOARD) { + navigate(routes.boardSection({ entityTypeId: firstEt.id }), { + replace: true, + }); + + return; + } + + navigate(routes.listSection(firstEt.id), { replace: true }); + }, [navigate]); + + useEffect(() => { + if (appStore.isLoaded) redirect(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [appStore.isLoaded]); + + if (authStore.user && authStore.user.isPartner()) + return ; + + return ; +}); diff --git a/frontend/src/app/pages/index.tsx b/frontend/src/app/pages/index.tsx new file mode 100644 index 0000000..a6a38dc --- /dev/null +++ b/frontend/src/app/pages/index.tsx @@ -0,0 +1 @@ +export { HomePage } from './HomePage/HomePage'; diff --git a/frontend/src/app/routes/AppBoundary.tsx b/frontend/src/app/routes/AppBoundary.tsx new file mode 100644 index 0000000..b580334 --- /dev/null +++ b/frontend/src/app/routes/AppBoundary.tsx @@ -0,0 +1,30 @@ +import { appStore } from '@/app'; +import { MultichatModal } from '@/modules/multichat'; +import { NotesModal } from '@/modules/notes'; +import { TelephonyModal } from '@/modules/telephony'; +import { ErrorBoundary, PageTracker, ReloadModal, ToastContainer, VersionModal } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { Outlet } from 'react-router-dom'; + +const AppBoundary = observer(() => { + return ( + + + + + {appStore.isLoaded && ( + <> + + + + + + + + )} + + ); +}); + +AppBoundary.displayName = 'AppBoundary'; +export { AppBoundary }; diff --git a/frontend/src/app/routes/createRouter.tsx b/frontend/src/app/routes/createRouter.tsx new file mode 100644 index 0000000..2bbc37b --- /dev/null +++ b/frontend/src/app/routes/createRouter.tsx @@ -0,0 +1,379 @@ +import { AppBoundary } from '@/app'; +import { LoginLinkPage, LoginPage } from '@/modules/auth'; +import { AutomationProcessesPage, ListSectionAutomationProcessesPage } from '@/modules/bpmn'; +import { + BuilderHomePage, + EtSectionBuilderPage, + HeadlessSiteFormBuilderPage, + OnlineBookingSiteFormBuilderPage, + ProductsSectionBuilderPage, + SchedulerBuilderPage, + SiteFormBuilderPage, +} from '@/modules/builder'; +import { AddCardPage, CardPage } from '@/modules/card'; +import { MailingPage, MailingSettingsPage } from '@/modules/mailing'; +import { MultichatPage } from '@/modules/multichat'; +import { NotesPage } from '@/modules/notes'; +import { PartnerInfoPage } from '@/modules/partner'; +import { ProductPage, ProductsPage, ShipmentPage } from '@/modules/products'; +import { GoalSettingsPage } from '@/modules/reporting'; +import { SchedulerPage } from '@/modules/scheduler'; +import { + EntitiesBoardSettingsPage, + EntitiesListAutomationPage, + EntitiesListPage, + EntitiesPage, + EverythingPage, + TasksBoardSettingsPage, +} from '@/modules/section'; +import { + AccountApiAccessPage, + CommonBillingPage, + DocumentCreationFieldsPage, + DocumentTemplatesPage, + EditDepartmentsPage, + EditUserPage, + GeneralSettingsPage, + GoogleCalendarRedirectPage, + IntegrationsPage, + MyworkRequestInvoiceBillingPage, + StripeBillingPage, + SuperadminPage, + UsersSettingsPage, +} from '@/modules/settings'; +import { ActivitiesPage, TasksPage, TimeBoardPage } from '@/modules/tasks'; +import { + CallsConfiguringScenariosPage, + CallsSettingsAccountPage, + CallsSettingsUsersPage, + CallsSipRegistrationsPage, +} from '@/modules/telephony'; +import { + BrowserNotSupportedPage, + EntitiesBoardSettingsTab, + ForbiddenPage, + NotFoundPage, + WithAdminRole, + WithAuth, + WithPartnerRole, + WithSuperadminRole, + WithViewEntityReportPermission, + WithViewSchedulerReportPermission, + withPage, +} from '@/shared'; +import { HttpStatusCode } from 'axios'; +import { Navigate, createBrowserRouter } from 'react-router-dom'; +import { HomePage } from '../pages'; + +// https://reactrouter.com/en/6.21.1/routers/create-browser-router +export const createRouter = ({ + isBrowserSupported, +}: { + isBrowserSupported: boolean; +}): ReturnType => { + if (!isBrowserSupported) + return createBrowserRouter([ + { + element: , + children: [ + { + path: '/browser-not-supported', + element: , + }, + { + path: '*', + element: , + }, + ], + }, + ]); + + return createBrowserRouter([ + { + // https://reactrouter.com/en/main/components/outlet + element: , + children: [ + // Home + { + path: '/', + element: WithAuth(HomePage), + }, + + // Login + { + path: '/login', + element: , + }, + { + path: '/login-link', + element: , + }, + + // Mail + { + path: '/mail', + element: WithAuth(MailingPage), + }, + + // Multichat + { + path: '/chat', + element: WithAuth(MultichatPage), + }, + { + path: '/chat/:chatId', + element: WithAuth(MultichatPage), + }, + { + path: '/chat/:chatId/message/:messageId', + element: WithAuth(MultichatPage), + }, + + // Notes + { + path: '/notes', + element: WithAuth(NotesPage), + }, + + // Tasks and Activities + { + path: '/activities/:tab/:view?/:year?/:month?/:day?', + element: WithAuth(withPage(ActivitiesPage)), + }, + { + path: '/tasks/deadline/:tab/:view?/:year?/:month?/:day?', + element: WithAuth(withPage(TimeBoardPage)), + }, + { + path: '/tasks/b/:boardId/:tab/:view?/:year?/:month?/:day?', + element: WithAuth(withPage(TasksPage)), + }, + { + path: '/tasks/b/:boardId/board-settings', + element: WithAuth(TasksBoardSettingsPage), + }, + + // Entities list and board + { + path: '/et/:entityTypeId/list', + element: WithAuth(withPage(EntitiesListPage)), + }, + { + path: `/et/:entityTypeId/list/${EntitiesBoardSettingsTab.AUTOMATION}`, + element: WithAdminRole(withPage(EntitiesListAutomationPage)), + }, + { + path: `/et/:entityTypeId/list/${EntitiesBoardSettingsTab.AUTOMATION_BPMN}`, + element: WithAdminRole(withPage(ListSectionAutomationProcessesPage)), + }, + { + path: '/et/:entityTypeId/b/:boardId/:tab/:view?', + element: WithViewEntityReportPermission(withPage(EntitiesPage)), + }, + { + path: '/et/:entityTypeId/everything', + element: WithAuth(withPage(EverythingPage)), + }, + { + path: '/et/:entityTypeId/b/:boardId/board-settings/:tab', + element: WithAdminRole(withPage(EntitiesBoardSettingsPage)), + }, + { + path: `/et/:entityTypeId/b/:boardId/board-settings/${EntitiesBoardSettingsTab.AUTOMATION_BPMN}`, + element: WithAdminRole(withPage(AutomationProcessesPage)), + }, + + // Set sales goals page + { + path: '/et/:entityTypeId/goal-settings', + element: WithAdminRole(withPage(GoalSettingsPage)), + }, + + // Entity card + { + path: '/et/:entityTypeId/card/:entityId/:tab/:view?/:year?/:month?/:day?', + element: WithAuth(CardPage), + }, + { + path: '/et/:entityTypeId/card/add', + element: WithAuth(AddCardPage), + }, + + // Users settings + { + path: '/settings/users/list', + element: WithAdminRole(UsersSettingsPage), + }, + { + path: '/settings/users/list/new', + element: WithAdminRole(EditUserPage), + }, + { + path: '/settings/users/groups', + element: WithAdminRole(EditDepartmentsPage), + }, + { + path: '/settings/users/list/:id', + element: WithAdminRole(EditUserPage), + }, + + // Billing + { + path: '/settings/billing/common', + element: WithAdminRole(CommonBillingPage, false), + }, + { + path: '/settings/billing/stripe', + element: WithAdminRole(StripeBillingPage, false), + }, + { + path: '/settings/billing/invoice', + element: WithAdminRole(MyworkRequestInvoiceBillingPage, false), + }, + + { + path: '/settings/general', + element: WithAdminRole(GeneralSettingsPage), + }, + { + path: '/settings/api', + element: WithAdminRole(AccountApiAccessPage), + }, + { + path: '/settings/superadmin', + element: WithSuperadminRole(SuperadminPage), + }, + { + // Change carefully, should be synced with backend + path: '/settings/integrations', + element: WithAuth(IntegrationsPage), + }, + // Wazzup wauth – do not change the path + // https://wazzup24.com/help/api-en/wauth/ + { + path: '/settings/integrations/wazzup/wauth', + element: WithAuth(IntegrationsPage), + }, + { + path: '/settings/mailing', + element: WithAuth(MailingSettingsPage), + }, + { + // Google calendar backlink leads here – do not change the path + path: '/settings/integrations/google-calendar', + element: WithAuth(GoogleCalendarRedirectPage), + }, + + // Documents creation settings + { + path: '/settings/documents/templates', + element: WithAdminRole(DocumentTemplatesPage), + }, + { + path: '/settings/documents/fields', + element: WithAdminRole(DocumentCreationFieldsPage), + }, + + // Calls settings + { + path: '/settings/calls/account', + element: WithAdminRole(CallsSettingsAccountPage), + }, + { + path: '/settings/calls/users', + element: WithAdminRole(CallsSettingsUsersPage), + }, + { + path: '/settings/calls/scenarios', + element: WithAdminRole(CallsConfiguringScenariosPage), + }, + { + path: '/settings/calls/sip-registrations', + element: WithAdminRole(CallsSipRegistrationsPage), + }, + + // Builder + { + path: '/builder/:tab', + element: WithAdminRole(BuilderHomePage), + }, + + // Builder – Entity type + { + path: '/builder/et/:moduleId?', + element: WithAdminRole(EtSectionBuilderPage), + }, + + // Builder – Products + { + path: '/builder/products/:moduleId?', + element: WithAdminRole(ProductsSectionBuilderPage), + }, + + // Builder – Scheduler + { + path: '/builder/scheduler/:moduleId?', + element: WithAdminRole(SchedulerBuilderPage), + }, + + // Builder – Forms + { + path: '/builder/site-forms/:moduleId?', + element: WithAdminRole(SiteFormBuilderPage), + }, + { + path: '/builder/site-forms/headless/:moduleId?', + element: WithAdminRole(HeadlessSiteFormBuilderPage), + }, + { + path: '/builder/site-forms/online-booking/:moduleId?', + element: WithAdminRole(OnlineBookingSiteFormBuilderPage), + }, + + // Partners page + { + path: '/partners/:partnerId', + element: WithPartnerRole(PartnerInfoPage), + }, + + // Products + { + path: '/p/:sectionType/:sectionId/product/:productId', + element: WithAuth(withPage(ProductPage)), + }, + { + path: '/p/:sectionType/:sectionId/:tab', + element: WithAuth(ProductsPage), + }, + + // Shipments + { + path: '/p/:sectionType/:sectionId/shipments/:shipmentId', + element: WithAuth(ShipmentPage), + }, + + // Scheduler + { + path: '/scheduler/:scheduleType/:scheduleId/:tab', + element: WithViewSchedulerReportPermission(withPage(SchedulerPage)), + }, + + // System pages + { + path: `/${HttpStatusCode.Forbidden}`, + element: , + }, + { + path: `/${HttpStatusCode.NotFound}`, + element: , + }, + + // Wildcard + { + path: '*', + element: , + }, + ], + }, + ]); +}; diff --git a/frontend/src/app/routes/index.tsx b/frontend/src/app/routes/index.tsx new file mode 100644 index 0000000..9ac288f --- /dev/null +++ b/frontend/src/app/routes/index.tsx @@ -0,0 +1,3 @@ +export { AppBoundary } from './AppBoundary'; +export { createRouter } from './createRouter'; +export { PBX_PROVIDER_TYPE_QUERY_PARAM, routes } from './routes'; diff --git a/frontend/src/app/routes/routes.ts b/frontend/src/app/routes/routes.ts new file mode 100644 index 0000000..eb6fe59 --- /dev/null +++ b/frontend/src/app/routes/routes.ts @@ -0,0 +1,645 @@ +import type { BuilderTabs, ModuleCategory } from '@/modules/builder'; +import { + CardTab, + ORDER_ID_QUERY_PARAM, + generateProductsSectionOrderTabValue, +} from '@/modules/card'; +import type { GanttView } from '@/modules/gantt'; +import { MAILBOX_ID_QUERY_PARAM } from '@/modules/mailing'; +import { ProductsPageTabs, type ProductsSectionType } from '@/modules/products'; +import type { ReportsSection } from '@/modules/reporting'; +import { generateSchedulerLinkedEntityTypeTab, type ScheduleType } from '@/modules/scheduler'; +import { + ALBATO_INFO_MODAL_QUERY_PARAM, + APIX_DRIVE_INFO_MODAL_QUERY_PARAM, + FB_FIRST_INFO_MODAL_QUERY_PARAM, + GOOGLE_CALENDAR_CONNECT_MODAL_QUERY_PARAM, + GOOGLE_CALENDAR_MANAGE_MODAL_QUERY_PARAM, + MAKE_INFO_MODAL_QUERY_PARAM, + ONE_C_INFO_MODAL_QUERY_PARAM, + PBX_GROUP_ID, + SALESFORCE_FIRST_INFO_MODAL_QUERY_PARAM, + TILDA_INFO_MODAL_QUERY_PARAM, + TWILIO_FIRST_INFO_MODAL_QUERY_PARAM, + WAZZUP_FIRST_INFO_MODAL_QUERY_PARAM, + WORDPRESS_INFO_MODAL_QUERY_PARAM, +} from '@/modules/settings'; +import { TasksTab } from '@/modules/tasks'; +import { + CommonQueryParams, + EntitiesBoardSettingsTab, + SectionView, + type CalendarView, + type EntityType, + type Nullable, + type Optional, +} from '@/shared'; +import { HttpStatusCode } from 'axios'; + +export const PBX_PROVIDER_TYPE_QUERY_PARAM = 'pbxProviderType'; + +export const routes = { + root: '/', + login: '/login', + sectionBase(entityTypeId: number) { + return `/et/${entityTypeId}`; + }, + everything(entityTypeId: number) { + return `${this.sectionBase(entityTypeId)}/everything`; + }, + listSectionBase(entityTypeId: number) { + return `${this.sectionBase(entityTypeId)}/list`; + }, + listSectionAutomation(entityTypeId: number) { + return `${this.sectionBase(entityTypeId)}/list/${EntitiesBoardSettingsTab.AUTOMATION}`; + }, + listSectionBpmn(entityTypeId: number) { + return `${this.sectionBase(entityTypeId)}/list/${EntitiesBoardSettingsTab.AUTOMATION_BPMN}`; + }, + listSection(entityTypeId: number) { + return `${this.listSectionBase(entityTypeId)}?${CommonQueryParams.PAGE}=1`; + }, + listSectionWithBoard({ entityTypeId, boardId }: { entityTypeId: number; boardId: number }) { + return `${this.sectionBase(entityTypeId)}/b/${boardId}/${SectionView.LIST}?${CommonQueryParams.PAGE}=1`; + }, + entitiesSectionBase({ entityTypeId, boardId }: { entityTypeId: number; boardId?: number }) { + return `${this.sectionBase(entityTypeId)}/b/${boardId ?? -1}`; + }, + entitiesSection({ + entityTypeId, + boardId, + tab, + }: { + entityTypeId: number; + boardId?: number; + tab: SectionView; + }) { + return `${this.entitiesSectionBase({ entityTypeId, boardId })}/${tab}${tab === SectionView.LIST ? `?${CommonQueryParams.PAGE}=1` : ''}`; + }, + entitiesSectionTimeline({ + entityTypeId, + boardId, + view, + }: { + entityTypeId: number; + boardId: number; + view: GanttView; + }) { + return `${this.entitiesSectionBase({ entityTypeId, boardId })}/${SectionView.TIMELINE}/${view}`; + }, + boardSectionBase(entityTypeId: number) { + return `${this.sectionBase(entityTypeId)}/b`; + }, + // if boardId is not provided, user will be navigated to page with boardId = -1 + // Then, page will request available boards and renavigate user to actual board + boardSection({ entityTypeId, boardId }: { entityTypeId: number; boardId?: Nullable }) { + return `${this.boardSectionBase(entityTypeId)}/${boardId ?? -1}/${SectionView.BOARD}`; + }, + boardSectionReports({ + boardId, + entityTypeId, + reportsSection, + }: { + boardId: number; + entityTypeId: number; + reportsSection: ReportsSection | string; + }) { + return `${this.boardSectionBase(entityTypeId)}/${boardId}/${SectionView.REPORTS}${reportsSection ? `?${CommonQueryParams.SECTION}=${reportsSection}` : ''}`; + }, + boardSectionDashboard({ entityTypeId, boardId }: { entityTypeId: number; boardId: number }) { + return `${this.boardSectionBase(entityTypeId)}/${boardId}/${SectionView.DASHBOARD}`; + }, + goalSettings(entityTypeId: number) { + return `${this.sectionBase(entityTypeId)}/goal-settings`; + }, + section({ + entityType, + firstBoardId, + }: { + entityType: EntityType; + firstBoardId: Optional>; + }) { + if (entityType.section.view === SectionView.BOARD) + return this.boardSection({ entityTypeId: entityType.id, boardId: firstBoardId }); + + return this.listSection(entityType.id); + }, + cardBase(entityTypeId: number) { + return `${this.sectionBase(entityTypeId)}/card`; + }, + card({ + entityTypeId, + entityId, + from, + tab = CardTab.OVERVIEW, + }: { + entityTypeId: number; + entityId: number; + from?: string; + tab?: CardTab; + }) { + return `${this.cardBase(entityTypeId)}/${entityId}/${tab}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}`; + }, + cardProductsOrder({ + entityTypeId, + entityId, + sectionId, + sectionType, + orderId, + from, + }: { + entityTypeId: number; + entityId: number; + sectionId: number; + sectionType: ProductsSectionType; + orderId?: number; + from?: string; + }) { + return ( + this.cardBase(entityTypeId) + + `/${entityId}/${generateProductsSectionOrderTabValue({ + sectionId, + sectionType, + })}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}${orderId ? `${from ? '&' : '?'}${ORDER_ID_QUERY_PARAM}=${orderId}` : ''}` + ); + }, + cardAfterAdd({ + entityTypeId, + entityId, + from, + }: { + entityTypeId: number; + entityId: number; + from?: string; + }) { + return `${this.cardBase(entityTypeId)}/${entityId}/${CardTab.OVERVIEW}?${CardTab.AFTER_ADD}=true${ + from ? `&${CommonQueryParams.FROM}=${from}` : '' + }`; + }, + addCard({ + entityTypeId, + boardId, + from, + }: { + entityTypeId: number; + boardId?: number; + from?: string; + }) { + return `${this.cardBase(entityTypeId)}/add${boardId ? `?boardId=${boardId}` : ''}${from ? `${boardId ? '&' : '?'}${CommonQueryParams.FROM}=${from}` : ''}`; + }, + productsBase({ + sectionType, + sectionId, + }: { + sectionType: ProductsSectionType; + sectionId: number; + }) { + return `/p/${sectionType}/${sectionId}`; + }, + products({ + sectionId, + sectionType, + addProduct, + sku, + tab = ProductsPageTabs.PRODUCTS, + }: { + sectionId: number; + sectionType: ProductsSectionType; + addProduct?: boolean; + sku?: string; + tab?: ProductsPageTabs; + }) { + let path; + + if (addProduct && sku) { + path = `${this.productsBase({ sectionType, sectionId })}/${tab}?add=true&sku=${sku}`; + } else if (addProduct) { + path = `${this.productsBase({ sectionType, sectionId })}/${tab}?add=true`; + } else { + path = `${this.productsBase({ sectionType, sectionId })}/${tab}`; + } + + return `${path}${tab === ProductsPageTabs.PRODUCTS ? `?${CommonQueryParams.PAGE}=1` : ''}`; + }, + productsReports({ + sectionId, + sectionType, + reportsSection, + }: { + sectionId: number; + sectionType: ProductsSectionType; + reportsSection: string; + }) { + return `${this.productsBase({ sectionType, sectionId })}/${ProductsPageTabs.REPORTS}?${CommonQueryParams.SECTION}=${reportsSection}`; + }, + product({ + sectionId, + sectionType, + productId, + from, + }: { + sectionId: number; + sectionType: ProductsSectionType; + productId: number; + from?: string; + }) { + if (from) + return `${this.productsBase({ sectionType, sectionId })}/product/${productId}?${CommonQueryParams.FROM}=${from}`; + + return `${this.productsBase({ sectionType, sectionId })}/product/${productId}`; + }, + shipments({ sectionId, sectionType }: { sectionId: number; sectionType: ProductsSectionType }) { + return `${this.productsBase({ sectionType, sectionId })}/shipments?${CommonQueryParams.PAGE}=1`; + }, + shipment({ + sectionId, + sectionType, + shipmentId, + }: { + sectionId: number; + sectionType: ProductsSectionType; + shipmentId: number; + }) { + return `${this.productsBase({ sectionType, sectionId })}/shipments/${shipmentId}`; + }, + settingsBase: '/settings', + settingsUsersBase() { + return `${this.settingsBase}/users`; + }, + settingsUsers() { + return `${this.settingsUsersBase()}/list`; + }, + settingsDepartments() { + return `${this.settingsUsersBase()}/groups`; + }, + settingsUsersAdd() { + return `${this.settingsUsersBase()}/list/new`; + }, + settingsUsersUpdate(userId: number) { + return `${this.settingsUsersBase()}/list/${userId}`; + }, + settingsGeneral() { + return `${this.settingsBase}/general`; + }, + settingsApiKeys() { + return `${this.settingsBase}/api`; + }, + settingsBillingBase() { + return `${this.settingsBase}/billing`; + }, + settingsBillingCommon() { + return `${this.settingsBillingBase()}/common`; + }, + settingsBillingStripe() { + return `${this.settingsBillingBase()}/stripe`; + }, + settingsBillingMyworkRequestInvoiceBase() { + return `${this.settingsBillingBase()}/invoice`; + }, + settingsBillingMyworkRequestInvoice({ plan, users }: { plan: string; users: number }) { + return `${this.settingsBillingMyworkRequestInvoiceBase()}?plan=${plan}&users=${users}`; + }, + settingsIntegrations() { + return `${this.settingsBase}/integrations`; + }, + settingsIntegrationsGoogleCalendarConnect({ code, state }: { code: string; state?: string }) { + return `${this.settingsBase}/integrations?${GOOGLE_CALENDAR_CONNECT_MODAL_QUERY_PARAM}=true&code=${code}${state ? `&state=${state}` : ''}`; + }, + settingsIntegrationsGoogleCalendarManage() { + return `${this.settingsIntegrations()}?${GOOGLE_CALENDAR_MANAGE_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsWazzupInfo() { + return `${this.settingsIntegrations()}?${WAZZUP_FIRST_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsPbxGroup() { + return `${this.settingsIntegrations()}#${PBX_GROUP_ID}`; + }, + settingsIntegrationsTildaInfo() { + return `${this.settingsIntegrations()}?${TILDA_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsWordpressInfo() { + return `${this.settingsIntegrations()}?${WORDPRESS_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsTwilioInfo() { + return `${this.settingsIntegrations()}?${TWILIO_FIRST_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsFbMessengerInfo() { + return `${this.settingsIntegrations()}?${FB_FIRST_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsSalesforceInfo() { + return `${this.settingsIntegrations()}?${SALESFORCE_FIRST_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsMakeInfo() { + return `${this.settingsIntegrations()}?${MAKE_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsApixDriveInfo() { + return `${this.settingsIntegrations()}?${APIX_DRIVE_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsAlbatoInfo() { + return `${this.settingsIntegrations()}?${ALBATO_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsIntegrationsOneCInfo() { + return `${this.settingsIntegrations()}?${ONE_C_INFO_MODAL_QUERY_PARAM}=true`; + }, + settingsMailing() { + return `${this.settingsBase}/mailing`; + }, + settingsMailingEditMailbox(mailboxId: number) { + return `${this.settingsMailing()}?${MAILBOX_ID_QUERY_PARAM}=${mailboxId}`; + }, + settingsDocumentsBase() { + return `${this.settingsBase}/documents`; + }, + settingsDocumentTemplates() { + return `${this.settingsDocumentsBase()}/templates`; + }, + settingsDocumentCreationFields() { + return `${this.settingsDocumentsBase()}/fields`; + }, + settingsMailingAddMailbox() { + return `${this.settingsBase}/mailing?add=true`; + }, + settingsCallsBase() { + return `${this.settingsBase}/calls`; + }, + settingsCallsAccount() { + return `${this.settingsCallsBase()}/account`; + }, + settingsCallsUsers() { + return `${this.settingsCallsBase()}/users`; + }, + settingsCallsScenarios() { + return `${this.settingsCallsBase()}/scenarios`; + }, + settingsCallsSchemas() { + return `${this.settingsCallsBase()}/schemas`; + }, + settingsSuperadmin() { + return `${this.settingsBase}/superadmin`; + }, + settingsCallsSipRegistrations({ + add, + pbxProviderType, + }: { + add?: boolean; + pbxProviderType?: string; + }) { + return `${this.settingsCallsBase()}/sip-registrations${add ? `?${CommonQueryParams.ADD}=true` : ''}${ + pbxProviderType ? `${add ? '&' : '?'}${PBX_PROVIDER_TYPE_QUERY_PARAM}=${pbxProviderType}` : '' + }`; + }, + entityTypeBoardSettings({ + boardId, + entityTypeId, + tab = EntitiesBoardSettingsTab.AUTOMATION, + from, + }: { + boardId: number; + entityTypeId: number; + tab?: EntitiesBoardSettingsTab; + from?: string; + }) { + return `${this.sectionBase(entityTypeId)}/b/${boardId}/board-settings/${tab}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}`; + }, + activitiesBase: '/activities', + activities: `/activities/${TasksTab.BOARD}`, + activitiesCalendar({ + view, + year, + month, + day, + }: { + view: CalendarView; + year: number; + month: number; + day: number; + }) { + return `/activities/${TasksTab.CALENDAR}/${view}/${year}/${month}/${day}`; + }, + tasksBase: '/tasks', + timeBoardBase() { + return `${this.tasksBase}/deadline`; + }, + timeBoard() { + return `${this.tasksBase}/deadline/${TasksTab.BOARD}`; + }, + timeBoardCalendar({ + view, + year, + month, + day, + }: { + view: CalendarView; + year: number; + month: number; + day: number; + }) { + return `${this.tasksBase}/deadline/${TasksTab.CALENDAR}/${view}/${year}/${month}/${day}`; + }, + tasksBoardBase(boardId: number) { + return `${this.tasksBase}/b/${boardId}`; + }, + tasksBoard(boardId: number) { + return `${this.tasksBoardBase(boardId)}/${TasksTab.BOARD}`; + }, + taskBoardSettings({ + boardId, + from, + entityId, + entityTypeId, + }: { + boardId: number; + from?: string; + entityId?: number; + entityTypeId?: number; + }) { + return `${this.tasksBoardBase(boardId)}/board-settings${from ? `?${CommonQueryParams.FROM}=${from}` : ''}${ + entityId && entityTypeId + ? `${from ? '&' : '?'}entityTypeId=${entityTypeId}&entityId=${entityId}` + : '' + }`; + }, + tasksList(boardId: number) { + return `${this.tasksBoardBase(boardId)}/${TasksTab.LIST}`; + }, + tasksTimeline({ boardId, view }: { boardId: number; view: GanttView }) { + return `${this.tasksBoardBase(boardId)}/${TasksTab.TIMELINE}/${view}`; + }, + projectTasksBoard({ + entityTypeId, + entityId, + from, + }: { + entityTypeId: number; + entityId: number; + from?: string; + }) { + return ( + this.cardBase(entityTypeId) + + `/${entityId}/${CardTab.BOARD}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}` + ); + }, + tasksCalendar({ + boardId, + view, + year, + month, + day, + }: { + boardId: number; + view: CalendarView; + year: number; + month: number; + day: number; + }) { + return `${this.tasksBoardBase(boardId)}/${TasksTab.CALENDAR}/${view}/${year}/${month}/${day}`; + }, + projectTasksCalendar({ + entityTypeId, + entityId, + view, + year, + month, + day, + from, + }: { + entityTypeId: number; + entityId: number; + view: CalendarView; + year: number; + month: number; + day: number; + from?: string; + }) { + return ( + this.cardBase(entityTypeId) + + `/${entityId}/${CardTab.CALENDAR}/${view}/${year}/${month}/${day}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}` + ); + }, + projectTasksList({ + entityTypeId, + entityId, + from, + }: { + entityTypeId: number; + entityId: number; + from?: string; + }) { + return ( + this.cardBase(entityTypeId) + + `/${entityId}/${CardTab.LIST}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}` + ); + }, + projectTasksTimeline({ + entityTypeId, + entityId, + from, + view, + }: { + entityTypeId: number; + entityId: number; + view: GanttView; + from?: string; + }) { + return `${this.cardBase(entityTypeId)}/${entityId}/${CardTab.TIMELINE}/${view}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}`; + }, + partnerInfo(partnerId: number) { + return `/partners/${partnerId}`; + }, + mail: '/mail', + forbiddenPage: `/${HttpStatusCode.Forbidden}`, + multichatBase: '/chat', + multichat(from?: string) { + return `${this.multichatBase}${from ? `?${CommonQueryParams.FROM}=${from}` : ''}`; + }, + multichatChat(chatId: number) { + return `${this.multichatBase}/${chatId}`; + }, + multichatChatMessage({ chatId, messageId }: { chatId: number; messageId: number }) { + return `${this.multichatBase}/${chatId}/message/${messageId}`; + }, + builderBase: '/builder', + builder(tab: BuilderTabs) { + return `${this.builderBase}/${tab}`; + }, + builderCreateSiteForm() { + return `${this.builderBase}/site-forms`; + }, + builderUpdateSiteForm(siteFormId: number) { + return `${this.builderCreateSiteForm()}/${siteFormId}`; + }, + builderCreateHeadlessSiteForm() { + return `${this.builderBase}/site-forms/headless`; + }, + builderUpdateHeadlessSiteForm(siteFormId: number) { + return `${this.builderCreateHeadlessSiteForm()}/${siteFormId}`; + }, + builderCreateOnlineBookingSiteForm() { + return `${this.builderBase}/site-forms/online-booking`; + }, + builderUpdateOnlineBookingSiteForm(siteFormId: number) { + return `${this.builderCreateOnlineBookingSiteForm()}/${siteFormId}`; + }, + builderCreateProductsSection(type: ProductsSectionType) { + return `${this.builderBase}/products?type=${type}`; + }, + builderUpdateProductsSection({ + moduleId, + moduleType, + }: { + moduleId: number; + moduleType: ProductsSectionType; + }) { + return `${this.builderBase}/products/${moduleId}?type=${moduleType}`; + }, + builderCreateEt(module: ModuleCategory) { + return `${this.builderBase}/et?${CommonQueryParams.CATEGORY}=${module}`; + }, + builderUpdateEt(moduleId: number) { + return `${this.builderBase}/et/${moduleId}`; + }, + builderCreateScheduler() { + return `${this.builderBase}/scheduler`; + }, + builderUpdateScheduler(moduleId: number) { + return `${this.builderBase}/scheduler/${moduleId}`; + }, + schedulerBase({ scheduleType, scheduleId }: { scheduleType: ScheduleType; scheduleId: number }) { + return `/scheduler/${scheduleType}/${scheduleId}`; + }, + scheduler({ + scheduleId, + scheduleType, + tab, + }: { + scheduleId: number; + scheduleType: ScheduleType; + tab: SectionView; + }) { + return `${this.schedulerBase({ scheduleId, scheduleType })}/${tab}`; + }, + schedulerReports({ + scheduleId, + scheduleType, + reportsSection, + }: { + scheduleId: number; + scheduleType: ScheduleType; + reportsSection: ReportsSection; + }) { + return `${this.schedulerBase({ scheduleId, scheduleType })}/${SectionView.REPORTS}?${CommonQueryParams.SECTION}=${reportsSection}`; + }, + schedulerClients({ + scheduleId, + scheduleType, + entityTypeId, + }: { + scheduleId: number; + scheduleType: ScheduleType; + entityTypeId: number; + }) { + return `${this.schedulerBase({ scheduleId, scheduleType })}/${generateSchedulerLinkedEntityTypeTab(entityTypeId)}`; + }, + notes: '/notes', + notFoundPage: `/${HttpStatusCode.NotFound}`, +}; diff --git a/frontend/src/app/store/AppStore.ts b/frontend/src/app/store/AppStore.ts new file mode 100644 index 0000000..855c7b1 --- /dev/null +++ b/frontend/src/app/store/AppStore.ts @@ -0,0 +1,89 @@ +import { watchdogStore } from '@/app'; +import { authStore } from '@/modules/auth'; +import { mailboxSettingsStore } from '@/modules/mailing'; +import { notificationsStore, toastNotificationsStore } from '@/modules/notifications'; +import { productsModuleStore } from '@/modules/products'; +import { schedulerEventHandlerStore } from '@/modules/scheduler'; +import { departmentsSettingsStore } from '@/modules/settings'; +import { activityTypeStore, taskSettingsStore } from '@/modules/tasks'; +import { voximplantConnectorStore } from '@/modules/telephony'; +import type { DataStore, SubscriberStore } from '@/shared'; +import { flow, makeAutoObservable, runInAction, when } from 'mobx'; +import { entityTypeStore } from './EntityTypeStore'; +import { featureStore } from './FeatureStore'; +import { generalSettingsStore } from './GeneralSettingsStore'; +import { identityStore } from './IdentityStore'; +import { subscriptionStore } from './SubscriptionStore'; +import { userStore } from './UserStore'; + +class AppStore { + isLoaded = false; + + private _dataStores: DataStore[] = [ + userStore, + identityStore, + featureStore, + entityTypeStore, + subscriptionStore, + activityTypeStore, + taskSettingsStore, + productsModuleStore, + generalSettingsStore, + departmentsSettingsStore, + notificationsStore, + mailboxSettingsStore, + ]; + + private _subscriberStores: SubscriberStore[] = [ + notificationsStore, + toastNotificationsStore, + schedulerEventHandlerStore, + ]; + + constructor() { + makeAutoObservable(this); + } + + load = flow(function* (this: AppStore) { + try { + // wait for auth + yield when(() => authStore.isAuthenticated); + + // we assume that this data is important but not mandatory for app functionality, + // so some promises could possibly reject + Promise.allSettled([voximplantConnectorStore.loadData()]); + + // load global stores, we assume that this data is essential for app functionality + yield Promise.all(this._dataStores.map(ds => ds.loadData())); + + // subscribe to global events + yield Promise.all(this._subscriberStores.map(ss => ss.subscribe())); + + this._dataStores.forEach(ds => watchdogStore.watch(ds)); + } catch (e) { + throw new Error(`Error loading app: ${e}`); + } finally { + this.isLoaded = true; + } + }); + + reset = (): void => { + // clear all global stores + runInAction(() => { + voximplantConnectorStore.reset(); + + this._dataStores.forEach(ds => ds.reset()); + }); + + // unsubscribe from global events + runInAction(() => { + this._subscriberStores.forEach(ss => ss.unsubscribe()); + }); + + watchdogStore.reset(); + + this.isLoaded = false; + }; +} + +export const appStore = new AppStore(); diff --git a/frontend/src/app/store/EntityTypeStore.ts b/frontend/src/app/store/EntityTypeStore.ts new file mode 100644 index 0000000..d4667f4 --- /dev/null +++ b/frontend/src/app/store/EntityTypeStore.ts @@ -0,0 +1,185 @@ +import { authStore } from '@/modules/auth'; +import { + EntityCategory, + PermissionLevel, + PermissionObjectType, + type DataStore, + type EntityType, + type Nullable, + type Option, +} from '@/shared'; +import { computed, makeAutoObservable, toJS } from 'mobx'; +import { entityTypeApi, type UpdateEntityTypeDto, type UpdateEntityTypeFieldsModel } from '../api'; + +export class EntityTypeStore implements DataStore { + entityTypes: EntityType[] = []; + + isLoaded = false; + + constructor() { + makeAutoObservable(this); + } + + get entityTypesOptions(): Option[] { + return this.sortedEntityTypes.map>(et => ({ + value: et.id, + label: et.section.name, + })); + } + + get contacts(): EntityType[] { + return this.getByCategory(EntityCategory.CONTACT); + } + + get contactsOptions(): Option[] { + return this.contacts.map>(et => ({ value: et.id, label: et.name })); + } + + get companies(): EntityType[] { + return this.getByCategory(EntityCategory.COMPANY); + } + + get companiesOptions(): Option[] { + return this.companies.map>(et => ({ value: et.id, label: et.name })); + } + + get contactsAndCompaniesOptions(): Option[] { + return [...this.contactsOptions, ...this.companiesOptions]; + } + + get firstDealEntityTypeId(): Nullable { + return this.sortedEntityTypes.find(et => et.entityCategory === EntityCategory.DEAL)?.id ?? null; + } + + get entityTypesExceptContactAndCompanies(): EntityType[] { + return this.sortedEntityTypes.filter( + et => ![EntityCategory.CONTACT, EntityCategory.COMPANY].includes(et.entityCategory) + ); + } + + get entityTypesExceptContactAndCompaniesOptions(): Option[] { + return this.entityTypesExceptContactAndCompanies.map>(et => ({ + value: et.id, + label: et.name, + })); + } + + get firstEntityTypeId(): number { + const firstEntityTypeId = this.sortedEntityTypes[0]?.id; + + if (!firstEntityTypeId) + throw new Error('First entity type was not found, no entity types exist'); + + return firstEntityTypeId; + } + + @computed.struct + get sortedEntityTypes(): EntityType[] { + return toJS(this.entityTypes).sort((a, b) => a.sortOrder - b.sortOrder); + } + + deleteEntityType = async (entityTypeId: number): Promise => { + await entityTypeApi.deleteEntityType(entityTypeId); + + this.entityTypes = this.entityTypes.filter(et => et.id !== entityTypeId); + }; + + updateEntityType = async (entityType: UpdateEntityTypeDto): Promise => { + const updatedEntityType = await entityTypeApi.updateEntityType(entityType); + + this.entityTypes = this.entityTypes.map(et => + et.id === entityType.id ? updatedEntityType : et + ); + + return updatedEntityType; + }; + + updateEntityTypeFields = async (model: UpdateEntityTypeFieldsModel): Promise => { + const updated = await entityTypeApi.updateEntityTypeFields(model); + const idx = this.entityTypes.findIndex(et => et.id === model.entityTypeId); + + this.entityTypes.splice(idx, 1, updated); + + return updated; + }; + + getById = (id: number): EntityType => { + const entityType = this.entityTypes.find(et => et.id === id); + + if (!entityType) throw new Error(`Entity type with id ${id} was not found`); + + return entityType; + }; + + getByIdAsync = async (id: number): Promise => { + try { + const entityType = await entityTypeApi.getEntityType(id); + + // invalidate cache + this.entityTypes = this.entityTypes.map(et => (et.id === id ? entityType : et)); + + return entityType; + } catch (e) { + throw new Error(`Failed to get entity type ${id}: ${e}`); + } + }; + + loadData = async (): Promise => { + try { + this.entityTypes = await entityTypeApi.getEntityTypes(); + } catch (e) { + throw new Error(`Failed to load entity types: ${e}`); + } finally { + this.isLoaded = true; + } + }; + + invalidateEntityTypesInCache = async (): Promise => { + try { + this.entityTypes = await entityTypeApi.getEntityTypes(); + } catch (e) { + throw new Error(`Failed to invalidate entity types cache: ${e}`); + } + }; + + getByCategory = (entityCategory: EntityCategory): EntityType[] => { + return this.sortedEntityTypes.filter(et => et.entityCategory === entityCategory); + }; + + getAvailableEntityTypes = (): EntityType[] => { + const { user: currentUser } = authStore; + + if (!currentUser) throw new Error('User is not logged in'); + + const entityTypes = this.sortedEntityTypes; + const objectPermissions = currentUser.objectPermissions; + + return authStore.user?.isAdmin() + ? entityTypes + : entityTypes.filter(et => { + const permission = objectPermissions.find( + op => op.objectType === PermissionObjectType.ENTITY_TYPE && op.objectId === et.id + ); + + return permission ? permission.viewPermission !== PermissionLevel.DENIED : true; + }); + }; + + getLinkedDealsOptions = (etId: number): Option[] => { + return this.getById(etId).linkedEntityTypes.flatMap>(l => { + const linkedEt = entityTypeStore.getById(l.targetId); + + return linkedEt.entityCategory === EntityCategory.DEAL + ? [{ label: linkedEt.name, value: linkedEt.id }] + : []; + }); + }; + + reset = (): void => { + this.entityTypes = []; + + this.isLoaded = false; + }; +} + +export const entityTypeStore = new EntityTypeStore(); diff --git a/frontend/src/app/store/FeatureStore.ts b/frontend/src/app/store/FeatureStore.ts new file mode 100644 index 0000000..ab0b656 --- /dev/null +++ b/frontend/src/app/store/FeatureStore.ts @@ -0,0 +1,25 @@ +import type { DataStore } from '@/shared'; +import { makeAutoObservable } from 'mobx'; +import { featureApi, type FeatureDto } from '../api'; + +class FeatureStore implements DataStore { + features: FeatureDto[] = []; + + constructor() { + makeAutoObservable(this); + } + + loadData = async (): Promise => { + try { + this.features = await featureApi.getFeatures(); + } catch (e) { + throw new Error(`Error while loading features: ${e}`); + } + }; + + reset = (): void => { + this.features = []; + }; +} + +export const featureStore = new FeatureStore(); diff --git a/frontend/src/app/store/GeneralSettingsFormData.ts b/frontend/src/app/store/GeneralSettingsFormData.ts new file mode 100644 index 0000000..1732be0 --- /dev/null +++ b/frontend/src/app/store/GeneralSettingsFormData.ts @@ -0,0 +1,46 @@ +import type { Account, AccountSettings } from '@/modules/settings'; +import { BooleanModel, InputModel, MultiselectModel, SelectModel, WeekDays } from '@/shared'; +import { makeAutoObservable } from 'mobx'; + +export class GeneralSettingsFormData { + companyName: InputModel; + subdomain: InputModel; + language: SelectModel; + workingDays: MultiselectModel; + startOfWeek: SelectModel; + workingTimeFrom: InputModel; + workingTimeTo: InputModel; + timeZone: SelectModel; + currency: SelectModel; + numberFormat: SelectModel; + phoneFormat: SelectModel; + dateFormat: SelectModel; + allowDuplicates: BooleanModel; + + constructor() { + makeAutoObservable(this); + } + + initializeFormData = ({ + account, + accountSettings, + }: { + account: Account; + accountSettings: AccountSettings; + }) => { + this.companyName = InputModel.create(account.companyName); + this.subdomain = InputModel.create(account.subdomain); + + this.language = SelectModel.create(accountSettings.language).required(); + this.workingDays = MultiselectModel.createFromNullable(accountSettings.workingDays); + this.startOfWeek = SelectModel.create(accountSettings.startOfWeek ?? WeekDays.MONDAY); + this.workingTimeFrom = InputModel.create(accountSettings.workingTimeFrom); + this.workingTimeTo = InputModel.create(accountSettings.workingTimeTo); + this.timeZone = SelectModel.create(accountSettings.timeZone); + this.currency = SelectModel.create(accountSettings.currency).required(); + this.numberFormat = SelectModel.create(accountSettings.numberFormat); + this.phoneFormat = SelectModel.create(accountSettings.phoneFormat).required(); + this.dateFormat = SelectModel.create(accountSettings.dateFormat ?? null); + this.allowDuplicates = BooleanModel.create(accountSettings.allowDuplicates); + }; +} diff --git a/frontend/src/app/store/GeneralSettingsStore.ts b/frontend/src/app/store/GeneralSettingsStore.ts new file mode 100644 index 0000000..87c6bea --- /dev/null +++ b/frontend/src/app/store/GeneralSettingsStore.ts @@ -0,0 +1,185 @@ +import type { Account, AccountSettings } from '@/modules/settings'; +import { + type DataStore, + DayNumberMap, + type Nullable, + type Optional, + UtcDate, + type WeekDays, +} from '@/shared'; +import { makeAutoObservable } from 'mobx'; +import { generalSettingsApi, type UpdateAccountSettingsDto } from '../api'; +import { GeneralSettingsFormData } from './GeneralSettingsFormData'; + +class GeneralSettingsStore implements DataStore { + account: Nullable = null; + accountSettings: Nullable = null; + generalSettingsForm: GeneralSettingsFormData = new GeneralSettingsFormData(); + + isLoaded = false; + hasDemoData = false; + isDemoDeleting = false; + + constructor() { + makeAutoObservable(this); + } + + get updateAccountSettingsDto(): UpdateAccountSettingsDto { + return { + language: this.generalSettingsForm.language.value, + workingDays: + this.generalSettingsForm.workingDays.values.length > 0 + ? this.generalSettingsForm.workingDays.values + : null, + startOfWeek: this.generalSettingsForm.startOfWeek.value, + workingTimeFrom: this.generalSettingsForm.workingTimeFrom.value, + workingTimeTo: this.generalSettingsForm.workingTimeTo.value, + timeZone: this.generalSettingsForm.timeZone.value, + currency: this.generalSettingsForm.currency.value, + numberFormat: this.generalSettingsForm.numberFormat.value, + phoneFormat: this.generalSettingsForm.phoneFormat.value, + dateFormat: this.generalSettingsForm.dateFormat.value, + allowDuplicates: this.generalSettingsForm.allowDuplicates.value, + }; + } + + get startOfWeekAsNumber(): Optional { + if (!this.accountSettings?.startOfWeek) return undefined; + + return DayNumberMap[this.accountSettings.startOfWeek]; + } + + get workingDaysAsNumberArray(): Nullable { + if (!this.accountSettings?.workingDays) return null; + + return this.accountSettings.workingDays.map(day => DayNumberMap[day as WeekDays]); + } + + get nonWorkingDaysAsNumberArray(): Nullable { + const accountWorkingDays = this.workingDaysAsNumberArray; + + if (!accountWorkingDays) return null; + + const wholeWeekAsNumberArray = [0, 1, 2, 3, 4, 5, 6]; + + return wholeWeekAsNumberArray.filter(d => !accountWorkingDays.includes(d)); + } + + get accountCreationYear(): Nullable { + if (!this.account) return null; + + return this.account.createdAt.year; + } + + loadAccount = async (): Promise => { + try { + this.account = await generalSettingsApi.getAccount(); + } catch (e) { + throw new Error(`Failed to load account: ${e}`); + } + }; + + loadAccountSettings = async (): Promise => { + try { + this.accountSettings = await generalSettingsApi.getAccountSettings(); + } catch (e) { + throw new Error(`Failed to load account settings: ${e}`); + } + }; + + loadDemoDataExists = async (): Promise => { + try { + this.hasDemoData = await generalSettingsApi.getDemoDataExists(); + } catch (e) { + this.hasDemoData = false; + } + }; + + loadData = async (): Promise => { + try { + await Promise.all([ + this.loadAccount(), + this.loadAccountSettings(), + this.loadDemoDataExists(), + ]); + + if (this.account && this.accountSettings) { + this.generalSettingsForm.initializeFormData({ + account: this.account, + accountSettings: this.accountSettings, + }); + + UtcDate.setLocale(this.accountSettings.language); + UtcDate.setFormat(this.accountSettings.dateFormat); + } + } catch (e) { + throw new Error(`Failed to load general settings store data: ${e}`); + } finally { + this.isLoaded = true; + } + }; + + updateAccountSettings = async (): Promise => { + this.accountSettings = await generalSettingsApi.updateAccountSettings( + this.updateAccountSettingsDto + ); + }; + + uploadAccountLogo = async (blob: Blob): Promise => { + const formData = new FormData(); + formData.append('logo', blob, 'logo.jpg'); + + try { + this.account = await generalSettingsApi.uploadAccountLogo(formData); + } catch (e) { + throw new Error(`Failed to upload account logo: ${e}`); + } + }; + + removeAccountLogo = async (): Promise => { + try { + this.account = await generalSettingsApi.removeAccountLogo(); + } catch (e) { + throw new Error(`Failed to remove account logo: ${e}`); + } + }; + + deleteDemoData = async (): Promise => { + try { + this.isDemoDeleting = true; + + await generalSettingsApi.deleteDemoData(); + + this.hasDemoData = false; + } catch (e) { + throw new Error(`Failed to delete demo data: ${e}`); + } finally { + this.isDemoDeleting = false; + } + }; + + clearApplicationCache = async (): Promise => { + const doNotClearKeys: string[] = [ + 'accountId', + 'id_pools', + 'i18nextLng', + 'gaUserId', + 'token', + 'userId', + ]; + + for (const key in localStorage) { + if (!doNotClearKeys.includes(key)) localStorage.removeItem(key); + } + + window.location.reload(); + }; + + reset = (): void => { + this.isLoaded = false; + this.account = null; + this.accountSettings = null; + }; +} + +export const generalSettingsStore = new GeneralSettingsStore(); diff --git a/frontend/src/app/store/IconStore.tsx b/frontend/src/app/store/IconStore.tsx new file mode 100644 index 0000000..44523a4 --- /dev/null +++ b/frontend/src/app/store/IconStore.tsx @@ -0,0 +1,412 @@ +import { + BoxIcon, + BriefcaseIcon, + Building1Icon, + Building2Icon, + Building3Icon, + BulbIcon, + Calendar1Icon, + Calendar2Icon, + Calendar3Icon, + Calendar4Icon, + CallIcon, + Chart1Icon, + Chart2Icon, + Clock1Icon, + Clock2Icon, + CrownIcon, + EqualizerIcon, + HandIcon, + InOutIcon, + LightningIcon, + MailIcon, + Product1Icon, + Product2Icon, + Rocket1Icon, + Rocket2Icon, + Rocket3Icon, + Settings1Icon, + Settings2Icon, + Settings3Icon, + Settings4Icon, + Shapes1Icon, + Shapes2Icon, + Shapes3Icon, + Shapes4Icon, + SiteFormIcon, + SoundIcon, + Star1Icon, + Star2Icon, + Star3Icon, + Target1Icon, + Target2Icon, + Tick1Icon, + Tick2Icon, + Tie1Icon, + Tie2Icon, + User1Icon, + User2Icon, + User3Icon, + User4Icon, + type Icon, +} from '@/shared'; +import { EntityCategory } from '@/shared/lib/models/EntityType/EntityCategory'; +import { IconName } from '@/shared/lib/models/Icon/IconName'; + +interface IconWithColor { + icon: Icon; + color: string; +} +class IconStore { + icons: Icon[] = [ + { + name: IconName.ROCKET_1, + icon: , + }, + { + name: IconName.ROCKET_2, + icon: , + }, + { + name: IconName.ROCKET_3, + icon: , + }, + { + name: IconName.CROWN, + icon: , + }, + { + name: IconName.LIGHTNING, + icon: , + }, + { + name: IconName.TICK_1, + icon: , + }, + { + name: IconName.TICK_2, + icon: , + }, + { + name: IconName.BULB, + icon: , + }, + { + name: IconName.TARGET_1, + icon: , + }, + { + name: IconName.TARGET_2, + icon: , + }, + { + name: IconName.STAR_1, + icon: , + }, + { + name: IconName.STAR_2, + icon: , + }, + { + name: IconName.STAR_3, + icon: , + }, + { + name: IconName.CHART_1, + icon: , + }, + { + name: IconName.CHART_2, + icon: , + }, + { + name: IconName.CLOCK_1, + icon: , + }, + { + name: IconName.CLOCK_2, + icon: , + }, + { + name: IconName.SHAPES_1, + icon: , + }, + { + name: IconName.SHAPES_2, + icon: , + }, + { + name: IconName.SHAPES_3, + icon: , + }, + { + name: IconName.SHAPES_4, + icon: , + }, + { + name: IconName.USER_1, + icon: , + }, + { + name: IconName.USER_2, + icon: , + }, + { + name: IconName.USER_3, + icon: , + }, + { + name: IconName.USER_4, + icon: , + }, + { + name: IconName.HAND, + icon: , + }, + { + name: IconName.BUILDING_1, + icon: , + }, + { + name: IconName.BUILDING_2, + icon: , + }, + { + name: IconName.BUILDING_3, + icon: , + }, + { + name: IconName.TIE_1, + icon: , + }, + { + name: IconName.TIE_2, + icon: , + }, + { + name: IconName.IN_OUT, + icon: , + }, + { + name: IconName.PRODUCT_1, + icon: , + }, + { + name: IconName.PRODUCT_2, + icon: , + }, + { + name: IconName.BOX, + icon: , + }, + { + name: IconName.BRIEFCASE, + icon: , + }, + { + name: IconName.CALENDAR_1, + icon: , + }, + { + name: IconName.CALENDAR_2, + icon: , + }, + { + name: IconName.CALENDAR_3, + icon: , + }, + { + name: IconName.CALENDAR_4, + icon: , + }, + { + name: IconName.EQUALIZER, + icon: , + }, + { + name: IconName.SOUND, + icon: , + }, + { + name: IconName.MAIL, + icon: , + }, + { + name: IconName.SETTINGS_1, + icon: , + }, + { + name: IconName.SETTINGS_2, + icon: , + }, + { + name: IconName.SETTINGS_3, + icon: , + }, + { + name: IconName.SETTINGS_4, + icon: , + }, + { + name: IconName.CALL, + icon: , + }, + { + name: IconName.SITE_FORM, + icon: , + }, + ]; + + defaultEntityIconWithColor: Map = new Map([ + [ + EntityCategory.COMPANY, + { + icon: { + name: IconName.BUILDING_2, + icon: , + }, + color: 'var(--primary-statuses-orange-440)', + }, + ], + [ + EntityCategory.CONTACT, + { + icon: { + name: IconName.USER_2, + icon: , + }, + color: 'var(--primary-statuses-noun-440)', + }, + ], + [ + EntityCategory.CONTRACTOR, + { + icon: { + name: IconName.TIE_2, + icon: , + }, + color: 'var(--primary-statuses-amethyst-360)', + }, + ], + [ + EntityCategory.DEAL, + { + icon: { + name: IconName.CROWN, + icon: , + }, + color: 'var(--primary-statuses-pink-360)', + }, + ], + [ + EntityCategory.HR, + { + icon: { + name: IconName.STAR_2, + icon: , + }, + color: 'var(--primary-statuses-purple-360)', + }, + ], + [ + EntityCategory.PARTNER, + { + icon: { + name: IconName.SHAPES_3, + icon: , + }, + color: 'var(--primary-statuses-amethyst-360)', + }, + ], + [ + EntityCategory.PROJECT, + { + icon: { + name: IconName.BULB, + icon: , + }, + color: 'var(--primary-statuses-blue-360)', + }, + ], + [ + EntityCategory.SUPPLIER, + { + icon: { + name: IconName.PRODUCT_1, + icon: , + }, + color: 'var(--primary-statuses-amethyst-360)', + }, + ], + [ + EntityCategory.UNIVERSAL, + { + icon: { + name: IconName.STAR_1, + icon: , + }, + color: 'var(--primary-statuses-malachite-480)', + }, + ], + ]); + + get firstIcon(): Icon { + const firstIcon = this.icons[0]; + + if (!firstIcon) throw new Error('IconStore has no icons, failed to get the first icon'); + + return firstIcon; + } + + getByName = (name: IconName): Icon => { + const icon = this.icons.find(i => i.name === name); + + if (!icon) { + console.error(`Icon with name ${name} was not found`); + + return this.firstIcon; + } + + return icon; + }; + + get defaultModuleColor(): string { + return 'var(--primary-statuses-crimson-360)'; + } + + get defaultSchedulerIcon(): Icon { + const icon = this.icons.find(i => i.name === IconName.CALENDAR_4); + + return icon ?? this.firstIcon; + } + + get defaultProductsIcon(): Icon { + const icon = this.icons.find(i => i.name === IconName.BOX); + + return icon ?? this.firstIcon; + } + + get schedulerColor(): string { + return 'var(--primary-statuses-crimson-360)'; + } + + get productsColor(): string { + return 'var(--primary-statuses-salad-480)'; + } + + get systemModuleColor(): string { + return 'var(--primary-statuses-green-520)'; + } + + getDefaultIconByEntityCategory = (entityCategory: EntityCategory): Icon => { + const icon = this.defaultEntityIconWithColor.get(entityCategory)?.icon; + + return icon ?? this.firstIcon; + }; + + getEntityColorByEntityCategory = (entityCategory: EntityCategory): string => { + return this.defaultEntityIconWithColor.get(entityCategory)?.color ?? this.defaultModuleColor; + }; +} + +export const iconStore = new IconStore(); diff --git a/frontend/src/app/store/IdentityStore.ts b/frontend/src/app/store/IdentityStore.ts new file mode 100644 index 0000000..e788fc1 --- /dev/null +++ b/frontend/src/app/store/IdentityStore.ts @@ -0,0 +1,129 @@ +import type { DataStore } from '@/shared'; +import { makeAutoObservable } from 'mobx'; +import { SequenceName, identityApi, type IdentityPool } from '../api'; + +const ID_POOLS_LS_KEY = 'id_pools'; + +class IdentityStore implements DataStore { + isFetchingMore = false; + + constructor() { + makeAutoObservable(this); + } + + loadData = async (): Promise => { + const poolsInStorage = localStorage.getItem(ID_POOLS_LS_KEY); + + if (poolsInStorage) return; + + try { + const pools = await identityApi.getAllIdPools(); + + localStorage.setItem(ID_POOLS_LS_KEY, JSON.stringify(pools)); + } catch (e) { + throw new Error(`Error while loading id pools: ${e}`); + } + }; + + getFieldId = (): number => { + return this.getIdFromPool(SequenceName.FIELD); + }; + + getFieldGroupId = (): number => { + return this.getIdFromPool(SequenceName.FIELD_GROUP); + }; + + getFieldOptionId = (): number => { + return this.getIdFromPool(SequenceName.FIELD_OPTION); + }; + + getPoolFromStorage = (seqName: SequenceName): { pool: IdentityPool; idx: number } => { + const parsedPools = this.getParsedPoolsFromStorage(); + + const idx = parsedPools.findIndex(p => p.name === seqName); + const parsedPool = idx === -1 ? null : parsedPools[idx]; + + if (!parsedPool) throw new Error(`Can not get id pool for sequence ${seqName}`); + + return { pool: parsedPool, idx }; + }; + + getParsedPoolsFromStorage = (): IdentityPool[] => { + const pools = localStorage.getItem(ID_POOLS_LS_KEY); + + if (!pools) throw new Error('Id pools are not initialized'); + + return JSON.parse(pools) as IdentityPool[]; + }; + + addIdsToPool = ({ seqName, ids }: { seqName: SequenceName; ids: number[] }): void => { + const { pool, idx } = this.getPoolFromStorage(seqName); + + const parsedPools = this.getParsedPoolsFromStorage(); + const parsedPool = parsedPools[idx]; + + if (!parsedPool) throw new Error(`Can not addIdsToPool, parsed pool was not found, ${seqName}`); + + parsedPool.values = [...pool.values, ...ids]; + + localStorage.setItem(ID_POOLS_LS_KEY, JSON.stringify(parsedPools)); + }; + + updateIdsInPool = ({ seqName, ids }: { seqName: SequenceName; ids: number[] }): void => { + const parsedPools = this.getParsedPoolsFromStorage(); + const { idx } = this.getPoolFromStorage(seqName); + + const parsedPool = parsedPools[idx]; + + if (!parsedPool) + throw new Error(`Can not updateIdsInPool, parsed pool was not found, ${seqName}`); + + parsedPool.values = ids; + + localStorage.setItem(ID_POOLS_LS_KEY, JSON.stringify(parsedPools)); + }; + + getIdFromPool = (seqName: SequenceName): number => { + const { pool } = this.getPoolFromStorage(seqName); + + let poolValues = pool.values; + + if (poolValues.length <= 10 && !this.isFetchingMore) { + let additionalIds: number[] = []; + + this.isFetchingMore = true; + + identityApi + .getIdPoolValues(seqName) + .then(res => { + additionalIds = res; + + this.addIdsToPool({ seqName, ids: additionalIds }); + }) + .catch(err => { + console.error('Error while getting more ids for pool', err); + }) + .finally(() => { + this.isFetchingMore = false; + }); + } + + const id = poolValues.shift(); + + if (id) { + this.updateIdsInPool({ seqName, ids: poolValues }); + + return id; + } + + throw new Error(`Can not get id from pool ${seqName}`); + }; + + reset = (): void => { + localStorage.removeItem(ID_POOLS_LS_KEY); + + this.isFetchingMore = false; + }; +} + +export const identityStore = new IdentityStore(); diff --git a/frontend/src/app/store/RecordSingularityStore.ts b/frontend/src/app/store/RecordSingularityStore.ts new file mode 100644 index 0000000..a7124a8 --- /dev/null +++ b/frontend/src/app/store/RecordSingularityStore.ts @@ -0,0 +1,22 @@ +import { type Nullable } from '@/shared'; +import { makeAutoObservable } from 'mobx'; + +// we want to only play one record at a time (e.g. in Player component), +// so we need to store the current playing record url +class RecordSingularityStore { + currentRecordUrl: Nullable = null; + + constructor() { + makeAutoObservable(this); + } + + setCurrentRecordUrl = (url: Nullable): void => { + this.currentRecordUrl = url; + }; + + clear = (): void => { + this.currentRecordUrl = null; + }; +} + +export const recordSingularityStore = new RecordSingularityStore(); diff --git a/frontend/src/app/store/RecordStateStore.ts b/frontend/src/app/store/RecordStateStore.ts new file mode 100644 index 0000000..55e5700 --- /dev/null +++ b/frontend/src/app/store/RecordStateStore.ts @@ -0,0 +1,44 @@ +import { type RecordState } from '@/shared'; +import { makeAutoObservable } from 'mobx'; + +export class RecordStateStore { + recordState: RecordState; + + constructor(recordState: RecordState) { + this.recordState = recordState; + + makeAutoObservable(this); + } + + setPlaybackRate = (rate: RecordState['playbackRate']): void => { + this.recordState.playbackRate = rate; + }; + + setPlaying = (playing: RecordState['playing']): void => { + this.recordState.playing = playing; + }; + + togglePlaying = (): void => { + this.recordState.playing = !this.recordState.playing; + }; + + setPlayed = (played: RecordState['played']): void => { + this.recordState.played = played; + }; + + setSeekingStatus = (status: RecordState['seekingStatus']): void => { + this.recordState.seekingStatus = status; + }; + + setDuration = (duration: RecordState['duration']): void => { + this.recordState.duration = duration; + }; + + setExpanded = (expanded: RecordState['expanded']): void => { + this.recordState.expanded = expanded; + }; + + toggleExpanded = (): void => { + this.recordState.expanded = !this.recordState.expanded; + }; +} diff --git a/frontend/src/app/store/SettingsStore.ts b/frontend/src/app/store/SettingsStore.ts new file mode 100644 index 0000000..a94fc6e --- /dev/null +++ b/frontend/src/app/store/SettingsStore.ts @@ -0,0 +1,22 @@ +import { makeAutoObservable, observe } from 'mobx'; + +export class SettingsStore> { + settings: T; + + private constructor(name: string) { + const stored = localStorage.getItem(name); + this.settings = stored ? (JSON.parse(stored) as T) : ({} as T); + + makeAutoObservable(this); + } + + static getSettingsStore>(name: string): SettingsStore { + const store = new SettingsStore(name); + + observe(store.settings, () => { + localStorage.setItem(name, JSON.stringify(store.settings)); + }); + + return store; + } +} diff --git a/frontend/src/app/store/SubscriptionStore.ts b/frontend/src/app/store/SubscriptionStore.ts new file mode 100644 index 0000000..ef520d2 --- /dev/null +++ b/frontend/src/app/store/SubscriptionStore.ts @@ -0,0 +1,100 @@ +import type { DataStore, Nullable, Subscription, SubscriptionPlan } from '@/shared'; +import { makeAutoObservable } from 'mobx'; +import { subscriptionApi } from '../api'; + +class SubscriptionStore implements DataStore { + subscription: Nullable = null; + + isGettingPortalUrl = false; + isSubscriptionLoaded = false; + areSubscriptionPlansLoaded = false; + + constructor() { + makeAutoObservable(this); + } + + get isTrial(): boolean { + if (!this.subscription) return false; + + return this.subscription.isTrial; + } + + get isValid(): boolean { + if (!this.subscription) return false; + + return this.subscription.isValid; + } + + loadData = async (): Promise => { + try { + this.subscription = await subscriptionApi.getSubscription(); + } catch (e) { + throw new Error(`Error while loading subscription: ${e}`); + } finally { + this.isSubscriptionLoaded = true; + } + }; + + getSubscriptionPlans = async (): Promise => { + try { + this.areSubscriptionPlansLoaded = false; + + return await subscriptionApi.getSubscriptionPlans(); + } catch (e) { + throw new Error(`Error while loading subscription plans: ${e}`); + } finally { + this.areSubscriptionPlansLoaded = true; + } + }; + + getCheckoutUrl = async ({ + amount, + priceId, + productId, + couponId, + numberOfUsers, + }: { + numberOfUsers: number; + amount?: number; + priceId?: string; + couponId?: string; + productId?: string; + }): Promise => { + try { + this.isGettingPortalUrl = true; + + return await subscriptionApi.getCheckoutUrl({ + productId, + amount, + priceId, + couponId, + numberOfUsers, + }); + } catch (e) { + throw new Error(`Error while loading subscription plans: ${e}`); + } finally { + this.isGettingPortalUrl = false; + } + }; + + getPortalUrl = async (): Promise => { + try { + this.isGettingPortalUrl = true; + + return await subscriptionApi.getPortalUrl(); + } catch (e) { + throw new Error(`Error while loading subscription plans: ${e}`); + } finally { + this.isGettingPortalUrl = false; + } + }; + + reset = (): void => { + this.subscription = null; + + this.areSubscriptionPlansLoaded = false; + this.isGettingPortalUrl = false; + }; +} + +export const subscriptionStore = new SubscriptionStore(); diff --git a/frontend/src/app/store/UserProfileStore.ts b/frontend/src/app/store/UserProfileStore.ts new file mode 100644 index 0000000..4e113df --- /dev/null +++ b/frontend/src/app/store/UserProfileStore.ts @@ -0,0 +1,43 @@ +import { userStore } from '@/app'; +import { type Nullable, type UserProfile } from '@/shared'; +import { makeAutoObservable } from 'mobx'; +import { userProfileApi, type UpdateUserDto, type UpdateUserProfileDto } from '../api'; + +export class UserProfileStore { + userProfile: Nullable = null; + + isLoading = false; + + constructor() { + makeAutoObservable(this); + } + + loadData = async (id: number): Promise => { + try { + this.isLoading = true; + + this.userProfile = await userProfileApi.getUserProfile(id); + } catch (e) { + throw new Error(`Failed to load user profile: ${e}`); + } finally { + this.isLoading = false; + } + }; + + update = async ({ + id, + profileDto, + userDto, + }: { + id: number; + profileDto: UpdateUserProfileDto; + userDto: UpdateUserDto; + }): Promise => { + if (this.userProfile) this.userProfile.update(profileDto); + + await Promise.all([ + userProfileApi.updateUserProfile({ id, dto: profileDto }), + userStore.update({ id, dto: userDto }), + ]); + }; +} diff --git a/frontend/src/app/store/UserStore.ts b/frontend/src/app/store/UserStore.ts new file mode 100644 index 0000000..22042d3 --- /dev/null +++ b/frontend/src/app/store/UserStore.ts @@ -0,0 +1,153 @@ +import { authStore } from '@/modules/auth'; +import { resetAllSchedulesQueries } from '@/modules/scheduler'; +import { UserRole, type DataStore, type Nullable, type Option, type User } from '@/shared'; +import { makeAutoObservable } from 'mobx'; +import { + userApi, + type ChangeUserPasswordDto, + type CreateUserDto, + type UpdateUserDto, +} from '../api'; + +export class UserStore implements DataStore { + private _users: User[] = []; + + isLoaded = false; + + constructor() { + makeAutoObservable(this); + } + + get allUsers(): User[] { + return this._users; + } + + get activeUsers(): User[] { + return this._users.filter(u => u.isActive); + } + + get activeUsersWithoutDepartments(): User[] { + return this.activeUsers.filter(u => !u.departmentId); + } + + get activeUsersWithoutCurrent(): User[] { + const currentUser = authStore.user; + + return this.activeUsers.filter(u => u.id !== currentUser?.id); + } + + get firstActiveUser(): User { + const user = this.activeUsers[0]; + + if (!user) throw new Error('No active users available, failed to get the first active user'); + + return user; + } + + get activeUserOptions(): Option[] { + return this.activeUsers.map>(u => ({ + value: u.id, + label: u.fullName, + })); + } + + getById = (id: number): User => { + const user = this._users.find(u => u.id === id); + + if (!user) throw new Error(`User with id ${id} was not found`); + + return user; + }; + + getActiveById = (id: number): User => { + const user = this.getById(id); + + if (!user) throw new Error(`User with id ${id} not active`); + + return user; + }; + + getDepartmentActiveUsers = (departmentId: number): User[] => { + return this.activeUsers.filter(u => u.departmentId === departmentId); + }; + + loadData = async (): Promise => { + try { + this.isLoaded = false; + + this._users = await userApi.getUsers(); + } catch (e) { + throw new Error(`Error while loading users: ${e}`); + } finally { + this.isLoaded = true; + } + }; + + invalidateUsersCache = async (): Promise => { + this._users = await userApi.getUsers(); + }; + + delete = async ({ userId, newUserId }: { userId: number; newUserId?: number }): Promise => { + try { + await userApi.deleteUser({ userId, newUserId }); + } catch (e) { + throw new Error(`Error while deleting user ${userId}: ${e}`); + } + + const deletedUser = this._users.find(u => u.id === userId); + + if (!deletedUser) throw new Error(`User with id ${userId} was not found`); + + deletedUser.isActive = false; + + await resetAllSchedulesQueries(); + }; + + update = async ({ id, dto }: { id: number; dto: UpdateUserDto }): Promise => { + const updatedUser = await userApi.updateUser({ id, dto }); + + this._users = this._users.map(u => (u.id === id ? updatedUser : u)); + }; + + add = async (dto: CreateUserDto): Promise => { + const newUser = await userApi.addUser(dto); + + this._users.push(newUser); + + return newUser; + }; + + isOwner = (id: number): boolean => { + const user = this.getById(id); + + return user.role === UserRole.OWNER; + }; + + updateUserAvatar = async ({ + id, + avatarUrl, + }: { + id: number; + avatarUrl: Nullable; + }): Promise => { + const user = this.getById(id); + + user.avatarUrl = avatarUrl; + }; + + changeUserPassword = async (dto: ChangeUserPasswordDto): Promise => { + try { + return await userApi.changeUserPassword(dto); + } catch (e) { + return false; + } + }; + + reset = (): void => { + this._users = []; + + this.isLoaded = false; + }; +} + +export const userStore = new UserStore(); diff --git a/frontend/src/app/store/WatchdogStore.ts b/frontend/src/app/store/WatchdogStore.ts new file mode 100644 index 0000000..84ee09e --- /dev/null +++ b/frontend/src/app/store/WatchdogStore.ts @@ -0,0 +1,98 @@ +import { authStore } from '@/modules/auth'; +import { voximplantConnectorStore } from '@/modules/telephony'; +import { type DataStore, UtcDate } from '@/shared'; +import { makeAutoObservable } from 'mobx'; + +// 1 minute – consider data as stale +const INACTIVE_THRESHOLD = 60; + +// 2 hours – consider tab as stale +const HARD_RESET_THRESHOLD = 2 * 60 * 60; + +// 24 hours – consider tab as old and notify user about page reload +const RELOAD_THRESHOLD = 24 * 60 * 60; + +// 5 minutes – monitor tab open time +const HEARTBEAT_INTERVAL = 5 * 60 * 1000; + +class WatchdogStore { + lastVisibilityChange = UtcDate.now(); + tabOpenTime = UtcDate.now(); + + isSessionStale = false; + + dataStores: WeakRef[] = []; + + constructor() { + document.addEventListener('visibilitychange', this.handleVisibilityChange); + + this.startHeartbeat(); + + makeAutoObservable(this); + } + + watch = (store: DataStore): void => { + this.dataStores.push(new WeakRef(store)); + }; + + handleVisibilityChange = async (): Promise => { + if (document.visibilityState === 'visible') { + const now = UtcDate.now(); + const inactiveTime = now.diff(this.lastVisibilityChange); + + if (inactiveTime > RELOAD_THRESHOLD) { + this.notifyReload(); + } else if (inactiveTime > HARD_RESET_THRESHOLD) { + await this.hardReloadData(); + } else if (inactiveTime > INACTIVE_THRESHOLD) { + await this.reloadData(); + } + + this.lastVisibilityChange = now; + } + }; + + cleanupOldRefs = (): void => { + this.dataStores = this.dataStores.filter(ref => ref.deref() !== undefined); + }; + + startHeartbeat = (): void => { + setInterval(() => { + if (!this.isSessionStale && UtcDate.now().diff(this.tabOpenTime) > RELOAD_THRESHOLD) { + this.notifyReload(); + } else { + this.cleanupOldRefs(); + } + }, HEARTBEAT_INTERVAL); + }; + + reloadData = async (): Promise => { + this.cleanupOldRefs(); + + await Promise.all(this.dataStores.map(ref => ref.deref()?.loadData())); + }; + + hardReloadData = async (): Promise => { + console.log( + 'Watchdog marked this tab as stale! It is recommended to reload the page in case of errors.' + ); + + await Promise.all([ + this.reloadData(), + voximplantConnectorStore.loadData(), + authStore.invalidateCurrentUserInCache(), + ]); + }; + + notifyReload = () => { + this.hardReloadData(); + + this.isSessionStale = true; + }; + + reset = (): void => { + this.dataStores = []; + }; +} + +export const watchdogStore = new WatchdogStore(); diff --git a/frontend/src/app/store/index.tsx b/frontend/src/app/store/index.tsx new file mode 100644 index 0000000..1665dc2 --- /dev/null +++ b/frontend/src/app/store/index.tsx @@ -0,0 +1,13 @@ +export { appStore } from './AppStore'; +export { entityTypeStore } from './EntityTypeStore'; +export { featureStore } from './FeatureStore'; +export { generalSettingsStore } from './GeneralSettingsStore'; +export { iconStore } from './IconStore'; +export { identityStore } from './IdentityStore'; +export { recordSingularityStore } from './RecordSingularityStore'; +export { RecordStateStore } from './RecordStateStore'; +export { SettingsStore } from './SettingsStore'; +export { subscriptionStore } from './SubscriptionStore'; +export { UserProfileStore } from './UserProfileStore'; +export { userStore } from './UserStore'; +export { watchdogStore } from './WatchdogStore'; diff --git a/frontend/src/app/styles/emoji-picker.css b/frontend/src/app/styles/emoji-picker.css new file mode 100644 index 0000000..88a27ba --- /dev/null +++ b/frontend/src/app/styles/emoji-picker.css @@ -0,0 +1,13 @@ +em-emoji-picker { + height: 320px; + + --border-radius: var(--border-radius-block); + --category-icon-size: 20px; + --font-family: var(--system-font-families); + --duration: var(--transition-duration); + --easing: var(--transition-timing-function); + + --rgb-accent: var(--button-text-graphite-primary-text-rgb); + --rgb-background: var(--primary-statuses-white-0-rgb); + --rgb-color: var(--button-text-graphite-priory-text-rgb); +} diff --git a/frontend/src/app/styles/fonts.css b/frontend/src/app/styles/fonts.css new file mode 100644 index 0000000..c21b51a --- /dev/null +++ b/frontend/src/app/styles/fonts.css @@ -0,0 +1,23 @@ +@font-face { + font-family: 'Geologica'; + font-weight: 600; + font-style: normal; + font-display: swap; + src: url('/fonts/Geologica/SemiBold/GeologicaSemiBold.ttf'); +} + +@font-face { + font-family: 'Nunito SemiBold'; + font-weight: 600; + font-style: normal; + font-display: swap; + src: url('/fonts/Nunito/SemiBold/NunitoSemiBold.ttf'); +} + +@font-face { + font-family: 'Nunito'; + font-weight: 400; + font-style: normal; + font-display: swap; + src: url('/fonts/Nunito/Regular/NunitoRegular.ttf'); +} diff --git a/frontend/src/app/styles/main.css b/frontend/src/app/styles/main.css new file mode 100644 index 0000000..b34fe1b --- /dev/null +++ b/frontend/src/app/styles/main.css @@ -0,0 +1,46 @@ +@import 'normalize.css/normalize.css'; +@import './variables.css'; +@import './reset.css'; +@import './fonts.css'; +@import './emoji-picker.css'; +@import './toastify.css'; +@import '@react-pdf-viewer/core/lib/styles/index.css'; +@import '@react-pdf-viewer/default-layout/lib/styles/index.css'; +@import './react-pdf.css'; + +* { + box-sizing: border-box; +} + +html, +body { + /* font display strategy */ + font-variant: normal; + font-feature-settings: normal; + -webkit-text-size-adjust: 100%; + -webkit-font-smoothing: initial; + -moz-osx-font-smoothing: auto; + font-family: var(--system-font-families); + + /* global background */ + background-color: var(--graphite-graphite-20); +} + +/* we add this class to body when resizing tables or panels for consistent cursor view */ +body.resize-col { + cursor: col-resize; +} + +body.no-selection { + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +} + +/* disable mac-scrollbar on mobile to prevent scrollbar conflicts */ +@media (max-width: 480px) { + .ms-track-global { + display: none; + } +} diff --git a/frontend/src/app/styles/react-pdf.css b/frontend/src/app/styles/react-pdf.css new file mode 100644 index 0000000..7802eff --- /dev/null +++ b/frontend/src/app/styles/react-pdf.css @@ -0,0 +1,4 @@ +:root { + --rpv-core__page-layer-box-shadow: 0 1px 2px 1px #d0daeb, 0 0 2px 0 #eef4fe; + --rpv-core__inner-page-background-color: var(--primary-statuses-white-0); +} diff --git a/frontend/src/app/styles/reset.css b/frontend/src/app/styles/reset.css new file mode 100644 index 0000000..746a30f --- /dev/null +++ b/frontend/src/app/styles/reset.css @@ -0,0 +1,237 @@ +@charset "utf-8"; + +/*** + First reset part – https://meyerweb.com/ +***/ + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: none; + font: inherit; + font-size: 100%; + line-height: normal; +} + +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} + +body { + line-height: 1; +} + +ul { + list-style: none; +} + +blockquote, +q { + quotes: none; +} + +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +/*** + Second reset part – https://github.com/elad2412/the-new-css-reset +***/ + +/* reapply the pointer cursor for anchor tags */ +a, +button { + cursor: revert; +} + +/* for images to not be able to exceed their container */ +img { + max-block-size: 100%; +} + +/* preferred box-sizing value */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Safari - solving issue when using user-select:none on the text input doesn't working */ +input, +textarea { + -webkit-user-select: auto; + user-select: auto; +} + +/* revert the 'white-space' property for textarea elements on Safari */ +textarea { + white-space: revert; +} + +/* minimum style to allow to style meter element */ +meter { + -webkit-appearance: revert; + appearance: revert; +} + +/* revert for bug in Chromium browsers + - fix for the content editable attribute will work properly. + - webkit-user-select: auto; added for Safari in case of using user-select:none on wrapper element */ +:where([contenteditable]:not([contenteditable='false'])) { + -moz-user-modify: read-write; + -webkit-user-modify: read-write; + overflow-wrap: break-word; + -webkit-line-break: after-white-space; + line-break: after-white-space; + -webkit-user-select: auto; + user-select: auto; +} + +/* apply back the draggable feature - exist only in Chromium and Safari */ +:where([draggable='true']) { + -webkit-user-drag: element; +} + +/* Revert Modal native behavior */ +:where(dialog:modal) { + all: revert; +} + +/* reset default text opacity of input placeholder */ +::placeholder { + color: unset; +} + +/*** + Third reset part – custom +***/ + +input { + border: none; +} + +hr { + border: none; + margin: 0; +} + +button { + padding: 0; + border: none; + background: none; +} + +strong, +b { + font-weight: 600; +} + +i, +em { + font-style: italic; +} + +a, +a * { + text-decoration: none; +} diff --git a/frontend/src/app/styles/toastify.css b/frontend/src/app/styles/toastify.css new file mode 100644 index 0000000..aa64717 --- /dev/null +++ b/frontend/src/app/styles/toastify.css @@ -0,0 +1,3 @@ +.workspace__Toast--NotificationBlock { + font-family: var(--system-font-families) !important; +} diff --git a/frontend/src/app/styles/variables.css b/frontend/src/app/styles/variables.css new file mode 100644 index 0000000..d766ded --- /dev/null +++ b/frontend/src/app/styles/variables.css @@ -0,0 +1,189 @@ +:root { + /* Fonts */ + --system-font-families: + -apple-system, 'system-ui', 'BlinkMacSystemFont', 'Segoe UI', 'Noto Sans', Helvetica, Arial, + sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-family-mono: ui-monospace, monospace; + + /* Colors */ + /* Button (Text) */ + --primary-blue: #5293f4; + --button-text-blue-default: var(--primary-blue); + --button-text-blue-hover: #79abf6; + --button-text-blue-active: #2c7bf2; + --button-text-green-default: #69d222; + --button-text-green-hover: #7fe03e; + --button-text-green-active: #58af1d; + --button-text-red-default: #f8654f; + --button-text-red-hover: #f98776; + --button-text-red-active: #f64328; + --button-text-graphite-primary-text: #7d89a1; + --button-text-graphite-primary-text-rgb: 123, 138, 171; + --button-text-graphite-secondary-text: #acb4c3; + --button-text-graphite-priory-text: #454f5e; + --button-text-graphite-priory-text-rgb: 85, 85, 85; + + /* Graphite */ + --graphite-graphite-840: #23282f; + --graphite-graphite-680: var(--button-text-graphite-priory-text); + --graphite-graphite-520: #67778e; + --graphite-graphite-440: #7d8ba1; + --graphite-graphite-360: #94a0b2; + --graphite-graphite-280: #acb5c3; + --graphite-graphite-200: #c4cad4; + --graphite-graphite-120: #dbdfe5; + --graphite-graphite-80: #e8eaee; + --graphite-graphite-40: #f3f4f6; + --graphite-graphite-20: #f9fafb; + + /* Background */ + --background-red-20: #fff6f5; + --background-orange-20: #fffaf5; + --background-yellow-20: #fffdf5; + --background-salad-20: #fcfef6; + --background-green-20: #f9fef6; + --background-malachite-20: #f6fef9; + --background-aquamarine-20: #f6fefc; + --background-turquoise-20: #f6fefe; + --background-noun-20: #f5fcfe; + --background-blue-20: #f6f9fe; + --background-blue-40: #edf3fd; + --background-purple-20: #f7f6fe; + --background-amethyst-20: #faf7fd; + --background-fuchsia-20: #fcf7fd; + --background-crimson-20: #fef6fb; + --background-pink-20: #fef5f8; + + /* Primary (statuses) */ + --primary-statuses-red-360: #f8654f; + --primary-statuses-orange-440: #f68828; + --primary-statuses-yellow-400: #fbd437; + --primary-statuses-salad-480: #b0e228; + --primary-statuses-green-520: #69d222; + --primary-statuses-malachite-480: #23e664; + --primary-statuses-aquamarine-480: #23e7b2; + --primary-statuses-turquoise-520: #1dd7d7; + --primary-statuses-noun-440: #2cbdf2; + --primary-statuses-blue-360: #5293f4; + --primary-statuses-purple-360: #7e70d7; + --primary-statuses-amethyst-360: #a770d7; + --primary-statuses-fuchsia-400: #cb60d2; + --primary-statuses-crimson-360: #e561b9; + --primary-statuses-pink-360: #f45288; + --primary-statuses-white-0: #ffffff; + --primary-statuses-white-0-rgb: 255, 255, 255; + + /* Secondary */ + --secondary-red-240: #fa9989; + --secondary-orange-240: #fabe89; + --secondary-yellow-240: #fde587; + --secondary-salad-280: #d1ee81; + --secondary-green-280: #adee81; + --secondary-malachite-240: #91f3b1; + --secondary-aquamarine-240: #91f3d9; + --secondary-turquoise-240: #93f0f0; + --secondary-noun-240: #8cdbf8; + --secondary-blue-240: #8cb7f8; + --secondary-purple-200: #b1a7f1; + --secondary-amethyst-200: #ceafe9; + --secondary-fuchsia-200: #e3b1e7; + --secondary-crimson-200: #f3a5d9; + --secondary-pink-200: #f99fbd; + + /* Neutral */ + --neutral-red-100: #fdd4ce; + --neutral-orange-80: #fde9d8; + --neutral-yellow-80: #fef6d7; + --neutral-salad-120: #ebf8c9; + --neutral-green-120: #dcf8c9; + --neutral-malachite-80: #dafbe5; + --neutral-aquamarine-80: #dafbf2; + --neutral-turquoise-80: #dbfafa; + --neutral-noun-80: #d9f3fd; + --neutral-blue-80: #d9e7fd; + --neutral-purple-60: #e8e5fb; + --neutral-amethyst-80: #ecdff6; + --neutral-fuchsia-80: #f4e0f5; + --neutral-crimson-80: #fadbf0; + --neutral-pink-80: #fdd9e5; + --neutral-black-primary: #555555; + + /* Avatars gradients */ + --avatar-orange-grad: linear-gradient(135deg, #fb7f5c 0%, #da5949 100%); + --avatar-orange-light-grad: linear-gradient(135deg, #fdb457 0%, #f7873a 100%); + --avatar-gray-grad: linear-gradient(135deg, #56c5e1 0%, #3aa1d6 100%); + --avatar-blue-grad: linear-gradient(135deg, #ad8ef6 0%, #7366e2 100%); + --avatar-green-grad: linear-gradient(135deg, #92cf61 0%, #4fbc46 100%); + --avatar-red-grad: linear-gradient(135deg, #fa84a5 0%, #dd5a79 100%); + + /* Transitions */ + --transition-duration: 200ms; + --transition-timing-function: ease; + --transition-200: var(--transition-duration) var(--transition-timing-function); + + /* Borders */ + --border-radius-element: 4px; + --border-radius-block: 8px; + --border-radius-modal: 8px; + + /* Gabarits */ + --header-height: 56px; + --subheader-height: 40px; + --header-with-subheader-height: calc(var(--header-height) + var(--subheader-height)); + --sidebar-width: 56px; + --settings-sidebar-width: 240px; + --reports-navigation-sidebar-width: 264px; + --scheduler-statistics-height: 56px; + + --base-page-min-width: 1200px; + + --telephony-header-height: 49px; + --telephony-call-initializer-header-height: 38px; + + --mailing-settings-sidebar-width: 335px; + --mailing-sidebar-width-opened: 248px; + --mailing-sidebar-width-closed: 53px; + --mailing-message-panel-width: 340px; + + --card-root-gap: 16px; + --card-stages-height: 32px; + --card-stages-margin-top: 16px; + --card-stages-total-height: calc(var(--card-stages-height) + var(--card-stages-margin-top)); + + --chats-header-search-block-height: 62px; + --chats-header-providers-tabs-height: 36px; + --chats-sidebar-header-height: calc( + var(--chats-header-search-block-height) + var(--chats-header-providers-tabs-height) + ); + --chats-panel-block-min-height: 78px; + + --toast-width: 328px; + + --builder-nav-width: 232px; + + --board-offset: 16px; + + --field-component-height: 32px; + + --field-dropdown-max-width: 256px; + --field-dropdown-min-width: 176px; + + --report-filter-select-width: 180px; + + --tutorial-item-column-width: 152px; + + --fixed-builder-step-controls-height: 56px; + + --bpmn-automations-djs-palette-width: 110px; + --bpmn-automation-process-save-controls-height: 56px; + --bpmn-toggle-minimap-toggle-button-height: 36px; + + /* Z-indexes */ + --modal-z-index: 1050; + --dropdown-z-index: 1100; + --header-z-index: 50; + --subheader-z-index: 49; + + /* Box-shadows */ + --dropdown-box-shadow: 0 0 1.5px 0 #eef4fe, 0 0 1.5px 0 #d0daeb; +} diff --git a/frontend/src/declarations/bpmn-js.d.ts b/frontend/src/declarations/bpmn-js.d.ts new file mode 100644 index 0000000..c994a0d --- /dev/null +++ b/frontend/src/declarations/bpmn-js.d.ts @@ -0,0 +1,11 @@ +declare module 'bpmn-js-color-picker' { + const ColorPickerModule: ModuleDeclaration; +} + +declare module 'diagram-js-minimap' { + const MinimapModule: ModuleDeclaration; +} + +declare module 'diagram-js-grid' { + const GridModule: ModuleDeclaration; +} diff --git a/frontend/src/declarations/custom-elements.d.ts b/frontend/src/declarations/custom-elements.d.ts new file mode 100644 index 0000000..f3f840a --- /dev/null +++ b/frontend/src/declarations/custom-elements.d.ts @@ -0,0 +1,13 @@ +interface TinkoffButtonProps extends React.HTMLAttributes { + size: string; + shopId: string; + showcaseId: string; + 'ui-data': string; + 'payment-data': string; +} + +declare namespace JSX { + interface IntrinsicElements { + 'tinkoff-create-button': TinkoffButtonProps; + } +} diff --git a/frontend/src/declarations/emoji-mart.d.ts b/frontend/src/declarations/emoji-mart.d.ts new file mode 100644 index 0000000..35252a0 --- /dev/null +++ b/frontend/src/declarations/emoji-mart.d.ts @@ -0,0 +1,90 @@ +// https://github.com/missive/emoji-mart + +interface EmojiMartData { + id: string; + emoticons: string[]; + keywords: string[]; + name: string; + native: string; + shortcodes: string; + unified: string; +} + +declare module '@emoji-mart/react' { + import { type FC } from 'react'; + + type PickerLocale = + | 'en' + | 'ar' + | 'be' + | 'cs' + | 'de' + | 'es' + | 'fa' + | 'fi' + | 'fr' + | 'hi' + | 'it' + | 'ja' + | 'kr' + | 'nl' + | 'pl' + | 'pt' + | 'ru' + | 'sa' + | 'tr' + | 'uk' + | 'vi' + | 'zh'; + + interface EmojiProps { + emoji: string; + size?: number; + set?: string; + skin?: number; + sheetSize?: number; + } + + interface PickerProps { + emojiSize?: number; + perLine?: number; + i18n?: { + categories: { + [key: string]: string; + }; + notfound: string; + search: string; + skintext: string; + categorieslabel: string; + skintones: { + [key: string]: string; + }; + }; + custom?: Array; + recent?: string[]; + autoFocus?: boolean; + style?: Record; + theme?: 'auto' | 'dark' | 'light'; + title?: string; + color?: string; + native?: boolean; + include?: string[]; + exclude?: string[]; + data?: EmojiMartData[]; + previewPosition?: 'top' | 'bottom' | 'none'; + emojiButtonColors?: string[]; + locale: PickerLocale | string; + onEmojiSelect: (emoji: EmojiMartData) => void; + } + + const Picker: FC; + + export default Picker; + export const Emoji: FC; +} + +declare module '@emoji-mart/data' { + const data: EmojiMartData[]; + + export default data; +} diff --git a/frontend/src/declarations/env.d.ts b/frontend/src/declarations/env.d.ts new file mode 100644 index 0000000..feb700f --- /dev/null +++ b/frontend/src/declarations/env.d.ts @@ -0,0 +1,100 @@ +interface ImportMetaEnv { + readonly VITE_APP_NAME: string; + readonly VITE_APP_URL: string; + readonly VITE_APP_DEMO_EMAIL: string; + readonly VITE_APP_LANGUAGES: string; + readonly VITE_APP_LOGO: string; + readonly VITE_BASE_API_URL: string; + readonly VITE_BASE_API_KEY: string; + readonly VITE_GTM_ID: string; + readonly VITE_IP_REGISTRY_API_KEY: string; + readonly VITE_APP_URL_TEMPLATE: string; + readonly VITE_APP_RU_SEGMENT: string; + + readonly VITE_BILLING_PATH: string; + readonly VITE_BILLING_SHOW_CREDIT_BUTTON; + + readonly VITE_BUILDER_HIDE_MAIN_MODULES: string; + readonly VITE_BUILDER_HIDE_FORM_BUILDER: string; + readonly VITE_BUILDER_HIDE_TELEPHONY: string; + readonly VITE_BUILDER_HIDE_BPMN: string; + readonly VITE_BUILDER_HIDE_MARKETING: string; + readonly VITE_BUILDER_HIDE_FINANCES: string; + + readonly VITE_INTEGRATIONS_SHOW_1C: string; + readonly VITE_INTEGRATIONS_SHOW_SALESFORCE: string; + readonly VITE_INTEGRATIONS_SHOW_WAZZUP: string; + readonly VITE_INTEGRATIONS_SHOW_FB_MESSENGER: string; + readonly VITE_INTEGRATIONS_SHOW_TWILIO: string; + readonly VITE_INTEGRATIONS_SHOW_PBX: string; + readonly VITE_INTEGRATIONS_SHOW_RU_PBX_PROVIDERS: string; + readonly VITE_INTEGRATIONS_SHOW_MAKE: string; + readonly VITE_INTEGRATIONS_SHOW_APIX_DRIVE: string; + readonly VITE_INTEGRATIONS_SHOW_ALBATO: string; + + readonly VITE_VOXIMPLANT_SHOW_TELEPHONY: string; + readonly VITE_VOXIMPLANT_CONNECTION_NODE: string; + readonly VITE_VOXIMPLANT_INTEGRATION_GUIDE_TYPE: string; + + readonly VITE_DOCUMENTS_SHOW_ORDER_FIELDS: string; + + readonly VITE_NEW_RELIC_ENABLED: string; + readonly VITE_NEW_RELIC_ACCOUNT_ID: string; + readonly VITE_NEW_RELIC_LICENSE_KEY: string; + readonly VITE_NEW_RELIC_APPLICATION_ID: string; + + readonly VITE_REQUEST_BPMN_WORKSPACE_FORM_NAME_FIELD_ID: string; + readonly VITE_REQUEST_BPMN_WORKSPACE_FORM_PHONE_FIELD_ID: string; + readonly VITE_REQUEST_BPMN_WORKSPACE_FORM_EMAIL_FIELD_ID: string; + readonly VITE_REQUEST_BPMN_WORKSPACE_FORM_DOMAIN_FIELD_ID: string; + readonly VITE_REQUEST_BPMN_WORKSPACE_FORM_COMMENT_FIELD_ID: string; + readonly VITE_SUBMIT_REQUEST_BPMN_WORKSPACE_FORM_URL: string; + + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_NAME_FIELD_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_PHONE_FIELD_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_EMAIL_FIELD_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_DOMAIN_FIELD_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_COMMENT_FIELD_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_STORAGE_FIELD_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_TEN_GB_OPTION_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_ONE_HUNDRED_GB_OPTION_ID: string; + readonly VITE_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_ONE_TB_OPTION_ID: string; + readonly VITE_SUBMIT_REQUEST_ADDITIONAL_STORAGE_WORKSPACE_FORM_URL: string; + + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_NUMBER_OF_USERS_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PLAN_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_NAME_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_ITN_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_ADDRESS_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_CONTACT_NAME_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PHONE_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_EMAIL_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_SUBDOMAIN_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_INVOICE_ID_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_TOTAL_PRICE_FIELD_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PLAN_LTD_BUSINESS_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PLAN_LTD_ADVANCED_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PLAN_YEAR_BUSINESS_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PLAN_YEAR_ADVANCED_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PLAN_MONTH_BUSINESS_ID: string; + readonly VITE_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_PLAN_MONTH_ADVANCED_ID: string; + readonly VITE_SUBMIT_REQUEST_MYWORK_INVOICE_WORKSPACE_FORM_URL: string; + + readonly VITE_REQUEST_SETUP_HEADLESS_FORM_NAME_FIELD_ID: string; + readonly VITE_REQUEST_SETUP_HEADLESS_FORM_PHONE_FIELD_ID: string; + readonly VITE_REQUEST_SETUP_HEADLESS_FORM_EMAIL_FIELD_ID: string; + readonly VITE_REQUEST_SETUP_HEADLESS_FORM_COMMENT_FIELD_ID: string; + readonly VITE_REQUEST_SETUP_HEADLESS_FORM_SUBDOMAIN_FIELD_ID: string; + readonly VITE_REQUEST_SETUP_HEADLESS_FORM_URL: string; + + readonly VITE_DIRECTUS_PUBLIC_BASE_URL: string; + readonly VITE_DIRECTUS_STATIC_TOKEN: string; + + readonly VITE_REACT_QUERY_DEVTOOLS_ENABLED: string; + + readonly VITE_AUTOMATION_HIDE_BPMN: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/frontend/src/declarations/reset.d.ts b/frontend/src/declarations/reset.d.ts new file mode 100644 index 0000000..12bd3ed --- /dev/null +++ b/frontend/src/declarations/reset.d.ts @@ -0,0 +1 @@ +import '@total-typescript/ts-reset'; diff --git a/frontend/src/declarations/svgr.d.ts b/frontend/src/declarations/svgr.d.ts new file mode 100644 index 0000000..879b542 --- /dev/null +++ b/frontend/src/declarations/svgr.d.ts @@ -0,0 +1,7 @@ +declare module '*.svg' { + import type * as React from 'react'; + + export const ReactComponent: React.FunctionComponent< + React.ComponentProps<'svg'> & { title?: string } + >; +} diff --git a/frontend/src/declarations/vite.env.d.ts b/frontend/src/declarations/vite.env.d.ts new file mode 100644 index 0000000..20075e2 --- /dev/null +++ b/frontend/src/declarations/vite.env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +declare module 'phone-number-to-timezone'; +declare const __APP_VERSION__: string; diff --git a/frontend/src/declarations/voxengine.d.ts b/frontend/src/declarations/voxengine.d.ts new file mode 100644 index 0000000..2f176ab --- /dev/null +++ b/frontend/src/declarations/voxengine.d.ts @@ -0,0 +1,24797 @@ +/** + * [ACDRequest] parameters. Can be passed as arguments to the [VoxEngine.enqueueACDRequest] method. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.ACD); + * ``` + */ +declare interface ACDEnqueueParameters { + /** + * Priority (1-100, 1 is the highest priority). + * If two or more objects have the same priorities, they are handled according to the order of HTTP requests from JS scenario to the ACD queue. + */ + priority: number; + /** + * Optional. Extra headers to be passed with the call to the agent. + * Custom header names have to begin with the 'X-' prefix except the 'VI-CallTimeout': '60' which switches to another agent if current one does not answer after the timeout (in seconds, the default value is **60**, must not be less than **10** or greater than **400**). + * The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + */ + headers?: { [header: string]: string }; + /** + * Whether the call has video support. Please note that prices for audio only and video calls are different. + */ + video: boolean; + /** + * Custom data for the current call object. + */ + customData: string; +} + +/** + * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.ACD); + * ``` + * @event + */ +declare enum ACDEvents { + /** + * Triggered when an agent is reached. + * @typedef _ACDReachedEvent + */ + OperatorReached = 'ACD.OperatorReached', + /** + * Triggered when an ACD makes a call to a user via the [callUser] method. + * @typedef _ACDCallAttempt + */ + OperatorCallAttempt = 'ACD.OperatorCallAttempt', + /** + * Triggered when an ACD request tries to reach an agent, but the agent declines the call. IMPORTANT NOTE: This is just a notification, the request processing does not stop. The ACD request automatically redirects to the next free agent. + * @typedef _ACDFailedEvent + */ + OperatorFailed = 'ACD.OperatorFailed', + /** + * Triggers if the ACD service returns an internal error. The JS scenarios are not able to cause internal errors because these errors depend on internal and network issues. + * @typedef _ACDErrorEvent + */ + Error = 'ACD.Error', + /** + * Triggers as a result of the [ACDRequest.getStatus] method call. + * @typedef _ACDWaitingEvent + */ + Waiting = 'ACD.Waiting', + /** + * Triggered when an [ACDRequest] is put to the queue. + * @typedef _ACDQueuedEvent + */ + Queued = 'ACD.Queued', + /** + * Triggers if all agents that can handle a request in the specified queue are offline. In this case, the request is not queued. + * @typedef _ACDOfflineEvent + */ + Offline = 'ACD.Offline', + /** + * Triggered when we have one more request to put in the queue but the maximum number of requests (max_queue_size) is already reached. In this case, the new request is not queued. The max_queue_size and max_waiting_time default values are “unlimited”, you can change these values for every new or existing queue in the [control panel](https://manage.voximplant.com/applications). + * @typedef _ACDQueueFullEvent + */ + QueueFull = 'ACD.QueueFull', +} + +/** + * @private + */ +declare interface _ACDEvents { + [ACDEvents.OperatorReached]: _ACDReachedEvent; + [ACDEvents.OperatorCallAttempt]: _ACDCallAttempt; + [ACDEvents.OperatorFailed]: _ACDFailedEvent; + [ACDEvents.Error]: _ACDErrorEvent; + [ACDEvents.Waiting]: _ACDWaitingEvent; + [ACDEvents.Queued]: _ACDQueuedEvent; + [ACDEvents.Offline]: _ACDOfflineEvent; + [ACDEvents.QueueFull]: _ACDQueueFullEvent; +} + +/** + * @private + */ +declare interface _ACDBaseEvent { + /** + * Request that generated the event + */ + request: ACDRequest; +} + +/** + * @private + */ +declare interface _ACDReachedEvent extends _ACDBaseEvent { + /** + * Established call with agent + */ + operatorCall: Call; +} + +/** + * @private + */ +declare interface _ACDCallAttempt extends _ACDBaseEvent { + /** + * Agent's call + */ + operatorCall: Call; +} + +/** + * @private + */ +declare interface _ACDFailedEvent extends _ACDBaseEvent { + /** + * Username of failed agent + */ + operatorUserName: string; + /** + * Call status code + */ + statusCode: number; +} + +/** + * @private + */ +declare interface _ACDErrorEvent extends _ACDBaseEvent { + /** + * Error message + */ + error: string; +} + +/** + * @private + */ +declare interface _ACDWaitingEvent extends _ACDBaseEvent { + /** + * Estimated waiting time in minutes (value of 0 is also possible) + */ + ewt: number; + + /** + * Position of the request in the queue + */ + position: number; +} + +/** + * @private + */ +declare interface _ACDQueuedEvent extends _ACDBaseEvent {} + +/** + * @private + */ +declare interface _ACDOfflineEvent extends _ACDBaseEvent {} + +/** + * @private + */ +declare interface _ACDQueueFullEvent extends _ACDBaseEvent {} + +/** + * Represents a request that is put to the ACD queue. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.ACD); + * ``` + */ +declare class ACDRequest { + /** + * Returns the request's id. It can be used as the acd\_request\_id parameter in the [GetACDHistory](/docs/references/httpapi/managing_history#getacdhistory) method to search in ACD history. + */ + id(): string; + + /** + * Gets status of the current request. Not to be called before the request is successfully queued (the [ACDEvents.Queued] event). This method's call triggers the [ACDEvents.Waiting] event; it is possible to retrieve an estimated waiting time in minutes via the ewt property of the event. + */ + getStatus(): void; + + /** + * Adds a handler for the specified [ACDEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [ACDEvents.Offline]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: ACDEvents | T, + callback: (event: _ACDEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [ACDEvents] event. + * @param event Event class (i.e., [ACDEvents.Offline]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: ACDEvents | T, + callback?: (event: _ACDEvents[T]) => any + ): void; + + /** + * Cancel pending request and remove it from the queue + */ + cancel(): void; +} + +/** + * The AI module provides additional methods that use Artificial Intelligence. These methods allow solving business tasks in a more productive way. + *
+ * Add the following line to your scenario code to use the namespace: + * ``` + * require(Modules.AI); + * ``` + */ +declare namespace AI {} + +declare namespace AI { + /** + * Creates a new [AI.Dialogflow] instance which provides resources for exchanging data with the Dialogflow API, handling events, etc. You can attach media streams later via the [AI.DialogflowInstance.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.AI); + * ``` + * @param parameters Dialogflow parameters + */ + function createDialogflow(parameters: DialogflowSettings): DialogflowInstance; +} + +declare namespace AI { + /** + * Represents a parameters of the voicemail recognition session. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + * @deprecated Use [AMD] instead + **/ + interface DetectVoicemailParameters { + /** + * Optional. Recognition model. The possible values are **ru**, **colombia**. The default value is **ru**. + */ + model?: string; + /** + * Optional. Detection threshold in the **0.0 - 1.0 milliseconds** range. Durations shorter than this value are considered human speech, and durations longer than this value are considered voicemail. The default value is **0.8**. Available only with the **latam** model. + */ + threshold?: number; + } +} + +declare namespace AI { + /** + * Start a voicemail recognition session. You can check how many times voicemail was detected in the call history. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.AI); + * ``` + * @param call + * @param parameters + * @deprecated Use [AMD] instead + */ + function detectVoicemail(call: Call, parameters: DetectVoicemailParameters): Promise; +} + +declare namespace AI { + /** + * Dialogflow events allow matching intents by event name instead of the natural language input. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowEventInput { + /** + * The unique identifier of the event. + */ + name: string; + /** + * The collection of parameters associated with the event + */ + parameters: { [key: string]: any }; + /** + * The language ot this conversation query + */ + languageCode: DialogflowLanguage; + } +} + +declare namespace AI { + /** + * Represents a Dialogflow instance. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.AI); + * ``` + */ + class DialogflowInstance { + /** + * @param id + * @param parameters Dialogflow parameters + */ + constructor(id: string, parameters: Object); + + /** + * Returns the dialogflow instance's id. + */ + id(): string; + + /** + * Set parameters for the intents. + * @param queryParameters Query parameters + */ + setQueryParameters(queryParameters: DialogflowQueryParameters): void; + + /** + * Set a collection of phrase hints for the intents. + * @param phraseHints The collection of phrase hints to boost the speech recognition accuracy + */ + setPhraseHints(phraseHints: { [id: string]: string }): void; + + /** + * Update the audio output configuration. + * @param outputAudioConfig Config of the audio output + */ + setOutputAudioConfig(outputAudioConfig: DialogflowOutputAudioConfig): void; + + /** + * Stop and destroy the current Dialogflow instance. + */ + stop(): void; + + /** + * Send a query to the DialogFlow instance. You can send either a text string up to **256 characters** or an event object with the event name and additional data. + * @param dialogflowQuery Text string (up to **256 characters**) or an event object + */ + sendQuery(dialogflowQuery: DialogflowQueryInput): void; + + /** + * Add a Dialogflow speech synthesis playback marker. The [AI.Events.DialogflowPlaybackMarkerReached](/docs/references/voxengine/ai/events#dialogflowplaybackmarkerreached) event is triggered when the marker is reached. + * @param offset Positive/negative offset in milliseconds from the start/end of media + */ + addMarker(offset: number): void; + + /** + * Starts sending media (voice) from a Dialogflow participant to the media unit. + * @param mediaUnit Media unit that receives media + * @param parameters Optional. WebSocket interaction only parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media (voice) from a Dialogflow participant to the media unit. + */ + stopMediaTo(mediaUnit: VoxMediaUnit): void; + + /** + * Adds a handler for the specified [AI.Events]. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [AI.Events.DialogflowResponse]) + * @param callback Handler function. A single parameter is passed: object with event information + */ + addEventListener( + event: AI.Events | T, + callback: (event: AI._Events[T]) => any + ): void; + + /** + * Removes a handler for the specified [AI.Events] event. + * @param event Event class (i.e., [AI.Events.DialogflowResponse]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: AI.Events | T, + callback?: (event: AI._Events[T]) => any + ): void; + } +} + +declare namespace AI { + /** + * Instructs the speech synthesizer on how to generate the output audio content. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowOutputAudioConfig { + /** + * Optional. Configuration of how speech should be synthesized. + */ + synthesizeSpeechConfig?: DialogflowSynthesizeSpeechConfig; + } +} + +declare namespace AI { + /** + * Represents a Dialogflow query input. It can contain either: + * 1. A conversational query in the form of text + * 2. An event that specifies which intent to trigger + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowQueryInput { + /** + * The natural language text to be processed. + */ + text: DialogflowTextInput; + /** + * The event to be processed. + */ + event: DialogflowEventInput; + } +} + +declare namespace AI { + /** + * Represents a parameters of the conversational query. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + **/ + interface DialogflowQueryParameters { + /** + * Optional. The time zone of the conversational query from the time zone database, e.g., America/New_York, Europe/Paris. If not provided, the system uses the time zone specified in agent settings. + */ + timeZone?: string; + /** + * Optional. The geolocation of the conversational query. + */ + geoLocation?: { [key: string]: any }; + /** + * Optional. The collection of contexts to be activated before the query execution. + */ + contexts?: any[]; + /** + * Optional. Whether to delete all contexts in the current session before activation of a new one. + */ + resetContexts?: boolean; + /** + * Optional. The collection of session entity types to replace or extend developer entities with for this query only. The entity synonyms apply to all languages. + */ + sessionEntityTypes?: any[]; + /** + * Optional. Use this field to pass custom data into the webhook associated with the agent. Arbitrary JSON objects are supported. + */ + payload?: { [key: string]: any }; + } +} + +declare namespace AI { + /** + * Represents a Dialogflow intent response. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowResponse { + /** + * The unique identifier of the response. Use it to locate a response in the training example set or for reporting issues. + */ + responseId: string; + /** + * Optional. The result of the conversational query or event processing. + */ + queryResult?: DialogflowResult; + /** + * Optional. The result of speech recognition. + */ + recognitionResult?: DialogflowStreamingRecognitionResult; + /** + * Status of the webhook request. + */ + webhookStatus: { [id: string]: any }; + } +} + +declare namespace AI { + /** + * Represents a result of an intent response. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + **/ + interface DialogflowResult { + /** + * The original conversational query text: - If natural language text was provided as input, query_text contains a copy of the input. - If natural language speech audio was provided as input, query_text contains the speech recognition result. If speech recognizer produced multiple alternatives, a particular one is picked. - If an event was provided as input, query_text is not set. + */ + queryText: string; + /** + * The action name from the matched intent. + */ + action: string; + /** + * The collection of extracted parameters. + */ + parameters: { [id: string]: any }; + /** + * Whether all the required parameters are present. This field is set to: - false if the matched intent has required parameters and not all the required parameter values have been collected. - true if all required parameter values have been collected, or if the matched intent does not contain any required parameters. + */ + allRequiredParamsPresent: boolean; + /** + * This field is set to: - false if the matched intent has required parameters and not all the required parameter values have been collected. - true if all required parameter values have been collected, or if the matched intent does not contain any required parameters. + * @deprecated + */ + fulfillmentText: string; + /** + * The collection of rich messages to present to the user. + */ + fulfillmentMessages: any[]; + /** + * The intent that matched the conversational query. Some, not all fields are filled in this message, including but not limited to: name, display_name and webhook_state. + */ + intent: { [id: string]: any }; + /** + * The intent detection confidence. Values range from 0.0 (completely uncertain) to 1.0 (completely certain). + */ + intentDetectionConfidence: number; + /** + * The free-form diagnostic info. For example, this field could contain webhook call latency. + */ + diagnosticInfo: { [id: string]: any }; + /** + * The language that was triggered during intent detection. See [Language support](https://cloud.google.com/dialogflow/docs/reference/language) for a list of the currently supported language codes. + */ + languageCode: string; + } +} + +declare namespace AI { + /** + * Settings for setting up a new Dialogflow instance + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + **/ + interface DialogflowSettings { + /** + * Language for the Dialogflow instance. + */ + lang: DialogflowLanguage; + /** + * Optional. The collection of phrase hints to boost accuracy of speech recognition. + */ + phraseHints?: any[]; + /** + * Initial query parameters. + */ + queryParameters: DialogflowQueryParameters; + /** + * Whether to enable single utterance. + */ + singleUtterance: boolean; + /** + * Instructs the speech synthesizer how to generate the output audio content. + */ + outputAudioConfig: DialogflowOutputAudioConfig; + /** + * Optional. Dialogflow session id. Use it for connection to the existing Dialogflow session or to specify your own id for a new session. + */ + sessionId?: string; + /** + * Optional. ID of the Dialogflow agent certificate to use. It can be any of the certificates previously added in the [control panel](https://manage.voximplant.com/applications), see the Dialogflow Connector section in your Voximplant application. You do not need to specify agentId if you have only one Dialogflow agent certificate in your Voximplant application. + */ + agentId?: number; + /** + * Optional. Part of the Dialogflow [session](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1#google.cloud.dialogflow.v2beta1.StreamingDetectIntentRequest) name. If Environment ID is not specified, we assume default ‘draft’ environment. + */ + environmentId?: string; + /** + * Optional. Part of the Dialogflow [session](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1#google.cloud.dialogflow.v2beta1.StreamingDetectIntentRequest) name. If User ID is not specified, we use “-”. + */ + userId?: string; + /** + * Optional. Whether to use beta. The Voximplant Dialogflow Connector uses Dialogflow v2 Beta by default. Set false to use the non-Beta version of Dialogflow v2. + */ + beta?: boolean; + /** + * Optional. The agent’s location. + */ + region?: string; + /** + * Optional. Machine learning model to transcribe your audio file. + */ + model?: DialogflowModel; + /** + * Optional. Variant of the specified Speech model to use. + */ + modelVariant?: DialogflowModelVariant; + } +} + +declare namespace AI { + /** + * Contains a speech recognition result, corresponding to a portion of the audio that is currently being processed; or an indication that this is the end of the single requested utterance. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowStreamingRecognitionResult { + /** + * Type of the result message. + */ + messageType: string; + /** + * Optional. Transcript text representing the words that the user spoke. + */ + transcript: string; + /** + * The default of **0.0** is a sentinel value indicating confidence was not set. If **false**, the *StreamingRecognitionResult* represents an interim result that may change. If **true**, the recognizer does not return any further hypotheses about this piece of the audio. + */ + isFinal: boolean; + /** + * The Speech confidence between **0.0** and **1.0** for the current portion of audio. The default of **0.0** is a sentinel value indicating that confidence was not set. Note that the field is typically only provided if **is_final: true**, and you should not rely on it being accurate or even set. + */ + confidence: number; + } +} + +declare namespace AI { + /** + * Configuration of how speech should be synthesized. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowSynthesizeSpeechConfig { + /** + * Optional. Speaking rate/speed, in the range [0.25, 4.0]. 1.0 is the normal native speed supported by the specific voice. 2.0 is twice as fast, and 0.5 is half as fast. If not set (0.0), defaults to the native 1.0 speed. Any other values < 0.25 or > 4.0 return an error. + */ + speakingRate?: number; + /** + * Optional. Speaking pitch, in the range [-20.0, 20.0]. 20 means increase 20 semitones from the original pitch. -20 means decrease 20 semitones from the original pitch. + */ + pitch?: number; + /** + * Optional. Volume gain (in dB) of the normal native volume supported by the specific voice, in the range [-96.0, 16.0]. If not set, or set to a value of 0.0 (dB), plays at normal native signal amplitude. A value of -6.0 (dB) plays at approximately half the amplitude of the normal native signal amplitude. A value of +6.0 (dB) plays at approximately twice the amplitude of the normal native signal amplitude. We strongly recommend not to exceed +10 (dB) as there is usually no effective increase in loudness for any value greater than that. + */ + volumeGainDb?: number; + /** + * Optional. An identifier which selects 'audio effects' profiles that are applied on (post synthesized) text to speech. Effects are applied on top of each other in the order they are given. + */ + effectsProfileId?: string[]; + /** + * Optional. The desired voice of the synthesized audio. + */ + voice?: DialogflowVoiceSelectionParameters; + } +} + +declare namespace AI { + /** + * Represents a natural language text to be processed. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowTextInput { + /** + * The UTF-8 encoded natural language text to be processed. Text length must not exceed 256 bytes + */ + text: string; + /** + * The language ot the conversation query + */ + languageCode: DialogflowLanguage; + } +} + +declare namespace AI { + /** + * Description of which voice to use for speech synthesis. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.AI); + * ``` + */ + interface DialogflowVoiceSelectionParameters { + /** + * Optional. The name of the voice. If not set, the service chooses a voice based on the other parameters such as language_code and gender. + */ + name?: string; + /** + * Optional. The preferred gender of the voice. If not set, the service chooses a voice based on the other parameters such as language_code and name. Note that this is only a preference, not requirement. If a voice of the appropriate gender is not available, the synthesizer should substitute a voice with a different gender rather than failing the request. + */ + ssmlGender?: DialogflowSsmlVoiceGender; + } +} + +declare namespace AI { + /** + * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.AI); + * ``` + * @event + */ + enum Events { + /** + * Triggered when a Dialogflow instance returns a query result. + * @typedef _DialogflowQueryResultEvent + */ + DialogflowQueryResult = 'AI.Events.DialogflowQueryResult', + /** + * Triggered when a Dialogflow instance returns a recognition result. + * @typedef _DialogflowRecognitionResultEvent + */ + DialogflowRecognitionResult = 'AI.Events.DialogflowRecognitionResult', + /** + * Triggered when a Dialogflow instance returns an intent response. + * @typedef _DialogflowResponseEvent + */ + DialogflowResponse = 'AI.Events.DialogflowResponse', + /** + * Triggered when a Dialogflow instance causes error. + * @typedef _DialogflowErrorEvent + */ + DialogflowError = 'AI.Events.DialogflowError', + /** + * Triggered when a Dialogflow instance is stopped. + * @typedef _DialogflowStoppedEvent + */ + DialogflowStopped = 'AI.Events.DialogflowStopped', + /** + * Triggered when a playback of a single phrase is finished successfully or in case of playback error. + * @typedef _DialogflowPlaybackFinishedEvent + */ + DialogflowPlaybackFinished = 'AI.Events.DialogflowPlaybackFinished', + /** + * Triggered when a playback of a single phrase is started. + * @typedef _DialogflowPlaybackStartedEvent + */ + DialogflowPlaybackStarted = 'AI.Events.DialogflowPlaybackStarted', + /** + * Triggered when 'DialogflowInstance.addMarker' is reached. + * @typedef _DialogflowPlaybackMarkerReachedEvent + */ + DialogflowPlaybackMarkerReached = 'AI.Events.DialogflowPlaybackMarkerReached', + /** + * Triggered when an answering machine or voicemail is detected. + * @typedef _VoicemailDetectedEvent + * @deprecated Use [AMD] instead + */ + VoicemailDetected = 'AI.Events.VoicemailDetected', + /** + * Triggered when an answering machine or voicemail is not detected. + * @typedef _VoicemailNotDetectedEvent + * @deprecated Use [AMD] instead + */ + VoicemailNotDetected = 'AI.Events.VoicemailNotDetected', + } + + /** + * @private + */ + interface _Events { + [Events.DialogflowQueryResult]: _DialogflowQueryResultEvent; + [Events.DialogflowRecognitionResult]: _DialogflowRecognitionResultEvent; + [Events.DialogflowResponse]: _DialogflowResponseEvent; + [Events.DialogflowError]: _DialogflowErrorEvent; + [Events.DialogflowStopped]: _DialogflowStoppedEvent; + [Events.DialogflowPlaybackFinished]: _DialogflowPlaybackFinishedEvent; + [Events.DialogflowPlaybackStarted]: _DialogflowPlaybackStartedEvent; + [Events.DialogflowPlaybackMarkerReached]: _DialogflowPlaybackMarkerReachedEvent; + [Events.VoicemailDetected]: _VoicemailDetectedEvent; + [Events.VoicemailNotDetected]: _VoicemailNotDetectedEvent; + } + + /** + * @private + */ + interface _DialogflowEvent { + /** + * Link to the Dialogflow instance. + */ + dialogflow: DialogflowInstance; + } + + /** + * @private + */ + interface _DialogflowQueryResultEvent extends _DialogflowEvent { + /** + * The results of the conversational query or event processing. + */ + result: DialogflowResult; + } + + /** + * @private + */ + interface _DialogflowRecognitionResultEvent extends _DialogflowEvent { + /** + * The default of **0.0** is a sentinel value indicating confidence was not set. If **false**, the *StreamingRecognitionResult* represents an interim result that may change. If **true**, the recognizer does not return any further hypotheses about this piece of the audio. + */ + isFinal: boolean; + } + + /** + * @private + */ + interface _DialogflowResponseEvent extends _DialogflowEvent { + /** + * The intent response. + */ + response: DialogflowResponse; + } + + /** + * @private + */ + interface _DialogflowErrorEvent extends _DialogflowEvent { + /** + * The cause of the event. + */ + cause: string; + } + + /** + * @private + */ + interface _DialogflowStoppedEvent extends _DialogflowEvent { + /** + * The cause of the event. + */ + cause: string; + } + + /** + * @private + */ + interface _DialogflowPlaybackFinishedEvent extends _DialogflowEvent { + error?: string; + } + + /** + * @private + */ + interface _DialogflowPlaybackStartedEvent extends _DialogflowEvent { + /** + * Playback duration. + */ + duration: number; + } + + /** + * @private + */ + interface _DialogflowPlaybackMarkerReachedEvent extends _DialogflowEvent { + /** + * Marker offset. + */ + offset: number; + } + + /** + * @private + */ + interface _VoicemailBaseEvent { + /** + * Call that triggers the event. + */ + call: Call; + } + + /** + * @private + */ + interface _VoicemailDetectedEvent extends _VoicemailBaseEvent { + /** + * Recognition confidence. Values range from **0** (completely uncertain) to **100** (completely certain). The value is not guaranteed to be accurate, consider it while handling the event. + */ + confidence: number; + } + + /** + * @private + */ + interface _VoicemailNotDetectedEvent extends _VoicemailBaseEvent {} +} + +declare namespace AMD { + /** + * Parameters for the [AMD.create] method. + */ + interface AMDParameters { + /** + * Recognition model - [AMD.Model]. + */ + model: AMD.Model; + /** + * Optional. Detection timeout in milliseconds. Note that the timeout is only triggered after the [CallEvents.Connected] event. The default value is **6500**. Must not be less than **0** or greater than **20000**. + */ + timeout?: number; + /** + * Optional. Detection threshold in the range **0.0** - **1.0**. + */ + thresholds?: AMD.Thresholds; + } +} + +/** + * Answering Machine Detection provides methods that allow developers to recognize voicemail prompts with the help of artificial intelligence. + * Read more about the topic in the [Voicemail detection](/docs/guides/calls/voicemail-detection) article. + */ +declare namespace AMD {} + +declare namespace AMD { + /** + * Answering machine or voicemail detector class. + */ + class AnsweringMachineDetector { + readonly call?: Call; + + readonly model: AMD.Model; + readonly timeout?: number; + + /** + * Returns the Answering machine detector's id. + */ + id(): string; + + /** + * Starts answering machine or voicemail recognition session. + */ + detect(): Promise; + + /** + * Adds a handler for the specified [AMD.Events]. Use only functions as handlers; anything except a function leads to an error and scenario termination when a handler is called. + * @param event Event class (e.g., [AMD.Events.DetectionComplete]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: AMD.Events | T, + callback: (event: AMD._Events[T]) => any + ): void; + + /** + * Removes a handler for the specified [AMD.Events] event. + * @param event Event class (i.e., [AMD.Events.DetectionComplete]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: AMD.Events | T, + callback?: (event: AMD._Events[T]) => any + ): void; + } +} + +declare namespace AMD { + /** + * Creates a new [AMD.AnsweringMachineDetector](answering machine or voicemail detector) instance. You can attach sources later via the [VoxMediaUnit] **sendMediaTo** method. + */ + function create(parameters: AMD.AMDParameters): AMD.AnsweringMachineDetector; +} + +declare namespace AMD { + /** + * @event + */ + enum Events { + /** + * Triggered when answering machine or voicemail detection is complete. + * @typedef AMD._DetectionCompleteEvent + */ + DetectionComplete = 'AMD.Events.DetectionComplete', + /** + * Triggered when answering machine detector throw an error. + * @typedef AMD._DetectionErrorEvent + */ + DetectionError = 'AMD.Events.DetectionError', + } + + /** + * @private + */ + interface _Events { + [AMD.Events.DetectionComplete]: AMD._DetectionCompleteEvent; + [AMD.Events.DetectionError]: AMD._DetectionErrorEvent; + } + + /** + * @private + */ + interface _AMDEvent { + /** + * AMD istance that generated the event + */ + amd: AMD.AnsweringMachineDetector; + } + + /** + * @private + */ + interface _DetectionCompleteEvent extends _AMDEvent { + /** + * Call that triggers the event. + */ + call: Call; + /** + * The id of the Call. + */ + callId: string; + /** + * Answering machine result class, such as human, voicemail, timeout or call termination. + */ + resultClass: AMD.ResultClass; + /** + * Answering machine result subtype, such as MIMIC or NONE. + */ + resultSubtype?: AMD.ResultSubtype; + /** + * Recognition confidence. Values range from 0 (completely uncertain) to 100 (completely certain). The value is not guaranteed to be accurate, consider it while handling the event. + */ + confidence?: number; + } + + /** + * @private + */ + interface _DetectionErrorEvent extends _AMDEvent { + /** + * The id of the Call. + */ + callId?: string; + /** + * Error message. + */ + message: string; + } +} + +declare namespace AMD { + /** + * Answering machine or voicemail detector model. + */ + enum Model { + /** + * Brazil + */ + BR = 'br', + /** + * Chile + */ + CL = 'cl', + /** + * Colombia + */ + CO = 'colombia', + /** + * Kazakhstan + */ + KZ = 'kz', + /** + * Mexico + */ + MX = 'mx', + /** + * Russia + */ + RU = 'ru', + /** + * Philippines + */ + PH = 'ph', + /** + * Peru + */ + PE = 'pe', + /** + * United States + */ + US = 'us', + /** + * General European multilingual model + */ + EU_GENERAL = 'eu_general', + } +} + +declare namespace AMD { + /** + * Answering machine result class, such as human, voicemail, timeout or call termination. + */ + enum ResultClass { + /** + * AMD detected a voicemail prompt. + */ + VOICEMAIL = 'VOICEMAIL', + /** + * AMD detected a human answering. + */ + HUMAN = 'HUMAN', + /** + * AMD reached the recognition timeout. + */ + TIMEOUT = 'TIMEOUT', + /** + * AMD detected a call hangup. + */ + CALL_ENDED = 'CALL_ENDED', + } +} + +declare namespace AMD { + /** + * Answering machine result subtype, such as mimic or none. + */ + enum ResultSubtype { + MIMIC = 'MIMIC', + NONE = 'NONE', + } +} + +declare namespace AMD { + /** + * Detection threshold in the range **0.0** - **1.0** + */ + interface Thresholds { + human?: number; + voicemail?: number; + mimic?: number; + } +} + +/** + * @event + */ +declare enum AppEvents { + /** + * Triggered when an incoming call arrives. + * @typedef _CallAlertingEvent + */ + CallAlerting = 'Application.CallAlerting', + /** + * Triggered when a session is about to terminate. Triggers in two cases:
+ * 1) when there are no calls and/or ACD requests in a call session. See the [session limits](/docs/guides/voxengine/limits) for details;
+ * 2) when the [VoxEngine.terminate](/docs/references/voxengine/voxengine/terminate) method is called. Timers and any other external resources are not available after this event is triggered, + * but you can perform one HTTP request inside the event handler (e.g. to notify an external system about the fact that the session is finished). + * When that request is finished (or no such request is made), the [AppEvents.Terminated] event is triggered. + * @typedef _TerminatingEvent + */ + Terminating = 'Application.Terminating', + /** + * Triggered when a session is terminated and after the [AppEvents.Terminating] event is triggered. + * The time between these events depends on handler for [AppEvents.Terminating] event. + * Use the event just for debugging, only the [Logger.write] method could be used in a handler. + * @typedef _TerminatedEvent + */ + Terminated = 'Application.Terminated', + /** + * The very first event is triggered due to incoming call or HTTP request to Voximplant cloud over the internet. + * Triggers only once in a session, so if you execute the same HTTP request again it creates the new, separate session. + * Note that usage of the event in your JS scenario is optional. + * @typedef _StartedEvent + */ + Started = 'Application.Started', + /** + * Triggered when the managing HTTP request is received by the session. + * If you [start a call session with HTTP request](/docs/references/httpapi/managing_scenarios#startscenarios), you get an answer: an object with media\_session\_access\_url property. + * The property's value is the managing URL for the specified session, so it can be used in managing HTTP request that triggers [AppEvents.HttpRequest] event. + * @typedef _HttpRequestEvent + */ + HttpRequest = 'Application.HttpRequest', + + /** + * Triggered when there is a new connection to a WebSocket. + * @typedef _NewWebSocketEvent + */ + WebSocket = 'AppEvents.NewWebSocketConnection', + + /** + * Triggered when a WebSocket fails. It can happen when the number of incoming WebSocket connections exceeds the number of calls in one session + 3. + * @typedef _NewWebSocketFailedEvent + */ + NewWebSocketFailed = 'Application.OnNewWebSocketFailed', +} + +/** + * @private + */ +declare interface _AppEvents { + [AppEvents.CallAlerting]: _CallAlertingEvent; + [AppEvents.Terminating]: _TerminatingEvent; + [AppEvents.Terminated]: _TerminatedEvent; + [AppEvents.Started]: _StartedEvent; + [AppEvents.HttpRequest]: _HttpRequestEvent; + [AppEvents.WebSocket]: _NewWebSocketEvent; + [AppEvents.NewWebSocketFailed]: _NewWebSocketFailedEvent; +} + +/** + * @private + */ +declare interface _HttpRequestEvent { + /** + * HTTP request method. E.g. 'POST'. + */ + method: string; + /** + * HTTP path requested (without the domain name). E.g. '/request/1d61f27ba2faad53.1500645140.80028_185.164.148.244/eb4b0539b13e2401'. + */ + path: string; + /** + * HTTP request content. E.g. '{"param1": "value1", "param2": "value2"}'. + */ + content: string; + /** + * List of dictionaries with key and value fields representing HTTP headers (the ones starting with "X-"). + */ + headers: { key: string; value: string }[]; +} + +/** + * @private + */ +declare interface _NewWebSocketFailedEvent extends _HttpRequestEvent { + reason: string; +} + +/** + * @private + */ +declare interface _NewWebSocketEvent extends _HttpRequestEvent { + websocket: WebSocket; +} + +/** + * @private + */ +declare interface _StartedEvent { + /** + * HTTP URL that can be used to send commands to this scenario from the external systems. + */ + accessURL: string; + /** + * HTTPS URL that can be used to send commands to this scenario from the external systems. + */ + accessSecureURL: string; + /** + * Unique identification number of the Voximplant account. Can be used as one of the [authentication parameters](/docs/references/httpapi/auth_parameters) in management API methods. + */ + accountId: number; + /** + * Unique identification number of Voximplant application. Can be used in [Managing Applications](/docs/references/httpapi/managing_applications) in management API methods. + */ + applicationId: number; + /** + * Direct link to the call's log. + */ + logURL: string; + /** + * Identification number of JS session that is unique within an account and its child accounts. Can be used in [Managing History](/docs/references/httpapi/managing_history) in management API methods. + */ + sessionId: number; + /** + * Conference name that is passed to the conference session created via the management API. + */ + conference_name: string; +} + +/** + * @private + */ +declare interface _TerminatedEvent {} + +/** + * @private + */ +declare interface _TerminatingEvent {} + +/** + * @private + */ +declare interface _CallAlertingEvent { + /** + * Incoming call that triggered the event. + */ + call: Call; + /** + * Name of the event - "Application.CallAlerting". + */ + name: string; + /** + * CallerID for current call. + */ + callerid: string; + /** + * Dialed number. + */ + destination: string; + /** + * Dialed SIP URI. + */ + toURI: string; + /** + * Source CallerID with domain or SIP URI for incoming SIP call. + */ + fromURI: string; + /** + * Displayable name of the caller. + */ + displayName: string; + /** + * Custom SIP headers received with the call (the ones starting with "X-"). + */ + headers: { [header: string]: string }; + /** + * Optional. Custom data for the current call object. It can be passed from Web SDK via the [Client.call](/docs/references/websdk/voximplant/client#call) method in the *customData* parameter. + */ + customData?: string; + /** + * Internal information about codecs, should be passed to the [VoxEngine.callUser], [VoxEngine.callUserDirect], [VoxEngine.callSIP], [VoxEngine.callConference], [Call.answer], [Call.answerDirect], [Call.startEarlyMedia] methods call. + */ + scheme: string; +} + +/** + * Represents an application storage object to manipulate key-value pairs. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.ApplicationStorage); + * ``` + */ +declare namespace ApplicationStorage {} + +declare namespace ApplicationStorage { + /** + * Retrieves a value of the specified key. + * @param key Key to get + */ + function get(key: string): Promise; +} + +declare namespace ApplicationStorage { + /** + * Retrieves all the keys assigned to a Voximplant application. + * @param pattern Optional. Namespace that keys should contain + */ + function keys(pattern?: string): Promise; +} + +declare namespace ApplicationStorage { + /** + * Creates a key-value pair. If an already existing **key** is passed, the method updates its **value**. + *
+ * The keys should be unique within a Voximplant application. + * @param key Key to create/update, up to 200 characters. A key can contain a namespace that is written before a colon, for example, test:1234. Thus, namespace "test" can be used as a **pattern** in the [keys](/docs/references/voxengine/applicationstorage#keys) method to find the keys with the same namespace. If no namespace is set, the key itself is considered as namespace + * @param value Value for the specified key, up to 2000 characters + * @param ttl Key expiry time in seconds. The value is in range of 0..7,776,000 (90 days). The TTL is converted to an `expireAt` Unix timestamp field as part of the storage object. Note that the pricing is tiered in three day-based pieces: 0-30, 31-60, 61-90. See the details [here](https://voximplant.com/pricing) + */ + function put(key: string, value: string, ttl: number): Promise; +} + +declare namespace ApplicationStorage { + /** + * Removes the specified key. Note that the returned **StorageKey** always has zero **ttl**. + * @param key Key to delete + */ + function remove(key: string): Promise; +} + +/** + * List of available dictionaries for ASR. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + * @deprecated Use [ASRModelList] instead + */ +declare enum ASRDictionary { + /** + * ASR Russian addresses dictionary + * @deprecated Use [ASRModelList] instead + */ + ADDRESS_RU = 'asr-dict-address-ru', + /** + * ASR Turkish addresses dictionary + * @deprecated Use [ASRModelList] instead + */ + ADDRESS_TR = 'asr-dict-address-tr', + /** + * ASR addresses dictionary + * Available languages 'ru-RU','en-US','uk-UK','tr-TR','de-DE','fr-FR','es-ES' + * @deprecated Use [ASRModelList] instead + */ + ADDRESS = 'asr-dict-yand-maps', + /** + * ASR notes dictionary + * @deprecated Use [ASRModelList] instead + */ + NOTES = 'asr-dict-yand-notes', + /** + * ASR queries dictionary + * Available languages 'ru-RU','en-US','uk-UK','tr-TR' + * @deprecated Use [ASRModelList] instead + */ + SEARCH_QUERIES = 'asr-dict-yand-queries', + /** + * ASR music dictionary + * @deprecated Use [ASRModelList] instead + */ + MUSIC = 'asr-dict-yand-music', + /** + * ASR buying dictionary + * @deprecated Use [ASRModelList] instead + */ + ECOMMERCE = 'asr-dict-yand-buying', + /** + * ASR Russian phone numbers dictionary + * @deprecated Use [ASRModelList] instead + */ + NUMBERS_RU = 'asr-dict-yand-numbers', + /** + * ASR Russian general dictionary + * @deprecated Use [ASRModelList] instead + */ + GENERAL_RU = 'asr-dict-yand-general', + /** + * ASR Russian date dictionary + * @deprecated Use [ASRModelList] instead + */ + DATE_RU = 'asr-dict-yand-dates', + /** + * ASR Russian names dictionary + * @deprecated Use [ASRModelList] instead + */ + NAMES_RU = 'asr-dict-yand-names', + /** + * ASR Russian questionnaire dictionary + * @deprecated Use [ASRModelList] instead + */ + QUESTIONNAIRE_RU = 'asr-dict-yand-questionnaire', + /** + * ASR Russian T-Bank dictionary + * @deprecated Use [ASRModelList] instead + */ + TBANK = 'asr-dict-tinkoff-', +} + +/** + * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.ASR); + * ``` + * @event + */ +declare enum ASREvents { + /** + * Triggers in case of errors during the recognition process. + * @typedef _ASRErrorEvent + */ + ASRError = 'ASR.Error', + /** + * Triggers after ASR instance is created. + * @typedef _ASRStartedEvent + */ + Started = 'ASR.Started', + /** + * Triggers after ASR detected voice input and started collecting audio data for ASR. + * @typedef _ASRCaptureStartedEvent + */ + CaptureStarted = 'ASR.CaptureStarted', + /** + * Triggers after ASR captured audio data, before recognition process. + * @typedef _ASRSpeechCapturedEvent + */ + SpeechCaptured = 'ASR.SpeechCaptured', + /** + * Triggered when a speech recognition result has been received from ASR. + * + * We strongly recommend to create recognition timeout manually to prevent unexpectedly long recognition time. + * Note: We recommend to take a decision about continuing speech recognition in this event's handler. Otherwise, speech recognition continues automatically. + * @typedef _ASRResultEvent + */ + Result = 'ASR.Result', + /** + * Triggered when interim recognition result received from ASR. Note that event could be triggered only if the [ASRParameters.interimResults] option is set to **true**. + * @typedef _ASRInterimResultEvent + */ + InterimResult = 'ASR.InterimResult', + /** + * Triggers as a result of the [ASR.stop] method call. + * @typedef _ASRStoppedEvent + */ + Stopped = 'ASR.Stopped', +} + +/** + * @private + */ +declare interface _ASREvents { + [ASREvents.ASRError]: _ASRErrorEvent; + [ASREvents.Started]: _ASRStartedEvent; + [ASREvents.CaptureStarted]: _ASRCaptureStartedEvent; + [ASREvents.SpeechCaptured]: _ASRSpeechCapturedEvent; + [ASREvents.Result]: _ASRResultEvent; + [ASREvents.InterimResult]: _ASRInterimResultEvent; + [ASREvents.Stopped]: _ASRStoppedEvent; +} + +/** + * @private + */ +declare interface _ASREvent { + /** + * ASR instance that generated the event + */ + asr: ASR; +} + +/** + * @private + */ +declare interface _ASRErrorEvent extends _ASREvent { + /** + * Error message + */ + error: string; +} + +/** + * @private + */ +declare interface _ASRStartedEvent extends _ASREvent {} + +/** + * @private + */ +declare interface _ASRCaptureStartedEvent extends _ASREvent {} + +/** + * @private + */ +declare interface _ASRSpeechCapturedEvent extends _ASREvent {} + +/** + * @private + */ +declare interface _ASRResultEvent extends _ASREvent { + /** + * Recognition result. Depending on the ASR provider, this parameter is called text or transcript (in rare cases). + */ + text: string; + /** + * Recognition confidence. Depending on the ASR provider, can be in 0..100 or 0..1 range (100 or 1 means full confidence, 0 - not confident at all) + */ + confidence: number; + /** + * Optional. Time offset of the end of this result relative to the beginning of the audio. + */ + resultEndTime?: string | number; + /** + * Optional. For multichannel audio, this is the channel number corresponding to the recognized result for the audio from that channel. + */ + channelTag?: number; + /** + * Optional. Output only. The BCP-47 language tag of the language in this result. This language code is detected to have the most likelihood of being spoken in the audio. + */ + languageCode?: string; +} + +/** + * @private + */ +declare interface _ASRInterimResultEvent extends _ASREvent { + /** + * Recognition result + */ + text: string; +} + +/** + * @private + */ +declare interface _ASRStoppedEvent extends _ASREvent { + /** + * Record cost (in the account's currency: USD, EUR or RUB) + */ + cost: string; + /** + * Record duration (sec) + */ + duration: number; +} + +/** + * List of available languages for ASR. + *
+ * Note that the T-Bank VoiceKit and Yandex Speechkit supports only 'ASRLanguage.RUSSIAN_RU' language. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ +declare enum ASRLanguage { + /** + * English (United States) + * @const + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_US = 'en-US', + /** + * English (Canada) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_CA = 'en-CA', + /** + * English (United Kingdom) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_UK = 'en-GB', + /** + * English (Australia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_AU = 'en-AU', + /** + * Spanish (Spain) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_ES = 'es-ES', + /** + * Spanish (Mexico) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_MX = 'es-MX', + /** + * Italian (Italy) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ITALIAN_IT = 'it-IT', + /** + * French (France) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + FRENCH_FR = 'fr-FR', + /** + * French (Canada) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + FRENCH_CA = 'fr-CA', + /** + * Polish (Poland) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + POLISH_PL = 'pl-PL', + /** + * Portuguese (Portugal) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + PORTUGUES_PT = 'pt-PT', + /** + * Catalan (Catalan) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + CATALAN_ES = 'ca-ES', + /** + * Chinese (Taiwan) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + CHINESE_TW = 'cmn-Hant-TW', + /** + * Danish (Denmark) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + DANISH_DK = 'da-DK', + /** + * German (Germany) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + GERMAN_DE = 'de-DE', + /** + * Finnish (Finland) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + FINNISH_FI = 'fi-FI', + /** + * Japanese (Japan) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + JAPANESE_JP = 'ja-JP', + /** + * Korean (Korea) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + KOREAN_KR = 'ko-KR', + /** + * Dutch (Netherlands) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + DUTCH_NL = 'nl-NL', + /** + * Norwegian (Norway) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + NORWEGIAN_NO = 'nb-NO', + /** + * Portuguese (Brazil) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + PORTUGUESE_BR = 'pt-BR', + /** + * Russian (Russia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + RUSSIAN_RU = 'ru-RU', + /** + * Swedish (Sweden) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SWEDISH_SE = 'sv-SE', + /** + * Chinese (People's Republic of China) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + CHINESE_CN = 'cmn-Hans-CN', + /** + * Chinese (Hong Kong S.A.R.) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + CHINESE_HK = 'cmn-Hans-HK', + /** + * Afrikaans (South Africa) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + AFRIKAANS_ZA = 'af-ZA', + /** + * Indonesian (Indonesia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + INDONESIAN_ID = 'id-ID', + /** + * Malay (Malaysia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + MALAYSIA_MY = 'ms-MY', + /** + * Czech (Czech Republic) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + CZECH_CZ = 'cs-CZ', + /** + * English (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_IN = 'en-IN', + /** + * English (Ireland) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_IE = 'en-IE', + /** + * English (New Zealand) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_NZ = 'en-NZ', + /** + * English (Philippines) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_PH = 'en-PH', + /** + * English (South Africa) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_ZA = 'en-ZA', + /** + * Spanish (Argentina) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_AR = 'es-AR', + /** + * Spanish (Bolivia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_BO = 'es-BO', + /** + * Spanish (Chile) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_CL = 'es-CL', + /** + * Spanish (Colombia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_CO = 'es-CO', + /** + * Spanish (Costa Rica) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_CR = 'es-CR', + /** + * Spanish (Ecuador) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_EC = 'es-EC', + /** + * Spanish (El Salvador) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_SV = 'es-SV', + /** + * Spanish (United States) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_US = 'es-US', + /** + * Spanish (Guatemala) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_GT = 'es-GT', + /** + * Spanish (Honduras) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_HN = 'es-HN', + /** + * Spanish (Nicaragua) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_NI = 'es-NI', + /** + * Spanish (Panama) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_PA = 'es-PA', + /** + * Spanish (Paraguay) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_PY = 'es-PY', + /** + * Spanish (Peru) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_PE = 'es-PE', + /** + * Spanish (Puerto Rico) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_PR = 'es-PR', + /** + * Spanish (Republican Dominican) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_DO = 'es-DO', + /** + * Spanish (Uruguay) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_UY = 'es-UY', + /** + * Spanish (Venezuela) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SPANISH_VE = 'es-VE', + /** + * Basque (Spain) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + BASQUE_ES = 'eu-ES', + /** + * Filipino (Philippines) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + FILIPINO_PH = 'fil-PH', + /** + * Galician (Spain) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + GALICIAN_ES = 'gl-ES', + /** + * Croatian (Croatia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + CROATIAN_HR = 'hr-HR', + /** + * Zulu (South Africa) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ZULU_ZA = 'zu-ZA', + /** + * Icelandic (Iceland) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ICELANDIC_IS = 'is-IS', + /** + * Lithuanian (Lithuania) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + LITHUANIAN_LT = 'lt-LT', + /** + * Hungarian (Hungary) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + HUNGARIAN_HU = 'hu-HU', + /** + * Romanian (Romania) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ROMANIAN_RO = 'ro-RO', + /** + * Slovak (Slovakia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SLOVAK_SK = 'sk-SK', + /** + * Slovenian (Slovenia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SLOVENIAN_SL = 'sl-SI', + /** + * Vietnamese (Viet Nam) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + VIETNAMESE_VN = 'vi-VN', + /** + * Turkish (Turkiye) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + TURKISH_TR = 'tr-TR', + /** + * Greek (Greece) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + GREEK_GR = 'el-GR', + /** + * Bulgarian (Bulgaria) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + BULGARIAN_BG = 'bg-BG', + /** + * Serbian + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SERBIAN_RS = 'sr-RS', + /** + * Ukrainian (Ukraine) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + UKRAINIAN_UA = 'uk-UA', + /** + * Hebrew (Israel) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + HEBREW_IL = 'he-IL', + /** + * Arabic (Israel) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_IL = 'ar-IL', + /** + * Arabic (Jordan) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_JO = 'ar-JO', + /** + * Arabic (U.A.E.) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_AE = 'ar-AE', + /** + * Arabic (Bahrain) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_BH = 'ar-BH', + /** + * Arabic (Algeria) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_DZ = 'ar-DZ', + /** + * Arabic (Saudi Arabia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_SA = 'ar-SA', + /** + * Arabic (Iraq) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_IQ = 'ar-IQ', + /** + * Arabic (Kuwait) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_KW = 'ar-KW', + /** + * Arabic (Morocco) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_MA = 'ar-MA', + /** + * Arabic (Tunisia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_TN = 'ar-TN', + /** + * Arabic (Oman) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_OM = 'ar-OM', + /** + * Arabic (Palestinian) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_PS = 'ar-PS', + /** + * Arabic (Qatar) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_QA = 'ar-QA', + /** + * Arabic (Lebanon) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_LB = 'ar-LB', + /** + * Arabic (Egypt) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARABIC_EG = 'ar-EG', + /** + * Farsi (Iran) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + FARSI_IR = 'fa-IR', + /** + * Hindi (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + HINDI_IN = 'hi-IN', + /** + * Thai (Thailand) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + THAI_TH = 'th-TH', + /** + * Cantonese, Traditional script, Hong Kong SAR + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + CANTONESE_HK = 'yue-Hant-HK', + /** + * Amharic (Ethiopia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + AMHARIC_ET = 'am-ET', + /** + * Armenian (Armenia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ARMENIAN_AM = 'hy-AM', + /** + * Azerbaijani (Azerbaijan) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + AZERBAIJANI_AZ = 'az-AZ', + /** + * Bengali (Bangladesh) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + BENGALI_BD = 'bn-BD', + /** + * Bengali (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + BENGALI_IN = 'bn-IN', + /** + * English (Ghana) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_GH = 'en-GH', + /** + * English (Kenya) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_KE = 'en-KE', + /** + * English (Nigeria) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_NG = 'en-NG', + /** + * English (Tanzania) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + ENGLISH_TZ = 'en-TZ', + /** + * Georgian (Georgia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + GEORGIAN_GE = 'ka-GE', + /** + * Gujarati (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + GUJARATI_IN = 'gu-IN', + /** + * Javanese (Indonesia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + JAVANESE_ID = 'jv-ID', + /** + * Kannada (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + KANNADA_IN = 'kn-IN', + /** + * Khmer (Cambodia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + KHMER_KH = 'km-KH', + /** + * Lao (Laos) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + LAO_LA = 'lo-LA', + /** + * Latvian (Latvia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + LATVIAN_LV = 'lv-LV', + /** + * Malayalam (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + MALAYALAM_IN = 'ml-IN', + /** + * Marathi (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + MARATHI_IN = 'mr-IN', + /** + * Nepali (Nepal) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + NEPALI_NP = 'ne-NP', + /** + * Sinhala (Srilanka) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SINHALA_LK = 'si-LK', + /** + * Sundanese (Indonesia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SUNDANESE_ID = 'su-ID', + /** + * Swahili (Tanzania) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SWAHILI_TZ = 'sw-TZ', + /** + * Swahili (Kenya) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + SWAHILI_KE = 'sw-KE', + /** + * Tamil (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + TAMIL_IN = 'ta-IN', + /** + * Tamil (Singapore) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + TAMIL_SG = 'ta-SG', + /** + * Tamil (Sri Lanka) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + TAMIL_LK = 'ta-LK', + /** + * Tamil (Malaysia) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + TAMIL_MY = 'ta-MY', + /** + * Telugu (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + TELUGU_IN = 'te-IN', + /** + * Urdu (Pakistan) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + URDU_PK = 'ur-PK', + /** + * Urdu (India) + * @warning Use [ASRLanguage] for [RecorderParameters] 'language' parameter. For [ASRParameters] 'profile' parameter use [ASRProfileList] instead. + */ + URDU_IN = 'ur-IN', +} + +/** + * List of available models for [ASR]. + *
+ * Note that T-Bank VoiceKit supports only **PHONE_CALL** model. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + * @deprecated For [ASRParameters] **model** parameter use [ASRModelList] instead. + */ +declare enum ASRModel { + /** + * Best for short queries such as voice commands or voice search. + * @const + * @deprecated For [ASRParameters] 'model' parameter use [ASRModelList] instead. + */ + COMMAND_AND_SEARCH = 'command_and_search', + + /** + * Best for audio that originated from a phone call (typically recorded at a 8khz sampling rate). + * @const + * @deprecated For [ASRParameters] 'model' parameter use [ASRModelList] instead. + */ + PHONE_CALL = 'phone_call', + + /** + * Best for audio that originated from video or includes multiple speakers. Ideally the audio is recorded at a 16khz or greater sampling rate. This is a premium model that costs more than the standard rate. + * @const + * @deprecated For [ASRParameters] 'model' parameter use [ASRModelList] instead. + */ + VIDEO = 'video', + + /** + * Best for audio that is not one of the specific audio models. For example, long-form audio. Ideally the audio is high-fidelity, recorded at a 16khz or greater sampling rate. + * @const + * @deprecated For [ASRParameters] 'model' parameter use [ASRModelList] instead. + */ + DEFAULT = 'default', +} + +/** + * @private + */ +declare interface BaseRecorderParameters { + /** + * Optional. Whether to restrict access to the record without management API authorization (available only in the [VoxEngine.createRecorder] method). + */ + secure?: boolean; + /** + * Optional. Whether to create the call record transcription. Note that transcription is not available for the [Recorder module](/docs/references/voxengine/modules#recorder). See the details [in the article](/docs/guides/speech/asr). + */ + transcribe?: boolean; + /** + * Optional. Transcription language. The parameter uses [ASRLanguage] from the [ASR module](/docs/references/voxengine/modules#asr) as possible values. Note that it is necessary to include the [ASR module](/docs/references/voxengine/modules#asr) in the scenario to use the language constants. The parameter is not available for the [Recorder module](/docs/references/voxengine/modules#recorder). + */ + language?: ASRLanguage; + /** + * Optional. Whether to use the HD audio. The default value is **false**. If set to **false**, 8 KHz / 32 kbps mp3 file is generated. If set to **true**, "wideband audio" 48 KHz / 192 kbps mp3 file is generated. Note that transcription's quality does not depend on this parameter. The parameter is not compatible with **lossless: true** parameter. + */ + hd_audio?: boolean; + /** + * Optional. Storage time for recorded files. The default value is **[RecordExpireTime.THREEMONTHS]**. + */ + expire?: RecordExpireTime; + /** + * Optional. Whether to save the record in flac format. The default value is **false**. The parameter is not compatible with **hd_audio: true** parameter. + */ + lossless?: boolean; + /** + * Optional. Whether to record video. The default value is **false**. + */ + video?: boolean; + /** + * Optional. Recorder video parameters. + */ + videoParameters?: RecorderVideoParameters; + /** + * Optional. The prefix to add to the record names when storing to your S3 storage. Works only for custom S3-compatible storages. + */ + recordNamePrefix?: string; +} + +/** + * The parameters can be passed as arguments to the [Call.answer] method. + */ +declare interface CallAnswerParameters extends CallParameters {} + +/** + * @event + */ +declare enum CallEvents { + /** + * Triggers after remote peer answered the call or set the call into the [Call.startEarlyMedia] state. Note that event is not triggered in P2P mode. + * @typedef _AudioStartedEvent + * */ + AudioStarted = 'Call.AudioStarted', + /** + * Triggers when [voicemail detection](/docs/guides/calls/voicemail-detection) system connects to the VMD/AMD server and starts detecting voicemail. + * @typedef _AudioIdentificationStartedEvent + * */ + AudioIdentificationStarted = 'Call.AudioIdentificationStarted', + /** + * Triggers when [voicemail detection](/docs/guides/calls/voicemail-detection) ends detecting voicemail and the audio identification result is received. + * @typedef _AudioIdentificationResultEvent + * @hidden + */ + AudioIdentificationResult = 'Call.AudioIdentificationResult', + /** + * Triggers when [voicemail detection](/docs/guides/calls/voicemail-detection) stops detecting voicemail. + * @typedef _AudioIdentificationStoppedEvent + * */ + AudioIdentificationStopped = 'Call.AudioIdentificationStopped', + /** + * Triggers when [voicemail detection](/docs/guides/calls/voicemail-detection) occurs an error. + * @typedef _AudioIdentificationErrorEvent + */ + AudioIdentificationError = 'Call.AudioIdentificationError', + /** + * Triggered when blind transfers are enabled by [Call.handleBlindTransfer]. + * @typedef _BlindTransferRequestedEvent + */ + BlindTransferRequested = 'Call.BlindTransferRequested', + /** + * Triggers after an incoming/outgoing call is connected. For incoming call, it happens after the [Call.answer] is called. For outgoing call, it happens when a remote peer answers the call. + * @typedef _ConnectedEvent + */ + Connected = 'Call.Connected', + /** + * Triggers on an incoming/outgoing call forwarding. + * @typedef _ForwardingEvent + */ + Forwarding = 'Call.Forwarding', + /** + * Triggered when a call is terminated. + * Most frequent status codes (returned when a call is terminated before being answered):
+ * + * + * + * + * + * + * + * + *
CodeDescription
408Call is not answered within 60 seconds
603Call is rejected
486Destination number is busy
487Request terminated
+ * Note that this event does not mean the end of the JavaScript session. + * The session without calls and/or ACD requests are automatically terminated after some time (see the [session limits](/docs/guides/voxengine/limits) for details). + * It is a good idea to explicitly terminate the session with [VoxEngine.terminate](/docs/references/voxengine/voxengine/terminate) after it is no longer needed. + * @typedef _DisconnectedEvent + */ + Disconnected = 'Call.Disconnected', + /** + * Triggered when an outgoing call is terminated before connection. + * Most frequent status codes:
+ * + * + * + * + * + * + * + * + * + * + * + *
CodeDescription
486Destination number is busy
487Request terminated
404Invalid number
480Destination number is unavailable
402Insufficient funds
603Call was rejected
408Call was not answered within 60 seconds
+ * @typedef _FailedEvent + */ + Failed = 'Call.Failed', + /** + * Triggered when an INFO message is received. + * @typedef _InfoReceivedEvent + */ + InfoReceived = 'Call.InfoReceived', + /** + * Triggered when a text message is received. + * @typedef _MessageReceivedEvent + */ + MessageReceived = 'Call.MessageReceived', + /** + * Triggers each time when microphone status changes. There is the method for enabling status analyzing - [Call.handleMicStatus]. + * @typedef _MicStatusChangeEvent + * */ + MicStatusChange = 'Call.MicStatusChange', + /** + * Triggered when a call is taken off hold. + * @typedef _OffHoldEvent + */ + OffHold = 'Call.OffHold', + /** + * Triggered when a call is put on hold. + * @typedef _OnHoldEvent + */ + OnHold = 'Call.OnHold', + /** + * Triggered when the audio/voice playback is completed. + * Note that the [Call.stopPlayback] method finishes any media, so the PlaybackFinished event is not triggered. The playback may be started by the [Call.say] or [Call.startPlayback] methods. + * @typedef _PlaybackFinishedEvent + */ + PlaybackFinished = 'Call.PlaybackFinished', + /** + * Triggers by the [Call.startPlayback] and [Call.say] methods when:
+ * 1) the audio file download to the Voximplant cache is finished;
+ * 2) the audio file is found in the cache (i.e., it is in the cache before). + * @typedef _PlaybackReadyEvent + */ + PlaybackReady = 'Call.PlaybackReady', + /** + * Triggers by the [Call.startPlayback] and [Call.say] methods when audio/voice playback is started. + * @typedef _PlaybackStartedEvent + */ + PlaybackStarted = 'Call.PlaybackStarted', + /** + * Triggered when a push notification is sent. + * @typedef _PushSentEvent + */ + PushSent = 'Call.PushSent', + /** + * Triggered when the Voximplant cloud receives the ReInviteAccepted message. This message means that a call received video from the other participant. + * @typedef _ReInviteAcceptedEvent + */ + ReInviteAccepted = 'Call.ReInviteAccepted', + /** + * Triggered when the Voximplant cloud receives the ReInviteReceived message. This message means that a caller:
+ * 1) started sending video;
+ * 2) started/stopped screensharing;
+ * 3) put a call on hold / took a call off hold. + * @typedef _ReInviteReceivedEvent + */ + ReInviteReceived = 'Call.ReInviteReceived', + /** + * Triggered when the Voximplant cloud receives the ReInviteRejected message. This message means that a call does not receive video from the other participant. + * @typedef _ReInviteRejectedEvent + */ + ReInviteRejected = 'Call.ReInviteRejected', + /** + * Triggers in case of errors during the recording process. + * @typedef _RecordErrorEvent + */ + RecordError = 'Call.RecordError', + /** + * Triggered when call recording is started as a result of the [Call.record] method call. + * @typedef _RecordStartedEvent + */ + RecordStarted = 'Call.RecordStarted', + /** + * Triggered when call recording is stopped. This happens after the [CallEvents.Disconnected] event is triggered. + * @typedef _RecordStoppedEvent + */ + RecordStopped = 'Call.RecordStopped', + /** + * Triggers after outgoing call receives progress signal from a remote peer. + * @typedef _RingingEvent + */ + Ringing = 'Call.Ringing', + /** + * Triggered when a call status is changed. + * @typedef _StateChangedEvent + */ + StateChanged = 'Call.StateChanged', + /** + * Triggered when call statistic changed. + * @deprecated + * @typedef _StatisticsEvent + */ + Statistics = 'Call.Statistics', + /** + * Triggered when a call dial tone is detected (either dial tone or busy tone). + * There is the deprecated method for enabling the tone detection - 'Call.detectProgressTone'. Note that:
+ * 1) triggers only if the [CallEvents.Connected] event is triggered;
+ * 2) the event is only triggered once in a call session. + * @typedef _ToneDetectedEvent + */ + ToneDetected = 'Call.ToneDetected', + /** + * Triggered when a DTMF signal is received. Note that by default DTMF signals do not trigger this event, this behavior needs to be set explicitly via the [Call.handleTones] method. + * @typedef _ToneReceivedEvent + */ + ToneReceived = 'Call.ToneReceived', + /** + * Triggered when a call transfer is complete. + * @typedef _TransferCompleteEvent + */ + TransferComplete = 'Call.TransferComplete', + /** + * Triggered when a call transfer is failed. + * @typedef _TransferFailedEvent + */ + TransferFailed = 'Call.TransferFailed', + /** + * Triggers after the video track is created. This could happen only if the [Call.record] method with **{video: true}** parameters is called. + * @typedef _VideoTrackCreatedEvent + */ + VideoTrackCreated = 'Call.Video.TrackCreated', + /** + * Triggers after the first audio packet is received. + * @typedef _FirstAudioPacketReceivedEvent + */ + FirstAudioPacketReceived = 'Call.FirstAudioPacketReceived', + /** + * Triggers after the first video packet is received. + * @typedef _FirstVideoPacketReceivedEvent + */ + FirstVideoPacketReceived = 'Call.FirstVideoPacketReceived', + /** + * Triggers after the RTP stopped. + * @typedef _RtpStoppedEvent + */ + RtpStopped = 'Call.RtpStopped', + /** + * Triggers after the RTP resumed. + * @typedef _RtpResumedEvent + */ + RtpResumed = 'Call.RtpResumed', +} + +/** + * @private + */ +declare interface _CallEvents { + [CallEvents.AudioStarted]: _AudioStartedEvent; + [CallEvents.AudioIdentificationStarted]: _AudioIdentificationStartedEvent; + [CallEvents.AudioIdentificationResult]: _AudioIdentificationResultEvent; + [CallEvents.AudioIdentificationStopped]: _AudioIdentificationStoppedEvent; + [CallEvents.AudioIdentificationError]: _AudioIdentificationErrorEvent; + [CallEvents.BlindTransferRequested]: _BlindTransferRequestedEvent; + [CallEvents.Connected]: _ConnectedEvent; + [CallEvents.Forwarding]: _ForwardingEvent; + [CallEvents.Disconnected]: _DisconnectedEvent; + [CallEvents.Failed]: _FailedEvent; + [CallEvents.InfoReceived]: _InfoReceivedEvent; + [CallEvents.MessageReceived]: _MessageReceivedEvent; + [CallEvents.MicStatusChange]: _MicStatusChangeEvent; + [CallEvents.OffHold]: _OffHoldEvent; + [CallEvents.OnHold]: _OnHoldEvent; + [CallEvents.PlaybackFinished]: _PlaybackFinishedEvent; + [CallEvents.PlaybackReady]: _PlaybackReadyEvent; + [CallEvents.PlaybackStarted]: _PlaybackStartedEvent; + [CallEvents.PushSent]: _PushSentEvent; + [CallEvents.ReInviteAccepted]: _ReInviteAcceptedEvent; + [CallEvents.ReInviteReceived]: _ReInviteReceivedEvent; + [CallEvents.ReInviteRejected]: _ReInviteRejectedEvent; + [CallEvents.RecordError]: _RecordErrorEvent; + [CallEvents.RecordStarted]: _RecordStartedEvent; + [CallEvents.RecordStopped]: _RecordStoppedEvent; + [CallEvents.Ringing]: _RingingEvent; + [CallEvents.StateChanged]: _StateChangedEvent; + [CallEvents.Statistics]: _StatisticsEvent; + [CallEvents.ToneDetected]: _ToneDetectedEvent; + [CallEvents.ToneReceived]: _ToneReceivedEvent; + [CallEvents.TransferComplete]: _TransferCompleteEvent; + [CallEvents.TransferFailed]: _TransferFailedEvent; + [CallEvents.VideoTrackCreated]: _VideoTrackCreatedEvent; + [CallEvents.FirstAudioPacketReceived]: _FirstAudioPacketReceivedEvent; + [CallEvents.FirstVideoPacketReceived]: _FirstVideoPacketReceivedEvent; + [CallEvents.RtpStopped]: _RtpStoppedEvent; + [CallEvents.RtpResumed]: _RtpResumedEvent; +} + +/** + * @private + */ +declare interface _CallEvent { + /** + * Call that triggered the event + */ + call: Call; + /** + * The name of the event + */ + name: string; + /** + * The call's ID + */ + id: string; +} + +/** + * @private + */ +declare interface _CallHeaderEvent extends _CallEvent { + /** + * Optional. SIP headers received with the message (the ones starting with "X-") + */ + headers?: { [header: string]: string }; +} + +/** + * @private + */ +declare interface _AudioStartedEvent extends _CallHeaderEvent {} + +/** + * @private + */ +declare interface _AudioIdentificationStartedEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _AudioIdentificationResultEvent extends _CallEvent { + audioType: AMD.ResultClass; + audioSubType: AMD.ResultSubtype; + confidence: number; +} + +/** + * @private + */ +declare interface _AudioIdentificationStoppedEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _AudioIdentificationErrorEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _BlindTransferRequestedEvent extends _CallHeaderEvent { + /** + * Username + */ + transferTo: string; +} + +/** + * @private + */ +declare interface _ConnectedEvent extends _CallEvent { + /** + * Optional. Custom data that was passed from the client with call accept command + */ + customData?: string; + /** + * Optional. SIP headers received with the message (the ones starting with "X-") + */ + headers: { [header: string]: string }; +} + +/** + * @private + */ +declare interface _ForwardingEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _DisconnectedEvent extends _CallHeaderEvent { + /** + * Status code of the call (i.e., 486) + */ + internalCode: number; + /** + * Reason of the call failure + */ + reason: string; + /** + * Total call duration in seconds + */ + duration: number; + /** + * Call cost in account currency + */ + cost: number; + /** + * Call direction type according to billing + */ + direction: string; +} + +/** + * @private + */ +declare interface _FailedEvent extends _CallHeaderEvent { + /** + * Status code of the call (i.e., 486) + */ + code: number; + /** + * Status message of call failure + */ + reason: string; +} + +/** + * @private + */ +declare interface _CallRecordEvent extends _CallEvent { + /** + * Record URL + */ + url: string; +} + +/** + * @private + */ +declare interface _InfoReceivedEvent extends _CallHeaderEvent { + /** + * MIME type of INFO message + */ + mimeType: string; + /** + * Content of the message + */ + body: string; +} + +/** + * @private + */ +declare interface _MessageReceivedEvent extends _CallHeaderEvent { + /** + * Content of the message + */ + text: string; +} + +/** + * @private + */ +declare interface _MicStatusChangeEvent extends _CallEvent { + /** + * Whether the microphone is active + */ + active: boolean; +} + +/** + * @private + */ +declare interface _OffHoldEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _OnHoldEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _PlaybackFinishedEvent extends _CallEvent { + /** + * Optional. Error that occurred during the playback + */ + error?: string; +} + +/** + * @private + */ +declare interface _PlaybackReadyEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _PlaybackStartedEvent extends _CallEvent { + /** + * Playback duration + */ + duration: number; +} + +/** + * @private + */ +declare interface _PushSentEvent extends _CallEvent { + /** + * + */ + result: string; +} + +/** + * @private + */ +declare interface _CallReInviteEvent extends _CallHeaderEvent { + /** + * MIME type of INFO message + */ + mimeType: string; + /** + * Content of the message + */ + body: string; +} + +/** + * @private + */ +declare interface _ReInviteAcceptedEvent extends _CallReInviteEvent {} + +/** + * @private + */ +declare interface _ReInviteReceivedEvent extends _CallReInviteEvent {} + +/** + * @private + */ +declare interface _ReInviteRejectedEvent extends _CallHeaderEvent {} + +/** + * @private + */ +declare interface _RecordErrorEvent extends _CallEvent { + /** + * Triggers in case of errors during the recording process + */ + error: string; +} + +/** + * @private + */ +declare interface _RecordStartedEvent extends _CallEvent { + /** + * Link to the record file. + */ + url: string; +} + +/** + * @private + */ +declare interface _RecordStoppedEvent extends _CallEvent { + /** + * Link to the record file. + */ + url: string; + /** + * Record cost (in the account's currency: USD, EUR or RUB) + */ + cost: string; + /** + * Record duration (sec) + */ + duration: number; +} + +/** + * @private + */ +declare interface _RingingEvent extends _CallHeaderEvent {} + +/** + * @private + */ +declare interface _StateChangedEvent extends _CallEvent { + oldState: string; + newState: string; +} + +/** + * @private + */ +declare interface _StatisticsEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _ToneDetectedEvent extends _CallEvent { + /** + * Whether the detected tone is a dial tone. + */ + ProgressTone: boolean; + /** + * Whether the detected tone is a voicemail tone. + */ + VoicemailTone: boolean; +} + +/** + * @private + */ +declare interface _ToneReceivedEvent extends _CallEvent { + /** + * Tone received in this event: the possible values are 0-9,*,# + */ + tone: string; +} + +/** + * @private + */ +declare interface _TransferCompleteEvent extends _CallHeaderEvent { + /** + * Optional. The transfer roles. + */ + role?: 'transferor' | 'target' | 'transferee'; +} + +/** + * @private + */ +declare interface _TransferFailedEvent extends _TransferCompleteEvent { + /** + * Failed transfer's status (e.g., 486) + */ + code: number; + /** + * Failed transfer's status message + */ + reason: string; +} + +/** + * @private + */ +declare interface _VideoTrackCreatedEvent extends _CallRecordEvent {} + +/** + * @private + */ +declare interface _FirstAudioPacketReceivedEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _FirstVideoPacketReceivedEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _RtpStoppedEvent extends _CallEvent {} + +/** + * @private + */ +declare interface _RtpResumedEvent extends _CallEvent {} + +/** + * @private + */ +declare interface CallParameters { + /** + * Optional. Name of the caller that is displayed to the user. Normally it is a human-readable version of CallerID, e.g. a person's name. + */ + displayName?: string; + /** + * Optional. Internal information about codecs from the [AppEvents.CallAlerting] event. + */ + scheme?: { [id: string]: { audio: any; video: any } }; + /** + * Optional. Sets the maximum possible video bitrate for the customer device in kbps. + */ + maxVideoBitrate?: number; + /** + * Optional. Whether to disable the RTP header extension for transmission offset if provided. + */ + disableExtVideoOffset?: boolean; + /** + * Optional. Whether to disable the RTP header extension for video orientation, **3gpp:video-orientation**, if provided. Browsers that do not support that extension display the video correctly, however, the battery consumption is higher. + */ + disableExtVideoOrientation?: boolean; + /** + * Optional. Whether to disable the RTP header extension to control playout delay if provided. + */ + disableExtPlayoutDelay?: boolean; + /** + * Optional. Whether to disable the RTP header extension for video timing if provided. + */ + disableExtVideoTiming?: boolean; + /** + * Optional. Whether the call is coming from a conference. The default value is **false**. + */ + conferenceCall?: boolean; +} + +/** + * [Call] parameters. Can be passed as arguments to the [VoxEngine.callPSTN] method. + */ +declare interface CallPSTNParameters { + /** + * Optional. Answering machine or voicemail detector. + */ + amd?: AMD.AnsweringMachineDetector; + /** + * Optional. Whether to use the inbound caller ID for the outbound call from the scenario. The default value is false. + */ + followDiversion?: boolean; +} + +/** + * The parameters can be passed as arguments to the [Call.record] method. + */ +declare interface CallRecordParameters extends BaseRecorderParameters { + /** + * Optional. Whether the sound is stereo. The default value is **false**. The parameter does not change anything for the [Recorder module](/docs/references/voxengine/modules#recorder): it records stereo with mixed streams in both channels. For the [Call.record] method it works in another way: 1) if it is False, it records stereo with mixed streams in both channels 2) If it is True, the Audio stream from a call endpoint to voximplant cloud is recorded into right channel. Audio stream from voximplant cloud to a call endpoint is recorded into left channel. + */ + stereo?: boolean; + /** + * Optional. Transcription dictionary. Array of words that are possible values. Note that dict does not limit the transcription to the specific list. Instead, words in the specified list have a higher chance to be selected. Note that the parameter does not affect the [Recorder module](/docs/references/voxengine/modules#recorder) because the transcription is not available for it. + */ + dict?: ASRDictionary | string[]; + /** + * Optional. An array of two strings. Each string names the label in resulting transcription: the first string names a call/stream that initiated recording, the second string names the other call. If there is only one string in the array or the parameter is not specified at all, the recording's initiate call has the "Left" name and the second stream has the "Right" name. The parameter requires the **transcribe: true** parameter. The parameter is not available for the [Recorder module](/docs/references/voxengine/modules#recorder). + */ + labels?: string[]; + /** + * Optional. Transcription provider. + */ + provider?: TranscriptionProvider; + /** + * Optional. Transcription format. Could be specified as "json". In that case the transcription result is saved in JSON format. The parameter is not available for the [Recorder module](/docs/references/voxengine/modules#recorder). + */ + format?: string; +} + +/** + * The parameters can be passed as arguments to the [Call.say] method. + */ +declare interface CallSayParameters { + /** + * Optional. Language and voice for TTS. List of all supported voices: [VoiceList]. The default value is **VoiceList.Amazon.en_US_Joanna**.

*Available for providers: Amazon, Google, IBM, Microsoft, SaluteSpeech, T-Bank, Yandex.* + */ + language?: Voice; + /** + * Optional. Whether to use progressive playback. If true, the generated speech is delivered in chunks which reduces delay before a method call and playback. The default value is **false**.

*Available for providers: Amazon, Google, IBM, Microsoft, SaluteSpeech, T-Bank, Yandex.* + */ + progressivePlayback?: boolean; + /** + * Optional. Parameters for TTS. Note that support of the [TTSOptions.pitch] parameter depends on the language and dictionary used. For unsupported combinations the [CallEvents.PlaybackFinished] event is triggered with error 400.

*Available for providers: Amazon, Google, IBM, Microsoft, SaluteSpeech, T-Bank, Yandex.* + */ + ttsOptions?: TTSOptions; + /** + * Optional. Provide the TTS parameters directly to the provider in this parameter. Find more information in the documentation.

*Available for providers: Google, SaluteSpeech, T-Bank, YandexV3.* + */ + request?: Object; +} + +/** + * [Call] parameters. Can be passed as arguments to the [VoxEngine.callSIP] method. + */ +declare interface CallSIPParameters { + /** + * Optional. Identifier of Voximplant SIP registration that is used for outgoing call. + */ + regId?: number; + /** + * Optional. CallerID of the caller that is displayed to the callee. Usage of whitespaces is not allowed. Normally it is a phone number that can be used for callback. + */ + callerid?: string; + /** + * Optional. Name of the caller that is displayed to the callee. Normally it is a human-readable version of CallerID, e.g. a person's name. + */ + displayName?: string; + /** + * Optional. Password for SIP authentication. + */ + password?: string; + /** + * Optional. Username for SIP authentication. If not specified, callerid is used as the username for authentication. + */ + authUser?: string; + /** + * Optional. Custom parameters (SIP headers) that should be passed with a call (INVITE) message. + * Custom header names have to begin with the 'X-' prefix. The "X-" headers can be handled by a SIP phone or WEB SDK + * (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + */ + headers?: Object; + /** + * Optional. Whether the call has video support. Please note that the price for audio-only and video calls is different! + */ + video?: boolean; + /** + * Optional. Outgoing proxy, e.g. "69.167.178.6" + */ + outProxy?: string; + /** + * Optional. Answering machine or voicemail detector. + */ + amd?: AMD.AnsweringMachineDetector; +} + +/** + * [Call] parameters. Can be passed as arguments to the [VoxEngine.callUserDirect] method. + */ +declare interface CallUserDirectParameters { + /** + * CallerID to display to the callee. Usage of whitespaces is not allowed. Normally it is a phone number that can be used for callback. IMPORTANT: test numbers rented from Voximplant cannot be used as CallerIDs, use only real numbers. + */ + callerid: string; + /** + * Name of the caller that is displayed to the callee. Normally it is a human-readable version of CallerID, e.g. a person's name. + */ + displayName: string; + /** + * Optional. Send custom tags along with the push notification of an incoming call. + */ + analyticsLabel?: string; + /** + * Optional. Custom parameters (SIP headers) to be passed with a call (INVITE) message. Custom header names have to begin with the 'X-' prefix. The "X-" headers can be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + */ + extraHeaders?: { [header: string]: string }; + /** + * Optional. Push notification timeout in milliseconds. Note that the timeout is only triggered after the [CallEvents.Failed] event with *480 User Offline*. The default value is **20000**. Must not be less than **10000** or greater than **60000**. + */ + pushNotificationTimeout?: number; +} + +/** + * [Call] parameters. Can be passed as arguments to the [VoxEngine.callUser] method. + */ +declare interface CallUserParameters extends CallParameters { + /** + * Name of the Voximplant user to call. + */ + username: string; + /** + * CallerID to display to the callee. Usage of whitespaces is not allowed. Normally it is a phone number that can be used for callback. IMPORTANT: test numbers rented from Voximplant cannot be used as CallerID, use only real numbers. + */ + callerid: string; + /** + * Optional. Custom parameters (SIP headers) that should be passed with a call (INVITE) message. Custom header names have to begin with the 'X-' prefix except the 'VI-CallTimeout': '60' which hangs up if there is no answer after the timeout (in seconds, the default value is **60**, must not be less than **10** or greater than **400**). The "X-" headers can be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'}. + */ + extraHeaders?: { [header: string]: string }; + /** + * Optional. Whether the call has video support. Please note that prices for audio-only and video calls are different! + */ + video?: boolean; + /** + * Optional. Whether to send an RTP extension header to communicate video orientation information (`a=extmap:12 urn:3gpp:video-orientation`). If **false**, browsers that do not support that extension are correctly displaying video; however, the battery consumption is higher. The default value is **true**. + */ + videoOrientationExtension?: boolean; + /** + * Optional. Sends custom tags along with the push notification of an incoming call. + */ + analyticsLabel?: string; + /** + * Optional. Answering machine or voicemail detector. + */ + amd?: AMD.AnsweringMachineDetector; + /** + * Optional. Push notification timeout in milliseconds. Note that the timeout is only triggered after the [CallEvents.Failed] event with *480 User Offline*. The default value is **20000**. Must not be less than **10000** or greater than **60000**. + */ + pushNotificationTimeout?: number; +} + +/** + * Represents an audio or video call. + */ +declare class Call { + /** + * Returns the current state of the call. Possible values are: **TERMINATED** | **CONNECTED** | **PROGRESSING** | **ALERTING**. + */ + state(): string; + + /** + * Returns the human-readable description of the call's status. + */ + toString(): string; + + /** + * Sets or gets a custom string associated with the particular call (the Call object). The **customData** value could be sent from WEB/iOS/Android SDKs, and then it becomes the **customData** value in the [Call] object. Note that if you receive a value from an SDK, you can always replace it manually. + * SDKs can pass customData in two ways:
+ * 1) when SDK calls the Voximplant cloud
+ * 2) when SDK answers the call from the Voximplant cloud. See the syntax and details in the corresponding references: [WEB SDK call()](/docs/references/websdk/voximplant/client#call) / [WEB SDK answer()](/docs/references/websdk/voximplant/call#answer) / [iOS call:settings:](/docs/references/iossdk/client/viclient#callsettings) / [iOS answerWithSettings](/docs/references/iossdk/call/vicall#answerwithsettings:) / [Android call()](/docs/references/androidsdk/client/iclient#call) / [Android answer()](/docs/references/androidsdk/call/icall#answer) + * @param customData Optional. Custom call data to set. Maximum size is **200 bytes** + */ + customData(customData?: string): string; + + /** + * Returns the call's id. Each call in a JavaScript session has its own unique id. + */ + id(): string; + + /** + * Whether the call is incoming. + */ + incoming(): boolean; + + /** + * Returns the callerID of the caller, which is displayed to the callee. Normally it is some phone number that can be used for callback. IMPORTANT: test numbers rented from Voximplant cannot be used as CallerIDs, the values can be only real numbers. + */ + callerid(): string; + + /** + * Returns the name of the caller, which is displayed to the callee. Normally it is a human-readable version of [Call.callerid], e.g. a person's name. + */ + displayName(): string; + + /** + * Returns a dialed number of the incoming or outgoing call. + */ + number(): string; + + /** + * Returns VAD (Voice Activity Detection) status. The including of the ASR also activates VAD so in that case vad() returns true. + */ + vad(): boolean; + + /** + * Adds a handler for the specified [CallEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [CallEvents.Connected]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: CallEvents | T, + callback: (event: _CallEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [CallEvents] event. + * @param event Event class (i.e., [CallEvents.Connected]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: CallEvents | T, + callback?: (event: _CallEvents[T]) => any + ): void; + + /** + * Returns a type of the client. + */ + clientType(): string; + + /** + * Attempts finishing the current call. Triggers one of the following events: + * 1. [CallEvents.Disconnected] if the call is active before hangup. + * 2. [CallEvents.Failed] if it is an outgoing call that is not connected previously. + *
+ * If there are no other active calls and/or SmartQueue requests in the call session, the [AppEvents.Terminating] and [AppEvents.Terminated] events are triggered in 60 seconds (see the [session limits](/docs/guides/voxengine/limits) for details). + * @param extraHeaders Optional. Custom parameters (SIP headers) that should be passed with the hangup request. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + */ + hangup(extraHeaders?: { [header: string]: string }): void; + + /** + * Answers the incoming call. Use it only for non-P2P call legs connection. Remember that you can use the [Call.startEarlyMedia] method before answering a call. + * @param extraHeaders Optional. Custom parameters (SIP headers) that should be passed with the answer request. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the [Connected](/docs/references/websdk/voximplant/callevents#connected) event). Example: {'X-header':'value'} + * @param parameters Optional. Answering call parameters + */ + answer(extraHeaders?: { [header: string]: string }, parameters?: CallAnswerParameters): void; + + /** + * Answer the incoming call in the peer-to-peer mode. Use it only for P2P call legs connection. + * @param peerCall The other P2P call leg + * @param extraHeaders Optional. Custom parameters (SIP headers) that should be passed with the answer request. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the Connected (/docs/references/websdk/enums/callevents.html#connected) event). Example: {'X-header':'value'} + * @param parameters Optional. Answering call parameters + */ + answerDirect( + peerCall: Call, + extraHeaders?: { [header: string]: string }, + parameters?: CallAnswerParameters + ): void; + + /** + * Rejects the incoming call. + * @param code SIP status code + * @param extraHeaders Optional. Custom parameters (SIP headers) that should be passed with the reject request. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g., see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + * @deprecated Use [Call.reject] instead + */ + decline(code: number, extraHeaders?: { [header: string]: string }): void; + + /** + * Rejects the incoming call. First it triggers the [CallEvents.Disconnected] event immediately. The [AppEvents.Terminating] and [AppEvents.Terminated] events are triggered in 60 seconds. + * @param code SIP status code with the rejection reason. You can pass any [standard SIP code](https://en.wikipedia.org/wiki/List_of_SIP_response_codes) starting with 3xx, 4xx, 5xx and 6xx as a reject reason + * @param extraHeaders Optional. Custom parameters (SIP headers) that should be passed with the reject request. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + */ + reject(code: number, extraHeaders?: { [header: string]: string }): void; + + /** + * Plays dial tones for the incoming call. The method sends a low-level command to the endpoint device to start playing dial tones for the call. So the dial tones depend on endpoint device's behavior rather than on the Voximplant cloud. IMPORTANT: each call object can send media to any number of other calls (media units), but can receive only one audio stream. A new incoming stream always replaces the previous one. + * @param extraHeaders Optional. Custom parameters (SIP headers) that should be passed with the request. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + */ + ring(extraHeaders?: { [header: string]: string }): void; + + /** + * Informs the call endpoint that early media is sent before accepting the call. It allows playing voicemail prompt or music before establishing the connection. It does not allow to listen to call endpoint. Note that unanswered call can be in "early media" state only for 60 seconds, see the [session limits](/docs/guides/voxengine/limits) for details. + * @param extraHeaders Optional. Custom parameters (SIP headers) that should be passed with the request. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + * @param scheme Optional. Internal information about codecs from the [AppEvents.CallAlerting] event + * @param maxVideoBitrate Optional. Set the maximum possible video bitrate for the customer device in kbps + * @param audioLevelExtension Optional. Audio level extension + * @param conferenceCall Optional. Whether the call is coming from a conference. The default value is **false** + */ + startEarlyMedia( + extraHeaders?: { [header: string]: string }, + scheme?: string, + maxVideoBitrate?: number, + audioLevelExtension?: boolean, + conferenceCall?: boolean + ): void; + + /** + * Starts to play an audio file to the answered call. You can stop playback manually via the [Call.stopPlayback] method. You can attach media streams later via the [Call.sendMediaTo] method etc. IMPORTANT: each call object can send media to any number of other calls (media units), but can receive only one audio stream. A new incoming stream always replaces the previous one. + * @param url HTTP/HTTPS url to the audio file. The file is cached after the first playing. Supported formats are: mp3, ogg, flac, and wav (mp3, speex, vorbis, flac, and wav codecs respectively). Maximum file size is 10 Mb + * @param parameters Optional. Playback parameters + */ + startPlayback(url: string, parameters?: StartPlaybackParameters): void; + + /** + * Say some text to the [CallEvents.Connected] call. If text length exceeds 1500 characters the [PlayerEvents.PlaybackFinished] event is triggered with error description. + * IMPORTANT: each call object can send media to any number of other calls (media units), but can receive only one audio stream. A new incoming stream always replaces the previous one. + * @param text Message that is played to the call. To put an accent to the specified syllable, use the tag + * @param parameters Optional. TTS parameters + * @warning This method internally operates with the [Player] class and its events. Use the [VoxEngine.createTTSPlayer] to get more flexibility + */ + say(text: string, parameters?: CallSayParameters): void; + + /** + * Starts recording the incoming and outgoing audio for this call. + * This method triggers the [CallEvents.RecordStarted] event. + * The default quality is **8kHz / 32kbps**; the format is **mp3**. + * @param parameters Recorder parameters + */ + record(parameters: CallRecordParameters): void; + + /** + * Stops audio playback started before via the [Call.startPlayback] method. + */ + stopPlayback(): void; + + /** + * Provides country-specific dial tones. The method sends a command to the Voximplant cloud to start playing dial tones in the call. The dial tones fully depend on the Voximplant cloud. Note that in order to work properly in a call that is not connected yet, you need to call the [Call.startEarlyMedia] method before using this function. IMPORTANT: each call object can send media to any number of other calls (media units), but can receive only one audio stream. A new incoming stream always replaces the previous one. + * @param country 2-letter country code. Currently supported values are **US**, **RU** + */ + playProgressTone(country: string): void; + + /** + * Sends a text message to the call. + * @param text Message text. Maximum size is **8192 bytes** according to the limits + */ + sendMessage(text: string): void; + + /** + * Starts sending media (voice and video) from the call to the media unit. The target call has to be [CallEvents.Connected] earlier. IMPORTANT: each call object can send media to any number of the media units, but can receive only one audio stream. A new incoming stream always replaces the previous one. + * @param mediaUnit Media unit that receives media + * @param parameters Optional. WebSocket interaction only parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media (voice and video) from the call to media unit. + * @param mediaUnit Media unit that does not need to receive media from this call anymore + */ + stopMediaTo(mediaUnit: VoxMediaUnit): void; + + /** + * Changes DTMF processing mode (in-band DTMF, RFC 2833 DTMF and DTMF over SIP INFO) telephony signals. If true, each received DTMF signal triggers the [CallEvents.ToneReceived] and removes from audio stream. + * @param doHandle Whether to enable DTMF analysis. The default values is **true** + * @param supportedDtmfTypes The DTMF type to process. The default value is **ALL** + */ + handleTones(doHandle: boolean, supportedDtmfTypes?: DTMFType): void; + + /** + * Sends info (SIP INFO) message to the call. + * @param mimeType MIME type of the message + * @param body Message content. Maximum size is 8192 bytes according to the limits + * @param headers Optional. Headers to be passed with the message. Custom header names have to begin with the 'X-' prefix. The "X-" headers could be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: **{'X-header':'value'}** + */ + sendInfo(mimeType: string, body: string, headers?: { [header: string]: string }): void; + + /** + * Sends DTMF digits to the remote peer. + * @param digits Any combination of 0-9, *, #, p (pause) symbols + */ + sendDigits(digits: string): void; + + /** + * Whether to enable detection of microphone status in the call. If detection is enabled, the [CallEvents.MicStatusChange] event is triggered at each status' change. + * @param handle Enable/disable microphone status analysis. The default value is **false** + */ + handleMicStatus(handle: boolean): void; + + /** + * Whether to enable blind transfers. When enabled, the [CallEvents.BlindTransferRequested] event is triggered to request for the third call leg within an existing session and notify the transfer initiator of the result. + * @param handle Enable/disable blind transfers + */ + handleBlindTransfer(handle: boolean): void; + + /** + * Sends a notification of a successful call transfer with the **200 OK** message. + */ + notifyBlindTransferSuccess(): void; + + /** + * Sends a notification about a failed call transfer with an error code and reason. + * @param code Error code + * @param reason Reason why the blind transfer is failed + */ + notifyBlindTransferFailed(code: number, reason: string): void; +} + +/** + * Represents a call list to interact with Voximplant's call list processing functionality. + */ +declare namespace CallList {} + +declare namespace CallList { + /** + * Reports error to the CallList module asynchronously and continues the call list. + * + * Call this method if the call attempt is not successful. If you do not call this method or + * [reportErrorAsync](/docs/references/voxengine/calllist/reporterrorasync), the call list considers this task + * successful and does not make any more attempts to call this task. + * + * @param error Error string or JSON + */ + function reportErrorAsync(error: string | Object): Promise; +} + +declare namespace CallList { + /** + * Reports error to the CallList module and continues the call list. + * + * Call this method if the call attempt is not successful. If you do not call this method or + * [reportError](/docs/references/voxengine/calllist/reporterror), the call list considers this task + * successful and does not make any more attempts to call this task. + * + * @param error Error string or JSON + * @param callback Optional. Callback to execute when a result is processed + */ + function reportError( + error: string | Object, + callback?: (result: Net.HttpRequestResult) => void + ): void; +} + +declare namespace CallList { + /** + * Report progress to the CallList module + * @param progress Progress description string or JSON + */ + function reportProgressAsync(progress: string | Object): Promise; +} + +declare namespace CallList { + /** + * Report progress to the CallList module + * @param progress Progress description string or JSON + * @param callback Optional. Callback to execute when a result is processed + */ + function reportProgress( + progress: string | Object, + callback?: (result: Net.HttpRequestResult) => void + ): void; +} + +declare namespace CallList { + /** + * Reports successful result to the CallList module asynchronously, saves the report to result_data field in the sheet, + * stops the calling attempts for this task and proceeds to the next task. + * @param result Result description string or JSON + */ + function reportResultAsync(result: string | Object): Promise; +} + +declare namespace CallList { + /** + * Reports successful result to the CallList module, saves the report to result_data field in the sheet, + * stops the calling attempts for this task and proceeds to the next task. + * @param result Result description string or JSON + * @param callback Optional. Callback to execute when a result is processed + */ + function reportResult( + result: string | Object, + callback?: (result: Net.HttpRequestResult) => void + ): void; +} + +declare namespace CallList { + /** + * Changes parameters for the current task and request another calling attempt with updated data asynchronously. + * + * This method can change the following fields for the current task: `start_at`, `attempts_left`, `custom_data`, + * `start_execution_time`, `end_execution_time` and `next_attempt_time`. The new values work for all remaining attempts. + * This method does not change the global call list settings. + * + * Note: if you do not change the `attempts_left` manually, the call list decreases its value by 1 automatically. + * + * After an unsuccessful calling attempt, this method executes the + * [reportError](/docs/references/voxengine/calllist/reporterror) method automatically. + * + * Refer to the [Editable call lists](/docs/guides/solutions/editable-call-lists) guide to learn more. + * @param data Data to update + */ + function requestNextAttemptAsync(data: Object): Promise; +} + +declare namespace CallList { + /** + * Changes parameters for the current task and request another calling attempt with updated data. + * + * This method can change the following fields for the current task: `start_at`, `attempts_left`, `custom_data`, + * `start_execution_time`, `end_execution_time` and `next_attempt_time`. The new values work for all remaining attempts. + * This method does not change the global call list settings. + * + * Note: if you do not change the `attempts_left` manually, the call list decreases its value by 1 automatically. + * + * After an unsuccessful calling attempt, this method executes the + * [reportError](/docs/references/voxengine/calllist/reporterror) method automatically. + * + * Refer to the [Editable call lists](/docs/guides/solutions/editable-call-lists) guide to learn more. + * @param data Data to update + * @param callback Optional. Callback function to execute after the request is done + */ + function requestNextAttempt( + data: Object, + callback?: (result: Net.HttpRequestResult) => void + ): void; +} + +declare module CCAI { + /** + * Represents a CCAI Agent instance. + */ + class Agent { + constructor(agentId: string, region?: string); + + /** + * Returns the CCAI Agent id. + */ + id(): string; + + /** + * Destroys a CCAI Agent instance. + */ + destroy(): void; + + /** + * Gets the list of a Dialogflow conversation profiles. + */ + getProfilesList(): Promise; + + /** + * Gets the Dialogflow conversation profile. + * @param request Dialogflow get conversation profile [request data](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2beta1#getconversationprofilerequest) + */ + getConversationProfile(request: Object): Promise; + + /** + * Updates the Dialogflow conversation profile. + * @param request Dialogflow update conversation profile [request data](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2beta1#updateconversationprofilerequest) + */ + updateConversationProfile(request: Object): Promise; + + /** + * Adds a handler for the specified [CCAI.Events.Agent] event. Use only functions as handlers; anything except a function leads to an error and scenario termination when a handler is called. + * @param event Event class (i.e., [CCAI.Events.Agent.Started]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: CCAI.Events.Agent | T, + callback: (event: CCAI.Events._AgentEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [CCAI.Events.Agent] event. + * @param event Event class (i.e., [CCAI.Events.Agent.Started]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: CCAI.Events.Agent | T, + callback?: (event: CCAI.Events._AgentEvents[T]) => any + ): void; + } +} + +declare module CCAI {} + +declare module CCAI { + /** + * [Conversation] settings. + */ + interface ConversationSettings { + /** + * CCAI agent to use in the Dialogflow conversation. + */ + agent: CCAI.Agent; + /** + * Service to connect to the incoming Dialogflow conversation. + */ + profile: CCAI.Vendor.ConversationProfile; + /** + * Name of the Dialogflow conversation. + */ + project: string; + } +} + +declare module CCAI { + /** + * Represents a CCAI conversation instance. + */ + class Conversation { + constructor(settings: CCAI.ConversationSettings); + + /** + * Adds a participant to the conversation. + */ + addParticipant(settings: CCAI.ParticipantSettings): CCAI.Participant; + + /** + * Removes a participant from the conversation. + */ + removeParticipant(participant: CCAI.Participant): void; + + /** + * Adds a handler for the specified [CCAI.Events.Conversation] event. Use only functions as handlers; anything except a function leads to an error and scenario termination when a handler is called. + * @param event Event class (i.e., [CCAI.Events.Conversation.Created]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: CCAI.Events.Conversation | T, + callback: (event: CCAI.Events._ConversationEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [CCAI.Events.Conversation] event. + * @param event Event class (i.e., [CCAI.Events.Conversation.Created]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: CCAI.Events.Conversation | T, + callback?: (event: CCAI.Events._ConversationEvents[T]) => any + ): void; + } +} + +declare module CCAI { + module Events { + /** + * Events related to CCAI agents. + * @event + */ + enum Agent { + /** + * Triggers after the [CCAI.Agent] instance is created. + * @typedef _AgentStartedEvent + */ + Started = 'AI.Events.CcaiAgentStarted', + /** + * Triggers after the [CCAI.Agent] instance is destroyed. + * @typedef _AgentStoppedEvent + */ + Stopped = 'AI.Events.CcaiAgentStopped', + } + + /** + * @private + */ + interface _AgentEvents { + [Agent.Started]: _AgentStartedEvent; + [Agent.Stopped]: _AgentStoppedEvent; + } + + /** + * @private + */ + interface _AgentEvent { + /** + * CCAI Agent istance that generated the event + */ + ccaiAgent: CCAI.Agent; + } + + /** + * @private + */ + interface _AgentStartedEvent extends _AgentEvent {} + + /** + * @private + */ + interface _AgentStoppedEvent extends _AgentEvent {} + } +} + +declare module CCAI { + module Events { + /** + * Events related to CCAI conversations. + * @event + */ + enum Conversation { + /** + * Triggers after the [CCAI.Conversation] instance is created. + * @typedef _ConversationCreatedEvent + */ + Created = 'AI.Events.CcaiConversationCreated', + /** + * Triggered when a conversation profile is created in the specified project. + * @typedef _ConversationProfileCreatedEvent + */ + ProfileCreated = 'AI.Events.CcaiConversationProfileCreated', + /** + * Triggered when the conversation is completed. + * @typedef _ConversationCompletedEvent + */ + Completed = 'AI.Events.CcaiConversationCompleted', + /** + * Triggered when a [CCAI.Conversation] instance causes an error. + * @typedef _ConversationErrorEvent + */ + Error = 'AI.Events.CcaiConversationError', + } + + /** + * @private + */ + interface _ConversationEvents { + [Conversation.Created]: _ConversationCreatedEvent; + [Conversation.ProfileCreated]: _ConversationProfileCreatedEvent; + [Conversation.Completed]: _ConversationCompletedEvent; + [Conversation.Error]: _ConversationErrorEvent; + } + + /** + * @private + */ + interface _ConversationEvent { + /** + * CCAI Conversation istance that generated the event + */ + ccaiConversation: CCAI.Conversation; + } + + /** + * @private + */ + interface _ConversationCreatedEvent extends _ConversationEvent {} + + /** + * @private + */ + interface _ConversationProfileCreatedEvent extends _ConversationEvent {} + + /** + * @private + */ + interface _ConversationCompletedEvent extends _ConversationEvent {} + + /** + * @private + */ + interface _ConversationErrorEvent extends _ConversationEvent {} + + /** + * @private + */ + interface _ConversationErrorEvent extends _ConversationEvent {} + } +} + +declare module CCAI { + module Events {} +} + +declare module CCAI { + module Events { + /** + * Events related to CCAI participants. + * @event + */ + enum Participant { + /** + * Triggers after the [CCAI.Participant] instance is created. + * @typedef _ParticipantCreatedEvent + */ + Created = 'AI.Events.CcaiParticipantCreated', + /** + * Triggered when a [CCAI.Participant] instance returns an intent response. + * @typedef _ParticipantResponseEvent + */ + Response = 'AI.Events.CcaiParticipantResponse', + /** + * Triggered when playback of a single phrase has finished successfully or in case of a playback error. + * @typedef _ParticipantPlaybackFinishedEvent + */ + PlaybackFinished = 'AI.Events.CcaiParticipantPlaybackFinished', + /** + * Triggered when playback of a single phrase has started. + * @typedef _ParticipantPlaybackStartedEvent + */ + PlaybackStarted = 'AI.Events.CcaiParticipantPlaybackStarted', + /** + * Triggered when playback of a single phrase has stoped. + * @typedef _ParticipantPlaybackStoppedEvent + */ + PlaybackStopped = 'AI.Events.CcaiParticipantPlaybackStopped', + /** + * Triggered when **audio_segments** from Google are ready to be played. + * @typedef _ParticipantPlaybackReadyEvent + */ + PlaybackReady = 'AI.Events.CcaiParticipantPlaybackReady', + /** + * Triggered when [CCAI.Participant.addPlaybackMarker] is reached. + * @typedef _ParticipantMarkerReachedEvent + */ + MarkerReached = 'AI.Events.CcaiParticipantMarkerReached', + } + + /** + * @private + */ + interface _ParticipantEvents { + [Participant.Created]: _ParticipantCreatedEvent; + [Participant.Response]: _ParticipantResponseEvent; + [Participant.PlaybackFinished]: _ParticipantPlaybackFinishedEvent; + [Participant.MarkerReached]: _ParticipantMarkerReachedEvent; + [Participant.PlaybackReady]: _ParticipantPlaybackReadyEvent; + [Participant.PlaybackStarted]: _ParticipantPlaybackStartedEvent; + [Participant.PlaybackStopped]: _ParticipantPlaybackStoppedEvent; + } + + /** + * @private + */ + interface _ParticipantEvent { + /** + * CCAI Participant istance that generated the event + */ + ccaiParticipant: CCAI.Participant; + } + + /** + * @private + */ + interface _ParticipantCreatedEvent extends _ParticipantEvent {} + + /** + * @private + */ + interface _ParticipantResponseEvent extends _ParticipantEvent {} + + /** + * @private + */ + interface _ParticipantPlaybackFinishedEvent extends _ParticipantEvent {} + + /** + * @private + */ + interface _ParticipantPlaybackStartedEvent extends _ParticipantEvent {} + + /** + * @private + */ + interface _ParticipantPlaybackStoppedEvent extends _ParticipantEvent {} + + /** + * @private + */ + interface _ParticipantPlaybackReadyEvent extends _ParticipantEvent {} + + /** + * @private + */ + interface _ParticipantMarkerReachedEvent extends _ParticipantEvent {} + } +} + +declare module CCAI { + /** + * [CCAI.Agent.getConversationProfile] method result. + */ + interface GetConversationProfileResult { + /** + * Event ID. + */ + id: string; + /** + * Event name — 'AI.Events.CcaiGetConversationProfileResponse'. + */ + name: string; + /** + * Dialogflow [response data](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2beta1#conversationprofile). + */ + response: Object; + } +} + +declare module CCAI { + /** + * [CCAI.Agent.getProfilesList] method result. + */ + interface GetProfilesListResult { + /** + * Event ID. + */ + id: string; + /** + * Event name — 'AI.Events.CcaiListConversationProfilesResponse'. + */ + name: string; + /** + * Dialogflow [response data](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2beta1#listconversationsresponse). + */ + response: Object; + } +} + +declare module CCAI { + /** + * [Participant] settings. + */ + interface ParticipantSettings { + /** + * Current call object. + */ + call: Call; + /** + * Settings for a new CCAI Dialogflow participant instance setup. + */ + dialogflowSettings: any; + /** + * Options of a single side of the conversation. + */ + options: CCAI.Vendor.Participant; + } +} + +declare module CCAI { + /** + * Represents a CCAI participant instance. + */ + class Participant { + /** + * Returns the participant's id. + */ + id(): string; + + /** + * Returns the call associated with the participant. + */ + call(): Call; + + /** + * Adds a message from a participant into the Dialogflow CCAI. + * @param query Message + */ + analyzeContent(query: CCAI.Vendor.EventInput | CCAI.Vendor.TextInput): void; + + /** + * Adds a Dialogflow speech synthesis playback marker. The [CCAI.Events.Participant.MarkerReached] event is triggered when the marker is reached. + * @param offset Marker + * @param playbackId Playback id + */ + addPlaybackMarker(offset: number, playbackId?: string): void; + + /** + * Adds a handler for the specified [CCAI.Events.Participant] event. Use only functions as handlers; anything except a function leads to an error and scenario termination when a handler is called. + * @param event Event class (i.e., [CCAI.Events.Participant.Created]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: CCAI.Events.Participant | T, + callback: (event: CCAI.Events._ParticipantEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [CCAI.Events.Participant] event. + * @param event Event class (i.e., [CCAI.Events.Participant.Created]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: CCAI.Events.Participant | T, + callback?: (event: CCAI.Events._ParticipantEvents[T]) => any + ): void; + + /** + * Starts sending media (voice) from the Dialogflow participant to the media unit. + * @param mediaUnit Media unit that receives media + * @param parameters Optional. WebSocket interaction only parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending voice from the Dialogflow participant to the media unit. + * @param mediaUnit Media unit that stops receiving media + */ + stopMediaTo(mediaUnit: VoxMediaUnit): void; + } +} + +declare module CCAI { + /** + * [CCAI.Agent.updateConversationProfile] method result. + */ + interface UpdateConversationProfileResult { + /** + * Event ID. + */ + id: string; + /** + * Event name — 'AI.Events.CcaiUpdateConversationProfileResponse'. + */ + name: string; + /** + * Dialogflow [response data](https://cloud.google.com/dialogflow/es/docs/reference/rpc/google.cloud.dialogflow.v2beta1#conversationprofile). + */ + response: Object; + } +} + +declare module CCAI { + module Vendor { + /** + * Defines the services to connect to the incoming Dialogflow conversations. + */ + interface ConversationProfile { + /** + * The unique identifier of this conversation profile. Format: projects//conversationProfiles/. + */ + name: string; + /** + * Optional. A human-readable name for this profile. Max length is **1024 bytes**. + */ + display_name?: string; + } + } +} + +declare module CCAI { + module Vendor { + /** + * Events allow matching intents by event name instead of the natural language input. For instance, the input can trigger a personalized welcome response. The parameter `name` may be used by the agent in the response: `"Hello #welcome_event.name! What can I do for you today?"`. + */ + interface EventInput { + /** + * The unique identifier of the event. + */ + name: string; + /** + * The collection of parameters associated with the event. + * Depending on your protocol or client library language, this is a map, associative array, symbol table, dictionary, or JSON object composed of a collection of (MapKey, MapValue) pairs: + * * MapKey type: string + * * MapKey value: parameter name + * * MapValue type: + * * If parameter's entity type is a composite entity: map + * * Else: string or number, depending on the parameter value type + * * MapValue value: + * * If parameter's entity type is a composite entity: map from composite entity property names to property values + * * Else: parameter value + */ + parameters?: { [key: string]: any }; + /** + * The language of this query. See [Language Support](https://cloud.google.com/dialogflow/docs/reference/language) for a list of the currently supported language codes. Note that queries in the same session do not necessarily need to have the same language. + */ + language_code: string; + } + } +} + +declare module CCAI { + module Vendor { + /** + * Represents a single side of the conversation. + */ + interface Participant { + /** + * Optional. The unique identifier of this participant. Format: `projects//conversations//participants/`. + */ + name?: string; + /** + * Immutable. The role this participant plays in the conversation. This field can only be set during creation. + */ + role: Role; + /** + * Optional. Label applied to streams representing this participant in SIPREC XML metadata and SDP. Use it to assign transcriptions from that media stream to this participant. This field can be updated. + */ + sip_recording_media_label?: string; + /** + * Optional. Obfuscated user id that should be associated with the created participant. + * You can specify a user id as follows: + * * If you set this field in `CreateParticipantRequest` or `UpdateParticipantRequest`, Dialogflow adds the obfuscated user id with the participant. + * * If you set this field in `AnalyzeContent` or `StreamingAnalyzeContent`, Dialogflow updates `Participant.obfuscated_external_user_id`. + * Dialogflow returns an error if you try to add a user id for a non-`END_USER` participant. + * Dialogflow uses this user id for billing and measurement purposes. For example, Dialogflow determines whether a user in one conversation returned in a later conversation. + * Note: + * * Never pass raw user ids to Dialogflow. Always obfuscate your user id first. + * * Dialogflow only accepts a UTF-8 encoded string, e.g., a hex digest of a hash function like SHA-512. + * * The length of the user id must be <= 256 characters. + */ + obfuscated_external_user_id?: string; + /** + * Optional. Key-value filters on the metadata of documents returned by article suggestion. If specified, article suggestion only returns suggested documents that match all filters in their **Document.metadata**. Multiple values for a metadata key should be concatenated by a comma. For example, filters to match all documents that have 'US' or 'CA' in their market metadata values and 'agent' in their user metadata values are documents_metadata_filters { key: "market" value: "US,CA" } documents_metadata_filters { key: "user" value: "agent" }. + */ + documents_metadata_filters?: { [key: string]: string }; + } + } +} + +declare module CCAI { + module Vendor { + /** + * Enumeration of the roles a participant can play in a conversation. + */ + enum Role { + /** + * Participant role not set. + */ + ROLE_UNSPECIFIED, + /** + * Participant is an automated agent, such as a Dialogflow agent. + */ + AUTOMATED_AGENT, + /** + * Participant is a customer that has called or chatted with Dialogflow services. + */ + END_USER, + } + } +} + +declare module CCAI { + module Vendor { + /** + * Represents a natural language text to be processed. + */ + interface TextInput { + /** + * The UTF-8 encoded natural language text to be processed. Text length must not exceed **256 characters**. + */ + text: string; + /** + * The language of this conversational query. See [Language Support](https://cloud.google.com/dialogflow/docs/reference/language) for a list of the currently supported language codes. Note that queries in the same session do not necessarily need to have the same language. + */ + language_codes: string; + /** + * Whether to split the text into single sentence text entries at serving time. This provides a way for a user to input email content as text input. + */ + enable_splitting_text: boolean; + } + } +} + +declare module CCAI { + module Vendor {} +} + +/** + * Custom parameters for [WebSocket] media buffer clearing. Can be passed as arguments to the [WebSocket.clearMediaBuffer] method. + */ +declare interface ClearMediaBufferParameters { + tag?: string; +} + +/** + * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.Conference); + * ``` + */ +declare enum ConferenceDirection { + /** + * Provides only outgoing stream from endpoint to conference. + */ + SEND, + /** + * Provides only incoming stream from conference to endpoint. + */ + RECEIVE, + /** + * Provides only outgoing stream from endpoint to conference. + */ + BOTH, +} + +/** + * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.Conference); + * ``` + * @event + */ +declare enum ConferenceEvents { + /** + * Triggers in case of errors in the conference. + * @typedef _ConferenceErrorEvent + */ + ConferenceError = 'Conference.Error', + /** + * Triggered when the conference has started. I.e., the call of [VoxEngine.createConference] triggers the event. + * @typedef _ConferenceEvent + */ + Started = 'Conference.Started', + /** + * Triggered when the conference is stopped. I.e., the call of [Conference.stop] triggers the event. + * @typedef _ConferenceStoppedEvent + */ + Stopped = 'Conference.Stopped', + /** + * Triggered when the endpoint is added. + * @typedef _ConferenceEndpointEvent + */ + EndpointAdded = 'Conference.EndpointAdded', + /** + * Triggered when the endpoint is updated. + * @typedef _ConferenceEndpointEvent + */ + EndpointUpdated = 'Conference.EndpointUpdated', + /** + * Triggered when the endpoint is removed. + * @typedef _ConferenceEndpointEvent + */ + EndpointRemoved = 'Conference.EndpointRemoved', +} + +/** + * @private + */ +declare interface _ConferenceEvents { + [ConferenceEvents.ConferenceError]: _ConferenceErrorEvent; + [ConferenceEvents.Started]: _ConferenceEvent; + [ConferenceEvents.Stopped]: _ConferenceStoppedEvent; + [ConferenceEvents.EndpointAdded]: _ConferenceEndpointAddedEvent; + [ConferenceEvents.EndpointUpdated]: _ConferenceEndpointUpdatedEvent; + [ConferenceEvents.EndpointRemoved]: _ConferenceEndpointRemovedEvent; +} + +/** + * @private + */ +declare interface _ConferenceEvent { + /** + * Conference that triggered the event. + */ + conference: Conference; +} + +/** + * @private + */ +declare interface _ConferenceEndpointEvent extends _ConferenceEvent { + /** + * **MIX** mode combines all streams in one, **FORWARD** mode sends only one stream. + */ + mode: 'MIX' | 'FORWARD'; + /** + * **SEND** provides only outgoing stream from endpoint to conference; **RECEIVE** provides only incoming stream from conference to endpoint; **BOTH** allows both incoming and outgoing streams. + */ + direction: 'SEND' | 'RECEIVE' | 'BOTH'; + /** + * The unique ID of the endpoint. + */ + endpointId: string; + /** + * The endpoint object. + */ + endpoint: Endpoint; +} + +/** + * @private + */ +declare interface _ConferenceEndpointAddedEvent extends _ConferenceEndpointEvent {} + +/** + * @private + */ +declare interface _ConferenceEndpointUpdatedEvent extends _ConferenceEndpointEvent {} + +/** + * @private + */ +declare interface _ConferenceEndpointRemovedEvent extends _ConferenceEndpointEvent {} + +/** + * @private + */ +declare interface _ConferenceStoppedEvent extends _ConferenceEvent {} + +/** + * @private + */ +declare interface _ConferenceErrorEvent extends _ConferenceEvent { + /** + * Error description. + */ + error: string; + /** + * Error code. + */ + code: number; + /** + * Optional. The id of the endpoint that caused the error. + */ + endpointId?: string; +} + +/** + * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.Conference); + * ``` + */ +declare enum ConferenceMode { + /** + * Combine all streams simultaneously. + */ + MIX, + /** + * Send only one stream. + */ + FORWARD, +} + +/** + * [Conference] parameters. Can be passed as arguments to the [VoxEngine.createConference] method. + */ +declare interface ConferenceParameters { + /** + * Whether the audio is high definition. If set to **false** (default), audio stream has the frequency of 8kHz/32kbps. Otherwise, audio stream has the frequency of 48kHz/192kbps. Please note that default audio mode costs nothing while the high definition audio is billed additionally - for more details see the pricing page. + */ + hd_audio: boolean; +} + +/** + * Represents a conference recorder. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare class ConferenceRecorder extends Recorder { + /** + * Conference object to record. + */ + setConference(conference: Conference): void; + + /** + * Sets an endpoint's priority. + */ + setPriority(priority: Endpoint[]): Promise; + + /** + * Gets an endpoint's priority. + */ + getPriority(): Endpoint[]; + + /** + * Updates the current video recorder parameters. + */ + update(parameters: UpdateRecorderVideoParameters): void; +} + +/** + * Represents audio or video conference. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.Conference); + * ``` + */ +declare class Conference { + /** + * Adds a handler for the specified [ConferenceEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [ConferenceEvents.Started]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: ConferenceEvents | T, + callback: (event: _ConferenceEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [ConferenceEvents] event. + * @param event Event class (i.e., [ConferenceEvents.Started]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: ConferenceEvents | T, + callback?: (event: _ConferenceEvents[T]) => any + ): void; + + /** + * Returns the conference's id. + */ + id(): string; + + /** + * Stops the conference. Triggers the [ConferenceEvents.Stopped] event. + */ + stop(): void; + + /** + * Gets the endpoint list for current conference. + */ + getList(): Endpoint[]; + + /** + * Gets the endpoint by the id. + * @param id Endpoint's id + */ + get(id: string): Endpoint; + + /** + * Creates a new [Endpoint] instance and adds it to the specified conference. ***IMPORTANT!*** You can only use this function for a conference with the “video conference” option checked in the routing rule. + * Otherwise, you receive the [ConferenceEvents.ConferenceError] event with code **102**. The maximum number of endpoints is **100**. + * @param parameters Endpoint parameters + */ + add(parameters: EndpointParameters): Endpoint; + + /** + * Starts sending media (voice and video) from the conference to the media unit. + * @param mediaUnit Media unit that receives media + * @param parameters Optional. WebSocket interaction only parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media (voice and video) from the conference to the media unit. + * @param mediaUnit Media unit that does not need to receive media from this conference anymore + */ + stopMediaTo(mediaUnit: VoxMediaUnit): void; +} + +declare module Crypto {} + +declare module Crypto { + /** + * Calculates HMAC-SHA256 hash of the specified data. + * @param key Key for calculation purposes + * @param data String to calculate hash of + */ + function hmac_sha256(key: string, data: string): string; +} + +declare module Crypto { + /** + * Calculates MD5 hash. Can be used with HTTP requests that require hash. + * @param data String to calculate hash of + */ + function md5(data: string | string[]): string; +} + +declare module Crypto { + /** + * Calculates SHA1 hash. Can be used with HTTP requests that require hash. + * @param data String to calculate hash of + */ + function sha1(data: string): string; +} + +declare module Crypto { + /** + * Calculates SHA256 hash of the specified data. + * @param data String to calculate hash of + */ + function sha256(data: string): string; +} + +/** + * See https://cloud.google.com/dialogflow/es/docs/reference/language#table + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.AI); + * ``` + */ +declare enum DialogflowLanguage { + /** + * Afrikaans. + */ + AF = 'af', + /** + * Albanian. + */ + SQ = 'sq', + /** + * Amharic. + */ + AM = 'am', + /** + * Armenian. + */ + HY = 'hy', + /** + * Azerbaijani. + */ + AZ = 'az', + /** + * Basque. + */ + EU = 'eu', + /** + * Belarusian. + */ + BE = 'be', + /** + * Bangla. + */ + BN = 'bn', + /** + * Bangla (Bangladesh). + */ + BN_BD = 'bn-bd', + /** + * Bangla (India). + */ + BN_IN = 'bn-in', + /** + * Bosnian. + */ + BS = 'bs', + /** + * Bulgarian. + */ + BG = 'bg', + /** + * Catalan. + */ + CA = 'ca', + /** + * Cebuano. + */ + CEB = 'ceb', + /** + * Nyanja. + */ + NY = 'ny', + /** + * Chinese (Hong Kong SAR China). + */ + ZH_HK = 'zh-hk', + /** + * Chinese (China). + */ + ZH_CN = 'zh-cn', + /** + * Chinese (Taiwan). + */ + ZH_TW = 'zh-tw', + /** + * Corsican. + */ + CO = 'co', + /** + * Croatian. + */ + HR = 'hr', + /** + * Czech. + */ + CS = 'cs', + /** + * Danish. + */ + DA = 'da', + /** + * Dutch. + */ + NL = 'nl', + /** + * English. + */ + EN = 'en', + /** + * Australian English. + */ + EN_AU = 'en-au', + /** + * Canadian English. + */ + EN_CA = 'en-ca', + /** + * British English. + */ + EN_GB = 'en-gb', + /** + * English (India). + */ + EN_IN = 'en-in', + /** + * American English. + */ + EN_US = 'en-us', + /** + * Esperanto. + */ + EO = 'eo', + /** + * Estonian. + */ + ET = 'et', + /** + * Filipino. + */ + FIL = 'fil', + /** + * Filipino (Philippines). + */ + FIL_PH = 'fil-ph', + /** + * Finnish. + */ + FI = 'fi', + /** + * French. + */ + FR = 'fr', + /** + * Canadian French. + */ + FR_CA = 'fr-ca', + /** + * French (France). + */ + FR_FR = 'fr-fr', + /** + * Western Frisian. + */ + FY = 'fy', + /** + * Galician. + */ + GL = 'gl', + /** + * Georgian. + */ + KA = 'ka', + /** + * German. + */ + DE = 'de', + /** + * Greek. + */ + EL = 'el', + /** + * Gujarati. + */ + GU = 'gu', + /** + * Haitian Creole. + */ + HT = 'ht', + /** + * Hausa. + */ + HA = 'ha', + /** + * Hindi. + */ + HI = 'hi', + /** + * Hmong. + */ + HMN = 'hmn', + /** + * Hungarian. + */ + HU = 'hu', + /** + * Icelandic. + */ + IS = 'is', + /** + * Igbo. + */ + IG = 'ig', + /** + * Indonesian. + */ + ID = 'id', + /** + * Irish. + */ + GA = 'ga', + /** + * Italian. + */ + IT = 'it', + /** + * Japanese. + */ + JA = 'ja', + /** + * Javanese. + */ + JV = 'jv', + /** + * Kannada. + */ + KN = 'kn', + /** + * Kazakh. + */ + KK = 'kk', + /** + * Khmer. + */ + KM = 'km', + /** + * Kinyarwanda. + */ + RW = 'rw', + /** + * Korean. + */ + KO = 'ko', + /** + * Kurdish. + */ + KU = 'ku', + /** + * Kyrgyz. + */ + KY = 'ky', + /** + * Latin. + */ + LA = 'la', + /** + * Latvian. + */ + LV = 'lv', + /** + * Lithuanian. + */ + LT = 'lt', + /** + * Luxembourgish. + */ + LB = 'lb', + /** + * Macedonian. + */ + MK = 'mk', + /** + * Malagasy. + */ + MG = 'mg', + /** + * Malay. + */ + MS = 'ms', + /** + * Malay (Malaysia). + */ + MS_MY = 'ms-my', + /** + * Malayalam. + */ + ML = 'ml', + /** + * Maltese. + */ + MT = 'mt', + /** + * Māori. + */ + MI = 'mi', + /** + * Marathi. + */ + MR = 'mr', + /** + * Marathi (India). + */ + MR_IN = 'mr-in', + /** + * Mongolian. + */ + MN = 'mn', + /** + * Nepali. + */ + NE = 'ne', + /** + * Norwegian. + */ + NO = 'no', + /** + * Odia. + */ + OR = 'or', + /** + * Polish. + */ + PL = 'pl', + /** + * Brazilian Portuguese. + */ + PT_BR = 'pt-br', + /** + * Portuguese. + */ + PT = 'pt', + /** + * Punjabi. + */ + PA = 'pa', + /** + * Romanian. + */ + RO = 'ro', + /** + * Romanian (Romania). + */ + RO_RO = 'ro-ro', + /** + * Russian. + */ + RU = 'ru', + /** + * Samoan. + */ + SM = 'sm', + /** + * Scottish Gaelic. + */ + GD = 'gd', + /** + * Serbian. + */ + SR = 'sr', + /** + * Southern Sotho. + */ + ST = 'st', + /** + * Shona. + */ + SN = 'sn', + /** + * Sinhala. + */ + SI = 'si', + /** + * Sinhala (Sri Lanka). + */ + SI_LK = 'si-lk', + /** + * Slovak. + */ + SK = 'sk', + /** + * Slovenian. + */ + SL = 'sl', + /** + * Somali. + */ + SO = 'so', + /** + * Spanish. + */ + ES = 'es', + /** + * Latin American Spanish. + */ + ES_419 = 'es-419', + /** + * European Spanish. + */ + ES_ES = 'es-es', + /** + * Sundanese. + */ + SU = 'su', + /** + * Swahili. + */ + SW = 'sw', + /** + * Swedish. + */ + SV = 'sv', + /** + * Tajik. + */ + TG = 'tg', + /** + * Tamil. + */ + TA = 'ta', + /** + * Tamil (India). + */ + TA_IN = 'ta-in', + /** + * Tamil (Sri Lanka). + */ + TA_LK = 'ta-lk', + /** + * Tamil (Malaysia). + */ + TA_MY = 'ta-my', + /** + * Tamil (Singapore). + */ + TA_SG = 'ta-sg', + /** + * Tatar. + */ + TT = 'tt', + /** + * Telugu. + */ + TE = 'te', + /** + * Telugu (India). + */ + TE_IN = 'te-in', + /** + * Thai. + */ + TH = 'th', + /** + * Turkish. + */ + TR = 'tr', + /** + * Turkmen. + */ + TK = 'tk', + /** + * Ukrainian. + */ + UK = 'uk', + /** + * Uzbek. + */ + UZ = 'uz', + /** + * Vietnamese. + */ + VI = 'vi', + /** + * Vietnamese (Vietnam). + */ + VI_VN = 'vi-vn', + /** + * Welsh. + */ + CY = 'cy', + /** + * Xhosa. + */ + XH = 'xh', + /** + * Yoruba. + */ + YO = 'yo', + /** + * Zulu. + */ + ZU = 'zu', +} + +/** + * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.AI); + * ``` + */ +declare enum DialogflowModelVariant { + /** + * No model variant specified. In this case Dialogflow defaults to USE_BEST_AVAILABLE. + * @const + */ + SPEECH_MODEL_VARIANT_UNSPECIFIED = 'SPEECH_MODEL_VARIANT_UNSPECIFIED', + /** + * Use the best available variant of the Speech model that the caller is eligible for. + * Please see the Dialogflow docs for how to make your project eligible for enhanced models. + * @const + */ + USE_BEST_AVAILABLE = 'USE_BEST_AVAILABLE', + /** + * Use standard model variant even if an enhanced model is available. See the Cloud Speech documentation for details about enhanced models. + * @const + */ + USE_STANDARD = 'USE_STANDARD', + /** + * Use an enhanced model variant: + * - If an enhanced variant does not exist for the given model and request language, Dialogflow falls back to the standard variant. + * The Cloud Speech documentation describes which models have enhanced variants. + * - If the API caller is not eligible for enhanced models, Dialogflow returns an error. Please see the Dialogflow docs for how to make your project eligible. + * @const + */ + USE_ENHANCED = 'USE_ENHANCED', +} + +/** + * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.AI); + * ``` + */ +declare enum DialogflowModel { + /** + * Use this model for transcribing audio in video clips or ones that includes multiple speakers. For best results, provide audio recorded at 16,000Hz or greater sampling rate. + * Note: This is a premium model that costs more than the standard rate. + */ + VIDEO = 'video', + /** + * Use this model for transcribing audio from a phone call. Typically, phone audio is recorded at 8,000Hz sampling rate. + * Note: The enhanced phone model is a premium model that costs more than the standard rate. + */ + PHONE_CALL = 'phone_call', + /** + * Use this model for transcribing shorter audio clips. Some examples include voice commands or voice search. + */ + COMMAND_AND_SEARCH = 'command_and_search', + /** + * Use this model if your audio does not fit one of the previously described models. For example, you can use this for long-form audio recordings that feature a single speaker only. Ideally, the audio is high-fidelity, recorded at 16,000Hz or greater sampling rate. + */ + DEFAULT = 'default', +} + +/** + * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.AI); + * ``` + */ +declare enum DialogflowSsmlVoiceGender { + /** + * An unspecified gender, which means that the customer does not care which gender the selected voice has. + */ + UNSPECIFIED = 'SSML_VOICE_GENDER_UNSPECIFIED', + /** + * A male voice. + */ + MALE = 'SSML_VOICE_GENDER_MALE', + /** + * A female voice. + */ + FEMALE = 'SSML_VOICE_GENDER_FEMALE', + /** + * A gender-neutral voice. + */ + NEUTRAL = 'SSML_VOICE_GENDER_NEUTRAL', +} + +/** + * The DTMF type. + */ +declare enum DTMFType { + /** + * All types of DTMF tones trigger the [CallEvents.ToneReceived] event: in-band , RFC 2833 and SIP INFO. Receiving an RFC 2833 tone disables processing of in-band tones to avoid duplicating + */ + ALL = 0, + /** + * Only RFC 2833 DTMF tones trigger the [CallEvents.ToneReceived] event + */ + TELEPHONE_EVENT = 1, + /** + * Only in-band DTMF tones trigger the [CallEvents.ToneReceived] event + */ + IN_BAND = 2, + /** + * Only SIP INFO DTMF tones trigger the [CallEvents.ToneReceived] event + */ + SIP_INFO = 3, +} + +/** + * [Endpoint] parameters. Can be passed as arguments to the [Conference.add] method. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Conference); + * ``` + */ +declare interface EndpointParameters { + /** + * [Call] to be connected to the conference. + */ + call: Call; + /** + * **MIX** mode combines all streams into one, **FORWARD** mode sends only one stream. + */ + mode: 'MIX' | 'FORWARD'; + /** + * **SEND** provides only outgoing stream from endpoint to conference, **RECEIVE** provides only incoming stream from conference to endpoint, **BOTH** allows both incoming and outgoing streams. + */ + direction: 'SEND' | 'RECEIVE' | 'BOTH'; + /** + * Internal information about codecs. + */ + scheme: any; + /** + * Human-readable endpoint's name. + */ + displayName: string; + /** + * Optional. Endpoints and their streams (audio and/or video) to receive. These settings apply to the target endpoint right after adding it to a conference. + * @beta + */ + receiveParameters?: ReceiveParameters; + /** + * Maximum endpoint's video bitrate in kbps. + */ + maxVideoBitrate: number; +} + +/** + * Represents any remote media unit in a session. An endpoint can be represented as [ASR], [Recorder], [Player] or another [Call]. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.Conference); + * ``` + */ +declare class Endpoint { + /** + * Returns the endpoint's id. + */ + id(): string; + + /** + * Returns the endpoint's direction. **SEND** provides only outgoing stream from endpoint to conference, **RECEIVE** provides only incoming stream from conference to endpoint, **BOTH** allows both incoming and outgoing streams. + */ + getDirection(): 'SEND' | 'RECEIVE' | 'BOTH'; + + /** + * Returns the endpoint's mode. **MIX** mode combines all streams in one, **FORWARD** mode sends only one stream. + */ + getMode(): 'MIX' | 'FORWARD'; + + /** + * Sets the display name for the specified endpoint. When the display name is set, all SDK clients receive 'EndpointEvents.InfoUpdated' event. + * @param displayName + */ + setDisplayName(displayName: string): void; + + /** + * Enables/disables receiving media streams from other conference participants. + * @param parameters Media stream receive parameters + * @beta + */ + manageEndpoint(parameters: ReceiveParameters): Promise; + + /** + * Returns the endpoint's [Call] object if the endpoint is not a player or recorder instance. + */ + getCall(): Call; +} + +/** + * Global IVR control module. + *
+ * Add the following line to your scenario code to use the namespace: + * ``` + * require(Modules.IVR); + * ``` + */ +declare module IVR {} + +declare module IVR { + /** + * Resets the IVR; i.e., the method clears the list of existed [IVRState] objects. Use it to stop the entire IVR logic (e.g. near the call's ending). + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.IVR); + * ``` + */ + function reset(): void; +} + +/** + * IVR menu prompt settings. Can be passed via the [IVRSettings.prompt] parameter. Note that it is possible to specify playing parameter or a pair of the say and lang parameters. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.IVR); + * ``` + */ +declare interface IVRPrompt { + /** + * Voice message to say. Use it together with the lang parameter. SSML is supported; to use it, specify [TTSOptions] before creating an IVRState instance:
IVR.ttsOptions = { "pitch": "low", "rate": "slow", "volume": "loud" } + */ + say: string; + /** + * TTS language for pronouncing a value of the say parameter. List of all supported voices: [VoiceList]. + */ + lang: string; + /** + * Voice message url to play. Supported formats are mp3 and ogg. + */ + play: string; +} + +/** + * IVR menu state settings. Can be passed via the [IVRState.settings] parameter. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.IVR); + * ``` + */ +declare interface IVRSettings { + /** + * Prompt settings object. + */ + prompt: IVRPrompt; + /** + * Menu type. Possible values: **select**, **inputfixed**, **inputunknown**, **noinput**. + */ + type: string; + /** + * For **inputunknown** states - whether input is complete (input is passed as string). + */ + inputValidator: (input: string) => boolean; + /** + * For **inputfixed** - length of desired input. + */ + inputLength: number; + /** + * Timeout in milliseconds for user input. The default value is **5000**. + */ + timeout: number; + /** + * For **select** type, map of IVR states to go to according to user input. If there is no next state for specific input, **onInputComplete** is invoked. + */ + nextStates: { [name: string]: IVRState }; + /** + * When this digit is entered in **inputunknown** mode, input is considered to be complete. + */ + terminateOn: string; + /** + * Next state to go - for **noinput** state type. + */ + nextState: IVRState | null; +} + +/** + * Represents an IVR menu state. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.IVR); + * ``` + */ +declare class IVRState { + /** + * This property is set when IVR leaves the specific state and holds user input + */ + input: string; + /** + * IVR state settings object + */ + settings: IVRSettings; + + /** + * @param name State name + * @param settings IVR menu state settings + * @param onInputComplete Function to call after a user correctly provides input. User input should be passed as an argument + * @param onInputTimeout Function to call in case of input timeout. User input should be passed as an argument + */ + constructor( + name: string, + settings: IVRSettings, + onInputComplete: (input: string) => void, + onInputTimeout: (input: string) => void + ); + + /** + * Starts the IVR from the current state for the specified call + * @param call Call that IVR works with + */ + enter(call: Call): void; +} + +declare namespace Logger { + /** + * Whether to disable DTMF logging. + * @param flag The default value is **false** + */ + function hideTones(flag: boolean): void; +} + +declare namespace MeasurementProtocol { + /** + * Forces the current session to end with this hit. All other values are ignored. + * @returns {MeasurementProtocol} + */ + function endSession(): typeof MeasurementProtocol; +} + +/** + * Implementation of the Measurement Protocol v1. + * [https://developers.google.com/analytics/devguides/collection/protocol/v1](https://developers.google.com/analytics/devguides/collection/protocol/v1) + */ +declare namespace MeasurementProtocol {} + +declare namespace MeasurementProtocol { + interface SendEventOptions { + nonInteractionHit?: string; + category: string; + action: string; + label?: string; + value?: string; + } +} + +declare namespace MeasurementProtocol { + /** + * Send an event to the Universal Analytics + * @param {SendEventOptions} options + * @returns {MeasurementProtocol} + */ + function sendEvent(options: SendEventOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface SendExceptionOptions { + nonInteractionHit?: string; + description?: string; + isFatal?: boolean; + } +} + +declare namespace MeasurementProtocol { + /** + * Sends a record about exception + * @param {SendExceptionOptions} options + * @returns {MeasurementProtocol} + */ + function sendException(options: SendExceptionOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface SendItemOptions { + nonInteractionHit?: string; + transactionId: number; + name: string; + price: number; + quantity: number; + code?: string; + category?: string; + } +} + +declare namespace MeasurementProtocol { + /** + * Sends an item to E-commerce + * @param {SendItemOptions} options + * @returns {MeasurementProtocol} + */ + function sendItem(options: SendItemOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface SendSocialOptions { + nonInteractionHit?: string; + network?: string; + action?: string; + trigger?: string; + } +} + +declare namespace MeasurementProtocol { + /** + * Sends a social interaction + * @param {SendSocialOptions} options + * @returns {MeasurementProtocol} + */ + function sendSocial(options: SendSocialOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface SendTimingOptions { + nonInteractionHit?: string; + category?: string; + name?: string; + time?: number; + label?: string; + } +} + +declare namespace MeasurementProtocol { + /** + * Measures the user's timings + * @param {SendTimingOptions} options + * @returns {MeasurementProtocol} + */ + function sendTiming(options: SendTimingOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface SendTransactionOptions { + nonInteractionHit?: string; + id: number; + affiliation?: string; + revenue: number; + shipping?: number; + tax?: number; + } +} + +declare namespace MeasurementProtocol { + /** + * Sends a transaction to E-commerce + * @param {SendTransactionOptions} options + * @returns {MeasurementProtocol} + */ + function sendTransaction(options: SendTransactionOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface SetApplicationInfoOptions { + name: string; + id?: string; + version?: string; + installerID?: string; + } +} + +declare namespace MeasurementProtocol { + /** + * Sets application name and version + * @param {SetApplicationInfoOptions} options + * @returns {MeasurementProtocol} + */ + function setApplicationInfo(options: SetApplicationInfoOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface SetSessionByCallerIdOptions { + callerId?: string; + userID?: string; + anonymizeIP?: string; + IPOverride?: string; + } +} + +declare namespace MeasurementProtocol { + function setSessionByCallerId( + options: SetSessionByCallerIdOptions + ): Promise; +} + +declare namespace MeasurementProtocol { + interface SetTrafficSourceOptions { + documentReferrer?: string; + campaignName?: string; + campaignSource?: string; + campaignMedium?: string; + campaignKeyword?: string; + campaignContent?: string; + campaignID?: string; + googleAdsID?: string; + googleDisplayAdsID?: string; + } +} + +declare namespace MeasurementProtocol { + /** + * Sets traffic source values. + * @param {SetTrafficSourceOptions} options + * @returns {MeasurementProtocol} + */ + function setTrafficSource(options: SetTrafficSourceOptions): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + /** + * Setups the most required tracking parameters + */ + function setup( + trackingId: string, + debug: boolean, + dataSource: string + ): typeof MeasurementProtocol; +} + +declare namespace MeasurementProtocol { + interface StartSessionOptions { + clientID: string; + userID?: string; + anonymizeIP?: boolean; + IPOverride?: string; + geographicalOverride?: string; + } +} + +declare namespace MeasurementProtocol { + /** + * Forces a new session to start with this hit. All other values are ignored. + * @param {StartSessionOptions} options + * @returns {MeasurementProtocol} + */ + function startSession(options: StartSessionOptions): typeof MeasurementProtocol; +} + +declare enum Modules { + /** + * Provides the [ACD v1](/docs/guides/smartqueue/acdv1) functionality. + *
+ * We recommend using [SmartQueue] instead of ACD v1. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.ACD); + * ``` + */ + ACD = 'acd', + /** + * Provides additional methods that use Artificial Intelligence. These methods allow solving business tasks in more productive way. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.AI); + * ``` + */ + AI = 'ai', + /** + * Provides the [key-value storage](/docs/guides/voxengine/kvs) functionality. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.ApplicationStorage); + * ``` + */ + ApplicationStorage = 'applicationstorage', + /** + * Provides the [speech recognition](/docs/guides/speech/stt) functionality. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.ASR); + * ``` + */ + ASR = 'asr', + /** + * Provides the [Voximplant Avatar](/docs/guides/ai/avatar) (virtual assistant based on AI and NLP) functionality. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.Avatar); + * ``` + */ + Avatar = 'avatar', + /** + * Provides the [audio and video conferencing](/docs/guides/conferences) functionality. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.Conference); + * ``` + */ + Conference = 'conference', + /** + * Provides the [interactive voice menus](/docs/guides/speech/ivr) functionality. + *
+ * Instead, you can implement this functionality via the [Call.say], [Call.startPlayback] and [Call.handleTones] methods, but this module gives more straightforward approach. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.IVR); + * ``` + */ + IVR = 'ivr', + /** + * Provides the OpenAI functionality. + */ + OpenAI = 'openai', + /** + * Provides the push notification functionality for [iOS](/docs/guides/sdk/iospush) and [Android](/docs/guides/sdk/androidpush) devices. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.PushService); + * ``` + */ + PushService = 'pushservice', + /** + * Provides the [call recording](/docs/guides/calls/record) and [conference recording](/docs/guides/conferences/record) functionality. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.Recorder); + * ``` + */ + Recorder = 'recorder', + /** + * Provides the SmartQueue (ACD v2) functionality for implementing a [contact center](/docs/guides/smartqueue). + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.SmartQueue); + * ``` + */ + SmartQueue = 'smartqueue', + /** + * Provides the [streaming](/docs/guides/calls/stream) functionality. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.StreamingAgent); + * ``` + */ + StreamingAgent = 'streamingagent', + /** + * Provides the [Voximplant HTTP API](https://voximplant.com/docs/references/httpapi) functionality. + *
+ * Add the following line to your scenario code to use the module: + * ``` + * require(Modules.VoximplantAPI); + * ``` + */ + VoximplantAPI = 'voximplantapi', +} + +declare module Net { + /** + * Performs an asynchronous HTTP request. TCP connect timeout is 6 seconds and total request timeout is 90 seconds. Learn more about the [limits](/docs/guides/voxengine/limits). + * @param url HTTP url to query + * @param options Advanced settings + */ + function httpRequestAsync(url: string, options?: HttpRequestOptions): Promise; +} + +declare module Net { + /** + * Performs a regular HTTP or HTTPS request. To perform an HTTPS request, insert "https://" at the URL's beginning. The default request method is **GET**, TCP connect timeout is **6** seconds and total request timeout is **90** seconds. Learn more about the [limits](/docs/guides/voxengine/limits). + * @param url HTTP url to query + * @param callback Function to be called on completion. The function receives a response object of type [HttpRequestResult] as a first argument + * @param options Advanced settings + */ + function httpRequest( + url: string, + callback: (result: HttpRequestResult) => void, + options?: HttpRequestOptions + ): void; +} + +declare module Net { + /** + * Send an email via a specified email server + * @param mailServerAddress SMTP server address + * @param from From address of the email + * @param to To address or list of addresses + * @param title Message title + * @param body Message body + * @param options Advanced settings + */ + function sendMailAsync( + mailServerAddress: string, + from: string, + to: string | string[], + title: string, + body: string, + options?: SendMailOptions + ): Promise; +} + +declare module Net { + /** + * Advanced options for sendMail method + */ + interface SendMailOptions { + /** + * Alternative HTML body + */ + html?: string; + /** + * CC addresses + */ + cc?: string[]; + /** + * BCC addresses + */ + bcc?: string[]; + /** + * Mail server port + */ + port: number; + /** + * Login for mail server + */ + login: string; + /** + * Password for mail server + */ + password: string; + } +} + +declare module Net { + /** + * Result of sending an email + */ + interface SendMailResult { + /** + * SMTP server response code + */ + code: number; + /** + * Optional. SMTP server error message + */ + error?: string; + } +} + +declare module Net { + /** + * Send email via the specified email server + * @param mailServerAddress SMTP server to send email + * @param from From address of email + * @param to To address or list of addresses + * @param title Message title + * @param body Message body + * @param callback Function to be called on completion. The function receives a response object of type [SendMailResult] as a first argument + * @param options Advanced settings + */ + function sendMail( + mailServerAddress: string, + from: string, + to: string | string[], + title: string, + body: string, + callback: (result: SendMailResult) => void, + options?: SendMailOptions + ): void; +} + +declare namespace OpenAI { + /** + * The Realtime API (Beta) enables you to build low-latency, multi-modal conversational experiences. + */ + namespace Beta {} +} +declare namespace OpenAI { + namespace Beta { + /** + * Creates an [OpenAI.Beta.RealtimeAPIClient] instance. + * @param parameters The [OpenAI.Beta.RealtimeAPIClient] parameters + */ + function createRealtimeAPIClient( + parameters: RealtimeAPIClientParameters + ): Promise; + } +} +declare namespace OpenAI { + namespace Beta { + /** + * @event + */ + enum Events { + /** + * Triggered when the audio stream sent by a third party through an OpenAI WebSocket is started playing. + * @typedef _WebSocketMediaStartedEvent + */ + WebSocketMediaStarted = 'OpenAI.Beta.Events.WebSocketMediaStarted', + /** + * Triggers after the end of the audio stream sent by a third party through an OpenAI WebSocket (**1 second of silence**). + * @typedef _WebSocketMediaEndedEvent + */ + WebSocketMediaEnded = 'OpenAI.Beta.Events.WebSocketMediaEnded', + } + + /** + * @private + */ + interface _Events { + [Events.WebSocketMediaStarted]: _WebSocketMediaStartedEvent; + [Events.WebSocketMediaEnded]: _WebSocketMediaEndedEvent; + } + + /** + * @private + */ + interface _Event { + /** + * The [OpenAI.Beta.RealtimeAPIClient] instance. + */ + client: RealtimeAPIClient; + } + + /** + * @private + */ + interface _WebSocketMediaEvent extends _Event { + /** + * Special tag to name audio streams sent over one OpenAI WebSocket connection. With it, one can send 2 audios to 2 different media units at the same time. + */ + tag?: string; + } + + /** + * @private + */ + interface _WebSocketMediaStartedEvent extends _WebSocketMediaEvent { + /** + * Audio encoding formats. + */ + encoding?: string; + /** + * Custom parameters. + */ + customParameters?: { [key: string]: string }; + } + + /** + * @private + */ + interface _WebSocketMediaEndedEvent extends _WebSocketMediaEvent { + /** + * Information about the audio stream that can be obtained after the stream stops or pauses (**1 second of silence**). + */ + mediaInfo?: WebSocketMediaInfo; + } + } +} + +declare namespace OpenAI { + namespace Beta { + /** + * @private + */ + interface _RealtimeAPIClientEvents extends _Events, _RealtimeAPIEvents {} + } +} +declare namespace OpenAI { + namespace Beta { + /** + * [OpenAI.Beta.RealtimeAPIClient] parameters. Can be passed as arguments to the [OpenAI.Beta.createRealtimeAPIClient] method. + */ + interface RealtimeAPIClientParameters { + /** + * The API key for the OpenAI Realtime API. + */ + apiKey: string; + /** + * Optional. The model to use for OpenAI Realtime API processing. The default value is **gpt-4o-realtime-preview-2024-10-01**. + */ + model?: string; + /** + * Optional. A callback function that is called when the [WebSocket] connection is closed. + */ + onWebSocketClose?: (event: _WebSocketCloseEvent) => void; + } + } +} + +declare namespace OpenAI { + namespace Beta { + class RealtimeAPIClient { + /** + * Returns the RealtimeAPIClient id. + */ + id(): string; + + /** + * Returns the RealtimeAPIClient WebSocket id. + */ + webSocketId(): string; + + /** + * Closes the RealtimeAPIClient connection (over WebSocket) or connection attempt. + */ + close(): void; + + /** + * Starts sending media from the RealtimeAPIClient (via WebSocket) to the media unit. RealtimeAPIClient works in real time. + * @param mediaUnit Media unit that receives media + * @param parameters Optional interaction parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media from the RealtimeAPIClient (via WebSocket) to the media unit. + * @param mediaUnit Media unit that stops receiving media + */ + stopMediaTo(mediaUnit: VoxMediaUnit): void; + + /** + * Adds a handler for the specified [OpenAI.Beta.RealtimeAPIEvents] or [OpenAI.Beta.Events] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [OpenAI.Beta.RealtimeAPIEvents.Error]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: OpenAI.Beta.Events | OpenAI.Beta.RealtimeAPIEvents | T, + callback: (event: OpenAI.Beta._RealtimeAPIClientEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [OpenAI.Beta.RealtimeAPIEvents] or [OpenAI.Beta.Events] event. + * @param event Event class (i.e., [OpenAI.Beta.RealtimeAPIEvents.Error]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: OpenAI.Beta.Events | OpenAI.Beta.RealtimeAPIEvents | T, + callback?: (event: OpenAI.Beta._RealtimeAPIClientEvents[T]) => any + ): void; + + /** + * Add a new Item to the Conversation's context, including messages, function calls, and function call responses. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create). + * @param previousItemId The ID of the preceding item after which the new item will be inserted. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create#realtime-client-events/conversation/item/create-previous_item_id](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create#realtime-client-events/conversation/item/create-previous_item_id) + * @param item The item to add to the conversation. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create#realtime-client-events/conversation/item/create-item](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create#realtime-client-events/conversation/item/create-item) + * @param eventId Optional. Client-generated ID used to identify this event. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create#realtime-client-events/conversation/item/create-event_id](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/create#realtime-client-events/conversation/item/create-event_id) + */ + conversationItemCreate(previousItemId: string, item: Object, eventId?: string): void; + + /** + * Send this event to truncate a previous assistant message’s audio. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate). + * @param itemId The ID of the assistant message item to truncate. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-item_id](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-item_id) + * @param contentIndex The index of the content part to truncate. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-content_index](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-content_index) + * @param audioEndMs Inclusive duration up to which audio is truncated, in milliseconds. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-audio_end_ms](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-audio_end_ms) + * @param eventId Optional. Client-generated ID used to identify this event. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-event_id](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/truncate#realtime-client-events/conversation/item/truncate-event_id) + */ + conversationItemTruncate( + itemId: string, + contentIndex: number, + audioEndMs: number, + eventId?: string + ): void; + + /** + * Send this event when you want to remove any item from the conversation history. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/delete](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/delete). + * @param itemId The ID of the item to delete. (https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/delete#realtime-client-events/conversation/item/delete-item_id) + * @param eventId Optional. Client-generated ID used to identify this event. [https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/delete#realtime-client-events/conversation/item/delete-event_id](https://platform.openai.com/docs/api-reference/realtime-client-events/conversation/item/delete#realtime-client-events/conversation/item/delete-event_id) + */ + conversationItemDelete(itemId: string, eventId?: string): void; + + /** + * Updates the session’s default configuration. [https://platform.openai.com/docs/api-reference/realtime-client-events/session/update](https://platform.openai.com/docs/api-reference/realtime-client-events/session/update). + * @param session Realtime session object configuration.[https://platform.openai.com/docs/api-reference/realtime-client-events/session/update#realtime-client-events/session/update-session](https://platform.openai.com/docs/api-reference/realtime-client-events/session/update#realtime-client-events/session/update-session). NOTE: the 'input_audio_format' parameter will be ignored + * @param eventId Optional. Client-generated ID used to identify this event. [https://platform.openai.com/docs/api-reference/realtime-client-events/session/update#realtime-client-events/session/update-event_id](https://platform.openai.com/docs/api-reference/realtime-client-events/session/update#realtime-client-events/session/update-event_id) + */ + sessionUpdate(session: Object, eventId?: string): void; + + /** + * Instructs the server to create a Response, which means triggering model inference. [https://platform.openai.com/docs/api-reference/realtime-client-events/response/create](https://platform.openai.com/docs/api-reference/realtime-client-events/response/create). + * @param response The response resource. [https://platform.openai.com/docs/api-reference/realtime-client-events/response/create#realtime-client-events/response/create-response](https://platform.openai.com/docs/api-reference/realtime-client-events/response/create#realtime-client-events/response/create-response). NOTE: the 'input_audio_format' parameter will be ignored + * @param eventId Optional. Client-generated ID used to identify this event. [https://platform.openai.com/docs/api-reference/realtime-client-events/response/create#realtime-client-events/response/create-event_id](https://platform.openai.com/docs/api-reference/realtime-client-events/response/create#realtime-client-events/response/create-event_id) + */ + responseCreate(response: Object, eventId?: string): void; + + /** + * Cancels an in-progress response. [https://platform.openai.com/docs/api-reference/realtime-client-events/response/cancel](https://platform.openai.com/docs/api-reference/realtime-client-events/response/cancel). + * @param eventId Optional. Client-generated ID used to identify this event. [https://platform.openai.com/docs/api-reference/realtime-client-events/response/cancel#realtime-client-events/response/cancel-event_id](https://platform.openai.com/docs/api-reference/realtime-client-events/response/cancel#realtime-client-events/response/cancel-event_id) + */ + responseCancel(eventId?: string): void; + } + } +} +declare namespace OpenAI { + namespace Beta { + /** + * @event + */ + enum RealtimeAPIEvents { + /** + * The unknown event. + * @typedef _RealtimeAPIEvent + */ + Unknown = 'OpenAI.Beta.RealtimeAPI.Unknown', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/error](https://platform.openai.com/docs/api-reference/realtime-server-events/error) + * @typedef _RealtimeAPIEvent + */ + Error = 'OpenAI.Beta.RealtimeAPI.Error', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/session/created](https://platform.openai.com/docs/api-reference/realtime-server-events/session/created) + * @typedef _RealtimeAPIEvent + */ + SessionCreated = 'OpenAI.Beta.RealtimeAPI.SessionCreated', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/session/updated](https://platform.openai.com/docs/api-reference/realtime-server-events/session/updated) + * @typedef _RealtimeAPIEvent + */ + SessionUpdated = 'OpenAI.Beta.RealtimeAPI.SessionUpdated', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/created](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/created) + * @typedef _RealtimeAPIEvent + */ + ConversationCreated = 'OpenAI.Beta.RealtimeAPI.ConversationCreated', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/created](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/created) + * @typedef _RealtimeAPIEvent + */ + ConversationItemCreated = 'OpenAI.Beta.RealtimeAPI.ConversationItemCreated', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/input_audio_transcription/completed](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/input_audio_transcription/completed) + * @typedef _RealtimeAPIEvent + */ + ConversationItemInputAudioTranscriptionCompleted = 'OpenAI.Beta.RealtimeAPI.conversationItemInputAudioTranscriptionCompleted', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/input_audio_transcription/failed](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/input_audio_transcription/failed) + * @typedef _RealtimeAPIEvent + */ + ConversationItemInputAudioTranscriptionFailed = 'OpenAI.Beta.RealtimeAPI.conversationItemInputAudioTranscriptionFailed', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/truncated](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/truncated) + * @typedef _RealtimeAPIEvent + */ + ConversationItemTruncated = 'OpenAI.Beta.RealtimeAPI.ConversationItemTruncated', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/deleted](https://platform.openai.com/docs/api-reference/realtime-server-events/conversation/item/deleted) + * @typedef _RealtimeAPIEvent + */ + ConversationItemDeleted = 'OpenAI.Beta.RealtimeAPI.ConversationItemDeleted', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/committed](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/committed) + * @typedef _RealtimeAPIEvent + */ + InputAudioBufferCommitted = 'OpenAI.Beta.RealtimeAPI.InputAudioBufferCommitted', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/cleared](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/cleared) + * @typedef _RealtimeAPIEvent + */ + InputAudioBufferCleared = 'OpenAI.Beta.RealtimeAPI.InputAudioBufferCleared', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/speech_started](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/speech_started) + * @typedef _RealtimeAPIEvent + */ + InputAudioBufferSpeechStarted = 'OpenAI.Beta.RealtimeAPI.InputAudioBufferSpeechStarted', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/speech_stopped](https://platform.openai.com/docs/api-reference/realtime-server-events/input_audio_buffer/speech_stopped) + * @typedef _RealtimeAPIEvent + */ + InputAudioBufferSpeechStopped = 'OpenAI.Beta.RealtimeAPI.InputAudioBufferSpeechStopped', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/created](https://platform.openai.com/docs/api-reference/realtime-server-events/response/created) + * @typedef _RealtimeAPIEvent + */ + ResponseCreated = 'OpenAI.Beta.RealtimeAPI.ResponseCreated', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/done](https://platform.openai.com/docs/api-reference/realtime-server-events/response/done) + * @typedef _RealtimeAPIEvent + */ + ResponseDone = 'OpenAI.Beta.RealtimeAPI.ResponseDone', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/output_item/added](https://platform.openai.com/docs/api-reference/realtime-server-events/response/output_item/added) + * @typedef _RealtimeAPIEvent + */ + ResponseOutputItemAdded = 'OpenAI.Beta.RealtimeAPI.ResponseOutputItemAdded', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/output_item/done](https://platform.openai.com/docs/api-reference/realtime-server-events/response/output_item/done) + * @typedef _RealtimeAPIEvent + */ + ResponseOutputItemDone = 'OpenAI.Beta.RealtimeAPI.ResponseOutputItemDone', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/content_part/added](https://platform.openai.com/docs/api-reference/realtime-server-events/response/content_part/added) + * @typedef _RealtimeAPIEvent + */ + ResponseContentPartAdded = 'OpenAI.Beta.RealtimeAPI.ResponseContentPartAdded', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/content_part/done](https://platform.openai.com/docs/api-reference/realtime-server-events/response/content_part/done) + * @typedef _RealtimeAPIEvent + */ + ResponseContentPartDone = 'OpenAI.Beta.RealtimeAPI.ResponseContentPartDone', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/text/delta](https://platform.openai.com/docs/api-reference/realtime-server-events/response/text/delta) + * @typedef _RealtimeAPIEvent + */ + ResponseTextDelta = 'OpenAI.Beta.RealtimeAPI.ResponseTextDelta', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/text/done](https://platform.openai.com/docs/api-reference/realtime-server-events/response/text/done) + * @typedef _RealtimeAPIEvent + */ + ResponseTextDone = 'OpenAI.Beta.RealtimeAPI.ResponseTextDone', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio_transcript/delta](https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio_transcript/delta) + * @typedef _RealtimeAPIEvent + */ + ResponseAudioTranscriptDelta = 'OpenAI.Beta.RealtimeAPI.ResponseAudioTranscriptDelta', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio_transcript/done](https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio_transcript/done) + * @typedef _RealtimeAPIEvent + */ + ResponseAudioTranscriptDone = 'OpenAI.Beta.RealtimeAPI.ResponseAudioTranscriptDone', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio/delta](https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio/delta) + * @typedef _RealtimeAPIEvent + */ + ResponseAudioDelta = 'OpenAI.Beta.RealtimeAPI.ResponseAudioDelta', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio/done](https://platform.openai.com/docs/api-reference/realtime-server-events/response/audio/done) + * @typedef _RealtimeAPIEvent + */ + ResponseAudioDone = 'OpenAI.Beta.RealtimeAPI.ResponseAudioDone', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/function_call_arguments/delta](https://platform.openai.com/docs/api-reference/realtime-server-events/response/function_call_arguments/delta) + * @typedef _RealtimeAPIEvent + */ + ResponseFunctionCallArgumentsDelta = 'OpenAI.Beta.RealtimeAPI.ResponseFunctionCallArgumentsDelta', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/response/function_call_arguments/done](https://platform.openai.com/docs/api-reference/realtime-server-events/response/function_call_arguments/done) + * @typedef _RealtimeAPIEvent + */ + ResponseFunctionCallArgumentsDone = 'OpenAI.Beta.RealtimeAPI.ResponseFunctionCallArgumentsDone', + /** + * [https://platform.openai.com/docs/api-reference/realtime-server-events/rate_limits/updated](https://platform.openai.com/docs/api-reference/realtime-server-events/rate_limits/updated) + * @typedef _RealtimeAPIEvent + */ + RateLimitsUpdated = 'OpenAI.Beta.RealtimeAPI.RateLimitsUpdated', + } + + /** + * @private + */ + interface _RealtimeAPIEvents { + [RealtimeAPIEvents.Unknown]: _RealtimeAPIEvent; + [RealtimeAPIEvents.Error]: _RealtimeAPIEvent; + [RealtimeAPIEvents.SessionCreated]: _RealtimeAPIEvent; + [RealtimeAPIEvents.SessionUpdated]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ConversationCreated]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ConversationItemCreated]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ConversationItemInputAudioTranscriptionCompleted]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ConversationItemInputAudioTranscriptionFailed]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ConversationItemTruncated]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ConversationItemDeleted]: _RealtimeAPIEvent; + [RealtimeAPIEvents.InputAudioBufferCommitted]: _RealtimeAPIEvent; + [RealtimeAPIEvents.InputAudioBufferCleared]: _RealtimeAPIEvent; + [RealtimeAPIEvents.InputAudioBufferSpeechStarted]: _RealtimeAPIEvent; + [RealtimeAPIEvents.InputAudioBufferSpeechStopped]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseCreated]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseDone]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseOutputItemAdded]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseOutputItemDone]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseContentPartAdded]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseContentPartDone]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseTextDelta]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseTextDone]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseAudioTranscriptDelta]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseAudioTranscriptDone]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseAudioDelta]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseAudioDone]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseFunctionCallArgumentsDelta]: _RealtimeAPIEvent; + [RealtimeAPIEvents.ResponseFunctionCallArgumentsDone]: _RealtimeAPIEvent; + [RealtimeAPIEvents.RateLimitsUpdated]: _RealtimeAPIEvent; + } + + /** + * @private + */ + interface _RealtimeAPIEvent { + /** + * The [OpenAI.Beta.RealtimeAPIClient] instance. + */ + client: RealtimeAPIClient; + /** + * The event's data. + */ + data?: Object; + } + } +} + +declare namespace OpenAI {} +/** + * Which media streams to receive from the endpoint. Can be passed as a [ReceiveParameters] parameter. Consists of optional video and audio keys. + *
+ * For each key, specify the ["default"] value to receive all media streams, specify the empty array [] value to receive no media streams, or specify the media stream IDs (for example, ["v1", "v10"]) to receive specific media streams. + */ +interface ParticipantReceiveParameters { + /** + * Video streams to receive. + */ + video?: Array<'default' | string>; + /** + * Audio streams to receive. + */ + audio?: Array<'default' | string>; +} + +declare module PhoneNumber { + /** + * Get the phone number info. + * @param number Phone number in country specific format, or E.164 if starts with + + * @param country 2-digit country code to get number format, if not specified, number is treated as E.164, e.g. "RU", "US" + */ + function getInfo(number: string, country?: string): Info; +} + +declare module PhoneNumber { + interface Info { + /** + * Number type, one of: FIXED\_LINE, MOBILE, FIXED\_LINE\_OR\_MOBILE, TOLL\_FREE, PREMIUM\_RATE, SHARED\_COST, VOIP, PERSONAL\_NUMBER, PAGER, UAN, VOICEMAIL, UNKNOWN + */ + numberType: string; + /** + * 2-letter country code of specified phone number (ISO 3166-1) + */ + region: string; + /** + * The phone number's city, state and country. If the city (or state) is unavailable, only state and country (or just country) is shown + */ + location: string; + /** + * Whether the number is possible in specified country (just by analyzing length information) + */ + isPossibleNumber: boolean; + /** + * Whether the number is valid in specified country + */ + isValidNumber: boolean; + /** + * Whether the number is valid in detected region + */ + isValidNumberForRegion: boolean; + /** + * Number in international E.164 format, starting with + + */ + number: string; + /** + * Optional. Error string. Possible values are: INVALID\_COUNTRY\_CODE, NOT\_A\_NUMBER, TOO\_SHORT\_AFTER\_IDD, TOO\_SHORT\_NSN, TOO\_LONG\_NSN + */ + error?: string; + } +} + +declare module PhoneNumber {} + +/** + * @event + */ +declare enum PlayerEvents { + /** + * Triggered when [Player] created. + * @typedef _PlayerCreatedEvent + */ + Created = 'Player.Created', + + /** + * Triggers by the [createURLPlayer] and [createTTSPlayer] methods when
+ * 1) the audio file download to the Voximplant cache is finished;
+ * 2) the audio file is found in the cache (i.e., it is in the cache before). + * @typedef _PlayerPlaybackReadyEvent + */ + PlaybackReady = 'Player.PlaybackReady', + + /** + * Triggered when playback is started. Note that if the [createURLPlayer] method is called with the **onPause** parameter set to true, the event is not triggered; it is triggered after the [Player.resume] method call. + * @typedef _PlayerStartedEvent + */ + Started = 'Player.Started', + + /** + * Triggers as a result of the [Player.stop] method call. + * @typedef _PlayerStoppedEvent + */ + Stopped = 'Player.Stopped', + + /** + * Triggered when playback has finished successfully or with an error + * @typedef _PlayerPlaybackFinishedEvent + */ + PlaybackFinished = 'Player.PlaybackFinished', + + /** + * Triggered when playback has finished with an error + * @typedef _PlayerErrorEvent + */ + Error = 'Player.Error', + + /** + * Triggered when [Player.addMarker] is reached + * @typedef _PlayerPlaybackMarkerReachedEvent + */ + PlaybackMarkerReached = 'Player.PlaybackMarkerReached', + + /** + * Triggered when an audio file is playing faster than it is being loaded. + * @typedef _PlayerPlaybackBufferingEvent + */ + PlaybackBuffering = 'Player.Buffering', +} + +/** + * @private + */ +declare interface _PlayerEvents { + [PlayerEvents.Created]: _PlayerCreatedEvent; + [PlayerEvents.PlaybackReady]: _PlayerPlaybackReadyEvent; + [PlayerEvents.Started]: _PlayerStartedEvent; + [PlayerEvents.Stopped]: _PlayerStoppedEvent; + [PlayerEvents.PlaybackFinished]: _PlayerPlaybackFinishedEvent; + [PlayerEvents.Error]: _PlayerErrorEvent; + [PlayerEvents.PlaybackMarkerReached]: _PlayerPlaybackMarkerReachedEvent; + [PlayerEvents.PlaybackBuffering]: _PlayerPlaybackBufferingEvent; +} + +/** + * @private + */ +declare interface _PlayerEvent { + /** + * Player that generated the event + */ + player: Player; +} + +/** + * @private + */ +declare interface _PlayerCreatedEvent extends _PlayerEvent {} + +/** + * @private + */ +declare interface _PlayerPlaybackReadyEvent extends _PlayerEvent {} + +/** + * @private + */ +declare interface _PlayerStartedEvent extends _PlayerEvent { + /** + * Playback duration + */ + duration: number; +} + +/** + * @private + */ +declare interface _PlayerStoppedEvent extends _PlayerEvent {} + +/** + * @private + */ +declare interface _PlayerPlaybackFinishedEvent extends _PlayerEvent { + /** + * Optional. Error message + */ + error?: string; +} + +/** + * @private + */ +declare interface _PlayerErrorEvent extends _PlayerEvent { + /** + * Error message + */ + error: string; +} + +/** + * @private + */ +declare interface _PlayerPlaybackMarkerReachedEvent extends _PlayerEvent { + /** + * The marker offset + */ + offset: number; +} + +/** + * @private + */ +declare interface _PlayerPlaybackBufferingEvent extends _PlayerEvent {} + +/** + * Represents an audio player. + */ +declare class Player { + /** + * Adds a handler for the specified [PlayerEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [PlayerEvents.PlaybackFinished]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: PlayerEvents | T, + callback: (event: _PlayerEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [PlayerEvents] event. + * @param event Event class (i.e., [PlayerEvents.PlaybackFinished]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: PlayerEvents | T, + callback?: (event: _PlayerEvents[T]) => any + ): void; + + /** + * Returns the player's id. + */ + id(): string; + + /** + * Pauses playback. To continue the playback use the [Player.resume] method. + */ + pause(): void; + + /** + * Resumes playback after the [Player.pause] method is called. + */ + resume(): void; + + /** + * Stops playback. The current player's instance is destroyed. + */ + stop(): void; + + /** + * Adds a playback marker. The [PlayerEvents.PlaybackMarkerReached] event is triggered when the marker is reached. + * @param offset Positive/negative offset in milliseconds from the start/end of media + */ + addMarker(offset: number): void; + + /** + * Starts sending media from the player to the media unit. + * @param mediaUnit Media unit that receives media + * @param parameters Optional. WebSocket interaction only parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media from the player to the media unit. + * @param mediaUnit Media unit that does not need to receive media from this conference anymore + */ + stopMediaTo(mediaUnit: VoxMediaUnit): void; +} + +/** + * An object that specifies what media streams to receive from each [Endpoint]. Can be passed via the [EndpointParameters.receiveParameters] parameter or an argument to the [Endpoint.manageEndpoint] method. + *
+ * The object can accept the following keys: + *
    + *
  • A string value with the endpoint ID. Applies the setting only for the specified endpoint.
  • + *
  • The all keyword. Applies the setting for all the endpoints.
  • + *
  • The new keyword. Applies the setting for the new endpoints only.
  • + *
+ */ +interface ReceiveParameters { + /** + * Substitute one of the available keys from the description. + */ + [remoteParticipantId: string]: ParticipantReceiveParameters; +} + +/** + * List of available values for the [RecorderParameters.expire] parameter. + */ +declare enum RecordExpireTime { + THREEMONTHS = '', + SIXMONTHS = '-6m', + ONEYEAR = '-1y', + TWOYEARS = '-2y', + THREEYEARS = '-3y', + FIVEYEARS = '-5y', +} + +/** + * An object that specifies video frame's direction. Can be passed via the [RecorderVideoParameters.layoutSettings] and [UpdateRecorderVideoParameters.layoutSettings] parameter. + *
+ * Add the following line to your scenario code to use the type: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare enum RecorderDirection { + ltr = 'ltr', + rtl = 'rtl', +} + +/** + * An object that specifies video frame parameters. Can be passed via the [RecorderVideoParameters.layoutSettings] and [UpdateRecorderVideoParameters.layoutSettings] parameter. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Recorder); + * ``` + * */ +declare interface RecorderDrawArea { + /** + * Video frame's priority. + */ + priority: number; + /** + * Video frame's width. + */ + width: number; + /** + * Video frame's height. + */ + height: number; + /** + * Video frame's top margin. + */ + top: number; + /** + * Video frame's left margin. + */ + left: number; + /** + * The corresponding grid parameters object. + */ + grid: RecorderGridDefinition[]; +} + +/** + * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.Recorder); + * ``` + * @event + */ +declare enum RecorderEvents { + /** + * Triggers in case of errors during the recording process. + * @typedef _RecorderErrorEvent + */ + RecorderError = 'Recorder.Error', + /** + * Triggers after the recording's start. + * @typedef _RecorderURLEvent + */ + Started = 'Recorder.Started', + /** + * Triggers after the recording's stop. + * @typedef _RecorderStoppedEvent + */ + Stopped = 'Recorder.Stopped', +} + +/** + * @private + */ +declare interface _RecorderEvent { + /** + * Recorder that generated the event. + */ + recorder: Recorder; +} + +/** + * @private + */ +declare interface _RecorderURLEvent extends _RecorderEvent { + /** + * The link to the record. + */ + url: string; +} + +/** + * @private + */ +declare interface _RecorderErrorEvent extends _RecorderEvent { + /** + * Error message. + */ + error: string; +} + +/** + * @private + */ +declare interface _RecorderStartedEvent extends _RecorderURLEvent {} + +/** + * @private + */ +declare interface _RecorderStoppedEvent extends _RecorderEvent { + /** + * Record cost (in the account's currency: USD, EUR or RUB). + */ + cost: string; + /** + * Record duration in seconds. + */ + duration: number; +} + +/** + * @private + */ +declare interface _RecorderEvents { + [RecorderEvents.RecorderError]: _RecorderErrorEvent; + [RecorderEvents.Started]: _RecorderStartedEvent; + [RecorderEvents.Stopped]: _RecorderStoppedEvent; +} + +/** + * An object that specifies grid parameters. Can be passed via the [RecorderDrawArea.grid] parameter. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare interface RecorderGridDefinition { + /** + * Minimum video frames for the grid. + */ + fromCount: number; + /** + * Optional. Maximum video frames for the grid. + */ + toCount?: number; + /** + * Number of columns in the grid. + */ + colCount: number; + /** + * Number of rows in the grid. + */ + rowCount: number; +} + +/** + * Enumeration of the recorder video fonts. Can be passed via the [RecorderLabels.font] parameter. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare enum RecorderLabelFont { + ROBOTO_SLAB_SEMI_BOLD = 'RobotoSlab-SemiBold', + ROBOTO_SLAB_THIN = 'RobotoSlab-Thin', + ROBOTO_CONDENSED_BOLD = 'RobotoCondensed-Bold', + ROBOTO_CONDENSED_BOLD_ITALIC = 'RobotoCondensed-BoldItalic', + ROBOTO_CONDENSED_ITALIC = 'RobotoCondensed-Italic', + ROBOTO_CONDENSED_LIGHT = 'RobotoCondensed-Light', + ROBOTO_CONDENSED_LIGHT_ITALIC = 'RobotoCondensed-LightItalic', + ROBOTO_CONDENSED_REGULAR = 'RobotoCondensed-Regular', + ROBOTO_FLEX_REGULAR = 'RobotoFlex-Regular', + ROBOTO_MONO_BOLD = 'RobotoMono-Bold', + ROBOTO_MONO_BOLD_ITALIC = 'RobotoMono-BoldItalic', + ROBOTO_MONO_EXTRA_LIGHT = 'RobotoMono-ExtraLight', + ROBOTO_MONO_EXTRA_LIGHT_ITALIC = 'RobotoMono-ExtraLightItalic', + ROBOTO_MONO_ITALIC = 'RobotoMono-Italic', + ROBOTO_MONO_LIGHT = 'RobotoMono-Light', + ROBOTO_MONO_LIGHT_ITALIC = 'RobotoMono-LightItalic', + ROBOTO_MONO_MEDIUM = 'RobotoMono-Medium', + ROBOTO_MONO_MEDIUM_ITALIC = 'RobotoMono-MediumItalic', + ROBOTO_MONO_REGULAR = 'RobotoMono-Regular', + ROBOTO_MONO_SEMI_BOLD = 'RobotoMono-SemiBold', + ROBOTO_MONO_SEMI_BOLD_ITALIC = 'RobotoMono-SemiBoldItalic', + ROBOTO_MONO_THIN = 'RobotoMono-Thin', + ROBOTO_MONO_THIN_ITALIC = 'RobotoMono-ThinItalic', + ROBOTO_SERIF_CONDENSED_BLACK = 'RobotoSerif_Condensed-Black', + ROBOTO_SERIF_CONDENSED_BLACK_ITALIC = 'RobotoSerif_Condensed-BlackItalic', + ROBOTO_SERIF_CONDENSED_BOLD = 'RobotoSerif_Condensed-Bold', + ROBOTO_SERIF_CONDENSED_BOLD_ITALIC = 'RobotoSerif_Condensed-BoldItalic', + ROBOTO_SERIF_CONDENSED_EXTRA_BOLD = 'RobotoSerif_Condensed-ExtraBold', + ROBOTO_SERIF_CONDENSED_EXTRA_BOLD_ITALIC = 'RobotoSerif_Condensed-ExtraBoldItalic', + ROBOTO_SERIF_CONDENSED_EXTRA_LIGHT = 'RobotoSerif_Condensed-ExtraLight', + ROBOTO_SERIF_CONDENSED_EXTRA_LIGHT_ITALIC = 'RobotoSerif_Condensed-ExtraLightItalic', + ROBOTO_SERIF_CONDENSED_ITALIC = 'RobotoSerif_Condensed-Italic', + ROBOTO_SERIF_CONDENSED_LIGHT = 'RobotoSerif_Condensed-Light', + ROBOTO_SERIF_CONDENSED_LIGHT_ITALIC = 'RobotoSerif_Condensed-LightItalic', + ROBOTO_SERIF_CONDENSED_MEDIUM = 'RobotoSerif_Condensed-Medium', + ROBOTO_SERIF_CONDENSED_MEDIUM_ITALIC = 'RobotoSerif_Condensed-MediumItalic', + ROBOTO_SERIF_CONDENSED_REGULAR = 'RobotoSerif_Condensed-Regular', + ROBOTO_SERIF_CONDENSED_SEMI_BOLD = 'RobotoSerif_Condensed-SemiBold', + ROBOTO_SERIF_CONDENSED_SEMI_BOLD_ITALIC = 'RobotoSerif_Condensed-SemiBoldItalic', + ROBOTO_SERIF_CONDENSED_THIN = 'RobotoSerif_Condensed-Thin', + ROBOTO_SERIF_CONDENSED_THIN_ITALIC = 'RobotoSerif_Condensed-ThinItalic', + ROBOTO_SERIF_EXPANDED_BLACK = 'RobotoSerif_Expanded-Black', + ROBOTO_SERIF_EXPANDED_BLACK_ITALIC = 'RobotoSerif_Expanded-BlackItalic', + ROBOTO_SERIF_EXPANDED_BOLD = 'RobotoSerif_Expanded-Bold', + ROBOTO_SERIF_EXPANDED_BOLD_ITALIC = 'RobotoSerif_Expanded-BoldItalic', + ROBOTO_SERIF_EXPANDED_EXTRA_BOLD = 'RobotoSerif_Expanded-ExtraBold', + ROBOTO_SERIF_EXPANDED_EXTRA_BOLD_ITALIC = 'RobotoSerif_Expanded-ExtraBoldItalic', + ROBOTO_SERIF_EXPANDED_EXTRA_LIGHT = 'RobotoSerif_Expanded-ExtraLight', + ROBOTO_SERIF_EXPANDED_EXTRA_LIGHT_ITALIC = 'RobotoSerif_Expanded-ExtraLightItalic', + ROBOTO_SERIF_EXPANDED_ITALIC = 'RobotoSerif_Expanded-Italic', + ROBOTO_SERIF_EXPANDED_LIGHT = 'RobotoSerif_Expanded-Light', + ROBOTO_SERIF_EXPANDED_LIGHT_ITALIC = 'RobotoSerif_Expanded-LightItalic', + ROBOTO_SERIF_EXPANDED_MEDIUM = 'RobotoSerif_Expanded-Medium', + ROBOTO_SERIF_EXPANDED_MEDIUM_ITALIC = 'RobotoSerif_Expanded-MediumItalic', + ROBOTO_SERIF_EXPANDED_REGULAR = 'RobotoSerif_Expanded-Regular', + ROBOTO_SERIF_EXPANDED_SEMI_BOLD = 'RobotoSerif_Expanded-SemiBold', + ROBOTO_SERIF_EXPANDED_SEMI_BOLD_ITALIC = 'RobotoSerif_Expanded-SemiBoldItalic', + ROBOTO_SERIF_EXPANDED_THIN = 'RobotoSerif_Expanded-Thin', + ROBOTO_SERIF_EXPANDED_THIN_ITALIC = 'RobotoSerif_Expanded-ThinItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_BLACK = 'RobotoSerif_ExtraCondensed-Black', + ROBOTO_SERIF_EXTRA_CONDENSED_BLACK_ITALIC = 'RobotoSerif_ExtraCondensed-BlackItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_BOLD = 'RobotoSerif_ExtraCondensed-Bold', + ROBOTO_SERIF_EXTRA_CONDENSED_BOLD_ITALIC = 'RobotoSerif_ExtraCondensed-BoldItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_EXTRA_BOLD = 'RobotoSerif_ExtraCondensed-ExtraBold', + ROBOTO_SERIF_EXTRA_CONDENSED_EXTRA_BOLD_ITALIC = 'RobotoSerif_ExtraCondensed-ExtraBoldItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_EXTRA_LIGHT = 'RobotoSerif_ExtraCondensed-ExtraLight', + ROBOTO_SERIF_EXTRA_CONDENSED_EXTRA_LIGHT_ITALIC = 'RobotoSerif_ExtraCondensed-ExtraLightItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_ITALIC = 'RobotoSerif_ExtraCondensed-Italic', + ROBOTO_SERIF_EXTRA_CONDENSED_LIGHT = 'RobotoSerif_ExtraCondensed-Light', + ROBOTO_SERIF_EXTRA_CONDENSED_LIGHT_ITALIC = 'RobotoSerif_ExtraCondensed-LightItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_MEDIUM = 'RobotoSerif_ExtraCondensed-Medium', + ROBOTO_SERIF_EXTRA_CONDENSED_MEDIUM_ITALIC = 'RobotoSerif_ExtraCondensed-MediumItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_REGULAR = 'RobotoSerif_ExtraCondensed-Regular', + ROBOTO_SERIF_EXTRA_CONDENSED_SEMI_BOLD = 'RobotoSerif_ExtraCondensed-SemiBold', + ROBOTO_SERIF_EXTRA_CONDENSED_SEMI_BOLD_ITALIC = 'RobotoSerif_ExtraCondensed-SemiBoldItalic', + ROBOTO_SERIF_EXTRA_CONDENSED_THIN = 'RobotoSerif_ExtraCondensed-Thin', + ROBOTO_SERIF_EXTRA_CONDENSED_THIN_ITALIC = 'RobotoSerif_ExtraCondensed-ThinItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_BLACK = 'RobotoSerif_ExtraExpanded-Black', + ROBOTO_SERIF_EXTRA_EXPANDED_BLACK_ITALIC = 'RobotoSerif_ExtraExpanded-BlackItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_BOLD = 'RobotoSerif_ExtraExpanded-Bold', + ROBOTO_SERIF_EXTRA_EXPANDED_BOLD_ITALIC = 'RobotoSerif_ExtraExpanded-BoldItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_EXTRA_BOLD = 'RobotoSerif_ExtraExpanded-ExtraBold', + ROBOTO_SERIF_EXTRA_EXPANDED_EXTRA_BOLD_ITALIC = 'RobotoSerif_ExtraExpanded-ExtraBoldItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_EXTRA_LIGHT = 'RobotoSerif_ExtraExpanded-ExtraLight', + ROBOTO_SERIF_EXTRA_EXPANDED_EXTRA_LIGHT_ITALIC = 'RobotoSerif_ExtraExpanded-ExtraLightItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_ITALIC = 'RobotoSerif_ExtraExpanded-Italic', + ROBOTO_SERIF_EXTRA_EXPANDED_LIGHT = 'RobotoSerif_ExtraExpanded-Light', + ROBOTO_SERIF_EXTRA_EXPANDED_LIGHT_ITALIC = 'RobotoSerif_ExtraExpanded-LightItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_MEDIUM = 'RobotoSerif_ExtraExpanded-Medium', + ROBOTO_SERIF_EXTRA_EXPANDED_MEDIUM_ITALIC = 'RobotoSerif_ExtraExpanded-MediumItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_REGULAR = 'RobotoSerif_ExtraExpanded-Regular', + ROBOTO_SERIF_EXTRA_EXPANDED_SEMI_BOLD = 'RobotoSerif_ExtraExpanded-SemiBold', + ROBOTO_SERIF_EXTRA_EXPANDED_SEMI_BOLD_ITALIC = 'RobotoSerif_ExtraExpanded-SemiBoldItalic', + ROBOTO_SERIF_EXTRA_EXPANDED_THIN = 'RobotoSerif_ExtraExpanded-Thin', + ROBOTO_SERIF_EXTRA_EXPANDED_THIN_ITALIC = 'RobotoSerif_ExtraExpanded-ThinItalic', + ROBOTO_SERIF_SEMI_CONDENSED_BLACK = 'RobotoSerif_SemiCondensed-Black', + ROBOTO_SERIF_SEMI_CONDENSED_BLACK_ITALIC = 'RobotoSerif_SemiCondensed-BlackItalic', + ROBOTO_SERIF_SEMI_CONDENSED_BOLD = 'RobotoSerif_SemiCondensed-Bold', + ROBOTO_SERIF_SEMI_CONDENSED_BOLD_ITALIC = 'RobotoSerif_SemiCondensed-BoldItalic', + ROBOTO_SERIF_SEMI_CONDENSED_EXTRA_BOLD = 'RobotoSerif_SemiCondensed-ExtraBold', + ROBOTO_SERIF_SEMI_CONDENSED_EXTRA_BOLD_ITALIC = 'RobotoSerif_SemiCondensed-ExtraBoldItalic', + ROBOTO_SERIF_SEMI_CONDENSED_EXTRA_LIGHT = 'RobotoSerif_SemiCondensed-ExtraLight', + ROBOTO_SERIF_SEMI_CONDENSED_EXTRA_LIGHT_ITALIC = 'RobotoSerif_SemiCondensed-ExtraLightItalic', + ROBOTO_SERIF_SEMI_CONDENSED_ITALIC = 'RobotoSerif_SemiCondensed-Italic', + ROBOTO_SERIF_SEMI_CONDENSED_LIGHT = 'RobotoSerif_SemiCondensed-Light', + ROBOTO_SERIF_SEMI_CONDENSED_LIGHT_ITALIC = 'RobotoSerif_SemiCondensed-LightItalic', + ROBOTO_SERIF_SEMI_CONDENSED_MEDIUM = 'RobotoSerif_SemiCondensed-Medium', + ROBOTO_SERIF_SEMI_CONDENSED_MEDIUM_ITALIC = 'RobotoSerif_SemiCondensed-MediumItalic', + ROBOTO_SERIF_SEMI_CONDENSED_REGULAR = 'RobotoSerif_SemiCondensed-Regular', + ROBOTO_SERIF_SEMI_CONDENSED_SEMI_BOLD = 'RobotoSerif_SemiCondensed-SemiBold', + ROBOTO_SERIF_SEMI_CONDENSED_SEMI_BOLD_ITALIC = 'RobotoSerif_SemiCondensed-SemiBoldItalic', + ROBOTO_SERIF_SEMI_CONDENSED_THIN = 'RobotoSerif_SemiCondensed-Thin', + ROBOTO_SERIF_SEMI_CONDENSED_THIN_ITALIC = 'RobotoSerif_SemiCondensed-ThinItalic', + ROBOTO_SERIF_SEMI_EXPANDED_BLACK = 'RobotoSerif_SemiExpanded-Black', + ROBOTO_SERIF_SEMI_EXPANDED_BLACK_ITALIC = 'RobotoSerif_SemiExpanded-BlackItalic', + ROBOTO_SERIF_SEMI_EXPANDED_BOLD = 'RobotoSerif_SemiExpanded-Bold', + ROBOTO_SERIF_SEMI_EXPANDED_BOLD_ITALIC = 'RobotoSerif_SemiExpanded-BoldItalic', + ROBOTO_SERIF_SEMI_EXPANDED_EXTRA_BOLD = 'RobotoSerif_SemiExpanded-ExtraBold', + ROBOTO_SERIF_SEMI_EXPANDED_EXTRA_BOLD_ITALIC = 'RobotoSerif_SemiExpanded-ExtraBoldItalic', + ROBOTO_SERIF_SEMI_EXPANDED_EXTRA_LIGHT = 'RobotoSerif_SemiExpanded-ExtraLight', + ROBOTO_SERIF_SEMI_EXPANDED_EXTRA_LIGHT_ITALIC = 'RobotoSerif_SemiExpanded-ExtraLightItalic', + ROBOTO_SERIF_SEMI_EXPANDED_ITALIC = 'RobotoSerif_SemiExpanded-Italic', + ROBOTO_SERIF_SEMI_EXPANDED_LIGHT = 'RobotoSerif_SemiExpanded-Light', + ROBOTO_SERIF_SEMI_EXPANDED_LIGHT_ITALIC = 'RobotoSerif_SemiExpanded-LightItalic', + ROBOTO_SERIF_SEMI_EXPANDED_MEDIUM = 'RobotoSerif_SemiExpanded-Medium', + ROBOTO_SERIF_SEMI_EXPANDED_MEDIUM_ITALIC = 'RobotoSerif_SemiExpanded-MediumItalic', + ROBOTO_SERIF_SEMI_EXPANDED_REGULAR = 'RobotoSerif_SemiExpanded-Regular', + ROBOTO_SERIF_SEMI_EXPANDED_SEMI_BOLD = 'RobotoSerif_SemiExpanded-SemiBold', + ROBOTO_SERIF_SEMI_EXPANDED_SEMI_BOLD_ITALIC = 'RobotoSerif_SemiExpanded-SemiBoldItalic', + ROBOTO_SERIF_SEMI_EXPANDED_THIN = 'RobotoSerif_SemiExpanded-Thin', + ROBOTO_SERIF_SEMI_EXPANDED_THIN_ITALIC = 'RobotoSerif_SemiExpanded-ThinItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_BLACK = 'RobotoSerif_UltraCondensed-Black', + ROBOTO_SERIF_ULTRA_CONDENSED_BLACK_ITALIC = 'RobotoSerif_UltraCondensed-BlackItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_BOLD = 'RobotoSerif_UltraCondensed-Bold', + ROBOTO_SERIF_ULTRA_CONDENSED_BOLD_ITALIC = 'RobotoSerif_UltraCondensed-BoldItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_EXTRA_BOLD = 'RobotoSerif_UltraCondensed-ExtraBold', + ROBOTO_SERIF_ULTRA_CONDENSED_EXTRA_BOLD_ITALIC = 'RobotoSerif_UltraCondensed-ExtraBoldItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_EXTRA_LIGHT = 'RobotoSerif_UltraCondensed-ExtraLight', + ROBOTO_SERIF_ULTRA_CONDENSED_EXTRA_LIGHT_ITALIC = 'RobotoSerif_UltraCondensed-ExtraLightItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_ITALIC = 'RobotoSerif_UltraCondensed-Italic', + ROBOTO_SERIF_ULTRA_CONDENSED_LIGHT = 'RobotoSerif_UltraCondensed-Light', + ROBOTO_SERIF_ULTRA_CONDENSED_LIGHT_ITALIC = 'RobotoSerif_UltraCondensed-LightItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_MEDIUM = 'RobotoSerif_UltraCondensed-Medium', + ROBOTO_SERIF_ULTRA_CONDENSED_MEDIUM_ITALIC = 'RobotoSerif_UltraCondensed-MediumItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_REGULAR = 'RobotoSerif_UltraCondensed-Regular', + ROBOTO_SERIF_ULTRA_CONDENSED_SEMI_BOLD = 'RobotoSerif_UltraCondensed-SemiBold', + ROBOTO_SERIF_ULTRA_CONDENSED_SEMI_BOLD_ITALIC = 'RobotoSerif_UltraCondensed-SemiBoldItalic', + ROBOTO_SERIF_ULTRA_CONDENSED_THIN = 'RobotoSerif_UltraCondensed-Thin', + ROBOTO_SERIF_ULTRA_CONDENSED_THIN_ITALIC = 'RobotoSerif_UltraCondensed-ThinItalic', + ROBOTO_SLAB_BLACK = 'RobotoSlab-Black', + ROBOTO_SLAB_BOLD = 'RobotoSlab-Bold', + ROBOTO_SLAB_EXTRA_BOLD = 'RobotoSlab-ExtraBold', + ROBOTO_SLAB_EXTRA_LIGHT = 'RobotoSlab-ExtraLight', + ROBOTO_SLAB_LIGHT = 'RobotoSlab-Light', + ROBOTO_SLAB_MEDIUM = 'RobotoSlab-Medium', + ROBOTO_SLAB_REGULAR = 'RobotoSlab-Regular', + ROBOTO_BLACK = 'Roboto-Black', + ROBOTO_BLACK_ITALIC = 'Roboto-BlackItalic', + ROBOTO_BOLD = 'Roboto-Bold', + ROBOTO_BOLD_ITALIC = 'Roboto-BoldItalic', + ROBOTO_LIGHT = 'Roboto-Light', + ROBOTO_LIGHT_ITALIC = 'Roboto-LightItalic', + ROBOTO_MEDIUM = 'Roboto-Medium', + ROBOTO_MEDIUM_ITALIC = 'Roboto-MediumItalic', + ROBOTO_REGULAR = 'Roboto-Regular', + ROBOTO_REGULAR_ITALIC = 'Roboto-RegularItalic', + ROBOTO_THIN = 'Roboto-Thin', + ROBOTO_THIN_ITALIC = 'Roboto-ThinItalic', +} + +/** + * Enumeration of the recorder label position. Can be passed via the [RecorderLabels.position] parameter. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare enum RecorderLabelPosition { + TOP_LEFT = 'top left', + TOP_CENTER = 'top center', + TOP_RIGHT = 'top right', + MIDDLE_LEFT = 'middle left', + MIDDLE_CENTER = 'middle center', + MIDDLE_RIGHT = 'middle right', + BOTTOM_LEFT = 'bottom left', + BOTTOM_CENTER = 'bottom center', + BOTTOM_RIGHT = 'bottom right', +} + +/** + * Enumeration of the recorder label text alignment. Can be passed via the [RecorderLabels.textAlign] parameter. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare enum RecorderLabelTextAlign { + TOP_LEFT = 'T+L', + TOP_CENTER = 'T+C', + TOP_RIGHT = 'T+R', + MIDDLE_LEFT = 'M+L', + MIDDLE_CENTER = 'M+C', + MIDDLE_RIGHT = 'M+R', + BOTTOM_LEFT = 'B+L', + BOTTOM_CENTER = 'B+C', + BOTTOM_RIGHT = 'B+R', +} + +/** + * An object that specifies video frame with the participants' name settings. Can be passed via the [RecorderVideoParameters.labels] or [UpdateRecorderVideoParameters.labels] parameter. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare interface RecorderLabels { + /** + * Optional. Participant's label font. + * The default value is **[RecorderLabelFont.ROBOTO_REGULAR]** + */ + font?: RecorderLabelFont; + /** + * Optional. Participant's label text horizontal and vertical alignment. + * The default value is **[RecorderLabelTextAlign.MIDDLE_LEFT]** + */ + textAlign?: RecorderLabelTextAlign; + /** + * Optional. Margin space outside the label in pixels. + * The default value is **8** + */ + margin?: number; + /** + * Optional. Participant's label position. + * The default value is **[RecorderLabelPosition.BOTTOM_RIGHT]** + */ + position?: RecorderLabelPosition; + /** + * Optional. Participant's label background color in HEX format. + * The default value is **#c7c7cc** + */ + background?: string; + /** + * Optional. Participant's label color in HEX format. + * The default value is **#000000** + */ + color?: string; + /** + * Optional. Participant's label width in pixels. + * The default value is **104** + */ + width?: number; + /** + * Optional. Participant's label height in pixels. + * The default value is **24** + */ + height?: number; +} + +/** + * An object that specifies video frame layout priority. Can be passed via the [CallRecordParameters.videoParameters] and [RecorderParameters.videoParameters] parameter. + *
+ * Add the following line to your scenario code to use the type: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare type RecorderLayoutPriority = 'vad' | string[]; + +/** + * An object that specifies video layout settings. Can be passed via the [RecorderVideoParameters.layoutSettings] and [UpdateRecorderVideoParameters.layoutSettings] parameter. + *
+ * Add the following line to your scenario code to use the type: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare enum RecorderLayout { + grid = 'grid', + tribune = 'tribune', + custom = 'custom', +} +/** + * An object that specifies how to fill a participant's video source to the conference frame. Can be passed via the [RecorderVideoParameters.layoutSettings] and [UpdateRecorderVideoParameters.layoutSettings] parameter. + *
+ * Add the following line to your scenario code to use the type: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare enum RecorderObjectFit { + fill = 'fill', + contain = 'contain', + cover = 'cover', + none = 'none', +} + +/** + * [Recorder] and [ConferenceRecorder] parameters. Can be passed as arguments to the [VoxEngine.createRecorder] method. + */ +declare interface RecorderParameters extends BaseRecorderParameters { + /** + * Optional. Name of the recorder for the call history. + */ + name?: string; + /** + * Optional. Speech recognition provider. + */ + provider?: + | ASRProfileList.Amazon + | ASRProfileList.Deepgram + | ASRProfileList.Google + | ASRProfileList.Microsoft + | ASRProfileList.SaluteSpeech + | ASRProfileList.TBank + | ASRProfileList.Yandex + | ASRProfileList.YandexV3; +} + +/** + * Enumeration of the video quality profiles. Can be passed via the [RecorderVideoParameters.profile] parameter. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare enum RecorderProfile { + NHD = 'NHD', + VGA = 'VGA', + HD = 'HD', + FHD = 'FHD', + QHD = 'QHD', + '4K' = '4K', +} + +/** + * An object that specifies speaking participant highlight video frame parameters. Can be passed via the [RecorderVideoParameters.vad] or [UpdateRecorderVideoParameters.vad] parameter. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare interface RecorderVad { + /** + * Optional. Highlighting frame thickness in pixels. For **width > 1280** the default value is **3**, for **width < 1280** the default value is **1**. + */ + thickness?: number; + /** + * Optional. Highlighting frame color in HEX format. The default value is **#009933**. + */ + color?: string; +} + +/** + * An object that specifies recorder video parameters. Can be passed via the [CallRecordParameters.videoParameters] and [RecorderParameters.videoParameters] parameter. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare interface RecorderVideoParameters extends UpdateRecorderVideoParameters { + /** + * Whether to create single video file of multiple participants. + */ + mixing: boolean; + /** + * Optional. Video quality profile. + */ + profile?: RecorderProfile; + /** + * Optional. Video width in pixels. + */ + width?: number; + /** + * Optional. Video height in pixels. + */ + height?: number; + /** + * Optional. Video bitrate in kbps. + */ + bitrate?: number; + /** + * Optional. Video frames per second. + */ + fps?: number; +} + +/** + * Represents an audio and video recorder. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare class Recorder { + /** + * Adds a handler for the specified [RecorderEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [RecorderEvents.Stopped]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: RecorderEvents | T, + callback: (event: _RecorderEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [RecorderEvents] event. + * @param event Event class (i.e., [RecorderEvents.Stopped]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: RecorderEvents | T, + callback?: (event: _RecorderEvents[T]) => any + ): void; + + /** + * Returns the recorder's id. + */ + id(): string; + + /** + * Whether to mute whole record without detaching media sources from it. + * @param doMute Mute/unmute switch + */ + mute(doMute: boolean): void; + + /** + * Stops recording and triggers the [RecorderEvents.Stopped] event. + */ + stop(): void; +} + +declare function require(module: Modules): void; + +/** + * Custom parameters for [WebSocket] interaction. Can be passed as arguments to the [VoxMediaUnit] **sendMediaTo** method. + */ +declare interface SendMediaParameters { + tag: string; + customParameters?: any; + encoding?: WebSocketAudioEncoding; +} + +/** + * @event + */ +declare enum SequencePlayerEvents { + /** + * Triggered when [SequencePlayer] created. + * @typedef _SequencePlayerCreatedEvent + */ + Created = 'SequencePlayer.Created', + + /** + * Triggered by the [VoxEngine.createSequencePlayer] methods when
+ * 1) all the segments audio files download to the Voximplant cache is finished;
+ * 2) all the audio files is found in the cache (i.e., it is in the cache before). + * @typedef _SequencePlayerPlaybackReadyEvent + */ + PlaybackReady = 'SequencePlayer.PlaybackReady', + + /** + * Triggered when playback of the first SequencePlayer segment starts. + * @typedef _SequencePlayerStartedEvent + */ + Started = 'SequencePlayer.Started', + + /** + * Triggers as a result of the [SequencePlayer.stop] method call. + * @typedef _SequencePlayerStoppedEvent + */ + Stopped = 'SequencePlayer.Stopped', + + /** + * Triggered when playback has finished successfully or with an error. + * @typedef _SequencePlayerPlaybackFinishedEvent + */ + PlaybackFinished = 'SequencePlayer.PlaybackFinished', + + /** + * Triggered when playback has finished with an error + * @typedef _SequencePlayerErrorEvent + */ + Error = 'SequencePlayer.Error', + + /** + * Triggered when [SequencePlayer.addMarker] is reached. + * @typedef _SequencePlayerPlaybackMarkerReachedEvent + */ + PlaybackMarkerReached = 'SequencePlayer.PlaybackMarkerReached', +} + +/** + * @private + */ +declare interface _SequencePlayerEvents { + [SequencePlayerEvents.Created]: _SequencePlayerCreatedEvent; + [SequencePlayerEvents.PlaybackReady]: _SequencePlayerPlaybackReadyEvent; + [SequencePlayerEvents.Started]: _SequencePlayerStartedEvent; + [SequencePlayerEvents.Stopped]: _SequencePlayerStoppedEvent; + [SequencePlayerEvents.Error]: _SequencePlayerErrorEvent; + [SequencePlayerEvents.PlaybackFinished]: _SequencePlayerPlaybackFinishedEvent; + [SequencePlayerEvents.PlaybackMarkerReached]: _SequencePlayerPlaybackMarkerReachedEvent; +} + +/** + * @private + */ +declare interface _SequencePlayerEvent { + /** + * Sequence player that generated the event + */ + sequencePlayer: SequencePlayer; +} + +/** + * @private + */ +declare interface _SequencePlayerCreatedEvent extends _SequencePlayerEvent {} + +/** + * @private + */ +declare interface _SequencePlayerPlaybackReadyEvent extends _SequencePlayerEvent {} + +/** + * @private + */ +declare interface _SequencePlayerStartedEvent extends _SequencePlayerEvent {} + +/** + * @private + */ +declare interface _SequencePlayerStoppedEvent extends _SequencePlayerEvent {} + +/** + * @private + */ +declare interface _SequencePlayerPlaybackFinishedEvent extends _SequencePlayerEvent { + /** + * Error message + */ + error?: string; +} + +/** + * @private + */ +declare interface _SequencePlayerErrorEvent extends _SequencePlayerEvent { + /** + * Error message + */ + error: string; +} + +/** + * @private + */ +declare interface _SequencePlayerPlaybackMarkerReachedEvent extends _SequencePlayerEvent { + /** + * The marker offset + */ + offset: number; +} + +/** + * Sequence player segment, represented by TTS or URL [Player]. Can be passed via the [SequencePlayerParameters.segments] parameter. + */ +declare type SequencePlayerSegment = TTSPlayerSegment | URLPlayerSegment; + +/** + * Represents an instance with segments represented by audio and URL players. + *
+ * Can be used by calling the [VoxEngine.createSequencePlayer] method. + */ +declare class SequencePlayer { + /** + * Adds a handler for the specified [SequencePlayerEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [SequencePlayerEvents.PlaybackFinished]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: SequencePlayerEvents | T, + callback: (event: _SequencePlayerEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [SequencePlayerEvents] event. + * @param event Event class (i.e., [SequencePlayerEvents.PlaybackFinished]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: SequencePlayerEvents | T, + callback?: (event: _SequencePlayerEvents[T]) => any + ): void; + + /** + * Returns the sequence player's id. + */ + id(): string; + + /** + * Pauses playback. To continue the playback use the [SequencePlayer.resume] method. + */ + pause(): void; + + /** + * Resumes playback after the [SequencePlayer.pause] method is called. + */ + resume(): void; + + /** + * Stops playback. The current sequence player's instance with all its segments is destroyed. + */ + stop(): void; + + /** + * Adds a playback marker to the specified segment. The [SequencePlayerEvents.PlaybackMarkerReached] event is triggered when the marker is reached. + * @param offset Positive/negative offset in milliseconds from the start/end of media + * @param segment The segment to add the marker + */ + addMarker(offset: number, segment: PlaybackParameters): void; + + /** + * Starts sending media from the sequence player to the media unit. + * @param mediaUnit Media unit that receives media + * @param parameters Optional. WebSocket interaction only parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media from the sequence player to the media unit. + * @param mediaUnit Media unit that does not need to receive media from this conference anymore + */ + stopMediaTo(mediaUnit: VoxMediaUnit): void; +} + +/** + * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.SmartQueue); + * ``` + * @event + */ +declare enum SmartQueueEvents { + /** + * The task is waiting for distribution to an agent. This event occurs every 10 or 15 seconds and contains information about task's position in a queue and approximate response time. + * @typedef _SmartQueueWaitingEvent + */ + Waiting = 'SmartQueue.Waiting', + /** + * An agent responded to the task, e.g. answered the call. This event indicates that SmartQueue processed the task successfully. + * @typedef _SmartQueueOperatorReachedEvent + */ + OperatorReached = 'SmartQueue.OperatorReached', + /** + * The task has been enqueued successfully. + * @typedef _SmartQueueEnqueueSuccessEvent + */ + EnqueueSuccess = 'SmartQueue.EnqueueSuccess', + /** + * SmartQueue distributed the task to an agent. This event can occur multiple times if an agent does not respond during the timeout. + * @typedef _SmartQueueTaskDistributedEvent + */ + TaskDistributed = 'SmartQueue.DistributeTask', + /** + * The client disconnected.

When you process the ClientDisconnected event, call the e.cancel() method inside the event manually to cancel the task. + * @typedef _SmartQueueClientDisconnectedEvent + */ + ClientDisconnected = 'SmartQueue.ClientDisconnected', + /** + * The task is cancelled. + * @typedef _SmartQueueTaskCanceledEvent + */ + TaskCanceled = 'SmartQueue.TaskCanceled', + /** + * An error occurred. + * @typedef _SmartQueueErrorEvent + */ + Error = 'SmartQueue.Error', +} + +/** + * @private + */ +declare interface _SmartQueueEvents { + /** + * The task is waiting for distribution to an agent. This event occurs every 10 or 15 seconds and contains information about task's position in a queue and approximate response time. + */ + [SmartQueueEvents.Waiting]: _SmartQueueWaitingEvent; + /** + * An agent responded to the task, e.g. answered the call. This event indicates that SmartQueue processed the task successfully. + */ + [SmartQueueEvents.OperatorReached]: _SmartQueueOperatorReachedEvent; + /** + * The task has been enqueued successfully. + */ + [SmartQueueEvents.EnqueueSuccess]: _SmartQueueEnqueueSuccessEvent; + /** + * SmartQueue distributed the task to an agent. This event can occur multiple times if an agent does not respond during the timeout. + */ + [SmartQueueEvents.TaskDistributed]: _SmartQueueTaskDistributedEvent; + /** + * The task has ended because the client disconnected. + */ + [SmartQueueEvents.ClientDisconnected]: _SmartQueueClientDisconnectedEvent; + /** + * The task is cancelled. + */ + [SmartQueueEvents.TaskCanceled]: _SmartQueueTaskCanceledEvent; + /** + * An error occurred. + */ + [SmartQueueEvents.Error]: _SmartQueueErrorEvent; +} + +/** + * @private + */ +declare interface _SmartQueueEvent { + /** + * A [SmartQueue] task + */ + task: SmartQueueTask; +} + +/** + * @private + */ +declare interface _SmartQueueWaitingEvent extends _SmartQueueEvent { + /** + * Estimated time of agent's response in milliseconds + */ + ewt: number; + /** + * The task's position in the queue + */ + position: number; + /** + * The task's waiting code + */ + code: TaskWaitingCode; + /** + * The task's waiting status + */ + message: string; +} + +/** + * @private + */ +declare interface _SmartQueueOperatorReachedEvent extends _SmartQueueEvent { + /** + * The agent's Call object + */ + agentCall: Call; +} + +/** + * @private + */ +declare interface _SmartQueueEnqueueSuccessEvent extends _SmartQueueEvent {} + +/** + * @private + */ +declare interface _SmartQueueTaskDistributedEvent extends _SmartQueueEvent { + /** + * The id of the task's responsible agent + */ + operatorId: number; + /** + * The name of the task's responsible agent + */ + operatorName: string; +} + +/** + * @private + */ +declare interface _SmartQueueClientDisconnectedEvent extends _SmartQueueEvent { + /** + * Cancels the pending request and removes it from the queue + */ + cancel: Function; +} + +/** + * @private + */ +declare interface _SmartQueueTaskCanceledEvent extends _SmartQueueEvent { + /** + * The [SmartQueue] termination status + */ + status: TerminationStatus; + /** + * The [SmartQueue] task's error description + */ + description: string; +} + +/** + * @private + */ +declare interface _SmartQueueErrorEvent extends _SmartQueueEvent { + /** + * The [SmartQueue] error code + */ + type: TerminationStatus; + /** + * The [SmartQueue] task's error description + */ + description: string; +} + +/** + * The [SmartQueue] task distribution mode. Can be passed via the [SmartQueueOperatorSettings.mode] parameter. + */ +declare enum SmartQueueOperatorSettingsMode { + /** + * Cancels the task if it is impossible to select the specific operator + */ + STRICT = 'STRICT', + /** + * Distributes the task to another operator if it is impossible to select the specific operator + */ + SMART = 'SMART', +} + +/* + * The [SmartQueueTask] operator settings. Can be passed via the [SmartQueueTaskParameters.operatorSettings] parameter. + */ +declare interface SmartQueueOperatorSettings { + /** + * Operator's id. + */ + operatorId: number; + /** + * Task distribution mode. + */ + mode: SmartQueueOperatorSettingsMode; + /** + * Timeout in seconds to search for a specific operator. The default value is **0**. + */ + timeout: number; +} + +/* + * The [SmartQueue] skill level is used to characterize an agent or a requirement for a task. Can be passed via the [SmartQueueTaskParameters.skills] parameter. + */ +declare interface SmartQueueSkill { + /** + * A readable skill name. + */ + name: string; + /** + * The skill level from 1 to 5. + */ + level: (1 | 2 | 3 | 4 | 5)[]; +} + +/* + * Settings of a certain [SmartQueueTask]. Can be passed as arguments to the [VoxEngine.enqueueTask] method. + */ +declare interface SmartQueueTaskParameters { + /** + * Current task's Call object. + */ + call?: Call; + /** + * Task's operator settings. + */ + operatorSettings?: SmartQueueOperatorSettings; + /** + * A timeout in seconds for the task to be accepted by an agent. + */ + timeout: number; + /** + * The task's priority. Accept values from 1 to 100. The default value is **50**. + */ + priority: number; + /** + * Required [skills](/docs/references/voxengine/smartqueueskill) for the task. + */ + skills: SmartQueueSkill[]; + /** + * Queue for the current task. + */ + queue: SmartQueue; + /** + * Custom data text string for the current task. After you specify the data in this field, you can find it in the [SmartQueueState_Task](/docs/references/httpapi/structure/smartqueuestate_task) object for this task. To get this object, call the [GetSQState](/docs/references/httpapi/smartqueue#getsqstate) method. + */ + customData: string; + /** + * Optional. Custom parameters (SIP headers) to be passed with the task. Custom header names have to begin with the 'X-' prefix. The "X-" headers can be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + */ + extraHeaders?: { [header: string]: string }; + /** + * Whether the call has video support. Please note that prices for audio only and video calls are different. + */ + video: boolean; + /** + * Internal information about codecs. + */ + scheme: string; + /** + * Maximum possible video bitrate for the customer device in kbps + */ + maxVideoBitrate: number; +} + +/* + * A [SmartQueueTask] status enumeration value. + */ +declare interface SmartQueueTaskStatus { + /** + * Smartqueue is distributing the task to a suitable agent. + */ + DISTRIBUTING: 'DISTRIBUTING'; + /** + * Smartqueue is connecting the task to an agent. + */ + CONNECTING: 'CONNECTING'; + /** + * The agent connected to the task. + */ + CONNECTED: 'CONNECTED'; + /** + * The agent has ended the task. + */ + ENDED: 'ENDED'; + /** + * An error occurred. + */ + FAILED: 'FAILED'; +} + +/** + * A [SmartQueue] task is for a certain agent, which can be a call or a chat. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.SmartQueue); + * ``` + */ +declare class SmartQueueTask { + /** + * Current status of the task, whether it is distributing, connecting, connected, ended or failed. + */ + status: SmartQueueTaskStatus; + /** + * Reason of task's termination. + */ + terminationStatus: TerminationStatus | null; + /** + * The client's Call object. + */ + clientCall: Call | null; + /** + * The agent's Call object. + */ + agentCall: Call | null; + /** + * SmartQueue task's settings, such as required skills, priority, queue and more. + */ + settings: SmartQueueTaskParameters; + /** + * A [SmartQueue] task's ID. + */ + id: string; + + /** + * Ends the current task. + */ + end(description: string): void; + + /** + * Adds a handler for the specified [SmartQueueEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [SmartQueueEvents.OperatorReached]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: SmartQueueEvents | T, + callback: (event: _SmartQueueEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [SmartQueueEvents] event. + * @param event Event class (i.e., [SmartQueueEvents.OperatorReached]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: SmartQueueEvents | T, + callback?: (event: _SmartQueueEvents[T]) => any + ): void; +} + +/* + * A [SmartQueue] object. + */ +declare interface SmartQueue { + /** + * Queue's identification number. + */ + id: number; + /** + * Queue's name. + */ + name: string; +} + +/** + * The parameters can be passed as arguments to the [Call.startPlayback] method. + */ +declare interface StartPlaybackParameters { + /* + * Whether to loop playback. + */ + loop?: boolean; + /** + * Whether to use progressive playback. If **true**, the file is delivered in chunks which reduces delay before a method call and playback. The default value is **false**. + */ + progressivePlayback?: boolean; +} + +/** + * Result of the [get](/docs/references/voxengine/applicationstorage#get), [put](/docs/references/voxengine/applicationstorage#put), and [delete](/docs/references/voxengine/applicationstorage#delete) methods. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.ApplicationStorage); + * ``` + */ +declare interface StorageKey { + /** + * Key name + */ + key: string; + /** + * Key value + */ + value: string; + /** + * Expiration date based on **ttl** specified via the [put](/docs/references/voxengine/applicationstorage#put) method + */ + expireAt: number; +} + +/** + * Result of the [keys](/docs/references/voxengine/applicationstorage#keys) method. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.ApplicationStorage); + * ``` + */ +declare interface StoragePage { + /** + * Array of keys + */ + keys: string[]; +} + +/** + * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.StreamingAgent); + * ``` + * @event + */ +declare enum StreamingAgentEvents { + /** + * Triggered when a streaming object is connected to a streaming platform. + * @typedef _StreamingAgentConnectedEvent + */ + Connected = 'StreamingAgent.Connected', + /** + * Triggered when connection to a streaming platform is failed. + * @typedef _StreamingAgentConnectionFailedEvent + */ + ConnectionFailed = 'StreamingAgent.ConnectionFailed', + /** + * Triggers if a streaming object cannot be created, *e.g., due to incorrect server url*. + * @typedef _StreamingAgentErrorEvent + */ + Error = 'StreamingAgent.Error', + /** + * Triggered when a streaming object is disconnected from a streaming platform. + * @typedef _StreamingAgentDisconnectedEvent + */ + Disconnected = 'StreamingAgent.Disconnected', + /** + * Triggered when a stream is successfully started. + * @typedef _StreamingAgentStreamStartedEvent + */ + StreamStarted = 'StreamingAgent.Stream.Started', + /** + * Triggered when a stream is stopped. + * @typedef _StreamingAgentStreamStoppedEvent + */ + StreamStopped = 'StreamingAgent.Stream.Stopped', + /** + * Triggered when a streaming object caused an error, *e.g., due to a codec mismatch*. + * @typedef _StreamingAgentStreamErrorEvent + */ + StreamError = 'StreamingAgent.Stream.Error', + /** + * Triggered when there is audio data in the stream. + * @typedef _StreamingAgentAudioStreamCreatedEvent + */ + AudioStreamCreated = 'StreamingAgent.Stream.AudioStarted', + /** + * Triggered when there is video data in the stream. + * @typedef _StreamingAgentVideoStreamCreatedEvent + */ + VideoStreamCreated = 'StreamingAgent.Stream.VideoStarted', + /** + * Triggered when the audio stream is switched. + * @typedef _StreamingAgentAudioSwitchedEvent + */ + AudioSwitched = 'StreamingAgent.Stream.AudioSwitched', + /** + * Triggered when the video stream is switched. + * @typedef _StreamingAgentVideoSwitchedEvent + */ + VideoSwitched = 'StreamingAgent.Stream.VideoSwitched', +} + +/** + * @private + */ +declare interface _StreamingAgentEvents { + [StreamingAgentEvents.Connected]: _StreamingAgentConnectedEvent; + [StreamingAgentEvents.ConnectionFailed]: _StreamingAgentConnectionFailedEvent; + [StreamingAgentEvents.Error]: _StreamingAgentErrorEvent; + [StreamingAgentEvents.Disconnected]: _StreamingAgentDisconnectedEvent; + [StreamingAgentEvents.StreamStarted]: _StreamingAgentStreamStartedEvent; + [StreamingAgentEvents.StreamStopped]: _StreamingAgentStreamStoppedEvent; + [StreamingAgentEvents.StreamError]: _StreamingAgentStreamErrorEvent; + [StreamingAgentEvents.AudioStreamCreated]: _StreamingAgentAudioStreamCreatedEvent; + [StreamingAgentEvents.VideoStreamCreated]: _StreamingAgentVideoStreamCreatedEvent; + [StreamingAgentEvents.AudioSwitched]: _StreamingAgentAudioSwitchedEvent; + [StreamingAgentEvents.VideoSwitched]: _StreamingAgentSwitchedEvent; +} + +/** + * @private + */ +declare interface _StreamingAgentEvent { + /** + * Streaming object that triggered the event. + */ + streamingAgent: StreamingAgent; +} + +/** + * @private + */ +declare interface _StreamingAgentReasonedEvent extends _StreamingAgentEvent { + /** + * Reason why the stream is switched. Possible values are: **New stream**, **Set stream**. + */ + reason: string; +} + +/** + * @private + */ +declare interface _StreamingAgentConnectedEvent extends _StreamingAgentReasonedEvent {} + +/** + * @private + */ +declare interface _StreamingAgentConnectionFailedEvent extends _StreamingAgentReasonedEvent {} + +/** + * @private + */ +declare interface _StreamingAgentErrorEvent extends _StreamingAgentReasonedEvent {} + +/** + * @private + */ +declare interface _StreamingAgentDisconnectedEvent extends _StreamingAgentReasonedEvent {} + +/** + * @private + */ +declare interface _StreamingAgentStreamEvent extends _StreamingAgentReasonedEvent { + /** + * Name of a streaming object that triggered the event. + */ + streamName: string; +} + +/** + * @private + */ +declare interface _StreamingAgentStreamStartedEvent extends _StreamingAgentStreamEvent {} + +/** + * @private + */ +declare interface _StreamingAgentStreamStoppedEvent extends _StreamingAgentStreamEvent {} + +/** + * @private + */ +declare interface _StreamingAgentStreamErrorEvent extends _StreamingAgentStreamEvent {} + +/** + * @private + */ +declare interface _StreamingAgentStreamCreatedEvent extends _StreamingAgentEvent { + /** + * Id of an audio or video track. Equals to -1 if there is no track. + */ + trackId: number; +} + +/** + * @private + */ +declare interface _StreamingAgentAudioStreamCreatedEvent + extends _StreamingAgentStreamCreatedEvent {} + +/** + * @private + */ +declare interface _StreamingAgentVideoStreamCreatedEvent + extends _StreamingAgentStreamCreatedEvent {} + +/** + * @private + */ +declare interface _StreamingAgentSwitchedEvent extends _StreamingAgentReasonedEvent { + /** + * Id of an audio or video track. Equals to -1 if there is no track. + */ + trackId: number; +} + +/** + * @private + */ +declare interface _StreamingAgentAudioSwitchedEvent extends _StreamingAgentSwitchedEvent {} + +/** + * @private + */ +declare interface _StreamingAgentVideoSwitchedEvent extends _StreamingAgentSwitchedEvent {} + +/** + * [StreamingAgent] parameters. Can be passed as arguments to the [VoxEngine.createStreamingAgent] method. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.StreamingAgent); + * ``` + */ +declare interface StreamingAgentParameters { + /** + * Protocol for streaming purposes. Currently only RTMP is supported. + */ + protocol: 'RTMP'; + /** + * URL of a server which processes video streaming. Can be retrieved from a live-streaming CDN provider. + */ + url: string; + /** + * URL of another streaming server to use if the server specified in **url** does not respond. + */ + backupUrl?: string; + /** + * Unique stream name, use along with the **url**. Can be retrieved from a live-streaming CDN provider. + */ + streamName?: string; + /** + * How often a keyframe in a stream is created (seconds). The default value is **2**. Any value less than "2" yields "2". + */ + keyframeInterval?: number; + /** + * Part of **streamName**, e.g, *live2*. The parameter is platform dependent, use it if it is required by the streaming platform. + */ + applicationName?: string; +} + +/** + * The [StreamingAgent] termination status + */ +declare interface StreamingAgentTrack { + /** + * The kind of the track + */ + kind: string; + /** + * The id of the track + */ + trackId: string; +} + +/** + * Represents a streaming object to interact with streaming platforms. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.StreamingAgent); + * ``` + */ +declare class StreamingAgent { + /** + * Adds a handler for the specified [StreamingAgentEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [StreamingAgentEvents.Connected]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: StreamingAgentEvents | T, + callback: (event: _StreamingAgentEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [StreamingAgentEvents] event. + * @param event Event class (i.e., [StreamingAgentEvents.Connected]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: StreamingAgentEvents | T, + callback?: (event: _StreamingAgentEvents[T]) => any + ): void; + + /** + * Returns the StreamingAgent's id. + */ + id(): string; + + /** + * Stops streaming. Triggers the StreamStopped event. Do not call any other streaming methods after a [StreamingAgent.stop] call. + */ + stop(): void; + + /** + * Gets the track ID of an active audio track or -1 if there is none. + */ + activeAudioTrack(): number; + + /** + * Gets the track ID of an active video track or -1 if there is none. + */ + activeVideoTrack(): number; + + /** + * Gets the list of all current audio tracks. + */ + audioTracks(): StreamingAgentTrack[]; + + /** + * Gets the list of all current video tracks. + */ + videoTracks(): StreamingAgentTrack[]; + + /** + * Sets a certain audio and/or video track as active. + * If an active video track is set, it is not replaced by the new one unlike in the default mode. + * Default mode: The active video track is the one that started sending data last. The active audio track is always the first one. + * To return to the default mode, set the track IDs equal to -1. + * @param tracks Audio and video track to set as active + */ + setActiveTrack(tracks: { audioTrack?: number; videoTrack?: number }): void; +} + +/** + * The [SmartQueue] task's waiting code + */ +declare enum TaskWaitingCode { + /** + * The task is not in a queue. + */ + NONE = 500, + /** + * The task is queued successfully, the estimated response time is calculated. + */ + SUCCESS = 200, + /** + * ETA for the task is not estimated. + */ + CANNOT_BE_ESTIMATED = 501, + /** + * The task is not queued because of the queue overflow. + */ + OVERFLOWED = 503, +} + +/** + * The [SmartQueue] termination status + */ +declare enum TerminationStatus { + /** + * The task is completed in the scenario (TaskCanceledCode) + */ + NORMAL = 1000, + /** + * The timeout for the task expired (TaskCanceledCode) + */ + TIMEOUT_REACHED = 1001, + /** + * Media server does not answer (TaskCanceledCode) + */ + MS_NOT_ANSWERED = 1002, + /** + * The customer has cancelled the task (EndTaskCode) + */ + CLIENT_TERMINATE = 1100, + /** + * The customer has finished the task (EndOperatorActivityCode) + */ + FINISHED_BY_CLIENT = 1201, + /** + * The agent has finished the task (EndOperatorActivityCode) + */ + FINISHED_BY_OPERATOR = 1202, + /** + * The agent has missed or declined the task (EndOperatorActivityCode) + */ + FAILED = 1203, + /** + * The task is cancelled in the scenario (EndOperatorActivityCode) + */ + CANCELED = 1204, + /** + * The task is transferred (EndOperatorActivityCode) + */ + TRANSFERRED = 1205, + /** + * An internal error occurred (ErrorCode, EndTaskCode, EndOperatorActivityCode) + */ + INTERNAL_ERROR = 500, +} + +/** + * [Player] parameters. Can be passed as arguments to the [VoxEngine.createToneScriptPlayer] method. + */ +declare interface ToneScriptPlayerParameters { + /** + * Optional. Whether to loop playback. + */ + loop?: boolean; + /** + * Optional. Whether to use progressive playback. If **true**, the generated tone is delivered in chunks which reduces delay before a method call and playback. The default value is **false**. + */ + progressivePlayback?: boolean; +} + +/** + * List of available values for the [CallRecordParameters.provider] parameter. + *
+ * Note that the T-Bank VoiceKit and Yandex Speechkit supports only 'ASRLanguage.RUSSIAN_RU' language. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ +declare enum TranscriptionProvider { + /** + * Google + * @const + */ + GOOGLE = 'google', + /** + * Yandex + * @const + */ + YANDEX = 'yandex', + /** + * T-Bank + * @const + */ + TBANK = 'tcs', +} +/** + * TTS [Player] parameters. Can be passed via the [SequencePlayerParameters.segments] parameter. + *
+ * Same as the [VoxEngine.createTTSPlayer] method arguments. Has a similar interface to [TTSPlaybackParameters](/docs/references/avatarengine/ttsplaybackparameters). + */ +declare interface TTSPlayerSegment { + /** + * Text to synthesize. + */ + text: string; + /** + * Optional. [Player](/docs/references/voxengine/player) parameters: language, progressivePlayback, volume, rate, etc. + */ + parameters?: TTSPlayerParameters; +} + +/** + * Updates the current video recorder options. Can be passed as arguments to the [ConferenceRecorder.update] method. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.Recorder); + * ``` + */ +declare interface UpdateRecorderVideoParameters { + /** + * Optional. Video layout settings. If set to **grid**, all the video frames are the same size. If set to **tribune**, one active video frame is bigger than the others. If set to **custom**, you need to provide a 'layout' option with an object that specifies custom layout settings. + */ + layout?: RecorderLayout; + /** + * Optional. If 'layout' option is set to **custom**, specifies custom video layout settings. + */ + layoutSettings?: RecorderDrawArea[]; + /** + * Optional. If 'layout' option is set to **tribune**, specifies which frame is bigger than the others. Set to **vad** if you want the bigger frame to change to the speaking participant, or specify the participant's ID to show one person constantly. + */ + layoutPriority?: RecorderLayoutPriority; + /** + * Optional. Whether to show the participants' names on their video frames. + */ + labels?: RecorderLabels; + /** + * Optional. Whether to highlight video frame of the speaking participant. + */ + vad?: RecorderVad; + /** + * Optional. HTML color code for the video file background. + */ + background?: string; + /** + * Optional. Video frame's direction, left to right or right to left. + */ + direction?: RecorderDirection; + /** + * Optional. How to fill a participant's video source to the conference frame. + */ + objectFit?: RecorderObjectFit; + /** + * Optional. A container to store custom data for the current recorder. + */ + customData?: Object; +} + +/** + * URL [Player] parameters. Can be passed via the [SequencePlayerParameters.segments] parameter. + *
+ * Same as the [VoxEngine.createURLPlayer] method arguments. Has a similar interface to [URLPlaybackParameters](/docs/references/avatarengine/urlplaybackparameters). + */ +declare interface URLPlayerSegment { + /** + * Url of an audio file. Supported formats are: mp3, ogg, flac, and wav (mp3, speex, vorbis, flac, and wav codecs respectively). Maximum file size is 10 Mb. + */ + url: string; + /** + * Optional. URL [Player](/docs/references/voxengine/player) parameters. + */ + parameters?: URLPlayerParameters; +} + +declare namespace VoxEngine { + /** + * Adds a handler for the specified [AppEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [AppEvents.Started]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + function addEventListener( + event: AppEvents | T, + callback: (event: _AppEvents[T]) => any + ): void; +} + +declare namespace VoxEngine { + /** + * Allows accepting incoming connections to ensure WebSocket bidirectional exchange. + */ + function allowWebSocketConnections(): void; +} + +declare namespace VoxEngine { + /** + * Makes a call to a conference via Conference module. If there is no such conference, it is created in the first method's call. The method is designed to be called in a simple incoming scenario, then it can trigger another special scenario which contains logic of the conference. + * The method can trigger the Failed event in 60 sec, see the [session limits](/docs/guides/voxengine/limits) for details. + * @param conferenceId ID of the conference. The parameter has to be the same as the pattern in the rule so the method triggers appropriate rule with conference logic + * @param callerid CallerID of the caller that is displayed to the user. Spaces usage is not allowed. Normally it is some phone number that can be used for callback. IMPORTANT: you cannot use test numbers rented from Voximplant as CallerID, use only real numbers + * @param displayName Name of the caller that is displayed to the user. Normally it is a human-readable version of CallerID, e.g. a person's name + * @param headers Optional. SIP headers to be passed with a call to conference. Custom header names have to begin with the 'X-' prefix. The "X-" headers can be handled by a SIP phone or WEB SDK (e.g. see the [incomingCall](/docs/references/websdk/voximplant/events#incomingcall) event). Example: {'X-header':'value'} + * @param scheme Optional. Internal information about codecs from the [AppEvents.CallAlerting] event + */ + function callConference( + conferenceId: string, + callerid: string, + displayName: string, + headers?: { [header: string]: string }, + scheme?: string + ): Call; +} + +declare namespace VoxEngine { + /** + * Starts an outgoing call to the specified phone number. Calls that are more expensive than 20 cents per minute and calls to Africa are blocked by default for security reasons. + * The method can trigger the [CallEvents.Failed] event in 60 sec, see the [session limits](/docs/guides/voxengine/limits) for details. + * @param number phone number to start a call to in the international format (E.164) + * @param callerid CallerID of the caller that is displayed to the user. Spaces usage is not allowed. A valid phone number that can be used to call back is required. Following phone numbers can be used: + * * A real phone number that is [rented](https://manage.voximplant.com/numbers/my_numbers) from Voximplant. **IMPORTANT**: test numbers cannot be used. + * * Any phone number that is [verified](https://manage.voximplant.com/settings/caller_ids) via an automated call from Voximplant and confirmation code. + * * A phone number from an incoming call to the rented number. It can be retrieved as [Caller ID](/docs/references/voxengine/call#callerid). + * @param parameters Optional. Call parameters + */ + function callPSTN(number: string, callerid: string, parameters?: CallPSTNParameters): Call; +} + +declare namespace VoxEngine { + /** + * Starts an outgoing call to the external SIP system or to another user of the same application. Supported codecs are: [G.722](https://www.itu.int/rec/T-REC-G.722), [G.711 (u-law and a-law)](https://www.itu.int/rec/T-REC-G.711), [Opus](https://opus-codec.org/), [ILBC](https://webrtc.org/license/ilbc-freeware/), [H.264](https://www.itu.int/rec/T-REC-H.264), [VP8](https://tools.ietf.org/html/rfc6386). The method can trigger the [CallEvents.Failed] event in 60 sec, see the [session limits](/docs/guides/voxengine/limits) for details. + * @param to SIP URI to make a call to. Example of an external call: **sip:alice@example.org**. Examples with TLS usage: **sips:alice@example.org:5061** ; **alice@example.org:5061;transport=tls**. The format for calls to another user of the same Voximplant application: user-of-the-application@application.account.voximplant.com + * @param parameters Call parameters. Note that if this parameter is not an object, it is treated as "callerid". Further parameters are treated as "displayName", "password", "authUser", "extraHeaders", "video", "outProxy" respectively + * @param scheme Internal information about codecs from the [AppEvents.CallAlerting] event + */ + function callSIP(to: string, parameters?: CallSIPParameters, scheme?: Object): Call; +} + +declare namespace VoxEngine { + /** + * Start an outgoing call to the specified Voximplant user in peer-to-peer mode. + * The JavaScript scenario with this method and the destination user should be both within the same Voximplant application. + * Audio playback and recording does not work. P2P mode is available only for calls between SDKs. + * The method can trigger the [CallEvents.Failed] event in 60 sec, see the [session limits](/docs/guides/voxengine/limits) for details. **IMPORTANT**: calling this method makes impossible to use the non-P2P mode for a new call and specified incomingCall. + * So the following methods cannot be used: [Call.say], [Call.sendDigits], [Call.sendMediaTo], [Call.stopMediaTo]. + * @param incomingCall incoming call that needs to be forwarded + * @param username Name of the Voximplant user to call + * @param parameters Call parameters + */ + function callUserDirect( + incomingCall: Call, + username: string, + parameters: CallUserDirectParameters + ): Call; +} + +declare namespace VoxEngine { + /** + * Starts an outgoing call to the specified Voximplant user. The JavaScript scenario that uses this method and user being called should be both associated with the same Voximplant application. + * The method can trigger the [CallEvents.Failed] event in 60 sec, see the [session limits](/docs/guides/voxengine/limits) for details. + * @param parameters Call parameters + */ + function callUser(parameters: CallUserParameters): Call; +} + +declare namespace VoxEngine { + /** + * Creates a new [ASR] (speech recognizer) instance and starts recognition. You can attach sources later via the [VoxMediaUnit] **sendMediaTo** method. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.ASR); + * ``` + * @param parameters ASR parameters. IMPORTANT: the **profile** parameter is required, the other parameters are optional + */ + function createASR(parameters: ASRParameters): ASR; +} + +declare namespace VoxEngine { + /** + * Creates a new [Conference] instance. You can attach media streams later via the [Conference.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.Conference); + * ``` + * @param parameters Conference parameters + */ + function createConference(parameters: ConferenceParameters): Conference; +} + +declare namespace VoxEngine { + /** + * Creates a new [Recorder] (audio recorder) or [ConferenceRecorder] (conference recorder) object. You can attach sources later via the [VoxMediaUnit] **sendMediaTo** method. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.Recorder); + * ``` + * @param parameters Recorder parameters. Note that if the first parameter is not an object, it is treated as 'name', with second optional parameter as 'secure' boolean flag, default to **false** + */ + function createRecorder(parameters?: RecorderParameters): Recorder | ConferenceRecorder; +} + +declare namespace VoxEngine { + /** + * Creates a new [SequencePlayer] instance. You can attach media streams later via the [SequencePlayer.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + * @param parameters Sequence player parameters + **/ + function createSequencePlayer(parameters: SequencePlayerParameters): SequencePlayer; +} + +declare namespace VoxEngine { + /** + * Creates a new [StreamingAgent](streaming) instance. You can attach sources later via the [VoxMediaUnit] **sendMediaTo** method. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.StreamingAgent); + * ``` + * @param parameters Streaming agent parameters + */ + function createStreamingAgent(parameters: StreamingAgentParameters): StreamingAgent; +} + +declare namespace VoxEngine { + /** + * Creates a new [Player](audio player) instance with the specified [ToneScript](https://en.wikipedia.org/wiki/ToneScript) sequence. You can attach media streams later via the [Player.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + * @param script ToneScript string + * @param parameters Optional. Tone script player parameters + **/ + function createToneScriptPlayer(script: string, parameters?: ToneScriptPlayerParameters): Player; +} + +declare namespace VoxEngine { + /** + * Creates a new [Player] (audio player) instance with specified text (TTS is used to play the text). You can attach media streams later via the [Player.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + * If the text length exceeds 1500 characters, the [PlayerEvents.PlaybackFinished] event is triggered with error description. After the very first playing, a phrase is cached; each createTTSPlayer instance stores the cache data up to 2 weeks. Note that cache addresses only the URL, without additional headers. The cached phrase is available for all applications and further sessions. + * @param text Text to synthesize + * @param parameters Optional. TTS player parameters + **/ + function createTTSPlayer(text: string, parameters?: TTSPlayerParameters): Player; +} + +declare namespace VoxEngine { + /** + * Creates a new [Player] (audio player) instance with specified audio file URL. You can attach media streams later via the [Player.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + *
+ * After the very first playback, a file is cached; each + * 'createURLPlayer' instance stores the cache data up to 2 weeks. + * Note that cache addresses only the URL, without additional headers. + * The cached file is available for all applications and further sessions. + *
+ * File download has a timeout of 12 seconds. Reaching this timeout causes the "Timeout is reached" error. + *
+ * You can attach media streams later via the [Player.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + * method etc. IMPORTANT: each call object can send media to any number of other calls (media units), but can receive only one audio stream. A new incoming stream always replaces the previous one. + * @param request URL player request + * @param parameters Optional. URL player parameters + **/ + function createURLPlayer(request: URLPlayerRequest, parameters?: URLPlayerParameters): Player; +} + +declare namespace VoxEngine { + /** + * Creates a new [WebSocket] object. You can attach media streams later via the [WebSocket.sendMediaTo] or [VoxEngine.sendMediaBetween] methods. + * @param url URL to connect **(wss:// + domain + path)** + * @param parameters Optional. [WebSocket] parameters + */ + function createWebSocket(url: string, parameters?: WebSocketParameters): WebSocket; +} + +declare namespace VoxEngine { + /** + * Set or get custom string associated with current JavaScript session. + * There are two kinds of the customData values: one is for JavaScript session (i.e., VoxEngine object), another is for the particular call (i.e., Call.customData and web SDK parameter of the method). + * It is possible to use them at the same time because they are independent entities. Remember that if you receive some value from web SDK, it does not overwrite the VoxEngine's value. Any of customData's type values can be later obtained from call history via management API or control panel. + * @param customData Optional. Custom session data to set. Maximum size is 200 bytes + */ + function customData(customData?: string): string; +} + +declare namespace VoxEngine { + /** + * Destroys an existing conference. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.Conference); + * ``` + * @param conference + */ + function destroyConference(conference: Conference): void; +} + +declare namespace VoxEngine { + /** + * Adds all default event listeners to pass signaling information between two calls. The source code of the method is available on [GitHub](https://github.com/voximplant/easyprocess). + * @param call1 incoming alerting call + * @param call2 Newly created outgoing call + * @param onEstablishedCallback Function to be called once the call is established. Both call1 and call2 are passed to this function as parameters + * @param direct Whether the call is in the P2P mode. It is The default value is **false** + */ + function easyProcess( + call1: Call, + call2: Call, + onEstablishedCallback?: (call1: Call, call2: Call) => void, + direct?: boolean + ): void; +} + +declare namespace VoxEngine { + /** + * Adds a new request to the specified queue. The request is dispatched to the free agent according to the agent's status (must be "Ready") and skills. + *
+ * Add the following line to your scenario code to use the function: + * ``` + * require(Modules.ACD); + * ``` + * @param queueName The name of the queue, to where the call is directed. Queue name must be specified exactly as in the control panel + * @param callerid ID of the caller which is put to the queue. After request is dispatched to the agent, it is possible to get this ID by assigning a handler to the [ACDEvents.OperatorReached] event. The call is stored in the operatorCall property, so you can use the Call.callerid() method. IMPORTANT: virtual numbers rented from Voximplant cannot be used as CallerID, use only real numbers + * @param parameters Optional. ACD request parameters + */ + function enqueueACDRequest( + queueName: string, + callerid: string, + parameters?: ACDEnqueueParameters + ): ACDRequest; +} + +declare namespace VoxEngine { + /** + * Appends the task to the [SmartQueue]. + */ + function enqueueTask(parameters: SmartQueueTaskParameters): SmartQueueTask; +} + +declare namespace VoxEngine { + /** + * Helper function to forward an incoming call to PSTN. The method handles numbers only in the E.164 format by default. If you need to handle a number in another format, pass an additional function (as a parameter) to the method. For more details see the [GitHub repo](https://github.com/voximplant/easyprocess). + * @param numberTransform Optional. Function used to transform dialed number to international format. This function accepts dialed number and returns phone number in E.164 format + * @param onEstablishedCallback Optional. Function that is invoked after a call is established. Both calls (incoming and outgoing) are passed to this function + * @param options An object with a number used as the callerid that is displayed to the callee. Whitespaces are not allowed. A valid phone number that can be used to call back if required + */ + function forwardCallToPSTN( + numberTransform?: (number: string) => string, + onEstablishedCallback?: (call1: Call, call2: Call) => void, + options?: { callerid: string } + ): void; +} + +declare namespace VoxEngine { + /** + * Helper function to forward an incoming call to a dialed SIP URI. For more details see the [GitHub repo](https://github.com/voximplant/easyprocess). + * @param onEstablishedCallback Optional. Function that is invoked after call is established. Both calls (incoming and outgoing) are passed to this function + * @param video Whether the call has video support. Please note that the price for audio-only and video calls is different! + */ + function forwardCallToSIP( + onEstablishedCallback?: (call1: Call, call2: Call) => void, + video?: boolean + ): void; +} + +declare namespace VoxEngine { + /** + * Helper function to forward an incoming call to a user of the current application in the P2P mode. Dialed number is considered as username. Due to the P2P mode, media player and recording do not work. For more details see the [GitHub repo](https://github.com/voximplant/easyprocess). + * @param onEstablishedCallback Optional. Function that is invoked after call is established. Both calls (incoming and outgoing) are passed to this function + */ + function forwardCallToUserDirect( + onEstablishedCallback?: (call1: Call, call2: Call) => void + ): void; +} + +declare namespace VoxEngine { + /** + * Helper function to forward an incoming call to a user of the current application. Dialed number is considered as username. For more details see the [GitHub repo](https://github.com/voximplant/easyprocess). + * @param onEstablishedCallback Optional. Function that is invoked after call is established. Both calls (incoming and outgoing) are passed to this function + * @param video Whether the call has video support. Please note that the price for audio-only and video calls is different! + */ + function forwardCallToUser( + onEstablishedCallback?: (call1: Call, call2: Call) => void, + video?: boolean + ): void; +} + +declare namespace VoxEngine { + /** + * Helper function to play sound to incoming call. It terminates a call in three cases: + * 1) playback is finished + * 2) call failed + * 3) call disconnected + * @param fileURL URL of audio (mp3) file to play + */ + function playSoundAndHangup(fileURL: string): void; +} + +declare namespace VoxEngine { + /** + * Removes a handler for the specified [AppEvents] event. + * @param event Event class (i.e., [AppEvents.Started]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + function removeEventListener( + event: AppEvents | T, + callback?: (event: _AppEvents[T]) => any + ): void; +} + +declare namespace VoxEngine { + /** + * Start sending media between mediaUnit1 and mediaUnit2. This method binds two audio/video streams. + * @param mediaUnit1 First media unit + * @param mediaUnit2 Second media unit + */ + function sendMediaBetween(mediaUnit1: VoxMediaUnit, mediaUnit2: VoxMediaUnit): void; +} + +/** + * [SequencePlayer] parameters. Can be passed as arguments to the [VoxEngine.createSequencePlayer] method. + */ +declare interface SequencePlayerParameters { + /** + * Array of the segments. + */ + segments: SequencePlayerSegment[]; +} + +declare namespace VoxEngine { + /** + * Stops sending media between mediaUnit1 and mediaUnit2. + * @param mediaUnit1 First media unit + * @param mediaUnit2 Second media unit + */ + function stopMediaBetween(mediaUnit1: VoxMediaUnit, mediaUnit2: VoxMediaUnit): void; +} + +declare namespace VoxEngine { + /** + * Terminates the current JavaScript session. All audio/video streams are disconnected and scenario execution stops. Note that after this function, only the [AppEvents.Terminating] and [AppEvents.Terminated] events are triggered. + * + * Note: if you are using this method inside a code block (e.g., an "if" block), it does not stop the execution of the current block. Use `return;` after using this method to exit the current code block. + */ + function terminate(): void; +} + +declare namespace VoxEngine {} + +declare namespace VoximplantAPI { + interface APIError { + /** + * The error code + */ + code: number; + /** + * The error description + */ + msg: string; + } + interface AccountInfo { + /** + * The account's ID + */ + accountId: number; + /** + * The account's name + */ + accountName: string; + /** + * The account's email + */ + accountEmail: string; + /** + * The account API key. Use password or api_key authentication to show the api_key + */ + apiKey?: string; + /** + * The first name + */ + accountFirstName?: string; + /** + * The last name + */ + accountLastName?: string; + /** + * The UTC account created time in 24-h format: YYYY-MM-DD HH:mm:ss + */ + created: Date; + /** + * The notification language code (2 symbols, ISO639-1). Examples: en, ru + */ + languageCode?: string; + /** + * The account location (timezone). Examples: America/Los_Angeles, Etc/GMT-8, Etc/GMT+10 + */ + location?: string; + /** + * The min balance value to notify by email or SMS + */ + minBalanceToNotify?: number; + /** + * Whether Voximplant notifications are required + */ + accountNotifications?: boolean; + /** + * Whether Voximplant plan changing notifications are required + */ + tariffChangingNotifications?: boolean; + /** + * Whether Voximplant news notifications are required + */ + newsNotifications?: boolean; + /** + * The company or businessman name + */ + billingAddressName?: string; + /** + * The billing address country code (2 symbols, ISO 3166-1 alpha-2). Examples: US, RU, GB + */ + billingAddressCountryCode?: string; + /** + * The office address + */ + billingAddressAddress?: string; + /** + * The office ZIP + */ + billingAddressZip?: string; + /** + * The office phone number + */ + billingAddressPhone?: string; + /** + * The office state (US) or province (Canada), up to 100 characters. Examples: California, Illinois, British Columbia + */ + billingAddressState?: string; + /** + * Whether the account is ctive + */ + active: boolean; + /** + * Whether account is blocked by Voximplant admins + */ + frozen?: boolean; + /** + * The account's money + */ + balance?: number; + /** + * The account's credit limit + */ + creditLimit?: number; + /** + * The currency code (USD, RUR, EUR, ...) + */ + currency?: string; + /** + * Whether Robokassa payments are allowed + */ + supportRobokassa?: boolean; + /** + * Whether Bank card payments are allowed + */ + supportBankCard?: boolean; + /** + * Whether Bank invoices are allowed + */ + supportInvoice?: boolean; + /** + * The custom data + */ + accountCustomData?: string; + /** + * The allowed access entries (the API function names) + */ + accessEntries?: string[]; + /** + * Whether the admin user permissions are granted + */ + withAccessEntries?: boolean; + /** + * If URL is specified, Voximplant cloud makes HTTP POST requests to it when something happens. For a full list of reasons see the type field of the [AccountCallback] structure. The HTTP request has a JSON-encoded body that conforms to the [AccountCallbacks] structure + */ + callbackUrl?: string; + /** + * If salt string is specified, each HTTP request made by the Voximplant cloud toward the callback_url has a salt field set to MD5 hash of account information and salt. That hash can be used be a developer to ensure that HTTP request is made by the Voximplant cloud + */ + callbackSalt?: string; + /** + * Whether to send an email when a JS error occures + */ + sendJsError?: boolean; + /** + * The payments limits applicable to each payment method + */ + billingLimits?: BillingLimits; + /** + * Whether to activate one-way SMS + */ + a2pSmsEnabled?: boolean; + } + interface BillingLimits { + /** + * The Robokassa limits + */ + robokassa?: BillingLimitInfo; + /** + * The bank card limits + */ + bankCard?: BankCardBillingLimitInfo; + /** + * The invoice limits + */ + invoice?: BillingLimitInfo; + } + interface BillingLimitInfo { + /** + * The minimum amount + */ + minAmount: number; + /** + * The currency + */ + currency: string; + } + interface BankCardBillingLimitInfo { + /** + * The minimum amount + */ + minAmount: number; + /** + * The currency + */ + currency: string; + } + interface ApplicationInfo { + /** + * The application ID + */ + applicationId: number; + /** + * The full application name + */ + applicationName: string; + /** + * The application editing UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified: Date; + /** + * Whether a secure storage for logs and records is enabled + */ + secureRecordStorage: boolean; + } + interface UserInfo { + /** + * The user ID + */ + userId: number; + /** + * The user name + */ + userName: string; + /** + * The display user name + */ + userDisplayName: string; + /** + * Whether the user is active. Inactive users cannot log in to applications + */ + userActive: boolean; + /** + * Whether the user uses the parent account's money, 'false' if the user has a separate balance + */ + parentAccounting: boolean; + /** + * The current user's money in the currency specified for the account. The value is the number rounded to 4 decimal places and it changes during the calls, transcribing, purchases etc + */ + liveBalance: number; + /** + * The current user's money in the currency specified for the account. The value is the number rounded to 4 decimal places. The parameter is the alias to live_balance by default. But there is a possibility to make the alias to fixed_balance: just to pass return_live_balance=false into the [GetAccountInfo] method + */ + balance: number; + /** + * The last committed balance which has been approved by billing's transaction + */ + fixedBalance: number; + /** + * The custom data + */ + userCustomData?: string; + /** + * The bound applications + */ + applications?: ApplicationInfo[]; + /** + * The bound skills + */ + skills?: SkillInfo[]; + /** + * The bound ACD queues + */ + acdQueues?: ACDQueueOperatorInfo[]; + /** + * The ACD operator status. The following values are possible: OFFLINE, ONLINE, READY, BANNED, IN_SERVICE, AFTER_SERVICE, TIMEOUT, DND + */ + acdStatus?: string; + /** + * The ACD status changing UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + acdStatusChangeTime: Date; + /** + * The user editing UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + created: Date; + /** + * The user editing UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified: Date; + } + interface SipWhiteListInfo { + /** + * The SIP white list item ID + */ + sipWhitelistId: number; + /** + * The network address in format A.B.C.D/L + */ + sipWhitelistNetwork: string; + /** + * The network address description + */ + description?: string; + } + interface CallSessionInfo { + /** + * The routing rule name + */ + ruleName: string; + /** + * The application name + */ + applicationName: string; + /** + * The unique JS session identifier + */ + callSessionHistoryId: number; + /** + * The account ID that initiates the JS session + */ + accountId: number; + /** + * The application ID that initiates the JS session + */ + applicationId: number; + /** + * The user ID that initiates the JS session + */ + userId: number; + /** + * The start date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + startDate: Date; + /** + * The entire JS session duration in seconds. The session can contain multiple calls + */ + duration?: number; + /** + * The initiator IP address + */ + initiatorAddress: string; + /** + * The media server IP address + */ + mediaServerAddress: string; + /** + * The link to the session log. The log retention policy is 1 month, after that time this field clears. If you have issues accessing the log file, check if the application has "Secure storage of applications and logs" feature enabled. In this case, you need to authorize. + */ + logFileUrl: string; + /** + * Finish reason. Possible values are __Normal termination__, __Insufficient funds__, __Internal error (billing timeout)__, __Terminated administratively__, __JS session error__, __Timeout__ + */ + finishReason?: string; + /** + * Calls within the JS session, including durations, cost, phone numbers and other information + */ + calls?: CallInfo[]; + /** + * Used resources + */ + otherResourceUsage?: ResourceUsage[]; + /** + * Bound records + */ + records?: Record[]; + /** + * Custom data + */ + customData?: string; + } + interface CallInfo { + /** + * The call history ID + */ + callId: number; + /** + * The start time in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + startTime: Date; + /** + * The call duration in seconds + */ + duration?: number; + /** + * The local number on the platform side + */ + localNumber: string; + /** + * The remote number on the client side + */ + remoteNumber: string; + /** + * The type of the remote number, such as PSTN, mobile, user or sip address + */ + remoteNumberType: string; + /** + * Whether the call is incoming + */ + incoming: boolean; + /** + * Whether the call is successful + */ + successful: boolean; + /** + * The transaction ID + */ + transactionId: number; + /** + * The record URL + */ + recordUrl?: string; + /** + * The media server IP address + */ + mediaServerAddress: string; + /** + * The call cost + */ + cost?: number; + /** + * The custom data passed to the JS session + */ + customData?: string; + } + interface TransactionInfo { + /** + * The transaction ID + */ + transactionId: number; + /** + * The account ID + */ + accountId: string; + /** + * The transaction date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + performedAt: Date; + /** + * The transaction amount, $ + */ + amount: number; + /** + * The amount currency (USD, RUR, EUR, ...). + */ + currency: string; + /** + * The transaction type. The following values are possible: gift_revoke, resource_charge, money_distribution, subscription_charge, subscription_installation_charge, card_periodic_payment, card_overrun_payment, card_payment, rub_card_periodic_payment, rub_card_overrun_payment, rub_card_payment, robokassa_payment, gift, promo, adjustment, wire_transfer, us_wire_transfer, refund, discount, mgp_charge, mgp_startup, mgp_business, mgp_big_business, mgp_enterprise, mgp_large_enterprise, techsupport_charge, tax_charge, monthly_fee_charge, grace_credit_payment, grace_credit_provision, mau_charge, mau_overrun, im_charge, im_overrun, fmc_charge, sip_registration_charge, development_fee, money_transfer_to_child, money_transfer_to_parent, money_acceptance_from_child, money_acceptance_from_parent, phone_number_installation, phone_number_charge, toll_free_phone_number_installation, toll_free_phone_number_charge, services, user_money_transfer, paypal_payment, paypal_overrun_payment, paypal_periodic_payment + */ + transactionType: string; + /** + * The transaction description + */ + transactionDescription?: string; + } + interface ResourceUsage { + /** + * The resource usage ID + */ + resourceUsageId: number; + /** + * The resource type. The possible values are CALLSESSION, VIDEOCALL, VIDEORECORD, VOICEMAILDETECTION, YANDEXASR, ASR, TRANSCRIPTION, TTS_TEXT_GOOGLE, TTS_YANDEX, AUDIOHDCONFERENCE + */ + resourceType: string; + /** + * The resource cost + */ + cost?: number; + /** + * The description + */ + description?: string; + /** + * The start resource using time in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + usedAt: Date; + /** + * The transaction ID + */ + transactionId: number; + /** + * The resource quantity + */ + resourceQuantity?: number; + /** + * The resource unit + */ + unit?: string; + /** + * The reference to call + */ + refCallId?: number; + } + interface Record { + /** + * The record ID + */ + recordId: number; + /** + * The record name + */ + recordName?: string; + /** + * The record cost + */ + cost?: number; + /** + * The start recording time in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + startTime: Date; + /** + * The call duration in seconds + */ + duration?: number; + /** + * The record URL. If you have issues accessing the record file, check if the application has "Secure storage of applications and logs" feature enabled. In this case, you need to authorize. + */ + recordUrl?: string; + /** + * The transaction ID + */ + transactionId: number; + /** + * The file size + */ + fileSize?: number; + /** + * Transcription URL. To open the URL, please add authorization parameters and record_id to it + */ + transcriptionUrl?: string; + /** + * The status of transcription. The possible values are Not required, In progress, Complete + */ + transcriptionStatus?: string; + } + interface QueueInfo { + /** + * The ACD queue ID + */ + acdQueueId: number; + /** + * The queue name + */ + acdQueueName: string; + /** + * The application ID + */ + applicationId?: number; + /** + * The integer queue priority. The highest priority is 0 + */ + acdQueuePriority: number; + /** + * The value in the range of [0.5 ... 1.0]. The value 1.0 means the service probability 100% in challenge with a lower priority queue + */ + serviceProbability: number; + /** + * Whether to enable the auto binding of operators to a queue by skills comparing + */ + autoBinding: boolean; + /** + * The maximum predicted waiting time in minutes. When a call is going to be enqueued to the queue, its predicted waiting time should be less or equal to the maximum predicted waiting time; otherwise, a call would be rejected + */ + maxWaitingTime?: number; + /** + * The maximum number of calls that can be enqueued into this queue + */ + maxQueueSize?: number; + /** + * The average service time in seconds. Specify the parameter to correct or initialize the waiting time prediction + */ + averageServiceTime?: number; + /** + * The ACD queue creating UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + created: Date; + /** + * The ACD queue editing UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified: Date; + /** + * The ACD queue deleting UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + deleted?: Date; + /** + * The queue users info + */ + users?: QueueUsers[]; + /** + * The queue skills info + */ + skills?: QueueSkills[]; + /** + * The service level thresholds in seconds + */ + slThresholds?: number[]; + /** + * Number of agents bound to the queue + */ + operatorcount?: number; + } + interface QueueSkills { + /** + * The skill ID + */ + skillId: number; + /** + * The skill name + */ + skillName: string; + } + interface QueueUsers { + /** + * The user ID + */ + userId: number; + } + interface ACDState { + /** + * The queues' states + */ + acdQueues: ACDQueueState[]; + } + interface ACDQueueState { + /** + * The ACD queue ID + */ + acdQueueId: number; + /** + * List of operators with the 'READY' state that can accept a call from this queue + */ + readyOperators: ACDReadyOperatorState[]; + /** + * Number of ready operators + */ + readyOperatorsCount: number; + /** + * List of operators with the 'READY' state that cannot accept a call from this queue. Operator cannot accept a call if they are temporarily banned or they are servicing a call right now + */ + lockedOperators: ACDLockedOperatorState[]; + /** + * Number of locked operators + */ + lockedOperatorsCount: number; + /** + * List of operators with the 'AFTER_SERVICE' state. This state is set right after a call is ended to indicate a call postprocessing + */ + afterServiceOperators: ACDAfterServiceOperatorState[]; + /** + * Number of operators with the 'AFTER SERVICE' state + */ + afterServiceOperatorCount: number; + /** + * List of calls enqueued into this queue that are being serviced right now by operators + */ + servicingCalls: ACDServicingCallState[]; + /** + * List of calls enqueued into this queue that are not yet serviced by operators + */ + waitingCalls: ACDWaitingCallState[]; + } + interface ACDReadyOperatorState { + /** + * The user ID of the operator + */ + userId: number; + /** + * The user name of the operator + */ + userName: string; + /** + * The display user name of the operator + */ + userDisplayName: string; + /** + * The idle duration in seconds. The minimum of the duration after the last hangup and the duration after the operator status changing to READY + */ + idleDuration: number; + } + interface ACDLockedOperatorState { + /** + * The user ID of the operator + */ + userId: number; + /** + * The user name of the operator + */ + userName: string; + /** + * The display user name of the operator + */ + userDisplayName: string; + /** + * The UTC time when the operator becomes unavailable in 24-h format: YYYY-MM-DD HH:mm:ss + */ + unreached?: Date; + /** + * The operator locks + */ + locks?: ACDLock[]; + /** + * The ACD operator calls + */ + acdCalls?: ACDOperatorCall[]; + /** + * The operator status string. 'BANNED' string indicates temporarily banned operators. The following values are possible: READY, BANNED + */ + status?: string; + } + interface ACDAfterServiceOperatorState { + /** + * The user ID of the operator + */ + userId: number; + /** + * The user name of the operator + */ + userName: string; + /** + * The display user name of the operator + */ + userDisplayName: string; + /** + * The operator status string + */ + status?: string; + } + interface ACDLock { + /** + * The ACD lock ID + */ + id: string; + /** + * The UTC lock created time in 24-h format: YYYY-MM-DD HH:mm:ss + */ + created: Date; + } + interface ACDOperatorCall { + /** + * The ACD session history ID of the request + */ + acdSessionHistoryId: number; + /** + * The internal ACD session history ID + */ + acdRequestId: string; + /** + * The ACD queue ID + */ + acdQueueId: number; + /** + * The ACD queue name + */ + acdQueueName: string; + /** + * The client callerid + */ + callerid?: string; + /** + * The begin time of the request in 24-h format: YYYY-MM-DD HH:mm:ss + */ + beginTime: Date; + /** + * The submission time of the request in 24-h format: YYYY-MM-DD HH:mm:ss + */ + submitted?: Date; + } + interface ACDServicingCallState { + /** + * The user ID of the operator + */ + userId: number; + /** + * The user name of the operator + */ + userName: string; + /** + * The display user name of the operator + */ + userDisplayName: string; + /** + * The request priority + */ + priority: number; + /** + * The client callerid + */ + callerid?: string; + /** + * The begin time of the request in 24-h format: YYYY-MM-DD HH:mm:ss + */ + beginTime: Date; + /** + * The waiting time before servicing in seconds + */ + waitingTime: number; + /** + * The ACD session history ID of the request + */ + acdSessionHistoryId: number; + } + interface ACDWaitingCallState { + /** + * The user ID of the operator to try to service the request + */ + userId?: number; + /** + * The user name of the operator + */ + userName: string; + /** + * The display user name of the operator + */ + userDisplayName: string; + /** + * The request priority + */ + priority: number; + /** + * The client callerid + */ + callerid?: string; + /** + * The begin time of the request in 24-h format: YYYY-MM-DD HH:mm:ss + */ + beginTime: Date; + /** + * The waiting time in seconds + */ + waitingTime: number; + /** + * The predicted minutes left to start servicing + */ + minutesToSubmit: number; + /** + * The ACD session history ID of the request + */ + acdSessionHistoryId: number; + } + interface AttachedPhoneInfo { + /** + * The phone ID + */ + phoneId: number; + /** + * The phone number + */ + phoneNumber: string; + /** + * The phone monthly charge + */ + phonePrice: number; + /** + * The phone country code (2 symbols) + */ + phoneCountryCode: string; + /** + * The next renewal date in format: YYYY-MM-DD + */ + phoneNextRenewal: Date; + /** + * The purchase date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + phonePurchaseDate: Date; + /** + * Whether the subscription is frozen + */ + deactivated: boolean; + /** + * Whether the subscription is cancelled + */ + canceled: boolean; + /** + * Whether to charge automatically + */ + autoCharge: boolean; + /** + * The id of the bound application + */ + applicationId?: number; + /** + * The name of the bound application + */ + applicationName?: string; + /** + * The id of the bound rule + */ + ruleId?: number; + /** + * The name of the bound rule + */ + ruleName?: string; + /** + * The phone category name (MOBILE, GEOGRAPHIC, TOLLFREE, MOSCOW495) + */ + categoryName: string; + /** + * Whether the verification is required for the account + */ + requiredVerification?: boolean; + /** + * The account verification status. The following values are possible: REQUIRED, IN_PROGRESS, VERIFIED + */ + verificationStatus?: string; + /** + * Unverified phone hold until the date in format: YYYY-MM-DD (if the account verification is required). The number is detached on that day automatically! + */ + unverifiedHoldUntil?: Date; + /** + * Whether a not verified account can use the phone + */ + canBeUsed: boolean; + /** + * Whether SMS is supported for this phone number. SMS needs to be explicitly enabled via the [ControlSms] Management API before sending or receiving SMS. If SMS is supported and enabled, SMS can be sent from this phone number via the [SendSmsMessage] Management API and received via the [InboundSmsCallback] property of the HTTP callback. See this article for HTTP callback details + */ + isSmsSupported: boolean; + /** + * Whether SMS sending and receiving is enabled for this phone number via the [ControlSms] Management API + */ + isSmsEnabled: boolean; + /** + * If set, the callback of an incoming SMS is sent to this url, otherwise, it is sent to the general account URL + */ + incomingSmsCallbackUrl?: string; + /** + * Whether you need to make a request to enable calls to emergency numbers + */ + emergencyCallsToBeEnabled: boolean; + /** + * Whether calls to emergency numbers are enabled + */ + emergencyCallsEnabled: boolean; + /** + * Phone number subscription ID + */ + subscriptionId: number; + /** + * Full application name, e.g. myapp.myaccount.n1.voximplant.com + */ + extendedApplicationName?: string; + /** + * Phone region name + */ + phoneRegionName?: string; + /** + * UTC date of an event associated with the number in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified: Date; + } + interface CallerIDInfo { + /** + * The callerID id + */ + calleridId: number; + /** + * The callerID number + */ + calleridNumber: string; + /** + * Whether active + */ + active: boolean; + /** + * The code entering attempts left for the unverified callerID + */ + codeEnteringAttemptsLeft?: number; + /** + * The verification call attempts left for the unverified callerID + */ + verificationCallAttemptsLeft?: number; + /** + * The verification ending date in format: YYYY-MM-DD (for the verified callerID) + */ + verifiedUntil?: Date; + } + interface OutboundTestPhonenumberInfo { + /** + * The personal phone number + */ + phoneNumber: string; + /** + * Whether the phone number is verified + */ + isVerified: boolean; + /** + * The country code + */ + countryCode: string; + } + interface ACDQueueOperatorInfo { + /** + * The ACD queue ID + */ + acdQueueId: number; + /** + * The ACD queue name + */ + acdQueueName: string; + /** + * Whether the user is bound to the ACD queue in manual mode if false + */ + autoLink: boolean; + } + interface SkillInfo { + /** + * The skill ID + */ + skillId: number; + /** + * The skill name + */ + skillName: string; + } + interface ExchangeRates { + /** + * The RUR exchange rate + */ + RUR?: number; + /** + * The KZT exchange rate + */ + KZT?: number; + /** + * The EUR exchange rate + */ + EUR?: number; + /** + * The USD exchange rate. It is always equal to 1 + */ + USD?: number; + } + interface CallList { + /** + * The list ID + */ + listId: number; + /** + * The list name + */ + listName: string; + /** + * The priority of the call list + */ + priority: number; + /** + * The rule id + */ + ruleId: number; + /** + * The maximum number of simultaneous tasks + */ + maxSimultaneous: number; + /** + * The number of task attempts run, which failed to call + */ + numAttempts: number; + /** + * The date of submitted the list in 24-h format: YYYY-MM-DD HH:mm:ss + */ + dtSubmit: Date; + /** + * The completion date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + dtComplete?: Date; + /** + * The interval between attempts in seconds + */ + intervalSeconds: number; + /** + * The status name. The possible values are __In progress__, __Completed__, __Canceled__ + */ + status: string; + } + interface SIPRegistration { + /** + * The SIP registration ID + */ + sipRegistrationId: number; + /** + * The user name from sip proxy + */ + sipUsername: string; + /** + * The sip proxy + */ + proxy: string; + /** + * The last time updated + */ + lastUpdated: number; + /** + * The SIP authentications user + */ + authUser?: string; + /** + * The outgoing proxy + */ + outboundProxy?: string; + /** + * Whether the SIP registration is successful + */ + successful?: boolean; + /** + * The status code from a SIP registration + */ + statusCode?: number; + /** + * The error message from a SIP registration + */ + errorMessage?: string; + /** + * Whether the subscription is deactivation. The SIP registration is frozen if true + */ + deactivated: boolean; + /** + * The next subscription renewal date in format: YYYY-MM-DD + */ + nextSubscriptionRenewal: Date; + /** + * The purchase date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + purchaseDate: Date; + /** + * The subscription monthly charge + */ + subscriptionPrice: string; + /** + * Whether the SIP registration is persistent. Set false to activate it only on the user login + */ + isPersistent: boolean; + /** + * The id of the bound user + */ + userId?: number; + /** + * The name of the bound user + */ + userName?: string; + /** + * The id of the bound application + */ + applicationId?: number; + /** + * The name of the bound application + */ + applicationName?: string; + /** + * The id of the bound rule + */ + ruleId?: number; + /** + * The name of the bound rule + */ + ruleName?: string; + } + interface AdminRole { + /** + * The admin role ID + */ + adminRoleId: number; + /** + * The admin role name + */ + adminRoleName: string; + /** + * Whether to ignore the allowed and denied entries + */ + adminRoleActive: boolean; + /** + * Whether it is a system role + */ + systemRole: boolean; + /** + * The admin role editing UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified: Date; + /** + * The allowed access entries (the API function names) + */ + allowedEntries?: string[]; + /** + * The denied access entries (the API function names) + */ + deniedEntries?: string[]; + } + interface AdminUser { + /** + * The admin user ID + */ + adminUserId: number; + /** + * The admin user name + */ + adminUserName: string; + /** + * The admin user display name + */ + adminUserDisplayName: string; + /** + * Whether login is allowed + */ + adminUserActive: boolean; + /** + * The admin user editing UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified: Date; + /** + * The allowed access entries (the API function names) + */ + accessEntries?: string[]; + /** + * The attached admin roles + */ + adminRoles?: AdminRole[]; + } + interface AuthorizedAccountIP { + /** + * The authorized IP4 or network + */ + authorizedIp: string; + /** + * Whether the IP is allowed (true - whitelist, false - blacklist) + */ + allowed: boolean; + /** + * The item creating UTC date in 24-h format: YYYY-MM-DD HH:mm:ss + */ + created: Date; + } + interface PstnBlackListInfo { + /** + * The black list item ID + */ + pstnBlacklistId: number; + /** + * The phone number + */ + pstnBlacklistPhone: string; + } + interface RecordStorageInfo { + /** + * The record storage ID + */ + recordStorageId?: number; + /** + * The record storage name + */ + recordStorageName?: string; + } + interface SmsTransaction { + /** + * Message ID + */ + messageId: number; + /** + * The SMS destination number + */ + destinationNumber: string; + } + interface FailedSms { + /** + * The SMS destination number + */ + destinationNumber: string; + /** + * The error description + */ + errorDescription: string; + /** + * The error code + */ + errorCode: number; + } + interface RoleGroupView { + /** + * The role group ID + */ + id: number; + /** + * The role group name + */ + name: string; + } + interface SmsHistory { + /** + * Message ID + */ + messageId: number; + /** + * Number being called from + */ + sourceNumber: number; + /** + * Number being called to + */ + destinationNumber: number; + /** + * Incoming or outgoing message + */ + direction: string; + /** + * Number of fragments the initial message is divided into + */ + fragments: number; + /** + * Cost of the message + */ + cost: number; + /** + * Status of the message. 1 - Success, 2 - Error + */ + statusId: string; + /** + * Error message (if any) + */ + errorMessage?: string; + /** + * Date of message processing. The format is yyyy-MM-dd HH:mm:ss + */ + processedDate: Date; + /** + * Id of the transaction for this message + */ + transactionId?: number; + /** + * Stored message text + */ + text?: string; + } + interface A2PSmsHistory { + /** + * Message ID + */ + messageId: number; + /** + * SMS source number + */ + sourceNumber: number; + /** + * SMS destination number + */ + destinationNumber: number; + /** + * Number of fragments the initial message is divided into + */ + fragments: number; + /** + * The message cost + */ + cost: number; + /** + * The message status. 1 - Success, 2 - Error + */ + statusId: string; + /** + * Error message (if any) + */ + errorMessage?: string; + /** + * Date of message processing. The format is yyyy-MM-dd HH:mm:ss + */ + processingDate: Date; + /** + * The transaction ID for this message + */ + transactionId: number; + /** + * Delivery status: QUEUED, DISPATCHED, ABORTED, REJECTED, DELIVERED, FAILED, EXPIRED, UNKNOWN + */ + deliveryStatus: string; + /** + * Stored message text + */ + text?: string; + } + interface GetSQQueuesResult { + /** + * ID of the SmartQueue + */ + sqQueueId: number; + /** + * Name of the SmartQueue + */ + sqQueueName: string; + /** + * Agent selection strategy + */ + agentSelection: string; + /** + * Strategy of prioritizing requests for service + */ + taskSelection: string; + /** + * Comment + */ + description?: string; + /** + * UTC date of the queue creation in 24-h format: YYYY-MM-DD HH:mm:ss + */ + created?: Date; + /** + * UTC date of the queue modification in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified?: Date; + /** + * Maximum time in minutes that a CALL-type request can remain in the queue without being assigned to an agent + */ + callMaxWaitingTime?: number; + /** + * Maximum time in minutes that an IM-type request can remain in the queue without being assigned to an agent + */ + imMaxWaitingTime?: number; + /** + * Maximum size of the queue with CALL-type requests + */ + callMaxQueueSize?: number; + /** + * Maximum size of the queue with IM-type requests + */ + imMaxQueueSize?: number; + /** + * Number of agents bound to the queue + */ + agentcount?: number; + } + interface GetSQSkillsResult { + /** + * ID of the skill + */ + sqSkillId: number; + /** + * Name of the skill + */ + sqSkillName: string; + /** + * Comment + */ + description?: string; + /** + * UTC date of the queue creation in 24-h format: YYYY-MM-DD HH:mm:ss + */ + created?: Date; + /** + * UTC date of the queue modification in 24-h format: YYYY-MM-DD HH:mm:ss + */ + modified?: Date; + } + interface GetSQAgentsResult { + /** + * ID of the user + */ + userId?: number; + /** + * Name of the user + */ + userName?: string; + /** + * Display name of the user + */ + userDisplayName?: string; + /** + * Maximum number of chats that the user processes simultaneously + */ + maxSimultaneousConversations?: number; + /** + * Agent statuses info + */ + sqStatuses?: SmartQueueStateAgentStatus[]; + /** + * JSON array of the agent's queues + */ + sqQueues?: any; + /** + * JSON array of the agent's skills + */ + sqSkills?: any; + } + interface SmartQueueMetricsResult { + /** + * The report type(s). Possible values are calls_blocked_percentage, count_blocked_calls, average_abandonment_rate, count_abandonment_calls, service_level, occupancy_rate, sum_agents_online_time, sum_agents_ready_time, sum_agents_dialing_time, sum_agents_in_service_time, sum_agents_afterservice_time, sum_agents_dnd_time, sum_agents_banned_time, min_time_in_queue,max_time_in_queue, average_time_in_queue, min_answer_speed, max_answer_speed, average_answer_speed, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime, sum_agents_custom_1_time ... sum_agents_custom_10_time + */ + reportType: string; + /** + * Grouping by agent or queue + */ + groups: SmartQueueMetricsGroups[]; + } + interface SmartQueueMetricsGroups { + /** + * The SmartQueue ID + */ + sqQueueId?: number; + /** + * The SmartQueue name + */ + sqQueueName?: string; + /** + * The user ID + */ + userId?: number; + /** + * The user name + */ + userName?: string; + /** + * The user display name + */ + userDisplayName?: string; + /** + * The group values + */ + values: SmartQueueMetricsGroupsValues[]; + } + interface SmartQueueMetricsGroupsValues { + /** + * The start of the period + */ + fromDate: Date; + /** + * The end of the period + */ + toDate: Date; + /** + * The report value + */ + value: number; + } + interface SmartQueueState { + /** + * The SmartQueue ID + */ + sqQueueId: number; + /** + * The SmartQueue name + */ + sqQueueName: string; + /** + * The list of logged-in agents with their skills and statuses + */ + sqAgents: SmartQueueStateAgent[]; + /** + * The list of tasks + */ + tasks: SmartQueueStateTask[]; + } + interface SmartQueueStateTask { + /** + * The task type. Possible values are CALL, IM + */ + taskType: string; + /** + * The task status. Possible values are IN_QUEUE, DISTRIBUTED, IN_PROCESSING + */ + status: string; + /** + * Selected agent + */ + userId?: number; + /** + * Task skills + */ + sqSkills: SmartQueueTaskSkill[]; + /** + * Waiting time in ms + */ + waitingTime: number; + /** + * Processing time in ms + */ + processingTime: number; + /** + * Custom data text string for the current task. You can set the custom data in the [enqueueTask](/docs/references/voxengine/voxengine/enqueuetask#enqueuetask) method + */ + customData?: any; + } + interface SmartQueueStateAgent { + /** + * The user ID + */ + userId: number; + /** + * The user name + */ + userName: string; + /** + * The display user name + */ + userDisplayName: string; + /** + * Agent skills + */ + sqSkills: SmartQueueAgentSkill[]; + /** + * Agent statuses info + */ + sqStatuses: SmartQueueStateAgentStatus[]; + } + interface SmartQueueAgentSkill { + /** + * The agent skill ID + */ + sqSkillId: number; + /** + * The agent skill name + */ + sqSkillName: string; + /** + * The agent skill level + */ + sqSkillLevel: number; + } + interface SmartQueueTaskSkill { + /** + * The skill name + */ + sqSkillName: string; + /** + * The skill level + */ + sqSkillLevel: number; + } + interface SmartQueueStateAgentStatus { + /** + * The IM status info + */ + IM: SmartQueueStateAgentStatus_; + /** + * The CALL status info + */ + CALL: SmartQueueStateAgentStatus_; + } + interface SmartQueueStateAgentStatus_ { + /** + * The status name + */ + sqStatusName: string; + /** + * Time in 24-h format: YYYY-MM-DD HH:mm:ss + */ + fromDate: Date; + } + interface KeyValueItems { + /** + * Key that matches the specified key or key pattern + */ + key: string; + /** + * Value for the specified key + */ + value: string; + /** + * Expiration date based on **ttl** (timestamp without milliseconds) + */ + expiresAt: number; + } + interface KeyValueKeys { + /** + * Key that matches the pattern + */ + key: string; + /** + * Expiration date based on **ttl** (timestamp without milliseconds) + */ + expiresAt: number; + } + interface AccountInvoice { + /** + * Invoice period + */ + period: InvoicePeriod; + /** + * Info on all money spent in the invoice + */ + amount: InvoiceTotalDetails; + /** + * Invoice id + */ + invoiceId: number; + /** + * Detailed info on each spending + */ + rows: InvoiceSpendingDetails; + /** + * Unique invoice number + */ + invoiceNumber: string; + /** + * Date when the invoice is created in format: YYYY-MM-DD + */ + invoiceDate: Date; + /** + * Invoice status + */ + status: string; + } + interface InvoicePeriod { + /** + * From date in format: YYYY-MM-DD + */ + from: Date; + /** + * To date in format: YYYY-MM-DD + */ + to: Date; + } + interface InvoiceTotalDetails { + /** + * Total amount of taxes + */ + taxAmount: number; + /** + * Invoice total amount including taxes + */ + totalAmount: number; + /** + * Discounted amount to pay + */ + amountToPay: number; + /** + * Discount + */ + discountAmount: number; + /** + * Invoice currency + */ + currency: string; + } + interface InvoiceSpendingDetails { + /** + * Paid amount + */ + amount: InvoiceTotalDetails; + /** + * Service name + */ + serviceName: string; + /** + * Array of taxes + */ + taxes: InvoiceTaxesDetails; + } + interface InvoiceTaxesDetails { + /** + * Taxable sum + */ + taxableMeasure: number; + /** + * Paid amount + */ + amount: number; + /** + * Tax type. Possible values: Federal, State, County, City, Unincorporated + */ + level: string; + /** + * Tax rate + */ + rate: number; + /** + * Tax name + */ + name: string; + /** + * Tax currency + */ + currency: string; + /** + * Tax category + */ + category: string; + } + interface GetAccountInfoRequest { + /** + * Whether to get the account's live balance + */ + returnLiveBalance?: boolean; + } + interface GetAccountInfoResponse { + /** + * Account's info as the [AccountInfoType] object instance + */ + result: AccountInfo; + /** + * The preferred address for the Management API requests + */ + apiAddress: string; + error?: APIError; + } + interface GetCurrencyRateRequest { + /** + * The currency code list separated by semicolons (;). Examples: RUR, KZT, EUR, USD + */ + currency: string | string[]; + /** + * The date, format: YYYY-MM-DD + */ + date?: Date; + } + interface GetCurrencyRateResponse { + /** + * The exchange rates + */ + result: ExchangeRates; + error?: APIError; + } + interface AccountsInterface { + /** + * Gets the account's info such as account_id, account_name, account_email etc. + */ + getAccountInfo: (request: GetAccountInfoRequest) => Promise; + /** + * Gets the exchange rate on selected date (per USD). + */ + getCurrencyRate: (request: GetCurrencyRateRequest) => Promise; + } + interface AddApplicationRequest { + /** + * The short application name in format \[a-z\]\[a-z0-9-\]{1,64} + */ + applicationName: string; + /** + * Whether to enable secure storage for all logs and records of the application + */ + secureRecordStorage?: boolean; + } + interface AddApplicationResponse { + /** + * 1 + */ + result: number; + /** + * The application ID + */ + applicationId: number; + /** + * The full application name + */ + applicationName: string; + /** + * Whether a secure storage for logs and records is enabled or not + */ + secureRecordStorage: boolean; + error?: APIError; + } + interface DelApplicationRequest { + /** + * The application ID list separated by semicolons (;). Use the 'all' value to select all applications + */ + applicationId: 'any' | number | number[]; + /** + * The application name list separated by semicolons (;). Can be used instead of application_id + */ + applicationName: string | string[]; + } + interface DelApplicationResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SetApplicationInfoRequest { + /** + * The application ID + */ + applicationId: number; + /** + * The application name that can be used instead of application_id + */ + requiredApplicationName: string; + /** + * The new short application name in format [a-z][a-z0-9-]{1,79} + */ + applicationName?: string; + /** + * Whether to enable secure storage for all logs and records of the application + */ + secureRecordStorage?: boolean; + } + interface SetApplicationInfoResponse { + /** + * 1 + */ + result: number; + /** + * The new full application name + */ + applicationName: string; + /** + * Whether a secure storage for logs and records is enabled or not + */ + secureRecordStorage: boolean; + error?: APIError; + } + interface GetApplicationsRequest { + /** + * The application ID to filter + */ + applicationId?: number; + /** + * The application name part to filter + */ + applicationName?: string; + /** + * Whether to get bound rules info + */ + withRules?: boolean; + /** + * Whether to get bound rules and scenarios info + */ + withScenarios?: boolean; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetApplicationsResponse { + result: ApplicationInfo[]; + /** + * The total found application count + */ + totalCount: number; + /** + * The returned application count + */ + count: number; + error?: APIError; + } + interface ApplicationsInterface { + /** + * Adds a new account's application. + */ + addApplication: (request: AddApplicationRequest) => Promise; + /** + * Deletes the account's application. + */ + delApplication: (request: DelApplicationRequest) => Promise; + /** + * Edits the account's application. + */ + setApplicationInfo: (request: SetApplicationInfoRequest) => Promise; + /** + * Gets the account's applications. + */ + getApplications: (request: GetApplicationsRequest) => Promise; + } + interface AddUserRequest { + /** + * The user name in format [a-z0-9][a-z0-9_-]{2,49} + */ + userName: string; + /** + * The user display name. The length must be less than 256 + */ + userDisplayName: string; + /** + * The user password. Must be at least 8 characters long and contain at least one uppercase and lowercase letter, one number, and one special character + */ + userPassword: string; + /** + * The application ID which a new user is to be bound to. Can be used instead of the application_name parameter + */ + applicationId: number; + /** + * The application name which a new user is to be bound to. Can be used instead of the application_id parameter + */ + applicationName: string; + /** + * Whether the user uses the parent account's money, 'false' if the user has a separate balance + */ + parentAccounting?: boolean; + mobilePhone?: string; + /** + * Whether the user is active. Inactive users cannot log in to applications + */ + userActive?: boolean; + /** + * Any string + */ + userCustomData?: string; + } + interface AddUserResponse { + /** + * 1 + */ + result: number; + /** + * The new user ID + */ + userId: number; + error?: APIError; + } + interface DelUserRequest { + /** + * The user ID list separated by semicolons (;). Use the 'all' value to select all users + */ + userId: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;) that can be used instead of user_id + */ + userName: string | string[]; + /** + * Delete the specified users bound to the application ID. It is required if the user_name is specified + */ + applicationId?: number; + /** + * Delete the specified users bound to the application name. Can be used instead of the application_id parameter + */ + applicationName?: string; + } + interface DelUserResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SetUserInfoRequest { + /** + * The user to edit + */ + userId: number; + /** + * The user name that can be used instead of user_id + */ + userName: string; + /** + * The application ID. It is required if the user_name is specified + */ + applicationId?: number; + /** + * The application name that can be used instead of application_id + */ + applicationName?: string; + /** + * The new user name in format [a-z0-9][a-z0-9_-]{2,49} + */ + newUserName?: string; + /** + * The new user display name. The length must be less than 256 + */ + userDisplayName?: string; + /** + * The new user password. Must be at least 8 characters long and contain at least one uppercase and lowercase letter, one number, and one special character + */ + userPassword?: string; + /** + * Whether to use the parent account's money, 'false' to use a separate user balance + */ + parentAccounting?: boolean; + /** + * Whether the user is active. Inactive users cannot log in to applications + */ + userActive?: boolean; + /** + * Any string + */ + userCustomData?: string; + mobilePhone?: string; + } + interface SetUserInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetUsersRequest { + /** + * The application ID to filter + */ + applicationId: number; + /** + * The application name part to filter + */ + applicationName: string; + /** + * The skill ID to filter + */ + skillId?: number; + /** + * The excluded skill ID to filter + */ + excludedSkillId?: number; + /** + * The ACD queue ID to filter + */ + acdQueueId?: number; + /** + * The excluded ACD queue ID to filter + */ + excludedAcdQueueId?: number; + /** + * The user ID to filter + */ + userId?: number; + /** + * The user name part to filter + */ + userName?: string; + /** + * Whether the user is active to filter. Inactive users cannot log in to applications + */ + userActive?: boolean; + /** + * The user display name part to filter + */ + userDisplayName?: string; + /** + * Whether to get the bound skills + */ + withSkills?: boolean; + /** + * Whether to get the bound queues + */ + withQueues?: boolean; + /** + * The ACD status list separated by semicolons (;) to filter. The following values are possible: OFFLINE, ONLINE, READY, BANNED, IN_SERVICE, AFTER_SERVICE, TIMEOUT, DND + */ + acdStatus?: string | string[]; + /** + * The skill to show in the 'skills' field output + */ + showingSkillId?: number; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + /** + * The following values are available: 'user_id', 'user_name' and 'user_display_name' + */ + orderBy?: string; + /** + * Whether to get the user live balance + */ + returnLiveBalance?: boolean; + } + interface GetUsersResponse { + /** + * The UserInfoType records + */ + result: UserInfo[]; + /** + * The total found user count + */ + totalCount: number; + /** + * The returned user count + */ + count: number; + error?: APIError; + } + interface TransferMoneyToUserRequest { + /** + * The user ID list separated by semicolons (;). Use the 'all' value to select all users + */ + userId: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;) that can be used instead of user_id + */ + userName: string | string[]; + /** + * The money amount, $. The absolute amount value must be equal or greater than 0.01 + */ + amount: number; + /** + * The application ID. It is required if the user_name is specified + */ + applicationId?: number; + /** + * The application name that can be used instead of application_id + */ + applicationName?: string; + /** + * The amount currency. Examples: RUR, EUR, USD + */ + currency?: string; + /** + * Whether to enable the strict mode. Returns error if strict_mode is true and a user or the account does not have enough money + */ + strictMode?: boolean; + /** + * The user transaction description + */ + userTransactionDescription?: string; + /** + * The account transaction description. The following macro available: ${user_id}, ${user_name} + */ + accountTransactionDescription?: string; + } + interface TransferMoneyToUserResponse { + /** + * 1 + */ + result: number; + /** + * The new account balance + */ + balance: number; + error?: APIError; + } + interface UsersInterface { + /** + * Adds a new user. + */ + addUser: (request: AddUserRequest) => Promise; + /** + * Deletes the specified user(s). + */ + delUser: (request: DelUserRequest) => Promise; + /** + * Edits the user. + */ + setUserInfo: (request: SetUserInfoRequest) => Promise; + /** + * Shows the users of the specified account. + */ + getUsers: (request: GetUsersRequest) => Promise; + /** + * Transfer the account's money to the user or transfer the user's money to the account if the money amount is negative. + */ + transferMoneyToUser: ( + request: TransferMoneyToUserRequest + ) => Promise; + } + interface CreateCallListRequest { + /** + * The rule ID. It is specified in the Applications section of the Control Panel + */ + ruleId: number; + /** + * Call list priority. The value is in the range of [0 ... 2^31] where zero is the highest priority + */ + priority: number; + /** + * Number of simultaneously processed tasks + */ + maxSimultaneous: number; + /** + * Number of attempts. Minimum is 1, maximum is 5 + */ + numAttempts: number; + /** + * File name, up to 255 characters and cannot contain the '/' and '\' symbols + */ + name: string; + /** + * Send as "body" part of the HTTP request or as multiform. The sending "file_content" via URL is at its own risk because the network devices tend to drop HTTP requests with large headers + */ + fileContent: Buffer; + /** + * Interval between call attempts in seconds. The default is 0 + */ + intervalSeconds?: number; + /** + * Encoding file. The default is UTF-8 + */ + encoding?: string; + /** + * Separator values. The default is ';' + */ + delimiter?: string; + /** + * Escape character for parsing csv + */ + escape?: string; + /** + * Specifies the IP from the geolocation of the call list subscribers. It allows selecting the nearest server for serving subscribers + */ + referenceIp?: string; + /** + * Specifies the location of the server where the scenario needs to be executed. Has higher priority than `reference_ip`. Request [getServerLocations](https://api.voximplant.com/getServerLocations) for possible values + */ + serverLocation?: string; + } + interface CreateCallListResponse { + /** + * true + */ + result: boolean; + /** + * The number of stored records + */ + count: number; + /** + * The list ID + */ + listId: number; + error?: APIError; + } + interface GetCallListsRequest { + /** + * The list ID to filter. Can be a list separated by semicolons (;). Use the 'all' value to select all lists + */ + listId?: 'any' | number | number[]; + /** + * Find call lists by name + */ + name?: string; + /** + * Whether to find only active call lists + */ + isActive?: boolean; + /** + * The UTC 'from' date filter in 24-h format: YYYY-MM-DD HH:mm:ss + */ + fromDate?: Date; + /** + * The UTC 'to' date filter in 24-h format: YYYY-MM-DD HH:mm:ss + */ + toDate?: Date; + /** + * The type of the call list. The possible values are AUTOMATIC and MANUAL + */ + typeList?: string; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + /** + * The application ID to filter. Can be a list separated by semicolons (;). Use the 'all' value to select all applications + */ + applicationId?: 'any' | number | number[]; + } + interface GetCallListsResponse { + /** + * Array of lists + */ + result: CallList[]; + /** + * The returned call list count + */ + count: number; + /** + * The total found call list count + */ + totalCount: number; + error?: APIError; + } + interface CallListsInterface { + /** + * Adds a new CSV file for call list processing and starts the specified rule immediately. To send a file, use the request body. To set the call time constraints, use the following options in a CSV file:
  • **__start_execution_time** – when the call list processing starts every day, UTC+0 24-h format: HH:mm:ss
  • **__end_execution_time** – when the call list processing stops every day, UTC+0 24-h format: HH:mm:ss
  • **__start_at** – when the call list processing starts, UNIX timestamp. If not specified, the processing starts immediately after a method call
  • **__task_uuid** – call list UUID. A string up to 40 characters, can contain latin letters, digits, hyphens (-) and colons (:). Unique within the call list

This method accepts CSV files with custom delimiters, such a commas (,), semicolons (;) and other. To specify a delimiter, pass it to the delimiter parameter.
IMPORTANT: the account's balance should be equal or greater than 1 USD. If the balance is lower than 1 USD, the call list processing does not start, or it stops immediately if it is active. + */ + createCallList: (request: CreateCallListRequest) => Promise; + /** + * Get all call lists for the specified user. + */ + getCallLists: (request: GetCallListsRequest) => Promise; + } + interface StartConferenceRequest { + /** + * The conference name. The name length must be less than 50 symbols + */ + conferenceName: string; + /** + * The rule ID that needs to be launched. Please note, the necessary scenario needs to be attached to the rule + */ + ruleId: number; + /** + * The user ID. Run the scripts from the user if set + */ + userId?: number; + /** + * The user name that can be used instead of user_id. Run the scripts from the user if set + */ + userName?: string; + /** + * The application ID + */ + applicationId?: number; + /** + * The application name that can be used instead of application_id + */ + applicationName?: string; + /** + * The script custom data, that can be accessed in the scenario via the VoxEngine.customData() method. Use the application/x-www-form-urlencoded content type with UTF-8 encoding. + */ + scriptCustomData?: string; + /** + * Specifies the IP from the geolocation of predicted subscribers. It allows selecting the nearest server for serving subscribers + */ + referenceIp?: string; + /** + * Specifies the location of the server where the scenario needs to be executed. Has higher priority than `reference_ip`. Request [getServerLocations](https://api.voximplant.com/getServerLocations) for possible values + */ + serverLocation?: string; + } + interface StartConferenceResponse { + /** + * 1 + */ + result: number; + /** + * The URL to control a created media session. It can be used for arbitrary tasks such as stopping scenario or passing additional data to it. Making HTTP request on this URL results in the [AppEvents.HttpRequest](/docs/references/voxengine/appevents#httprequest) VoxEngine event being triggered for a scenario, with an HTTP request data passed to it + */ + mediaSessionAccessUrl: string; + /** + * The URL to control a created media session. It can be used for arbitrary tasks such as stopping scenario or passing additional data to it. Making HTTPS request on this URL results in the [AppEvents.HttpRequest](/docs/references/voxengine/appevents#httprequest) VoxEngine event being triggered for a scenario, with an HTTP request data passed to it + */ + mediaSessionAccessSecureUrl: string; + /** + * The call session history ID. To search a call session result, paste the ID to the GetCallHistory method's call_session_history_id parameter + */ + callSessionHistoryId: number; + error?: APIError; + } + interface ScenariosInterface { + /** + * Runs a session for video conferencing or joins the existing video conference session.

When you create a session by calling this method, a scenario runs on one of the servers dedicated to video conferencing. All further method calls with the same **conference_name** do not create a new video conference session but join the existing one.

Use the [StartScenarios] method for creating audio conferences. + */ + startConference: (request: StartConferenceRequest) => Promise; + } + interface GetCallHistoryRequest { + /** + * The from date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + fromDate: Date; + /** + * The to date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + toDate: Date; + timezone?: string; + /** + * To get the call history for the specific sessions, pass the session IDs to this parameter separated by a semicolon (;). You can find the session ID in the AppEvents.Started event's sessionID property in a scenario, or retrieve it from the call_session_history_id value returned from the StartScenarios or StartConference methods + */ + callSessionHistoryId?: 'any' | number | number[]; + /** + * To receive the call history for a specific application, pass the application ID to this parameter + */ + applicationId?: number; + /** + * The application name, can be used instead of application_id + */ + applicationName?: string; + /** + * To receive the call history for a specific users, pass the user ID list separated by semicolons (;). If it is specified, the output contains the calls from the listed users only + */ + userId?: 'any' | number | number[]; + /** + * To receive the call history for a specific routing rule, pass the rule name to this parameter. Applies only if you set application_id or application_name + */ + ruleName?: string; + /** + * To receive a call history for a specific remote numbers, pass the number list separated by semicolons (;). A remote number is a number on the client side + */ + remoteNumber?: string | string[]; + /** + * To receive a call history for a specific local numbers, pass the number list separated by semicolons (;). A local number is a number on the platform side + */ + localNumber?: string | string[]; + /** + * To filter the call history by the custom_data passed to the call sessions, pass the custom data to this parameter + */ + callSessionHistoryCustomData?: string; + /** + * Whether to receive a list of sessions with all calls within the sessions, including phone numbers, call cost and other information + */ + withCalls?: boolean; + /** + * Whether to get the calls' records + */ + withRecords?: boolean; + /** + * Whether to get other resources usage (see [ResourceUsageType]) + */ + withOtherResources?: boolean; + /** + * The child account ID list separated by semicolons (;) + */ + childAccountId?: 'any' | number | number[]; + /** + * Whether to get the children account calls only + */ + childrenCallsOnly?: boolean; + /** + * Whether to get a CSV file with the column names if the output=csv + */ + withHeader?: boolean; + /** + * Whether to get records in the descent order + */ + descOrder?: boolean; + /** + * Whether to include the 'total_count' and increase performance + */ + withTotalCount?: boolean; + /** + * The number of returning records. In the synchronous mode, the maximum value is 1000 + */ + count?: number; + /** + * The number of records to skip in the output with a maximum value of 10000 + */ + offset?: number; + /** + * The output format. The following values available: json, csv + */ + output?: string; + /** + * Whether to get records in the asynchronous mode (for csv output only). Use this mode to download large amounts of data. See the [GetHistoryReports], [DownloadHistoryReport] functions for details + */ + isAsync?: boolean; + } + interface GetCallHistoryResponse { + /** + * The CallSessionInfoType records in sync mode or 1 in async mode + */ + result: CallSessionInfo[]; + /** + * The total found call session count (sync mode) + */ + totalCount: number; + /** + * The returned call session count (sync mode) + */ + count: number; + /** + * The used timezone + */ + timezone: string; + /** + * The history report ID (async mode) + */ + historyReportId: number; + error?: APIError; + } + interface GetBriefCallHistoryRequest { + /** + * The from date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + fromDate: Date; + /** + * The to date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + toDate: Date; + /** + * The output format. The following values available: csv + */ + output: string; + /** + * Whether to get records in the asynchronous mode. Use this mode to download large amounts of data. See the [GetHistoryReports], [DownloadHistoryReport] functions for details + */ + isAsync: boolean; + timezone?: string; + /** + * To get the call history for the specific sessions, pass the session IDs to this parameter separated by a semicolon (;). You can find the session ID in the AppEvents.Started event's sessionID property in a scenario, or retrieve it from the call_session_history_id value returned from the StartScenarios or StartConference methods + */ + callSessionHistoryId?: 'any' | number | number[]; + /** + * To receive the call history for a specific application, pass the application ID to this parameter + */ + applicationId?: number; + /** + * The application name, can be used instead of application_id + */ + applicationName?: string; + /** + * To receive the call history for a specific routing rule, pass the rule name to this parameter. Applies only if you set application_id or application_name + */ + ruleName?: string; + /** + * To receive a call history for a specific remote numbers, pass the number list separated by semicolons (;). A remote number is a number on the client side + */ + remoteNumber?: string | string[]; + /** + * To receive a call history for a specific local numbers, pass the number list separated by semicolons (;). A local number is a number on the platform side + */ + localNumber?: string | string[]; + /** + * To filter the call history by the custom_data passed to the call sessions, pass the custom data to this parameter + */ + callSessionHistoryCustomData?: string; + /** + * Whether to get a CSV file with the column names if the output=csv + */ + withHeader?: boolean; + /** + * Whether to get records in the descent order + */ + descOrder?: boolean; + } + interface GetBriefCallHistoryResponse { + /** + * In the async mode, the value is always 1 + */ + result: number; + /** + * The history report ID + */ + historyReportId: number; + error?: APIError; + } + interface GetTransactionHistoryRequest { + /** + * The from date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + fromDate: Date; + /** + * The to date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss + */ + toDate: Date; + timezone?: string; + /** + * The transaction ID list separated by semicolons (;) + */ + transactionId?: 'any' | number | number[]; + paymentReference?: string; + /** + * The transaction type list separated by semicolons (;). The following values are possible: gift_revoke, resource_charge, money_distribution, subscription_charge, subscription_installation_charge, card_periodic_payment, card_overrun_payment, card_payment, rub_card_periodic_payment, rub_card_overrun_payment, rub_card_payment, robokassa_payment, gift, promo, adjustment, wire_transfer, us_wire_transfer, refund, discount, mgp_charge, mgp_startup, mgp_business, mgp_big_business, mgp_enterprise, mgp_large_enterprise, techsupport_charge, tax_charge, monthly_fee_charge, grace_credit_payment, grace_credit_provision, mau_charge, mau_overrun, im_charge, im_overrun, fmc_charge, sip_registration_charge, development_fee, money_transfer_to_child, money_transfer_to_parent, money_acceptance_from_child, money_acceptance_from_parent, phone_number_installation, phone_number_charge, toll_free_phone_number_installation, toll_free_phone_number_charge, services, user_money_transfer, paypal_payment, paypal_overrun_payment, paypal_periodic_payment + */ + transactionType?: string | string[]; + /** + * The user ID list separated by semicolons (;) + */ + userId?: 'any' | number | number[]; + /** + * The child account ID list separated by semicolons (;). Use the 'all' value to select all child accounts + */ + childAccountId?: 'any' | number | number[]; + /** + * Whether to get the children account transactions only + */ + childrenTransactionsOnly?: boolean; + /** + * Whether to get the users' transactions only + */ + usersTransactionsOnly?: boolean; + /** + * Whether to get records in the descent order + */ + descOrder?: boolean; + /** + * The number of returning records. In the synchronous mode, the maximum value is 1000 + */ + count?: number; + /** + * The number of records to skip in the output with a maximum value of 10000 + */ + offset?: number; + /** + * The output format. The following values available: json, csv + */ + output?: string; + /** + * Whether to get records in the asynchronous mode (for csv output only). Use this mode to download large amounts of data. See the [GetHistoryReports], [DownloadHistoryReport] functions for details + */ + isAsync?: boolean; + /** + * Whether to get transactions on hold (transactions for which money is reserved but not yet withdrawn from the account) + */ + isUncommitted?: boolean; + } + interface GetTransactionHistoryResponse { + result: TransactionInfo[]; + /** + * The total found transaction count + */ + totalCount: number; + /** + * The used timezone. 'Etc/GMT' for example + */ + timezone: string; + /** + * The returned transaction count + */ + count: number; + /** + * The history report ID (async mode) + */ + historyReportId: number; + error?: APIError; + } + interface HistoryInterface { + /** + * Gets the account's call history, including call duration, cost, logs and other call information. You can filter the call history by a certain date + */ + getCallHistory: (request: GetCallHistoryRequest) => Promise; + /** + * Gets the account's brief call history. Use the [GetHistoryReports], [DownloadHistoryReport] methods to download the report. + */ + getBriefCallHistory: ( + request: GetBriefCallHistoryRequest + ) => Promise; + /** + * Gets the transaction history. + */ + getTransactionHistory: ( + request: GetTransactionHistoryRequest + ) => Promise; + } + interface AddPstnBlackListItemRequest { + /** + * The phone number in format e164 or regex pattern + */ + pstnBlacklistPhone: string; + } + interface AddPstnBlackListItemResponse { + /** + * 1 + */ + result: number; + /** + * The PSTN black list item ID + */ + pstnBlacklistId: number; + error?: APIError; + } + interface SetPstnBlackListItemRequest { + /** + * The PSTN black list item ID + */ + pstnBlacklistId: number; + /** + * The new phone number in format e164 + */ + pstnBlacklistPhone: string; + } + interface SetPstnBlackListItemResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface DelPstnBlackListItemRequest { + /** + * The PSTN black list item ID + */ + pstnBlacklistId: number; + } + interface DelPstnBlackListItemResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetPstnBlackListRequest { + /** + * The PSTN black list item ID for filter + */ + pstnBlacklistId?: number; + /** + * The phone number in format e164 for filter + */ + pstnBlacklistPhone?: string; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetPstnBlackListResponse { + result: PstnBlackListInfo[]; + /** + * The total found phone numbers count + */ + totalCount: number; + /** + * The returned phone numbers count + */ + count: number; + error?: APIError; + } + interface PSTNBlacklistInterface { + /** + * Add a new phone number to the PSTN blacklist. Use blacklist to block incoming calls from specified phone numbers to numbers purchased from Voximplant. Since we have no control over exact phone number format for calls from SIP integrations, blacklisting such numbers should be done via JavaScript scenarios. + */ + addPstnBlackListItem: ( + request: AddPstnBlackListItemRequest + ) => Promise; + /** + * Update the PSTN blacklist item. BlackList works for numbers that are purchased from Voximplant only. Since we have no control over exact phone number format for calls from SIP integrations, blacklisting such numbers should be done via JavaScript scenarios. + */ + setPstnBlackListItem: ( + request: SetPstnBlackListItemRequest + ) => Promise; + /** + * Remove phone number from the PSTN blacklist. + */ + delPstnBlackListItem: ( + request: DelPstnBlackListItemRequest + ) => Promise; + /** + * Get the whole PSTN blacklist. + */ + getPstnBlackList: (request: GetPstnBlackListRequest) => Promise; + } + interface AddSipWhiteListItemRequest { + /** + * The network address in format A.B.C.D/L or A.B.C.D/a.b.c.d (example 192.168.1.5/16) + */ + sipWhitelistNetwork: string; + /** + * The network address description + */ + description?: string; + } + interface AddSipWhiteListItemResponse { + /** + * 1 + */ + result: number; + /** + * The SIP white list item ID + */ + sipWhitelistId: number; + error?: APIError; + } + interface DelSipWhiteListItemRequest { + /** + * The SIP white list item ID to delete + */ + sipWhitelistId: number; + } + interface DelSipWhiteListItemResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SetSipWhiteListItemRequest { + /** + * The SIP white list item ID + */ + sipWhitelistId: number; + /** + * The new network address in format A.B.C.D/L or A.B.C.D/a.b.c.d (example 192.168.1.5/16) + */ + sipWhitelistNetwork: string; + /** + * The network address description + */ + description?: string; + } + interface SetSipWhiteListItemResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetSipWhiteListRequest { + /** + * The SIP white list item ID to filter + */ + sipWhitelistId?: number; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetSipWhiteListResponse { + result: SipWhiteListInfo[]; + /** + * The total found networks count + */ + totalCount: number; + /** + * The returned networks count + */ + count: number; + error?: APIError; + } + interface SIPWhiteListInterface { + /** + * Adds a new network address to the SIP white list. + */ + addSipWhiteListItem: ( + request: AddSipWhiteListItemRequest + ) => Promise; + /** + * Deletes the network address from the SIP white list. + */ + delSipWhiteListItem: ( + request: DelSipWhiteListItemRequest + ) => Promise; + /** + * Edits the SIP white list. + */ + setSipWhiteListItem: ( + request: SetSipWhiteListItemRequest + ) => Promise; + /** + * Gets the SIP white list. + */ + getSipWhiteList: (request: GetSipWhiteListRequest) => Promise; + } + interface BindSipRegistrationRequest { + /** + * The registration ID + */ + sipRegistrationId?: number; + /** + * The application ID which the SIP registration is to be bound to. Can be used instead of the application_name parameter + */ + applicationId?: number; + /** + * The application name which the SIP registration is to be bound to. Can be used instead of the application_id parameter + */ + applicationName?: string; + /** + * The rule ID which the SIP registration is to be bound to. Can be used instead of the rule_name parameter + */ + ruleId?: number; + /** + * The rule name which the SIP registration is to be bound to. Can be used instead of the rule_id parameter + */ + ruleName?: string; + /** + * The user ID which the SIP registration is to be bound to. Can be used instead of the user_name parameter + */ + userId?: number; + /** + * The user name which the SIP registration is to be bound to. Can be used instead of the user_id parameter + */ + userName?: string; + /** + * Whether to bind or unbind (set true or false respectively) + */ + bind?: boolean; + } + interface BindSipRegistrationResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetSipRegistrationsRequest { + /** + * The rule ID list separated by semicolons (;) to filter. Can be used instead of rule_name + */ + ruleId: 'any' | number | number[]; + /** + * The rule name list separated by semicolons (;) to filter. Can be used instead of rule_id + */ + ruleName: string | string[]; + /** + * The user ID list separated by semicolons (;) to filter. Can be used instead of user_name + */ + userId: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;) to filter. Can be used instead of user_id + */ + userName: string | string[]; + /** + * The SIP registration ID + */ + sipRegistrationId?: number; + /** + * The SIP user name to filter + */ + sipUsername?: string; + /** + * Whether to show the frozen SIP registrations only + */ + deactivated?: boolean; + /** + * Whether to show the successful SIP registrations only + */ + successful?: boolean; + /** + * Whether the SIP registration is persistent to filter + */ + isPersistent?: boolean; + /** + * The application ID list separated by semicolons (;) to filter. Can be used instead of application_name + */ + applicationId?: 'any' | number | number[]; + /** + * The application name list separated by semicolons (;) to filter. Can be used instead of application_id + */ + applicationName?: string | string[]; + /** + * Whether SIP registration bound to an application + */ + isBoundToApplication?: boolean; + /** + * The list of proxy servers to use, divided by semicolon (;) + */ + proxy?: string | string[]; + /** + * Whether SIP registration is still in progress + */ + inProgress?: boolean; + /** + * The list of SIP response codes. The __code1:code2__ means a range from __code1__ to __code2__ including; the __code1;code2__ meanse either __code1__ or __code2__. You can combine ranges, e.g., __code1;code2:code3__ + */ + statusCode?: string; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetSipRegistrationsResponse { + /** + * Active SIP registrations + */ + result: SIPRegistration[]; + /** + * Count rows + */ + count: number; + error?: APIError; + } + interface SIPRegistrationInterface { + /** + * Bind the SIP registration to the application/user or unbind the SIP registration from the application/user. You should specify the application_id or application_name if you specify the rule_name or user_id, or user_name. You should specify the sip_registration_id if you set bind=true. You can bind only one SIP registration to the user (the previous SIP registration is automatically unbound). + */ + bindSipRegistration: ( + request: BindSipRegistrationRequest + ) => Promise; + /** + * Get active SIP registrations. + */ + getSipRegistrations: ( + request: GetSipRegistrationsRequest + ) => Promise; + } + interface GetPhoneNumbersRequest { + /** + * The particular phone ID to filter + */ + phoneId?: 'any' | number | number[]; + /** + * The phone number list separated by semicolons (;) that can be used instead of phone_id + */ + phoneNumber?: string | string[]; + /** + * The application ID + */ + applicationId?: number; + /** + * The application name that can be used instead of application_id + */ + applicationName?: string; + /** + * Whether the phone number bound to an application + */ + isBoundToApplication?: boolean; + /** + * The phone number start to filter + */ + phoneTemplate?: string; + /** + * The country code list separated by semicolons (;) + */ + countryCode?: string | string[]; + /** + * The phone category name. See the [GetPhoneNumberCategories] method + */ + phoneCategoryName?: string; + /** + * Whether the subscription is cancelled to filter + */ + canceled?: boolean; + /** + * Whether the subscription is frozen to filter + */ + deactivated?: boolean; + /** + * Whether the auto_charge flag is enabled + */ + autoCharge?: boolean; + /** + * The UTC 'from' date filter in format: YYYY-MM-DD + */ + fromPhoneNextRenewal?: Date; + /** + * The UTC 'to' date filter in format: YYYY-MM-DD + */ + toPhoneNextRenewal?: Date; + /** + * The UTC 'from' date filter in 24-h format: YYYY-MM-DD HH:mm:ss + */ + fromPhonePurchaseDate?: Date; + /** + * The UTC 'to' date filter in 24-h format: YYYY-MM-DD HH:mm:ss + */ + toPhonePurchaseDate?: Date; + /** + * The child account ID list separated by semicolons (;). Use the 'all' value to select all child accounts + */ + childAccountId?: 'any' | number | number[]; + /** + * Whether to get the children phones only + */ + childrenPhonesOnly?: boolean; + /** + * The required account verification name to filter + */ + verificationName?: string; + /** + * The account verification status list separated by semicolons (;). The following values are possible: REQUIRED, IN_PROGRESS, VERIFIED + */ + verificationStatus?: string | string[]; + /** + * Unverified phone hold until the date (from ...) in format: YYYY-MM-DD + */ + fromUnverifiedHoldUntil?: Date; + /** + * Unverified phone hold until the date (... to) in format: YYYY-MM-DD + */ + toUnverifiedHoldUntil?: Date; + /** + * Whether a not verified account can use the phone + */ + canBeUsed?: boolean; + /** + * The following values are available: 'phone_number' (ascent order), 'phone_price' (ascent order), 'phone_country_code' (ascent order), 'deactivated' (deactivated first, active last), 'purchase_date' (descent order), 'phone_next_renewal' (ascent order), 'verification_status', 'unverified_hold_until' (ascent order), 'verification_name' + */ + orderBy?: string; + /** + * Flag allows you to display only the numbers of the sandbox, real numbers, or all numbers. The following values are possible: 'all', 'true', 'false' + */ + sandbox?: string; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + smsSupported?: boolean; + /** + * The region names list separated by semicolons (;) + */ + phoneRegionName?: string | string[]; + /** + * The rule ID list separated by semicolons (;) + */ + ruleId?: 'any' | number | number[]; + /** + * The rule names list separated by semicolons (;). Can be used only if __application_id__ or __application_name__ is specified + */ + ruleName?: string | string[]; + /** + * Whether the phone number is bound to some rule + */ + isBoundToRule?: boolean; + } + interface GetPhoneNumbersResponse { + /** + * Phone numbers info + */ + result: AttachedPhoneInfo[]; + /** + * The total found phone count + */ + totalCount: number; + /** + * The returned phone count + */ + count: number; + error?: APIError; + } + interface PhoneNumbersInterface { + /** + * Gets the account phone numbers. + */ + getPhoneNumbers: (request: GetPhoneNumbersRequest) => Promise; + } + interface AddCallerIDRequest { + /** + * The callerID number in E.164 format + */ + calleridNumber: string; + } + interface AddCallerIDResponse { + /** + * 1 + */ + result: number; + /** + * The id of the callerID object + */ + calleridId: number; + error?: APIError; + } + interface ActivateCallerIDRequest { + /** + * The id of the callerID object + */ + calleridId: number; + /** + * The callerID number that can be used instead of callerid_id + */ + calleridNumber: string; + /** + * The verification code, see the VerifyCallerID function + */ + verificationCode: string; + } + interface ActivateCallerIDResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface DelCallerIDRequest { + /** + * The id of the callerID object + */ + calleridId: number; + /** + * The callerID number that can be used instead of callerid_id + */ + calleridNumber: string; + } + interface DelCallerIDResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetCallerIDsRequest { + /** + * The id of the callerID object to filter + */ + calleridId?: number; + /** + * The phone number to filter + */ + calleridNumber?: string; + /** + * Whether the account is active to filter + */ + active?: boolean; + /** + * The following values are available: 'caller_number' (ascent order), 'verified_until' (ascent order) + */ + orderBy?: string; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetCallerIDsResponse { + result: CallerIDInfo[]; + /** + * The total found record count + */ + totalCount: number; + /** + * The returned record count + */ + count: number; + error?: APIError; + } + interface VerifyCallerIDRequest { + /** + * The id of the callerID object + */ + calleridId: number; + /** + * The callerID number that can be used instead of callerid_id + */ + calleridNumber: string; + } + interface VerifyCallerIDResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface CallerIDsInterface { + /** + * Adds a new caller ID. Caller ID is the phone that is displayed to the called user. This number can be used for call back. + */ + addCallerID: (request: AddCallerIDRequest) => Promise; + /** + * Activates the CallerID by the verification code. + */ + activateCallerID: (request: ActivateCallerIDRequest) => Promise; + /** + * Deletes the CallerID. Note: you cannot delete a CID permanently (the antispam defence). + */ + delCallerID: (request: DelCallerIDRequest) => Promise; + /** + * Gets the account callerIDs. + */ + getCallerIDs: (request: GetCallerIDsRequest) => Promise; + /** + * Gets a verification code via phone call to the **callerid_number**. + */ + verifyCallerID: (request: VerifyCallerIDRequest) => Promise; + } + interface AddOutboundTestPhoneNumberRequest { + /** + * The personal phone number in the E.164 format + */ + phoneNumber: string; + } + interface AddOutboundTestPhoneNumberResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface VerifyOutboundTestPhoneNumberRequest {} + interface VerifyOutboundTestPhoneNumberResponse { + /** + * The number of attempts left for the day. The number is reset every day at 00:00 UTC + */ + dailyAttemptsLeft: number; + error?: APIError; + } + interface ActivateOutboundTestPhoneNumberRequest { + /** + * The verification code, see the [VerifyOutboundTestPhoneNumber] function + */ + verificationCode: string; + } + interface ActivateOutboundTestPhoneNumberResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface DelOutboundTestPhoneNumberRequest {} + interface DelOutboundTestPhoneNumberResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetOutboundTestPhoneNumbersRequest {} + interface GetOutboundTestPhoneNumbersResponse { + result: OutboundTestPhonenumberInfo[]; + error?: APIError; + } + interface OutboundTestNumbersInterface { + /** + * Adds a personal phone number to test outgoing calls. Only one personal phone number can be used. To replace it with another, delete the existing one first. + */ + addOutboundTestPhoneNumber: ( + request: AddOutboundTestPhoneNumberRequest + ) => Promise; + /** + * Starts a call to the added phone number and pronounces a verification code. You have only 5 verification attempts per day and 100 in total. 1 minute should pass between 2 attempts. + */ + verifyOutboundTestPhoneNumber: ( + request: VerifyOutboundTestPhoneNumberRequest + ) => Promise; + /** + * Activates the phone number by the verification code. + */ + activateOutboundTestPhoneNumber: ( + request: ActivateOutboundTestPhoneNumberRequest + ) => Promise; + /** + * Deletes the existing phone number. + */ + delOutboundTestPhoneNumber: ( + request: DelOutboundTestPhoneNumberRequest + ) => Promise; + /** + * Shows the phone number info. + */ + getOutboundTestPhoneNumbers: ( + request: GetOutboundTestPhoneNumbersRequest + ) => Promise; + } + interface AddQueueRequest { + /** + * The application ID + */ + applicationId: number; + /** + * The application name that can be used instead of application_id + */ + applicationName: string; + /** + * The queue name. The length must be less than 100 + */ + acdQueueName: string; + /** + * The integer queue priority. The highest priority is 0 + */ + acdQueuePriority?: number; + /** + * Whether to enable the auto binding of operators to a queue by skills comparing + */ + autoBinding?: boolean; + /** + * The value in the range of [0.5 ... 1.0]. The value 1.0 means the service probability 100% in challenge with a lower priority queue + */ + serviceProbability?: number; + /** + * The max queue size + */ + maxQueueSize?: number; + /** + * The max predicted waiting time in minutes. The client is rejected if the predicted waiting time is greater than the max predicted waiting time + */ + maxWaitingTime?: number; + /** + * The average service time in seconds. Specify the parameter to correct or initialize the waiting time prediction + */ + averageServiceTime?: number; + } + interface AddQueueResponse { + /** + * 1 + */ + result: number; + /** + * The ACD queue ID + */ + acdQueueId: number; + error?: APIError; + } + interface BindUserToQueueRequest { + /** + * Whether to bind or unbind users + */ + bind: boolean; + /** + * The application ID + */ + applicationId: number; + /** + * The application name that can be used instead of application_id + */ + applicationName: string; + /** + * The user ID list separated by semicolons (;). Use the 'all' value to specify all users bound to the application + */ + userId: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;). user_name can be used instead of user_id + */ + userName: string | string[]; + /** + * The ACD queue ID list separated by semicolons (;). Use the 'all' value to specify all queues bound to the application + */ + acdQueueId: 'any' | number | number[]; + /** + * The queue name that can be used instead of acd_queue_id. The queue name list separated by semicolons (;) + */ + acdQueueName: string | string[]; + } + interface BindUserToQueueResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface DelQueueRequest { + /** + * The ACD queue ID list separated by semicolons (;) + */ + acdQueueId: 'any' | number | number[]; + /** + * The ACD queue name that can be used instead of acd_queue_id. The ACD queue name list separated by semicolons (;) + */ + acdQueueName: string | string[]; + } + interface DelQueueResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SetQueueInfoRequest { + /** + * The ACD queue ID + */ + acdQueueId: number; + /** + * The ACD queue name that can be used instead of acd_queue_id + */ + acdQueueName: string; + /** + * The new queue name. The length must be less than 100 + */ + newAcdQueueName?: string; + /** + * The integer queue priority. The highest priority is 0 + */ + acdQueuePriority?: number; + /** + * Whether to enable the auto binding of operators to a queue by skills comparing + */ + autoBinding?: boolean; + /** + * The value in the range of [0.5 ... 1.0]. The value 1.0 means the service probability 100% in challenge with a lower priority queue + */ + serviceProbability?: number; + /** + * The max queue size + */ + maxQueueSize?: number; + /** + * The max predicted waiting time in minutes. The client is rejected if the predicted waiting time is greater than the max predicted waiting time + */ + maxWaitingTime?: number; + /** + * The average service time in seconds. Specify the parameter to correct or initialize the waiting time prediction + */ + averageServiceTime?: number; + /** + * The new application ID + */ + applicationId?: number; + } + interface SetQueueInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetQueuesRequest { + /** + * The ACD queue ID to filter + */ + acdQueueId?: number; + /** + * The ACD queue name part to filter + */ + acdQueueName?: string; + /** + * The application ID to filter + */ + applicationId?: number; + /** + * The skill ID to filter + */ + skillId?: number; + /** + * The excluded skill ID to filter + */ + excludedSkillId?: number; + /** + * Whether to get the bound skills + */ + withSkills?: boolean; + /** + * The skill to show in the 'skills' field output + */ + showingSkillId?: number; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + /** + * Whether to include the number of agents bound to the queue + */ + withOperatorcount?: boolean; + } + interface GetQueuesResponse { + result: QueueInfo[]; + /** + * The total found queue count + */ + totalCount: number; + /** + * The returned queue count + */ + count: number; + error?: APIError; + } + interface GetACDStateRequest { + /** + * The ACD queue ID list separated by semicolons (;). Use the 'all' value to select all ACD queues + */ + acdQueueId?: 'any' | number | number[]; + } + interface GetACDStateResponse { + result: ACDState; + error?: APIError; + } + interface QueuesInterface { + /** + * Adds a new ACD queue. + */ + addQueue: (request: AddQueueRequest) => Promise; + /** + * Bind/unbind users to/from the specified ACD queues. Note that users and queues should be already bound to the same application. + */ + bindUserToQueue: (request: BindUserToQueueRequest) => Promise; + /** + * Deletes the ACD queue. + */ + delQueue: (request: DelQueueRequest) => Promise; + /** + * Edits the ACD queue. + */ + setQueueInfo: (request: SetQueueInfoRequest) => Promise; + /** + * Gets the ACD queues. + */ + getQueues: (request: GetQueuesRequest) => Promise; + /** + * Gets the current ACD queue state. + */ + getACDState: (request: GetACDStateRequest) => Promise; + } + interface GetSmartQueueRealtimeMetricsRequest { + /** + * The application ID to search by + */ + applicationId: number; + /** + * The application name to search by. Can be used instead of the application_id parameter + */ + applicationName: string; + /** + * The report type. Possible values are: calls_blocked_percentage, count_blocked_calls, im_blocked_chats_percentage, im_count_blocked_chats, im_answered_chats_rate, average_abandonment_rate, count_abandonment_calls, service_level, im_service_level, occupancy_rate, im_agent_occupancy_rate, agent_utilization_rate, im_agent_utilization_rate, sum_agents_online_time, sum_agents_ready_time, sum_agents_dialing_time, sum_agents_in_service_time, sum_agents_in_service_incoming_time, sum_agents_in_service_outcoming_time, sum_agents_afterservice_time, sum_agents_dnd_time, sum_agents_custom_1_time, sum_agents_custom_2_time, sum_agents_custom_3_time, sum_agents_custom_4_time, sum_agents_custom_5_time, sum_agents_custom_6_time, sum_agents_custom_7_time, sum_agents_custom_8_time, sum_agents_custom_9_time, sum_agents_custom_10_time, sum_agents_banned_time, im_sum_agents_online_time, im_sum_agents_ready_time, im_sum_agents_in_service_time, im_sum_agents_dnd_time, im_sum_agents_custom_1_time, im_sum_agents_custom_2_time, im_sum_agents_custom_3_time, im_sum_agents_custom_4_time, im_sum_agents_custom_5_time, im_sum_agents_custom_6_time, im_sum_agents_custom_7_time, im_sum_agents_custom_8_time, im_sum_agents_custom_9_time, im_sum_agents_custom_10_time, im_sum_agents_banned_time, average_agents_idle_time, max_agents_idle_time, min_agents_idle_time, percentile_0_25_agents_idle_time, percentile_0_50_agents_idle_time, percentile_0_75_agents_idle_time, min_time_in_queue, max_time_in_queue, average_time_in_queue, min_answer_speed, max_answer_speed, average_answer_speed, im_min_answer_speed, im_max_answer_speed, im_average_answer_speed, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime, count_agent_unanswered_calls, im_count_agent_unanswered_chats, min_reaction_time, max_reaction_time, average_reaction_time, im_min_reaction_time, im_max_reaction_time, im_average_reaction_time, im_count_abandonment_chats, im_count_lost_chats, im_lost_chats_rate + */ + reportType: string | string[]; + /** + * The user ID list with a maximum of 5 values separated by semicolons (;). Use the 'all' value to select all users. Can operate as a filter for the **occupancy_rate**, **sum_agents_online_time**, **sum_agents_ready_time**, **sum_agents_dialing_time**, **sum_agents_in_service_time**, **sum_agents_afterservice_time**, **sum_agents_dnd_time**, **sum_agents_banned_time**, **min_handle_time**, **max_handle_time**, **average_handle_time**, **count_handled_calls**, **min_after_call_worktime**, **max_after_call_worktime**, **average_after_call_worktime** report types + */ + userId?: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;). user_name can be used instead of user_id + */ + userName?: string | string[]; + /** + * The SmartQueue name list separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + /** + * The from date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss. Default is the current time minus 30 minutes + */ + fromDate?: Date; + /** + * The to date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss. Default is the current time + */ + toDate?: Date; + /** + * The selected timezone or the 'auto' value (the account location) + */ + timezone?: string; + /** + * Interval format: YYYY-MM-DD HH:mm:ss. Default is 30 minutes + */ + interval?: string; + /** + * Group the result by **agent** or *queue*. The **agent** grouping is allowed for 1 queue and for the occupancy_rate, sum_agents_online_time, sum_agents_ready_time, sum_agents_dialing_time, sum_agents_in_service_time, sum_agents_afterservice_time, sum_agents_dnd_time, sum_agents_banned_time, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime report types. The **queue** grouping allowed for the calls_blocked_percentage, count_blocked_calls, average_abandonment_rate, count_abandonment_calls, service_level, occupancy_rate, min_time_in_queue, max_time_in_queue, average_time_in_queue, min_answer_speed, max_answer_speed, average_answer_speed, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime report types + */ + groupBy?: string; + /** + * Maximum waiting time. Required for the **service_level** report type + */ + maxWaitingSec?: number; + } + interface GetSmartQueueRealtimeMetricsResponse { + result: SmartQueueMetricsResult[]; + /** + * The used timezone, e.g., 'Etc/GMT' + */ + timezone: string; + error?: APIError; + } + interface GetSmartQueueDayHistoryRequest { + /** + * The application ID to search by + */ + applicationId: number; + /** + * The application name to search by. Can be used instead of the application_id parameter + */ + applicationName: string; + /** + * The SmartQueue ID list with a maximum of 5 values separated by semicolons (;). Can operate as filter for the **calls_blocked_percentage**, **count_blocked_calls**, **average_abandonment_rate**, **count_abandonment_calls**, **service_level**, **occupancy_rate**, **min_time_in_queue**, **max_time_in_queue**, **average_time_in_queue**, **min_answer_speed**, **max_answer_speed**, **average_answer_speed**, **min_handle_time**, **max_handle_time**, **average_handle_time**, **count_handled_calls**, **min_after_call_worktime**, **max_after_call_worktime**, **average_after_call_worktime** report types + */ + sqQueueId: 'any' | number | number[]; + /** + * The report type. Possible values are: calls_blocked_percentage, count_blocked_calls, im_blocked_chats_percentage, im_count_blocked_chats, im_answered_chats_rate, average_abandonment_rate, count_abandonment_calls, service_level, im_service_level, occupancy_rate, im_agent_occupancy_rate, agent_utilization_rate, im_agent_utilization_rate, sum_agents_online_time, sum_agents_ready_time, sum_agents_dialing_time, sum_agents_in_service_time, sum_agents_in_service_incoming_time, sum_agents_in_service_outcoming_time, sum_agents_afterservice_time, sum_agents_dnd_time, sum_agents_custom_1_time, sum_agents_custom_2_time, sum_agents_custom_3_time, sum_agents_custom_4_time, sum_agents_custom_5_time, sum_agents_custom_6_time, sum_agents_custom_7_time, sum_agents_custom_8_time, sum_agents_custom_9_time, sum_agents_custom_10_time, sum_agents_banned_time, im_sum_agents_online_time, im_sum_agents_ready_time, im_sum_agents_in_service_time, im_sum_agents_dnd_time, im_sum_agents_custom_1_time, im_sum_agents_custom_2_time, im_sum_agents_custom_3_time, im_sum_agents_custom_4_time, im_sum_agents_custom_5_time, im_sum_agents_custom_6_time, im_sum_agents_custom_7_time, im_sum_agents_custom_8_time, im_sum_agents_custom_9_time, im_sum_agents_custom_10_time, im_sum_agents_banned_time, average_agents_idle_time, max_agents_idle_time, min_agents_idle_time, percentile_0_25_agents_idle_time, percentile_0_50_agents_idle_time, percentile_0_75_agents_idle_time, min_time_in_queue, max_time_in_queue, average_time_in_queue, min_answer_speed, max_answer_speed, average_answer_speed, im_min_answer_speed, im_max_answer_speed, im_average_answer_speed, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime, count_agent_unanswered_calls, im_count_agent_unanswered_chats, min_reaction_time, max_reaction_time, average_reaction_time, im_min_reaction_time, im_max_reaction_time, im_average_reaction_time, im_count_abandonment_chats, im_count_lost_chats, im_lost_chats_rate + */ + reportType: string | string[]; + /** + * The user ID list with a maximum of 5 values separated by semicolons (;). Use the 'all' value to select all users. Can operate as a filter for the **occupancy_rate**, **sum_agents_online_time**, **sum_agents_ready_time**, **sum_agents_dialing_time**, **sum_agents_in_service_time**, **sum_agents_afterservice_time**, **sum_agents_dnd_time**, **sum_agents_banned_time**, **min_handle_time**, **max_handle_time**, **average_handle_time**, **count_handled_calls**, **min_after_call_worktime**, **max_after_call_worktime**, **average_after_call_worktime** report types + */ + userId?: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;). user_name can be used instead of user_id + */ + userName?: string | string[]; + /** + * The SmartQueue name list separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + /** + * The from date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss. Default is the current time minus 1 day + */ + fromDate?: Date; + /** + * The to date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss. Default is the current time + */ + toDate?: Date; + /** + * The selected timezone or the 'auto' value (the account location) + */ + timezone?: string; + /** + * Interval format: YYYY-MM-DD HH:mm:ss. Default is 1 day + */ + interval?: string; + /** + * Group the result by **agent** or *queue*. The **agent** grouping is allowed only for 1 queue and for the occupancy_rate, sum_agents_online_time, sum_agents_ready_time, sum_agents_dialing_time, sum_agents_in_service_time, sum_agents_afterservice_time, sum_agents_dnd_time, sum_agents_banned_time, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime report types. The **queue** grouping allowed for the calls_blocked_percentage, count_blocked_calls, average_abandonment_rate, count_abandonment_calls, service_level, occupancy_rate, min_time_in_queue, max_time_in_queue, average_time_in_queue, min_answer_speed, max_answer_speed, average_answer_speed, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime report types + */ + groupBy?: string; + /** + * Maximum waiting time. Required for the **service_level** report type + */ + maxWaitingSec?: number; + } + interface GetSmartQueueDayHistoryResponse { + result: SmartQueueMetricsResult[]; + /** + * The used timezone, e.g., 'Etc/GMT' + */ + timezone: string; + error?: APIError; + } + interface RequestSmartQueueHistoryRequest { + /** + * The application ID to search by + */ + applicationId: number; + /** + * The application name to search by. Can be used instead of the application_id parameter + */ + applicationName: string; + /** + * The SmartQueue ID list with a maximum of 5 values separated by semicolons (;). Can operate as filter for the **calls_blocked_percentage**, **count_blocked_calls**, **average_abandonment_rate**, **count_abandonment_calls**, **service_level**, **occupancy_rate**, **min_time_in_queue**, **max_time_in_queue**, **average_time_in_queue**, **min_answer_speed**, **max_answer_speed**, **average_answer_speed**, **min_handle_time**, **max_handle_time**, **average_handle_time**, **count_handled_calls**, **min_after_call_worktime**, **max_after_call_worktime**, **average_after_call_worktime** report types + */ + sqQueueId: 'any' | number | number[]; + /** + * The from date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss. Default is the current time minus 1 day + */ + fromDate: Date; + /** + * The to date in the selected timezone in 24-h format: YYYY-MM-DD HH:mm:ss. Default is the current time + */ + toDate: Date; + /** + * The report type. Possible values are: calls_blocked_percentage, count_blocked_calls, im_blocked_chats_percentage, im_count_blocked_chats, im_answered_chats_rate, average_abandonment_rate, count_abandonment_calls, service_level, im_service_level, occupancy_rate, im_agent_occupancy_rate, agent_utilization_rate, im_agent_utilization_rate, sum_agents_online_time, sum_agents_ready_time, sum_agents_dialing_time, sum_agents_in_service_time, sum_agents_in_service_incoming_time, sum_agents_in_service_outcoming_time, sum_agents_afterservice_time, sum_agents_dnd_time, sum_agents_custom_1_time, sum_agents_custom_2_time, sum_agents_custom_3_time, sum_agents_custom_4_time, sum_agents_custom_5_time, sum_agents_custom_6_time, sum_agents_custom_7_time, sum_agents_custom_8_time, sum_agents_custom_9_time, sum_agents_custom_10_time, sum_agents_banned_time, im_sum_agents_online_time, im_sum_agents_ready_time, im_sum_agents_in_service_time, im_sum_agents_dnd_time, im_sum_agents_custom_1_time, im_sum_agents_custom_2_time, im_sum_agents_custom_3_time, im_sum_agents_custom_4_time, im_sum_agents_custom_5_time, im_sum_agents_custom_6_time, im_sum_agents_custom_7_time, im_sum_agents_custom_8_time, im_sum_agents_custom_9_time, im_sum_agents_custom_10_time, im_sum_agents_banned_time, average_agents_idle_time, max_agents_idle_time, min_agents_idle_time, percentile_0_25_agents_idle_time, percentile_0_50_agents_idle_time, percentile_0_75_agents_idle_time, min_time_in_queue, max_time_in_queue, average_time_in_queue, min_answer_speed, max_answer_speed, average_answer_speed, im_min_answer_speed, im_max_answer_speed, im_average_answer_speed, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime, count_agent_unanswered_calls, im_count_agent_unanswered_chats, min_reaction_time, max_reaction_time, average_reaction_time, im_min_reaction_time, im_max_reaction_time, im_average_reaction_time, im_count_abandonment_chats, im_count_lost_chats, im_lost_chats_rate + */ + reportType: string | string[]; + /** + * The user ID list with a maximum of 5 values separated by semicolons (;). Use the 'all' value to select all users. Can operate as a filter for the **occupancy_rate**, **sum_agents_online_time**, **sum_agents_ready_time**, **sum_agents_dialing_time**, **sum_agents_in_service_time**, **sum_agents_afterservice_time**, **sum_agents_dnd_time**, **sum_agents_banned_time**, **min_handle_time**, **max_handle_time**, **average_handle_time**, **count_handled_calls**, **min_after_call_worktime**, **max_after_call_worktime**, **average_after_call_worktime** report types + */ + userId?: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + /** + * The SmartQueue name list separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + /** + * The selected timezone or the 'auto' value (the account location) + */ + timezone?: string; + /** + * Interval format: YYYY-MM-DD HH:mm:ss. Default is 1 day + */ + interval?: string; + /** + * Group the result by **agent** or *queue*. The **agent** grouping is allowed only for 1 queue and for the occupancy_rate, sum_agents_online_time, sum_agents_ready_time, sum_agents_dialing_time, sum_agents_in_service_time, sum_agents_afterservice_time, sum_agents_dnd_time, sum_agents_banned_time, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime report types. The **queue** grouping allowed for the calls_blocked_percentage, count_blocked_calls, average_abandonment_rate, count_abandonment_calls, service_level, occupancy_rate, min_time_in_queue, max_time_in_queue, average_time_in_queue, min_answer_speed, max_answer_speed, average_answer_speed, min_handle_time, max_handle_time, average_handle_time, count_handled_calls, min_after_call_worktime, max_after_call_worktime, average_after_call_worktime report types + */ + groupBy?: string; + /** + * Maximum waiting time. Required for the **service_level** report type + */ + maxWaitingSec?: number; + } + interface RequestSmartQueueHistoryResponse { + /** + * 1 + */ + result: number; + /** + * History report ID + */ + historyReportId: number; + error?: APIError; + } + interface GetSQStateRequest { + /** + * The application ID to search by + */ + applicationId: number; + /** + * The SmartQueue ID list separated by semicolons (;). Use the 'all' value to select all SmartQueues + */ + sqQueueId: 'any' | number | number[]; + /** + * The application name to search by. Can be used instead of the application_id parameter + */ + applicationName?: string; + /** + * The SmartQueue name list separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + /** + * The selected timezone or the 'auto' value (the account location) + */ + timezone?: string; + } + interface GetSQStateResponse { + result: SmartQueueState[]; + error?: APIError; + } + interface SQ_SetAgentCustomStatusMappingRequest { + /** + * Status name + */ + sqStatusName: string; + /** + * Custom status name + */ + customStatusName: string; + /** + * Application ID + */ + applicationId: number; + } + interface SQ_SetAgentCustomStatusMappingResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_GetAgentCustomStatusMappingRequest { + /** + * Application ID + */ + applicationId?: number; + } + interface SQ_GetAgentCustomStatusMappingResponse { + /** + * Status name + */ + sqStatusName: string; + /** + * Custom status name + */ + customStatusName: string; + error?: APIError; + } + interface SQ_DeleteAgentCustomStatusMappingRequest { + /** + * Application ID + */ + applicationId: number; + /** + * Status name + */ + sqStatusName?: string; + } + interface SQ_DeleteAgentCustomStatusMappingResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_AddQueueRequest { + /** + * ID of the application to bind to + */ + applicationId: number; + /** + * Unique SmartQueue name within the application, up to 100 characters + */ + sqQueueName: string; + /** + * Agent selection strategy for calls. Accepts one of the following values: "MOST_QUALIFIED", "LEAST_QUALIFIED", "MAX_WAITING_TIME" + */ + callAgentSelection: string; + /** + * Call type requests prioritizing strategy. Accepts one of the [SQTaskSelectionStrategies] enum values + */ + callTaskSelection: string; + /** + * Name of the application to bind to. Can be used instead of application_id + */ + applicationName?: string; + /** + * Agent selection strategy for messages. Accepts one of the following values: "MOST_QUALIFIED", "LEAST_QUALIFIED", "MAX_WAITING_TIME". The default value is **call_agent_selection** + */ + imAgentSelection?: string; + /** + * IM type requests prioritizing strategy. Accepts one of the [SQTaskSelectionStrategies] enum values. The default value is **call_task_selection** + */ + imTaskSelection?: string; + fallbackAgentSelection?: string; + /** + * Comment, up to 200 characters + */ + description?: string; + /** + * Maximum time in minutes that a CALL-type request can remain in the queue without being assigned to an agent + */ + callMaxWaitingTime?: number; + /** + * Maximum time in minutes that an IM-type request can remain in the queue without being assigned to an agent + */ + imMaxWaitingTime?: number; + /** + * Maximum size of the queue with CALL-type requests + */ + callMaxQueueSize?: number; + /** + * Maximum size of the queue with IM-type requests + */ + imMaxQueueSize?: number; + /** + * The queue's priority from 1 to 100 + */ + priority?: number; + } + interface SQ_AddQueueResponse { + /** + * ID of the added queue + */ + sqQueueId: number; + error?: APIError; + } + interface SQ_SetQueueInfoRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * ID of the SmartQueue to search for + */ + sqQueueId: number; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * Name of the SmartQueue to search for. Can be used instead of sq_queue_id + */ + sqQueueName?: string; + /** + * New SmartQueue name within the application, up to 100 characters + */ + newSqQueueName?: string; + /** + * Agent selection strategy for calls. Accepts one of the following values: "MOST_QUALIFIED", "LEAST_QUALIFIED", "MAX_WAITING_TIME" + */ + callAgentSelection?: string; + /** + * Agent selection strategy for messages. Accepts one of the following values: "MOST_QUALIFIED", "LEAST_QUALIFIED", "MAX_WAITING_TIME". The default value is **call_agent_selection** + */ + imAgentSelection?: string; + /** + * Strategy of prioritizing CALL-type requests for service. Accepts one of the following values: "MAX_PRIORITY", "MAX_WAITING_TIME" + */ + callTaskSelection?: string; + /** + * Strategy of prioritizing IM-type requests for service. Accepts one of the following values: "MAX_PRIORITY", "MAX_WAITING_TIME". The default value is **call_task_selection** + */ + imTaskSelection?: string; + fallbackAgentSelection?: string; + /** + * Comment, up to 200 characters + */ + description?: string; + /** + * Maximum time in minutes that a CALL-type request can remain in the queue without being assigned to an agent + */ + callMaxWaitingTime?: number; + /** + * Maximum time in minutes that an IM-type request can remain in the queue without being assigned to an agent + */ + imMaxWaitingTime?: number; + /** + * Maximum size of the queue with CALL-type requests + */ + callMaxQueueSize?: number; + /** + * Maximum size of the queue with IM-type requests + */ + imMaxQueueSize?: number; + /** + * The queue's priority from 1 to 100 + */ + priority?: number; + } + interface SQ_SetQueueInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_DelQueueRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * List of SmartQueue IDs separated by semicolons (;). Use 'all' to delete all the queues + */ + sqQueueId: 'any' | number | number[]; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of SmartQueue names separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + } + interface SQ_DelQueueResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_GetQueuesRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of SmartQueue IDs separated by semicolons (;) + */ + sqQueueId?: 'any' | number | number[]; + /** + * List of SmartQueue names separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + /** + * Substring of the SmartQueue name to filter + */ + sqQueueNameTemplate?: string; + /** + * ID of the user that is bound to the queue + */ + userId?: number; + /** + * Name of the user that is bound to the queue. Can be used instead of user_id + */ + userName?: string; + /** + * ID of the user that is not bound to the queue + */ + excludedUserId?: number; + /** + * Name of the user that is not bound to the queue. Can be used instead of excluded_user_id + */ + excludedUserName?: string; + /** + * Number of items to show in the output + */ + count?: number; + /** + * Number of items to skip in the output + */ + offset?: number; + /** + * Whether to include the number of agents bound to the queue + */ + withAgentcount?: boolean; + } + interface SQ_GetQueuesResponse { + /** + * The found queue(s) + */ + result: GetSQQueuesResult; + error?: APIError; + } + interface SQ_AddSkillRequest { + /** + * ID of the application to bind to + */ + applicationId: number; + /** + * Unique skill name within the application + */ + sqSkillName: string; + /** + * Name of the application to bind to. Can be used instead of application_id + */ + applicationName?: string; + /** + * Comment, up to 200 characters + */ + description?: string; + } + interface SQ_AddSkillResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_DelSkillRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * List of skill IDs separated by semicolons (;). Use 'all' to delete all the skills + */ + sqSkillId: 'any' | number | number[]; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of skill names separated by semicolons (;). Can be used instead of sq_skill_id + */ + sqSkillName?: string | string[]; + } + interface SQ_DelSkillResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_SetSkillInfoRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * ID of the skill + */ + sqSkillId: number; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * Name of the skill. Can be used instead of sq_skill_id + */ + sqSkillName?: string; + /** + * New unique skill name within the application + */ + newSqSkillName?: string; + /** + * Comment, up to 200 characters + */ + description?: string; + } + interface SQ_SetSkillInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_BindSkillRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * List of user IDs separated by semicolons (;). Use 'all' to select all the users + */ + userId: 'any' | number | number[]; + /** + * Skills to be bound to agents in the json array format. The array should contain objects with the sq_skill_id/sq_skill_name and sq_skill_level keys where skill levels range from 1 to 5 + */ + sqSkills: any; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of user names separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + /** + * Binding mode. Accepts one of the [SQSkillBindingModes] enum values + */ + bindMode?: string; + } + interface SQ_BindSkillResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_UnbindSkillRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * List of user IDs separated by semicolons (;). Use 'all' to select all the users + */ + userId: 'any' | number | number[]; + /** + * List of skill IDs separated by semicolons (;). Use 'all' to undbind all the skills + */ + sqSkillId: 'any' | number | number[]; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of user names separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + /** + * List of skill names separated by semicolons (;). Can be used instead of sq_skill_id + */ + sqSkillName?: string | string[]; + } + interface SQ_UnbindSkillResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_GetSkillsRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of user IDs separated by semicolons (;) + */ + userId?: 'any' | number | number[]; + /** + * List of user names separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + /** + * List of skill IDs separated by semicolons (;) + */ + sqSkillId?: 'any' | number | number[]; + /** + * List of skill names separated by semicolons (;). Can be used instead of sq_skill_id + */ + sqSkillName?: string | string[]; + /** + * Substring of the skill name to filter, case-insensitive + */ + sqSkillNameTemplate?: string; + /** + * ID of the user that is not bound to the skill + */ + excludedUserId?: number; + /** + * Name of the user that is not bound to the skill. Can be used instead of excluded_user_id + */ + excludedUserName?: string; + /** + * Number of items to show in the output + */ + count?: number; + /** + * Number of items to skip in the output + */ + offset?: number; + } + interface SQ_GetSkillsResponse { + /** + * The found skill(s). + */ + result: GetSQSkillsResult; + error?: APIError; + } + interface SQ_BindAgentRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * ID of the SmartQueue. Pass a list of values divided by ; or the "all" keyword + */ + sqQueueId: string; + /** + * List of user IDs separated by semicolons (;). Use 'all' to select all the users + */ + userId: 'any' | number | number[]; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * Name of the SmartQueue. Pass a list of names divided by ; or the "all" keyword + */ + sqQueueName?: string; + /** + * List of user names separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + /** + * Binding mode. Accepts one of the [SQAgentBindingModes] enum values + */ + bindMode?: string; + } + interface SQ_BindAgentResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_UnbindAgentRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * List of SmartQueue IDs separated by semicolons (;). Use 'all' to select all the queues + */ + sqQueueId: 'any' | number | number[]; + /** + * List of user IDs separated by semicolons (;). Use 'all' to select all the users + */ + userId: 'any' | number | number[]; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of SmartQueue names separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + /** + * List of user names separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + } + interface SQ_UnbindAgentResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SQ_GetAgentsRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * Whether the agent can handle calls. When set to false, the agent is excluded from the CALL-request distribution + */ + handleCalls: boolean; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of SmartQueue IDs separated by semicolons (;). Use 'all' to select all the queues + */ + sqQueueId?: 'any' | number | number[]; + /** + * List of SmartQueue names separated by semicolons (;). Can be used instead of sq_queue_id + */ + sqQueueName?: string | string[]; + /** + * ID of the SmartQueue to exclude + */ + excludedSqQueueId?: number; + /** + * Name of the SmartQueue to exclude. Can be used instead of excluded_sq_queue_id + */ + excludedSqQueueName?: string; + /** + * Skills to filter in the json array format. The array should contain objects with the sq_skill_id/sq_skill_name, min_sq_skill_level, and max_sq_skill_level keys where skill levels range from 1 to 5 + */ + sqSkills?: any; + /** + * List of user IDs separated by semicolons (;) + */ + userId?: 'any' | number | number[]; + /** + * List of user names separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + /** + * Substring of the user name to filter + */ + userNameTemplate?: string; + /** + * Filter statuses in the json array format. The array should contain objects with the sq_status_type and sq_status_name keys. Possible values for sq_status_type are 'CALL' and'IM'. Possible values for sq_status_name are 'OFFLINE', 'ONLINE', 'READY', 'IN_SERVICE', 'AFTER_SERVICE', 'DND' + */ + sqStatuses?: any; + /** + * Whether to display agent skills + */ + withSqSkills?: boolean; + /** + * Whether to display agent queues + */ + withSqQueues?: boolean; + /** + * Whether to display agent current statuses + */ + withSqStatuses?: boolean; + /** + * Number of items to show in the output + */ + count?: number; + /** + * Number of items to skip in the output + */ + offset?: number; + } + interface SQ_GetAgentsResponse { + /** + * The found agent(s) + */ + result: GetSQAgentsResult; + error?: APIError; + } + interface SQ_SetAgentInfoRequest { + /** + * ID of the application to search by + */ + applicationId: number; + /** + * List of user IDs separated by semicolons (;). Use 'all' to select all the users + */ + userId: 'any' | number | number[]; + /** + * Whether the agent can handle calls. When set to false, the agent is excluded from the CALL-request distribution + */ + handleCalls: boolean; + /** + * Name of the application to search by. Can be used instead of application_id + */ + applicationName?: string; + /** + * List of user names separated by semicolons (;). Can be used instead of user_id + */ + userName?: string | string[]; + /** + * Maximum number of chats that the user processes simultaneously + */ + maxSimultaneousConversations?: number; + } + interface SQ_SetAgentInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SmartQueueInterface { + /** + * Gets the metrics for the specified SmartQueue for the last 30 minutes. Refer to the SmartQueue reporting guide to learn more. + */ + getSmartQueueRealtimeMetrics: ( + request: GetSmartQueueRealtimeMetricsRequest + ) => Promise; + /** + * Gets the metrics for the specified SmartQueue for the last 2 days. Refer to the SmartQueue reporting guide to learn more. + */ + getSmartQueueDayHistory: ( + request: GetSmartQueueDayHistoryRequest + ) => Promise; + /** + * Gets history for the specified SmartQueue. Refer to the SmartQueue reporting guide to learn more. + */ + requestSmartQueueHistory: ( + request: RequestSmartQueueHistoryRequest + ) => Promise; + /** + * Gets the current state of the specified SmartQueue. + */ + getSQState: (request: GetSQStateRequest) => Promise; + /** + * Adds a status if there is no match for the given internal status and renames it if there is a match. It means that if the passed **sq_status_name** parameter is not in the mapping table, a new entry is created in there; if it is, the **name** field in its mapping is replaced with **custom_status_name**. + */ + sQ_SetAgentCustomStatusMapping: ( + request: SQ_SetAgentCustomStatusMappingRequest + ) => Promise; + /** + * Returns the mapping list of SQ statuses and custom statuses. SQ statuses are returned whether or not they have mappings to custom statuses. + */ + sQ_GetAgentCustomStatusMapping: ( + request: SQ_GetAgentCustomStatusMappingRequest + ) => Promise; + /** + * Removes a mapping from the mapping table. If there is no such mapping, does nothing. + */ + sQ_DeleteAgentCustomStatusMapping: ( + request: SQ_DeleteAgentCustomStatusMappingRequest + ) => Promise; + /** + * Adds a new queue. + */ + sQ_AddQueue: (request: SQ_AddQueueRequest) => Promise; + /** + * Edits an existing queue. + */ + sQ_SetQueueInfo: (request: SQ_SetQueueInfoRequest) => Promise; + /** + * Deletes a queue. + */ + sQ_DelQueue: (request: SQ_DelQueueRequest) => Promise; + /** + * Gets the queue(s). + */ + sQ_GetQueues: (request: SQ_GetQueuesRequest) => Promise; + /** + * Adds a new skill to the app. + */ + sQ_AddSkill: (request: SQ_AddSkillRequest) => Promise; + /** + * Deletes a skill and detaches it from agents. + */ + sQ_DelSkill: (request: SQ_DelSkillRequest) => Promise; + /** + * Edits an existing skill. + */ + sQ_SetSkillInfo: (request: SQ_SetSkillInfoRequest) => Promise; + /** + * Binds skills to agents. + */ + sQ_BindSkill: (request: SQ_BindSkillRequest) => Promise; + /** + * Unbinds skills from agents. + */ + sQ_UnbindSkill: (request: SQ_UnbindSkillRequest) => Promise; + /** + * Gets the skill(s). + */ + sQ_GetSkills: (request: SQ_GetSkillsRequest) => Promise; + /** + * Binds agents to a queue. + */ + sQ_BindAgent: (request: SQ_BindAgentRequest) => Promise; + /** + * Unbinds agents from queues. + */ + sQ_UnbindAgent: (request: SQ_UnbindAgentRequest) => Promise; + /** + * Gets agents. + */ + sQ_GetAgents: (request: SQ_GetAgentsRequest) => Promise; + /** + * Edits the agent settings. + */ + sQ_SetAgentInfo: (request: SQ_SetAgentInfoRequest) => Promise; + } + interface AddSkillRequest { + /** + * The ACD operator skill name. The length must be less than 512 + */ + skillName: string; + } + interface AddSkillResponse { + /** + * 1 + */ + result: number; + /** + * The skill ID + */ + skillId: number; + error?: APIError; + } + interface DelSkillRequest { + /** + * The skill ID + */ + skillId: number; + /** + * The skill name that can be used instead of skill_id + */ + skillName: string; + } + interface DelSkillResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SetSkillInfoRequest { + /** + * The skill ID + */ + skillId: number; + /** + * The skill name that can be used instead of skill_id + */ + skillName: string; + /** + * The new skill name. The length must be less than 512 + */ + newSkillName: string; + } + interface SetSkillInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetSkillsRequest { + /** + * The skill ID to filter + */ + skillId?: number; + /** + * The skill name part to filter + */ + skillName?: string; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetSkillsResponse { + result: SkillInfo[]; + /** + * The total found skill count + */ + totalCount: number; + /** + * The returned skill count + */ + count: number; + error?: APIError; + } + interface BindSkillRequest { + /** + * The skill ID list separated by semicolons (;). Use the 'all' value to select all skills + */ + skillId: 'any' | number | number[]; + /** + * The skill name list separated by semicolons (;). Can be used instead of skill_id + */ + skillName: string | string[]; + /** + * The user ID list separated by semicolons (;). Use the 'all' value to select all users + */ + userId: 'any' | number | number[]; + /** + * The user name list separated by semicolons (;). user_name can be used instead of user_id + */ + userName: string | string[]; + /** + * The ACD queue ID list separated by semicolons (;). Use the 'all' value to select all ACD queues + */ + acdQueueId: 'any' | number | number[]; + /** + * The ACD queue name that can be used instead of acd_queue_id. The ACD queue name list separated by semicolons (;) + */ + acdQueueName: string | string[]; + /** + * The application ID. It is required if the user_name is specified + */ + applicationId?: number; + /** + * The application name that can be used instead of application_id + */ + applicationName?: string; + /** + * Whether to bind or unbind (set true or false respectively) + */ + bind?: boolean; + } + interface BindSkillResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SkillsInterface { + /** + * Adds a new operator's skill. Works only for ACDv1. For SmartQueue/ACDv2, use this reference. + */ + addSkill: (request: AddSkillRequest) => Promise; + /** + * Deletes an operator's skill. Works only for ACDv1. For SmartQueue/ACDv2, use this reference. + */ + delSkill: (request: DelSkillRequest) => Promise; + /** + * Edits an operator's skill. Works only for ACDv1. For SmartQueue/ACDv2, use this reference. + */ + setSkillInfo: (request: SetSkillInfoRequest) => Promise; + /** + * Gets the skills of an operator. Works only for ACDv1. For SmartQueue/ACDv2, use this reference. + */ + getSkills: (request: GetSkillsRequest) => Promise; + /** + * Binds the specified skills to the users (ACD operators) and/or the ACD queues. Works only for ACDv1. For SmartQueue/ACDv2, use this reference. + */ + bindSkill: (request: BindSkillRequest) => Promise; + } + interface AddAdminUserRequest { + /** + * The admin user name. The length must be less than 50 + */ + newAdminUserName: string; + /** + * The admin user display name. The length must be less than 256 + */ + adminUserDisplayName: string; + /** + * The admin user password. The length must be at least 6 symbols + */ + newAdminUserPassword: string; + /** + * Whether the admin user is active + */ + adminUserActive?: boolean; + /** + * The role(s) ID created via Managing Admin Roles methods. The attaching admin role ID list separated by semicolons (;). Use the 'all' value to select all admin roles + */ + adminRoleId?: string; + /** + * The role(s) name(s) created via Managing Admin Roles methods. The attaching admin role name that can be used instead of admin_role_id + */ + adminRoleName?: string | string[]; + } + interface AddAdminUserResponse { + /** + * 1 + */ + result: number; + /** + * The new admin user ID + */ + adminUserId: number; + /** + * The admin user API key + */ + adminUserApiKey: string; + error?: APIError; + } + interface DelAdminUserRequest { + /** + * The admin user ID list separated by semicolons (;). Use the 'all' value to select all admin users + */ + requiredAdminUserId: 'any' | number | number[]; + /** + * The admin user name to delete, can be used instead of required_admin_user_id + */ + requiredAdminUserName: string | string[]; + } + interface DelAdminUserResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SetAdminUserInfoRequest { + /** + * The admin user to edit + */ + requiredAdminUserId: number; + /** + * The admin user to edit, can be used instead of required_admin_user_id + */ + requiredAdminUserName: string; + /** + * The new admin user name. The length must be less than 50 + */ + newAdminUserName?: string; + /** + * The new admin user display name. The length must be less than 256 + */ + adminUserDisplayName?: string; + /** + * The new admin user password. The length must be at least 6 symbols + */ + newAdminUserPassword?: string; + /** + * Whether the admin user is active + */ + adminUserActive?: boolean; + } + interface SetAdminUserInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetAdminUsersRequest { + /** + * The admin user ID to filter + */ + requiredAdminUserId?: number; + /** + * The admin user name part to filter + */ + requiredAdminUserName?: string; + /** + * The admin user display name part to filter + */ + adminUserDisplayName?: string; + /** + * Whether the admin user is active to filter + */ + adminUserActive?: boolean; + /** + * Whether to get the attached admin roles + */ + withRoles?: boolean; + /** + * Whether to get the admin user permissions + */ + withAccessEntries?: boolean; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetAdminUsersResponse { + result: AdminUser[]; + /** + * The total found admin user count + */ + totalCount: number; + /** + * The returned admin user count + */ + count: number; + error?: APIError; + } + interface AttachAdminRoleRequest { + /** + * The admin user ID list separated by semicolons (;). Use the 'all' value to select all admin users + */ + requiredAdminUserId: 'any' | number | number[]; + /** + * The admin user name to bind, can be used instead of required_admin_user_id + */ + requiredAdminUserName: string | string[]; + /** + * The role(s) ID created via Managing Admin Roles methods. The attached admin role ID list separated by semicolons (;). Use the 'all' value to select alladmin roles + */ + adminRoleId: 'any' | number | number[]; + /** + * The role(s) name(s) created via Managing Admin Roles methods. The admin role name to attach, can be used instead of admin_role_id + */ + adminRoleName: string | string[]; + /** + * The merge mode. The following values are possible: add, del, set + */ + mode?: string; + } + interface AttachAdminRoleResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface AdminUsersInterface { + /** + * Adds a new admin user into the specified parent or child account. + */ + addAdminUser: (request: AddAdminUserRequest) => Promise; + /** + * Deletes the specified admin user. + */ + delAdminUser: (request: DelAdminUserRequest) => Promise; + /** + * Edits the specified admin user. + */ + setAdminUserInfo: (request: SetAdminUserInfoRequest) => Promise; + /** + * Gets the admin users of the specified account. Note that both account types - parent and child - can have its own admins. + */ + getAdminUsers: (request: GetAdminUsersRequest) => Promise; + /** + * Attaches the admin role(s) to the already existing admin(s). + */ + attachAdminRole: (request: AttachAdminRoleRequest) => Promise; + } + interface AddAdminRoleRequest { + /** + * The admin role name. The length must be less than 50 + */ + adminRoleName: string; + /** + * Whether the admin role is enabled. If false the allowed and denied entries have no affect + */ + adminRoleActive?: boolean; + /** + * The admin role ID list separated by semicolons (;). Use the 'all' value to select all admin roles. The list specifies the roles from which the new role automatically copies all permissions (allowed_entries and denied_entries) + */ + likeAdminRoleId?: 'any' | number | number[]; + /** + * The admin role name that can be used instead of like_admin_role_id. The name specifies a role from which the new role automatically copies all permissions (allowed_entries and denied_entries) + */ + likeAdminRoleName?: string | string[]; + /** + * The list of allowed access entries separated by semicolons (;) (the API function names) + */ + allowedEntries?: string | string[]; + /** + * The list of denied access entries separated by semicolons (;) (the API function names) + */ + deniedEntries?: string | string[]; + } + interface AddAdminRoleResponse { + /** + * 1 + */ + result: number; + /** + * The new admin role ID + */ + adminRoleId: number; + error?: APIError; + } + interface DelAdminRoleRequest { + /** + * The admin role ID list separated by semicolons (;). Use the 'all' value to select all admin roles + */ + adminRoleId: 'any' | number | number[]; + /** + * The admin role name to delete, can be used instead of admin_role_id + */ + adminRoleName: string | string[]; + } + interface DelAdminRoleResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface SetAdminRoleInfoRequest { + /** + * The admin role to edit + */ + adminRoleId: number; + /** + * The admin role to edit, can be used instead of admin_role_id + */ + adminRoleName: string; + /** + * The new admin role name. The length must be less than 50 + */ + newAdminRoleName?: string; + /** + * Whether the admin role is enabled. If false the allowed and denied entries have no affect + */ + adminRoleActive?: boolean; + /** + * The modification mode of the permission lists (allowed_entries and denied_entries). The following values are possible: add, del, set + */ + entryModificationMode?: string; + /** + * The list of allowed access entry changes separated by semicolons (;) (the API function names) + */ + allowedEntries?: string | string[]; + /** + * The list of denied access entry changes separated by semicolons (;) (the API function names) + */ + deniedEntries?: string | string[]; + /** + * The admin role ID list separated by semicolons (;). Use the 'all' value to select all admin roles. The list specifies the roles from which the allowed_entries and denied_entries are merged + */ + likeAdminRoleId?: 'any' | number | number[]; + /** + * The admin role name, can be used instead of like_admin_role_id. The name specifies a role from which the allowed_entries and denied_entries are merged + */ + likeAdminRoleName?: string | string[]; + } + interface SetAdminRoleInfoResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface GetAdminRolesRequest { + /** + * The admin role ID to filter + */ + adminRoleId?: number; + /** + * The admin role name part to filter + */ + adminRoleName?: string; + /** + * Whether the admin role is enabled to filter + */ + adminRoleActive?: boolean; + /** + * Whether to get the permissions + */ + withEntries?: boolean; + /** + * Whether to include the account roles + */ + withAccountRoles?: boolean; + /** + * Whether to include the parent roles + */ + withParentRoles?: boolean; + withSystemRoles?: boolean; + /** + * The attached admin user ID list separated by semicolons (;). Use the 'all' value to select all admin users + */ + includedAdminUserId?: 'any' | number | number[]; + /** + * Not attached admin user ID list separated by semicolons (;). Use the 'all' value to select all admin users + */ + excludedAdminUserId?: 'any' | number | number[]; + /** + * Set false to get roles with partial admin user list matching + */ + fullAdminUsersMatching?: string; + /** + * The admin user to show in the 'admin_users' field output + */ + showingAdminUserId?: number; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + } + interface GetAdminRolesResponse { + result: AdminRole[]; + /** + * The total found admin role count + */ + totalCount: number; + /** + * The returned admin role count + */ + count: number; + error?: APIError; + } + interface GetAvailableAdminRoleEntriesRequest {} + interface GetAvailableAdminRoleEntriesResponse { + /** + * Array of the admin role entries + */ + result: string[]; + error?: APIError; + } + interface AdminRolesInterface { + /** + * Adds a new admin role. + */ + addAdminRole: (request: AddAdminRoleRequest) => Promise; + /** + * Deletes the specified admin role. + */ + delAdminRole: (request: DelAdminRoleRequest) => Promise; + /** + * Edits the specified admin role. + */ + setAdminRoleInfo: (request: SetAdminRoleInfoRequest) => Promise; + /** + * Gets the admin roles. + */ + getAdminRoles: (request: GetAdminRolesRequest) => Promise; + /** + * Gets the all available admin role entries. + */ + getAvailableAdminRoleEntries: ( + request: GetAvailableAdminRoleEntriesRequest + ) => Promise; + } + interface AddAuthorizedAccountIPRequest { + /** + * The authorized IP4 or network + */ + authorizedIp: string; + /** + * Whether to remove the IP from the blacklist + */ + allowed?: boolean; + /** + * The IP address description + */ + description?: string; + } + interface AddAuthorizedAccountIPResponse { + /** + * 1 + */ + result: number; + error?: APIError; + } + interface DelAuthorizedAccountIPRequest { + /** + * The authorized IP4 or network to remove. Set to 'all' to remove all items + */ + authorizedIp: string; + /** + * Specify the parameter to remove the networks that contains the particular IP4. Can be used instead of autharized_ip + */ + containsIp: string; + /** + * Whether to remove the network from the white list. Set false to remove the network from the black list. Omit the parameter to remove the network from all lists + */ + allowed?: boolean; + } + interface DelAuthorizedAccountIPResponse { + /** + * The removed network count + */ + result: number; + error?: APIError; + } + interface GetAuthorizedAccountIPsRequest { + /** + * The authorized IP4 or network to filter + */ + authorizedIp?: string; + /** + * Whether the IP is allowed + */ + allowed?: boolean; + /** + * Specify the parameter to filter the networks that contains the particular IP4 + */ + containsIp?: string; + /** + * The max returning record count + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + /** + * The IP address description + */ + description?: string; + } + interface GetAuthorizedAccountIPsResponse { + result: AuthorizedAccountIP[]; + /** + * The total found network count + */ + totalCount: number; + /** + * The returned network count + */ + count: number; + error?: APIError; + } + interface CheckAuthorizedAccountIPRequest { + /** + * The IP4 to test + */ + authorizedIp: string; + } + interface CheckAuthorizedAccountIPResponse { + /** + * Whether the IP is allowed + */ + result: boolean; + /** + * The matched authorized IP or network (if found) + */ + authorizedIp?: string; + error?: APIError; + } + interface AuthorizedIPsInterface { + /** + * Adds a new authorized IP4 or network to the white/black list. + */ + addAuthorizedAccountIP: ( + request: AddAuthorizedAccountIPRequest + ) => Promise; + /** + * Removes the authorized IP4 or network from the white/black list. + */ + delAuthorizedAccountIP: ( + request: DelAuthorizedAccountIPRequest + ) => Promise; + /** + * Gets the authorized IP4 or network. + */ + getAuthorizedAccountIPs: ( + request: GetAuthorizedAccountIPsRequest + ) => Promise; + /** + * Tests whether the IP4 is banned or allowed. + */ + checkAuthorizedAccountIP: ( + request: CheckAuthorizedAccountIPRequest + ) => Promise; + } + interface SetDialogflowKeyRequest { + /** + * The Dialogflow key's ID + */ + dialogflowKeyId: number; + /** + * The Dialogflow keys's description. To clear previously set description leave the parameter blank or put whitespaces only + */ + description: string; + } + interface SetDialogflowKeyResponse { + result: number; + error?: APIError; + } + interface DialogflowCredentialsInterface { + /** + * Edits a Dialogflow key. + */ + setDialogflowKey: (request: SetDialogflowKeyRequest) => Promise; + } + interface SendSmsMessageRequest { + /** + * The source phone number + */ + source: string; + /** + * The destination phone number + */ + destination: string; + /** + * The message text, up to 765 characters. We split long messages greater than 160 GSM-7 characters or 70 UTF-16 characters into multiple segments. Each segment is charged as one message + */ + smsBody: string; + /** + * Whether to store outgoing message texts. Default value is false + */ + storeBody?: boolean; + } + interface SendSmsMessageResponse { + result: number; + /** + * Message ID + */ + messageId: number; + /** + * The number of fragments the message is divided into + */ + fragmentsCount: number; + error?: APIError; + } + interface A2PSendSmsRequest { + /** + * The SenderID for outgoing SMS. Please contact support for installing a SenderID + */ + srcNumber: string; + /** + * The destination phone numbers separated by semicolons (;). The maximum number of these phone numbers is 100 + */ + dstNumbers: string | string[]; + /** + * The message text, up to 1600 characters. We split long messages greater than 160 GSM-7 characters or 70 UTF-16 characters into multiple segments. Each segment is charged as one message + */ + text: string; + /** + * Whether to store outgoing message texts. Default value is false + */ + storeBody?: boolean; + } + interface A2PSendSmsResponse { + result: SmsTransaction[]; + failed: FailedSms[]; + /** + * The number of fragments the message is divided into + */ + fragmentsCount: number; + error?: APIError; + } + interface ControlSmsRequest { + /** + * The phone number + */ + phoneNumber: string; + /** + * The SMS control command. The following values are possible: enable, disable + */ + command: string; + } + interface ControlSmsResponse { + result: number; + error?: APIError; + } + interface GetSmsHistoryRequest { + /** + * The source phone number + */ + sourceNumber?: string; + /** + * The destination phone number + */ + destinationNumber?: string; + /** + * Sent or received SMS. Possible values: 'IN', 'OUT', 'in, 'out'. Leave blank to get both incoming and outgoing messages + */ + direction?: string; + /** + * Maximum number of resulting rows fetched. Must be not bigger than 1000. If left blank, then the default value of 1000 is used + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + /** + * Date from which to perform search. Format is 'yyyy-MM-dd HH:mm:ss', time zone is UTC + */ + fromDate?: Date; + /** + * Date until which to perform search. Format is 'yyyy-MM-dd HH:mm:ss', time zone is UTC + */ + toDate?: Date; + /** + * The output format. The following values available: json, csv + */ + output?: string; + } + interface GetSmsHistoryResponse { + result: SmsHistory[]; + /** + * Total number of messages matching the query parameters + */ + totalCount: number; + error?: APIError; + } + interface A2PGetSmsHistoryRequest { + /** + * The source phone number + */ + sourceNumber?: string; + /** + * The destination phone number + */ + destinationNumber?: string; + /** + * Maximum number of resulting rows fetched. Must be not bigger than 1000. If left blank, then the default value of 1000 is used + */ + count?: number; + /** + * The first N records are skipped in the output + */ + offset?: number; + /** + * Date from which the search is to start. Format is 'yyyy-MM-dd HH:mm:ss', time zone is UTC + */ + fromDate?: Date; + /** + * Date from which the search is to end. Format is 'yyyy-MM-dd HH:mm:ss', time zone is UTC + */ + toDate?: Date; + /** + * The output format. The possible values are json, csv + */ + output?: string; + /** + * The delivery status ID: QUEUED - 1, DISPATCHED - 2, ABORTED - 3, REJECTED - 4, DELIVERED - 5, FAILED - 6, EXPIRED - 7, UNKNOWN - 8 + */ + deliveryStatus?: number; + } + interface A2PGetSmsHistoryResponse { + result: A2PSmsHistory[]; + /** + * Total number of messages matching the query parameters + */ + totalCount: number; + error?: APIError; + } + interface SMSInterface { + /** + * Sends an SMS message between two phone numbers. The source phone number should be purchased from Voximplant and support SMS (which is indicated by the is_sms_supported property in the objects returned by the [GetPhoneNumbers] Management API) and SMS should be enabled for it via the [ControlSms] Management API. SMS messages can be received via HTTP callbacks, see this article for details. + */ + sendSmsMessage: (request: SendSmsMessageRequest) => Promise; + /** + * Sends an SMS message from the application to customers. The source phone number should be purchased from Voximplant and support SMS (which is indicated by the is_sms_supported property in the objects returned by the /GetPhoneNumbers Management API) and SMS should be enabled for it via the /ControlSms Management API. + */ + a2PSendSms: (request: A2PSendSmsRequest) => Promise; + /** + * Enables or disables sending and receiving SMS for the phone number. Can be used only for phone numbers with SMS support, which is indicated by the is_sms_supported property in the objects returned by the [GetPhoneNumbers] Management API. Each incoming SMS message is charged according to the pricing. If enabled, SMS can be sent from this phone number via the [SendSmsMessage] Management API and received via the [InboundSmsCallback] property of the HTTP callback. See this article for HTTP callback details. + */ + controlSms: (request: ControlSmsRequest) => Promise; + /** + * Gets the history of sent and/or received SMS. + */ + getSmsHistory: (request: GetSmsHistoryRequest) => Promise; + /** + * Gets the history of sent/or received A2P SMS. + */ + a2PGetSmsHistory: (request: A2PGetSmsHistoryRequest) => Promise; + } + interface GetRecordStoragesRequest { + /** + * The record storage ID list separated by semicolons (;) + */ + recordStorageId?: 'any' | number | number[]; + /** + * The record storage name list separated by semicolons (;) + */ + recordStorageName?: string | string[]; + withPrivate?: boolean; + } + interface GetRecordStoragesResponse { + result: RecordStorageInfo; + error?: APIError; + } + interface RecordStoragesInterface { + /** + * Gets the record storages. + */ + getRecordStorages: (request: GetRecordStoragesRequest) => Promise; + } + interface GetRoleGroupsRequest {} + interface GetRoleGroupsResponse { + result: RoleGroupView[]; + error?: APIError; + } + interface RoleSystemInterface { + /** + * Gets role groups. + */ + getRoleGroups: (request: GetRoleGroupsRequest) => Promise; + } + interface SetKeyValueItemRequest { + /** + * Key, up to 200 characters. A key can contain a namespace that is written before the ':' symbol, for example, test:1234. Thus, namespace 'test' can be used as a pattern in the [GetKeyValueItems](/docs/references/httpapi/keyvaluestorage#getkeyvalueitems) and [GetKeyValueKeys](/docs/references/httpapi/keyvaluestorage#getkeyvaluekeys) methods to find the keys with the same namespace + */ + key: string; + /** + * Value for the specified key, up to 2000 characters + */ + value: string; + /** + * The application ID + */ + applicationId: number; + /** + * The application name + */ + applicationName?: string; + /** + * Key expiry time in seconds. The value is in range of 0..7,776,000 (90 days), the default value is 30 days (2,592,000 seconds). The TTL is converted to an **expires_at** Unix timestamp field as part of the storage object. Note that one of the two parameters (ttl or expires_at) must be set + */ + ttl?: number; + /** + * Expiration date based on **ttl** (timestamp without milliseconds). Note that one of the two parameters (ttl or expires_at) must be set + */ + expiresAt?: number; + } + interface SetKeyValueItemResponse { + /** + * The key-value item + */ + result: KeyValueItems; + error?: APIError; + } + interface DelKeyValueItemRequest { + /** + * Key, up to 200 characters + */ + key: string; + /** + * The application ID + */ + applicationId: number; + /** + * The application name + */ + applicationName?: string; + } + interface DelKeyValueItemResponse { + result: number; + error?: APIError; + } + interface GetKeyValueItemRequest { + /** + * Key, up to 200 characters + */ + key: string; + /** + * The application ID + */ + applicationId: number; + /** + * The application name + */ + applicationName?: string; + } + interface GetKeyValueItemResponse { + /** + * The key-value item + */ + result: KeyValueItems; + error?: APIError; + } + interface GetKeyValueItemsRequest { + /** + * Namespace that keys should contain, up to 200 characters + */ + key: string; + /** + * The application ID + */ + applicationId: number; + /** + * Number of items to show per page with a maximum value of 50. Default value is 10 + */ + count?: number; + /** + * Number of items to skip (e.g. if you set count = 20 and offset = 0 the first time, the next time, offset has to be equal to 20 to skip the items shown earlier). Default value is 0 + */ + offset?: number; + /** + * The application name + */ + applicationName?: string; + } + interface GetKeyValueItemsResponse { + /** + * The key-value pairs + */ + result: KeyValueItems; + error?: APIError; + } + interface GetKeyValueKeysRequest { + /** + * The application ID + */ + applicationId: number; + /** + * Namespace that keys should contain, up to 200 characters + */ + key?: string; + /** + * Number of items to show per page with a maximum value of 50. Default value is 10 + */ + count?: number; + /** + * Number of items to skip (e.g. if you set count = 20 and offset = 0 the first time, the next time, offset has to be equal to 20 to skip the items shown earlier). Default value is 0 + */ + offset?: number; + /** + * The application name + */ + applicationName?: string; + } + interface GetKeyValueKeysResponse { + /** + * The key-value keys + */ + result: KeyValueKeys; + error?: APIError; + } + interface KeyValueStorageInterface { + /** + * Creates or updates a key-value pair. If an existing key is passed, the method returns the existing item and changes the value if needed. The keys should be unique within a Voximplant application. + */ + setKeyValueItem: (request: SetKeyValueItemRequest) => Promise; + /** + * Deletes the specified key-value pair from the storage. + */ + delKeyValueItem: (request: DelKeyValueItemRequest) => Promise; + /** + * Gets the specified key-value pair from the storage. + */ + getKeyValueItem: (request: GetKeyValueItemRequest) => Promise; + /** + * Gets all the key-value pairs in which the keys begin with a pattern. + */ + getKeyValueItems: (request: GetKeyValueItemsRequest) => Promise; + /** + * Gets all the keys of key-value pairs. + */ + getKeyValueKeys: (request: GetKeyValueKeysRequest) => Promise; + } + interface GetAccountInvoicesRequest { + status?: string; + /** + * Number of invoices to show per page. Default value is 20 + */ + count?: number; + /** + * Number of invoices to skip (e.g. if you set count = 20 and offset = 0 the first time, the next time, offset has to be equal to 20 to skip the items shown earlier). Default value is 0 + */ + offset?: number; + } + interface GetAccountInvoicesResponse { + /** + * Array of the account invoices + */ + result: AccountInvoice; + /** + * Total number of invoices matching the query parameters + */ + totalCount: number; + /** + * Number of returned invoices matching the query parameters + */ + count: number; + error?: APIError; + } + interface InvoicesInterface { + /** + * Gets all invoices of the specified USD or EUR account. + */ + getAccountInvoices: (request: GetAccountInvoicesRequest) => Promise; + } + class Client { + Accounts: AccountsInterface; + Applications: ApplicationsInterface; + Users: UsersInterface; + CallLists: CallListsInterface; + Scenarios: ScenariosInterface; + History: HistoryInterface; + PSTNBlacklist: PSTNBlacklistInterface; + SIPWhiteList: SIPWhiteListInterface; + SIPRegistration: SIPRegistrationInterface; + PhoneNumbers: PhoneNumbersInterface; + CallerIDs: CallerIDsInterface; + OutboundTestNumbers: OutboundTestNumbersInterface; + Queues: QueuesInterface; + SmartQueue: SmartQueueInterface; + Skills: SkillsInterface; + AdminUsers: AdminUsersInterface; + AdminRoles: AdminRolesInterface; + AuthorizedIPs: AuthorizedIPsInterface; + DialogflowCredentials: DialogflowCredentialsInterface; + SMS: SMSInterface; + RecordStorages: RecordStoragesInterface; + RoleSystem: RoleSystemInterface; + KeyValueStorage: KeyValueStorageInterface; + Invoices: InvoicesInterface; + } +} + +declare namespace VoximplantAvatar { + /** + * [Avatar](/docs/references/voxengine/voximplantavatar/avatar) configuration object. Can be passed as arguments to the [VoximplantAvatar.createAvatar] method. + */ + interface AvatarConfig { + /** + * Unique avatar id. + */ + avatarId: string; + /** + * Optional. Set of key-value pairs to be passed to an avatar for personalization (e.g., a customer's name). Can be obtained in the avatar script via [getCustomData](/docs/references/avatarengine/getcustomdata#getcustomdata) function. + */ + customData?: Object; + /** + * Optional. Whether an avatar should return detailed information on recognizing the user input (i.e. whether the **intents** are passed to [VoximplantAvatar.Events.UtteranceParsed](/docs/references/voxengine/voximplantavatar/events#utteranceparsed) in the avatar script). NOTE: starting from the text implementation the avatar always returns detailed information. + */ + extended?: boolean; + } +} + +declare namespace VoximplantAvatar { + /** + * Voximplant Avatar class (machine learning powered bot engine which allows your system to handle natural conversations with customers). + */ + class Avatar { + constructor(config: VoximplantAvatar.AvatarConfig); + + /** + * Adds a handler for the specified [VoximplantAvatar.Events] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [VoximplantAvatar.Events.Loaded](/docs/references/voxengine/voximplantavatar/events#loaded)) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: VoximplantAvatar.Events | T, + callback: (event: VoximplantAvatar._Events[T]) => any + ): void; + + /** + * Removes a handler for the specified [VoximplantAvatar.Events] event. + * @param event Event class (i.e., [VoximplantAvatar.Events.Loaded](/docs/references/voxengine/voximplantavatar/events#loaded)) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: VoximplantAvatar.Events | T, + callback?: (event: VoximplantAvatar._Events[T]) => any + ): void; + + /** + * Changes the avatar state to the specified one. This method is designed to react to the [VoximplantAvatar.Events.Reply](/docs/references/voxengine/voximplantavatar/events#reply) event: often, after the avatar replies to a user, the dialogue is moved to the next state. + * @param stateName Name of the next state + */ + goToState(stateName: string): Promise; + + /** + * Sends a user phase to the avatar. In response, the avatar triggers the [VoximplantAvatar.Events.Reply](/docs/references/voxengine/voximplantavatar/events#reply) event. + * @param text Utterance text + */ + handleUtterance(text: string): Promise; + + /** + * Triggers the [AvatarState.onTimeout](/docs/references/avatarengine/avatarstate#ontimeout) callback function in the avatar scenario. + */ + handleTimeout(): Promise; + + /** + * Transfers control to the avatar, so it starts a conversation. Should be called only after the [VoximplantAvatar.Events.Loaded](/docs/references/voxengine/voximplantavatar/events#loaded) event is triggered. + */ + start(): void; + } +} + +declare namespace VoximplantAvatar { + /** + * Creates a new [Avatar] instance to implement a custom bundle with ASR and TTS. + * @param config Avatar configuration + */ + function createAvatar(config: VoximplantAvatar.AvatarConfig): VoximplantAvatar.Avatar; +} + +declare namespace VoximplantAvatar { + /** + * Creates a new [VoiceAvatar] instance with a pre-added bundle of ASR and TTS to handle calls. + * @param config VoiceAvatar configuration + */ + function createVoiceAvatar( + config: VoximplantAvatar.VoiceAvatarConfig + ): VoximplantAvatar.VoiceAvatar; +} + +declare namespace VoximplantAvatar { + /** + * Avatar events. + *
+ * Add the following line to your scenario code to use the events: + * ``` + * require(Modules.Avatar); + * ``` + * @event + */ + enum Events { + /** + * Triggered when an avatar script is loaded and ready to use. + * @typedef _AvatarLoadedEvent + */ + Loaded = 'AvatarEvents.Loaded', + /** + * Triggered when an avatar ends a conversation with a customer. + * @typedef _AvatarFinishEvent + */ + Finish = 'AvatarEvents.Finish', + /** + * Triggered when an avatar parses a customer's phrase. The recognized phrase can be used for debugging and logging recognition results if needed. + * @typedef _AvatarUtteranceParsedEvent + */ + UtteranceParsed = 'AvatarEvents.UtteranceParsed', + /** + * Triggered when an avatar is ready to reply to a customer. + * @typedef _AvatarReplyEvent + */ + Reply = 'AvatarEvents.Reply', + /** + * Triggered when an error occurs. + * @typedef _AvatarErrorEvent + */ + Error = 'AvatarEvents.Error', + } + + /** + * @private + */ + interface _Events { + [Events.Loaded]: _AvatarLoadedEvent; + [Events.Finish]: _AvatarFinishEvent; + [Events.UtteranceParsed]: _AvatarUtteranceParsedEvent; + [Events.Reply]: _AvatarReplyEvent; + [Events.Error]: _AvatarErrorEvent; + } + + /** + * @private + */ + interface _AvatarEvent { + avatar: Avatar; + } + + /** + * @private + */ + interface _AvatarLoadedEvent extends _AvatarEvent {} + + /** + * @private + */ + interface _AvatarFinishEvent extends _AvatarEvent { + /** + * Optional. Utterance to reply to the customer with. + */ + utterance?: string; + /** + * Optional. Additional data returned from the avatar. Can be passed via the [AvatarResponseParameters.customData](/docs/references/avatarengine/avatarresponseparameters#customdata) parameter. + */ + customData?: Object; + /** + * Current avatar state. + */ + currentState: string; + /** + * Optional. Avatar text and voice [ChannelParameters](/docs/references/avatarengine/channelparameters). + */ + channelParameters?: ChannelParameters; + } + + /** + * @private + */ + interface _AvatarUtteranceParsedEvent extends _AvatarEvent { + /** + * Recognized phrase text. + */ + text: string; + /** + * Most suitable intent recognized for the phrase (or 'unknown' if unclear). + */ + intent: string; + /** + * Optional. Recognized phrase confidence. + */ + confidence?: number; + /** + * Current avatar state. + */ + currentState: string; + /** + * Number of the state visits. + */ + readonly visitsCounter: number; + /** + * Number of user phrases processed in this state. + */ + readonly utteranceCounter: number; + /** + * Default response to the intent from the UI. + */ + response: string; + /** + * Optional. Extended information of the intent recognition results [AvatarUtteranceIntent](/docs/references/avatarengine/avatarutteranceintent). + */ + intents?: Object[]; + /** + * Optional.Extracted entities (both system and custom) [AvatarEntities](/docs/references/avatarengine/avatarentities). + */ + entities?: Object; + } + + /** + * @private + */ + interface _AvatarReplyEvent extends _AvatarEvent { + /** + * Optional. Utterance to reply to the customer with. + */ + utterance?: string; + /** + * Optional. Next avatar state. + */ + nextState?: string; + /** + * Current avatar state. + */ + currentState: string; + /** + * Optional. Whether an avatar listens to the user after saying its utterance (or during it, if interruptions are enabled). + */ + listen?: true; + /** + * Optional. Additional data returned from an avatar. Can be passed via the [AvatarResponseParameters.customData](/docs/references/avatarengine/avatarresponseparameters#customdata) parameter. + */ + customData?: Object; + /** + * Optional. Time after which an avatar is ready to handle customer's interruptions (in case the avatar voices its response). + */ + interruptableAfter?: number; + /** + * Optional. Whether an avatar's reply is final. If true, all other parameters except **customData** are ignored and the avatar does not process any more inputs in the current conversation. + */ + isFinal?: boolean; + /** + * Optional. Number value that specifies how long an avatar listens to the user after saying its utterance (or during it, if interruptions are enabled). + */ + listenTimeout?: number; + /** + * Optional. Avatar text and voice [ChannelParameters](/docs/references/avatarengine/channelparameters). + */ + channelParameters?: ChannelParameters; + } + + /** + * @private + */ + interface _AvatarErrorEvent extends _AvatarEvent { + /** + * Error description. + */ + reason: string; + } +} + +declare namespace VoximplantAvatar { + /** + * [VoiceAvatar](/docs/references/voxengine/voximplantavatar/voiceavatar) configuration. Can be passed as arguments to the [VoximplantAvatar.createVoiceAvatar] method. + */ + interface VoiceAvatarConfig { + /** + * Current call object. + */ + call: Call; + /** + * Avatar configuration. + */ + avatarConfig: VoximplantAvatar.AvatarConfig; + /** + * ASR parameters. + */ + asrParameters: ASRParameters; + /** + * Optional. [Player](/docs/references/voxengine/player) parameters: language, progressivePlayback, volume, rate, etc. + */ + ttsPlayerParameters: TTSPlayerParameters; + /** + * Optional. End of phrase timeout in milliseconds. If the ASR is running in the interim mode, we may not wait for the final response from the ASR, but instead, take the last interim, after which there are no new ones during this timeout. It allows us to reduce the time of voice recognition. This parameter should be set individually for each ASR vendor. **1000ms** is a good default value not to interrupt the user aggressively. + */ + asrEndOfPhraseDetectorTimeout?: number; + /** + * Optional. ASR listen timeout in milliseconds. If there is no response from the customer, the [AvatarState.onTimeout](/docs/references/avatarengine/avatarstate#ontimeout) required callback function executes. You can override the global timeout via the [AvatarResponseParameters.listenTimeout](/docs/references/avatarengine/avatarresponseparameters#listentimeout) parameter for the current response. The default value is **10000** milliseconds (10 seconds). + */ + defaultListenTimeout?: number; + /** + * Optional. Triggered when the avatar finishes talking. Returns a dictionary with the data collected during the avatar working process. + */ + onFinishCallback?: ( + avatarFinishEvent: VoximplantAvatar._AvatarFinishEvent + ) => void | Promise; + /** + * Optional. Event handler that defines what happens to the call in case of internal errors of the avatar (for example, playing an error phrase or transferring the call to an agent). + *
+ * NOTE: the handler ends current javascript session using the [VoxEngine.terminate] method by default. + */ + onErrorCallback?: ( + avatarErrorEvent: VoximplantAvatar._AvatarErrorEvent + ) => void | Promise; + } +} + +declare namespace VoximplantAvatar { + /** + * @private + */ + interface _VoiceAvatarEvents extends VoximplantAvatar._Events, _ASREvents, _PlayerEvents {} +} + +declare namespace VoximplantAvatar { + /** + * Voximplant voice avatar class is a superstructure over avatar with a pre-added bundle of ASR and TTS to handle calls. + * As arguments, it accepts: a set of configuration parameters, callback functions and the [Call]. It independently implements automation for the interaction of [Avatar] and [Call] via the [TTS] and [ASR] modules (handles the events, causes business logic and execute the callback functions). + * For more details see the [VoximplantAvatar.VoiceAvatarConfig]. + */ + class VoiceAvatar { + constructor(config: VoximplantAvatar.VoiceAvatarConfig); + + /** + * Adds a handler for the specified [VoximplantAvatar.Events], [ASREvents] or [PlayerEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [VoximplantAvatar.Events.Loaded](/docs/references/voxengine/voximplantavatar/events#loaded), [ASREvents.Stopped], [PlayerEvents.PlaybackFinished]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: VoximplantAvatar.Events | ASREvents | PlayerEvents | SequencePlayerEvents | T, + callback: (event: VoximplantAvatar._VoiceAvatarEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [VoximplantAvatar.Events], [ASREvents] or [PlayerEvents] event. + * @param event Event class (i.e., [VoximplantAvatar.Events.Loaded](/docs/references/voxengine/voximplantavatar/events#loaded), [ASREvents.Stopped], [PlayerEvents.PlaybackFinished]) + * @param callback + */ + removeEventListener( + event: VoximplantAvatar.Events | ASREvents | PlayerEvents | SequencePlayerEvents | T, + callback: (event: VoximplantAvatar._VoiceAvatarEvents[T]) => any + ): void; + } +} + +/** + * Represents an ML-powered bot engine that allows your system to handle natural conversations with users. + *
+ * Add the following line to your scenario code to use the namespace: + * ``` + * require(Modules.Avatar); + * ``` + */ +declare namespace VoximplantAvatar {} + +declare type VoxMediaUnit = + | Call + | Player + | SequencePlayer + | ASR + | Conference + | Recorder + | WebSocket + | StreamingAgent + | OpenAI.Beta.RealtimeAPIClient; + +/** + * Available audio encoding formats. Can be passed via the [SendMediaParameters.encoding] parameter. The default value is **PCM8**. + */ +declare enum WebSocketAudioEncoding { + /** + * Pulse-code modulation, 8kHz. + */ + PCM8 = 'PCM8', + /** + * Pulse-code modulation, 8kHz. + */ + PCM16_8KHZ = 'PCM8', + /** + * Pulse-code modulation, 16kHz. + */ + PCM16 = 'PCM16', + /** + * Pulse-code modulation, 16kHz. + */ + PCM16_16KHZ = 'PCM16', + /** + * A-law algorithm, 8kHz. + */ + ALAW = 'ALAW', + /** + * μ-law algorithm, 8kHz. + */ + ULAW = 'ULAW', + /** + * Codec for **audio/ogg** and **audio/opus** MIME types, 48kHz. + */ + OPUS = 'OPUS', +} + +declare enum WebSocketCloseCode { + /** + * Normal connection closure. + */ + CLOSE_NORMAL = 1000, + /** + * Endpoint left (browser tab closing). + */ + CLOSE_GOING_AWAY = 1001, + /** + * Endpoint received a malformed frame. + */ + CLOSE_PROTOCOL_ERROR = 1002, + /** + * Endpoint received an unsupported frame. + */ + CLOSE_UNSUPPORTED = 1003, + /** + * Expected close status, received none. + */ + CLOSED_NO_STATUS = 1005, + /** + * No close code frame has been received. + */ + CLOSE_ABNORMAL = 1006, + /** + * Endpoint received inconsistent message. + */ + CLOSE_UNSUPPORTED_PAYLOAD = 1007, + /** + * Received message violates endpoint policy. + */ + CLOSE_POLICY_VIOLATION = 1008, + /** + * Too big frame. + */ + CLOSE_TOO_LARGE = 1009, + /** + * Client wanted an extension which server does not negotiate. + */ + CLOSE_MANDATORY_EXTENSION = 1010, + /** + * Internal server error while operating. + */ + CLOSE_SERVER_ERROR = 1011, + /** + * Server/service is restarting. + */ + CLOSE_SERVICE_RESTART = 1012, + /** + * Temporary server condition forced blocking the client's request. + */ + CLOSE_TRY_AGAIN_LATER = 1013, + /** + * Server acting as gateway received an invalid response. + */ + CLOSE_BAD_GATEWAY = 1014, + /** + * Failed to perform a TLS handshake. + */ + CLOSE_TLS_FAIL = 1015, +} + +/** + * @event + */ +declare enum WebSocketEvents { + /** + * Triggered when the WebSocket connection is opened. [WebSocket.onopen] is called right before any other handlers. + * @typedef _WebSocketOpenEvent + */ + OPEN = 'WebSocket.Open', + /** + * Triggered when the WebSocket connection is closed. [WebSocket.onclose] is called right before any other handlers. + * @typedef _WebSocketCloseEvent + */ + CLOSE = 'WebSocket.Close', + /** + * Triggered when a message is received by a target object. [WebSocket.onmessage] is called right before any other handlers. + * @typedef _WebSocketMessageEvent + */ + MESSAGE = 'WebSocket.Message', + /** + * Triggers when an error occurs during the WebSocket connection. [WebSocket.onerror] is called right before any other handlers. + * @typedef _WebSocketErrorEvent + */ + ERROR = 'WebSocket.Error', + /** + * Triggered when the audio stream sent by a third party through a WebSocket is started playing. + * @typedef _WebSocketMediaStartedEvent + */ + MEDIA_STARTED = 'WebSocket.MediaEventStarted', + /** + * Triggers after the end of the audio stream sent by a third party through a WebSocket (**1 second of silence**). + * @typedef _WebSocketMediaEndedEvent + */ + MEDIA_ENDED = 'WebSocket.MediaEventEnded', +} + +/** + * @private + */ +declare interface _WebSocketEvents { + [WebSocketEvents.OPEN]: _WebSocketOpenEvent; + [WebSocketEvents.CLOSE]: _WebSocketCloseEvent; + [WebSocketEvents.MESSAGE]: _WebSocketMessageEvent; + [WebSocketEvents.ERROR]: _WebSocketErrorEvent; + [WebSocketEvents.MEDIA_STARTED]: _WebSocketMediaStartedEvent; + [WebSocketEvents.MEDIA_ENDED]: _WebSocketMediaEndedEvent; +} + +/** + * @private + */ +declare interface _WebSocketEvent { + /** + * WebSocket object that triggered the event. + */ + readonly websocket: WebSocket; +} + +/** + * @private + */ +declare interface _WebSocketOpenEvent extends _WebSocketEvent {} + +/** + * @private + */ +declare interface _WebSocketCloseEvent extends _WebSocketEvent { + /** + * WebSocket close code. + */ + readonly code: WebSocketCloseCode; + /** + * Reason why the connection is closed. + */ + readonly reason: string; + /** + * Whether the connection is cleanly closed. + */ + readonly wasClean: boolean; +} + +/** + * @private + */ +declare interface _WebSocketMessageEvent extends _WebSocketEvent { + /** + * The data sent by the message emitter. + */ + readonly text: string; +} + +/** + * @private + */ +declare interface _WebSocketErrorEvent extends _WebSocketEvent {} + +/** + * @private + */ +declare interface _WebSocketMediaEvent extends _WebSocketEvent { + /** + * Special tag to name audio streams sent over one WebSocket connection. With it, one can send 2 audios to 2 different media units at the same time. + */ + tag?: string; +} + +/** + * @private + */ +declare interface _WebSocketMediaStartedEvent extends _WebSocketMediaEvent { + /** + * Audio encoding formats. + */ + encoding?: string; + /** + * Custom parameters. + */ + customParameters?: { [key: string]: string }; +} + +/** + * @private + */ +declare interface _WebSocketMediaEndedEvent extends _WebSocketMediaEvent { + /** + * Information about the audio stream that can be obtained after the stream stops or pauses (**1 second of silence**). + */ + mediaInfo?: WebSocketMediaInfo; +} + +/** + * Information about the audio stream that can be obtained after the stream stops or pauses (1-sec silence). + */ +declare interface WebSocketMediaInfo { + /** + * Audio stream duration. + */ + duration: number; +} + +/** + * [WebSocket] parameters. Can be passed as arguments to the [VoxEngine.createWebSocket] method. + */ +declare interface WebSocketParameters { + /** + * Optional. Either a single protocol string or an array of protocol strings. The default value is **chat**. + */ + protocols?: string | string[]; + /** + * Optional. List of dictionaries with key and value fields representing headers. + */ + headers?: { name: string; value: string }[]; +} + +declare enum WebSocketReadyState { + /** + * Connection is closed or cannot be opened. + */ + CLOSED = 'closed', + /** + * Connection is closing. + */ + CLOSING = 'closing', + /** + * Connection is in the process. + */ + CONNECTING = 'connecting', + /** + * Connection is open and ready to communicate. + */ + OPEN = 'open', +} + +/** + * Represents a [WebSocket] object that provides the API for creating and managing an outgoing or incoming WebSocket connection, as well as for sending and receiving data to/from it. + * @param url URL to connect **(wss:// + domain + path)** + * @param parameters Optional. [WebSocket] parameters + */ +declare class WebSocket { + constructor(url: string, parameters?: WebSocketParameters); + + /** + * Event handler to call when the connection is closed. + */ + onclose: ((ev: _WebSocketCloseEvent) => any) | null; + + /** + * Event handler to call when an error occurs. + */ + onerror: ((ev: _WebSocketErrorEvent) => any) | null; + + /** + * Event handler to call when a message is received. + */ + onmessage: ((ev: _WebSocketMessageEvent) => any) | null; + + /** + * Event handler to call when the connection is open (ready to send and receive data). + */ + onopen: ((ev: _WebSocketOpenEvent) => any) | null; + + /** + * Event handler to call when the audio stream is started playing. + */ + onmediastarted: ((ev: _WebSocketMediaStartedEvent) => any) | null; + + /** + * Event handler to call after the end of the audio stream. + */ + onmediaended: ((ev: _WebSocketMediaEndedEvent) => any) | null; + + /** + * Returns the current state of the WebSocket connection. + */ + readonly readyState: WebSocketReadyState; + + /** + * Returns the absolute URL of the WebSocket. For outgoing connection, it is the URL to which to connect; for incoming, it is the WebSocket session URL. + */ + readonly url: string; + + /** + * Returns the WebSocket's id. + */ + id(): string; + + /** + * Closes the WebSocket connection or connection attempt. + */ + close(): void; + + /** + * Enqueues the specified data to be transmitted over the WebSocket connection. + * @param data Data to send through a [WebSocket] + */ + send(data: string): void; + + /** + * Adds a handler for the specified [WebSocketEvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [WebSocketEvents.OPEN]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: WebSocketEvents | T, + callback: (event: _WebSocketEvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [WebSocketEvents] event. + * @param event Event class (i.e., [WebSocketEvents.OPEN]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: WebSocketEvents | T, + callback?: (event: _WebSocketEvents[T]) => any + ): void; + + /** + * Starts sending media from the websocket to the media unit. WebSocket works in real time and the recommended duration of one audio chunk is **20** milliseconds. + * @param mediaUnit Media unit that receives media + * @param parameters Optional. WebSocket interaction only parameters + */ + sendMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media from the websocket to the media unit. + * @param mediaUnit Media unit that stops receiving media + * @param parameters Optional. WebSocket interaction only parameters + */ + stopMediaTo(mediaUnit: VoxMediaUnit, parameters?: SendMediaParameters): void; + + /** + * Stops sending media from the websocket to the media unit. + * @param parameters Optional. Media buffer clearing parameters + */ + clearMediaBuffer(parameters?: ClearMediaBufferParameters): void; +} + +declare module ASRModelList { + /** + * List of Amazon ASR models. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Amazon { + /** + * Best for audio that originated from a phone call (typically recorded at a 8khz sampling rate). + * @const + */ + default, + } +} + +/** + * List of available ASR models. + *
+ * Add the following line to your scenario code to use the namespace: + * ``` + * require(Modules.ASR); + * ``` + * @namespace + */ +declare module ASRModelList {} + +declare module ASRModelList { + /** + * List of Deepgram ASR models. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Deepgram { + /** + * The default **General** model. + * @const + */ + default, + /** + * Optimized for everyday audio processing. + * @const + */ + general, + /** + * Optimized for everyday audio processing. Applies the newest ASR module with higher accuracy. + */ + general_enhanced, + /** + * Optimized for conference room settings, which include multiple speakers with a single microphone. + * @const + */ + meeting, + /** + * Optimized for conference room settings, which include multiple speakers with a single microphone. Applies the newest ASR module with higher accuracy. + * @const + */ + meeting_enhanced, + /** + * Optimized for low-bandwidth audio phone calls. + * @const + */ + phonecall, + /** + * Optimized for low-bandwidth audio phone calls. Applies the newest ASR module with higher accuracy. + * @const + */ + phonecall_enhanced, + /** + * Optimized for low-bandwidth audio clips with a single speaker. Derived from the phonecall model. + * @const + */ + voicemail, + /** + * Optimized for multiple speakers with varying audio quality, such as might be found on a typical earnings call. Vocabulary is heavily finance oriented. + * @const + */ + finance, + /** + * Optimized for multiple speakers with varying audio quality, such as might be found on a typical earnings call. Vocabulary is heavily finance oriented. Applies the newest ASR module with higher accuracy. + * @const + */ + finance_enhanced, + /** + * Optimized to allow artificial intelligence technologies, such as chatbots, to interact with people in a human-like way. + * @const + */ + conversational, + /** + * Optimized for audio sourced from videos. + * @const + */ + video, + } +} + +declare module ASRModelList { + /** + * List of Google ASR models. The **enhanced** models cost more than the standard rate. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Google { + /** + * Best for audio that is not one of the specific audio models. For example, long-form audio. Ideally the audio is high-fidelity, recorded at a 16khz or greater sampling rate. + * @const + */ + default, + + /** + * **Default** model with more accurate recognition. + * @const + */ + default_enhanced, + + /** + * Best for short queries such as voice commands or voice search. + * @const + */ + command_and_search, + + /** + * **Command_and_search** model with more accurate recognition. + * @const + */ + command_and_search_enhanced, + + /** + * Best for audio that originated from a phone call (typically recorded at a 8khz sampling rate). + * @const + */ + phone_call, + + /** + * **Phone_call** model with more accurate recognition. + * @const + */ + phone_call_enhanced, + + /** + * Best for audio that originated from video or includes multiple speakers. Ideally the audio is recorded at a 16khz or greater sampling rate. + * @const + */ + video, + + /** + * **Video** model with more accurate recognition. + * @const + */ + video_enhanced, + } +} + +declare module ASRModelList { + /** + * List of Microsoft ASR models. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Microsoft { + /** + * Best for generic, day-to-day language and if there is little or no background noise. + * @const + */ + default, + } +} + +declare module ASRModelList { + /** + * List of SaluteSpeech ASR models. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum SaluteSpeech { + /** + * The default **General** model. + * @const + */ + default, + /** + * Short arbitrary phrases, e.g., search queries. + * @const + */ + general, + /** + * The model for media usage. + * @const + */ + media, + /** + * The model to use in a call center. + * @const + */ + callcenter, + } +} + +declare module ASRModelList { + /** + * List of T-Bank ASR models. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum TBank { + /** + * Best for audio that originated from a phone call (typically recorded at a 8khz sampling rate). + * @const + */ + default, + } +} + +declare module ASRModelList { + /** + * List of Yandex ASR models. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Yandex { + /** + * The default **General** model. + * @const + */ + default, + /** + * Short arbitrary phrases, e.g., search queries. + * @const + */ + general, + /** + * Short arbitrary phrases, e.g., search queries. Release candidate version. + * @const + */ + generalrc, + /** + * Month names, cardinal and ordinal numbers. + *
+ * Supported by [ASRProfileList.Yandex.ru_RU] only. + * @const + * @deprecated + */ + dates, + /** + * People's first and last names, as well as requests to put someone on the phone. + *
+ * Supported by [ASRProfileList.Yandex.ru_RU] only. + * @const + * @deprecated + */ + names, + /** + * Addresses, organizations, and geographical features. + * @const + * @deprecated + */ + maps, + /** + * Cardinal numbers and delimiters (comma, period). + *
+ * Supported by [ASRProfileList.Yandex.ru_RU] only. + * @const + * @deprecated + */ + numbers, + } +} + +declare module ASRModelList { + /** + * List of YandexV3 ASR models. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum YandexV3 { + /** + * The default **General** model. + * @const + */ + default, + /** + * Short arbitrary phrases, e.g., search queries. + * @const + */ + general, + /** + * Short arbitrary phrases, e.g., search queries. Release candidate version. + * @const + */ + generalrc, + /** + * Month names, cardinal and ordinal numbers. + *
+ * Supported by [ASRProfileList.Yandex.ru_RU] only. + * @const + * @deprecated + */ + dates, + /** + * People's first and last names, as well as requests to put someone on the phone. + *
+ * Supported by [ASRProfileList.Yandex.ru_RU] only. + * @const + * @deprecated + */ + names, + /** + * Addresses, organizations, and geographical features. + * @const + * @deprecated + */ + maps, + /** + * Cardinal numbers and delimiters (comma, period). + *
+ * Supported by [ASRProfileList.Yandex.ru_RU] only. + * @const + * @deprecated + */ + numbers, + } +} + +/** + * [ASR] parameters. Can be passed as arguments to the [VoxEngine.createASR] method. + *
+ * Add the following line to your scenario code to use the interface: + * ``` + * require(Modules.ASR); + * ``` + */ +declare interface ASRParameters { + /** + * Profile that specifies an ASR provider and a language to use. + *
+ *
+ * *Available for providers: Amazon, Deepgram, Google, Microsoft, SaluteSpeech, T-Bank, Yandex, YandexV3.* + */ + profile: + | ASRProfileList.Amazon + | ASRProfileList.Deepgram + | ASRProfileList.Google + | ASRProfileList.Microsoft + | ASRProfileList.SaluteSpeech + | ASRProfileList.TBank + | ASRProfileList.Yandex + | ASRProfileList.YandexV3; + + /** + * Optional. Recognition model. Select the model best suited to your domain to get the best results. If it is not specified, the **default** model is used. + *
+ *
+ * *Available for providers: Amazon, Deepgram, Google, Microsoft, SaluteSpeech, T-Bank, Yandex, YandexV3.* + */ + model?: + | ASRModelList.Amazon + | ASRModelList.Deepgram + | ASRModelList.Google + | ASRModelList.Microsoft + | ASRModelList.SaluteSpeech + | ASRModelList.TBank + | ASRModelList.Yandex + | ASRModelList.YandexV3; + + /** + * Optional. Whether to enable interim ASR results. If set to **true**, the [ASREvents.InterimResult] triggers many times according to the speech. + *
+ *
+ * *Available for providers: Amazon, Deepgram, Google, SaluteSpeech, T-Bank, Yandex.* + */ + interimResults?: boolean; + + /** + * Optional. Whether to enable single utterance. The default value is **false**, so: + *
+ * 1) if the speech is shorter than 60 sec, [ASREvents.Result] is triggered in unpredictable time. You could mute the mic when the speech is over - this increases the probability of [ASREvents.Result] catching; + *
+ * 2) if the speech is longer than 60 sec, [ASREvents.Result] is triggered each 60 seconds. + *
+ * If it is **true**, the [ASREvents.Result] is triggered after every utterance. + *
+ *
+ * *Available for providers: Amazon, Google, Microsoft, SaluteSpeech, T-Bank, Yandex.* + *
+ * *Note: for the SaluteSpeech provider the default value is **true**.* + */ + singleUtterance?: boolean; + + /** + * Optional. Preferable words to recognize. Note that **phraseHints** do not limit the recognition to the specific list. Instead, words in the specified list has a higher chance to be selected. + *
+ *
+ * *Available for providers: Google.* + */ + phraseHints?: string[]; + + /** + * Optional. Whether to enable profanity filter. The default value is **false**. + *
+ * If set to **true**, the server attempts to filter out profanities, replacing all but the initial character in each filtered word with asterisks, e.g. "f***". If set to **false** or omitted, profanities are not filtered out. + *
+ *
+ * *Available for providers: Amazon, Deepgram, Google, Microsoft, SaluteSpeech, T-Bank, Yandex, YandexV3.* + */ + profanityFilter?: boolean; + + /** + * Optional. Request headers: {'x-data-logging-enabled': true}. + *
+ *
+ * *Available for providers: Amazon, Deepgram, Google, Microsoft, SaluteSpeech, T-Bank, Yandex, YandexV3.* + */ + headers?: { [key: string]: any }; + + /** + * Optional. Whether to use the Google [v1p1beta1 Speech API](https://cloud.google.com/speech-to-text/docs/reference/rest/v1p1beta1/speech), e.g., **enableSeparateRecognitionPerChannel**, **alternativeLanguageCodes**, **enableWordTimeOffsets**, etc. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + beta?: boolean; + + /** + * v1p1beta1 Speech API feature. + *
+ * Optional. The recognition result contains a [_ASRResultEvent.channelTag] field to state which channel that result belongs to. If set to **false** or omitted, only the first channel is recognized. + *
+ * Requires the **beta** parameter set to **true**. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + enableSeparateRecognitionPerChannel?: boolean; + + /** + * v1p1beta1 Speech API feature. + *
+ * Optional. A list of up to 3 additional BCP-47 language tags, listing possible alternative languages of the supplied audio. See [Language Support](https://cloud.google.com/speech-to-text/docs/languages) for a list of the currently supported language codes. + *
+ * Requires the **beta** parameter set to **true**. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + alternativeLanguageCodes?: string[]; + + /** + * v1p1beta1 Speech API feature. + *
+ * Optional. If set to **true**, the top result includes a list of words and the start and end time offsets (timestamps) for those words. If set to **false** or omitted, no word-level time offset information is returned. The default value is **false**. + *
+ * Requires the **beta** parameter set to **true**. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + enableWordTimeOffsets?: boolean; + + /** + * v1p1beta1 Speech API feature. + *
+ * Optional. If set to **true**, the top result includes a list of words and the confidence for those words. If set to **false** or omitted, no word-level confidence information is returned. The default value is **false**. + *
+ * Requires the **beta** parameter set to **true**. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + enableWordConfidence?: boolean; + + /** + * v1p1beta1 Speech API feature. + *
+ * Optional. If set to **true**, adds punctuation to recognition result hypotheses. This feature is only available in select languages. Setting this for requests in other languages has no effect at all. The **false** value does not add punctuation to result hypotheses. The default value is **false**. + * Requires the **beta** parameter set to **true**. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + enableAutomaticPunctuation?: boolean; + + /** + * v1p1beta1 Speech API feature. + *
+ * Optional. Config to enable speaker diarization and set additional parameters to make diarization better suited for your application. + *
+ * See the full list of available fields [here](https://cloud.google.com/speech-to-text/docs/reference/rest/v1p1beta1/RecognitionConfig#SpeakerDiarizationConfig). + *
+ * Requires the **beta** parameter set to **true**. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + diarizationConfig?: { + /** + * If set to **true**, enables speaker detection for each recognized word in the top alternative of the recognition result. + */ + enableSpeakerDiarization: boolean; + }; + + /** + * v1p1beta1 Speech API feature. + *
+ * Optional. Metadata regarding this request. + *
+ * See the full list of available fields [here](https://cloud.google.com/speech-to-text/docs/reference/rest/v1p1beta1/RecognitionConfig#RecognitionMetadata). + *
+ * Requires the **beta** parameter set to **true**. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + metadata?: { + /** + * The audio type that most closely describes the audio being recognized. Possible values are: **MICROPHONE_DISTANCE_UNSPECIFIED**, **NEARFIELD**, **MIDFIELD**, **FARFIELD**. + */ + microphoneDistance: string; + }; + + /** + * Optional. Increase the recognition model bias by assigning more weight to some phrases than others. **Phrases** is the word array, **boost** is the weight in the range of 1..20. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + speechContexts?: [ + { + phrases: string[]; + boost: number; + }, + ]; + + /** + * Optional. Maximum number of recognition hypotheses to be returned. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + maxAlternatives?: number; + + /** + * Optional. Speech adaptation configuration. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + adaptation?: Object; + + /** + * Optional. Whether to enable the spoken punctuation behavior for the call. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + enableSpokenPunctuation?: boolean; + + /** + * Optional. Whether to enable the spoken emoji behavior for the call. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + enableSpokenEmojis?: boolean; + + /** + * Optional. Whether to use the enhanced models for speech recognition. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + useEnhanced?: boolean; + + /** + * Optional. Transcription normalization configuration. Use transcription normalization to automatically replace parts of the transcript with phrases of your choosing. + *
+ *
+ * *Available for providers: Google.* + * @beta + */ + transcriptNormalization?: { + entries: [ + { + search: string; + replace: string; + caseSensitive: boolean; + }, + ]; + }; + + /** + * Optional. Provide the ASR parameters directly to the provider in this parameter. Find more information in the documentation. + *
+ *
+ * *Available for providers: Deepgram, Google, SaluteSpeech, T-Bank, Yandex, YandexV3.* + */ + request?: Object; +} + +declare module ASRProfileList { + /** + * List of Amazon ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Amazon { + /** + * English (United Kingdom) + * @const + */ + en_GB, + /** + * English (United States) + * @const + */ + en_US, + /** + * Spanish (United States) + * @const + */ + es_US, + /** + * French (Canada) + * @const + */ + fr_CA, + /** + * French (France) + * @const + */ + fr_FR, + } +} + +/** + * List of available ASR profiles. + *
+ * Add the following line to your scenario code to use the namespace: + * ``` + * require(Modules.ASR); + * ``` + * @namespace + */ +declare module ASRProfileList {} + +declare module ASRProfileList { + /** + * List of Deepgram ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Deepgram { + /** + * Chinese (China) + * @const + */ + zh, + /** + * Chinese (Simplified) + * @const + */ + zh_CN, + /** + * Chinese (Traditional) + * @const + */ + zh_TW, + /** + * Danish (Denmark) + * @const + */ + da, + /** + * Dutch (Netherlands) + * @const + */ + nl, + /** + * English (Common) + * @const + */ + en, + /** + * English (Australia) + * @const + */ + en_AU, + /** + * English (Great Britain) + * @const + */ + en_GB, + /** + * English (Indonesia) + * @const + */ + en_IN, + /** + * English (New Zealand) + * @const + */ + en_NZ, + /** + * English (United States) + * @const + */ + en_US, + /** + * French (France) + * @const + */ + fr, + /** + * French (Canada) + * @const + */ + fr_CA, + /** + * German (Germany) + * @const + */ + de, + /** + * Hindi (India) + * @const + */ + hi, + /** + * Hindi (Latin) + * @const + */ + hi_Latn, + /** + * Indonesian (Indonesia) + * @const + */ + id, + /** + * Italian (Italy) + * @const + */ + it, + /** + * Japanese (Japan) + * @const + */ + ja, + /** + * Korean (Korea) + * @const + */ + ko, + /** + * Norwegian (Norway) + * @const + */ + no, + /** + * Polish (Poland) + * @const + */ + pl, + /** + * Portuguese (Common) + * @const + */ + pt, + /** + * Portuguese (Brazil) + * @const + */ + pt_BR, + /** + * Portuguese (Portugal) + * @const + */ + pt_PT, + /** + * Russian (Russia) + * @const + */ + ru, + /** + * Spanish (Spain) + * @const + */ + es, + /** + * Spanish (Latin America) + * @const + */ + es_419, + /** + * Swedish (Sweden) + * @const + */ + sv, + /** + * Tamil (India) + * @const + */ + ta, + /** + * Turkish (Turkey) + * @const + */ + tr, + /** + * Ukrainian (Ukraine) + * @const + */ + uk, + } +} + +declare module ASRProfileList { + /** + * List of Google ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Google { + /** + * Afrikaans (South Africa) + * @const + */ + af_ZA, + + /** + * Albanian (Albania) + * @const + */ + sq_AL, + + /** + * Amharic (Ethiopia) + * @const + */ + am_ET, + + /** + * Arabic (Algeria) + * @const + */ + ar_DZ, + + /** + * Arabic (Bahrain) + * @const + */ + ar_BH, + + /** + * Arabic (Egypt) + * @const + */ + ar_EG, + + /** + * Arabic (Iraq) + * @const + */ + ar_IQ, + + /** + * Arabic (Israel) + * @const + */ + ar_IL, + + /** + * Arabic (Jordan) + * @const + */ + ar_JO, + + /** + * Arabic (Kuwait) + * @const + */ + ar_KW, + + /** + * Arabic (Lebanon) + * @const + */ + ar_LB, + + /** + * Arabic (Mauritania) + * @const + */ + ar_MR, + + /** + * Arabic (Morocco) + * @const + */ + ar_MA, + + /** + * Arabic (Oman) + * @const + */ + ar_OM, + + /** + * Arabic (Qatar) + * @const + */ + ar_QA, + + /** + * Arabic (Saudi Arabia) + * @const + */ + ar_SA, + + /** + * Arabic (State of Palestine) + * @const + */ + ar_PS, + + /** + * Arabic (Syria) + * @const + */ + ar_SY, + + /** + * Arabic (Tunisia) + * @const + */ + ar_TN, + + /** + * Arabic (United Arab Emirates) + * @const + */ + ar_AE, + + /** + * Arabic (Yemen) + * @const + */ + ar_YE, + + /** + * Armenian (Armenia) + * @const + */ + hy_AM, + + /** + * Azerbaijani (Azerbaijan) + * @const + */ + az_AZ, + + /** + * Basque (Spain) + * @const + */ + eu_ES, + + /** + * Bengali (Bangladesh) + * @const + */ + bn_BD, + + /** + * Bengali (India) + * @const + */ + bn_IN, + + /** + * Bosnian (Bosnia and Herzegovina) + * @const + */ + bs_BA, + + /** + * Bulgarian (Bulgaria) + * @const + */ + bg_BG, + + /** + * Burmese (Myanmar) + * @const + */ + my_MM, + + /** + * Catalan (Spain) + * @const + */ + ca_ES, + + /** + * Chinese (Simplified, China) + * @const + */ + cmn_Hans_CN, + + /** + * Chinese (Simplified, Hong Kong) + * @const + */ + cmn_Hans_HK, + + /** + * Chinese (Traditional, Taiwan) + * @const + */ + cmn_Hant_TW, + + /** + * Chinese, Cantonese (Traditional Hong Kong) + * @const + */ + yue_Hant_HK, + + /** + * Croatian (Croatia) + * @const + */ + hr_HR, + + /** + * Czech (Czech Republic) + * @const + */ + cs_CZ, + + /** + * Danish (Denmark) + * @const + */ + da_DK, + + /** + * Dutch (Belgium) + * @const + */ + nl_BE, + + /** + * Dutch (Netherlands) + * @const + */ + nl_NL, + + /** + * English (Australia) + * @const + */ + en_AU, + + /** + * English (Canada) + * @const + */ + en_CA, + + /** + * English (Ghana) + * @const + */ + en_GH, + + /** + * English (Hong Kong) + * @const + */ + en_HK, + + /** + * English (India) + * @const + */ + en_IN, + + /** + * English (Ireland) + * @const + */ + en_IE, + + /** + * English (Kenya) + * @const + */ + en_KE, + + /** + * English (New Zealand) + * @const + */ + en_NZ, + + /** + * English (Nigeria) + * @const + */ + en_NG, + + /** + * English (Pakistan) + * @const + */ + en_PK, + + /** + * English (Philippines) + * @const + */ + en_PH, + + /** + * English (Singapore) + * @const + */ + en_SG, + + /** + * English (South Africa) + * @const + */ + en_ZA, + + /** + * English (Tanzania) + * @const + */ + en_TZ, + + /** + * English (United Kingdom) + * @const + */ + en_GB, + + /** + * English (United States) + * @const + */ + en_US, + + /** + * Estonian (Estonia) + * @const + */ + et_EE, + + /** + * Filipino (Philippines) + * @const + */ + fil_PH, + + /** + * Finnish (Finland) + * @const + */ + fi_FI, + + /** + * French (Belgium) + * @const + */ + fr_BE, + + /** + * French (Canada) + * @const + */ + fr_CA, + + /** + * French (France) + * @const + */ + fr_FR, + + /** + * French (Switzerland) + * @const + */ + fr_CH, + + /** + * Galician (Spain) + * @const + */ + gl_ES, + + /** + * Georgian (Georgia) + * @const + */ + ka_GE, + + /** + * German (Austria) + * @const + */ + de_AT, + + /** + * German (Germany) + * @const + */ + de_DE, + + /** + * German (Switzerland) + * @const + */ + de_CH, + + /** + * Greek (Greece) + * @const + */ + el_GR, + + /** + * Gujarati (India) + * @const + */ + gu_IN, + + /** + * Hebrew (Israel) + * @const + */ + iw_IL, + + /** + * Hindi (India) + * @const + */ + hi_IN, + + /** + * Hungarian (Hungary) + * @const + */ + hu_HU, + + /** + * Icelandic (Iceland) + * @const + */ + is_IS, + + /** + * Indonesian (Indonesia) + * @const + */ + id_ID, + + /** + * Italian (Italy) + * @const + */ + it_IT, + + /** + * Italian (Switzerland) + * @const + */ + it_CH, + + /** + * Japanese (Japan) + * @const + */ + ja_JP, + + /** + * Javanese (Indonesia) + * @const + */ + jv_ID, + + /** + * Kannada (India) + * @const + */ + kn_IN, + + /** + * Kazakh (Kazakhstan) + * @const + */ + kk_KZ, + + /** + * Khmer (Cambodia) + * @const + */ + km_KH, + + /** + * Kinyarwanda (Rwanda) + * @const + */ + rw_RW, + + /** + * Korean (South Korea) + * @const + */ + ko_KR, + + /** + * Lao (Laos) + * @const + */ + lo_LA, + + /** + * Latvian (Latvia) + * @const + */ + lv_LV, + + /** + * Lithuanian (Lithuania) + * @const + */ + lt_LT, + + /** + * Macedonian (North Macedonia) + * @const + */ + mk_MK, + + /** + * Malay (Malaysia) + * @const + */ + ms_MY, + + /** + * Malayalam (India) + * @const + */ + ml_IN, + + /** + * Marathi (India) + * @const + */ + mr_IN, + + /** + * Mongolian (Mongolia) + * @const + */ + mn_MN, + + /** + * Nepali (Nepal) + * @const + */ + ne_NP, + + /** + * Norwegian Bokmål (Norway) + * @const + */ + no_NO, + + /** + * Persian (Iran) + * @const + */ + fa_IR, + + /** + * Polish (Poland) + * @const + */ + pl_PL, + + /** + * Portuguese (Brazil) + * @const + */ + pt_BR, + + /** + * Portuguese (Portugal) + * @const + */ + pt_PT, + + /** + * Punjabi (Gurmukhi India) + * @const + */ + pa_Guru_IN, + + /** + * Romanian (Romania) + * @const + */ + ro_RO, + + /** + * Russian (Russia) + * @const + */ + ru_RU, + + /** + * Serbian (Serbia) + * @const + */ + sr_RS, + + /** + * Sinhala (Sri Lanka) + * @const + */ + si_LK, + + /** + * Slovak (Slovakia) + * @const + */ + sk_SK, + + /** + * Slovenian (Slovenia) + * @const + */ + sl_SI, + + /** + * Southern Sotho (South Africa) + * @const + */ + st_ZA, + + /** + * Spanish (Argentina) + * @const + */ + es_AR, + + /** + * Spanish (Bolivia) + * @const + */ + es_BO, + + /** + * Spanish (Chile) + * @const + */ + es_CL, + + /** + * Spanish (Colombia) + * @const + */ + es_CO, + + /** + * Spanish (Costa Rica) + * @const + */ + es_CR, + + /** + * Spanish (Dominican Republic) + * @const + */ + es_DO, + + /** + * Spanish (Ecuador) + * @const + */ + es_EC, + + /** + * Spanish (El Salvador) + * @const + */ + es_SV, + + /** + * Spanish (Guatemala) + * @const + */ + es_GT, + + /** + * Spanish (Honduras) + * @const + */ + es_HN, + + /** + * Spanish (Mexico) + * @const + */ + es_MX, + + /** + * Spanish (Nicaragua) + * @const + */ + es_NI, + + /** + * Spanish (Panama) + * @const + */ + es_PA, + + /** + * Spanish (Paraguay) + * @const + */ + es_PY, + + /** + * Spanish (Peru) + * @const + */ + es_PE, + + /** + * Spanish (Puerto Rico) + * @const + */ + es_PR, + + /** + * Spanish (Spain) + * @const + */ + es_ES, + + /** + * Spanish (United States) + * @const + */ + es_US, + + /** + * Spanish (Uruguay) + * @const + */ + es_UY, + + /** + * Spanish (Venezuela) + * @const + */ + es_VE, + + /** + * Sundanese (Indonesia) + * @const + */ + su_ID, + + /** + * Swahili (Kenya) + * @const + */ + sw_KE, + + /** + * Swahili (Tanzania) + * @const + */ + sw_TZ, + + /** + * Swati (Latin, South Africa) + * @const + */ + ss_Latn_ZA, + + /** + * Swedish (Sweden) + * @const + */ + sv_SE, + + /** + * Tamil (India) + * @const + */ + ta_IN, + + /** + * Tamil (Malaysia) + * @const + */ + ta_MY, + + /** + * Tamil (Singapore) + * @const + */ + ta_SG, + + /** + * Tamil (Sri Lanka) + * @const + */ + ta_LK, + + /** + * Telugu (India) + * @const + */ + te_IN, + + /** + * Thai (Thailand) + * @const + */ + th_TH, + + /** + * Tsonga (South Africa) + * @const + */ + ts_ZA, + + /** + * Tswana (Latin, South Africa) + * @const + */ + tn_Latn_ZA, + + /** + * Turkish (Turkey) + * @const + */ + tr_TR, + + /** + * Ukrainian (Ukraine) + * @const + */ + uk_UA, + + /** + * Urdu (India) + * @const + */ + ur_IN, + + /** + * Urdu (Pakistan) + * @const + */ + ur_PK, + + /** + * Uzbek (Uzbekistan) + * @const + */ + uz_UZ, + + /** + * Venda (South Africa) + * @const + */ + ve_ZA, + + /** + * Vietnamese (Vietnam) + * @const + */ + vi_VN, + + /** + * Xhosa (South Africa) + * @const + */ + xh_ZA, + + /** + * Zulu (South Africa) + * @const + */ + zu_ZA, + } +} + +declare module ASRProfileList { + /** + * List of Microsoft ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Microsoft { + /** + * Afrikaans (South Africa) + * @const + */ + af_ZA, + + /** + * Amharic (Ethiopia) + * @const + */ + am_ET, + + /** + * Arabic (United Arab Emirates) + * @const + */ + ar_AE, + + /** + * Arabic (Bahrain) + * @const + */ + ar_BH, + + /** + * Arabic (Algeria) + * @const + */ + ar_DZ, + + /** + * Arabic (Egypt) + * @const + */ + ar_EG, + + /** + * Arabic (Israel) + * @const + */ + ar_IL, + + /** + * Arabic (Iraq) + * @const + */ + ar_IQ, + + /** + * Arabic (Jordan) + * @const + */ + ar_JO, + + /** + * Arabic (Kuwait) + * @const + */ + ar_KW, + + /** + * Arabic (Lebanon) + * @const + */ + ar_LB, + + /** + * Arabic (Libya) + * @const + */ + ar_LY, + + /** + * Arabic (Morocco) + * @const + */ + ar_MA, + + /** + * Arabic (Oman) + * @const + */ + ar_OM, + + /** + * Arabic (Palestinian Authority) + * @const + */ + ar_PS, + + /** + * Arabic (Qatar) + * @const + */ + ar_QA, + + /** + * Arabic (Saudi Arabia) + * @const + */ + ar_SA, + + /** + * Arabic (Syria) + * @const + */ + ar_SY, + + /** + * Arabic (Tunisia) + * @const + */ + ar_TN, + + /** + * Arabic (Yemen) + * @const + */ + ar_YE, + + /** + * Azerbaijani (Latin, Azerbaijan) + * @const + */ + az_AZ, + + /** + * Bulgarian (Bulgaria) + * @const + */ + bg_BG, + + /** + * Bengali (India) + * @const + */ + bn_IN, + + /** + * Bosnian (Bosnia and Herzegovina) + * @const + */ + bs_BA, + + /** + * Catalan + * @const + */ + ca_ES, + + /** + * Czech (Czechia) + * @const + */ + cs_CZ, + + /** + * Welsh (United Kingdom) + * @const + */ + cy_GB, + + /** + * Danish (Denmark) + * @const + */ + da_DK, + + /** + * German (Austria) + * @const + */ + de_AT, + + /** + * German (Switzerland) + * @const + */ + de_CH, + + /** + * German (Germany) + * @const + */ + de_DE, + + /** + * Greek (Greece) + * @const + */ + el_GR, + + /** + * English (Australia) + * @const + */ + en_AU, + + /** + * English (Canada) + * @const + */ + en_CA, + + /** + * English (United Kingdom) + * @const + */ + en_GB, + + /** + * English (Ghana) + * @const + */ + en_GH, + + /** + * English (Hong Kong SAR) + * @const + */ + en_HK, + + /** + * English (Ireland) + * @const + */ + en_IE, + + /** + * English (India) + * @const + */ + en_IN, + + /** + * English (Kenya) + * @const + */ + en_KE, + + /** + * English (Nigeria) + * @const + */ + en_NG, + + /** + * English (New Zealand) + * @const + */ + en_NZ, + + /** + * English (Philippines) + * @const + */ + en_PH, + + /** + * English (Singapore) + * @const + */ + en_SG, + + /** + * English (Tanzania) + * @const + */ + en_TZ, + + /** + * English (United States) + * @const + */ + en_US, + + /** + * English (South Africa) + * @const + */ + en_ZA, + + /** + * Spanish (Argentina) + * @const + */ + es_AR, + + /** + * Spanish (Bolivia) + * @const + */ + es_BO, + + /** + * Spanish (Chile) + * @const + */ + es_CL, + + /** + * Spanish (Colombia) + * @const + */ + es_CO, + + /** + * Spanish (Costa Rica) + * @const + */ + es_CR, + + /** + * Spanish (Cuba) + * @const + */ + es_CU, + + /** + * Spanish (Dominican Republic) + * @const + */ + es_DO, + + /** + * Spanish (Ecuador) + * @const + */ + es_EC, + + /** + * Spanish (Spain) + * @const + */ + es_ES, + + /** + * Spanish (Equatorial Guinea) + * @const + */ + es_GQ, + + /** + * Spanish (Guatemala) + * @const + */ + es_GT, + + /** + * Spanish (Honduras) + * @const + */ + es_HN, + + /** + * Spanish (Mexico) + * @const + */ + es_MX, + + /** + * Spanish (Nicaragua) + * @const + */ + es_NI, + + /** + * Spanish (Panama) + * @const + */ + es_PA, + + /** + * Spanish (Peru) + * @const + */ + es_PE, + + /** + * Spanish (Puerto Rico) + * @const + */ + es_PR, + + /** + * Spanish (Paraguay) + * @const + */ + es_PY, + + /** + * Spanish (El Salvador) + * @const + */ + es_SV, + + /** + * Spanish (United States) + * @const + */ + es_US, + + /** + * Spanish (Uruguay) + * @const + */ + es_UY, + + /** + * Spanish (Venezuela) + * @const + */ + es_VE, + + /** + * Estonian (Estonia) + * @const + */ + et_EE, + + /** + * Basque + * @const + */ + eu_ES, + + /** + * Persian (Iran) + * @const + */ + fa_IR, + + /** + * Finnish (Finland) + * @const + */ + fi_FI, + + /** + * Filipino (Philippines) + * @const + */ + fil_PH, + + /** + * French (Belgium) + * @const + */ + fr_BE, + + /** + * French (Canada) + * @const + */ + fr_CA, + + /** + * French (Switzerland) + * @const + */ + fr_CH, + + /** + * French (France) + * @const + */ + fr_FR, + + /** + * Irish (Ireland) + * @const + */ + ga_IE, + + /** + * Galician + * @const + */ + gl_ES, + + /** + * Gujarati (India) + * @const + */ + gu_IN, + + /** + * Hebrew (Israel) + * @const + */ + he_IL, + + /** + * Hindi (India) + * @const + */ + hi_IN, + + /** + * Croatian (Croatia) + * @const + */ + hr_HR, + + /** + * Hungarian (Hungary) + * @const + */ + hu_HU, + + /** + * Armenian (Armenia) + * @const + */ + hy_AM, + + /** + * Indonesian (Indonesia) + * @const + */ + id_ID, + + /** + * Icelandic (Iceland) + * @const + */ + is_IS, + + /** + * Italian (Switzerland) + * @const + */ + it_CH, + + /** + * Italian (Italy) + * @const + */ + it_IT, + + /** + * Japanese (Japan) + * @const + */ + ja_JP, + + /** + * Javanese (Latin, Indonesia) + * @const + */ + jv_ID, + + /** + * Georgian (Georgia) + * @const + */ + ka_GE, + + /** + * Kazakh (Kazakhstan) + * @const + */ + kk_KZ, + + /** + * Khmer (Cambodia) + * @const + */ + km_KH, + + /** + * Kannada (India) + * @const + */ + kn_IN, + + /** + * Korean (Korea) + * @const + */ + ko_KR, + + /** + * Lao (Laos) + * @const + */ + lo_LA, + + /** + * Lithuanian (Lithuania) + * @const + */ + lt_LT, + + /** + * Latvian (Latvia) + * @const + */ + lv_LV, + + /** + * Macedonian (North Macedonia) + * @const + */ + mk_MK, + + /** + * Malayalam (India) + * @const + */ + ml_IN, + + /** + * Mongolian (Mongolia) + * @const + */ + mn_MN, + + /** + * Marathi (India) + * @const + */ + mr_IN, + + /** + * Malay (Malaysia) + * @const + */ + ms_MY, + + /** + * Maltese (Malta) + * @const + */ + mt_MT, + + /** + * Burmese (Myanmar) + * @const + */ + my_MM, + + /** + * Norwegian Bokmål (Norway) + * @const + */ + nb_NO, + + /** + * Nepali (Nepal) + * @const + */ + ne_NP, + + /** + * Dutch (Belgium) + * @const + */ + nl_BE, + + /** + * Dutch (Netherlands) + * @const + */ + nl_NL, + + /** + * Punjabi (India) + * @const + */ + pa_IN, + + /** + * Polish (Poland) + * @const + */ + pl_PL, + + /** + * Pashto (Afghanistan) + * @const + */ + ps_AF, + + /** + * Portuguese (Brazil) + * @const + */ + pt_BR, + + /** + * Portuguese (Portugal) + * @const + */ + pt_PT, + + /** + * Romanian (Romania) + * @const + */ + ro_RO, + + /** + * Russian (Russia) + * @const + */ + ru_RU, + + /** + * Sinhala (Sri Lanka) + * @const + */ + si_LK, + + /** + * Slovak (Slovakia) + * @const + */ + sk_SK, + + /** + * Slovenian (Slovenia) + * @const + */ + sl_SI, + + /** + * Somali (Somalia) + * @const + */ + so_SO, + + /** + * Albanian (Albania) + * @const + */ + sq_AL, + + /** + * Serbian (Cyrillic, Serbia) + * @const + */ + sr_RS, + + /** + * Swedish (Sweden) + * @const + */ + sv_SE, + + /** + * Kiswahili (Kenya) + * @const + */ + sw_KE, + + /** + * Kiswahili (Tanzania) + * @const + */ + sw_TZ, + + /** + * Tamil (India) + * @const + */ + ta_IN, + + /** + * Telugu (India) + * @const + */ + te_IN, + + /** + * Thai (Thailand) + * @const + */ + th_TH, + + /** + * Turkish (Türkiye) + * @const + */ + tr_TR, + + /** + * Ukrainian (Ukraine) + * @const + */ + uk_UA, + + /** + * Urdu (India) + * @const + */ + ur_IN, + + /** + * Uzbek (Latin, Uzbekistan) + * @const + */ + uz_UZ, + + /** + * Vietnamese (Vietnam) + * @const + */ + vi_VN, + + /** + * Chinese (Wu, Simplified) + * @const + */ + wuu_CN, + + /** + * Chinese (Cantonese, Simplified) + * @const + */ + yue_CN, + + /** + * Chinese (Mandarin, Simplified) + * @const + */ + zh_CN, + + /** + * Chinese (Jilu Mandarin, Simplified) + * @const + */ + zh_CN_shandong, + + /** + * Chinese (Southwestern Mandarin, Simplified) + * @const + */ + zh_CN_sichuan, + + /** + * Chinese (Cantonese, Traditional) + * @const + */ + zh_HK, + + /** + * Chinese (Taiwanese Mandarin, Traditional) + * @const + */ + zh_TW, + + /** + * isiZulu (South Africa) + * @const + */ + zu_ZA, + } +} + +declare module ASRProfileList { + /** + * List of SaluteSpeech ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum SaluteSpeech { + /** + * Russian (Russia) + * @const + */ + ru_RU, + } +} + +declare module ASRProfileList { + /** + * List of T-Bank ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum TBank { + /** + * Russian (Russia) + * @const + */ + ru_RU, + } +} + +declare module ASRProfileList { + /** + * List of Yandex ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum Yandex { + /** + * Automatic language recognition + * @const + */ + auto, + /** + * German (Germany) + * @const + */ + de_DE, + /** + * English (United States) + * @const + */ + en_US, + /** + * Spanish (Spain) + * @const + */ + es_ES, + /** + * Finnish (Finland) + * @const + */ + fi_FI, + /** + * French (France) + * @const + */ + fr_FR, + /** + * Hebrew (Israel) + * @const + */ + he_HE, + /** + * Italian (Italy) + * @const + */ + it_IT, + /** + * Kazakh (Kazakhstan) + * @const + */ + kk_KK, + /** + * Dutch (Holland) + * @const + */ + nl_NL, + /** + * Polish (Poland) + * @const + */ + pl_PL, + /** + * Portuguese (Portugal) + * @const + */ + pt_PT, + /** + * Portuguese (Brazilian) + * @const + */ + pt_BR, + /** + * Russian (Russia) + * @const + */ + ru_RU, + /** + * Swedish (Sweden) + * @const + */ + sv_SE, + /** + * Turkish (Turkey) + * @const + */ + tr_TR, + /** + * Uzbek (Uzbekistan) + * @const + */ + uz_UZ, + } +} + +declare module ASRProfileList { + /** + * List of YandexV3 ASR profiles. + *
+ * Add the following line to your scenario code to use the enum: + * ``` + * require(Modules.ASR); + * ``` + */ + enum YandexV3 { + /** + * Automatic language recognition + * @const + */ + auto, + /** + * German (Germany) + * @const + */ + de_DE, + /** + * English (United States) + * @const + */ + en_US, + /** + * Spanish (Spain) + * @const + */ + es_ES, + /** + * Finnish (Finland) + * @const + */ + fi_FI, + /** + * French (France) + * @const + */ + fr_FR, + /** + * Hebrew (Israel) + * @const + */ + he_HE, + /** + * Italian (Italy) + * @const + */ + it_IT, + /** + * Kazakh (Kazakhstan) + * @const + */ + kk_KK, + /** + * Dutch (Holland) + * @const + */ + nl_NL, + /** + * Polish (Poland) + * @const + */ + pl_PL, + /** + * Portuguese (Portugal) + * @const + */ + pt_PT, + /** + * Portuguese (Brazilian) + * @const + */ + pt_BR, + /** + * Russian (Russia) + * @const + */ + ru_RU, + /** + * Swedish (Sweden) + * @const + */ + sv_SE, + /** + * Turkish (Turkey) + * @const + */ + tr_TR, + /** + * Uzbek (Uzbekistan) + * @const + */ + uz_UZ, + } +} + +/** + * Represents an ASR object provides speech recognition capabilities. Audio stream can be sent to an ASR instance from [Call], [Player] or [Conference] objects. Parameters **language** or **dictionary** should be passed to the [VoxEngine.createASR] function. + *
+ * Add the following line to your scenario code to use the class: + * ``` + * require(Modules.ASR); + * ``` + */ +declare class ASR { + /** + * @param id + * @param language + * @param dictionary + */ + constructor(id: string, language: string, dictionary: string); + + /** + * Returns the asr's id. + */ + id(): string; + + /** + * Returns the asr's language. + */ + language(): string; + + /** + * Returns the asr's dictionary. + */ + dictionary(): string[]; + + /** + * Adds a handler for the specified [ASREvents] event. Use only functions as handlers; anything except a function leads to the error and scenario termination when a handler is called. + * @param event Event class (i.e., [ASREvents.Stopped]) + * @param callback Handler function. A single parameter is passed - object with event information + */ + addEventListener( + event: ASREvents | T, + callback: (event: _ASREvents[T]) => any + ): void; + + /** + * Removes a handler for the specified [ASREvents] event. + * @param event Event class (i.e., [ASREvents.Stopped]) + * @param callback Optional. Handler function. If not specified, all handler functions are removed + */ + removeEventListener( + event: ASREvents | T, + callback?: (event: _ASREvents[T]) => any + ): void; + + /** + * Stops recognition. Triggers the [ASREvents.Stopped] event. Do not call any other ASR functions/handlers after the **ASR.stop** call. + */ + stop(): void; +} + +/** + * Decodes the data in the Base64 encoding + * @param data Data to decode + */ +declare function base64_decode(data: string): number[]; + +/** + * Encodes a string or array of integers from 0 to 255 to the Base64 encoding + * @param data String or array of integers from 0 to 255 to encode + */ +declare function base64_encode(data: string | number[]): string; + +/** + * Creates a hex string from given bytes array. + * @param data Array of numbers to convert into a string + * @param toUpperCase Whether the resulting string has uppercase 'A-F' chars. Default is **false** + */ +declare function bytes2hex(data: number[], toUpperCase: boolean): string; + +/** + * Creates a string from an array of numbers with specified encoding + * @param data Array of integers from 0 to 255 to create a string from + * @param encoding Encoding to use for string creation, the default value is **utf-8** + */ +declare function bytes2str(data: number[], encoding: string): string; + +/** + * Avatar text and voice channel parameters. Can be passed via the [AvatarResponseParameters.channelParameters](/docs/references/avatarengine/avatarresponseparameters#channelparameters) parameter. + */ +declare interface ChannelParameters { + /** + * Optional. Avatar voice channel parameters + */ + voice?: VoiceChannelParameters; + /** + * Optional. Avatar text channel parameters + */ + text?: TextChannelParameters; +} + +/** + * Cancels a timed, repeating action which is previously established by a call to setInterval(). + * @param intervalID The identifier of the repeated action you want to cancel. This ID is returned by the corresponding call + */ +declare function clearInterval(intervalID: number): void; + +/** + * Cancels a timeout previously established by calling setTimeout(). + * @param timeoutID The identifier of the timeout you want to cancel. This ID is returned by the corresponding call + */ +declare function clearTimeout(timeoutID: number): void; + +/** + * Converts the date to the specified local timezone. Note that `new Date()` always returns time in the UTC+0 timezone. + * @param timezone Local timezone in the AREA/LOCATION format of the [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) + * @param date Instance of the `Date` object + */ +declare function getLocalTime(timezone: string, date: Date): Date; + +/** + * Creates an array of numbers from parsing a hex string + * @param data Hex string like "cafec0de" + */ +declare function hex2bytes(data: string): number[]; + +/** + * Count the number of deletions, insertions, or substitutions required to transform str1 into str2. The number shows a measure of the similarity between two strings. It is also known as edit distance. + * @param str1 First string + * @param str2 Second string + */ +declare function levenshtein_distance(str1: string, str2: string): number; + +declare namespace Logger {} + +declare namespace Logger { + /** + * Writes a specified message to the session logger. Logs are stored in the [call history](https://manage.voximplant.com/calls). + * @param message Message to write. Maximum length is 15000 characters + */ + function write(message: string): void; +} + +declare module Net { + /** + * Advanced HTTP request options. + */ + interface HttpRequestOptions { + /** + * HTTP request type as a string: **GET**, **POST** etc. The default value is **GET**. + */ + method?: string; + /** + * Optional. Raw UTF-8 encoded data string or an array of bytes in any encoding generated by [str2bytes](/docs/references/voxengine/str2bytes) to send as the HTTP request body when 'method' is set to **POST**, **PUT**, or **PATCH**. + */ + postData?: string | number[]; + /** + * Optional. Request headers: {'Content-Type': 'text/html; charset=utf-8', 'User-Agent': 'YourCustomUserAgent/1.0'}. Note that the default value for the 'User-Agent' header is **VoxEngine/1.0**. + */ + headers?: { [key: string]: string }; + /** + * Optional. Request parameters. They can be specified in the URL itself as well. + */ + params?: { + [key: string]: string; + }; + /** + * Optional. Whether [HttpRequestResult.data](/docs/references/voxengine/net/httprequestresult#data) should contain a list of 1-byte numbers corresponding to the HTTP response data. If set to **false**, [HttpRequestResult.data](/docs/references/voxengine/net/httprequestresult#data) is undefined. + */ + rawOutput?: boolean; + /** + * Optional. Timeout for getting a response to the request in seconds. The default value is **90**. The value can be only decreased. + */ + timeout?: number; + /** + * Optional. Timeout for the TCP connection to the address in seconds. The default value is **6**. The value can be only decreased. + */ + connectionTimeout?: number; + /** + * Optional. Whether to enable logging the POST request body. The default value is **false**. + */ + enableSystemLog?: boolean; + } +} + +declare module Net { + /** + * HTTP response. + */ + interface HttpRequestResult { + /** + * Response code. HTTP code (2xx-5xx) or one of our internal status codes + *
0Voxengine limits are violated (e.g. HTTP request count exceeded)
-1Unknown error
-2Malformed URL
-3Host not found
-4Connection error
-5Too many redirects
-6Network error
-7Timeout
-8Internal error
-9Server response is larger than 2 MB
+ */ + code: number; + /** + * HTTP header string returned by the remote server, without processing + */ + raw_headers?: string; + /** + * List of dictionaries with key and value fields representing HTTP headers returned by the remote server + */ + headers?: { key: string; value: string }[]; + /** + * HTTP response body if Content-Type is not binary + */ + text?: string; + /** + * If [HttpRequestOptions.rawOutput](/docs/references/voxengine/net/httprequestoptions#rawoutput) is true, data contains a list of 1-byte numbers corresponding to HTTP response data. If [HttpRequestOptions.rawOutput](/docs/references/voxengine/net/httprequestoptions#rawoutput) is false, data is undefined. + */ + data?: number[]; + /** + * In case of an error contains the error description + */ + error?: string; + } +} + +declare module Net {} + +/** + * Avatar voice channel playback parameters. Can be passed via the [VoiceChannelParameters.playback] parameter. + */ +declare type PlaybackParameters = + | TTSPlaybackParameters + | URLPlaybackParameters + | SequencePlaybackParameters; + +/** + * Can be passed via the [RichContentButtonItem.action] parameter. + */ +declare interface RichContentButtonAction { + /** + * Rich content type. + */ + type: 'text' | 'location' | 'camera' | 'camera_roll' | 'phone_call' | 'contact' | 'uri'; + /** + * Optional. Used for the "uri" type only. + */ + uri?: string; +} + +/** + * Can be passed via the [RichContentButtons.items] parameter. + */ +declare interface RichContentButtonItem { + /** + * Rich content button text. + */ + text: string; + /** + * Rich content button action. + */ + action: RichContentButtonAction; + /** + * Message to the avatar when the button is clicked. + */ + payload?: any; +} + +/** + * Can be passed via the [RichContent.buttons] parameter. + */ +declare interface RichContentButtons { + /** + * Rich content button text. + */ + text: string; + /** + * Rich content button items. + */ + items: RichContentButtonItem[]; +} + +/* + * Can be passed via the [RichContent.contact] parameter. + */ +declare interface RichContentContact { + name: string; + number: string; + avatar?: string; +} + +/** + * Can be passed via the [RichContent.externalLink] parameter. + */ +declare interface RichContentExternalLink { + /** + * Link's text. + */ + caption: string; + /** + * Link's URL address. + */ + url: string; +} + +/** + * Can be passed via the [RichContent.image] or [RichContent.file] parameter. + */ +declare interface RichContentFile { + /** + * URL to the image/file location. + */ + url: string; + /** + * Caption for the image/file. + */ + caption: string; + /** + * File name. + */ + fileName: string; + /** + * File size. + */ + fileSize: number; + /** + * Content type. + */ + contentType: string; +} + +/** + * Can be passed via the [RichContent.location] parameter. + */ +declare interface RichContentLocation { + latitude: number; + longitude: number; + address?: string; +} + +/** + * Can be passed via the [RichContent.video] or [RichContent.audio] parameter. + */ +declare interface RichContentMedia extends RichContentFile { + duration: number; +} + +/** + * Can be passed via the [TextChannelParameters.richContent] parameter. + */ +declare interface RichContent { + /** + * Button for the rich content response. + */ + buttons?: RichContentButtons; + /** + * Location for the rich content response. + */ + location?: RichContentLocation; + /** + * Image for the rich content response. + */ + image?: RichContentFile; + /** + * File for the rich content response. + */ + file?: RichContentFile; + /** + * Video for the rich content response. + */ + video?: RichContentMedia; + /** + * Audio for the rich content response. + */ + audio?: RichContentMedia; + /** + * Link for the rich content response. + */ + externalLink?: RichContentExternalLink; + /** + * Contact data for the rich content response. + */ + contact?: RichContentContact; + /** + * Text string for the rich content response. + */ + text?: string; +} + +/** + * Avatar voice channel sequence playback parameters. Can be passed via the [VoiceChannelParameters.playback] parameter. + */ +declare interface SequencePlaybackParameters { + /** + * Array of the segments. + */ + segments: SequencePlaybackSegment[]; +} + +/** + * Sequence of the voice channel TTS and URL playback segments. Can be passed via the [SequencePlaybackParameters.segments] parameter. + */ +declare type SequencePlaybackSegment = TTSPlaybackParameters | URLPlaybackParameters; + +/** + * Repeatedly calls a function or executes a code snippet, with a fixed time delay between each call. + * @param callback A function to be executed every specified milliseconds. The function should not have any parameters, and no return value is expected + * @param timeout The time, in milliseconds (thousandths of a second), the timer specifies the delay between executions of the specified function or code. If this parameter is less than 100, a value of 100 is used. Note that the actual delay might be longer + */ +declare function setInterval(callback: () => any, timeout?: number): number; + +/** + * Sets a timer which executes a function or specified piece of code once after the timer expires. + * @param callback A function to be executed after the timer expires + * @param timeout The time, in milliseconds (thousandths of a second), the timer should wait before the specified function or code is executed. If this parameter is omitted, a value of 0 is used, meaning execute "immediately", or more accurately, as soon as possible. Note that in either case, the actual delay may be longer than intended + */ +declare function setTimeout(callback: () => any, timeout?: number): number; + +/** + * Creates an array of numbers from parsing string in specified codepage + * @param data String to parse + * @param encoding String encoding, the default value is **utf-8** + */ +declare function str2bytes(data: string, encoding: string): number[]; + +/** + * Avatar text channel parameters. Can be passed via the [ChannelParameters.text] parameter. + */ +declare interface TextChannelParameters { + /** + * Response of the rich content type. + */ + richContent: RichContent; +} + +/** + * List of available audio effect (profiles that are applied on post synthesized text to speech) for the [TTSOptions.effectsProfileId] parameter. + */ +declare enum TTSEffectsProfile { + /** + * Smartwatches and other wearables, like Apple Watch, Wear OS watch + */ + WearableClassDevice = 'wearable-class-device', + /** + * Smartphones, like Google Pixel, Samsung Galaxy, Apple iPhone + */ + HandsetClassDevice = 'handset-class-device', + /** + * Earbuds or headphones for audio playback, like Sennheiser headphones + */ + HeadphoneClassDevice = 'headphone-class-device', + /** + * Small home speakers, like Google Home Mini + */ + SmallBluetoothSpeakerClassDevice = 'small-bluetooth-speaker-class-device', + /** + * Smart home speakers, like Google Home + */ + MediumBluetoothSpeakerClassDevice = 'medium-bluetooth-speaker-class-device', + /** + * Home entertainment systems or smart TVs, like Google Home Max, LG TV + */ + LargeHomeEntertainmentClassDevice = 'large-home-entertainment-class-device', + /** + * Car speakers, home theaters + */ + LargeAutomotiveClassDevice = 'large-automotive-class-device', + /** + * Interactive Voice Response (IVR) systems + */ + TelephonyClassApplication = 'telephony-class-application', +} + +/** + * Text-to-speech options. Can be passed via the [CallSayParameters.ttsOptions] and [TTSPlayerParameters.ttsOptions] parameter. See the details in the official specs. + *
+ * Alternatively, you can pass the speech synthesis parameters to your TTS provider directly in the [request](https://voximplant.com/docs/references/voxengine/ttsplayerparameters#request) parameter in the JSON format. + * Read more about passing the parameters directly in the [Speech synthesis](https://voximplant.com/docs/guides/speech/tts#passing-parameters-directly-to-the-provider) guide. + */ +declare interface TTSOptions { + /** + * Optional. Voice sentiment. For Yandex voices, works only for ru_RU voices. + *
+ *
+ * *Available for providers: Yandex.* + */ + emotion?: string; + /** + * Optional. Voice pitch. Acceptable ranges: 1) the numbers followed by "Hz" from 0.5Hz to 2Hz 2) x-low, low, medium, high, x-high, default. + *
+ *
+ * *Available for providers: Google.* + */ + pitch?: string; + /** + * Optional. Speech speed. Possible values are x-slow, slow, medium, fast, x-fast, default. + *
+ *
+ * *Available for providers: Google, Yandex.* + */ + rate?: string; + /** + * Optional. Speech volume. Possible values are silent, x-soft, soft, medium, loud, x-loud, default. + *
+ *
+ * *Available for providers: Google.* + */ + volume?: string; + /** + * Optional. Speech speed. Possible values are from "0.1" to "3.0". + *
+ *
+ * *Available for providers: Yandex.* + */ + speed?: string; + /** + * Optional. An identifier which selects 'audio effects' profiles that are applied on (post synthesized) text to speech. Effects are applied additionally to each other in the order they are provided. + *
+ *
+ * *Available for providers: Google.* + */ + effectsProfileId?: TTSEffectsProfile[]; + /** + * Optional. If you have a custom Yandex engine voice, specify it in this field. Please contact support to activate this feature for your account. + *
+ *
+ * *Available for providers: Yandex.* + */ + yandexCustomModelName?: string; +} + +/** + * Avatar voice channel TTS playback parameters. Can be passed via the [VoiceChannelParameters.playback] parameter. + *
+ * Has a similar interface to [URLPlayerSegment]. + */ +declare interface TTSPlaybackParameters { + /** + * Text to synthesize. + *
+ * NOTE: this parameter is required for the [AvatarState] (not for the [AvatarFormState]), so if you want to use the value from the [VoximplantAvatar.Events.Reply](/docs/references/voxengine/voximplantavatar/events#reply) event's **utterance** parameter, specify it into the **text** parameter. + */ + text: string; + /** + * Whether to enable the playback interruption. + *
+ * NOTE: the segment with **allowPlaybackInterruption** parameter should be always followed by another segment eligible for playback interruption or should be the last segment. + */ + allowPlaybackInterruption: true; + /** + * Optional. TTS [Player](/docs/references/voxengine/player) parameters. + *
+ * NOTE: the default value is inherited from the [VoiceAvatarConfig.ttsPlayerOptions](/docs/references/voxengine/voximplantavatar/voiceavatarconfig) parameter. + */ + parameters?: TTSPlayerParameters; +} + +/** + * TTS [Player] parameters. Can be passed as arguments to the [VoxEngine.createTTSPlayer] method. + */ +declare interface TTSPlayerParameters { + /** + * Optional. Language and voice for TTS. List of all supported voices: [VoiceList]. The default value is **VoiceList.Amazon.en_US_Joanna**. + *
+ *
+ * *Available for providers: Amazon, Google, IBM, Microsoft, SaluteSpeech, T-Bank,Yandex.* + */ + language?: Voice; + /** + * Optional. Whether to use progressive playback. If **true**, the generated speech is delivered in chunks which reduces delay before a method call and playback. The default value is **false**. + *
+ *
+ * *Available for providers: Amazon, Google, IBM, Microsoft, SaluteSpeech, T-Bank,Yandex.* + */ + progressivePlayback?: boolean; + /** + * Optional. Parameters for TTS. Note that support of the [TTSOptions.pitch] parameter depends on the language and dictionary used. For unsupported combinations the [CallEvents.PlaybackFinished] event is triggered with error 400. + *
+ *
+ * *Available for providers: Amazon, Google, IBM, Microsoft, SaluteSpeech, T-Bank,Yandex.* + */ + ttsOptions?: TTSOptions; + /** + * Optional. Whether the player is on pause after creation. To continue the playback, use the [Player.resume] method. The default value is **false**. + *
+ *
+ * *Available for providers: Amazon, Google, IBM, Microsoft, SaluteSpeech, T-Bank,Yandex, YandexV3.* + */ + onPause?: boolean; + /** + * Optional. Provide the TTS parameters directly to the provider in this parameter. Find more information in the documentation. + *
+ *
+ * *Available for providers: Google, SaluteSpeech, T-Bank,YandexV3.* + */ + request?: Object; +} + +/** + * Avatar voice channel URL playback parameters. Can be passed via the [VoiceChannelParameters.playback] parameter. + *
+ * Has a similar interface to [URLPlayerSegment](/docs/references/voxengine/urlplayersegment). + */ +declare interface URLPlaybackParameters { + /** + * Url of an audio file. Supported formats are: **mp3**, **ogg**, **flac**, and **wav** (**mp3**, **speex**, **vorbis**, **flac**, and **wav** codecs respectively). Maximum file size is **10 Mb**. + */ + url: string; + /** + * Optional. Whether to enable the playback interruption. The default value is **false**. + *
+ * NOTE: the segment with 'allowPlaybackInterruption' parameter should be always followed by another segment eligible for playback interruption or should be the last segment. + */ + allowPlaybackInterruption: boolean; + /** + * Optional. URL [Player](/docs/references/voxengine/player) parameters. + *
+ * Same as [URLPlayerParameters](/docs/references/voxengine/urlplayerparameters). + */ + parameters?: URLPlayerParameters; +} + +/** + * URL [Player] parameters. Can be passed as arguments to the [VoxEngine.createURLPlayer] method. + */ +declare interface URLPlayerParameters { + /** + * Optional. Whether to loop playback. + */ + loop?: boolean; + /** + * Optional. Whether the player is on pause after creation. To continue the playback, use the [Player.resume] method. The default value is **false**. + */ + onPause?: boolean; + /** + * Optional. Whether to use progressive playback. If true, the file is delivered in chunks which reduces delay before a method call and playback. The default value is **false**. + */ + progressivePlayback?: boolean; + /** + * Optional. Whether to hide the HTTP request headers from the session logs. The default value is **false**. + */ + hideHeaders?: boolean; + /** + * Optional. Whether to hide the HTTP request body from the session logs. The default value is **false**. + */ + hideBody?: boolean; +} + +/** + * The [URLPlayerRequest] body. Should contain either ‘text’ or ‘binary’ keys. + */ +declare interface URLPlayerRequestBody { + /** + * Stringify object of the **'{"key":"value"}'** type. + */ + text?: string; + /** + * Base64 string. + */ + binary?: string; +} + +/** + * The [URLPlayerRequest] header. + */ +declare interface URLPlayerRequestHeader { + /** + * HTTP request header name. + */ + name: string; + /** + * HTTP request header value. + */ + value: string; +} + +/** + * The [URLPlayerRequest] method. + */ +declare enum URLPlayerRequestMethod { + /** + * The **GET** HTTP method. + */ + GET = 'GET', + /** + * The **POST** HTTP method. + */ + POST = 'POST', +} + +/** + * The URL [Player] request. + */ +declare interface URLPlayerRequest { + /** + * HTTP request url of an audio file. Supported formats are: **mp3**, **ogg**, **flac**, and **wav** (**mp3**, **speex**, **vorbis**, **flac**, and **wav** codecs respectively). Maximum file size is **10 Mb**. + */ + url: string; + /** + * Optional. HTTP request method. The default value is **GET**. + */ + method?: URLPlayerRequestMethod; + /** + * Optional. HTTP request headers. + */ + headers?: URLPlayerRequestHeader[]; + /** + * Optional. HTTP request body. + */ + body?: URLPlayerRequestBody; +} + +/** + * Generates unique identifier and returns it is string representation. + */ +declare function uuidgen(): string; + +/** + * Avatar voice channel parameters. Can be passed via the [ChannelParameters.voice](/docs/references/avatarengine/channelparameters#voice) parameter. + */ +declare interface VoiceChannelParameters { + /** + * Optional. [ASR](/docs/references/voxengine/asr) parameters. + *
+ * NOTE: the default value is inherited from the [VoiceAvatarConfig.asrParameters](/docs/references/voxengine/voximplantavatar/voiceavatarconfig#asrparameters) parameter. + */ + asr?: ASRParameters; + /** + * Avatar voice channel playback parameters. + *
+ * NOTE: the default value is inherited from the [VoiceAvatarConfig.ttsPlayerOptions](/docs/references/voxengine/voximplantavatar/voiceavatarconfig#ttsplayeroptions) parameter. + */ + playback?: PlaybackParameters; +} + +declare namespace VoiceList { + /** + * List of available Amazon TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace Amazon { + /** + * Amazon voice, Turkish Female, Filiz. + * @const + */ + const tr_TR_Filiz: Voice; + /** + * Amazon voice, Swedish Female, Astrid. + * @const + */ + const sv_SE_Astrid: Voice; + /** + * Amazon voice, Russian Female, Tatyana. + * @const + */ + const ru_RU_Tatyana: Voice; + /** + * Amazon voice, Russian Male, Maxim. + * @const + */ + const ru_RU_Maxim: Voice; + /** + * Amazon voice, Romanian Female, Carmen. + * @const + */ + const ro_RO_Carmen: Voice; + /** + * Amazon voice, Portuguese Female, Inês. + * @const + */ + const pt_PT_Ines: Voice; + /** + * Amazon voice, Portuguese Male, Cristiano. + * @const + */ + const pt_PT_Cristiano: Voice; + /** + * Amazon voice, Brazilian Portuguese Female, Vitória. + * @const + */ + const pt_BR_Vitoria: Voice; + /** + * Amazon voice, Brazilian Portuguese Male, Ricardo. + * @const + */ + const pt_BR_Ricardo: Voice; + /** + * Amazon voice, Brazilian Portuguese Female (second voice), Camila. + * @const + */ + const pt_BR_Camila: Voice; + /** + * Amazon voice, Polish Female, Maja. + * @const + */ + const pl_PL_Maja: Voice; + /** + * Amazon voice, Polish Male, Jan. + * @const + */ + const pl_PL_Jan: Voice; + /** + * Amazon voice, Polish Male (second voice), Jacek. + * @const + */ + const pl_PL_Jacek: Voice; + /** + * Amazon voice, Polish Female (second voice), Ewa. + * @const + */ + const pl_PL_Ewa: Voice; + /** + * Amazon voice, Dutch Male, Ruben. + * @const + */ + const nl_NL_Ruben: Voice; + /** + * Amazon voice, Dutch Female, Lotte. + * @const + */ + const nl_NL_Lotte: Voice; + /** + * Amazon voice, Norwegian Female, Liv. + * @const + */ + const nb_NO_Liv: Voice; + /** + * Amazon voice, Korean Female, Seoyeon. + * @const + */ + const ko_KR_Seoyeon: Voice; + /** + * Amazon voice, Japanese Male, Takumi. + * @const + */ + const ja_JP_Takumi: Voice; + /** + * Amazon voice, Japanese Female, Mizuki. + * @const + */ + const ja_JP_Mizuki: Voice; + /** + * Amazon voice, Italian Female, Bianca. + * @const + */ + const it_IT_Bianca: Voice; + /** + * Amazon voice, Italian Male, Giorgio. + * @const + */ + const it_IT_Giorgio: Voice; + /** + * Amazon voice, Italian Female (second voice), Carla. + * @const + */ + const it_IT_Carla: Voice; + /** + * Amazon voice, Icelandic Male, Karl. + * @const + */ + const is_IS_Karl: Voice; + /** + * Amazon voice, Icelandic Female, Dóra. + * @const + */ + const is_IS_Dora: Voice; + /** + * Amazon voice, French Male, Mathieu. + * @const + */ + const fr_FR_Mathieu: Voice; + /** + * Amazon voice, French Female, Léa. + * @const + */ + const fr_FR_Lea: Voice; + /** + * Amazon voice, French Female (second voice), Céline. + * @const + */ + const fr_FR_Celine: Voice; + /** + * Amazon voice, Canadian French Female, Chantal. + * @const + */ + const fr_CA_Chantal: Voice; + /** + * Amazon voice, US Spanish Female, Penélope. + * @const + */ + const es_US_Penelope: Voice; + /** + * Amazon voice, US Spanish Male, Miguel. + * @const + */ + const es_US_Miguel: Voice; + /** + * Amazon voice, US Spanish Female (second voice), Lupe. + * @const + */ + const es_US_Lupe: Voice; + /** + * Amazon voice, Mexican Spanish Female, Mia. + * @const + */ + const es_MX_Mia: Voice; + /** + * Amazon voice, Castilian Spanish Female, Lucia. + * @const + */ + const es_ES_Lucia: Voice; + /** + * Amazon voice, Castilian Spanish Male, Enrique. + * @const + */ + const es_ES_Enrique: Voice; + /** + * Amazon voice, Castilian Spanish Female (second voice), Conchita. + * @const + */ + const es_ES_Conchita: Voice; + /** + * Amazon voice, Welsh English Male, Geraint. + * @const + */ + const en_GB_WLS_Geraint: Voice; + /** + * Amazon voice, US English Female, Salli. + * @const + */ + const en_US_Salli: Voice; + /** + * Amazon voice, US English Male, Matthew. + * @const + */ + const en_US_Matthew: Voice; + /** + * Amazon voice, US English Female (second voice), Kimberly. + * @const + */ + const en_US_Kimberly: Voice; + /** + * Amazon voice, US English Female (third voice), Kendra. + * @const + */ + const en_US_Kendra: Voice; + /** + * Amazon voice, US English Male (second voice), Justin. + * @const + */ + const en_US_Justin: Voice; + /** + * Amazon voice, US English Male (third voice), Joey. + * @const + */ + const en_US_Joey: Voice; + /** + * Amazon voice, US English Female (fourth voice), Joanna. + * @const + */ + const en_US_Joanna: Voice; + /** + * Amazon voice, US English Female (fifth voice), Ivy. + * @const + */ + const en_US_Ivy: Voice; + /** + * Amazon voice, Indian English Female, Raveena. + * @const + */ + const en_IN_Raveena: Voice; + /** + * Amazon voice, Indian English Female (second voice), Aditi. + * @const + */ + const en_IN_Aditi: Voice; + /** + * Amazon voice, British English Female, Emma. + * @const + */ + const en_GB_Emma: Voice; + /** + * Amazon voice, British English Male, Brian. + * @const + */ + const en_GB_Brian: Voice; + /** + * Amazon voice, British English Female (second voice), Amy. + * @const + */ + const en_GB_Amy: Voice; + /** + * Amazon voice, Australian English Male, Russell. + * @const + */ + const en_AU_Russell: Voice; + /** + * Amazon voice, Australian English Female, Nicole. + * @const + */ + const en_AU_Nicole: Voice; + /** + * Amazon voice, German Female, Vicki. + * @const + */ + const de_DE_Vicki: Voice; + /** + * Amazon voice, German Female (second voice), Marlene. + * @const + */ + const de_DE_Marlene: Voice; + /** + * Amazon voice, German Male, Hans. + * @const + */ + const de_DE_Hans: Voice; + /** + * Amazon voice, Danish Female, Naja. + * @const + */ + const da_DK_Naja: Voice; + /** + * Amazon voice, Danish Male, Mads. + * @const + */ + const da_DK_Mads: Voice; + /** + * Amazon voice, Welsh Female, Gwyneth. + * @const + */ + const cy_GB_Gwyneth: Voice; + /** + * Amazon voice, Chinese Mandarin Female, Zhiyu. + * @const + */ + const cmn_CN_Zhiyu: Voice; + /** + * Amazon voice, Arabic Female, Zeina. + * @const + */ + const arb_Zeina: Voice; + } +} + +declare namespace VoiceList { + namespace Amazon { + /** + * List of available premium Amazon TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods that sound more natural due to advanced synthesis technology. + * @namespace + */ + namespace Neural { + /** + * Neural Amazon voice, Belgian French Female, Isabelle. + * @const + */ + const fr_BE_Isabelle: Voice; + /** + * Neural Amazon voice, US English Female, Danielle. + * @const + */ + const en_US_Danielle: Voice; + /** + * Neural Amazon voice, US English Male, Gregory. + * @const + */ + const en_US_Gregory: Voice; + /** + * Neural Amazon voice, Turkish Female, Burcu. + * @const + */ + const tr_TR_Burcu: Voice; + /** + * Neural Amazon voice, US English Male, Kevin. + * @const + */ + const en_US_Kevin: Voice; + /** + * Neural Amazon voice, Swedish Female, Elin. + * @const + */ + const sv_SE_Elin: Voice; + /** + * Neural Amazon voice, Portuguese Female, Inês. + * @const + */ + const pt_PT_Ines: Voice; + /** + * Neural Amazon voice, Brazilian Portuguese Female, Vitória. + * @const + */ + const pt_BR_Vitoria: Voice; + /** + * Neural Amazon voice, Brazilian Portuguese Female (second voice), Camila. + * @const + */ + const pt_BR_Camila: Voice; + /** + * Neural Amazon voice, Polish Female, Ola. + * @const + */ + const pl_PL_Ola: Voice; + /** + * Neural Amazon voice, Belgian Dutch Female, Lisa. + * @const + */ + const nl_BE_Lisa: Voice; + /** + * Neural Amazon voice, Dutch Female, Laura. + * @const + */ + const nl_NL_Laura: Voice; + /** + * Neural Amazon voice, Norwegian Female, Ida. + * @const + */ + const nb_NO_Ida: Voice; + /** + * Neural Amazon voice, Korean Female, Seoyeon. + * @const + */ + const ko_KR_Seoyeon: Voice; + /** + * Neural Amazon voice, Japanese Female, Kazuha. + * @const + */ + const ja_JP_Kazuha: Voice; + /** + * Neural Amazon voice, Japanese Female (second voice), Tomoko. + * @const + */ + const ja_JP_Tomoko: Voice; + /** + * Neural Amazon voice, Japanese Male, Takumi. + * @const + */ + const ja_JP_Takumi: Voice; + /** + * Neural Amazon voice, Italian Female, Bianca. + * @const + */ + const it_IT_Bianca: Voice; + /** + * Neural Amazon voice, French Female, Léa. + * @const + */ + const fr_FR_Lea: Voice; + /** + * Neural Amazon voice, Canadian French Female, Gabrielle. + * @const + */ + const fr_CA_Gabrielle: Voice; + /** + * Neural Amazon voice, US Spanish Female, Lupe. + * @const + */ + const es_US_Lupe: Voice; + /** + * Neural Amazon voice, Mexican Spanish Female, Mia. + * @const + */ + const es_MX_Mia: Voice; + /** + * Neural Amazon voice, Castilian Spanish Female, Lucia. + * @const + */ + const es_ES_Lucia: Voice; + /** + * Neural Amazon voice, US English Female, Salli. + * @const + */ + const en_US_Salli: Voice; + /** + * Neural Amazon voice, US English Male, Matthew. + * @const + */ + const en_US_Matthew: Voice; + /** + * Neural Amazon voice, US English Female (second voice), Kimberly. + * @const + */ + const en_US_Kimberly: Voice; + /** + * Neural Amazon voice, US English Female (third voice), Kendra. + * @const + */ + const en_US_Kendra: Voice; + /** + * Neural Amazon voice, US English Male (second voice), Justin. + * @const + */ + const en_US_Justin: Voice; + /** + * Neural Amazon voice, US English Male (third voice), Joey. + * @const + */ + const en_US_Joey: Voice; + /** + * Neural Amazon voice, US English Female (fourth voice), Joanna. + * @const + */ + const en_US_Joanna: Voice; + /** + * Neural Amazon voice, US English Female (fifth voice), Ivy. + * @const + */ + const en_US_Ivy: Voice; + /** + * Neural Amazon voice, New Zealand English Female, Aria. + * @const + */ + const en_NZ_Aria: Voice; + /** + * Neural Amazon voice, South African English Female, Ayanda. + * @const + */ + const en_ZA_Ayanda: Voice; + /** + * Neural Amazon voice, British English Female, Emma. + * @const + */ + const en_GB_Emma: Voice; + /** + * Neural Amazon voice, British English Male, Brian. + * @const + */ + const en_GB_Brian: Voice; + /** + * Neural Amazon voice, British English Female (second voice), Amy. + * @const + */ + const en_GB_Amy: Voice; + /** + * Neural Amazon voice, Australian English Female, Olivia. + * @const + */ + const en_AU_Olivia: Voice; + /** + * Neural Amazon voice, German Female, Vicki. + * @const + */ + const de_DE_Vicki: Voice; + /** + * Neural Amazon voice, Danish Female, Sofie. + * @const + */ + const da_DK_Sofie: Voice; + /** + * Neural Amazon voice, Chinese Mandarin Female, Zhiyu. + * @const + */ + const cmn_CN_Zhiyu: Voice; + /** + * Neural Amazon voice, Gulf Arabic Female, Hala. + * @const + */ + const ar_AE_Hala: Voice; + /** + * Neural Amazon voice, Catalan Female, Arlet. + * @const + */ + const ca_ES_Arlet: Voice; + /** + * Neural Amazon voice, Austrian German Female, Hannah. + * @const + */ + const de_AT_Hannah: Voice; + /** + * Neural Amazon voice, US English Female, Ruth. + * @const + */ + const en_US_Ruth: Voice; + /** + * Neural Amazon voice, US English Male, Stephen. + * @const + */ + const en_US_Stephen: Voice; + /** + * Neural Amazon voice, Indian English Female, Kajal. + * @const + */ + const en_IN_Kajal: Voice; + /** + * Neural Amazon voice, Cantonese Female, Hiujin. + * @const + */ + const yue_CN_Hiujin: Voice; + /** + * Neural Amazon voice, Finnish Female, Suvi. + * @const + */ + const fi_FI_Suvi: Voice; + /** + * Neural Amazon voice, Irish English Female, Niamh. + * @const + */ + const en_IE_Niamh: Voice; + /** + * Neural Amazon voice, British English Male, Arthur. + * @const + */ + const en_GB_Arthur: Voice; + /** + * Neural Amazon voice, German Male, Daniel. + * @const + */ + const de_DE_Daniel: Voice; + /** + * Neural Amazon voice, Canadian French Male, Liam. + * @const + */ + const fr_CA_Liam: Voice; + /** + * Neural Amazon voice, US Spanish Male, Pedro. + * @const + */ + const es_US_Pedro: Voice; + /** + * Neural Amazon voice, Castilian Spanish Male, Sergio. + * @const + */ + const es_ES_Sergio: Voice; + /** + * Neural Amazon voice, Mexican Spanish Male, Andrés. + * @const + */ + const es_MX_Andres: Voice; + /** + * Neural Amazon voice, French Male, Rémi. + * @const + */ + const fr_FR_Remi: Voice; + /** + * Neural Amazon voice, Italian Male, Adriano. + * @const + */ + const it_IT_Adriano: Voice; + /** + * Neural Amazon voice, Brazilian Portuguese Male, Thiago. + * @const + */ + const pt_BR_Thiago: Voice; + /** + * Neural Amazon voice, Gulf Arabic Male, Zayd. + * @const + */ + const ar_AE_Zayd: Voice; + } + } +} + +declare namespace VoiceList { + /** + * List of available freemium TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace Default { + /** + * Freemium voice, Russian (Russia) female. + * @const + */ + const ru_RU_Female: Voice; + /** + * Freemium voice, Russian (Russia) male. + * @const + */ + const ru_RU_Male: Voice; + /** + * Freemium voice, English (US) female. + * @const + */ + const en_US_Female: Voice; + /** + * Freemium voice, English (US) male. + * @const + */ + const en_US_Male: Voice; + /** + * Freemium voice, Mandarin Chinese female. + * @const + */ + const cmn_CN_Female: Voice; + /** + * Freemium voice, Dutch (Netherlands) female. + * @const + */ + const nl_NL_Female: Voice; + /** + * Freemium voice, Dutch (Netherlands) male. + * @const + */ + const nl_NL_Male: Voice; + /** + * Freemium voice, Danish (Denmark) female. + * @const + */ + const da_DK_Female: Voice; + /** + * Freemium voice, Danish (Denmark) male. + * @const + */ + const da_DK_Male: Voice; + /** + * Freemium voice, Hindi (India) female. + * @const + */ + const hi_IN_Female: Voice; + /** + * Freemium voice, German (Germany) female. + * @const + */ + const de_DE_Female: Voice; + /** + * Freemium voice, German (Germany) male. + * @const + */ + const de_DE_Male: Voice; + /** + * Freemium voice, Italian (Italy) female. + * @const + */ + const it_IT_Female: Voice; + /** + * Freemium voice, Italian (Italy) male. + * @const + */ + const it_IT_Male: Voice; + /** + * Freemium voice, Japanese (Japan) female. + * @const + */ + const ja_JP_Female: Voice; + /** + * Freemium voice, Japanese (Japan) male. + * @const + */ + const ja_JP_Male: Voice; + /** + * Freemium voice, Korean (South Korea) female. + * @const + */ + const ko_KR_Female: Voice; + /** + * Freemium voice, Norwegian (Norway) female. + * @const + */ + const nb_NO_Female: Voice; + /** + * Freemium voice, Polish (Poland) female. + * @const + */ + const pl_PL_Female: Voice; + /** + * Freemium voice, Polish (Poland) male. + * @const + */ + const pl_PL_Male: Voice; + /** + * Freemium voice, Portuguese (Portugal) female. + * @const + */ + const pt_PT_Female: Voice; + /** + * Freemium voice, Portuguese (Portugal) male. + * @const + */ + const pt_PT_Male: Voice; + /** + * Freemium voice, Romanian (Romania) female. + * @const + */ + const ro_RO_Female: Voice; + /** + * Freemium voice, Spanish (Spain) female. + * @const + */ + const es_ES_Female: Voice; + /** + * Freemium voice, Spanish (Spain) male. + * @const + */ + const es_ES_Male: Voice; + /** + * Freemium voice, Swedish (Sweden) female. + * @const + */ + const sv_SE_Female: Voice; + /** + * Freemium voice, Turkish (Turkiye) female. + * @const + */ + const tr_TR_Female: Voice; + } +} + +declare namespace VoiceList { + /** + * List of available ElevenLabs TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace ElevenLabs { + /** + * ElevenLabs voice, female, middle-aged, British, confident, news. + * @const + */ + const Alice: Voice; + + /** + * ElevenLabs voice, female, middle-aged, American, expressive, social media. + * @const + */ + const Aria: Voice; + + /** + * ElevenLabs voice, male, old, American, trustworthy, narration. + * @const + */ + const Bill: Voice; + + /** + * ElevenLabs voice, male, middle-aged, American, deep, narration. + * @const + */ + const Brian: Voice; + + /** + * ElevenLabs voice, male, middle-aged, Transatlantic, intense, characters. + * @const + */ + const Callum: Voice; + + /** + * ElevenLabs voice, male, middle-aged, Australian, natural, conversational. + * @const + */ + const Charlie: Voice; + + /** + * ElevenLabs voice, female, young, Swedish, seductive, characters. + * @const + */ + const Charlotte: Voice; + + /** + * ElevenLabs voice, male, middle-aged, American, casual, conversational. + * @const + */ + const Chris: Voice; + + /** + * ElevenLabs voice, male, middle-aged, British, authoritative, news. + * @const + */ + const Daniel: Voice; + + /** + * ElevenLabs voice, male, middle-aged, American, friendly, conversational. + * @const + */ + const Eric: Voice; + + /** + * ElevenLabs voice, male, middle-aged, British, warm, narration. + * @const + */ + const George: Voice; + + /** + * ElevenLabs voice, female, young, American, expressive, conversational. + * @const + */ + const Jessica: Voice; + + /** + * ElevenLabs voice, female, young, American, upbeat, social media. + * @const + */ + const Laura: Voice; + + /** + * ElevenLabs voice, male, young, American, articulate, narration. + * @const + */ + const Liam: Voice; + + /** + * ElevenLabs voice, female, middle-aged, British, warm, narration. + * @const + */ + const Lily: Voice; + + /** + * ElevenLabs voice, female, middle-aged, American, friendly, narration. + * @const + */ + const Matilda: Voice; + + /** + * ElevenLabs voice, non-binary, middle-aged, American, confident, social media. + * @const + */ + const River: Voice; + + /** + * ElevenLabs voice, male, middle-aged, American, confident, social media. + * @const + */ + const Roger: Voice; + + /** + * ElevenLabs voice, female, young, American, soft, news. + * @const + */ + const Sarah: Voice; + + /** + * ElevenLabs voice, male, young, American, friendly, social media. + * @const + */ + const Will: Voice; + + /** + * Creates a brand voice with ElevenLabs. To use this method, please contact support. + * @param name The name of the voice + */ + const createBrandVoice: (name: string) => Voice; + } +} + +declare namespace VoiceList { + /** + * List of available Google TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace Google { + /** + * Google voice, Afrikaans (South Africa) female. + * @const + */ + const af_ZA_Standard_A: Voice; + /** + * Google voice, Amharic (Ethiopia) female. + * @const + */ + const am_ET_Standard_A: Voice; + /** + * Google voice, Amharic (Ethiopia) male. + * @const + */ + const am_ET_Standard_B: Voice; + /** + * Google voice, Amharic (Ethiopia) female. + * @const + */ + const am_ET_Wavenet_A: Voice; + /** + * Google voice, Amharic (Ethiopia) male. + * @const + */ + const am_ET_Wavenet_B: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) female. + * @const + */ + const ar_XA_Standard_A: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) male. + * @const + */ + const ar_XA_Standard_B: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) male (second voice). + * @const + */ + const ar_XA_Standard_C: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) female (second voice). + * @const + */ + const ar_XA_Standard_D: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) female. + * @const + */ + const ar_XA_Wavenet_A: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) male. + * @const + */ + const ar_XA_Wavenet_B: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) male (second voice). + * @const + */ + const ar_XA_Wavenet_C: Voice; + /** + * Google voice, Arabic (Pseudo-Accents) female (second voice). + * @const + */ + const ar_XA_Wavenet_D: Voice; + /** + * Google voice, Bulgarian (Bulgaria) female. + * @const + */ + const bg_BG_Standard_A: Voice; + /** + * Google voice, Bangla (India) female. + * @const + */ + const bn_IN_Standard_A: Voice; + /** + * Google voice, Bangla (India) male. + * @const + */ + const bn_IN_Standard_B: Voice; + /** + * Google voice, Bangla (India) female (second voice). + * @const + */ + const bn_IN_Standard_C: Voice; + /** + * Google voice, Bangla (India) male (second voice). + * @const + */ + const bn_IN_Standard_D: Voice; + /** + * Google voice, Bangla (India) female. + * @const + */ + const bn_IN_Wavenet_A: Voice; + /** + * Google voice, Bangla (India) male. + * @const + */ + const bn_IN_Wavenet_B: Voice; + /** + * Google voice, Bangla (India) female (second voice). + * @const + */ + const bn_IN_Wavenet_C: Voice; + /** + * Google voice, Bangla (India) male (second voice). + * @const + */ + const bn_IN_Wavenet_D: Voice; + /** + * Google voice, Catalan (Spain) female. + * @const + */ + const ca_ES_Standard_A: Voice; + /** + * Google voice, Chinese (China) female. + * @const + */ + const cmn_CN_Standard_A: Voice; + /** + * Google voice, Chinese (China) male. + * @const + */ + const cmn_CN_Standard_B: Voice; + /** + * Google voice, Chinese (China) male (second voice). + * @const + */ + const cmn_CN_Standard_C: Voice; + /** + * Google voice, Chinese (China) female (second voice). + * @const + */ + const cmn_CN_Standard_D: Voice; + /** + * Google voice, Chinese (China) female. + * @const + */ + const cmn_CN_Wavenet_A: Voice; + /** + * Google voice, Chinese (China) male. + * @const + */ + const cmn_CN_Wavenet_B: Voice; + /** + * Google voice, Chinese (China) male (second voice). + * @const + */ + const cmn_CN_Wavenet_C: Voice; + /** + * Google voice, Chinese (China) female (second voice). + * @const + */ + const cmn_CN_Wavenet_D: Voice; + /** + * Google voice, Chinese (Taiwan) female. + * @const + */ + const cmn_TW_Standard_A: Voice; + /** + * Google voice, Chinese (Taiwan) male. + * @const + */ + const cmn_TW_Standard_B: Voice; + /** + * Google voice, Chinese (Taiwan) male (second voice). + * @const + */ + const cmn_TW_Standard_C: Voice; + /** + * Google voice, Chinese (Taiwan) female. + * @const + */ + const cmn_TW_Wavenet_A: Voice; + /** + * Google voice, Chinese (Taiwan) male. + * @const + */ + const cmn_TW_Wavenet_B: Voice; + /** + * Google voice, Chinese (Taiwan) male (second voice). + * @const + */ + const cmn_TW_Wavenet_C: Voice; + /** + * Google voice, Czech (Czechia) female. + * @const + */ + const cs_CZ_Standard_A: Voice; + /** + * Google voice, Czech (Czechia) female. + * @const + */ + const cs_CZ_Wavenet_A: Voice; + /** + * Google voice, Danish (Denmark) female. + * @const + */ + const da_DK_Neural2_D: Voice; + /** + * Google voice, Danish (Denmark) female. + * @const + */ + const da_DK_Standard_A: Voice; + /** + * Google voice, Danish (Denmark) male. + * @const + */ + const da_DK_Standard_C: Voice; + /** + * Google voice, Danish (Denmark) female (second voice). + * @const + */ + const da_DK_Standard_D: Voice; + /** + * Google voice, Danish (Denmark) female (third voice). + * @const + */ + const da_DK_Standard_E: Voice; + /** + * Google voice, Danish (Denmark) female. + * @const + */ + const da_DK_Wavenet_A: Voice; + /** + * Google voice, Danish (Denmark) male. + * @const + */ + const da_DK_Wavenet_C: Voice; + /** + * Google voice, Danish (Denmark) female (second voice). + * @const + */ + const da_DK_Wavenet_D: Voice; + /** + * Google voice, Danish (Denmark) female (third voice). + * @const + */ + const da_DK_Wavenet_E: Voice; + /** + * Google voice, German (Germany) female. + * @const + */ + const de_DE_Neural2_A: Voice; + /** + * Google voice, German (Germany) male. + * @const + */ + const de_DE_Neural2_B: Voice; + /** + * Google voice, German (Germany) female (second voice). + * @const + */ + const de_DE_Neural2_C: Voice; + /** + * Google voice, German (Germany) male (second voice). + * @const + */ + const de_DE_Neural2_D: Voice; + /** + * Google voice, German (Germany) female (third voice). + * @const + */ + const de_DE_Neural2_F: Voice; + /** + * Google voice, German (Germany) male. + * @const + */ + const de_DE_Polyglot_1: Voice; + /** + * Google voice, German (Germany) female. + * @const + */ + const de_DE_Standard_A: Voice; + /** + * Google voice, German (Germany) male. + * @const + */ + const de_DE_Standard_B: Voice; + /** + * Google voice, German (Germany) female (second voice). + * @const + */ + const de_DE_Standard_C: Voice; + /** + * Google voice, German (Germany) male (second voice). + * @const + */ + const de_DE_Standard_D: Voice; + /** + * Google voice, German (Germany) male (third voice). + * @const + */ + const de_DE_Standard_E: Voice; + /** + * Google voice, German (Germany) female (third voice). + * @const + */ + const de_DE_Standard_F: Voice; + /** + * Google voice, German (Germany) male. + * @const + */ + const de_DE_Studio_B: Voice; + /** + * Google voice, German (Germany) female. + * @const + */ + const de_DE_Studio_C: Voice; + /** + * Google voice, German (Germany) female. + * @const + */ + const de_DE_Wavenet_A: Voice; + /** + * Google voice, German (Germany) male. + * @const + */ + const de_DE_Wavenet_B: Voice; + /** + * Google voice, German (Germany) female (second voice). + * @const + */ + const de_DE_Wavenet_C: Voice; + /** + * Google voice, German (Germany) male (second voice). + * @const + */ + const de_DE_Wavenet_D: Voice; + /** + * Google voice, German (Germany) male (third voice). + * @const + */ + const de_DE_Wavenet_E: Voice; + /** + * Google voice, German (Germany) female (third voice). + * @const + */ + const de_DE_Wavenet_F: Voice; + /** + * Google voice, Greek (Greece) female. + * @const + */ + const el_GR_Standard_A: Voice; + /** + * Google voice, Greek (Greece) female. + * @const + */ + const el_GR_Wavenet_A: Voice; + /** + * Google voice, Australian English female. + * @const + */ + const en_AU_Neural2_A: Voice; + /** + * Google voice, Australian English male. + * @const + */ + const en_AU_Neural2_B: Voice; + /** + * Google voice, Australian English female (second voice). + * @const + */ + const en_AU_Neural2_C: Voice; + /** + * Google voice, Australian English male (second voice). + * @const + */ + const en_AU_Neural2_D: Voice; + /** + * Google voice, Australian English female. + * @const + */ + const en_AU_News_E: Voice; + /** + * Google voice, Australian English female (second voice). + * @const + */ + const en_AU_News_F: Voice; + /** + * Google voice, Australian English male. + * @const + */ + const en_AU_News_G: Voice; + /** + * Google voice, Australian English male. + * @const + */ + const en_AU_Polyglot_1: Voice; + /** + * Google voice, Australian English female. + * @const + */ + const en_AU_Standard_A: Voice; + /** + * Google voice, Australian English male. + * @const + */ + const en_AU_Standard_B: Voice; + /** + * Google voice, Australian English female (second voice). + * @const + */ + const en_AU_Standard_C: Voice; + /** + * Google voice, Australian English male (second voice). + * @const + */ + const en_AU_Standard_D: Voice; + /** + * Google voice, Australian English female. + * @const + */ + const en_AU_Wavenet_A: Voice; + /** + * Google voice, Australian English male. + * @const + */ + const en_AU_Wavenet_B: Voice; + /** + * Google voice, Australian English female (second voice). + * @const + */ + const en_AU_Wavenet_C: Voice; + /** + * Google voice, Australian English male (second voice). + * @const + */ + const en_AU_Wavenet_D: Voice; + /** + * Google voice, British English female. + * @const + */ + const en_GB_Neural2_A: Voice; + /** + * Google voice, British English male. + * @const + */ + const en_GB_Neural2_B: Voice; + /** + * Google voice, British English female (second voice). + * @const + */ + const en_GB_Neural2_C: Voice; + /** + * Google voice, British English male (second voice). + * @const + */ + const en_GB_Neural2_D: Voice; + /** + * Google voice, British English female (third voice). + * @const + */ + const en_GB_Neural2_F: Voice; + /** + * Google voice, British English female. + * @const + */ + const en_GB_News_G: Voice; + /** + * Google voice, British English female (second voice). + * @const + */ + const en_GB_News_H: Voice; + /** + * Google voice, British English female (third voice). + * @const + */ + const en_GB_News_I: Voice; + /** + * Google voice, British English male. + * @const + */ + const en_GB_News_J: Voice; + /** + * Google voice, British English male (second voice). + * @const + */ + const en_GB_News_K: Voice; + /** + * Google voice, British English male (third voice). + * @const + */ + const en_GB_News_L: Voice; + /** + * Google voice, British English male (fourth voice). + * @const + */ + const en_GB_News_M: Voice; + /** + * Google voice, British English female. + * @const + */ + const en_GB_Standard_A: Voice; + /** + * Google voice, British English male. + * @const + */ + const en_GB_Standard_B: Voice; + /** + * Google voice, British English female (second voice). + * @const + */ + const en_GB_Standard_C: Voice; + /** + * Google voice, British English male (second voice). + * @const + */ + const en_GB_Standard_D: Voice; + /** + * Google voice, British English female (third voice). + * @const + */ + const en_GB_Standard_F: Voice; + /** + * Google voice, British English male. + * @const + */ + const en_GB_Studio_B: Voice; + /** + * Google voice, British English female. + * @const + */ + const en_GB_Studio_C: Voice; + /** + * Google voice, British English female. + * @const + */ + const en_GB_Wavenet_A: Voice; + /** + * Google voice, British English male. + * @const + */ + const en_GB_Wavenet_B: Voice; + /** + * Google voice, British English female (second voice). + * @const + */ + const en_GB_Wavenet_C: Voice; + /** + * Google voice, British English male (second voice). + * @const + */ + const en_GB_Wavenet_D: Voice; + /** + * Google voice, British English female (third voice). + * @const + */ + const en_GB_Wavenet_F: Voice; + /** + * Google voice, English (India) female. + * @const + */ + const en_IN_Neural2_A: Voice; + /** + * Google voice, English (India) male. + * @const + */ + const en_IN_Neural2_B: Voice; + /** + * Google voice, English (India) male (second voice). + * @const + */ + const en_IN_Neural2_C: Voice; + /** + * Google voice, English (India) female (second voice). + * @const + */ + const en_IN_Neural2_D: Voice; + /** + * Google voice, English (India) female. + * @const + */ + const en_IN_Standard_A: Voice; + /** + * Google voice, English (India) male. + * @const + */ + const en_IN_Standard_B: Voice; + /** + * Google voice, English (India) male (second voice). + * @const + */ + const en_IN_Standard_C: Voice; + /** + * Google voice, English (India) female (second voice). + * @const + */ + const en_IN_Standard_D: Voice; + /** + * Google voice, English (India) female (third voice). + * @const + */ + const en_IN_Standard_E: Voice; + /** + * Google voice, English (India) male (third voice). + * @const + */ + const en_IN_Standard_F: Voice; + /** + * Google voice, English (India) female. + * @const + */ + const en_IN_Wavenet_A: Voice; + /** + * Google voice, English (India) male. + * @const + */ + const en_IN_Wavenet_B: Voice; + /** + * Google voice, English (India) male (second voice). + * @const + */ + const en_IN_Wavenet_C: Voice; + /** + * Google voice, English (India) female (second voice). + * @const + */ + const en_IN_Wavenet_D: Voice; + /** + * Google voice, English (India) female (third voice). + * @const + */ + const en_IN_Wavenet_E: Voice; + /** + * Google voice, English (India) male (third voice). + * @const + */ + const en_IN_Wavenet_F: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_Casual_K: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_Journey_D: Voice; + /** + * Google voice, American English female. + * @const + */ + const en_US_Journey_F: Voice; + /** + * Google voice, American English female (second voice). + * @const + */ + const en_US_Journey_O: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_Neural2_A: Voice; + /** + * Google voice, American English female. + * @const + */ + const en_US_Neural2_C: Voice; + /** + * Google voice, American English male (second voice). + * @const + */ + const en_US_Neural2_D: Voice; + /** + * Google voice, American English female (second voice). + * @const + */ + const en_US_Neural2_E: Voice; + /** + * Google voice, American English female (third voice). + * @const + */ + const en_US_Neural2_F: Voice; + /** + * Google voice, American English female (fourth voice). + * @const + */ + const en_US_Neural2_G: Voice; + /** + * Google voice, American English female (fifth voice). + * @const + */ + const en_US_Neural2_H: Voice; + /** + * Google voice, American English male (third voice). + * @const + */ + const en_US_Neural2_I: Voice; + /** + * Google voice, American English male (fourth voice). + * @const + */ + const en_US_Neural2_J: Voice; + /** + * Google voice, American English female. + * @const + */ + const en_US_News_K: Voice; + /** + * Google voice, American English female (second voice). + * @const + */ + const en_US_News_L: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_News_N: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_Polyglot_1: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_Standard_A: Voice; + /** + * Google voice, American English male (second voice). + * @const + */ + const en_US_Standard_B: Voice; + /** + * Google voice, American English female. + * @const + */ + const en_US_Standard_C: Voice; + /** + * Google voice, American English male (third voice). + * @const + */ + const en_US_Standard_D: Voice; + /** + * Google voice, American English female (second voice). + * @const + */ + const en_US_Standard_E: Voice; + /** + * Google voice, American English female (third voice). + * @const + */ + const en_US_Standard_F: Voice; + /** + * Google voice, American English female (fourth voice). + * @const + */ + const en_US_Standard_G: Voice; + /** + * Google voice, American English female (fifth voice). + * @const + */ + const en_US_Standard_H: Voice; + /** + * Google voice, American English male (fourth voice). + * @const + */ + const en_US_Standard_I: Voice; + /** + * Google voice, American English male (fifth voice). + * @const + */ + const en_US_Standard_J: Voice; + /** + * Google voice, American English female. + * @const + */ + const en_US_Studio_O: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_Studio_Q: Voice; + /** + * Google voice, American English male. + * @const + */ + const en_US_Wavenet_A: Voice; + /** + * Google voice, American English male (second voice). + * @const + */ + const en_US_Wavenet_B: Voice; + /** + * Google voice, American English female. + * @const + */ + const en_US_Wavenet_C: Voice; + /** + * Google voice, American English male (third voice). + * @const + */ + const en_US_Wavenet_D: Voice; + /** + * Google voice, American English female (second voice). + * @const + */ + const en_US_Wavenet_E: Voice; + /** + * Google voice, American English female (third voice). + * @const + */ + const en_US_Wavenet_F: Voice; + /** + * Google voice, American English female (fourth voice). + * @const + */ + const en_US_Wavenet_G: Voice; + /** + * Google voice, American English female (fifth voice). + * @const + */ + const en_US_Wavenet_H: Voice; + /** + * Google voice, American English male (fourth voice). + * @const + */ + const en_US_Wavenet_I: Voice; + /** + * Google voice, American English male (fifth voice). + * @const + */ + const en_US_Wavenet_J: Voice; + /** + * Google voice, European Spanish female. + * @const + */ + const es_ES_Neural2_A: Voice; + /** + * Google voice, European Spanish male. + * @const + */ + const es_ES_Neural2_B: Voice; + /** + * Google voice, European Spanish female (second voice). + * @const + */ + const es_ES_Neural2_C: Voice; + /** + * Google voice, European Spanish female (third voice). + * @const + */ + const es_ES_Neural2_D: Voice; + /** + * Google voice, European Spanish female (fourth voice). + * @const + */ + const es_ES_Neural2_E: Voice; + /** + * Google voice, European Spanish male (second voice). + * @const + */ + const es_ES_Neural2_F: Voice; + /** + * Google voice, European Spanish male. + * @const + */ + const es_ES_Polyglot_1: Voice; + /** + * Google voice, European Spanish female. + * @const + */ + const es_ES_Standard_A: Voice; + /** + * Google voice, European Spanish male. + * @const + */ + const es_ES_Standard_B: Voice; + /** + * Google voice, European Spanish female (second voice). + * @const + */ + const es_ES_Standard_C: Voice; + /** + * Google voice, European Spanish female (third voice). + * @const + */ + const es_ES_Standard_D: Voice; + /** + * Google voice, European Spanish female. + * @const + */ + const es_ES_Studio_C: Voice; + /** + * Google voice, European Spanish male. + * @const + */ + const es_ES_Studio_F: Voice; + /** + * Google voice, European Spanish male. + * @const + */ + const es_ES_Wavenet_B: Voice; + /** + * Google voice, European Spanish female. + * @const + */ + const es_ES_Wavenet_C: Voice; + /** + * Google voice, European Spanish female (second voice). + * @const + */ + const es_ES_Wavenet_D: Voice; + /** + * Google voice, Spanish (United States) female. + * @const + */ + const es_US_Neural2_A: Voice; + /** + * Google voice, Spanish (United States) male. + * @const + */ + const es_US_Neural2_B: Voice; + /** + * Google voice, Spanish (United States) male (second voice). + * @const + */ + const es_US_Neural2_C: Voice; + /** + * Google voice, Spanish (United States) male. + * @const + */ + const es_US_News_D: Voice; + /** + * Google voice, Spanish (United States) male (second voice). + * @const + */ + const es_US_News_E: Voice; + /** + * Google voice, Spanish (United States) female. + * @const + */ + const es_US_News_F: Voice; + /** + * Google voice, Spanish (United States) female (second voice). + * @const + */ + const es_US_News_G: Voice; + /** + * Google voice, Spanish (United States) male. + * @const + */ + const es_US_Polyglot_1: Voice; + /** + * Google voice, Spanish (United States) female. + * @const + */ + const es_US_Standard_A: Voice; + /** + * Google voice, Spanish (United States) male. + * @const + */ + const es_US_Standard_B: Voice; + /** + * Google voice, Spanish (United States) male (second voice). + * @const + */ + const es_US_Standard_C: Voice; + /** + * Google voice, Spanish (United States) male. + * @const + */ + const es_US_Studio_B: Voice; + /** + * Google voice, Spanish (United States) female. + * @const + */ + const es_US_Wavenet_A: Voice; + /** + * Google voice, Spanish (United States) male. + * @const + */ + const es_US_Wavenet_B: Voice; + /** + * Google voice, Spanish (United States) male (second voice). + * @const + */ + const es_US_Wavenet_C: Voice; + /** + * Google voice, Basque (Spain) female. + * @const + */ + const eu_ES_Standard_A: Voice; + /** + * Google voice, Finnish (Finland) female. + * @const + */ + const fi_FI_Standard_A: Voice; + /** + * Google voice, Finnish (Finland) female. + * @const + */ + const fi_FI_Wavenet_A: Voice; + /** + * Google voice, Filipino (Philippines) female. + * @const + */ + const fil_PH_Standard_A: Voice; + /** + * Google voice, Filipino (Philippines) female (second voice). + * @const + */ + const fil_PH_Standard_B: Voice; + /** + * Google voice, Filipino (Philippines) male. + * @const + */ + const fil_PH_Standard_C: Voice; + /** + * Google voice, Filipino (Philippines) male (second voice). + * @const + */ + const fil_PH_Standard_D: Voice; + /** + * Google voice, Filipino (Philippines) female. + * @const + */ + const fil_PH_Wavenet_A: Voice; + /** + * Google voice, Filipino (Philippines) female (second voice). + * @const + */ + const fil_PH_Wavenet_B: Voice; + /** + * Google voice, Filipino (Philippines) male. + * @const + */ + const fil_PH_Wavenet_C: Voice; + /** + * Google voice, Filipino (Philippines) male (second voice). + * @const + */ + const fil_PH_Wavenet_D: Voice; + /** + * Google voice, Filipino (Philippines) female. + * @const + */ + const fil_ph_Neural2_A: Voice; + /** + * Google voice, Filipino (Philippines) male. + * @const + */ + const fil_ph_Neural2_D: Voice; + /** + * Google voice, Canadian French female. + * @const + */ + const fr_CA_Neural2_A: Voice; + /** + * Google voice, Canadian French male. + * @const + */ + const fr_CA_Neural2_B: Voice; + /** + * Google voice, Canadian French female (second voice). + * @const + */ + const fr_CA_Neural2_C: Voice; + /** + * Google voice, Canadian French male (second voice). + * @const + */ + const fr_CA_Neural2_D: Voice; + /** + * Google voice, Canadian French female. + * @const + */ + const fr_CA_Standard_A: Voice; + /** + * Google voice, Canadian French male. + * @const + */ + const fr_CA_Standard_B: Voice; + /** + * Google voice, Canadian French female (second voice). + * @const + */ + const fr_CA_Standard_C: Voice; + /** + * Google voice, Canadian French male (second voice). + * @const + */ + const fr_CA_Standard_D: Voice; + /** + * Google voice, Canadian French female. + * @const + */ + const fr_CA_Wavenet_A: Voice; + /** + * Google voice, Canadian French male. + * @const + */ + const fr_CA_Wavenet_B: Voice; + /** + * Google voice, Canadian French female (second voice). + * @const + */ + const fr_CA_Wavenet_C: Voice; + /** + * Google voice, Canadian French male (second voice). + * @const + */ + const fr_CA_Wavenet_D: Voice; + /** + * Google voice, French (France) female. + * @const + */ + const fr_FR_Neural2_A: Voice; + /** + * Google voice, French (France) male. + * @const + */ + const fr_FR_Neural2_B: Voice; + /** + * Google voice, French (France) female (second voice). + * @const + */ + const fr_FR_Neural2_C: Voice; + /** + * Google voice, French (France) male (second voice). + * @const + */ + const fr_FR_Neural2_D: Voice; + /** + * Google voice, French (France) female (third voice). + * @const + */ + const fr_FR_Neural2_E: Voice; + /** + * Google voice, French (France) male. + * @const + */ + const fr_FR_Polyglot_1: Voice; + /** + * Google voice, French (France) female. + * @const + */ + const fr_FR_Standard_A: Voice; + /** + * Google voice, French (France) male. + * @const + */ + const fr_FR_Standard_B: Voice; + /** + * Google voice, French (France) female (second voice). + * @const + */ + const fr_FR_Standard_C: Voice; + /** + * Google voice, French (France) male (second voice). + * @const + */ + const fr_FR_Standard_D: Voice; + /** + * Google voice, French (France) female (third voice). + * @const + */ + const fr_FR_Standard_E: Voice; + /** + * Google voice, French (France) female (fourth voice). + * @const + */ + const fr_FR_Standard_F: Voice; + /** + * Google voice, French (France) male (third voice). + * @const + */ + const fr_FR_Standard_G: Voice; + /** + * Google voice, French (France) female. + * @const + */ + const fr_FR_Studio_A: Voice; + /** + * Google voice, French (France) male. + * @const + */ + const fr_FR_Studio_D: Voice; + /** + * Google voice, French (France) female. + * @const + */ + const fr_FR_Wavenet_A: Voice; + /** + * Google voice, French (France) male. + * @const + */ + const fr_FR_Wavenet_B: Voice; + /** + * Google voice, French (France) female (second voice). + * @const + */ + const fr_FR_Wavenet_C: Voice; + /** + * Google voice, French (France) male (second voice). + * @const + */ + const fr_FR_Wavenet_D: Voice; + /** + * Google voice, French (France) female (third voice). + * @const + */ + const fr_FR_Wavenet_E: Voice; + /** + * Google voice, French (France) female (fourth voice). + * @const + */ + const fr_FR_Wavenet_F: Voice; + /** + * Google voice, French (France) male (third voice). + * @const + */ + const fr_FR_Wavenet_G: Voice; + /** + * Google voice, Galician (Spain) female. + * @const + */ + const gl_ES_Standard_A: Voice; + /** + * Google voice, Gujarati (India) female. + * @const + */ + const gu_IN_Standard_A: Voice; + /** + * Google voice, Gujarati (India) male. + * @const + */ + const gu_IN_Standard_B: Voice; + /** + * Google voice, Gujarati (India) female (second voice). + * @const + */ + const gu_IN_Standard_C: Voice; + /** + * Google voice, Gujarati (India) male (second voice). + * @const + */ + const gu_IN_Standard_D: Voice; + /** + * Google voice, Gujarati (India) female. + * @const + */ + const gu_IN_Wavenet_A: Voice; + /** + * Google voice, Gujarati (India) male. + * @const + */ + const gu_IN_Wavenet_B: Voice; + /** + * Google voice, Gujarati (India) female (second voice). + * @const + */ + const gu_IN_Wavenet_C: Voice; + /** + * Google voice, Gujarati (India) male (second voice). + * @const + */ + const gu_IN_Wavenet_D: Voice; + /** + * Google voice, Hebrew (Israel) female. + * @const + */ + const he_IL_Standard_A: Voice; + /** + * Google voice, Hebrew (Israel) male. + * @const + */ + const he_IL_Standard_B: Voice; + /** + * Google voice, Hebrew (Israel) female (second voice). + * @const + */ + const he_IL_Standard_C: Voice; + /** + * Google voice, Hebrew (Israel) male (second voice). + * @const + */ + const he_IL_Standard_D: Voice; + /** + * Google voice, Hebrew (Israel) female. + * @const + */ + const he_IL_Wavenet_A: Voice; + /** + * Google voice, Hebrew (Israel) male. + * @const + */ + const he_IL_Wavenet_B: Voice; + /** + * Google voice, Hebrew (Israel) female (second voice). + * @const + */ + const he_IL_Wavenet_C: Voice; + /** + * Google voice, Hebrew (Israel) male (second voice). + * @const + */ + const he_IL_Wavenet_D: Voice; + /** + * Google voice, Hindi (India) female. + * @const + */ + const hi_IN_Neural2_A: Voice; + /** + * Google voice, Hindi (India) male. + * @const + */ + const hi_IN_Neural2_B: Voice; + /** + * Google voice, Hindi (India) male (second voice). + * @const + */ + const hi_IN_Neural2_C: Voice; + /** + * Google voice, Hindi (India) female (second voice). + * @const + */ + const hi_IN_Neural2_D: Voice; + /** + * Google voice, Hindi (India) female. + * @const + */ + const hi_IN_Standard_A: Voice; + /** + * Google voice, Hindi (India) male. + * @const + */ + const hi_IN_Standard_B: Voice; + /** + * Google voice, Hindi (India) male (second voice). + * @const + */ + const hi_IN_Standard_C: Voice; + /** + * Google voice, Hindi (India) female (second voice). + * @const + */ + const hi_IN_Standard_D: Voice; + /** + * Google voice, Hindi (India) female (third voice). + * @const + */ + const hi_IN_Standard_E: Voice; + /** + * Google voice, Hindi (India) male (third voice). + * @const + */ + const hi_IN_Standard_F: Voice; + /** + * Google voice, Hindi (India) female. + * @const + */ + const hi_IN_Wavenet_A: Voice; + /** + * Google voice, Hindi (India) male. + * @const + */ + const hi_IN_Wavenet_B: Voice; + /** + * Google voice, Hindi (India) male (second voice). + * @const + */ + const hi_IN_Wavenet_C: Voice; + /** + * Google voice, Hindi (India) female (second voice). + * @const + */ + const hi_IN_Wavenet_D: Voice; + /** + * Google voice, Hindi (India) female (third voice). + * @const + */ + const hi_IN_Wavenet_E: Voice; + /** + * Google voice, Hindi (India) male (third voice). + * @const + */ + const hi_IN_Wavenet_F: Voice; + /** + * Google voice, Hungarian (Hungary) female. + * @const + */ + const hu_HU_Standard_A: Voice; + /** + * Google voice, Hungarian (Hungary) female. + * @const + */ + const hu_HU_Wavenet_A: Voice; + /** + * Google voice, Indonesian (Indonesia) female. + * @const + */ + const id_ID_Standard_A: Voice; + /** + * Google voice, Indonesian (Indonesia) male. + * @const + */ + const id_ID_Standard_B: Voice; + /** + * Google voice, Indonesian (Indonesia) male (second voice). + * @const + */ + const id_ID_Standard_C: Voice; + /** + * Google voice, Indonesian (Indonesia) female (second voice). + * @const + */ + const id_ID_Standard_D: Voice; + /** + * Google voice, Indonesian (Indonesia) female. + * @const + */ + const id_ID_Wavenet_A: Voice; + /** + * Google voice, Indonesian (Indonesia) male. + * @const + */ + const id_ID_Wavenet_B: Voice; + /** + * Google voice, Indonesian (Indonesia) male (second voice). + * @const + */ + const id_ID_Wavenet_C: Voice; + /** + * Google voice, Indonesian (Indonesia) female (second voice). + * @const + */ + const id_ID_Wavenet_D: Voice; + /** + * Google voice, Icelandic (Iceland) female. + * @const + */ + const is_IS_Standard_A: Voice; + /** + * Google voice, Italian (Italy) female. + * @const + */ + const it_IT_Neural2_A: Voice; + /** + * Google voice, Italian (Italy) male. + * @const + */ + const it_IT_Neural2_C: Voice; + /** + * Google voice, Italian (Italy) female. + * @const + */ + const it_IT_Standard_A: Voice; + /** + * Google voice, Italian (Italy) female (second voice). + * @const + */ + const it_IT_Standard_B: Voice; + /** + * Google voice, Italian (Italy) male. + * @const + */ + const it_IT_Standard_C: Voice; + /** + * Google voice, Italian (Italy) male (second voice). + * @const + */ + const it_IT_Standard_D: Voice; + /** + * Google voice, Italian (Italy) female. + * @const + */ + const it_IT_Wavenet_A: Voice; + /** + * Google voice, Italian (Italy) female (second voice). + * @const + */ + const it_IT_Wavenet_B: Voice; + /** + * Google voice, Italian (Italy) male. + * @const + */ + const it_IT_Wavenet_C: Voice; + /** + * Google voice, Italian (Italy) male (second voice). + * @const + */ + const it_IT_Wavenet_D: Voice; + /** + * Google voice, Japanese (Japan) female. + * @const + */ + const ja_JP_Neural2_B: Voice; + /** + * Google voice, Japanese (Japan) male. + * @const + */ + const ja_JP_Neural2_C: Voice; + /** + * Google voice, Japanese (Japan) male (second voice). + * @const + */ + const ja_JP_Neural2_D: Voice; + /** + * Google voice, Japanese (Japan) female. + * @const + */ + const ja_JP_Standard_A: Voice; + /** + * Google voice, Japanese (Japan) female (second voice). + * @const + */ + const ja_JP_Standard_B: Voice; + /** + * Google voice, Japanese (Japan) male. + * @const + */ + const ja_JP_Standard_C: Voice; + /** + * Google voice, Japanese (Japan) male (second voice). + * @const + */ + const ja_JP_Standard_D: Voice; + /** + * Google voice, Japanese (Japan) female. + * @const + */ + const ja_JP_Wavenet_A: Voice; + /** + * Google voice, Japanese (Japan) female (second voice). + * @const + */ + const ja_JP_Wavenet_B: Voice; + /** + * Google voice, Japanese (Japan) male. + * @const + */ + const ja_JP_Wavenet_C: Voice; + /** + * Google voice, Japanese (Japan) male (second voice). + * @const + */ + const ja_JP_Wavenet_D: Voice; + /** + * Google voice, Kannada (India) female. + * @const + */ + const kn_IN_Standard_A: Voice; + /** + * Google voice, Kannada (India) male. + * @const + */ + const kn_IN_Standard_B: Voice; + /** + * Google voice, Kannada (India) female (second voice). + * @const + */ + const kn_IN_Standard_C: Voice; + /** + * Google voice, Kannada (India) male (second voice). + * @const + */ + const kn_IN_Standard_D: Voice; + /** + * Google voice, Kannada (India) female. + * @const + */ + const kn_IN_Wavenet_A: Voice; + /** + * Google voice, Kannada (India) male. + * @const + */ + const kn_IN_Wavenet_B: Voice; + /** + * Google voice, Kannada (India) female (second voice). + * @const + */ + const kn_IN_Wavenet_C: Voice; + /** + * Google voice, Kannada (India) male (second voice). + * @const + */ + const kn_IN_Wavenet_D: Voice; + /** + * Google voice, Korean (South Korea) female. + * @const + */ + const ko_KR_Neural2_A: Voice; + /** + * Google voice, Korean (South Korea) female (second voice). + * @const + */ + const ko_KR_Neural2_B: Voice; + /** + * Google voice, Korean (South Korea) male. + * @const + */ + const ko_KR_Neural2_C: Voice; + /** + * Google voice, Korean (South Korea) female. + * @const + */ + const ko_KR_Standard_A: Voice; + /** + * Google voice, Korean (South Korea) female (second voice). + * @const + */ + const ko_KR_Standard_B: Voice; + /** + * Google voice, Korean (South Korea) male. + * @const + */ + const ko_KR_Standard_C: Voice; + /** + * Google voice, Korean (South Korea) male (second voice). + * @const + */ + const ko_KR_Standard_D: Voice; + /** + * Google voice, Korean (South Korea) female. + * @const + */ + const ko_KR_Wavenet_A: Voice; + /** + * Google voice, Korean (South Korea) female (second voice). + * @const + */ + const ko_KR_Wavenet_B: Voice; + /** + * Google voice, Korean (South Korea) male. + * @const + */ + const ko_KR_Wavenet_C: Voice; + /** + * Google voice, Korean (South Korea) male (second voice). + * @const + */ + const ko_KR_Wavenet_D: Voice; + /** + * Google voice, Lithuanian (Lithuania) male. + * @const + */ + const lt_LT_Standard_A: Voice; + /** + * Google voice, Latvian (Latvia) male. + * @const + */ + const lv_LV_Standard_A: Voice; + /** + * Google voice, Malayalam (India) female. + * @const + */ + const ml_IN_Standard_A: Voice; + /** + * Google voice, Malayalam (India) male. + * @const + */ + const ml_IN_Standard_B: Voice; + /** + * Google voice, Malayalam (India) female (second voice). + * @const + */ + const ml_IN_Standard_C: Voice; + /** + * Google voice, Malayalam (India) male (second voice). + * @const + */ + const ml_IN_Standard_D: Voice; + /** + * Google voice, Malayalam (India) female. + * @const + */ + const ml_IN_Wavenet_A: Voice; + /** + * Google voice, Malayalam (India) male. + * @const + */ + const ml_IN_Wavenet_B: Voice; + /** + * Google voice, Malayalam (India) female (second voice). + * @const + */ + const ml_IN_Wavenet_C: Voice; + /** + * Google voice, Malayalam (India) male (second voice). + * @const + */ + const ml_IN_Wavenet_D: Voice; + /** + * Google voice, Marathi (India) female. + * @const + */ + const mr_IN_Standard_A: Voice; + /** + * Google voice, Marathi (India) male. + * @const + */ + const mr_IN_Standard_B: Voice; + /** + * Google voice, Marathi (India) female (second voice). + * @const + */ + const mr_IN_Standard_C: Voice; + /** + * Google voice, Marathi (India) female. + * @const + */ + const mr_IN_Wavenet_A: Voice; + /** + * Google voice, Marathi (India) male. + * @const + */ + const mr_IN_Wavenet_B: Voice; + /** + * Google voice, Marathi (India) female (second voice). + * @const + */ + const mr_IN_Wavenet_C: Voice; + /** + * Google voice, Malay (Malaysia) female. + * @const + */ + const ms_MY_Standard_A: Voice; + /** + * Google voice, Malay (Malaysia) male. + * @const + */ + const ms_MY_Standard_B: Voice; + /** + * Google voice, Malay (Malaysia) female (second voice). + * @const + */ + const ms_MY_Standard_C: Voice; + /** + * Google voice, Malay (Malaysia) male (second voice). + * @const + */ + const ms_MY_Standard_D: Voice; + /** + * Google voice, Malay (Malaysia) female. + * @const + */ + const ms_MY_Wavenet_A: Voice; + /** + * Google voice, Malay (Malaysia) male. + * @const + */ + const ms_MY_Wavenet_B: Voice; + /** + * Google voice, Malay (Malaysia) female (second voice). + * @const + */ + const ms_MY_Wavenet_C: Voice; + /** + * Google voice, Malay (Malaysia) male (second voice). + * @const + */ + const ms_MY_Wavenet_D: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) female. + * @const + */ + const nb_NO_Standard_A: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) male. + * @const + */ + const nb_NO_Standard_B: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) female (second voice). + * @const + */ + const nb_NO_Standard_C: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) male (second voice). + * @const + */ + const nb_NO_Standard_D: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) female (third voice). + * @const + */ + const nb_NO_Standard_E: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) female. + * @const + */ + const nb_NO_Wavenet_A: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) male. + * @const + */ + const nb_NO_Wavenet_B: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) female (second voice). + * @const + */ + const nb_NO_Wavenet_C: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) male (second voice). + * @const + */ + const nb_NO_Wavenet_D: Voice; + /** + * Google voice, Norwegian Bokmål (Norway) female (third voice). + * @const + */ + const nb_NO_Wavenet_E: Voice; + /** + * Google voice, Flemish female. + * @const + */ + const nl_BE_Standard_A: Voice; + /** + * Google voice, Flemish male. + * @const + */ + const nl_BE_Standard_B: Voice; + /** + * Google voice, Flemish female. + * @const + */ + const nl_BE_Wavenet_A: Voice; + /** + * Google voice, Flemish male. + * @const + */ + const nl_BE_Wavenet_B: Voice; + /** + * Google voice, Dutch (Netherlands) female. + * @const + */ + const nl_NL_Standard_A: Voice; + /** + * Google voice, Dutch (Netherlands) male. + * @const + */ + const nl_NL_Standard_B: Voice; + /** + * Google voice, Dutch (Netherlands) male (second voice). + * @const + */ + const nl_NL_Standard_C: Voice; + /** + * Google voice, Dutch (Netherlands) female (second voice). + * @const + */ + const nl_NL_Standard_D: Voice; + /** + * Google voice, Dutch (Netherlands) female (third voice). + * @const + */ + const nl_NL_Standard_E: Voice; + /** + * Google voice, Dutch (Netherlands) female. + * @const + */ + const nl_NL_Wavenet_A: Voice; + /** + * Google voice, Dutch (Netherlands) male. + * @const + */ + const nl_NL_Wavenet_B: Voice; + /** + * Google voice, Dutch (Netherlands) male (second voice). + * @const + */ + const nl_NL_Wavenet_C: Voice; + /** + * Google voice, Dutch (Netherlands) female (second voice). + * @const + */ + const nl_NL_Wavenet_D: Voice; + /** + * Google voice, Dutch (Netherlands) female (third voice). + * @const + */ + const nl_NL_Wavenet_E: Voice; + /** + * Google voice, Punjabi (India) female. + * @const + */ + const pa_IN_Standard_A: Voice; + /** + * Google voice, Punjabi (India) male. + * @const + */ + const pa_IN_Standard_B: Voice; + /** + * Google voice, Punjabi (India) female (second voice). + * @const + */ + const pa_IN_Standard_C: Voice; + /** + * Google voice, Punjabi (India) male (second voice). + * @const + */ + const pa_IN_Standard_D: Voice; + /** + * Google voice, Punjabi (India) female. + * @const + */ + const pa_IN_Wavenet_A: Voice; + /** + * Google voice, Punjabi (India) male. + * @const + */ + const pa_IN_Wavenet_B: Voice; + /** + * Google voice, Punjabi (India) female (second voice). + * @const + */ + const pa_IN_Wavenet_C: Voice; + /** + * Google voice, Punjabi (India) male (second voice). + * @const + */ + const pa_IN_Wavenet_D: Voice; + /** + * Google voice, Polish (Poland) female. + * @const + */ + const pl_PL_Standard_A: Voice; + /** + * Google voice, Polish (Poland) male. + * @const + */ + const pl_PL_Standard_B: Voice; + /** + * Google voice, Polish (Poland) male (second voice). + * @const + */ + const pl_PL_Standard_C: Voice; + /** + * Google voice, Polish (Poland) female (second voice). + * @const + */ + const pl_PL_Standard_D: Voice; + /** + * Google voice, Polish (Poland) female (third voice). + * @const + */ + const pl_PL_Standard_E: Voice; + /** + * Google voice, Polish (Poland) female. + * @const + */ + const pl_PL_Wavenet_A: Voice; + /** + * Google voice, Polish (Poland) male. + * @const + */ + const pl_PL_Wavenet_B: Voice; + /** + * Google voice, Polish (Poland) male (second voice). + * @const + */ + const pl_PL_Wavenet_C: Voice; + /** + * Google voice, Polish (Poland) female (second voice). + * @const + */ + const pl_PL_Wavenet_D: Voice; + /** + * Google voice, Polish (Poland) female (third voice). + * @const + */ + const pl_PL_Wavenet_E: Voice; + /** + * Google voice, Brazilian Portuguese female. + * @const + */ + const pt_BR_Neural2_A: Voice; + /** + * Google voice, Brazilian Portuguese male. + * @const + */ + const pt_BR_Neural2_B: Voice; + /** + * Google voice, Brazilian Portuguese female (second voice). + * @const + */ + const pt_BR_Neural2_C: Voice; + /** + * Google voice, Brazilian Portuguese female. + * @const + */ + const pt_BR_Standard_A: Voice; + /** + * Google voice, Brazilian Portuguese male. + * @const + */ + const pt_BR_Standard_B: Voice; + /** + * Google voice, Brazilian Portuguese female (second voice). + * @const + */ + const pt_BR_Standard_C: Voice; + /** + * Google voice, Brazilian Portuguese female (third voice). + * @const + */ + const pt_BR_Standard_D: Voice; + /** + * Google voice, Brazilian Portuguese male (second voice). + * @const + */ + const pt_BR_Standard_E: Voice; + /** + * Google voice, Brazilian Portuguese female. + * @const + */ + const pt_BR_Wavenet_A: Voice; + /** + * Google voice, Brazilian Portuguese male. + * @const + */ + const pt_BR_Wavenet_B: Voice; + /** + * Google voice, Brazilian Portuguese female (second voice). + * @const + */ + const pt_BR_Wavenet_C: Voice; + /** + * Google voice, Brazilian Portuguese female (third voice). + * @const + */ + const pt_BR_Wavenet_D: Voice; + /** + * Google voice, Brazilian Portuguese male (second voice). + * @const + */ + const pt_BR_Wavenet_E: Voice; + /** + * Google voice, European Portuguese female. + * @const + */ + const pt_PT_Standard_A: Voice; + /** + * Google voice, European Portuguese male. + * @const + */ + const pt_PT_Standard_B: Voice; + /** + * Google voice, European Portuguese male (second voice). + * @const + */ + const pt_PT_Standard_C: Voice; + /** + * Google voice, European Portuguese female (second voice). + * @const + */ + const pt_PT_Standard_D: Voice; + /** + * Google voice, European Portuguese female. + * @const + */ + const pt_PT_Wavenet_A: Voice; + /** + * Google voice, European Portuguese male. + * @const + */ + const pt_PT_Wavenet_B: Voice; + /** + * Google voice, European Portuguese male (second voice). + * @const + */ + const pt_PT_Wavenet_C: Voice; + /** + * Google voice, European Portuguese female (second voice). + * @const + */ + const pt_PT_Wavenet_D: Voice; + /** + * Google voice, Romanian (Romania) female. + * @const + */ + const ro_RO_Standard_A: Voice; + /** + * Google voice, Romanian (Romania) female. + * @const + */ + const ro_RO_Wavenet_A: Voice; + /** + * Google voice, Russian (Russia) female. + * @const + */ + const ru_RU_Standard_A: Voice; + /** + * Google voice, Russian (Russia) male. + * @const + */ + const ru_RU_Standard_B: Voice; + /** + * Google voice, Russian (Russia) female (second voice). + * @const + */ + const ru_RU_Standard_C: Voice; + /** + * Google voice, Russian (Russia) male (second voice). + * @const + */ + const ru_RU_Standard_D: Voice; + /** + * Google voice, Russian (Russia) female (third voice). + * @const + */ + const ru_RU_Standard_E: Voice; + /** + * Google voice, Russian (Russia) female. + * @const + */ + const ru_RU_Wavenet_A: Voice; + /** + * Google voice, Russian (Russia) male. + * @const + */ + const ru_RU_Wavenet_B: Voice; + /** + * Google voice, Russian (Russia) female (second voice). + * @const + */ + const ru_RU_Wavenet_C: Voice; + /** + * Google voice, Russian (Russia) male (second voice). + * @const + */ + const ru_RU_Wavenet_D: Voice; + /** + * Google voice, Russian (Russia) female (third voice). + * @const + */ + const ru_RU_Wavenet_E: Voice; + /** + * Google voice, Slovak (Slovakia) female. + * @const + */ + const sk_SK_Standard_A: Voice; + /** + * Google voice, Slovak (Slovakia) female. + * @const + */ + const sk_SK_Wavenet_A: Voice; + /** + * Google voice, Serbian (Serbia) female. + * @const + */ + const sr_RS_Standard_A: Voice; + /** + * Google voice, Swedish (Sweden) female. + * @const + */ + const sv_SE_Standard_A: Voice; + /** + * Google voice, Swedish (Sweden) female (second voice). + * @const + */ + const sv_SE_Standard_B: Voice; + /** + * Google voice, Swedish (Sweden) female (third voice). + * @const + */ + const sv_SE_Standard_C: Voice; + /** + * Google voice, Swedish (Sweden) male. + * @const + */ + const sv_SE_Standard_D: Voice; + /** + * Google voice, Swedish (Sweden) male (second voice). + * @const + */ + const sv_SE_Standard_E: Voice; + /** + * Google voice, Swedish (Sweden) female. + * @const + */ + const sv_SE_Wavenet_A: Voice; + /** + * Google voice, Swedish (Sweden) female (second voice). + * @const + */ + const sv_SE_Wavenet_B: Voice; + /** + * Google voice, Swedish (Sweden) male. + * @const + */ + const sv_SE_Wavenet_C: Voice; + /** + * Google voice, Swedish (Sweden) female (third voice). + * @const + */ + const sv_SE_Wavenet_D: Voice; + /** + * Google voice, Swedish (Sweden) male (second voice). + * @const + */ + const sv_SE_Wavenet_E: Voice; + /** + * Google voice, Tamil (India) female. + * @const + */ + const ta_IN_Standard_A: Voice; + /** + * Google voice, Tamil (India) male. + * @const + */ + const ta_IN_Standard_B: Voice; + /** + * Google voice, Tamil (India) female (second voice). + * @const + */ + const ta_IN_Standard_C: Voice; + /** + * Google voice, Tamil (India) male (second voice). + * @const + */ + const ta_IN_Standard_D: Voice; + /** + * Google voice, Tamil (India) female. + * @const + */ + const ta_IN_Wavenet_A: Voice; + /** + * Google voice, Tamil (India) male. + * @const + */ + const ta_IN_Wavenet_B: Voice; + /** + * Google voice, Tamil (India) female (second voice). + * @const + */ + const ta_IN_Wavenet_C: Voice; + /** + * Google voice, Tamil (India) male (second voice). + * @const + */ + const ta_IN_Wavenet_D: Voice; + /** + * Google voice, Telugu (India) female. + * @const + */ + const te_IN_Standard_A: Voice; + /** + * Google voice, Telugu (India) male. + * @const + */ + const te_IN_Standard_B: Voice; + /** + * Google voice, Thai (Thailand) female. + * @const + */ + const th_TH_Neural2_C: Voice; + /** + * Google voice, Thai (Thailand) female. + * @const + */ + const th_TH_Standard_A: Voice; + /** + * Google voice, Turkish (Türkiye) female. + * @const + */ + const tr_TR_Standard_A: Voice; + /** + * Google voice, Turkish (Türkiye) male. + * @const + */ + const tr_TR_Standard_B: Voice; + /** + * Google voice, Turkish (Türkiye) female (second voice). + * @const + */ + const tr_TR_Standard_C: Voice; + /** + * Google voice, Turkish (Türkiye) female (third voice). + * @const + */ + const tr_TR_Standard_D: Voice; + /** + * Google voice, Turkish (Türkiye) male (second voice). + * @const + */ + const tr_TR_Standard_E: Voice; + /** + * Google voice, Turkish (Türkiye) female. + * @const + */ + const tr_TR_Wavenet_A: Voice; + /** + * Google voice, Turkish (Türkiye) male. + * @const + */ + const tr_TR_Wavenet_B: Voice; + /** + * Google voice, Turkish (Türkiye) female (second voice). + * @const + */ + const tr_TR_Wavenet_C: Voice; + /** + * Google voice, Turkish (Türkiye) female (third voice). + * @const + */ + const tr_TR_Wavenet_D: Voice; + /** + * Google voice, Turkish (Türkiye) male (second voice). + * @const + */ + const tr_TR_Wavenet_E: Voice; + /** + * Google voice, Ukrainian (Ukraine) female. + * @const + */ + const uk_UA_Standard_A: Voice; + /** + * Google voice, Ukrainian (Ukraine) female. + * @const + */ + const uk_UA_Wavenet_A: Voice; + /** + * Google voice, Urdu (India) female. + * @const + */ + const ur_IN_Standard_A: Voice; + /** + * Google voice, Urdu (India) male. + * @const + */ + const ur_IN_Standard_B: Voice; + /** + * Google voice, Urdu (India) female. + * @const + */ + const ur_IN_Wavenet_A: Voice; + /** + * Google voice, Urdu (India) male. + * @const + */ + const ur_IN_Wavenet_B: Voice; + /** + * Google voice, Vietnamese (Vietnam) female. + * @const + */ + const vi_VN_Neural2_A: Voice; + /** + * Google voice, Vietnamese (Vietnam) male. + * @const + */ + const vi_VN_Neural2_D: Voice; + /** + * Google voice, Vietnamese (Vietnam) female. + * @const + */ + const vi_VN_Standard_A: Voice; + /** + * Google voice, Vietnamese (Vietnam) male. + * @const + */ + const vi_VN_Standard_B: Voice; + /** + * Google voice, Vietnamese (Vietnam) female (second voice). + * @const + */ + const vi_VN_Standard_C: Voice; + /** + * Google voice, Vietnamese (Vietnam) male (second voice). + * @const + */ + const vi_VN_Standard_D: Voice; + /** + * Google voice, Vietnamese (Vietnam) female. + * @const + */ + const vi_VN_Wavenet_A: Voice; + /** + * Google voice, Vietnamese (Vietnam) male. + * @const + */ + const vi_VN_Wavenet_B: Voice; + /** + * Google voice, Vietnamese (Vietnam) female (second voice). + * @const + */ + const vi_VN_Wavenet_C: Voice; + /** + * Google voice, Vietnamese (Vietnam) male (second voice). + * @const + */ + const vi_VN_Wavenet_D: Voice; + /** + * Google voice, Cantonese (Hong Kong SAR China) female. + * @const + */ + const yue_HK_Standard_A: Voice; + /** + * Google voice, Cantonese (Hong Kong SAR China) male. + * @const + */ + const yue_HK_Standard_B: Voice; + /** + * Google voice, Cantonese (Hong Kong SAR China) female (second voice). + * @const + */ + const yue_HK_Standard_C: Voice; + /** + * Google voice, Cantonese (Hong Kong SAR China) male (second voice). + * @const + */ + const yue_HK_Standard_D: Voice; + } +} + +declare namespace VoiceList { + /** + * List of available IBM TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace IBM {} +} + +declare namespace VoiceList { + namespace IBM { + /** + * List of available premium IBM TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods that sound more natural due to advanced synthesis technology. + * @namespace + */ + namespace Neural { + /** + * IBM voice, Dutch(Netherlands) female, Merel. + * @const + */ + const nl_NL_Merel: Voice; + + /** + * IBM voice, English(United Kingdom) female, Charlotte. + * @const + */ + const en_GB_Charlotte: Voice; + + /** + * IBM voice, English(United Kingdom) female, James. + * @const + */ + const en_GB_James: Voice; + + /** + * IBM voice, English(United Kingdom) female, Kate. + * @const + */ + const en_GB_Kate: Voice; + + /** + * IBM voice, English(United States) female, Allison. + * @const + */ + const en_US_Allison: Voice; + + /** + * IBM voice, English(United States) female, Emily. + * @const + */ + const en_US_Emily: Voice; + + /** + * IBM voice, English(United States) male, Henry. + * @const + */ + const en_US_Henry: Voice; + + /** + * IBM voice, English(United States) male, Kevin. + * @const + */ + const en_US_Kevin: Voice; + + /** + * IBM voice, English(United States) female, Lisa. + * @const + */ + const en_US_Lisa: Voice; + + /** + * IBM voice, English(United States) male, Michael. + * @const + */ + const en_US_Michael: Voice; + + /** + * IBM voice, English(United States) female, Olivia. + * @const + */ + const en_US_Olivia: Voice; + + /** + * IBM voice, French(Canadian) female, Louise. + * @const + */ + const fr_CA_Louise: Voice; + + /** + * IBM voice, French female, Nicolas. + * @const + */ + const fr_FR_Nicolas: Voice; + + /** + * IBM voice, French female, Renee. + * @const + */ + const fr_FR_Renee: Voice; + + /** + * IBM voice, German female, Birgit. + * @const + */ + const de_DE_Birgit: Voice; + + /** + * IBM voice, German male, Dieter. + * @const + */ + const de_DE_Dieter: Voice; + + /** + * IBM voice, German female, Erika. + * @const + */ + const de_DE_Erika: Voice; + + /** + * IBM voice, Italian female, Francesca. + * @const + */ + const it_IT_Francesca: Voice; + + /** + * IBM voice, Japanese female, Emi. + * @const + */ + const ja_JP_Emi: Voice; + + /** + * IBM voice, Korean female, Jin. + * @const + */ + const ko_KR_Jin: Voice; + + /** + * IBM voice, Brazilian Portuguese female, Isabela. + * @const + */ + const pt_BR_Isabela: Voice; + + /** + * IBM voice, Spanish(Castilian) male, Enrique. + * @const + */ + const es_ES_Enrique: Voice; + + /** + * IBM voice, Spanish(Castilian) female, Laura. + * @const + */ + const es_ES_Laura: Voice; + + /** + * IBM voice, Spanish(Latin American) female, Sofia. + * @const + */ + const es_LA_Sofia: Voice; + + /** + * IBM voice, Spanish(North American) female, Sofia. + * @const + */ + const es_US_Sofia: Voice; + + /** + * IBM voice, English(Australian) female, Heidi. + * @const + */ + const en_AU_Heidi_Expressive: Voice; + + /** + * IBM voice, English(Australian) female, Jack. + * @const + */ + const en_AU_Jack_Expressive: Voice; + + /** + * IBM voice, English(United States) female, Allison. + * @const + */ + const en_US_Allison_Expressive: Voice; + + /** + * IBM voice, English(United States) female, Emma. + * @const + */ + const en_US_Emma_Expressive: Voice; + + /** + * IBM voice, English(United States) female, Lisa. + * @const + */ + const en_US_Lisa_Expressive: Voice; + + /** + * IBM voice, English(United States) female, Michael. + * @const + */ + const en_US_Michael_Expressive: Voice; + + /** + * IBM voice, Spanish(Latin American) female, Daniela. + * @const + */ + const es_LA_DanielaExpressive: Voice; + } + } +} + +declare namespace VoiceList { + /** + * List of available Microsoft TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace Microsoft {} +} + +declare namespace VoiceList { + namespace Microsoft { + /** + * List of available premium Microsoft TTS voices that sound more natural due to advanced synthesis technology. + * @namespace + */ + namespace Neural { + /** + * Neural Microsoft voice, Afrikaans (South Africa) Female, AdriNeural. + * @const + */ + const af_ZA_AdriNeural: Voice; + /** + * Neural Microsoft voice, Afrikaans (South Africa) Male, WillemNeural. + * @const + */ + const af_ZA_WillemNeural: Voice; + /** + * Neural Microsoft voice, Amharic (Ethiopia) Female, MekdesNeural. + * @const + */ + const am_ET_MekdesNeural: Voice; + /** + * Neural Microsoft voice, Amharic (Ethiopia) Male, AmehaNeural. + * @const + */ + const am_ET_AmehaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (United Arab Emirates) Female, FatimaNeural. + * @const + */ + const ar_AE_FatimaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (United Arab Emirates) Male, HamdanNeural. + * @const + */ + const ar_AE_HamdanNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Bahrain) Female, LailaNeural. + * @const + */ + const ar_BH_LailaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Bahrain) Male, AliNeural. + * @const + */ + const ar_BH_AliNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Algeria) Female, AminaNeural. + * @const + */ + const ar_DZ_AminaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Algeria) Male, IsmaelNeural. + * @const + */ + const ar_DZ_IsmaelNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Egypt) Female, SalmaNeural. + * @const + */ + const ar_EG_SalmaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Egypt) Male, ShakirNeural. + * @const + */ + const ar_EG_ShakirNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Iraq) Female, RanaNeural. + * @const + */ + const ar_IQ_RanaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Iraq) Male, BasselNeural. + * @const + */ + const ar_IQ_BasselNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Jordan) Female, SanaNeural. + * @const + */ + const ar_JO_SanaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Jordan) Male, TaimNeural. + * @const + */ + const ar_JO_TaimNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Kuwait) Female, NouraNeural. + * @const + */ + const ar_KW_NouraNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Kuwait) Male, FahedNeural. + * @const + */ + const ar_KW_FahedNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Lebanon) Female, LaylaNeural. + * @const + */ + const ar_LB_LaylaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Lebanon) Male, RamiNeural. + * @const + */ + const ar_LB_RamiNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Libya) Female, ImanNeural. + * @const + */ + const ar_LY_ImanNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Libya) Male, OmarNeural. + * @const + */ + const ar_LY_OmarNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Morocco) Female, MounaNeural. + * @const + */ + const ar_MA_MounaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Morocco) Male, JamalNeural. + * @const + */ + const ar_MA_JamalNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Oman) Female, AyshaNeural. + * @const + */ + const ar_OM_AyshaNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Oman) Male, AbdullahNeural. + * @const + */ + const ar_OM_AbdullahNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Qatar) Female, AmalNeural. + * @const + */ + const ar_QA_AmalNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Qatar) Male, MoazNeural. + * @const + */ + const ar_QA_MoazNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Saudi Arabia) Female, ZariyahNeural. + * @const + */ + const ar_SA_ZariyahNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Saudi Arabia) Male, HamedNeural. + * @const + */ + const ar_SA_HamedNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Syria) Female, AmanyNeural. + * @const + */ + const ar_SY_AmanyNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Syria) Male, LaithNeural. + * @const + */ + const ar_SY_LaithNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Tunisia) Female, ReemNeural. + * @const + */ + const ar_TN_ReemNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Tunisia) Male, HediNeural. + * @const + */ + const ar_TN_HediNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Yemen) Female, MaryamNeural. + * @const + */ + const ar_YE_MaryamNeural: Voice; + /** + * Neural Microsoft voice, Arabic (Yemen) Male, SalehNeural. + * @const + */ + const ar_YE_SalehNeural: Voice; + /** + * Neural Microsoft voice, Azerbaijani (Latin, Azerbaijan) Female, BanuNeural. + * @const + */ + const az_AZ_BanuNeural: Voice; + /** + * Neural Microsoft voice, Azerbaijani (Latin, Azerbaijan) Male, BabekNeural. + * @const + */ + const az_AZ_BabekNeural: Voice; + /** + * Neural Microsoft voice, Bulgarian (Bulgaria) Female, KalinaNeural. + * @const + */ + const bg_BG_KalinaNeural: Voice; + /** + * Neural Microsoft voice, Bulgarian (Bulgaria) Male, BorislavNeural. + * @const + */ + const bg_BG_BorislavNeural: Voice; + /** + * Neural Microsoft voice, Bangla (Bangladesh) Female, NabanitaNeural. + * @const + */ + const bn_BD_NabanitaNeural: Voice; + /** + * Neural Microsoft voice, Bangla (Bangladesh) Male, PradeepNeural. + * @const + */ + const bn_BD_PradeepNeural: Voice; + /** + * Neural Microsoft voice, Bengali (India) Female, TanishaaNeural. + * @const + */ + const bn_IN_TanishaaNeural: Voice; + /** + * Neural Microsoft voice, Bengali (India) Male, BashkarNeural. + * @const + */ + const bn_IN_BashkarNeural: Voice; + /** + * Neural Microsoft voice, Bosnian (Bosnia and Herzegovina) Female, VesnaNeural. + * @const + */ + const bs_BA_VesnaNeural: Voice; + /** + * Neural Microsoft voice, Bosnian (Bosnia and Herzegovina) Male, GoranNeural. + * @const + */ + const bs_BA_GoranNeural: Voice; + /** + * Neural Microsoft voice, Catalan (Spain) Female, JoanaNeural. + * @const + */ + const ca_ES_JoanaNeural: Voice; + /** + * Neural Microsoft voice, Catalan (Spain) Male, EnricNeural. + * @const + */ + const ca_ES_EnricNeural: Voice; + /** + * Neural Microsoft voice, Catalan (Spain) Female, AlbaNeural. + * @const + */ + const ca_ES_AlbaNeural: Voice; + /** + * Neural Microsoft voice, Czech (Czechia) Female, VlastaNeural. + * @const + */ + const cs_CZ_VlastaNeural: Voice; + /** + * Neural Microsoft voice, Czech (Czechia) Male, AntoninNeural. + * @const + */ + const cs_CZ_AntoninNeural: Voice; + /** + * Neural Microsoft voice, Welsh (United Kingdom) Female, NiaNeural. + * @const + */ + const cy_GB_NiaNeural: Voice; + /** + * Neural Microsoft voice, Welsh (United Kingdom) Male, AledNeural. + * @const + */ + const cy_GB_AledNeural: Voice; + /** + * Neural Microsoft voice, Danish (Denmark) Female, ChristelNeural. + * @const + */ + const da_DK_ChristelNeural: Voice; + /** + * Neural Microsoft voice, Danish (Denmark) Male, JeppeNeural. + * @const + */ + const da_DK_JeppeNeural: Voice; + /** + * Neural Microsoft voice, German (Austria) Female, IngridNeural. + * @const + */ + const de_AT_IngridNeural: Voice; + /** + * Neural Microsoft voice, German (Austria) Male, JonasNeural. + * @const + */ + const de_AT_JonasNeural: Voice; + /** + * Neural Microsoft voice, German (Switzerland) Female, LeniNeural. + * @const + */ + const de_CH_LeniNeural: Voice; + /** + * Neural Microsoft voice, German (Switzerland) Male, JanNeural. + * @const + */ + const de_CH_JanNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, KatjaNeural. + * @const + */ + const de_DE_KatjaNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, ConradNeural. + * @const + */ + const de_DE_ConradNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, AmalaNeural. + * @const + */ + const de_DE_AmalaNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, BerndNeural. + * @const + */ + const de_DE_BerndNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, ChristophNeural. + * @const + */ + const de_DE_ChristophNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, ElkeNeural. + * @const + */ + const de_DE_ElkeNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, FlorianMultilingualNeural. + * @const + */ + const de_DE_FlorianMultilingualNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, GiselaNeural. + * @const + */ + const de_DE_GiselaNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, KasperNeural. + * @const + */ + const de_DE_KasperNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, KillianNeural. + * @const + */ + const de_DE_KillianNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, KlarissaNeural. + * @const + */ + const de_DE_KlarissaNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, KlausNeural. + * @const + */ + const de_DE_KlausNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, LouisaNeural. + * @const + */ + const de_DE_LouisaNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, MajaNeural. + * @const + */ + const de_DE_MajaNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Male, RalfNeural. + * @const + */ + const de_DE_RalfNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, SeraphinaMultilingualNeural. + * @const + */ + const de_DE_SeraphinaMultilingualNeural: Voice; + /** + * Neural Microsoft voice, German (Germany) Female, TanjaNeural. + * @const + */ + const de_DE_TanjaNeural: Voice; + /** + * Neural Microsoft voice, Greek (Greece) Female, AthinaNeural. + * @const + */ + const el_GR_AthinaNeural: Voice; + /** + * Neural Microsoft voice, Greek (Greece) Male, NestorasNeural. + * @const + */ + const el_GR_NestorasNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, NatashaNeural. + * @const + */ + const en_AU_NatashaNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Male, WilliamNeural. + * @const + */ + const en_AU_WilliamNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, AnnetteNeural. + * @const + */ + const en_AU_AnnetteNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, CarlyNeural. + * @const + */ + const en_AU_CarlyNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Male, DarrenNeural. + * @const + */ + const en_AU_DarrenNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Male, DuncanNeural. + * @const + */ + const en_AU_DuncanNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, ElsieNeural. + * @const + */ + const en_AU_ElsieNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, FreyaNeural. + * @const + */ + const en_AU_FreyaNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, JoanneNeural. + * @const + */ + const en_AU_JoanneNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Male, KenNeural. + * @const + */ + const en_AU_KenNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, KimNeural. + * @const + */ + const en_AU_KimNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Male, NeilNeural. + * @const + */ + const en_AU_NeilNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Male, TimNeural. + * @const + */ + const en_AU_TimNeural: Voice; + /** + * Neural Microsoft voice, English (Australia) Female, TinaNeural. + * @const + */ + const en_AU_TinaNeural: Voice; + /** + * Neural Microsoft voice, English (Canada) Female, ClaraNeural. + * @const + */ + const en_CA_ClaraNeural: Voice; + /** + * Neural Microsoft voice, English (Canada) Male, LiamNeural. + * @const + */ + const en_CA_LiamNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Female, SoniaNeural. + * @const + */ + const en_GB_SoniaNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Male, RyanNeural. + * @const + */ + const en_GB_RyanNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Female, LibbyNeural. + * @const + */ + const en_GB_LibbyNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Female, AbbiNeural. + * @const + */ + const en_GB_AbbiNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Male, AlfieNeural. + * @const + */ + const en_GB_AlfieNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Female, BellaNeural. + * @const + */ + const en_GB_BellaNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Male, ElliotNeural. + * @const + */ + const en_GB_ElliotNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Male, EthanNeural. + * @const + */ + const en_GB_EthanNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Female, HollieNeural. + * @const + */ + const en_GB_HollieNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Female, MaisieNeural. + * @const + */ + const en_GB_MaisieNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Male, NoahNeural. + * @const + */ + const en_GB_NoahNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Male, OliverNeural. + * @const + */ + const en_GB_OliverNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Female, OliviaNeural. + * @const + */ + const en_GB_OliviaNeural: Voice; + /** + * Neural Microsoft voice, English (United Kingdom) Male, ThomasNeural. + * @const + */ + const en_GB_ThomasNeural: Voice; + /** + * Neural Microsoft voice, English (Hong Kong SAR) Female, YanNeural. + * @const + */ + const en_HK_YanNeural: Voice; + /** + * Neural Microsoft voice, English (Hong Kong SAR) Male, SamNeural. + * @const + */ + const en_HK_SamNeural: Voice; + /** + * Neural Microsoft voice, English (Ireland) Female, EmilyNeural. + * @const + */ + const en_IE_EmilyNeural: Voice; + /** + * Neural Microsoft voice, English (Ireland) Male, ConnorNeural. + * @const + */ + const en_IE_ConnorNeural: Voice; + /** + * Neural Microsoft voice, English (India) Female, NeerjaNeural. + * @const + */ + const en_IN_NeerjaNeural: Voice; + /** + * Neural Microsoft voice, English (India) Male, PrabhatNeural. + * @const + */ + const en_IN_PrabhatNeural: Voice; + /** + * Neural Microsoft voice, English (Kenya) Female, AsiliaNeural. + * @const + */ + const en_KE_AsiliaNeural: Voice; + /** + * Neural Microsoft voice, English (Kenya) Male, ChilembaNeural. + * @const + */ + const en_KE_ChilembaNeural: Voice; + /** + * Neural Microsoft voice, English (Nigeria) Female, EzinneNeural. + * @const + */ + const en_NG_EzinneNeural: Voice; + /** + * Neural Microsoft voice, English (Nigeria) Male, AbeoNeural. + * @const + */ + const en_NG_AbeoNeural: Voice; + /** + * Neural Microsoft voice, English (New Zealand) Female, MollyNeural. + * @const + */ + const en_NZ_MollyNeural: Voice; + /** + * Neural Microsoft voice, English (New Zealand) Male, MitchellNeural. + * @const + */ + const en_NZ_MitchellNeural: Voice; + /** + * Neural Microsoft voice, English (Philippines) Female, RosaNeural. + * @const + */ + const en_PH_RosaNeural: Voice; + /** + * Neural Microsoft voice, English (Philippines) Male, JamesNeural. + * @const + */ + const en_PH_JamesNeural: Voice; + /** + * Neural Microsoft voice, English (Singapore) Female, LunaNeural. + * @const + */ + const en_SG_LunaNeural: Voice; + /** + * Neural Microsoft voice, English (Singapore) Male, WayneNeural. + * @const + */ + const en_SG_WayneNeural: Voice; + /** + * Neural Microsoft voice, English (Tanzania) Female, ImaniNeural. + * @const + */ + const en_TZ_ImaniNeural: Voice; + /** + * Neural Microsoft voice, English (Tanzania) Male, ElimuNeural. + * @const + */ + const en_TZ_ElimuNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, AvaMultilingualNeural. + * @const + */ + const en_US_AvaMultilingualNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, AndrewMultilingualNeural. + * @const + */ + const en_US_AndrewMultilingualNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, EmmaMultilingualNeural. + * @const + */ + const en_US_EmmaMultilingualNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, BrianMultilingualNeural. + * @const + */ + const en_US_BrianMultilingualNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, AvaNeural. + * @const + */ + const en_US_AvaNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, AndrewNeural. + * @const + */ + const en_US_AndrewNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, EmmaNeural. + * @const + */ + const en_US_EmmaNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, BrianNeural. + * @const + */ + const en_US_BrianNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, JennyNeural. + * @const + */ + const en_US_JennyNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, GuyNeural. + * @const + */ + const en_US_GuyNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, AriaNeural. + * @const + */ + const en_US_AriaNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, DavisNeural. + * @const + */ + const en_US_DavisNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, JaneNeural. + * @const + */ + const en_US_JaneNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, JasonNeural. + * @const + */ + const en_US_JasonNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, SaraNeural. + * @const + */ + const en_US_SaraNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, TonyNeural. + * @const + */ + const en_US_TonyNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, NancyNeural. + * @const + */ + const en_US_NancyNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, AmberNeural. + * @const + */ + const en_US_AmberNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, AnaNeural. + * @const + */ + const en_US_AnaNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, AshleyNeural. + * @const + */ + const en_US_AshleyNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, BrandonNeural. + * @const + */ + const en_US_BrandonNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, ChristopherNeural. + * @const + */ + const en_US_ChristopherNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, CoraNeural. + * @const + */ + const en_US_CoraNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, ElizabethNeural. + * @const + */ + const en_US_ElizabethNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, EricNeural. + * @const + */ + const en_US_EricNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, JacobNeural. + * @const + */ + const en_US_JacobNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, JennyMultilingualNeural. + * @const + */ + const en_US_JennyMultilingualNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, MichelleNeural. + * @const + */ + const en_US_MichelleNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Female, MonicaNeural. + * @const + */ + const en_US_MonicaNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, RogerNeural. + * @const + */ + const en_US_RogerNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, RyanMultilingualNeural. + * @const + */ + const en_US_RyanMultilingualNeural: Voice; + /** + * Neural Microsoft voice, English (United States) Male, SteffanNeural. + * @const + */ + const en_US_SteffanNeural: Voice; + /** + * Neural Microsoft voice, English (South Africa) Female, LeahNeural. + * @const + */ + const en_ZA_LeahNeural: Voice; + /** + * Neural Microsoft voice, English (South Africa) Male, LukeNeural. + * @const + */ + const en_ZA_LukeNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Argentina) Female, ElenaNeural. + * @const + */ + const es_AR_ElenaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Argentina) Male, TomasNeural. + * @const + */ + const es_AR_TomasNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Bolivia) Female, SofiaNeural. + * @const + */ + const es_BO_SofiaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Bolivia) Male, MarceloNeural. + * @const + */ + const es_BO_MarceloNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Chile) Female, CatalinaNeural. + * @const + */ + const es_CL_CatalinaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Chile) Male, LorenzoNeural. + * @const + */ + const es_CL_LorenzoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Colombia) Female, SalomeNeural. + * @const + */ + const es_CO_SalomeNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Colombia) Male, GonzaloNeural. + * @const + */ + const es_CO_GonzaloNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Costa Rica) Female, MariaNeural. + * @const + */ + const es_CR_MariaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Costa Rica) Male, JuanNeural. + * @const + */ + const es_CR_JuanNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Cuba) Female, BelkysNeural. + * @const + */ + const es_CU_BelkysNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Cuba) Male, ManuelNeural. + * @const + */ + const es_CU_ManuelNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Dominican Republic) Female, RamonaNeural. + * @const + */ + const es_DO_RamonaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Dominican Republic) Male, EmilioNeural. + * @const + */ + const es_DO_EmilioNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Ecuador) Female, AndreaNeural. + * @const + */ + const es_EC_AndreaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Ecuador) Male, LuisNeural. + * @const + */ + const es_EC_LuisNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, ElviraNeural. + * @const + */ + const es_ES_ElviraNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Male, AlvaroNeural. + * @const + */ + const es_ES_AlvaroNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, AbrilNeural. + * @const + */ + const es_ES_AbrilNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Male, ArnauNeural. + * @const + */ + const es_ES_ArnauNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Male, DarioNeural. + * @const + */ + const es_ES_DarioNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Male, EliasNeural. + * @const + */ + const es_ES_EliasNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, EstrellaNeural. + * @const + */ + const es_ES_EstrellaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, IreneNeural. + * @const + */ + const es_ES_IreneNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, LaiaNeural. + * @const + */ + const es_ES_LaiaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, LiaNeural. + * @const + */ + const es_ES_LiaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Male, NilNeural. + * @const + */ + const es_ES_NilNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Male, SaulNeural. + * @const + */ + const es_ES_SaulNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Male, TeoNeural. + * @const + */ + const es_ES_TeoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, TrianaNeural. + * @const + */ + const es_ES_TrianaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, VeraNeural. + * @const + */ + const es_ES_VeraNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Spain) Female, XimenaNeural. + * @const + */ + const es_ES_XimenaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Equatorial Guinea) Female, TeresaNeural. + * @const + */ + const es_GQ_TeresaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Equatorial Guinea) Male, JavierNeural. + * @const + */ + const es_GQ_JavierNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Guatemala) Female, MartaNeural. + * @const + */ + const es_GT_MartaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Guatemala) Male, AndresNeural. + * @const + */ + const es_GT_AndresNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Honduras) Female, KarlaNeural. + * @const + */ + const es_HN_KarlaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Honduras) Male, CarlosNeural. + * @const + */ + const es_HN_CarlosNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, DaliaNeural. + * @const + */ + const es_MX_DaliaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Male, JorgeNeural. + * @const + */ + const es_MX_JorgeNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, BeatrizNeural. + * @const + */ + const es_MX_BeatrizNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, CandelaNeural. + * @const + */ + const es_MX_CandelaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, CarlotaNeural. + * @const + */ + const es_MX_CarlotaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Male, CecilioNeural. + * @const + */ + const es_MX_CecilioNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Male, GerardoNeural. + * @const + */ + const es_MX_GerardoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, LarissaNeural. + * @const + */ + const es_MX_LarissaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Male, LibertoNeural. + * @const + */ + const es_MX_LibertoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Male, LucianoNeural. + * @const + */ + const es_MX_LucianoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, MarinaNeural. + * @const + */ + const es_MX_MarinaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, NuriaNeural. + * @const + */ + const es_MX_NuriaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Male, PelayoNeural. + * @const + */ + const es_MX_PelayoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Female, RenataNeural. + * @const + */ + const es_MX_RenataNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Mexico) Male, YagoNeural. + * @const + */ + const es_MX_YagoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Nicaragua) Female, YolandaNeural. + * @const + */ + const es_NI_YolandaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Nicaragua) Male, FedericoNeural. + * @const + */ + const es_NI_FedericoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Panama) Female, MargaritaNeural. + * @const + */ + const es_PA_MargaritaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Panama) Male, RobertoNeural. + * @const + */ + const es_PA_RobertoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Peru) Female, CamilaNeural. + * @const + */ + const es_PE_CamilaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Peru) Male, AlexNeural. + * @const + */ + const es_PE_AlexNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Puerto Rico) Female, KarinaNeural. + * @const + */ + const es_PR_KarinaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Puerto Rico) Male, VictorNeural. + * @const + */ + const es_PR_VictorNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Paraguay) Female, TaniaNeural. + * @const + */ + const es_PY_TaniaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Paraguay) Male, MarioNeural. + * @const + */ + const es_PY_MarioNeural: Voice; + /** + * Neural Microsoft voice, Spanish (El Salvador) Female, LorenaNeural. + * @const + */ + const es_SV_LorenaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (El Salvador) Male, RodrigoNeural. + * @const + */ + const es_SV_RodrigoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (United States) Female, PalomaNeural. + * @const + */ + const es_US_PalomaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (United States) Male, AlonsoNeural. + * @const + */ + const es_US_AlonsoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Uruguay) Female, ValentinaNeural. + * @const + */ + const es_UY_ValentinaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Uruguay) Male, MateoNeural. + * @const + */ + const es_UY_MateoNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Venezuela) Female, PaolaNeural. + * @const + */ + const es_VE_PaolaNeural: Voice; + /** + * Neural Microsoft voice, Spanish (Venezuela) Male, SebastianNeural. + * @const + */ + const es_VE_SebastianNeural: Voice; + /** + * Neural Microsoft voice, Estonian (Estonia) Female, AnuNeural. + * @const + */ + const et_EE_AnuNeural: Voice; + /** + * Neural Microsoft voice, Estonian (Estonia) Male, KertNeural. + * @const + */ + const et_EE_KertNeural: Voice; + /** + * Neural Microsoft voice, Basque Female, AinhoaNeural. + * @const + */ + const eu_ES_AinhoaNeural: Voice; + /** + * Neural Microsoft voice, Basque Male, AnderNeural. + * @const + */ + const eu_ES_AnderNeural: Voice; + /** + * Neural Microsoft voice, Persian (Iran) Female, DilaraNeural. + * @const + */ + const fa_IR_DilaraNeural: Voice; + /** + * Neural Microsoft voice, Persian (Iran) Male, FaridNeural. + * @const + */ + const fa_IR_FaridNeural: Voice; + /** + * Neural Microsoft voice, Finnish (Finland) Female, SelmaNeural. + * @const + */ + const fi_FI_SelmaNeural: Voice; + /** + * Neural Microsoft voice, Finnish (Finland) Male, HarriNeural. + * @const + */ + const fi_FI_HarriNeural: Voice; + /** + * Neural Microsoft voice, Finnish (Finland) Female, NooraNeural. + * @const + */ + const fi_FI_NooraNeural: Voice; + /** + * Neural Microsoft voice, Filipino (Philippines) Female, BlessicaNeural. + * @const + */ + const fil_PH_BlessicaNeural: Voice; + /** + * Neural Microsoft voice, Filipino (Philippines) Male, AngeloNeural. + * @const + */ + const fil_PH_AngeloNeural: Voice; + /** + * Neural Microsoft voice, French (Belgium) Female, CharlineNeural. + * @const + */ + const fr_BE_CharlineNeural: Voice; + /** + * Neural Microsoft voice, French (Belgium) Male, GerardNeural. + * @const + */ + const fr_BE_GerardNeural: Voice; + /** + * Neural Microsoft voice, French (Canada) Female, SylvieNeural. + * @const + */ + const fr_CA_SylvieNeural: Voice; + /** + * Neural Microsoft voice, French (Canada) Male, JeanNeural. + * @const + */ + const fr_CA_JeanNeural: Voice; + /** + * Neural Microsoft voice, French (Canada) Male, AntoineNeural. + * @const + */ + const fr_CA_AntoineNeural: Voice; + /** + * Neural Microsoft voice, French (Canada) Male, ThierryNeural. + * @const + */ + const fr_CA_ThierryNeural: Voice; + /** + * Neural Microsoft voice, French (Switzerland) Female, ArianeNeural. + * @const + */ + const fr_CH_ArianeNeural: Voice; + /** + * Neural Microsoft voice, French (Switzerland) Male, FabriceNeural. + * @const + */ + const fr_CH_FabriceNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, DeniseNeural. + * @const + */ + const fr_FR_DeniseNeural: Voice; + /** + * Neural Microsoft voice, French (France) Male, HenriNeural. + * @const + */ + const fr_FR_HenriNeural: Voice; + /** + * Neural Microsoft voice, French (France) Male, AlainNeural. + * @const + */ + const fr_FR_AlainNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, BrigitteNeural. + * @const + */ + const fr_FR_BrigitteNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, CelesteNeural. + * @const + */ + const fr_FR_CelesteNeural: Voice; + /** + * Neural Microsoft voice, French (France) Male, ClaudeNeural. + * @const + */ + const fr_FR_ClaudeNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, CoralieNeural. + * @const + */ + const fr_FR_CoralieNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, EloiseNeural. + * @const + */ + const fr_FR_EloiseNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, JacquelineNeural. + * @const + */ + const fr_FR_JacquelineNeural: Voice; + /** + * Neural Microsoft voice, French (France) Male, JeromeNeural. + * @const + */ + const fr_FR_JeromeNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, JosephineNeural. + * @const + */ + const fr_FR_JosephineNeural: Voice; + /** + * Neural Microsoft voice, French (France) Male, MauriceNeural. + * @const + */ + const fr_FR_MauriceNeural: Voice; + /** + * Neural Microsoft voice, French (France) Male, RemyMultilingualNeural. + * @const + */ + const fr_FR_RemyMultilingualNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, VivienneMultilingualNeural. + * @const + */ + const fr_FR_VivienneMultilingualNeural: Voice; + /** + * Neural Microsoft voice, French (France) Male, YvesNeural. + * @const + */ + const fr_FR_YvesNeural: Voice; + /** + * Neural Microsoft voice, French (France) Female, YvetteNeural. + * @const + */ + const fr_FR_YvetteNeural: Voice; + /** + * Neural Microsoft voice, Irish (Ireland) Female, OrlaNeural. + * @const + */ + const ga_IE_OrlaNeural: Voice; + /** + * Neural Microsoft voice, Irish (Ireland) Male, ColmNeural. + * @const + */ + const ga_IE_ColmNeural: Voice; + /** + * Neural Microsoft voice, Galician Female, SabelaNeural. + * @const + */ + const gl_ES_SabelaNeural: Voice; + /** + * Neural Microsoft voice, Galician Male, RoiNeural. + * @const + */ + const gl_ES_RoiNeural: Voice; + /** + * Neural Microsoft voice, Gujarati (India) Female, DhwaniNeural. + * @const + */ + const gu_IN_DhwaniNeural: Voice; + /** + * Neural Microsoft voice, Gujarati (India) Male, NiranjanNeural. + * @const + */ + const gu_IN_NiranjanNeural: Voice; + /** + * Neural Microsoft voice, Hebrew (Israel) Female, HilaNeural. + * @const + */ + const he_IL_HilaNeural: Voice; + /** + * Neural Microsoft voice, Hebrew (Israel) Male, AvriNeural. + * @const + */ + const he_IL_AvriNeural: Voice; + /** + * Neural Microsoft voice, Hindi (India) Female, SwaraNeural. + * @const + */ + const hi_IN_SwaraNeural: Voice; + /** + * Neural Microsoft voice, Hindi (India) Male, MadhurNeural. + * @const + */ + const hi_IN_MadhurNeural: Voice; + /** + * Neural Microsoft voice, Croatian (Croatia) Female, GabrijelaNeural. + * @const + */ + const hr_HR_GabrijelaNeural: Voice; + /** + * Neural Microsoft voice, Croatian (Croatia) Male, SreckoNeural. + * @const + */ + const hr_HR_SreckoNeural: Voice; + /** + * Neural Microsoft voice, Hungarian (Hungary) Female, NoemiNeural. + * @const + */ + const hu_HU_NoemiNeural: Voice; + /** + * Neural Microsoft voice, Hungarian (Hungary) Male, TamasNeural. + * @const + */ + const hu_HU_TamasNeural: Voice; + /** + * Neural Microsoft voice, Armenian (Armenia) Female, AnahitNeural. + * @const + */ + const hy_AM_AnahitNeural: Voice; + /** + * Neural Microsoft voice, Armenian (Armenia) Male, HaykNeural. + * @const + */ + const hy_AM_HaykNeural: Voice; + /** + * Neural Microsoft voice, Indonesian (Indonesia) Female, GadisNeural. + * @const + */ + const id_ID_GadisNeural: Voice; + /** + * Neural Microsoft voice, Indonesian (Indonesia) Male, ArdiNeural. + * @const + */ + const id_ID_ArdiNeural: Voice; + /** + * Neural Microsoft voice, Icelandic (Iceland) Female, GudrunNeural. + * @const + */ + const is_IS_GudrunNeural: Voice; + /** + * Neural Microsoft voice, Icelandic (Iceland) Male, GunnarNeural. + * @const + */ + const is_IS_GunnarNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, ElsaNeural. + * @const + */ + const it_IT_ElsaNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, IsabellaNeural. + * @const + */ + const it_IT_IsabellaNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, DiegoNeural. + * @const + */ + const it_IT_DiegoNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, BenignoNeural. + * @const + */ + const it_IT_BenignoNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, CalimeroNeural. + * @const + */ + const it_IT_CalimeroNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, CataldoNeural. + * @const + */ + const it_IT_CataldoNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, FabiolaNeural. + * @const + */ + const it_IT_FabiolaNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, FiammaNeural. + * @const + */ + const it_IT_FiammaNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, GianniNeural. + * @const + */ + const it_IT_GianniNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, GiuseppeNeural. + * @const + */ + const it_IT_GiuseppeNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, ImeldaNeural. + * @const + */ + const it_IT_ImeldaNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, IrmaNeural. + * @const + */ + const it_IT_IrmaNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, LisandroNeural. + * @const + */ + const it_IT_LisandroNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, PalmiraNeural. + * @const + */ + const it_IT_PalmiraNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Female, PierinaNeural. + * @const + */ + const it_IT_PierinaNeural: Voice; + /** + * Neural Microsoft voice, Italian (Italy) Male, RinaldoNeural. + * @const + */ + const it_IT_RinaldoNeural: Voice; + /** + * Neural Microsoft voice, Japanese (Japan) Female, NanamiNeural. + * @const + */ + const ja_JP_NanamiNeural: Voice; + /** + * Neural Microsoft voice, Japanese (Japan) Male, KeitaNeural. + * @const + */ + const ja_JP_KeitaNeural: Voice; + /** + * Neural Microsoft voice, Japanese (Japan) Female, AoiNeural. + * @const + */ + const ja_JP_AoiNeural: Voice; + /** + * Neural Microsoft voice, Japanese (Japan) Male, DaichiNeural. + * @const + */ + const ja_JP_DaichiNeural: Voice; + /** + * Neural Microsoft voice, Japanese (Japan) Female, MayuNeural. + * @const + */ + const ja_JP_MayuNeural: Voice; + /** + * Neural Microsoft voice, Japanese (Japan) Male, NaokiNeural. + * @const + */ + const ja_JP_NaokiNeural: Voice; + /** + * Neural Microsoft voice, Japanese (Japan) Female, ShioriNeural. + * @const + */ + const ja_JP_ShioriNeural: Voice; + /** + * Neural Microsoft voice, Javanese (Latin, Indonesia) Female, SitiNeural. + * @const + */ + const jv_ID_SitiNeural: Voice; + /** + * Neural Microsoft voice, Javanese (Latin, Indonesia) Male, DimasNeural. + * @const + */ + const jv_ID_DimasNeural: Voice; + /** + * Neural Microsoft voice, Georgian (Georgia) Female, EkaNeural. + * @const + */ + const ka_GE_EkaNeural: Voice; + /** + * Neural Microsoft voice, Georgian (Georgia) Male, GiorgiNeural. + * @const + */ + const ka_GE_GiorgiNeural: Voice; + /** + * Neural Microsoft voice, Kazakh (Kazakhstan) Female, AigulNeural. + * @const + */ + const kk_KZ_AigulNeural: Voice; + /** + * Neural Microsoft voice, Kazakh (Kazakhstan) Male, DauletNeural. + * @const + */ + const kk_KZ_DauletNeural: Voice; + /** + * Neural Microsoft voice, Khmer (Cambodia) Female, SreymomNeural. + * @const + */ + const km_KH_SreymomNeural: Voice; + /** + * Neural Microsoft voice, Khmer (Cambodia) Male, PisethNeural. + * @const + */ + const km_KH_PisethNeural: Voice; + /** + * Neural Microsoft voice, Kannada (India) Female, SapnaNeural. + * @const + */ + const kn_IN_SapnaNeural: Voice; + /** + * Neural Microsoft voice, Kannada (India) Male, GaganNeural. + * @const + */ + const kn_IN_GaganNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Female, SunHiNeural. + * @const + */ + const ko_KR_SunHiNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Male, InJoonNeural. + * @const + */ + const ko_KR_InJoonNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Male, BongJinNeural. + * @const + */ + const ko_KR_BongJinNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Male, GookMinNeural. + * @const + */ + const ko_KR_GookMinNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Male, HyunsuNeural. + * @const + */ + const ko_KR_HyunsuNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Female, JiMinNeural. + * @const + */ + const ko_KR_JiMinNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Female, SeoHyeonNeural. + * @const + */ + const ko_KR_SeoHyeonNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Female, SoonBokNeural. + * @const + */ + const ko_KR_SoonBokNeural: Voice; + /** + * Neural Microsoft voice, Korean (Korea) Female, YuJinNeural. + * @const + */ + const ko_KR_YuJinNeural: Voice; + /** + * Neural Microsoft voice, Lao (Laos) Female, KeomanyNeural. + * @const + */ + const lo_LA_KeomanyNeural: Voice; + /** + * Neural Microsoft voice, Lao (Laos) Male, ChanthavongNeural. + * @const + */ + const lo_LA_ChanthavongNeural: Voice; + /** + * Neural Microsoft voice, Lithuanian (Lithuania) Female, OnaNeural. + * @const + */ + const lt_LT_OnaNeural: Voice; + /** + * Neural Microsoft voice, Lithuanian (Lithuania) Male, LeonasNeural. + * @const + */ + const lt_LT_LeonasNeural: Voice; + /** + * Neural Microsoft voice, Latvian (Latvia) Female, EveritaNeural. + * @const + */ + const lv_LV_EveritaNeural: Voice; + /** + * Neural Microsoft voice, Latvian (Latvia) Male, NilsNeural. + * @const + */ + const lv_LV_NilsNeural: Voice; + /** + * Neural Microsoft voice, Macedonian (North Macedonia) Female, MarijaNeural. + * @const + */ + const mk_MK_MarijaNeural: Voice; + /** + * Neural Microsoft voice, Macedonian (North Macedonia) Male, AleksandarNeural. + * @const + */ + const mk_MK_AleksandarNeural: Voice; + /** + * Neural Microsoft voice, Malayalam (India) Female, SobhanaNeural. + * @const + */ + const ml_IN_SobhanaNeural: Voice; + /** + * Neural Microsoft voice, Malayalam (India) Male, MidhunNeural. + * @const + */ + const ml_IN_MidhunNeural: Voice; + /** + * Neural Microsoft voice, Mongolian (Mongolia) Female, YesuiNeural. + * @const + */ + const mn_MN_YesuiNeural: Voice; + /** + * Neural Microsoft voice, Mongolian (Mongolia) Male, BataaNeural. + * @const + */ + const mn_MN_BataaNeural: Voice; + /** + * Neural Microsoft voice, Marathi (India) Female, AarohiNeural. + * @const + */ + const mr_IN_AarohiNeural: Voice; + /** + * Neural Microsoft voice, Marathi (India) Male, ManoharNeural. + * @const + */ + const mr_IN_ManoharNeural: Voice; + /** + * Neural Microsoft voice, Malay (Malaysia) Female, YasminNeural. + * @const + */ + const ms_MY_YasminNeural: Voice; + /** + * Neural Microsoft voice, Malay (Malaysia) Male, OsmanNeural. + * @const + */ + const ms_MY_OsmanNeural: Voice; + /** + * Neural Microsoft voice, Maltese (Malta) Female, GraceNeural. + * @const + */ + const mt_MT_GraceNeural: Voice; + /** + * Neural Microsoft voice, Maltese (Malta) Male, JosephNeural. + * @const + */ + const mt_MT_JosephNeural: Voice; + /** + * Neural Microsoft voice, Burmese (Myanmar) Female, NilarNeural. + * @const + */ + const my_MM_NilarNeural: Voice; + /** + * Neural Microsoft voice, Burmese (Myanmar) Male, ThihaNeural. + * @const + */ + const my_MM_ThihaNeural: Voice; + /** + * Neural Microsoft voice, Norwegian Bokmål (Norway) Female, PernilleNeural. + * @const + */ + const nb_NO_PernilleNeural: Voice; + /** + * Neural Microsoft voice, Norwegian Bokmål (Norway) Male, FinnNeural. + * @const + */ + const nb_NO_FinnNeural: Voice; + /** + * Neural Microsoft voice, Norwegian Bokmål (Norway) Female, IselinNeural. + * @const + */ + const nb_NO_IselinNeural: Voice; + /** + * Neural Microsoft voice, Nepali (Nepal) Female, HemkalaNeural. + * @const + */ + const ne_NP_HemkalaNeural: Voice; + /** + * Neural Microsoft voice, Nepali (Nepal) Male, SagarNeural. + * @const + */ + const ne_NP_SagarNeural: Voice; + /** + * Neural Microsoft voice, Dutch (Belgium) Female, DenaNeural. + * @const + */ + const nl_BE_DenaNeural: Voice; + /** + * Neural Microsoft voice, Dutch (Belgium) Male, ArnaudNeural. + * @const + */ + const nl_BE_ArnaudNeural: Voice; + /** + * Neural Microsoft voice, Dutch (Netherlands) Female, FennaNeural. + * @const + */ + const nl_NL_FennaNeural: Voice; + /** + * Neural Microsoft voice, Dutch (Netherlands) Male, MaartenNeural. + * @const + */ + const nl_NL_MaartenNeural: Voice; + /** + * Neural Microsoft voice, Dutch (Netherlands) Female, ColetteNeural. + * @const + */ + const nl_NL_ColetteNeural: Voice; + /** + * Neural Microsoft voice, Polish (Poland) Female, AgnieszkaNeural. + * @const + */ + const pl_PL_AgnieszkaNeural: Voice; + /** + * Neural Microsoft voice, Polish (Poland) Male, MarekNeural. + * @const + */ + const pl_PL_MarekNeural: Voice; + /** + * Neural Microsoft voice, Polish (Poland) Female, ZofiaNeural. + * @const + */ + const pl_PL_ZofiaNeural: Voice; + /** + * Neural Microsoft voice, Pashto (Afghanistan) Female, LatifaNeural. + * @const + */ + const ps_AF_LatifaNeural: Voice; + /** + * Neural Microsoft voice, Pashto (Afghanistan) Male, GulNawazNeural. + * @const + */ + const ps_AF_GulNawazNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, FranciscaNeural. + * @const + */ + const pt_BR_FranciscaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Male, AntonioNeural. + * @const + */ + const pt_BR_AntonioNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, BrendaNeural. + * @const + */ + const pt_BR_BrendaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Male, DonatoNeural. + * @const + */ + const pt_BR_DonatoNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, ElzaNeural. + * @const + */ + const pt_BR_ElzaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Male, FabioNeural. + * @const + */ + const pt_BR_FabioNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, GiovannaNeural. + * @const + */ + const pt_BR_GiovannaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Male, HumbertoNeural. + * @const + */ + const pt_BR_HumbertoNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Male, JulioNeural. + * @const + */ + const pt_BR_JulioNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, LeilaNeural. + * @const + */ + const pt_BR_LeilaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, LeticiaNeural. + * @const + */ + const pt_BR_LeticiaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, ManuelaNeural. + * @const + */ + const pt_BR_ManuelaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Male, NicolauNeural. + * @const + */ + const pt_BR_NicolauNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, ThalitaNeural. + * @const + */ + const pt_BR_ThalitaNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Male, ValerioNeural. + * @const + */ + const pt_BR_ValerioNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Brazil) Female, YaraNeural. + * @const + */ + const pt_BR_YaraNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Portugal) Female, RaquelNeural. + * @const + */ + const pt_PT_RaquelNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Portugal) Male, DuarteNeural. + * @const + */ + const pt_PT_DuarteNeural: Voice; + /** + * Neural Microsoft voice, Portuguese (Portugal) Female, FernandaNeural. + * @const + */ + const pt_PT_FernandaNeural: Voice; + /** + * Neural Microsoft voice, Romanian (Romania) Female, AlinaNeural. + * @const + */ + const ro_RO_AlinaNeural: Voice; + /** + * Neural Microsoft voice, Romanian (Romania) Male, EmilNeural. + * @const + */ + const ro_RO_EmilNeural: Voice; + /** + * Neural Microsoft voice, Russian (Russia) Female, SvetlanaNeural. + * @const + */ + const ru_RU_SvetlanaNeural: Voice; + /** + * Neural Microsoft voice, Russian (Russia) Male, DmitryNeural. + * @const + */ + const ru_RU_DmitryNeural: Voice; + /** + * Neural Microsoft voice, Russian (Russia) Female, DariyaNeural. + * @const + */ + const ru_RU_DariyaNeural: Voice; + /** + * Neural Microsoft voice, Sinhala (Sri Lanka) Female, ThiliniNeural. + * @const + */ + const si_LK_ThiliniNeural: Voice; + /** + * Neural Microsoft voice, Sinhala (Sri Lanka) Male, SameeraNeural. + * @const + */ + const si_LK_SameeraNeural: Voice; + /** + * Neural Microsoft voice, Slovak (Slovakia) Female, ViktoriaNeural. + * @const + */ + const sk_SK_ViktoriaNeural: Voice; + /** + * Neural Microsoft voice, Slovak (Slovakia) Male, LukasNeural. + * @const + */ + const sk_SK_LukasNeural: Voice; + /** + * Neural Microsoft voice, Slovenian (Slovenia) Female, PetraNeural. + * @const + */ + const sl_SI_PetraNeural: Voice; + /** + * Neural Microsoft voice, Slovenian (Slovenia) Male, RokNeural. + * @const + */ + const sl_SI_RokNeural: Voice; + /** + * Neural Microsoft voice, Somali (Somalia) Female, UbaxNeural. + * @const + */ + const so_SO_UbaxNeural: Voice; + /** + * Neural Microsoft voice, Somali (Somalia) Male, MuuseNeural. + * @const + */ + const so_SO_MuuseNeural: Voice; + /** + * Neural Microsoft voice, Albanian (Albania) Female, AnilaNeural. + * @const + */ + const sq_AL_AnilaNeural: Voice; + /** + * Neural Microsoft voice, Albanian (Albania) Male, IlirNeural. + * @const + */ + const sq_AL_IlirNeural: Voice; + /** + * Neural Microsoft voice, Serbian (Latin, Serbia) Male, RS. + * @const + */ + const sr_Latn_RS_NicholasNeural: Voice; + /** + * Neural Microsoft voice, Serbian (Latin, Serbia) Female, RS. + * @const + */ + const sr_Latn_RS_SophieNeural: Voice; + /** + * Neural Microsoft voice, Serbian (Cyrillic, Serbia) Female, SophieNeural. + * @const + */ + const sr_RS_SophieNeural: Voice; + /** + * Neural Microsoft voice, Serbian (Cyrillic, Serbia) Male, NicholasNeural. + * @const + */ + const sr_RS_NicholasNeural: Voice; + /** + * Neural Microsoft voice, Sundanese (Indonesia) Female, TutiNeural. + * @const + */ + const su_ID_TutiNeural: Voice; + /** + * Neural Microsoft voice, Sundanese (Indonesia) Male, JajangNeural. + * @const + */ + const su_ID_JajangNeural: Voice; + /** + * Neural Microsoft voice, Swedish (Sweden) Female, SofieNeural. + * @const + */ + const sv_SE_SofieNeural: Voice; + /** + * Neural Microsoft voice, Swedish (Sweden) Male, MattiasNeural. + * @const + */ + const sv_SE_MattiasNeural: Voice; + /** + * Neural Microsoft voice, Swedish (Sweden) Female, HilleviNeural. + * @const + */ + const sv_SE_HilleviNeural: Voice; + /** + * Neural Microsoft voice, Swahili (Kenya) Female, ZuriNeural. + * @const + */ + const sw_KE_ZuriNeural: Voice; + /** + * Neural Microsoft voice, Swahili (Kenya) Male, RafikiNeural. + * @const + */ + const sw_KE_RafikiNeural: Voice; + /** + * Neural Microsoft voice, Swahili (Tanzania) Female, RehemaNeural. + * @const + */ + const sw_TZ_RehemaNeural: Voice; + /** + * Neural Microsoft voice, Swahili (Tanzania) Male, DaudiNeural. + * @const + */ + const sw_TZ_DaudiNeural: Voice; + /** + * Neural Microsoft voice, Tamil (India) Female, PallaviNeural. + * @const + */ + const ta_IN_PallaviNeural: Voice; + /** + * Neural Microsoft voice, Tamil (India) Male, ValluvarNeural. + * @const + */ + const ta_IN_ValluvarNeural: Voice; + /** + * Neural Microsoft voice, Tamil (Sri Lanka) Female, SaranyaNeural. + * @const + */ + const ta_LK_SaranyaNeural: Voice; + /** + * Neural Microsoft voice, Tamil (Sri Lanka) Male, KumarNeural. + * @const + */ + const ta_LK_KumarNeural: Voice; + /** + * Neural Microsoft voice, Tamil (Malaysia) Female, KaniNeural. + * @const + */ + const ta_MY_KaniNeural: Voice; + /** + * Neural Microsoft voice, Tamil (Malaysia) Male, SuryaNeural. + * @const + */ + const ta_MY_SuryaNeural: Voice; + /** + * Neural Microsoft voice, Tamil (Singapore) Female, VenbaNeural. + * @const + */ + const ta_SG_VenbaNeural: Voice; + /** + * Neural Microsoft voice, Tamil (Singapore) Male, AnbuNeural. + * @const + */ + const ta_SG_AnbuNeural: Voice; + /** + * Neural Microsoft voice, Telugu (India) Female, ShrutiNeural. + * @const + */ + const te_IN_ShrutiNeural: Voice; + /** + * Neural Microsoft voice, Telugu (India) Male, MohanNeural. + * @const + */ + const te_IN_MohanNeural: Voice; + /** + * Neural Microsoft voice, Thai (Thailand) Female, PremwadeeNeural. + * @const + */ + const th_TH_PremwadeeNeural: Voice; + /** + * Neural Microsoft voice, Thai (Thailand) Male, NiwatNeural. + * @const + */ + const th_TH_NiwatNeural: Voice; + /** + * Neural Microsoft voice, Thai (Thailand) Female, AcharaNeural. + * @const + */ + const th_TH_AcharaNeural: Voice; + /** + * Neural Microsoft voice, Turkish (Turkey) Female, EmelNeural. + * @const + */ + const tr_TR_EmelNeural: Voice; + /** + * Neural Microsoft voice, Turkish (Turkey) Male, AhmetNeural. + * @const + */ + const tr_TR_AhmetNeural: Voice; + /** + * Neural Microsoft voice, Ukrainian (Ukraine) Female, PolinaNeural. + * @const + */ + const uk_UA_PolinaNeural: Voice; + /** + * Neural Microsoft voice, Ukrainian (Ukraine) Male, OstapNeural. + * @const + */ + const uk_UA_OstapNeural: Voice; + /** + * Neural Microsoft voice, Urdu (India) Female, GulNeural. + * @const + */ + const ur_IN_GulNeural: Voice; + /** + * Neural Microsoft voice, Urdu (India) Male, SalmanNeural. + * @const + */ + const ur_IN_SalmanNeural: Voice; + /** + * Neural Microsoft voice, Urdu (Pakistan) Female, UzmaNeural. + * @const + */ + const ur_PK_UzmaNeural: Voice; + /** + * Neural Microsoft voice, Urdu (Pakistan) Male, AsadNeural. + * @const + */ + const ur_PK_AsadNeural: Voice; + /** + * Neural Microsoft voice, Uzbek (Latin, Uzbekistan) Female, MadinaNeural. + * @const + */ + const uz_UZ_MadinaNeural: Voice; + /** + * Neural Microsoft voice, Uzbek (Latin, Uzbekistan) Male, SardorNeural. + * @const + */ + const uz_UZ_SardorNeural: Voice; + /** + * Neural Microsoft voice, Vietnamese (Vietnam) Female, HoaiMyNeural. + * @const + */ + const vi_VN_HoaiMyNeural: Voice; + /** + * Neural Microsoft voice, Vietnamese (Vietnam) Male, NamMinhNeural. + * @const + */ + const vi_VN_NamMinhNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Wu, Simplified) Female, XiaotongNeural. + * @const + */ + const wuu_CN_XiaotongNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Wu, Simplified) Male, YunzheNeural. + * @const + */ + const wuu_CN_YunzheNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Cantonese, Simplified) Female, XiaoMinNeural. + * @const + */ + const yue_CN_XiaoMinNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Cantonese, Simplified) Male, YunSongNeural. + * @const + */ + const yue_CN_YunSongNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoxiaoNeural. + * @const + */ + const zh_CN_XiaoxiaoNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunxiNeural. + * @const + */ + const zh_CN_YunxiNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunjianNeural. + * @const + */ + const zh_CN_YunjianNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoyiNeural. + * @const + */ + const zh_CN_XiaoyiNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunyangNeural. + * @const + */ + const zh_CN_YunyangNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaochenNeural. + * @const + */ + const zh_CN_XiaochenNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaochenMultilingualNeural. + * @const + */ + const zh_CN_XiaochenMultilingualNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaohanNeural. + * @const + */ + const zh_CN_XiaohanNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaomengNeural. + * @const + */ + const zh_CN_XiaomengNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaomoNeural. + * @const + */ + const zh_CN_XiaomoNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoqiuNeural. + * @const + */ + const zh_CN_XiaoqiuNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaorouNeural. + * @const + */ + const zh_CN_XiaorouNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoruiNeural. + * @const + */ + const zh_CN_XiaoruiNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoshuangNeural. + * @const + */ + const zh_CN_XiaoshuangNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoxiaoDialectsNeural. + * @const + */ + const zh_CN_XiaoxiaoDialectsNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoxiaoMultilingualNeural. + * @const + */ + const zh_CN_XiaoxiaoMultilingualNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoyanNeural. + * @const + */ + const zh_CN_XiaoyanNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoyouNeural. + * @const + */ + const zh_CN_XiaoyouNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaoyuMultilingualNeural. + * @const + */ + const zh_CN_XiaoyuMultilingualNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Female, XiaozhenNeural. + * @const + */ + const zh_CN_XiaozhenNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunfengNeural. + * @const + */ + const zh_CN_YunfengNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunhaoNeural. + * @const + */ + const zh_CN_YunhaoNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunjieNeural. + * @const + */ + const zh_CN_YunjieNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunxiaNeural. + * @const + */ + const zh_CN_YunxiaNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunyeNeural. + * @const + */ + const zh_CN_YunyeNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunyiMultilingualNeural. + * @const + */ + const zh_CN_YunyiMultilingualNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Mandarin, Simplified) Male, YunzeNeural. + * @const + */ + const zh_CN_YunzeNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Zhongyuan Mandarin Henan, Simplified) Male, henan. + * @const + */ + const zh_CN_henan_YundengNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Northeastern Mandarin, Simplified) Female, liaoning. + * @const + */ + const zh_CN_liaoning_XiaobeiNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Zhongyuan Mandarin Shaanxi, Simplified) Female, shaanxi. + * @const + */ + const zh_CN_shaanxi_XiaoniNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Jilu Mandarin, Simplified) Male, shandong. + * @const + */ + const zh_CN_shandong_YunxiangNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Southwestern Mandarin, Simplified) Male, sichuan. + * @const + */ + const zh_CN_sichuan_YunxiNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Cantonese, Traditional) Female, HiuMaanNeural. + * @const + */ + const zh_HK_HiuMaanNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Cantonese, Traditional) Male, WanLungNeural. + * @const + */ + const zh_HK_WanLungNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Cantonese, Traditional) Female, HiuGaaiNeural. + * @const + */ + const zh_HK_HiuGaaiNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Taiwanese Mandarin, Traditional) Female, HsiaoChenNeural. + * @const + */ + const zh_TW_HsiaoChenNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Taiwanese Mandarin, Traditional) Male, YunJheNeural. + * @const + */ + const zh_TW_YunJheNeural: Voice; + /** + * Neural Microsoft voice, Chinese (Taiwanese Mandarin, Traditional) Female, HsiaoYuNeural. + * @const + */ + const zh_TW_HsiaoYuNeural: Voice; + /** + * Neural Microsoft voice, Zulu (South Africa) Female, ThandoNeural. + * @const + */ + const zu_ZA_ThandoNeural: Voice; + /** + * Neural Microsoft voice, Zulu (South Africa) Male, ThembaNeural. + * @const + */ + const zu_ZA_ThembaNeural: Voice; + } + } +} + +declare namespace VoiceList { + /** + * List of availabl SaluteSpeech TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace SaluteSpeech { + /** + * SaluteSpeech voice, Russian female. + * @const + */ + const ru_RU_natalia: Voice; + /** + * SaluteSpeech voice, Russian male. + * @const + */ + const ru_RU_boris: Voice; + /** + * SaluteSpeech voice, Russian male. + * @const + */ + const ru_RU_marfa: Voice; + /** + * SaluteSpeech voice, Russian male. + * @const + */ + const ru_RU_taras: Voice; + /** + * SaluteSpeech voice, Russian female. + * @const + */ + const ru_RU_alexandra: Voice; + /** + * SaluteSpeech voice, Russian male. + * @const + */ + const ru_RU_sergey: Voice; + /** + * Creates a brand voice with SaluteSpeech. To use this method, please contact support. + * @param name The name of the voice + */ + const createBrandVoice: (name: string) => Voice; + } +} + +declare namespace VoiceList { + /** + * List of available T-Bank TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace TBank { + /** + * T-Bank voice, Russian female. + * @const + */ + const ru_RU_Alyona: Voice; + /** + * T-Bank voice, Russian female, sad. + * @const + */ + const ru_RU_Alyona_sad: Voice; + /** + * T-Bank voice, Russian female, funny. + * @const + */ + const ru_RU_Alyona_funny: Voice; + /** + * T-Bank voice, Russian female, flirt. + * @const + */ + const ru_RU_Alyona_flirt: Voice; + /** + * T-Bank voice, Russian male, neutral. + * @const + */ + const ru_RU_Dorofeev_neutral: Voice; + /** + * T-Bank voice, Russian male, drama. + * @const + */ + const ru_RU_Dorofeev_drama: Voice; + /** + * T-Bank voice, Russian male, comedy. + * @const + */ + const ru_RU_Dorofeev_comedy: Voice; + /** + * T-Bank voice, Russian male, info. + * @const + */ + const ru_RU_Dorofeev_info: Voice; + /** + * T-Bank voice, Russian male, tragedy. + * @const + */ + const ru_RU_Dorofeev_tragedy: Voice; + } +} + +/** + * List of available TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. + */ +declare namespace VoiceList {} + +declare namespace VoiceList { + namespace Yandex { + /** + * List of available premium Yandex TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods that sound more natural due to advanced synthesis technology. + * @namespace + */ + namespace Neural { + /** + * Neural Yandex voice, German female, Lea. + * @const + */ + const de_DE_lea: Voice; + /** + * Neural Yandex voice, English (US) male, John. + * @const + */ + const en_US_john: Voice; + /** + * Neural Yandex voice, Kazakh (Kazakhstan) male, Amira. + * @const + */ + const kk_KK_amira: Voice; + /** + * Neural Yandex voice, Kazakh (Kazakhstan) male, Madi. + * @const + */ + const kk_KK_madi: Voice; + /** + * Neural Yandex voice, Russian female, Alena. + * @const + */ + const ru_RU_alena: Voice; + /** + * Neural Yandex voice, Russian male, Filipp. + * @const + */ + const ru_RU_filipp: Voice; + /** + * Neural Yandex voice, Russian male, Ermil. + * @const + */ + const ru_RU_ermil: Voice; + /** + * Neural Yandex voice, Russian female, Jane. + * @const + */ + const ru_RU_jane: Voice; + /** + * Neural Yandex voice, Russian male, Madirus. + * @const + */ + const ru_RU_madirus: Voice; + /** + * Neural Yandex voice, Russian female, Omazh. + * @const + */ + const ru_RU_omazh: Voice; + /** + * Neural Yandex voice, Russian male, Zahar. + * @const + */ + const ru_RU_zahar: Voice; + /** + * Neural Yandex voice, Uzbek (Uzbekistan) female, Nigora. + * @const + */ + const uz_UZ_nigora: Voice; + } + } +} + +declare namespace VoiceList { + /** + * List of available Yandex TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace Yandex {} +} + +declare namespace VoiceList { + /** + * List of available YandexV3 TTS voices for the [Call.say] and [VoxEngine.createTTSPlayer] methods. Depending on the voice, different technologies are used to make synthesized voices sound as close as possible to live human voices. Please note that using these text-to-speech capabilities are charged according to the pricing. + */ + namespace YandexV3 { + /** + * Neural Yandex voice, German female, Lea. + * @const + */ + const de_DE_lea: Voice; + /** + * Neural Yandex voice, English (US) male, John. + * @const + */ + const en_US_john: Voice; + /** + * Neural Yandex voice, Hebrew female, Naomi. + * @const + */ + const he_IL_naomi: Voice; + /** + * Yandex voice, Kazakh (Kazakhstan) male, Amira. + * @const + */ + const kk_KK_amira: Voice; + /** + * Yandex voice, Kazakh (Kazakhstan) male, Madi. + * @const + */ + const kk_KK_madi: Voice; + /** + * Neural Yandex voice, Russian female, Alena. + * @const + */ + const ru_RU_alena: Voice; + /** + * Neural Yandex voice, Russian male, Filipp. + * @const + */ + const ru_RU_filipp: Voice; + /** + * Yandex voice, Russian male, Ermil. + * @const + */ + const ru_RU_ermil: Voice; + /** + * Yandex voice, Russian female, Jane. + * @const + */ + const ru_RU_jane: Voice; + /** + * Yandex voice, Russian male, Madirus. + * @const + */ + const ru_RU_madirus: Voice; + /** + * Yandex voice, Russian female, Omazh. + * @const + */ + const ru_RU_omazh: Voice; + /** + * Yandex voice, Russian male, Zahar. + * @const + */ + const ru_RU_zahar: Voice; + /** + * Yandex voice, Russian female, Dasha. + * @const + */ + const ru_RU_dasha: Voice; + /** + * Yandex voice, Russian female, Julia. + * @const + */ + const ru_RU_julia: Voice; + /** + * Yandex voice, Russian female, Lera. + * @const + */ + const ru_RU_lera: Voice; + /** + * Yandex voice, Russian female, Masha. + * @const + */ + const ru_RU_masha: Voice; + /** + * Yandex voice, Russian female, Marina. + * @const + */ + const ru_RU_marina: Voice; + /** + * Yandex voice, Russian male, Alexander. + * @const + */ + const ru_RU_alexander: Voice; + /** + * Yandex voice, Russian male, Kirill. + * @const + */ + const ru_RU_kirill: Voice; + /** + * Yandex voice, Russian male, Anton. + * @const + */ + const ru_RU_anton: Voice; + /** + * Yandex voice, Uzbek (Uzbekistan) female, Nigora. + * @const + */ + const uz_UZ_nigora: Voice; + } +} + +/** + * Represents a language and a voice for TTS. List of all supported voices: [VoiceList]. + */ +declare class Voice {} diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx new file mode 100644 index 0000000..6642eae --- /dev/null +++ b/frontend/src/index.tsx @@ -0,0 +1,102 @@ +import { MantineProvider } from '@mantine/core'; +import '@mantine/core/styles.css'; +import '@mantine/dates/styles.css'; +import '@mantine/tiptap/styles.css'; +import { BrowserAgent } from '@newrelic/browser-agent/loaders/browser-agent'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import 'mac-scrollbar/dist/mac-scrollbar.css'; +import { configure } from 'mobx'; +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import 'react-toastify/dist/ReactToastify.css'; +import 'reflect-metadata'; +import { App } from './app'; +import './app/config/i18n/i18n'; +import NewrelicConfig from './app/config/newrelic/newrelic'; +import './app/styles/main.css'; +import { MultichatProvider } from './modules/multichat'; +import { TelephonyProvider } from './modules/telephony'; +import { envUtil } from './shared'; + +// Error Boundary Component +class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { hasError: boolean; error?: Error }> { + constructor(props: { children: React.ReactNode }) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('Application error:', error, errorInfo); + // Send error to logging service if available + if (window.gtag) { + window.gtag('event', 'exception', { + description: error.toString(), + fatal: false, + }); + } + } + + render() { + if (this.state.hasError) { + return ( +
+

Something went wrong

+

The application encountered an error. Please try refreshing the page.

+ + {process.env.NODE_ENV === 'development' && ( +
+ Error Details +
{this.state.error?.stack}
+
+ )} +
+ ); + } + + return this.props.children; + } +} + +declare global { + interface Window { + dataLayer: any; + gtag: any; + } +} + +if (envUtil.newRelicEnabled) new BrowserAgent(NewrelicConfig); + +export const queryClient = new QueryClient(); + +const container = document.getElementById('root') as HTMLElement; +const root = createRoot(container); + +// this way mobx will consider all actions in methods as they are wrapped in runInAction +setTimeout(() => { + configure({ + enforceActions: 'never', + isolateGlobalState: true, + reactionScheduler: f => setTimeout(f), + }); +}); + +root.render( + + + + + + + + + + + {envUtil.reactQueryDevtoolsEnabled && } + + +); diff --git a/frontend/src/modules/auth/api/AuthApi/AuthApi.ts b/frontend/src/modules/auth/api/AuthApi/AuthApi.ts new file mode 100644 index 0000000..c4aefac --- /dev/null +++ b/frontend/src/modules/auth/api/AuthApi/AuthApi.ts @@ -0,0 +1,27 @@ +import { baseApi } from '@/app'; +import type { JwtToken } from '@/shared'; +import { AuthApiRoutes } from '../AuthApiRoutes'; + +class AuthApi { + login = async ({ email, password }: { email: string; password: string }): Promise => { + const response = await baseApi.post(AuthApiRoutes.LOGIN, { email, password }); + + return response.data; + }; + + decodeLoginLink = async (loginLink: string): Promise => { + const response = await baseApi.post(AuthApiRoutes.DECODE_LOGIN_LINK, { loginLink }); + + return response.data; + }; + + refreshToken = async (token: string): Promise => { + baseApi.setAuthToken(token); + + const response = await baseApi.post(AuthApiRoutes.REFRESH_TOKEN); + + return response.data; + }; +} + +export const authApi = new AuthApi(); diff --git a/frontend/src/modules/auth/api/AuthApiRoutes.ts b/frontend/src/modules/auth/api/AuthApiRoutes.ts new file mode 100644 index 0000000..daeb25e --- /dev/null +++ b/frontend/src/modules/auth/api/AuthApiRoutes.ts @@ -0,0 +1,6 @@ +export enum AuthApiRoutes { + // auth + LOGIN = '/api/iam/auth/login', + REFRESH_TOKEN = '/api/iam/auth/refresh-token', + DECODE_LOGIN_LINK = '/api/iam/auth/decode-login-link', +} diff --git a/frontend/src/modules/auth/api/index.tsx b/frontend/src/modules/auth/api/index.tsx new file mode 100644 index 0000000..7064525 --- /dev/null +++ b/frontend/src/modules/auth/api/index.tsx @@ -0,0 +1 @@ +export { authApi } from './AuthApi/AuthApi'; diff --git a/frontend/src/modules/auth/index.tsx b/frontend/src/modules/auth/index.tsx new file mode 100644 index 0000000..7d33382 --- /dev/null +++ b/frontend/src/modules/auth/index.tsx @@ -0,0 +1,2 @@ +export * from './pages'; +export * from './store'; diff --git a/frontend/src/modules/auth/pages/LoginLinkPage/LoginLinkPage.tsx b/frontend/src/modules/auth/pages/LoginLinkPage/LoginLinkPage.tsx new file mode 100644 index 0000000..8ca71a1 --- /dev/null +++ b/frontend/src/modules/auth/pages/LoginLinkPage/LoginLinkPage.tsx @@ -0,0 +1,43 @@ +import { routes } from '@/app'; +import { CommonQueryParams, WholePageLoaderWithLogo } from '@/shared'; +import { useEffect } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { authStore } from '../../store'; + +const LoginLinkPage = () => { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + + useEffect(() => { + const login = async (): Promise => { + // currently, this is used for wazzup wauth integration, redirect path in that case would + // be /settings/integrations and params will include state value + const loginLink = searchParams.get(CommonQueryParams.LOGIN_LINK); + const redirectPath = searchParams.get(CommonQueryParams.REDIRECT_PATH); + + const searchParamsWithoutRedirectAndLoginLink = new URLSearchParams(searchParams); + searchParamsWithoutRedirectAndLoginLink.delete(CommonQueryParams.LOGIN_LINK); + searchParamsWithoutRedirectAndLoginLink.delete(CommonQueryParams.REDIRECT_PATH); + + if (!loginLink) { + navigate(routes.login); + + return; + } + + await authStore.decodeLoginLink(loginLink); + + if (redirectPath && searchParamsWithoutRedirectAndLoginLink) { + navigate(`${redirectPath}?${searchParamsWithoutRedirectAndLoginLink.toString()}`); + } else { + navigate(routes.root); + } + }; + + login(); + }, [searchParams, navigate]); + + return ; +}; + +export { LoginLinkPage }; diff --git a/frontend/src/modules/auth/pages/LoginPage/LoginPage.tsx b/frontend/src/modules/auth/pages/LoginPage/LoginPage.tsx new file mode 100644 index 0000000..28f35f9 --- /dev/null +++ b/frontend/src/modules/auth/pages/LoginPage/LoginPage.tsx @@ -0,0 +1,12 @@ +import { LoginForm } from '../../shared'; +import { LoginTemplate } from '../../templates'; + +const LoginPage = () => { + return ( + + + + ); +}; + +export { LoginPage }; diff --git a/frontend/src/modules/auth/pages/index.tsx b/frontend/src/modules/auth/pages/index.tsx new file mode 100644 index 0000000..4f1b506 --- /dev/null +++ b/frontend/src/modules/auth/pages/index.tsx @@ -0,0 +1,2 @@ +export { LoginLinkPage } from './LoginLinkPage/LoginLinkPage'; +export { LoginPage } from './LoginPage/LoginPage'; diff --git a/frontend/src/modules/auth/shared/assets/hide_password.svg b/frontend/src/modules/auth/shared/assets/hide_password.svg new file mode 100644 index 0000000..9d5a321 --- /dev/null +++ b/frontend/src/modules/auth/shared/assets/hide_password.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/frontend/src/modules/auth/shared/assets/index.tsx b/frontend/src/modules/auth/shared/assets/index.tsx new file mode 100644 index 0000000..0741bad --- /dev/null +++ b/frontend/src/modules/auth/shared/assets/index.tsx @@ -0,0 +1,2 @@ +export { ReactComponent as HidePasswordIcon } from './hide_password.svg'; +export { ReactComponent as ShowPasswordIcon } from './show_password.svg'; diff --git a/frontend/src/modules/auth/shared/assets/show_password.svg b/frontend/src/modules/auth/shared/assets/show_password.svg new file mode 100644 index 0000000..c8f0a31 --- /dev/null +++ b/frontend/src/modules/auth/shared/assets/show_password.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/frontend/src/modules/auth/shared/index.tsx b/frontend/src/modules/auth/shared/index.tsx new file mode 100644 index 0000000..281530d --- /dev/null +++ b/frontend/src/modules/auth/shared/index.tsx @@ -0,0 +1,2 @@ +export * from './assets'; +export * from './lib'; diff --git a/frontend/src/modules/auth/shared/lib/components/LoginButton/LoginButton.tsx b/frontend/src/modules/auth/shared/lib/components/LoginButton/LoginButton.tsx new file mode 100644 index 0000000..1666af3 --- /dev/null +++ b/frontend/src/modules/auth/shared/lib/components/LoginButton/LoginButton.tsx @@ -0,0 +1,54 @@ +import { MiniLoader } from '@/shared'; +import styled from 'styled-components'; + +const Button = styled.button<{ $loading?: boolean }>` + width: 100%; + + gap: 8px; + display: flex; + align-items: center; + justify-content: center; + + font-size: 18px; + text-align: center; + line-height: normal; + font-family: Nunito SemiBold; + color: var(--primary-statuses-white-0); + + padding: 14px 22px; + border-radius: 8px; + background-color: var(--button-text-green-default); + border: 2px solid var(--button-text-green-default); + transition: var(--transition-200); + + &:hover { + cursor: pointer; + + background-color: var(--button-text-green-hover); + border: 2px solid var(--button-text-green-hover); + } + + &:active { + background-color: var(--button-text-green-active); + border: 2px solid var(--button-text-green-active); + } +`; + +interface Props { + name: string; + loading: boolean; +} + +const LoginButton = (props: Props) => { + const { name, loading } = props; + + return ( + + ); +}; + +export { LoginButton }; diff --git a/frontend/src/modules/auth/shared/lib/components/LoginForm/LoginForm.tsx b/frontend/src/modules/auth/shared/lib/components/LoginForm/LoginForm.tsx new file mode 100644 index 0000000..35bebeb --- /dev/null +++ b/frontend/src/modules/auth/shared/lib/components/LoginForm/LoginForm.tsx @@ -0,0 +1,200 @@ +import { + CommonQueryParams, + GTMUtil, + InputModel, + LogoLink, + UriCodingUtil, + UrlUtil, + envUtil, + validateForm, +} from '@/shared'; +import { FocusTrap } from '@mantine/core'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useEffect, useState, type FormEvent } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link, useSearchParams } from 'react-router-dom'; +import styled from 'styled-components'; +import { authStore } from '../../../../store'; +import { LoginButton } from '../LoginButton/LoginButton'; +import { LoginInput } from '../LoginInput/LoginInput'; +import { LoginOrDelimiter } from '../LoginOrDelimiter/LoginOrDelimiter'; +import { SuggestionLink } from '../SuggestionLink/SuggestionLink'; + +const Root = styled.div` + height: 100%; + width: 100%; + + gap: 24px; + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; +`; + +const Title = styled.span` + font-size: 18px; + font-weight: 400; + line-height: 32px; + font-family: Nunito SemiBold; + color: var(--graphite-graphite-840); + + margin-bottom: 12px; +`; + +const InputWrapper = styled.div` + width: 100%; +`; + +const Form = styled.form` + width: 100%; + + gap: 24px; + display: flex; + align-items: center; + flex-direction: column; +`; + +const ForgotPasswordLink = styled(Link)` + width: fit-content; + + font-size: 16px; + font-weight: 600; + line-height: 28px; + font-family: Nunito SemiBold; + color: var(--button-text-green-default); + transition: var(--transition-200); + + &:hover { + color: var(--button-text-green-hover); + } + + &:active { + color: var(--button-text-green-active); + } +`; + +const ErrorMessage = styled.div` + font-size: 12px; + font-weight: 400; + text-align: center; + font-family: Nunito; + color: var(--button-text-red-default); + + margin-top: 20px; +`; + +interface InitialForm { + email: InputModel; + password: InputModel; +} + +const LoginForm = observer(() => { + const { t } = useTranslation('page.login', { + keyPrefix: 'login.login_form', + }); + + const [searchParams] = useSearchParams(); + + const redirectPathFromParams = searchParams.get(CommonQueryParams.REDIRECT_PATH); + + const searchParamsWithoutRedirectPath = new URLSearchParams(searchParams); + searchParamsWithoutRedirectPath.delete(CommonQueryParams.REDIRECT_PATH); + + const redirectPath = redirectPathFromParams ? UriCodingUtil.decode(redirectPathFromParams) : null; + + const form = useLocalObservable(() => ({ + email: InputModel.create().email(t('invalid_email_error')).required(), + password: InputModel.create().required(), + })); + + const [isError, setIsError] = useState(false); + const [loading, setLoading] = useState(false); + + useEffect(() => { + GTMUtil.sendAnalyticsEvent('login_start'); + }, []); + + const onSubmit = async (e: FormEvent): Promise => { + e.preventDefault(); + + if (!validateForm(form)) return; + + const isValidEmail = form.email.value && form.email.validate(); + const isValidPassword = Boolean(form.password.value); + + if (!isValidEmail || !isValidPassword) { + if (!isValidEmail) form.email.showError(t('invalid_email_error')); + + if (!isValidPassword) form.password.showError(t('invalid_password_error')); + + return; + } + + try { + setLoading(true); + + const redirectUrl = await authStore.login({ + redirectPath, + email: form.email.trimmedValue, + password: form.password.trimmedValue, + searchParams: searchParamsWithoutRedirectPath.toString(), + }); + + if (redirectUrl) { + GTMUtil.sendAnalyticsEvent('login'); + + setIsError(false); + + window.location.href = redirectUrl; + } else { + setIsError(true); + } + } finally { + setLoading(false); + } + }; + + return ( + + + {t('title')} + +
+ + + + + + + + + + {t('forgot_your_password')} + + + + + {isError && {t('invalid')}} + +
+ + + + +
+ ); +}); + +export { LoginForm }; diff --git a/frontend/src/modules/auth/shared/lib/components/LoginInput/LoginInput.tsx b/frontend/src/modules/auth/shared/lib/components/LoginInput/LoginInput.tsx new file mode 100644 index 0000000..b573c6f --- /dev/null +++ b/frontend/src/modules/auth/shared/lib/components/LoginInput/LoginInput.tsx @@ -0,0 +1,124 @@ +import type { InputModel } from '@/shared'; +import { useDisclosure } from '@mantine/hooks'; +import { observer } from 'mobx-react-lite'; +import { + useEffect, + useState, + type ChangeEvent, + type HTMLInputTypeAttribute, + type InputHTMLAttributes, +} from 'react'; +import styled from 'styled-components'; +import { HidePasswordIcon, ShowPasswordIcon } from '../../../assets'; + +const InputRoot = styled.div` + position: relative; +`; + +interface InputProps { + $invalid: boolean; + $password: boolean; +} + +const Input = styled.input` + width: 100%; + height: 48px; + + font-size: 16px; + line-height: 28px; + font-family: Nunito; + color: var(--graphite-graphite-840); + + border-radius: var(--border-radius-block); + border: 1px solid var(--button-text-graphite-secondary-text); + padding: ${p => (p.$password ? '12px 40px 12px 16px' : '12px 16px')}; + transition: border var(--transition-200); + + &::placeholder { + color: var(--graphite-graphite-200); + } + + ${p => p.$invalid && `border-color: var(--button-text-red-default)`}; +`; + +const ErrorMessage = styled.div` + position: absolute; + + font-size: 12px; + font-weight: 400; + line-height: 18px; + font-family: Nunito; + color: var(--button-text-red-default); + + padding-left: 8px; + transform: translateY(2px); +`; + +const VisibilityIconWrapper = styled.button` + position: absolute; + top: 14px; + right: 12px; + + width: 24px; + height: 24px; + + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: center; + + &:hover { + cursor: pointer; + } +`; + +interface Props extends InputHTMLAttributes { + model: InputModel; + placeholder?: string; + type?: HTMLInputTypeAttribute; +} + +const LoginInput = observer((props: Props) => { + const { model, placeholder, type = 'text', ...rest } = props; + + const [value, setValue] = useState(model.value); + + useEffect(() => { + // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect + setValue(model.value); + }, [model.value]); + + const onChange = (e: ChangeEvent) => { + const newValue = e.target.value; + + setValue(newValue); + model.setValue(newValue); + }; + + const [isPasswordShown, { toggle: togglePasswordShown }] = useDisclosure(false); + const isPassword = type === 'password'; + + return ( + + + + {!model.isValid() && model.isErrorShown && {model.errorMessage}} + + {isPassword && ( + + {isPasswordShown ? : } + + )} + + ); +}); + +export { LoginInput }; diff --git a/frontend/src/modules/auth/shared/lib/components/LoginOrDelimiter/LoginOrDelimiter.tsx b/frontend/src/modules/auth/shared/lib/components/LoginOrDelimiter/LoginOrDelimiter.tsx new file mode 100644 index 0000000..0690ba2 --- /dev/null +++ b/frontend/src/modules/auth/shared/lib/components/LoginOrDelimiter/LoginOrDelimiter.tsx @@ -0,0 +1,38 @@ +import styled from 'styled-components'; + +const Root = styled.div` + width: 100%; + + display: flex; + gap: 32px; + align-items: center; + + font-size: 16px; + line-height: 28px; + font-family: Nunito; +`; + +const Delimiter = styled.hr` + flex: 1; + border-top: 1px solid var(--graphite-graphite-80); +`; + +interface Props { + caption: string; +} + +const LoginOrDelimiter = (props: Props) => { + const { caption } = props; + + return ( + + + + {caption} + + + + ); +}; + +export { LoginOrDelimiter }; diff --git a/frontend/src/modules/auth/shared/lib/components/SuggestionLink/SuggestionLink.tsx b/frontend/src/modules/auth/shared/lib/components/SuggestionLink/SuggestionLink.tsx new file mode 100644 index 0000000..cd6b9f9 --- /dev/null +++ b/frontend/src/modules/auth/shared/lib/components/SuggestionLink/SuggestionLink.tsx @@ -0,0 +1,51 @@ +import styled from 'styled-components'; + +const Root = styled.div` + gap: 8px; + display: flex; + align-items: center; + justify-content: center; + + font-size: 16px; + font-weight: 400; + line-height: 28px; + font-family: Nunito; + color: var(--graphite-graphite-840); +`; + +const FormLink = styled.a` + font-size: 16px; + font-weight: 600; + line-height: 28px; + font-family: Nunito SemiBold; + color: var(--button-text-green-default); + transition: var(--transition-200); + + &:hover { + color: var(--button-text-green-hover); + } + + &:active { + color: var(--button-text-green-active); + } +`; + +interface Props { + href: string; + caption: string; + linkTitle: string; +} + +const SuggestionLink = (props: Props) => { + const { href, caption, linkTitle } = props; + + return ( + + {caption} + + {linkTitle} + + ); +}; + +export { SuggestionLink }; diff --git a/frontend/src/modules/auth/shared/lib/components/index.tsx b/frontend/src/modules/auth/shared/lib/components/index.tsx new file mode 100644 index 0000000..f7ccd1b --- /dev/null +++ b/frontend/src/modules/auth/shared/lib/components/index.tsx @@ -0,0 +1 @@ +export { LoginForm } from './LoginForm/LoginForm'; diff --git a/frontend/src/modules/auth/shared/lib/index.tsx b/frontend/src/modules/auth/shared/lib/index.tsx new file mode 100644 index 0000000..07635cb --- /dev/null +++ b/frontend/src/modules/auth/shared/lib/index.tsx @@ -0,0 +1 @@ +export * from './components'; diff --git a/frontend/src/modules/auth/store/AuthStore.ts b/frontend/src/modules/auth/store/AuthStore.ts new file mode 100644 index 0000000..c4f16d6 --- /dev/null +++ b/frontend/src/modules/auth/store/AuthStore.ts @@ -0,0 +1,219 @@ +import { appStore, baseApi, userApi } from '@/app'; +import { partnerApi } from '@/modules/partner'; +import { + CommonQueryParams, + GTMUtil, + JwtParserUtil, + TokenUtil, + UriCodingUtil, + UrlUtil, + UtcDate, + envUtil, + type JwtToken, + type Nullable, + type User, +} from '@/shared'; +import { HttpStatusCode, type AxiosError } from 'axios'; +import { makeAutoObservable, runInAction } from 'mobx'; +import { authApi } from '../api'; + +class AuthStore { + accountId: Nullable = null; + user: Nullable = null; + + isAuthenticated = false; + isLoading = true; + + constructor() { + makeAutoObservable(this); + } + + isAdmin = (): boolean => { + return this.user ? this.user.isAdmin() : false; + }; + + startAuth = async (): Promise => { + this.isLoading = true; + + // first, we're trying to get the jwt token from the local storage + let token = TokenUtil.getLocalToken(); + + // else, we're trying to get it from the query string, this is the case + // when we're logging from no subdomain to specific subdomain + const searchParams = new URLSearchParams(window.location.search); + const tokenFromSearch = searchParams.get(CommonQueryParams.TOKEN); + + if (!token) token = tokenFromSearch ? UriCodingUtil.decode(tokenFromSearch) : null; + + if (tokenFromSearch) { + searchParams.delete(CommonQueryParams.TOKEN); + + // if token exists in search – clear the "token" param, but keep other params + window.history.replaceState( + null, + '', + searchParams.size > 0 + ? `${window.location.pathname}?${searchParams.toString()}` + : window.location.pathname + ); + } + + // if we don't have any token, we need to reset auth + if (!token) { + this.resetAuthorization(); + + return; + } + + try { + const payload = JwtParserUtil.parse(token); + + if (UtcDate.fromTimestamp(payload['exp']).isExpired()) { + this.resetAuthorization(); + + return; + } + } catch (e) { + this.resetAuthorization(); + + throw new Error(`Invalid jwt token, failed to authenticate: ${e}`); + } + + try { + const jwtToken = await authApi.refreshToken(token); + + await this.setupAuthorization(jwtToken); + } catch (e: unknown) { + const error = e as AxiosError; + + if ( + error.isAxiosError && + error.response && + error.response.status === HttpStatusCode.Unauthorized + ) + this.resetAuthorization(); + + throw new Error(`Failed to refresh jwt token: ${e}`); + } finally { + this.isLoading = false; + } + }; + + setupAuthorization = async (jwtToken: JwtToken): Promise => { + TokenUtil.updateJwtToken(jwtToken); + + baseApi.setAuthToken(jwtToken.token); + + await runInAction(async (): Promise => { + this.accountId = jwtToken.accountId; + + this.user = jwtToken.isPartner + ? await partnerApi.getPartnerUser(jwtToken.userId) + : await userApi.getUserById(jwtToken.userId); + + this.isAuthenticated = Boolean(this.user); + + if (this.user && !jwtToken.isPartner) { + GTMUtil.setupDataLayer(this.accountId, this.user.id, this.user.analyticsId); + + appStore.load(); + } + }); + }; + + resetAuthorization = (): void => { + TokenUtil.resetJwtToken(); + + this.isAuthenticated = false; + this.isLoading = false; + + this.user = null; + this.accountId = null; + + appStore.reset(); + }; + + decodeLoginLink = async (loginLink: string): Promise => { + const jwtToken = await authApi.decodeLoginLink(loginLink); + + await this.setupAuthorization(jwtToken); + }; + + logout = (): void => { + this.resetAuthorization(); + }; + + login = async ({ + email, + redirectPath, + password, + searchParams, + }: { + email: string; + // path to redirect to after login + redirectPath: Nullable; + password: string; + searchParams: string; + }): Promise> => { + try { + const jwtToken = await authApi.login({ email, password }); + + return jwtToken ? this.getRedirectUrl({ jwtToken, redirectPath, searchParams }) : null; + } catch (e) { + const jwtToken = await partnerApi.login({ email, password }); + + return jwtToken ? this.getRedirectUrl({ jwtToken, redirectPath, searchParams }) : null; + } + }; + + invalidateCurrentUserInCache = async (): Promise => { + if (!this.user) return; + + try { + this.user = await userApi.getUserById(this.user.id); + } catch (e) { + throw new Error(`Failed to invalidate current user in AuthStore cache: ${e}`); + } + }; + + uploadUserAvatar = async ({ userId, blob }: { userId: number; blob: Blob }): Promise => { + const formData = new FormData(); + formData.append('avatar', blob, 'avatar.jpg'); + + this.user = await userApi.uploadUserAvatar({ userId, formData }); + }; + + removeUserAvatar = async (userId: number): Promise => { + this.user = await userApi.removeUserAvatar(userId); + }; + + private getRedirectUrl = ({ + jwtToken, + redirectPath, + searchParams, + }: { + jwtToken: JwtToken; + redirectPath: Nullable; + searchParams: string; + }): string => { + const { subdomain } = jwtToken; + + // app url template must be in http://{subdomain}.amwork.loc:3000 format (must include {subdomain}) + let redirectUrl = envUtil.appUrlTemplate.replace('{subdomain}', subdomain); + + if (redirectPath) redirectUrl = `${redirectUrl}${redirectPath}`; + + // if we're on localhost:3000 – local testing scenario + if (UrlUtil.isLocalhost3000()) redirectUrl = 'http://localhost:3000'; + + // if we're on local network + if (UrlUtil.isLocalNetwork()) redirectUrl = `http://${UrlUtil.getCurrentHostname()}:3000`; + + // redirectUrl + old searchParams + token + return `${redirectUrl}${searchParams ? `?${searchParams}` : ''}${searchParams ? '&' : '?'}${CommonQueryParams.TOKEN}=${UriCodingUtil.encode( + jwtToken.token + )}`; + }; +} + +export const authStore = new AuthStore(); diff --git a/frontend/src/modules/auth/store/index.tsx b/frontend/src/modules/auth/store/index.tsx new file mode 100644 index 0000000..f4239a1 --- /dev/null +++ b/frontend/src/modules/auth/store/index.tsx @@ -0,0 +1 @@ +export { authStore } from './AuthStore'; diff --git a/frontend/src/modules/auth/templates/LoginTemplate/LoginTemplate.tsx b/frontend/src/modules/auth/templates/LoginTemplate/LoginTemplate.tsx new file mode 100644 index 0000000..99f1037 --- /dev/null +++ b/frontend/src/modules/auth/templates/LoginTemplate/LoginTemplate.tsx @@ -0,0 +1,126 @@ +import { envUtil } from '@/shared'; +import { MediaBreakpoints } from '@/shared/lib/models/MediaBreakpoints'; +import type { ReactNode } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +const Root = styled.div` + width: 100%; + height: 100dvh; + + display: grid; + grid-template-columns: 1fr 560px; + + background-color: var(--background-green-20); + + @media ${MediaBreakpoints.SM} { + height: 100%; + min-height: 100dvh; + + display: flex; + + padding: 16px; + } +`; + +const LeftBlock = styled.section` + display: flex; + flex-direction: column; + gap: 80px; + + padding: 80px; + overflow: hidden; + + @media ${MediaBreakpoints.SM} { + display: none; + } +`; + +const RightBlock = styled.section` + width: 100%; + height: 100%; + + overflow-y: auto; + padding: 40px 64px 24px; + background: var(--primary-statuses-white-0); + border-left: 1px solid var(--graphite-graphite-80); + + @media ${MediaBreakpoints.SM} { + min-height: 100dvh; + + display: flex; + align-items: center; + + overflow-y: auto; + border-radius: 16px; + box-shadow: var(--shadow-3); + padding: 16px 24px 48px 16px; + border: 1px solid var(--graphite-graphite-40); + } +`; + +const TitleWrapper = styled.div` + max-width: 596px; + + display: flex; + flex-direction: column; + gap: 24px; +`; + +const Title = styled.h1` + font-size: 38px; + font-weight: 600; + line-height: 48px; + white-space: pre-wrap; + font-family: Geologica; + color: var(--graphite-graphite-840); +`; + +const Annotation = styled.p` + font-size: 24px; + line-height: 43px; + font-family: Nunito; + color: var(--graphite-graphite-840); +`; + +const Image = styled.img` + overflow: hidden; + + width: 100%; + max-width: 640px; + height: auto; + + object-fit: contain; + object-position: left; +`; + +interface Props { + children: ReactNode; +} + +const LoginTemplate = (props: Props) => { + const { children } = props; + + const { i18n, t } = useTranslation('page.login', { + keyPrefix: 'login.left_block', + }); + + return ( + + + + {t('title', { company: envUtil.appName })} + {t('annotation')} + + + {t('image_alt', + + {children} + + ); +}; + +export { LoginTemplate }; diff --git a/frontend/src/modules/auth/templates/index.tsx b/frontend/src/modules/auth/templates/index.tsx new file mode 100644 index 0000000..493364f --- /dev/null +++ b/frontend/src/modules/auth/templates/index.tsx @@ -0,0 +1 @@ +export { LoginTemplate } from './LoginTemplate/LoginTemplate'; diff --git a/frontend/src/modules/automation/api/AutomationApiRoutes.ts b/frontend/src/modules/automation/api/AutomationApiRoutes.ts new file mode 100644 index 0000000..2e83348 --- /dev/null +++ b/frontend/src/modules/automation/api/AutomationApiRoutes.ts @@ -0,0 +1,7 @@ +export enum AutomationApiRoutes { + // Entity type automations + GET_ENTITY_TYPE_AUTOMATIONS = '/api/automation/entity-types', + CREATE_ENTITY_TYPE_AUTOMATION = '/api/automation/entity-types', + UPDATE_ENTITY_TYPE_AUTOMATION = '/api/automation/entity-types/:automationId', + DELETE_ENTITY_TYPE_AUTOMATION = '/api/automation/entity-types/:automationId', +} diff --git a/frontend/src/modules/automation/api/AutomationEntityTypeApi/AutomationEntityTypeApi.ts b/frontend/src/modules/automation/api/AutomationEntityTypeApi/AutomationEntityTypeApi.ts new file mode 100644 index 0000000..b4f05e2 --- /dev/null +++ b/frontend/src/modules/automation/api/AutomationEntityTypeApi/AutomationEntityTypeApi.ts @@ -0,0 +1,55 @@ +import { baseApi } from '@/app'; +import { type Nullable, UrlTemplateUtil } from '@/shared'; +import { AutomationEntityType } from '../../shared'; +import { AutomationApiRoutes } from '../AutomationApiRoutes'; +import type { CreateAutomationEntityTypeDto, UpdateAutomationEntityType } from '../dtos'; + +class AutomationEntityTypeApi { + getEntityTypeAutomations = async ({ + entityTypeId, + boardId, + }: { + entityTypeId: number; + boardId: Nullable; + }): Promise => { + const response = await baseApi.get(AutomationApiRoutes.GET_ENTITY_TYPE_AUTOMATIONS, { + params: { + entityTypeId, + boardId, + }, + }); + + return AutomationEntityType.fromDtos(response.data); + }; + + createEntityTypeAutomation = async ( + dto: CreateAutomationEntityTypeDto + ): Promise => { + const response = await baseApi.post(AutomationApiRoutes.CREATE_ENTITY_TYPE_AUTOMATION, dto); + + return AutomationEntityType.fromDto(response.data); + }; + + updateEntityTypeAutomation = async ({ + automationId, + dto, + }: { + automationId: number; + dto: UpdateAutomationEntityType; + }): Promise => { + const response = await baseApi.patch( + UrlTemplateUtil.toPath(AutomationApiRoutes.UPDATE_ENTITY_TYPE_AUTOMATION, { automationId }), + dto + ); + + return AutomationEntityType.fromDto(response.data); + }; + + deleteEntityTypeAutomation = async (automationId: number): Promise => { + await baseApi.delete( + UrlTemplateUtil.toPath(AutomationApiRoutes.DELETE_ENTITY_TYPE_AUTOMATION, { automationId }) + ); + }; +} + +export const automationEntityTypeApi = new AutomationEntityTypeApi(); diff --git a/frontend/src/modules/automation/api/dtos/AutomationEntityType/AutomationEntityTypeDto.ts b/frontend/src/modules/automation/api/dtos/AutomationEntityType/AutomationEntityTypeDto.ts new file mode 100644 index 0000000..4e98335 --- /dev/null +++ b/frontend/src/modules/automation/api/dtos/AutomationEntityType/AutomationEntityTypeDto.ts @@ -0,0 +1,16 @@ +import type { EntityTypeTrigger, Nullable } from '@/shared'; +import type { EntityTypeAction, EntityTypeCondition } from '../../../shared'; + +export interface AutomationEntityTypeDto { + id: number; + name: string; + createdAt: string; + createdBy: number; + isActive: boolean; + entityTypeId: number; + boardId?: Nullable; + stageId?: Nullable; + triggers: EntityTypeTrigger[]; + actions?: Nullable; + conditions?: Nullable; +} diff --git a/frontend/src/modules/automation/api/dtos/AutomationEntityType/CreateAutomationEntityTypeDto.ts b/frontend/src/modules/automation/api/dtos/AutomationEntityType/CreateAutomationEntityTypeDto.ts new file mode 100644 index 0000000..56b5c81 --- /dev/null +++ b/frontend/src/modules/automation/api/dtos/AutomationEntityType/CreateAutomationEntityTypeDto.ts @@ -0,0 +1,36 @@ +import type { EntityTypeTrigger, Nullable } from '@/shared'; +import type { EntityTypeAction, EntityTypeCondition } from '../../../shared'; + +export class CreateAutomationEntityTypeDto { + name: string; + isActive: boolean; + entityTypeId: number; + boardId?: Nullable; + stageId?: Nullable; + triggers: EntityTypeTrigger[]; + actions?: Nullable; + conditions?: Nullable; + applyImmediately?: boolean; + + constructor({ + name, + isActive, + entityTypeId, + boardId, + stageId, + triggers, + actions, + conditions, + applyImmediately, + }: CreateAutomationEntityTypeDto) { + this.name = name; + this.isActive = isActive; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.stageId = stageId; + this.triggers = triggers; + this.actions = actions; + this.conditions = conditions; + this.applyImmediately = applyImmediately; + } +} diff --git a/frontend/src/modules/automation/api/dtos/AutomationEntityType/UpdateAutomationEntityType.ts b/frontend/src/modules/automation/api/dtos/AutomationEntityType/UpdateAutomationEntityType.ts new file mode 100644 index 0000000..4e23c5c --- /dev/null +++ b/frontend/src/modules/automation/api/dtos/AutomationEntityType/UpdateAutomationEntityType.ts @@ -0,0 +1,36 @@ +import type { EntityTypeTrigger, Nullable } from '@/shared'; +import type { EntityTypeAction, EntityTypeCondition } from '../../../shared'; + +export class UpdateAutomationEntityType { + name?: string; + isActive?: boolean; + entityTypeId?: number; + boardId?: Nullable; + stageId?: Nullable; + triggers?: EntityTypeTrigger[]; + actions?: Nullable; + conditions?: Nullable; + applyImmediately?: boolean; + + constructor({ + name, + isActive, + entityTypeId, + boardId, + stageId, + triggers, + actions, + conditions, + applyImmediately, + }: UpdateAutomationEntityType) { + this.name = name; + this.isActive = isActive; + this.entityTypeId = entityTypeId; + this.boardId = boardId; + this.stageId = stageId; + this.triggers = triggers; + this.actions = actions; + this.conditions = conditions; + this.applyImmediately = applyImmediately; + } +} diff --git a/frontend/src/modules/automation/api/dtos/index.tsx b/frontend/src/modules/automation/api/dtos/index.tsx new file mode 100644 index 0000000..6550598 --- /dev/null +++ b/frontend/src/modules/automation/api/dtos/index.tsx @@ -0,0 +1,3 @@ +export type { AutomationEntityTypeDto } from './AutomationEntityType/AutomationEntityTypeDto'; +export { CreateAutomationEntityTypeDto } from './AutomationEntityType/CreateAutomationEntityTypeDto'; +export { UpdateAutomationEntityType } from './AutomationEntityType/UpdateAutomationEntityType'; diff --git a/frontend/src/modules/automation/api/index.tsx b/frontend/src/modules/automation/api/index.tsx new file mode 100644 index 0000000..a4d64e8 --- /dev/null +++ b/frontend/src/modules/automation/api/index.tsx @@ -0,0 +1,2 @@ +export { automationEntityTypeApi } from './AutomationEntityTypeApi/AutomationEntityTypeApi'; +export * from './dtos'; diff --git a/frontend/src/modules/automation/index.tsx b/frontend/src/modules/automation/index.tsx new file mode 100644 index 0000000..2776e43 --- /dev/null +++ b/frontend/src/modules/automation/index.tsx @@ -0,0 +1,2 @@ +export * from './shared'; +export * from './store'; diff --git a/frontend/src/modules/automation/shared/assets/activity.svg b/frontend/src/modules/automation/shared/assets/activity.svg new file mode 100644 index 0000000..6e884c7 --- /dev/null +++ b/frontend/src/modules/automation/shared/assets/activity.svg @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/frontend/src/modules/automation/shared/assets/change_of_stage.svg b/frontend/src/modules/automation/shared/assets/change_of_stage.svg new file mode 100644 index 0000000..0d8df65 --- /dev/null +++ b/frontend/src/modules/automation/shared/assets/change_of_stage.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/frontend/src/modules/automation/shared/assets/create_entity.svg b/frontend/src/modules/automation/shared/assets/create_entity.svg new file mode 100644 index 0000000..f924564 --- /dev/null +++ b/frontend/src/modules/automation/shared/assets/create_entity.svg @@ -0,0 +1,6 @@ + + + + diff --git a/frontend/src/modules/automation/shared/assets/index.tsx b/frontend/src/modules/automation/shared/assets/index.tsx new file mode 100644 index 0000000..d1fd592 --- /dev/null +++ b/frontend/src/modules/automation/shared/assets/index.tsx @@ -0,0 +1,6 @@ +export { ReactComponent as ActivityIcon } from './activity.svg'; +export { ReactComponent as ChangeStageIcon } from './change_of_stage.svg'; +export { ReactComponent as CreateEntityIcon } from './create_entity.svg'; +export { ReactComponent as MailIcon } from './mail.svg'; +export { ReactComponent as PlusIcon } from './plus.svg'; +export { ReactComponent as TaskIcon } from './task.svg'; diff --git a/frontend/src/modules/automation/shared/assets/mail.svg b/frontend/src/modules/automation/shared/assets/mail.svg new file mode 100644 index 0000000..538a3c2 --- /dev/null +++ b/frontend/src/modules/automation/shared/assets/mail.svg @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/modules/automation/shared/assets/plus.svg b/frontend/src/modules/automation/shared/assets/plus.svg new file mode 100644 index 0000000..0888b39 --- /dev/null +++ b/frontend/src/modules/automation/shared/assets/plus.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/frontend/src/modules/automation/shared/assets/task.svg b/frontend/src/modules/automation/shared/assets/task.svg new file mode 100644 index 0000000..9cdcba5 --- /dev/null +++ b/frontend/src/modules/automation/shared/assets/task.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/frontend/src/modules/automation/shared/index.tsx b/frontend/src/modules/automation/shared/index.tsx new file mode 100644 index 0000000..f41a696 --- /dev/null +++ b/frontend/src/modules/automation/shared/index.tsx @@ -0,0 +1 @@ +export * from './lib'; diff --git a/frontend/src/modules/automation/shared/lib/components/AutomationBlock/AddAutomationButton.tsx b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/AddAutomationButton.tsx new file mode 100644 index 0000000..4efc257 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/AddAutomationButton.tsx @@ -0,0 +1,51 @@ +import styled from 'styled-components'; +import { PlusIcon } from '../../../assets'; + +const Root = styled.button` + width: 24px; + height: 24px; + + display: flex; + align-items: center; + justify-content: center; + + border-radius: 50%; + border: 1px solid var(--button-text-graphite-secondary-text); + transition: var(--transition-200); + + svg path { + transition: var(--transition-200); + } + + &:hover { + cursor: pointer; + + border-color: var(--button-text-green-hover); + background-color: var(--button-text-green-hover); + + svg path { + fill: var(--primary-statuses-white-0); + } + } + + &:active { + border-color: var(--button-text-green-active); + background-color: var(--button-text-green-active); + } +`; + +interface Props { + onClick: (...args: any[]) => any; +} + +const AddAutomationButton = (props: Props) => { + const { onClick } = props; + + return ( + + + + ); +}; + +export { AddAutomationButton }; diff --git a/frontend/src/modules/automation/shared/lib/components/AutomationBlock/AutomationBlock.tsx b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/AutomationBlock.tsx new file mode 100644 index 0000000..427381b --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/AutomationBlock.tsx @@ -0,0 +1,425 @@ +import { EntityTypeActionType, type Nullable, type Option, truncateNumber } from '@/shared'; +import { useDisclosure } from '@mantine/hooks'; +import { type ReactNode, type RefObject, useCallback, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled, { css } from 'styled-components'; +import { useOnClickOutside } from 'usehooks-ts'; +import type { AutomationStore } from '../../../../store'; +import { type AutomationEntityType, type AutomationModalBaseProps } from '../../models'; +import { Block } from '../Block/Block'; +import { AddActivityAutomationModal } from '../Modals/AddActivityAutomationModal/AddActivityAutomationModal'; +import { AddTaskAutomationModal } from '../Modals/AddTaskAutomationModal/AddTaskAutomationModal'; +import { ChangeLinkedStageAutomationModal } from '../Modals/ChangeLinkedStageAutomationModal/ChangeLinkedStageAutomationModal'; +import { ChangeResponsibleAutomationModal } from '../Modals/ChangeResponsibleAutomationModal/ChangeResponsibleAutomationModal'; +import { ChangeStageAutomationModal } from '../Modals/ChangeStageAutomationModal/ChangeStageAutomationModal'; +import { CreateEntityAutomationModal } from '../Modals/CreateEntityAutomationModal/CreateEntityAutomationModal'; +import { RequestHttpAutomationModal } from '../Modals/RequestHttpAutomationModal/RequestHttpAutomationModal'; +import { SendEmailAutomationModal } from '../Modals/SendEmailAutomationModal/SendEmailAutomationModal'; +import { SendExternalChatAutomationModal } from '../Modals/SendExternalChatAutomationModal/SendExternalChatAutomationModal'; +import { SendInternalChatAutomationModal } from '../Modals/SendInternalChatAutomationModal/SendInternalChatAutomationModal'; +import { AddAutomationButton } from './AddAutomationButton'; +import { HeaderDropdown } from './HeaderDropdown'; +import { HeaderDropdownList } from './HeaderDropdownList'; + +const Root = styled(Block)` + height: 63px; + + display: flex; + flex-direction: column; +`; + +interface CountProps { + $active: boolean; + $activeBgColor: string; +} + +const Count = styled.div` + height: 16px; + min-width: 21px; + width: fit-content; + + display: flex; + align-items: center; + justify-content: center; + + font-size: 12px; + font-weight: 600; + line-height: 14px; + color: var(--primary-statuses-white-0); + + padding: 0 4px; + border-radius: var(--border-radius-element); + border: 1px solid var(--primary-statuses-white-0); + transition: var(--transition-200); + + ${p => + p.$active && + css` + background-color: var(--primary-statuses-white-0); + color: ${p.$activeBgColor}; + `} +`; + +interface HeaderProps { + $bgColor: string; + $hasShadow: boolean; +} + +const Header = styled.div` + position: relative; + + width: 100%; + height: 32px; + + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + + color: var(--primary-statuses-white-0); + + padding: 6px 8px 4px; + background-color: ${p => p.$bgColor}; + border-top-left-radius: var(--border-radius-block); + border-top-right-radius: var(--border-radius-block); + box-shadow: ${p => (p.$hasShadow ? '0px 0px 2px #eef4fe, 0px 1px 2px #d0daeb' : 'none')}; + transition: var(--transition-200); + + &:hover { + cursor: pointer; + + ${Count} { + color: ${p => p.$bgColor}; + + background-color: var(--primary-statuses-white-0); + } + } +`; + +const Content = styled.div` + margin: auto; +`; + +interface Props { + stageId: Nullable; + headerColor: string; + type: EntityTypeActionType; + automationStore: AutomationStore; + automations: AutomationEntityType[]; +} + +const AutomationBlock = (props: Props) => { + const { stageId, headerColor, type, automationStore, automations } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.block', + }); + + const headerRef = useRef(null); + const dropdownRef = useRef(null); + + const [selectedAutomation, setSelectedAutomation] = + useState>(null); + + const [addActivityModalOpened, { close: hideAddActivityModal, open: showAddActivityModal }] = + useDisclosure(false); + const [addTaskModalOpened, { close: hideAddTaskModal, open: showAddTaskModal }] = + useDisclosure(false); + const [changeStageModalOpened, { close: hideChangeStageModal, open: showChangeStageModal }] = + useDisclosure(false); + const [ + changeLinkedStageModalOpened, + { close: hideChangeLinkedStageModal, open: showChangeLinkedStageModal }, + ] = useDisclosure(false); + const [ + changeResponsibleModalOpened, + { close: hideChangeResponsibleModal, open: showChangeResponsibleModal }, + ] = useDisclosure(false); + const [sendEmailModalOpened, { close: hideSendEmailModal, open: showSendEmailModal }] = + useDisclosure(false); + const [createEntityModalOpened, { close: hideCreateEntityModal, open: showCreateEntityModal }] = + useDisclosure(false); + const [ + sendInternalChatModalOpened, + { close: hideSendInternalChatModal, open: showSendInternalChatModal }, + ] = useDisclosure(false); + const [ + sendExternalChatModalOpened, + { close: hideSendExternalChatModal, open: showSendExternalChatModal }, + ] = useDisclosure(false); + const [requestHttpModalOpened, { close: hideRequestHttpModal, open: showRequestHttpModal }] = + useDisclosure(false); + + const [dropdownOpened, { toggle: toggleDropdown, close: hideDropdown }] = useDisclosure(false); + + useOnClickOutside(dropdownRef as RefObject, e => { + if (headerRef.current?.contains(e.target as Node)) return; + + hideDropdown(); + }); + + const automationOptions = useMemo[]>( + () => + automations.map>(a => ({ + label: a.name, + value: a.id, + })), + [automations] + ); + + const handleOpenModal = useCallback(() => { + switch (type) { + case EntityTypeActionType.ACTIVITY_CREATE: { + showAddActivityModal(); + + break; + } + + case EntityTypeActionType.TASK_CREATE: { + showAddTaskModal(); + + break; + } + + case EntityTypeActionType.ENTITY_STAGE_CHANGE: { + showChangeStageModal(); + + break; + } + + case EntityTypeActionType.ENTITY_LINKED_STAGE_CHANGE: { + showChangeLinkedStageModal(); + + break; + } + + case EntityTypeActionType.ENTITY_RESPONSIBLE_CHANGE: { + showChangeResponsibleModal(); + + break; + } + + case EntityTypeActionType.EMAIL_SEND: { + showSendEmailModal(); + + break; + } + + case EntityTypeActionType.ENTITY_CREATE: { + showCreateEntityModal(); + + break; + } + + case EntityTypeActionType.CHAT_SEND_AMWORK: { + showSendInternalChatModal(); + + break; + } + + case EntityTypeActionType.CHAT_SEND_EXTERNAL: { + showSendExternalChatModal(); + + break; + } + + case EntityTypeActionType.HTTP_CALL: { + showRequestHttpModal(); + + break; + } + } + }, [ + type, + showAddTaskModal, + showSendEmailModal, + showAddActivityModal, + showChangeStageModal, + showRequestHttpModal, + showCreateEntityModal, + showSendInternalChatModal, + showSendExternalChatModal, + showChangeResponsibleModal, + showChangeLinkedStageModal, + ]); + + const handleSelectAutomation = useCallback( + (id: number) => { + const automation = automations.find(a => a.id === id); + + if (!automation) return; + + hideDropdown(); + setSelectedAutomation(automation); + }, + [automations, hideDropdown, setSelectedAutomation] + ); + + const handleResetSelectedAutomation = useCallback(() => setSelectedAutomation(null), []); + + const getSelectedAutomationModal = useCallback( + (selectedAutomation: AutomationEntityType): ReactNode => { + const commonProps: AutomationModalBaseProps = { + stageId, + automationStore, + automation: selectedAutomation, + isOpened: Boolean(selectedAutomation), + onClose: handleResetSelectedAutomation, + }; + + switch (selectedAutomation.firstAction.type) { + case EntityTypeActionType.ACTIVITY_CREATE: + return ; + + case EntityTypeActionType.TASK_CREATE: + return ; + + case EntityTypeActionType.ENTITY_STAGE_CHANGE: + return ; + + case EntityTypeActionType.ENTITY_LINKED_STAGE_CHANGE: + return ; + + case EntityTypeActionType.ENTITY_RESPONSIBLE_CHANGE: + return ; + + case EntityTypeActionType.EMAIL_SEND: + return ; + + case EntityTypeActionType.ENTITY_CREATE: + return ; + + case EntityTypeActionType.CHAT_SEND_AMWORK: + return ; + + case EntityTypeActionType.CHAT_SEND_EXTERNAL: + return ; + + case EntityTypeActionType.HTTP_CALL: + return ; + } + }, + [stageId, automationStore, handleResetSelectedAutomation] + ); + + return ( + + {automations.length > 0 && ( +
+ {t(type)} + + + {truncateNumber({ num: automations.length, precision: 3 })} + + + + + +
+ )} + + + + + + {selectedAutomation && getSelectedAutomationModal(selectedAutomation)} + + {addActivityModalOpened && ( + + )} + + {addTaskModalOpened && ( + + )} + + {changeStageModalOpened && ( + + )} + + {changeLinkedStageModalOpened && ( + + )} + + {changeResponsibleModalOpened && ( + + )} + + {sendEmailModalOpened && ( + + )} + + {createEntityModalOpened && ( + + )} + + {sendInternalChatModalOpened && ( + + )} + + {sendExternalChatModalOpened && ( + + )} + + {requestHttpModalOpened && ( + + )} +
+ ); +}; + +export { AutomationBlock }; diff --git a/frontend/src/modules/automation/shared/lib/components/AutomationBlock/HeaderDropdown.tsx b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/HeaderDropdown.tsx new file mode 100644 index 0000000..d63d8dc --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/HeaderDropdown.tsx @@ -0,0 +1,64 @@ +import { type MouseEvent, type ReactNode, type Ref } from 'react'; +import styled from 'styled-components'; + +const Root = styled.div<{ $active: boolean }>` + position: absolute; + top: 100%; + left: -2px; + + width: calc(100% + 4px); + + display: flex; + align-items: center; + justify-content: center; + + z-index: 10; + overflow: hidden; + + padding: 0 2px 4px; + + pointer-events: ${p => (p.$active ? 'all' : 'none')}; + scale: ${p => (p.$active ? 1 : 0)}; +`; + +const Menu = styled.button<{ $active: boolean }>` + width: 100%; + + display: block; + + overflow: hidden; + + box-shadow: + 0px 0px 2px #eef4fe, + 0px 1px 2px #d0daeb; + background: var(--primary-statuses-white-0); + border-bottom-left-radius: var(--border-radius-block); + border-bottom-right-radius: var(--border-radius-block); + + transform: ${p => (p.$active ? 'translateY(0%)' : 'translateY(-102%)')}; + transition: 350ms ease-out; +`; + +interface Props { + ref?: Ref; + opened: boolean; + children: ReactNode; +} + +const HeaderDropdown = (props: Props) => { + const { ref, opened, children } = props; + + const handleStopPropagation = (e: MouseEvent) => { + e.stopPropagation(); + }; + + return ( + + + {children} + + + ); +}; + +export { HeaderDropdown }; diff --git a/frontend/src/modules/automation/shared/lib/components/AutomationBlock/HeaderDropdownList.tsx b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/HeaderDropdownList.tsx new file mode 100644 index 0000000..9861ef8 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/AutomationBlock/HeaderDropdownList.tsx @@ -0,0 +1,34 @@ +import { DropdownListItem, SpanWithEllipsis, type Option } from '@/shared'; +import { memo, useCallback } from 'react'; +import styled from 'styled-components'; + +const Root = styled.div` + display: flex; + flex-direction: column; + + padding: 4px 0; +`; + +interface Props { + options: Option[]; + onSelect: (id: number) => void; +} + +const HeaderDropdownList = memo((props: Props) => { + const { options, onSelect } = props; + + const getSelectHandler = useCallback((id: number) => () => onSelect(id), [onSelect]); + + return ( + + {options.map(o => ( + + + + ))} + + ); +}); + +HeaderDropdownList.displayName = 'HeaderDropdownList'; +export { HeaderDropdownList }; diff --git a/frontend/src/modules/automation/shared/lib/components/Block/Block.tsx b/frontend/src/modules/automation/shared/lib/components/Block/Block.tsx new file mode 100644 index 0000000..3169768 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Block/Block.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +export const Block = styled.div<{ $noShadow?: boolean }>` + font-size: 14px; + font-weight: 600; + line-height: 20px; + color: var(--button-text-graphite-priory-text); + + border-radius: var(--border-radius-block); + background: var(--primary-statuses-white-0); + box-shadow: ${p => (p.$noShadow ? 'none' : '0px 0px 2px #eef4fe, 0px 1px 2px #d0daeb')}; +`; diff --git a/frontend/src/modules/automation/shared/lib/components/ExternalChatProvidersSelect/ExternalChatProvidersSelect.tsx b/frontend/src/modules/automation/shared/lib/components/ExternalChatProvidersSelect/ExternalChatProvidersSelect.tsx new file mode 100644 index 0000000..622e550 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/ExternalChatProvidersSelect/ExternalChatProvidersSelect.tsx @@ -0,0 +1,75 @@ +import { routes } from '@/app'; +import { authStore } from '@/modules/auth'; +import { ChatProviderTransport, useGetChatProviders } from '@/modules/multichat'; +import { AddPlaceholderTemplate } from '@/modules/products'; +import { MySelect, NoOptionsMessage, type Option, type SelectModel } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +interface Props { + model: SelectModel; + setPhoneNumbersAvailable: (value: boolean) => void; +} + +const ExternalChatProvidersSelect = observer((props: Props) => { + const { model, setPhoneNumbersAvailable } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.external_providers_select', + }); + + const { data: providers } = useGetChatProviders(); + + const { user: currentUser } = authStore; + + const providerOptions = useMemo[]>( + () => + providers + ?.filter(p => p.transport !== ChatProviderTransport.AMWORK) + .map(p => ({ + label: p.title, + value: p.id, + })) ?? [], + [providers] + ); + + const onSelectProvider = useCallback( + (providerId: Option) => { + const selectedTransport = providers?.find(p => p.id === providerId.value)?.transport ?? ''; + + if ( + ![ChatProviderTransport.WHATSAPP, ChatProviderTransport.TELEGRAM].includes( + selectedTransport + ) + ) { + setPhoneNumbersAvailable(false); + } else { + setPhoneNumbersAvailable(true); + } + }, + [providers, setPhoneNumbersAvailable] + ); + + return ( + + ) : ( + {t('no_available_providers')} + ) + } + /> + ); +}); + +ExternalChatProvidersSelect.displayName = 'ExternalChatProvidersSelect'; +export { ExternalChatProvidersSelect }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/AddActivityAutomationModal/AddActivityAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/AddActivityAutomationModal/AddActivityAutomationModal.tsx new file mode 100644 index 0000000..801f84b --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/AddActivityAutomationModal/AddActivityAutomationModal.tsx @@ -0,0 +1,102 @@ +import { + EntityTypeActionType, + InputModel, + NumberModel, + SelectModel, + UuidUtil, + validateForm, + type Optional, +} from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + AutomationEntityTypeDeadline, + AutomationEntityTypeTemplateFormData, + DeadlineType, + type ActionActivityCreateSettings, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + AddActivityAutomationModalContent, + type AddActivityAutomationModalContentForm, +} from './AddActivityAutomationModalContent'; + +const AddActivityAutomationModal = observer((props: AutomationModalBaseProps) => { + const { stageId, isOpened, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.add_activity_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('activity_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + text: InputModel.create(settings?.text), + deferStart: NumberModel.create(settings?.deferStart ?? null), + activityTypeId: SelectModel.create(settings?.activityTypeId).required(), + responsibleUserId: SelectModel.create(settings?.responsibleUserId ?? null), + deadline: new AutomationEntityTypeDeadline({ + time: settings?.deadlineTime ?? null, + type: settings?.deadlineType ?? DeadlineType.IN_ONE_DAY, + }), + })); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const createActivityActionSettings: ActionActivityCreateSettings = { + text: form.text.trimmedValue, + deadlineTime: form.deadline.time, + deadlineType: form.deadline.type, + deferStart: form.deferStart.value, + activityTypeId: form.activityTypeId.value, + responsibleUserId: form.responsibleUserId.value, + }; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: createActivityActionSettings, + type: EntityTypeActionType.ACTIVITY_CREATE, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +AddActivityAutomationModal.displayName = 'AddActivityAutomationModal'; +export { AddActivityAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/AddActivityAutomationModal/AddActivityAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/AddActivityAutomationModal/AddActivityAutomationModalContent.tsx new file mode 100644 index 0000000..91640b2 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/AddActivityAutomationModal/AddActivityAutomationModalContent.tsx @@ -0,0 +1,96 @@ +import { userStore } from '@/app'; +import { activityTypeStore } from '@/modules/tasks'; +import type { Option } from '@/shared'; +import { + MySelect, + MyUsersSelect, + type InputModel, + type NumberModel, + type SelectModel, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { AutomationEntityTypeDeadline } from '../../../models'; +import { + AutomationFormItem, + DeferStartSelect, + DescriptionBlock, + DueDateSelect, + WrapperWithLeftOffset, +} from '../components'; + +export interface AddActivityAutomationModalContentForm { + text: InputModel; + deferStart: NumberModel; + activityTypeId: SelectModel; + responsibleUserId: SelectModel; + deadline: AutomationEntityTypeDeadline; +} + +interface Props { + form: AddActivityAutomationModalContentForm; +} + +const AddActivityAutomationModalContent = observer((props: Props) => { + const { form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.add_activity_automation_modal', + }); + + const changeDeadline = useCallback( + (deadline: AutomationEntityTypeDeadline) => (form.deadline = deadline), + [form] + ); + + const activityTypeOptions = useMemo[]>( + () => + activityTypeStore.activeActivityTypes.map>(a => ({ + label: a.name, + value: a.id, + })), + [] + ); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}); + +AddActivityAutomationModalContent.displayName = 'AddActivityAutomationModalContent'; +export { AddActivityAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/AddTaskAutomationModal/AddTaskAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/AddTaskAutomationModal/AddTaskAutomationModal.tsx new file mode 100644 index 0000000..2ec3fb4 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/AddTaskAutomationModal/AddTaskAutomationModal.tsx @@ -0,0 +1,102 @@ +import { + EntityTypeActionType, + InputModel, + NumberModel, + SelectModel, + UuidUtil, + validateForm, + type Optional, +} from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + AutomationEntityTypeDeadline, + AutomationEntityTypeTemplateFormData, + DeadlineType, + type ActionTaskCreateSettings, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + AddTaskAutomationModalContent, + type AddTaskAutomationModalContentForm, +} from './AddTaskAutomationModalContent'; + +const AddTaskAutomationModal = observer((props: AutomationModalBaseProps) => { + const { stageId, isOpened, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.add_task_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('task_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + text: InputModel.create(settings?.text), + title: InputModel.create(settings?.title).required(), + responsibleUserId: SelectModel.create(settings?.responsibleUserId ?? null), + deferStart: NumberModel.create(settings?.deferStart ?? null), + deadline: new AutomationEntityTypeDeadline({ + time: settings?.deadlineTime ?? null, + type: settings?.deadlineType ?? DeadlineType.IN_ONE_DAY, + }), + })); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const createTaskActionSettings: ActionTaskCreateSettings = { + text: form.text.trimmedValue, + title: form.title.trimmedValue, + deadlineType: form.deadline.type, + deadlineTime: form.deadline.time, + deferStart: form.deferStart.value, + responsibleUserId: form.responsibleUserId.value, + }; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: createTaskActionSettings, + type: EntityTypeActionType.TASK_CREATE, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +AddTaskAutomationModal.displayName = 'AddTaskAutomationModal'; +export { AddTaskAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/AddTaskAutomationModal/AddTaskAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/AddTaskAutomationModal/AddTaskAutomationModalContent.tsx new file mode 100644 index 0000000..ae14c25 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/AddTaskAutomationModal/AddTaskAutomationModalContent.tsx @@ -0,0 +1,81 @@ +import { userStore } from '@/app'; +import { + MyInput, + MyUsersSelect, + type InputModel, + type NumberModel, + type SelectModel, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { AutomationEntityTypeDeadline } from '../../../models'; +import { + AutomationFormItem, + DeferStartSelect, + DescriptionBlock, + DueDateSelect, + WrapperWithLeftOffset, +} from '../components'; + +export interface AddTaskAutomationModalContentForm { + text: InputModel; + title: InputModel; + deferStart: NumberModel; + responsibleUserId: SelectModel; + deadline: AutomationEntityTypeDeadline; +} + +interface Props { + form: AddTaskAutomationModalContentForm; +} + +const AddTaskAutomationModalContent = observer((props: Props) => { + const { form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.add_task_automation_modal', + }); + + const handleChangeDeadline = useCallback( + (deadline: AutomationEntityTypeDeadline) => (form.deadline = deadline), + [form] + ); + + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}); + +AddTaskAutomationModalContent.displayName = 'AddTaskAutomationModalContent'; +export { AddTaskAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/AutomationModalTemplate.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/AutomationModalTemplate.tsx new file mode 100644 index 0000000..6042843 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/AutomationModalTemplate.tsx @@ -0,0 +1,312 @@ +import { entityTypeStore } from '@/app'; +import { + DelaySelect, + DeleteButton, + DialogModalSecondary, + Hint, + MyCheckboxWithBooleanModel, + MyInput, + MyRadio, + MySwitchWithModel, + validateForm, + type Nullable, +} from '@/shared'; +import autoAnimate from '@formkit/auto-animate'; +import { useDisclosure } from '@mantine/hooks'; +import { observer } from 'mobx-react-lite'; +import { + useCallback, + useEffect, + useRef, + useState, + type CSSProperties, + type ReactNode, +} from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import type { AutomationStore } from '../../../../store'; +import { useGetAutomationDelayOptions } from '../../hooks'; +import type { AutomationEntityType, AutomationEntityTypeTemplateFormData } from '../../models'; +import { + AutomationFormItem, + ConditionsBlock, + DeleteAutomationModal, + TriggerSelect, + WrapperWithLeftOffset, +} from './components'; + +const Root = styled.div` + display: flex; + flex-direction: column; + gap: 24px; + + padding: 24px 32px; +`; + +const HeaderWrapper = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const Delimiter = styled.div` + height: 1px; + width: 100%; + + background-color: var(--graphite-graphite-80); +`; + +const AutomationControlBlock = styled.div` + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +`; + +const AutomationControlTitleWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +`; + +const AutomationControlTitle = styled.span` + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: var(--button-text-graphite-priory-text); +`; + +const AutomationControlSubtitle = styled.div<{ $paddingLeft?: CSSProperties['paddingLeft'] }>` + line-height: 1; + font-size: 10px; + font-weight: 400; + color: var(--button-text-graphite-secondary-text); + + padding-left: ${p => p.$paddingLeft}; +`; + +const DelayItemsWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const NarrowBlock = styled.div` + display: flex; + flex-direction: column; + gap: 6px; +`; + +const ControlWrapper = styled.label` + display: flex; + align-items: center; + gap: 8px; +`; + +const ControlText = styled.p` + font-size: 14px; + font-weight: 400; + line-height: 16px; + color: var(--button-text-graphite-priory-text); +`; + +const DeleteButtonWrapper = styled.div` + margin-right: auto; +`; + +interface Props { + title: string; + isOpened: boolean; + children: ReactNode; + automationStore: AutomationStore; + templateFormData: AutomationEntityTypeTemplateFormData; + hint?: string; + width?: string; + maxHeight?: string; + automation?: Nullable; + onClose: () => void; + onSave: (templateFormData: AutomationEntityTypeTemplateFormData) => Promise; +} + +const AutomationModalTemplate = observer((props: Props) => { + const { + title, + isOpened, + children, + automationStore, + templateFormData, + hint, + width = '592px', + automation = null, + maxHeight = '720px', + onClose, + onSave, + } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.automation_modal_template', + }); + + const delayStageRef = useRef(null); + + useEffect(() => { + delayStageRef.current && autoAnimate(delayStageRef.current); + }, [delayStageRef]); + + const isEditMode = Boolean(automation); + + const [isSaving, setIsSaving] = useState(false); + const [isDeleteModalOpened, { close: hideDeleteModal, open: showDeleteModal }] = + useDisclosure(false); + + const delayOptions = useGetAutomationDelayOptions(); + + const handleSave = useCallback(async (): Promise => { + if (!validateForm(templateFormData)) return; + + try { + setIsSaving(true); + + await onSave(templateFormData); + } catch (e) { + if (automation) { + throw new Error( + `Failed to save automation ${automation.id} with name ${templateFormData.name.trimmedValue}: ${e}` + ); + } else { + throw new Error( + `Failed to create automation with name ${templateFormData.name.trimmedValue}: ${e}` + ); + } + } finally { + setIsSaving(false); + } + }, [templateFormData, automation, onSave]); + + const handleDelete = useCallback(async (): Promise => { + if (!automation) throw new Error('Failed to delete automation: automation does not exist'); + + await automationStore.delete(automation.id); + + onClose(); + hideDeleteModal(); + }, [automation, automationStore, onClose, hideDeleteModal]); + + const entityType = entityTypeStore.getById(automationStore.entityTypeId); + + const isListAutomation = automationStore.boardId === null; + + return ( + + {title} {hint && } + + } + LeftControls={ + isEditMode && ( + + + + ) + } + onClose={onClose} + onApprove={handleSave} + > + + + + + + + + + + + + + + + + + + + + +
+ {Boolean(templateFormData.delay && templateFormData.delay > 0 && !isListAutomation) && ( + + + + + {t('delay_one_stage_title')} + + + + + + + + {t('delay_all_stages_title')} + + + + + )} +
+
+ + {children} + + + + {t('enable')} + {t('enable_annotation')} + + + + + + + + + + + + + {isListAutomation ? t('apply_trigger_list') : t('apply_trigger')} + + + + + {isListAutomation ? t('apply_trigger_hint_list') : t('apply_trigger_hint')} + + +
+ + {isDeleteModalOpened && ( + + )} +
+ ); +}); + +AutomationModalTemplate.displayName = 'AutomationModalTemplate'; +export { AutomationModalTemplate }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/ChangeLinkedStageAutomationModal/ChangeLinkedStageAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeLinkedStageAutomationModal/ChangeLinkedStageAutomationModal.tsx new file mode 100644 index 0000000..d462386 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeLinkedStageAutomationModal/ChangeLinkedStageAutomationModal.tsx @@ -0,0 +1,96 @@ +import { + ChangeStageType, + EntityTypeActionType, + InputModel, + SelectModel, + UuidUtil, + validateForm, + type Optional, +} from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + AutomationEntityTypeTemplateFormData, + type ActionEntityLinkedStageChangeSettings, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + ChangeLinkedStageAutomationModalContent, + type ChangeLinkedStageAutomationModalContentForm, +} from './ChangeLinkedStageAutomationModalContent'; + +const ChangeLinkedStageAutomationModal = observer((props: AutomationModalBaseProps) => { + const { stageId, isOpened, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.change_linked_stage_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('change_stage_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + stageId: SelectModel.create(settings?.stageId).required(), + entityTypeId: SelectModel.create(settings?.entityTypeId).required(), + operationType: InputModel.create(settings?.operationType ?? ChangeStageType.MOVE).required(), + })); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const changeLinkedStageActionSettings: ActionEntityLinkedStageChangeSettings = { + stageId: form.stageId.value, + entityTypeId: form.entityTypeId.value, + operationType: form.operationType.value as ChangeStageType, + }; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: changeLinkedStageActionSettings, + type: EntityTypeActionType.ENTITY_LINKED_STAGE_CHANGE, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +ChangeLinkedStageAutomationModal.displayName = 'ChangeLinkedStageAutomationModal'; +export { ChangeLinkedStageAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/ChangeLinkedStageAutomationModal/ChangeLinkedStageAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeLinkedStageAutomationModal/ChangeLinkedStageAutomationModalContent.tsx new file mode 100644 index 0000000..4f83d24 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeLinkedStageAutomationModal/ChangeLinkedStageAutomationModalContent.tsx @@ -0,0 +1,142 @@ +import { boardApiUtil, entityTypeStore } from '@/app'; +import { + ChangeStageType, + InputModel, + MyRadio, + MySelect, + Option, + SelectModel, + StagesSelect, +} from '@/shared'; +import autoAnimate from '@formkit/auto-animate'; +import { observer } from 'mobx-react-lite'; +import { useEffect, useMemo, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { AutomationFormItem, WrapperWithLeftOffset } from '../components'; + +const RadioWrapper = styled.label` + display: flex; + gap: 8px; + + font-size: 14px; + font-weight: 400; + line-height: 16px; + color: var(--button-text-graphite-priory-text); +`; + +const SelectsWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const BoardLabel = styled.div` + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: var(--button-text-graphite-priory-text); +`; + +export interface ChangeLinkedStageAutomationModalContentForm { + stageId: SelectModel; + entityTypeId: SelectModel; + operationType: InputModel; +} + +interface Props { + entityTypeId: number; + form: ChangeLinkedStageAutomationModalContentForm; +} + +const ChangeLinkedStageAutomationModalContent = observer((props: Props) => { + const { entityTypeId, form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.change_linked_stage_automation_modal', + }); + + const ref = useRef(null); + + useEffect(() => { + ref.current && autoAnimate(ref.current); + }, [ref]); + + const entityType = entityTypeStore.getById(entityTypeId); + + const { data: boards, isLoading } = boardApiUtil.useGetBoardsByEntityTypeId({ + entityTypeId: form.entityTypeId.value, + }); + + const entityTypes = useMemo( + () => + entityTypeStore.entityTypesOptions.filter(et => + entityType.linkedEntityTypes.map(le => le.targetId).includes(et.value) + ), + [entityType] + ); + + const changeStageTypesOptions = useMemo[]>( + () => [ + { + label: t('automation_copy.move_label'), + value: ChangeStageType.MOVE, + }, + { + label: t('automation_copy.copy_original_label'), + value: ChangeStageType.COPY_ORIGINAL, + }, + { + label: t('automation_copy.copy_new_label'), + value: ChangeStageType.COPY_NEW, + }, + ], + [t] + ); + + return ( + + + + + + + +
+ {(boards && boards.length > 0) || isLoading ? ( + + + + ) : form.entityTypeId.value ? ( + {t('no_stages')} + ) : null} +
+
+ + + {changeStageTypesOptions.map(o => ( + + + + {o.label} + + ))} + +
+
+ ); +}); + +ChangeLinkedStageAutomationModalContent.displayName = 'ChangeLinkedStageAutomationModalContent'; +export { ChangeLinkedStageAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/ChangeResponsibleAutomationModal/ChangeResponsibleAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeResponsibleAutomationModal/ChangeResponsibleAutomationModal.tsx new file mode 100644 index 0000000..5ea1e2d --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeResponsibleAutomationModal/ChangeResponsibleAutomationModal.tsx @@ -0,0 +1,81 @@ +import { EntityTypeActionType, SelectModel, UuidUtil, validateForm, type Optional } from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + AutomationEntityTypeTemplateFormData, + type ActionEntityResponsibleChangeSettings, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + ChangeResponsibleAutomationModalContent, + type ChangeResponsibleAutomationModalContentForm, +} from './ChangeResponsibleAutomationModalContent'; + +const ChangeResponsibleAutomationModal = observer((props: AutomationModalBaseProps) => { + const { stageId, isOpened, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.change_responsible_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('change_responsible_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + responsibleUserId: SelectModel.create(settings?.responsibleUserId).required(), + })); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const changeStageActionSettings: ActionEntityResponsibleChangeSettings = { + responsibleUserId: form.responsibleUserId.value, + }; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: changeStageActionSettings, + type: EntityTypeActionType.ENTITY_RESPONSIBLE_CHANGE, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +ChangeResponsibleAutomationModal.displayName = 'ChangeResponsibleAutomationModal'; +export { ChangeResponsibleAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/ChangeResponsibleAutomationModal/ChangeResponsibleAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeResponsibleAutomationModal/ChangeResponsibleAutomationModalContent.tsx new file mode 100644 index 0000000..2702d75 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeResponsibleAutomationModal/ChangeResponsibleAutomationModalContent.tsx @@ -0,0 +1,37 @@ +import { userStore } from '@/app'; +import type { SelectModel } from '@/shared'; +import { MyUsersSelect } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import { AutomationFormItem } from '../components'; + +export interface ChangeResponsibleAutomationModalContentForm { + responsibleUserId: SelectModel; +} + +interface Props { + form: ChangeResponsibleAutomationModalContentForm; +} + +const ChangeResponsibleAutomationModalContent = observer((props: Props) => { + const { form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.change_responsible_automation_modal', + }); + + return ( + + + + ); +}); + +ChangeResponsibleAutomationModalContent.displayName = 'ChangeResponsibleAutomationModalContent'; +export { ChangeResponsibleAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/ChangeStageAutomationModal/ChangeStageAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeStageAutomationModal/ChangeStageAutomationModal.tsx new file mode 100644 index 0000000..d1d3a56 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeStageAutomationModal/ChangeStageAutomationModal.tsx @@ -0,0 +1,91 @@ +import { + ChangeStageType, + EntityTypeActionType, + InputModel, + SelectModel, + UuidUtil, + validateForm, + type Optional, +} from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + AutomationEntityTypeTemplateFormData, + type ActionEntityStageChangeSettings, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + ChangeStageAutomationModalContent, + type ChangeStageAutomationModalContentForm, +} from './ChangeStageAutomationModalContent'; + +const ChangeStageAutomationModal = observer((props: AutomationModalBaseProps) => { + const { stageId, isOpened, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.change_stage_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('change_stage_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + stageId: SelectModel.create(settings?.stageId).required(), + operationType: InputModel.create(settings?.operationType ?? ChangeStageType.MOVE).required(), + })); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const changeStageActionSettings: ActionEntityStageChangeSettings = { + stageId: form.stageId.value, + operationType: form.operationType.value as ChangeStageType, + }; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: changeStageActionSettings, + type: EntityTypeActionType.ENTITY_STAGE_CHANGE, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +ChangeStageAutomationModal.displayName = 'ChangeStageAutomationModal'; +export { ChangeStageAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/ChangeStageAutomationModal/ChangeStageAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeStageAutomationModal/ChangeStageAutomationModalContent.tsx new file mode 100644 index 0000000..02b98f3 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/ChangeStageAutomationModal/ChangeStageAutomationModalContent.tsx @@ -0,0 +1,93 @@ +import { entityTypeStore } from '@/app'; +import type { InputModel, Option, SelectModel } from '@/shared'; +import { ChangeStageType, MyRadio, StagesSelect } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { AutomationFormItem, WrapperWithLeftOffset } from '../components'; + +const RadioWrapper = styled.label` + display: flex; + gap: 8px; + + font-size: 14px; + font-weight: 400; + line-height: 16px; + color: var(--button-text-graphite-priory-text); +`; + +const SelectsWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export interface ChangeStageAutomationModalContentForm { + stageId: SelectModel; + operationType: InputModel; +} + +interface Props { + entityTypeId: number; + form: ChangeStageAutomationModalContentForm; +} + +const ChangeStageAutomationModalContent = observer((props: Props) => { + const { entityTypeId, form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.change_stage_automation_modal', + }); + + const entityType = entityTypeStore.getById(entityTypeId); + + const changeStageTypesOptions = useMemo[]>( + () => [ + { + label: t('automation_copy.move_label'), + value: ChangeStageType.MOVE, + }, + { + label: t('automation_copy.copy_original_label'), + value: ChangeStageType.COPY_ORIGINAL, + }, + { + label: t('automation_copy.copy_new_label'), + value: ChangeStageType.COPY_NEW, + }, + ], + [t] + ); + + return ( + + + + + + + + + + {changeStageTypesOptions.map(o => ( + + + + {o.label} + + ))} + + + + ); +}); + +ChangeStageAutomationModalContent.displayName = 'ChangeStageAutomationModalContent'; +export { ChangeStageAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/CreateEntityAutomationModal/CreateEntityAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/CreateEntityAutomationModal/CreateEntityAutomationModal.tsx new file mode 100644 index 0000000..6555905 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/CreateEntityAutomationModal/CreateEntityAutomationModal.tsx @@ -0,0 +1,102 @@ +import type { AutomationModalBaseProps } from '@/modules/automation'; +import { + EntityTypeActionType, + InputModel, + SelectModel, + UuidUtil, + validateForm, + type Optional, +} from '@/shared'; +import { useDidUpdate } from '@mantine/hooks'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + AutomationEntityTypeTemplateFormData, + type ActionEntityCreateSettings, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + CreateEntityAutomationModalContent, + type CreateEntityAutomationModalContentForm, +} from './CreateEntityAutomationModalContent'; + +const CreateEntityAutomationModal = observer((props: AutomationModalBaseProps) => { + const { isOpened, stageId, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.create_entity_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('create_entity_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + entityTypeId: SelectModel.create(settings?.entityTypeId).required(), + boardId: SelectModel.create(settings?.boardId), + stageId: SelectModel.create(settings?.stageId), + ownerId: SelectModel.create(settings?.ownerId ?? null), + name: InputModel.create(settings?.name), + })); + + useDidUpdate(() => { + form.boardId.resetValue(); + form.stageId.resetValue(); + }, [form.entityTypeId.value]); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const createLinkedEntityActionSettings: ActionEntityCreateSettings = { + entityTypeId: form.entityTypeId.value, + boardId: form.boardId.value, + stageId: form.stageId.value, + ownerId: form.ownerId.value, + name: form.name.trimmedValue, + }; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: createLinkedEntityActionSettings, + type: EntityTypeActionType.ENTITY_CREATE, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +CreateEntityAutomationModal.displayName = 'CreateEntityAutomationModal'; +export { CreateEntityAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/CreateEntityAutomationModal/CreateEntityAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/CreateEntityAutomationModal/CreateEntityAutomationModalContent.tsx new file mode 100644 index 0000000..bb9149d --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/CreateEntityAutomationModal/CreateEntityAutomationModalContent.tsx @@ -0,0 +1,92 @@ +import { boardApiUtil, entityTypeStore, userStore } from '@/app'; +import { + type InputModel, + MyInput, + MySelect, + MyUsersSelect, + type SelectModel, + StagesSelect, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { AutomationFormItem, WrapperWithLeftOffset } from '../components'; + +export interface CreateEntityAutomationModalContentForm { + entityTypeId: SelectModel; + boardId: SelectModel; + stageId: SelectModel; + ownerId: SelectModel; + name: InputModel; +} + +interface Props { + entityTypeId: number; + form: CreateEntityAutomationModalContentForm; +} + +const CreateEntityAutomationModalContent = observer((props: Props) => { + const { entityTypeId, form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.create_entity_automation_modal', + }); + + const { data: boards } = boardApiUtil.useGetBoardsByEntityTypeId({ + entityTypeId: form.entityTypeId.value, + }); + + const entityTypesOptions = useMemo( + () => entityTypeStore.entityTypesOptions.filter(et => et.value !== entityTypeId), + [entityTypeId] + ); + + return ( + + + + + + + {Boolean(boards && boards.length > 0) && ( + + + + )} + + + + + + + + + + + ); +}); + +CreateEntityAutomationModalContent.displayName = 'CreateEntityAutomationModalContent'; +export { CreateEntityAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/RequestHttpAutomationModal/RequestHttpAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/RequestHttpAutomationModal/RequestHttpAutomationModal.tsx new file mode 100644 index 0000000..815d11a --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/RequestHttpAutomationModal/RequestHttpAutomationModal.tsx @@ -0,0 +1,96 @@ +import { + EntityTypeActionType, + HttpMethod, + InputModel, + KeyValueListModel, + type Optional, + SelectModel, + UuidUtil, + validateForm, +} from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + type ActionHttpCallSettings, + AutomationEntityTypeTemplateFormData, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + RequestHttpAutomationModalContent, + type RequestHttpAutomationModalContentForm, +} from './RequestHttpAutomationModalContent'; + +const RequestHttpAutomationModal = observer((props: AutomationModalBaseProps) => { + const { isOpened, stageId, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.request_http_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('http_request_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + url: InputModel.create(settings?.url).httpUrl().required(), + method: SelectModel.create(settings?.method ?? HttpMethod.POST).required(), + headers: KeyValueListModel.createFromObject(settings?.headers).httpHeaderFormat(), + params: KeyValueListModel.createFromObject(settings?.params).printableAsciiFormat(), + })); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const requestHttpActionSettings: ActionHttpCallSettings = { + url: form.url.value, + method: form.method.value, + headers: form.headers.toObject(), + params: form.params.toObject(), + }; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: requestHttpActionSettings, + type: EntityTypeActionType.HTTP_CALL, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +RequestHttpAutomationModal.displayName = 'RequestHttpAutomationModal'; +export { RequestHttpAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/RequestHttpAutomationModal/RequestHttpAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/RequestHttpAutomationModal/RequestHttpAutomationModalContent.tsx new file mode 100644 index 0000000..c88b043 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/RequestHttpAutomationModal/RequestHttpAutomationModalContent.tsx @@ -0,0 +1,81 @@ +import { + HttpMethod, + type InputModel, + KeyValueInput, + type KeyValueListModel, + MyInput, + MySelect, + type Option, + type SelectModel, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { AutomationFormItem, TemplateList, WrapperWithLeftOffset } from '../components'; + +const TemplateListWrapper = styled.div` + padding-top: 12px; +`; + +export interface RequestHttpAutomationModalContentForm { + url: InputModel; + method: SelectModel; + headers: KeyValueListModel; + params: KeyValueListModel; +} + +interface Props { + form: RequestHttpAutomationModalContentForm; + entityTypeId: number; +} + +const RequestHttpAutomationModalContent = observer((props: Props) => { + const { form, entityTypeId } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.request_http_automation_modal', + }); + + const methodOptions = useMemo( + () => + Object.values(HttpMethod).map(m => ({ + value: m, + label: m, + })), + [] + ); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}); + +RequestHttpAutomationModalContent.displayName = 'RequestHttpAutomationModalContent'; +export { RequestHttpAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/SendEmailAutomationModal/SendEmailAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/SendEmailAutomationModal/SendEmailAutomationModal.tsx new file mode 100644 index 0000000..b012daa --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/SendEmailAutomationModal/SendEmailAutomationModal.tsx @@ -0,0 +1,101 @@ +import { MailboxStore, type MailboxSignature } from '@/modules/mailing'; +import { EntityTypeActionType, UuidUtil, validateForm, type Optional } from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { getSendEmailActionSettings } from '../../../helpers'; +import { useInitializeSendEmailActionSettingsForm } from '../../../hooks'; +import { + AutomationEntityTypeTemplateFormData, + type ActionEmailSendSettings, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { SendEmailAutomationModalContent } from './SendEmailAutomationModalContent'; + +const SendEmailAutomationModal = observer((props: AutomationModalBaseProps) => { + const { isOpened, stageId, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.send_email_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('send_email_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const [signatures, setSignatures] = useState([]); + + const { areSignaturesLoading, loadMailboxSignatures } = useMemo(() => new MailboxStore(), []); + + const form = useInitializeSendEmailActionSettingsForm(settings); + + useEffect(() => { + const loadSignatures = async (): Promise => { + if (!form.mailboxId.value) return; + + const signatures = await loadMailboxSignatures(form.mailboxId.value); + + setSignatures(signatures); + }; + + loadSignatures(); + }, [form.mailboxId.value, loadMailboxSignatures]); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const sendEmailActionSettings = getSendEmailActionSettings(form); + + if (!sendEmailActionSettings) return; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: sendEmailActionSettings, + type: EntityTypeActionType.EMAIL_SEND, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +SendEmailAutomationModal.displayName = 'SendEmailAutomationModal'; +export { SendEmailAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/SendEmailAutomationModal/SendEmailAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/SendEmailAutomationModal/SendEmailAutomationModalContent.tsx new file mode 100644 index 0000000..919352b --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/SendEmailAutomationModal/SendEmailAutomationModalContent.tsx @@ -0,0 +1,272 @@ +import { routes, userStore } from '@/app'; +import { + EmailSignatureEditor, + EmailTextEditor, + type Mailbox, + mailboxSettingsStore, + type MailboxSignature, + TextFormatButton, +} from '@/modules/mailing'; +import type { Nullable, Option, User } from '@/shared'; +import { + type BooleanModel, + FormItemLabel, + type FTEShowHTMLProps, + type InputModel, + MyCheckboxWithBooleanModel, + MyInput, + MySelect, + MyUsersSelect, + type Optional, + type SelectModel, + SpanWithEllipsis, + StyledLink, +} from '@/shared'; +import autoAnimate from '@formkit/auto-animate'; +import { useDisclosure } from '@mantine/hooks'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import type { ActionSendOptionsForm } from '../../../models'; +import { + AutomationBooleanRadioSelect, + AutomationFormItem, + AutomationSendOptionsBlock, + TemplateList, + WrapperWithLeftOffset, +} from '../components'; + +const CheckboxWrapper = styled.label` + display: flex; + align-items: center; + gap: 8px; + + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: var(--button-text-graphite-priory-text); +`; + +const SelectWrapper = styled.div` + display: grid; + grid-template-columns: 1fr 55%; + gap: 32px; +`; + +const TextEditorWrapper = styled.div` + position: relative; + + display: flex; + flex-direction: column; + flex: 1; +`; + +const EditorControlsWrapper = styled.div` + position: absolute; + right: 12px; + bottom: 12px; + + display: flex; + align-items: center; + gap: 16px; + + z-index: 1; + + padding: 4px; + border-radius: 32px; + background-color: var(--primary-statuses-white-0); +`; + +const OptionsBlockWrapper = styled.div<{ $hiddenGap?: boolean }>` + margin-top: 0; + transition: var(--transition-200); + + ${p => p.$hiddenGap && 'margin-top: -8px'}; +`; + +export interface SendEmailAutomationModalContentForm { + content: InputModel; + userId: SelectModel; + subject: InputModel; + signature: InputModel; + mailboxId: SelectModel; + sendAsHTML: BooleanModel; + options: ActionSendOptionsForm; +} + +interface Props { + entityTypeId: number; + areSignaturesLoading: boolean; + signatures: MailboxSignature[]; + form: SendEmailAutomationModalContentForm; +} + +const LABEL_COLOR = 'var(--button-text-graphite-primary-text)'; + +const SendEmailAutomationModalContent = observer((props: Props) => { + const { entityTypeId, areSignaturesLoading, signatures, form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.send_email_automation_modal', + }); + + const optionsBlockRef = useRef(null); + + useEffect(() => { + optionsBlockRef.current && autoAnimate(optionsBlockRef.current); + }, [optionsBlockRef]); + + const [areTextFormatControlsShown, { toggle: toggleTextFormatControls }] = useDisclosure(false); + + const handleChangeMailbox = useCallback( + (mailboxId: number) => { + const mailbox = mailboxSettingsStore.getById(mailboxId); + + if (form.userId.value && !mailbox.hasUserAccess(form.userId.value)) + form.userId.setValue(null); + }, + [form] + ); + + const handleClearEmailContent = useCallback(() => (form.content.value = ''), [form]); + + const showHTMLProps = useMemo>( + () => ({ + maxRows: 16, + minRows: 16, + show: form.sendAsHTML.value, + }), + [form.sendAsHTML.value] + ); + + const sendersMailboxOptions = useMemo[]>( + () => + mailboxSettingsStore.activeMailboxes.map(m => ({ + value: m.id, + label: m.email, + })), + [] + ); + + const mailbox = useMemo>( + () => (form.mailboxId.value ? mailboxSettingsStore.getById(form.mailboxId.value) : null), + [form.mailboxId.value] + ); + + const accessibleUsers = useMemo( + () => (mailbox ? userStore.activeUsers.filter(u => mailbox.hasUserAccess(u.id)) : []), + [mailbox] + ); + + return ( + <> + + + + + + + + + + + + + + + + + + + + + {form.mailboxId.value && ( + + + + {t('configure_mailbox_limits')} + + + + )} + + + + + + {form.options.enabled.value === 'true' && ( + + )} + + + + + + + + + + + + + {!form.sendAsHTML.value && ( + + )} + + + + + + + + + + {t('send_with_html')} + + + ); +}); + +SendEmailAutomationModalContent.displayName = 'SendEmailAutomationModalContent'; +export { SendEmailAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/SendExternalChatAutomationModal/SendExternalChatAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/SendExternalChatAutomationModal/SendExternalChatAutomationModal.tsx new file mode 100644 index 0000000..b73d350 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/SendExternalChatAutomationModal/SendExternalChatAutomationModal.tsx @@ -0,0 +1,91 @@ +import { EntityTypeActionType, UuidUtil, validateForm, type Optional } from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { getSendExternalChatActionSettings } from '../../../helpers'; +import { useInitializeSendExternalChatForm } from '../../../hooks'; +import { + AutomationEntityTypeTemplateFormData, + type ActionChatSendSettings, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { SendExternalChatAutomationModalContent } from './SendExternalChatAutomationModalContent'; + +const SendExternalChatAutomationModal = observer((props: AutomationModalBaseProps) => { + const { isOpened, stageId, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.send_external_chat_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('send_external_chat_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useInitializeSendExternalChatForm(settings); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!form.phoneNumbersEnabled.value) { + form.phoneNumbers = []; + } else if (form.phoneNumbers.some(pn => !pn.trimmedValue)) { + form.phoneNumbers.forEach(pn => { + pn.showError(t('errors.phone')); + }); + + return; + } + + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const sendExternalChatActionSettings = getSendExternalChatActionSettings(form); + + if (!sendExternalChatActionSettings) return; + + await automationStore.saveAutomation({ + stageId, + templateModel: templateFormData, + automationId: automation?.id, + action: { + delay: templateFormData.delay, + settings: sendExternalChatActionSettings, + type: EntityTypeActionType.CHAT_SEND_EXTERNAL, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose, t] + ); + + return ( + + + + ); +}); + +SendExternalChatAutomationModal.displayName = 'SendExternalChatAutomationModal'; +export { SendExternalChatAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/SendExternalChatAutomationModal/SendExternalChatAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/SendExternalChatAutomationModal/SendExternalChatAutomationModalContent.tsx new file mode 100644 index 0000000..fdf8cf1 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/SendExternalChatAutomationModal/SendExternalChatAutomationModalContent.tsx @@ -0,0 +1,234 @@ +import { userStore } from '@/app'; +import { + DeleteButton, + Hint, + InputModel, + MyCheckboxWithBooleanModel, + MyFloatingTooltip, + MyTextArea, + MyUsersSelect, + PhoneFieldInput, + PlusIconButton, + type BooleanModel, + type SelectModel, +} from '@/shared'; +import autoAnimate from '@formkit/auto-animate'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import type { ActionSendOptionsForm } from '../../../models'; +import { ExternalChatProvidersSelect } from '../../ExternalChatProvidersSelect/ExternalChatProvidersSelect'; +import { + AutomationBooleanRadioSelect, + AutomationFormItem, + AutomationSendOptionsBlock, + TemplateList, + WrapperWithLeftOffset, +} from '../components'; + +const OptionsBlockWrapper = styled.div<{ $hiddenGap?: boolean }>` + margin-top: 0; + transition: var(--transition-200); + + ${p => p.$hiddenGap && 'margin-top: -8px'}; +`; + +const PhoneFieldWrapper = styled.div` + display: flex; + align-items: center; + gap: 12px; +`; + +const TargetWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +const CheckboxWrapper = styled.div<{ $disabled?: boolean }>` + display: flex; + flex-direction: column; + gap: 8px; + + ${p => p.$disabled && 'opacity: 0.7'}; +`; + +const CheckboxLabelWrapper = styled.div` + display: flex; + align-items: center; + gap: 8px; + + font-size: 14px; + line-height: 20px; + color: var(--button-text-graphite-primary-text); +`; + +export interface SendExternalChatAutomationModalContentForm { + message: InputModel; + providerId: SelectModel; + userId: SelectModel; + phoneNumbers: InputModel[]; + chatsEnabled: BooleanModel; + phoneNumbersEnabled: BooleanModel; + options: ActionSendOptionsForm; +} + +interface Props { + entityTypeId: number; + form: SendExternalChatAutomationModalContentForm; +} + +const SendExternalChatAutomationModalContent = observer((props: Props) => { + const { entityTypeId, form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.send_external_chat_automation_modal', + }); + + const [phoneNumbersAvailable, setPhoneNumbersAvailable] = useState(true); + + const optionsBlockRef = useRef(null); + + useEffect(() => { + optionsBlockRef.current && autoAnimate(optionsBlockRef.current); + }, [optionsBlockRef]); + + // clear checkbox errors that could possibly appear on save attempt + useEffect(() => { + if (form.chatsEnabled.value || form.phoneNumbersEnabled.value) { + form.chatsEnabled.clearError(); + form.phoneNumbersEnabled.clearError(); + } + }, [form.chatsEnabled, form.phoneNumbersEnabled]); + + useEffect(() => { + if (!phoneNumbersAvailable) form.phoneNumbersEnabled.value = false; + }, [form.phoneNumbersEnabled, phoneNumbersAvailable]); + + const addPhoneNumberModel = useCallback(() => { + if (form.phoneNumbers.some(pn => !pn.value.length)) { + form.phoneNumbers.find(pn => !pn.value.length)?.showError(t('errors.phone')); + + return; + } + + form.phoneNumbers.push(InputModel.create().phoneInternational()); + }, [form.phoneNumbers, t]); + + const getDeletePhoneNumberHandler = useCallback( + (idx: number) => () => { + if (form.phoneNumbers.length < 2) return; + + form.phoneNumbers = form.phoneNumbers.filter((_, pnIdx) => pnIdx !== idx); + }, + [form] + ); + + return ( + <> + + + + + + + + + + + + + + {t('send_to_chats')} + + + + + + {form.chatsEnabled.value && ( + <> + + + + {form.options.enabled.value === 'true' && ( + + )} + + + )} + + + + + + + + + {t('send_to_phone_numbers')} + + + + + + {phoneNumbersAvailable && form.phoneNumbersEnabled.value && ( + + {form.phoneNumbers.map((pn, idx) => ( + + + + {form.phoneNumbers.length > 1 && ( + + )} + + ))} + + + + )} + + + + + + + + + + + + ); +}); + +SendExternalChatAutomationModalContent.displayName = 'SendExternalChatAutomationModalContent'; +export { SendExternalChatAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/SendInternalChatAutomationModal/SendInternalChatAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/SendInternalChatAutomationModal/SendInternalChatAutomationModal.tsx new file mode 100644 index 0000000..3b36183 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/SendInternalChatAutomationModal/SendInternalChatAutomationModal.tsx @@ -0,0 +1,97 @@ +import { + EntityTypeActionType, + InputModel, + MultiselectModel, + SelectModel, + UuidUtil, + validateForm, + type Optional, +} from '@/shared'; +import { observer, useLocalObservable } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { ActionChatSendAmworkSettings } from '../../../models'; +import { + AutomationEntityTypeTemplateFormData, + type AutomationModalBaseProps, +} from '../../../models'; +import { AutomationModalTemplate } from '../AutomationModalTemplate'; +import { + SendInternalChatAutomationModalContent, + type SendInternalChatAutomationModalContentForm, +} from './SendInternalChatAutomationModalContent'; + +const SendInternalChatAutomationModal = observer((props: AutomationModalBaseProps) => { + const { isOpened, stageId, automationStore, automation = null, onClose } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.send_internal_chat_automation_modal', + }); + + const templateModel = useLocalObservable(() => + automation + ? AutomationEntityTypeTemplateFormData.fromAutomation(automation) + : AutomationEntityTypeTemplateFormData.getDefaultTemplate( + t('send_internal_chat_default_name', { id: UuidUtil.generate6() }) + ) + ); + + const settings = useMemo>( + () => automation?.firstAction?.settings as Optional, + [automation] + ); + + const form = useLocalObservable(() => ({ + message: InputModel.create(settings?.message).required(), + userId: SelectModel.create(settings?.userId ?? null), + sendTo: MultiselectModel.create(settings?.sendTo ?? [-1]).required(), + })); + + const handleSave = useCallback( + async (templateFormData: AutomationEntityTypeTemplateFormData): Promise => { + if (!validateForm(form) || !validateForm(templateFormData)) return; + + const sendInternalChatActionSettings: ActionChatSendAmworkSettings = { + userId: form.userId.value, + sendTo: + !form.sendTo.values.length || form.sendTo.values[0] === -1 ? null : form.sendTo.values, + message: form.message.trimmedValue, + }; + + await automationStore.saveAutomation({ + stageId, + automationId: automation?.id, + templateModel: templateFormData, + action: { + delay: templateFormData.delay, + settings: sendInternalChatActionSettings, + type: EntityTypeActionType.CHAT_SEND_AMWORK, + }, + }); + + onClose(); + }, + [automation, automationStore, form, stageId, onClose] + ); + + return ( + + + + ); +}); + +SendInternalChatAutomationModal.displayName = 'SendInternalChatAutomationModal'; +export { SendInternalChatAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/SendInternalChatAutomationModal/SendInternalChatAutomationModalContent.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/SendInternalChatAutomationModal/SendInternalChatAutomationModalContent.tsx new file mode 100644 index 0000000..c8c731e --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/SendInternalChatAutomationModal/SendInternalChatAutomationModalContent.tsx @@ -0,0 +1,69 @@ +import { userStore } from '@/app'; +import { + type InputModel, + type MultiselectModel, + MyTextArea, + MyUsersSelect, + type SelectModel, + UsersMultiselect, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import { AutomationFormItem, TemplateList } from '../components'; + +export interface SendInternalChatAutomationModalContentForm { + message: InputModel; + userId: SelectModel; + sendTo: MultiselectModel; +} + +interface Props { + entityTypeId: number; + form: SendInternalChatAutomationModalContentForm; +} + +const SendInternalChatAutomationModalContent = observer((props: Props) => { + const { entityTypeId, form } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.send_internal_chat_automation_modal', + }); + + return ( + <> + + + + + + + + + + + + + + + ); +}); + +export { SendInternalChatAutomationModalContent }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationBooleanRadioSelect/AutomationBooleanRadioSelect.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationBooleanRadioSelect/AutomationBooleanRadioSelect.tsx new file mode 100644 index 0000000..d6dfaf4 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationBooleanRadioSelect/AutomationBooleanRadioSelect.tsx @@ -0,0 +1,22 @@ +import { FormItem, type InputModel } from '@/shared'; +import { AutomationRadioButton } from '../AutomationRadioButton/AutomationRadioButton'; + +interface Props { + model: InputModel; + trueLabel: string; + falseLabel: string; +} + +const AutomationBooleanRadioSelect = (props: Props) => { + const { model, trueLabel, falseLabel } = props; + + return ( + + + + + + ); +}; + +export { AutomationBooleanRadioSelect }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationFormItem/AutomationFormItem.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationFormItem/AutomationFormItem.tsx new file mode 100644 index 0000000..875bfa4 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationFormItem/AutomationFormItem.tsx @@ -0,0 +1,27 @@ +import { FormItem, FormItemLabel, Hint } from '@/shared'; +import type { CSSProperties, ReactNode } from 'react'; + +interface Props { + text: string; + children: ReactNode; + gap?: CSSProperties['gap']; + hint?: string; +} + +const AutomationFormItem = (props: Props) => { + const { text, children, gap = '8px', hint } = props; + + return ( + + + {text} + + {hint && } + + + {children} + + ); +}; + +export { AutomationFormItem }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationRadioButton/AutomationRadioButton.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationRadioButton/AutomationRadioButton.tsx new file mode 100644 index 0000000..e5963a5 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationRadioButton/AutomationRadioButton.tsx @@ -0,0 +1,30 @@ +import { FormItemLabel, type InputModel, MyRadio, SpanWithEllipsis } from '@/shared'; +import styled from 'styled-components'; + +const Root = styled.label` + display: flex; + align-items: center; + gap: 8px; +`; + +interface Props { + value: string; + label: string; + model: InputModel; +} + +const AutomationRadioButton = (props: Props) => { + const { value, label, model } = props; + + return ( + + + + + + + + ); +}; + +export { AutomationRadioButton }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/AutomationSendOptionsBlock.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/AutomationSendOptionsBlock.tsx new file mode 100644 index 0000000..99f10ed --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/AutomationSendOptionsBlock.tsx @@ -0,0 +1,131 @@ +import { FormItem, FormItemLabel, Hint, MyCheckboxWithBooleanModel } from '@/shared'; +import { Transition } from '@mantine/core'; +import { observer } from 'mobx-react-lite'; +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import type { ActionSendOptionsForm } from '../../../../models'; +import { AutomationBooleanRadioSelect } from '../AutomationBooleanRadioSelect/AutomationBooleanRadioSelect'; +import { WrapperWithLeftOffset } from '../WrapperWithLeftOffset/WrapperWithLeftOffset'; +import { EntitySendOptionsBlock } from './components'; + +const ItemWrapper = styled.label` + display: flex; + align-items: center; + gap: 8px; +`; + +interface Props { + form: ActionSendOptionsForm; + localePrefix: 'email' | 'external_chat'; +} + +const AutomationSendOptionsBlock = observer((props: Props) => { + const { form, localePrefix } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: `automation.modals.send_${localePrefix}_automation_modal.options`, + }); + + // Clear error on change checkboxes value + useEffect(() => { + form.main.enabled.clearError(); + form.contact.enabled.clearError(); + form.company.enabled.clearError(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [form.contact.enabled.value, form.company.enabled.value, form.main.enabled.value]); + + return ( + + + + + + + + {t('main_entity')} + + + + + + + {styles => ( + + + + )} + + + + + + + + + {t('contact')} + + + + + + + {styles => ( + + + + )} + + + + + + + + + {t('company')} + + + + + + + {styles => ( + + + + )} + + + + + ); +}); + +AutomationSendOptionsBlock.displayName = 'AutomationSendOptionsBlock'; +export { AutomationSendOptionsBlock }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/components/EntitySendOptionsBlock/EntitySendOptionsBlock.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/components/EntitySendOptionsBlock/EntitySendOptionsBlock.tsx new file mode 100644 index 0000000..68594bd --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/components/EntitySendOptionsBlock/EntitySendOptionsBlock.tsx @@ -0,0 +1,50 @@ +import { FormItem, type InputModel } from '@/shared'; +import { useTranslation } from 'react-i18next'; +import { ActionSendVariant } from '../../../../../../models'; +import { AutomationRadioButton } from '../../../AutomationRadioButton/AutomationRadioButton'; + +interface Props { + model: InputModel; + localePrefix: string; + localeModalPrefix: 'email' | 'external_chat'; +} + +const EntitySendOptionsBlock = (props: Props) => { + const { model, localePrefix, localeModalPrefix } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: `automation.modals.send_${localeModalPrefix}_automation_modal.options`, + }); + + return ( + <> + + + + + + + + + + + ); +}; + +export { EntitySendOptionsBlock }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/components/index.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/components/index.tsx new file mode 100644 index 0000000..411e2ee --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationSendOptionsBlock/components/index.tsx @@ -0,0 +1 @@ +export { EntitySendOptionsBlock } from './EntitySendOptionsBlock/EntitySendOptionsBlock'; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationUserSelect/AutomationUserSelect.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationUserSelect/AutomationUserSelect.tsx new file mode 100644 index 0000000..ab5ac4a --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/AutomationUserSelect/AutomationUserSelect.tsx @@ -0,0 +1,55 @@ +import { userStore } from '@/app'; +import { MySelect, SelectModel, type Option } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import { ResponsibleUser, ResponsibleUserType } from '../../../../models'; + +interface Props { + responsibleUser: ResponsibleUser; + onChange: (responsibleUser: ResponsibleUser) => void; +} + +const CURRENT_VALUE = 0; + +const AutomationUserSelect = observer((props: Props) => { + const { responsibleUser, onChange } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.add_task_automation_modal', + }); + + const selectedValue = + responsibleUser.type === ResponsibleUserType.CURRENT ? CURRENT_VALUE : responsibleUser.id; + + const model = SelectModel.create(selectedValue); + + const userOptions = userStore.activeUsers.map>(u => ({ + label: u.fullName, + value: u.id, + })); + + userOptions.unshift({ label: t('current_responsible_user'), value: CURRENT_VALUE }); + + const handleChange = () => { + const type = + model.value === CURRENT_VALUE ? ResponsibleUserType.CURRENT : ResponsibleUserType.CUSTOM; + + const id = model.value === CURRENT_VALUE ? null : model.value; + + onChange(new ResponsibleUser({ type, id })); + }; + + return ( + + ); +}); + +AutomationUserSelect.displayName = 'AutomationUserSelect'; +export { AutomationUserSelect }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/ConditionsBlock.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/ConditionsBlock.tsx new file mode 100644 index 0000000..d23ad88 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/ConditionsBlock.tsx @@ -0,0 +1,65 @@ +import { userStore } from '@/app'; +import { PlusIconButton, UsersMultiselect, type EntityType } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import type { EntityTypeConditionFormData } from '../../../../models'; +import { AutomationFormItem } from '../AutomationFormItem/AutomationFormItem'; +import { WrapperWithLeftOffset } from '../WrapperWithLeftOffset/WrapperWithLeftOffset'; +import { EntityTypeFieldsConditions } from './components'; + +const Root = styled.div` + width: 100%; + + display: flex; + flex-direction: column; + gap: 16px; +`; + +interface Props { + entityType: EntityType; + conditions: EntityTypeConditionFormData; +} + +const ConditionsBlock = observer((props: Props) => { + const { entityType, conditions } = props; + + const { ownerIds, fieldsFormData, createEmptyFieldFormData, deleteFieldsFormData } = conditions; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + + return ( + + + + + + + + + + + + + + ); +}); + +ConditionsBlock.displayName = 'ConditionsBlock'; +export { ConditionsBlock }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterBoolean/EntityTypeFieldFilterBoolean.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterBoolean/EntityTypeFieldFilterBoolean.tsx new file mode 100644 index 0000000..c26c41d --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterBoolean/EntityTypeFieldFilterBoolean.tsx @@ -0,0 +1,40 @@ +import { MySelect, type BooleanFilterFormData, type Option } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { AutomationFormItem } from '../../../AutomationFormItem/AutomationFormItem'; + +interface Props { + formData: BooleanFilterFormData; +} + +const EntityTypeFieldFilterBoolean = observer((props: Props) => { + const { formData } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + + const switchOptions = useMemo[]>( + () => [ + { + label: t('entity_type_field_filter_boolean.switch_on'), + value: true, + }, + { + label: t('entity_type_field_filter_boolean.switch_off'), + value: false, + }, + ], + [t] + ); + + return ( + + + + ); +}); + +EntityTypeFieldFilterBoolean.displayName = 'EntityTypeFieldFilterBoolean'; +export { EntityTypeFieldFilterBoolean }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterDate/EntityTypeFieldFilterDate.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterDate/EntityTypeFieldFilterDate.tsx new file mode 100644 index 0000000..5160e95 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterDate/EntityTypeFieldFilterDate.tsx @@ -0,0 +1,36 @@ +import { MyDatePeriodPicker, type DateFilterFormData, type UtcDateValue } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { AutomationFormItem } from '../../../AutomationFormItem/AutomationFormItem'; + +interface Props { + formData: DateFilterFormData; +} + +const EntityTypeFieldFilterDate = observer((props: Props) => { + const { formData } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + + const handleChangeTo = useCallback((date: UtcDateValue) => (formData.to = date), [formData]); + const handleChangeFrom = useCallback((date: UtcDateValue) => (formData.from = date), [formData]); + + return ( + + + + ); +}); + +EntityTypeFieldFilterDate.displayName = 'EntityTypeFieldFilterDate'; +export { EntityTypeFieldFilterDate }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterNumber/EntityTypeFieldFilterNumber.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterNumber/EntityTypeFieldFilterNumber.tsx new file mode 100644 index 0000000..c6f1bfb --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterNumber/EntityTypeFieldFilterNumber.tsx @@ -0,0 +1,58 @@ +import { MyInput, type NumberFilterFormData } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { AutomationFormItem } from '../../../AutomationFormItem/AutomationFormItem'; + +const InputsWrapper = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const Delimiter = styled.hr` + width: 6px; + + flex-shrink: 0; + + border-top: 1px solid var(--button-text-graphite-primary-text); +`; + +interface Props { + formData: NumberFilterFormData; +} + +const EntityTypeFieldFilterNumber = observer((props: Props) => { + const { formData } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + + return ( + + + + + + + + + + ); +}); + +EntityTypeFieldFilterNumber.displayName = 'EntityTypeFieldFilterNumber'; +export { EntityTypeFieldFilterNumber }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterParticipantsSelect/EntityTypeFieldFilterParticipantsSelect.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterParticipantsSelect/EntityTypeFieldFilterParticipantsSelect.tsx new file mode 100644 index 0000000..8568c7e --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterParticipantsSelect/EntityTypeFieldFilterParticipantsSelect.tsx @@ -0,0 +1,32 @@ +import { userStore } from '@/app'; +import { UsersMultiselect, type SelectFilterFormData } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import { AutomationFormItem } from '../../../AutomationFormItem/AutomationFormItem'; + +interface Props { + formData: SelectFilterFormData; +} + +const EntityTypeFieldFilterParticipantsSelect = observer((props: Props) => { + const { formData } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + + return ( + + + + ); +}); + +EntityTypeFieldFilterParticipantsSelect.displayName = 'EntityTypeFieldFilterParticipantsSelect'; +export { EntityTypeFieldFilterParticipantsSelect }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterSelect/EntityTypeFieldFilterSelect.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterSelect/EntityTypeFieldFilterSelect.tsx new file mode 100644 index 0000000..cb8b483 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterSelect/EntityTypeFieldFilterSelect.tsx @@ -0,0 +1,43 @@ +import type { Field } from '@/modules/fields'; +import { MultiselectWithCheckboxes, type Option, type SelectFilterFormData } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { AutomationFormItem } from '../../../AutomationFormItem/AutomationFormItem'; + +interface Props { + field: Field; + formData: SelectFilterFormData; +} + +const EntityTypeFieldFilterSelect = observer((props: Props) => { + const { field, formData } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + + const options = useMemo[]>( + () => + field.options.map>(o => ({ + value: o.id, + label: o.label, + })), + [field.options] + ); + + return ( + + + + ); +}); + +EntityTypeFieldFilterSelect.displayName = 'EntityTypeFieldFilterSelect'; +export { EntityTypeFieldFilterSelect }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterString/EntityTypeFieldFilterString.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterString/EntityTypeFieldFilterString.tsx new file mode 100644 index 0000000..3bdffe6 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldFilterString/EntityTypeFieldFilterString.tsx @@ -0,0 +1,74 @@ +import { + MyInput, + MySelect, + StringFilterType, + type Option, + type StringFilterFormData, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { AutomationFormItem } from '../../../AutomationFormItem/AutomationFormItem'; + +const Root = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +interface Props { + formData: StringFilterFormData; +} + +const EntityTypeFieldFilterString = observer((props: Props) => { + const { formData } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + + const typeOptions = useMemo[]>( + () => [ + { label: t('entity_type_field_filter_string.is_empty'), value: StringFilterType.EMPTY }, + { + label: t('entity_type_field_filter_string.is_not_empty'), + value: StringFilterType.NOT_EMPTY, + }, + { label: t('entity_type_field_filter_string.contains'), value: StringFilterType.CONTAINS }, + ], + [t] + ); + + const handleChangeType = useCallback( + (type: StringFilterType) => { + if (type !== StringFilterType.CONTAINS) formData.text.setValue(''); + }, + [formData] + ); + + return ( + + + + + {formData.type.value === StringFilterType.CONTAINS && ( + + )} + + + ); +}); + +export { EntityTypeFieldFilterString }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldsConditions/EntityTypeFieldsConditions.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldsConditions/EntityTypeFieldsConditions.tsx new file mode 100644 index 0000000..95ab7dd --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldsConditions/EntityTypeFieldsConditions.tsx @@ -0,0 +1,204 @@ +import type { Option } from '@/shared'; +import { + BooleanFilterFormData, + DateFilterFormData, + DeleteButton, + FieldType, + MySelect, + NumberFilterFormData, + SelectFilterFormData, + SimpleFilterType, + SpanWithEllipsis, + StringFilterFormData, + type EntityType, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import type { EntityTypeFieldConditionFormData } from '../../../../../../models'; +import { EntityTypeFieldsConditionsSwitch } from '../EntityTypeFieldsConditionsSwitch/EntityTypeFieldsConditionsSwitch'; + +const FieldBlock = styled.div` + position: relative; + + width: 100%; + + display: flex; + flex-direction: column; + gap: 12px; + + padding: 16px; + border-radius: var(--border-radius-element); + border: 1px solid var(--graphite-graphite-80); +`; + +const FieldName = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: var(--button-text-graphite-primary-text); +`; + +const SelectWrapper = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const IndexTag = styled.span` + position: absolute; + top: -8px; + right: -8px; + + width: 20px; + height: 20px; + + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + + font-size: 10px; + font-weight: 500; + line-height: 12px; + color: var(--primary-statuses-white-0); + + border-radius: 50%; + background-color: var(--primary-statuses-green-520); +`; + +interface Props { + entityType: EntityType; + fieldsFormData: EntityTypeFieldConditionFormData[]; + deleteFieldsFormData: (formDataId: number) => void; +} + +const EntityTypeFieldsConditions = observer((props: Props) => { + const { entityType, fieldsFormData, deleteFieldsFormData } = props; + + const { t: t1 } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.conditions_block', + }); + const { t: t2 } = useTranslation('module.fields', { + keyPrefix: 'fields', + }); + + const fieldsOptions = useMemo[]>( + () => + entityType.fields.map>(f => ({ + value: f.id, + // Some fields, like project fields, are translated by code + label: f.code && f.isProjectField ? t2(`project_fields_block.${f.code}`) : f.name, + })), + [entityType.fields, t2] + ); + + const handleChangeField = useCallback( + (fieldId: number) => { + const fieldFormData = fieldsFormData.find(f => f.fieldId.value === fieldId); + + if (!fieldFormData) + throw new Error(`Failed to find field form data for field with id ${fieldId}`); + + const field = entityType.getFieldById(fieldId); + + switch (field.type) { + case FieldType.VALUE: + case FieldType.NUMBER: + case FieldType.FORMULA: { + fieldFormData.type = SimpleFilterType.NUMBER; + fieldFormData.filterFormData = NumberFilterFormData.empty(); + + break; + } + + case FieldType.TEXT: + case FieldType.LINK: + case FieldType.PHONE: + case FieldType.EMAIL: + case FieldType.RICHTEXT: + case FieldType.CHECKLIST: + case FieldType.MULTITEXT: { + fieldFormData.type = SimpleFilterType.STRING; + fieldFormData.filterFormData = StringFilterFormData.empty(); + + break; + } + + case FieldType.SWITCH: { + fieldFormData.type = SimpleFilterType.BOOLEAN; + fieldFormData.filterFormData = BooleanFilterFormData.empty(); + + break; + } + + case FieldType.SELECT: + case FieldType.MULTISELECT: + case FieldType.COLORED_SELECT: + case FieldType.PARTICIPANT: + case FieldType.PARTICIPANTS: + case FieldType.CHECKED_MULTISELECT: + case FieldType.COLORED_MULTISELECT: { + fieldFormData.type = SimpleFilterType.SELECT; + fieldFormData.filterFormData = SelectFilterFormData.empty(); + + break; + } + + case FieldType.DATE: { + fieldFormData.type = SimpleFilterType.DATE; + fieldFormData.filterFormData = DateFilterFormData.empty(); + + break; + } + + default: + throw new Error(`Failed to handle field type ${field.type}`); + } + }, + [fieldsFormData, entityType] + ); + + const getDeleteFieldsFormDataHandler = useCallback( + (formDataId: number) => () => deleteFieldsFormData(formDataId), + [deleteFieldsFormData] + ); + + return fieldsFormData.map((f, idx) => { + return ( + + {idx + 1} + + + + + + + + + + + + {f.fieldId.value > 0 && ( + + )} + + ); + }); +}); + +EntityTypeFieldsConditions.displayName = 'EntityTypeFieldsConditions'; +export { EntityTypeFieldsConditions }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldsConditionsSwitch/EntityTypeFieldsConditionsSwitch.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldsConditionsSwitch/EntityTypeFieldsConditionsSwitch.tsx new file mode 100644 index 0000000..3a42440 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/EntityTypeFieldsConditionsSwitch/EntityTypeFieldsConditionsSwitch.tsx @@ -0,0 +1,98 @@ +import { + FieldType, + type BooleanFilterFormData, + type DateFilterFormData, + type EntityType, + type NumberFilterFormData, + type SelectFilterFormData, + type StringFilterFormData, +} from '@/shared'; +import { observer } from 'mobx-react-lite'; +import type { EntityTypeFieldConditionFormData } from '../../../../../../models'; +import { EntityTypeFieldFilterBoolean } from '../EntityTypeFieldFilterBoolean/EntityTypeFieldFilterBoolean'; +import { EntityTypeFieldFilterDate } from '../EntityTypeFieldFilterDate/EntityTypeFieldFilterDate'; +import { EntityTypeFieldFilterNumber } from '../EntityTypeFieldFilterNumber/EntityTypeFieldFilterNumber'; +import { EntityTypeFieldFilterParticipantsSelect } from '../EntityTypeFieldFilterParticipantsSelect/EntityTypeFieldFilterParticipantsSelect'; +import { EntityTypeFieldFilterSelect } from '../EntityTypeFieldFilterSelect/EntityTypeFieldFilterSelect'; +import { EntityTypeFieldFilterString } from '../EntityTypeFieldFilterString/EntityTypeFieldFilterString'; + +interface Props { + entityType: EntityType; + fieldsFormData: EntityTypeFieldConditionFormData; +} + +const EntityTypeFieldsConditionsSwitch = observer((props: Props) => { + const { entityType, fieldsFormData } = props; + + const field = entityType.getFieldById(fieldsFormData.fieldId.value); + + switch (field.type) { + case FieldType.VALUE: + case FieldType.NUMBER: + case FieldType.FORMULA: { + return ( + + ); + } + + case FieldType.TEXT: + case FieldType.LINK: + case FieldType.PHONE: + case FieldType.EMAIL: + case FieldType.RICHTEXT: + case FieldType.CHECKLIST: + case FieldType.MULTITEXT: { + return ( + + ); + } + + case FieldType.SWITCH: { + return ( + + ); + } + + case FieldType.PARTICIPANT: + case FieldType.PARTICIPANTS: { + return ( + + ); + } + + case FieldType.SELECT: + case FieldType.MULTISELECT: + case FieldType.COLORED_SELECT: + case FieldType.CHECKED_MULTISELECT: + case FieldType.COLORED_MULTISELECT: { + return ( + + ); + } + + case FieldType.DATE: { + return ( + + ); + } + + default: + throw new Error( + `Unknown field type: ${field.type}, failed to render corresponding entity type field filter block` + ); + } +}); + +EntityTypeFieldsConditionsSwitch.displayName = 'EntityTypeFieldsConditionsSwitch'; +export { EntityTypeFieldsConditionsSwitch }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/index.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/index.tsx new file mode 100644 index 0000000..d416cda --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/ConditionsBlock/components/index.tsx @@ -0,0 +1 @@ +export { EntityTypeFieldsConditions } from './EntityTypeFieldsConditions/EntityTypeFieldsConditions'; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/DeferStartSelect/DeferStartSelect.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/DeferStartSelect/DeferStartSelect.tsx new file mode 100644 index 0000000..401e503 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/DeferStartSelect/DeferStartSelect.tsx @@ -0,0 +1,87 @@ +import { + CustomIntervalInputGroup, + MySelectCustomTemplate, + type Nullable, + type NumberModel, + SelectOptionItem, + SelectOptionItemRoot, + SelectOptionsList, + useDropdownWidth, + useGetDHMDateStringFromSeconds, +} from '@/shared'; +import { useDisclosure } from '@mantine/hooks'; +import { observer } from 'mobx-react-lite'; +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useGetDeferStartOptions } from '../../../../hooks'; + +interface Props { + model: NumberModel; +} + +const DeferStartSelect = observer((props: Props) => { + const { model } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.defer_start_select', + }); + + const [isOpened, { close, open }] = useDisclosure(false); + + const [dropdownWidth, titleRef] = useDropdownWidth(); + + const handleSelect = useCallback( + (value: Nullable) => { + model.setValue(value); + + close(); + }, + [close, model] + ); + + const getSelectHandler = useCallback( + (value: Nullable) => () => handleSelect(value), + [handleSelect] + ); + + const options = useGetDeferStartOptions(); + + const selectedOption = options.find(o => o.value === model.value); + + const formattedDeadlineTime = useGetDHMDateStringFromSeconds({ value: model.value ?? 0 }); + + const label = selectedOption ? selectedOption.label : formattedDeadlineTime; + + return ( + + + {options.map(o => ( + + ))} + + + + + + + ); +}); + +DeferStartSelect.displayName = 'DeferStartSelect'; +export { DeferStartSelect }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/DeleteAutomationModal/DeleteAutomationModal.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/DeleteAutomationModal/DeleteAutomationModal.tsx new file mode 100644 index 0000000..e666bf4 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/DeleteAutomationModal/DeleteAutomationModal.tsx @@ -0,0 +1,48 @@ +import { WarningModal } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +interface Props { + isOpened: boolean; + onClose: () => void; + onApprove: () => Promise; +} + +const DeleteAutomationModal = observer((props: Props) => { + const { isOpened, onClose, onApprove } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.delete_automation_modal', + }); + + const [loading, setLoading] = useState(false); + + const handleDelete = async (): Promise => { + try { + setLoading(true); + + await onApprove(); + } catch (e) { + throw new Error(`Error while deleting automation: ${e}`); + } finally { + setLoading(false); + } + }; + + return ( + + ); +}); + +DeleteAutomationModal.displayName = 'DeleteAutomationModal'; +export { DeleteAutomationModal }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/DescriptionBlock/DescriptionBlock.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/DescriptionBlock/DescriptionBlock.tsx new file mode 100644 index 0000000..833cf9e --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/DescriptionBlock/DescriptionBlock.tsx @@ -0,0 +1,27 @@ +import { FunctionalTextEditor, type InputModel } from '@/shared'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; + +interface Props { + model: InputModel; +} + +const DescriptionBlock = observer((props: Props) => { + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.description_block', + }); + + const { model } = props; + + return ( + + ); +}); + +DescriptionBlock.displayName = 'DescriptionBlock'; +export { DescriptionBlock }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/DueDateSelect/DueDateSelect.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/DueDateSelect/DueDateSelect.tsx new file mode 100644 index 0000000..015eb06 --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/DueDateSelect/DueDateSelect.tsx @@ -0,0 +1,100 @@ +import { + CustomIntervalInputGroup, + MySelectCustomTemplate, + NumberModel, + SelectOptionItem, + SelectOptionItemRoot, + SelectOptionsList, + useDropdownWidth, + useGetDHMDateStringFromSeconds, + type Nullable, +} from '@/shared'; +import { useDisclosure } from '@mantine/hooks'; +import { observer } from 'mobx-react-lite'; +import { useTranslation } from 'react-i18next'; +import { useGetDueDateOptions } from '../../../../hooks/useGetDueDateOptions'; +import { AutomationEntityTypeDeadline, DeadlineType } from '../../../../models'; + +interface Props { + deadline: AutomationEntityTypeDeadline; + onChange: (deadline: AutomationEntityTypeDeadline) => void; +} + +const DueDateSelect = observer((props: Props) => { + const { deadline, onChange } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.due_date_select', + }); + + const [isOpen, { close: hide, open: show }] = useDisclosure(false); + + const [dropdownWidth, titleRef] = useDropdownWidth(); + + const { type: deadlineType, time: deadlineTime } = deadline; + + const handleSelect = (value: DeadlineType) => { + const deadline = new AutomationEntityTypeDeadline({ type: value, time: null }); + + onChange(deadline); + hide(); + }; + + const time = NumberModel.create(deadlineTime); + + const options = useGetDueDateOptions(); + + const selectedOption = options.find(o => o.value === deadlineType); + const isPredefined = deadline.type ? Boolean(selectedOption) : false; + + const formattedDeadlineTime = useGetDHMDateStringFromSeconds({ value: deadlineTime ?? 0 }); + + const label = deadlineType + ? isPredefined + ? selectedOption + ? selectedOption.label + : undefined + : deadlineTime + ? formattedDeadlineTime + : undefined + : undefined; + + const handleCustomSave = (time: Nullable) => { + onChange(new AutomationEntityTypeDeadline({ type: DeadlineType.CUSTOM, time })); + + hide(); + }; + + return ( + + + {options.map(o => ( + handleSelect(o.value)} + /> + ))} + + + + + + + ); +}); + +DueDateSelect.displayName = 'DueDateSelect'; +export { DueDateSelect }; diff --git a/frontend/src/modules/automation/shared/lib/components/Modals/components/TemplateList/TemplateList.tsx b/frontend/src/modules/automation/shared/lib/components/Modals/components/TemplateList/TemplateList.tsx new file mode 100644 index 0000000..c8cc95a --- /dev/null +++ b/frontend/src/modules/automation/shared/lib/components/Modals/components/TemplateList/TemplateList.tsx @@ -0,0 +1,252 @@ +import { entityTypeStore } from '@/app'; +import { type Field, FieldsStore } from '@/modules/fields'; +import { removeSpecialChars } from '@/modules/settings'; +import { + CopyButton, + DropdownScrollbarMixin, + type EntityType, + PrimaryButton, + SpanWithEllipsis, +} from '@/shared'; +import autoAnimate from '@formkit/auto-animate'; +import { useDisclosure } from '@mantine/hooks'; +import { observer } from 'mobx-react-lite'; +import { Fragment, useCallback, useEffect, useMemo, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +const Root = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const EntityTypeWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const ButtonWrapper = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const Tooltip = styled.div` + width: 250px; + + line-height: 1; + font-size: 10px; + font-weight: 400; + color: var(--button-text-graphite-secondary-text); +`; + +const EntityTypeLabel = styled.span` + font-size: 16px; + font-weight: 400; + line-height: 22px; + color: var(--button-text-graphite-primary-text); + + padding-bottom: 4px; + padding-left: 4px; + border-bottom: 1px solid var(--graphite-graphite-120); +`; + +const Content = styled.ul` + max-height: 200px; + max-width: 100%; + overflow-y: auto; + + display: flex; + flex-direction: column; + gap: 16px; + + border-radius: var(--border-radius-block); + border: 1px solid var(--graphite-graphite-120); + + ${DropdownScrollbarMixin} + + padding: 8px; +`; + +const TemplatesGrid = styled.div` + display: grid; + grid-template-columns: 1fr 240px max-content; + grid-auto-rows: auto; + gap: 8px; + + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: var(--button-text-graphite-priory-text); +`; + +const Template = styled(SpanWithEllipsis)` + color: var(--primary-statuses-green-520); +`; + +interface Props { + entityTypeId: number; +} + +const TemplateList = observer((props: Props) => { + const { entityTypeId } = props; + + const { t } = useTranslation('module.automation', { + keyPrefix: 'automation.modals.common.template_list', + }); + + const ref = useRef(null); + + useEffect(() => { + ref.current && autoAnimate(ref.current); + }, [ref]); + + const [isOpened, { toggle }] = useDisclosure(false); + + const entityTypes = entityTypeStore.sortedEntityTypes; + const entityType = entityTypeStore.getById(entityTypeId); + + const fieldsStore = useMemo( + () => new FieldsStore({ initialFields: entityType.fields }), + [entityType.fields] + ); + + const entityTypesWithSameName = useMemo(() => { + const entityTypesWithSameName: number[] = []; + + entityTypes.forEach(et => { + entityTypes.forEach(et2 => { + if (et.id !== et2.id && et.name === et2.name) entityTypesWithSameName.push(et.id); + }); + }); + + return entityTypesWithSameName; + }, [entityTypes]); + + const linkedContactsAndCompanies = useMemo( + () => + entityType.sortedLinkedEntityTypes + .map(l => entityTypeStore.getById(l.targetId)) + .filter(et => et.isContactCategory() || et.isCompanyCategory()), + [entityType.sortedLinkedEntityTypes] + ); + + const getEntityTypeCode = useCallback( + (et: EntityType) => { + const parsedName = removeSpecialChars(et.name); + + if (entityTypesWithSameName.includes(et.id)) return `${parsedName}${et.id}`; + + return parsedName; + }, + [entityTypesWithSameName] + ); + + const getFieldsWithSameName = useCallback((fields: Field[]) => { + const fieldsWithSameName: number[] = []; + + fields.forEach(f1 => { + fields.forEach(f2 => { + if (f1.id !== f2.id && f1.name === f2.name) fieldsWithSameName.push(f1.id); + }); + }); + + return fieldsWithSameName; + }, []); + + const generateFieldCode = useCallback( + (et: EntityType, fields: Field[], field: Field): string => { + const fieldNameWithoutSlashes = removeSpecialChars(field.name); + + const fieldsWithSameName = getFieldsWithSameName(fields); + + if (fieldsWithSameName.includes(field.id)) + return `{{${getEntityTypeCode(et)}.${fieldNameWithoutSlashes}${field.id}}}`; + + return `{{${getEntityTypeCode(et)}.${fieldNameWithoutSlashes}}}`; + }, + [getEntityTypeCode, getFieldsWithSameName] + ); + + const getOptions = useCallback( + (entityType: EntityType) => { + const result = [ + { + value: `{{${getEntityTypeCode(entityType)}.name}}`, + label: t('name'), + }, + { + value: `{{${getEntityTypeCode(entityType)}.owner}}`, + label: t('owner'), + }, + ]; + + fieldsStore.setFields(entityType.fields); + + entityType.fieldGroups.forEach(fg => { + fieldsStore.getFieldsByGroupId(fg.id).forEach(field => { + result.push({ + value: generateFieldCode(entityType, fieldsStore.getFieldsByGroupId(fg.id), field), + label: field.name, + }); + }); + }); + + return result; + }, + [getEntityTypeCode, t, fieldsStore, generateFieldCode] + ); + + return ( + + + + {isOpened ? t('hide_templates') : t('show_templates')} + + + {t('tooltip')} + + +
+ {isOpened && ( + + {linkedContactsAndCompanies.map(linkedEt => ( + + {linkedEt.name} + + + {getOptions(linkedEt).map(o => ( + + +